The first nail in the coffin of Python appscript

Yesterday I noticed that my iTunes info script wasn’t working. This is the script that NerdTool uses to display the currently playing track down in the lower right corner of my Desktop. Like this:

iTunes info via NerdTool

There are actually two scripts at work here: one that gets the album artwork, and one that gets and prints the informational text. It was only the latter that failed. That script, named itunes-playing and written in Python using the appscript library, looks like this:

python:
 1:  #!/usr/bin/env python
 2:  
 3:  from appscript import *
 4:  
 5:  template = '"%s" by %s (%d)\n from %s'
 6:  info = ''
 7:  
 8:  iTunesCount = app('System Events').processes[its.name == 'iTunes'].count()
 9:  if iTunesCount > 0:
10:    iTunes = app('iTunes')
11:    if iTunes.player_state.get() == k.playing:
12:      track = iTunes.current_track.get()
13:      who = track.artist.get()
14:      what = track.name.get()
15:      onwhat = track.album.get()
16:      stars = track.rating.get()/20
17:      info = template % (what, who, stars, onwhat)
18:  print info

It can be run from the command line, and when I did I got the following error message:

AttributeError: Unknown property, element or command: 'player_state'

suggesting a problem on Line 11. Since I had just updated iTunes to version 10.6.3, my first guess was that the new version had removed the player state property from its AppleScript library. But a quick review of the library in the AppleScript Editor (and a one-line script to test it out) showed that player state is still there—the problem is that appscript can’t access it the way it used to.

I take this as the first of a series of failures that my appscript-using programs will suffer as time goes on. As I’ve mentioned here before, appscript’s developer, Hamish Sanderson, has stopped developing appscript because Apple is deprecating certain Carbon libraries it depends on and not providing equivalent functionality in Cocoa. He recommended several months ago that appscript not be used in any new projects.

It may well be that I could tweak itunes-playing to work around the AttributeError, but the effort I’d put into that fix would be, in the long run, a waste of time. Appscript is a dead end; when a script using it breaks, the best solution is to rewrite it in AppleScript or a combination of AppleScript and another language. I’m a little surprised to be forced into this before Mountain Lion, but the failures had to start coming sooner or later.

In this case, the fix was quite easy. Because the information is just a concatenation of several track properties, my Python/appscript program could be replaced with pure AppleScript:

 1:  tell application "System Events"
 2:    if name of processes does not contain "iTunes" then return ""
 3:  end tell
 4:  tell application "iTunes"
 5:    if player state is playing then
 6:      set ct to current track
 7:      set stars to round((rating of ct)/20)
 8:      set info to "“" & name of ct & "”" & " by " & artist of ct & " (" & stars & ")" & "
 9:  " & album of ct
10:      return info
11:    else
12:      return ""
13:    end if
14:  end tell

This is saved as a Script named itunes-playing.scpt in my ~/bin/ directory. NerdTool doesn’t know how to run AppleScripts directly, but because it can run shell scripts, all I have to do is have it run the shell command

osascript ~/bin/itunes-playing.scpt

NerdTool iTunes playing script

The osascript command is one of those wonderful little utilities Apple included in OS X back when it was trying to make life easier for scripters.

I’m not certain how many of my active scripts still use appscript, but you can expect to see more posts like this in the future. And since most of these scripts do a lot of text manipulation—something AppleScript is particularly bad at—I won’t be able to get away with pure AppleScript solutions very often. I don’t see much value in going out to change scripts that are still working, but I’ve been looking into convenient ways to mix Python and shell scripts so I’ll be ready when they do start to break.

Update 6/27/12
The comments are especially good and especially lengthy. If you have any appscript-using scripts that have been broken by the iTunes update, and you really don’t want to write in AppleScript, you can use the technique described below by Matt Neuburg to generate a static “glue” file that will get appscript working with iTunes again. Matt’s provided a glue file for Ruby and a summary of how to use it in his online Ruby/Appscript book. Andrew Barnert has extended Matt’s work to generate glue files for Python, Ruby, and Objective-C and put them into a GitHub repository.