Mimsy Were the Borogoves

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

Using appscript in Python to control GUI applications

Jerry Stratton, October 7, 2006

I have a laptop in one room and my main computer in another. My music is all on the other computer, which is plugged directly into my stereo via an M-Audio FireWire Audiophile. Sometimes, I feel like controlling iTunes from the laptop. There are tools that will do this, but they require opening up remote scripting or similar technologies on the host computer. I prefer a nice, simple interface that I can use via the already-existing SSH command line.

My first attempt at this involved a Perl script that called an AppleScript, waited for the script to finish, and then operated on files that the script left behind. This worked fine for play and for pause, but was very unreliable for finding and playing tracks based on various criteria.

Several weeks ago, I ran across appscript. This is a Python module that brings scriptability down directly into Python. It makes extensive use of Python’s strengths, and it is extremely useful. However, the documentation for it is extraordinarily sparse. It’s great that the help is built in, but the built-in help is also decidedly hit-or-miss. You have to guess where something is in order to get the help for it.

Each object has a .help() method attached to it. For example, after installing appscript, start up pythonw (you need to use pythonw rather than python so that Python can attach to the GUI) and type:

  • pythonw
  • import appscript
  • iTunes = appscript.app("iTunes")
  • iTunes.help()
  • iTunes.play.help()
  • browserWindows = iTunes.browser_windows()
  • browserWindow = browserWindows[0]
  • browserWindow.help()
  • playList = browserWindow.view()
  • playList.help()
  • thirdTrack = playList.tracks[3]
  • thirdTrack.help()

You can also do “help(appscript)”. And you may find “help(appscript.specifier)” useful. There is very little otherwise in the way of documentation. This is very much for Python programmers who don’t like the Applescript way. Those of us who do like Applescript but want a solid command-line scripting environment will have to muddle through.

Methods and properties appear to be the same as in the dictionary that ScriptEditor shows, with spaces replaced with underscores.

If you want a value of a property, you need to append it with “()”: both properties and actions appear to be functions. I suspect that “()” is shorthand for the “.get()” method on the object.

If you don’t want the value, but plan on getting another value off of it later, you probably do not want to use “()” or “.get()”. Doing so may give you a huge, time-consuming list. For example, if you want the third track, you do not want to do:

  • tracks = playList.tracks()
  • thirdTrack = tracks[3]
  • thirdTrack.name()

If there are 10,000 tracks in your playlist, this will grab them all and will be noticeably slow. If you really only want the third track, just do:

  • tracks = playList.tracks
  • thirdTrack = tracks[3]
  • thirdTrack.name()

The help will often look something like this, from iTunes.play.help():

Terminology for play command Command: play(...) -- play the current track or the specified track or file.
[Reference] -- item to play
[once=Boolean] -- If true, play this track once and then stop.

The “Reference” is the object on which this method rests: you don’t have to provide it. The remaining options need to be specified by name. So you can call play as “object.play()”, as “object.play(once=True)’, or as “object.play(once=False)”.

Finally, some of the lists you get back start at 0 and some start at 1.

Command-line iTunes controller

This seemed perfect for implementing my iTunes command line. And, in fact, it was. But it took a lot of work to figure out everything I needed to know.

Before I get into how it works, here are a few examples of what it does:

itunes
SLOW RIDE (8:14)
Foghat (Fool For The City, 1975)
****

itunes --extended
SLOW RIDE (8:14)
Foghat (Fool For The City, 1975)
Genre: Hard Rock
Last Played: 2006-03-06 06:58:55
Playcount: 12
Remaining Time: 7:32
****

itunes --upcoming 5
3 - Tuesday Morning by Chris Reed and the Anime Raiders on Something Positive Thanks (4:41)
4 - Overture by Orchestra on George M! (3:35)
5 - Hindustan by Bing Crosby and Rosemary Clooney on fancy meeting you here (2:52)
6 - Take It Anyway You Want It by Pat Benatar on Precious Time (2:49)
7 - Hot And Bothered by Combustible Edison on The Impossible World (3:17)

302: itunes --find lola
Found 2 tracks in A-List
1199 - Whatever Lola Wants by Ella Fitzgerald on Ella Sings Broadway (3:15)
4802 - Whatever Lola Wants by Cashmere Jungle Lords on Southern Barber Supply (2:34)

Whenever the command results in a list, the number on the left is an index. I can play the Ella Fitzgerald tune above, for example, using “itunes --index 1199”.

How does it work?

I couldn’t figure out how to subclass a track, so I ended up writing basically a filter class to it, called “smarterTrack”.

[toggle code]

  • tell application "FileMaker Pro"
    • tell database "After Midnight"
      • show every record
    • end tell
  • end tell

Paste this into ASTranslate and it will become:

  • app(u'/Applications/Apps/FileMaker Pro 6 Folder/FileMaker Pro.app').databases['After Midnight'].records.show()

It’s still up to you to turn this into more readable Python, but that’s a much easier task than trying to guess at what appscript is expecting.

  • import appscript
  • fm = appscript.app(u'/Applications/Apps/FileMaker Pro 6 Folder/FileMaker Pro.app')
  • db = fm.databases['After Midnight']
  • records = db.records.show()

And finally, the better choice for opening FileMaker is probably to use the ID, in case you move it later:

  • fm = appscript.app(id="com.filemaker.filemakerpro")

If you’re using a more modern version of FileMaker Pro, you’ll also use a different ID.

With ASTranslate, as long as you can construct the “tell” block(s) in AppleScript, you can quickly get the necessary appscript calls.

December 25, 2006: Python command-line option parser

Just a note about the command-line parser in my iTunes python script. Back in Python 2.2 (Mac OS X 10.2) I needed a simpler and more versatile command-line parser than getopt, so I wrote commandline.py. Erik Osheim points out that I don’t need to use it any more: as of Python 2.3 there is a new command-line options parser called optparse that does everything I need, and it comes standard with Python 2.3 and greater.

It’s also easier to use and works better. Last weekend I took a close look at it and will be replacing commandline.py in my other scripts when I have time. Here’s a simple example of how it works, from a more recent script: