This script helps me manage my digital photos by filing them in a directory based on the EXIF date and applying keywords and by-line information to the IPTC metadata.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 | """
---------------------------------------------------------------------------------------------------
Filename: RenameCopyPhotos.py
Author: Chad Cooper
Date: 2006-09-11
Description: Iterates through a directory, reading the EXIF data from each jpg. Parses the
date/time and focal length from EXIF data, renames the photo using the date/time
and focal length. Copies photo to a directory based on the date, ie 2006/09/11.
Adds date-related keywords to photo, determines by-line based on camera used, adds
average RGB code of image as a tuple keyword.
Changelog: 2006-09-11: Script finalized and out of testing.
2007-01-10: Everything wrapped into functions. Functions for adding by-line and
date info keywords added.
2007-01-18: Added error handling to all functions and added Timer() function
---------------------------------------------------------------------------------------------------
"""
import string, time, sys, calendar, Image, traceback
from time import localtime, strftime
from IPTC import IPTCInfo
import EXIF, os, shutil
base = '/Users/chad/Pictures/testing' # base dir
rawFolder = '/Users/chad/Pictures/exifImport' # dir where raw images from camera await import
fileExt = '.JPG'
filelist = os.listdir(rawFolder)
count = 0
def RenameFile(base, filename, rawFolder):
"""
Grabs EXIF tags and renames file based on DateTimeDigitized tag info
--------------------------------------------------------------------------
Inputs:
base: Base directory for photos --> '/Users/chad/Pictures/testing'
filename: Old filename of raw file from camera --> 'DSC_0001.jpg'
rawFolder: Temp directory where images are transferred to from camera
--------------------------------------------------------------------------
"""
print 'Original filename: ', filename
try:
picNumber = filename[len(filename)-8:len(filename)-4]
# Open file, get EXIF tags, get date string and focal length
os.chdir(rawFolder)
f = open(filename, 'rb')
tags = EXIF.process_file(f)
datestr = str(tags['EXIF DateTimeDigitized'])
focalLen = str(tags['EXIF FocalLength'])
# Start parsing EXIF tags we just grabbed
datestr = datestr.split(' ')
dt = datestr[0] # date
tm = datestr[1] # time
# Date
y = dt.split(':')[0] # year
if len(dt.split(':')[1]) < 2: # month
m = str('0') + dt.split(':')[1]
else:
m = dt.split(':')[1]
if len(dt.split(':')[2]) < 2: # day
d = str('0') + dt.split(':')[2]
else:
d = dt.split(':')[2]
# Time
if int(tm.split(':')[0]) < 13: # hour
hr = tm.split(':')[0]
ampm = 'AM'
elif int(tm.split(':')[0]) > 12:
hr = (int(tm.split(':')[0]) - 12)
ampm = 'PM'
min = tm.split(':')[1] # minute
sec = tm.split(':')[2] # second
# Establish new filename in form of:
# 0000_yyyy-mm-dd_hh-mm-ss_00mm.jpg
newName = picNumber + '_' + dt.replace(':', '-') + '_' + tm.replace(':', '-') + '_' + focalLen + 'mm.jpg'
except:
tb = sys.exc_info()[2]
tbinfo = traceback.format_tb(tb)[0]
pymsg = "PYTHON ERRORS:\nTraceback Info:\n" + tbinfo + "\nError Info:\n " + \
str(sys.exc_type)+ ": " + str(sys.exc_value) + "\n"
FilePic(rawFolder, base, filename, newName, y, m, d)
AddDateInfoKeywords(rawFolder, filename, base, newName, y, m, d, hr, ampm)
WriteByLine(base, newName, y, m, d)
def FilePic(rawFolder, base, filename, newName, y, m, d):
"""
Files the input image according to date taken --> 2006/07/09
/Users
/Chad
/Pictures
/2006
/07
/09
image goes here
--------------------------------------------------------------------------
Inputs:
rawFolder: Temp directory where images are transferred to from camera
base: Base directory for photos --> '/Users/chad/Pictures/testing'
filename: Old filename of raw file from camera --> 'DSC_0001.jpg'
newName : New filename as result of RenameFile --> 0000_yyyy-mm-dd_hh-mm-ss_00mm.jpg
y: Year from DateTimeDigitized Exif tag
m: Month from DateTimeDigitized Exif tag
d: Day from DateTimeDigitized Exif tag
--------------------------------------------------------------------------
"""
# Check for/make dirs for file to go into
# If dir already exists, use it - if it doesn't exist, then create it
try:
if os.path.isdir(base + '/' + y) != 1:
os.mkdir(base + '/' + y)
if os.path.isdir(base + '/' + y + '/' + m) != 1:
os.mkdir(base + '/' + y + '/' + m)
if os.path.isdir(base + '/' + y + '/' + m + '/' + d) != 1:
os.mkdir(base + '/' + y + '/' + m + '/' + d)
# Copy file, renaming it with new filename
shutil.copyfile(rawFolder + '/' + filename, base + '/' + y + '/' + m + '/' + d + '/' + newName)
imageName = base + '/' + y + '/' + m + '/' + d + '/' + newName
except:
tb = sys.exc_info()[2]
tbinfo = traceback.format_tb(tb)[0]
pymsg = "PYTHON ERRORS:\nTraceback Info:\n" + tbinfo + "\nError Info:\n " + \
str(sys.exc_type)+ ": " + str(sys.exc_value) + "\n"
print 'New home: ', imageName, '\n'
def WriteByLine(base, newName, y, m, d):
"""
Writes author info to the IPTC metadata of jpeg. Does test on original filename
to see which camera it came from, if Nikon, I took it with my D70; if HP,
Will took it with his camera
--------------------------------------------------------------------------
Inputs:
base: Base directory for photos --> '/Users/chad/Pictures/testing'
newName : New filename as result of RenameFile --> 0000_yyyy-mm-dd_hh-mm-ss_00mm.jpg
y: Year from DateTimeDigitized Exif tag
m: Month from DateTimeDigitized Exif tag
d: Day from DateTimeDigitized Exif tag
--------------------------------------------------------------------------
"""
try:
info = IPTCInfo(os.path.join(base, y, m, d, newName))
# Test to see who took the pic, depending on the camera
if filename[:2] == 'HP':
info.data['by-line'] = 'William L.R. Booher'
info.data['keywords'] = ['taken by will']
elif filename[:3] == 'DSC':
info.data['by-line'] = 'Chad D. Cooper'
else:
info.data['by-line'] = ''
info.saveAs(os.path.join(base, y, m, d, newName))
except:
tb = sys.exc_info()[2]
tbinfo = traceback.format_tb(tb)[0]
pymsg = "PYTHON ERRORS:\nTraceback Info:\n" + tbinfo + "\nError Info:\n " + \
str(sys.exc_type)+ ": " + str(sys.exc_value) + "\n"
def AddDateInfoKeywords(rawFolder, filename, base, newName, y, m, d, hr, ampm):
"""
Adds keywords for year, month, and date image was take to jpegs
Date data acquired from EXIF via exif.py module
Adds them as a list appendage, as in:
info.keyword.extend(['y'+y, 'm'+m, 'd'+d, dd])
--------------------------------------------------------------------------
Inputs:
base: Base directory for photos --> '/Users/chad/Pictures/testing'
newName : New filename as result of RenameFile --> 0000_yyyy-mm-dd_hh-mm-ss_00mm.jpg
y: Year from DateTimeDigitized Exif tag
m: Month from DateTimeDigitized Exif tag
d: Day from DateTimeDigitized Exif tag
--------------------------------------------------------------------------
"""
try:
info = IPTCInfo(os.path.join(base, y, m, d, newName))
# Day of week
y = int(y)
m = int(m)
d = int(d)
dd = calendar.weekday(y, m, d)
dict = {0:'Monday',1:'Tuesday',2:'Wednesday',3:'Thursday',4:'Friday',
5:'Saturday',6:'Sunday'}
day = dict[dd]
# Friendly time of day
tod = TimeOfDay(hr, ampm)
# Average RGB value of pic
rgb = 'rgb' + str(GetRgb(rawFolder, filename))
# Write our keywords to image
info.keywords.extend(['y' + str(y), 'm' + str(m), 'd' + str(d), day, ampm, tod, rgb])
info.saveAs(os.path.join(base, str(y), str(m), str(d), newName))
except:
tb = sys.exc_info()[2]
tbinfo = traceback.format_tb(tb)[0]
pymsg = "PYTHON ERRORS:\nTraceback Info:\n" + tbinfo + "\nError Info:\n " + \
str(sys.exc_type)+ ": " + str(sys.exc_value) + "\n"
def TimeOfDay(hour, ampm):
"""
From 24 hour time, take the hour and create a human-friendly tag telling us what period
of the day the photo was taken. Adapted from Dunstan Orchards PHP function at:
http://1976design.com/blog/archive/2004/07/23/redesign-time-presentation/
--------------------------------------------------------------------------
Inputs:
hour: Hour, in 24-hour format, our pic was shot
--------------------------------------------------------------------------
"""
try:
if ampm == 'PM':
hour = int(hour) + 12
else:
hour = int(hour)
# Get our human-friendly time of day
if hour < 4:
tod = 'the wee hours'
elif hour >= 4 and hour <= 6:
tod = 'terribly early in the morning'
elif hour >=7 and hour <= 9:
tod = 'early morning'
elif hour == 10:
tod = 'mid-morning'
elif hour == 11:
tod = 'late morning'
elif hour ==12:
tod = 'noon hour'
elif hour >= 13 and hour <= 14:
tod = 'early afternoon'
elif hour >= 15 and hour <= 16:
tod = 'mid=afternoon'
elif hour == 17:
tod = 'late afternoon'
elif hour >= 18 and hour <= 19:
tod = 'early evening'
elif hour >= 20 and hour <= 21:
tod = 'evening time'
elif hour == 22:
tod = 'late evening'
elif hour == 23:
tod = 'late at night'
else:
tod = ''
return tod
except:
tb = sys.exc_info()[2]
tbinfo = traceback.format_tb(tb)[0]
pymsg = "PYTHON ERRORS:\nTraceback Info:\n" + tbinfo + "\nError Info:\n " + \
str(sys.exc_type)+ ": " + str(sys.exc_value) + "\n"
def Timer(start, end):
"""
Calculates the time it takes to run a process, based on start and finish times
---------------------------------------------------------------------------------------------
Inputs:
start: Start time of process
end: End time of process
---------------------------------------------------------------------------------------------
"""
elapsed = end - start
# Convert process time, if needed
if elapsed <= 59:
time = str(round(elapsed,2)) + " seconds\n"
if elapsed >= 60 and elapsed <= 3590:
min = elapsed / 60
time = str(round(min,2)) + " minutes\n"
if elapsed >= 3600:
hour = elapsed / 3600
time = str(round(hour,2)) + " hours\n"
return time
def GetRgb(rawFolder, filename):
"""
Resizes the image down to one pixel in size, then returns a tuple of the
RGB values of that pixel. Gives an average RGB value for our image. Might
be good for something one day.
---------------------------------------------------------------------------------------------
Inputs:
rawFolder: Folder where our raw imported files are
filename: Image we are processing
---------------------------------------------------------------------------------------------
"""
try:
i=Image.open(os.path.join(rawFolder, filename))
rgb = i.quantize(1).convert('RGB').getpixel((0, 0))
return rgb
except:
tb = sys.exc_info()[2]
tbinfo = traceback.format_tb(tb)[0]
pymsg = "PYTHON ERRORS:\nTraceback Info:\n" + tbinfo + "\nError Info:\n " + \
str(sys.exc_type)+ ": " + str(sys.exc_value) + "\n"
##### RUN #####
if __name__ == '__main__':
start = time.clock()
for file in filelist:
filename = os.path.basename(file)
if filename[-4:] == fileExt:
RenameFile(base, filename, rawFolder)
count = count + 1
finish = time.clock()
print '\nProcessing done in ', Timer(start, finish)
print 'Images processed: ', str(count)
|
Are you obsessive about managing your digital photos? Me too! Are your digital photos filed in some arbitrary way that makes finding anything impossible? Mine used to be. If you are either of these, read on.
This script requires three great modules: PIL, IPTCInfo and EXIF, available at:
PIL: http://cheeseshop.python.org/pypi/PIL/1.1.6 * PIL 1.1.5 or later is required. I'm currently running 1.1.5
EXIF.py: http://home.cfl.rr.com/genecash/digital_camera/EXIF.py EXIF.py page: http://home.cfl.rr.com/genecash/digital_camera/index.html
IPTCInfo: http://cheeseshop.python.org/pypi/IPTCInfo/1.9.2-rc5
All of these modules are very well documented, so I won't go into what they do. RenameCopyPhotos uses EXIF.py to extract the date/time your photo was taken from the image metadata and file the picture (copy it) in the proper directory based on that date. It then applies keywords to the IPTC metadata of the image based on things like the date, and in my case, who took the picture; myself or my son, based on which camera was used (which is determined by parsing out the image file name - but you can get the camera make/model from the EXIF also). Some of these keywords I'm applying are not that necessary, but make for easy searching later on. I also grab (via PIL) the average RGB value of the image and apply that as an IPTC keyword. If anyone knows how to convert RGB values to human-friendly strings such as "blue", "red", "yellow", etc., I'd love to hear about it.
Script tested on Mac OSX 10.4.8 with Python 2.4.3, PIL 1.1.5, IPTCInfo 1.9.2-rc5, and EXIF