Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, PHP, and whatever else I happen to feel like hacking at.

A present for Palm

Jerry Stratton, January 20, 2010

I was browsing the web, minding my own business, when a twelve-point Macalope strode past. Being from flyover country, my first thought was “dinner on the table”. But since this is the holiday season and it’s possible he’s a distant relation to Rudolph, I offered him a beer and we started talking.

Well, not so much talking. When a creature has a computer screen for a head, their side of the conversation is all blog. It’s not like the Macalope’s got high-res graphics in that MacPlus.

Apparently Computerworld is taking Apple to task because Palm’s software doesn’t work. Some idiocy is so bad it requires fictional creatures to respond; the Macalope, in a springtime editorial, does the job.

  1. Palm is hardly a “little guy”.
  2. It’s hardly Apple’s job to do Palm’s work for them.

But the mythical man/Mac/beast forgot point 3: Apple has already done Palm’s work for them. The Macalope writes:

Just move the files. All you’re really complaining about is losing the playlists. Even in the realm of first-world problems, that rates pretty low.

In fact, even that isn’t a problem no matter what world you’re in (or on). Apple has made all of this data easily available in a standard, easy-to-read (for computers) format. Every iTunes user’s Music folder contains an “iTunes Music Library.xml” file. As you might guess from the extension, this file is simple XML. It continuously updates with not just your music, but also all of your playlists: both the static ones and the dynamic ones.

Why do they do this? So that ungrateful third-parties like Palm can use it to make life easier for iTunes-using music lovers.

So if you want to blame someone for Palm not syncing your iTunes music, blame Palm; Apple’s already done the work, clocked out, and gone home to the little tablet and six tiny iPods.

But why talk about blame? It’s a new year and a new era of bipartisan peace, love, and understanding. In the spirit of the post-holiday season, here’s a present for Palm.

[toggle code]

  • #!/usr/bin/python
  • import os, sys, optparse
  • import lxml.etree
  • home = os.getenv('HOME')
  • iTunesFile = os.path.join(home, 'Music/iTunes/iTunes Music Library.xml')
  • def parsePlaylist(playlist):
    • name = None
    • identifier = None
    • itemList = None
    • for element in playlist.iterchildren('key'):
      • if element.text == 'Name':
        • name = element.getnext().text
      • elif element.text == 'Playlist Persistent ID':
        • identifier = element.getnext().text
      • elif element.text == 'Playlist Items':
        • itemList = element.getnext()
    • return name, identifier, itemList
  • def parseTrack(trackInfo, tracks):
    • track = None
    • trackId = None
    • path = None
    • for element in trackInfo.iterchildren('key'):
      • if element.text == 'Track ID':
        • trackId = element.getnext().text
    • for possibleTrack in tracks.iterchildren('key'):
      • if possibleTrack.text == trackId:
        • trackElement = possibleTrack.getnext()
        • track = {}
        • for trackdata in trackElement.iterchildren('key'):
          • track[trackdata.text] = trackdata.getnext().text
    • return track, trackId
  • def humanReadableFilesize(filesize):
    • for bytesLevel in ['B', 'KB', 'MB', 'GB', 'TB']:
      • if filesize < 1024.0:
        • return "%3.1f%s" % (filesize, bytesLevel)
      • filesize /= 1024.0
    • return "%3.1f%s" % (filesize, 'PB')
  • def quit(message):
    • print message
    • sys.exit()
  • parser = optparse.OptionParser("itunes [-options] <playlist>")
  • parser.add_option('-s', '--space', help='Tally up disk usage for tracks', default=False, action='store_true', dest='tallySpace')
  • parser.add_option('-t', '--tracks', help='Show all tracks', default=False, action='store_true', dest='showTracks')
  • parser.add_option('-m', '--main', help='Include main library', default=False, action='store_true', dest='showMain')
  • (options, listsToShow) = parser.parse_args()
  • if not os.path.exists(iTunesFile):
    • quit("You do not have an iTunes XML file at " + iTunesFile)
  • root = lxml.etree.parse(iTunesFile)
  • iTunes = root.find('dict')
  • for element in iTunes.iterchildren('key'):
    • if element.text == 'Music Folder':
      • iTunesFolder = element.getnext().text
    • if element.text == 'Playlists':
      • playlists = element.getnext()
    • if element.text == 'Tracks':
      • tracks = element.getnext()
  • for playlist in playlists:
    • playlistName, playlistId, playlistItems = parsePlaylist(playlist)
    • if playlistName == 'Library':
      • continue
    • if playlistName == 'Music' and not options.showMain and playlistName not in listsToShow:
      • continue
    • if playlistItems is None:
      • continue
    • if not listsToShow or playlistName in listsToShow:
      • itemCount = len(playlistItems)
      • spaceUsed = 0
      • print playlistName, "("+str(itemCount), 'item'+('s' if itemCount!=1 else '') +')'
      • if options.showTracks or options.tallySpace:
        • for track in playlistItems:
          • track, trackId = parseTrack(track, tracks)
          • if options.showTracks:
            • info = [track['Name']]
            • if 'Album' in track:
              • info.append(track['Album'])
            • if 'Artist' in track:
              • info.append(track['Artist'])
            • print "\t", "\t".join(info)
          • if options.tallySpace and 'Size' in track:
            • spaceUsed = spaceUsed + int(track['Size'])
        • if options.tallySpace:
          • print "\t", 'Space used:', humanReadableFilesize(spaceUsed)

It requires lxml, because lxml is a lot faster than Python’s built-in XML module, and music lovers have big XML files, IYKWIMAITYD.

It’s very simple; run the script and it will display a list of all playlists; type some playlist names as arguments, and it will only display those playlists. Add the option --space to tally up how much space the playlist will use, and --tracks to list all of the tracks in the playlist.

You’re welcome! Happy New Year to you, too.

  1. <- Inkscape extension scripts
  2. All roads lead up ->