1 /* grabbag - Convenience lib for various routines common to several tools
2 * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009 Josh Coalson
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.
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.
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
26 #include "FLAC/assert.h"
27 #include "share/compat.h"
28 #include "share/grabbag.h"
29 #include "share/safe_str.h"
31 unsigned grabbag__cuesheet_msf_to_frame(unsigned minutes, unsigned seconds, unsigned frames)
33 return ((minutes * 60) + seconds) * 75 + frames;
36 void grabbag__cuesheet_frame_to_msf(unsigned frame, unsigned *minutes, unsigned *seconds, unsigned *frames)
40 *seconds = frame % 60;
45 /* since we only care about values >= 0 or error, returns < 0 for any illegal string, else value */
46 static int local__parse_int_(const char *s)
54 while('\0' != (c = *s++))
55 if(c >= '0' && c <= '9')
56 ret = ret * 10 + (c - '0');
63 /* since we only care about values >= 0 or error, returns < 0 for any illegal string, else value */
64 static FLAC__int64 local__parse_int64_(const char *s)
72 while('\0' != (c = *s++))
73 if(c >= '0' && c <= '9')
74 ret = ret * 10 + (c - '0');
81 /* accept minute:second:frame syntax of '[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
82 * return sample number or <0 for error
83 * WATCHOUT: if sample rate is not evenly divisible by 75, the resulting sample number will be approximate
85 static FLAC__int64 local__parse_msf_(const char *s, unsigned sample_rate)
87 FLAC__int64 ret, field;
91 if(c >= '0' && c <= '9')
95 while(':' != (c = *s++)) {
96 if(c >= '0' && c <= '9')
97 field = field * 10 + (c - '0');
102 ret = field * 60 * sample_rate;
105 if(c >= '0' && c <= '9')
109 if(':' != (c = *s++)) {
110 if(c >= '0' && c <= '9') {
111 field = field * 10 + (c - '0');
123 ret += field * sample_rate;
126 if(c >= '0' && c <= '9')
130 if('\0' != (c = *s++)) {
131 if(c >= '0' && c <= '9') {
132 field = field * 10 + (c - '0');
145 ret += field * (sample_rate / 75);
150 /* accept minute:second syntax of '[0-9]+:[0-9][0-9]?{,.[0-9]+}', but second < 60, e.g. 0:0.0, 3:5, 15:31.731
151 * return sample number or <0 for error
152 * WATCHOUT: depending on the sample rate, the resulting sample number may be approximate with fractional seconds
154 static FLAC__int64 local__parse_ms_(const char *s, unsigned sample_rate)
156 FLAC__int64 ret, field;
161 if(c >= '0' && c <= '9')
165 while(':' != (c = *s++)) {
166 if(c >= '0' && c <= '9')
167 field = field * 10 + (c - '0');
172 ret = field * 60 * sample_rate;
174 s++; /* skip the ':' */
175 if(strspn(s, "0123456789.") != strlen(s))
180 if(x < 0.0 || x >= 60.0)
183 ret += (FLAC__int64)(x * sample_rate);
188 static char *local__get_field_(char **s, FLAC__bool allow_quotes)
190 FLAC__bool has_quote = false;
193 FLAC__ASSERT(0 != s);
198 /* skip leading whitespace */
199 while(**s && 0 != strchr(" \t\r\n", **s))
207 if(allow_quotes && (**s == '"')) {
219 *s = strchr(*s, '\"');
220 /* if there is no matching end quote, it's an error */
229 while(**s && 0 == strchr(" \t\r\n", **s))
242 static FLAC__bool local__cuesheet_parse_(FILE *file, const char **error_message, unsigned *last_line_read, FLAC__StreamMetadata *cuesheet, unsigned sample_rate, FLAC__bool is_cdda, FLAC__uint64 lead_out_offset)
244 #if defined _MSC_VER || defined __MINGW32__
245 #define FLAC__STRCASECMP stricmp
247 #define FLAC__STRCASECMP strcasecmp
249 char buffer[4096], *line, *field;
250 unsigned forced_leadout_track_num = 0;
251 FLAC__uint64 forced_leadout_track_offset = 0;
252 int in_track_num = -1, in_index_num = -1;
253 FLAC__bool disc_has_catalog = false, track_has_flags = false, track_has_isrc = false, has_forced_leadout = false;
254 FLAC__StreamMetadata_CueSheet *cs = &cuesheet->data.cue_sheet;
256 FLAC__ASSERT(!is_cdda || sample_rate == 44100);
257 /* double protection */
258 if(is_cdda && sample_rate != 44100) {
259 *error_message = "CD-DA cuesheet only allowed with 44.1kHz sample rate";
263 cs->lead_in = is_cdda? 2 * 44100 /* The default lead-in size for CD-DA */ : 0;
266 while(0 != fgets(buffer, sizeof(buffer), file)) {
271 size_t linelen = strlen(line);
272 if((linelen == sizeof(buffer)-1) && line[linelen-1] != '\n') {
273 *error_message = "line too long";
278 if(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) {
279 if(0 == FLAC__STRCASECMP(field, "CATALOG")) {
280 if(disc_has_catalog) {
281 *error_message = "found multiple CATALOG commands";
284 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/true))) {
285 *error_message = "CATALOG is missing catalog number";
288 if(strlen(field) >= sizeof(cs->media_catalog_number)) {
289 *error_message = "CATALOG number is too long";
292 if(is_cdda && (strlen(field) != 13 || strspn(field, "0123456789") != 13)) {
293 *error_message = "CD-DA CATALOG number must be 13 decimal digits";
296 safe_strncpy(cs->media_catalog_number, field, sizeof(cs->media_catalog_number));
297 disc_has_catalog = true;
299 else if(0 == FLAC__STRCASECMP(field, "FLAGS")) {
300 if(track_has_flags) {
301 *error_message = "found multiple FLAGS commands";
304 if(in_track_num < 0 || in_index_num >= 0) {
305 *error_message = "FLAGS command must come after TRACK but before INDEX";
308 while(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) {
309 if(0 == FLAC__STRCASECMP(field, "PRE"))
310 cs->tracks[cs->num_tracks-1].pre_emphasis = 1;
312 track_has_flags = true;
314 else if(0 == FLAC__STRCASECMP(field, "INDEX")) {
316 FLAC__StreamMetadata_CueSheet_Track *track = &cs->tracks[cs->num_tracks-1];
317 if(in_track_num < 0) {
318 *error_message = "found INDEX before any TRACK";
321 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
322 *error_message = "INDEX is missing index number";
325 in_index_num = local__parse_int_(field);
326 if(in_index_num < 0) {
327 *error_message = "INDEX has invalid index number";
330 FLAC__ASSERT(cs->num_tracks > 0);
331 if(track->num_indices == 0) {
332 /* it's the first index point of the track */
333 if(in_index_num > 1) {
334 *error_message = "first INDEX number of a TRACK must be 0 or 1";
339 if(in_index_num != track->indices[track->num_indices-1].number + 1) {
340 *error_message = "INDEX numbers must be sequential";
344 if(is_cdda && in_index_num > 99) {
345 *error_message = "CD-DA INDEX number must be between 0 and 99, inclusive";
348 /*@@@ search for duplicate track number? */
349 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
350 *error_message = "INDEX is missing an offset after the index number";
353 /* first parse as minute:second:frame format */
354 xx = local__parse_msf_(field, sample_rate);
356 /* CD-DA must use only MM:SS:FF format */
358 *error_message = "illegal INDEX offset (not of the form MM:SS:FF)";
361 /* as an extension for non-CD-DA we allow MM:SS.SS or raw sample number */
362 xx = local__parse_ms_(field, sample_rate);
364 xx = local__parse_int64_(field);
366 *error_message = "illegal INDEX offset";
371 else if(sample_rate % 75 && xx) {
372 /* only sample zero is exact */
373 *error_message = "illegal INDEX offset (MM:SS:FF form not allowed if sample rate is not a multiple of 75)";
376 if(is_cdda && cs->num_tracks == 1 && cs->tracks[0].num_indices == 0 && xx != 0) {
377 *error_message = "first INDEX of first TRACK must have an offset of 00:00:00";
380 if(is_cdda && track->num_indices > 0 && (FLAC__uint64)xx <= track->indices[track->num_indices-1].offset) {
381 *error_message = "CD-DA INDEX offsets must increase in time";
384 /* fill in track offset if it's the first index of the track */
385 if(track->num_indices == 0)
386 track->offset = (FLAC__uint64)xx;
387 if(is_cdda && cs->num_tracks > 1) {
388 const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-2];
389 if((FLAC__uint64)xx <= prev->offset + prev->indices[prev->num_indices-1].offset) {
390 *error_message = "CD-DA INDEX offsets must increase in time";
394 if(!FLAC__metadata_object_cuesheet_track_insert_blank_index(cuesheet, cs->num_tracks-1, track->num_indices)) {
395 *error_message = "memory allocation error";
398 track->indices[track->num_indices-1].offset = (FLAC__uint64)xx - track->offset;
399 track->indices[track->num_indices-1].number = in_index_num;
401 else if(0 == FLAC__STRCASECMP(field, "ISRC")) {
404 *error_message = "found multiple ISRC commands";
407 if(in_track_num < 0 || in_index_num >= 0) {
408 *error_message = "ISRC command must come after TRACK but before INDEX";
411 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
412 *error_message = "ISRC is missing ISRC number";
415 /* strip out dashes */
416 for(l = r = field; *r; r++) {
421 if(strlen(field) != 12 || strspn(field, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") < 5 || strspn(field+5, "1234567890") != 7) {
422 *error_message = "invalid ISRC number";
425 safe_strncpy(cs->tracks[cs->num_tracks-1].isrc, field, sizeof(cs->tracks[cs->num_tracks-1].isrc));
426 track_has_isrc = true;
428 else if(0 == FLAC__STRCASECMP(field, "TRACK")) {
429 if(cs->num_tracks > 0) {
430 const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-1];
432 prev->num_indices == 0 ||
436 (prev->num_indices == 1 && prev->indices[0].number != 1) ||
437 (prev->num_indices == 2 && prev->indices[0].number != 1 && prev->indices[1].number != 1)
441 *error_message = is_cdda?
442 "previous TRACK must specify at least one INDEX 01" :
443 "previous TRACK must specify at least one INDEX";
447 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
448 *error_message = "TRACK is missing track number";
451 in_track_num = local__parse_int_(field);
452 if(in_track_num < 0) {
453 *error_message = "TRACK has invalid track number";
456 if(in_track_num == 0) {
457 *error_message = "TRACK number must be greater than 0";
461 if(in_track_num > 99) {
462 *error_message = "CD-DA TRACK number must be between 1 and 99, inclusive";
467 if(in_track_num == 255) {
468 *error_message = "TRACK number 255 is reserved for the lead-out";
471 else if(in_track_num > 255) {
472 *error_message = "TRACK number must be between 1 and 254, inclusive";
476 if(is_cdda && cs->num_tracks > 0 && in_track_num != cs->tracks[cs->num_tracks-1].number + 1) {
477 *error_message = "CD-DA TRACK numbers must be sequential";
480 /*@@@ search for duplicate track number? */
481 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
482 *error_message = "TRACK is missing a track type after the track number";
485 if(!FLAC__metadata_object_cuesheet_insert_blank_track(cuesheet, cs->num_tracks)) {
486 *error_message = "memory allocation error";
489 cs->tracks[cs->num_tracks-1].number = in_track_num;
490 cs->tracks[cs->num_tracks-1].type = (0 == FLAC__STRCASECMP(field, "AUDIO"))? 0 : 1; /*@@@ should we be more strict with the value here? */
492 track_has_flags = false;
493 track_has_isrc = false;
495 else if(0 == FLAC__STRCASECMP(field, "REM")) {
496 if(0 != (field = local__get_field_(&line, /*allow_quotes=*/false))) {
497 if(0 == strcmp(field, "FLAC__lead-in")) {
499 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
500 *error_message = "FLAC__lead-in is missing offset";
503 xx = local__parse_int64_(field);
505 *error_message = "illegal FLAC__lead-in offset";
508 if(is_cdda && xx % 588 != 0) {
509 *error_message = "illegal CD-DA FLAC__lead-in offset, must be even multiple of 588 samples";
512 cs->lead_in = (FLAC__uint64)xx;
514 else if(0 == strcmp(field, "FLAC__lead-out")) {
517 if(has_forced_leadout) {
518 *error_message = "multiple FLAC__lead-out commands";
521 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
522 *error_message = "FLAC__lead-out is missing track number";
525 track_num = local__parse_int_(field);
527 *error_message = "illegal FLAC__lead-out track number";
530 forced_leadout_track_num = (unsigned)track_num;
531 /*@@@ search for duplicate track number? */
532 if(0 == (field = local__get_field_(&line, /*allow_quotes=*/false))) {
533 *error_message = "FLAC__lead-out is missing offset";
536 offset = local__parse_int64_(field);
538 *error_message = "illegal FLAC__lead-out offset";
541 forced_leadout_track_offset = (FLAC__uint64)offset;
542 if(forced_leadout_track_offset != lead_out_offset) {
543 *error_message = "FLAC__lead-out offset does not match end-of-stream offset";
546 has_forced_leadout = true;
553 if(cs->num_tracks == 0) {
554 *error_message = "there must be at least one TRACK command";
558 const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-1];
560 prev->num_indices == 0 ||
564 (prev->num_indices == 1 && prev->indices[0].number != 1) ||
565 (prev->num_indices == 2 && prev->indices[0].number != 1 && prev->indices[1].number != 1)
569 *error_message = is_cdda?
570 "previous TRACK must specify at least one INDEX 01" :
571 "previous TRACK must specify at least one INDEX";
576 if(!has_forced_leadout) {
577 forced_leadout_track_num = is_cdda? 170 : 255;
578 forced_leadout_track_offset = lead_out_offset;
580 if(!FLAC__metadata_object_cuesheet_insert_blank_track(cuesheet, cs->num_tracks)) {
581 *error_message = "memory allocation error";
584 cs->tracks[cs->num_tracks-1].number = forced_leadout_track_num;
585 cs->tracks[cs->num_tracks-1].offset = forced_leadout_track_offset;
588 *error_message = "read error";
592 #undef FLAC__STRCASECMP
595 FLAC__StreamMetadata *grabbag__cuesheet_parse(FILE *file, const char **error_message, unsigned *last_line_read, unsigned sample_rate, FLAC__bool is_cdda, FLAC__uint64 lead_out_offset)
597 FLAC__StreamMetadata *cuesheet;
599 FLAC__ASSERT(0 != file);
600 FLAC__ASSERT(0 != error_message);
601 FLAC__ASSERT(0 != last_line_read);
604 cuesheet = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET);
607 *error_message = "memory allocation error";
611 if(!local__cuesheet_parse_(file, error_message, last_line_read, cuesheet, sample_rate, is_cdda, lead_out_offset)) {
612 FLAC__metadata_object_delete(cuesheet);
619 void grabbag__cuesheet_emit(FILE *file, const FLAC__StreamMetadata *cuesheet, const char *file_reference)
621 const FLAC__StreamMetadata_CueSheet *cs;
622 unsigned track_num, index_num;
624 FLAC__ASSERT(0 != file);
625 FLAC__ASSERT(0 != cuesheet);
626 FLAC__ASSERT(cuesheet->type == FLAC__METADATA_TYPE_CUESHEET);
628 cs = &cuesheet->data.cue_sheet;
630 if(*(cs->media_catalog_number))
631 fprintf(file, "CATALOG %s\n", cs->media_catalog_number);
632 fprintf(file, "FILE %s\n", file_reference);
634 for(track_num = 0; track_num < cs->num_tracks-1; track_num++) {
635 const FLAC__StreamMetadata_CueSheet_Track *track = cs->tracks + track_num;
637 fprintf(file, " TRACK %02u %s\n", (unsigned)track->number, track->type == 0? "AUDIO" : "DATA");
639 if(track->pre_emphasis)
640 fprintf(file, " FLAGS PRE\n");
642 fprintf(file, " ISRC %s\n", track->isrc);
644 for(index_num = 0; index_num < track->num_indices; index_num++) {
645 const FLAC__StreamMetadata_CueSheet_Index *index = track->indices + index_num;
647 fprintf(file, " INDEX %02u ", (unsigned)index->number);
649 const unsigned logical_frame = (unsigned)((track->offset + index->offset) / (44100 / 75));
651 grabbag__cuesheet_frame_to_msf(logical_frame, &m, &s, &f);
652 fprintf(file, "%02u:%02u:%02u\n", m, s, f);
655 fprintf(file, "%" PRIu64 "\n", (track->offset + index->offset));
659 fprintf(file, "REM FLAC__lead-in %" PRIu64 "\n", cs->lead_in);
660 fprintf(file, "REM FLAC__lead-out %u %" PRIu64 "\n", (unsigned)cs->tracks[track_num].number, cs->tracks[track_num].offset);