1 /* flac - Command-line FLAC encoder/decoder
2 * Copyright (C) 2002-2009 Josh Coalson
3 * Copyright (C) 2011-2023 Xiph.Org Foundation
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include <math.h>
25 #include <stdarg.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include "utils.h"
30 #include "FLAC/assert.h"
31 #include "FLAC/metadata.h"
32 #include "share/compat.h"
33 #ifndef _WIN32
34 #ifndef _XOPEN_SOURCE
35 #define _XOPEN_SOURCE
36 #endif
37 #include <wchar.h>
38 #ifdef HAVE_TERMIOS_H
39 # include <termios.h>
40 #endif
41 #ifdef HAVE_SYS_IOCTL_H
42 # include <sys/ioctl.h>
43 #endif
44 #endif
45
46 const char *CHANNEL_MASK_TAG = "WAVEFORMATEXTENSIBLE_CHANNEL_MASK";
47
48 int flac__utils_verbosity_ = 2;
49
local__parse_uint64_(const char * s,FLAC__uint64 * value)50 static FLAC__bool local__parse_uint64_(const char *s, FLAC__uint64 *value)
51 {
52 FLAC__uint64 ret = 0;
53 char c;
54
55 if(*s == '\0')
56 return false;
57
58 while('\0' != (c = *s++))
59 if(c >= '0' && c <= '9') {
60 if(ret > UINT64_MAX / 10) /* check for overflow */
61 return false;
62 else if(ret == UINT64_MAX / 10) {
63 FLAC__uint64 tmp = ret;
64 ret = ret * 10 + (c - '0');
65 if(ret < tmp)
66 return false;
67 }
68 else
69 ret = ret * 10 + (c - '0');
70 }
71 else
72 return false;
73
74 *value = ret;
75 return true;
76 }
77
local__parse_timecode_(const char * s,double * value)78 static FLAC__bool local__parse_timecode_(const char *s, double *value)
79 {
80 double ret;
81 uint32_t i;
82 char c, *endptr;
83
84 /* parse [0-9][0-9]*: */
85 c = *s++;
86 if(c >= '0' && c <= '9')
87 i = (c - '0');
88 else
89 return false;
90 while(':' != (c = *s++)) {
91 if(c >= '0' && c <= '9')
92 i = i * 10 + (c - '0');
93 else
94 return false;
95 }
96 ret = (double)i * 60.;
97
98 /* parse [0-9]*[.,]?[0-9]* i.e. a sign-less rational number (. or , OK for fractional seconds, to support different locales) */
99 if(strspn(s, "1234567890.,") != strlen(s))
100 return false;
101 ret += strtod(s, &endptr);
102 if (endptr == s || *endptr)
103 return false;
104
105 *value = ret;
106 return true;
107 }
108
local__parse_cue_(const char * s,const char * end,uint32_t * track,uint32_t * indx)109 static FLAC__bool local__parse_cue_(const char *s, const char *end, uint32_t *track, uint32_t *indx)
110 {
111 FLAC__bool got_track = false, got_index = false;
112 uint32_t t = 0, i = 0;
113 char c;
114
115 while(end? s < end : *s != '\0') {
116 c = *s++;
117 if(c >= '0' && c <= '9') {
118 t = t * 10 + (c - '0');
119 got_track = true;
120 }
121 else if(c == '.')
122 break;
123 else
124 return false;
125 }
126 while(end? s < end : *s != '\0') {
127 c = *s++;
128 if(c >= '0' && c <= '9') {
129 i = i * 10 + (c - '0');
130 got_index = true;
131 }
132 else
133 return false;
134 }
135 *track = t;
136 *indx = i;
137 return got_track && got_index;
138 }
139
140 /*
141 * this only works with sorted cuesheets (the spec strongly recommends but
142 * does not require sorted cuesheets). but if it's not sorted, picking a
143 * nearest cue point has no significance.
144 */
local__find_closest_cue_(const FLAC__StreamMetadata_CueSheet * cuesheet,uint32_t track,uint32_t indx,FLAC__uint64 total_samples,FLAC__bool look_forward)145 static FLAC__uint64 local__find_closest_cue_(const FLAC__StreamMetadata_CueSheet *cuesheet, uint32_t track, uint32_t indx, FLAC__uint64 total_samples, FLAC__bool look_forward)
146 {
147 int t, i;
148 if(look_forward) {
149 for(t = 0; t < (int)cuesheet->num_tracks; t++)
150 for(i = 0; i < (int)cuesheet->tracks[t].num_indices; i++)
151 if(cuesheet->tracks[t].number > track || (cuesheet->tracks[t].number == track && cuesheet->tracks[t].indices[i].number >= indx))
152 return cuesheet->tracks[t].offset + cuesheet->tracks[t].indices[i].offset;
153 return total_samples;
154 }
155 else {
156 for(t = (int)cuesheet->num_tracks - 1; t >= 0; t--)
157 for(i = (int)cuesheet->tracks[t].num_indices - 1; i >= 0; i--)
158 if(cuesheet->tracks[t].number < track || (cuesheet->tracks[t].number == track && cuesheet->tracks[t].indices[i].number <= indx))
159 return cuesheet->tracks[t].offset + cuesheet->tracks[t].indices[i].offset;
160 return 0;
161 }
162 }
163
flac__utils_printf(FILE * stream,int level,const char * format,...)164 void flac__utils_printf(FILE *stream, int level, const char *format, ...)
165 {
166 if(flac__utils_verbosity_ >= level) {
167 va_list args;
168
169 FLAC__ASSERT(0 != format);
170
171 va_start(args, format);
172
173 (void) flac_vfprintf(stream, format, args);
174
175 va_end(args);
176
177 #ifdef _MSC_VER
178 if(stream == stderr)
179 fflush(stream); /* for some reason stderr is buffered in at least some if not all MSC libs */
180 #endif
181 }
182 }
183
184 /* variables and functions for console status output */
185 static FLAC__bool is_name_printed;
186 static int stats_char_count = 0;
187 static int console_width;
188 static int console_chars_left;
189
get_console_width(void)190 int get_console_width(void)
191 {
192 int width = 0;
193 #if defined _WIN32
194 width = win_get_console_width();
195 #elif defined __EMX__
196 int s[2];
197 _scrsize (s);
198 width = s[0];
199 #elif defined TIOCGWINSZ
200 struct winsize w;
201 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1)
202 width = w.ws_col;
203 #endif
204 if (width <= 0)
205 width = 80;
206 return width;
207 }
208
strlen_console(const char * text)209 size_t strlen_console(const char *text)
210 {
211 #ifdef _WIN32
212 return strlen_utf8(text);
213 #elif defined(__DJGPP__) /* workaround for DJGPP missing wcswidth() */
214 return strlen(text);
215 #else
216 size_t len;
217 wchar_t *wtmp;
218
219 len = strlen(text)+1;
220 wtmp = (wchar_t *)malloc(len*sizeof(wchar_t));
221 if (wtmp == NULL) return len-1;
222 mbstowcs(wtmp, text, len);
223 len = wcswidth(wtmp, len);
224 free(wtmp);
225
226 return len;
227 #endif
228 }
229
stats_new_file(void)230 void stats_new_file(void)
231 {
232 is_name_printed = false;
233 }
234
stats_clear(void)235 void stats_clear(void)
236 {
237 while (stats_char_count > 0 && stats_char_count--)
238 fprintf(stderr, "\b");
239 }
240
stats_print_name(int level,const char * name)241 void stats_print_name(int level, const char *name)
242 {
243 int len;
244
245 if (flac__utils_verbosity_ >= level) {
246 stats_clear();
247 if(is_name_printed) return;
248
249 console_width = get_console_width();
250 len = strlen_console(name)+2;
251 console_chars_left = console_width - (len % console_width);
252 flac_fprintf(stderr, "%s: ", name);
253 is_name_printed = true;
254 }
255 }
256
stats_print_info(int level,const char * format,...)257 void stats_print_info(int level, const char *format, ...)
258 {
259 char tmp[80];
260 int len, clear_len;
261
262 if (flac__utils_verbosity_ >= level) {
263 va_list args;
264 va_start(args, format);
265 len = flac_vsnprintf(tmp, sizeof(tmp), format, args);
266 va_end(args);
267 stats_clear();
268 if (len >= console_chars_left) {
269 clear_len = console_chars_left;
270 while (clear_len > 0 && clear_len--) fprintf(stderr, " ");
271 fprintf(stderr, "\n");
272 console_chars_left = console_width;
273 }
274 stats_char_count = fprintf(stderr, "%s", tmp);
275 fflush(stderr);
276 }
277 }
278
279 #ifdef FLAC__VALGRIND_TESTING
flac__utils_fwrite(const void * ptr,size_t size,size_t nmemb,FILE * stream)280 size_t flac__utils_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
281 {
282 size_t ret = fwrite(ptr, size, nmemb, stream);
283 if(!ferror(stream))
284 fflush(stream);
285 return ret;
286 }
287 #endif
288
flac__utils_parse_skip_until_specification(const char * s,utils__SkipUntilSpecification * spec)289 FLAC__bool flac__utils_parse_skip_until_specification(const char *s, utils__SkipUntilSpecification *spec)
290 {
291 FLAC__uint64 val;
292 FLAC__bool is_negative = false;
293
294 FLAC__ASSERT(0 != spec);
295
296 spec->is_relative = false;
297 spec->value_is_samples = true;
298 spec->value.samples = 0;
299
300 if(0 != s) {
301 if(s[0] == '-') {
302 is_negative = true;
303 spec->is_relative = true;
304 s++;
305 }
306 else if(s[0] == '+') {
307 spec->is_relative = true;
308 s++;
309 }
310
311 if(local__parse_uint64_(s, &val)) {
312 spec->value_is_samples = true;
313 if(val > INT64_MAX)
314 return false;
315 spec->value.samples = (FLAC__int64)val;
316 if(is_negative)
317 spec->value.samples = -(spec->value.samples);
318 }
319 else {
320 double d;
321 if(!local__parse_timecode_(s, &d))
322 return false;
323 spec->value_is_samples = false;
324 spec->value.seconds = d;
325 if(is_negative)
326 spec->value.seconds = -(spec->value.seconds);
327 }
328 }
329
330 return true;
331 }
332
flac__utils_canonicalize_skip_until_specification(utils__SkipUntilSpecification * spec,uint32_t sample_rate)333 FLAC__bool flac__utils_canonicalize_skip_until_specification(utils__SkipUntilSpecification *spec, uint32_t sample_rate)
334 {
335 FLAC__ASSERT(0 != spec);
336 if(!spec->value_is_samples) {
337 double samples = spec->value.seconds * (double)sample_rate;
338 if(samples >= (double)INT64_MAX || samples <= (double)INT64_MIN)
339 return false;
340 spec->value.samples = (FLAC__int64)(samples);
341 spec->value_is_samples = true;
342 }
343 return true;
344 }
345
flac__utils_parse_cue_specification(const char * s,utils__CueSpecification * spec)346 FLAC__bool flac__utils_parse_cue_specification(const char *s, utils__CueSpecification *spec)
347 {
348 const char *start = s, *end = 0;
349
350 FLAC__ASSERT(0 != spec);
351
352 spec->has_start_point = spec->has_end_point = false;
353
354 s = strchr(s, '-');
355
356 if(0 != s) {
357 if(s == start)
358 start = 0;
359 end = s+1;
360 if(*end == '\0')
361 end = 0;
362 }
363
364 if(start) {
365 if(!local__parse_cue_(start, s, &spec->start_track, &spec->start_index))
366 return false;
367 spec->has_start_point = true;
368 }
369
370 if(end) {
371 if(!local__parse_cue_(end, 0, &spec->end_track, &spec->end_index))
372 return false;
373 spec->has_end_point = true;
374 }
375
376 return true;
377 }
378
flac__utils_canonicalize_cue_specification(const utils__CueSpecification * cue_spec,const FLAC__StreamMetadata_CueSheet * cuesheet,FLAC__uint64 total_samples,utils__SkipUntilSpecification * skip_spec,utils__SkipUntilSpecification * until_spec)379 void flac__utils_canonicalize_cue_specification(const utils__CueSpecification *cue_spec, const FLAC__StreamMetadata_CueSheet *cuesheet, FLAC__uint64 total_samples, utils__SkipUntilSpecification *skip_spec, utils__SkipUntilSpecification *until_spec)
380 {
381 FLAC__ASSERT(0 != cue_spec);
382 FLAC__ASSERT(0 != cuesheet);
383 FLAC__ASSERT(0 != total_samples);
384 FLAC__ASSERT(0 != skip_spec);
385 FLAC__ASSERT(0 != until_spec);
386
387 skip_spec->is_relative = false;
388 skip_spec->value_is_samples = true;
389
390 until_spec->is_relative = false;
391 until_spec->value_is_samples = true;
392
393 if(cue_spec->has_start_point)
394 skip_spec->value.samples = local__find_closest_cue_(cuesheet, cue_spec->start_track, cue_spec->start_index, total_samples, /*look_forward=*/false);
395 else
396 skip_spec->value.samples = 0;
397
398 if(cue_spec->has_end_point)
399 until_spec->value.samples = local__find_closest_cue_(cuesheet, cue_spec->end_track, cue_spec->end_index, total_samples, /*look_forward=*/true);
400 else
401 until_spec->value.samples = total_samples;
402 }
403
flac__utils_set_channel_mask_tag(FLAC__StreamMetadata * object,FLAC__uint32 channel_mask)404 FLAC__bool flac__utils_set_channel_mask_tag(FLAC__StreamMetadata *object, FLAC__uint32 channel_mask)
405 {
406 FLAC__StreamMetadata_VorbisComment_Entry entry = { 0, 0 };
407 char tag[128];
408
409 FLAC__ASSERT(object);
410 FLAC__ASSERT(object->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
411 FLAC__ASSERT(strlen(CHANNEL_MASK_TAG)+1+2+16+1 <= sizeof(tag)); /* +1 for =, +2 for 0x, +16 for digits, +1 for NUL */
412 entry.entry = (FLAC__byte*)tag;
413 if((entry.length = flac_snprintf(tag, sizeof(tag), "%s=0x%04X", CHANNEL_MASK_TAG, (uint32_t)channel_mask)) >= sizeof(tag))
414 return false;
415 if(!FLAC__metadata_object_vorbiscomment_replace_comment(object, entry, /*all=*/true, /*copy=*/true))
416 return false;
417 return true;
418 }
419
flac__utils_get_channel_mask_tag(const FLAC__StreamMetadata * object,FLAC__uint32 * channel_mask)420 FLAC__bool flac__utils_get_channel_mask_tag(const FLAC__StreamMetadata *object, FLAC__uint32 *channel_mask)
421 {
422 int offset;
423 uint32_t val;
424 char *p;
425 FLAC__ASSERT(object);
426 FLAC__ASSERT(object->type == FLAC__METADATA_TYPE_VORBIS_COMMENT);
427 if(0 > (offset = FLAC__metadata_object_vorbiscomment_find_entry_from(object, /*offset=*/0, CHANNEL_MASK_TAG)))
428 return false;
429 if(object->data.vorbis_comment.comments[offset].length < strlen(CHANNEL_MASK_TAG)+4)
430 return false;
431 if(0 == (p = strchr((const char *)object->data.vorbis_comment.comments[offset].entry, '='))) /* should never happen, but just in case */
432 return false;
433 if(FLAC__STRNCASECMP(p, "=0x", 3))
434 return false;
435 if(sscanf(p+3, "%x", &val) != 1)
436 return false;
437 *channel_mask = val;
438 return true;
439 }
440