Using PowerShell to Repair Orphaned Tracks in a Windows Media Player Library

When you rip a CD into Windows Media Player (WMP), WMP will attempt to retrieve information about the album from the Internet. I have encountered a problem with this where, sometimes, all of the album information is retrieved except for the information for the first track. Instead of showing the correct track name and associated album, the track is orphaned as ‘Track 1’ belonging to ‘Unknown Album (DD/MM/YY HH/MM/SS)’.

When ripping music, for a single album, this is trivial, a minor inconvenience quickly fixed with a manual edit. However, if WMP rebuilds the media library, such as when you move your music collection to a different partition, you may be left with hundreds of orphaned tracks. The reason for this is that WMP repopulates the library using the meta data for the music file. If you view the meta data by opening the Details tab for the file in Windows Explorer you will see that it’s incorrect.

In this article I will show how the information for the track can be retrieved from the full filename and then used to populate the information in the WMP library, this will ensure that the tracks are displayed correctly when viewed in WMP.

PowerShell provides no native cmdlets for controlling WMP or for manipulating the contents of the media library. However, we can use a COM object provided by the WMP ActiveX control. The WMP object model is comprehensively documented at:
http://msdn.microsoft.com/en-us/library/windows/desktop/dd563945(v=vs.85).aspx

The first thing to do is to create an instance of a Player COM object:

$wmp = New-Object -ComObject WMPlayer.ocx

Like all objects in PowerShell, we can inspect the available properties and methods of this object by piping it to Get-Member. A sample of the output is shown below.

$wmp | Get-Member

Name               MemberType Definition
----               ---------- ----------
close              Method     void close ()
launchURL          Method     void launchURL (string)
cdromCollection    Property   IWMPCdromCollection cdromCollection () {get}
currentMedia       Property   IWMPMedia currentMedia () {get} {set}
currentPlaylist    Property   IWMPPlaylist currentPlaylist () {get} {set}
mediaCollection    Property   IWMPMediaCollection mediaCollection () {get}
playerApplication  Property   IWMPPlayerApplication playerApplication () {get}

The output from Get-Member shows us that the $wmp object has a mediaCollection property. The mediaCollection property is itself an object and we can pipe this object to Get-Member to explore its properties and methods.

$wmp.mediaCollection | Get-Member

Name                         MemberType Definition
----                         ---------- ----------
getAll                       Method     IWMPPlaylist getAll ()
getAttributeStringCollection Method     IWMPStringCollection getAttributeStringCollection (string, string)
getByAlbum                   Method     IWMPPlaylist getByAlbum (string)
getByAttribute               Method     IWMPPlaylist getByAttribute (string, string)
getByAuthor                  Method     IWMPPlaylist getByAuthor (string)
getByGenre                   Method     IWMPPlaylist getByGenre (string)
getByName                    Method     IWMPPlaylist getByName (string)

We can see that the mediaCollection object provides several methods that could be used to query the library. Our orphaned tracks are missing or have incorrect data for Author, Genre, and Album but do we know that they’re consistently named ‘Track 1’ so we will use the getByName() method.

The getbyName() method will return a Playlist object containing all of the media items in the library called ‘Track 1’.

$playList = $wmp.mediaCollection.getByName('Track 1')

Each item in the playlist is referenced by an index number, starting at 0. So to assign the first item in the playlist to the variable $item we would use:

$item = $playList.Item(0)

To access each item in the playlist in turn we can use a for loop to increment the index number.

for ($i = 0; $i -lt $playList.count; $i++) {

    $item = $playList.Item($i)
    … do more stuff …

}

All of the information about the track that we want to update can be obtained from its full filename. The full filename is stored in the sourceURL attribute. This is accessed using the getItemInfo() method which accepts an attribute name as an argument.

$sourceURL = $item.getItemInfo("sourceURL")

More information about media item attributes can be found here:
http://msdn.microsoft.com/en-us/library/windows/desktop/dd563866(v=vs.85).aspx

To obtain the track name we can use Split-Path to get the filename from the sourceURL.

$fileName = $sourceURL | Split-Path -Leaf

The filename will have the format ‘nn Track Name.abc’ where nn is the track number and .abc is the file extension of the media file. To set the track name, we only require the ‘Track Name’ substring which we can obtain as follows:

$chars = $filename.Length -7
$trackName = $fileName.Substring(3,$chars)

The album name is the parent folder of the media file which we can obtain by splitting the path twice.

$albumName = $sourceURL | Split-Path | Split-Path -leaf

The artist name is the parent folder of the album folder which we can obtain by splitting the path three times.

$artistName = $sourceURL | Split-Path | Split-Path | Split-Path -leaf

One situation where this does not have the desired result is where the album is by various artists as this will result in the Artist attribute being set to ‘Various Artists’ rather than the actual artist that performed that particular track.

Once we have the required information we can update the item’s information using the setItemInfo() method which we call with two arguments, the name of the attribute that we want to update and the new value for the attribute.

$item.setItemInfo("Name",$trackName)
$item.setItemInfo("Album",$albumName)
$item.setItemInfo("Artist",$artistName)

In the final version of the script, I wrapped getting and setting of the information in a try catch block in case I was left with any tracks that could not be updated. I also chose not to process any items where the filename included the string ‘Unknown Artist’. If WMP has stored the album as ‘Unknown Artist’ it was unable to get information for that CD from the Internet, therefore the information in the file name is not correct and it’s pointless updating the library.

Finally, during testing, I found that the library does not always update straight away.
There is mention made of this in the documentation which states that “If you write code using the Windows Media Player control to change the value of an existing read/write attribute in a media item that has been added to the library, the effect is nearly the same as if the user had modified the attribute using Windows Media Player. The value is written to the library database and at some indeterminate time the database synchronizes with the file.”.
Releasing the COM object, with the final line of code, seems to consistently speed up this process.

The finished script:

# Note: Close any open instances of Windows Media Player before running the script

# Create an instance of a Player object

$wmp = New-Object -ComObject WMPlayer.ocx

# Generate a Playlist object containing all media items named 'Track 1'

$playList = $wmp.mediaCollection.getByName('Track 1')

for ($i = 0; $i -lt $playList.count; $i++) {

    try {

    # Get the sourceURL (full filename) of the current item

    $item = $playList.Item($i)
    $sourceURL = $item.getItemInfo("sourceURL")

    # Exclude items where the filename includes unknown artist to prevent processing
    # of unknown albums

        if ($sourceURL -notlike '*Unknown Artist*') {

            # Obtain the correct information for current item

            $fileName = $sourceURL | Split-Path -Leaf
            $chars = $filename.Length -7
            $trackName = $fileName.Substring(3,$chars)
            $albumName = $sourceURL | Split-Path | Split-Path -leaf
            $artistName = $sourceURL | Split-Path | Split-Path | Split-Path -leaf

            # Update the library information for the current item

            $item.setItemInfo("Name",$trackName)
            $item.setItemInfo("Album",$albumName)
            $item.setItemInfo("Artist",$artistName)

        } #end if
    } #end try

    catch {

        Write-Output "Error processing $playlist.Item($i). Track: $trackName, Album: $albumName, Artist: $artistName"
    } #end catch
} #end for

#Release the COM Object

[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wmp) | Out-Null

Post Script
When I originally wrote this article I had not found a way to update the file meta data so although I now had a way to repair the orphaned tracks by updating the information in the Windows Media Player database the underlying problem still remained. During further research I found a library, TagLib that can be used to write file meta data. In a future article I’ll look at how that library can be used with PowerShell to update the file meta data.

This entry was posted in PowerShell, Windows, Windows Media Player and tagged , , . Bookmark the permalink.

Leave a Reply

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