Boston University School of Theology (as viewed from the College of Arts and Sciences)



I had some free time the other day and decided I wanted to try to start learning Python. I had a need for it too: My usual protocol for sorting new photos for archiving was becoming a bit tiresome. So I read a few tutorials and started writing a script to read the EXIF data and put the photos in folders by year > month > camera model.

One byproduct of this little venture was that I saw some of my old photos and started getting excited about them. I knew there was a reason I’d saved all that crap! (Currently, there are 70,965 photos in the archive.) Here is a month’s worth, spiced up a bit in the contrast and cropping departments via Photoshop.

By the way, here’s the code:

# import logging module
import logging

# configure logging
logging.basicConfig(filename='AndrePhotoSort--LOG.log', format='%(asctime)s %(levelname)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p', filemode='w', level=logging.INFO)

# start message
logging.info('Started sorting photos\n')

# get operating system functions like listing files in directory and making directories
import os 

# get the current directory (__file__ is this module)
current_directory = os.path.dirname(os.path.abspath(__file__)) 

# get a list of files in the current directory
file_list = os.listdir(current_directory)

# initialize a list for jpg files only
photos = []

# put jpg files only in the directory
for file in file_list:

	# check file extension to see if file is a jpg (the lower() method converts the filename to lowercase for files with .JPG extensions)
	if file.lower().endswith('.jpg'):
		
		# if file is a jpg, add it do the photos list
		photos.append(file)

# count photo files
photo_count = str(len(photos))

# count files and send message to log
logging.info('Number of photos to be sorted: ' + photo_count + '\n')

# import modules
import PIL.Image 				# for working with images
import PIL.ExifTags				# for reading exif data in images
import string 					# for replacing problematic characters in exif strings
from datetime import datetime 	# for getting data from camera datetime strings
from shutil import copyfile		# for making a copy of the photo and putting it in a new directory

# loop through each of the entries in the list of photos
for idx, photo_file in enumerate(photos):
	
	# output for console
	print("Moving file " + str(idx + 1) + " of " + str(len(photos)) + ": " + str(photo_file))
	
	# open the photo
	img = PIL.Image.open(photo_file)
	
	# exif dictionary indexed by exif string names
	exif = {
		PIL.ExifTags.TAGS[k]: v
		for k, v in img._getexif().items()
		if k in PIL.ExifTags.TAGS
	}
	
	if exif.has_key('Make') and exif.has_key('Model') and exif.has_key('DateTimeOriginal'):
		
		# get the camera model exif data, cast it as a string, get rid of problematic characters, and strip trailing null bytes
		camera_make = str(exif['Make'])
		camera_make = string.replace(camera_make, '/', ' ')
		camera_make = string.replace(camera_make, '.', ' ')
		camera_make = string.replace(camera_make, ' ', ' ')
		camera_make = camera_make.rstrip('\0')

		# get the camera model exif data, cast it as a string, get rid of problematic characters, and strip trailing null bytes
		camera_model = str(exif['Model'])
		camera_model = string.replace(camera_model, '/', ' ')
		camera_model = string.replace(camera_model, '.', ' ')
		camera_model = string.replace(camera_model, ' ', ' ')
		camera_model = camera_model.rstrip('\0')
		
		# convert the exif DateTimeOriginal string (AKA, Date Taken) and convert it into a datetime object
		date_taken = datetime.strptime(exif['DateTimeOriginal'], '%Y:%m:%d %H:%M:%S')
		
		# get the year the photo was taken
		photo_year = str(date_taken.year)
		
		# get the month the photo was taken
		photo_month = str(date_taken.month).zfill(2)
		
		# build path string for destination directory
		destination_directory = os.path.join(current_directory, photo_year, photo_month, (camera_make + ' - ' + camera_model))

		# check to see if destination directory exists
		if not (os.path.isdir(destination_directory)):
			
			# if it doesn't, make it
			os.makedirs(destination_directory)
		
		# copy the file to the new directory
		copyfile(os.path.join(current_directory, photo_file), os.path.join(destination_directory, photo_file))
		
		# send message to log
		logging.info('Moved ' + photo_file + ' to ' + destination_directory)
	
	else:
		# build path string for destination directory
		destination_directory = current_directory + '\\NO EXIF DATA\\'

		# check to see if destination directory exists
		if not (os.path.isdir(destination_directory)):
			
			# if it doesn't, make it
			os.makedirs(destination_directory)
			
		copyfile(current_directory + '\\' + photo_file, destination_directory + '\\' + photo_file)
		logging.info('Moved ' + photo_file + ' to .\\NO EXIF DATA\\')


if (int(photo_count) == (idx + 1)):
	logging.info('All ' + photo_count + ' photos were copied to a new directory\n')
else:
	logging.warning('ONE OR MORE OF THE FILES WAS NOT COPIED TO A NEW DIRECTORY!\n')

logging.info('Finished sorting photos')
Boston University School of Theology (as viewed from the College of Arts and Sciences)