Add track number to available placeholders.
MsJaye opened this issue · 6 comments
NowPlayingToFile is great and works really well, but it would be nice to also be able to include the track number of the currently playing track (the contents of the "tracknumber" tag) on the album in the generated text file?
Thanks. :)
Hi @MsJaye,
This is a great request, I've been wanting to add genre as well. And it appears that the underlying Winamp API method does expose lots of additional metadata about files, including track
, genre
, albumartist
, length
, bitrate
, and many others. Apparently the full list can be found somewhere in the Winamp SDK, and maybe in Winamp's Media Library column set and smart filtering wizard too.
However, it looks like there's an API cliff here, unfortunately. The underlying plugin framework, Daniel15/Sharpamp, is hardcoded to only query 5 pieces of metadata. Additionally, that method is unfortunately private, so I can't subclass and override it, and the method to request metadata is private as well.
I can think of five ways to approach this problem, each with their own drawbacks:
- Fork my own copy of Sharpamp and make it fetch more metadata: now there's an additional repo
- Try to submit upstream changes to the Sharpamp project that fetch more metadata: not very active, other users may not want these changes
- Try to use reflection to call
Winamp.GetMetadata()
from WinampNowPlayingToFile and retrieve additional fields: I forget if you can call a private method using reflection in C#, I believe you can but maybe I'm thinking of Java, possible performance penalty - Copy just the methods and structs needed to extend this behavior, then request more metadata from WinampNowPlayingToFile: now those elements are duplicated
- Query other metadata using TagLib instead of Winamp, like we already do for album art: seems roughly equivalent to 4
Personally, I'm leaning towards 3 or 4 at the moment. I'll try some of these techniques and see which ones work and look right.
Technique 3 seems to be working. Any metadata fields requested which aren't already returned by Sharpamp are fetched from Winamp through Sharpamp's private GetMetadata(string, string)
method via reflection.
Remaining work:
-
Cache metadata values for the song, so it will only request a given field once, even if you add it to your template multiple timesit's fast enough without caching (about 30–150 microseconds per metadata field on my Ryzen 3900X) - Figure out all the field names to add to the Insert menu. I have a tentative list from searching through the SDK, forums, and ATF reference page, but some of those might be incorrect.
- Make the configuration dialog box preview include placeholders for these new metadata fields when a song is not currently playing, as that uses a hardcoded example song which doesn't have this new metadata yet.
- Remove or adjust template validation for missing keys.
Here is a development snapshot build that can include any metadata returned by Winamp, including track number.
You can exit Winamp, replace WinampNowPlayingToFile.dll
in your Winamp installation directory with this copy, and try it out. Let me know if it works for you.
New metadata field names
The Insert menu has been augmented to include all the extra metadata fields I could get Winamp to return, which are
AlbumArtist
Bitrate
BPM
Category
Comment
Composer
Conductor
Director
Disc
Family
Gain
Genre
ISRC
Key
Length
Lossless
Lyricist
Media
Producer
Publisher
Rating
ReplayGain_Album_Gain
ReplayGain_Album_Peak
ReplayGain_Track_Gain
ReplayGain_Track_Peak
Stereo
Subtitle
Tool
Track
Type
VBR
Examples
Track number and title
{{Track}}. {{Title}}
10. Gaff's Eulogy
Conditional track number and title
{{#if Track}}{{Track}}. {{/if}}{{Title}}
10. Gaff's Eulogy
If this song didn't have a track number it would just say Gaff's Eulogy
instead.
Lots of fields, including conditional track number and genre
{{Artist}} {{#newline}} {{#if Track}}{{Track}}. {{/if}}{{Title}} {{#newline}} {{Album}}{{#if Year}} ({{Year}}{{#if Genre}}, {{Genre}}{{/if}}){{#elif Genre}} ({{Genre}}){{/if}}
Andrew Bayer
10. Gaff's Eulogy
If It Were You, We'd Never Leave (2013, Trance)
Wonderful! The new build with the changes seems to work well. The format string I'm testing with is:
{{#if Track}}{{Track}} - {{/if}}{{#if Title}}{{Title}}{{/if}}{{#if Album}}{{#newline}}{{Album}}{{/if}}{{#if Artist}}{{#newline}}{{Artist}}{{/if}}{{#if Year}}{{#newline}}{{Year}}{{/if}}
Thanks so much! <3
Notes on performance impact of using reflection to call a private method:
- Calling method directly without reflection: 0.6 ns
- Finding private method to call using reflection: 460.5 ns
- Calling method using reflection after finding it: 1.6 ns
So finding the method once and caching a Func
delegate to call repeatedly is pretty efficient. Each invocation is still quite fast.
This is on a Ryzen 3900X running .NET 7 x64 with RyuJIT.