Random iTunes playlists (part 1)

published: Wed, 3-Oct-2007   |   updated: Sat, 13-Oct-2007

Enough of all this angst, let's get down to some programming and algorithms.

For a while now, I've been, shall we say, displeased with the shuffle capabilities of iTunes and with the AutoFill feature for my iPod Shuffle. It's weird but no matter how I set the Smart Shuffle slider, iTunes persists in latching onto an album and, every n tracks in the shuffled playlist where n is usually too small, selects a track from the same album. So, I can only take one Daft Punk track every now and then but when iTunes plays two or three in one day, I'm ready to delete the two albums from my library and feed the CDs into the paper shredder so I'm not tempted to re-rip them.

And then there's the problem of the music on my Sony Clié NX80V. I'd like a random selection to be copied onto the CompactFlash card, but iTunes can't help me there.

But, hey, I'm a programmer right? How difficult can it be? Time to do some research.

First things first: the iTunes folder in the My Music folder. There's two files in there: "iTunes Library.itl" and "iTunes Music Library.xml". What's the difference? Apple's support website has a page called What are the iTunes library files? and indicates that the former is the iTunes library database, whereas for the latter it says

This file contains some (but not all) of the same information stored in the iTunes 4 Music Library file. The purpose of the iTunes Music Library.xml file is to make your music and playlists available to other applications on your computer.

Yuk, parsing an XML file is not my idea of a fun programming project, but the support website seems to indicate that this is the way to go. But I was worried about that parenthetical "but not all" so, before starting, I researched a bit more and found the Apple Developer Connection website. I searched for "iTunes" and ended up on the page with a license agreement for the iTunes COM for Windows SDK. I accepted the license and download the aforementioned SDK.

This was more like it. It describes how you can access the COM objects that iTunes makes available. The SDK has a CHM help file that describes it all, albeit in a very terse manner, no quarter or examples given. Ace. I created a small spike application in Visual Studio and played around a little with the COM object, all the time listening to music on iTunes.

After a while I learnt enough to make the whole idea of creating a Julian-style random playlist generator eminently feasible. And no XML in sight: I would be accessing the iTunes database directly through iTunes. Cool. Time to think about what I wanted this application to do.

My first goal would be to help with my Clié problem; after that I could think about generating a random playlist for my iPod Shuffle. So without further ado: my goal is to write an application that will create a random collection of tracks from iTunes to copy to a given folder.

I jotted down some notes about this goal:

  • The program is intended to copy random collection of tracks to a CompactFlash (CF) card for the Sony Clié NX70V. I'm assuming the CF card is in a card reader, so that I can copy MP3s to the CF card at USB 2.0 speeds; unfortunately the dock for the Clié only uses USB 1.1.
  • I obviously need to be able to specify the folder location on the CF card (for the Sony it'll be of the form K:\PALM\PROGRAMS\MSAUDIO)
  • I need to be able to specify the maximum space in MB to use for the collection of tracks (eg, 1024MB). I don't particularly want to fill the CF card completely up.
  • I need to have some way of specifying the weighting of each track being selected. My idea here is to merge my pop/rock and classical libraries together, but only to have random selections from my pop collection. I hate "random" classical tracks and much prefer to listen to the whole piece, all movements, at once. So in this example I need to rate all classical tracks with zero weighting so they wouldn't be selected.

Bearing those notes in mind, let's break that overarching goal down into some smaller stories, so that I can achieve measurable progress through completing smaller tasks quicker, and not get swamped and disillusioned into trying to do it all at once.

  1. Access and open iTunes through COM
  2. Enumerate all songs in the iTunes library
  3. For each song, call a routine to specify its weighting
  4. Store the weighting with the track
  5. Generate a random number, and select the track according to that number (taking into account the weighting of each track -- now there's an algorithm to get one's teeth into)
  6. Store the selected tracks in a collection
  7. Ensure the collection of selected tracks doesn't exceed the size constraint
  8. Enumerate the selected tracks to copy them to the folder

I'll start off with a console application because it's easier and I won't get so tied up in UI fiddling at this stage. The first story is to access and open iTunes using COM. Luckily this is the one and only example snippet of code in the SDK help file. (There are some example programs written in Javascript, though, but that creates things in a different way to interop in .NET.)

Here's my first bit of code, based on that small example snippet. I added the iTunes library to the project's references which led to the using iTunesLib; statement:

  
  using System;
  using System.Text;
  using iTunesLib;
  
  namespace RandomITunesPlaylist {
    class Program {
      private static IiTunes iTunesApp = null;
  
      private static bool ConnectToITunes() {
        try {
          iTunesApp = new iTunesAppClass();
          return true;
        }
        catch {
          return false;
        }
      }
      private static void Main(string[] args) {
  
        Console.WriteLine("Random iTunes Playlist Generator");
        Console.WriteLine("(c) 2007 Julian M Bucknall");
        
        if (!ConnectToITunes()) {
          Console.WriteLine("*** iTunes application not found, terminating");
          return;
        }
        Console.WriteLine(String.Format("iTunes version {0} detected.", iTunesApp.Version));
      }
    }
  }

Not too difficult. I guessed with the try..catch but the program worked on both a machine with iTunes installed and on a machine that had never seen iTunes, so I must have been right. That was story 1 complete, although I did embellish a little because I found the Version property of the iTunesAppClass object and thought I'd display it.

A quick note about that call to String.Format. I didn't write it like that originally; I'm just too lazy. Instead I wrote this

  
  Console.WriteLine("iTunes version " + iTunesApp.Version + " detected.");

and then used Developer Express' Refactor! Pro to refactor the string concatenation into the String.Format call you now see.

The next story, number 2, is to enumerate all of the songs in the iTunes library. According to the SDK, the way that things are organized is that there is a special playlist called the library playlist that you access from the iTunesApp object (the property is LibraryPlaylist), and that playlist has a Tracks property that contains all the tracks.

So I wrote the following code inside my Main method, after opening iTunes:

  
  IITLibraryPlaylist playlist = iTunesApp.LibraryPlaylist;
  if (playlist == null) {
    Console.WriteLine("*** Could not retrieve library from iTunes");
    return;
  }

  IITTrackCollection trackCollection = playlist.Tracks;
  if (trackCollection == null) {
    Console.WriteLine("*** Could not retrieve track list from iTunes library");
    return;
  }

  Console.WriteLine("Reading all tracks from library...");
  foreach (IITTrack track in trackCollection) {	
    Console.WriteLine(track.Name);
  }

I'm probably being way overcautious in my validity checks for the library playlist and its tracks collection, but just in case I wrote it anyway, despite the fact I can't think of a way to test it. (well, OK, I would mock an iTunesApp object that returned the outputs I wanted to test, but I think it's a little overkill for this project.) To have something to do with each track, I just output its name to the console, but I'll be removing that line of code pretty quickly in story 3.

A quick CodeRush feature helped me here in writing thst foreach loop. I copied the trackCollection identifier onto the clipboard and then used CodeRush's fe template. If the clipboard holds an identifier that's also an enumberable collection, the fe template works out the type of the items in the collection and constructs the correct syntax in the parentheses of the foreach. A real time saver.

To be continued...

(In this series: part 1, part 2)