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
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

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 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) {