Syncing iTunes

One of the major components of Tiger is .Mac syncing, which allows developers to incorporate the syncing of application data across multiple computers through the .Mac API. Safari, Address Book, and iCal are just a few examples of Apple's own applications that have taken advantage of .Mac syncing for a while now. With the release of Tiger, that group has grown to include Mail, KeyChain Access, and several others (and with a newly public API, all developers are free to incorporate it into their own applications). However, surprisingly enough, iTunes is not among the list of Apple's applications that use .Mac syncing (not yet, at least).

The Situation

iTunes has long had the capability to share music libraries across your local network, allowing you to listen to your friends', roommates', or co-workers' music collections by streaming music over the network. So now everyone is happy: the record companies can't complain about illegal duplication of their products (although where there's a will, there's a way), you and your friends can listen to each other's music libraries with the click of a button, and the network effect means that Apple has a constantly growing customer base for the iTunes Music Store. Perfect, right?

The Problem

Well, almost; but not quite. What happens if two of the shared music libraries happen to be yours? Chances are that instead of sharing your libraries, you want to keep them synchronized. This should sound familiar to anyone with a laptop computer on the go and a desktop sitting at home. Throughout the course of the day, you tweak some playlists and would like to have those changes automatically reflected in your library at home. A similar--yet unique--situation arises when someone else creates a playlist that you would like to add to your own library.

Figure 1. The shared music library "willard" allows you to play its playlists, but it is tedious to create a local copy of a shared playlist with the songs from your own library

There are essentially three ways to add a shared playlist--that is, a playlist that can be streamed through iTunes's music sharing--to your library:

Search through your library for the songs that compose the shared playlist, then add each song by hand to a new playlist.

If you have a copy of the exported file generated by iTunes (which can be obtained using iTunes' "File -> Export Song List..." command, and is likely only if both computers are your own), then iTunes' "File -> Import..." command will do the dirty work.

Roll your own solution to fix the problem.

I think you know which route we're going to take.

The first two situations outlined above require the least amount of work up front; every time you want to synchronize a playlist from another source (whether it be from your other computer or from another person), simply add songs to and delete songs from the local copy of the playlist until the two are identical: click, type, click, drag, click, click, drag, click, click, drag, click, etc.

However, if you intend to repeat the steps on a regular basis, then things can get tedious very quickly. There's a reason we use computers, and performing the same mindless action over and over again isn't one of them. iSync and .Mac's iDisk manage to transparently keep your contacts and calendars in sync between multiple computers; let's see if we can't we make managing playlists almost as easy.

Of course, several concepts are common to both solutions, the most notable being creating the actual playlist once we have determined the songs that compose it. But we'll cross that bridge when we get to it; let's get our hands dirty.

Solution, Part 1: Importing Accessible Playlists

Importing playlists from shared sources (such as "willard," shown in Figure 1), is relatively straightforward. The following steps outline the required steps:

Create a new playlist that will act as the local copy of the shared playlist.

Loop through all of the tracks in the shared playlist.

Check to see if the track exists in our library.

If the track does exist in our library, then add it the local copy.

The tricky part in the solution outlined above is to determine whether or not a given track exists in our library. The only reliable method is to do an exhaustive search through the entire music library looking for a track with a particular artist, album, and name to see if it exists. (Of course, you could check more fields--such as the track number, year, genre, etc.--but the three listed above should be enough to uniquely identify tracks.)

Why? Because the tracks originate from different sources, it is improbable that they will share the same database ID across different music libraries. However, because iTunes uses the CDDB to populate a track's metadata, we should be able to rely on the three fields mentioned above to uniquely identify the same track across different libraries:

on getTrack(_artist, _album, _track)
tell application "iTunes"
-- search for the track in our library
set mytrack to every track of playlist 1 whose
(artist is _artist and album is _album and name is _track)
-- if the track exists, then return it
if (length of mytrack is 1) then
return item 1 of mytrack
-- else return false to indicate that no such track exists
else
return false
end if
end tell
end getTrack

If tracks contain different information in one of the album, artist, or track name ID3 tags across two different libraries, then they will not be considered identical, according to the above function. If in fact they should be considered identical, then it will be necessary to manually edit the artist, name, and album tags so that they are identical across the music libraries.

An AppleScript that solves our problem can be downloaded here; the file can be opened and inspected from within Script Editor to see how it works.

Importing a shared playlist from iTunes is now simply a matter of selecting the desired list in iTunes, and running the above copyPlaylist AppleScript from the Finder or through the Script menu. No fuss, no muss. And in the event that a shared playlist contains at least one track that does not belong to our library, you will be alerted to how many tracks are missing from your copy of the playlist.

Figure 2. The dialog shown after importing a playlist, if you are missing one or more tracks.

Try it for yourself to see how it works, and then let's move on to step two: importing playlists that aren't accessible from within iTunes.

Solution, Part 2: Inaccessible Playlists

In the solution to the first part of our problem, we were able to import a shared playlist directly from iTunes. However, because our copies of iTunes are no longer able to "see" each other, we must now obtain the playlists' tracks through an intermediate step that is then obtained by our library and imported. Sounds like something XML might useful for, doesn't it?

Design Decision

iTunes maintains an iTunes Music LibraryXML file in the ~/Music/iTunes/ folder (or ~/My Documents/My Music/iTunes/ on Windows) that serves as a copy of the music database in Cocoa's plist format; in fact, files with the same structure can be used by iTunes' File -> Import and File -> Export commands to import/export playlists. Thus, the plist format serves as an obvious medium to transfer a playlist from one music library to another, due to the ease with which it is both created and read by iTunes. However, the fact that the format is easily parsed by a machine does not make it lightweight or easily understood by humans--two aspects that we are looking for, since the file should be quick to download over slow connections and understandable at a quick glance.

Thus, a "lighter" format will be used, which can be summed up by the following example:

The first step is to create the XML file that will serve as the glue between the two libraries. To do this, we will need a utility to extract the information from iTunes and transform it to the document structure shown above. As always, there are a variety of methods to get this done: anything from an XSLT transformation on iTunes Music Library.xml to a quick hack written in AppleScript. However, I decided to use my pet project, mytunes, since it takes care of the heavy lifting.

The first thing on the menu is to create the class that extends com.fivevoltlogic.mytunes.Chord in order to generate the XML file discussed above (the details on how to complete this step are covered in the article that introduced mytunes, and is also available in the project documentation); a copy of the class is available here, and has the following command-line options/arguments:

--library (argument optional)

If an argument is included, then its value is taken to be the location of your iTunes Music Library.xml file; otherwise, the default location is used. If no argument is included then the program will read in from STDIN.

--destination (argument required)

Will store the XML document in the specified file, rather than echoing it to STDOUT.

--id (argument required)

Sets the id attribute in the playlists.xml file; if omitted, then your OS X username will be used.

--xslt (argument required)

Will include the value as the location of an XSLT stylesheet within the playlist's XML document; no stylesheet is included if this option is omitted.

(Remaining arguments)

All remaining arguments to the application will be interpreted to be the names of the playlists that are to be exported.

The following examples show how the application behaves in response to the various command-line options:

# reads iTunes Music Library.xml from standard in and
# echoes the glue for all playlists to standard out
java com.fivevoltlogic.mytunes.sync.Export
# reads iTunes Music Library.xml from its default location
# (~/Music/iTunes/iTunes Music Library.xml) and echoes the
# glue for all playlists to standard out
java com.fivevoltlogic.mytunes.sync.Export --library
# reads iTunes Music Library.xml from its default location,
# sets the creator id to "fvl", and echoes the glue for
# all playlists to standard out
java com.fivevoltlogic.mytunes.sync.Export --library --id fvl
# reads iTunes Music Library.xml from ~/Desktop/backup.xml,
# sets the creator id to "fvl", and echoes the
# glue for the playlists top10 and ugly ducklings to standard out
java com.fivevoltlogic.mytunes.sync.Export --id fvl --library
~/Desktop/backup.xml top10 "ugly ducklings"

Note: as with all Java applications, you will have to ensure that all Java classes required are included in your classpath; the easiest way of doing this on OS X is to place the freshest copy of the JAR file(s) in /Library/Java/Extensions and remove any older versions of the file(s) to avoid the chance that the Java interpreter might load the older versions before the newer copies. Also, you can save yourself from the pain of repeatedly typing out the command java com.fivevoltlogic.mytunes.sync.Export by using a simple shell script to act as a shortcut; just make sure that the shell script is saved in a location that is included in your shell's PATHenvironment variable.

Since the release of the OS X 10.3.9 update, browsers that use WebKit rendering (the most notable being Safari and Shiira) are now capable of rendering XML documents that are transformed through XSLT stylesheets; this capability has been available in Internet Explorer and Mozilla and its many cousins for some time now. With an XSLT stylesheet working its mojo, the exported file XML can be made into something a little more browser-friendly, as shown in the following screenshot taken from www.fivevoltlogic.com/mytunes/playlists.xml:

Importing the Glue

But let's get back to the topic at hand. We now have the XMLglue--the next thing we'll need is a class that can parse the file and generate the appropriate AppleScript commands to recreate the playlist(s) in iTunes. Creating this class is much simpler than you might first think; all we have to do is create a buffer where AppleScript commands are appended every time an element is stumbled upon while digging through the XML file.

Note: Creating a playlists.xml file can be accomplished on any platform that iTunes runs on (currently Windows XP/2000 and OS X), since the heavy lifting is taken care of by a Java application that parses iTunes's XML database file. All pieces of this part of the puzzle are platform-independent. However, in order to recreate a playlist in iTunes from the markup of a playlist's XML document, AppleScript is going to have to be part of the equation. Thus, this solution only allows you to import playlists on OS X; those using iTunes under Windows will only be able to export their playlists. That being said, let's move on to how we can go about importing our playlists into a copy of iTunes.

The application has the following options:

--library (argument required)

If not specified, then the program will read the playlists file from STDIN; otherwise, if specified, then the path to the playlists.xml file/URL must be included.

--marker (argument required)

The string of characters inserted into the imported playlist name between the playlist's creator and its original name (if this option is not specified, ":" will be used).

--delete (no argument)

If specified, then all existing playlists from the person whose playlists we are importing (discovered by using the id attribute of the document) will be deleted before new ones are created.

--id (argument required)

Sets the id attribute in the playlists.xml file; if omitted, then your OS X username will be used.

(Remaining arguments)

All remaining arguments to the application will be interpreted to be the names of the playlists that are to be imported.

Again, the following examples show how the different arguments to the program alter its behavior (these examples make use of this shell script to save some more typing):

# reads a playlist.xml file from STDIN and creates all playlists
# contained in it
importTunes
# reads a playlist.xml file from STDIN and creates all playlists
# contained in it, separating the playlist creator and name
# by the string ">>" instead of the default ":"
importTunes --marker ">>"
# reads a playlist.xml file from STDIN and creates three of
# the playlists contained in the file (top10, anne murray,
# and kim mitchell)
importTunes top10 "anne murray" "kim mitchell"
# reads the playlists.xml file located on your desktop, deletes
# all existing imported playlists from the user that created the
# file, and then creates all of the files' playlists
importTunes --library ~/Desktop/playlists.xml --delete
# reads file located at
# http://www.fivevoltlogic.com/mytunes/playlists.xml and
# creates all its playlists (enter command with no line break)
importTunes --library
http://www.fivevoltlogic.com/mytunes/playlists.xml

As shown in the last example above, it is also possible to import files over the network (whether it be from your company's intranet site or from a friend who's posted playlists to his/her website). Importing your friends' playlists is now simply a matter of entering a few commands in a Terminal window:

Figure 4. Importing a music library through the terminal

Everything is fine and dandy as long as you're comfortable with the Terminal and don't mind switching to it to update your music library with your friends' newest playlists. However, there's no reason to stop now--by sticking on an accessible interface, importing playlists can be made even easier.

Taking Things One Step Further

Through the magic of AppleScript, it is possible to make importing playlists into iTunes even easier. First, we must obtain the URL of the current document in Safari (or Shiira, if you prefer) and then feed that to the com.fivevoltlogic.mytunes.sync.Import application. Save the following AppleScript (without the extra line breaks) in your ~/Library/Scripts/ folder and execute it via the Script menu when viewing a playlists.xml file in Safari:

set myresult to ""
set myurl to false
tell application "Safari"
try
set myurl to URL of document 1
on error e
display dialog "The playlist file could not be located."
buttons {"OK"} with icon stop default button 1
giving up after 10
end try
end tell
if (myurl is not false) then
set myresult to do shell script "curl \"" & myurl &
"\" | java com.fivevoltlogic.mytunes.sync.Import
--delete & "
end if
if (myresult is not "") then
display dialog "The playlists could not be imported: "
& myresult & "." buttons {"OK"} with icon stop
default button 1 giving up after 10
end if

Just remember to have the mytunesJAR file in your classpath (available here); otherwise, the script will fail because it still relies upon the mytunes Java classes.

A similar script can be created for those who prefer to use Shiira; in fact, it is also possible to import the playlists from OmniWeb. However, because OmniWeb does not (yet) render XML documents with XSLT stylesheets, you won't actually know what you're importing unless you resort to viewing the source of the page. Other OS X browsers (such as Firefox and Camino) that are able to view XML documents are not AppleScript-savvy, which means that you'll have to resort to the Terminal to import playlists.

Loose Ends

This solution is by no means perfect. The most obvious wart is the relatively poor performance of importing a large number of playlists or playlists with a large number of songs (or both), since this solution is reliant on AppleScript to do the heavy lifting. However, it's perfect for running late at night when your computer would otherwise be idle--when you wake up in the morning, you'll have new playlists to listen to!

David Miller
is combining his passions of photography and working on the web at iStockphoto; when not hacking away with a text editor and a few web browsers in hand, he can be seen huddled over his laptop tweaking levels and curves for his freelance photography. Keep track of David's latest projects over at his home on the web.