Trying to follow Buckminster Fuller's example in life, one step at a time to bring you brain scrapings for a new millennium.
This site uses cookies which allow us to remember you, and to understand how you use our site so we can improve it. You can accept or decline our use of cookies now. To understand more about cookies, and to change your cookie settings, please see our Cookie Policy. Read about what is done with the data we gather in our Privacy Policy.
One of my TaskFactory procrastination tasks over the last few years involved digitising my small pool of CDs and vinyl and pulling the resulting tracks into iTunes. To reduce the total time cost, part of this involved knocking up a quick and dirty tool to convert tracks with metadata based on naming conventions and directory structure, originally written in C# using the iTunes COM API available from the Apple Developer site. In another burst of procrastination I thought I'd try and craft a version using Objective-C... the original took a couple of hours to research and put together... should be easier with Objective-C right?
No, It Is Actually A Lot More Involved
After a brief spot of research, it appears that iTunes is almost fully controllable using AppleScript. Writing any kind of involved application using a scripting language when you have access to a compiled language with a rich IDE to generate code and debug doesn't float my boat. Using the AppleScript Editor to program with is like attempting to escape captivity from a fortune cookie factory by replacing a fair number of said fortunes with pleas for help and hoping that the general populace can separate them from the vague clairvoyance otherwise contained. Whilst possible, there are easier ways to get things done.
Rather than use AppleScripts directly, any scriptable application can have its interface translated into a header allowing Objective-C to message and control it. In the handful of applications tested a few generate uncompilable rubbish leaving a number, including the iTunes header, programmatically correct (-ish, there are a few issues I've outlined below.)
Apple have provided the scripting definition file tool sdef to dump the iTunes AppleScript API to XML. The output can be piped to sdp, the scripting definition processor, which will transform a scripting definition file into an Objective-C header file and use a tag such as iTunes to prefix any types contained therein. The header can be generated as part of a pre-build step using the command line below:
The header file gives us the ability, through the Apple Scripting Bridge, to pass events to and from iTunes, allowing us to create a main function similar to that shown below:
By exercising the Scripting Bridge API we can return an iTunesApplication interface which gives us direct access to iTunes without needing to know what events to pass through the Apple eventing framework.
The code below allows us to parse the URLs of the leaf directories in a structure and add them to worklists for later use if they reference sound or image files. Any sound files will be converted and the last image file found in the leaf directory will be used as album cover artwork:
Thus far putting the script together was relatively painless. This changed during the generation of the function below which allowed us to convert a track using the default import properties currently in use in iTunes:
...there were a few oddities which turned what should have been a smooth conversion from C# to Objective-C into something a bit more scruffy:
The iTunesApp convert method actually returns NSArray* rather than iTunesTrack* as stated in the interface. Its a gotcha and caused me to spend some time digging through the interface generation code to see whether there was something wrong with the header creation steps before realising that it was a problem somewhere in the Scripting Bridge and out of my control.
The NSArray* actually contains iTunesFileTrack* rather than iTunesTrack* objects.
There is nothing to link against to give us the types that underpin the iTunes Scripting Bridge interfaces. This makes class method usage impossible. For example a simple statement such as [addedTracks isKindOfClass:[iTunesFileTrack class]] which should return True/False cannot be used as the iTunesFileTrack has no concrete type and cannot be linked.
After converting and adding the tracks to iTunes we need to import any relevant cover art. This can be accomplished using something that looks similar to the code below:
There is no mechanism for adding artwork to an iTunesFile that does not already have artwork which proved to be a problem for the newly imported audio files used in this project. After a lot of research and various dead-end prototypes, the only mechanism with the required functionality exercised the eventing framework directly. This resulted in the code shown above which created a script referenced in an NSAppleScript* object. Something to take note is that AppleScript paths are POSIX compliant and the directory separators are colons rather than the slash separators used for standard NSURL paths.
Next...
It took over twice as long to create this tool than the original C# version after fixing various inconsistent types in the autogenerated header file and working around a lack of functionality for importing artwork through the iTunesApplication interface. After my brief procrastination experience with the Scripting Bridge I will be sticking to C# for similar tasks, the COM APIs are more fully featured as far as I can see.
Next we'll be looking at more Test Driven Development and some C++ hints and tips.