Python and Applescript in OS-X

This last weekend I wrote some small Python programs for geo- and time-tagging scans of my analog photos. It is typical of recent trends in programming that I spent more time figuring out how to interface with OS-X programs from within Python than typing code.

Applescript is the way to go when you need to talk to other programs within OS-X. It is an ugly and verbose programming language —or maybe it's just me, and my lack of familiarity with it— so I have done my best to keep it to a minimum.

Ask Google Earth the latitude and longitude

Google Earth's Applescript support includes the ability to tell your program the geographical coordinates at the center of its screen. This is how you do it (with the Applescript more or less borrowed from Geotagger):

import commands

def earth_coords():
    cmd = """arch -i386 osascript -e 'tell application "Google Earth"
                       set viewInf to GetViewInfo
                       set coords to {latitude of viewInf, longitude of viewInf}
              end tell
              return coords'"""
    return [float(c) for c in commands.getoutput(cmd).split(", ")]

if __name__ == '__main__':
    print earth_coords()

Google Earth also has a SetViewInfo command that allows you to center it on a pair of coordinates.

The arg -i386 forces osascript to run in 32 bit mode, which avoids clashing with 32-bit scripting additions (Adobe's in my case).

What document is Preview showing?

This one requires you to enable scripting support in Preview, which is disabled by default (go figure).

Open a terminal window (in Applications -> Utilities) and type:

sudo defaults write /Applications/ NSAppleScriptEnabled -bool YES

Enter your password when it prompts for it, and you are all set.

Now the following function will return the full path of the document showing now:

import commands

def in_preview():
    cmd = """arch -i386 osascript -e 'tell application "Preview"
                set file_name to path of document 1
              end tell
              return file_name'"""
    return commands.getoutput(cmd)

if __name__ == '__main__':
    print in_preview()

Close Preview documents

This is even simpler, but can be counter-intuitive. When you ask Preview to close the document it will close all the documents in the active Preview window (which were loaded at a single Open event).

import os

def close_preview_doc():
    cmd = """arch -i386 osascript -e 'tell application "Preview"
                  close document 1
             end tell'"""
    return os.system(cmd)

if __name__ == '__main__':
    print close_preview_doc()

Query the date in a dialog window

This is convenient when you need to get user input and you want your script to be able to run as an app. What you do is you use the Finder's Applescript capabilities, like this:

import subprocess
from datetime import datetime

def run_applescript(script, *args):
    p = subprocess.Popen(['arch', '-i386', 'osascript', '-e', script] +
                         [unicode(arg).encode('utf8') for arg in args],
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    err = p.wait()
    if err:
        raise RuntimeError(err,[:-1].decode('utf8'))

def query_string(query, default=''):
    script = """on run {querystr, defstr}
                tell application "Finder"
                end tell
                tell application "Finder"
                     display dialog querystr default answer defstr buttons {"None", "Continue"} default button 2
                end tell
                if button returned of the result is equal to "Continue" then
                   return text returned of the result
                   return ""
                end if
                end run"""
    return run_applescript(script, query, default)

def parse_date(dt):
    if dt and isinstance(dt, basestring):
        date, time = dt.split(' ')
        if '-' in date:
            y, M, d = date.split('-')
        elif ':' in date:             # as in the exif datetime format
            y, M, d = date.split(':')
        h, m, s = time.split(':')
        dt = datetime(*[int(i) for i in (y, M, d, h, m, s)])
    return dt

def query_date(default=''):
    if not default:
        default = datetime.utcnow().replace(microsecond=0)
    return parse_date(query_string("enter date (yyyy-mm-dd hh:mm:ss)", default))

if __name__ == '__main__':
    print query_date()

This example is more complex than the previous ones. The code, courtesy of HAS, shows the right way to send arguments to your Applescript. For anything more complex than this you should consider appscript (thanks to the commenters, both here and at comp.lang.python, that pointed me to it).

Juan Reyero Barcelona, 2009-12-08

blog comments powered by Disqus