Add CLI option to write multiple subsongs

This commit is contained in:
bnnm 2021-07-08 22:12:35 +02:00
parent 0854565b92
commit bbc55c2036
2 changed files with 107 additions and 34 deletions

View File

@ -28,14 +28,14 @@ More technical docs: https://github.com/vgmstream/vgmstream/tree/master/doc
## Usage ## Usage
There are multiple end-user bits: There are multiple end-user bits:
- a command line decoder called "test.exe/vgmstream-cli" - a command line decoder called *test.exe/vgmstream-cli*
- a Winamp plugin called "in_vgmstream" - a Winamp plugin called *in_vgmstream*
- a foobar2000 component called "foo_input_vgmstream" - a foobar2000 component called *foo_input_vgmstream*
- an XMPlay plugin called "xmp-vgmstream" - an XMPlay plugin called *xmp-vgmstream*
- an Audacious plugin called "libvgmstream" - an Audacious plugin called *libvgmstream*
- a command line player called "vgmstream123" - 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 above components are what you use to actually get sound. See *components* below for
explanations about each one. 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: 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 -m file.adx`: print info but don't decode
- `test.exe -i -o file_noloop.wav file.hca`: convert without looping - `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 -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` - `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.
With files multiple subsongs you need to specify manually subsong (by design, to avoid Output filename in `-o` may use wildcards:
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:
- `?s`: sets current subsong (or 0 if format doesn't have subsongs) - `?s`: sets current subsong (or 0 if format doesn't have subsongs)
- `?0Ns`: same, but left pads subsong with up to `N` zeroes - `?0Ns`: same, but left pads subsong with up to `N` zeroes
- `?n`: internal stream name, or input filename if format doesn't have name - `?n`: internal stream name, or input filename if format doesn't have name
- `?f`: input filename - `?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) ### 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. more functions, plus doesn't modify original data.
Usage example (used when opening an unknown file named `bgm_01.pcm`): Usage example (used when opening an unknown file named `bgm_01.pcm`):
**.pcm.txth** **.pcm.txth**
``` ```
codec = PCM16LE codec = PCM16LE
@ -421,6 +425,7 @@ per-file configurations like number of loops, remove unneeded channels,
force looping, and many other features. force looping, and many other features.
Usage examples (open directly, name can be set freely): Usage examples (open directly, name can be set freely):
**bgm01-full.txtp** **bgm01-full.txtp**
``` ```
# plays 2 files as a single one # plays 2 files as a single one
@ -599,6 +604,7 @@ enabled in preferences):
### TXTP matching ### TXTP matching
To ease *TXTP* config, tags with plain files will match `.txtp` with config, and tags To ease *TXTP* config, tags with plain files will match `.txtp` with config, and tags
with `.txtp` config also match plain files: with `.txtp` config also match plain files:
**!tags.m3u** **!tags.m3u**
``` ```
# @TITLE Title1 # @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. Since it matches when a tag is found, some cases that depend on order won't work.
You can disable this feature manually then: You can disable this feature manually then:
**!tags.m3u** **!tags.m3u**
``` ```
# $EXACTMATCH # $EXACTMATCH

View File

@ -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\n"
" -E: force end-to-end looping even if file has real loop points\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 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" " -m: print metadata only, don't decode\n"
" -L: append a smpl chunk and create a looping wav\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" " -2 N: only output the Nth (first is 0) set of stereo channels\n"
@ -103,7 +104,8 @@ typedef struct {
int print_batchvar; int print_batchvar;
int write_lwav; int write_lwav;
int only_stereo; int only_stereo;
int stream_index; int subsong_index;
int subsong_end;
double loop_count; double loop_count;
double fade_time; double fade_time;
@ -145,7 +147,7 @@ static int parse_config(cli_config* cfg, int argc, char** argv) {
opterr = 0; opterr = 0;
/* read config */ /* 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 #ifdef HAVE_JSON
"VI" "VI"
#endif #endif
@ -207,10 +209,17 @@ static int parse_config(cli_config* cfg, int argc, char** argv) {
cfg->ignore_fade = 1; cfg->ignore_fade = 1;
break; break;
case 's': 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; break;
case 't': case 't':
cfg->tag_filename= optarg; cfg->tag_filename = optarg;
break; break;
case 'T': case 'T':
cfg->show_title = 1; 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_file(cli_config* cfg);
static int convert_subsongs(cli_config* cfg);
static int write_file(VGMSTREAM* vgmstream, cli_config* cfg); static int write_file(VGMSTREAM* vgmstream, cli_config* cfg);
int main(int argc, char** argv) { int main(int argc, char** argv) {
cli_config cfg = {0}; cli_config cfg = {0};
int i, res, ok; int i, res, ok;
@ -582,11 +593,19 @@ int main(int argc, char** argv) {
if (cfg.outfilename_config) if (cfg.outfilename_config)
cfg.outfilename = NULL; cfg.outfilename = NULL;
res = convert_file(&cfg); if (cfg.subsong_index > 0 && cfg.subsong_end != 0) {
//if (!res) goto fail; /* keep on truckin' */ res = convert_subsongs(&cfg);
if (res) ok = 1; /* return ok if at least one succeeds, for programs that check result code */ //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) if (!ok)
goto fail; goto fail;
@ -595,6 +614,40 @@ fail:
return EXIT_FAILURE; 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) { static int convert_file(cli_config* cfg) {
VGMSTREAM* vgmstream = NULL; VGMSTREAM* vgmstream = NULL;
char outfilename_temp[PATH_LIMIT]; char outfilename_temp[PATH_LIMIT];
@ -623,7 +676,7 @@ static int convert_file(cli_config* cfg) {
goto fail; goto fail;
} }
sf->stream_index = cfg->stream_index; sf->stream_index = cfg->subsong_index;
vgmstream = init_vgmstream_from_STREAMFILE(sf); vgmstream = init_vgmstream_from_STREAMFILE(sf);
close_streamfile(sf); close_streamfile(sf);
@ -631,6 +684,13 @@ static int convert_file(cli_config* cfg) {
fprintf(stderr, "failed opening %s\n", cfg->infilename); fprintf(stderr, "failed opening %s\n", cfg->infilename);
goto fail; 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 */ /* 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) { if (cfg->outfilename_config) {
/* special substitution */ /* special substitution */
replace_filename(outfilename_temp, sizeof(outfilename_temp), cfg->outfilename_config, cfg->infilename, vgmstream); replace_filename(outfilename_temp, sizeof(outfilename_temp), cfg->outfilename_config, cfg->infilename, vgmstream);
cfg->outfilename = outfilename_temp; cfg->outfilename = outfilename_temp;
} }
else if (!cfg->outfilename) {
/* note that outfilename_temp must persist outside this block, hence the external array */ if (!cfg->outfilename)
strcpy(outfilename_temp, cfg->infilename); goto fail;
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) */
}
/* don't overwrite itself! */ /* don't overwrite itself! */
if (strcmp(cfg->outfilename, cfg->infilename) == 0) { if (strcmp(cfg->outfilename, cfg->infilename) == 0) {