vgmstream/cli/vgmstream_cli.c

701 lines
21 KiB
C
Raw Normal View History

2021-08-15 19:24:48 +02:00
/**
* vgmstream CLI decoder
*/
#define POSIXLY_CORRECT
2021-07-29 23:21:59 +02:00
2024-07-28 17:29:43 +02:00
#include <stdio.h>
#include <getopt.h>
2024-07-25 17:15:49 +02:00
#ifdef WIN32
#include <io.h>
#include <fcntl.h>
#else
#include <unistd.h>
#endif
#ifndef STDOUT_FILENO
#define STDOUT_FILENO 1
#endif
2021-08-15 19:24:48 +02:00
2024-07-25 18:21:23 +02:00
#include "vgmstream_cli.h"
#include "wav_utils.h"
#include "../version.h"
#ifndef VGMSTREAM_VERSION
2021-08-15 19:24:48 +02:00
#define VGMSTREAM_VERSION "unknown version " __DATE__
#endif
2021-08-15 19:24:48 +02:00
#define APP_NAME "vgmstream CLI decoder " VGMSTREAM_VERSION
#define APP_INFO APP_NAME " (" __DATE__ ")"
/* Low values are ok as there is very little performance difference, but higher may improve write I/O in some systems.
2021-09-26 19:31:43 +02:00
* For systems with less memory (like wasm without -s ALLOW_MEMORY_GROWTH) lower helps a bit. */
#ifdef __EMSCRIPTEN__
#define SAMPLE_BUFFER_SIZE 1024
#else
#define SAMPLE_BUFFER_SIZE (1024*8)
2021-09-26 19:31:43 +02:00
#endif
#define MIN_SAMPLE_BUFFER_SIZE 128
#define MAX_SAMPLE_BUFFER_SIZE (1024*128)
2021-07-29 23:21:59 +02:00
/* getopt globals from .h, for reference (the horror...) */
//extern char* optarg;
//extern int optind, opterr, optopt;
2024-07-23 22:31:13 +02:00
static void print_usage(const char* progname, bool is_help) {
2021-10-01 00:03:48 +02:00
fprintf(is_help ? stdout : stderr, APP_INFO "\n"
2021-08-15 19:24:48 +02:00
"Usage: %s [-o <outfile.wav>] [options] <infile> ...\n"
2018-09-07 00:21:17 +02:00
"Options:\n"
2020-07-26 11:20:49 +02:00
" -o <outfile.wav>: name of output .wav file, default <infile>.wav\n"
" <outfile> wildcards can be ?s=subsong, ?n=stream name, ?f=infile\n"
2021-10-01 00:03:48 +02:00
" -m: print metadata only, don't decode\n"
2018-09-07 00:21:17 +02:00
" -i: ignore looping information and play the whole stream once\n"
2021-10-01 00:03:48 +02:00
" -l N.n: loop count, default 2.0\n"
" -f N.n: fade time in seconds after N loops, default 10.0\n"
" -d N.n: fade delay in seconds, default 0.0\n"
" -F: don't fade after N loops and play the rest of the stream\n"
" -e: set end-to-end looping (if file has no loop points)\n"
2018-09-07 00:21:17 +02:00
" -E: force end-to-end looping even if file has real loop points\n"
" -s N: select subsong N, if the format supports multiple subsongs\n"
2021-10-01 00:03:48 +02:00
" -S N: select end subsong N (set 0 for 'all')\n"
2018-09-07 00:21:17 +02:00
" -p: output to stdout (for piping into another program)\n"
" -P: output to stdout even if stdout is a terminal\n"
" -c: loop forever (continuously) to stdout\n"
2024-07-25 17:15:49 +02:00
" -L: append a smpl chunk and create a looping wav\n"
2024-07-23 22:31:13 +02:00
//" -w: allow .wav in original sample format rather than downmixing to PCM16\n"
2020-09-28 03:07:10 +02:00
" -V: print version info and supported extensions as JSON\n"
" -I: print requested file info as JSON\n"
2024-07-25 17:15:49 +02:00
" -h: print all commands\n"
, progname);
2024-07-23 22:31:13 +02:00
if (!is_help)
2020-08-01 12:25:00 +02:00
return;
2024-07-23 22:31:13 +02:00
fprintf(is_help ? stdout : stderr,
2024-07-23 22:31:13 +02:00
"Extra options:\n"
2021-10-01 00:03:48 +02:00
" -2 N: only output the Nth (first is 0) set of stereo channels\n"
" -x: decode and print adxencd command line to encode as ADX\n"
" -g: decode and print oggenc command line to encode as OGG\n"
" -b: decode and print batch variable commands\n"
2020-08-01 12:25:00 +02:00
" -v: validate extensions (for extension testing)\n"
2021-10-01 00:03:48 +02:00
" -r: reset and output a second file (for reset testing)\n"
" -k N: kills (seeks) N samples before decoding (for seek testing)\n"
2023-05-29 00:38:31 +02:00
" -2 seeks to loop start, -3 seeks to loop end\n"
2021-10-01 00:03:48 +02:00
" -K N: kills (seeks) again to N samples before decoding (for seek testing)\n"
2021-10-23 13:02:34 +02:00
" -t: print !tags found in !tags.m3u (for tag testing)\n"
" -T: print title (for title testing)\n"
2020-08-01 12:25:00 +02:00
" -D <max channels>: downmix to <max channels> (for plugin downmix testing)\n"
" -B <samples> force a sample buffer size (for api testing)\n"
2020-08-01 12:25:00 +02:00
" -O: decode but don't write to file (for performance testing)\n"
);
}
2024-07-25 17:15:49 +02:00
static bool parse_config(cli_config_t* cfg, int argc, char** argv) {
int opt;
/* non-zero defaults */
cfg->sample_buffer_size = SAMPLE_BUFFER_SIZE;
cfg->loop_count = 2.0;
cfg->fade_time = 10.0;
cfg->seek_samples1 = -1;
cfg->seek_samples2 = -1;
2021-09-26 19:31:43 +02:00
opterr = 0; /* don't let getopt print errors to stdout automatically */
optind = 1; /* reset getopt's ugly globals (needed in wasm that may call same main() multiple times) */
/* read config */
2024-07-28 18:24:40 +02:00
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:tTk:K:hOvD:S:B:VI")) != -1) {
switch (opt) {
case 'o':
cfg->outfilename = optarg;
break;
2024-07-25 17:15:49 +02:00
// playback config
case 'l':
2021-10-01 00:03:48 +02:00
//cfg->loop_count = strtod(optarg, &end); //C99, allow?
//if (*end != '\0') goto fail_arg;
cfg->loop_count = atof(optarg);
break;
case 'f':
cfg->fade_time = atof(optarg);
break;
case 'd':
cfg->fade_delay = atof(optarg);
break;
2024-07-25 17:15:49 +02:00
case 'F':
cfg->ignore_fade = true;
break;
case 'i':
2024-07-23 22:31:13 +02:00
cfg->ignore_loop = true;
break;
2024-07-25 17:15:49 +02:00
case 'e':
cfg->force_loop = true;
break;
case 'E':
cfg->really_force_loop = true;
break;
case 'p':
2024-07-23 22:31:13 +02:00
cfg->play_sdtout = true;
break;
case 'P':
2024-07-23 22:31:13 +02:00
cfg->play_wreckless = true;
cfg->play_sdtout = true;
break;
case 'c':
2024-07-23 22:31:13 +02:00
cfg->play_forever = true;
break;
2024-07-25 17:15:49 +02:00
// subsongs
case 's':
cfg->subsong_index = atoi(optarg);
break;
case 'S':
cfg->subsong_end = atoi(optarg);
if (!cfg->subsong_end)
cfg->subsong_end = -1; /* signal up to end (otherwise 0 = not set) */
if (!cfg->subsong_index)
cfg->subsong_index = 1;
break;
// wav config
case 'L':
cfg->write_lwav = true;
break;
case 'w':
cfg->write_original_wav = true;
break;
// print flags
case 'm':
2024-07-23 22:31:13 +02:00
cfg->print_metaonly = true;
break;
case 'x':
2024-07-23 22:31:13 +02:00
cfg->print_adxencd = true;
break;
case 'g':
2024-07-23 22:31:13 +02:00
cfg->print_oggenc = true;
break;
case 'b':
2024-07-23 22:31:13 +02:00
cfg->print_batchvar = true;
break;
2024-07-25 17:15:49 +02:00
case 'T':
cfg->print_title = true;
break;
2024-07-25 17:15:49 +02:00
case 't':
cfg->tag_filename = "!tags.m3u";
break;
2024-07-25 17:15:49 +02:00
// debug stuff
case 'O':
cfg->decode_only = true;
break;
case 'r':
2024-07-23 22:31:13 +02:00
cfg->test_reset = true;
break;
2024-07-25 17:15:49 +02:00
case 'v':
cfg->validate_extensions = true;
break;
2019-03-22 00:37:59 +01:00
case 'k':
cfg->seek_samples1 = atoi(optarg);
break;
case 'K':
cfg->seek_samples2 = atoi(optarg);
2019-03-22 00:37:59 +01:00
break;
2020-08-01 12:25:00 +02:00
case 'D':
cfg->downmix_channels = atoi(optarg);
break;
case 'B':
cfg->sample_buffer_size = atoi(optarg);
if (cfg->sample_buffer_size < MIN_SAMPLE_BUFFER_SIZE || cfg->sample_buffer_size > MAX_SAMPLE_BUFFER_SIZE) {
fprintf(stderr, "incorrect sample buffer value\n");
goto fail;
}
break;
2024-07-25 17:15:49 +02:00
case '2':
cfg->stereo_track = atoi(optarg) + 1;
2024-07-25 17:15:49 +02:00
break;
case 'h':
2024-07-23 22:31:13 +02:00
print_usage(argv[0], true);
goto fail;
2020-09-28 03:07:10 +02:00
case 'V':
2024-07-25 19:10:46 +02:00
print_json_version(VGMSTREAM_VERSION);
2020-09-28 03:07:10 +02:00
goto fail;
case 'I':
2024-07-23 22:31:13 +02:00
cfg->print_metajson = true;
2020-09-28 03:07:10 +02:00
break;
case '?':
2021-10-01 00:03:48 +02:00
fprintf(stderr, "missing argument or unknown option -%c\n", optopt);
2018-09-07 00:21:17 +02:00
goto fail;
default:
2024-07-23 22:31:13 +02:00
print_usage(argv[0], false);
2018-09-07 00:21:17 +02:00
goto fail;
}
}
2021-10-01 00:03:48 +02:00
/* filenames go last in POSIX getopt, not so in glibc getopt */ //TODO unify
if (optind != argc - 1) {
2021-05-09 23:27:10 +02:00
/* check there aren't commands after filename */
2024-07-23 22:31:13 +02:00
for (int i = optind; i < argc; i++) {
2021-05-09 23:27:10 +02:00
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) {
2021-10-01 00:03:48 +02:00
fprintf(stderr, "missing input file\n");
2024-07-23 22:31:13 +02:00
print_usage(argv[0], 0);
2018-09-07 00:21:17 +02:00
goto fail;
}
2021-05-09 23:27:10 +02:00
if (cfg->outfilename && strchr(cfg->outfilename, '?') != NULL) {
cfg->outfilename_config = cfg->outfilename;
cfg->outfilename = NULL;
}
2024-07-23 22:31:13 +02:00
return true;
2018-09-07 00:21:17 +02:00
fail:
2024-07-23 22:31:13 +02:00
return false;
}
2024-07-25 17:15:49 +02:00
static bool validate_config(cli_config_t* cfg) {
if (cfg->play_sdtout && (!cfg->play_wreckless && isatty(STDOUT_FILENO))) {
2021-05-09 23:27:10 +02:00
fprintf(stderr, "Are you sure you want to output wave data to the terminal?\nIf so use -P instead of -p.\n");
2018-09-07 00:21:17 +02:00
goto fail;
}
if (cfg->play_forever && !cfg->play_sdtout) {
2021-05-09 23:27:10 +02:00
fprintf(stderr, "-c must use -p or -P\n");
2018-09-07 00:21:17 +02:00
goto fail;
}
if (cfg->play_sdtout && cfg->outfilename) {
2021-05-09 23:27:10 +02:00
fprintf(stderr, "use either -p or -o\n");
2018-09-07 00:21:17 +02:00
goto fail;
}
2020-07-21 19:46:55 +02:00
/* other options have built-in priority defined */
2024-07-23 22:31:13 +02:00
return true;
2018-09-07 00:21:17 +02:00
fail:
2024-07-23 22:31:13 +02:00
return false;
}
2024-07-25 17:15:49 +02:00
/* ************************************************************ */
2018-09-07 00:21:17 +02:00
2024-07-25 17:15:49 +02:00
static void apply_config(VGMSTREAM* vgmstream, cli_config_t* cfg) {
2020-07-21 19:46:55 +02:00
vgmstream_cfg_t vcfg = {0};
2020-07-21 19:46:55 +02:00
/* write loops in the wav, but don't actually loop it */
if (cfg->write_lwav) {
2024-07-23 22:31:13 +02:00
vcfg.disable_config_override = true;
cfg->ignore_loop = true;
if (vgmstream->loop_start_sample < vgmstream->loop_end_sample) {
cfg->lwav_loop_start = vgmstream->loop_start_sample;
cfg->lwav_loop_end = vgmstream->loop_end_sample;
cfg->lwav_loop_end--; /* from spec, +1 is added when reading "smpl" */
}
2021-08-22 13:14:47 +02:00
else {
/* reset for subsongs */
cfg->lwav_loop_start = 0;
cfg->lwav_loop_end = 0;
}
}
2020-07-21 19:46:55 +02:00
/* only allowed if manually active */
if (cfg->play_forever) {
2024-07-23 22:31:13 +02:00
vcfg.allow_play_forever = true;
}
2020-07-21 19:46:55 +02:00
vcfg.play_forever = cfg->play_forever;
vcfg.fade_time = cfg->fade_time;
vcfg.loop_count = cfg->loop_count;
2020-07-21 19:46:55 +02:00
vcfg.fade_delay = cfg->fade_delay;
2020-07-21 19:46:55 +02:00
vcfg.ignore_loop = cfg->ignore_loop;
vcfg.force_loop = cfg->force_loop;
vcfg.really_force_loop = cfg->really_force_loop;
vcfg.ignore_fade = cfg->ignore_fade;
2018-09-07 00:21:17 +02:00
2020-07-21 19:46:55 +02:00
vgmstream_apply_config(vgmstream, &vcfg);
}
2024-07-25 17:15:49 +02:00
static bool write_file(VGMSTREAM* vgmstream, cli_config_t* cfg) {
FILE* outfile = NULL;
int32_t len_samples;
sample_t* buf = NULL;
int channels, input_channels;
2020-07-21 19:46:55 +02:00
2020-09-28 03:07:10 +02:00
2024-07-25 17:15:49 +02:00
channels = vgmstream->channels;
input_channels = vgmstream->channels;
2020-09-28 03:07:10 +02:00
2024-07-25 17:15:49 +02:00
vgmstream_mixing_enable(vgmstream, 0, &input_channels, &channels);
2020-09-28 03:07:10 +02:00
2024-07-25 17:15:49 +02:00
/* last init */
buf = malloc(cfg->sample_buffer_size * sizeof(sample_t) * input_channels);
2024-07-25 17:15:49 +02:00
if (!buf) {
fprintf(stderr, "failed allocating output buffer\n");
goto fail;
}
2024-07-25 17:15:49 +02:00
/* simulate seek */
len_samples = vgmstream_get_samples(vgmstream);
if (cfg->seek_samples2 >= 0)
len_samples -= cfg->seek_samples2;
else if (cfg->seek_samples1 >= 0)
len_samples -= cfg->seek_samples1;
2024-07-25 17:15:49 +02:00
if (cfg->seek_samples1 >= 0)
seek_vgmstream(vgmstream, cfg->seek_samples1);
if (cfg->seek_samples2 >= 0)
seek_vgmstream(vgmstream, cfg->seek_samples2);
2024-07-25 17:15:49 +02:00
/* output file */
if (cfg->play_sdtout) {
outfile = stdout;
}
2024-07-25 17:15:49 +02:00
else if (!cfg->decode_only) {
outfile = fopen(cfg->outfilename, "wb");
if (!outfile) {
fprintf(stderr, "failed to open %s for output\n", cfg->outfilename);
goto fail;
}
2024-07-25 17:15:49 +02:00
/* no improvement */
//setvbuf(outfile, NULL, _IOFBF, cfg->sample_buffer_size * sizeof(sample_t) * input_channels);
2024-07-25 17:15:49 +02:00
//setvbuf(outfile, NULL, _IONBF, 0);
}
2024-07-25 17:15:49 +02:00
else {
// decode only: outfile is NULL (won't write anything)
}
2024-07-23 22:31:13 +02:00
2024-07-25 17:15:49 +02:00
/* decode forever */
while (cfg->play_forever && !cfg->decode_only) {
int to_get = cfg->sample_buffer_size;
2024-07-25 17:15:49 +02:00
render_vgmstream(buf, to_get, vgmstream);
2021-05-09 23:27:10 +02:00
2024-07-25 17:15:49 +02:00
wav_swap_samples_le(buf, channels * to_get, 0);
fwrite(buf, sizeof(sample_t), to_get * channels, outfile);
/* should write infinitely until program kill */
2021-05-09 23:27:10 +02:00
}
2024-07-25 17:15:49 +02:00
/* slap on a .wav header */
if (!cfg->decode_only) {
uint8_t wav_buf[0x100];
size_t bytes_done;
2021-05-09 23:27:10 +02:00
2024-07-25 17:15:49 +02:00
wav_header_t wav = {
.sample_count = len_samples,
.sample_rate = vgmstream->sample_rate,
.channels = channels,
.write_smpl_chunk = cfg->write_lwav,
.loop_start = cfg->lwav_loop_start,
.loop_end = cfg->lwav_loop_end
};
2024-07-25 17:15:49 +02:00
bytes_done = wav_make_header(wav_buf, 0x100, &wav);
fwrite(wav_buf, sizeof(uint8_t), bytes_done, outfile);
}
2024-07-25 17:15:49 +02:00
/* decode */
for (int i = 0; i < len_samples; i += cfg->sample_buffer_size) {
int to_get = cfg->sample_buffer_size;
if (i + cfg->sample_buffer_size > len_samples)
2024-07-25 17:15:49 +02:00
to_get = len_samples - i;
2024-07-25 17:15:49 +02:00
render_vgmstream(buf, to_get, vgmstream);
2024-07-25 17:15:49 +02:00
if (!cfg->decode_only) {
wav_swap_samples_le(buf, channels * to_get, 0);
fwrite(buf, sizeof(sample_t), to_get * channels, outfile);
}
}
2024-07-25 17:15:49 +02:00
if (outfile && outfile != stdout)
fclose(outfile);
free(buf);
2024-07-23 22:31:13 +02:00
return true;
fail:
2024-07-25 17:15:49 +02:00
if (outfile && outfile != stdout)
fclose(outfile);
free(buf);
2024-07-23 22:31:13 +02:00
return false;
}
static bool is_valid_extension(cli_config_t* cfg) {
/* for plugin testing */
if (!cfg->validate_extensions)
return true;
vgmstream_ctx_valid_cfg vcfg = {0};
vcfg.skip_standard = 0;
vcfg.reject_extensionless = 0;
vcfg.accept_unknown = 0;
vcfg.accept_common = 0;
return vgmstream_ctx_is_valid(cfg->infilename, &vcfg);
}
2024-07-28 17:29:43 +02:00
static VGMSTREAM* open_vgmstream(cli_config_t* cfg) {
STREAMFILE* sf = NULL;
VGMSTREAM* vgmstream = NULL;
sf = open_stdio_streamfile(cfg->infilename);
if (!sf) {
fprintf(stderr, "file %s not found\n", cfg->infilename);
return NULL;
}
sf->stream_index = cfg->subsong_index;
vgmstream = init_vgmstream_from_STREAMFILE(sf);
if (!vgmstream) {
fprintf(stderr, "failed opening %s\n", cfg->infilename);
goto fail;
}
/* modify the VGMSTREAM if needed (before printing file info) */
apply_config(vgmstream, cfg);
/* enable after config but before outbuf */
if (cfg->downmix_channels) {
vgmstream_mixing_autodownmix(vgmstream, cfg->downmix_channels);
}
else if (cfg->stereo_track > 0) {
vgmstream_mixing_stereo_only(vgmstream, cfg->stereo_track - 1);
}
vgmstream_mixing_enable(vgmstream, cfg->sample_buffer_size, NULL, NULL);
close_streamfile(sf);
return vgmstream;
fail:
close_streamfile(sf);
close_vgmstream(vgmstream);
return NULL;
}
2024-07-25 17:15:49 +02:00
static bool convert_file(cli_config_t* cfg) {
2021-05-09 23:27:10 +02:00
VGMSTREAM* vgmstream = NULL;
2024-07-28 17:29:43 +02:00
char outfilename_temp[CLI_PATH_LIMIT];
2021-05-09 23:27:10 +02:00
int32_t len_samples;
/* for plugin testing */
if (!is_valid_extension(cfg))
return false;
/* open streamfile and pass subsong */
2024-07-28 17:29:43 +02:00
vgmstream = open_vgmstream(cfg);
if (!vgmstream) goto fail;
2024-07-28 17:29:43 +02:00
/* force load total subsongs if signalled */
if (cfg->subsong_end == -1) {
cfg->subsong_end = vgmstream->num_streams;
close_vgmstream(vgmstream);
return true;
2021-06-17 22:54:33 +02:00
}
2019-03-18 00:05:44 +01:00
2020-07-26 11:20:49 +02:00
/* get final play config */
len_samples = vgmstream_get_samples(vgmstream);
2023-05-29 00:38:31 +02:00
if (len_samples <= 0) {
fprintf(stderr, "wrong time config\n");
2020-07-26 11:20:49 +02:00
goto fail;
2023-05-29 00:38:31 +02:00
}
2020-07-26 11:20:49 +02:00
2023-05-29 00:38:31 +02:00
/* special values for loop testing */
if (cfg->seek_samples1 == -2) { /* loop start...end */
2021-06-17 22:54:33 +02:00
cfg->seek_samples1 = vgmstream->loop_start_sample;
2023-05-29 00:38:31 +02:00
}
if (cfg->seek_samples1 == -3) { /* loop end..end */
cfg->seek_samples1 = vgmstream->loop_end_sample;
}
/* would be ignored by seek code though (allowed for seek_samples2 to test this) */
if (cfg->seek_samples1 < -1 || cfg->seek_samples1 >= len_samples) {
fprintf(stderr, "wrong seek config\n");
goto fail;
}
2021-06-17 22:54:33 +02:00
2021-05-09 23:27:10 +02:00
if (cfg->play_forever && !vgmstream_get_play_forever(vgmstream)) {
fprintf(stderr, "file can't be played forever");
2018-09-07 00:21:17 +02:00
goto fail;
}
/* prepare output */
2021-06-17 22:54:33 +02:00
{
/* note that outfilename_temp must persist outside this block, hence the external array */
if (!cfg->outfilename_config && !cfg->outfilename) {
/* defaults */
2021-08-08 13:31:39 +02:00
int has_subsongs = (cfg->subsong_index >= 1 && vgmstream->num_streams >= 1);
cfg->outfilename_config = has_subsongs ?
"?f#?s.wav" :
"?f.wav";
/* maybe should avoid overwriting with this auto-name, for the unlikely
* case of file header-body pairs (file.ext+file.ext.wav) */
}
2021-05-09 23:27:10 +02:00
if (cfg->outfilename_config) {
/* special substitution */
2021-08-08 13:31:39 +02:00
replace_filename(outfilename_temp, sizeof(outfilename_temp), cfg, vgmstream);
2021-05-09 23:27:10 +02:00
cfg->outfilename = outfilename_temp;
}
if (!cfg->outfilename)
goto fail;
/* don't overwrite itself! */
2021-05-09 23:27:10 +02:00
if (strcmp(cfg->outfilename, cfg->infilename) == 0) {
fprintf(stderr, "same infile and outfile name: %s\n", cfg->outfilename);
goto fail;
}
}
2017-10-28 10:54:46 +02:00
/* prints */
2021-05-09 23:27:10 +02:00
if (!cfg->print_metajson) {
print_info(vgmstream, cfg);
print_tags(cfg);
print_title(vgmstream, cfg);
2020-09-28 03:07:10 +02:00
}
else {
2024-07-25 19:10:46 +02:00
print_json_info(vgmstream, cfg, VGMSTREAM_VERSION);
2020-09-28 03:07:10 +02:00
}
2018-11-03 19:29:45 +01:00
/* prints done */
2021-05-09 23:27:10 +02:00
if (cfg->print_metaonly) {
2017-10-28 10:54:46 +02:00
close_vgmstream(vgmstream);
2024-07-23 22:31:13 +02:00
return true;
}
2021-06-17 22:54:33 +02:00
/* main decode */
write_file(vgmstream, cfg);
2024-07-23 22:31:13 +02:00
/* try again with reset (for testing, simulates a seek to 0 after changing internal state)
* (could simulate by seeking to last sample then to 0, too) */
2021-06-17 22:54:33 +02:00
if (cfg->test_reset) {
2024-07-28 17:29:43 +02:00
char outfilename_reset[CLI_PATH_LIMIT];
2024-07-23 22:31:13 +02:00
snprintf(outfilename_reset, sizeof(outfilename_reset), "%s.reset.wav", cfg->outfilename);
2021-06-17 22:54:33 +02:00
cfg->outfilename = outfilename_reset;
2021-06-17 22:54:33 +02:00
reset_vgmstream(vgmstream);
write_file(vgmstream, cfg);
}
close_vgmstream(vgmstream);
2024-07-23 22:31:13 +02:00
return true;
2021-06-17 22:54:33 +02:00
fail:
close_vgmstream(vgmstream);
2024-07-23 22:31:13 +02:00
return false;
2021-06-17 22:54:33 +02:00
}
2024-07-25 17:15:49 +02:00
static bool convert_subsongs(cli_config_t* cfg) {
int res, ko_count;
/* restore original values in case of multiple parsed files */
int start_temp = cfg->subsong_index;
int end_temp = cfg->subsong_end;
2021-06-17 22:54:33 +02:00
2024-07-25 17:15:49 +02:00
/* first call should force load max subsongs */
if (cfg->subsong_end == -1) {
res = convert_file(cfg);
if (!res) goto fail;
}
2024-07-25 17:15:49 +02:00
//;VGM_LOG("CLI: subsongs %i to %i\n", cfg->subsong_index, cfg->subsong_end + 1);
2020-07-26 20:15:13 +02:00
2024-07-25 17:15:49 +02:00
/* convert subsong range */
ko_count = 0 ;
for (int subsong = cfg->subsong_index; subsong < cfg->subsong_end + 1; subsong++) {
cfg->subsong_index = subsong;
2020-07-26 20:15:13 +02:00
2024-07-25 17:15:49 +02:00
res = convert_file(cfg);
if (!res) ko_count++;
2020-07-26 20:15:13 +02:00
}
2024-07-25 17:15:49 +02:00
if (ko_count) {
fprintf(stderr, "failed %i subsongs\n", ko_count);
}
2024-07-25 17:15:49 +02:00
cfg->subsong_index = start_temp;
cfg->subsong_end = end_temp;
2024-07-23 22:31:13 +02:00
return true;
2018-09-07 00:21:17 +02:00
fail:
2024-07-25 17:15:49 +02:00
cfg->subsong_index = start_temp;
cfg->subsong_end = end_temp;
2024-07-23 22:31:13 +02:00
return false;
}
2024-07-25 17:15:49 +02:00
int main(int argc, char** argv) {
cli_config_t cfg = {0};
bool res, ok;
2021-06-17 22:54:33 +02:00
2024-07-28 17:29:43 +02:00
vgmstream_set_log_stdout(VGM_LOG_LEVEL_ALL);
2024-07-25 17:15:49 +02:00
/* read args */
res = parse_config(&cfg, argc, argv);
if (!res) goto fail;
2024-07-25 17:15:49 +02:00
res = validate_config(&cfg);
if (!res) goto fail;
2021-05-09 23:27:10 +02:00
2024-07-25 17:15:49 +02:00
#ifdef WIN32
/* make stdout output work with windows */
if (cfg.play_sdtout) {
_setmode(fileno(stdout),_O_BINARY);
2020-09-29 09:11:19 +02:00
}
2024-07-25 17:15:49 +02:00
#endif
2020-09-29 09:11:19 +02:00
2024-07-25 17:15:49 +02:00
ok = false;
for (int i = 0; i < cfg.infilenames_count; i++) {
/* current name, to avoid passing params all the time */
cfg.infilename = cfg.infilenames[i];
if (cfg.outfilename_config)
cfg.outfilename = NULL;
2024-07-25 17:15:49 +02:00
if (cfg.subsong_index > 0 && cfg.subsong_end != 0) {
res = convert_subsongs(&cfg);
//if (!res) goto fail;
if (res) ok = true;
}
else {
res = convert_file(&cfg);
//if (!res) goto fail;
if (res) ok = true;
}
}
2021-05-09 23:27:10 +02:00
2024-07-25 17:15:49 +02:00
/* ok if at least one succeeds, for programs that check result code */
if (!ok)
goto fail;
2024-07-25 17:15:49 +02:00
return EXIT_SUCCESS;
fail:
return EXIT_FAILURE;
}