Navidrome, Clients and the Compilation MP3 Tag
As I outlined in a previous post, I switched from Jellyfin to Navidrome for my music. While on the whole I’m happy with the change, I discovered that Navidrome handles Various Artists albums such that if the Compilation tag isn’t set, it indexes the album from one into as many as there are artists. And, as outlined in the aforementioned post, this is not a bug, but by design. The intent is to account for different artists with the same album name, and that makes sense.
So I set out to attempt to automate the modification of the Compilation tag – a quest that turned out to be more complicated than I anticipated. The Compilation tag is apparently not a ‘standard’ tag. If you load up an mp3 in taglibsharp and have it vomit out all the tags, the Compilation tag won’t be among them. Here, try it yourself in PowerShell. You’ll have to update the path to your taglibsharp.dll and your test mp3 file.
1 2 3 4 |
$null = [System.Reflection.Assembly]::LoadFrom("PATH\TO\TagLibSharp.dll") $Media = [TagLib.File]::Create('FULL\PATH\TO\YOUR.mp3') $Media.tag |
Here’s an example output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
StartTag : TagLib.NonContainer.StartTag EndTag : TagLib.NonContainer.EndTag TagTypes : Id3v1, Id3v2, Ape Tags : {, , } Title : Cut Me In Subtitle : Description : Performers : {The Chosen Few} PerformersSort : {} PerformersRole : {$null} AlbumArtistsSort : {} AlbumArtists : {The Chosen Few} Composers : {} ComposersSort : {} TitleSort : AlbumSort : Album : Funky Funky Chicago Comment : Genres : {Funk} Year : 2009 Track : 1 TrackCount : 0 Disc : 0 DiscCount : 0 Lyrics : Grouping : BeatsPerMinute : 0 Conductor : Copyright : DateTagged : MusicBrainzArtistId : MusicBrainzReleaseGroupId : MusicBrainzReleaseId : MusicBrainzReleaseArtistId : MusicBrainzTrackId : MusicBrainzDiscId : MusicIpId : AmazonId : MusicBrainzReleaseStatus : MusicBrainzReleaseType : MusicBrainzReleaseCountry : Pictures : { [image/jpeg] 79442 bytes} ReplayGainTrackGain : -2.955 ReplayGainTrackPeak : 0.587382 ReplayGainAlbumGain : -3.345 ReplayGainAlbumPeak : 0.588975 InitialKey : RemixedBy : Publisher : ISRC : Length : IsEmpty : False Artists : {The Chosen Few} FirstArtist : The Chosen Few FirstAlbumArtist : The Chosen Few FirstAlbumArtistSort : FirstPerformer : The Chosen Few FirstPerformerSort : FirstComposerSort : FirstComposer : FirstGenre : Funk JoinedArtists : The Chosen Few JoinedAlbumArtists : The Chosen Few JoinedPerformers : The Chosen Few JoinedPerformersSort : JoinedComposers : JoinedGenres : Funk |
As you can see, no Compilation tag there. Compilation is technically an iTunes Flag Text Frame, and is set with a Boolean value (1 for Yup, 0 for Nope). It’s defined as TCMP. As of this writing id3.org is down, but here’s the archived page on this tag.
Given all this, I threw together the below. As per usual it’s unrefined, has almost zero error handling, and will be something I improve upon over time. This thing assumes your compilation albums are in directories formatted as “Various – Name of Album.”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# uses taglib-sharp.dll #[Reflection.Assembly]::LoadFrom("E:\mp3tools\taglib-sharp.dll") # No true errorchecking in this yet. josefek $null = [System.Reflection.Assembly]::LoadFrom("E:\mp3tools\TagLibSharp.dll") $basedir = Read-Host -Prompt 'Enter the base dir' $i = 0 $various = get-ChildItem -Directory $basedir | where {$_.name -like "various*"} #$various = get-ChildItem -Directory $basedir Write-host -foregroundcolor yellow "Working..." foreach ($vdir in $various) { $i++ Write-Progress -activity "Scanning $vdir . . ." -status "Scanned: $i of $($various.count)" -percentComplete (($i / $various.count) * 100) $files = @(Get-ChildItem -file -recurse $vdir.fullname | Where-Object { $_.Extension -eq ".mp3"} ) foreach ($file in $files) { if ($file) { try { $prop = Get-ItemProperty -literalpath $($file.fullname) | Select-Object IsReadOnly } catch { # Handling for situations like invalid path characters, which prevent properties from being read Write-host "Cannot detect readonly $($file.fullname)" #exit } if ($($prop.IsReadOnly)) { # Remove Read-Only $file.IsReadOnly = $false } try { $f = [TagLib.File]::Create($file.fullname) } catch { write-host "Error on $f " $file.fullname #exit } } [TagLib.Id3v2.Tag]$currId3v2 = $f.GetTag([TagLib.TagTypes]::Id3v2) $currId3v2.SetTextFrame('TCMP',1) $f.Save(); $completed += @($vdir.fullname) } $nonmp3files = @(Get-ChildItem -file -recurse $vdir.fullname | Where-Object { $_.Extension -ne ".mp3"} ) if ($nonmp3files) { $skipped += @($vdir.fullname) } } write-host -foregroundcolor green "The following were updated as Compilation albums:" $completed | select -Unique if ($skipped) { Write-host -foregroundcolor red "`r`n`r`nThe following could not be updated and were skipped:" $skipped Write-host "Hit any key for details." $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') foreach ($skip in $skipped) { Get-ChildItem -file -recurse $skip | Where-Object { $_.Extension -ne ".mp3"} $skip } } exit |
Bonus: If you, like me, discovered you have a whole mess of compilation albums in directories with the format “VA – Album Title” instead of “Various – Album Title,” This one liner will help correct that.
1 |
(Get-ChildItem -literalpath "\PATH\TO\ALBUMS\" -Directory -Filter "VA *" ) | Rename-Item -NewName {$_.Name -replace "VA ","Various "} |