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