How to read an iTunes playlist, sample code

Sage
Posts: 1,199
Joined: 2004.10
Post: #1
All,

Knowing that I'm a poor musician, and yet wanting music in my game, I decided to support playing of iTunes playlists from within the game. Playing music is easy enough, and I'm sure everybody here has their own way ( I'm using QTKit ).

But getting the iTunes playlist info, while easy, is non-obvious. I figured it out, and I'm attaching a Cocoa class for your use, if anybody wants.

First, the header:

Code:
//
//  iTunesPlaylistReader.h
//  AudioPlayer
//
//  Created by Shamyl Zakariya on 8/31/05.
//  Copyright 2005 Shamyl Zakariya. All rights reserved.
//

#import <Cocoa/Cocoa.h>


@interface iTunesPlaylistReader : NSObject {
    NSDictionary *_iTunesData;
}

/**
    @return an autoreleased array of the playlists in your
    iTunes library. It's just the names of the playlists.
*/
-(NSArray *) playlists;

/**
    @return an autoreleased array of NSDictionaries which represent each track
    in the passed in playlist. Each dictionary is the track dict
    in the itunes XML, meaning it can be represented as such:

    @begincode
    <dict>
        <key>Track ID</key><integer>36</integer>
        <key>Name</key><string>The Stars of Track and Field</string>
        <key>Artist</key><string>Belle & Sebastian</string>
        <key>Album</key><string>If You're Feeling Sinister</string>
        <key>Genre</key><string>Rock/Pop</string>
        <key>Kind</key><string>MPEG audio file</string>
        <key>Size</key><integer>4621294</integer>
        <key>Total Time</key><integer>288182</integer>
        <key>Track Number</key><integer>1</integer>
        <key>Date Modified</key><date>2004-06-21T11:20:55Z</date>
        <key>Date Added</key><date>2004-07-30T01:47:28Z</date>
        <key>Bit Rate</key><integer>128</integer>
        <key>Sample Rate</key><integer>44100</integer>
        <key>Play Count</key><integer>1</integer>
        <key>Play Date</key><integer>-1119524786</integer>
        <key>Play Date UTC</key><date>2004-08-15T23:21:50Z</date>
        <key>Artwork Count</key><integer>1</integer>
        <key>Track Type</key><string>File</string>
        <key>Location</key><string>file://localhost/Users/zakariya/Music/iTunes/iTunes%20Music/Belle%20&%20Sebastian/If%20You're%20Feeling%20Sinister/01%20The%20Stars%20of%20Track%20and%20Field.mp3</string>
        <key>File Folder Count</key><integer>4</integer>
        <key>Library Folder Count</key><integer>1</integer>
    </dict>
    @endcode
*/
- (NSArray *) tracksForPlaylist: (NSString *) playlist;

/**
    @returns an autoreleased array of the URLS to the music files
    in a given playlist. The URLS are actually NSStrings, in url syntax,
    like "file://localhost/..."

    They can easily be fed to NSURL and have the local path extracted.
*/
- (NSArray *) musicURLSForPlaylist: (NSString *) playlist;

@end

Now, the implementation:
Code:
//
//  iTunesPlaylistReader.m
//  AudioPlayer
//
//  Created by Shamyl Zakariya on 8/31/05.
//  Copyright 2005 Shamyl Zakariya. All rights reserved.
//

#import "iTunesPlaylistReader.h"


@implementation iTunesPlaylistReader

- (id) init {
    if ( (self = [super init]) )
    {
        NSString *home = NSHomeDirectory();
        NSString *iTunesXMLPath = [home stringByAppendingPathComponent: @"Music/iTunes/iTunes Music Library.xml"];
        _iTunesData = [[NSDictionary alloc] initWithContentsOfFile: iTunesXMLPath];

        return self;
    }
    
    return nil;
}

-(NSArray *) playlists
{
    NSArray *ignore = [NSArray arrayWithObjects: @"Library", @"Podcasts", @"Purchased Music", nil];

    /*
        The playlists entry is an array
    */
    
    NSArray *playlistsArray = (NSArray *)[_iTunesData objectForKey: @"Playlists"];
    NSMutableArray *playlistNames = [NSMutableArray array];
    
    int i, nPlaylists = [playlistsArray count];
    for ( i = 0; i < nPlaylists; i++ )
    {
        NSDictionary *playlistEntry = [playlistsArray objectAtIndex: i];
        NSString *playlistName = [playlistEntry objectForKey: @"Name"];

        if ( [ignore indexOfObject: playlistName] == NSNotFound )
        {
            [playlistNames addObject: playlistName];
        }
    }

    return playlistNames;
}

- (NSArray *) tracksForPlaylist: (NSString *) playlistName
{
    /*
        First, grab the playlist entries and build an array of the track ids
    */
    NSArray *playlistsArray = (NSArray *)[_iTunesData objectForKey: @"Playlists"];
    NSMutableArray *trackIDs = [NSMutableArray array];
    
    int i, nPlaylists = [playlistsArray count];
    for ( i = 0; i < nPlaylists; i++ )
    {
        NSDictionary *playlistEntry = [playlistsArray objectAtIndex: i];
        if ( [[playlistEntry objectForKey: @"Name"] isEqualToString: playlistName] )
        {
            NSArray *playlistItems = [playlistEntry objectForKey: @"Playlist Items"];
            
            /* playlist items is an array of dicts */

            NSEnumerator *e = [playlistItems objectEnumerator];
            id obj = nil;
            
            while ( (obj = [e nextObject]) )
            {
                [trackIDs addObject: [obj objectForKey: @"Track ID"]];
            }
        }
    }
    
    /*
        Now dig up the track information
    */
    NSMutableArray *tracks = [NSMutableArray array];
    NSEnumerator *e = [trackIDs objectEnumerator];
    id trackKey = nil;
    
    NSDictionary *trackListing = [_iTunesData objectForKey: @"Tracks"];
    
    while( (trackKey = [e nextObject]) )
    {
        NSObject *track = [trackListing objectForKey: [trackKey stringValue]];
        
        if ( track != nil )
        {
            [tracks addObject: track];
        }

    }

    return tracks;
}

- (NSArray *) musicURLSForPlaylist: (NSString *) playlist
{
    NSArray *tracks = [self tracksForPlaylist: playlist];
    NSMutableArray *files = [NSMutableArray array];
    
    NSEnumerator *e = [tracks objectEnumerator];
    NSDictionary *track = nil;
    while ( (track = [e nextObject]) )
    {
        [files addObject: [track objectForKey: @"Location"]];
    }
    
    return files;
}

@end

Usage is super simple: you just create an iTunesPlaylistReader, ask it for available playlists, and then ask for the URLs of the playlist you want. You can then feed those files to whatever mechanism you've got to play them. Since I'm using QTKit, I can play any audio file quicktime supports, and that appears to support music from the iTunes music store.

One note, the file urls returned by musicURLSForPlaylist: are strings in the form of "file://localhost/path/to/file". You've got to transform that into a local file path.

Here's my MusicPlayer's way:

Code:
- (void) playCurrentTrack
{
    if ( _movie )
    {
        [_movie release];    
    }
    
    NSString *fileURLString = [_urls objectAtIndex: _currentPlayingIndex ];
    NSURL *fileURL = [NSURL URLWithString: fileURLString];
    NSString *file = [fileURL path];
    
    NSError *error = nil;

    _movie = [[QTMovie movieWithFile: file error: &error] retain];
    
    if ( !_movie )
    {
        if ( error )
        {
            NSLog( @"MusicPlayer::playCurrentTrack\tUnable to open file %@ error: %@", file, error );
        }
        else
        {
            NSLog( @"MusicPlayer::playCurrentTrack\tUnable to open file %@", file );
        }
    }
    else
    {
        [_movie setVolume: _volume];
        [_movie play];
    }
}

And, here's a shot of it in my game config:
[Image: iTunesPlaylist.png]

Anyway, I hope somebody finds it useful.
Quote this message in a reply
Member
Posts: 257
Joined: 2004.06
Post: #2
Hey, that's pretty neat. I'm in the same boat as you too -- not a musician but I'd like to add music to my games. Giving the user the option to select an iTunes playlist sounds like a great idea.

Speaking of which, we really need a repository for code bits like this. I have on my web site a C++ class for generic handling of a high scores file that's free for people to use and I know other people have posted code bits and what not that they've written for others to use but without a central location to store all these things, it's hard to know what's out there (especially when you vaguely remember that someone a while back posted something useful that wasn't useful at the time but it is now).

The brains and fingers behind Malarkey Software (plus caretaker of the world's two brattiest felines).
Quote this message in a reply
Sage
Posts: 1,403
Joined: 2005.07
Post: #3
Yes thanks for the code,
I second that, An iDG code repo would be really useful.

Malarkey on your site I found Bill, he is back!
http://www.somegames.net/flashgames/BillTheDemon.html

Sir, e^iπ + 1 = 0, hence God exists; reply!
Quote this message in a reply
Member
Posts: 257
Joined: 2004.06
Post: #4
Ah, I was confused for a second about what you were talking about then I realized I put a link to my del.icio.us bookmarks on my website.

The brains and fingers behind Malarkey Software (plus caretaker of the world's two brattiest felines).
Quote this message in a reply
Moderator
Posts: 133
Joined: 2008.05
Post: #5
I got a quick question on this, it seems like the xml file is there for reading only, any changes done made to it will not be reflected in iTunes, and after a run of iTunes it is restored. Is there any way to go about changing the actual library file?
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Sample Code Apple removed? SaxMan 12 6,721 Jan 17, 2009 05:26 PM
Last Post: AnotherJake
  FBO sample code posted arekkusu 5 4,724 Sep 20, 2006 09:23 PM
Last Post: Frank C.
  PBuffer &amp; Render to Texture (read &amp; write) habicht 4 4,316 Feb 7, 2005 05:33 PM
Last Post: habicht
  TextureRange sample Red Marble Game 1 3,004 Dec 9, 2004 12:04 PM
Last Post: arekkusu
  Does Anyone have a sample of SDL being implemented without Project Builder? tingham 10 4,559 Oct 7, 2003 11:42 AM
Last Post: Fenris