1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
|
/*
* cdtooldb2cddb.c
* convert .cdtooldb to cddb files
* Copyright (C) 2000 Peter Samuelson
*
* 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
* v0.1 10 Dec 2000 initial public release
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_GETOPT
# include <getopt.h>
#endif
#define PROGNAME "cdtool2cddb"
#define PROGVERSION "0.1"
#define MAXTRKLEN 1024
#define MAXTRACKS 256
struct cdbuf {
unsigned int id;
int length, tracks;
int offset[MAXTRACKS+1];
char title[MAXTRKLEN];
char artist[MAXTRKLEN];
char track[MAXTRACKS][MAXTRKLEN];
};
/********** options **********/
char *report_submitter = PROGNAME " " PROGVERSION; /* tell the truth... */
#define VERSION_CDDB "xmcd 2.6 PL0" /* lie */
#define OUTFILE_FORMAT "%s/%08x" /* for xmcd, groovycd */
char *crlf = "\n"; /* groovycd seems to prefer "\r\n" */
char *outdir = "."; /* output directory */
int cddb_max_line = 76; /* the official limit (?) */
int verbose = 0;
/* chompsp: inspired by Perl chomp. Kills all trailing space. */
static void chompsp (char *str)
{
int l = strlen (str) - 1;
while (l >= 0 && isspace (str[l]))
l--;
str[l+1] = '\0';
}
/*
* parse .cdtooldb entry into struct cdbuf
* This does not calculate the checksum because I figured that
* is more "processing" than "input"
* return 0 on EOF or parse error, 1 if successful
*/
int read_cdtooldb(FILE *in, struct cdbuf *b)
{
int i, trk = 0;
static char buf[1024];
memset (b, 0, sizeof (*b));
if (!buf[0])
fgets (buf, 1024, in);
while (1) {
if (!buf[0] || isspace(buf[0])) {
if (b->tracks || feof (in))
break;
}
else if (!strncmp (buf, "tracks ", 7)) {
char *p = buf+7;
if (b->tracks)
break;
while (*p && isspace(*p)) p++;
b->tracks = atoi (p);
if (b->tracks <= 0 || b->tracks > MAXTRACKS)
return 0;
for (i=0; i<=b->tracks; i++) { /* fencepost, but on purpose */
while (*p && !isspace(*p)) p++;
while (*p && isspace(*p)) p++;
if (!*p) /* not enough space-separated numbers */
return 0;
b->offset[i] = atoi (p);
if (b->offset[i] <= 0)
return 0;
}
b->length = b->offset[--i];
b->offset[i] = 0;
} else if (!strncmp (buf, "cdname ", 7)) {
chompsp (buf+7);
if (MAXTRKLEN < strlen(b->title) + strlen(buf+7) + 1)
return 0;
strcat (b->title, buf+7);
} else if (!strncmp (buf, "artist ", 7)) {
chompsp (buf+7);
if (MAXTRKLEN < strlen(b->artist) + strlen(buf+7) + 1)
return 0;
strcat (b->artist, buf+7);
} else if (!strncmp (buf, "track ", 6)) {
if (trk == b->tracks) /* too many track labels */
return 0;
chompsp (buf+6);
strncpy (b->track[trk], buf+6, MAXTRKLEN-1);
b->track[trk++][MAXTRKLEN-1] = 0;
} else
return 0;
buf[0] = 0;
fgets (buf, 1024, in);
}
if (!b->tracks || !b->offset[b->tracks-1] || !b->length)
return 0;
return 1;
}
/*
* Output a string, with a given prefix, with a given max line length
* repeat the prefix at the beginning of each line
* Needed for proper line wrap in put_cddb().
*/
static void fputstring (FILE *f, int linelen, char *prefix, char *fmt, ...)
{
char buf[1024];
int buflen, perline, l = 0;
va_list ap;
va_start (ap, fmt);
buflen = vsnprintf (buf, sizeof(buf)-1, fmt, ap);
va_end (ap);
if (buflen > sizeof(buf)-1)
fprintf(stderr, "warning: truncated fputstring\n");
buf[sizeof(buf)-1] = 0;
buflen = strlen(buf);
perline = linelen - strlen (prefix);
for (l=0; l < buflen; l += perline) {
char *outbuf = malloc(strlen(prefix) + perline + 1);
strcpy (outbuf, prefix);
strncat (outbuf, buf+l, perline);
outbuf[linelen] = 0;
fprintf (f, "%s%s", outbuf, crlf);
free(outbuf);
}
}
/*
* put_cddb: output the struct cdbuf in CDDB format
* return value currently ignored
*/
int put_cddb (FILE *out, struct cdbuf *b)
{
int i;
fprintf (out,
"# xmcd CD database file%s"
"#%s"
"# Track frame offsets:%s",
crlf, crlf, crlf);
for (i=0; i<b->tracks; i++)
fprintf (out, "#\t%d%s", b->offset[i], crlf);
fprintf (out,
"#%s"
"# Disc length: %d seconds%s"
"#%s"
"# Revision: 1%s"
"# Submitted via: %s%s"
"#%s"
"DISCID=%08x%s",
crlf, b->length, crlf, crlf, crlf,
report_submitter, crlf, crlf, b->id, crlf);
fputstring (out, cddb_max_line, "DTITLE=", "%s / %s", b->artist, b->title);
for (i=0; i<b->tracks; i++) {
char label[12];
sprintf (label, "TTITLE%d=", i);
fputstring (out, cddb_max_line, label, "%s", b->track[i]);
}
fprintf (out, "EXTD=%s", crlf);
for (i=0; i<b->tracks; i++)
fprintf (out, "EXTT%d=%s", i, crlf);
fprintf (out, "PLAYORDER=%s", crlf);
return 1;
}
/*
* CDDB checksum. Algorithm learned from cdown.c by Byron Ellacott,
* who claims to have gotten docs and code at http://www.cddb.com/.
*
* These days cddb.com do not allow anonymous access to docs, and I
* am not willing to "register" for them, therefore we do not support
* the all-singing, all-dancing "CDDB v2". Only the clumsy old v1.
*/
#define TICKS_PER_SEC 75
void calc_cddb (struct cdbuf *b)
{
/* b->length is in seconds, b->offset[] are in ticks */
int i, sum = 0, sum2 = b->length - b->offset[0]/TICKS_PER_SEC;
for (i=0; i < b->tracks; i++) {
int n = b->offset[i]/TICKS_PER_SEC;
while (n) {
sum += (n % 10);
n /= 10;
}
}
b->id = ((sum % 0xff) << 24) | (sum2 << 8) | b->tracks;
}
void do_help (int bad_bad_user, FILE *out)
{
fprintf (out, "Usage: " PROGNAME " [-rvx] [-d dir] [-l len] [input]\n");
if (bad_bad_user) {
fprintf (out, " '" PROGNAME " -h' for help.\n");
exit (1);
}
fprintf
(out,
" -d dir set output directory (default: current directory)\n"
" -l len set maximum length for CDDB data line (default: %d)\n"
" -r output MS-DOS line endings (works around a bug in groovycd)\n"
" -v verbose\n"
" -x use 'xmcd' submitter string, so cddb.com will accept upload\n"
" input input file in .cdtooldb format (default: stdin)\n",
cddb_max_line);
exit (0);
}
int main(int argc, char *argv[])
{
struct cdbuf cdb;
int c;
FILE *fin, *fout;
argv[0] = PROGNAME; /* for getopt */
while ((c = getopt (argc, argv, "hd:l:rvx")) > -1) {
switch (c) {
case 'h':
do_help (0, stdout); break;
case 'd':
outdir = optarg; break;
case 'l':
cddb_max_line = atoi (optarg);
if (!cddb_max_line)
do_help (1, stderr);
break;
case 'r':
crlf = "\r\n"; break;
case 'v':
verbose++; break;
case 'x':
report_submitter = VERSION_CDDB; /* Heh. */
break;
default:
do_help (1, stderr);
}
}
fin = stdin;
do {
if (optind < argc) {
fin = fopen (argv[optind], "r");
if (!fin) {
fprintf (stderr,
"Cannot open %s for reading: %s\n", argv[optind],
strerror(errno));
exit (1);
}
if (verbose > 1)
fprintf (stderr, "Reading %s...\n", argv[optind]);
optind++;
} else if (verbose > 1)
fprintf (stderr, "Reading standard input...\n");
while (read_cdtooldb (fin, &cdb)) {
char *outfile;
calc_cddb (&cdb);
if (verbose)
fprintf (stderr, "%08x: %s / %s\n", cdb.id, cdb.artist, cdb.title);
outfile = malloc(strlen(outdir) + 1 + 16 + 1);
sprintf (outfile, OUTFILE_FORMAT, outdir, cdb.id);
fout = fopen (outfile, "w");
if (!fout) {
fprintf (stderr,
"Cannot open %s for writing: %s", outfile,
strerror(errno));
free(outfile);
continue;
}
put_cddb (fout, &cdb);
if (verbose > 1)
fprintf (stderr, "%08x: output to %s\n", cdb.id, outfile);
fclose (fout);
free(outfile);
}
fclose (fin);
} while (optind < argc);
return 0;
}
|