/* toc2mp3 - encodes a audio CD disk image to mp3 files for each track
*
* Copyright (C) 2002 Andreas Mueller <andreas@daneb.de>
*
* 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 <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdarg.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <lame/lame.h>
#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 <andreas@daneb.de>",
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 <n> Sets verbose level to <n> (0..2).");
message(0, " -d <target-dir> 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 <target-dir> specified with -d.");
message(0, " -b <bit rate> 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);
}
syntax highlighted by Code2HTML, v. 0.9.1