1/* flac - Command-line FLAC encoder/decoder 2 * Copyright (C) 2002,2003,2004,2005,2006,2007 Josh Coalson 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License 6 * as published by the Free Software Foundation; either version 2 7 * of the License, or (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17 */ 18 19#if HAVE_CONFIG_H 20# include <config.h> 21#endif 22 23#include "utils.h" 24#include "FLAC/assert.h" 25#include "FLAC/metadata.h" 26#include <math.h> 27#include <stdarg.h> 28#include <stdio.h> 29#include <stdlib.h> 30#include <string.h> 31 32const char *CHANNEL_MASK_TAG = "WAVEFORMATEXTENSIBLE_CHANNEL_MASK"; 33 34int flac__utils_verbosity_ = 2; 35 36static FLAC__bool local__parse_uint64_(const char *s, FLAC__uint64 *value) 37{ 38 FLAC__uint64 ret = 0; 39 char c; 40 41 if(*s == '\0') 42 return false; 43 44 while('\0' != (c = *s++)) 45 if(c >= '0' && c <= '9') 46 ret = ret * 10 + (c - '0'); 47 else 48 return false; 49 50 *value = ret; 51 return true; 52} 53 54static FLAC__bool local__parse_timecode_(const char *s, double *value) 55{ 56 double ret; 57 unsigned i; 58 char c; 59 60 /* parse [0-9][0-9]*: */ 61 c = *s++; 62 if(c >= '0' && c <= '9') 63 i = (c - '0'); 64 else 65 return false; 66 while(':' != (c = *s++)) { 67 if(c >= '0' && c <= '9') 68 i = i * 10 + (c - '0'); 69 else 70 return false; 71 } 72 ret = (double)i * 60.; 73 74 /* parse [0-9]*[.,]?[0-9]* i.e. a sign-less rational number (. or , OK for fractional seconds, to support different locales) */ 75 if(strspn(s, "1234567890.,") != strlen(s)) 76 return false; 77 { 78 const char *p = strpbrk(s, ".,"); 79 if(p && 0 != strpbrk(++p, ".,")) 80 return false; 81 } 82 ret += atof(s); 83 84 *value = ret; 85 return true; 86} 87 88static FLAC__bool local__parse_cue_(const char *s, const char *end, unsigned *track, unsigned *index) 89{ 90 FLAC__bool got_track = false, got_index = false; 91 unsigned t = 0, i = 0; 92 char c; 93 94 while(end? s < end : *s != '\0') { 95 c = *s++; 96 if(c >= '0' && c <= '9') { 97 t = t * 10 + (c - '0'); 98 got_track = true; 99 } 100 else if(c == '.') 101 break; 102 else 103 return false; 104 } 105 while(end? s < end : *s != '\0') { 106 c = *s++; 107 if(c >= '0' && c <= '9') { 108 i = i * 10 + (c - '0'); 109 got_index = true; 110 } 111 else 112 return false; 113 } 114 *track = t; 115 *index = i; 116 return got_track && got_index; 117} 118 119/* 120 * this only works with sorted cuesheets (the spec strongly recommends but 121 * does not require sorted cuesheets). but if it's not sorted, picking a 122 * nearest cue point has no significance. 123 */ 124static FLAC__uint64 local__find_closest_cue_(const FLAC__StreamMetadata_CueSheet *cuesheet, unsigned track, unsigned index, FLAC__uint64 total_samples, FLAC__bool look_forward) 125{ 126 int t, i; 127 if(look_forward) { 128 for(t = 0; t < (int)cuesheet->num_tracks; t++) 129 for(i = 0; i < (int)cuesheet->tracks[t].num_indices; i++) 130 if(cuesheet->tracks[t].number > track || (cuesheet->tracks[t].number == track && cuesheet->tracks[t].indices[i].number >= index)) 131 return cuesheet->tracks[t].offset + cuesheet->tracks[t].indices[i].offset; 132 return total_samples; 133 } 134 else { 135 for(t = (int)cuesheet->num_tracks - 1; t >= 0; t--) 136 for(i = (int)cuesheet->tracks[t].num_indices - 1; i >= 0; i--) 137 if(cuesheet->tracks[t].number < track || (cuesheet->tracks[t].number == track && cuesheet->tracks[t].indices[i].number <= index)) 138 return cuesheet->tracks[t].offset + cuesheet->tracks[t].indices[i].offset; 139 return 0; 140 } 141} 142 143void flac__utils_printf(FILE *stream, int level, const char *format, ...) 144{ 145 if(flac__utils_verbosity_ >= level) { 146 va_list args; 147 148 FLAC__ASSERT(0 != format); 149 150 va_start(args, format); 151 152 (void) vfprintf(stream, format, args); 153 154 va_end(args); 155 } 156} 157 158#ifdef FLAC__VALGRIND_TESTING 159size_t flac__utils_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) 160{ 161 size_t ret = fwrite(ptr, size, nmemb, stream); 162 if(!ferror(stream)) 163 fflush(stream); 164 return ret; 165} 166#endif 167 168FLAC__bool flac__utils_parse_skip_until_specification(const char *s, utils__SkipUntilSpecification *spec) 169{ 170 FLAC__uint64 val; 171 FLAC__bool is_negative = false; 172 173 FLAC__ASSERT(0 != spec); 174 175 spec->is_relative = false; 176 spec->value_is_samples = true; 177 spec->value.samples = 0; 178 179 if(0 != s) { 180 if(s[0] == '-') { 181 is_negative = true; 182 spec->is_relative = true; 183 s++; 184 } 185 else if(s[0] == '+') { 186 spec->is_relative = true; 187 s++; 188 } 189 190 if(local__parse_uint64_(s, &val)) { 191 spec->value_is_samples = true; 192 spec->value.samples = (FLAC__int64)val; 193 if(is_negative) 194 spec->value.samples = -(spec->value.samples); 195 } 196 else { 197 double d; 198 if(!local__parse_timecode_(s, &d)) 199 return false; 200 spec->value_is_samples = false; 201 spec->value.seconds = d; 202 if(is_negative) 203 spec->value.seconds = -(spec->value.seconds); 204 } 205 } 206 207 return true; 208} 209 210void flac__utils_canonicalize_skip_until_specification(utils__SkipUntilSpecification *spec, unsigned sample_rate) 211{ 212 FLAC__ASSERT(0 != spec); 213 if(!spec->value_is_samples) { 214 spec->value.samples = (FLAC__int64)(spec->value.seconds * (double)sample_rate); 215 spec->value_is_samples = true; 216 } 217} 218 219FLAC__bool flac__utils_parse_cue_specification(const char *s, utils__CueSpecification *spec) 220{ 221 const char *start = s, *end = 0; 222 223 FLAC__ASSERT(0 != spec); 224 225 spec->has_start_point = spec->has_end_point = false; 226 227 s = strchr(s, '-'); 228 229 if(0 != s) { 230 if(s == start) 231 start = 0; 232 end = s+1; 233 if(*end == '\0') 234 end = 0; 235 } 236 237 if(start) { 238 if(!local__parse_cue_(start, s, &spec->start_track, &spec->start_index)) 239 return false; 240 spec->has_start_point = true; 241 } 242 243 if(end) { 244 if(!local__parse_cue_(end, 0, &spec->end_track, &spec->end_index)) 245 return false; 246 spec->has_end_point = true; 247 } 248 249 return true; 250} 251 252void 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) 253{ 254 FLAC__ASSERT(0 != cue_spec); 255 FLAC__ASSERT(0 != cuesheet); 256 FLAC__ASSERT(0 != total_samples); 257 FLAC__ASSERT(0 != skip_spec); 258 FLAC__ASSERT(0 != until_spec); 259 260 skip_spec->is_relative = false; 261 skip_spec->value_is_samples = true; 262 263 until_spec->is_relative = false; 264 until_spec->value_is_samples = true; 265 266 if(cue_spec->has_start_point) 267 skip_spec->value.samples = local__find_closest_cue_(cuesheet, cue_spec->start_track, cue_spec->start_index, total_samples, /*look_forward=*/false); 268 else 269 skip_spec->value.samples = 0; 270 271 if(cue_spec->has_end_point) 272 until_spec->value.samples = local__find_closest_cue_(cuesheet, cue_spec->end_track, cue_spec->end_index, total_samples, /*look_forward=*/true); 273 else 274 until_spec->value.samples = total_samples; 275} 276 277FLAC__bool flac__utils_set_channel_mask_tag(FLAC__StreamMetadata *object, FLAC__uint32 channel_mask) 278{ 279 FLAC__StreamMetadata_VorbisComment_Entry entry = { 0, 0 }; 280 char tag[128]; 281 282 FLAC__ASSERT(object); 283 FLAC__ASSERT(object->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); 284 FLAC__ASSERT(strlen(CHANNEL_MASK_TAG+1+2+16+1) <= sizeof(tag)); /* +1 for =, +2 for 0x, +16 for digits, +1 for NUL */ 285 entry.entry = (FLAC__byte*)tag; 286#if defined _MSC_VER || defined __MINGW32__ 287 if((entry.length = _snprintf(tag, sizeof(tag), "%s=0x%04X", CHANNEL_MASK_TAG, (unsigned)channel_mask)) >= sizeof(tag)) 288#else 289 if((entry.length = snprintf(tag, sizeof(tag), "%s=0x%04X", CHANNEL_MASK_TAG, (unsigned)channel_mask)) >= sizeof(tag)) 290#endif 291 return false; 292 if(!FLAC__metadata_object_vorbiscomment_replace_comment(object, entry, /*all=*/true, /*copy=*/true)) 293 return false; 294 return true; 295} 296 297FLAC__bool flac__utils_get_channel_mask_tag(const FLAC__StreamMetadata *object, FLAC__uint32 *channel_mask) 298{ 299 int offset; 300 unsigned val; 301 char *p; 302 FLAC__ASSERT(object); 303 FLAC__ASSERT(object->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); 304 if(0 > (offset = FLAC__metadata_object_vorbiscomment_find_entry_from(object, /*offset=*/0, CHANNEL_MASK_TAG))) 305 return false; 306 if(object->data.vorbis_comment.comments[offset].length < strlen(CHANNEL_MASK_TAG)+4) 307 return false; 308 if(0 == (p = strchr((const char *)object->data.vorbis_comment.comments[offset].entry, '='))) /* should never happen, but just in case */ 309 return false; 310 if(strncmp(p, "=0x", 3)) 311 return false; 312 if(sscanf(p+3, "%x", &val) != 1) 313 return false; 314 *channel_mask = val; 315 return true; 316} 317