diff --git a/utils/toc2mp3.cc b/utils/toc2mp3.cc index 1b22b79..5b221e5 100644 --- a/utils/toc2mp3.cc +++ b/utils/toc2mp3.cc @@ -143,8 +143,8 @@ static void printUsage() message(0, "LAME encoder version: %s", get_lame_version()); message(0, "Supported bit rates: "); - for (int i = 0; i < 16 && bitrate_table[1][i] >= 0; i++) { - message(0, "%d ", bitrate_table[1][i]); + for (int br, i = 0; (br = lame_get_bitrate(1, i)) >= 0; i++) { + message(0, "%d ", br); } message(0, ""); } @@ -238,8 +238,8 @@ lame_global_flags *init_encoder(int bitrate) lame_global_flags *lf; int bitrateOk = 0; - for (int i = 0; bitrate_table[1][i] >= 0 && !bitrateOk; i++) { - if (bitrate == bitrate_table[1][i]) + for (int br, i = 0; (br = lame_get_bitrate(1, i)) >= 0 && !bitrateOk; i++) { + if (br == bitrate) bitrateOk = 1; } diff --git a/utils/toc2mp3.cc.lame b/utils/toc2mp3.cc.lame new file mode 100644 index 0000000..1b22b79 --- /dev/null +++ b/utils/toc2mp3.cc.lame @@ -0,0 +1,654 @@ +/* toc2mp3 - encodes a audio CD disk image to mp3 files for each track + * + * Copyright (C) 2002 Andreas Mueller + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_GETOPT_H +#include +#endif +#include +#include +#include +#include + +#include + +#include "util.h" +#include "Toc.h" +#include "CdTextItem.h" + +// set desired default bit rate for encoding here: +#define DEFAULT_ENCODER_BITRATE 192 + +static const char *PRGNAME = NULL; +static int VERBOSE = 1; +static int CREATE_ALBUM_DIRECTORY = 0; +static std::string TARGET_DIRECTORY; + + +void message_args(int level, int addNewLine, const char *fmt, va_list args) +{ + long len = strlen(fmt); + char last = len > 0 ? fmt[len - 1] : 0; + + if (level < 0) { + switch (level) { + case -1: + fprintf(stderr, "WARNING: "); + break; + case -2: + fprintf(stderr, "ERROR: "); + break; + case -3: + fprintf(stderr, "INTERNAL ERROR: "); + break; + default: + fprintf(stderr, "FATAL ERROR: "); + break; + } + vfprintf(stderr, fmt, args); + if (addNewLine) { + if (last != ' ' && last != '\r') + fprintf(stderr, "\n"); + } + + fflush(stderr); + if (level <= -10) + exit(1); + } + else if (level <= VERBOSE) { + vfprintf(stderr, fmt, args); + + if (addNewLine) { + if (last != ' ' && last != '\r') + fprintf(stderr, "\n"); + } + + fflush(stderr); + } +} + +void message(int level, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + + message_args(level, 1, fmt, args); + + va_end(args); +} + +void lame_message(const char *fmt, va_list args) +{ + message_args(1, 0, fmt, args); +} + +void lame_error_message(const char *fmt, va_list args) +{ + message_args(-2, 0, fmt, args); +} + + +static void printVersion() +{ + message(1, "toc2mp3 version %s - (C) Andreas Mueller ", + VERSION); + message(1, ""); +} + +static void printUsage() +{ + message(0, "Usage: %s [-v #] [-d target-dir ] [-c] { -V | toc-file }", PRGNAME); + message(0, "\nConverts an audio CD disk image (.toc file) to mp3 files."); + message(0, "Each track will be written to a separate mp3 file."); + message(0, "Special care is taken that the mp3 files can be played in sequence"); + message(0, "without having unwanted noise at the transition points."); + message(0, "CD-TEXT information (if available) is used to set ID3 (v2) tags and to"); + message(0, "construct the name of the mp3 files.\n"); + message(0, "Options:"); + message(0, " -h Shows this help."); + message(0, " -v Sets verbose level to (0..2)."); + message(0, " -d Specifies directory the mp3 files will be"); + message(0, " written to."); + message(0, " -c Adds a sub-directory composed out of CD title"); + message(0, " and author to specified with -d."); + message(0, " -b Sets bit rate used for encoding (default %d kbit/s).", + DEFAULT_ENCODER_BITRATE); + message(0, " See below for supported bit rates."); + + message(0, ""); + + message(0, "LAME encoder version: %s", get_lame_version()); + message(0, "Supported bit rates: "); + for (int i = 0; i < 16 && bitrate_table[1][i] >= 0; i++) { + message(0, "%d ", bitrate_table[1][i]); + } + message(0, ""); +} + +static int parseCommandLine(int argc, char **argv, char **tocFile, int *bitrate) +{ + int c; + int printVersion = 0; + extern char *optarg; + extern int optind, opterr, optopt; + + opterr = 0; + + while ((c = getopt(argc, argv, "Vhcv:d:b:")) != EOF) { + switch (c) { + case 'V': + printVersion = 1; + break; + + case 'v': + if (optarg != NULL) { + if ((VERBOSE = atoi(optarg)) < 0) { + message(-2, "Invalid verbose level: %s", optarg); + return 0; + } + } + else { + message(-2, "Missing verbose level after option '-v'."); + return 0; + } + break; + + case 'b': + if (optarg != NULL) { + *bitrate = atoi(optarg); + } + else { + message(-2, "Missing bit rate value after option '-b'."); + return 0; + } + break; + + case 'c': + CREATE_ALBUM_DIRECTORY = 1; + break; + + case 'h': + return 0; + break; + + case 'd': + if (optarg != NULL) { + TARGET_DIRECTORY = optarg; + } + else { + message(-2, "Missing target directory after option '-d'."); + return 0; + } + break; + + case '?': + message(-2, "Invalid option: %c", optopt); + return 0; + break; + } + } + + if (printVersion) { + return 1; + } + + if (optind < argc) { + *tocFile = strdupCC(argv[optind]); + optind++; + } + else { + message(-2, "Missing toc-file name."); + return 0; + } + + if (optind != argc) { + message(-2, "More arguments than expected."); + return 0; + } + + return 2; +} + +lame_global_flags *init_encoder(int bitrate) +{ + lame_global_flags *lf; + int bitrateOk = 0; + + for (int i = 0; bitrate_table[1][i] >= 0 && !bitrateOk; i++) { + if (bitrate == bitrate_table[1][i]) + bitrateOk = 1; + } + + if (!bitrateOk) { + message(-2, "Invalid bit rate: %d kbit/s", bitrate); + return NULL; + } + + if ((lf = lame_init()) == NULL) { + return NULL; + } + + lame_set_msgf(lf, lame_message); + lame_set_debugf(lf, lame_message); + lame_set_errorf(lf, lame_error_message); + + lame_set_in_samplerate(lf, 44100); + + lame_set_num_channels(lf, 2); + + lame_set_quality(lf, 2); + + lame_set_mode(lf, STEREO); + + lame_set_brate(lf, bitrate); + + //lame_set_VBR(lf, vbr_abr); + + //lame_set_VBR(lf, vbr_mtrh); + //lame_set_VBR_q(lf, 2); + + //lame_set_VBR_mean_bitrate_kbps(lf, bitrate); + //lame_set_VBR_min_bitrate_kbps(lf, 112); + //lame_set_VBR_hard_min(lf, 1); + + //lame_set_bWriteVbrTag(lf, 1); + + //lame_set_asm_optimizations(lf, AMD_3DNOW, 1); + + return lf; +} + +void set_id3_tags(lame_global_flags *lf, int tracknr, const std::string &title, + const std::string &artist, const std::string &album) +{ + char buf[100]; + + id3tag_init(lf); + + id3tag_add_v2(lf); + + if (!title.empty()) + id3tag_set_title(lf, title.c_str()); + + if (!artist.empty()) + id3tag_set_artist(lf, artist.c_str()); + + if (!album.empty()) + id3tag_set_album(lf, album.c_str()); + + if (tracknr > 0 && tracknr <= 255) { + sprintf(buf, "%d", tracknr); + id3tag_set_track(lf, buf); + } +} + +int encode_track(lame_global_flags *lf, const Toc *toc, + const std::string &fileName, long startLba, + long len) +{ + int fd; + int ret = 1; + TocReader reader(toc); + Sample audioData[SAMPLES_PER_BLOCK]; + short int leftSamples[SAMPLES_PER_BLOCK]; + short int rightSamples[SAMPLES_PER_BLOCK]; + unsigned char mp3buffer[LAME_MAXMP3BUFFER]; + + if (reader.openData() != 0) { + message(-2, "Cannot open audio data."); + return 0; + } + + if (reader.seekSample(Msf(startLba).samples()) != 0) { + message(-2, "Cannot seek to start sample of track."); + return 0; + } + + if ((fd = open(fileName.c_str(), O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) { + message(-2, "Cannot open \"%s\" for writing: %s", fileName.c_str(), + strerror(errno)); + return 0; + } + + while (len > 0) { + if (reader.readSamples(audioData, SAMPLES_PER_BLOCK) != SAMPLES_PER_BLOCK) { + message(-2, "Cannot read audio data."); + ret = 0; + break; + } + + for (int i = 0; i < SAMPLES_PER_BLOCK; i++) { + leftSamples[i] = audioData[i].left(); + rightSamples[i] = audioData[i].right(); + } + + int count = lame_encode_buffer(lf, leftSamples, rightSamples, + SAMPLES_PER_BLOCK, mp3buffer, + sizeof(mp3buffer)); + + if (count < 0) { + message(-2, "Lame encoder failed: %d", count); + ret = 0; + break; + } + + if (count > 0) { + if (fullWrite(fd, mp3buffer, count) != count) { + message(-2, "Failed to write encoded data: %s", strerror(errno)); + ret = 0; + break; + } + } + + len--; + } + + if (ret != 0) { + int count = lame_encode_flush_nogap(lf, mp3buffer, sizeof(mp3buffer)); + + if (count > 0) { + if (fullWrite(fd, mp3buffer, count) != count) { + message(-2, "Failed to write encoded data: %s", strerror(errno)); + ret = 0; + } + } + } + + if (close(fd) != 0) { + message(-2, "Failed to close encoded data file: %s", strerror(errno)); + ret = 0; + } + + if (ret != 0) { + FILE *fp = fopen(fileName.c_str(), "a+"); + + if (fp != NULL) { + lame_mp3_tags_fid(lf, fp); + fclose(fp); + } + else { + message(-2, "Cannot reopen output file for adding headers: %s", + strerror(errno)); + ret = 0; + } + } + + return ret; +} + +std::string &clean_string(std::string &s) +{ + int i = 0; + int len = s.length(); + char c; + + while (i < len && (c = s[i]) != 0) { + if (c == '_') { + s[i] = ' '; + i++; + } + else if (isalnum(c) || c == ' ' || c == '.' || c== '-' || c == '(' || + c == ')' || c == ')' || c == ',' || c == ':' || c == ';' || + c == '"' || c == '!' || c == '?' || c == '\'' || c == '$') { + i++; + } + else { + s.erase(i, 1); + len = s.length(); + } + } + + len = s.length(); + + for (i = 0; i < len && s[i] != 0 && isspace(s[i]); i++) ; + + if (i >= len) { + // string just contains space + s = ""; + } + + return s; +} + +int main(int argc, char **argv) +{ + char *tocFile; + int bitrate = DEFAULT_ENCODER_BITRATE; + Toc *toc; + lame_global_flags *lf; + std::string album, albumPerformer, title, performer; + int cdTextLanguage = 0; + const CdTextItem *cdTextItem; + char *tocfileBaseName, *p; + char sbuf[100]; + int err = 0; + + PRGNAME = *argv; + + switch (parseCommandLine(argc, argv, &tocFile, &bitrate)) { + case 0: + printUsage(); + exit(1); + break; + + case 1: + printf("%s\n", VERSION); + exit(0); + break; + } + + printVersion(); + + if ((toc = Toc::read(tocFile)) == NULL) { + message(-10, "Failed to read toc-file '%s'.", tocFile); + } + + if ((lf = init_encoder(bitrate)) == NULL) { + message(-10, "Cannot initialize lame encoder"); + } + + + if ((p = strrchr(tocFile, '/')) != NULL) + tocfileBaseName = strdupCC(p + 1); + else + tocfileBaseName = strdupCC(tocFile); + + if ((p = strrchr(tocfileBaseName, '.')) != NULL && + (strcmp(p, ".toc") == 0 || strcmp(p, ".cue") == 0)) { + *p = 0; + } + + if (strlen(tocfileBaseName) == 0) { + delete[] tocfileBaseName; + tocfileBaseName = strdupCC("unknown"); + } + + if ((cdTextItem = toc->getCdTextItem(0, cdTextLanguage, + CdTextItem::CDTEXT_TITLE)) != NULL) { + album = (const char*)cdTextItem->data(); + clean_string(album); + if (album.empty()) + album = tocfileBaseName; + } + else { + album = tocfileBaseName; + } + + if ((cdTextItem = toc->getCdTextItem(0, cdTextLanguage, + CdTextItem::CDTEXT_PERFORMER)) != NULL) { + albumPerformer = (const char*)cdTextItem->data(); + clean_string(albumPerformer); + } + else { + albumPerformer = ""; + } + + + std::string mp3TargetDir; + + if (!TARGET_DIRECTORY.empty()) { + mp3TargetDir = TARGET_DIRECTORY; + + if (*(TARGET_DIRECTORY.end() - 1) != '/') + mp3TargetDir += "/"; + + if (CREATE_ALBUM_DIRECTORY) { + if (!album.empty() && !albumPerformer.empty()) { + mp3TargetDir += albumPerformer; + mp3TargetDir += "_"; + mp3TargetDir += album; + } + else { + mp3TargetDir += tocfileBaseName; + } + + if (mkdir(mp3TargetDir.c_str(), 0777) != 0) { + message(-10, "Cannot create album directory \"%s\": %s", + mp3TargetDir.c_str(), strerror(errno)); + } + + mp3TargetDir += "/"; + } + } + + + Msf astart, aend, nstart, nend; + const Track *actTrack, *nextTrack; + int trackNr; + TrackIterator titr(toc); + int firstEncodedTrack = 1; + + trackNr = 1; + actTrack = titr.first(astart, aend); + nextTrack = titr.next(nstart, nend); + + while (actTrack != NULL && err == 0) { + + if (actTrack->type() == TrackData::AUDIO) { + + // Retrieve CD-TEXT data for track title and performer + if ((cdTextItem = toc->getCdTextItem(trackNr, cdTextLanguage, + CdTextItem::CDTEXT_TITLE)) != NULL) { + title = (const char*)cdTextItem->data(); + clean_string(title); + } + else { + title = ""; + } + + if ((cdTextItem = toc->getCdTextItem(trackNr, cdTextLanguage, + CdTextItem::CDTEXT_PERFORMER)) != NULL) { + performer = (const char*)cdTextItem->data(); + clean_string(performer); + } + else { + performer = ""; + } + + // build mp3 file name + std::string mp3FileName; + + sprintf(sbuf, "%02d_", trackNr); + + mp3FileName += sbuf; + + if (!title.empty()) { + mp3FileName += title; + mp3FileName += "_"; + } + + mp3FileName += album; + + if (!albumPerformer.empty()) { + mp3FileName += "_"; + mp3FileName += albumPerformer; + } + + mp3FileName += ".mp3"; + + + long len = aend.lba() - astart.lba(); + + if (nextTrack != NULL && nextTrack->type() == TrackData::AUDIO) + len += nextTrack->start().lba(); + + if (len > 0) { + set_id3_tags(lf, trackNr, title, performer, album); + + if (firstEncodedTrack) { + if (lame_init_params(lf) < 0) { + message(-2, "Setting of lame parameters failed"); + err = 1; + break; + } + message(1, "Lame encoder settings:"); + lame_print_config(lf); + message(1, "Selected bit rate: %d kbit/s", bitrate); + + if (VERBOSE >= 2) + lame_print_internals(lf); + + message(1, ""); + + + message(1, "Starting encoding to target directory \"%s\"...", + mp3TargetDir.empty() ? "." : mp3TargetDir.c_str()); + + firstEncodedTrack = 0; + } + else { + if (lame_init_bitstream(lf) != 0) { + message(-2, "Cannot initialize bit stream."); + err = 1; + break; + } + } + + message(1, "Encoding track %d to \"%s\"...", trackNr, + mp3FileName.c_str()); + + if (!encode_track(lf, toc, mp3TargetDir + mp3FileName, astart.lba(), len)) { + message(-2, "Encoding of track %d failed.", trackNr); + err = 1; + break; + } + } + } + + actTrack = nextTrack; + astart = nstart; + aend = nend; + trackNr++; + + if (actTrack != NULL) + nextTrack = titr.next(nstart, nend); + } + + lame_close(lf); + + exit(err); +}