Submit Hint Search The Forums LinksStatsPollsHeadlinesRSS
14,000 hints and counting!

Export iPhoto events into appropriate directory structure Apps
A common gripe with iPhoto is that it cannot create directory structures for events, albums, etc. during export. However, if you export the photos with meaningful filenames, you can easily post-process them and create the intended directory structure. This can be done by selecting all your events and doing a Batch Change to set the Title to Event Name. Then export your photos using the Title as the File Name. Copy the following code into a pure text editor, save it somewhere on your computer, and make it executable (chmod a+x filename in Terminal).
#	First, in iPhoto select all events and do Photos > Batch Change
#	Set Title to Event Name
#	With all events highlighted, export events a folder somewhere, for filename use title
#	Photos will be exported with filename of "event - number.jpg" (ex: Animals - 0004.jpg)
#	This script will create the appropriate directory structure based on those filenames
#	Set the photo_dir variable below to the full path of the directory

photo_dir=/Users/Thom/Desktop/iPhotoExport

cd $photo_dir

for filename in *
do

#takes the string preceding the dash, trims leading and trailing whitespace, then removes all non web-friendly characters
dir=`echo $filename | cut -d- -f1 | sed 's/^[ ]//' | sed 's/[ ]$//' | sed 's/[^A-Za-z0-9 _]//g'`

#takes the string following the dash, trims leading and trailing whitespace
file=`echo $filename | cut -d- -f2 | sed 's/^[ ]//' | sed 's/[ ]$//'`

#if directory doesn't yet exist, create it
if ! test -d "${dir}"
then
mkdir "${dir}"
fi

#move file to the appropriate directory and rename it to numeric filename
mv "${filename}" "${dir}/${file}"

done
Set the photo_dir variable to the path of your iPhoto Export. Executing the script will create the directories and move the photos into them. I use this to batch export all of my photos for easy upload to my web server.

[robg adds: I haven't tested this one.]
    •    
  • Currently 4.00 / 5
  You rated: 4 / 5 (8 votes cast)
 
[61,893 views]  

Export iPhoto events into appropriate directory structure | 12 comments | Create New Account
Click here to return to the 'Export iPhoto events into appropriate directory structure' hint
The following comments are owned by whoever posted them. This site is not responsible for what they say.
u 4got...
Authored by: airdrummer on Nov 11, '08 05:51:14PM

the shebang: 1st line should be:

#!/bin/bash

not strictly necessary if ur shell is bash, but just good form;-)



[ Reply to This | # ]
Export iPhoto events into appropriate directory structure
Authored by: csholmq on Jul 14, '09 07:16:01AM

I had an issue with events containing a dash (-). Alot of photos were turned into a single file. I tweaked the code a bit and replaced the two parse code rows with;

dir=`echo $filename | sed 's/ - .*//' | sed 's/^[ ]//' | sed 's/[ ]$//' | sed 's/[^A-Za-z0-9 _-]//g'`
file=`echo $filename | sed 's/.* - //' | sed 's/^[ ]//' | sed 's/[ ]$//'`

This way dash is supported and it's easier to tweak the script to use another delimiter than ' - '.



[ Reply to This | # ]
Export iPhoto events into appropriate directory structure
Authored by: ricelid on Aug 26, '09 08:44:28AM

This ALMOST did exactly what I want, but now my photos have non-meaningful file names. Is there a way to process the files and rename them to have the title from the iphoto metadata

Thanks.



[ Reply to This | # ]
Export iPhoto events into appropriate directory structure
Authored by: dchilders on Dec 29, '09 11:12:00AM
This has really bugged me for a while and I really don't like the solution where all my files get renamed. I've written a python script that so far is doing what I want it. I'm parsing the AlbumData.xml file and walking through the list of all of the events/rolls. The roll lists all the imageId's that belong to it. From there I can get the original and modified image path. Then I look for a corresponding file in my target directory and compare the timestamps to see if I need to recopy the file (incase I've made changes to it in iPhoto). Anyway, I've just started using it and it appears to be working ok.

I'm running:

  • Snow Leopard 10.6.2
  • python 2.6.1
  • iPhoto 8.1.1
You'll want to update the following lines to be correct on your system:
albumDataXml="/Users/YOURUSERNAME/Pictures/iPhoto Library/AlbumData.xml"
targetDir="/Volumes/share/pictures"

Here's the script:


from xml.dom.minidom import parse, parseString, Node
import os, time, stat, shutil, sys

def findChildElementsByName(parent, name):
    result = []
    for child in parent.childNodes:
        if child.nodeName == name:
            result.append(child)
    return result

def getElementText(element):
    if element is None: return None
    if len(element.childNodes) == 0: return None
    else: return element.childNodes[0].nodeValue

def getValueElementForKey(parent, keyName):
    for key in findChildElementsByName(parent, "key"):
        if getElementText(key) == keyName:
            sib = key.nextSibling
            while(sib is not None and sib.nodeType != Node.ELEMENT_NODE):
                sib = sib.nextSibling
            return sib

albumDataXml="/Users/YOURUSERNAME/Pictures/iPhoto Library/AlbumData.xml"
targetDir="/Volumes/share/pictures"
copyImg=True #set to false to run with out copying files or creating directories

print "Parsing AlbumData.xml"
albumDataDom = parse(albumDataXml)
topElement = albumDataDom.documentElement
topMostDict = topElement.getElementsByTagName('dict')[0]
listOfRollsArray = getValueElementForKey(topMostDict, "List of Rolls")
masterImageListDict = getValueElementForKey(topMostDict, "Master Image List")

#walk through all the rolls (events)
for rollDict in findChildElementsByName(listOfRollsArray, 'dict'):
    rollName = getElementText(getValueElementForKey(rollDict, "RollName"))
    print "\n\nProcessing Roll: %s" % (rollName)

    #walk through all the images in this roll/event
    imageIdArray = getValueElementForKey(rollDict, "KeyList")
    for imageIdElement in findChildElementsByName(imageIdArray, 'string'):
        imageId = getElementText(imageIdElement)
        imageDict = getValueElementForKey(masterImageListDict, imageId)
        modifiedFilePath = getElementText(getValueElementForKey(imageDict, "ImagePath"))
        originalFilePath = getElementText(getValueElementForKey(imageDict, "OriginalPath"))

        sourceImageFilePath = modifiedFilePath

        modifiedStat = os.stat(sourceImageFilePath)
        basename = os.path.basename(sourceImageFilePath)
        year = str(time.gmtime(modifiedStat[stat.ST_CTIME])[0])
        targetFileDir = targetDir + "/" + year + "/" + rollName

        if not os.path.exists(targetFileDir):
            print "Directory did not exist - Creating: %s" % targetFileDir
            if copyImg:
                os.makedirs(targetFileDir)

        targetFilePath = targetFileDir + "/" + basename
        iPhotoFileIsNewer = False

        if os.path.exists(targetFilePath):
            targetStat = os.stat(targetFilePath)

            #print "modified: %d %d" % (modifiedStat[stat.ST_MTIME], modifiedStat[stat.ST_SIZE])
            #print "target  : %d %d" % (targetStat[stat.ST_MTIME], targetStat[stat.ST_SIZE])

            #why oh why is modified time not getting copied over exactly the same?
            if abs(targetStat[stat.ST_MTIME] - modifiedStat[stat.ST_MTIME]) > 10 or targetStat[stat.ST_SIZE] != modifiedStat[stat.ST_SIZE]:
                iPhotoFileIsNewer = True
        else:
            iPhotoFileIsNewer = True

        if iPhotoFileIsNewer:
            msg = "copy from:%s to:%s" % (sourceImageFilePath, targetFilePath)
            if copyImg:
                print msg
                shutil.copy2(sourceImageFilePath, targetFilePath)
            else:
                print "test - %s" % (msg)
        else:
            sys.stdout.write(".")
            sys.stdout.flush()

albumDataDom.unlink()



[ Reply to This | # ]
Export iPhoto events into appropriate directory structure
Authored by: dchilders on Dec 29, '09 11:23:59AM
This has really bugged me for a while and I really don't like the solution where all my files get renamed. I've written a python script that so far is doing what I want it. I'm parsing the AlbumData.xml file and walking through the list of all of the events/rolls. The roll lists all the imageId's that belong to it. From there I can get the original and modified image path. Then I look for a corresponding file in my target directory and compare the timestamps to see if I need to recopy the file (incase I've made changes to it in iPhoto). Anyway, I've just started using it and it appears to be working ok.

I'm running:

  • Snow Leopard 10.6.2
  • python 2.6.1
  • iPhoto 8.1.1
You'll want to update the following lines to be correct on your system:
albumDataXml="/Users/YOURUSERNAME/Pictures/iPhoto Library/AlbumData.xml"
targetDir="/Volumes/share/pictures"

Here's the script:


from xml.dom.minidom import parse, parseString, Node
import os, time, stat, shutil, sys

def findChildElementsByName(parent, name):
    result = []
    for child in parent.childNodes:
        if child.nodeName == name:
            result.append(child)
    return result

def getElementText(element):
    if element is None: return None
    if len(element.childNodes) == 0: return None
    else: return element.childNodes[0].nodeValue

def getValueElementForKey(parent, keyName):
    for key in findChildElementsByName(parent, "key"):
        if getElementText(key) == keyName:
            sib = key.nextSibling
            while(sib is not None and sib.nodeType != Node.ELEMENT_NODE):
                sib = sib.nextSibling
            return sib

albumDataXml="/Users/YOURUSERNAME/Pictures/iPhoto Library/AlbumData.xml"
targetDir="/Volumes/share/pictures"
copyImg=True #set to false to run with out copying files or creating directories

print "Parsing AlbumData.xml"
albumDataDom = parse(albumDataXml)
topElement = albumDataDom.documentElement
topMostDict = topElement.getElementsByTagName('dict')[0]
listOfRollsArray = getValueElementForKey(topMostDict, "List of Rolls")
masterImageListDict = getValueElementForKey(topMostDict, "Master Image List")

#walk through all the rolls (events)
for rollDict in findChildElementsByName(listOfRollsArray, 'dict'):
    rollName = getElementText(getValueElementForKey(rollDict, "RollName"))
    print "\n\nProcessing Roll: %s" % (rollName)

    #walk through all the images in this roll/event
    imageIdArray = getValueElementForKey(rollDict, "KeyList")
    for imageIdElement in findChildElementsByName(imageIdArray, 'string'):
        imageId = getElementText(imageIdElement)
        imageDict = getValueElementForKey(masterImageListDict, imageId)
        modifiedFilePath = getElementText(getValueElementForKey(imageDict, "ImagePath"))
        originalFilePath = getElementText(getValueElementForKey(imageDict, "OriginalPath"))

        sourceImageFilePath = modifiedFilePath

        modifiedStat = os.stat(sourceImageFilePath)
        basename = os.path.basename(sourceImageFilePath)
        year = str(time.gmtime(modifiedStat[stat.ST_CTIME])[0])
        targetFileDir = targetDir + "/" + year + "/" + rollName

        if not os.path.exists(targetFileDir):
            print "Directory did not exist - Creating: %s" % targetFileDir
            if copyImg:
                os.makedirs(targetFileDir)

        targetFilePath = targetFileDir + "/" + basename
        iPhotoFileIsNewer = False

        if os.path.exists(targetFilePath):
            targetStat = os.stat(targetFilePath)

            #print "modified: %d %d" % (modifiedStat[stat.ST_MTIME], modifiedStat[stat.ST_SIZE])
            #print "target  : %d %d" % (targetStat[stat.ST_MTIME], targetStat[stat.ST_SIZE])

            #why oh why is modified time not getting copied over exactly the same?
            if abs(targetStat[stat.ST_MTIME] - modifiedStat[stat.ST_MTIME]) > 10 or targetStat[stat.ST_SIZE] != modifiedStat[stat.ST_SIZE]:
                iPhotoFileIsNewer = True
        else:
            iPhotoFileIsNewer = True

        if iPhotoFileIsNewer:
            msg = "copy from:%s to:%s" % (sourceImageFilePath, targetFilePath)
            if copyImg:
                print msg
                shutil.copy2(sourceImageFilePath, targetFilePath)
            else:
                print "test - %s" % (msg)
        else:
            sys.stdout.write(".")
            sys.stdout.flush()

albumDataDom.unlink()



[ Reply to This | # ]
Export iPhoto events into appropriate directory structure
Authored by: t3hite on Feb 21, '10 04:59:14AM

I ran the script and it has done pretty much what I hoped for, copied all my iPhoto events into external directories with matching names. I was surprised by one thing. I modified the script to copy into a /Volumes/Photo_disk/Python_export folder. It created a folder inside that named "2010" and all of my new folders were created inside that folder.

Did I miss something when I modified the script or is this behavior expected in a way I don't understan.

Otherwise it's a fine tool for getting images out of iPhoto in to the "open". Thanks for posting it.

Thomas



[ Reply to This | # ]
Export iPhoto events into appropriate directory structure
Authored by: gboudrea on Apr 13, '10 05:57:23PM

I modified dchilders' script above to:

  1. Allow you to either use rolls or albums names to create directories where your pictures will be saved; change the useEvents value from True to False to use albums names.
  2. Remove the year in the export path; it didn't work very well...
  3. Add an option to only export specific albums/rolls; either by looking for specific text at the beginning of the name, or anywhere in the name. Uncomment (remove the # character) the lines you want to use, if any, and change Something to whatever you want to look for.

Big cheer to dchilders for the nice script.

Note that if you have a big library, the script can take quite a while to do it's thing... On my 70GB library, just the Parsing AlbumData.xml part took 45m-1h. Be patient!

from xml.dom.minidom import parse, parseString, Node
import os, time, stat, shutil, sys

def findChildElementsByName(parent, name):
    result = []
    for child in parent.childNodes:
        if child.nodeName == name:
            result.append(child)
    return result

def getElementText(element):
    if element is None: return None
    if len(element.childNodes) == 0: return None
    else: return element.childNodes[0].nodeValue

def getValueElementForKey(parent, keyName):
    for key in findChildElementsByName(parent, "key"):
        if getElementText(key) == keyName:
            sib = key.nextSibling
            while(sib is not None and sib.nodeType != Node.ELEMENT_NODE):
                sib = sib.nextSibling
            return sib

albumDataXml="/Volumes/Photos/iPhoto Archive/AlbumData.xml"
targetDir="/Users/gb/Downloads/iPhoto Export"
copyImg=True #set to false to run with out copying files or creating directories
useEvents=True #set to False to use Albums instead of Events

print "Parsing AlbumData.xml"
albumDataDom = parse(albumDataXml)
topElement = albumDataDom.documentElement
topMostDict = topElement.getElementsByTagName('dict')[0]
listOfRollsArray = getValueElementForKey(topMostDict, "List of Rolls")
listOfAlbumsArray = getValueElementForKey(topMostDict, "List of Albums")
masterImageListDict = getValueElementForKey(topMostDict, "Master Image List")

#walk through all the rolls (events) / albums
if useEvents:
    listOfSomethingArray = listOfRollsArray
else:
    listOfSomethingArray = listOfAlbumsArray

for folderDict in findChildElementsByName(listOfSomethingArray, 'dict'):
    if useEvents:
        folderName = getElementText(getValueElementForKey(folderDict, "RollName"))
        print "\n\nProcessing Roll: %s" % (folderName)
    else:
        folderName = getElementText(getValueElementForKey(folderDict, "AlbumName"))
        if folderName == 'Photos':
            continue
        # Uncomment the following 3 lines to only export rolls/albums that start with "Something"
        #if folderName.find('Something') != 0:
            #print "\nSkipping Album: %s" % (folderName)
            #continue
        # Uncomment the following 3 lines to only export rolls/albums that containt with "Something"
        #if folderName.find('Something') == -1:
            #print "\nSkipping Album: %s" % (folderName)
            #continue
        print "\n\nProcessing Album: %s" % (folderName)

    #walk through all the images in this roll/event/album
    imageIdArray = getValueElementForKey(folderDict, "KeyList")
    for imageIdElement in findChildElementsByName(imageIdArray, 'string'):
        imageId = getElementText(imageIdElement)
        imageDict = getValueElementForKey(masterImageListDict, imageId)
        modifiedFilePath = getElementText(getValueElementForKey(imageDict, "ImagePath"))
        originalFilePath = getElementText(getValueElementForKey(imageDict, "OriginalPath"))

        sourceImageFilePath = modifiedFilePath

        modifiedStat = os.stat(sourceImageFilePath)
        basename = os.path.basename(sourceImageFilePath)
        year = str(time.gmtime(modifiedStat[stat.ST_CTIME])[0])
        #targetFileDir = targetDir + "/" + year + "/" + folderName
        targetFileDir = targetDir + "/" + folderName

        if not os.path.exists(targetFileDir):
            print "Directory did not exist - Creating: %s" % targetFileDir
            if copyImg:
                os.makedirs(targetFileDir)

        targetFilePath = targetFileDir + "/" + basename
        iPhotoFileIsNewer = False

        if os.path.exists(targetFilePath):
            targetStat = os.stat(targetFilePath)

            #print "modified: %d %d" % (modifiedStat[stat.ST_MTIME], modifiedStat[stat.ST_SIZE])
            #print "target  : %d %d" % (targetStat[stat.ST_MTIME], targetStat[stat.ST_SIZE])

            #why oh why is modified time not getting copied over exactly the same?
            if abs(targetStat[stat.ST_MTIME] - modifiedStat[stat.ST_MTIME]) > 10 or targetStat[stat.ST_SIZE] != modifiedStat[stat.ST_SIZE]:
                iPhotoFileIsNewer = True
        else:
            iPhotoFileIsNewer = True

        if iPhotoFileIsNewer:
            msg = "copy from:%s to:%s" % (sourceImageFilePath, targetFilePath)
            if copyImg:
                print msg
                shutil.copy2(sourceImageFilePath, targetFilePath)
            else:
                print "test - %s" % (msg)
        else:
            sys.stdout.write(".")
            sys.stdout.flush()

albumDataDom.unlink()


[ Reply to This | # ]
Export iPhoto events into appropriate directory structure
Authored by: bmorearty on Jul 07, '10 11:14:48PM

This Python script by dchilders is great. It does almost exactly what I needed.

I modified gboudrea's latest version of the Python script to add back the year (which he removed because it didn't work very well before). I calculate the year based on the event date (when exporting events) rather than the filestamp. The event date is more accurate. When exporting albums the date is not used since the XML doesn't give a date for an album.

Instead of posting the source in this forum I have put it in github. Feel free to fork it and make more improvements. The code is here:

http://github.com/BMorearty/exportiphoto/



[ Reply to This | # ]
Export iPhoto events into appropriate directory structure
Authored by: erictabellion on Oct 09, '10 11:29:38PM

Thanks for putting this script together, I will give it a try.

One question I have though is: your script seems to be copying each image from the "modified" folder from inside the iphoto library. I am wondering if this is always equivalent to doing an export of the "Current" photos from iphoto's export dialog ?

With iphoto's nondestructive editing, I wonder if all operations _always_ trigger an actual copy of the original photo to be placed in the "modified" folder. Could it be that simple color correction operations are handled in real time from the original image (iphoto's marketing claims to save disk space by doing that) ? In that case your script could be copying an image from the iphoto library that doesn't correspond to the version you would expect.

I would hate to lose a lot of color correction and cropping work.
Thanks for even reading this far.



[ Reply to This | # ]
Export iPhoto events into appropriate directory structure
Authored by: jgenuard on Dec 17, '11 08:23:17AM

I found this script very helpful. However I would like the event folders created in the format "yyyy-mm-dd Eventname" instead of just "Eventname", where "yyyy-mm-dd" represents the most recent modified date of the photos in that Event. Ths format would allow the event folders to be sorted by date, which is helpful in finding photos and also when creating other backups (e.g. backup to DVD). Would it be possible to provide a modified script that does this?



[ Reply to This | # ]
Export iPhoto events into appropriate directory structure
Authored by: danis on May 15, '12 10:11:00PM

Yes dates would help tremendously!



[ Reply to This | # ]
Export iPhoto events into appropriate directory structure
Authored by: belet on Feb 09, '13 12:41:16PM

You are great! It works perfectly fine...
Does not work with Python 2.7.xx, but with 3.3.xx.

Thank you guys!



[ Reply to This | # ]