Aldaviva/WinampNowPlayingToFile

Add track number to available placeholders.

MsJaye opened this issue · 6 comments

MsJaye commented

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:

  1. Fork my own copy of Sharpamp and make it fetch more metadata: now there's an additional repo
  2. Try to submit upstream changes to the Sharpamp project that fetch more metadata: not very active, other users may not want these changes
  3. 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
  4. Copy just the methods and structs needed to extend this behavior, then request more metadata from WinampNowPlayingToFile: now those elements are duplicated
  5. 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.

image

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 times it'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.

WinampNowPlayingToFile.zip

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

image

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) 
MsJaye commented

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

Published version 2.1.0.0.

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.