diff options
-rw-r--r-- | cuerecover.c | 319 | ||||
-rw-r--r-- | cuerecover.rst | 29 |
2 files changed, 224 insertions, 124 deletions
diff --git a/cuerecover.c b/cuerecover.c index 34542d2..40698e3 100644 --- a/cuerecover.c +++ b/cuerecover.c @@ -5,6 +5,8 @@ https://en.wikipedia.org/wiki/CD-ROM */ #include <stdio.h> #include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> #include <unistd.h> #include <limits.h> #include <string.h> @@ -25,6 +27,7 @@ int silence_frames; /* same as above in 1/75 sec frames */ int silence_thresh = 0; int multi_bin = 0; int verbose = 0; +char *outfile = NULL; void die(const char *msg) { if(!msg) msg = strerror(errno); @@ -39,13 +42,56 @@ void usage() { exit(0); } +int looks_like_filesystem(const char *filename, FILE *f) { + int have_apple_magic = 0, have_iso_magic = 0; + char buf[2048]; + + if(fseek(f, 0L, SEEK_SET) < 0) + die(NULL); + + if(!fread(buf, 2048, 1, f)) { + fprintf(stderr, "%s: file %s is too short to be a CD image file\n", self, filename); + return 0; + } + + if(buf[0] == 'E' && buf[1] == 'R' && buf[3] == 0 && (buf[2] == 0x02 || buf[2] == 0x08)) + { + have_apple_magic++; + } + + if(fseek(f, 16 * 2048L, SEEK_SET) < 0) + die(NULL); + + if(!fread(buf, 2048, 1, f)) { + fprintf(stderr, "%s: file %s is too short to be a CD image file\n", self, filename); + return 0; + } + + if(strncmp("\x01" "CD001", buf, 6) == 0) { + have_iso_magic++; + } + + if(fseek(f, 0L, SEEK_SET) < 0) + die(NULL); + + if(have_apple_magic && have_iso_magic) { + fprintf(stderr, "%s: file %s looks like hybrid ISO/HFS image\n", self, filename); + } else if(have_apple_magic) { + fprintf(stderr, "%s: file %s looks like Mac HFS image\n", self, filename); + } else if(have_iso_magic) { + fprintf(stderr, "%s: file %s looks like ISO9660 image\n", self, filename); + } + + return have_apple_magic || have_iso_magic; +} + int frame_is_silent(const char *data) { int i, nonzero = 0; for(i=0; i<RAW_SECTOR_SIZE; i++) if(data[i]) nonzero++; if(nonzero <= silence_thresh) - return 1; + return 1; return 0; } @@ -66,8 +112,8 @@ const char *frame2time(int frame) { int read_sector(FILE *f, char *buf) { /* if(verbose) - fprintf(stderr, "read_sector at pos %d\n", ftell(f)); - */ + fprintf(stderr, "read_sector at pos %d\n", ftell(f)); + */ return fread(buf, RAW_SECTOR_SIZE, 1, f); } @@ -77,7 +123,7 @@ int has_mode1_sync(char *buf) { int get_sector_mode(char *buf) { if(!has_mode1_sync(buf)) - return 0; /* audio, or something we don't grok */ + return 0; /* audio, or something we don't grok */ return buf[15]; } @@ -100,9 +146,9 @@ int get_sector_addr(char *buf) { const char *mode_string(int mode) { switch(mode) { - case 1: return "MODE1/2352"; - case 2: return "MODE2/2352"; - default: return "AUDIO"; + case 1: return "MODE1/2352"; + case 2: return "MODE2/2352"; + default: return "AUDIO"; } } @@ -124,51 +170,69 @@ void process_single(char *filename) { struct track *t = tracklist; if(!(f = fopen(filename, "rb"))) - die(NULL); + die(NULL); + + if(looks_like_filesystem(filename, f)) { + fprintf(stderr, "%s: %s looks like 'cooked' data!\n", self, filename); + /* + // don't know all the details on how this works yet + int skipped_sectors = 0; + fprintf(stderr, "%s: %s looks like 'cooked' data, ", self, filename); + if((skipped_sectors = skip_fs(filename, f))) { + fprintf(stderr, "skipped %d 2048-byte sectors\n", skipped_sectors); + } else { + fprintf(stderr, "but not ISO9660, don't know how to skip it\n"); + } + */ + } /* only the first track can be data */ if(read_sector(f, buf) && (mode = get_sector_mode(buf)) != 0) { - if(verbose) fprintf(stderr, "track %d is data, mode %d\n", track, mode); - t->index0 = sector; - t->index1 = sector; - t->mode = mode; - - /* skip over the data sectors */ - while(read_sector(f, buf) && (get_sector_mode(buf) == mode)) - sector++; - - /* now buf holds the first audio sector, which might or might not - begin with silence */ - t++; track++; - t->mode = 0; - t->index0 = sector; - t->index1 = sector; + if(verbose) fprintf(stderr, "track %d is data, mode %d\n", track, mode); + t->index0 = sector; + t->index1 = sector; + t->mode = mode; + + /* skip over the data sectors */ + while(read_sector(f, buf) && (get_sector_mode(buf) == mode)) + sector++; + + /* now buf holds the first audio sector, which might or might not + begin with silence */ + t++; track++; + t->mode = 0; + t->index0 = sector; + t->index1 = sector; } if(silence_frames) while(read_sector(f, buf)) { - if(frame_is_silent(buf)) { - if(in_silence) { - /* nothing */ - } else { - t->index0 = sector; - t->index1 = -1; - silence_start = sector; - in_silence = 1; - } - } else { - if(in_silence) { - if((sector - silence_start) >= silence_frames) { - in_silence = 0; - t->index1 = sector; - t++; - track++; - t->mode = 0; - } else { - /* nothing */ - } - } - } - sector++; + if(frame_is_silent(buf)) { + if(in_silence) { + /* nothing */ + } else { + t->index0 = sector; + t->index1 = -1; + silence_start = sector; + in_silence = 1; + } + } else { + if(in_silence) { + if((sector - silence_start) >= silence_frames) { + in_silence = 0; + t->index1 = sector; + t++; + track++; + if(track > 99) { + fprintf(stderr, "%s: too many tracks (>99), skipping rest of file\n", self); + break; + } + t->mode = 0; + } else { + /* nothing */ + } + } + } + sector++; } fclose(f); @@ -176,71 +240,79 @@ void process_single(char *filename) { t = tracklist; printf("FILE \"%s\" BINARY\r\n", filename); for(i=0; i<=track; t++, i++) { - if(verbose) { - fprintf(stderr, "track %d, mode %d, index0 %d, index1 %d\n", - i, t->mode, t->index0, t->index1); - } - if(t->index1 > -1) { - printf(" TRACK %02d %s\r\n", i + 1, mode_string(t->mode)); - if(t->index0 < t->index1) { - if(silence_frames && t->index1 - t->index0 > silence_frames) { - t->index0 = t->index1 - silence_frames; - if(verbose) - fprintf(stderr, " (index0 adjusted to %d)\n", t->index0); - } - printf(" INDEX 00 %s\r\n", frame2time(t->index0)); - } - printf(" INDEX 01 %s\r\n", frame2time(t->index1)); - } + if(verbose) { + fprintf(stderr, "track %d, mode %d, index0 %d, index1 %d\n", + i, t->mode, t->index0, t->index1); + } + if(t->index1 > -1) { + printf(" TRACK %02d %s\r\n", i + 1, mode_string(t->mode)); + if(t->index0 < t->index1) { + if(silence_frames && t->index1 - t->index0 > silence_frames) { + t->index0 = t->index1 - silence_frames; + if(verbose) + fprintf(stderr, " (index0 adjusted to %d)\n", t->index0); + } + printf(" INDEX 00 %s\r\n", frame2time(t->index0)); + } + printf(" INDEX 01 %s\r\n", frame2time(t->index1)); + } } } void process_multi(char **filenames) { - int track = 1; + char *filename; + int track = 0; char buf[RAW_SECTOR_SIZE]; FILE *f; - while(*filenames) { - int index0 = 0, index1 = -1; - int mode = -1; - int sector = 0; - - if(verbose) fprintf(stderr, "reading %s\n", *filenames); - if(!(f = fopen(*filenames, "rb"))) - die(NULL); - - printf("FILE \"%s\" BINARY\r\n", *filenames); - - while(read_sector(f, buf)) { - mode = get_sector_mode(buf); - - /* if first sector is data, there's no index 01, we're done */ - if(mode > 0) { - index0 = 0; - index1 = -1; - break; - } - - /* for audio, keep reading silence until we hit sound */ - if(mode == 0 && index1 == -1 && !frame_is_silent(buf)) { - index1 = (sector / 75) * 75; /* round to integer number of seconds */ - break; - } - - sector++; - } - fclose(f); - - printf(" TRACK %02d %s\r\n", track, mode_string(mode)); - if(index1 < 1) { - printf(" INDEX 01 %s\r\n", frame2time(index0)); - } else { - printf(" INDEX 00 %s\r\n", frame2time(index0)); - printf(" INDEX 01 %s\r\n", frame2time(index1)); - } - - track++; - filenames++; + while((filename = *filenames++)) { + int index0 = 0, index1 = -1; + int mode = -1; + int sector = 0; + + track++; + + if(verbose) fprintf(stderr, "reading %s\n", filename); + if(!(f = fopen(filename, "rb"))) + die(NULL); + + printf("FILE \"%s\" BINARY\r\n", filename); + + if(looks_like_filesystem(filename, f)) { + fprintf(stderr, "%s: %s looks like 'cooked' data, assuming MODE1/2048\n", + self, filename); + printf(" TRACK %02d MODE1/2048\r\n", track); + printf(" INDEX 01 00:00:00\r\n"); + continue; + } + + while(read_sector(f, buf)) { + mode = get_sector_mode(buf); + + /* if first sector is data, there's no index 01, we're done */ + if(mode > 0) { + index0 = 0; + index1 = -1; + break; + } + + /* for audio, keep reading silence until we hit sound */ + if(mode == 0 && index1 == -1 && !frame_is_silent(buf)) { + index1 = (sector / 75) * 75; /* round to integer number of seconds */ + break; + } + + sector++; + } + fclose(f); + + printf(" TRACK %02d %s\r\n", track, mode_string(mode)); + if(index1 < 1) { + printf(" INDEX 01 %s\r\n", frame2time(index0)); + } else { + printf(" INDEX 00 %s\r\n", frame2time(index0)); + printf(" INDEX 01 %s\r\n", frame2time(index1)); + } } } @@ -252,7 +324,15 @@ char **parse_args(int argc, char **argv) { if(argv[0][0] == '-') { char *nextarg = argv[1]; switch(argv[0][1]) { - case 'v': verbose++; break; + case 'v': + verbose++; break; + case 'o': + if(nextarg) + outfile = nextarg; + else + die("-o requires an output filename"); + argv++, argc--; + break; case 's': if(nextarg && ((*nextarg >= '0' && *nextarg <= '9') || (*nextarg == '.'))) { silence_sec = atof(nextarg); @@ -263,9 +343,9 @@ char **parse_args(int argc, char **argv) { break; case 't': if(nextarg && - (*nextarg >= '0' && *nextarg <= '9') && - (silence_thresh = atoi(nextarg) <= 100)) - { + (*nextarg >= '0' && *nextarg <= '9') && + (silence_thresh = atoi(nextarg) <= 100)) + { silence_thresh = (RAW_SECTOR_SIZE / 100.0) * atoi(nextarg); argv++, argc--; } else { @@ -278,7 +358,7 @@ char **parse_args(int argc, char **argv) { break; } } else { - break; + break; } } @@ -287,6 +367,16 @@ char **parse_args(int argc, char **argv) { return argv; } +void redirect_output(void) { + struct stat ss; + + if(lstat(outfile, &ss) >= 0) + die("output file exists, not overwriting"); + + if(!(freopen(outfile, "w", stdout))) + die(NULL); +} + void set_exe_name(const char *argv0) { const char *p; self = argv0; @@ -299,13 +389,16 @@ int main(int argc, char **argv) { argv = parse_args(argc, argv); - if(verbose) fprintf(stderr, "writing %s CUE file to stdout\n", - (multi_bin ? "multi-bin" : "single-bin")); + if(verbose) fprintf(stderr, "writing %s CUE file to %s\n", + (multi_bin ? "multi-bin" : "single-bin"), + (outfile ? outfile : "<standard output>")); + + if(outfile) redirect_output(); if(multi_bin) - process_multi(argv); + process_multi(argv); else - process_single(*argv); + process_single(*argv); return 0; } diff --git a/cuerecover.rst b/cuerecover.rst index d4d932b..813ed4c 100644 --- a/cuerecover.rst +++ b/cuerecover.rst @@ -24,15 +24,13 @@ generate .cue file for CD image .bin SYNOPSIS ======== -cuerecover [-s sec] **bin-file** [**bin-file** ...] +cuerecover [-o output] [-s sec] [-t thresh] **bin-file** [**bin-file** ...] DESCRIPTION =========== cuerecover attempts to generate a usable cue sheet for CD images which -are missing their .cue (or .ccd, .mds, etc) files. The generated cue -sheet is written to standard output [*], so add e.g. "> filename.cue" -to redirect it to a file. +are missing their .cue (or .ccd, .mds, etc) files. If a single .bin file is given, it's assumed to hold all the tracks (which might only be one). If multiple .bin files are given, each one is assumed to @@ -59,19 +57,27 @@ OPTIONS --help Print short usage string. +-o <file> Write cue file to *file* rather than standard output. If + *file* already exists, cuerecover will refuse to overwrite it, + which makes this safer than redirecting stdout. + -s <sec> Minimum amount of silence for detecting the split point between - two audio tracks (default: 2). Argument is in seconds, and - non-integers are allowed. 0 means to disable splitting tracks: - all the audio tracks will be combined into one in the .cue - sheet. This option is ignored when multiple .bin file arguments - are given, since they're already split into tracks. + two audio tracks. Argument is in seconds, and decimals + are allowed. The default is 2, which is the standard sized + gap between tracks in the Redbook standard and in most CD + authoring software. 0 means to disable splitting tracks: + all the audio tracks will be combined into one in the + .cue sheet. This option is ignored when multiple .bin file + arguments are given, since they're already split into tracks. -t <thresh> Silence threshold, 0 to 100. Default is 0. This is the percentage of non-zero bytes allowed in a sector for it to be considered silent. Sometimes audio tracks have random data in the pregap (before the INDEX 01), which will fool cuerecover into thinking there's no pregap. This option can - help with those, but don't set it too high. + help with those, but don't set it too high. This option is ignored + when multiple .bin file arguments are given, since they're already + split into tracks. -v Verbose mode. Prints (on stderr) some extra messages about what cuerecover is doing. Probably only of interest to the author. @@ -96,7 +102,8 @@ assumptions. These are usually valid, but should be mentioned here: - If there's a data track, it's a raw image (2352 bytes per sector, includes the sync pulse, address, CRC, ECC, etc). If the data track was stored as 'cooked' data (2048 bytes/sector, MODE1/2048 in the original .cue file), - it'll be treated as an audio track. You can check for this by trying to + it'll be treated as an audio track, and you'll get a warning that it + looks like 'cooked' data. You can check for this by trying to mount the .bin file as an ISO or HFS image: if it mounts, the first track is 'cooked'. It'll also be obvious if you use the .cue sheet to extract the image into files: ISO images don't sound musical at all! |