1 /* flac - Command-line FLAC encoder/decoder
2 * Copyright (C) 2002-2009 Josh Coalson
3 * Copyright (C) 2011-2016 Xiph.Org Foundation
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.
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.
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.
30 #include "FLAC/assert.h"
31 #include "FLAC/metadata.h"
32 #include "share/compat.h"
41 #ifdef HAVE_SYS_IOCTL_H
42 # include <sys/ioctl.h>
46 const char *CHANNEL_MASK_TAG
= "WAVEFORMATEXTENSIBLE_CHANNEL_MASK";
48 int flac__utils_verbosity_
= 2;
50 static FLAC__bool
local__parse_uint64_(const char *s
, FLAC__uint64
*value
)
58 while('\0' != (c
= *s
++))
59 if(c
>= '0' && c
<= '9')
60 ret
= ret
* 10 + (c
- '0');
68 static FLAC__bool
local__parse_timecode_(const char *s
, double *value
)
74 /* parse [0-9][0-9]*: */
76 if(c
>= '0' && c
<= '9')
80 while(':' != (c
= *s
++)) {
81 if(c
>= '0' && c
<= '9')
82 i
= i
* 10 + (c
- '0');
86 ret
= (double)i
* 60.;
88 /* parse [0-9]*[.,]?[0-9]* i.e. a sign-less rational number (. or , OK for fractional seconds, to support different locales) */
89 if(strspn(s
, "1234567890.,") != strlen(s
))
91 ret
+= strtod(s
, &endptr
);
92 if (endptr
== s
|| *endptr
)
99 static FLAC__bool
local__parse_cue_(const char *s
, const char *end
, uint32_t *track
, uint32_t *indx
)
101 FLAC__bool got_track
= false, got_index
= false;
102 uint32_t t
= 0, i
= 0;
105 while(end
? s
< end
: *s
!= '\0') {
107 if(c
>= '0' && c
<= '9') {
108 t
= t
* 10 + (c
- '0');
116 while(end
? s
< end
: *s
!= '\0') {
118 if(c
>= '0' && c
<= '9') {
119 i
= i
* 10 + (c
- '0');
127 return got_track
&& got_index
;
131 * this only works with sorted cuesheets (the spec strongly recommends but
132 * does not require sorted cuesheets). but if it's not sorted, picking a
133 * nearest cue point has no significance.
135 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
)
139 for(t
= 0; t
< (int)cuesheet
->num_tracks
; t
++)
140 for(i
= 0; i
< (int)cuesheet
->tracks
[t
].num_indices
; i
++)
141 if(cuesheet
->tracks
[t
].number
> track
|| (cuesheet
->tracks
[t
].number
== track
&& cuesheet
->tracks
[t
].indices
[i
].number
>= indx
))
142 return cuesheet
->tracks
[t
].offset
+ cuesheet
->tracks
[t
].indices
[i
].offset
;
143 return total_samples
;
146 for(t
= (int)cuesheet
->num_tracks
- 1; t
>= 0; t
--)
147 for(i
= (int)cuesheet
->tracks
[t
].num_indices
- 1; i
>= 0; i
--)
148 if(cuesheet
->tracks
[t
].number
< track
|| (cuesheet
->tracks
[t
].number
== track
&& cuesheet
->tracks
[t
].indices
[i
].number
<= indx
))
149 return cuesheet
->tracks
[t
].offset
+ cuesheet
->tracks
[t
].indices
[i
].offset
;
154 void flac__utils_printf(FILE *stream
, int level
, const char *format
, ...)
156 if(flac__utils_verbosity_
>= level
) {
159 FLAC__ASSERT(0 != format
);
161 va_start(args
, format
);
163 (void) flac_vfprintf(stream
, format
, args
);
169 fflush(stream
); /* for some reason stderr is buffered in at least some if not all MSC libs */
174 /* variables and functions for console status output */
175 static FLAC__bool is_name_printed
;
176 static int stats_char_count
= 0;
177 static int console_width
;
178 static int console_chars_left
;
180 int get_console_width(void)
184 width
= win_get_console_width();
185 #elif defined __EMX__
189 #elif defined TIOCGWINSZ
191 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &w
) != -1)
199 size_t strlen_console(const char *text
)
202 return strlen_utf8(text
);
203 #elif defined(__DJGPP__) /* workaround for DJGPP missing wcswidth() */
209 len
= strlen(text
)+1;
210 wtmp
= (wchar_t *)malloc(len
*sizeof(wchar_t));
211 if (wtmp
== NULL
) return len
-1;
212 mbstowcs(wtmp
, text
, len
);
213 len
= wcswidth(wtmp
, len
);
220 void stats_new_file(void)
222 is_name_printed
= false;
225 void stats_clear(void)
227 while (stats_char_count
> 0 && stats_char_count
--)
228 fprintf(stderr
, "\b");
231 void stats_print_name(int level
, const char *name
)
235 if (flac__utils_verbosity_
>= level
) {
237 if(is_name_printed
) return;
239 console_width
= get_console_width();
240 len
= strlen_console(name
)+2;
241 console_chars_left
= console_width
- (len
% console_width
);
242 flac_fprintf(stderr
, "%s: ", name
);
243 is_name_printed
= true;
247 void stats_print_info(int level
, const char *format
, ...)
252 if (flac__utils_verbosity_
>= level
) {
254 va_start(args
, format
);
255 len
= flac_vsnprintf(tmp
, sizeof(tmp
), format
, args
);
258 if (len
>= console_chars_left
) {
259 clear_len
= console_chars_left
;
260 while (clear_len
> 0 && clear_len
--) fprintf(stderr
, " ");
261 fprintf(stderr
, "\n");
262 console_chars_left
= console_width
;
264 stats_char_count
= fprintf(stderr
, "%s", tmp
);
269 #ifdef FLAC__VALGRIND_TESTING
270 size_t flac__utils_fwrite(const void *ptr
, size_t size
, size_t nmemb
, FILE *stream
)
272 size_t ret
= fwrite(ptr
, size
, nmemb
, stream
);
279 FLAC__bool
flac__utils_parse_skip_until_specification(const char *s
, utils__SkipUntilSpecification
*spec
)
282 FLAC__bool is_negative
= false;
284 FLAC__ASSERT(0 != spec
);
286 spec
->is_relative
= false;
287 spec
->value_is_samples
= true;
288 spec
->value
.samples
= 0;
293 spec
->is_relative
= true;
296 else if(s
[0] == '+') {
297 spec
->is_relative
= true;
301 if(local__parse_uint64_(s
, &val
)) {
302 spec
->value_is_samples
= true;
303 spec
->value
.samples
= (FLAC__int64
)val
;
305 spec
->value
.samples
= -(spec
->value
.samples
);
309 if(!local__parse_timecode_(s
, &d
))
311 spec
->value_is_samples
= false;
312 spec
->value
.seconds
= d
;
314 spec
->value
.seconds
= -(spec
->value
.seconds
);
321 void flac__utils_canonicalize_skip_until_specification(utils__SkipUntilSpecification
*spec
, uint32_t sample_rate
)
323 FLAC__ASSERT(0 != spec
);
324 if(!spec
->value_is_samples
) {
325 spec
->value
.samples
= (FLAC__int64
)(spec
->value
.seconds
* (double)sample_rate
);
326 spec
->value_is_samples
= true;
330 FLAC__bool
flac__utils_parse_cue_specification(const char *s
, utils__CueSpecification
*spec
)
332 const char *start
= s
, *end
= 0;
334 FLAC__ASSERT(0 != spec
);
336 spec
->has_start_point
= spec
->has_end_point
= false;
349 if(!local__parse_cue_(start
, s
, &spec
->start_track
, &spec
->start_index
))
351 spec
->has_start_point
= true;
355 if(!local__parse_cue_(end
, 0, &spec
->end_track
, &spec
->end_index
))
357 spec
->has_end_point
= true;
363 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
)
365 FLAC__ASSERT(0 != cue_spec
);
366 FLAC__ASSERT(0 != cuesheet
);
367 FLAC__ASSERT(0 != total_samples
);
368 FLAC__ASSERT(0 != skip_spec
);
369 FLAC__ASSERT(0 != until_spec
);
371 skip_spec
->is_relative
= false;
372 skip_spec
->value_is_samples
= true;
374 until_spec
->is_relative
= false;
375 until_spec
->value_is_samples
= true;
377 if(cue_spec
->has_start_point
)
378 skip_spec
->value
.samples
= local__find_closest_cue_(cuesheet
, cue_spec
->start_track
, cue_spec
->start_index
, total_samples
, /*look_forward=*/false);
380 skip_spec
->value
.samples
= 0;
382 if(cue_spec
->has_end_point
)
383 until_spec
->value
.samples
= local__find_closest_cue_(cuesheet
, cue_spec
->end_track
, cue_spec
->end_index
, total_samples
, /*look_forward=*/true);
385 until_spec
->value
.samples
= total_samples
;
388 FLAC__bool
flac__utils_set_channel_mask_tag(FLAC__StreamMetadata
*object
, FLAC__uint32 channel_mask
)
390 FLAC__StreamMetadata_VorbisComment_Entry entry
= { 0, 0 };
393 FLAC__ASSERT(object
);
394 FLAC__ASSERT(object
->type
== FLAC__METADATA_TYPE_VORBIS_COMMENT
);
395 FLAC__ASSERT(strlen(CHANNEL_MASK_TAG
)+1+2+16+1 <= sizeof(tag
)); /* +1 for =, +2 for 0x, +16 for digits, +1 for NUL */
396 entry
.entry
= (FLAC__byte
*)tag
;
397 if((entry
.length
= flac_snprintf(tag
, sizeof(tag
), "%s=0x%04X", CHANNEL_MASK_TAG
, (uint32_t)channel_mask
)) >= sizeof(tag
))
399 if(!FLAC__metadata_object_vorbiscomment_replace_comment(object
, entry
, /*all=*/true, /*copy=*/true))
404 FLAC__bool
flac__utils_get_channel_mask_tag(const FLAC__StreamMetadata
*object
, FLAC__uint32
*channel_mask
)
409 FLAC__ASSERT(object
);
410 FLAC__ASSERT(object
->type
== FLAC__METADATA_TYPE_VORBIS_COMMENT
);
411 if(0 > (offset
= FLAC__metadata_object_vorbiscomment_find_entry_from(object
, /*offset=*/0, CHANNEL_MASK_TAG
)))
413 if(object
->data
.vorbis_comment
.comments
[offset
].length
< strlen(CHANNEL_MASK_TAG
)+4)
415 if(0 == (p
= strchr((const char *)object
->data
.vorbis_comment
.comments
[offset
].entry
, '='))) /* should never happen, but just in case */
417 if(FLAC__STRNCASECMP(p
, "=0x", 3))
419 if(sscanf(p
+3, "%x", &val
) != 1)