// $Id: maketoc.pgcc 3879 2011-03-29 01:07:35Z flaterco $ // maketoc: create one or more toc files (as needed) to burn a // collection of 44.1 kHz wav files to CD using cdrdao. Track titles // are stored in and retrieved from a PostgreSql table. Files are // identified by hash, so you can rename them as much as you want and // maketoc won't forget their titles. // Requires libdstr, libmhash, and PostgreSql. // One-time configuration in default PostgreSql database: // CREATE TABLE MAKETOC (HASH TEXT, TITLE TEXT, PERFORMER TEXT); // ecpg -I/usr/local/pgsql/include -o maketoc.cc maketoc.pgcc // g++ -I/usr/local/pgsql/include -L/usr/local/pgsql/lib -Wall -Wextra -Wno-long-long -pedantic -O2 -s -o maketoc maketoc.cc -lmhash -lecpg -lpq -ldstr // Generated toc files are used with cdrdao as follows: // cdrdao write --device /dev/cdrom --overburn --driver generic-mmc:0x10 disc1.toc // cdrdao write --device /dev/cdrom --overburn --driver generic-mmc:0x10 disc2.toc ... // The 0x10 option is necessary to enable CD_TEXT writing. // Add --speed 16 or whatever if desired. // (C) 2006 David Flater. // 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. #include #include #include #include #include #include #include #include #include #include #include // Size of hashing buffer in bytes. // 0x100000 = 1 MiB #define hashbufsize 0x100000 EXEC SQL INCLUDE sqlca; void check_err () { if (sqlca.sqlcode) { fprintf (stderr, "%s\n", sqlca.sqlerrm.sqlerrmc); if (sqlca.sqlcode < 0) { EXEC SQL ROLLBACK; exit (-1); } } } // Based on function of same name in ta version 1.2.1. // in filename: name of a regular file (not a directory, not a symlink). // out shash: stringified SHA-512 hash of that file (128 characters). void getHash (const Dstr &filename, Dstr &shash) { { struct stat s; if (lstat (filename.aschar(), &s) == 0) { if (!S_ISREG (s.st_mode)) { fprintf (stderr, "getHash called on something not a regular file: %s\n", filename.aschar()); exit (-1); } } else { perror (filename.aschar()); // lstat failed exit (-1); } } assert (mhash_get_block_size (MHASH_SHA512) == 64); unsigned char hash[64]; { FILE *fp = fopen (filename.aschar(), "r"); if (!fp) { perror (filename.aschar()); exit (-1); } MHASH td = mhash_init (MHASH_SHA512); if (td == MHASH_FAILED) { fprintf (stderr, "Mhash failed to initialize\n"); exit (-1); } // The return value of mhash is undocumented, but in the sources // it is hard-coded to always return 0. unsigned char buffer[hashbufsize]; size_t nread; while ((nread = fread (buffer, 1, hashbufsize, fp))) mhash (td, buffer, nread); if (ferror (fp)) { fprintf (stderr, "Error reading from %s\n", filename.aschar()); fclose (fp); exit (-1); } fclose (fp); mhash_deinit (td, hash); } shash = ""; char buf[3]; for (unsigned i=0; i<64; ++i) { sprintf (buf, "%.2X", hash[i]); shash += buf; } } // From wavlength.cc 750 // Return length of wav file in seconds. double lengthofwav (char *fname) { struct stat s; if (stat (fname, &s) != 0) { perror (fname); exit (-1); } if (s.st_size < 44) { fprintf (stderr, "%s: too short to be a wav file\n", fname); exit (-1); } // wav files from sox and normalize have 44 bytes of header. // 1 second = 44100 samples * 2 channels * 16 bits = 176400 bytes. return (double)(s.st_size - 44) / 176400.0; } void getTitle (const Dstr &fname, Dstr &title, Dstr &performer) { EXEC SQL BEGIN DECLARE SECTION; char *t, *p, *h; EXEC SQL END DECLARE SECTION; Dstr shash; getHash (fname, shash); h = shash.aschar(); t = p = NULL; EXEC SQL SELECT TITLE, PERFORMER INTO :t, :p FROM MAKETOC WHERE HASH = :h; if (sqlca.sqlcode == 100) { Dstr prompt ("Title for "); prompt += fname; title.pruser (prompt.aschar(), "Unknown title"); t = title.aschar(); prompt = "Performer for "; prompt += fname; performer.pruser (prompt.aschar(), "Unknown"); p = performer.aschar(); EXEC SQL INSERT INTO MAKETOC VALUES (:h, :t, :p); check_err(); EXEC SQL COMMIT; check_err(); } else { check_err(); title = t; free (t); performer = p; free (p); } } static unsigned long snum = 0; void begintoc (FILE *&fp) { Dstr fname ("disc"); fname += ++snum; fname += ".toc"; fp = fopen (fname.aschar(), "w"); if (!fp) { perror (fname.aschar()); exit (-1); } fprintf (fp, "\ CD_DA\n\ CD_TEXT {\n\ LANGUAGE_MAP {\n\ 0: 9\n\ }\n\ LANGUAGE 0 {\n\ TITLE \"Disc %lu\"\n\ PERFORMER \"Various\"\n\ }\n\ }\n", snum); } void addtocfile (FILE *fp, const Dstr &fname, double length_s) { Dstr title, performer; getTitle (fname, title, performer); title.repstr ("\"", "\\\""); performer.repstr ("\"", "\\\""); fprintf (fp, "\ \n\ TRACK AUDIO\n\ COPY\n\ NO PRE_EMPHASIS\n\ TWO_CHANNEL_AUDIO\n\ CD_TEXT {\n\ LANGUAGE 0 {\n\ TITLE \"%s\"\n\ PERFORMER \"%s\"\n\ }\n\ }\n\ FILE \"%s\" 0\n\ // Track length %02lu:%05.2f\n", title.aschar(), performer.aschar(), fname.aschar(), (unsigned long)(length_s/60), fmod(length_s,60.0)); } void endtoc (FILE *fp, double length_s) { fprintf (stderr, "Disc %lu length %02lu:%05.2f\n", snum, (unsigned long)(length_s/60), fmod(length_s,60.0)); fprintf (fp, "\n// CD length %02lu:%05.2f\n", (unsigned long)(length_s/60), fmod(length_s,60.0)); fclose (fp); } int main (int argc, char **argv) { if (argc < 2) { fprintf (stderr, "Usage: maketoc file.wav [file.wav...]\n"); fprintf (stderr, "Writes output to disc1.toc, disc2.toc...\n"); exit (-1); } EXEC SQL CONNECT TO DEFAULT; check_err(); double len = 0.0; FILE *fp; begintoc (fp); for (int i=1; i 80.5 * 60) { endtoc (fp, len); begintoc (fp); len = 0.0; } len += thislen; addtocfile (fp, argv[i], thislen); } endtoc (fp, len); return 0; }