Thursday 31 May 2012

Powershell Script to Shuffle Albums in Windows Media Player

How I used Powershell to create playlists of random albums from my library, avoiding single tracks and allowing me to exclude the tracks I always skip.

It’s not polite to ask a techiebird her age, but I’ll tell you that I’m old enough that most of the music I’ve listened to has been albums, not singles. And many of my albums sound better as albums, i.e. played from start to finish in the order the artist intended.
Sometimes I don’t want to have to decide what to listen to. I just want to hear something good and I want to hear all of it. Or sometimes I just like the familiarity of a compilation playing the tracks in the order I expect. So it’s a shame there isn’t a “shuffle albums” feature in any media player hardware I’ve come across, or any of the major software media players.
However, we have PowerShell, Windows Media Player has an object model, and how hard can it be to create a playlist of complete albums anyway?
Actually, harder than I expected. But, helpfully, James O’Neill wrote about the quirks of the WMP Object Model some time ago, which helped a lot. (Definitely read his two posts on scripting for WMP if you’re thinking of doing something similar. They’re written for PowerShell v1 but everything works much the same.)
Anyway, here’s what I came up with (usual disclaimer applies):
   1: Function Shuffle-Albums

   2: {Param ([int]$NumberOfAlbums, [int]$MinTracks, [int]$MinStars=0)

   3:  

   4: write-host $NumberOfAlbums.tostring() "albums, containing at least" $MinTracks.tostring() "tracks rated at least" $MinStars.tostring() "/5"

   5:  

   6: #Set up objects

   7: $wmp=new-object -COM WMPlayer.ocx

   8: $all=$wmp.mediacollection

   9:  

  10: #Get the names of all the albums from the track attribute db

  11: $albs=$all.getAttributeStringCollection("Album","Audio")

  12: $Albums=@()

  13: $AlbumNames=@()

  14:  

  15: #Put the album names in an array

  16: for ($n=0;$n -lt $albs.count;$n++)

  17:     {$AlbumNames+=$albs.item($n)}

  18:  

  19: #Step through and put the tracks for each album in a temporary playlist

  20: $Albumnames | foreach {

  21:     $p=$all.getByAlbum($_)

  22:     if (($p.count -ge $MinTracks) -and ($_ -ne "")) {

  23:         $p.name = $_

  24:         $Albums+=$p

  25:         }

  26:     }

  27:  

  28: #Pick some random albums

  29: $AlbumSet=$Albums|get-random -count $NumberOfAlbums

  30:  

  31: $m=$wmp.mediacollection

  32:  

  33: #If we already created a playlist, delete it

  34: if($m.getbyname("Random albums").count -gt 0) {

  35:     For ($i=0;$i -lt $m.getbyname("Random albums").count;$i++) {

  36:         $r=$m.getbyname("Random albums").item($i)

  37:         $rURL=$r.sourceURL

  38:         $wmp.playlistcollection.remove($r)

  39:         remove-item $rURL

  40:     }

  41: }

  42:  

  43: #Create our new playlist

  44: $PlayList=$wmp.newPlaylist("Random albums","")

  45:  

  46: #Add all our songs to the new playlist, checking they meet the minimum star rating (or haven't been rated)

  47: $AlbumSet | foreach {

  48:     for ($t=0;$t -lt $_.count;$t++){

  49:         $s=$_.item($t)

  50:         if(($s.getItemInfo("UserRating") -gt $MinStars-1*25-13) -or $s.getItemInfo("UserRating") -eq 0) {

  51:             $Playlist.appendItem($s)

  52:         }

  53:     }

  54: }

  55:  

  56: "Added " + ($playlist.count).tostring() + " items to playlist" | out-host

  57:  

  58: #Write our playlist to a file

  59: $wmp.playlistcollection.importplaylist($Playlist) | out-null

  60:  

  61: #Pass back the filename (URL) of our playlist

  62: Write-Output $wmp.mediacollection.getbyname("Random albums").item(0).sourceurl

  63:  

  64: $wmp.close | Out-Null

  65: $wmp = $null | Out-Null

  66:     

  67: }

  68:  

  69: $url=$null

  70: $url=Shuffle-Albums 10 6 3

  71:  

  72: "Playlist saved as " + ($url).tostring() | out-host

  73:  

  74: while ((Get-Item $url).length -le 250) {start-sleep -seconds 5}

  75:  

  76: $Player=new-object -COM WMPlayer.ocx

  77: $Player.openPlayer($url)
Most of the work is done by the function Shuffle-Albums, which takes the following parameters:
NumberOfAlbums How many albums to add to the playlist
MinTracks The minimum number of tracks for an album to be considered ‘complete’. (I have singles and other miscellaneous tracks which have album information in WMP, and the database doesn’t have anything to distinguish these from complete albums. This seemed like the easiest way to avoid having one-track ‘albums’ in my playlist.
MinStars Counter to my argument for having an album shuffler in the first place, some albums have one or two sucky tracks I prefer to skip over. This parameter gives me the option of giving those tracks a low rating and having them missed out from my playlist. Most of my tracks are unrated, so those always get included.
So the line $url=Shuffle-Albums 10 6 3 will return a URL (file path) to a playlist of 10 albums, each containing 6 tracks or more, skipping any tracks rated with fewer than 3 stars.
I’d quite like to put a UI on this but realistically I may never get round to it. If anyone else wants to have a go then I’m very happy for you to use my code as long as you share the results for free :)
And as always, please let me know in the comments if you spot anything I could fix to make the code better.
Creative Commons License This work by TechieBird is licensed under a Creative Commons Attribution-No Derivative Works 2.0 UK: England & Wales License.