Navidrome, Not Compilation Albums, and Tagsharp-DLL
I love Navidrome, but one of the things I don’t love about it is how it opts to index albums. It uses the Album Artist tag to determine albums. If an album has differing Album Artist tags, it’s going to break that album into multiples, showing the tracks for Album Artist A as one album, Album Artist B as another and so on. In a world where featured artists are a common thing among all genres, this is annoying. The option, other than to update Album Artist to one consistent artist, is to set the Compilation tag. With the Compilation tag set, Navidrome ignores the multiple artists and keeps the album categorized based on Album.
But what if, like me, you have a massive library that you haven’t set this for?
Pain. Here’s what I’ve done.
First, carve off a copy of your database file (don’t use your production copy, even though we’re not making any changes to it. Safety first!). Open the database in DB Browser, and run the following query:
1 |
select distinct paths from album where song_count = 1 and compilation = 0 |
This will return a list of albums that have a song count of 1 and a compilation tag that isn’t set. This isn’t perfect – you’re going to catch single track albums, and you’re going to miss albums that are split in increments other than a single tune, but it’ll get you close to catching most of what you’re after. Given that I run Navidrome in Docker with a mounted volume to a network share I had to massage the data to represent their actual paths, so keep that in mind if you’re doing something similar.
Once you’ve got your list of album paths, save it as compalbums.txt. If you don’t have taglib-sharp.dll, you can get it with nuget if you’re using Visual Studio. If you aren’t go to the nuget site, download the package, open the file in your favorite zip file manager (which is 7-Zip, right?) and pull out the appropriate dll. For the purposes of these instructions I have everything in E:\mp3tools. Adjust accordingly. Create the below .ps1 file and run it. If you’re uncomfortable – and I don’t blame you if you are, I currently have pretty much zero error checking thus far – copy some sample albums to a different path and test.
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. jk $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 |