Using PowerShell to add images to mp3 files

PowerShellI spent a fair chunk of the Christmas break ripping yet more of my CD collection to mp3s so I can shove them on the home NAS, put the CDs in the loft and reclaim a load of space. Ripping them was a fairly predictably repetitive job of shoving CDs into the drive, waiting for the FreeDB lookup to run (or fail, which meant a bit of typing) and then hitting the go button.

Not the most fun of jobs, but still a lot less work that when I digitised some large parts of my vinyl collection a few years ago After a couple of days I had a nice collection of mp3s all sat there on a disk ready to be imported into the main collection folder.

Just one job remained, getting the Album art together. This is always a fun job of getting the right image. I’ve been using Album Art Downloader for this for a while, as you can set plenty of options to remove most of the effort. But, it doesn’t embed the images into the files. Which as an iDevice user I want so I can have the pictures transfer across when I sync. This got me thinking. I’ve got a windows file system with a nice hierarchical folder structure containing files, and I want to perform an action on each of the files in that structure.

Now if only there was some sort of Windows scripting language that was good at this sort of thing……..

So after some experimentation and some bingling around the interwebs I had this prototype put together:

[Reflection.Assembly]::LoadFrom( (Resolve-Path "c:\tagsharp\taglib-sharp.dll") )

$collection = "e:\temp-music"

foreach ($file in gci $collection -Filter *.mp3 -recurse){
    $mp = [TagLib.File]::Create($file.fullname)
    #Prefer png if it exists, if not we'll settle for a jpg
    if (test-path ($file.Directoryname+"\folder.png")){
        $pic = [taglib.picture]::createfrompath($file.Directoryname+"\folder.png")
    }elseif (test-path ($file.Directoryname+"\folder.jpg")){
        $pic = [taglib.picture]::createfrompath($file.Directoryname+"\folder.jpg")

    $mp.tag.Pictures = $pic
    write-output ($file.FullName +" updated")

Now that first line probably looks a bit funny if you’ve never used a .Net Assembly via PowerShell before.

Instead of importing a PowerShell module we’re actualling loading a .Net library into PowerShell. Gonging around the internet had turned up a .Net library called Tag# (aka Tag-Sharp) at which appeared to offer all the functionality I needed (the ability to embed images inside digital media files), as well as lots more than I’ll probably put to more use in the future. A compiled .Net library is just another dll, and since PowerShell is .Net based we can load that DLL into memory and access the methods and properties it exposes. This lets us exploit lots of other people’s code, and also the performance benefit of compiled code for complex operations.

We then fall into a fairly standard foreach loop through all the mp3s in my collection (Tag# also works with oggs, flacs, etc so this scriptlet can be quickly adapted if mp3 isn’t your thing). For each file we create a new $mp object to hold the Tag# file object which we’ll actually work with, need to pass in the full path to the file:

$mp = [TagLib.File]::Create($file.fullname)

I’ve configured Album Art Downloader to grab either png or jpg images, but I prefer png as it saves a bit of room. So I check which we’ve got, build up a new image object using the Tag#’s TagLib.Picture method:

        $pic = [taglib.picture]::createfrompath($file.Directoryname+"\folder.png")

And then it’s a simple case of adding the image object to our tag object and saving it.

One thing I still want to work out is how to find if the audio file already has any images embedded. I’d hoped that I could use something like:

if ($mp.Tag.Pictures.count -eq 0){
    #Do Something

Which should have left my already image tagged files alone. But it appears that Count always returns 0 unless the image was embedded using Tag#. Little bit annoying as I was hoping to be able to run this across the whole of my collection to fill in the ones that have gotten missed out, or at least get a list of them. I’ll continue having a look, but think I might have to brush up my C# a bit to delve into the source.

If anyone’s had any luck getting that to work, please let me know.

Leave a Reply

Your email address will not be published. Required fields are marked *