/* flac - Command-line FLAC encoder/decoder * Copyright (C) 2002-2009 Josh Coalson * Copyright (C) 2011-2016 Xiph.Org Foundation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "utils.h" #include "FLAC/assert.h" #include "FLAC/metadata.h" #include "share/compat.h" #ifndef _WIN32 #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE #endif #include #ifdef HAVE_TERMIOS_H # include #endif #ifdef HAVE_SYS_IOCTL_H # include #endif #endif const char *CHANNEL_MASK_TAG = "WAVEFORMATEXTENSIBLE_CHANNEL_MASK"; int flac__utils_verbosity_ = 2; static FLAC__bool local__parse_uint64_(const char *s, FLAC__uint64 *value) { FLAC__uint64 ret = 0; char c; if(*s == '\0') return false; while('\0' != (c = *s++)) if(c >= '0' && c <= '9') ret = ret * 10 + (c - '0'); else return false; *value = ret; return true; } static FLAC__bool local__parse_timecode_(const char *s, double *value) { double ret; uint32_t i; char c, *endptr; /* parse [0-9][0-9]*: */ c = *s++; if(c >= '0' && c <= '9') i = (c - '0'); else return false; while(':' != (c = *s++)) { if(c >= '0' && c <= '9') i = i * 10 + (c - '0'); else return false; } ret = (double)i * 60.; /* parse [0-9]*[.,]?[0-9]* i.e. a sign-less rational number (. or , OK for fractional seconds, to support different locales) */ if(strspn(s, "1234567890.,") != strlen(s)) return false; ret += strtod(s, &endptr); if (endptr == s || *endptr) return false; *value = ret; return true; } static FLAC__bool local__parse_cue_(const char *s, const char *end, uint32_t *track, uint32_t *indx) { FLAC__bool got_track = false, got_index = false; uint32_t t = 0, i = 0; char c; while(end? s < end : *s != '\0') { c = *s++; if(c >= '0' && c <= '9') { t = t * 10 + (c - '0'); got_track = true; } else if(c == '.') break; else return false; } while(end? s < end : *s != '\0') { c = *s++; if(c >= '0' && c <= '9') { i = i * 10 + (c - '0'); got_index = true; } else return false; } *track = t; *indx = i; return got_track && got_index; } /* * this only works with sorted cuesheets (the spec strongly recommends but * does not require sorted cuesheets). but if it's not sorted, picking a * nearest cue point has no significance. */ 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) { int t, i; if(look_forward) { for(t = 0; t < (int)cuesheet->num_tracks; t++) for(i = 0; i < (int)cuesheet->tracks[t].num_indices; i++) if(cuesheet->tracks[t].number > track || (cuesheet->tracks[t].number == track && cuesheet->tracks[t].indices[i].number >= indx)) return cuesheet->tracks[t].offset + cuesheet->tracks[t].indices[i].offset; return total_samples; } else { for(t = (int)cuesheet->num_tracks - 1; t >= 0; t--) for(i = (int)cuesheet->tracks[t].num_indices - 1; i >= 0; i--) if(cuesheet->tracks[t].number < track || (cuesheet->tracks[t].number == track && cuesheet->tracks[t].indices[i].number <= indx)) return cuesheet->tracks[t].offset + cuesheet->tracks[t].indices[i].offset; return 0; } } void flac__utils_printf(FILE *stream, int level, const char *format, ...) { if(flac__utils_verbosity_ >= level) { va_list args; FLAC__ASSERT(0 != format); va_start(args, format); (void) flac_vfprintf(stream, format, args); va_end(args); #ifdef _MSC_VER if(stream == stderr) fflush(stream); /* for some reason stderr is buffered in at least some if not all MSC libs */ #endif } } /* variables and functions for console status output */ static FLAC__bool is_name_printed; static int stats_char_count = 0; static int console_width; static int console_chars_left; int get_console_width(void) { int width = 0; #if defined _WIN32 width = win_get_console_width(); #elif defined __EMX__ int s[2]; _scrsize (s); width = s[0]; #elif defined TIOCGWINSZ struct winsize w; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) width = w.ws_col; #endif if (width <= 0) width = 80; return width; } size_t strlen_console(const char *text) { #ifdef _WIN32 return strlen_utf8(text); #elif defined(__DJGPP__) /* workaround for DJGPP missing wcswidth() */ return strlen(text); #else size_t len; wchar_t *wtmp; len = strlen(text)+1; wtmp = (wchar_t *)malloc(len*sizeof(wchar_t)); if (wtmp == NULL) return len-1; mbstowcs(wtmp, text, len); len = wcswidth(wtmp, len); free(wtmp); return len; #endif } void stats_new_file(void) { is_name_printed = false; } void stats_clear(void) { while (stats_char_count > 0 && stats_char_count--) fprintf(stderr, "\b"); } void stats_print_name(int level, const char *name) { int len; if (flac__utils_verbosity_ >= level) { stats_clear(); if(is_name_printed) return; console_width = get_console_width(); len = strlen_console(name)+2; console_chars_left = console_width - (len % console_width); flac_fprintf(stderr, "%s: ", name); is_name_printed = true; } } void stats_print_info(int level, const char *format, ...) { char tmp[80]; int len, clear_len; if (flac__utils_verbosity_ >= level) { va_list args; va_start(args, format); len = flac_vsnprintf(tmp, sizeof(tmp), format, args); va_end(args); stats_clear(); if (len >= console_chars_left) { clear_len = console_chars_left; while (clear_len > 0 && clear_len--) fprintf(stderr, " "); fprintf(stderr, "\n"); console_chars_left = console_width; } stats_char_count = fprintf(stderr, "%s", tmp); fflush(stderr); } } #ifdef FLAC__VALGRIND_TESTING size_t flac__utils_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { size_t ret = fwrite(ptr, size, nmemb, stream); if(!ferror(stream)) fflush(stream); return ret; } #endif FLAC__bool flac__utils_parse_skip_until_specification(const char *s, utils__SkipUntilSpecification *spec) { FLAC__uint64 val; FLAC__bool is_negative = false; FLAC__ASSERT(0 != spec); spec->is_relative = false; spec->value_is_samples = true; spec->value.samples = 0; if(0 != s) { if(s[0] == '-') { is_negative = true; spec->is_relative = true; s++; } else if(s[0] == '+') { spec->is_relative = true; s++; } if(local__parse_uint64_(s, &val)) { spec->value_is_samples = true; spec->value.samples = (FLAC__int64)val; if(is_negative) spec->value.samples = -(spec->value.samples); } else { double d; if(!local__parse_timecode_(s, &d)) return false; spec->value_is_samples = false; spec->value.seconds = d; if(is_negative) spec->value.seconds = -(spec->value.seconds); } } return true; } void flac__utils_canonicalize_skip_until_specification(utils__SkipUntilSpecification *spec, uint32_t sample_rate) { FLAC__ASSERT(0 != spec); if(!spec->value_is_samples) { spec->value.samples = (FLAC__int64)(spec->value.seconds * (double)sample_rate); spec->value_is_samples = true; } } FLAC__bool flac__utils_parse_cue_specification(const char *s, utils__CueSpecification *spec) { const char *start = s, *end = 0; FLAC__ASSERT(0 != spec); spec->has_start_point = spec->has_end_point = false; s = strchr(s, '-'); if(0 != s) { if(s == start) start = 0; end = s+1; if(*end == '\0') end = 0; } if(start) { if(!local__parse_cue_(start, s, &spec->start_track, &spec->start_index)) return false; spec->has_start_point = true; } if(end) { if(!local__parse_cue_(end, 0, &spec->end_track, &spec->end_index)) return false; spec->has_end_point = true; } return true; } 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) { FLAC__ASSERT(0 != cue_spec); FLAC__ASSERT(0 != cuesheet); FLAC__ASSERT(0 != total_samples); FLAC__ASSERT(0 != skip_spec); FLAC__ASSERT(0 != until_spec); skip_spec->is_relative = false; skip_spec->value_is_samples = true; until_spec->is_relative = false; until_spec->value_is_samples = true; if(cue_spec->has_start_point) skip_spec->value.samples = local__find_closest_cue_(cuesheet, cue_spec->start_track, cue_spec->start_index, total_samples, /*look_forward=*/false); else skip_spec->value.samples = 0; if(cue_spec->has_end_point) until_spec->value.samples = local__find_closest_cue_(cuesheet, cue_spec->end_track, cue_spec->end_index, total_samples, /*look_forward=*/true); else until_spec->value.samples = total_samples; } FLAC__bool flac__utils_set_channel_mask_tag(FLAC__StreamMetadata *object, FLAC__uint32 channel_mask) { FLAC__StreamMetadata_VorbisComment_Entry entry = { 0, 0 }; char tag[128]; FLAC__ASSERT(object); FLAC__ASSERT(object->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); FLAC__ASSERT(strlen(CHANNEL_MASK_TAG)+1+2+16+1 <= sizeof(tag)); /* +1 for =, +2 for 0x, +16 for digits, +1 for NUL */ entry.entry = (FLAC__byte*)tag; if((entry.length = flac_snprintf(tag, sizeof(tag), "%s=0x%04X", CHANNEL_MASK_TAG, (uint32_t)channel_mask)) >= sizeof(tag)) return false; if(!FLAC__metadata_object_vorbiscomment_replace_comment(object, entry, /*all=*/true, /*copy=*/true)) return false; return true; } FLAC__bool flac__utils_get_channel_mask_tag(const FLAC__StreamMetadata *object, FLAC__uint32 *channel_mask) { int offset; uint32_t val; char *p; FLAC__ASSERT(object); FLAC__ASSERT(object->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); if(0 > (offset = FLAC__metadata_object_vorbiscomment_find_entry_from(object, /*offset=*/0, CHANNEL_MASK_TAG))) return false; if(object->data.vorbis_comment.comments[offset].length < strlen(CHANNEL_MASK_TAG)+4) return false; if(0 == (p = strchr((const char *)object->data.vorbis_comment.comments[offset].entry, '='))) /* should never happen, but just in case */ return false; if(FLAC__STRNCASECMP(p, "=0x", 3)) return false; if(sscanf(p+3, "%x", &val) != 1) return false; *channel_mask = val; return true; }