From bc800419258e63bd0c40527ec857714cc30e7182 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 12 Sep 2020 15:03:22 +0200 Subject: [PATCH 1/2] Add subsongs and !tags.m3u support for Audacious --- README.md | 92 ++++++++++++++++---------- audacious/plugin.cc | 156 ++++++++++++++++++++++++++++++++++---------- audacious/plugin.h | 3 +- audacious/vfs.cc | 18 ++++- cli/vgmstream_cli.c | 23 +++++-- src/plugins.c | 52 +++++++++++++++ src/plugins.h | 9 +++ 7 files changed, 276 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 254cd7d8..3d8e7576 100644 --- a/README.md +++ b/README.md @@ -50,17 +50,17 @@ or in a system directory, or any other directory in the PATH variable. *Installation*: unzip the file and follow the above instructions for installing the other files needed. -Converts playable files to wav. Typical usage would be: +Converts playable files to wav. Typical usage would be: - `test.exe -o happy.wav happy.adx` to decode `happy.adx` to `happy.wav`. If command-line isn't your thing you can also drag and drop files to the -executable to decode them as (filename).wav +executable to decode them as `(filename).wav`. There are multiple options that alter how the file is converted, for example: - `test.exe -m -o file.wav file.adx`: print info but don't decode - `test.exe -i -o file.wav file.hca`: convert without looping - `test.exe -s 2 -F -o file.wav file.fsb`: play 2nd subsong + ending after 2.0 loops -- `test.exe -l 3.0 -f 5.0 -d 3.0 -o file.wav file.wem`: 3 loops, 3s delay, 5s fade +- `test.exe -l 3.0 -f 5.0 -d 3.0 -o file.wav file.wem`: 3 loops, 3s delay, 5s fade Available commands are printed when run with no flags. Note that you can also achieve similar results for other plugins using TXTP, described later. @@ -83,27 +83,30 @@ For example `test.exe -s 2 -o ?04s_?n.wav file.fsb` could generate `0002_song1.w ### in_vgmstream -*Installation*: drop the ```in_vgmstream.dll``` in your Winamp plugins directory, +*Installation*: drop the `in_vgmstream.dll` in your Winamp plugins directory, and follow the above instructions for installing the other files needed. Once installed supported files should be playable. ### xmp-vgmstream -*Installation*: drop the ```xmp-vgmstream.dll``` in your XMPlay plugins directory, +*Installation*: drop the `xmp-vgmstream.dll` in your XMPlay plugins directory, and follow the above instructions for installing the other files needed. Note that this has less features compared to in_vgmstream and has no configuration. -Since XMPlay supports Winamp plugins you may also use ```in_vgmstream.dll``` instead. +Since XMPlay supports Winamp plugins you may also use `in_vgmstream.dll` instead. Because the XMPlay MP3 decoder incorrectly tries to play some vgmstream exts, you need to manually fix it by going to **options > plugins > input > vgmstream** and in the "priority filetypes" put: `ahx,asf,awc,ckd,fsb,genh,msf,p3d,rak,scd,txth,xvag` +XMPlay cannot support subsongs due to player limitations, try using *TXTP* instead +(explained below). + ### foo_input_vgmstream *Installation*: every file should be installed automatically by the `.fb2k-component` bundle. -A known quirk is that when loop options or tags change, playlist won't refresh +A known quirk is that when loop options or tags change, playlist info won't refresh automatically. You need to manually refresh it by selecting songs and doing **shift + right click > Tagging > Reload info from file(s)**. @@ -149,6 +152,11 @@ not meant to be extracted (no simple separation). Some plugins are able to "unpa those files automatically into the playlist. For others without support, you can create multiple .txtp (explained below) to select one of the subsongs (like `bgm.sxd#10.txtp`). +You can use this python script to autogenerate one `.txtp` per subsong: +https://github.com/losnoco/vgmstream/tree/master/cli/txtp_maker.py +Put in the same dir as test.exe/vgmstream_cli, then to drag-and-drop files with subsongs +to `txtp_maker.py`. + ### Renamed files A few extensions that vgmstream supports clash with common ones. Since players like foobar or Winamp don't react well to that, they may be renamed to make @@ -180,11 +188,11 @@ internal loop info, or apply subtle fixes, but is also limited in some ways may work as a last resort to make a file playable. Some plugins have options that allow any extension (common or unknown) to be -played, making renaming is unnecessary (may need to adjust plugin priority in +played, making renaming unnecessary (may need to adjust plugin priority in player's options). Also be aware that some plugins can tell the player they handle some extension, -then not actually play it. This makes the file unplayable as vgmstream doesn't +then not actually play it. This makes the file unplayable as vgmstream doesn't even get the chance to parse that file, so you may need to disable the offending plugin or rename the file (for example this may happen with .asf and foobar). @@ -228,7 +236,7 @@ and use those as needed and must be together, even if only one of the two will be used to play. .pos is a small file with 32 bit little endian values: loop start sample -and loop end sample. For FFmpeg formats (.vgmstream.pos) it may optionally +and loop end sample. For FFmpeg formats (.vgmstream.pos) it may optionally have total samples after those. ### Decryption keys @@ -254,11 +262,11 @@ Those can be played using an artificial header with info vgmstream needs. **GENH**: a byte header placed right before the original data, modyfing it. The resulting file must be (name).genh. Contains static header data. Programs like VGMToolbox can help to create GENH. - + **TXTH**: a text header placed in an external file. The TXTH must be named `.txth` or `.(ext).txth` (for the whole folder), or `(name.ext).txth` (for a single file). Contains dynamic text commands to read data from the original -file, or static values. +file, or static values. *TXTH* is recomended over *GENH* as it's far easier to create and has many more functions. @@ -266,11 +274,11 @@ more functions. For files that already play, sometimes they are used by the game in various complex and non-standard ways, like playing multiple small songs as a single -one, or using some channels as a section of the song. For those cases we +one, or using some channels as a section of the song. For those cases we can use create a *TXTP* file. -**TXTP**: a text player configurator named `(name).txtp`. Text inside can -contain a list of filenames to play as one (ex. `intro.vag(line)loop.vag`), +**TXTP**: text files with player configuration, named `(name).txtp`. Text inside +can contain a list of filenames to play as one (ex. `intro.vag(line)loop.vag`), list of separate channel files to join as a single multichannel file, subsong index (ex. `bgm.sxd#10`), per-file configurations like number of loops, remove unneeded channels, and many other features. @@ -283,7 +291,7 @@ Since vgmstream supports a huge amount of formats it's possibly that some of them are also supported in other plugins, and this sometimes causes conflicts. If a file that should isn't playing or looping, first make sure vgmstream is really opening it (should show "VGMSTREAM" somewhere in the file info), and -try to remove a few other plugins. +try to remove a few other plugins. foobar's FFmpeg plugin and foo_adpcm are known to cause issues, but in recent versions (1.4.x) you can configure plugin priority. @@ -294,7 +302,7 @@ However other plugins may set themselves higher, stealing formats instead. If current Audacious version doesn't let to change plugin priority you may need to disable some plugins (requires restart) or set priority on compile time. Particularly, mpg123 plugin may steal formats that aren't even MP3, -making impossible for vgmstream to play it properly. +making impossible for vgmstream to play them properly. ### Channel issues Some games layer a huge number of channels, that are disabled or downmixed @@ -303,7 +311,8 @@ foobar can only play up to 8 channels, and Winamp depends on your sound card). For those files you can set the "downmix" option in vgmstream, that can reduce the number of channels to a playable amount. Note that this type of downmixing is very generic, not meant to be used when converting to other -formats. +formats (channels are re-assigned and volumes modified in simplistic ways, +since it can't guess how the file should be properly adjusted). You can also choose which channels to play using *TXTP*. For example, create a file named `song.adx#C1,2.txtp` to play only channels 1 and 2 from `song.adx`. @@ -327,10 +336,10 @@ filename1 # %LOCAL_TAG text (applies to next track only) filename2 ``` -Accepted tags depend on the player (foobar: any; winamp: see ATF config), -typically *ALBUM/ARTIST/TITLE/DISC/TRACK/COMPOSER/etc*, lower or uppercase, -separated by one or multiple spaces. Repeated tags overwrite previous (ex.- -may define *@COMPOSER* multiple times for "sections"). It only reads up to +Accepted tags depend on the player (foobar: any; winamp: see ATF config, Audacious: +few standard ones), typically *ALBUM/ARTIST/TITLE/DISC/TRACK/COMPOSER/etc*, lower +or uppercase, separated by one or multiple spaces. Repeated tags overwrite previous +(ex.- may define *@COMPOSER* multiple times for "sections"). It only reads up to current *filename* though, so any *@TAG* below would be ignored. Playlist title formatting should follow player's config. ASCII or UTF-8 tags work. @@ -359,6 +368,8 @@ filename1 ``` As a side effect if text has @/% inside you also need them: `# @ALBUMARTIST@ Tom-H@ck` +For interoperability with other plugins, consider using only common tags without spaces. + ### ReplayGain foobar2000/Winamp can apply the following replaygain tags (if ReplayGain is enabled in preferences): @@ -408,24 +419,28 @@ BGM01.adx #I 1.0 90.0 .txtp # Could use "BGM01.adx.txtp" as first entry in !tags.m3u instead (different configs won't match) BGM01.adx #I 1.0 90.0 .txtp ``` - + ### Issues If your player isn't picking tags make sure vgmstream is detecting the song (as other plugins can steal its extensions, see above), .m3u is properly named and that filenames inside match the song filename. For Winamp you need to make sure *options > titles > advanced title formatting* checkbox is set and -the format defined. For foobar2000 don't forget you need to force refresh when -tags change (for reasons outside vgmstream's control): -**select songs > shift + right click > Tagging > Reload info from file(s)**. +the format defined. -Currently there is no tool to aid in the creation of there m3u, but you can create -a base m3u and edit as a text file. +When tags change behavior varies depending on player: +- *Winamp*: should refresh tags when file is played again. +- *foobar2000*: needs to force refresh (for reasons outside vgmstream's control) + - **select songs > shift + right click > Tagging > Reload info from file(s)**. +- *Audacious*: files need to be readded to the playlist + +Currently there is no tool to aid in the creation of these tags, but you can create +a base .m3u and edit as a text file. vgmstream's "m3u tagging" is meant to be simple to make and share (just a text file), easier to support in multiple players (rather than needing a custom plugin), -having OST-like ordering in the M3U, and be flexible enough to have commands. -If you are not satisfied with vgmstream's tagging format, foobar2000 has other -plugins (with write support) that may be of use: +allow OST-like ordering but also combinable with other .m3u, and be flexible enough +to have commands. If you are not satisfied with vgmstream's tagging format, +foobar2000 has other plugins (with write support) that may be of use: - m-TAGS: http://www.m-tags.org/ - foo_external_tags: https://foobar.hyv.fi/?view=foo_external_tags @@ -484,7 +499,7 @@ are used in few games. - Nintendo AFC ADPCM - ITU-T G.721 - CD-ROM XA ADPCM -- Sony PSX ADPCM a.k.a VAG (standard, badflags, configurable, Pivotal) +- Sony PSX ADPCM a.k.a VAG (standard, badflags, configurable, extended) - Sony HEVAG - Electronic Arts EA-XA (stereo, mono, Maxis) - Electronic Arts EA-XAS (v0, v1) @@ -537,10 +552,17 @@ are used in few games. - FLAC - Others +Sometimes standard codecs come in non-standard layouts that aren't normally +supported by other players (like multiple `.ogg` or `.mp3` files chunked and +interleaved together in custom ways). + +Some codecs are not fully correct compared to the games due to minor bugs, but +in most cases it isn't audible, and general accuracy is high, with emphasis in +proper support of encoder delay, accurate sample counts and seeking that other +plugins may lack. + Note that vgmstream doesn't (can't) reproduce in-game music 1:1, as internal -resampling, filters, volume, etc, are not replicated. Some codecs are not -fully accurate compared to the games due to minor bugs, but in most cases -it isn't audible. +resampling, filters, volume, etc, are not replicated. ## Supported file types diff --git a/audacious/plugin.cc b/audacious/plugin.cc index dbc8c827..3a0d54ee 100644 --- a/audacious/plugin.cc +++ b/audacious/plugin.cc @@ -36,6 +36,8 @@ extern "C" { /*EXPORT*/ VgmstreamPlugin aud_plugin_instance; audacious_settings_t settings; +static const char* tagfile_name = "!tags.m3u"; + /* Audacious will first send the file to a plugin based on this static extension list. If none * accepts it'll try again all plugins, ordered by priority, until one accepts the file. Problem is, * mpg123 plugin has higher priority and tendency to accept files that aren't even MP3. To fix this @@ -56,6 +58,7 @@ const char *const VgmstreamPlugin::defaults[] = { "downmix_channels", "2", "exts_unknown_on", "FALSE", "exts_common_on", "FALSE", + "tagfile_disable", "FALSE", NULL }; @@ -81,10 +84,11 @@ const PreferencesWidget VgmstreamPlugin::widgets[] = { WidgetSpin(N_("Fade length:"), WidgetFloat(settings.fade_time), {0, 60, 0.1}), WidgetSpin(N_("Fade delay:"), WidgetFloat(settings.fade_delay), {0, 60, 0.1}), WidgetSpin(N_("Downmix:"), WidgetInt(settings.downmix_channels), {0, 8, 1}), - WidgetCheck(N_("Enable unknown exts"), WidgetBool(settings.exts_unknown_on)), + WidgetCheck(N_("Enable unknown exts:"), WidgetBool(settings.exts_unknown_on)), // Audacious 3.6 will only match one plugin so this option has no actual use // (ex. a fake .flac only gets to the FLAC plugin and never to vgmstream, even on error) //WidgetCheck(N_("Enable common exts"), WidgetBool(settings.exts_common_on)), + WidgetCheck(N_("Disable tagfile:"), WidgetBool(settings.tagfile_disable)), }; void vgmstream_settings_load() { @@ -143,18 +147,25 @@ void VgmstreamPlugin::cleanup() { vgmstream_settings_save(); } -#if 0 -static int get_filename_subtune(const char* filename) { +static int get_basename_subtune(const char* filename, char* buf, int buf_len, int* p_subtune) { int subtune; - int pos = strstr("?")) - if (pos <= 0) - return -1; - if (sscanf(filename + pos, "%i", &subtune) != 1) - return -1; - return subtune; + const char* pos = strrchr(filename, '?'); + if (!pos) + return 0; + if (sscanf(pos, "?%i", &subtune) != 1) + return 0; + + if (p_subtune) + *p_subtune = subtune; + + strncpy(buf, filename, buf_len); + char* pos2 = strrchr(buf, '?'); + if (pos2) //removes '?' + pos2[0] = '\0'; + + return 1; } -#endif static void apply_config(VGMSTREAM* vgmstream, audacious_settings_t* settings) { vgmstream_cfg_t vcfg = {0}; @@ -173,41 +184,41 @@ static void apply_config(VGMSTREAM* vgmstream, audacious_settings_t* settings) { static bool read_info(const char* filename, Tuple & tuple) { AUDINFO("read file=%s\n", filename); -#if 0 - //todo subsongs: - // in theory just set FlagSubtunes in plugin.h, and set_subtunes below - // Audacious will call "play" and this(?) with filename?N where N=subtune - // and you just load subtune N, but I can't get it working - int subtune; - string basename; - int subtune = get_filename_subtune(basename, &subtune); - //must use basename to open streamfile -#endif + // Audacious first calls this as a regular file (use_subtune is 0). If file has subsongs, + // you need to detect and call set_subtunes below and return. Then Audacious will call again + // this and "play" with "filename?N" (where N=subtune, 1=first), that must be detected and handled + // (see plugin.h) + char basename[PATH_LIMIT]; //filename without '?' + int subtune = 0; + int use_subtune = get_basename_subtune(filename, basename, sizeof(basename), &subtune); - STREAMFILE* sf = open_vfs(filename); + STREAMFILE* sf = open_vfs(use_subtune ? basename : filename); if (!sf) return false; + if (use_subtune) + sf->stream_index = subtune; + VGMSTREAM* infostream = init_vgmstream_from_STREAMFILE(sf); if (!infostream) { close_streamfile(sf); return false; } -#if 0 + int total_subtunes = infostream->num_streams; - //somehow max was changed to short in recent versions, though - // some formats can exceed this + // int was changed to short in some version, though vgmstream formats can exceed it if (total_subtunes > 32767) total_subtunes = 32767; - if (infostream->num_streams > 1 && subtune <= 0) { + // format has subsongs but Audacious didn't ask for subsong yet + if (total_subtunes >= 1 && !use_subtune) { //set nullptr to leave subsong index linear (must add +1 to subtune) tuple.set_subtunes(total_subtunes, nullptr); - return true; //must return? - } - sf->stream_index = (subtune + 1); -#endif + close_streamfile(sf); + close_vgmstream(infostream); + return true; + } apply_config(infostream, &settings); @@ -229,13 +240,78 @@ static bool read_info(const char* filename, Tuple & tuple) { //todo here we could call describe_vgmstream() and get substring to add tags and stuff tuple.set_str(Tuple::Codec, "vgmstream codec"); - //tuple.set_int(Tuple::Subtune, subtune); - //tuple.set_int(Tuple::NumSubtunes, subtune); //done un set_subtunes + if (use_subtune) { + tuple.set_int(Tuple::Subtune, subtune); + tuple.set_int(Tuple::NumSubtunes, infostream->num_streams); + + char title[1024]; + vgmstream_get_title(title, sizeof(title), basename, infostream, NULL); + tuple.set_str(Tuple::Title, title); //may be overwritten by tags + } + + + // this function is only called when files are added to playlist, + // so to reload tags files need to readded + if (!settings.tagfile_disable) { + //todo improve string functions + char tagfile_path[PATH_LIMIT]; + strcpy(tagfile_path, filename); + + char *path = strrchr(tagfile_path,'/'); + if (path != NULL) { + path[1] = '\0'; /* includes "/", remove after that from tagfile_path */ + strcat(tagfile_path,tagfile_name); + } + else { /* ??? */ + strcpy(tagfile_path,tagfile_name); + } + + STREAMFILE* sf_tags = open_vfs(tagfile_path); + if (sf_tags != NULL) { + VGMSTREAM_TAGS* tags; + const char *tag_key, *tag_val; + + tags = vgmstream_tags_init(&tag_key, &tag_val); + vgmstream_tags_reset(tags, filename); + while (vgmstream_tags_next_tag(tags, sf_tags)) { + // see tuple.h (ugly but other plugins do it like this) + if (strcasecmp(tag_key, "ARTIST") == 0) + tuple.set_str(Tuple::Artist, tag_val); + else if (strcasecmp(tag_key, "ALBUMARTIST") == 0) + tuple.set_str(Tuple::AlbumArtist, tag_val); + else if (strcasecmp(tag_key, "TITLE") == 0) + tuple.set_str(Tuple::Title, tag_val); + else if (strcasecmp(tag_key, "ALBUM") == 0) + tuple.set_str(Tuple::Album, tag_val); + else if (strcasecmp(tag_key, "PERFORMER") == 0) + tuple.set_str(Tuple::Performer, tag_val); + else if (strcasecmp(tag_key, "COMPOSER") == 0) + tuple.set_str(Tuple::Composer, tag_val); + else if (strcasecmp(tag_key, "COMMENT") == 0) + tuple.set_str(Tuple::Comment, tag_val); + else if (strcasecmp(tag_key, "GENRE") == 0) + tuple.set_str(Tuple::Genre, tag_val); + else if (strcasecmp(tag_key, "TRACK") == 0) + tuple.set_int(Tuple::Track, atoi(tag_val)); + else if (strcasecmp(tag_key, "YEAR") == 0) + tuple.set_int(Tuple::Year, atoi (tag_val)); +#if defined(_AUD_PLUGIN_VERSION) && _AUD_PLUGIN_VERSION >= 48 // Audacious 3.8+ + else if (strcasecmp(tag_key, "REPLAYGAIN_TRACK_GAIN") == 0) + tuple.set_gain(Tuple::TrackGain, Tuple::GainDivisor, tag_val); + else if (strcasecmp(tag_key, "REPLAYGAIN_TRACK_PEAK") == 0) + tuple.set_gain(Tuple::TrackPeak, Tuple::PeakDivisor, tag_val); + else if (strcasecmp(tag_key, "REPLAYGAIN_ALBUM_GAIN") == 0) + tuple.set_gain(Tuple::AlbumGain, Tuple::GainDivisor, tag_val); + else if (strcasecmp(tag_key, "REPLAYGAIN_ALBUM_PEAK") == 0) + tuple.set_gain(Tuple::AlbumPeak, Tuple::PeakDivisor, tag_val); +#endif + } + + vgmstream_tags_close(tags); + close_streamfile(sf_tags); + } + } - //todo tags (see tuple.h) - //tuple.set_int (Tuple::Track, ...); - //tuple.set_str (Tuple::Artist, ...); - //tuple.set_str (Tuple::Album, ...); close_streamfile(sf); close_vgmstream(infostream); @@ -271,12 +347,20 @@ static void do_seek(VGMSTREAM* vgmstream, int seek_ms, int& current_sample_pos) bool VgmstreamPlugin::play(const char * filename, VFSFile & file) { AUDINFO("play file=%s\n", filename); - STREAMFILE* sf = open_vfs(filename); + //handle subsongs (see read_info) + char basename[PATH_LIMIT]; //filename without '?' + int subtune = 0; + int use_subtune = get_basename_subtune(filename, basename, sizeof(basename), &subtune); + + STREAMFILE* sf = open_vfs(use_subtune ? basename : filename); if (!sf) { AUDERR("failed opening file %s\n", filename); return false; } + if (use_subtune) + sf->stream_index = subtune; + VGMSTREAM* vgmstream = init_vgmstream_from_STREAMFILE(sf); close_streamfile(sf); diff --git a/audacious/plugin.h b/audacious/plugin.h index 5cb8ce80..5b3e90db 100644 --- a/audacious/plugin.h +++ b/audacious/plugin.h @@ -29,7 +29,7 @@ public: }; constexpr VgmstreamPlugin() : InputPlugin (info, - InputInfo() //InputInfo(FlagSubtunes) // allow subsongs + InputInfo(FlagSubtunes) // allow subsongs .with_priority(AUDACIOUS_VGMSTREAM_PRIORITY) // where 0=highest, 10=lowest (older) or 5 (newer) .with_exts(exts)) {} // priority exts (accepted exts are still validated at runtime) @@ -52,6 +52,7 @@ typedef struct { int downmix_channels; bool exts_unknown_on; bool exts_common_on; + bool tagfile_disable; } audacious_settings_t; extern audacious_settings_t settings; diff --git a/audacious/vfs.cc b/audacious/vfs.cc index a20eeff8..6a349171 100644 --- a/audacious/vfs.cc +++ b/audacious/vfs.cc @@ -3,7 +3,9 @@ #include +extern "C" { #include "../src/vgmstream.h" +} #include "plugin.h" #include "vfs.h" @@ -19,7 +21,7 @@ static STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path); static size_t read_vfs(VFS_STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length) { size_t bytes_read; - if (!dest || length <= 0 || offset < 0) + if (/*!streamfile->vfsFile ||*/ !dest || length <= 0 || offset < 0) return 0; // if the offsets don't match, then we need to perform a seek @@ -36,11 +38,14 @@ static size_t read_vfs(VFS_STREAMFILE *streamfile, uint8_t *dest, off_t offset, } static void close_vfs(VFS_STREAMFILE *streamfile) { + //if (streamfile->vfsFile) delete streamfile->vfsFile; //fcloses the internal file too free(streamfile); } static size_t get_size_vfs(VFS_STREAMFILE *streamfile) { + //if (!streamfile->vfsFile) + // return 0; return streamfile->vfsFile->fsize(); } @@ -100,5 +105,16 @@ STREAMFILE *open_vfs(const char *path) { return NULL; } +#if 0 // files that don't exist seem blocked by probe.cc before reaching here + bool infile_exists = vfsFile && *vfsFile; + if (!infile_exists) { + /* allow non-existing files in some cases */ + if (!vgmstream_is_virtual_filename(path)) { + delete vfsFile; + return NULL; + } + vfsFile = NULL; + } +#endif return open_vfs_by_VFSFILE(vfsFile, path); } diff --git a/cli/vgmstream_cli.c b/cli/vgmstream_cli.c index 2e866756..28d0a74d 100644 --- a/cli/vgmstream_cli.c +++ b/cli/vgmstream_cli.c @@ -65,6 +65,7 @@ static void usage(const char* name, int is_full) { " -k N: seeks to N samples before decoding (for seek testing)\n" " -K N: seeks again to N samples before decoding (for seek testing)\n" " -t file: print tags found in file (for tag testing)\n" + " -T: print title (for title testing)\n" " -D : downmix to (for plugin downmix testing)\n" " -O: decode but don't write to file (for performance testing)\n" ); @@ -100,6 +101,7 @@ typedef struct { int seek_samples1; int seek_samples2; int decode_only; + int show_title; int downmix_channels; /* not quite config but eh */ @@ -122,7 +124,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:k:K:hOvD:")) != -1) { + while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:Tk:K:hOvD:")) != -1) { switch (opt) { case 'o': cfg->outfilename = optarg; @@ -185,6 +187,9 @@ static int parse_config(cli_config* cfg, int argc, char** argv) { case 't': cfg->tag_filename= optarg; break; + case 'T': + cfg->show_title = 1; + break; case 'k': cfg->seek_samples1 = atoi(optarg); break; @@ -351,6 +356,17 @@ static void print_tags(cli_config* cfg) { close_streamfile(sf_tags); } +static void print_title(VGMSTREAM* vgmstream, cli_config* cfg) { + char title[1024]; + + if (!cfg->show_title) + return; + + vgmstream_get_title(title, sizeof(title), cfg->infilename, vgmstream, NULL); + + printf("title: %s\n", title); +} + static void clean_filename(char* dst, int clean_paths) { int i; for (i = 0; i < strlen(dst); i++) { @@ -565,11 +581,10 @@ int main(int argc, char** argv) { } - /* print file info (or batch commands, depending on config) */ + /* prints */ print_info(vgmstream, &cfg); - - /* print tags info */ print_tags(&cfg); + print_title(vgmstream, &cfg); /* prints done */ if (cfg.print_metaonly) { diff --git a/src/plugins.c b/src/plugins.c index 3d033168..085c3dad 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -63,6 +63,58 @@ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg) { return 0; } +void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM* vgmstream, vgmstream_title_t* cfg) { + const char *pos; + char* pos2; + char temp[1024]; + + + /* name without path */ + pos = strrchr(filename, '\\'); + if (!pos) + pos = strrchr(filename, '/'); + if (!pos) + pos = filename; + else + pos++; + strncpy(buf, pos, buf_len); + + /* name without extension */ + pos2 = strrchr(buf, '.'); + if (pos2) + pos2[0] = '\0'; + + { + const char* stream_name = vgmstream->stream_name; + int total_subsongs = vgmstream->num_streams; + int target_subsong = vgmstream->stream_index; + //int is_first = vgmstream->stream_index == 0; + //int is_txtp = ; //todo don't show number/name for txtp but show for mini-txtp + int show_name; + + if (target_subsong == 0) + target_subsong = 1; + + /* show number if file has more than 1 subsong */ + if (total_subsongs > 1) { + if (cfg && cfg->subsong_range) + snprintf(temp, sizeof(temp), "%s#1~%i", buf, total_subsongs); + else + snprintf(temp, sizeof(temp), "%s#%i", buf, target_subsong); + strncpy(buf, temp, buf_len); + } + + /* show name for some cases */ + show_name = (total_subsongs > 0 && (!cfg || !cfg->subsong_range)) || + (cfg && cfg->force_title); + if (stream_name[0] != '\0' && show_name) { + snprintf(temp, sizeof(temp), "%s (%s)", buf, stream_name); + strncpy(buf, temp, buf_len); + } + } +} + + static void copy_time(int* dst_flag, int32_t* dst_time, double* dst_time_s, int* src_flag, int32_t* src_time, double* src_time_s) { if (!*src_flag) return; diff --git a/src/plugins.h b/src/plugins.h index a539c520..fff652e2 100644 --- a/src/plugins.h +++ b/src/plugins.h @@ -68,6 +68,15 @@ int vgmstream_get_play_forever(VGMSTREAM* vgmstream); void vgmstream_set_play_forever(VGMSTREAM* vgmstream, int enabled); +typedef struct { + int force_title; + int subsong_range; +} vgmstream_title_t; + +/* get a simple title for plugins */ +void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM* vgmstream, vgmstream_title_t* cfg); + + #if 0 //possible future public/opaque API From 898b0168053e68d59650be4f4acfb38cf5ad40f0 Mon Sep 17 00:00:00 2001 From: bnnm Date: Sat, 12 Sep 2020 15:03:43 +0200 Subject: [PATCH 2/2] Fix some .ktsl2asbin [Atelier Ryza (PC)] --- src/meta/ktsr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/meta/ktsr.c b/src/meta/ktsr.c index 3a90b86d..ce32b772 100644 --- a/src/meta/ktsr.c +++ b/src/meta/ktsr.c @@ -290,6 +290,7 @@ static int parse_ktsr_subfile(ktsr_header* ktsr, STREAMFILE* sf, off_t offset) { case 0x38D0437D: /* external [Nioh (PC), Atelier Ryza (PC)] */ case 0xDF92529F: /* external [Atelier Ryza (PC)] */ + case 0x6422007C: /* external [Atelier Ryza (PC)] */ /* 08 subtype? (ex. 0x522B86B9) * 0c channels * 10 ? (always 0x002706B8) @@ -443,7 +444,6 @@ static void parse_longname(ktsr_header* ktsr, STREAMFILE* sf, uint32_t target_id offset += size; } - } static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { @@ -481,6 +481,7 @@ static int parse_ktsr(ktsr_header* ktsr, STREAMFILE* sf) { case 0x6172DBA8: /* padding (empty) */ case 0xBD888C36: /* config (floats, stream id, etc, may have extended name) */ case 0xC9C48EC1: /* unknown (has some string inside like "boss") */ + case 0xA9D23BF1: /* "state container", some kind of config/floats, witgh names like 'State_bgm01'..N */ break; case 0xC5CCCB70: /* sound (internal data or external stream) */