/* OggEnc * * This program is distributed under the GNU General Public License, version 2. * A copy of this license is included with this source. * * Copyright 2000-2002, Michael Smith * * Portions from Vorbize, (c) Kenneth Arnold * and libvorbis examples, (c) Monty */ #include #include #include #include #include #include #include #include #ifdef Plan9 #include #endif #include "platform.h" #include "encode.h" #include "audio.h" #include "utf8.h" #include "i18n.h" #define VERSION_STRING "OggEnc v1.0.1 (libvorbis 1.0.1)\n" #define COPYRIGHT "(c) 2000-2003 Michael Smith \n" #define CHUNK 4096 /* We do reads, etc. in multiples of this */ struct option long_options[] = { {"quiet",0,0,'Q'}, {"help",0,0,'h'}, {"comment",1,0,'c'}, {"artist",1,0,'a'}, {"album",1,0,'l'}, {"title",1,0,'t'}, {"genre",1,0,'G'}, {"names",1,0,'n'}, {"name-remove",1,0,'X'}, {"name-replace",1,0,'P'}, {"output",1,0,'o'}, {"version",0,0,'v'}, {"raw",0,0,'r'}, {"raw-bits",1,0,'B'}, {"raw-chan",1,0,'C'}, {"raw-rate",1,0,'R'}, {"raw-endianness",1,0, 0}, {"bitrate",1,0,'b'}, {"min-bitrate",1,0,'m'}, {"max-bitrate",1,0,'M'}, {"quality",1,0,'q'}, {"date",1,0,'d'}, {"tracknum",1,0,'N'}, {"serial",1,0,'s'}, {"managed", 0, 0, 0}, {"resample",1,0,0}, {"downmix", 0,0,0}, {"scale", 1, 0, 0}, {"advanced-encode-option", 1, 0, 0}, {"discard-comments", 0, 0, 0}, {NULL,0,0,0} }; static char *generate_name_string(char *format, char *remove_list, char *replace_list, char *artist, char *title, char *album, char *track, char *date, char *genre); static void parse_options(int argc, char **argv, oe_options *opt); static void build_comments(vorbis_comment *vc, oe_options *opt, int filenum, char **artist,char **album, char **title, char **tracknum, char **date, char **genre); static void usage(void); int main(int argc, char **argv) { /* Default values */ oe_options opt = {NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, 1, 0, 0,16,44100,2, 0, NULL, DEFAULT_NAMEFMT_REMOVE, DEFAULT_NAMEFMT_REPLACE, NULL, 0, -1,-1,-1,.3,-1,0, 0,0.f, 0}; int i; char **infiles; int numfiles; int errors=0; setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); parse_options(argc, argv, &opt); if(optind >= argc) { fprintf(stderr, _("%s%s\nERROR: No input files specified. Use -h for help.\n"), VERSION_STRING, COPYRIGHT); return 1; } else { infiles = argv + optind; numfiles = argc - optind; } /* Now, do some checking for illegal argument combinations */ for(i = 0; i < numfiles; i++) { if(!strcmp(infiles[i], "-") && numfiles > 1) { fprintf(stderr, _("ERROR: Multiple files specified when using stdin\n")); exit(1); } } if(numfiles > 1 && opt.outfile) { fprintf(stderr, _("ERROR: Multiple input files with specified output filename: suggest using -n\n")); exit(1); } if(opt.serial == 0) { /* We randomly pick a serial number. This is then incremented for each file */ srand(time(NULL)); opt.serial = rand(); } for(i = 0; i < numfiles; i++) { /* Once through the loop for each file */ oe_enc_opt enc_opts; vorbis_comment vc; char *out_fn = NULL; FILE *in, *out = NULL; int foundformat = 0; int closeout = 0, closein = 0; char *artist=NULL, *album=NULL, *title=NULL, *track=NULL; char *date=NULL, *genre=NULL; input_format *format; /* Set various encoding defaults */ enc_opts.serialno = opt.serial++; enc_opts.progress_update = update_statistics_full; enc_opts.start_encode = start_encode_full; enc_opts.end_encode = final_statistics; enc_opts.error = encode_error; enc_opts.comments = &vc; enc_opts.copy_comments = opt.copy_comments; /* OK, let's build the vorbis_comments structure */ build_comments(&vc, &opt, i, &artist, &album, &title, &track, &date, &genre); if(!strcmp(infiles[i], "-")) { setbinmode(stdin); in = stdin; infiles[i] = NULL; if(!opt.outfile) { setbinmode(stdout); out = stdout; } } else { in = fopen(infiles[i], "rb"); if(in == NULL) { fprintf(stderr, _("ERROR: Cannot open input file \"%s\": %s\n"), infiles[i], strerror(errno)); free(out_fn); errors++; continue; } closein = 1; } /* Now, we need to select an input audio format - we do this before opening the output file so that we don't end up with a 0-byte file if the input file can't be read */ if(opt.rawmode) { enc_opts.rate=opt.raw_samplerate; enc_opts.channels=opt.raw_channels; enc_opts.samplesize=opt.raw_samplesize; enc_opts.endianness=opt.raw_endianness; raw_open(in, &enc_opts); foundformat=1; } else { format = open_audio_file(in, &enc_opts); if(format) { if(!opt.quiet) fprintf(stderr, _("Opening with %s module: %s\n"), format->format, format->description); foundformat=1; } } if(!foundformat) { fprintf(stderr, _("ERROR: Input file \"%s\" is not a supported format\n"), infiles[i]?infiles[i]:"(stdin)"); if(closein) fclose(in); errors++; continue; } /* Ok. We can read the file - so now open the output file */ if(opt.outfile && !strcmp(opt.outfile, "-")) { setbinmode(stdout); out = stdout; } else if(out == NULL) { if(opt.outfile) { out_fn = strdup(opt.outfile); } else if(opt.namefmt) { out_fn = generate_name_string(opt.namefmt, opt.namefmt_remove, opt.namefmt_replace, artist, title, album, track,date, genre); } /* This bit was widely derided in mid-2002, so it's been removed */ /* else if(opt.title) { out_fn = malloc(strlen(title) + 5); strcpy(out_fn, title); strcat(out_fn, ".ogg"); } */ else if(infiles[i]) { /* Create a filename from existing filename, replacing extension with .ogg */ char *start, *end; start = infiles[i]; end = strrchr(infiles[i], '.'); end = end?end:(start + strlen(infiles[i])+1); out_fn = malloc(end - start + 5); strncpy(out_fn, start, end-start); out_fn[end-start] = 0; strcat(out_fn, ".ogg"); } else { fprintf(stderr, _("WARNING: No filename, defaulting to \"default.ogg\"\n")); out_fn = strdup("default.ogg"); } /* Create any missing subdirectories, if possible */ if(create_directories(out_fn)) { if(closein) fclose(in); fprintf(stderr, _("ERROR: Could not create required subdirectories for output filename \"%s\"\n"), out_fn); errors++; free(out_fn); continue; } out = fopen(out_fn, "wb"); if(out == NULL) { if(closein) fclose(in); fprintf(stderr, _("ERROR: Cannot open output file \"%s\": %s\n"), out_fn, strerror(errno)); errors++; free(out_fn); continue; } closeout = 1; } /* Now, set the rest of the options */ enc_opts.out = out; enc_opts.comments = &vc; enc_opts.filename = out_fn; enc_opts.infilename = infiles[i]; enc_opts.managed = opt.managed; enc_opts.bitrate = opt.nominal_bitrate; enc_opts.min_bitrate = opt.min_bitrate; enc_opts.max_bitrate = opt.max_bitrate; enc_opts.quality = opt.quality; enc_opts.quality_set = opt.quality_set; enc_opts.advopt = opt.advopt; enc_opts.advopt_count = opt.advopt_count; if(opt.resamplefreq && opt.resamplefreq != enc_opts.rate) { int fromrate = enc_opts.rate; enc_opts.resamplefreq = opt.resamplefreq; if(setup_resample(&enc_opts)) { errors++; goto clear_all; } else if(!opt.quiet) fprintf(stderr, _("Resampling input from %d Hz to %d Hz\n"), fromrate, opt.resamplefreq); } if(opt.downmix) { if(enc_opts.channels == 2) { setup_downmix(&enc_opts); if(!opt.quiet) fprintf(stderr, _("Downmixing stereo to mono\n")); } else { fprintf(stderr, _("ERROR: Can't downmix except from stereo to mono\n")); errors++; if(opt.resamplefreq && opt.resamplefreq != enc_opts.rate) clear_resample(&enc_opts); goto clear_all; } } if(opt.scale > 0.f) { setup_scaler(&enc_opts, opt.scale); if(!opt.quiet) fprintf(stderr, _("Scaling input to %f\n"), opt.scale); } if(!enc_opts.total_samples_per_channel) enc_opts.progress_update = update_statistics_notime; if(opt.quiet) { enc_opts.start_encode = start_encode_null; enc_opts.progress_update = update_statistics_null; enc_opts.end_encode = final_statistics_null; } if(oe_encode(&enc_opts)) errors++; if(opt.scale > 0) clear_scaler(&enc_opts); if(opt.downmix) clear_downmix(&enc_opts); if(opt.resamplefreq && opt.resamplefreq != enc_opts.rate) clear_resample(&enc_opts); clear_all: if(out_fn) free(out_fn); if(opt.outfile) free(opt.outfile); vorbis_comment_clear(&vc); if(!opt.rawmode) format->close_func(enc_opts.readdata); if(closein) fclose(in); if(closeout) fclose(out); }/* Finished this file, loop around to next... */ return errors?1:0; } static void usage(void) { fprintf(stdout, _("%s%s\n" "Usage: oggenc [options] input.wav [...]\n" "\n" "OPTIONS:\n" " General:\n" " -Q, --quiet Produce no output to stderr\n" " -h, --help Print this help text\n" " -r, --raw Raw mode. Input files are read directly as PCM data\n" " -B, --raw-bits=n Set bits/sample for raw input. Default is 16\n" " -C, --raw-chan=n Set number of channels for raw input. Default is 2\n" " -R, --raw-rate=n Set samples/sec for raw input. Default is 44100\n" " --raw-endianness 1 for bigendian, 0 for little (defaults to 0)\n" " -b, --bitrate Choose a nominal bitrate to encode at. Attempt\n" " to encode at a bitrate averaging this. Takes an\n" " argument in kbps. This uses the bitrate management\n" " engine, and is not recommended for most users.\n" " See -q, --quality for a better alternative.\n" " -m, --min-bitrate Specify a minimum bitrate (in kbps). Useful for\n" " encoding for a fixed-size channel.\n" " -M, --max-bitrate Specify a maximum bitrate in kbps. Useful for\n" " streaming applications.\n" " -q, --quality Specify quality between 0 (low) and 10 (high),\n" " instead of specifying a particular bitrate.\n" " This is the normal mode of operation.\n" " Fractional qualities (e.g. 2.75) are permitted\n" " Quality -1 is also possible, but may not be of\n" " acceptable quality.\n" " --resample n Resample input data to sampling rate n (Hz)\n" " --downmix Downmix stereo to mono. Only allowed on stereo\n" " input.\n" " -s, --serial Specify a serial number for the stream. If encoding\n" " multiple files, this will be incremented for each\n" " stream after the first.\n" " --discard-comments Prevents comments in FLAC and Ogg FLAC files from\n" " being copied to the output Ogg Vorbis file.\n" "\n" " Naming:\n" " -o, --output=fn Write file to fn (only valid in single-file mode)\n" " -n, --names=string Produce filenames as this string, with %%a, %%t, %%l,\n" " %%n, %%d replaced by artist, title, album, track number,\n" " and date, respectively (see below for specifying these).\n" " %%%% gives a literal %%.\n" " -X, --name-remove=s Remove the specified characters from parameters to the\n" " -n format string. Useful to ensure legal filenames.\n" " -P, --name-replace=s Replace characters removed by --name-remove with the\n" " characters specified. If this string is shorter than the\n" " --name-remove list or is not specified, the extra\n" " characters are just removed.\n" " Default settings for the above two arguments are platform\n" " specific.\n" " -c, --comment=c Add the given string as an extra comment. This may be\n" " used multiple times. The argument should be in the\n" " format \"tag=value\".\n" " -d, --date Date for track (usually date of performance)\n" " -N, --tracknum Track number for this track\n" " -t, --title Title for this track\n" " -l, --album Name of album\n" " -a, --artist Name of artist\n" " -G, --genre Genre of track\n" " If multiple input files are given, then multiple\n" " instances of the previous five arguments will be used,\n" " in the order they are given. If fewer titles are\n" " specified than files, OggEnc will print a warning, and\n" " reuse the final one for the remaining files. If fewer\n" " track numbers are given, the remaining files will be\n" " unnumbered. For the others, the final tag will be reused\n" " for all others without warning (so you can specify a date\n" " once, for example, and have it used for all the files)\n" "\n" "INPUT FILES:\n" " OggEnc input files must currently be 24, 16, or 8 bit PCM WAV, AIFF, or AIFF/C\n" " files, 32 bit IEEE floating point WAV, and optionally FLAC or Ogg FLAC. Files\n" " may be mono or stereo (or more channels) and any sample rate.\n" " Alternatively, the --raw option may be used to use a raw PCM data file, which\n" " must be 16 bit stereo little-endian PCM ('headerless wav'), unless additional\n" " parameters for raw mode are specified.\n" " You can specify taking the file from stdin by using - as the input filename.\n" " In this mode, output is to stdout unless an output filename is specified\n" " with -o\n" "\n"), VERSION_STRING, COPYRIGHT); } static int strncpy_filtered(char *dst, char *src, int len, char *remove_list, char *replace_list) { char *hit, *drop_margin; int used=0; if(remove_list == NULL || *remove_list == 0) { strncpy(dst, src, len-1); dst[len-1] = 0; return strlen(dst); } drop_margin = remove_list + (replace_list == NULL?0:strlen(replace_list)); while(*src && used < len-1) { if((hit = strchr(remove_list, *src)) != NULL) { if(hit < drop_margin) { *dst++ = replace_list[hit - remove_list]; used++; } } else { *dst++ = *src; used++; } src++; } *dst = 0; return used; } static char *generate_name_string(char *format, char *remove_list, char *replace_list, char *artist, char *title, char *album, char *track, char *date, char *genre) { char *buffer; char next; char *string; int used=0; int buflen; buffer = calloc(CHUNK+1,1); buflen = CHUNK; while(*format && used < buflen) { next = *format++; if(next == '%') { switch(*format++) { case '%': *(buffer+(used++)) = '%'; break; case 'a': string = artist?artist:_("(none)"); used += strncpy_filtered(buffer+used, string, buflen-used, remove_list, replace_list); break; case 'd': string = date?date:_("(none)"); used += strncpy_filtered(buffer+used, string, buflen-used, remove_list, replace_list); break; case 'g': string = genre?genre:_("(none)"); used += strncpy_filtered(buffer+used, string, buflen-used, remove_list, replace_list); break; case 't': string = title?title:_("(none)"); used += strncpy_filtered(buffer+used, string, buflen-used, remove_list, replace_list); break; case 'l': string = album?album:_("(none)"); used += strncpy_filtered(buffer+used, string, buflen-used, remove_list, replace_list); break; case 'n': string = track?track:_("(none)"); used += strncpy_filtered(buffer+used, string, buflen-used, remove_list, replace_list); break; default: fprintf(stderr, _("WARNING: Ignoring illegal escape character '%c' in name format\n"), *(format - 1)); break; } } else *(buffer + (used++)) = next; } return buffer; } static void parse_options(int argc, char **argv, oe_options *opt) { int ret; int option_index = 1; while((ret = getopt_long(argc, argv, "A:a:b:B:c:C:d:G:hl:m:M:n:N:o:P:q:QrR:s:t:vX:", long_options, &option_index)) != -1) { switch(ret) { case 0: if(!strcmp(long_options[option_index].name, "managed")) { if(!opt->managed){ if(!opt->quiet) fprintf(stderr, _("Enabling bitrate management engine\n")); opt->managed = 1; } } else if(!strcmp(long_options[option_index].name, "raw-endianness")) { if (opt->rawmode != 1) { opt->rawmode = 1; fprintf(stderr, _("WARNING: Raw endianness specified for non-raw data. Assuming input is raw.\n")); } if(sscanf(optarg, "%d", &opt->raw_endianness) != 1) { fprintf(stderr, _("WARNING: Couldn't read endianness argument \"%s\"\n"), optarg); opt->raw_endianness = 0; } } else if(!strcmp(long_options[option_index].name, "resample")) { if(sscanf(optarg, "%d", &opt->resamplefreq) != 1) { fprintf(stderr, _("WARNING: Couldn't read resampling frequency \"%s\"\n"), optarg); opt->resamplefreq = 0; } if(opt->resamplefreq < 100) /* User probably specified it in kHz accidently */ fprintf(stderr, _("Warning: Resample rate specified as %d Hz. Did you mean %d Hz?\n"), opt->resamplefreq, opt->resamplefreq*1000); } else if(!strcmp(long_options[option_index].name, "downmix")) { opt->downmix = 1; } else if(!strcmp(long_options[option_index].name, "scale")) { opt->scale = atof(optarg); if(sscanf(optarg, "%f", &opt->scale) != 1) { opt->scale = 0; fprintf(stderr, _("Warning: Couldn't parse scaling factor \"%s\"\n"), optarg); } } else if(!strcmp(long_options[option_index].name, "advanced-encode-option")) { char *arg = strdup(optarg); char *val; val = strchr(arg, '='); if(val == NULL) { fprintf(stderr, _("No value for advanced encoder option found\n")); continue; } else *val++=0; opt->advopt = realloc(opt->advopt, (++opt->advopt_count)*sizeof(adv_opt)); opt->advopt[opt->advopt_count - 1].arg = arg; opt->advopt[opt->advopt_count - 1].val = val; } else if(!strcmp(long_options[option_index].name, "discard-comments")) { opt->copy_comments = 0; } else { fprintf(stderr, _("Internal error parsing command line options\n")); exit(1); } break; case 'a': opt->artist = realloc(opt->artist, (++opt->artist_count)*sizeof(char *)); opt->artist[opt->artist_count - 1] = strdup(optarg); break; case 'c': if(strchr(optarg, '=') == NULL) { fprintf(stderr, _("Warning: Illegal comment used (\"%s\"), ignoring.\n"), optarg); break; } opt->comments = realloc(opt->comments, (++opt->comment_count)*sizeof(char *)); opt->comments[opt->comment_count - 1] = strdup(optarg); break; case 'd': opt->dates = realloc(opt->dates, (++opt->date_count)*sizeof(char *)); opt->dates[opt->date_count - 1] = strdup(optarg); break; case 'G': opt->genre = realloc(opt->genre, (++opt->genre_count)*sizeof(char *)); opt->genre[opt->genre_count - 1] = strdup(optarg); break; case 'h': usage(); exit(0); break; case 'l': opt->album = realloc(opt->album, (++opt->album_count)*sizeof(char *)); opt->album[opt->album_count - 1] = strdup(optarg); break; case 's': /* Would just use atoi(), but that doesn't deal with unsigned * ints. Damn */ if(sscanf(optarg, "%u", &opt->serial) != 1) opt->serial = 0; /* Failed, so just set to zero */ break; case 't': opt->title = realloc(opt->title, (++opt->title_count)*sizeof(char *)); opt->title[opt->title_count - 1] = strdup(optarg); break; case 'b': if(sscanf(optarg, "%d", &opt->nominal_bitrate) != 1) { fprintf(stderr, _("Warning: nominal bitrate \"%s\" not recognised\n"), optarg); opt->nominal_bitrate = -1; } break; case 'm': if(sscanf(optarg, "%d", &opt->min_bitrate) != 1) { fprintf(stderr, _("Warning: minimum bitrate \"%s\" not recognised\n"), optarg); opt->min_bitrate = -1; } if(!opt->managed){ if(!opt->quiet) fprintf(stderr, _("Enabling bitrate management engine\n")); opt->managed = 1; } break; case 'M': if(sscanf(optarg, "%d", &opt->max_bitrate) != 1) { fprintf(stderr, _("Warning: maximum bitrate \"%s\" not recognised\n"), optarg); opt->max_bitrate = -1; } if(!opt->managed){ if(!opt->quiet) fprintf(stderr, _("Enabling bitrate management engine\n")); opt->managed = 1; } break; case 'q': if(sscanf(optarg, "%f", &opt->quality) != 1) { fprintf(stderr, _("Quality option \"%s\" not recognised, ignoring\n"), optarg); break; } opt->quality_set=1; opt->quality *= 0.1; if(opt->quality > 1.0f) { opt->quality = 1.0f; fprintf(stderr, _("WARNING: quality setting too high, setting to maximum quality.\n")); } break; case 'n': if(opt->namefmt) { fprintf(stderr, _("WARNING: Multiple name formats specified, using final\n")); free(opt->namefmt); } opt->namefmt = strdup(optarg); break; case 'X': if(opt->namefmt_remove && opt->namefmt_remove != DEFAULT_NAMEFMT_REMOVE) { fprintf(stderr, _("WARNING: Multiple name format filters specified, using final\n")); free(opt->namefmt_remove); } opt->namefmt_remove = strdup(optarg); break; case 'P': if(opt->namefmt_replace && opt->namefmt_replace != DEFAULT_NAMEFMT_REPLACE) { fprintf(stderr, _("WARNING: Multiple name format filter replacements specified, using final\n")); free(opt->namefmt_replace); } opt->namefmt_replace = strdup(optarg); break; case 'o': if(opt->outfile) { fprintf(stderr, _("WARNING: Multiple output files specified, suggest using -n\n")); free(opt->outfile); } opt->outfile = strdup(optarg); break; case 'Q': opt->quiet = 1; break; case 'r': opt->rawmode = 1; break; case 'v': fprintf(stdout, VERSION_STRING); exit(0); break; case 'B': if (opt->rawmode != 1) { opt->rawmode = 1; fprintf(stderr, _("WARNING: Raw bits/sample specified for non-raw data. Assuming input is raw.\n")); } if(sscanf(optarg, "%u", &opt->raw_samplesize) != 1) { opt->raw_samplesize = 16; /* Failed, so just set to 16 */ fprintf(stderr, _("WARNING: Invalid bits/sample specified, assuming 16.\n")); } if((opt->raw_samplesize != 8) && (opt->raw_samplesize != 16)) { fprintf(stderr, _("WARNING: Invalid bits/sample specified, assuming 16.\n")); } break; case 'C': if (opt->rawmode != 1) { opt->rawmode = 1; fprintf(stderr, _("WARNING: Raw channel count specified for non-raw data. Assuming input is raw.\n")); } if(sscanf(optarg, "%u", &opt->raw_channels) != 1) { opt->raw_channels = 2; /* Failed, so just set to 2 */ fprintf(stderr, _("WARNING: Invalid channel count specified, assuming 2.\n")); } break; case 'N': opt->tracknum = realloc(opt->tracknum, (++opt->track_count)*sizeof(char *)); opt->tracknum[opt->track_count - 1] = strdup(optarg); break; case 'R': if (opt->rawmode != 1) { opt->rawmode = 1; fprintf(stderr, _("WARNING: Raw sample rate specified for non-raw data. Assuming input is raw.\n")); } if(sscanf(optarg, "%u", &opt->raw_samplerate) != 1) { opt->raw_samplerate = 44100; /* Failed, so just set to 44100 */ fprintf(stderr, _("WARNING: Invalid sample rate specified, assuming 44100.\n")); } break; case '?': fprintf(stderr, _("WARNING: Unknown option specified, ignoring->\n")); break; default: usage(); exit(0); } } } static void add_tag(vorbis_comment *vc, oe_options *opt,char *name, char *value) { char *utf8; if(utf8_encode(value, &utf8) >= 0) { if(name == NULL) vorbis_comment_add(vc, utf8); else vorbis_comment_add_tag(vc, name, utf8); free(utf8); } else fprintf(stderr, _("Couldn't convert comment to UTF-8, cannot add\n")); } static void build_comments(vorbis_comment *vc, oe_options *opt, int filenum, char **artist, char **album, char **title, char **tracknum, char **date, char **genre) { int i; vorbis_comment_init(vc); for(i = 0; i < opt->comment_count; i++) add_tag(vc, opt, NULL, opt->comments[i]); if(opt->title_count) { if(filenum >= opt->title_count) { if(!opt->quiet) fprintf(stderr, _("WARNING: Insufficient titles specified, defaulting to final title.\n")); i = opt->title_count-1; } else i = filenum; *title = opt->title[i]; add_tag(vc, opt, "title", opt->title[i]); } if(opt->artist_count) { if(filenum >= opt->artist_count) i = opt->artist_count-1; else i = filenum; *artist = opt->artist[i]; add_tag(vc, opt, "artist", opt->artist[i]); } if(opt->genre_count) { if(filenum >= opt->genre_count) i = opt->genre_count-1; else i = filenum; *genre = opt->genre[i]; add_tag(vc, opt, "genre", opt->genre[i]); } if(opt->date_count) { if(filenum >= opt->date_count) i = opt->date_count-1; else i = filenum; *date = opt->dates[i]; add_tag(vc, opt, "date", opt->dates[i]); } if(opt->album_count) { if(filenum >= opt->album_count) { i = opt->album_count-1; } else i = filenum; *album = opt->album[i]; add_tag(vc, opt, "album", opt->album[i]); } if(filenum < opt->track_count) { i = filenum; *tracknum = opt->tracknum[i]; add_tag(vc, opt, "tracknumber", opt->tracknum[i]); } }