1/* grabbag - Convenience lib for various routines common to several tools 2 * Copyright (C) 2002,2003,2004,2005,2006,2007 Josh Coalson 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2.1 of the License, or (at your option) any later version. 8 * 9 * This library 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 GNU 12 * Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public 15 * License along with this library; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 */ 18 19#if HAVE_CONFIG_H 20# include <config.h> 21#endif 22 23#include "share/grabbag.h" 24#include "FLAC/assert.h" 25#include <stdio.h> 26#include <stdlib.h> 27#include <string.h> 28 29unsigned grabbag__cuesheet_msf_to_frame(unsigned minutes, unsigned seconds, unsigned frames) 30{ 31 return ((minutes * 60) + seconds) * 75 + frames; 32} 33 34void grabbag__cuesheet_frame_to_msf(unsigned frame, unsigned *minutes, unsigned *seconds, unsigned *frames) 35{ 36 *frames = frame % 75; 37 frame /= 75; 38 *seconds = frame % 60; 39 frame /= 60; 40 *minutes = frame; 41} 42 43/* since we only care about values >= 0 or error, returns < 0 for any illegal string, else value */ 44static int local__parse_int_(const char *s) 45{ 46 int ret = 0; 47 char c; 48 49 if(*s == '\0') 50 return -1; 51 52 while('\0' != (c = *s++)) 53 if(c >= '0' && c <= '9') 54 ret = ret * 10 + (c - '0'); 55 else 56 return -1; 57 58 return ret; 59} 60 61/* since we only care about values >= 0 or error, returns < 0 for any illegal string, else value */ 62static FLAC__int64 local__parse_int64_(const char *s) 63{ 64 FLAC__int64 ret = 0; 65 char c; 66 67 if(*s == '\0') 68 return -1; 69 70 while('\0' != (c = *s++)) 71 if(c >= '0' && c <= '9') 72 ret = ret * 10 + (c - '0'); 73 else 74 return -1; 75 76 return ret; 77} 78 79/* accept '[0-9]+:[0-9][0-9]?:[0-9][0-9]?', but max second of 59 and max frame of 74, e.g. 0:0:0, 123:45:67 80 * return sample number or <0 for error 81 */ 82static FLAC__int64 local__parse_msf_(const char *s) 83{ 84 FLAC__int64 ret, field; 85 char c; 86 87 c = *s++; 88 if(c >= '0' && c <= '9') 89 field = (c - '0'); 90 else 91 return -1; 92 while(':' != (c = *s++)) { 93 if(c >= '0' && c <= '9') 94 field = field * 10 + (c - '0'); 95 else 96 return -1; 97 } 98 99 ret = field * 60 * 44100; 100 101 c = *s++; 102 if(c >= '0' && c <= '9') 103 field = (c - '0'); 104 else 105 return -1; 106 if(':' != (c = *s++)) { 107 if(c >= '0' && c <= '9') { 108 field = field * 10 + (c - '0'); 109 c = *s++; 110 if(c != ':') 111 return -1; 112 } 113 else 114 return -1; 115 } 116 117 if(field >= 60) 118 return -1; 119 120 ret += field * 44100; 121 122 c = *s++; 123 if(c >= '0' && c <= '9') 124 field = (c - '0'); 125 else 126 return -1; 127 if('\0' != (c = *s++)) { 128 if(c >= '0' && c <= '9') { 129 field = field * 10 + (c - '0'); 130 c = *s++; 131 } 132 else 133 return -1; 134 } 135 136 if(c != '\0') 137 return -1; 138 139 if(field >= 75) 140 return -1; 141 142 ret += field * (44100 / 75); 143 144 return ret; 145} 146 147static char *local__get_field_(char **s, FLAC__bool allow_quotes) 148{ 149 FLAC__bool has_quote = false; 150 char *p; 151 152 FLAC__ASSERT(0 != s); 153 154 if(0 == *s) 155 return 0; 156 157 /* skip leading whitespace */ 158 while(**s && 0 != strchr(" \t\r\n", **s)) 159 (*s)++; 160 161 if(**s == 0) { 162 *s = 0; 163 return 0; 164 } 165 166 if(allow_quotes && (**s == '"')) { 167 has_quote = true; 168 (*s)++; 169 if(**s == 0) { 170 *s = 0; 171 return 0; 172 } 173 } 174 175 p = *s; 176 177 if(has_quote) { 178 *s = strchr(*s, '\"'); 179 /* if there is no matching end quote, it's an error */ 180 if(0 == *s) 181 p = *s = 0; 182 else { 183 **s = '\0'; 184 (*s)++; 185 } 186 } 187 else { 188 while(**s && 0 == strchr(" \t\r\n", **s)) 189 (*s)++; 190 if(**s) { 191 **s = '\0'; 192 (*s)++; 193 } 194 else 195 *s = 0; 196 } 197 198 return p; 199} 200 201static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message, unsigned *last_line_read, FLAC__StreamMetadata *cuesheet, FLAC__bool is_cdda, FLAC__uint64 lead_out_offset) 202{ 203#if defined _MSC_VER || defined __MINGW32__ || defined __EMX__ 204#define FLAC__STRCASECMP stricmp 205#else 206#define FLAC__STRCASECMP strcasecmp 207#endif 208 char buffer[4096], *line, *field; 209 unsigned forced_leadout_track_num = 0; 210 FLAC__uint64 forced_leadout_track_offset = 0; 211 int in_track_num = -1, in_index_num = -1; 212 FLAC__bool disc_has_catalog = false, track_has_flags = false, track_has_isrc = false, has_forced_leadout = false; 213 FLAC__StreamMetadata_CueSheet *cs = &cuesheet->data.cue_sheet; 214 215 cs->lead_in = is_cdda? 2 * 44100 /* The default lead-in size for CD-DA */ : 0; 216 cs->is_cd = is_cdda; 217 218 while(0 != fgets(buffer, sizeof(buffer), file)) { 219 (*last_line_read)++; 220 line = buffer; 221 222 { 223 size_t linelen = strlen(line); 224 if((linelen == sizeof(buffer)-1) && line[linelen-1] != '\n') { 225 *error_message = "line too long"; 226 return false; 227 } 228 } 229 230 if(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) { 231 if(0 == FLAC__STRCASECMP(field, "CATALOG")) { 232 if(disc_has_catalog) { 233 *error_message = "found multiple CATALOG commands"; 234 return false; 235 } 236 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/true))) { 237 *error_message = "CATALOG is missing catalog number"; 238 return false; 239 } 240 if(strlen(field) >= sizeof(cs->media_catalog_number)) { 241 *error_message = "CATALOG number is too long"; 242 return false; 243 } 244 if(is_cdda && (strlen(field) != 13 || strspn(field, "0123456789") != 13)) { 245 *error_message = "CD-DA CATALOG number must be 13 decimal digits"; 246 return false; 247 } 248 strcpy(cs->media_catalog_number, field); 249 disc_has_catalog = true; 250 } 251 else if(0 == FLAC__STRCASECMP(field, "FLAGS")) { 252 if(track_has_flags) { 253 *error_message = "found multiple FLAGS commands"; 254 return false; 255 } 256 if(in_track_num < 0 || in_index_num >= 0) { 257 *error_message = "FLAGS command must come after TRACK but before INDEX"; 258 return false; 259 } 260 while(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) { 261 if(0 == FLAC__STRCASECMP(field, "PRE")) 262 cs->tracks[cs->num_tracks-1].pre_emphasis = 1; 263 } 264 track_has_flags = true; 265 } 266 else if(0 == FLAC__STRCASECMP(field, "INDEX")) { 267 FLAC__int64 xx; 268 FLAC__StreamMetadata_CueSheet_Track *track = &cs->tracks[cs->num_tracks-1]; 269 if(in_track_num < 0) { 270 *error_message = "found INDEX before any TRACK"; 271 return false; 272 } 273 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { 274 *error_message = "INDEX is missing index number"; 275 return false; 276 } 277 in_index_num = local__parse_int_(field); 278 if(in_index_num < 0) { 279 *error_message = "INDEX has invalid index number"; 280 return false; 281 } 282 FLAC__ASSERT(cs->num_tracks > 0); 283 if(track->num_indices == 0) { 284 /* it's the first index point of the track */ 285 if(in_index_num > 1) { 286 *error_message = "first INDEX number of a TRACK must be 0 or 1"; 287 return false; 288 } 289 } 290 else { 291 if(in_index_num != track->indices[track->num_indices-1].number + 1) { 292 *error_message = "INDEX numbers must be sequential"; 293 return false; 294 } 295 } 296 if(is_cdda && in_index_num > 99) { 297 *error_message = "CD-DA INDEX number must be between 0 and 99, inclusive"; 298 return false; 299 } 300 /*@@@ search for duplicate track number? */ 301 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { 302 *error_message = "INDEX is missing an offset after the index number"; 303 return false; 304 } 305 xx = local__parse_msf_(field); 306 if(xx < 0) { 307 if(is_cdda) { 308 *error_message = "illegal INDEX offset (not of the form MM:SS:FF)"; 309 return false; 310 } 311 xx = local__parse_int64_(field); 312 if(xx < 0) { 313 *error_message = "illegal INDEX offset"; 314 return false; 315 } 316 } 317 if(is_cdda && cs->num_tracks == 1 && cs->tracks[0].num_indices == 0 && xx != 0) { 318 *error_message = "first INDEX of first TRACK must have an offset of 00:00:00"; 319 return false; 320 } 321 if(is_cdda && track->num_indices > 0 && (FLAC__uint64)xx <= track->indices[track->num_indices-1].offset) { 322 *error_message = "CD-DA INDEX offsets must increase in time"; 323 return false; 324 } 325 /* fill in track offset if it's the first index of the track */ 326 if(track->num_indices == 0) 327 track->offset = (FLAC__uint64)xx; 328 if(is_cdda && cs->num_tracks > 1) { 329 const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-2]; 330 if((FLAC__uint64)xx <= prev->offset + prev->indices[prev->num_indices-1].offset) { 331 *error_message = "CD-DA INDEX offsets must increase in time"; 332 return false; 333 } 334 } 335 if(!FLAC__metadata_object_cuesheet_track_insert_blank_index(cuesheet, cs->num_tracks-1, track->num_indices)) { 336 *error_message = "memory allocation error"; 337 return false; 338 } 339 track->indices[track->num_indices-1].offset = (FLAC__uint64)xx - track->offset; 340 track->indices[track->num_indices-1].number = in_index_num; 341 } 342 else if(0 == FLAC__STRCASECMP(field, "ISRC")) { 343 char *l, *r; 344 if(track_has_isrc) { 345 *error_message = "found multiple ISRC commands"; 346 return false; 347 } 348 if(in_track_num < 0 || in_index_num >= 0) { 349 *error_message = "ISRC command must come after TRACK but before INDEX"; 350 return false; 351 } 352 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { 353 *error_message = "ISRC is missing ISRC number"; 354 return false; 355 } 356 /* strip out dashes */ 357 for(l = r = field; *r; r++) { 358 if(*r != '-') 359 *l++ = *r; 360 } 361 *l = '\0'; 362 if(strlen(field) != 12 || strspn(field, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") < 5 || strspn(field+5, "1234567890") != 7) { 363 *error_message = "invalid ISRC number"; 364 return false; 365 } 366 strcpy(cs->tracks[cs->num_tracks-1].isrc, field); 367 track_has_isrc = true; 368 } 369 else if(0 == FLAC__STRCASECMP(field, "TRACK")) { 370 if(cs->num_tracks > 0) { 371 const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-1]; 372 if( 373 prev->num_indices == 0 || 374 ( 375 is_cdda && 376 ( 377 (prev->num_indices == 1 && prev->indices[0].number != 1) || 378 (prev->num_indices == 2 && prev->indices[0].number != 1 && prev->indices[1].number != 1) 379 ) 380 ) 381 ) { 382 *error_message = is_cdda? 383 "previous TRACK must specify at least one INDEX 01" : 384 "previous TRACK must specify at least one INDEX"; 385 return false; 386 } 387 } 388 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { 389 *error_message = "TRACK is missing track number"; 390 return false; 391 } 392 in_track_num = local__parse_int_(field); 393 if(in_track_num < 0) { 394 *error_message = "TRACK has invalid track number"; 395 return false; 396 } 397 if(in_track_num == 0) { 398 *error_message = "TRACK number must be greater than 0"; 399 return false; 400 } 401 if(is_cdda) { 402 if(in_track_num > 99) { 403 *error_message = "CD-DA TRACK number must be between 1 and 99, inclusive"; 404 return false; 405 } 406 } 407 else { 408 if(in_track_num == 255) { 409 *error_message = "TRACK number 255 is reserved for the lead-out"; 410 return false; 411 } 412 else if(in_track_num > 255) { 413 *error_message = "TRACK number must be between 1 and 254, inclusive"; 414 return false; 415 } 416 } 417 if(is_cdda && cs->num_tracks > 0 && in_track_num != cs->tracks[cs->num_tracks-1].number + 1) { 418 *error_message = "CD-DA TRACK numbers must be sequential"; 419 return false; 420 } 421 /*@@@ search for duplicate track number? */ 422 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { 423 *error_message = "TRACK is missing a track type after the track number"; 424 return false; 425 } 426 if(!FLAC__metadata_object_cuesheet_insert_blank_track(cuesheet, cs->num_tracks)) { 427 *error_message = "memory allocation error"; 428 return false; 429 } 430 cs->tracks[cs->num_tracks-1].number = in_track_num; 431 cs->tracks[cs->num_tracks-1].type = (0 == FLAC__STRCASECMP(field, "AUDIO"))? 0 : 1; /*@@@ should we be more strict with the value here? */ 432 in_index_num = -1; 433 track_has_flags = false; 434 track_has_isrc = false; 435 } 436 else if(0 == FLAC__STRCASECMP(field, "REM")) { 437 if(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) { 438 if(0 == strcmp(field, "FLAC__lead-in")) { 439 FLAC__int64 xx; 440 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { 441 *error_message = "FLAC__lead-in is missing offset"; 442 return false; 443 } 444 xx = local__parse_int64_(field); 445 if(xx < 0) { 446 *error_message = "illegal FLAC__lead-in offset"; 447 return false; 448 } 449 if(is_cdda && xx % 588 != 0) { 450 *error_message = "illegal CD-DA FLAC__lead-in offset, must be even multiple of 588 samples"; 451 return false; 452 } 453 cs->lead_in = (FLAC__uint64)xx; 454 } 455 else if(0 == strcmp(field, "FLAC__lead-out")) { 456 int track_num; 457 FLAC__int64 offset; 458 if(has_forced_leadout) { 459 *error_message = "multiple FLAC__lead-out commands"; 460 return false; 461 } 462 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { 463 *error_message = "FLAC__lead-out is missing track number"; 464 return false; 465 } 466 track_num = local__parse_int_(field); 467 if(track_num < 0) { 468 *error_message = "illegal FLAC__lead-out track number"; 469 return false; 470 } 471 forced_leadout_track_num = (unsigned)track_num; 472 /*@@@ search for duplicate track number? */ 473 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) { 474 *error_message = "FLAC__lead-out is missing offset"; 475 return false; 476 } 477 offset = local__parse_int64_(field); 478 if(offset < 0) { 479 *error_message = "illegal FLAC__lead-out offset"; 480 return false; 481 } 482 forced_leadout_track_offset = (FLAC__uint64)offset; 483 if(forced_leadout_track_offset != lead_out_offset) { 484 *error_message = "FLAC__lead-out offset does not match end-of-stream offset"; 485 return false; 486 } 487 has_forced_leadout = true; 488 } 489 } 490 } 491 } 492 } 493 494 if(cs->num_tracks == 0) { 495 *error_message = "there must be at least one TRACK command"; 496 return false; 497 } 498 else { 499 const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-1]; 500 if( 501 prev->num_indices == 0 || 502 ( 503 is_cdda && 504 ( 505 (prev->num_indices == 1 && prev->indices[0].number != 1) || 506 (prev->num_indices == 2 && prev->indices[0].number != 1 && prev->indices[1].number != 1) 507 ) 508 ) 509 ) { 510 *error_message = is_cdda? 511 "previous TRACK must specify at least one INDEX 01" : 512 "previous TRACK must specify at least one INDEX"; 513 return false; 514 } 515 } 516 517 if(!has_forced_leadout) { 518 forced_leadout_track_num = is_cdda? 170 : 255; 519 forced_leadout_track_offset = lead_out_offset; 520 } 521 if(!FLAC__metadata_object_cuesheet_insert_blank_track(cuesheet, cs->num_tracks)) { 522 *error_message = "memory allocation error"; 523 return false; 524 } 525 cs->tracks[cs->num_tracks-1].number = forced_leadout_track_num; 526 cs->tracks[cs->num_tracks-1].offset = forced_leadout_track_offset; 527 528 if(!feof(file)) { 529 *error_message = "read error"; 530 return false; 531 } 532 return true; 533#undef FLAC__STRCASECMP 534} 535 536FLAC__StreamMetadata *grabbag__cuesheet_parse(FILE *file, const char **error_message, unsigned *last_line_read, FLAC__bool is_cdda, FLAC__uint64 lead_out_offset) 537{ 538 FLAC__StreamMetadata *cuesheet; 539 540 FLAC__ASSERT(0 != file); 541 FLAC__ASSERT(0 != error_message); 542 FLAC__ASSERT(0 != last_line_read); 543 544 *last_line_read = 0; 545 cuesheet = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET); 546 547 if(0 == cuesheet) { 548 *error_message = "memory allocation error"; 549 return 0; 550 } 551 552 if(!local__cuesheet_parse_(file, error_message, last_line_read, cuesheet, is_cdda, lead_out_offset)) { 553 FLAC__metadata_object_delete(cuesheet); 554 return 0; 555 } 556 557 return cuesheet; 558} 559 560void grabbag__cuesheet_emit(FILE *file, const FLAC__StreamMetadata *cuesheet, const char *file_reference) 561{ 562 const FLAC__StreamMetadata_CueSheet *cs; 563 unsigned track_num, index_num; 564 565 FLAC__ASSERT(0 != file); 566 FLAC__ASSERT(0 != cuesheet); 567 FLAC__ASSERT(cuesheet->type == FLAC__METADATA_TYPE_CUESHEET); 568 569 cs = &cuesheet->data.cue_sheet; 570 571 if(*(cs->media_catalog_number)) 572 fprintf(file, "CATALOG %s\n", cs->media_catalog_number); 573 fprintf(file, "FILE %s\n", file_reference); 574 575 for(track_num = 0; track_num < cs->num_tracks-1; track_num++) { 576 const FLAC__StreamMetadata_CueSheet_Track *track = cs->tracks + track_num; 577 578 fprintf(file, " TRACK %02u %s\n", (unsigned)track->number, track->type == 0? "AUDIO" : "DATA"); 579 580 if(track->pre_emphasis) 581 fprintf(file, " FLAGS PRE\n"); 582 if(*(track->isrc)) 583 fprintf(file, " ISRC %s\n", track->isrc); 584 585 for(index_num = 0; index_num < track->num_indices; index_num++) { 586 const FLAC__StreamMetadata_CueSheet_Index *index = track->indices + index_num; 587 588 fprintf(file, " INDEX %02u ", (unsigned)index->number); 589 if(cs->is_cd) { 590 const unsigned logical_frame = (unsigned)((track->offset + index->offset) / (44100 / 75)); 591 unsigned m, s, f; 592 grabbag__cuesheet_frame_to_msf(logical_frame, &m, &s, &f); 593 fprintf(file, "%02u:%02u:%02u\n", m, s, f); 594 } 595 else 596#ifdef _MSC_VER 597 fprintf(file, "%I64u\n", track->offset + index->offset); 598#else 599 fprintf(file, "%llu\n", (unsigned long long)(track->offset + index->offset)); 600#endif 601 } 602 } 603 604#ifdef _MSC_VER 605 fprintf(file, "REM FLAC__lead-in %I64u\n", cs->lead_in); 606 fprintf(file, "REM FLAC__lead-out %u %I64u\n", (unsigned)cs->tracks[track_num].number, cs->tracks[track_num].offset); 607#else 608 fprintf(file, "REM FLAC__lead-in %llu\n", (unsigned long long)cs->lead_in); 609 fprintf(file, "REM FLAC__lead-out %u %llu\n", (unsigned)cs->tracks[track_num].number, (unsigned long long)cs->tracks[track_num].offset); 610#endif 611} 612