Spaces becomes usable in OS X 10.5.3

Spaces was one of the most anticipated features in Leopard, at least for Unix/X11 refugees like myself. X has had virtual desktops for decades, but users of “mainstream” desktop operating systems (i.e. Windows and Mac OS X) have had to rely on third-party utilities to get the same functionality.

In the case of OS X, Leopard was set to change that with Spaces. Unfortunately, the implementation was broken in such a way as to make it incredibly frustrating to use the way I’m used to using X11. I typically have Terminal and Safari (and often Emacs) windows open on multiple desktops. But on a desktop dedicated to a particular task, I want to be able to ⌘-⇥ (command-tab) between application windows on that desktop. Prior to 10.5.3, this would invariably do precisely the opposite of what I wanted, and flip to another desktop that had a window of that application open. This resulted in Spaces being about 5% as useful as X11 for serious keyboard-oriented work.

(For what it’s worth, this whole thing is mostly an issue because of the distinction OS X makes between apps and windows of apps—in X11, alt-tab usually cycles between all windows equally, regardless of what application they belong to. On OS X however, command-tab cycles between applications—⌘-` can be used to cycle between windows of an application.)

But good news! The recent 10.5.3 update to Mac OS X fixes it! Contrary to what Gruber says:

[Y]ou shouldn’t notice any changes, because the default behavior remains the same in 10.5.3

the default behaviour has changed: command-tabbing between applications now stays on the same desktop if the target application has a window there, and jumps to another desktop otherwise.

This is just about perfect. I actually like the jump-to-desktop behaviour for applications that aren’t on multiple desktops (e.g. iTunes), but now the default is to stay on-desktop for apps that are. (I still think I’d be slighly more comfortable if OS X behaved the same way as X11, and treated all windows as equal—but that could be Just What I’m Used To.)

Thanks Apple!

Using git on subversion projects

Despite all the noise lately about distributed version control systems, the chances are any given project you want to work on today will be using Subversion. But that’s OK, you can still get the benefit of all the advanced features of git by using it as a “front end” to subversion.

Before I get into the “how”, why would you want to do this?

The most obvious benefits are having a full local history, and cheap local branching. It’s trivial in git to create branches for features you’re working on, and then easily switch between them. Say you’re working on a feature for the next release, and an urgent bug for 1.0 comes in. Simply:

$ git commit -m "work in progress"
$ git checkout --track -b fix-urgent-bug-1234 release-1.0

...hack hack...

$ git commit -m "fixed bug #1234"
$ git checkout cool-feature-foo

and continue where you left off.

There’s also a bunch of other neat stuff in git that I miss whenever I have to use something else (keep in mind that I’m no svn guru, so there may be similar things in svn if you look hard enough. But I very much doubt they’re as fast). git grep for rapidly searching source trees. gitk for visualising branches and interactively searching for commit messages and changes. Local commits. Oh, and everything is much faster.

OK, on to the how.

We start by checking out the svn repo:

$ git svn clone -s http://svn.example.com/svn/cool-project

The -s switch means “standard layout”, i.e. the recommended subversion usage of trunk/branches/tags. If your project doesn’t follow this convention, you can specify the names of the subdirectories used:

$ git svn clone --trunk=MAIN --branches=branches --tags=releases \
    http://svn.example.com/svn/cool-project

There are lots of other options to clone that can help if you have a really non-standard repo to work with. Check the init command in man git-svn(1).

You should now have the HEAD of trunk in a directory called “cool-protect”. (You can specify a different target directory name by appending it to the git svn clone command.)

The full power of git is now at your command! You can grep the source tree:

$ git grep '^class Model('
django/db/models/base.py:class Model(object):
tests/modeltests/invalid_models/models.py:class Model(models.Model):

Find the git commit corresponding to a subversion revision:

$ git svn find-rev r1234
c5dfec042453672a27fd19ff81131edd01145584

$ git show c5dfec0
commit c5dfec042453672a27fd19ff81131edd01145584
Author: Michael Rowe <mrowe@mojain.com>
Date:   Sat Feb 16 10:14:57 2008 +1100
...

And interrogate the full history of the repo:

$ cd ~/src/django
$ git log '@{3 weeks ago}' -1
commit 696a3322d6709ebffcc436eb6188ea4d769ebfc5
Author: mtredinnick <mtredinnick@bcc190cf-cafb-0310-a4f2-bffc1f526a37>
Date:   Mon Feb 4 04:57:56 2008 +0000

    Fixed a simple TODO item in one error path of the "extends" tag.

In the time we’ve been playing with this, maybe some changes have been committed upstream. To make sure our local repository is up to date, we rebase:

$ git svn rebase

You could also just use git svn fetch to fetch the upstream changes into the repo without rebasing your working tree. In general, I would avoid this unless you know what you are doing, since it can make things complicated when you go to merge and push your changes upstream. If you are working on the main trunk of the svn repo, rebase is almost always what you want.

So now we have an up to date checkout, lets get to work! As you work, add files to git’s “index” and commit to the repo. Commits in git are fast, and should be used almost as frequently as saving a file in your editor. You can always consolidate these “micro-commits” into larger feature or bug fix commits later.

...hack hack...

$ git add src/module.py src/other.py
$ git commit -m "I did stuff"

and repeat.

Note for subversion users: you have to tell git about every file you change, even if it’s not a new file. Details of git usage are beyond the scope of this article (there are some excellent starting points), but be aware that you have to git add each file you want included in a commit.

(Note for lazy git users: This can be combined into a single command for existing files:

$ git commit -m "I did stuff" src/module.py src/other.py

but I tend to prefer the two-step approach for anything but the most trivial changes.)

As you work, you can periodically sync with the upstream subversion repo to get other people’s changes:

$ git svn rebase

This won’t work if you have any local uncommitted changes. However, you can “stash” them away temporarily (in git 1.5.3 and later):

$ git stash
$ git svn rebase
$ git stash apply

In any case, as mentioned above, you want to commit locally as often as possible.

When you have finished work on a feature or bug fix that you want to push back to the subversion repository, make sure all your changes are committed locally to git (git status), then review what you’ve done:

$ git log origin/trunk (by default, or whatever svn branch you're on)
$ git diff origin/trunk

Finally, when you are happy with the work you’ve done and are ready to push it up to subversion:

$ git svn dcommit

This will create individual svn check ins for each git commit since the last upstream revision. If you want to combine local commits into one large svn check in (e.g. because you followed my advice above and made frequent local commits), the interactive rebase command will help:

$ git rebase --interactive origin/trunk

Interactive rebase opens an editor with a list of all the commits since the revision you specify (remotes/trunk in our example).

pick d79a908 A small change to a file
pick c5dfec0 An unrelated change
pick db0346b Fix typo in hello

To combine the typo fix into the first commit, move its line directly below the line for the first commit and change “pick” to “squash”:

pick d79a908 A small change to a file
squash db0346b Fix typo in hello
pick c5dfec0 An unrelated change

The result will be two commits (d79a908 and c5dfec0), with d79a908 incorporating the changes from db0346b. You can do this for multiple consecutive lines if you want to combine many commits into one. See man git-rebase(1) for full details.

Now use git svn dcommit as above to push the revised commits upstream.

We’ve been working on a single branch so far, but one of the big benefits of using git is the cheap branching. Lets start work on a new experimental feature:

$ git checkout -b my-wacky-feature

The -b switch means create a new branch. Without that, git checkout switches to an existing branch.

...hack hack...

$ git add ...
$ git commit ...

At any time, we can commit locally and switch to another branch:

$ git checkout other-thing-to-work-on

...hack hack...

$ git add ...
$ git commit ...

then switch back and continue where we were:

$ git checkout my-wacky-feature

All of the commands we’ve discussed operate on the current branch (unless you specify otherwise). So you can grep for strings, get change logs and diffs and view visual history all in the context of the branch. You can also diff the current branch with another. To get a diff from release-1.0 to the current working tree (on branch fix-urgent-bug-1234):

$ git checkout fix-urgent-bug-1234
$ git diff release-1.0

Or to get diffs between arbitrary branches and revisions (without having to checkout either branch):

$ git diff release-1.0..my-wacky-feature

See man git-diff(1) for all the options to diff.

git svn dcommit will only push changes on the current branch up to the subversion repository, so you can clean up and consolidate your commits using rebase, then push them back to subversion when they’re ready.


I hope this quick introduction has whet your appetite for combining the power of git with the ubiquity of subversion. There is much more to git (we haven’t touched on merging at all), and once you’ve dipped your feet in, I recommend reading the intros and man pages at the git site.

Please let me know if you have any suggestions or notice any errors.

Aperture 2

I’m a little disappointed that Apple are charging AUD129 for the upgrade to Aperture 2. Sure, there are a bunch of new, and very attractive, features that would otherwise make paying for the upgrade acceptable, but it seems a bit rich given that the full retail price has dropped from USD300 to USD200. Effectively, people who bought 1.x are getting hit twice.

But I guess it doesn’t bother me enough to stop me buying the upgrade…

PHP namespace

This comment from the Drupal Theme developer’s guide is an example of why whatever your question, PHP is not the answer:

An important note- when developing a theme using any of the methods described here, you must be sure that the name of the theme is not the same as the name of any module being used on the site because the function names may collide and your site may no longer function correctly.

New home for a blog

My blog has moved to a dedicated new home: http://www.mikerowecode.com/

All appropriate redirects are in place, but please check your feed reader to be sure. I’ve done a far-reaching survey of a wide range of users and clients—ok, well, actually myself and one friend, both using NetNewsWire—and it seems that it works fine when the feed is accessed directly, but if you have it syncing via NewsGator it doesn’t correctly propagate the new feed URL. It does follow the redirect to get the feed content, but doesn’t to push the changed URL back to the client. I’d be interested to hear about experiences with other readers.

A word about what’s behind curtain

The new site is built from text files using the blosxom publishing system. The text files are formated using John Gruber’s Markdown, with punctuation fixed by his SmartyPants.

I use a number of plugins for blosxom to get things working the way I want. These include archives and recententries to provide the navigation options in the sidebar, entries_index to maintain article time stamps and atomfeed to produce, er, an atom feed. :)

Blosxom runs in “static” mode to generate the site locally, and then I rsync it to my web server, where it’s served as static HTML.

Why blosxom?

It probably seems like a strange choice, when there are so many “advanced” alternatives such as Drupal (which was my previous system), WordPress, MovableType, Blogger, etc., etc. But a couple of things convinced me that blosxom was the way to go.

First, my needs are minimal. I just want to publish the stuff I write with the minimum of fuss and overhead. I wanted a publishing system that would get out of the way.

Second, there is something very appealing about keeping things in plain text. I can write in emacs (which is of course the One True Editor), manage changes with git, search with grep (or spotlight). The directory layout is the same on my hard disk as on the public server. There’s no database to worry about backing up.

Finally, since I’m serving static HTML, in the (admittedly far-fetched) event that this site becomes wildly popular and sees huge amounts of traffic, scaling will be trivial. :)

Reviewboard git mirror

For some months now, I’ve been maintaining a git mirror of the Reviewboard project’s svn repository. The git-svn tool works really well for this, except for one small wrinkle: the reviewboard projects uses svn:external to include an external module, djblets, and git-svn provides no transparent way to support this.

For now, I manage this manually. When ever I notice an update to djblets (which are thankfully rather rare), I use the following process to merge the changes into a branch (with-djblets) in the git repo:

$ cd ~/src/djblets
$ git svn rebase
$ git log -1 | grep -v '^commit' > /tmp/djblets.log

Note: change “1” to whatever number of commits have happened in djblets since the last time I did this. The grep command removes the git-specific “commit” lines from the log, which won’t be interesting enough to include in the commit message below.

$ cd ~/src/reviewboard-with-djblets
$ git status # make sure working dir is clean
$ cp -rp ~/src/djblets/* reviewboards/djblets/

At this point, I do a git status and manual sanity check to make sure the changes I’m about to commit here match the incoming change to djblets.

$ git add <files that are changed/new>
$ git commit -F /tmp/djblets.log
$ git push public-repo with-djblets

Done! Simple, no? Well, no… This process has a number of problems, the main one of which is it’s manual, and I have to do it. I’m hoping that I’ll be able to bend git-submodule to my will enough to take care of this.

Job search update

It’s been a while coming, but here is a quick update on my job search:

Whether it was my letter to recruiters, or just dumb luck, I ended up finding and accepting a pretty good contract job back in November. A product company, smart people, great relaxed environment. More or less everything on my list. Even a kick-ass coffee machine in the office. As expected, it was a smaller “boutique” recruiter that came through.

I’ve had a happy and productive couple of months.

Then this week, the company was bought by Microsoft and my contract terminated early. *sigh* More job search news to come, I guess.

Multiple instances of the iiNet Usage Widget

There is a very handy usage widget for iiNet available at LemonJar. However, the way it stores its preferences for iiNet account and password means that you can’t run multiple instances of the widget to monitor multiple iiNet accounts.

This patch fixes it so you can:

--- MAIN.js.ORIG    2007-11-08 11:35:07.000000000 +1100
+++ MAIN.js 2007-11-08 11:33:59.000000000 +1100
@@ -144,14 +144,18 @@

 }

+function keyForUsername() { return widget.identifier + "-" + "userName"; }
+function keyForPassword() { return widget.identifier + "-" + "psword"; }
+function keyForAlertCol() { return widget.identifier + "-" + "alertColorOn"; }
+
 //Read in Username & Password Stored in OS .plist. Updates Global Variables.
 function readPrefs(){
    debug("Function: readPrefs() run.");

    if(window.widget) { 
-       var TMPuserName = widget.preferenceForKey("userName"); 
-       var TMPpsword = widget.preferenceForKey("psword"); 
-       var TMPalertColorOn = widget.preferenceForKey("alertColorOn"); 
+        var TMPuserName = widget.preferenceForKey(keyForUsername()); 
+       var TMPpsword = widget.preferenceForKey(keyForPassword()); 
+       var TMPalertColorOn = widget.preferenceForKey(keyForAlertCol()); 

        if ( TMPuserName && TMPuserName.length > 0) { 
            userName = TMPuserName;
@@ -199,10 +203,10 @@
    alertColorOn = document.getElementById("alertColorPref").checked;

    if(window.widget){  
-       widget.setPreferenceForKey(document.getElementById("userNamePref").value, "userName");
-       widget.setPreferenceForKey(rot13(document.getElementById("pswordPref").value), "psword");
-       widget.setPreferenceForKey(document.getElementById("alertColorPref").checked, "alertColorOn");
-   }   
+      widget.setPreferenceForKey(document.getElementById("userNamePref").value, keyForUsername());
+      widget.setPreferenceForKey(rot13(document.getElementById("pswordPref").value), keyForPassword());
+      widget.setPreferenceForKey(document.getElementById("alertColorPref").checked, keyForAlertCol());
+   }
 }

LemonJar also make widgets for other Australian ISPs. I suspect this patch would also work for those widgets (on the assumption that the code in MAIN.js is common), but I haven’t tested it.

For what it’s worth, I filed a bug in their issue tracker.

Another patch for pyblosxom entrycache - normalised keys

The entrycache plugin uses the absolute path of a file as the key for caching its date. This is problematic if the file is moved (e.g. your data dir is different locally to on your web server).

This patch normalises the key to remove the “datadir” component. It also cleans up how the cache is written to disk:

diff --git a/entrycache.py b/entrycache.py
index 0cc3196..b46f89d 100644
--- a/entrycache.py
+++ b/entrycache.py
@@ -52,19 +52,18 @@ def cb_filestat(args):
    request = args["request"]
    data = request.getData()
    cache = data["cache"]
-   if cache.has_key(args['filename']):
+   config = request.getConfiguration()
+   key = args['filename'].replace(config['datadir'], '')
+   if cache.has_key(key):
        mtime = []
        for i in args['mtime']:
            mtime.append(i)
-       mtime[8] = cache[args['filename']]
+       mtime[8] = cache[key]
        args['mtime'] = tuple(mtime)
    else:
-       cache[args['filename']] = args['mtime'][8]
+       cache[key] = args['mtime'][8]
        f = open(data['cachefile'],'w')
-       f.write("{\n")
-       f.write("\t'%s' : %i,\n" % (args['filename'], \
    args['mtime'][8]))
-       for i in cache:
-           f.write("\t'%s' : %i,\n" % (i, cache[i]))
-       f.write("}")
+       import pprint
+       pprint.pprint(cache, f)
        f.close()
    return args

I’ll get around to publishing my git repo of this soon.

Patch for pyblosxom entrycache plugin to make cache location configurable

The entrycache plugin for pyblosxom is really cool. I only wish I could configure the location of the file it uses to store its cached dates.

So here’s patch:

--- a/entrycache.py
+++ b/entrycache.py
@@ -21,24 +21,31 @@ __url__ = "http://joe.terrarum.net"

 import os.path

+def _get_cache_filename(args):
+   request = args["request"]
+   config = request.getConfiguration()
+        if config.has_key('entrycache_cachefile'):
+                return config['entrycache_cachefile']
+        else:
+                return os.path.join(config['datadir'],'.entrycache')
+
 def cb_start(args):
    t = { }
    request = args["request"]
-   config = request.getConfiguration()
    data = request.getData()
-   if os.path.isfile(os.path.join(config['datadir'],'.entrycache')):
-       data['cachefile'] = os.path.join(config['datadir'],'.entrycache')
-       f = file(os.path.join(config['datadir'],'.entrycache'))
+   if os.path.isfile(_get_cache_filename(args)):
+       data['cachefile'] = _get_cache_filename(args)
+       f = file(_get_cache_filename(args))
        t = eval(f.read())
        f.close()
         data['cache'] = t
    request.addData(data)

    if not data.has_key('cachefile'):
-       f = file(os.path.join(config['datadir'],'.entrycache'),'w')
+       f = file(_get_cache_filename(args),'w')
        f.write("{ }")
        f.close()
-       data['cachefile'] = os.path.join(config['datadir'],'.entrycache')
+       data['cachefile'] = _get_cache_filename(args)
        request.addData(data)

 def cb_filestat(args):

Then add a line like this to your pyblosxom config:

py["entrycache_cachefile"] = \
    "/Users/mrowe/Sites/blog/data/.entrycache"

Leopard breaking MacPorts git over ssh

As I have been twitting recently, git over ssh stopped working for me after the upgrade to Leopard:

$ git pull
percent_expand: NULL replacement
fatal: The remote end hung up unexpectedly
Cannot get the repository state from ssh://git.mojain.com/...

A quick google search quickly turned up the answer. The problem was not with git, but with ssh. Spefically, ssh from MacPorts. It’s worth noting that ssh in OS X 10.5 is not broken (which made my intial trouble-shooting harder, as ssh-ing from the command line worked just fine). But git in MacPorts is:

$ which ssh
/usr/bin/ssh
$ ssh git.mojain.com
Last login: Mon Oct 29 20:17:47 2007 from ...
$ ^D

$ /opt/local/bin/ssh git.mojain.com
percent_expand: NULL replacement

You can follow the google results above for the details, but essentially it seems that two things cause the git problem:

  1. Leopard changed some environment variables that caused the MacPorts version of git to get a NULL when it tried to determine what “identity” to use.

  2. git looks for ssh in the same directory as the git binary, causing it to find the MacPorts version before the “native” OS X version.

There are a number of ways to work around this problem (all found in the aforemetioned google results):

  1. Set GIT_SSH to the OS X version (/usr/bin/ssh). This works for git only of course.

  2. Rename your ssh key files so MacPorts ssh can find them (I didn’t try this).

  3. Tell ssh which key file to use by adding the following line to $HOME/.ssh/config (creating that file if it doesn’t exist):

    IdentityFile ~/.ssh/id_dsa

This last option is the one I chose, as it has the advantage of working for all versions and invocations of ssh, and is probably a good idea anyway. Presumably the MacPorts ssh package will be fixed at some point, but this is working for me now.

Making PHP work after Leopard upgrade

Sure enough, PHP didn’t work after upgrading my mac to Leopard. Easy to fix though:

$ diff -u /private/etc/apache2/httpd.conf.ORIG /private/etc/apache2/httpd.conf
--- /private/etc/apache2/httpd.conf.ORIG    2007-10-29 20:25:54.000000000 +1100
+++ /private/etc/apache2/httpd.conf 2007-10-29 20:26:08.000000000 +1100
@@ -111,7 +111,7 @@
 LoadModule alias_module libexec/apache2/mod_alias.so
 LoadModule rewrite_module libexec/apache2/mod_rewrite.so
 LoadModule bonjour_module     libexec/apache2/mod_bonjour.so
-#LoadModule php5_module        libexec/apache2/libphp5.so
+LoadModule php5_module        libexec/apache2/libphp5.so
 #LoadModule fastcgi_module     libexec/apache2/mod_fastcgi.so

and then

apachectl graceful

Perhaps surprisingly, MySQL (installed from the “official” package) continued to work without a hitch.

Making “personal web sharing” work after Leopard upgrade

My upgrade to Leopard has gone mostly well (if you don’t mind waiting half a day for spotlight to index everything, then another half a day for time machine to do its first back up). But tonight I tried to access my “personal website” via the local Apache server, and got a “Forbidden” message.

It turns out Leopard includes Apache 2.2 (up from the 1.3 in Tiger) and its configuration now lives in

/private/etc/apache2

(not /private/etc/httpd as in Tiger and earlier). However, the upgrade did not bring across my user configuration file (/private/etc/httpd/users/mrowe.conf).

The following commands fixed this for me:

cp /private/etc/httpd/users/mrowe.conf /private/etc/apache2/users/
echo 'Include /private/etc/apache2/users/*.conf' >> /private/etc/apache2/httpd.conf
apachectl graceful

(Obviously you would use your short username where I have “mrowe”.)

I haven’t tried PHP yet…

Update: it turns out adding that Include /private/etc/apache2/users/*.conf line to httpd.conf is not necessary. It is taken care of by the line:

Include /private/etc/apache2/extra/httpd-userdir.conf

earlier in httpd.conf that I hadn’t noticed. You still need to copy your user.conf from /private/etc/httpd/users to /private/etc/apache2/users.

Job search update

My job search experiment is sitting at about one and a half for four at the moment.

Of the four recruiters who received my letter, two were small independents and two were larger national companies.

The person I spoke to at one of the larger companies completely failed to get it, and asked the usual “so how many years of websphere experience do you have?” questions. *plonk*

I had a bit more luck with the other large shop, ending up speaking to their national manager, presumably because I was “too hard” for the front line folks. :) We had a promising conversation, but nothing concrete yet.

Of the two independents, one said he would “keep his eyes out”, which is actually a satisfactory result—a lot better than “no worries, I’ll try and push you in to whatever crap I have on the books right now”.

The other has come through with an interview for a reasonable-sounding job. The company appears to be a combination of in-house and customer-facing work. I don’t know much yet, but current employees rave about the place in their blogs.

More news as it happens!

Playing the job search game

I want a new job. And I want one that doesn’t suck. So doing the regular browse-the-job-boards-and-ring-recruiters dance didn’t appeal.

Instead, I sent this letter (lightly personalised) to a small number of the less sucky recruiters I know:

Dear [Recruiter],

I’d like to ask for your help in my search for the “perfect” development job.

As you can see from my resume at http://www.mojain.com/michael/resume, I’m currently employed as a Java developer (on a rolling contract), but am I looking for something better.

By better, I mean some fairly specific things:

I will be working at a product company. That is, a company that produces software as (one of) its primary activities. I am not interested in jobs in corporate IT shops or consulting companies.

I will be working with other enthusiastic developers who have a similar attention to detail and a commitment to doing things the right way. The organisation’s management will realise that a tightly constrained development environment is not usually the way to get the best out of skilled developers. And they’ll care about doing things the right way too.

I would prefer to work on back-end, infrastructure or framework software, but I have experience in front end web development too.

My perfect job would offer flexible work arrangements, ideally including the option of working at home at least some of the time. I’m not interested in a company that thinks the most important thing is that I’m sitting at a desk looking busy during “office hours”.

I know I haven’t said much about myself and what I offer here. I want to narrow down my search to jobs I’d actually consider taking before wasting everyone’s time talking about myself.

Thanks for listening, and I look forward to working with you on my search!

I’ll let you know what happens next… :)

Why EJBs are like lobsters

I should probably have included a link to this article with my email to recruiters…

Twitter and blogging

I was never exactly a blogging over-achiever, but I can’t help noticing that I’ve been even quieter here than usual, since discovering twitter. I guess being able to blast out anything I’m thinking in a sentence or two acts as a sort of pressure release, and the “head of steam” necessary to actually blog (that’s a verb now, right?) something is never able to build.

Must Try Harder. Thanks for the nudge, Daniel.

Oh, what am I doing right now? Consolidating dozens of email folders I thought I used to “organise” my email (i.e. spend too much time deciding where to file it and never look at it again) into a small number of actually-useful folders. Well, maybe-useful… we’ll see how it goes.

Setting iTunes metadata for downloaded TV shows

Below is a small piece of thoroughly untested AppleScript (well, it worked for me at least once) that will process files you might have downloaded from a torrent and attempt to parse out show, season and episode information from the file name.Below is a small piece of thoroughly untested AppleScript (well, it worked for me at least once) that will process files you might have downloaded from a torrent and attempt to parse out show, season and episode information from the file name. This relies on files being named in a regular way, but that’s been the case for everything I’ve downloaded recently.

Not that you’d illegally download torrents of course. Not if the major content producers weren’t such jackasses anyway.

My workflow for this is something like:

  1. find and download shows with Xtorrent

  2. convert to iTunes with Movie2iTunes (I’d love to know if there is a better way)

  3. go to the “Recently Added” smart playlist in iTunes and select all the newly-downloaded shows

  4. run this script

  5. grab your FrontRow remote and enjoy!

Opps, I left out “profit”! Feel free to send me stuff.

Here’s the script:

set AppleScript's text item delimiters to "."

tell application "iTunes"
    repeat with sel in selection

        -- try work out show, season and episode from file name (e.g. Californication.S01E04.HDTV.XViD-Caph.avi)

        set tokens to text items of (name of sel as text)

        set myShow to item 1 of tokens
        set myEpisodeId to item 2 of tokens
        set mySeason to text 2 through 3 of myEpisodeId as number
        set myEpisode to text 5 through 6 of myEpisodeId as number
        --display dialog myShow & " season " & mySeason & " episode " & myEpisode

        tell sel
            set video kind to TV show
            set album to myShow
            set year to "2007"
            set genre to "Drama"
            set show to myShow
            set season number to mySeason
            set episode number to myEpisode
            set episode ID to myEpisodeId
        end tell

    end repeat
end tell

I’m in America

I’m visiting family in the USA for Christmas. You can keep up with what we’re doing (well, at least photos) on my family blog.

I’ll be resuming my regular cracking pace of blogging in the new year… :-)

Adding captions in Aperture

I’ve just started using Apple’s Aperture to manage my photos (family and other), and I’m pretty pleased with it so far. One thing, however, that is harder than it needs to be is entering captions. As far as I can tell, the only way to do this in Aperture is to click on the photo in the browser, mouse over to the Information panel and click in the caption field (making sure you’ve selected IPTC fields to be displayed), type the caption and mouse back and click in the browser window so it has focus (otherwise any further keys you press will still be typing in the caption field). Then click on the next photo. Eeew.

This is bearable for one or two photos, but when you have a largish batch to do, it is very tedious. It’s also prone to error, such as when you start typing the caption without the field having focus, and Aperture gleefully trying to act on all your keypresses.

So I threw together a small AppleScript that provides a marginally friendlier interface. This is one of my first attempts at AppleScript, so I’d be happy to receive any feedback on better techniques.