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!