Allow multiple files in CLI

This commit is contained in:
bnnm 2021-05-09 23:27:10 +02:00
parent e6120669d4
commit 93a7c0d21e
2 changed files with 148 additions and 106 deletions

View File

@ -70,14 +70,15 @@ the other files needed.
Converts playable files to wav. Typical usage would be: Converts playable files to wav. Typical usage would be:
- `test.exe -o happy.wav happy.adx` to decode `happy.adx` to `happy.wav`. - `test.exe -o happy.wav happy.adx` to decode `happy.adx` to `happy.wav`.
If command-line isn't your thing you can also drag and drop files to the If command-line isn't your thing you can simply drag and drop one or multiple
executable to decode them as `(filename).wav`. files to the executable to decode them as `(filename).wav`.
There are multiple options that alter how the file is converted, for example: There are multiple options that alter how the file is converted, for example:
- `test.exe -m -o file.wav file.adx`: print info but don't decode - `test.exe -m file.adx`: print info but don't decode
- `test.exe -i -o file.wav file.hca`: convert without looping - `test.exe -i -o file_noloop.wav file.hca`: convert without looping
- `test.exe -s 2 -F -o file.wav file.fsb`: play 2nd subsong + ending after 2.0 loops - `test.exe -s 2 -F file.fsb`: play 2nd subsong + ending after 2.0 loops
- `test.exe -l 3.0 -f 5.0 -d 3.0 -o file.wav file.wem`: 3 loops, 3s delay, 5s fade - `test.exe -l 3.0 -f 5.0 -d 3.0 file.wem`: 3 loops, 3s delay, 5s fade
- `test.exe -o bgm_?f.wav file1.adx file2.adx`: convert multiple files to `bgm_(name).wav`
Available commands are printed when run with no flags. Note that you can also Available commands are printed when run with no flags. Note that you can also
achieve similar results for other plugins using TXTP, described later. achieve similar results for other plugins using TXTP, described later.

View File

@ -82,9 +82,15 @@ static void usage(const char* name, int is_full) {
typedef struct { typedef struct {
char* infilename; char** infilenames;
char* outfilename; int infilenames_count;
char* tag_filename; const char* infilename;
const char* outfilename_config;
const char* outfilename;
const char* tag_filename;
int play_forever; int play_forever;
int play_sdtout; int play_sdtout;
int play_wreckless; int play_wreckless;
@ -237,7 +243,7 @@ static int parse_config(cli_config* cfg, int argc, char** argv) {
break; break;
#endif #endif
case '?': case '?':
fprintf(stderr, "Unknown option -%c found\n", optopt); fprintf(stderr, "unknown option -%c found\n", optopt);
goto fail; goto fail;
default: default:
usage(argv[0], 0); usage(argv[0], 0);
@ -245,13 +251,30 @@ static int parse_config(cli_config* cfg, int argc, char** argv) {
} }
} }
/* filename goes last */ /* filenames go last */
if (optind != argc - 1) { if (optind != argc - 1) {
usage(argv[0], 0); int i;
/* check there aren't commands after filename */
for (i = optind; i < argc; i++) {
if (argv[i][0] == '-') {
fprintf(stderr, "input files must go after options\n");
goto fail;
}
}
}
cfg->infilenames = &argv[optind];
cfg->infilenames_count = argc - optind;
if (cfg->infilenames_count <= 0) {
fprintf(stderr, "missing input file\n");
goto fail; goto fail;
} }
cfg->infilename = argv[optind];
if (cfg->outfilename && strchr(cfg->outfilename, '?') != NULL) {
cfg->outfilename_config = cfg->outfilename;
cfg->outfilename = NULL;
}
return 1; return 1;
fail: fail:
@ -260,15 +283,15 @@ fail:
static int validate_config(cli_config* cfg) { static int validate_config(cli_config* cfg) {
if (cfg->play_sdtout && (!cfg->play_wreckless && isatty(STDOUT_FILENO))) { if (cfg->play_sdtout && (!cfg->play_wreckless && isatty(STDOUT_FILENO))) {
fprintf(stderr,"Are you sure you want to output wave data to the terminal?\nIf so use -P instead of -p.\n"); fprintf(stderr, "Are you sure you want to output wave data to the terminal?\nIf so use -P instead of -p.\n");
goto fail; goto fail;
} }
if (cfg->play_forever && !cfg->play_sdtout) { if (cfg->play_forever && !cfg->play_sdtout) {
fprintf(stderr,"-c must use -p or -P\n"); fprintf(stderr, "-c must use -p or -P\n");
goto fail; goto fail;
} }
if (cfg->play_sdtout && cfg->outfilename) { if (cfg->play_sdtout && cfg->outfilename) {
fprintf(stderr,"use either -p or -o\n"); fprintf(stderr, "use either -p or -o\n");
goto fail; goto fail;
} }
@ -287,20 +310,20 @@ static void print_info(VGMSTREAM* vgmstream, cli_config* cfg) {
if (!cfg->print_metaonly) if (!cfg->print_metaonly)
printf(" \"%s\"",cfg->outfilename); printf(" \"%s\"",cfg->outfilename);
if (vgmstream->loop_flag) if (vgmstream->loop_flag)
printf(" -lps%d -lpe%d",vgmstream->loop_start_sample,vgmstream->loop_end_sample); printf(" -lps%d -lpe%d", vgmstream->loop_start_sample, vgmstream->loop_end_sample);
printf("\n"); printf("\n");
} }
else if (cfg->print_oggenc) { else if (cfg->print_oggenc) {
printf("oggenc"); printf("oggenc");
if (!cfg->print_metaonly) if (!cfg->print_metaonly)
printf(" \"%s\"",cfg->outfilename); printf(" \"%s\"", cfg->outfilename);
if (vgmstream->loop_flag) if (vgmstream->loop_flag)
printf(" -c LOOPSTART=%d -c LOOPLENGTH=%d",vgmstream->loop_start_sample, vgmstream->loop_end_sample-vgmstream->loop_start_sample); printf(" -c LOOPSTART=%d -c LOOPLENGTH=%d", vgmstream->loop_start_sample, vgmstream->loop_end_sample-vgmstream->loop_start_sample);
printf("\n"); printf("\n");
} }
else if (cfg->print_batchvar) { else if (cfg->print_batchvar) {
if (!cfg->print_metaonly) if (!cfg->print_metaonly)
printf("set fname=\"%s\"\n",cfg->outfilename); printf("set fname=\"%s\"\n", cfg->outfilename);
printf("set tsamp=%d\nset chan=%d\n", vgmstream->num_samples, channels); printf("set tsamp=%d\nset chan=%d\n", vgmstream->num_samples, channels);
if (vgmstream->loop_flag) if (vgmstream->loop_flag)
printf("set lstart=%d\nset lend=%d\nset loop=1\n", vgmstream->loop_start_sample, vgmstream->loop_end_sample); printf("set lstart=%d\nset lend=%d\nset loop=1\n", vgmstream->loop_start_sample, vgmstream->loop_end_sample);
@ -308,10 +331,10 @@ static void print_info(VGMSTREAM* vgmstream, cli_config* cfg) {
printf("set loop=0\n"); printf("set loop=0\n");
} }
else if (cfg->print_metaonly) { else if (cfg->print_metaonly) {
printf("metadata for %s\n",cfg->infilename); printf("metadata for %s\n", cfg->infilename);
} }
else { else {
printf("decoding %s\n",cfg->infilename); printf("decoding %s\n", cfg->infilename);
} }
} }
@ -530,18 +553,11 @@ void replace_filename(char* dst, size_t dstsize, const char* outfilename, const
/* ************************************************************ */ /* ************************************************************ */
static int convert_file(cli_config* cfg);
int main(int argc, char** argv) { int main(int argc, char** argv) {
VGMSTREAM* vgmstream = NULL;
FILE* outfile = NULL;
char outfilename_temp[PATH_LIMIT];
sample_t* buf = NULL;
int channels, input_channels;
int32_t len_samples;
int i, j;
cli_config cfg = {0}; cli_config cfg = {0};
int res; int i, res;
/* read args */ /* read args */
@ -558,9 +574,34 @@ int main(int argc, char** argv) {
res = validate_config(&cfg); res = validate_config(&cfg);
if (!res) goto fail; if (!res) goto fail;
for (i = 0; i < cfg.infilenames_count; i++) {
/* current name, to avoid passing params all the time */
cfg.infilename = cfg.infilenames[i];
cfg.outfilename = NULL;
convert_file(&cfg);
//if (!res) goto fail; /* keep on truckin' */
}
return EXIT_SUCCESS;
fail:
return EXIT_FAILURE;
}
static int convert_file(cli_config* cfg) {
VGMSTREAM* vgmstream = NULL;
FILE* outfile = NULL;
char outfilename_temp[PATH_LIMIT];
sample_t* buf = NULL;
int channels, input_channels;
int32_t len_samples;
int i, j;
/* for plugin testing */ /* for plugin testing */
if (cfg.validate_extensions) { if (cfg->validate_extensions) {
int valid; int valid;
vgmstream_ctx_valid_cfg vcfg = {0}; vgmstream_ctx_valid_cfg vcfg = {0};
@ -569,38 +610,38 @@ int main(int argc, char** argv) {
vcfg.accept_unknown = 0; vcfg.accept_unknown = 0;
vcfg.accept_common = 0; vcfg.accept_common = 0;
valid = vgmstream_ctx_is_valid(cfg.infilename, &vcfg); valid = vgmstream_ctx_is_valid(cfg->infilename, &vcfg);
if (!valid) goto fail; if (!valid) goto fail;
} }
/* open streamfile and pass subsong */ /* open streamfile and pass subsong */
{ {
STREAMFILE* sf = open_stdio_streamfile(cfg.infilename); STREAMFILE* sf = open_stdio_streamfile(cfg->infilename);
if (!sf) { if (!sf) {
fprintf(stderr,"file %s not found\n",cfg.infilename); fprintf(stderr, "file %s not found\n", cfg->infilename);
goto fail; goto fail;
} }
sf->stream_index = cfg.stream_index; sf->stream_index = cfg->stream_index;
vgmstream = init_vgmstream_from_STREAMFILE(sf); vgmstream = init_vgmstream_from_STREAMFILE(sf);
close_streamfile(sf); close_streamfile(sf);
if (!vgmstream) { if (!vgmstream) {
fprintf(stderr,"failed opening %s\n",cfg.infilename); fprintf(stderr, "failed opening %s\n", cfg->infilename);
goto fail; goto fail;
} }
} }
/* modify the VGMSTREAM if needed (before printing file info) */ /* modify the VGMSTREAM if needed (before printing file info) */
apply_config(vgmstream, &cfg); apply_config(vgmstream, cfg);
channels = vgmstream->channels; channels = vgmstream->channels;
input_channels = vgmstream->channels; input_channels = vgmstream->channels;
/* enable after config but before outbuf */ /* enable after config but before outbuf */
if (cfg.downmix_channels) if (cfg->downmix_channels)
vgmstream_mixing_autodownmix(vgmstream, cfg.downmix_channels); vgmstream_mixing_autodownmix(vgmstream, cfg->downmix_channels);
vgmstream_mixing_enable(vgmstream, SAMPLE_BUFFER_SIZE, &input_channels, &channels); vgmstream_mixing_enable(vgmstream, SAMPLE_BUFFER_SIZE, &input_channels, &channels);
/* get final play config */ /* get final play config */
@ -608,40 +649,40 @@ int main(int argc, char** argv) {
if (len_samples <= 0) if (len_samples <= 0)
goto fail; goto fail;
if (cfg.play_forever && !vgmstream_get_play_forever(vgmstream)) { if (cfg->play_forever && !vgmstream_get_play_forever(vgmstream)) {
fprintf(stderr,"File can't be played forever"); fprintf(stderr, "file can't be played forever");
goto fail; goto fail;
} }
/* prepare output */ /* prepare output */
if (cfg.play_sdtout) { if (cfg->play_sdtout) {
outfile = stdout; outfile = stdout;
} }
else if (!cfg.print_metaonly && !cfg.decode_only) { else if (!cfg->print_metaonly && !cfg->decode_only) {
if (!cfg.outfilename) { if (cfg->outfilename_config) {
/* special substitution */
replace_filename(outfilename_temp, sizeof(outfilename_temp), cfg->outfilename_config, cfg->infilename, vgmstream);
cfg->outfilename = outfilename_temp;
}
else if (!cfg->outfilename) {
/* note that outfilename_temp must persist outside this block, hence the external array */ /* note that outfilename_temp must persist outside this block, hence the external array */
strcpy(outfilename_temp, cfg.infilename); strcpy(outfilename_temp, cfg->infilename);
strcat(outfilename_temp, ".wav"); strcat(outfilename_temp, ".wav");
cfg.outfilename = outfilename_temp; cfg->outfilename = outfilename_temp;
/* maybe should avoid overwriting with this auto-name, for the unlikely /* maybe should avoid overwriting with this auto-name, for the unlikely
* case of file header-body pairs (file.ext+file.ext.wav) */ * case of file header-body pairs (file.ext+file.ext.wav) */
} }
else if (strchr(cfg.outfilename, '?') != NULL) {
/* special substitution */
replace_filename(outfilename_temp, sizeof(outfilename_temp), cfg.outfilename, cfg.infilename, vgmstream);
cfg.outfilename = outfilename_temp;
}
/* don't overwrite itself! */ /* don't overwrite itself! */
if (strcmp(cfg.outfilename, cfg.infilename) == 0) { if (strcmp(cfg->outfilename, cfg->infilename) == 0) {
fprintf(stderr,"same infile and outfile name: %s\n", cfg.outfilename); fprintf(stderr, "same infile and outfile name: %s\n", cfg->outfilename);
goto fail; goto fail;
} }
outfile = fopen(cfg.outfilename,"wb"); outfile = fopen(cfg->outfilename,"wb");
if (!outfile) { if (!outfile) {
fprintf(stderr,"failed to open %s for output\n", cfg.outfilename); fprintf(stderr, "failed to open %s for output\n", cfg->outfilename);
goto fail; goto fail;
} }
@ -653,21 +694,21 @@ int main(int argc, char** argv) {
/* prints */ /* prints */
#ifdef HAVE_JSON #ifdef HAVE_JSON
if (!cfg.print_metajson) { if (!cfg->print_metajson) {
#endif #endif
print_info(vgmstream, &cfg); print_info(vgmstream, cfg);
print_tags(&cfg); print_tags(cfg);
print_title(vgmstream, &cfg); print_title(vgmstream, cfg);
#ifdef HAVE_JSON #ifdef HAVE_JSON
} }
else { else {
print_json_info(vgmstream, &cfg); print_json_info(vgmstream, cfg);
} }
#endif #endif
/* prints done */ /* prints done */
if (cfg.print_metaonly) { if (cfg->print_metaonly) {
if (!cfg.play_sdtout) { if (!cfg->play_sdtout) {
if (outfile != NULL) if (outfile != NULL)
fclose(outfile); fclose(outfile);
} }
@ -675,36 +716,36 @@ int main(int argc, char** argv) {
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
if (cfg.seek_samples1 < -1) /* ex value for loop testing */ if (cfg->seek_samples1 < -1) /* ex value for loop testing */
cfg.seek_samples1 = vgmstream->loop_start_sample; cfg->seek_samples1 = vgmstream->loop_start_sample;
if (cfg.seek_samples1 >= len_samples) if (cfg->seek_samples1 >= len_samples)
cfg.seek_samples1 = -1; cfg->seek_samples1 = -1;
if (cfg.seek_samples2 >= len_samples) if (cfg->seek_samples2 >= len_samples)
cfg.seek_samples2 = -1; cfg->seek_samples2 = -1;
if (cfg.seek_samples2 >= 0) if (cfg->seek_samples2 >= 0)
len_samples -= cfg.seek_samples2; len_samples -= cfg->seek_samples2;
else if (cfg.seek_samples1 >= 0) else if (cfg->seek_samples1 >= 0)
len_samples -= cfg.seek_samples1; len_samples -= cfg->seek_samples1;
/* last init */ /* last init */
buf = malloc(SAMPLE_BUFFER_SIZE * sizeof(sample_t) * input_channels); buf = malloc(SAMPLE_BUFFER_SIZE * sizeof(sample_t) * input_channels);
if (!buf) { if (!buf) {
fprintf(stderr,"failed allocating output buffer\n"); fprintf(stderr, "failed allocating output buffer\n");
goto fail; goto fail;
} }
/* decode forever */ /* decode forever */
while (cfg.play_forever) { while (cfg->play_forever) {
int to_get = SAMPLE_BUFFER_SIZE; int to_get = SAMPLE_BUFFER_SIZE;
render_vgmstream(buf, to_get, vgmstream); render_vgmstream(buf, to_get, vgmstream);
swap_samples_le(buf, channels * to_get); /* write PC endian */ swap_samples_le(buf, channels * to_get); /* write PC endian */
if (cfg.only_stereo != -1) { if (cfg->only_stereo != -1) {
for (j = 0; j < to_get; j++) { for (j = 0; j < to_get; j++) {
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample_t), 2, outfile); fwrite(buf + j*channels + (cfg->only_stereo*2), sizeof(sample_t), 2, outfile);
} }
} else { } else {
fwrite(buf, sizeof(sample_t) * channels, to_get, outfile); fwrite(buf, sizeof(sample_t) * channels, to_get, outfile);
@ -713,23 +754,23 @@ int main(int argc, char** argv) {
/* slap on a .wav header */ /* slap on a .wav header */
if (!cfg.decode_only) { if (!cfg->decode_only) {
uint8_t wav_buf[0x100]; uint8_t wav_buf[0x100];
int channels_write = (cfg.only_stereo != -1) ? 2 : channels; int channels_write = (cfg->only_stereo != -1) ? 2 : channels;
size_t bytes_done; size_t bytes_done;
bytes_done = make_wav_header(wav_buf,0x100, bytes_done = make_wav_header(wav_buf,0x100,
len_samples, vgmstream->sample_rate, channels_write, len_samples, vgmstream->sample_rate, channels_write,
cfg.write_lwav, cfg.lwav_loop_start, cfg.lwav_loop_end); cfg->write_lwav, cfg->lwav_loop_start, cfg->lwav_loop_end);
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile); fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
} }
if (cfg.seek_samples1 >= 0) if (cfg->seek_samples1 >= 0)
seek_vgmstream(vgmstream, cfg.seek_samples1); seek_vgmstream(vgmstream, cfg->seek_samples1);
if (cfg.seek_samples2 >= 0) if (cfg->seek_samples2 >= 0)
seek_vgmstream(vgmstream, cfg.seek_samples2); seek_vgmstream(vgmstream, cfg->seek_samples2);
/* decode */ /* decode */
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) { for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
@ -739,11 +780,11 @@ int main(int argc, char** argv) {
render_vgmstream(buf, to_get, vgmstream); render_vgmstream(buf, to_get, vgmstream);
if (!cfg.decode_only) { if (!cfg->decode_only) {
swap_samples_le(buf, channels * to_get); /* write PC endian */ swap_samples_le(buf, channels * to_get); /* write PC endian */
if (cfg.only_stereo != -1) { if (cfg->only_stereo != -1) {
for (j = 0; j < to_get; j++) { for (j = 0; j < to_get; j++) {
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample_t), 2, outfile); fwrite(buf + j*channels + (cfg->only_stereo*2), sizeof(sample_t), 2, outfile);
} }
} else { } else {
fwrite(buf, sizeof(sample_t), to_get * channels, outfile); fwrite(buf, sizeof(sample_t), to_get * channels, outfile);
@ -758,26 +799,26 @@ int main(int argc, char** argv) {
/* try again with (for testing reset_vgmstream, simulates a seek to 0 after changing internal state) */ /* try again with (for testing reset_vgmstream, simulates a seek to 0 after changing internal state) */
if (cfg.test_reset) { if (cfg->test_reset) {
char outfilename_reset[PATH_LIMIT]; char outfilename_reset[PATH_LIMIT];
strcpy(outfilename_reset, cfg.outfilename); strcpy(outfilename_reset, cfg->outfilename);
strcat(outfilename_reset, ".reset.wav"); strcat(outfilename_reset, ".reset.wav");
outfile = fopen(outfilename_reset,"wb"); outfile = fopen(outfilename_reset,"wb");
if (!outfile) { if (!outfile) {
fprintf(stderr,"failed to open %s for output\n",outfilename_reset); fprintf(stderr, "failed to open %s for output\n", outfilename_reset);
goto fail; goto fail;
} }
/* slap on a .wav header */ /* slap on a .wav header */
if (!cfg.decode_only) { if (!cfg->decode_only) {
uint8_t wav_buf[0x100]; uint8_t wav_buf[0x100];
int channels_write = (cfg.only_stereo != -1) ? 2 : channels; int channels_write = (cfg->only_stereo != -1) ? 2 : channels;
size_t bytes_done; size_t bytes_done;
bytes_done = make_wav_header(wav_buf,0x100, bytes_done = make_wav_header(wav_buf,0x100,
len_samples, vgmstream->sample_rate, channels_write, len_samples, vgmstream->sample_rate, channels_write,
cfg.write_lwav, cfg.lwav_loop_start, cfg.lwav_loop_end); cfg->write_lwav, cfg->lwav_loop_start, cfg->lwav_loop_end);
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile); fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
} }
@ -785,10 +826,10 @@ int main(int argc, char** argv) {
reset_vgmstream(vgmstream); reset_vgmstream(vgmstream);
if (cfg.seek_samples1 >= 0) if (cfg->seek_samples1 >= 0)
seek_vgmstream(vgmstream, cfg.seek_samples1); seek_vgmstream(vgmstream, cfg->seek_samples1);
if (cfg.seek_samples2 >= 0) if (cfg->seek_samples2 >= 0)
seek_vgmstream(vgmstream, cfg.seek_samples2); seek_vgmstream(vgmstream, cfg->seek_samples2);
/* decode */ /* decode */
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) { for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
@ -798,11 +839,11 @@ int main(int argc, char** argv) {
render_vgmstream(buf, to_get, vgmstream); render_vgmstream(buf, to_get, vgmstream);
if (!cfg.decode_only) { if (!cfg->decode_only) {
swap_samples_le(buf, channels * to_get); /* write PC endian */ swap_samples_le(buf, channels * to_get); /* write PC endian */
if (cfg.only_stereo != -1) { if (cfg->only_stereo != -1) {
for (j = 0; j < to_get; j++) { for (j = 0; j < to_get; j++) {
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample_t), 2, outfile); fwrite(buf + j*channels + (cfg->only_stereo*2), sizeof(sample_t), 2, outfile);
} }
} else { } else {
fwrite(buf, sizeof(sample_t) * channels, to_get, outfile); fwrite(buf, sizeof(sample_t) * channels, to_get, outfile);
@ -819,16 +860,16 @@ int main(int argc, char** argv) {
close_vgmstream(vgmstream); close_vgmstream(vgmstream);
free(buf); free(buf);
return EXIT_SUCCESS; return 1;
fail: fail:
if (!cfg.play_sdtout) { if (!cfg->play_sdtout) {
if (outfile != NULL) if (outfile != NULL)
fclose(outfile); fclose(outfile);
} }
close_vgmstream(vgmstream); close_vgmstream(vgmstream);
free(buf); free(buf);
return EXIT_FAILURE; return 0;
} }
#ifdef HAVE_JSON #ifdef HAVE_JSON