A present for Palm
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.
- Palm is hardly a “little guy”.
- 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()
-
if element.text == 'Name':
- 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
-
if element.text == 'Track ID':
-
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
-
if possibleTrack.text == trackId:
- 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
-
if filesize < 1024.0:
- return "%3.1f%s" % (filesize, 'PB')
-
for bytesLevel in ['B', 'KB', 'MB', 'GB', 'TB']:
-
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()
-
if element.text == 'Music Folder':
-
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)
-
for track in playlistItems:
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.
- May 15, 2011: Copying an iTunes playlist
-
When you give, you receive. For my Pioneer 3200BT review I needed to copy 32 gigabytes of music to an SD card, to see how fast the unit would load that many tracks. Making a giant playlist in iTunes was easy. But iTuneMyWalkman, an otherwise very useful application, was taking forever just to collect the list of songs from iTunes—it had taken over two hours and hadn’t even started a copy yet. And iTunes itself doesn’t let you drag that many items to the Finder.
But the iTunes script I wrote as a joke for Palm does almost everything I needed: it loops through all tracks in a named playlist. All I had to do was add a function to copy each track to a specified folder.
At the top, along with the other imports, add:
- import urllib, shutil
The urllib library is necessary because iTunes stores track locations in its XML file as file:// encoded URLs. The shutil library will copy a file.
In the OptionParser section, add a new option:
- parser.add_option('-c', '--copy', help='Copy tracks')
Because --copy means it has to loop through all tracks, just like --space and --tracks, change the “if options.showTracks or options.tallySpace:” line to:
- if options.showTracks or options.tallySpace or options.copy:
Underneath the other two track-based options, add an “if” for copying:
[toggle code]
-
if options.tallySpace and 'Size' in track:
- spaceUsed = spaceUsed + int(track['Size'])
-
if options.copy:
- copyTrack(track)
And, finally, among the other functions, add a copyTrack function:
- iTunes on Wikipedia at Wikipedia
- “The second file, iTunes Music Library.xml, is refreshed whenever information in iTunes is changed. It uses an XML format, allowing developers to easily write applications that can access the library information (including play count, last played date, and rating, which are not standard fields in the ID3v2.3 format). Apple's own iDVD, iMovie, and iPhoto applications all access the library.”
- lxml: Processing XML and HTML with Python
- “lxml is the most feature-rich and easy-to-use library for working with XML and HTML in the Python language.”
- The Macalope Weekly: Free Apple tablet: The Macalope
- “You know, every time this piece gets written as if it’s some brand-spankin’ new piece of brilliance that just occurred to the author, the Macalope wonders why they have not heard of the thing the kids call ‘the Google.’ There should be a law—the Macalope’s Law?—that says the minute you say Apple is Microsoft, you lose the argument.”
More iTunes
- Catalina vs. Mojave for Scripters
- More detail about the issues I ran into updating the scripts from 42 Astounding Scripts for Catalina.
- Catalina: iTunes Library XML
- What does Catalina mean for 42 Astounding Scripts?
- Getting the selected playlist in iTunes
- It took a while to figure out how to get iTunes’s selected playlist as opposed to the current playlist in AppleScript.
- Cleaning iTunes track information
- Python and appscript make it easy to modify iTunes track information in batches—if you’re willing to get your hands dirty on the Mac OS X command line.
- Play this song backwards in iTunes
- Using AppleScript and Quicktime, you can play any song backwards. Find out what Fred Schneider was saying on “Detour Thru Your Mind”.
- Five more pages with the topic iTunes, and other related pages
More XML
- Catalina: iTunes Library XML
- What does Catalina mean for 42 Astounding Scripts?
- Parsing JSKit/Echo XML using PHP
- In the comments, dpusa wants to import JSKit comments into WordPress, which uses PHP. Here’s how to parse them using PHP.
- Parsing JSKit/Echo XML comments files
- While I’m not a big fan of remote comment systems for privacy reasons, I was willing to use JSKit as a temporary solution because they provide an easy XML dump of posted comments. This weekend, I finally moved my main blog to custom comments; here’s how I parsed JSKit’s XML file.
- Auto-closing HTML tags in comments
- One of the biggest problems on blogs is that comments often get stuck with unclosed italics, bold, or links. You can automatically close them by transforming the HTML snippet into an XML document.
- minidom self-closes empty SCRIPT tags
- Python’s minidom will self-close empty script tags—as it should. But it turns out that Firefox 3.6 and IE 8 don’t support empty script tags.
- Five more pages with the topic XML, and other related pages
You’re welcome! Happy New Year to you, too.