Samuel Williams Wednesday, 27 May 2009

I recently had to make a tool for resizing and compressing images. It was a complete nightmare. AppleScript has to be one of the worst development environments I've ever worked with.

One of the most fascinating things about the design is the concept of the tell application clause. I don't know exactly how it works, but depending on what bundle you are within, the same syntax/code can have different results - or even what appears to be the same result, but further on in the program it doesn't work as expected. The syntax is also overly verbose, as can be seen in the example code. There appear to be multiple ways of doing the same thing, but sometimes with subtle differences.

Another thing I found overly complex is accessing and manipulating file paths. In, say, Ruby, we have Pathname and String for representing paths. It is all fairly straight forward. In AppleScript, we have alias (not exactly paths - kind of like a hard link to a file), string, file, folder. You can't have a file, folder or alias to a file that does not exist yet. Also, you have POSIX paths ("/a/b/c"), and also Mac OS paths ("Volume:a:b:c"). The "Image Events" tool was very peculiar, it required an alias as created in "System Events", the ones returned by "Finder" did not appear to work correctly.

Another fun fact, there is no simple to use function to tell if an alias is a file or folder (as far as I can tell). The only way I could do this was to create a "disk item" and check whether the derived class was "folder" or "file".

There is also no public type system. You just get errors when executing code. The documentation is also fairly difficult to access - you have to load up specific "dictionaries" of documentation and while they are individually fairly well structured, it is hard to cross reference material.

This code lets you drop items onto the application icon. It then descends into any directories you dropped and finds all image files. These files are then copied to the named folder you specify on the desktop. Once they have been resized as necessary, they are then compressed using ditto.

AppleScript has a lot of interesting ideas in terms of syntax and making code readable. I think that the access it provides to other applications is fantastic when compared to other similar tools. However, it has a high cost of entry for someone used to shell scripting / programming as its programming constructs are not consistent. I might need to use AppleScript again, and I'm sure as I use it more I will understand its peculiarities, but for now, I'm happy to be done with it.

Source code

-- Released under the MIT License.
-- Copyright, 2009, by Samuel Williams.
-- Drop files on top of this application to produce a zip archive on the desktop containing compressed JPEGs at a maximum size of 800px.
on open some_items
	-- If we drop a single folder onto the icon, we pull the name from the folder name
	set target_name to "Images"
	if (count of some_items) = 1 then
		set first_item to get item 1 of some_items
		tell application "System Events"
			set target_item to disk item (first_item as string)
			if class of target_item is folder then
				set target_name to name of first_item
			end if
		end tell
	end if
	display dialog "Name of Archive" default answer target_name buttons {"OK"} default button 1
	set target_name to text returned of the result
	tell application "Finder"
		set desktop_path to folder "Desktop" of home
		make new folder at desktop_path with properties {name:target_name}
		set destination to (folder target_name of desktop_path) as alias
		set archive_path to (POSIX path of (desktop_path as text)) & target_name & ".zip"
	end tell
	repeat with this_item in some_items
		tell application "System Events"
			set source_item to disk item (this_item as string)
		end tell
		process_path(source_item, destination)
	end repeat
	set archive_command to "/usr/bin/ditto -c -k -rsrc " & quoted form of (POSIX path of destination) & " " & quoted form of archive_path
	do shell script archive_command
	tell application "Finder"
		delete folder target_name of desktop_path
		reveal file (target_name & ".zip") of desktop_path
	end tell
end open
to process_path(source, destination)
	-- Figure out if it is a file or folder, and recurse appropriatly
	tell application "System Events"
		set is_folder to class of source is folder
		if is_folder then
			set sub_items to items in source
		end if
	end tell
	if is_folder then
		repeat with this_item in sub_items
			process_path(this_item, destination)
		end repeat
		tell application "System Events"
			set source_alias to (path of source) as alias
		end tell
		resize_and_save(source_alias, destination)
	end if
end process_path
to resize_and_save(image_alias, destination_alias)
	tell application "Image Events"
		set opened_image to open image_alias
		if class of opened_image is image and not dimensions of opened_image = {} then
			set dim to dimensions of opened_image
			-- We only resize if the dimension is bigger than 800
			if (first item of dim > 800 or second item of dim > 800) then
				scale opened_image to size 800
			end if
			save opened_image in destination_alias as JPEG with compression level medium
			close opened_image
		end if
	end tell
end resize_and_save

This script, when files are dragged on top of the application icon, will resize them proportionally so the maximum dimension is 800px. It will then ask the user for a name and create an archive on the desktop containing all the images.

The compiled application is available.


Leave a comment

Please note, comments must be formatted using Markdown. Links can be enclosed in angle brackets, e.g. <>.