One of my main familial duties is as CD-Ripping monkey. Once we get a CD I need to convert it into a format friendly for the various streamers, phones, players, alarm-clocks and secret digital diaries (yes, really). This usually just involves ripping an album to Flac and MP3 and uploading it to the file-server.
Some audio-books and multi-CD albums are more difficult though; the more CDs there are the more chance that iTunes or the FreeDB databases have mismatches of metadata between the discs (maybe CD #1s album title is “Status Quo Greatest Hits CD1′ and CD#9s album title is ‘Status Quo – Greatest Hits (CD 9)’). Clearly mismatched metadata is a crime against humanity and I can’t leave it uncorrected.
You can only imagine how diabolical the Harry Potter Unabridged audiobooks are; each has 25+ CDs with with 50-70 files per disc. When we get a new-one they often sit on my desk for days, taunting me while I choke back a sob and try not to remember the manual corrections required the last time I converted one.
This time my PowerShell-fu is strong and it comes to the rescue with some automatic tagging. The details are below.
First off the bat, PowerShell doesn’t have any inbuilt cmdlets for manipulating media file tags like Album, Track Number and Disc. But what PowerShell does have is a robust system for creating and importing new modules that reference .NET DLL files.
With a bit of Google-fu I found a link to someone who created a nice PowerShell module that does exactly that. The link is here.
The main problem I was trying to solve was that with an inconsistent naming conventio often the tracks would play in the wrong order. To compound matters, we found certain devices used the file name, some used the track name / album name and some used the album name and metadata (track number and disc number) to sort. The only way to make everyone work was to make everything nicely sorted.
So for the Harry Potter audiobooks, I wanted the file and title name to be of the format “[Track Number]-[Disk Number] Title”, ie “01-07 Harry Potter and Order of the Phoenix”. It was important the track and disk numbers were two characters wide (ie, “01” instead of “1”) as you could get all sorts of problems with track 2 playing after track 20, for example. Additionally, all the files should be in the same folder, shouldn’t have the CD number in the title and the relevant metadata should be correct.
Here’s the script to make it happen;
param ( [ValidateScript({Test-Path $_ -PathType Container})] [string]$Path="d:\media\flac", [string]$Pattern="\s\(CD(?'CD'\d+)\)" #Default pattern match looks for one or more digits #within '(CD' and ')'. This is the named subgroup #CD ) $myDocumentsFolder = [Environment]::GetFolderPath("MyDocuments") $scriptsFolder="\docs\scripts" $MPTagFolder="\mptag" #Import the MPTag module Import-Module ($myDocumentsFolder+$scriptsFolder+$MPTagFolder) #Get all the files $MusicFiles=gci -recurse -Path $Path -File foreach ($File in $MusicFiles) { $MediaInfo=Get-MediaInfo $File.FullName if ($MediaInfo.Tag.Album -match $Pattern) { $MediaInfo.Tag.Disc=$Matches.CD } $NewAlbum=$MediaInfo.Tag.Album -replace $Pattern $DiscNumber=$MediaInfo.Tag.Disc if ($DiscNumber.Length -eq 1) {$DiscNumber="0"+$DiscNumber} $TrackNumber=$MediaInfo.Tag.Track if ($TrackNumber.Length -eq 1) {$TrackNumber="0"+$TrackNumber} $MediaInfo.Tag.Album=$NewAlbum $MediaInfo.Tag.Title=($DiscNumber+"-"+$TrackNumber+" "+$NewAlbum) $MediaInfo.Save() Rename-Item $File.FullName ($File.DirectoryName+"\"+$DiscNumber+"-"+$TrackNumber+" "+$NewAlbum+$File.Extension) }
Here’s a breakdown of what the script does;
param ( [ValidateScript({Test-Path $_ -PathType Container})] [string]$Path="d:\media\flac", [string]$Pattern="\s\(CD(?'CD'\d+)\)" )
This defines the parameters we want the script to accept. I want to accept a path to where the files are (which is validated to ensure it’s a path to a folder) and a regular expression to match the part of the filename that contains the disk number.
The default pattern I have defined above looks for the pattern;
[Space](CD[One or more digits])
So this would match the pattern ” (CD9)” at the end of “Harry Potter and the Chamber of Secrets (CD9)”.
The (?’CD’\d+) section is interesting; this is a named sub-group in a regular expression. This will allow the script to refer to just the found digits in the $Matches variable (see below).
$myDocumentsFolder = [Environment]::GetFolderPath("MyDocuments") $scriptsFolder="\docs\scripts" $MPTagFolder="\mptag" #Import the MPTag module Import-Module ($myDocumentsFolder+$scriptsFolder+$MPTagFolder) #Get all the files $MusicFiles=gci -recurse -Path $Path -File
Initially this builds a path to where the MPTag module is located (in my case, in “My Documents\docs\scripts\mptag”. I then use this path to import the module.
Finally I get all the files within the passed path ($Path), recursively.
foreach ($File in $MusicFiles)
I then loop through all the files I retrieved from $Path.
$MediaInfo=Get-MediaInfo $File.FullName if ($MediaInfo.Tag.Album -match $Pattern) { $MediaInfo.Tag.Disc=$Matches.CD } $NewAlbum=$MediaInfo.Tag.Album -replace $Pattern $DiscNumber=$MediaInfo.Tag.Disc
Get-MediaInfo is one of the new cmdlets in the imported MPTag module. I assign all the media info for the current file to $MediaInfo.
Next I see if I can match the regular expression that is passed in $Pattern. If there is a match, I set the Disc tag on the file to the CD named sub-group. This extracts the disc number from the filename and sets the Disc tag to match it.
I then set $NewAlbum to be the same as the filename with the $Pattern part stripped (so the (CD1) part is removed). $DiscNumber is set to Disc tag on the file.
if ($DiscNumber.Length -eq 1) {$DiscNumber="0"+$DiscNumber} $TrackNumber=$MediaInfo.Tag.Track if ($TrackNumber.Length -eq 1) {$TrackNumber="0"+$TrackNumber} $MediaInfo.Tag.Album=$NewAlbum $MediaInfo.Tag.Title=($DiscNumber+"-"+$TrackNumber+" "+$NewAlbum) $MediaInfo.Save() Rename-Item $File.FullName ($File.DirectoryName+"\"+$DiscNumber+"-"+$TrackNumber+" "+$NewAlbum+$File.Extension)
The next few lines pad the $DiscNumber and $TrackNumber with a leading 0 if they’re single-digit.
The file’s Album tag is set to $NewAlbum.
Finally both the file’s Title tag and FileName are set to the format;
$DiscNumber-$TrackNumber $AlbumName.[File Extension]
One last note; the updates to $MediaInfo need to be written back to the original file with .Save().