mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 00:20:47 +01:00
Add CLI option to write multiple subsongs
This commit is contained in:
parent
0854565b92
commit
bbc55c2036
43
README.md
43
README.md
@ -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
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user