mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-24 06:50:20 +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
|
||||
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
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user