/* cue2toc.c - conversion routines
* Copyright (C) 2004 Matthias Czapla <dermatsch@gmx.de>
*
* This file is part of cue2toc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <stdarg.h>
#include "Cue2Toc.h"
#include "TrackData.h"
#define TCBUFLEN 9 /* Buffer length for timecode strings (HH:MM:SS) */
#define MAXCMDLEN 10 /* Longest command (currently SONGWRITER) */
long tc2fr(const char *);
int fr2tc(char *, long fr);
/*
* Input is divied into tokens that are separated by whitespace, horizantal
* tabulator, line feed and carriage return. Tokens can be either commands
* from a fixed set or strings. If a string is to contain any of the token
* delimiting characters it must be enclosed in double quotes.
*/
static const char token_delimiter[] = { ' ', '\t', '\n', '\r' };
/* Return true if c is one of token_delimiter */
static int
isdelim(int c)
{
int i;
int n = sizeof(token_delimiter);
for (i = 0; i < n; i++)
if (c == token_delimiter[i])
return 1;
return 0;
}
/* Used as return type for get_command and index into cmds */
enum command { REM, CATALOG, CDTEXTFILE,
FILECMD, PERFORMER, SONGWRITER, TITLE, TRACK, FLAGS, DCP,
FOURCH, PRE, SCMS, ISRC, PREGAP, INDEX, POSTGAP, BINARY,
MOTOROLA, AIFF, WAVE, MP3, OGG, UNKNOWN, END };
/* Except the last two these are the valid CUE commands */
char cmds[][MAXCMDLEN + 1] = { "REM", "CATALOG", "CDTEXTFILE",
"FILE", "PERFORMER", "SONGWRITER", "TITLE", "TRACK", "FLAGS", "DCP",
"4CH", "PRE", "SCMS", "ISRC", "PREGAP", "INDEX", "POSTGAP", "BINARY",
"MOTOROLA", "AIFF", "WAVE", "MP3", "OGG", "UNKNOWN", "END" };
/* These are for error messages */
static const char *fname = "stdin";
static long line; /* current line number */
static long tokenstart; /* line where last token started */
/* To generate meaningful error messages in check_once */
enum scope { CUESHEET, GLOBAL, ONETRACK };
/* Fatal error while processing input file */
static void
err_fail(const char *s)
{
fprintf(stderr, "%s:%ld: %s\n", fname, tokenstart, s);
exit(EXIT_FAILURE);
}
/* Fatal error */
static void
err_fail2(const char *s)
{
fprintf(stderr, "%s\n", s);
exit(EXIT_FAILURE);
}
/* EOF while expecting more */
static void
err_earlyend()
{
fprintf(stderr, "%s:%ld: Premature end of file\n", fname, line);
exit(EXIT_FAILURE);
}
/* Warning. Keep on going. */
static void
err_warn(const char *s)
{
fprintf(stderr, "%s:%ld: Warning, %s\n", fname, tokenstart, s);
}
/* Get next command from file */
static enum command
get_command(FILE *f)
{
int c;
char buf[MAXCMDLEN + 1];
int i = 0;
/* eat whitespace */
do {
c = getc(f);
if (c == '\n')
line++;
} while (isdelim(c));
if (c == EOF)
return END;
tokenstart = line;
/* get command, transform to upper case */
do {
buf[i++] = toupper(c);
c = getc(f);
} while (!isdelim(c) && c!= EOF && i < MAXCMDLEN);
if (!isdelim(c)) return UNKNOWN; /* command longer than MAXCMDLEN */
if (c == EOF) return END;
if (c == '\n') line++;
buf[i] = '\0';
if (strcmp(buf, cmds[REM]) == 0) return REM;
else if (strcmp(buf, cmds[CATALOG]) == 0) return CATALOG;
else if (strcmp(buf, cmds[CDTEXTFILE]) == 0) return CDTEXTFILE;
else if (strcmp(buf, cmds[FILECMD]) == 0) return FILECMD;
else if (strcmp(buf, cmds[PERFORMER]) == 0) return PERFORMER;
else if (strcmp(buf, cmds[SONGWRITER]) == 0) return SONGWRITER;
else if (strcmp(buf, cmds[TITLE]) == 0) return TITLE;
else if (strcmp(buf, cmds[TRACK]) == 0) return TRACK;
else if (strcmp(buf, cmds[FLAGS]) == 0) return FLAGS;
else if (strcmp(buf, cmds[DCP]) == 0) return DCP;
else if (strcmp(buf, cmds[FOURCH]) == 0) return FOURCH;
else if (strcmp(buf, cmds[PRE]) == 0) return PRE;
else if (strcmp(buf, cmds[SCMS]) == 0) return SCMS;
else if (strcmp(buf, cmds[ISRC]) == 0) return ISRC;
else if (strcmp(buf, cmds[PREGAP]) == 0) return PREGAP;
else if (strcmp(buf, cmds[INDEX]) == 0) return INDEX;
else if (strcmp(buf, cmds[POSTGAP]) == 0) return POSTGAP;
else if (strcmp(buf, cmds[BINARY]) == 0) return BINARY;
else if (strcmp(buf, cmds[MOTOROLA]) == 0) return MOTOROLA;
else if (strcmp(buf, cmds[AIFF]) == 0) return AIFF;
else if (strcmp(buf, cmds[WAVE]) == 0) return WAVE;
else if (strcmp(buf, cmds[MP3]) == 0) return MP3;
else if (strcmp(buf, cmds[OGG]) == 0) return OGG;
else return UNKNOWN;
}
/* Skip leading token delimiters then read at most n chars from f into s.
* Put terminating Null at the end of s. This implies that s must be
* really n + 1. Return number of characters written to s. The only case to
* return zero is on EOF before any character was read.
* Exit the program indicating failure if string is longer than n. */
static size_t
get_string(FILE *f, char *s, size_t n)
{
int c;
size_t i = 0;
/* eat whitespace */
do {
c = getc(f);
if (c == '\n')
line++;
} while (isdelim(c));
if (c == EOF)
return 0;
tokenstart = line;
if (c == '\"') {
c = getc(f);
if (c == '\n') line++;
while (c != '\"' && c != EOF && i < n) {
s[i++] = c;
c = getc(f);
if (c == '\n') line++;
}
if (i == n && c != '\"' && c != EOF)
err_fail("String too long");
} else {
while (!isdelim(c) && c != EOF && i < n) {
s[i++] = c;
c = getc(f);
}
if (i == n && !isdelim(c) && c != EOF)
err_fail("String too long");
}
if (i == 0) err_fail("Empty string");
if (c == '\n') line++;
s[i] = '\0';
return i;
}
/* Return track mode */
static TrackData::Mode get_track_mode(FILE *f)
{
char buf[] = "MODE1/2048";
char *pbuf = buf;
if (get_string(f, buf, sizeof(buf) - 1) < 1)
err_fail("Illegal track mode");
/* transform to upper case */
while (*pbuf) {
*pbuf = toupper(*pbuf);
pbuf++;
}
if (strcmp(buf, "AUDIO") == 0) return TrackData::AUDIO;
if (strcmp(buf, "MODE1/2048") == 0) return TrackData::MODE1;
if (strcmp(buf, "MODE1/2352") == 0) return TrackData::MODE1_RAW;
if (strcmp(buf, "MODE2/2336") == 0) return TrackData::MODE2;
if (strcmp(buf, "MODE2/2352") == 0) return TrackData::MODE2_RAW;
err_fail("Unsupported track mode");
return TrackData::AUDIO;
}
static void check_once(enum command cmd, char *s, enum scope sc);
/* Read at most CDTEXTLEN chars into s */
static void
get_cdtext(FILE *f, enum command cmd, char *s, enum scope sc)
{
check_once(cmd, s, sc);
if (get_string(f, s, CDTEXTLEN) < 1)
err_earlyend();
}
/* All strings have their first character initialized to '\0' so if s[0]
is not Null the cmd has already been seen in input. In this case print
a message end exit program indicating failure. The only purpose of the
arguments cmd and sc is to print meaningful error messages. */
static void
check_once(enum command cmd, char *s, enum scope sc)
{
if (s[0] == '\0')
return;
fprintf(stderr, "%s:%ld: %s allowed only once", fname, line, cmds[cmd]);
switch (sc) {
case CUESHEET: fprintf(stderr, "\n"); break;
case GLOBAL: fprintf(stderr, " in global section\n"); break;
case ONETRACK: fprintf(stderr, " per track\n"); break;
}
exit(EXIT_FAILURE);
}
/* Allocate, initialize and return new track */
static struct trackspec*
new_track(void)
{
struct trackspec *track;
int i;
if ((track = (struct trackspec*) malloc(sizeof(struct trackspec)))
== NULL)
err_fail("Memory allocation error in new_track()");
track->copy = track->pre_emphasis = track->four_channel_audio
= track->pregap_data_from_file = 0;
track->isrc[0] = track->title[0] = track->performer[0]
= track->songwriter[0] = track->filename[0] = '\0';
track->pregap = track->start = track->postgap = -1;
for (i = 0; i < NUM_OF_INDEXES; i++)
track->indexes[i] = -1;
track->next = NULL;
return track;
}
/* Read the cuefile and return a pointer to the cuesheet */
struct cuesheet*
read_cue(const char *cuefile, const char *wavefile)
{
FILE *f;
enum command cmd;
struct cuesheet *cs = NULL;
struct trackspec *track = NULL;
size_t n;
int c;
char file[FILENAMELEN + 1];
char timecode_buffer[TCBUFLEN];
char devnull[FILENAMELEN + 1]; /* just for eating CDTEXTFILE arg */
if (NULL == cuefile) {
f = stdin;
} else if (NULL == (f = fopen(cuefile, "r"))) {
fprintf(stderr, "Could not open file \"%s\" for "
"reading: %s\n", cuefile, strerror(errno));
exit(EXIT_FAILURE);
}
if (cuefile)
fname = cuefile;
if ((cs = (struct cuesheet*) malloc(sizeof(struct cuesheet))) == NULL)
err_fail("Memory allocation error in read_cue()");
cs->catalog[0] = '\0';
cs->type = (session_type)0;
cs->title[0] = '\0';
cs->performer[0] = '\0';
cs->songwriter[0] = '\0';
cs->tracklist = NULL;
file[0] = '\0';
line = 1;
/* global section */
while ((cmd = get_command(f)) != TRACK) {
switch (cmd) {
case UNKNOWN:
err_fail("Unknown command");
case END:
err_earlyend();
case REM:
c = getc(f);
while (c != '\n' && c != EOF)
c = getc(f);
break;
case CDTEXTFILE:
err_warn("ignoring CDTEXTFILE...");
if (get_string(f, devnull, FILENAMELEN) == 0)
err_warn("Syntactically incorrect "
"CDTEXTFILE command. But who "
"cares...");
break;
case CATALOG:
check_once(CATALOG, cs->catalog, CUESHEET);
n = get_string(f, cs->catalog, 13);
if (n != 13)
err_fail("Catalog number must be 13 "
"characters long");
break;
case TITLE:
get_cdtext(f, TITLE, cs->title, GLOBAL);
break;
case PERFORMER:
get_cdtext(f, PERFORMER, cs->performer, GLOBAL);
break;
case SONGWRITER:
get_cdtext(f, SONGWRITER, cs->songwriter, GLOBAL);
break;
case FILECMD:
check_once(FILECMD, file, GLOBAL);
if (get_string(f, file, FILENAMELEN) < 1)
err_earlyend();
switch (cmd = get_command(f)) {
case MOTOROLA:
err_warn("big endian binary file");
case BINARY:
break;
case AIFF:
err_warn("AIFF not supported by cdrdao");
case MP3:
case OGG:
case WAVE:
if (wavefile) {
strncpy(file, wavefile, FILENAMELEN);
file[FILENAMELEN] = '\0';
}
break;
default:
err_fail("Unsupported file type");
}
break;
default:
err_fail("Command not allowed in global section");
break;
}
}
/* leaving global section, entering track specifications */
if (file[0] == '\0')
err_fail("TRACK without previous FILE");
while (cmd != END) {
switch(cmd) {
case UNKNOWN:
err_fail("Unknown command");
case REM:
c = getc(f);
while (c != '\n' && c != EOF)
c = getc(f);
break;
case TRACK:
if (track == NULL) /* first track */
cs->tracklist = track = new_track();
else {
track = track->next = new_track();
}
/* the CUE format is "TRACK nn MODE" but we are not
interested in the track number */
while (isdelim(c = getc(f)))
if (c == '\n') line++;
while (!isdelim(c = getc(f))) ;
if (c == '\n') line++;
track->mode = get_track_mode(f);
/* audio tracks with binary files seem quite common */
/*
if (track->mode == AUDIO && filetype == BINARY
|| track->mode != AUDIO && filetype == WAVE)
err_fail("File and track type mismatch");
*/
strcpy(track->filename, file);
break;
case TITLE:
get_cdtext(f, TITLE, track->title, ONETRACK);
break;
case PERFORMER:
get_cdtext(f, PERFORMER, track->performer, ONETRACK);
break;
case SONGWRITER:
get_cdtext(f, SONGWRITER, track->songwriter, ONETRACK);
break;
case ISRC:
check_once(ISRC, track->isrc, ONETRACK);
if (get_string(f, track->isrc, 12) != 12)
err_fail("ISRC must be 12 characters long");
break;
case FLAGS:
if (track->copy || track->pre_emphasis
|| track->four_channel_audio)
err_fail("FLAGS allowed only once per track");
/* get the flags */
cmd = get_command(f);
while (cmd == DCP || cmd == FOURCH || cmd == PRE
|| cmd == SCMS) {
switch (cmd) {
case DCP:
track->copy = 1; break;
case FOURCH:
track->four_channel_audio = 1; break;
case PRE:
track->pre_emphasis = 1; break;
case SCMS:
err_warn("serial copy management "
"system flag not supported "
"by cdrdao"); break;
default:
err_fail("Should not get here");
}
cmd = get_command(f);
}
/* current non-FLAG command is already in cmd, so
avoid get_command() call below */
continue; break;
case PREGAP:
if (track->pregap != -1)
err_fail("PREGAP allowed only once per track");
if (get_string(f, timecode_buffer, TCBUFLEN - 1) < 1)
err_earlyend();
track->pregap = tc2fr(timecode_buffer);
if (track->pregap == -1)
err_fail("Timecode out of range");
track->pregap_data_from_file = 0;
break;
case POSTGAP:
if (track->postgap != -1)
err_fail("POSTGAP allowed only once per track");
if (get_string(f, timecode_buffer, TCBUFLEN - 1) < 1)
err_earlyend();
track->postgap = tc2fr(timecode_buffer);
if (track->postgap == -1)
err_fail("Timecode out of range");
break;
case INDEX:
if (get_string(f, timecode_buffer, 2) < 1)
err_earlyend();
n = atoi(timecode_buffer);
if (n < 0 || n > 99)
err_fail("Index out of range");
/* Index 0 is track pregap and Index 1 is start
of track. Index 2 to 99 are the true subindexes
and only allowed if the preceding one was there
before */
switch (n) {
case 0:
if (track->start != -1)
err_fail("Indexes must be sequential");
if (track->pregap != -1)
err_fail("PREGAP allowed only once "
"per track");
if (get_string(f, timecode_buffer,
TCBUFLEN - 1) < 1)
err_earlyend();
/* This is only a temporary value until
index 01 is read */
track->pregap = tc2fr(timecode_buffer);
if (track->pregap == -1)
err_fail("Timecode out of range");
track->pregap_data_from_file = 1;
break;
case 1:
if (track->start != -1)
err_fail("Each index allowed only "
"once per track");
if (get_string(f, timecode_buffer,
TCBUFLEN - 1) < 1)
err_fail("Missing timecode");
track->start = tc2fr(timecode_buffer);
if (track->start == -1)
err_fail("Timecode out of range");
/* Fix the pregap value */
if (track->pregap_data_from_file)
track->pregap = track->start
- track->pregap;
break;
case 2:
if (track->start == -1)
err_fail("Indexes must be sequential");
if (track->indexes[n - 2] != -1)
err_fail("Each index allowed only "
"once per track");
if (get_string(f, timecode_buffer,
TCBUFLEN - 1) < 1)
err_fail("Missing timecode");
track->indexes[n - 2] = tc2fr(timecode_buffer);
if (track->indexes[n - 2] == -1)
err_fail("Timecode out of range");
break;
default: /* the other 97 indexes */
/* check if previous index is there */
if (track->indexes[n - 3] == -1)
err_fail("Indexes must be sequential");
if (track->indexes[n - 2] != -1)
err_fail("Each index allowed only "
"once per track");
if (get_string(f, timecode_buffer,
TCBUFLEN - 1) < 1)
err_fail("Missing timecode");
track->indexes[n - 2] = tc2fr(timecode_buffer);
if (track->indexes[n - 2] == -1)
err_fail("Timecode out of range");
break;
}
break;
case FILECMD:
if (get_string(f, file, FILENAMELEN) < 1)
err_earlyend();
switch (cmd = get_command(f)) {
case MOTOROLA:
err_warn("big endian binary file");
case BINARY:
break;
case AIFF:
err_warn("AIFF and MP3 not supported by "
"cdrdao");
case MP3:
case OGG:
case WAVE:
if (wavefile) {
strncpy(file, wavefile, FILENAMELEN);
file[FILENAMELEN] = '\0';
}
break;
default:
err_fail("Unsupported file type");
}
break;
default:
err_fail("Command not allowed in track spec");
break;
}
cmd = get_command(f);
}
return cs;
}
/* Deduce the disc session type from the track modes */
static enum session_type
determine_session_type(struct trackspec *list)
{
struct trackspec *track = list;
/* set to true if track of corresponding type is found */
int audio = 0;
int mode1 = 0;
int mode2 = 0;
while (track != NULL) {
switch (track->mode) {
case TrackData::AUDIO:
audio = 1; break;
case TrackData::MODE1: case TrackData::MODE1_RAW:
mode1 = 1; break;
case TrackData::MODE2: case TrackData::MODE2_RAW:
mode2 = 1; break;
default: /* should never get here */
err_fail2("Dont know how this could happen, but here "
"is a track with an unknown mode :|");
}
track = track->next;
}
/* CD_DA only audio
* CD_ROM only mode1 with or without audio
* CD_ROM_XA only mode2 with or without audio
*/
if (audio && !mode1 && !mode2)
return CD_DA;
else if (audio && mode1 && !mode2 || !audio && mode1 && !mode2)
return CD_ROM;
else if (audio && !mode1 && mode2 || !audio && !mode1 && mode2)
return CD_ROM_XA;
else
return INVALID;
}
/* Return true if cuesheet contains any CD-Text data */
static int
contains_cdtext(struct cuesheet *cs)
{
struct trackspec *track = cs->tracklist;
if (cs->title[0] != '\0' || cs->performer[0] != '\0'
|| cs->songwriter[0] != '\0')
return 1;
while (track) {
if (track->title[0] != '\0' || track->performer[0] != '\0'
|| track->songwriter[0] != '\0')
return 1;
track = track->next;
}
return 0;
}
/* fprintf() with indentation. The argument indent is the number of spaces
to print per level. E.g. with indent=4 and level=3 there are 12 spaces
printed. Every eight spaces are replaced by a single tabulator. The
return value is the return value of fprintf(). */
static int ifprintf(std::ostream& o, int indent, int level,
const char *format, ...)
{
static char twolines[161];
va_list ap;
int fprintf_return = 0;
int tabs = indent * level / 8;
int spaces = indent * level % 8;
int i;
for (i = 0; i < tabs; i++)
o << '\t';
for (i = 0; i < spaces; i++)
o << ' ';
va_start(ap, format);
fprintf_return = vsprintf(twolines, format, ap);
va_end(ap);
o << twolines;
return fprintf_return;
}
/* Write a track to the file f. The arguments i and l are the indentation
amount and level (see ifprintf above). Do not write CD-Text data if
cdtext is zero. */
static void
write_track(struct trackspec *tr, std::ostream& f, int i, int l, int cdtext)
{
char timecode_buffer[TCBUFLEN];
long start = 0, len = 0;
int j = 0;
f << std::endl;
ifprintf(f, i, l++, "TRACK ");
switch(tr->mode) {
case TrackData::AUDIO: f << "AUDIO\n"; break;
case TrackData::MODE1: f << "MODE1\n"; break;
case TrackData::MODE1_RAW: f << "MODE1_RAW\n"; break;
case TrackData::MODE2: f << "MODE2\n"; break;
case TrackData::MODE2_RAW: f << "MODE2_RAW\n"; break;
default: err_fail2("Unknown track mode"); /* cant get here */
}
/* Flags and ISRC */
if (tr->copy)
ifprintf(f, i, l, "COPY\n");
if (tr->pre_emphasis)
ifprintf(f, i, l, "PRE_EMPHASIS\n");
if (tr->four_channel_audio)
ifprintf(f, i, l, "FOUR_CHANNEL_AUDIO\n");
if (tr->isrc[0] != '\0')
ifprintf(f, i, l, "ISRC \"%s\"\n", tr->isrc);
/* CD-Text data */
if (cdtext && (tr->title[0] != '\0' || tr->performer[0] != '\0'
|| tr->songwriter[0] != '\0')) {
ifprintf(f, i, l++, "CD_TEXT {\n");
ifprintf(f, i, l++, "LANGUAGE 0 {\n");
if (tr->title[0] != '\0')
ifprintf(f, i, l, "TITLE \"%s\"\n", tr->title);
if (tr->performer[0] != '\0')
ifprintf(f, i, l, "PERFORMER \"%s\"\n", tr->performer);
if (tr->songwriter[0] != '\0')
ifprintf(f, i, l, "SONGWRITER \"%s\"\n",
tr->songwriter);
ifprintf(f, i, --l, "}\n"); /* LANGUAGE 0 { */
ifprintf(f, i, --l, "}\n"); /* CD_TEXT { */
}
/* Pregap with zero data */
if (tr->pregap != -1 && !tr->pregap_data_from_file) {
if (fr2tc(timecode_buffer, tr->pregap) == -1)
err_fail2("Pregap out of range");
ifprintf(f, i, l, "PREGAP %s\n", timecode_buffer);
}
/* Specify the file */
start = 0;
if (tr->mode == TrackData::AUDIO) {
ifprintf(f, i, l, "AUDIOFILE \"%s\" ", tr->filename);
if (tr->start != -1) {
if (tr->pregap_data_from_file) {
start = tr->start - tr->pregap;
} else
start = tr->start;
}
if (fr2tc(timecode_buffer, start) == -1)
err_fail2("Track start out of range");
f << timecode_buffer;
} else {
if (tr->start) {
long datastart = (tr->pregap_data_from_file ?
tr->start - tr->pregap : tr->start);
ifprintf(f, i, l, "DATAFILE \"%s\" #%d", tr->filename,
datastart * AUDIO_BLOCK_LEN);
start = datastart;
} else {
ifprintf(f, i, l, "DATAFILE \"%s\"", tr->filename);
}
}
/* If next track has the same filename and specified a start
value use the difference between start of this and start of
the next track as the length of the current track */
if (tr->next
&& strcmp(tr->filename, tr->next->filename) == 0
&& tr->next->start != -1) {
if (tr->next->pregap_data_from_file)
len = tr->next->start - tr->next->pregap - start;
else
len = tr->next->start - start;
if (fr2tc(timecode_buffer, len) == -1)
err_fail2("Track length out of range");
f << ' ' << timecode_buffer << std::endl;
} else {
f << std::endl;
}
/* Pregap with data from file */
if (tr->pregap_data_from_file) {
if (fr2tc(timecode_buffer, tr->pregap) == -1)
err_fail2("Pregap out of range");
ifprintf(f, i, l, "START %s\n", timecode_buffer);
}
/* Postgap */
if (tr->postgap != -1) {
if (fr2tc(timecode_buffer, tr->postgap) == -1)
err_fail2("Postgap out of range");
if (tr->mode == TrackData::AUDIO)
ifprintf(f, i, l, "SILENCE %s\n", timecode_buffer);
else
ifprintf(f, i, l, "ZERO %s\n", timecode_buffer);
}
/* Indexes */
while (tr->indexes[j] != -1 && i < NUM_OF_INDEXES) {
if (fr2tc(timecode_buffer, tr->indexes[j++]) == -1)
err_fail2("Index out of range");
ifprintf(f, i, l, "INDEX %s\n", timecode_buffer);
}
}
// Write the cuesheet cs to the output stream. Do not write CD-Text
// data if cdt is zero.
void write_toc(std::ostream& f, struct cuesheet *cs, bool cdt)
{
int i = 4; /* number of chars for indentation */
int l = 0; /* current leven of indentation */
bool cdtext = contains_cdtext(cs) && cdt;
struct trackspec *track = cs->tracklist;
if ((cs->type = determine_session_type(cs->tracklist)) == INVALID)
err_fail2("Invalid combination of track modes");
ifprintf(f, i, l, "// Generated by cue2toc 0.2\n");
ifprintf(f, i, l, "// Report bugs to <dermatsch@gmx.de>\n");
if (cs->catalog[0] != '\0')
ifprintf(f, i, l, "CATALOG \"%s\"\n", cs->catalog);
switch (cs->type) {
case CD_DA: ifprintf(f, i, l, "CD_DA\n"); break;
case CD_ROM: ifprintf(f, i, l, "CD_ROM\n"); break;
case CD_ROM_XA: ifprintf(f, i, l, "CD_ROM_XA\n"); break;
default: err_fail2("Should never get here");
}
if (cdtext) {
ifprintf(f, i, l++, "CD_TEXT {\n");
ifprintf(f, i, l++, "LANGUAGE_MAP {\n");
ifprintf(f, i, l, "0 : EN\n");
ifprintf(f, i, --l, "}\n");
ifprintf(f, i, l++, "LANGUAGE 0 {\n");
if (cs->title[0] != '\0')
ifprintf(f, i, l, "TITLE \"%s\"\n", cs->title);
if (cs->performer[0] != '\0')
ifprintf(f, i, l, "PERFORMER \"%s\"\n", cs->performer);
if (cs->songwriter[0] != '\0')
ifprintf(f, i, l, "SONGWRITER \"%s\"\n",
cs->songwriter);
ifprintf(f, i, --l, "}\n");
ifprintf(f, i, --l, "}\n");
}
while (track) {
write_track(track, f, i, l, cdtext);
track = track->next;
}
}
/* Interpret argument as timecode value ("MM:SS:FF") and return the total
number of frames. Tries to work in a way similar to atoi(), ignoring any
trailing non-timecode junk. Skips leading whitespace.
I want it to be as flexible as possible, recognizing simple values like
"0" (interpreted as "00:00:00"), "1:2" ("00:01:02") and so on.
Returns -1 on error (argument NULL or some value out of range) */
#define MAXDIGITS 2
#define NUMOFNUMS 3
long
tc2fr(const char *tc)
{
int minutes = 0;
int seconds = 0;
int frames = 0;
long totalframes = 0;
char tmp[MAXDIGITS + 1];
int nums[NUMOFNUMS];
int n = 0;
int i = 0;
int last_was_colon = 0;
int stop = 0;
if (tc == NULL)
return -1;
for (i = 0; i <= MAXDIGITS; i++)
tmp[i] = '\0';
while (isspace(*tc))
tc++;
for (n = 0; n < NUMOFNUMS; n++) {
if (n > 0) {
if (tc[0] != ':') {
--n;
break;
} else
tc++;
}
for (i = 0; i < MAXDIGITS; i++) {
if (isdigit(tc[i])) {
tmp[i] = tc[i];
last_was_colon = 0;
} else if (tc[i] == ':') {
if (i == 0)
stop = 1;
break;
} else {
stop = 1;
break;
}
}
if (i != 0) {
tmp[i] = '\0';
nums[n] = atoi(tmp);
tc = &tc[i];
} else
--n;
if (stop)
break;
}
if (n == NUMOFNUMS)
--n;
frames = seconds = minutes = 0;
switch (n) {
case 0:
frames = nums[0];
break;
case 1:
seconds = nums[0];
frames = nums[1];
break;
case 2:
minutes = nums[0];
seconds = nums[1];
frames = nums[2];
break;
}
totalframes = ((60 * minutes) + seconds) * 75 + frames;
if (seconds > 59 || frames > 74)
return -1;
return totalframes;
}
/* Writes formatted timecode string ("MM:SS:FF") into tc, calculated from
frame number fr.
Returns -1 on error (frames value out of range) */
int
fr2tc(char *tc, long fr)
{
int m;
int s;
int f;
if (fr > 449999 || fr < 0) { /* 99:59:74 */
strcpy(tc, "00:00:00");
return -1;
}
f = fr % 75;
fr -= f;
s = (fr / 75) % 60;
fr -= s * 75;
m = fr / 75 / 60;
sprintf(tc, "%02d:%02d:%02d", m, s, f);
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1