Skip to content

Instantly share code, notes, and snippets.

@slindner05
Forked from cliss/organize-photos.py
Last active November 8, 2019 19:43
Show Gist options
  • Save slindner05/377e787a88c38df5ab59bd23952cd402 to your computer and use it in GitHub Desktop.
Save slindner05/377e787a88c38df5ab59bd23952cd402 to your computer and use it in GitHub Desktop.
Photo management script. This script will copy photos from "~/Pictures/iPhone Incoming" into a tree the script creates, with folders representing month and years, and photo names timestamped.Completely based on the work of the amazing Dr. Drang; see here: http://www.leancrew.com/all-this/2013/10/photo-management-via-the-finder/You can see more a…
#!/usr/bin/python
import sys
import os
import subprocess
import shlex
import os.path
from datetime import datetime
from dateutil import tz
from dateutil.parser import parse
import argparse
######################## Functions #########################
fileTypeGroupings = {'photos': ['jpg', 'jpeg', 'jpe', 'png', 'bmp', 'raw'],
'movies': ['.3g2','.3gp','.asf','.asx','.avi','.flv','.m4v','.mov','.mp4','.mpg',
'.rm','.srt','.swf','.vob','.wmv','.aepx','.ale','.avp','.avs','.bdm',
'.bik','.bin','.bsf','.camproj','.cpi','.dash','.divx','.dmsm','.dream',
'.dvdmedia','.dvr-ms','.dzm','.dzp','.edl','.f4v','.fbr','.fcproject',
'.hdmov','.imovieproj','.ism','.ismv','.m2p','.mkv','.mod','.moi',
'.mpeg','.mts','.mxf','.ogv','.otrkey','.pds','.prproj','.psh','.r3d',
'.rcproject','.rmvb','.scm','.smil','.snagproj','.sqz','.stx','.swi','.tix',
'.trp','.ts','.veg','.vf','.vro','.webm','.wlmp','.wtv','.xvid','.yuv'],
'audio': ['mp3', 'wav']}
inverted = {}
for group in fileTypeGroupings:
for extension in fileTypeGroupings[group]:
if inverted.setdefault(extension, group) != group:
raise Exception('duplicate shit for %s' % extension)
def fileDate(f):
"Return the date/time on which the given file was created"
try:
# cDate = subprocess.check_output(['sips', '-g', 'creation', f])
# cDate = cDate.split('\n')[1].lstrip().split(': ')[1]
# cDate = subprocess.check_output(['mdls', f, '|', 'grep', 'kMDItemFSCreationDate'])
# proc1 = subprocess.Popen(['mdls', '-name', 'kMDItemContentCreationDate', f], stdout=subprocess.PIPE,stderr=subprocess.PIPE)
proc1 = subprocess.Popen(['exiftool', '-CreateDate', f], stdout=subprocess.PIPE,stderr=subprocess.PIPE)
#
# proc2 = subprocess.Popen(['grep','kMDItemFSCreationDate'],stdin=proc1.stdout,
# stdout=subprocess.PIPE,stderr=subprocess.PIPE)
# proc1.stdout.close()
out,err = proc1.communicate()
print '"%s"' % out
cDate = out.split('\n')[0].lstrip().split(': ')[1]
theDate = datetime.strptime(cDate, "%Y:%m:%d %H:%M:%S") #parse(cDate).astimezone(tz.tzlocal())
except Exception as e:
# fall back to sips
print 'failed to use exif tool... falling back to sips'
try:
cDate = subprocess.check_output(['sips', '-g', 'creation', f])
cDate = cDate.split('\n')[1].lstrip().split(': ')[1]
theDate = datetime.strptime(cDate, "%Y:%m:%d %H:%M:%S")
except Exception as ee:
# worst case use mdls
print 'failed to use sips... falling back to mdls'
try:
proc1 = subprocess.Popen(['mdls', '-name', 'kMDItemContentCreationDate', f], stdout=subprocess.PIPE,stderr=subprocess.PIPE)
out,err = proc1.communicate()
print '"%s"' % out
cDate = out.split('\n')[0].lstrip().split('= ')[1]
theDate = parse(cDate).astimezone(tz.tzlocal())
except Exception as eee:
print 'failed to use mdls... raising exception'
# fallback to modified time???
# return datetime.fromtimestamp(os.path.getmtime(f))
raise
return theDate
def filesHaveDifferentMd5s(file, duplicate):
md5orig = getMd5(file)
md5dup = getMd5(duplicate)
return md5orig != md5dup
md5cache = {}
def getMd5(file):
if not file in md5cache:
proc1 = subprocess.Popen(['md5', file], stdout=subprocess.PIPE,stderr=subprocess.PIPE)
out,err = proc1.communicate()
md5 = out.split('\n')[0].lstrip().split('= ')[1]
md5cache[file] = md5
return md5cache[file]
def make_parser():
""" Creates an ArgumentParser to parse the command line options. """
parser = argparse.ArgumentParser(description='Rename photos to names based on when they were taken.')
parser.add_argument('--srcdir', help='source directory.', default=os.environ['HOME'] + '/Pictures/iPhone Incoming')
parser.add_argument('--destdir', help='destination directory.', default=os.environ['HOME'] + '/Pictures/iPhone')
parser.add_argument('--format', help='The format for the new file names', default='%Y-%m-%d %H-%M-%S')
return parser
###################### Main program ########################
parser = make_parser()
arguments = parser.parse_args()
sourceDir = arguments.srcdir
destDir = arguments.destdir
errorDir = destDir + '/Unsorted/'
fmt = arguments.format
# Where the files are and where they're going.
#sourceDir = os.environ['HOME'] + '/Desktop/synology/Unsorted'
#sourceDir = '/Volumes/media/Pictures/'
#sourceDir = '/Users/lindner/Desktop/synology'
#sourceDir = '/Volumes/Macintosh HD/Users/lindner/Pictures/Photos Library.photoslibrary/Originals/'
#destDir = os.environ['HOME'] + '/Desktop/synology/renamed'
#destDir = '/Users/lindner/Desktop/photos renamed exif'
#destDir = '/Volumes/media/iPhoto/copy_from_script'
#sourceDir = os.environ['HOME'] + '/foo/bar'
#destDir = os.environ['HOME'] + '/zed/blah'
#errorDir = destDir + '/Unsorted2/'
# The format for the new file names.
#fmt = "%Y-%m-%d %H-%M-%S"
# The problem files.
problems = []
md5duplicates = []
# Get all the files in the source folder - include sub directories
file_list = []
for root, dirs, files in os.walk(sourceDir, topdown=False):
for name in files:
if not name.endswith('.DS_Store'): # should I exclude other unknown extensions? or just organize everything?
file_list.append(os.path.join(root, name))
# Prepare to output as processing occurs
lastMonth = 0
lastYear = 0
# Create the destination folder if necessary
if not os.path.exists(destDir):
os.makedirs(destDir)
if not os.path.exists(errorDir):
os.makedirs(errorDir)
# Copy files into year and month subfolders. Name the copies according to
# their timestamps. If more than one file has the same timestamp, add
# suffixes 'a', 'b', etc. to the names.
for file in file_list:
# print "Processing %s..." % file
original = file
filename, extension_with_dot = os.path.splitext(original)
lowercase_ext = extension_with_dot.lower()
no_dot_ext = lowercase_ext.rpartition(".")[-1]
if no_dot_ext == '':
no_dot_ext = 'no_extension'
suffix = 'a'
try:
fDate = fileDate(original)
yr = fDate.year
mo = fDate.month
if not lastYear == yr or not lastMonth == mo:
sys.stdout.write('\nProcessing %04d-%02d...' % (yr, mo))
lastMonth = mo
lastYear = yr
else:
sys.stdout.write('.')
newname = fDate.strftime(fmt)
if no_dot_ext in inverted:
group = inverted[no_dot_ext]
else:
group = no_dot_ext
thisDestDir = destDir + '/%s/%04d/%02d' % (group, yr, mo)
if not os.path.exists(thisDestDir):
os.makedirs(thisDestDir)
duplicate = thisDestDir + '/%s%s' % (newname,extension_with_dot)
md5dup = False
while os.path.exists(duplicate):
if not filesHaveDifferentMd5s(file, duplicate):
md5dup = True
break;
newname = fDate.strftime(fmt) + suffix
duplicate = destDir + '/%s/%04d/%02d/%s%s' % (group,yr, mo, newname,extension_with_dot)
suffix = chr(ord(suffix) + 1)
if not md5dup:
if subprocess.call(['cp', '-p', original, duplicate]) != 0:
raise Exception("failed to copy files")
else:
print "MD5 DUP!!!"
md5duplicates.append(file)
except Exception as e:
print '%s --> %s' % (e, file)
final_dir = '%s/%s/' % (errorDir, no_dot_ext)
if not os.path.exists(final_dir):
os.makedirs(final_dir)
subprocess.call(['cp', '-p', original, final_dir])
problems.append(file)
except:
sys.exit("Execution stopped.")
# Report the problem files, if any.
if len(problems) > 0:
print "\nProblem files: %s" % len(problems)
print "\n".join(problems)
print "These can be found in: %s" % errorDir
if len(md5duplicates) > 0:
print "\nMD5 Duplicate files: %s" % len(md5duplicates)
print "\n".join(md5duplicates)
print "These files were not copied"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment