diff --git a/README.md b/README.md index 4d650f7f..12bf1e5e 100644 --- a/README.md +++ b/README.md @@ -28,14 +28,14 @@ More technical docs: https://github.com/vgmstream/vgmstream/tree/master/doc ## Usage There are multiple end-user bits: -- a command line decoder called "test.exe/vgmstream-cli" -- a Winamp plugin called "in_vgmstream" -- a foobar2000 component called "foo_input_vgmstream" -- an XMPlay plugin called "xmp-vgmstream" -- an Audacious plugin called "libvgmstream" -- a command line player called "vgmstream123" +- a command line decoder called *test.exe/vgmstream-cli* +- a Winamp plugin called *in_vgmstream* +- a foobar2000 component called *foo_input_vgmstream* +- an XMPlay plugin called *xmp-vgmstream* +- an Audacious plugin called *libvgmstream* +- a command line player called *vgmstream123* -Main lib (plain vgmstream) is the code that handles internal conversion, while the +Main lib (plain *vgmstream*) is the code that handles internal conversion, while the above components are what you use to actually get sound. See *components* below for explanations about each one. @@ -97,28 +97,31 @@ files to the executable to decode them as `(filename.ext).wav`. There are multiple options that alter how the file is converted, for example: - `test.exe -m file.adx`: print info but don't decode - `test.exe -i -o file_noloop.wav file.hca`: convert without looping -- `test.exe -s 2 -F file.fsb`: play 2nd subsong + ending after 2.0 loops +- `test.exe -s 2 -F file.fsb`: write 2nd subsong + ending after 2.0 loops - `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 achieve similar results for other plugins using TXTP, described later. -With files multiple subsongs you need to specify manually subsong (by design, to avoid -massive data dumps since some formats have hundreds of subsongs), but you could do -some command line tricks: -``` -: REM extracts from subsong 5 to 10 in file.fsb -for /L %A in (5,1,10) do test.exe -s %A -o file_%A.wav file.fsb -``` - -Output filename in `-o` may use multiple wildcards: +Output filename in `-o` may use wildcards: - `?s`: sets current subsong (or 0 if format doesn't have subsongs) - `?0Ns`: same, but left pads subsong with up to `N` zeroes - `?n`: internal stream name, or input filename if format doesn't have name - `?f`: input filename -For example `test.exe -s 2 -o ?04s_?n.wav file.fsb` could generate `0002_song1.wav` +For example `test.exe -s 2 -o ?04s_?n.wav file.fsb` could generate `0002_song1.wav`. +Default output filename is `?f.wav`, or `?f#?s.wav` if you set subsongs (`-s/S`). + +For files containing multiple subsongs, you can write them all using some flags. +**WARNING, MAY TAKE A LOT OF SPACE!** Some files have been observed to contain +20000 +subsongs, so don't use this lightly. Remember to set an output name (`-o`) with subsong +wilcards (or leave it alone for the defaults). +- `test.exe -s 1 -S 100 file.bank`: writes from subsong 1 to subsong 100 +- `test.exe -s 101 -S 0 file.bank`: writes from subsong 101 to max subsong +- `test.exe -S 0 file.bank`: writes from subsong 1 to max subsong (automatically changes 0 to max) +- `test.exe -s 1 -S 5 -o bgm.wav file.bank`: writes 5 subsongs, but all overwrite the same file = wrong. +- `test.exe -s 1 -S 5 -o bgm_?02s.wav file.bank`: writes 5 subsongs, each named differently = correct. ### in_vgmstream (Winamp plugin) @@ -397,6 +400,7 @@ file, or static values. This allows vgmstream to play unsupported formats. more functions, plus doesn't modify original data. Usage example (used when opening an unknown file named `bgm_01.pcm`): + **.pcm.txth** ``` codec = PCM16LE @@ -421,6 +425,7 @@ per-file configurations like number of loops, remove unneeded channels, force looping, and many other features. Usage examples (open directly, name can be set freely): + **bgm01-full.txtp** ``` # plays 2 files as a single one @@ -599,6 +604,7 @@ enabled in preferences): ### TXTP matching To ease *TXTP* config, tags with plain files will match `.txtp` with config, and tags with `.txtp` config also match plain files: + **!tags.m3u** ``` # @TITLE Title1 @@ -620,6 +626,7 @@ BGM01.adx #P 10.0.txtp Since it matches when a tag is found, some cases that depend on order won't work. You can disable this feature manually then: + **!tags.m3u** ``` # $EXACTMATCH diff --git a/cli/vgmstream_cli.c b/cli/vgmstream_cli.c index 993c581f..a623ff5d 100644 --- a/cli/vgmstream_cli.c +++ b/cli/vgmstream_cli.c @@ -50,6 +50,7 @@ static void usage(const char* name, int is_full) { " -e: force end-to-end looping\n" " -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" + " -S N: select end subsong (set 0 for 'all')\n" " -m: print metadata only, don't decode\n" " -L: append a smpl chunk and create a looping wav\n" " -2 N: only output the Nth (first is 0) set of stereo channels\n" @@ -103,7 +104,8 @@ typedef struct { int print_batchvar; int write_lwav; int only_stereo; - int stream_index; + int subsong_index; + int subsong_end; double loop_count; double fade_time; @@ -145,7 +147,7 @@ static int parse_config(cli_config* cfg, int argc, char** argv) { opterr = 0; /* read config */ - while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:Tk:K:hOvD:" + while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:Tk:K:hOvD:S:" #ifdef HAVE_JSON "VI" #endif @@ -207,10 +209,17 @@ static int parse_config(cli_config* cfg, int argc, char** argv) { cfg->ignore_fade = 1; break; case 's': - cfg->stream_index = atoi(optarg); + 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; case 't': - cfg->tag_filename= optarg; + cfg->tag_filename = optarg; break; case 'T': cfg->show_title = 1; @@ -554,8 +563,10 @@ void replace_filename(char* dst, size_t dstsize, const char* outfilename, const /* ************************************************************ */ static int convert_file(cli_config* cfg); +static int convert_subsongs(cli_config* cfg); static int write_file(VGMSTREAM* vgmstream, cli_config* cfg); + int main(int argc, char** argv) { cli_config cfg = {0}; int i, res, ok; @@ -582,11 +593,19 @@ int main(int argc, char** argv) { if (cfg.outfilename_config) cfg.outfilename = NULL; - res = convert_file(&cfg); - //if (!res) goto fail; /* keep on truckin' */ - if (res) ok = 1; /* return ok if at least one succeeds, for programs that check result code */ + if (cfg.subsong_index > 0 && cfg.subsong_end != 0) { + res = convert_subsongs(&cfg); + //if (!res) goto fail; + if (res) ok = 1; + } + else { + res = convert_file(&cfg); + //if (!res) goto fail; + if (res) ok = 1; + } } + /* ok if at least one succeeds, for programs that check result code */ if (!ok) goto fail; @@ -595,6 +614,40 @@ fail: return EXIT_FAILURE; } +static int convert_subsongs(cli_config* cfg) { + int res; + int subsong; + /* restore original values in case of multiple parsed files */ + int start_temp = cfg->subsong_index; + int end_temp = cfg->subsong_end; + + /* first call should force load max subsongs */ + if (cfg->subsong_end == -1) { + res = convert_file(cfg); + if (!res) goto fail; + } + + + //;VGM_LOG("CLI: subsongs %i to %i\n", cfg->subsong_index, cfg->subsong_end + 1); + + /* convert subsong range */ + for (subsong = cfg->subsong_index; subsong < cfg->subsong_end + 1; subsong++) { + cfg->subsong_index = subsong; + + res = convert_file(cfg); + if (!res) goto fail; + } + + cfg->subsong_index = start_temp; + cfg->subsong_end = end_temp; + return 1; +fail: + cfg->subsong_index = start_temp; + cfg->subsong_end = end_temp; + return 0; +} + + static int convert_file(cli_config* cfg) { VGMSTREAM* vgmstream = NULL; char outfilename_temp[PATH_LIMIT]; @@ -623,7 +676,7 @@ static int convert_file(cli_config* cfg) { goto fail; } - sf->stream_index = cfg->stream_index; + sf->stream_index = cfg->subsong_index; vgmstream = init_vgmstream_from_STREAMFILE(sf); close_streamfile(sf); @@ -631,6 +684,13 @@ static int convert_file(cli_config* cfg) { fprintf(stderr, "failed opening %s\n", cfg->infilename); goto fail; } + + /* force load total subsongs if signalled */ + if (cfg->subsong_end == -1) { + cfg->subsong_end = vgmstream->num_streams; + close_vgmstream(vgmstream); + return 1; + } } @@ -666,19 +726,25 @@ static int convert_file(cli_config* cfg) { /* prepare output */ { + /* note that outfilename_temp must persist outside this block, hence the external array */ + + if (!cfg->outfilename_config && !cfg->outfilename) { + /* defaults */ + cfg->outfilename_config = (cfg->subsong_index >= 1) ? + "?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) */ + } + 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 */ - strcpy(outfilename_temp, cfg->infilename); - strcat(outfilename_temp, ".wav"); - cfg->outfilename = outfilename_temp; - /* maybe should avoid overwriting with this auto-name, for the unlikely - * case of file header-body pairs (file.ext+file.ext.wav) */ - } + + if (!cfg->outfilename) + goto fail; /* don't overwrite itself! */ if (strcmp(cfg->outfilename, cfg->infilename) == 0) {