Album Level Tags

Tags: #<Tag:0x00007f510f7d5188>

There has been some discussion recently about the inability to persist information in scripts between tracks. I took this as a challenge and have come up with a plugin that allows sharing information between tracks at an album level. It’s still in early testing, but if you want to have a look I would appreciate any feedback. Thanks.


Album Level Tags [Download]

Overview

This plugin provides the ability to access album level tags from scripts run during track processing. The plugin adds three new scripting functions to set, get and unset variables that are common to all tracks for an album. This allows things like finding and storing the earliest recording date of all of the tracks on the album. Each album’s information is stored separately, and is reset when the album is refreshed. The information is cleared when an album is removed.

What it Does

This plugin creates a temporary storage dictionary in Picard each time an album is retrieved from MusicBrainz.

Information can be added, updated, retrieved or removed from the dictionary, and the information stored is consistent across all of the tracks for the album. When the album is removed from Picard, all of the information in the temporary dictionary is removed and the memory released.

NOTE: This plugin makes no additional calls to the MusicBrainz website api for the information.

Scripting Functions Added

The plugin adds three new scripting functions:

$setalbumtag(name,value)

This records the value in an album-specific dictionary under the key name.

$getalbumtag(name)

This retrieves the value for the key name from an album-specific dictionary.

$unsetalbumtag(name)

This removes the value for the key name from an album-specific dictionary.

Examples

Example 1:

Find the earliest recording date of any of the tracks on the album, so that it can be used in the file naming script.

Create a tagging script containing the following:

$set(_testdate1,$if2($getalbumtag(earliest_date),9999))
$setalbumtag(earliest_date,$min(%_testdate1%,$if2(%_recording_firstreleasedate%,9999)))

The date for the album is now available in the file naming script using $getalbumtag(earliest_date).

Example 2:

Get a list of all tracks on the album so that it can be included as a tag in each of the tracks.

Create a tagging script containing the following:

$setalbumtag(all_tracks,$if($getalbumtag(all_tracks),; )%tracknumber%. %title%)

Now to make the combined list available as a tag for all tracks, you need to create another tagging script after the previous one containing the following:

$set(all_tracks,$getalbumtag(all_tracks))

The complete track list will now be stored in each of the track files under the tag name all_tracks.

4 Likes

@rdswift Bob - I cannot thank you enough for starting to look at this functionality - and even better for soliciting and being open to ideas from the community. Here are my comments for your consideration - please feel free to use them or not as you see fit…

A. If I understand your approach, you are only providing the ability for track scripts to get, set and delete data in a (shared) dictionary. I am assuming that this dictionary is keyed by both Release MBID and AlbumTag name.

The issue with this approach, is that you need all the track scripts to be run before you have the album data available, which means that you can really only use this data in the file naming script - you cannot set tags with this approach.

I would personally prefer to see a different approach - having functions which get and summarise data across all the album tracks at the time they are called, so that the data can be used in both tagging and file naming scripts.

B. Here is my wish list of functions:

  • $album_getall(name) - get as a multi-value the consolidated superset of track metadata with the given name
  • $album_getcommon(name) - get as a multi-value any multi-value items (treating non multi-value as if they were single multi-value) which are common to all tracks
  • $album_getmajority(name) - get as a multi-value any multi-value items (treating non multi-value as if they were single multi-value) which are common to a majority of tracks (> 50%)
  • $album_getlength() - get the total album length as a sum of track lengths (need to decide whether to use MB metadata or actual file track lengths)
  • $album_countall(name, value[s]) - count the number of tracks in the album that have all the given value[s] in the variable named.
  • $album_countany(name, value[s]) - count the number of tracks in the album that have any of the given value[s] in the variable named.

I haven’t had the time to see what functions are already available to operate on multi-values, but we need to be able to:

  • sort
  • max/min - including special handling of dates if needed (though I think the format will work as a string max) and track lengths (which probably will need special handling because no leading zeros i.e 10:00 is clearly greater than 9:59.

If I think of any more I will edit the above list.

C. For performance reasons you may want to cache the results of these $album_* calls, so that you only collect the data once per album - the cache would need to be invalidated if the album is refreshed (and tagging scripts run again) - I am not sure if there is an event triggered that you can hook into when the album is refreshed - so you may need to have a tagging-script-last-called-time-stamp and rerun the scan of track metadata if A) the function is called from a tagging script and B) the current time is more than (say) 5 seconds after the last call.

D. You would also need to do special handling for NAT tracks which appear to be part of an Album but need to be treated individually.

Actually you can set tags, as long as it is done in a tagging script executed after the script that updates the data in the shared directory. This is demonstrated in the second example, and is possible because of the order that tagging scripts are executed, explained in the Scripts section of the Picard User Guide. It would be pretty useless otherwise.

I did it this way because it provided the greatest flexibility of use, allowing access to all of the existing tags, variables, and scripting functions.

I think all of your wishlist items in ‘B.’ are already available using existing tags, variables and script functions, with the possible exception of $album_getmajority(). If this is the case, then ‘C.’ may be a moot point.

I’ll have to look into how stand-alone recordings are handled, but if they don’t present a musicbrainz_albumid tag, then there will be no key to use in the temporary dictionary, and all function calls will be logged as errors.

@rdswift Bob - yes - I see how it can work for tagging scripts.

However, it still feels counter-intuitive - my way you simply use a function to say what you want, and the function goes out and consolidates the data across all the album tracks. Your way you have to work out how to build up the data as a script executes multiple times, once per track.

I also expect that the dictionary would be persistent across Reloads of the album, so you would have to add some extra script code to check whether the absolute track number was one and initialise the dictionary.

Sorry, but it just feels like an odd way to do it.

OTOH I can instinctively feel that a dictionary would potentially be more flexible, but struggle to think of an example of how it would be so.

Please no - please do NOT log as an error when it is a NAT track - that does NOT constitute an error.

P.S. We will need to point this out bluntly in the plugin documentation - “create a separate tagging script and put it at the top of the list of tagging scripts”.

Corrected Examples

Example 1:

Find the earliest recording date of any of the tracks on the album, so that it can be used in the file naming script.

Create a tagging script containing the following:

$if($eq(%_absolutetracknumber%,1),$unsetalbumtag(earliest_date))
$set(_testdate1,$if2($getalbumtag(earliest_date),9999))
$setalbumtag(earliest_date,$min(%_testdate1%,$if2(%_recording_firstreleasedate%,9999)))

The date for the album is now available in the file naming script using $getalbumtag(earliest_date).

Example 2:

Get a list of all tracks on the album so that it can be included as a tag in each of the tracks.

Create a tagging script (and move it to the top of the list) containing the following:

$if($eq(%_absolutetracknumber%,1),$unsetalbumtag(all_tracks))
$setalbumtag(all_tracks,$if($getalbumtag(all_tracks),; )%tracknumber%. %title%)

Now to make the combined list available as a tag for all tracks, you need to create another tagging script after the previous one containing the following:

$set(all_tracks,$getalbumtag(all_tracks))

The complete track list will now be stored in each of the track files under the tag name all_tracks.

Alternative Examples

I thought it might help to rework the examples to see whether they would be easier with my proposed approach:

Example 1:

Find the earliest recording date of any of the tracks on the album, so that it can be used in the file naming script.

In the file naming script use “$min($album_getall(_recording_firstreleasedate))”

So this example is a fair bit easier and also more intuitive.

Example 2:

Get a list of all tracks on the album so that it can be included as a tag in each of the tracks.

Create a tagging script containing the following:

$set(_trackname,%tracknumber%. %title%)

Now to make the combined list available as a tag for all tracks, you need to create another tagging script after the previous one containing the following:

$set(all_tracks,$album_getall(_trackname))

The complete track list will now be stored in each of the track files under the tag name all_tracks.

This second example is very similar in complexity, but I still feel it is a tiny bit more intuitive.

However, if we were to change the specification of the $album_get* so you could also do e.g. “$album_getall(%tracknumber%. %title%)” and have the variables substituted for each track, then you could do it in one line:

$set(all_tracks,$album_getall(%tracknumber%. %title%))

This is both simpler and a bit more intuitive - except of course for variable substitution being done for other tracks than this one - but e.g. $foreach already does late variable substitution.