Managing Photos with Python and the Finder
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)