Apple's iPhoto is a photo browser/cataloguer [L1 ]. It's not as widespread as iTunes, since it's only for the Mac and is part of their "iLife" suite.
Wanting to get the XML-based catalog data into a Metakit datafile, I came up with a Tcl script to do the work.
First a usage example:
$ ls -l AlbumData.xml -rw-r--r-- 1 jcw jcw 15241634 Jun 20 00:20 AlbumData.xml $ time iphoto2mk.tcl iphoto.db AlbumData.xml iphoto.db (4786706 bytes): 33 albums 240 rolls 16 keywords 16776 images real 0m33.225s user 0m32.412s sys 0m0.424s
So while it's not very fast, it does get the job done and produced a useful dataset to play with.
The code is self-contained and uses a variation of TAX: A Tiny API for XML to do the parsing. Which is why the process takes a fair amount of memory and time to complete.
#!/usr/bin/env tclkit85 # Copyright (c) 2007 Jean-Claude Wippler # http://www.opensource.org/licenses/mit-license.php # Convert an iPhoto XML catalog to a Metakit datafile # # Usage: tclkit85 iphoto.tcl outfile ?infile? # # Will use iPhoto's default catalog if no input file is specified. # Requires Tcl 8.5 due to the use of dict and lassign. # # jcw, 2007-06-20 package require Tcl 8.5 package require Mk4tcl # TAX is a tiny SAX-like parser for XML, not perfect but good enough here # Adapted from https://wiki.tcl-lang.org/14534 by Eric Kemp-Benedict proc tax {cmd xml {top docstart}} { regsub -all {<(/?)([^\s/>]+)\s*([^/>]*)(/?)>} \ [string map {\{ &ob; \} &cb;} $xml] "\};tax@ {\\2} {\\1} {\\4} {\\3} \{" xml eval "tax@ {$top} {} {} {} {$xml}; tax@ {$top} / {} {} {}" } proc tax@ {e a b p t} { set m {&ob; \{ &cb; \} < < > > " \\\" & & ' ' = " "} if {$b ne ""} { set u $t; set t "" } uplevel {$cmd} [list $e$a [string map $m $p] $t] if {$b ne ""} { uplevel {$cmd} [list $e/ $b $u] } } namespace eval props { namespace eval v { set out ""; set keyed 0 } proc key {p t} { if {!$v::keyed} { error "keyed? $t" } set v::key $t } proc string {p t} { tag $t } proc integer {p t} { tag $t } proc real {p t} { tag $t } proc true {p t} { tag 1 } proc false {p t} { tag 0 } proc dict {p t} { enter 1 } proc dict/ {p t} { leave } proc array {p t} { enter 0 } proc array/ {p t} { leave } namespace export * proc tag {t} { if {$v::keyed} { append v::out " " [list $v::key $t] } else { append v::out " " [list $t] } } proc enter {k} { if {$v::keyed} { append v::out " " [list $v::key] " \{" } else { append v::out " \{" } lappend v::stack $v::keyed set v::keyed $k } proc leave {} { append v::out " \}" set v::keyed [lindex $v::stack end] set v::stack [lrange $v::stack 0 end-1] } proc ? {args} {} namespace ensemble create \ -unknown {apply {{args} { return "::props::? $args" }}} } proc get {vname {default ""}} { upvar $vname v if {![info exists v] || $v eq ""} { return $default } return $v } proc load2mk {data} { mk::view layout db.albums { id:I name keys master:I count:I play repeat rate:I titles:I playlist tdir:I tname tspeed:F } mk::view layout db.rolls { id:I name parent:I keys type count:I } mk::view layout db.keywords { id:I name } mk::view layout db.images { id:I media caption comment aspect:F rating:I roll:I date:D mdate:D mmdate:D ipath opath tpath } foreach {id info} [dict get $data {List of Albums}] { dict with info { mk::row append db.albums \ id ${AlbumId} \ name ${AlbumName} \ keys ${KeyList} \ master:I [get Master 0] \ count:I ${PhotoCount} \ play ${PlayMusic} \ repeat ${RepeatSlideShow} \ rate ${SecondsPerSlide} \ titles ${SlideShowUseTitles} \ playlist [get PlaylistName] \ tdir:I ${TransitionDirection} \ tname ${TransitionName} \ tspeed ${TransitionSpeed} } } foreach {id info} [dict get $data {List of Rolls}] { dict with info { mk::row append db.rolls \ id ${AlbumId} \ name ${AlbumName} \ parent ${Parent} \ keys ${KeyList} \ type ${Album Type} \ count ${PhotoCount} } } foreach {id info} [dict get $data {List of Keywords}] { mk::row append db.keywords id $id name $info } foreach {id info} [dict get $data {Master Image List}] { dict with info { mk::row append db.images id $id \ media ${MediaType} \ caption ${Caption} \ comment ${Comment} \ aspect ${Aspect Ratio} \ rating ${Rating} \ roll ${Roll} \ date ${DateAsTimerInterval} \ mdate ${ModDateAsTimerInterval} \ mmdate ${MetaModDateAsTimerInterval} \ ipath ${ImagePath} \ opath [get OriginalPath] \ tpath ${ThumbPath} } } } lassign $argv outfile infile if {$outfile eq ""} { puts stderr "Usage: $argv0 outfile ?infile" exit 1 } set fd [open [get infile "~/Pictures/iPhoto Library/AlbumData.xml"]] set data [read $fd] close $fd tax ::props $data set data [lindex $::props::v::out 0] #puts [dict keys $data] file delete $outfile mk::file open db $outfile load2mk $data mk::file commit db puts "$outfile ([file size $outfile] bytes):" foreach x {albums rolls keywords images} { puts " [mk::view size db.$x] $x" }
Dates are not converted - they are stored as the same doubles present in the input file for now.