diff options
author | B. Watson <yalhcru@gmail.com> | 2020-05-11 21:43:44 -0400 |
---|---|---|
committer | B. Watson <yalhcru@gmail.com> | 2020-05-11 21:43:44 -0400 |
commit | 38a9d0ec489925eb25b8a0c837b5230da4eb8a68 (patch) | |
tree | 8ad6118bf451b1fc85c8d367bb7babe828c22273 | |
parent | 0b19a8ae810169cbdfd15ba79b3e7165e40cf535 (diff) | |
download | miragextract-38a9d0ec489925eb25b8a0c837b5230da4eb8a68.tar.gz |
add cuerecover
-rw-r--r-- | Makefile | 26 | ||||
-rw-r--r-- | README | 14 | ||||
-rw-r--r-- | check_version | 8 | ||||
-rw-r--r-- | cuerecover.1 | 158 | ||||
-rw-r--r-- | cuerecover.c | 311 | ||||
-rw-r--r-- | cuerecover.html | 462 | ||||
-rw-r--r-- | cuerecover.rst | 139 |
7 files changed, 1106 insertions, 12 deletions
@@ -39,7 +39,10 @@ RST2HTML=rst2html4.py PROJ=miragextract VERSION=0.1.0 -DOCS=README FAQ ChangeLog LICENSE $(PROJ).html +BINS=$(PROJ) cuerecover +MANS=$(PROJ).1 cuerecover.1 +HTMLS=$(PROJ).html cuerecover.html +DOCS=README FAQ ChangeLog LICENSE $(PROJ).html cuerecover.html SNDFILE_CFLAGS:=$(shell pkg-config --cflags sndfile) SNDFILE_LIBS:=$(shell pkg-config --libs sndfile) @@ -47,20 +50,25 @@ SNDFILE_LIBS:=$(shell pkg-config --libs sndfile) MIRAGE_CFLAGS:=$(shell pkg-config --cflags libmirage) MIRAGE_LIBS:=$(shell pkg-config --libs libmirage) -CFLAGS=-Wall $(OPTFLAGS) $(MIRAGE_CFLAGS) $(SNDFILE_CFLAGS) -DVERSION=\"$(VERSION)\" +PROJCFLAGS=-Wall $(OPTFLAGS) -DVERSION=\"$(VERSION)\" +CFLAGS=$(PROJCFLAGS) $(MIRAGE_CFLAGS) $(SNDFILE_CFLAGS) LDFLAGS=$(MIRAGE_LIBS) $(SNDFILE_LIBS)$ $(LDEXTRA) -all: $(PROJ) +all: $(BINS) -man: $(PROJ).1 +man: $(MANS) -html: $(PROJ).html +html: $(HTMLS) -$(PROJ).1: $(PROJ).rst - $(RST2MAN) $(PROJ).rst > $(PROJ).1 +# don't need to use libmirage and libsndfile flags with this one +cuerecover: cuerecover.c + $(CC) $(PROJCFLAGS) -o $@ $< -$(PROJ).html: $(PROJ).rst - $(RST2HTML) $(PROJ).rst > $(PROJ).html +%.1: %.rst + $(RST2MAN) $< > $@ || rm -f $@ + +%.html: %.rst + $(RST2HTML) $< > $@ || rm -f $@ ifeq ($(shell whoami),root) CHOWN=chown @@ -3,9 +3,14 @@ README for miragextract v0.1.0 Description ----------- -Extracts data and audio tracks from any CD image supported by libmirage. -Data tracks are written as-is, and audio tracks can be written as-is -or converted to wav, flag, or ogg/vorbis (via libsndfile). +A pair of tools for working with CD image files. + +miragextract extracts data and audio tracks from any CD image supported +by libmirage. Data tracks are written as-is, and audio tracks can be +written as-is or converted to wav, flag, or ogg/vorbis (via libsndfile). + +cuerecover attempts to create a .cue file for .bin files whose .cue +files are missing. Prerequisites ------------- @@ -24,6 +29,9 @@ If you're on a system that has separate "dev" or "devel" packages If you for some reason need to rebuild the man and html pages, you'll need docutils (I used 0.14). +If you *just* want cuerecover, you don't need libmirage nor libsndfile. +Run "make cuerecover" in this case. + Installation ------------ To build, run "make" (might be spelled "gmake" on your OS). To install, diff --git a/check_version b/check_version index e42a865..6d7f25c 100644 --- a/check_version +++ b/check_version @@ -18,4 +18,12 @@ if [ "$VER" != "$1" ]; then X=1 fi +VER="$( sed -n '/^\.\. |version| replace:: /s,.* ,,p' cuerecover.rst )" +if [ "$VER" != "$1" ]; then + echo + echo "*** You forgot to update the version in cuerecover.rst" + echo + X=1 +fi + exit $X diff --git a/cuerecover.1 b/cuerecover.1 new file mode 100644 index 0000000..d57408e --- /dev/null +++ b/cuerecover.1 @@ -0,0 +1,158 @@ +.\" Man page generated from reStructuredText. +. +.TH CUERECOVER 1 "2020-05-11" "0.1.0" "Urchlay" +.SH NAME +cuerecover \- generate .cue file for CD image .bin +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.\" RST source for cuerecover(1) man page. Convert with: +. +.\" rst2man.py cuerecover.rst > cuerecover.1 +. +.\" rst2man.py comes from the SBo development/docutils package. +. +.\" note to self: don't forget to check the generated man and html pages +. +.\" into git since we don't want to require our users to have rst2man.py. +. +.SH SYNOPSIS +.sp +cuerecover [\-s sec] \fBbin\-file\fP [\fBbin\-file\fP ...] +.SH DESCRIPTION +.sp +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. +.sp +If a single .bin file is given, it\(aqs assumed to hold all the tracks (which +might only be one). If multiple .bin files are given, each one is assumed to +represent one track of the same CD image. +.sp +For data tracks, the recovered track should be correct, provided the +bin file wasn\(aqt truncated or otherwise corrupted. +.sp +For audio tracks, silence detection is used to find the start of the track +(the INDEX 01 in the .cue file). +.sp +For audio tracks in a single .bin file, silence detection is used to +find the split points between tracks. This means that in cases where +one track segues into another, the two tracks will be combined in the +resulting cue sheet. Also, if there are long periods of silence within +a single track, this track will be split into two or more tracks. +.sp +[*] \fIOnly\fP the .cue file is written to stdout. Status and progress +messages are printed to standard error. +.SH OPTIONS +.INDENT 0.0 +.TP +.B \-\-help +Print short usage string. +.TP +.BI \-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\(aqre already split into tracks. +.TP +.BI \-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\(aqs no pregap. This option can +help with those, but don\(aqt set it too high. +.TP +.B \-v +Verbose mode. Prints (on stderr) some extra messages about what +cuerecover is doing. Probably only of interest to the author. +.UNINDENT +.sp +Always include a space between an option and its argument (e.g. \fB\-s 1\fP, not \fB\-s1\fP). +.SH NOTES +.sp +When reading multiple tracks from the same file, cuerecover makes some +assumptions. These are usually valid, but should be mentioned here: +.INDENT 0.0 +.IP \(bu 2 +The tracks are all MODE1 (data), MODE2 (also data, usually not found) or +CD\-DA (regular CD audio). Extended format CDs like XA or CD+I are not +supported, though you might still get listenable audio tracks from them. +.IP \(bu 2 +If there\(aqs a data track, it will be the only data track, and it will be +track 1. This is almost always the case, since most operating systems +from the CD\-ROM era (and even modern ones) don\(aqt provide access to +data tracks that aren\(aqt the first track on the disc. +.IP \(bu 2 +If there\(aqs a data track, it\(aqs a raw image (2352 bytes per sector, includes +the sync pulse, address, CRC, ECC, etc). If the data track was stored as +\(aqcooked\(aq data (2048 bytes/sector, MODE1/2048 in the original .cue file), +it\(aqll be treated as an audio track. 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 \(aqcooked\(aq. It\(aqll also be obvious if you use the .cue sheet to +extract the image into files: ISO images don\(aqt sound musical at all! +.IP \(bu 2 +cuerecover will generate \fIa\fP cue file, which will be valid... but it may +not match the original (missing) one exactly. This is because cuerecover +has to look for silent sections of the image and use those as split points +for the tracks. If there\(aqs a 3\-second silent section between tracks 2 and +3, is that 1 second of silence at the end of track 2 + 2 seconds of silence +at the start of track 3, or vice versa, or 1.5 seconds each, or...? +.UNINDENT +.\" other sections we might want, uncomment as needed. +. +.\" FILES +. +.\" ===== +. +.\" ENVIRONMENT +. +.\" =========== +. +.SH EXIT STATUS +.sp +As usual, 0 for success, non\-zero for failure. +.\" BUGS +. +.\" ==== +. +.\" EXAMPLES +. +.\" ======== +. +.SH AUTHORS +.sp +cuerecover was written by B. Watson <\fI\%yalhcru@gmail.com\fP> and +released under the WTFPL: Do WTF you want with this. +.SH SEE ALSO +.sp +miragextract(1) +.\" Generated by docutils manpage writer. +. diff --git a/cuerecover.c b/cuerecover.c new file mode 100644 index 0000000..34542d2 --- /dev/null +++ b/cuerecover.c @@ -0,0 +1,311 @@ +/* references: +https://www.gnu.org/software/ccd2cue/manual/html_node/CUE-sheet-format.html +https://wiki.osdev.org/User:Combuster/CDRom_BS +https://en.wikipedia.org/wiki/CD-ROM */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <limits.h> +#include <string.h> +#include <errno.h> + +const char *self; + +#define RAW_SECTOR_SIZE 2352 +#define DATA_SECTOR_SIZE 2048 + +unsigned char mode1_sync[] = { + 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x00 }; + +double silence_sec = 2.0l; +int silence_frames; /* same as above in 1/75 sec frames */ +int silence_thresh = 0; +int multi_bin = 0; +int verbose = 0; + +void die(const char *msg) { + if(!msg) msg = strerror(errno); + fprintf(stderr, "%s: %s\n", self, msg); + exit(1); +} + +void usage() { + fprintf(stderr, "cuerecover v" VERSION " by B. Watson, WTFPL\n"); + fprintf(stderr, "Usage: %s [-s sec] [-t thresh] bin-file [bin-file ...]\n", self); + fprintf(stderr, "See man page for details\n"); + exit(0); +} + +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 0; +} + +const char *frame2time(int frame) { + static char buf[10]; + int min, sec; + + min = frame / (75 * 60); + frame %= (75 * 60); + sec = frame / 75; + frame %= 75; + + sprintf(buf, "%02d:%02d:%02d", min, sec, frame); + + return buf; +} + +int read_sector(FILE *f, char *buf) { + /* + if(verbose) + fprintf(stderr, "read_sector at pos %d\n", ftell(f)); + */ + return fread(buf, RAW_SECTOR_SIZE, 1, f); +} + +int has_mode1_sync(char *buf) { + return (memcmp(buf, mode1_sync, sizeof(mode1_sync)) == 0); +} + +int get_sector_mode(char *buf) { + if(!has_mode1_sync(buf)) + return 0; /* audio, or something we don't grok */ + + return buf[15]; +} + +int unbcd(int bcd) { + int hi, lo; + + lo = bcd & 0x0f; + hi = (bcd >> 4) & 0x0f; + return hi * 10 + lo; +} + +int get_sector_addr(char *buf) { + int mm, ss, ff; + mm = unbcd(buf[12]); + ss = unbcd(buf[13]); + ff = unbcd(buf[14]); + return mm * 75 * 60 + ss * 75 + ff; +} + +const char *mode_string(int mode) { + switch(mode) { + case 1: return "MODE1/2352"; + case 2: return "MODE2/2352"; + default: return "AUDIO"; + } +} + +struct track { + int mode; + int index0; + int index1; +}; + +void process_single(char *filename) { + char buf[RAW_SECTOR_SIZE]; + FILE *f; + int i; + int track = 0; + int sector = 0; + int mode; + int silence_start, in_silence = 0; + struct track tracklist[100]; + struct track *t = tracklist; + + if(!(f = fopen(filename, "rb"))) + die(NULL); + + /* 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(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++; + } + + fclose(f); + + 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)); + } + } +} + +void process_multi(char **filenames) { + int track = 1; + 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++; + } +} + +char **parse_args(int argc, char **argv) { + if(argc == 1) usage(); + if(argc > 1 && strcmp(argv[1], "--help") == 0) usage(); + + while(++argv, --argc) { + if(argv[0][0] == '-') { + char *nextarg = argv[1]; + switch(argv[0][1]) { + case 'v': verbose++; break; + case 's': + if(nextarg && ((*nextarg >= '0' && *nextarg <= '9') || (*nextarg == '.'))) { + silence_sec = atof(nextarg); + argv++, argc--; + } else { + die("-s requires numeric seconds argument"); + } + break; + case 't': + if(nextarg && + (*nextarg >= '0' && *nextarg <= '9') && + (silence_thresh = atoi(nextarg) <= 100)) + { + silence_thresh = (RAW_SECTOR_SIZE / 100.0) * atoi(nextarg); + argv++, argc--; + } else { + die("-t requires numeric percentage argument, 0-100"); + } + break; + default: + fprintf(stderr, "unrecognized option '%s', try --help\n", *argv); + exit(1); + break; + } + } else { + break; + } + } + + silence_frames = (int)(75.0l * silence_sec); + multi_bin = (argc > 1); + return argv; +} + +void set_exe_name(const char *argv0) { + const char *p; + self = argv0; + for(p = self; *p; p++) + if(p[0] == '/' && p[1]) self = p + 1; +} + +int main(int argc, char **argv) { + set_exe_name(argv[0]); + + argv = parse_args(argc, argv); + + if(verbose) fprintf(stderr, "writing %s CUE file to stdout\n", + (multi_bin ? "multi-bin" : "single-bin")); + + if(multi_bin) + process_multi(argv); + else + process_single(*argv); + + return 0; +} diff --git a/cuerecover.html b/cuerecover.html new file mode 100644 index 0000000..3bb5510 --- /dev/null +++ b/cuerecover.html @@ -0,0 +1,462 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" /> +<title>cuerecover</title> +<meta name="date" content="2020-05-09" /> +<style type="text/css"> + +/* +:Author: David Goodger (goodger@python.org) +:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $ +:Copyright: This stylesheet has been placed in the public domain. + +Default cascading style sheet for the HTML output of Docutils. + +See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to +customize this style sheet. +*/ + +/* used to remove borders from tables and images */ +.borderless, table.borderless td, table.borderless th { + border: 0 } + +table.borderless td, table.borderless th { + /* Override padding for "table.docutils td" with "! important". + The right padding separates the table cells. */ + padding: 0 0.5em 0 0 ! important } + +.first { + /* Override more specific margin styles with "! important". */ + margin-top: 0 ! important } + +.last, .with-subtitle { + margin-bottom: 0 ! important } + +.hidden { + display: none } + +.subscript { + vertical-align: sub; + font-size: smaller } + +.superscript { + vertical-align: super; + font-size: smaller } + +a.toc-backref { + text-decoration: none ; + color: black } + +blockquote.epigraph { + margin: 2em 5em ; } + +dl.docutils dd { + margin-bottom: 0.5em } + +object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { + overflow: hidden; +} + +/* Uncomment (and remove this text!) to get bold-faced definition list terms +dl.docutils dt { + font-weight: bold } +*/ + +div.abstract { + margin: 2em 5em } + +div.abstract p.topic-title { + font-weight: bold ; + text-align: center } + +div.admonition, div.attention, div.caution, div.danger, div.error, +div.hint, div.important, div.note, div.tip, div.warning { + margin: 2em ; + border: medium outset ; + padding: 1em } + +div.admonition p.admonition-title, div.hint p.admonition-title, +div.important p.admonition-title, div.note p.admonition-title, +div.tip p.admonition-title { + font-weight: bold ; + font-family: sans-serif } + +div.attention p.admonition-title, div.caution p.admonition-title, +div.danger p.admonition-title, div.error p.admonition-title, +div.warning p.admonition-title, .code .error { + color: red ; + font-weight: bold ; + font-family: sans-serif } + +/* Uncomment (and remove this text!) to get reduced vertical space in + compound paragraphs. +div.compound .compound-first, div.compound .compound-middle { + margin-bottom: 0.5em } + +div.compound .compound-last, div.compound .compound-middle { + margin-top: 0.5em } +*/ + +div.dedication { + margin: 2em 5em ; + text-align: center ; + font-style: italic } + +div.dedication p.topic-title { + font-weight: bold ; + font-style: normal } + +div.figure { + margin-left: 2em ; + margin-right: 2em } + +div.footer, div.header { + clear: both; + font-size: smaller } + +div.line-block { + display: block ; + margin-top: 1em ; + margin-bottom: 1em } + +div.line-block div.line-block { + margin-top: 0 ; + margin-bottom: 0 ; + margin-left: 1.5em } + +div.sidebar { + margin: 0 0 0.5em 1em ; + border: medium outset ; + padding: 1em ; + background-color: #ffffee ; + width: 40% ; + float: right ; + clear: right } + +div.sidebar p.rubric { + font-family: sans-serif ; + font-size: medium } + +div.system-messages { + margin: 5em } + +div.system-messages h1 { + color: red } + +div.system-message { + border: medium outset ; + padding: 1em } + +div.system-message p.system-message-title { + color: red ; + font-weight: bold } + +div.topic { + margin: 2em } + +h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, +h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { + margin-top: 0.4em } + +h1.title { + text-align: center } + +h2.subtitle { + text-align: center } + +hr.docutils { + width: 75% } + +img.align-left, .figure.align-left, object.align-left, table.align-left { + clear: left ; + float: left ; + margin-right: 1em } + +img.align-right, .figure.align-right, object.align-right, table.align-right { + clear: right ; + float: right ; + margin-left: 1em } + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left } + +.align-center { + clear: both ; + text-align: center } + +.align-right { + text-align: right } + +/* reset inner alignment in figures */ +div.align-right { + text-align: inherit } + +/* div.align-center * { */ +/* text-align: left } */ + +.align-top { + vertical-align: top } + +.align-middle { + vertical-align: middle } + +.align-bottom { + vertical-align: bottom } + +ol.simple, ul.simple { + margin-bottom: 1em } + +ol.arabic { + list-style: decimal } + +ol.loweralpha { + list-style: lower-alpha } + +ol.upperalpha { + list-style: upper-alpha } + +ol.lowerroman { + list-style: lower-roman } + +ol.upperroman { + list-style: upper-roman } + +p.attribution { + text-align: right ; + margin-left: 50% } + +p.caption { + font-style: italic } + +p.credits { + font-style: italic ; + font-size: smaller } + +p.label { + white-space: nowrap } + +p.rubric { + font-weight: bold ; + font-size: larger ; + color: maroon ; + text-align: center } + +p.sidebar-title { + font-family: sans-serif ; + font-weight: bold ; + font-size: larger } + +p.sidebar-subtitle { + font-family: sans-serif ; + font-weight: bold } + +p.topic-title { + font-weight: bold } + +pre.address { + margin-bottom: 0 ; + margin-top: 0 ; + font: inherit } + +pre.literal-block, pre.doctest-block, pre.math, pre.code { + margin-left: 2em ; + margin-right: 2em } + +pre.code .ln { color: grey; } /* line numbers */ +pre.code, code { background-color: #eeeeee } +pre.code .comment, code .comment { color: #5C6576 } +pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } +pre.code .literal.string, code .literal.string { color: #0C5404 } +pre.code .name.builtin, code .name.builtin { color: #352B84 } +pre.code .deleted, code .deleted { background-color: #DEB0A1} +pre.code .inserted, code .inserted { background-color: #A3D289} + +span.classifier { + font-family: sans-serif ; + font-style: oblique } + +span.classifier-delimiter { + font-family: sans-serif ; + font-weight: bold } + +span.interpreted { + font-family: sans-serif } + +span.option { + white-space: nowrap } + +span.pre { + white-space: pre } + +span.problematic { + color: red } + +span.section-subtitle { + /* font-size relative to parent (h1..h6 element) */ + font-size: 80% } + +table.citation { + border-left: solid 1px gray; + margin-left: 1px } + +table.docinfo { + margin: 2em 4em } + +table.docutils { + margin-top: 0.5em ; + margin-bottom: 0.5em } + +table.footnote { + border-left: solid 1px black; + margin-left: 1px } + +table.docutils td, table.docutils th, +table.docinfo td, table.docinfo th { + padding-left: 0.5em ; + padding-right: 0.5em ; + vertical-align: top } + +table.docutils th.field-name, table.docinfo th.docinfo-name { + font-weight: bold ; + text-align: left ; + white-space: nowrap ; + padding-left: 0 } + +/* "booktabs" style (no vertical lines) */ +table.docutils.booktabs { + border: 0px; + border-top: 2px solid; + border-bottom: 2px solid; + border-collapse: collapse; +} +table.docutils.booktabs * { + border: 0px; +} +table.docutils.booktabs th { + border-bottom: thin solid; + text-align: left; +} + +h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, +h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { + font-size: 100% } + +ul.auto-toc { + list-style-type: none } + +</style> +</head> +<body> +<div class="document" id="cuerecover"> +<h1 class="title">cuerecover</h1> +<h2 class="subtitle" id="generate-cue-file-for-cd-image-bin">generate .cue file for CD image .bin</h2> +<table class="docinfo" frame="void" rules="none"> +<col class="docinfo-name" /> +<col class="docinfo-content" /> +<tbody valign="top"> +<tr class="manual-section field"><th class="docinfo-name">Manual section:</th><td class="field-body">1</td> +</tr> +<tr class="manual-group field"><th class="docinfo-name">Manual group:</th><td class="field-body">Urchlay</td> +</tr> +<tr><th class="docinfo-name">Date:</th> +<td>2020-05-09</td></tr> +<tr><th class="docinfo-name">Version:</th> +<td>0.1.0</td></tr> +</tbody> +</table> +<!-- RST source for cuerecover(1) man page. Convert with: --> +<!-- rst2man.py cuerecover.rst > cuerecover.1 --> +<!-- rst2man.py comes from the SBo development/docutils package. --> +<!-- note to self: don't forget to check the generated man and html pages --> +<!-- into git since we don't want to require our users to have rst2man.py. --> +<div class="section" id="synopsis"> +<h1>SYNOPSIS</h1> +<p>cuerecover [-s sec] <strong>bin-file</strong> [<strong>bin-file</strong> ...]</p> +</div> +<div class="section" id="description"> +<h1>DESCRIPTION</h1> +<p>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.</p> +<p>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 +represent one track of the same CD image.</p> +<p>For data tracks that hold ISO9660, UFS, or Macintosh HFS filesystems, +the recovered track should be correct, provided the .bin file wasn't +truncated or otherwise corrupted.</p> +<p>For audio tracks, silence detection is used to find the split points +between tracks. This means that in cases where one track segues into +another, the two tracks will be combined in the resulting cue sheet. Also, +if there are long periods of silence within a single track, this track +will be split into two or more tracks.</p> +</div> +<div class="section" id="options"> +<h1>OPTIONS</h1> +<table class="docutils option-list" frame="void" rules="none"> +<col class="option" /> +<col class="description" /> +<tbody valign="top"> +<tr><td class="option-group"> +<kbd><span class="option">--help</span></kbd></td> +<td>Print short usage string.</td></tr> +<tr><td class="option-group"> +<kbd><span class="option">-a</span></kbd></td> +<td>Assume all tracks are audio. Disable data track detection. Trying +to listen to a data track as audio will usually give you a +headache.</td></tr> +<tr><td class="option-group"> +<kbd><span class="option">-d</span></kbd></td> +<td>Only include data track(s) in the .cue file.</td></tr> +<tr><td class="option-group"> +<kbd><span class="option">-s</span></kbd></td> +<td>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. 2. This option is ignored when multiple .bin file arguments +are given, since they're already split into tracks.</td></tr> +</tbody> +</table> +<p>Always include a space between an option and its argument (e.g. <strong>-s 1</strong>, not <strong>-s1</strong>).</p> +<!-- NOTES --> +<!-- ===== --> +<!-- other sections we might want, uncomment as needed. --> +<!-- FILES --> +<!-- ===== --> +<!-- ENVIRONMENT --> +<!-- =========== --> +</div> +<div class="section" id="exit-status"> +<h1>EXIT STATUS</h1> +<p>As usual, 0 for success, non-zero for failure.</p> +<!-- BUGS --> +<!-- ==== --> +<!-- EXAMPLES --> +<!-- ======== --> +</div> +<div class="section" id="authors"> +<h1>AUTHORS</h1> +<p>cuerecover was written by B. Watson <<a class="reference external" href="mailto:yalhcru@gmail.com">yalhcru@gmail.com</a>> and +released under the WTFPL: Do WTF you want with this.</p> +</div> +<div class="section" id="see-also"> +<h1>SEE ALSO</h1> +<p>miragextract(1)</p> +</div> +</div> +</body> +</html> diff --git a/cuerecover.rst b/cuerecover.rst new file mode 100644 index 0000000..d4d932b --- /dev/null +++ b/cuerecover.rst @@ -0,0 +1,139 @@ +.. RST source for cuerecover(1) man page. Convert with: +.. rst2man.py cuerecover.rst > cuerecover.1 +.. rst2man.py comes from the SBo development/docutils package. + +.. note to self: don't forget to check the generated man and html pages +.. into git since we don't want to require our users to have rst2man.py. + +.. |version| replace:: 0.1.0 +.. |date| date:: + +========== +cuerecover +========== + +------------------------------------ +generate .cue file for CD image .bin +------------------------------------ + +:Manual section: 1 +:Manual group: Urchlay +:Date: |date| +:Version: |version| + +SYNOPSIS +======== + +cuerecover [-s sec] **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. + +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 +represent one track of the same CD image. + +For data tracks, the recovered track should be correct, provided the +bin file wasn't truncated or otherwise corrupted. + +For audio tracks, silence detection is used to find the start of the track +(the INDEX 01 in the .cue file). + +For audio tracks in a single .bin file, silence detection is used to +find the split points between tracks. This means that in cases where +one track segues into another, the two tracks will be combined in the +resulting cue sheet. Also, if there are long periods of silence within +a single track, this track will be split into two or more tracks. + +[*] *Only* the .cue file is written to stdout. Status and progress +messages are printed to standard error. + +OPTIONS +======= + +--help + Print short usage string. + +-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. + +-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. + +-v Verbose mode. Prints (on stderr) some extra messages about what + cuerecover is doing. Probably only of interest to the author. + +Always include a space between an option and its argument (e.g. **-s 1**, not **-s1**). + +NOTES +===== + +When reading multiple tracks from the same file, cuerecover makes some +assumptions. These are usually valid, but should be mentioned here: + +- The tracks are all MODE1 (data), MODE2 (also data, usually video CDs) or + CD-DA (regular CD audio). Extended format CDs like XA or CD+I are not + supported, though you might still get listenable audio tracks from them. + +- If there's a data track, it will be the only data track, and it will be + track 1. This is almost always the case, since most operating systems + from the CD-ROM era (and even modern ones) don't provide access to + data tracks that aren't the first track on the disc. + +- 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 + 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! + +- cuerecover will generate *a* cue file, which will be valid... but it may + not match the original (missing) one exactly. This is because cuerecover + has to look for silent sections of the image and use those as split points + for the tracks. If there's a 3-second silent section between tracks 2 and + 3, is that 1 second of silence at the end of track 2 + 2 seconds of silence + at the start of track 3, or vice versa, or 1.5 seconds each, or...? + +.. other sections we might want, uncomment as needed. + +.. FILES +.. ===== + +.. ENVIRONMENT +.. =========== + +EXIT STATUS +=========== + +As usual, 0 for success, non-zero for failure. + +.. BUGS +.. ==== + +.. EXAMPLES +.. ======== + +AUTHORS +======= + +cuerecover was written by B. Watson <yalhcru@gmail.com> and +released under the WTFPL: Do WTF you want with this. + +SEE ALSO +======== + +miragextract(1) |