1
/* cue2toc.c - conversion routines
2
* Copyright (C) 2004 Matthias Czapla <dermatsch@gmx.de>
4
* This file is part of cue2toc.
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32
#define TCBUFLEN 9 /* Buffer length for timecode strings (HH:MM:SS) */
33
#define MAXCMDLEN 10 /* Longest command (currently SONGWRITER) */
35
extern const char *progname; /* Set to argv[0] by main */
36
extern int verbose; /* Set by main */
39
* Input is divied into tokens that are separated by whitespace, horizantal
40
* tabulator, line feed and carriage return. Tokens can be either commands
41
* from a fixed set or strings. If a string is to contain any of the token
42
* delimiting characters it must be enclosed in double quotes.
45
static const char token_delimiter[] = { ' ', '\t', '\n', '\r' };
47
/* Return true if c is one of token_delimiter */
52
int n = sizeof(token_delimiter);
54
for (i = 0; i < n; i++)
55
if (c == token_delimiter[i])
60
/* Used as return type for get_command and index into cmds */
61
enum command { REM, CATALOG, CDTEXTFILE,
62
FILECMD, PERFORMER, SONGWRITER, TITLE, TRACK, FLAGS, DCP,
63
FOURCH, PRE, SCMS, ISRC, PREGAP, INDEX, POSTGAP, BINARY,
64
MOTOROLA, AIFF, WAVE, MP3, UNKNOWN, END };
66
/* Except the last two these are the valid CUE commands */
67
char cmds[][MAXCMDLEN + 1] = { "REM", "CATALOG", "CDTEXTFILE",
68
"FILE", "PERFORMER", "SONGWRITER", "TITLE", "TRACK", "FLAGS", "DCP",
69
"4CH", "PRE", "SCMS", "ISRC", "PREGAP", "INDEX", "POSTGAP", "BINARY",
70
"MOTOROLA", "AIFF", "WAVE", "MP3", "UNKNOWN", "END" };
72
/* These are for error messages */
73
static const char *fname = "stdin";
74
static long line; /* current line number */
75
static long tokenstart; /* line where last token started */
77
/* To generate meaningful error messages in check_once */
78
enum scope { CUESHEET, GLOBAL, ONETRACK };
80
/* Fatal error while processing input file */
82
err_fail(const char *s)
84
fprintf(stderr, "%s:%s:%ld: %s\n", progname, fname, tokenstart, s);
90
err_fail2(const char *s)
92
fprintf(stderr, "%s: %s\n", progname, s);
96
/* EOF while expecting more */
100
fprintf(stderr, "%s:%s:%ld: Premature end of file\n", progname,
105
/* Warning. Keep on going. */
107
err_warn(const char *s)
111
fprintf(stderr, "%s:%s:%ld: Warning, %s\n", progname, fname,
115
/* Get next command from file */
120
char buf[MAXCMDLEN + 1];
128
} while (isdelim(c));
135
/* get command, transform to upper case */
137
buf[i++] = toupper(c);
139
} while (!isdelim(c) && c!= EOF && i < MAXCMDLEN);
141
if (!isdelim(c)) return UNKNOWN; /* command longer than MAXCMDLEN */
142
if (c == EOF) return END;
143
if (c == '\n') line++;
147
if (strcmp(buf, cmds[REM]) == 0) return REM;
148
else if (strcmp(buf, cmds[CATALOG]) == 0) return CATALOG;
149
else if (strcmp(buf, cmds[CDTEXTFILE]) == 0) return CDTEXTFILE;
150
else if (strcmp(buf, cmds[FILECMD]) == 0) return FILECMD;
151
else if (strcmp(buf, cmds[PERFORMER]) == 0) return PERFORMER;
152
else if (strcmp(buf, cmds[SONGWRITER]) == 0) return SONGWRITER;
153
else if (strcmp(buf, cmds[TITLE]) == 0) return TITLE;
154
else if (strcmp(buf, cmds[TRACK]) == 0) return TRACK;
155
else if (strcmp(buf, cmds[FLAGS]) == 0) return FLAGS;
156
else if (strcmp(buf, cmds[DCP]) == 0) return DCP;
157
else if (strcmp(buf, cmds[FOURCH]) == 0) return FOURCH;
158
else if (strcmp(buf, cmds[PRE]) == 0) return PRE;
159
else if (strcmp(buf, cmds[SCMS]) == 0) return SCMS;
160
else if (strcmp(buf, cmds[ISRC]) == 0) return ISRC;
161
else if (strcmp(buf, cmds[PREGAP]) == 0) return PREGAP;
162
else if (strcmp(buf, cmds[INDEX]) == 0) return INDEX;
163
else if (strcmp(buf, cmds[POSTGAP]) == 0) return POSTGAP;
164
else if (strcmp(buf, cmds[BINARY]) == 0) return BINARY;
165
else if (strcmp(buf, cmds[MOTOROLA]) == 0) return MOTOROLA;
166
else if (strcmp(buf, cmds[AIFF]) == 0) return AIFF;
167
else if (strcmp(buf, cmds[WAVE]) == 0) return WAVE;
168
else if (strcmp(buf, cmds[MP3]) == 0) return MP3;
172
/* Skip leading token delimiters then read at most n chars from f into s.
173
* Put terminating Null at the end of s. This implies that s must be
174
* really n + 1. Return number of characters written to s. The only case to
175
* return zero is on EOF before any character was read.
176
* Exit the program indicating failure if string is longer than n. */
178
get_string(FILE *f, char *s, size_t n)
188
} while (isdelim(c));
197
if (c == '\n') line++;
198
while (c != '\"' && c != EOF && i < n) {
201
if (c == '\n') line++;
203
if (i == n && c != '\"' && c != EOF)
204
err_fail("String too long");
206
while (!isdelim(c) && c != EOF && i < n) {
210
if (i == n && !isdelim(c) && c != EOF)
211
err_fail("String too long");
213
if (i == 0) err_fail("Empty string");
214
if (c == '\n') line++;
220
/* Return track mode */
221
static enum track_mode
222
get_track_mode(FILE *f)
224
char buf[] = "MODE1/2048";
227
if (get_string(f, buf, sizeof(buf) - 1) < 1)
228
err_fail("Illegal track mode");
230
/* transform to upper case */
232
*pbuf = toupper(*pbuf);
236
if (strcmp(buf, "AUDIO") == 0) return AUDIO;
237
else if (strcmp(buf, "MODE1/2048") == 0) return MODE1;
238
else if (strcmp(buf, "MODE1/2352") == 0) return MODE1_RAW;
239
else if (strcmp(buf, "MODE2/2336") == 0) return MODE2;
240
else if (strcmp(buf, "MODE2/2352") == 0) return MODE2_RAW;
241
else err_fail("Unsupported track mode");
244
static void check_once(enum command cmd, char *s, enum scope sc);
246
/* Read at most CDTEXTLEN chars into s */
248
get_cdtext(FILE *f, enum command cmd, char *s, enum scope sc)
250
check_once(cmd, s, sc);
251
if (get_string(f, s, CDTEXTLEN) < 1)
255
/* All strings have their first character initialized to '\0' so if s[0]
256
is not Null the cmd has already been seen in input. In this case print
257
a message end exit program indicating failure. The only purpose of the
258
arguments cmd and sc is to print meaningful error messages. */
260
check_once(enum command cmd, char *s, enum scope sc)
264
fprintf(stderr, "%s:%s:%ld: %s allowed only once", progname, fname,
267
case CUESHEET: fprintf(stderr, "\n"); break;
268
case GLOBAL: fprintf(stderr, " in global section\n"); break;
269
case ONETRACK: fprintf(stderr, " per track\n"); break;
274
/* If this is a data track and does not start at position zero exit the
275
program. The TOC format has no way to specify a data track using only a
276
portion past the first byte of a binary file. */
278
check_cutting_binary(struct trackspec *tr)
280
if (tr->mode == AUDIO)
282
if (tr->pregap_data_from_file) {
283
if (tr->pregap < tr->start)
284
err_fail("TOC format does not allow cutting binary "
285
"files. Try burning CUE file directly.\n");
288
err_fail("TOC format does not allow cutting binary "
289
"files. Try burning CUE file directly.\n");
292
/* Allocate, initialize and return new track */
293
static struct trackspec*
296
struct trackspec *track;
299
if ((track = (struct trackspec*) malloc(sizeof(struct trackspec)))
301
err_fail("Memory allocation error in new_track()");
303
track->copy = track->pre_emphasis = track->four_channel_audio
304
= track->pregap_data_from_file = 0;
305
track->isrc[0] = track->title[0] = track->performer[0]
306
= track->songwriter[0] = track->filename[0] = '\0';
307
track->pregap = track->start = track->postgap = -1;
309
for (i = 0; i < NUM_OF_INDEXES; i++)
310
track->indexes[i] = -1;
316
/* Read the cuefile and return a pointer to the cuesheet */
318
read_cue(const char *cuefile, const char *wavefile)
322
struct cuesheet *cs = NULL;
323
struct trackspec *track = NULL;
326
char file[FILENAMELEN + 1];
327
enum command filetype = UNKNOWN;
328
char timecode_buffer[TCBUFLEN];
329
char devnull[FILENAMELEN + 1]; /* just for eating CDTEXTFILE arg */
331
if (NULL == cuefile) {
333
} else if (NULL == (f = fopen(cuefile, "r"))) {
334
fprintf(stderr, "%s: Could not open file \"%s\" for "
335
"reading: %s\n", progname, cuefile, strerror(errno));
341
if ((cs = (struct cuesheet*) malloc(sizeof(struct cuesheet))) == NULL)
342
err_fail("Memory allocation error in read_cue()");
344
cs->catalog[0] = '\0';
347
cs->performer[0] = '\0';
348
cs->songwriter[0] = '\0';
349
cs->tracklist = NULL;
355
while ((cmd = get_command(f)) != TRACK) {
358
err_fail("Unknown command");
363
while (c != '\n' && c != EOF)
367
err_warn("ignoring CDTEXTFILE...");
368
if (get_string(f, devnull, FILENAMELEN) == 0)
369
err_warn("Syntactically incorrect "
370
"CDTEXTFILE command. But who "
374
check_once(CATALOG, cs->catalog, CUESHEET);
375
n = get_string(f, cs->catalog, 13);
377
err_fail("Catalog number must be 13 "
381
get_cdtext(f, TITLE, cs->title, GLOBAL);
384
get_cdtext(f, PERFORMER, cs->performer, GLOBAL);
387
get_cdtext(f, SONGWRITER, cs->songwriter, GLOBAL);
390
check_once(FILECMD, file, GLOBAL);
391
if (get_string(f, file, FILENAMELEN) < 1)
394
switch (cmd = get_command(f)) {
396
err_warn("big endian binary file");
398
filetype = BINARY; break;
400
err_warn("AIFF and MP3 not supported by "
404
strncpy(file, wavefile, FILENAMELEN);
405
file[FILENAMELEN] = '\0';
407
filetype = WAVE; break;
409
err_fail("Unsupported file type");
413
err_fail("Command not allowed in global section");
419
/* leaving global section, entering track specifications */
421
err_fail("TRACK without previous FILE");
426
err_fail("Unknown command");
429
while (c != '\n' && c != EOF)
433
if (track == NULL) /* first track */
434
cs->tracklist = track = new_track();
436
check_cutting_binary(track);
437
track = track->next = new_track();
440
/* the CUE format is "TRACK nn MODE" but we are not
441
interested in the track number */
442
while (isdelim(c = getc(f)))
443
if (c == '\n') line++;
444
while (!isdelim(c = getc(f))) ;
445
if (c == '\n') line++;
447
track->mode = get_track_mode(f);
449
/* audio tracks with binary files seem quite common */
451
if (track->mode == AUDIO && filetype == BINARY
452
|| track->mode != AUDIO && filetype == WAVE)
453
err_fail("File and track type mismatch");
456
strcpy(track->filename, file);
459
get_cdtext(f, TITLE, track->title, ONETRACK);
462
get_cdtext(f, PERFORMER, track->performer, ONETRACK);
465
get_cdtext(f, SONGWRITER, track->songwriter, ONETRACK);
468
check_once(ISRC, track->isrc, ONETRACK);
469
if (get_string(f, track->isrc, 12) != 12)
470
err_fail("ISRC must be 12 characters long");
473
if (track->copy || track->pre_emphasis
474
|| track->four_channel_audio)
475
err_fail("FLAGS allowed only once per track");
478
cmd = get_command(f);
479
while (cmd == DCP || cmd == FOURCH || cmd == PRE
483
track->copy = 1; break;
485
track->four_channel_audio = 1; break;
487
track->pre_emphasis = 1; break;
489
err_warn("serial copy management "
490
"system flag not supported "
493
err_fail("Should not get here");
495
cmd = get_command(f);
497
/* current non-FLAG command is already in cmd, so
498
avoid get_command() call below */
501
if (track->pregap != -1)
502
err_fail("PREGAP allowed only once per track");
503
if (get_string(f, timecode_buffer, TCBUFLEN - 1) < 1)
505
track->pregap = tc2fr(timecode_buffer);
506
if (track->pregap == -1)
507
err_fail("Timecode out of range");
508
track->pregap_data_from_file = 0;
511
if (track->postgap != -1)
512
err_fail("POSTGAP allowed only once per track");
513
if (get_string(f, timecode_buffer, TCBUFLEN - 1) < 1)
515
track->postgap = tc2fr(timecode_buffer);
516
if (track->postgap == -1)
517
err_fail("Timecode out of range");
520
if (get_string(f, timecode_buffer, 2) < 1)
522
n = atoi(timecode_buffer);
524
err_fail("Index out of range");
526
/* Index 0 is track pregap and Index 1 is start
527
of track. Index 2 to 99 are the true subindexes
528
and only allowed if the preceding one was there
532
if (track->start != -1)
533
err_fail("Indexes must be sequential");
534
if (track->pregap != -1)
535
err_fail("PREGAP allowed only once "
537
if (get_string(f, timecode_buffer,
540
/* This is only a temporary value until
542
track->pregap = tc2fr(timecode_buffer);
543
if (track->pregap == -1)
544
err_fail("Timecode out of range");
545
track->pregap_data_from_file = 1;
548
if (track->start != -1)
549
err_fail("Each index allowed only "
551
if (get_string(f, timecode_buffer,
553
err_fail("Missing timecode");
554
track->start = tc2fr(timecode_buffer);
555
if (track->start == -1)
556
err_fail("Timecode out of range");
557
/* Fix the pregap value */
558
if (track->pregap_data_from_file)
559
track->pregap = track->start
563
if (track->start == -1)
564
err_fail("Indexes must be sequential");
565
if (track->indexes[n - 2] != -1)
566
err_fail("Each index allowed only "
568
if (get_string(f, timecode_buffer,
570
err_fail("Missing timecode");
571
track->indexes[n - 2] = tc2fr(timecode_buffer);
572
if (track->indexes[n - 2] == -1)
573
err_fail("Timecode out of range");
575
default: /* the other 97 indexes */
576
/* check if previous index is there */
577
if (track->indexes[n - 3] == -1)
578
err_fail("Indexes must be sequential");
579
if (track->indexes[n - 2] != -1)
580
err_fail("Each index allowed only "
582
if (get_string(f, timecode_buffer,
584
err_fail("Missing timecode");
585
track->indexes[n - 2] = tc2fr(timecode_buffer);
586
if (track->indexes[n - 2] == -1)
587
err_fail("Timecode out of range");
592
if (get_string(f, file, FILENAMELEN) < 1)
595
switch (cmd = get_command(f)) {
597
err_warn("big endian binary file");
599
filetype = BINARY; break;
601
err_warn("AIFF and MP3 not supported by "
605
strncpy(file, wavefile, FILENAMELEN);
606
file[FILENAMELEN] = '\0';
608
filetype = WAVE; break;
610
err_fail("Unsupported file type");
614
err_fail("Command not allowed in track spec");
618
cmd = get_command(f);
621
check_cutting_binary(track);
626
/* Deduce the disc session type from the track modes */
627
static enum session_type
628
determine_session_type(struct trackspec *list)
630
struct trackspec *track = list;
631
/* set to true if track of corresponding type is found */
636
while (track != NULL) {
637
switch (track->mode) {
640
case MODE1: case MODE1_RAW:
642
case MODE2: case MODE2_RAW:
644
default: /* should never get here */
645
err_fail2("Dont know how this could happen, but here "
646
"is a track with an unknown mode :|");
652
* CD_ROM only mode1 with or without audio
653
* CD_ROM_XA only mode2 with or without audio
655
if (audio && !mode1 && !mode2)
657
else if (audio && mode1 && !mode2 || !audio && mode1 && !mode2)
659
else if (audio && !mode1 && mode2 || !audio && !mode1 && mode2)
665
/* Return true if cuesheet contains any CD-Text data */
667
contains_cdtext(struct cuesheet *cs)
669
struct trackspec *track = cs->tracklist;
671
if (cs->title[0] != '\0' || cs->performer[0] != '\0'
672
|| cs->songwriter[0] != '\0')
676
if (track->title[0] != '\0' || track->performer[0] != '\0'
677
|| track->songwriter[0] != '\0')
685
/* fprintf() with indentation. The argument indent is the number of spaces
686
to print per level. E.g. with indent=4 and level=3 there are 12 spaces
687
printed. Every eight spaces are replaced by a single tabulator. The
688
return value is the return value of fprintf(). */
690
ifprintf(FILE *f, int indent, int level, const char *format, ...)
693
int fprintf_return = 0;
694
int tabs = indent * level / 8;
695
int spaces = indent * level % 8;
698
for (i = 0; i < tabs; i++)
700
for (i = 0; i < spaces; i++)
703
va_start(ap, format);
704
fprintf_return = vfprintf(f, format, ap);
707
return fprintf_return;
710
/* Write a track to the file f. The arguments i and l are the indentation
711
amount and level (see ifprintf above). Do not write CD-Text data if
714
write_track(struct trackspec *tr, FILE *f, int i, int l, int cdtext)
716
char timecode_buffer[TCBUFLEN];
717
long start = 0, len = 0;
721
ifprintf(f, i, l++, "TRACK ");
723
case AUDIO: fprintf(f, "AUDIO\n"); break;
724
case MODE1: fprintf(f, "MODE1\n"); break;
725
case MODE1_RAW: fprintf(f, "MODE1_RAW\n"); break;
726
case MODE2: fprintf(f, "MODE2\n"); break;
727
case MODE2_RAW: fprintf(f, "MODE2_RAW\n"); break;
728
default: err_fail2("Unknown track mode"); /* cant get here */
733
ifprintf(f, i, l, "COPY\n");
734
if (tr->pre_emphasis)
735
ifprintf(f, i, l, "PRE_EMPHASIS\n");
736
if (tr->four_channel_audio)
737
ifprintf(f, i, l, "FOUR_CHANNEL_AUDIO\n");
738
if (tr->isrc[0] != '\0')
739
ifprintf(f, i, l, "ISRC \"%s\"\n", tr->isrc);
742
if (cdtext && (tr->title[0] != '\0' || tr->performer[0] != '\0'
743
|| tr->songwriter[0] != '\0')) {
744
ifprintf(f, i, l++, "CD_TEXT {\n");
745
ifprintf(f, i, l++, "LANGUAGE 0 {\n");
746
if (tr->title[0] != '\0')
747
ifprintf(f, i, l, "TITLE \"%s\"\n", tr->title);
748
if (tr->performer[0] != '\0')
749
ifprintf(f, i, l, "PERFORMER \"%s\"\n", tr->performer);
750
if (tr->songwriter[0] != '\0')
751
ifprintf(f, i, l, "SONGWRITER \"%s\"\n",
753
ifprintf(f, i, --l, "}\n"); /* LANGUAGE 0 { */
754
ifprintf(f, i, --l, "}\n"); /* CD_TEXT { */
757
/* Pregap with zero data */
758
if (tr->pregap != -1 && !tr->pregap_data_from_file) {
759
if (fr2tc(timecode_buffer, tr->pregap) == -1)
760
err_fail2("Pregap out of range");
761
ifprintf(f, i, l, "PREGAP %s\n", timecode_buffer);
764
/* Specify the file */
766
if (tr->mode == AUDIO) {
767
ifprintf(f, i, l, "AUDIOFILE \"%s\" ", tr->filename);
768
if (tr->start != -1) {
769
if (tr->pregap_data_from_file) {
770
start = tr->start - tr->pregap;
774
if (fr2tc(timecode_buffer, start) == -1)
775
err_fail2("Track start out of range");
776
fprintf(f, "%s", timecode_buffer);
778
ifprintf(f, i, l, "DATAFILE \"%s\"", tr->filename);
780
/* If next track has the same filename and specified a start
781
value use the difference between start of this and start of
782
the next track as the length of the current track */
784
&& strcmp(tr->filename, tr->next->filename) == 0
785
&& tr->next->start != -1) {
786
if (tr->next->pregap_data_from_file)
787
len = tr->next->start - tr->next->pregap
790
len = tr->next->start - start;
791
if (fr2tc(timecode_buffer, len) == -1)
792
err_fail2("Track length out of range");
793
fprintf(f, " %s\n", timecode_buffer);
797
/* Pregap with data from file */
798
if (tr->pregap_data_from_file) {
799
if (fr2tc(timecode_buffer, tr->pregap) == -1)
800
err_fail2("Pregap out of range");
801
ifprintf(f, i, l, "START %s\n", timecode_buffer);
805
if (tr->postgap != -1) {
806
if (fr2tc(timecode_buffer, tr->postgap) == -1)
807
err_fail2("Postgap out of range");
808
if (tr->mode == AUDIO)
809
ifprintf(f, i, l, "SILENCE %s\n", timecode_buffer);
811
ifprintf(f, i, l, "ZERO %s\n", timecode_buffer);
815
while (tr->indexes[j] != -1 && i < NUM_OF_INDEXES) {
816
if (fr2tc(timecode_buffer, tr->indexes[j++]) == -1)
817
err_fail2("Index out of range");
818
ifprintf(f, i, l, "INDEX %s\n", timecode_buffer);
823
/* Write the cuesheet cs to the file named in tocfile. If tocfile is NULL
824
write to stdout. Do not write CD-Text data if cdt is zero. */
826
write_toc(const char *tocfile, struct cuesheet *cs, int cdt)
829
int i = 4; /* number of chars for indentation */
830
int l = 0; /* current leven of indentation */
831
int cdtext = contains_cdtext(cs) && cdt;
832
struct trackspec *track = cs->tracklist;
835
if ((f = fopen(tocfile, "w")) == NULL) {
836
fprintf(stderr, "%s: Could not open file \"%s\" for "
837
"writing: %s\n", progname, tocfile, strerror(errno));
841
if ((cs->type = determine_session_type(cs->tracklist)) == INVALID)
842
err_fail2("Invalid combination of track modes");
844
ifprintf(f, i, l, "// Generated by cue2toc 0.2\n");
845
ifprintf(f, i, l, "// Report bugs to <dermatsch@gmx.de>\n");
847
if (cs->catalog[0] != '\0')
848
ifprintf(f, i, l, "CATALOG \"%s\"\n", cs->catalog);
851
case CD_DA: ifprintf(f, i, l, "CD_DA\n"); break;
852
case CD_ROM: ifprintf(f, i, l, "CD_ROM\n"); break;
853
case CD_ROM_XA: ifprintf(f, i, l, "CD_ROM_XA\n"); break;
854
default: err_fail2("Should never get here");
858
ifprintf(f, i, l++, "CD_TEXT {\n");
859
ifprintf(f, i, l++, "LANGUAGE_MAP {\n");
860
ifprintf(f, i, l, "0 : EN\n");
861
ifprintf(f, i, --l, "}\n");
862
ifprintf(f, i, l++, "LANGUAGE 0 {\n");
863
if (cs->title[0] != '\0')
864
ifprintf(f, i, l, "TITLE \"%s\"\n", cs->title);
865
if (cs->performer[0] != '\0')
866
ifprintf(f, i, l, "PERFORMER \"%s\"\n", cs->performer);
867
if (cs->songwriter[0] != '\0')
868
ifprintf(f, i, l, "SONGWRITER \"%s\"\n",
870
ifprintf(f, i, --l, "}\n");
871
ifprintf(f, i, --l, "}\n");
875
write_track(track, f, i, l, cdtext);