« Share, sneak peekThose shady atheists »

appscript: Python in Applescript territory

Appscript is a technology that bridges Applescript’s best feature with Python, Ruby, and Objective-C. It brings Apple Events to these languages and allows them to interact with applications similar to Applescript. There’s been a bit of press on MacDevCenter about using Appscript in Ruby. That’s all well and good but I don’t particularly care for Ruby. I’ve wanted to see some tutorial about using appscript with Python. I’ve also wanted to see how to interact with everyone’s favorite scriptable application, iTunes. I haven’t seen either so I spent part of today working something up.

Appscript is interesting and somewhat exciting. Its biggest hassle is the marriage between two different language styles. Applescript tries to be verbose and English-like. Python, Ruby, and Objective-C all have to do some peculiar things to approximate this readability. They don’t mesh particularly well, although appscript does a reasonably admirable job. If you are already an applescripter, you have a hurdle to get around. The different syntax makes you think in a different way than you normally would. Of course, if you are used to “normal” languages, you also have to reorient yourself to think a bit like an applescripter.

I wrote a quick little script that renames tracks for a particular artist to that it follows normal title format considering common prepositions and whatnot. The script and some comments are in the extended entry.

from appscript import *

That’s the first line. I should really put a shebang line in, but this is the first semantically interesting line. I had a bit of trouble getting appscript installed (I have Python 2.5 and 2.3 installed and sometimes they don’t play nice with each other) but most people shouldn’t have a problem.

Now, all of the examples show how to target an application (app('iTunes')) so I’m skipping a bit of the basic stuff.

library = app('iTunes').playlists()[0]
tracks = library.tracks[its.artist == artistName].get()

Here, I’m assuming that the Library is the first playlist. I don’t think that’s an unreasonable assumption but I could be wrong. Someone let me know in the comments. Here’s an interesting bit. The .get appears to be rather optional. I could have re-written the first line to say “app('iTunes').playlists.get()[0]” and the second to read “...[its.artist == artist]()”. I like that option for some reason.

You can also delay the get until you need it. I could do “gettracks = library.tracks[its.artist == artist]”. This would give me the accessor but not the value. Later, I’d call “gettracks()” to access the value.

So, that’s how accessing stuff works. The other point is interest is the whole “its.artist == artist” part. This is basically appscript’s version of Applescript’s whose condition. And it is spiffy. its is a reserved word for appscript. Its doc string is, unfortunately, empty. However, it appears to only be used in the above context. In that context, its refers to each track in the library. I’m asking only for the tracks of a particular artist. Note that I’m not getting the value of the artist accessor (it is its.artist, not its.artist()).

This whole replacement for the whose clause only shows how to get an artist of a particular name. It could be more generic than that. I want every track whose artist contains a particular string or, perhaps, every track whose name begins with a certain letter. This is all possible. Here are a few examples of what can go between the brackets (“[]”):

There’s also isin and isnotin but I didn’t play around with them enough to figure out how they worked. I also couldn’t figure out how to do complex statements (a particular artist and a particular rating).

Some more code:

exclude = ("to", "in", "of", "for", "on", "with", "as", "by", "at", "from", "are", "is", "were", "was", "the", "an", "a")
for track in tracks:
    n = track.name()
    fixed = ''
    words = n.split(' ')
    for i in range(len(words)):
        word = words[i]
        if (i == 0 or not word in exclude):
            fixed += word.capitalize()
        else:
            fixed += word.lower()
        fixed += ' '
    if not n == fixed[:-1]:
        print "Setting '%s' to '%s'" % (n, fixed[:-1])
        track.name.set(fixed[:-1])

There isn’t much other than basic Python code here. Basically, it loops through every word in a song name (track.name(), note that we’re accessing the value and could have used track.name.get() instead if we wanted). If it’s the first word or not in the exclude list, we capitalize it. Otherwise, the word is in lowercase. If the word has been changed, we change the track listing using the set command.

Here’s my script in its entirety:

from appscript import *

def fixName(artist):
    """Attempts to update the title of all the files of a particular artist to proper capitalization considering common prepositions and forms of 'to be.'"""
    library = app('iTunes').playlists()[0]
    tracks = library.tracks[its.artist == artist].get()
    exclude = ["to", "in", "of", "for", "on", "with", "as", "by", "at", "from", "are", "is", "were", "was", "the", "an", "a"]
    for track in tracks:
        n = track.name()
        fixed = ''
        words = n.split(' ')
        for i in range(len(words)):
            word = words[i]
            if (i == 0 or not word in exclude):
                fixed += word.capitalize()
            else:
                fixed += word.lower()
            fixed += ' '
        if not n == fixed[:-1]:
            print "Setting '%s' to '%s'" % (n, fixed[:-1])
            track.name.set(fixed[:-1])

if __name__ == '__main__':
    import sys
    try:
        fixName(sys.argv[1])
    except Exception, e:
        fixName('Wolfmother')

2 comments on “appscript: Python in Applescript territory”

scott212:

July 22nd, 2007 at 3:07 pm

this is the article I’ve been looking for!
So my big question is how did you figure out what the methods or structures are for a given application?

Grayson:

July 24th, 2007 at 8:11 am

There are basic rules (such as the whose clause and get syntax) that are just a part of appscript so it’s simply a matter of getting familiar with it. For specific applications, their applescript dictionaries are useful. If you are familiar with Applescript, you can drag and drop applications onto Script Editor to open their dictionaries. appscript follows some general rules when translating from Applescript into Python so you should be able to reasonably guess the appscript equivalent to Applescript code if you know the conventions. appscript should include an app called ASTranslate (check your Applications folder) that attempts to turn Applescript code into appscript/Python code. That helped me learn many of the conventions.

Alternatively, and better still, ASDictionary should have been installed in your Applications folder when you installed appscript. ASDictionary is a lot like Script Editor’s dictionary viewer except that it translates dictionaries into a variety of formats. Simply drop a scriptable application onto it and choose the option for Python html pages. It should spit out an html file that describes the app’s scripting support in appscript/Python terms. Very, very handy.

Leave a Reply