Somewhere around the middle of this year I decided that I just had too many photos for my preferred photo management software to handle in its own library. Trusting my photos to the backup process was flakey, and unreliable. I would lose backup archives, or they would take several hours to verify before the backup would even start. It just wasn’t working. So I cam up with a few shell scripts, later upgraded to python scripts, that help me organize photos after I’ve imported them into my archive drives.

First off, I created an Automator action that does a Get Selected Items > Run Script, the script language is set to /usr/bin/python, and the input passed as arguments, with the following code:

import sys, os, traceback


scripts = os.path.expanduser("~/scripts/")
os.chdir(scripts)
try:
    import finder

    for f in sys.argv[1:]:
        finder.update_dir_date(f)

except:
    traceback.print_exc(file=sys.stdout)

Then, in my ~/scripts directory I have a file called finder.py:

#import sys, os, os.path, time, re, string, EXIF
import sys, os, os.path, time, re, string

USE_EXIF_DATE = False

# our format for times YYYY-MM-DD
def time_string(t):
    return time.strftime("%Y-%m-%d", time.localtime(t))

#exif or sys time
def get_file_time(path):
    if USE_EXIF_DATE:
        try:
            #parse the EXIF data
            dateTag = 'EXIF DateTimeOriginal'
            file = open(path, 'rb')
            tags = EXIF.process_file(file, stop_tag=dateTag)

            # reformat the time and convert to seconds
            if tags.has_key(dateTag):
                ts = time.strptime(tags[dateTag].printable, "%Y:%m:%d %H:%M:%S")
                return time.mktime(ts)

        except:
            print "error parsing EXIF on " + path #raise

    return os.path.getmtime(path)

# return the oldest and newest times for the items in the directory
def date_range(dir):
    oldest, newest = -1, 0
    files = os.listdir(dir)
    for f in files:
        path = dir + "/" + f

        # recurse down
        if os.path.isdir(path):
            times = date_range(path)
            if len(times):
                if (times[0] <= oldest) | (oldest == -1):
                    oldest = times[0]
                if times[1] > newest:
                    newest = times[1]
            continue
        else:
            ctime = get_file_time(path)
            if ctime > newest:
                newest = ctime
            if (ctime <= oldest) | (oldest == -1):
                oldest = ctime

    # if we found something...
    if oldest == -1:
        return []
    else:
        return [oldest, newest]

# find the beginning of the string that matches NNNN-NN-NN or NNNN_NN_NN
def time_prefix(dir):
    m = re.match(r"([0-9]*)[-_]([0-9]*)[-_]([0-9]*) ", dir)
    if m != None:
        return m.group(0)
    else:
        return None

# Find the new name for the passed in directory
def dir_name(dir):
    base = os.path.basename(dir)
    times = date_range(dir)
    if len(times):
        # trim the old prefix if it exists
        old_prefix = time_prefix(base)
        if old_prefix != None:
            base = base[len(old_prefix):]

        t = times[0];
        prefix = time_string(t) + " "
        if prefix != base[:len(prefix)]:
            # update the mod/create dates for the folder
            os.utime(dir, (t,t))
            return prefix + base
    return base

# won't move a file over an existing file
def move_file( src, dst ):
    if os.path.exists(dst) == False:
        os.renames(src,dst)

# update the date string prefix for a dir based upon on its oldest file
def update_dir_date(dir):
    dir = os.path.abspath(dir)
    if os.path.isdir(dir):
        name = dir_name(dir)
        new_path = os.path.dirname(dir) + "/" + name
        if new_path != dir:
            print "renaming " + os.path.basename(dir) + " to " + name,
            move_file(dir, new_path)

#returns true if a file is a movie file
def is_movie_file(file):
    if os.path.isfile(file):
        ext = os.path.splitext(file)[1].lower()
        if ext in [".mp4", ".mov", ".avi", ".qt"]:
            return True
    return False

def move_movie_filesto_own_dir(dir):
    dir = os.path.abspath(dir)
    postfix = " Movies"
    if dir[-len(postfix):] == postfix:
        return

    if os.path.isdir(dir):
        movie_dir = dir + postfix
        files = os.listdir(dir)
        for f in files:
            src = os.path.join(dir, f)
            dst = os.path.join(movie_dir, f)
            if is_movie_file(src):
                move_file(src, dst)

#move files to a new directoy
def move_files_to_new_dir(files):
    if len(files) < 1:
        return
    files.sort()

    # go up two dirs
    parent_dir = os.path.dirname(files[0])
    parent_dir = os.path.dirname(parent_dir)

    # get the dest dir name
    first_file = os.path.splitext(os.path.basename(files[0]))[0]
    last_file = os.path.splitext(os.path.basename(files[-1]))[0]
    if first_file == last_file:
        dest_path = os.path.join(parent_dir, first_file)
    else:
        dest_path = os.path.join(parent_dir, first_file + "-" + last_file)

    #check to see if it already exists
    if (os.path.isdir(dest_path)==True) | (os.path.exists(dest_path)==False):
        for f in files:
            dst = os.path.join(dest_path, os.path.basename(f))
            move_file(f, dst)
            #print f + " to " + dst

        #and update the dir date when finished
        update_dir_date(dest_path)