1
/* grabbag - Convenience lib for various routines common to several tools
2
* Copyright (C) 2002,2003 Josh Coalson
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.
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.
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.
19
#include "share/grabbag.h"
20
#include "FLAC/assert.h"
25
unsigned grabbag__cuesheet_msf_to_frame(unsigned minutes, unsigned seconds, unsigned frames)
27
return ((minutes * 60) + seconds) * 75 + frames;
30
void grabbag__cuesheet_frame_to_msf(unsigned frame, unsigned *minutes, unsigned *seconds, unsigned *frames)
34
*seconds = frame % 60;
39
/* since we only care about values >= 0 or error, returns < 0 for any illegal string, else value */
40
static int local__parse_int_(const char *s)
48
while('\0' != (c = *s++))
49
if(c >= '0' && c <= '9')
50
ret = ret * 10 + (c - '0');
57
/* since we only care about values >= 0 or error, returns < 0 for any illegal string, else value */
58
static FLAC__int64 local__parse_int64_(const char *s)
66
while('\0' != (c = *s++))
67
if(c >= '0' && c <= '9')
68
ret = ret * 10 + (c - '0');
75
/* 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
76
* return sample number or <0 for error
78
static FLAC__int64 local__parse_msf_(const char *s)
80
FLAC__int64 ret, field;
84
if(c >= '0' && c <= '9')
88
while(':' != (c = *s++)) {
89
if(c >= '0' && c <= '9')
90
field = field * 10 + (c - '0');
95
ret = field * 60 * 44100;
98
if(c >= '0' && c <= '9')
102
if(':' != (c = *s++)) {
103
if(c >= '0' && c <= '9') {
104
field = field * 10 + (c - '0');
116
ret += field * 44100;
119
if(c >= '0' && c <= '9')
123
if('\0' != (c = *s++)) {
124
if(c >= '0' && c <= '9') {
125
field = field * 10 + (c - '0');
138
ret += field * (44100 / 75);
143
static char *local__get_field_(char **s)
147
FLAC__ASSERT(0 != s);
152
/* skip leading whitespace */
153
while(**s && 0 != strchr(" \t\r\n", **s))
162
while(**s && 0 == strchr(" \t\r\n", **s))
175
static 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)
177
#if defined _MSC_VER || defined __MINGW32__
178
#define FLAC__STRCASECMP stricmp
180
#define FLAC__STRCASECMP strcasecmp
182
char buffer[4096], *line, *field;
183
unsigned linelen, forced_leadout_track_num = 0;
184
FLAC__uint64 forced_leadout_track_offset = 0;
185
int in_track_num = -1, in_index_num = -1;
186
FLAC__bool disc_has_catalog = false, track_has_flags = false, track_has_isrc = false, has_forced_leadout = false;
187
FLAC__StreamMetadata_CueSheet *cs = &cuesheet->data.cue_sheet;
189
cs->lead_in = is_cdda? 2 * 44100 /* The default lead-in size for CD-DA */ : 0;
192
while(0 != fgets(buffer, sizeof(buffer), file)) {
196
linelen = strlen(line);
197
if(line[linelen-1] != '\n') {
198
*error_message = "line too long";
202
if(0 != (field = local__get_field_(&line))) {
203
if(0 == FLAC__STRCASECMP(field, "CATALOG")) {
204
if(disc_has_catalog) {
205
*error_message = "found multiple CATALOG commands";
208
if(0 == (field = local__get_field_(&line))) {
209
*error_message = "CATALOG is missing catalog number";
212
if(strlen(field) >= sizeof(cs->media_catalog_number)) {
213
*error_message = "CATALOG number is too long";
216
if(is_cdda && (strlen(field) != 13 || strspn(field, "0123456789") != 13)) {
217
*error_message = "CD-DA CATALOG number must be 13 decimal digits";
220
strcpy(cs->media_catalog_number, field);
221
disc_has_catalog = true;
223
else if(0 == FLAC__STRCASECMP(field, "FLAGS")) {
224
if(track_has_flags) {
225
*error_message = "found multiple FLAGS commands";
228
if(in_track_num < 0 || in_index_num >= 0) {
229
*error_message = "FLAGS command must come after TRACK but before INDEX";
232
while(0 != (field = local__get_field_(&line))) {
233
if(0 == FLAC__STRCASECMP(field, "PRE"))
234
cs->tracks[cs->num_tracks-1].pre_emphasis = 1;
236
track_has_flags = true;
238
else if(0 == FLAC__STRCASECMP(field, "INDEX")) {
240
FLAC__StreamMetadata_CueSheet_Track *track = &cs->tracks[cs->num_tracks-1];
241
if(in_track_num < 0) {
242
*error_message = "found INDEX before any TRACK";
245
if(0 == (field = local__get_field_(&line))) {
246
*error_message = "INDEX is missing index number";
249
in_index_num = local__parse_int_(field);
250
if(in_index_num < 0) {
251
*error_message = "INDEX has invalid index number";
254
FLAC__ASSERT(cs->num_tracks > 0);
255
if(track->num_indices == 0) {
256
/* it's the first index point of the track */
257
if(in_index_num > 1) {
258
*error_message = "first INDEX number of a TRACK must be 0 or 1";
263
if(in_index_num != track->indices[track->num_indices-1].number + 1) {
264
*error_message = "INDEX numbers must be sequential";
268
if(is_cdda && in_index_num > 99) {
269
*error_message = "CD-DA INDEX number must be between 0 and 99, inclusive";
272
/*@@@ search for duplicate track number? */
273
if(0 == (field = local__get_field_(&line))) {
274
*error_message = "INDEX is missing an offset after the index number";
277
xx = local__parse_msf_(field);
280
*error_message = "illegal INDEX offset (not of the form MM:SS:FF)";
283
xx = local__parse_int64_(field);
285
*error_message = "illegal INDEX offset";
289
if(is_cdda && cs->num_tracks == 1 && cs->tracks[0].num_indices == 0 && xx != 0) {
290
*error_message = "first INDEX of first TRACK must have an offset of 00:00:00";
293
if(is_cdda && track->num_indices > 0 && (FLAC__uint64)xx <= track->indices[track->num_indices-1].offset) {
294
*error_message = "CD-DA INDEX offsets must increase in time";
297
/* fill in track offset if it's the first index of the track */
298
if(track->num_indices == 0)
299
track->offset = (FLAC__uint64)xx;
300
if(is_cdda && cs->num_tracks > 1) {
301
const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-2];
302
if((FLAC__uint64)xx <= prev->offset + prev->indices[prev->num_indices-1].offset) {
303
*error_message = "CD-DA INDEX offsets must increase in time";
307
if(!FLAC__metadata_object_cuesheet_track_insert_blank_index(cuesheet, cs->num_tracks-1, track->num_indices)) {
308
*error_message = "memory allocation error";
311
track->indices[track->num_indices-1].offset = (FLAC__uint64)xx - track->offset;
312
track->indices[track->num_indices-1].number = in_index_num;
314
else if(0 == FLAC__STRCASECMP(field, "ISRC")) {
316
*error_message = "found multiple ISRC commands";
319
if(in_track_num < 0 || in_index_num >= 0) {
320
*error_message = "ISRC command must come after TRACK but before INDEX";
323
if(0 == (field = local__get_field_(&line))) {
324
*error_message = "ISRC is missing ISRC number";
327
if(strlen(field) != 12 || strspn(field, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") < 5 || strspn(field+5, "1234567890") != 7) {
328
*error_message = "invalid ISRC number";
331
strcpy(cs->tracks[cs->num_tracks-1].isrc, field);
332
track_has_isrc = true;
334
else if(0 == FLAC__STRCASECMP(field, "TRACK")) {
335
if(cs->num_tracks > 0) {
336
const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-1];
338
prev->num_indices == 0 ||
342
(prev->num_indices == 1 && prev->indices[0].number != 1) ||
343
(prev->num_indices == 2 && prev->indices[0].number != 1 && prev->indices[1].number != 1)
347
*error_message = is_cdda?
348
"previous TRACK must specify at least one INDEX 01" :
349
"previous TRACK must specify at least one INDEX";
353
if(0 == (field = local__get_field_(&line))) {
354
*error_message = "TRACK is missing track number";
357
in_track_num = local__parse_int_(field);
358
if(in_track_num < 0) {
359
*error_message = "TRACK has invalid track number";
362
if(in_track_num == 0) {
363
*error_message = "TRACK number must be greater than 0";
366
if(is_cdda && in_track_num > 99) {
367
*error_message = "CD-DA TRACK number must be between 1 and 99, inclusive";
370
if(is_cdda && cs->num_tracks > 0 && in_track_num != cs->tracks[cs->num_tracks-1].number + 1) {
371
*error_message = "CD-DA TRACK numbers must be sequential";
374
/*@@@ search for duplicate track number? */
375
if(0 == (field = local__get_field_(&line))) {
376
*error_message = "TRACK is missing a track type after the track number";
379
if(!FLAC__metadata_object_cuesheet_insert_blank_track(cuesheet, cs->num_tracks)) {
380
*error_message = "memory allocation error";
383
cs->tracks[cs->num_tracks-1].number = in_track_num;
384
cs->tracks[cs->num_tracks-1].type = (0 == FLAC__STRCASECMP(field, "AUDIO"))? 0 : 1; /*@@@ should we be more strict with the value here? */
386
track_has_flags = false;
387
track_has_isrc = false;
389
else if(0 == FLAC__STRCASECMP(field, "REM")) {
390
if(0 != (field = local__get_field_(&line))) {
391
if(0 == strcmp(field, "FLAC__lead-in")) {
393
if(0 == (field = local__get_field_(&line))) {
394
*error_message = "FLAC__lead-in is missing offset";
397
xx = local__parse_int64_(field);
399
*error_message = "illegal FLAC__lead-in offset";
402
if(is_cdda && xx % 588 != 0) {
403
*error_message = "illegal CD-DA FLAC__lead-in offset, must be even multiple of 588 samples";
406
cs->lead_in = (FLAC__uint64)xx;
408
else if(0 == strcmp(field, "FLAC__lead-out")) {
411
if(has_forced_leadout) {
412
*error_message = "multiple FLAC__lead-out commands";
415
if(0 == (field = local__get_field_(&line))) {
416
*error_message = "FLAC__lead-out is missing track number";
419
track_num = local__parse_int_(field);
421
*error_message = "illegal FLAC__lead-out track number";
424
forced_leadout_track_num = (unsigned)track_num;
425
/*@@@ search for duplicate track number? */
426
if(0 == (field = local__get_field_(&line))) {
427
*error_message = "FLAC__lead-out is missing offset";
430
offset = local__parse_int64_(field);
432
*error_message = "illegal FLAC__lead-out offset";
435
forced_leadout_track_offset = (FLAC__uint64)offset;
436
if(forced_leadout_track_offset != lead_out_offset) {
437
*error_message = "FLAC__lead-out offset does not match end-of-stream offset";
440
has_forced_leadout = true;
447
if(cs->num_tracks == 0) {
448
*error_message = "there must be at least one TRACK command";
452
const FLAC__StreamMetadata_CueSheet_Track *prev = &cs->tracks[cs->num_tracks-1];
454
prev->num_indices == 0 ||
458
(prev->num_indices == 1 && prev->indices[0].number != 1) ||
459
(prev->num_indices == 2 && prev->indices[0].number != 1 && prev->indices[1].number != 1)
463
*error_message = is_cdda?
464
"previous TRACK must specify at least one INDEX 01" :
465
"previous TRACK must specify at least one INDEX";
470
if(!has_forced_leadout) {
471
forced_leadout_track_num = is_cdda? 170 : cs->num_tracks;
472
forced_leadout_track_offset = lead_out_offset;
474
if(!FLAC__metadata_object_cuesheet_insert_blank_track(cuesheet, cs->num_tracks)) {
475
*error_message = "memory allocation error";
478
cs->tracks[cs->num_tracks-1].number = forced_leadout_track_num;
479
cs->tracks[cs->num_tracks-1].offset = forced_leadout_track_offset;
482
*error_message = "read error";
486
#undef FLAC__STRCASECMP
489
FLAC__StreamMetadata *grabbag__cuesheet_parse(FILE *file, const char **error_message, unsigned *last_line_read, FLAC__bool is_cdda, FLAC__uint64 lead_out_offset)
491
FLAC__StreamMetadata *cuesheet;
493
FLAC__ASSERT(0 != file);
494
FLAC__ASSERT(0 != error_message);
495
FLAC__ASSERT(0 != last_line_read);
498
cuesheet = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET);
501
*error_message = "memory allocation error";
505
if(!local__cuesheet_parse_(file, error_message, last_line_read, cuesheet, is_cdda, lead_out_offset)) {
506
FLAC__metadata_object_delete(cuesheet);
513
void grabbag__cuesheet_emit(FILE *file, const FLAC__StreamMetadata *cuesheet, const char *file_reference)
515
const FLAC__StreamMetadata_CueSheet *cs;
516
unsigned track_num, index_num;
518
FLAC__ASSERT(0 != file);
519
FLAC__ASSERT(0 != cuesheet);
520
FLAC__ASSERT(cuesheet->type == FLAC__METADATA_TYPE_CUESHEET);
522
cs = &cuesheet->data.cue_sheet;
524
if(*(cs->media_catalog_number))
525
fprintf(file, "CATALOG %s\n", cs->media_catalog_number);
526
fprintf(file, "FILE %s\n", file_reference);
528
for(track_num = 0; track_num < cs->num_tracks-1; track_num++) {
529
const FLAC__StreamMetadata_CueSheet_Track *track = cs->tracks + track_num;
531
fprintf(file, " TRACK %02u %s\n", (unsigned)track->number, track->type == 0? "AUDIO" : "DATA");
533
if(track->pre_emphasis)
534
fprintf(file, " FLAGS PRE\n");
536
fprintf(file, " ISRC %s\n", track->isrc);
538
for(index_num = 0; index_num < track->num_indices; index_num++) {
539
const FLAC__StreamMetadata_CueSheet_Index *index = track->indices + index_num;
541
fprintf(file, " INDEX %02u ", (unsigned)index->number);
543
const unsigned logical_frame = (unsigned)((track->offset + index->offset) / (44100 / 75));
545
grabbag__cuesheet_frame_to_msf(logical_frame, &m, &s, &f);
546
fprintf(file, "%02u:%02u:%02u\n", m, s, f);
549
fprintf(file, "%llu\n", track->offset + index->offset);
553
fprintf(file, "REM FLAC__lead-in %llu\n", cs->lead_in);
554
fprintf(file, "REM FLAC__lead-out %u %llu\n", (unsigned)cs->tracks[track_num].number, cs->tracks[track_num].offset);