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!

WordPress database error: [Can't open file: 'wp_comments.MYI'. (errno: 145)]
SELECT * FROM wp_comments WHERE comment_post_ID = '889' AND comment_approved = '1' ORDER BY comment_date

Leave a Reply