Processing iTunes Library with LINQ

Fresh from the inaugural cabal of ‘08, I wanted to do some automated processing tasks on my iTunes library.  I was delighted to see how easy it is to do this using LINQ to XML.

The iTunes library XML file is a rather awkward format.  The semantics are dependant on document node ordering, which is just unwieldy.  You can see a sample here.  In order to make the file more easy to query, I transformed it to a format like this:

<song>
  <TrackID>1999</TrackID>
  <Name>Classid</Name>
  <Artist>The Ace Of Clubs</Artist>
  <AlbumArtist>The Ace Of Clubs (Luke Vibert)</AlbumArtist>
  <Album>Benefist</Album>
  <Genre>Acid Disco</Genre>
  <Kind>MPEG audio file</Kind>
  <Size>9255508</Size>
  <TotalTime>385462</TotalTime>
  <TrackNumber>4</TrackNumber>
  <Year>2007</Year>
  <DateModified>2007-08-31T08:37:18Z</DateModified>
  <DateAdded>2008-03-09T18:37:50Z</DateAdded>
  <BitRate>192</BitRate>
  <SampleRate>44100</SampleRate>
  <SortAlbumArtist>Ace Of Clubs (Luke Vibert)</SortAlbumArtist>
  <SortArtist>Ace Of Clubs</SortArtist>
  <PersistentID>76F0C7752DA0CE57</PersistentID>
  <TrackType>File</TrackType>
  <Location>
    file://localhost/C:/data/music/Copied/copied%2009-04-07/luke%20viber
    t%20as%20ace%20of%20clubs%20-%20benefist/The%20Ace%20Of%20Clubs%20-%2004%20-%20C
    lassid.mp3
  </Location>
  <FileFolderCount>-1</FileFolderCount>
  <LibraryFolderCount>-1</LibraryFolderCount>
</song>

using the following LINQ code:

var rawsongs = from song in loaded.Descendants("plist").Descendants("dict").Descendants("dict").Descendants("dict")
        select new XElement("song",
            from key in song.Descendants("key")
            select new XElement(
                ((string)key).Replace(" ",""),
                (string)(XElement)key.NextNode)
                );

var songs = from song in rawsongs where song.Element("Location") != null select song;

Note that the code doesn’t actually read the file until I query; it transforms in a streaming manner.  Once I created the streaming filter, I could run queries like the following.

Show all protected (DRMd) songs:

from song in songs where song.Element("Protected") != null select song

Count the number of each type of song:

from song in songs
group song by (string)song.Element("Kind")
  into MyGroup
  select new { Key = MyGroup.Key, Count = MyGroup.Count() }

Count songs by rating:

from song in songs
group song by (string)song.Element("Rating")
  into MyGroup
  select new { Key = MyGroup.Key, Count = MyGroup.Count() }

From here it is trivial to do things like:

  • Eliminate duplicate files
  • Mirror ratings from the library to the file metadata
  • Create playlists with protected files to "archive" them
  • Find files which aren’t matched across playlist and hard drive

From an awkward file format to queries that are as easy as cake.  LINQ to XML is pretty sweet!

Leave a Reply

OPTION: 1