Files
cuetools/src/tools/cueprint.c
2013-08-16 12:53:34 -07:00

517 lines
10 KiB
C

/*
* cueprint.c -- print cd information based on a template
*
* Copyright (C) 2004, 2005, 2006, 2007, 2013 Svend Sorensen
* For license terms, see the file COPYING in this distribution.
*/
#include <ctype.h> /* isdigit() */
#include <getopt.h> /* getopt_long() */
#include <stdio.h> /* fprintf(), printf(), snprintf(), stderr */
#include <stdlib.h> /* exit() */
#include <string.h> /* strcasecmp() */
#include "cuefile.h"
#if HAVE_CONFIG_H
#include "config.h"
#else /* not HAVE_CONFIG_H */
#define PACKAGE_STRING "cueprint"
#endif /* HAVE_CONFIG_H */
/* default templates */
#define D_TEMPLATE "\
Disc Information\n\
arranger: %A\n\
composer: %C\n\
genre: %G\n\
message: %M\n\
no. of tracks: %N\n\
performer: %P\n\
songwriter: %S\n\
title: %T\n\
UPC/EAN: %U\n\
"
#define T_TEMPLATE "\
\n\
Track %n Information\n\
arranger: %a\n\
composer: %c\n\
genre: %g\n\
ISRC: %i\n\
message: %m\n\
track number: %n\n\
perfomer: %p\n\
title: %t\n\
ISRC (CD-TEXT): %u\n\
"
/* default string to print for unset (NULL) values */
#define VALUE_UNSET ""
/*
* *_get_* functions can return an int or a char *
*/
typedef union {
int ival;
char *sval;
char cval;
} Value;
char *progname;
/* Print usage information and exit */
void usage(int status)
{
if (0 == status) {
printf("Usage: %s [option...] [file...]\n", progname);
printf("Report disc and track information from a CUE or TOC file.\n"
"\n"
"OPTIONS\n"
"-h, --help print usage\n"
"-i, --input-format cue|toc set format of file(s)\n"
"-n, --track-number <number> only print track information for single track\n"
"-d, --disc-template <template> set disc template\n"
"-t, --track-template <template> set track template\n"
"-V, --version print version information\n"
"\n"
"Default disc template: %s\n"
"Default track template: %s\n"
"See the manual page for more information.\n",
D_TEMPLATE, T_TEMPLATE);
} else {
fprintf(stderr, "Try `%s --help' for more information.\n", progname);
}
exit (status);
}
/* Print version information and exit */
void version()
{
printf("%s\n", PACKAGE_STRING);
exit(0);
}
/*
* TODO: Shouldn't we be using vprintf() to help us out with this stuff?
* (Branden Robinson)
*/
void disc_field(char *conv, int length, Cd *cd, Value *value)
{
char *c; /* pointer to conversion character */
Cdtext *cdtext = NULL;
cdtext = cd_get_cdtext(cd);
c = conv + length - 1;
/*
* After setting value, set *c to specify value type:
* 'd' integer
* 's' string
* 'c' character
*/
switch (*c) {
case 'A':
value->sval = cdtext_get(PTI_ARRANGER, cdtext);
*c = 's';
break;
case 'C':
value->sval = cdtext_get(PTI_COMPOSER, cdtext);
*c = 's';
break;
case 'G':
value->sval = cdtext_get(PTI_GENRE, cdtext);
*c = 's';
break;
case 'M':
value->sval = cdtext_get(PTI_MESSAGE, cdtext);
*c = 's';
break;
case 'N':
value->ival = cd_get_ntrack(cd);
*c = 'd';
break;
case 'P':
value->sval = cdtext_get(PTI_PERFORMER, cdtext);
*c = 's';
break;
case 'R':
value->sval = cdtext_get(PTI_ARRANGER, cdtext);
*c = 's';
break;
case 'S':
value->sval = cdtext_get(PTI_SONGWRITER, cdtext);
*c = 's';
break;
case 'T':
value->sval = cdtext_get(PTI_TITLE, cdtext);
*c = 's';
break;
case 'U':
value->sval = cdtext_get(PTI_UPC_ISRC, cdtext);
*c = 's';
break;
default:
value->cval = *c;
*c = 'c';
break;
}
}
void track_field(char *conv, int length, Cd *cd, int trackno, Value *value)
{
char *c; /* pointer to conversion character */
Track *track = NULL;
Cdtext *cdtext = NULL;
track = cd_get_track(cd, trackno);
cdtext = track_get_cdtext(track);
c = conv + length - 1;
switch (*c) {
case 'a':
value->sval = cdtext_get(PTI_ARRANGER, cdtext);
*c = 's';
break;
case 'c':
value->sval = cdtext_get(PTI_COMPOSER, cdtext);
*c = 's';
break;
case 'f':
value->sval = track_get_filename(track);
*c = 's';
break;
case 'g':
value->sval = cdtext_get(PTI_GENRE, cdtext);
*c = 's';
break;
case 'i':
value->sval = track_get_isrc(track);
*c = 's';
break;
case 'm':
value->sval = cdtext_get(PTI_MESSAGE, cdtext);
*c = 's';
break;
case 'n':
value->ival = trackno;
*c = 'd';
break;
case 'p':
value->sval = cdtext_get(PTI_PERFORMER, cdtext);
*c = 's';
break;
case 's':
value->sval = cdtext_get(PTI_SONGWRITER, cdtext);
*c = 's';
break;
case 't':
value->sval = cdtext_get(PTI_TITLE, cdtext);
*c = 's';
break;
case 'u':
value->sval = cdtext_get(PTI_UPC_ISRC, cdtext);
*c = 's';
break;
default:
disc_field(conv, length, cd, value);
break;
}
}
/*
* Print a conversion specification.
* [flag(s)][width][.precision]<conversion-char>
*/
void print_conv(char *start, int length, Cd *cd, int trackno)
{
char *conv; /* copy of conversion specification */
Value value;
char *c; /* pointer to conversion-char */
/* TODO: use strndup? */
conv = malloc((unsigned) (length + 1));
strncpy(conv, start, length);
conv[length] = '\0';
/* conversion character */
if (0 == trackno) {
disc_field(conv, length, cd, &value);
} else {
track_field(conv, length, cd, trackno, &value);
}
c = conv + length - 1;
switch (*c) {
case 'c':
printf(conv, value.cval);
break;
case 'd':
printf(conv, value.ival);
break;
case 's':
if (NULL == value.sval)
printf(conv, VALUE_UNSET);
else
printf(conv, value.sval);
break;
default:
printf("%zu: ", strlen(conv));
printf("%s", conv);
}
free(conv);
}
void cd_printf(char *format, Cd *cd, int trackno)
{
char *c; /* pointer into format */
char *conv_start;
int conv_length;
for (c = format; '\0' != *c; c++) {
if ('%' == *c) {
conv_start = c;
conv_length = 1;
c++;
/* flags */
while ( \
'-' == *c \
|| '+' == *c \
|| ' ' == *c \
|| '0' == *c \
|| '#' == *c \
) {
conv_length++;
c++;
}
/* field width */
/* '*' not recognized */
while (0 != isdigit(*c)) {
conv_length++;
c++;
}
/* precision */
/* '*' not recognized */
if ('.' == *c) {
conv_length++;
c++;
while (0 != isdigit(*c)) {
conv_length++;
c++;
}
}
/* length modifier (h, l, or L) */
/* not recognized */
/* conversion character */
conv_length++;
print_conv(conv_start, conv_length, cd, trackno);
} else {
putchar(*c);
}
}
}
int info(char *name, int format, int trackno, char *d_template, char *t_template)
{
Cd *cd = NULL;
int ntrack;
if (NULL == (cd = cf_parse(name, &format))) {
fprintf(stderr, "%s: error: unable to parse input file"
" `%s'\n", progname, name);
return -1;
}
ntrack = cd_get_ntrack(cd);
if (-1 == trackno) {
cd_printf(d_template, cd, 0);
for (trackno = 1; trackno <= ntrack; trackno++) {
cd_printf(t_template, cd, trackno);
}
} else if (0 == trackno) {
cd_printf(d_template, cd, trackno);
} else if (0 < trackno && ntrack >= trackno) {
cd_printf(t_template, cd, trackno);
} else {
fprintf(stderr, "%s: error: track number out of range\n", progname);
return -1;
}
return 0;
}
/*
* Translate escape sequences in a string.
* The string is overwritten and terminated.
* TODO: this does not handle octal and hexidecimal escapes
* except for \0
*/
void translate_escapes(char *s)
{
char *read;
char *write;
read = s;
write = s;
while ('\0' != *read) {
if ('\\' == *read) {
read++;
switch (*read) {
case 'a':
*write = '\a';
break;
case 'b':
*write = '\b';
break;
case 'f':
*write = '\f';
break;
case 'n':
*write = '\n';
break;
case 'r':
*write = '\r';
break;
case 't':
*write = '\t';
break;
case 'v':
*write = '\v';
break;
case '0':
*write = '\0';
break;
default:
/* ?, ', " are handled by the default */
*write = *read;
break;
}
} else {
*write = *read;
}
read++;
write++;
}
*write = '\0';
}
int main(int argc, char *argv[])
{
int format = UNKNOWN;
int trackno = -1; /* track number (-1 = unspecified,
0 = disc info) */
char *d_template = NULL; /* disc template */
char *t_template = NULL; /* track template */
int ret = 0; /* return value of info() */
/* option variables */
int c;
/* getopt_long() variables */
extern char *optarg;
extern int optind;
static struct option longopts[] = {
{"help", no_argument, NULL, 'h'},
{"input-format", required_argument, NULL, 'i'},
{"track-number", required_argument, NULL, 'n'},
{"disc-template", required_argument, NULL, 'd'},
{"track-template", required_argument, NULL, 't'},
{"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0}
};
progname = argv[0];
while (-1 != (c = getopt_long(argc, argv, "hi:n:d:t:V", longopts, NULL))) {
switch (c) {
case 'h':
usage(0);
break;
case 'i':
if (0 == strcmp("cue", optarg)) {
format = CUE;
} else if (0 == strcmp("toc", optarg)) {
format = TOC;
} else {
fprintf(stderr, "%s: error: unknown input file"
" format `%s'\n", progname, optarg);
usage(1);
}
break;
case 'n':
trackno = atoi(optarg);
break;
case 'd':
d_template = optarg;
break;
case 't':
t_template = optarg;
break;
case 'V':
version();
break;
default:
usage(1);
break;
}
}
/* If no disc or track template is set, use the defaults for both. */
/* TODO: alternative to strdup to get variable strings? */
if (NULL == d_template && NULL == t_template) {
d_template = strdup(D_TEMPLATE);
t_template = strdup(T_TEMPLATE);
} else {
if (NULL == d_template) {
d_template = strdup("");
}
if (NULL == t_template) {
t_template = strdup("");
}
}
/* Translate escape sequences. */
translate_escapes(d_template);
translate_escapes(t_template);
/* What we do depends on the number of operands. */
if (optind == argc) {
/* No operands: report information about stdin. */
ret = info("-", format, trackno, d_template, t_template);
} else {
/* Report information about each operand. */
for (; optind < argc; optind++) {
ret = info(argv[optind], format, trackno, d_template, t_template);
/* Exit if info() returns nonzero. */
if (!ret) {
break;
}
}
}
return ret;
}