Ruby: Apple Aperture API
PLEASE NOTE: requires the following modules "active_record", "rbosa" and "hpricot" and my own Imageshack API to work.
USAGE:
You can include/require this as a module in your own scripts, or as I do, use it directly from irb in the Terminal. Documentation is provided within the script itself, but essentially I use it to get these things done:
With Aperture launched, select the pictures of your choice (one or multiples makes no diff), then entering the following inside irb results in:
1. Aperture.reveal is essentially "Reveal in Finder"
2. Aperture.paths returns the Unix paths to the locations of the pics.
3. Aperture.open opens the pictures with an external default app (usually Preview).
4. If you provide the script with Photoshop's path, then:
Aperture.edit will open the original file directly in Photoshop without creating a separate version inside Aperture.
5. Aperture.ids will give you an array of the unique ID asssigned by Aperture to each pic within the ZRKVERSION table. What this is for is up to you to find out.
6. NEW: Imageshack.us upload/backup
Aperture.upload will upload the pic(s) to Imageshack.us and return the direct link/URL of the pic.
Upon first launch, a new metadata field is created inside Aperture's metadata panel called "Imageshack URL" (if you don't see it, inside Aperture, Ctrl-D --> Others). the uploaded pic's URL will be stored inside this field automatically.
The API directly interrogates Aperture's SQLite DB. Who knows, could be a nice place to start from to building other stuff.
require 'rubygems' require 'active_record' require 'rbosa' require 'hpricot' require 'open3' require 'beckett/imageshack' # --- GLOBALS --- # HOME = ENV['HOME'] PATH_TO_PHOTOSHOP = "/Applications/Adobe\\ Photoshop\\ CS3/Adobe\\ Photoshop\\ CS3.app/Contents/MacOS/Adobe\\ Photoshop\\ CS3" Aperture = OSA.app('Aperture') # --- CONNECT TO CURRENT LIBRARY --- # class LibraryConnect def initialize findLibrary connect unless $PATH_TO_DB.nil? end protected def findLibrary aperture_plist = HOME + '/Library/Preferences/com.apple.Aperture.plist' plistdump = HOME + '/Library/Caches/' + 'plistdump.tmp' plutil_error = Open3.popen3("plutil -convert xml1 -o #{plistdump} #{aperture_plist}") { |stdin,stdout,stderr| stderr.read } if plutil_error == "" doc = Hpricot(File.open(plistdump)) ; %x( rm #{plistdump} ) relative_lib_path = (doc/:key).select { |key| key.inner_text == 'LibraryPath' }[0].next_sibling.inner_text $PATH_TO_LIB = relative_lib_path =~ /^~/ ? relative_lib_path.gsub(/^~/, HOME) : relative_lib_path $PATH_TO_DB = "#{$PATH_TO_LIB}/Aperture.aplib/Library.apdb" puts "Current library --> " + $PATH_TO_LIB else $PATH_TO_DB = nil puts "Cannot find library. Goodbye." end end def connect ActiveRecord::Base.establish_connection( :adapter => "sqlite3", :dbfile => $PATH_TO_DB ) end end # --- LIBRARY CLASSES-SQL TABLES MAPPINGS --- # class Pictures < ActiveRecord::Base # Contains the majority of non-metadata info on a picture set_table_name "ZRKVERSION" set_primary_key "ZUUID" end class Projects < ActiveRecord::Base # Every pic only belongs to one project set_table_name "ZRKFOLDER" set_primary_key "ZUUID" end class Masters < ActiveRecord::Base # Info on master images set_table_name "ZRKMASTER" set_primary_key "Z_PK" end class Files < ActiveRecord::Base # Info on the actual image file set_table_name "ZRKFILE" set_primary_key "Z_PK" end class Metadata < ActiveRecord::Base # Metadata information tagged to every picture in library set_table_name "ZRKSEARCHABLEPROPERTY" set_primary_key "Z_PK" end class Albums < ActiveRecord::Base # All albums within library set_table_name "ZRKPERSISTENTALBUM" set_primary_key "Z_PK" end class AlbumVersions < ActiveRecord::Base # Foreign key that links a picture's Z_PK (from ZRKVERSION) to an set_table_name "Z_11VERSIONS" # album's Z_PK in ZRKPERSISTENTALBUM end class Properties < ActiveRecord::Base # Metadata categories set_table_name "ZRKPROPERTYIDENTIFIER" set_primary_key "Z_PK" end class Picture # Custom class that contains all relevant info on a picture attr_reader :id, :project_id, :master_id, :file_id, :version_id, :width, :height, :filesize, :name, :moddate def initialize(pic_id) # Query ZRKVERSION @id = pic_id pic = Pictures.find(pic_id) @project_id = pic.ZPROJECTUUID @master_id = pic.ZMASTER @file_id = pic.ZFILE @version_id = pic.Z_PK # Query ZRKFILE file = Files.find(@file_id) @width = pic.ZPIXELWIDTH @height = pic.ZPIXELHEIGHT @filesize = file.ZFILESIZE @name = file.ZNAME @moddate = realtime(file.ZFILEMODIFICATIONDATE_before_type_cast) end def filepath path = $PATH_TO_LIB + '/' + self.projectPath + '/' + self.importGroup + '/' + self.folderName + '/' + self.filename end def nixpath self.filepath.gsub(/ /,'\ ') end def open `open #{self.nixpath}` end def edit if PATH_TO_PHOTOSHOP != "" `open -a #{$PATH_TO_PHOTOSHOP} #{self.nixpath}` else `open #{self.nixpath}` end end def reveal puts nixpath `open #{File.dirname(self.nixpath)}` end def shack_uri pic_meta = Metadata.find( :first, :conditions => { :ZVERSION => @version_id, :ZPROPERTYIDENTIFIER => $SHACKURLPROP } ) unless pic_meta == nil pic_meta.ZPROPERTYSPECIFICSTRING else return 'Does not exist.' end end def storeURI(url) records = Metadata.find( :all, :conditions => { :ZVERSION => @version_id, :ZPROPERTYIDENTIFIER => $SHACKURLPROP } ) if records.empty? == true new_uri = Metadata.create( :Z_ENT => 13, :Z_OPT => 1, :ZPROPERTYSPECIFICNUMBER => nil, :ZPROPERTYSPECIFICSTRING => url, :ZVALUETYPE => 3, :ZVERSION => @version_id, :ZPROPERTYIDENTIFIER => $SHACKURLPROP ) new_uri.Z_PK else records.each { |entry| Metadata.destroy(entry.Z_PK) } self.storeURI(url) end end def realtime(sqtime) t = Time.at(sqtime.to_i + 978307200) # => # Derives from: Time.utc(2001,"jan",1,0,0,0).to_i Time.utc(t.year,t.month,t.day,t.hour,t.min) end protected def projectPath Projects.find(@project_id).ZLIBRARYRELATIVEPATH end def importGroup Masters.find(@master_id).ZIMPORTGROUP end def folderName Masters.find(@master_id).ZNAME end def filename Files.find(@file_id).ZNAME end end class Album def initialize(zpk) @album = Albums.find(zpk) end def add_count(n=0) @album.update_attributes( :ZDATELASTSAVEDINDATABASE => (Time.now.to_f + 978307200.0), :ZMETADATACHANGEDATE => (Time.now.to_f + 978307200.0), :ZVERSIONCOUNT => @album.ZVERSIONCOUNT + n, :ZCREATEDATE => @album.ZCREATEDATE_before_type_cast.to_f ) end end # --- INTERACTING WITH APERTURE FROM IRB --- # # All operations are performed recursively on image(s) within the array returned by Aperture class OSA::Aperture::Application def new_prop(fieldName) Properties.create( :Z_ENT => 12, :Z_OPT => 1, :ZPROPERTYKEY => fieldName, :ZPROPERTYTYPE => 7) return Properties.find_by_ZPROPERTYKEY(fieldName).Z_PK end def ids # Returns the IDs used to query the ZRKVERSION table self.selection.collect { |pic| pic.id2 } end def paths # Returns the filepaths of selected images self.ids.collect { |id| Picture.new(id).filepath } end def reveal # 'Reveal in Finder' selected images self.ids.each { |id| Picture.new(id).reveal } end def open # Opens selected images in Preview self.ids.each { |id| Picture.new(id).open } end def edit # Opens the original file in Photoshop CS3 (if path provided or default) self.ids.each { |id| Picture.new(id).edit } end def upload(reveal=false) # Uploads the image to Imageshack and returns the direct link to the pic, if called with true, then opens it in browser after upload self.ids.each do |id| pic = Picture.new(id) pic_mirror = ShackMirror.new(pic.filepath) pic.storeURI(pic_mirror.url) `open #{pic_mirror.url}` if reveal puts pic_mirror.url end end def print(name) self.ids.each { |id| puts Picture.new(id).send(name) } end end # --- INITIALIZE --- # LibraryConnect.new $SHACKURLPROP = Properties.find_by_ZPROPERTYKEY("Imageshack URL") == nil ? Aperture.new_prop("Imageshack URL") : Properties.find_by_ZPROPERTYKEY("Imageshack URL").Z_PK