Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 307 lines
  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