/* @(#)cue.c 1.57 13/12/21 Copyright 2001-2013 J. Schilling */ #include #ifndef lint static UConst char sccsid[] = "@(#)cue.c 1.57 13/12/21 Copyright 2001-2013 J. Schilling"; #endif /* * Cue sheet parser * * Copyright (c) 2001-2013 J. Schilling */ /* * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * See the file CDDL.Schily.txt in this distribution for details. * A copy of the CDDL is also available via the Internet at * http://www.opensource.org/licenses/cddl1.txt * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file CDDL.Schily.txt from this distribution. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "xio.h" #include "cdtext.h" #include "cdrecord.h" #include "auheader.h" #include "schily/libport.h" /*#define PARSE_DEBUG*/ typedef struct state { char *filename; /* Name of file to open */ void *xfp; /* Open file */ Llong trackoff; /* Current offset in open file */ Llong filesize; /* Size of current open file */ int filetype; /* File type (e.g. K_WAVE) */ int tracktype; /* Track type (e.g. TOC_DA) */ int sectype; /* Sector type (e.g. SECT_AUDIO)*/ int dbtype; /* Data block type (e.g. DB_RAW)*/ int secsize; /* Sector size from TRACK type */ int dataoff; /* Data offset from Track type */ int state; /* Current state of the parser */ int prevstate; /* Previous state of the parser */ int track; /* Relative Track index */ int trackno; /* Absolute Track number on disk*/ int index; /* Last INDEX xx number parsed */ long index0; /* Current INDEX 00 if found */ long index1; /* Current INDEX 01 if found */ long secoff; /* Last INDEX 01 value in file */ long pregapsize; /* Pregap size from PREGAP */ long postgapsize; /* Postgap size from POSTGAP */ int flags; /* Track flags (e.g. TI_COPY) */ int pflags; /* Parser flags */ } state_t; /* * Values for "state" and "prevstate". */ #define STATE_NONE 0 /* Initial state of parser */ #define STATE_POSTGAP 1 /* Past INDEX before FILE/TRACK */ #define STATE_FILE 2 /* FILE keyword found */ #define STATE_TRACK 3 /* TRACK keyword found */ #define STATE_FLAGS 4 /* FLAGS past TRACK before INDEX*/ #define STATE_INDEX0 5 /* INDEX 00 found */ #define STATE_INDEX1 6 /* INDEX 01 found */ #define STATE_MAX 6 /* # of entries in states[] */ /* * Flag bits used in "pflags". */ #define PF_CDRTOOLS_EXT 0x01 /* Cdrtools extensions allowed */ #define PF_INDEX0_PREV 0x02 /* INDEX 00 belongs to prev FILE*/ #define PF_FILE_FOUND 0x04 /* FILE command found for TRACK */ LOCAL char *states[] = { "NONE", "POSTGAP", "FILE", "TRACK", "FLAGS", "INDEX 00", "INDEX 01" }; typedef struct keyw { char *k_name; int k_type; } keyw_t; /* * Keywords (first word on line): * CATALOG - global CATALOG * CDTEXTFILE - global CDTEXTFILE * FILE - track local FILE * FLAGS - track local FLAGS ... * INDEX - track local INDEX <#> * ISRC - track local ISRC * PERFORMER - global/local PERFORMER * POSTGAP - track locak POSTGAP * PREGAP - track local PREGAP * REM - anywhere REM * SONGWRITER - global/local SONGWRITER * TITLE - global/local TITLE * TRACK - track local TRACK <#> * * Order of keywords: * CATALOG * CDTEXTFILE * PERFORMER | SONGWRITER | TITLE Doc says past FILE... * FILE Must be past CATALOG * ------- Repeat the following: mehrere FILE Commands? * TRACK * FLAGS | ISRC | PERFORMER | PREGAP | SONGWRITER | TITLE * INDEX * POSTGAP * * Additional keyword rules: * CATALOG once * CDTEXTFILE once * FILE before "other command" * FLAGS one per TRACK, after TRACK before INDEX * INDEX >= 0, <= 99, first 0 or 1, sequential, * first index of a FILE at 00:00:00 * ISRC after TRACK before INDEX * PERFORMER * POSTGAP one per TRACK, after all INDEX for current TRACK * PREGAP one per TRACK, after TRACK before INDEX * REM * SONGWRITER * TITLE * TRACK >= 1, <= 99, sequential, >= 1 TRACK per FILE */ #define K_G 0x10000 /* Global */ #define K_T 0x20000 /* Track local */ #define K_A (K_T | K_G) /* Global & Track local */ #define K_ARRANGER (0 | K_A) /* CD-Text Arranger */ #define K_MCN (1 | K_G) /* Media catalog number */ #define K_TEXTFILE (2 | K_G) /* CD-Text binary file */ #define K_COMPOSER (3 | K_A) /* CD-Text Composer */ #define K_FILE (4 | K_T) /* Input data file */ #define K_FLAGS (5 | K_T) /* Flags for ctrl nibble */ #define K_INDEX (6 | K_T) /* Index marker for track */ #define K_ISRC (7 | K_T) /* ISRC string for track */ #define K_MESSAGE (8 | K_A) /* CD-Text Message */ #define K_PERFORMER (9 | K_A) /* CD-Text Performer */ #define K_POSTGAP (10 | K_T) /* Post gap for track (autogen) */ #define K_PREGAP (11 | K_T) /* Pre gap for track (autogen) */ #define K_REM (12 | K_A) /* Remark (Comment) */ #define K_SONGWRITER (13| K_A) /* CD-Text Songwriter */ #define K_TITLE (14| K_A) /* CD-Text Title */ #define K_TRACK (15| K_T) /* Track marker */ LOCAL keyw_t keywords[] = { { "ARRANGER", K_ARRANGER }, /* Not supported by CDR-WIN */ { "CATALOG", K_MCN }, { "CDTEXTFILE", K_TEXTFILE }, { "COMPOSER", K_COMPOSER }, /* Not supported by CDR-WIN */ { "FILE", K_FILE }, { "FLAGS", K_FLAGS }, { "INDEX", K_INDEX }, { "ISRC", K_ISRC }, { "MESSAGE", K_MESSAGE }, /* Not supported by CDR-WIN */ { "PERFORMER", K_PERFORMER }, { "POSTGAP", K_POSTGAP }, { "PREGAP", K_PREGAP }, { "REM", K_REM }, { "SONGWRITER", K_SONGWRITER }, { "TITLE", K_TITLE }, { "TRACK", K_TRACK }, { NULL, 0 }, }; /* * Filetypes - argument to FILE Keyword (one only): * * BINARY - Intel binary file (least significant byte first) * MOTOTOLA - Motorola binary file (most significant byte first) * AIFF - Audio AIFF file * WAVE - Audio WAVE file * MP3 - Audio MP3 file * AU - Sun Audio file * OGG - Audio OGG file */ #define K_BINARY 100 #define K_MOTOROLA 101 #define K_AIFF 102 #define K_WAVE 103 #define K_MP3 104 #define K_FT_CDRWIN_MAX 104 #define K_AU 105 #define K_OGG 106 LOCAL keyw_t filetypes[] = { { "BINARY", K_BINARY }, { "MOTOROLA", K_MOTOROLA }, { "AIFF", K_AIFF }, { "WAVE", K_WAVE }, { "MP3", K_MP3 }, { "AU", K_AU }, /* Not supported by CDR-WIN */ { "OGG", K_OGG }, /* Not supported by CDR-WIN */ { NULL, 0 }, }; /* * Flags - argument to FLAGS Keyword (more than one allowed): * DCP - Digital copy permitted * 4CH - Four channel audio * PRE - Pre-emphasis enabled (audio tracks only) * SCMS - Serial copy management system (not supported by all recorders) */ #define K_DCP 1000 #define K_4CH 1001 #define K_PRE 1002 #define K_SCMS 1003 #define K_FL_CDRWIN_MAX 1003 LOCAL keyw_t flags[] = { { "DCP", K_DCP }, { "4CH", K_4CH }, { "PRE", K_PRE }, { "SCMS", K_SCMS }, { NULL, 0 }, }; /* * Datatypes - argument to TRACK Keyword (one only): * AUDIO - Audio/Music (2352) * CDG - Karaoke CD+G (2448) * MODE1/2048 - CDROM Mode1 Data (cooked) * MODE1/2352 - CDROM Mode1 Data (raw) * MODE2/2336 - CDROM-XA Mode2 Data * MODE2/2352 - CDROM-XA Mode2 Data * CDI/2336 - CDI Mode2 Data * CDI/2352 - CDI Mode2 Data */ #define K_AUDIO 10000 #define K_CDG 10001 #define K_MODE1 10002 #define K_MODE2 10003 #define K_CDI 10004 #define K_DT_CDRWIN_MAX 10004 LOCAL keyw_t dtypes[] = { { "AUDIO", K_AUDIO }, { "CDG", K_CDG }, { "MODE1", K_MODE1 }, { "MODE2", K_MODE2 }, { "CDI", K_CDI }, { NULL, 0 }, }; #ifdef CUE_MAIN EXPORT int main __PR((int ac, char **av)); #endif EXPORT int parsecue __PR((char *cuefname, track_t trackp[])); EXPORT void fparsecue __PR((FILE *f, track_t trackp[])); LOCAL void parse_arranger __PR((track_t trackp[], state_t *sp)); LOCAL void parse_mcn __PR((track_t trackp[], state_t *sp)); LOCAL void parse_textfile __PR((track_t trackp[], state_t *sp)); LOCAL void parse_composer __PR((track_t trackp[], state_t *sp)); LOCAL void parse_file __PR((track_t trackp[], state_t *sp)); LOCAL void parse_flags __PR((track_t trackp[], state_t *sp)); LOCAL void parse_index __PR((track_t trackp[], state_t *sp)); LOCAL void parse_isrc __PR((track_t trackp[], state_t *sp)); LOCAL void parse_message __PR((track_t trackp[], state_t *sp)); LOCAL void parse_performer __PR((track_t trackp[], state_t *sp)); LOCAL void parse_postgap __PR((track_t trackp[], state_t *sp)); LOCAL void parse_pregap __PR((track_t trackp[], state_t *sp)); LOCAL void parse_rem __PR((track_t trackp[], state_t *sp)); LOCAL void parse_songwriter __PR((track_t trackp[], state_t *sp)); LOCAL void parse_title __PR((track_t trackp[], state_t *sp)); LOCAL void parse_track __PR((track_t trackp[], state_t *sp)); LOCAL void parse_offset __PR((long *lp)); LOCAL void newtrack __PR((track_t trackp[], state_t *sp)); LOCAL keyw_t *lookup __PR((char *word, keyw_t table[])); LOCAL char *state_name __PR((int st)); #ifdef DEBUG LOCAL void wdebug __PR((void)); #endif LOCAL FILE *cueopen __PR((char *name)); LOCAL char *cuename __PR((void)); LOCAL char *nextline __PR((FILE *f)); #ifdef __needed__ LOCAL void ungetline __PR((void)); #endif LOCAL char *skipwhite __PR((const char *s)); LOCAL char *peekword __PR((void)); LOCAL char *lineend __PR((void)); LOCAL char *markword __PR((char *delim)); LOCAL char getworddelim __PR((void)); LOCAL char *getnextitem __PR((char *delim)); LOCAL char *neednextitem __PR((char *delim, char *type)); #ifdef __needed__ LOCAL char *nextword __PR((void)); #endif LOCAL char *needword __PR((char *type)); LOCAL char *curword __PR((void)); LOCAL char *nextitem __PR((void)); LOCAL char *needitem __PR((char *type)); LOCAL void checkextra __PR((void)); LOCAL void statewarn __PR((state_t *sp, const char *fmt, ...)); #ifdef __needed__ LOCAL void cuewarn __PR((const char *fmt, ...)); #endif LOCAL void cueabort __PR((const char *fmt, ...)); LOCAL void extabort __PR((const char *fmt, ...)); #ifdef CUE_MAIN int debug; int xdebug = 1; int write_secs __PR((void)); int write_secs() { return (-1); } EXPORT int main(ac, av) int ac; char *av[]; { int i; track_t track[MAX_TRACK+2]; /* Max tracks + track 0 + track AA */ save_args(ac, av); fillbytes(track, sizeof (track), '\0'); for (i = 0; i < MAX_TRACK+2; i++) track[i].track = track[i].trackno = i; track[0].tracktype = TOC_MASK; parsecue(av[1], track); return (0); } #else extern int xdebug; #endif EXPORT int parsecue(cuefname, trackp) char *cuefname; track_t trackp[]; { FILE *f = cueopen(cuefname); fparsecue(f, trackp); return (0); } EXPORT void fparsecue(f, trackp) FILE *f; track_t trackp[]; { char *word; struct keyw *kp; BOOL isglobal = TRUE; state_t state; state.filename = NULL; state.xfp = NULL; state.trackoff = 0; state.filesize = 0; state.filetype = 0; state.tracktype = 0; state.sectype = 0; state.dbtype = 0; state.secsize = 0; state.dataoff = 0; state.state = STATE_NONE; state.prevstate = STATE_NONE; state.track = 0; state.trackno = 0; state.index = -1; state.index0 = -1; state.index1 = -1; state.secoff = 0; state.pregapsize = -1; state.postgapsize = -1; state.flags = 0; state.pflags = 0; if (xdebug > 1) printf(_("---> Entering CUE Parser...\n")); do { if (nextline(f) == NULL) { /* * EOF on CUE File * Do post processing here */ if (state.state < STATE_INDEX1 && state.state != STATE_POSTGAP) { statewarn(&state, _("INDEX 01 missing")); cueabort(_("Incomplete CUE file")); } if (state.xfp) { xclose(state.xfp); state.xfp = NULL; } if (xdebug > 1) { printf(_("---> CUE Parser got EOF, found %2.2d tracks.\n"), state.track); } return; } word = nextitem(); if (*word == '\0') /* empty line */ continue; if (xdebug > 1) printf(_("\nKEY: '%s' %s\n"), word, peekword()); kp = lookup(word, keywords); if (kp == NULL) cueabort(_("Unknown CUE keyword '%s'"), word); if ((kp->k_type & K_G) == 0) { if (isglobal) isglobal = FALSE; } if ((kp->k_type & K_T) == 0) { if (!isglobal) { statewarn(&state, _("%s keyword must be before first TRACK"), word); cueabort(_("Badly placed CUE keyword '%s'"), word); } } #ifdef DEBUG printf("%s-", isglobal ? "G" : "T"); wdebug(); #endif switch (kp->k_type) { case K_ARRANGER: parse_arranger(trackp, &state); break; case K_MCN: parse_mcn(trackp, &state); break; case K_TEXTFILE: parse_textfile(trackp, &state); break; case K_COMPOSER: parse_composer(trackp, &state); break; case K_FILE: parse_file(trackp, &state); break; case K_FLAGS: parse_flags(trackp, &state); break; case K_INDEX: parse_index(trackp, &state); break; case K_ISRC: parse_isrc(trackp, &state); break; case K_MESSAGE: parse_message(trackp, &state); break; case K_PERFORMER: parse_performer(trackp, &state); break; case K_POSTGAP: parse_postgap(trackp, &state); break; case K_PREGAP: parse_pregap(trackp, &state); break; case K_REM: parse_rem(trackp, &state); break; case K_SONGWRITER: parse_songwriter(trackp, &state); break; case K_TITLE: parse_title(trackp, &state); break; case K_TRACK: parse_track(trackp, &state); break; default: cueabort(_("Panic: unknown CUE command '%s'"), word); } } while (1); } LOCAL void parse_arranger(trackp, sp) track_t trackp[]; state_t *sp; { char *word; textptr_t *txp; if ((sp->pflags & PF_CDRTOOLS_EXT) == 0) extabort("ARRANGER"); if (sp->track > 0 && sp->state > STATE_INDEX0) { statewarn(sp, _("ARRANGER keyword cannot be after INDEX keyword")); cueabort(_("Badly placed ARRANGER keyword")); } word = needitem("arranger"); txp = gettextptr(sp->track, trackp); txp->tc_arranger = strdup(word); checkextra(); } LOCAL void parse_mcn(trackp, sp) track_t trackp[]; state_t *sp; { char *word; textptr_t *txp; if (sp->track != 0) cueabort(_("CATALOG keyword must be before first TRACK")); word = needitem("MCN"); setmcn(word, &trackp[0]); txp = gettextptr(0, trackp); /* MCN is isrc for trk 0 */ txp->tc_isrc = strdup(word); checkextra(); } LOCAL void parse_textfile(trackp, sp) track_t trackp[]; state_t *sp; { char *word; if (sp->track != 0) cueabort(_("CDTEXTFILE keyword must be before first TRACK")); word = needitem("cdtextfile"); if (trackp[MAX_TRACK+1].flags & TI_TEXT) { if (!checktextfile(word)) { comerrno(EX_BAD, _("Cannot use '%s' as CD-Text file.\n"), word); } trackp[0].flags |= TI_TEXT; } else { errmsgno(EX_BAD, _("Ignoring CDTEXTFILE '%s'.\n"), word); errmsgno(EX_BAD, _("If you like to write CD-Text, call cdrecord -text.\n")); } checkextra(); } LOCAL void parse_composer(trackp, sp) track_t trackp[]; state_t *sp; { char *word; textptr_t *txp; if ((sp->pflags & PF_CDRTOOLS_EXT) == 0) extabort("COMPOSER"); if (sp->track > 0 && sp->state > STATE_INDEX0) { statewarn(sp, _("COMPOSER keyword cannot be after INDEX keyword")); cueabort(_("Badly placed COMPOSER keyword")); } word = needitem("composer"); txp = gettextptr(sp->track, trackp); txp->tc_composer = strdup(word); checkextra(); } LOCAL void parse_file(trackp, sp) track_t trackp[]; state_t *sp; { char cname[1024]; char newname[1024]; struct keyw *kp; char *word; char *filetype; struct stat st; #ifdef hint Llong lsize; #endif if ((sp->state <= STATE_TRACK && sp->state > STATE_POSTGAP) || (sp->state >= STATE_TRACK && sp->state < STATE_INDEX1)) { if (sp->state >= STATE_INDEX0 && sp->state < STATE_INDEX1) { if ((sp->pflags & PF_CDRTOOLS_EXT) == 0) extabort(_("FILE keyword after INDEX 00 and before INDEX 01")); sp->prevstate = sp->state; goto file_ok; } if (sp->state <= STATE_TRACK && sp->state > STATE_POSTGAP) statewarn(sp, _("FILE keyword only allowed once before TRACK keyword")); if (sp->state >= STATE_TRACK && sp->state < STATE_INDEX1) statewarn(sp, _("FILE keyword not allowed after TRACK and before INDEX 01")); cueabort(_("Badly placed FILE keyword")); } file_ok: if (sp->state < STATE_INDEX1 && sp->pflags & PF_FILE_FOUND) cueabort(_("Only one FILE keyword allowed per TRACK")); sp->pflags |= PF_FILE_FOUND; sp->state = STATE_FILE; word = needitem("filename"); if (sp->xfp) { xclose(sp->xfp); sp->xfp = NULL; } sp->xfp = xopen(word, O_RDONLY|O_BINARY, 0, X_NOREWIND); if (sp->xfp == NULL && geterrno() == ENOENT) { char *p; if (strchr(word, '/') == 0 && strchr(cuename(), '/') != 0) { js_snprintf(cname, sizeof (cname), "%s", cuename()); p = strrchr(cname, '/'); if (p) *p = '\0'; js_snprintf(newname, sizeof (newname), "%s/%s", cname, word); word = newname; sp->xfp = xopen(word, O_RDONLY|O_BINARY, 0, X_NOREWIND); } } if (sp->xfp == NULL) { #ifdef PARSE_DEBUG errmsg(_("Cannot open FILE '%s'.\n"), word); #else comerr(_("Cannot open FILE '%s'.\n"), word); #endif } sp->filename = strdup(word); sp->trackoff = 0; sp->secoff = 0; sp->filesize = 0; sp->flags &= ~TI_SWAB; /* Reset what we might set for FILE */ filetype = needitem("filetype"); kp = lookup(filetype, filetypes); if (kp == NULL) cueabort(_("Unknown filetype '%s'"), filetype); if ((sp->pflags & PF_CDRTOOLS_EXT) == 0 && kp->k_type > K_FT_CDRWIN_MAX) extabort(_("Filetype '%s'"), kp->k_name); switch (kp->k_type) { case K_BINARY: case K_MOTOROLA: if (fstat(xfileno(sp->xfp), &st) >= 0 && S_ISREG(st.st_mode)) { sp->filesize = st.st_size; if (kp->k_type == K_BINARY) sp->flags |= TI_SWAB; } else { cueabort(_("Unknown file size for FILE '%s'"), sp->filename); } break; case K_AIFF: cueabort(_("Unsupported filetype '%s'"), kp->k_name); break; case K_AU: sp->filesize = ausize(xfileno(sp->xfp)); break; case K_WAVE: #ifdef PARSE_DEBUG sp->filesize = 1000000000; #else sp->filesize = wavsize(xfileno(sp->xfp)); #endif sp->flags |= TI_SWAB; break; case K_MP3: case K_OGG: cueabort(_("Unsupported filetype '%s'"), kp->k_name); break; default: cueabort(_("Panic: unknown filetype '%s'"), filetype); } if (sp->filesize == AU_BAD_CODING) { cueabort(_("Inappropriate audio coding in '%s'"), sp->filename); } if (xdebug > 0) printf(_("Track[%2.2d] %2.2d File '%s' Filesize %lld\n"), sp->track, sp->trackno, sp->filename, sp->filesize); sp->filetype = kp->k_type; checkextra(); #ifdef hint trackp->itracksize = lsize; if (trackp->itracksize != lsize) comerrno(EX_BAD, _("This OS cannot handle large audio images.\n")); #endif } LOCAL void parse_flags(trackp, sp) track_t trackp[]; state_t *sp; { struct keyw *kp; char *word; if ((sp->state < STATE_TRACK) || (sp->state >= STATE_INDEX0)) { statewarn(sp, _("FLAGS keyword must be after TRACK and before INDEX keyword")); cueabort(_("Badly placed FLAGS keyword")); } sp->state = STATE_FLAGS; do { word = needitem("flag"); kp = lookup(word, flags); if (kp == NULL) cueabort(_("Unknown flag '%s'"), word); switch (kp->k_type) { case K_DCP: sp->flags |= TI_COPY; break; case K_4CH: sp->flags |= TI_QUADRO; break; case K_PRE: sp->flags |= TI_PREEMP; break; case K_SCMS: sp->flags |= TI_SCMS; break; default: cueabort(_("Panic: unknown FLAG '%s'"), word); } } while (peekword() < lineend()); if (xdebug > 0) printf(_("Track[%2.2d] %2.2d flags 0x%08X\n"), sp->track, sp->trackno, sp->flags); } LOCAL void parse_index(trackp, sp) track_t trackp[]; state_t *sp; { char *word; long l; int track = sp->track; if (sp->state < STATE_TRACK) { if (sp->state == STATE_FILE && sp->prevstate >= STATE_TRACK && sp->prevstate <= STATE_INDEX1) { if ((sp->pflags & PF_CDRTOOLS_EXT) == 0) extabort(_("INDEX keyword after FILE keyword")); goto index_ok; } statewarn(sp, _("INDEX keyword must be after TRACK keyword")); cueabort(_("Badly placed INDEX keyword")); } index_ok: word = needitem("index"); if (*astolb(word, &l, 10) != '\0') cueabort(_("Not a number '%s'"), word); if (l < 0 || l > 99) cueabort(_("Illegal index '%s'"), word); if ((sp->index < l) && (((sp->index + 1) == l) || l == 1)) sp->index = l; else cueabort(_("Badly placed INDEX %2.2ld number"), l); if (sp->state == STATE_FILE) { if (track == 1 || l > 1) cueabort(_("INDEX %2.2d not allowed after FILE"), l); if (l == 1) sp->pflags |= PF_INDEX0_PREV; } if (l > 0) sp->state = STATE_INDEX1; else sp->state = STATE_INDEX0; sp->prevstate = sp->state; parse_offset(&l); if (xdebug > 1) printf(_("Track[%2.2d] %2.2d Index %2.2d %ld\n"), sp->track, sp->trackno, sp->index, l); if (track == 1 || !streql(sp->filename, trackp[track-1].filename)) { /* * Check for offset 0 when a new file begins. */ if (sp->index == 0 && l > 0) cueabort(_("Bad INDEX 00 offset in CUE file (must be 00:00:00 for new FILE)")); if (sp->index == 1 && sp->index0 < 0 && l > 0) cueabort(_("Bad INDEX 01 offset in CUE file (must be 00:00:00 for new FILE)")); } if (sp->index == 0) { sp->index0 = l; } else if (sp->index == 1) { sp->index1 = l; trackp[track].nindex = 1; newtrack(trackp, sp); if (xdebug > 1) { printf(_("Track[%2.2d] %2.2d pregapsize %ld\n"), sp->track, sp->trackno, trackp[track].pregapsize); } } else if (sp->index == 2) { trackp[track].tindex = malloc(100*sizeof (long)); trackp[track].tindex[1] = 0; trackp[track].tindex[2] = l - sp->index1; trackp[track].nindex = 2; } else if (sp->index > 2) { trackp[track].tindex[sp->index] = l - sp->index1; trackp[track].nindex = sp->index; } checkextra(); } LOCAL void parse_isrc(trackp, sp) track_t trackp[]; state_t *sp; { char *word; textptr_t *txp; int track = sp->track; if (track == 0) cueabort(_("ISRC keyword must be past first TRACK")); if ((sp->state < STATE_TRACK) || (sp->state >= STATE_INDEX0)) { statewarn(sp, _("ISRC keyword must be after TRACK and before INDEX keyword")); cueabort(_("Badly placed ISRC keyword")); } sp->state = STATE_FLAGS; word = needitem("ISRC"); if ((sp->pflags & PF_CDRTOOLS_EXT) == 0 && strchr(word, '-')) { extabort(_("'-' in ISRC arg")); } setisrc(word, &trackp[track]); txp = gettextptr(track, trackp); txp->tc_isrc = strdup(word); checkextra(); } LOCAL void parse_message(trackp, sp) track_t trackp[]; state_t *sp; { char *word; textptr_t *txp; if ((sp->pflags & PF_CDRTOOLS_EXT) == 0) extabort("MESSAGE"); if (sp->track > 0 && sp->state > STATE_INDEX0) { statewarn(sp, _("MESSAGE keyword cannot be after INDEX keyword")); cueabort(_("Badly placed MESSAGE keyword")); } word = needitem("message"); txp = gettextptr(sp->track, trackp); txp->tc_message = strdup(word); checkextra(); } LOCAL void parse_performer(trackp, sp) track_t trackp[]; state_t *sp; { char *word; textptr_t *txp; if (sp->track > 0 && sp->state > STATE_INDEX0) { statewarn(sp, _("PERFORMER keyword cannot be after INDEX keyword")); cueabort(_("Badly placed PERFORMER keyword")); } word = needitem("performer"); txp = gettextptr(sp->track, trackp); txp->tc_performer = strdup(word); checkextra(); } LOCAL void parse_postgap(trackp, sp) track_t trackp[]; state_t *sp; { long l; if (sp->state < STATE_INDEX1) { statewarn(sp, _("POSTGAP keyword must be after INDEX 01")); cueabort(_("Badly placed POSTGAP keyword")); } sp->state = STATE_POSTGAP; parse_offset(&l); sp->postgapsize = l; trackp[sp->track].padsecs = l; /* * Add to size of track. * In non-CUE mode, this is done in opentracks(). */ if (l > 0) trackp[sp->track].tracksecs += l; checkextra(); } LOCAL void parse_pregap(trackp, sp) track_t trackp[]; state_t *sp; { long l; if ((sp->state < STATE_TRACK) || (sp->state >= STATE_INDEX0)) { statewarn(sp, _("PREGAP keyword must be after TRACK and before INDEX keyword")); cueabort(_("Badly placed PREGAP keyword")); } sp->state = STATE_FLAGS; parse_offset(&l); sp->pregapsize = l; checkextra(); } LOCAL void parse_rem(trackp, sp) track_t trackp[]; state_t *sp; { char *oword = curword(); char *word; word = nextitem(); if ((oword == word) || (*word == '\0')) return; if ((sp->pflags & PF_CDRTOOLS_EXT) == 0 && streql(word, "CDRTOOLS")) { sp->pflags |= PF_CDRTOOLS_EXT; errmsgno(EX_BAD, _("Warning: Enabling cdrecord specific CUE extensions.\n")); } if ((sp->pflags & PF_CDRTOOLS_EXT) == 0 && streql(word, "COMMENT")) { oword = word; word = nextitem(); if ((oword == word) || (*word == '\0')) return; if (strncmp(word, "ExactAudioCopy ", 15) == 0) { sp->pflags |= PF_CDRTOOLS_EXT; errmsgno(EX_BAD, _("Warning: Found ExactAudioCopy, enabling CUE extensions.\n")); } } } LOCAL void parse_songwriter(trackp, sp) track_t trackp[]; state_t *sp; { char *word; textptr_t *txp; if (sp->track > 0 && sp->state > STATE_INDEX0) { statewarn(sp, _("SONGWRITER keyword cannot be after INDEX keyword")); cueabort(_("Badly placed SONGWRITER keyword")); } word = needitem("songwriter"); txp = gettextptr(sp->track, trackp); txp->tc_songwriter = strdup(word); checkextra(); } LOCAL void parse_title(trackp, sp) track_t trackp[]; state_t *sp; { char *word; textptr_t *txp; if (sp->track > 0 && sp->state > STATE_INDEX0) { statewarn(sp, _("TITLE keyword cannot be after INDEX keyword")); cueabort(_("Badly placed TITLE keyword")); } word = needitem("title"); txp = gettextptr(sp->track, trackp); txp->tc_title = strdup(word); checkextra(); } LOCAL void parse_track(trackp, sp) track_t trackp[]; state_t *sp; { struct keyw *kp; char *word; long l; long secsize = -1; if ((sp->state >= STATE_TRACK) && (sp->state < STATE_INDEX1)) { statewarn(sp, _("TRACK keyword must be after INDEX 01")); cueabort(_("Badly placed TRACK keyword")); } sp->pflags &= ~(PF_INDEX0_PREV|PF_FILE_FOUND); if (sp->state == STATE_FILE) sp->pflags |= PF_FILE_FOUND; sp->state = STATE_TRACK; sp->prevstate = STATE_TRACK; sp->track++; sp->index0 = -1; sp->index = -1; sp->pregapsize = -1; sp->postgapsize = -1; word = needitem("track number"); if (*astolb(word, &l, 10) != '\0') cueabort(_("Not a number '%s'"), word); if (l <= 0 || l > 99) cueabort(_("Illegal TRACK number '%s'"), word); if ((sp->trackno < l) && (((sp->trackno + 1) == l) || sp->trackno == 0)) sp->trackno = l; else cueabort(_("Badly placed TRACK %ld number"), l); word = needword("data type"); kp = lookup(word, dtypes); if (kp == NULL) cueabort(_("Unknown data type '%s'"), word); if (getworddelim() == '/') { word = needitem("sector size"); if (*astol(++word, &secsize) != '\0') cueabort(_("Not a number '%s'"), word); } /* * Reset all flags that may be set in TRACK & FLAGS lines */ sp->flags &= ~(TI_AUDIO|TI_COPY|TI_QUADRO|TI_PREEMP|TI_SCMS); if (kp->k_type == K_AUDIO) sp->flags |= TI_AUDIO; switch (kp->k_type) { case K_CDG: if (secsize < 0) secsize = 2448; case K_AUDIO: if (secsize < 0) secsize = 2352; sp->tracktype = TOC_DA; sp->sectype = SECT_AUDIO; sp->dbtype = DB_RAW; sp->secsize = secsize; sp->dataoff = 0; if (secsize != 2352) cueabort(_("Unsupported sector size %ld for audio"), secsize); break; case K_MODE1: if (secsize < 0) secsize = 2048; sp->tracktype = TOC_ROM; sp->sectype = SECT_ROM; sp->dbtype = DB_ROM_MODE1; sp->secsize = secsize; sp->dataoff = 16; /* * XXX Sector Size == 2352 ??? * XXX It seems that there exist bin/cue pairs with this value */ if (secsize != 2048) cueabort(_("Unsupported sector size %ld for data"), secsize); break; case K_MODE2: case K_CDI: sp->tracktype = TOC_ROM; sp->sectype = SECT_MODE_2; sp->dbtype = DB_ROM_MODE2; sp->secsize = secsize; sp->dataoff = 16; if (secsize == 2352) { sp->tracktype = TOC_XA2; sp->sectype = SECT_MODE_2_MIX; sp->sectype |= ST_MODE_RAW; sp->dbtype = DB_RAW; sp->dataoff = 0; } else if (secsize != 2336) cueabort(_("Unsupported sector size %ld for mode2"), secsize); if (kp->k_type == K_CDI) sp->tracktype = TOC_CDI; break; default: cueabort(_("Panic: unknown datatype '%s'"), word); } if (sp->flags & TI_PREEMP) sp->sectype |= ST_PREEMPMASK; sp->secsize = secsize; if (xdebug > 1) { printf(_("Track[%2.2d] %2.2d Tracktype %s/%d\n"), sp->track, sp->trackno, kp->k_name, sp->secsize); } checkextra(); } LOCAL void parse_offset(lp) long *lp; { char *word; char *p; long m = -1; long s = -1; long f = -1; word = needitem("time offset/length"); if (strchr(word, ':') == NULL) { if (*astol(word, lp) != '\0') cueabort(_("Not a number '%s'"), word); return; } if (*(p = astolb(word, &m, 10)) != ':') cueabort(_("Not a number '%s'"), word); if (m < 0 || m >= 160) cueabort(_("Illegal minute value in '%s'"), word); p++; if (*(p = astolb(p, &s, 10)) != ':') cueabort(_("Not a number '%s'"), p); if (s < 0 || s >= 60) cueabort(_("Illegal second value in '%s'"), word); p++; if (*(p = astolb(p, &f, 10)) != '\0') cueabort(_("Not a number '%s'"), p); if (f < 0 || f >= 75) cueabort(_("Illegal frame value in '%s'"), word); m = m * 60 + s; m = m * 75 + f; *lp = m; } /*--------------------------------------------------------------------------*/ LOCAL void newtrack(trackp, sp) track_t trackp[]; state_t *sp; { register int i; register int track = sp->track; Llong tracksize; if (xdebug > 1) printf(_("-->Newtrack %2.2d Trackno %2.2d\n"), track, sp->trackno); if (track > 1 && streql(sp->filename, trackp[track-1].filename)) { tracksize = (sp->index1 - sp->secoff) * trackp[track-1].isecsize; if (xdebug > 1) printf(" trackoff %lld filesize %lld index1 %ld size %ld/%lld secsize/isecsize %d/%d\n", sp->trackoff, sp->filesize, sp->index1, sp->index1 - sp->secoff, tracksize, trackp[track-1].secsize, trackp[track-1].isecsize); trackp[track-1].itracksize = tracksize; trackp[track-1].tracksize = tracksize; if (trackp[track-1].secsize != trackp[track-1].isecsize) { /* * In RAW mode, we need to recompute the track size. */ trackp[track-1].tracksize = (trackp[track-1].itracksize / trackp[track-1].isecsize) * trackp[track-1].secsize + trackp[track-1].itracksize % trackp[track-1].isecsize; } trackp[track-1].tracksecs = sp->index1 - sp->secoff; /* * Add to size of track. * In non-CUE mode, this is done in opentracks(). */ if (trackp[track-1].padsecs > 0) trackp[track-1].tracksecs += trackp[track-1].padsecs; sp->trackoff += tracksize; sp->secoff = sp->index1; } /* * Make 'tracks' immediately usable in track structure. */ for (i = 0; i < MAX_TRACK+2; i++) trackp[i].tracks = track; trackp[track].filename = sp->filename; trackp[track].xfp = xopen(sp->filename, O_RDONLY|O_BINARY, 0, X_NOREWIND); trackp[track].trackstart = 0L; /* * SEtzen wenn tracksecs bekannt sind * d.h. mit Index0 oder Index 1 vom nächsten track * * trackp[track].itracksize = tracksize; * trackp[track].tracksize = tracksize; * trackp[track].tracksecs = -1L; */ tracksize = sp->filesize - sp->trackoff; trackp[track].itracksize = tracksize; trackp[track].tracksize = tracksize; trackp[track].tracksecs = (tracksize + sp->secsize - 1) / sp->secsize; if (xdebug > 1) printf(_(" Remaining Filesize %lld (%lld secs)\n"), (sp->filesize-sp->trackoff), (sp->filesize-sp->trackoff +sp->secsize - 1) / sp->secsize); if (sp->pregapsize >= 0) { /* trackp[track].flags &= ~TI_PREGAP;*/ sp->flags &= ~TI_PREGAP; trackp[track].pregapsize = sp->pregapsize; } else { /* trackp[track].flags |= TI_PREGAP;*/ if (track > 1) sp->flags |= TI_PREGAP; if (track == 1) trackp[track].pregapsize = sp->index1 + 150; else if (sp->index0 < 0) trackp[track].pregapsize = 0; else if (sp->pflags & PF_INDEX0_PREV) /* INDEX0 in prev FILE */ trackp[track].pregapsize = trackp[track-1].tracksecs - sp->index0; else trackp[track].pregapsize = sp->index1 - sp->index0; } trackp[track].isecsize = sp->secsize; trackp[track].secsize = sp->secsize; trackp[track].flags = sp->flags | (trackp[0].flags & ~(TI_HIDDEN|TI_SWAB|TI_AUDIO|TI_COPY|TI_QUADRO|TI_PREEMP|TI_SCMS)); if (trackp[0].flags & TI_RAW) { if (is_raw16(&trackp[track])) trackp[track].secsize = RAW16_SEC_SIZE; else trackp[track].secsize = RAW96_SEC_SIZE; #ifndef HAVE_LIB_EDC_ECC if ((sp->sectype & ST_MODE_MASK) != ST_MODE_AUDIO) { errmsgno(EX_BAD, _("EDC/ECC library not compiled in.\n")); comerrno(EX_BAD, _("Data sectors are not supported in RAW mode.\n")); } #endif } /* * In RAW mode, we need to recompute the track size. */ trackp[track].tracksize = (trackp[track].itracksize / trackp[track].isecsize) * trackp[track].secsize + trackp[track].itracksize % trackp[track].isecsize; trackp[track].secspt = 0; /* transfer size is set up in set_trsizes() */ /* trackp[track].pktsize = pktsize; */ trackp[track].pktsize = 0; trackp[track].trackno = sp->trackno; trackp[track].sectype = sp->sectype; trackp[track].dataoff = sp->dataoff; trackp[track].tracktype = sp->tracktype; trackp[track].dbtype = sp->dbtype; if (track == 1) { track_t *tp0 = &trackp[0]; track_t *tp1 = &trackp[1]; tp0->tracktype &= ~TOC_MASK; tp0->tracktype |= sp->tracktype; /* * setleadinout() also sets: sectype dbtype dataoff */ tp0->sectype = tp1->sectype; tp0->dbtype = tp1->dbtype; tp0->dataoff = tp1->dataoff; tp0->isecsize = tp1->isecsize; tp0->secsize = tp1->secsize; if (sp->index0 == 0 && sp->index1 > 0) { tp0->filename = tp1->filename; tp0->xfp = xopen(sp->filename, O_RDONLY|O_BINARY, 0, X_NOREWIND); tp0->trackstart = tp1->trackstart; tp0->itracksize = sp->index1 * tp1->isecsize; tp0->tracksize = sp->index1 * tp1->secsize; tp0->tracksecs = sp->index1; tp1->tracksecs -= sp->index1; sp->secoff += sp->index1; sp->trackoff += sp->index1 * tp1->isecsize; tp1->flags &= ~TI_PREGAP; tp0->flags |= tp1->flags & (TI_SWAB|TI_AUDIO|TI_COPY|TI_QUADRO|TI_PREEMP|TI_SCMS); tp0->flags |= TI_HIDDEN; tp1->flags |= TI_HIDDEN; } if (xdebug > 1) { printf(_("Track[%2.2d] %2.2d Tracktype %X\n"), 0, 0, trackp[0].tracktype); } } if (xdebug > 1) { printf(_("Track[%2.2d] %2.2d Tracktype %X\n"), track, sp->trackno, trackp[track].tracktype); } trackp[track].nindex = 1; trackp[track].tindex = 0; if (xdebug > 1) { printf(_("Track[%2.2d] %2.2d flags 0x%08X\n"), 0, 0, trackp[0].flags); printf(_("Track[%2.2d] %2.2d flags 0x%08X\n"), track, sp->trackno, trackp[track].flags); } } /*--------------------------------------------------------------------------*/ LOCAL keyw_t * lookup(word, table) char *word; keyw_t table[]; { register keyw_t *kp = table; while (kp->k_name) { if (streql(kp->k_name, word)) return (kp); kp++; } return (NULL); } LOCAL char * state_name(st) int st; { if (st < STATE_NONE || st > STATE_MAX) return ("UNKNOWN"); return (states[st]); } /*--------------------------------------------------------------------------*/ /* * Parser low level functions start here... */ LOCAL char linebuf[4096]; LOCAL char *fname; LOCAL char *linep; LOCAL char *wordendp; LOCAL char wordendc; LOCAL int olinelen; LOCAL int linelen; LOCAL int lineno; LOCAL char worddelim[] = "=:,/"; LOCAL char nulldelim[] = ""; #ifdef DEBUG LOCAL void wdebug() { printf("WORD: '%s' rest '%s'\n", linep, peekword()); printf("linep %lX peekword %lX end %lX\n", (long)linep, (long)peekword(), (long)&linebuf[linelen]); } #endif LOCAL FILE * cueopen(name) char *name; { FILE *f; f = fileopen(name, "r"); if (f == NULL) comerr(_("Cannot open '%s'.\n"), name); fname = name; return (f); } LOCAL char * cuename() { return (fname); } LOCAL char * nextline(f) FILE *f; { register int len; do { fillbytes(linebuf, sizeof (linebuf), '\0'); len = fgetline(f, linebuf, sizeof (linebuf)); if (len < 0) return (NULL); if (len > 0 && linebuf[len-1] == '\r') { linebuf[len-1] = '\0'; len--; } linelen = len; lineno++; } while (linebuf[0] == '#'); olinelen = linelen; linep = linebuf; wordendp = linep; wordendc = *linep; return (linep); } #ifdef __needed__ LOCAL void ungetline() { linelen = olinelen; linep = linebuf; *wordendp = wordendc; wordendp = linep; wordendc = *linep; } #endif LOCAL char * skipwhite(s) const char *s; { register const Uchar *p = (const Uchar *)s; while (*p) { if (!isspace(*p)) break; p++; } return ((char *)p); } LOCAL char * peekword() { return (&wordendp[1]); } LOCAL char * lineend() { return (&linebuf[linelen]); } LOCAL char * markword(delim) char *delim; { register BOOL quoted = FALSE; register Uchar c; register Uchar *s; register Uchar *from; register Uchar *to; for (s = (Uchar *)linep; (c = *s) != '\0'; s++) { if (c == '"') { quoted = !quoted; for (to = s, from = &s[1]; *from; ) { c = *from++; if (c == '\\' && quoted && (*from == '\\' || *from == '"')) c = *from++; *to++ = c; } *to = '\0'; c = *s; linelen--; } if (!quoted && isspace(c)) break; if (!quoted && strchr(delim, c) && s > (Uchar *)linep) break; } wordendp = (char *)s; wordendc = (char)*s; *s = '\0'; return (linep); } LOCAL char getworddelim() { return (wordendc); } LOCAL char * getnextitem(delim) char *delim; { *wordendp = wordendc; linep = skipwhite(wordendp); return (markword(delim)); } LOCAL char * neednextitem(delim, type) char *delim; char *type; { char *olinep = linep; char *nlinep; nlinep = getnextitem(delim); if ((olinep == nlinep) || (*nlinep == '\0')) { if (type == NULL) cueabort(_("Missing text")); else cueabort(_("Missing '%s'"), type); } return (nlinep); } #ifdef __needed__ LOCAL char * nextword() { return (getnextitem(worddelim)); } #endif LOCAL char * needword(type) char *type; { return (neednextitem(worddelim, type)); } LOCAL char * curword() { return (linep); } LOCAL char * nextitem() { return (getnextitem(nulldelim)); } LOCAL char * needitem(type) char *type; { return (neednextitem(nulldelim, type)); } LOCAL void checkextra() { if (peekword() < lineend()) cueabort(_("Extra text '%s'"), peekword()); } /* VARARGS2 */ #ifdef PROTOTYPES LOCAL void statewarn(state_t *sp, const char *fmt, ...) #else LOCAL void statewarn(sp, fmt, va_alist) state_t *sp; char *fmt; va_dcl #endif { va_list args; #ifdef PROTOTYPES va_start(args, fmt); #else va_start(args); #endif errmsgno(EX_BAD, _("%r. Current state is '%s'.\n"), fmt, args, state_name(sp->state)); va_end(args); } #ifdef __needed__ /* VARARGS1 */ #ifdef PROTOTYPES LOCAL void cuewarn(const char *fmt, ...) #else LOCAL void cuewarn(fmt, va_alist) char *fmt; va_dcl #endif { va_list args; #ifdef PROTOTYPES va_start(args, fmt); #else va_start(args); #endif errmsgno(EX_BAD, _("%r on line %d col %d in '%s'.\n"), fmt, args, lineno, linep - linebuf, fname); va_end(args); } #endif /* VARARGS1 */ #ifdef PROTOTYPES LOCAL void cueabort(const char *fmt, ...) #else LOCAL void cueabort(fmt, va_alist) char *fmt; va_dcl #endif { va_list args; #ifdef PROTOTYPES va_start(args, fmt); #else va_start(args); #endif #ifdef PARSE_DEBUG errmsgno(EX_BAD, _("%r on line %d col %d in '%s'.\n"), #else comerrno(EX_BAD, _("%r on line %d col %d in '%s'.\n"), #endif fmt, args, lineno, linep - linebuf, fname); va_end(args); } /* VARARGS1 */ #ifdef PROTOTYPES LOCAL void extabort(const char *fmt, ...) #else LOCAL void extabort(fmt, va_alist) char *fmt; va_dcl #endif { va_list args; #ifdef PROTOTYPES va_start(args, fmt); #else va_start(args); #endif errmsgno(EX_BAD, _("Unsupported by CDRWIN: %r on line %d col %d in '%s'.\n"), fmt, args, lineno, linep - linebuf, fname); va_end(args); errmsgno(EX_BAD, _("Add 'REM CDRTOOLS' to enable cdrtools specific CUE extensions.\n")); comexit(EX_BAD); }