One of the new ‘developer’ features (i.e. a new API) in Leopard is ScriptingBridge. It’s a significant improvement to the mechanics of driving one application from another using AppleScript/AppleEvents.It’s always been possible to use NSAppleScript in Cocoa to send AppleScript to another application basically using a simple string :
NSString *appleScriptSource = [NSString stringWithFormat:@"tell iTunes to add (POSIX file \"%@\")", moviePath];
NSAppleScript *aScript = [[NSAppleScript alloc] initWithSource:appleScriptSource];
NSAppleEventDescriptor *aDescriptor = [aScript executeAndReturnError:&anError];
Not too bad, and you do get the returned data and in theory you can use that in subsequent scripts to affect the added track (adding metadata, setting it’s kind etc). However it’s slow and still really rather clumsy, and if you want to do any real argument munging you may be forced to revert to NSAppleEvent directly which is really painful and exceedingly longwinded.ScriptingBridge in Leopard makes this much, much more straightforward and friendly. Using the command line sdef and sdp tools you can create an Objective-C 2.0 header file which contains all the scriptable classes and elements of an application. Then you can include the header file and ScriptingBridge framework and the above code becomes:
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
[iTunes setDelegate:self];
NSArray *filesArray = [NSArray arrayWithObject:[NSURL fileURLWithPath:moviePath]];
iTunesTrack *theTrack = [iTunes add:filesArray to:nil];
At first glance it looks like this is about the same code, but it executes much faster (no script parsing/compilation to AppleEvents). The returned object is a real Objective-C object so in the next line you can do :
theTrack.videoKind = iTunesEVdKTVShow;
theTrack.show = movieTitle;
Note – you can use Objective-C 2.0 style properties. Much neater than lines and lines of coded AppleScript. There is one catch – the header file is built from parsing the scripting definitions in the application (the sdef). These can still tend to be rather opaque – and in particular rather ‘type free’. AppleScript generally is a very weak typing language, and if you’re used to strong typing (C like) languages it can be a bit of a mind bending experience. In the iTunes case I struggled for a couple of days with the add: to: message – the generated header says :
- (iTunesTrack *) add:(NSArray *)x to:(SBObject *)to; // add one or more files to a playlist
There’s no indication of type for the contents of the array. Experience with ScriptEditor (and the initial NSAppleScript solution) suggested I should be able to just use an NSString in the array with the POSIX style path. However that failed with a ‘not found’ error when the message was sent. I initially thought it was a problem with the to argument needing to be a valid playlist and I spent quite some time convincing myself that I had a valid playlist argument (basically iterating things in the iTunesApplication object to get down to a playlist). Finally I went back to ScriptEditor and launched it from Terminal with the AEDebug=1 and AEDebugSends=1 environment variable set. This shows on stdout a debug of the AppleEvents that ScriptEditor sends. Looking at the log I could see that ScriptEditor was converting my POSIX path into an ‘furl’ (File URL) ! The lightbulb came on and I adjusted my code to do likewise with an NSURL and everything ‘just worked’ as I’d hoped it would. It is odd however – the definition in the scripting def file actually refers to an ‘alias’ an old world MacOS 7 concept that’s pretty much fallen out of use. I expected the path string to be converted correctly, after all I’d already proven that other AppleEvents in other applications (Sketch2 in Leopard is a good test case) that take alias get converted, however it appears as though there’s something more specific in the iTunes case that needs the file URL.
Posted under Development, Leopard
This post was written by awk on October 26, 2007