Editing MP3 Meadata

I have a reasonable old but still kind of current Denon HEOS amplifier which sounds great; fits neatly into a cupboard and it turns out has some surprising features for something which is a bit old.

I knew that I could link a Spotify account to it; and then I recently (in 2023) discovered I could also link my Amazon Music account to it and this was a great thing. But it turns out that neither service has all the music I want. I have a bunch of albums on CD (yes, I'm old) that aren't available from "modern" streaming services. And there's a lot of cases where specific mixes (in the EDM world) or specific recordings (in the classical world) that can't be found.

So how to play these tracks on my amp? My CD player and "real" stereo system are in another room; the HEOS is out in the living area. As it turns out, I have all of my music as MP3 as well (I call it fair use even thought that's technically illegal here in Australia) so could the HEOS access that?

Why, yes it can! There's an option to conenct to a network share. Except that my HEOS is a bit old - the option is in the app but the amp doesn't see anything on the network. Hmmm. That's inconvenient.

But wait! There's a USB port on the back of the amplifier. Could it be used if I copy the files to a USB stick? Finally, success! Well, kind of.

I went and purchased a 64GB USB stick from my local supermarket. Turns out that the write performance is terrible but that's a one-time inconvience. Then I messed up and decided (rather than deleting all the files that I'd copied in the wrong structure) to just reformat the thing. Big mistake.

It originally came performatted as FAT32. But Windows won't format a 64GB drive as FAT32 because the cluster size is too large. It figures that you'll be wasting a bunch of space with smaller files. I proceeded to download a few tools that were all "free" but wouldn't actually format the drive without a purchase. I'm willing to pay for things but for this it seemed a bit of overkill to spend $20.

Then I stumbled across Fat32Format which is free and only requires you to know how to drive it at the command line so that was ok. Anyway. USB drive reformatted, the journey continues.

Once the files were on the USB stick I opened the HEOS app, selected the USB port, clicked "Browse Folders" and there were all of the music tracks! Magic! Except there was one problem. They weren't in track order - they were in alphabetical order. And no sort option in site.

Hmmm. So begins the journey.

In the old days the way to fix this was to name the files with the track number at the start - it's ugly but it works and here I didn't care too much about the track naming. So how to rename a few thousand files? Given I was on Windows I figured I'd do this with PowerShell - no better way to learn part of a language than to do something in it.

The plan: Iterate through each file; read the MP3 metadata (somehow) and extract the track number; rename the file. Easy. Oh, and don't rename the file twice. That's how I messed up the first time and got myself into trouble with reformatting... Oh, and deal with files that don't have a track number at all.

$PropertyArray = 0, 13, 14, 21, 26
# 0: Name
# 13: Contributing artists
# 14: Album
# 21: Title
# 26: #

$Shell = New-Object -COMObject Shell.Application

Get-ChildItem -Path D:\*.mp3 -Recurse | ForEach-Object {
    Write-Output $_.FullName

    $Folder = Split-Path($_.FullName)
    $ShellFolder = $Shell.Namespace($Folder)
    $ShellFile = $ShellFolder.ParseName($_.Name)

    $MetaData = [ordered]@{}

    Foreach ($Property in $PropertyArray) { 
        If ($ShellFolder.GetDetailsOf($ShellFile, $Property)) {
            $MetaData[$Property.ToString()] = $ShellFolder.GetDetailsOf($ShellFile, $Property)
        }
    }

    $IndexLength = $Metadata."26".Length
    if ($IndexLength -gt 0) {
        $ExistingStart = $_.Name.SubString(0, $IndexLength)
        If ($ExistingStart -ne $Metadata.26) {
            $NewName = $Metadata.26+" "+$_.Name+$_.Extension
        }
        Rename-Item -LiteralPath $_.FullName -NewName $NewName
    }
}

Excellent. Wait for the rename to happen (did I mention that the write performance was terrible?) and then plug the USB stick back into the amplifier!

Tragedy! Files are still sorted in alphabetical order. Well dammit. The amp is obviously sorting based on the track title that is in the MP3 metadata. That's really smart. But not handy in this case.

Now spend a few hours going down a rabbit-hole to discover that PowerShell can read a bunch of different metadata from files but can't write it. There are no native libraries that allow for writing MP3 metadata. A few older libraries exist but I couldn't get them working on Windows 10 so that whole exercise was a bust.

Python to the rescue! Turns out that the new Swiss Army Knife of languages (sorry, Perl) can do just about anything. So let's write that same script in Python but also update the title metadata in the file along the way. Even though I didn't have to, the renaming code is in here as well just in case I needed it in the future.

import os
from mutagen.mp3 import MP3  
from mutagen.easyid3 import EasyID3

rootPath = 'D:\\'

for (fullDirName, dirName, fileList) in os.walk(rootPath):
    for fileName in fileList:
        if os.path.splitext(fileName)[1] != '.mp3': continue

        fullName = os.path.join(fullDirName, fileName)
        try:
            mp3Details = MP3(fullName, ID3=EasyID3)
        except Exception as e:
            print(f'Failed on {fullName} for {e}')
            continue

        try:
            trackTitle = mp3Details['title'][0]
        except:
            trackTitle = os.path.splitext(fileName)[0]

        try:
            trackNumber = mp3Details['tracknumber'][0]
        except:
            print(f'{fullName} has no track number!')
            continue

        slashPos = trackNumber.find('/')
        if slashPos != -1: trackNumber = trackNumber[:slashPos]

        if not trackTitle.startswith(trackNumber+' '):
            newTitle = trackNumber+' '+trackTitle
            mp3Details['title'] = [newTitle]
            mp3Details.save()

#        if not fileName.startswith(trackNumber+' '):
#            newName = os.path.join(fullDirName, trackNumber+' '+fileName)
#            try:
#                os.rename(fullName, newName)
#            except:
#                print(f'Rename failed for {newName}')

Go have dinner while the files get edited (it takes even longer now because there is more data to write) but eventually it's all done. Plug the USB stick into the amplifier and...wait - the files are still sorted in alphabetical order! !@#$%^&!!!

Go down another rabbit hole to find out that there are two different ways of writing MP3 metadata (one at the end of the file, one at the start) and it looks like (by default) the Python library writes to the one at the end of the file (which is the "original" way of doing things) rather than at the start.

It was getting late at this stage so I gave up for the night. It was good enough having the music there - having it in the right order would have to wait until I dug back into the Python documentation to figure out how to rewrite the "other" metadata.

Next day, I glance a little closer at the HEOS app. Not only does it have an option to browser the folders, it also has selections for "Artists", "Albums", "Genres" and "Tracks". So it's reading all of the metadata to expose that to the user.

What happens if I go and look at albums and....wait...the tracks are sorted in the correct order in this view. #%*&*@(*$&*&!!! So I didn't really have to do anything at all to get what I wanted. The amplifier software was already smart enough. I just had to look.

Fast forward a few hours (while the original files without the mangled names and metadata) copied again and now the music is freely flowing. Zen.