#!/usr/bin/python ## =========================================================================== ## Exiftool is freeware by Robert F. Tobler . ## No warrenties whatsoever on the usefulness for whatever purpose. ## However if the tool is useful to you, you have improvements or ## suggestions, or if you just like it, I would appreciate feedback. ## =========================================================================== ## NAME: exiftool ## TYPE: python script ## CONTENT: decode EXIF header in JPEGs from digital cameras ## =========================================================================== ## AUTHORS: rft Robert F. Tobler ## =========================================================================== ## HISTORY: ## ## 10-Aug-01 11:16:19 rft added binary option ## 31-Jul-01 11:23:48 rft added byte-swap (DImage 7), E995 MakerNote ## 10-May-00 14:12:42 rft created ## =========================================================================== ## MANUAL: ## ## NAME ## exiftool, decode EXIF headers in JPEGs from digital cameras ## ## SYNOPSIS ## exiftool [options] ... ## ## DESCRIPTION ## Most digital cameras store additional information about each image ## directly within each JPEG file. This inforation is located in the ## so-called EXIF-header and contains such things as exposure time, ## f-stop, aso. This tool decodes this information and prints the ## informat, stores it in a HTML file, or renames the jpeg file ## according to the date and time the image was taken. ## ## OPTIONS ## -d Print debug output. This is most useful together with ## verbose output (-v). It reports the offset of each ## directory and data entry within the EXIF header. ## ## -h A short help page. ## ## -i Prints the tags of each JPEG according to a text ## template file that can be specified with the -t ## option. ## ## -n No action. Do not rename files or generate HTML ## files, just report intended actions. ## ## -m Print this manual. ## ## -o Specify the output directory where renamed files ## or HTML files should be placed. In this case, the ## rename option will actually copy the files, leaving ## the originals intact. ## ## -r Rename files according to exif date ## ## -t Take file contents as a template for either HTML ## files (if the file has an extension of '.html', ## '.HTML', '.htm', or '.HTM') or as a template for ## the info text used by the -i option otherwise. ## Within the template, the value of a tag can be ## printed by using the following syntax: ## ## %()s ## ## e.g. %(Model)s will report the camera model which ## produced the JPEG file. For a list of the tagnames ## that can be used, you can use the -v option. ## ## -v Verbose output. This reports all EXIF tags that are ## found in each JPEG. ## ## -w Writes a HTML file for each JPEG according to a ## template file that can be specified with the -t ## option. ## ## -b Binary option: mostly for debugging the exif library. ## ## AUTHOR ## Robert F. Tobler ## ## =========================================================================== import sys import os import string import struct import time import shutil from stat import * import exif ## --------------------------------------------------------------------------- def print_manual_and_exit(): script = open(sys.argv[0],'r') line = script.readline() while (line[0:11] != '## MANUAL:'): line = script.readline() line = script.readline() while (line[0:11] != '## ========'): if line[0:2] == '##': sys.stdout.write(' ' + line[2:]) else: sys.stdout.write(line) line = script.readline() script.close() sys.exit(0) ## --------------------------------------------------------------------------- def write_file_format(file_name, format, map): file = open(file_name, "w") file.write(format % map) file.close() ## --------------------------------------------------------------------------- def read_file(path): try: file = open(path, "rb") data = file.read() file.close() except IOError: print "ERROR: could not read file '%s'!" % path_name sys.exit(-1) return data ## --------------------------------------------------------------------------- ## In order to print the exif header with a % format string, even when some ## of the tags are missing, we use a PrintMap which returns 'unknown' for ## all missing tags. ## --------------------------------------------------------------------------- class PrintMap: def __init__(self, map): self.map = map def __getitem__(self, key): val = self.map.get(key) if val == None: val = 'unknown' return val ## --------------------------------------------------------------------------- ## This function creates a filename from the date as it is read in the ## exif header. In Nikon and Sony jpegs the format is: yyyy:mm:dd hh:mm:ss. ## --------------------------------------------------------------------------- def date_name(date_str): return date_str[:4] +'.'+ date_str[5:7] +'.'+ date_str[8:10] +'-'+\ date_str[11:13] +'.'+ date_str[14:16] +'.'+ date_str[17:19] ## --------------------------------------------------------------------------- ## 'TEXT_FORMAT' ## This is the default template for printing EXIF information with the ## -i option. ## --------------------------------------------------------------------------- TEXT_FORMAT = """__________________________________________ Photo Name: %(PhotoName)s Camera: %(Make)s %(Model)s Date: %(DateTime)s Exposure Time: %(ExposureTime)s sec Aperture: f/%(FNumber)s Exposure Program: %(ExposureProgram)s Exposure Bias: %(ExposureBiasValue)s Metering Mode: %(MeteringMode)s Flash: %(Flash)s FocalLength: %(FocalLength)s mm """ ## --------------------------------------------------------------------------- ## 'HTML_FORMAT' ## This is the default template for generating HTML files containing ## EXIF information with the -w option. ## --------------------------------------------------------------------------- HTML_FORMAT = """ %(PhotoName)s / Info

%(PhotoName)s

Camera %(Make)s %(Model)s
Date %(DateTime)s
Exposure Time %(ExposureTime)s sec
Aperture f/%(FNumber)s
Exposure Program %(ExposureProgram)s
Exposure Bias %(ExposureBiasValue)s
Metering Mode %(MeteringMode)s
Flash %(Flash)s
Focal Length %(FocalLength)s mm
""" ## --------------------------------------------------------------------------- def print_usage_and_exit(): print "USAGE: exiftool [opts] ... " print "OPTS: -h this help page" print " -m print manual page. use 'exiftool -m | less'" print " -d debug output" print " -i print info for each jpeg file" print " -n no action" print " -o specify output directory; copy not rename" print " -r rename files according to exif date" print " -t text or html template file (extension!)" print " -v verbose output, prints all tags" print " -w write a html file for each jpeg file" print " -b binary option for debugging exif library" print "NOTE: You have to specify at least one of: -i -r -w -v -d!" print " If no JPEGs are specified, all JPEGs in the current", print "dir are taken." sys.exit(-1) ## --------------------------------------------------------------------------- try: help_opt = 0 debug_opt = 0 verbose_opt = 0 write_opt = 0 rename_opt = 0 info_opt = 0 noact_opt = 0 outdir_par = '' mode_opt = 0 file_list = [] act = 0 argc = len(sys.argv) i = 0 while i+1 < argc: i = i+1 arg = sys.argv[i] if arg == '-h': print_usage_and_exit() if arg == '-m': print_manual_and_exit() if arg == '-d': debug_opt = 1 ; act = 1 ; continue if arg == '-v': verbose_opt = 1 ; act = 1 ; continue if arg == '-w': write_opt = 1 ; act = 1 ; continue if arg == '-r': rename_opt = 1 ; act = 1 ; continue if arg == '-i': info_opt = 1 ; act = 1 ; continue if arg == '-n': noact_opt = 1 ; continue if arg == '-b': mode_opt = exif.BINARY ; continue if arg == '-o': if i+1 < argc: i = i+1 ; outdir_par = sys.argv[i] ; continue print "ERROR: no directory for option '-d'!" sys.exit(-1) if arg == '-t': if i+1 < argc: i = i+1 t_par = sys.argv[i] (t_name, t_ext) = os.path.splitext(t_par) if t_ext == '.html' or t_ext == '.HTML' \ or t_ext == '.htm' or t_ext == '.HTM': HTML_FORMAT = read_file(t_par) else: TEXT_FORMAT = read_file(t_par) continue print "ERROR: no template file for option '-t'!" sys.exit(-1) if arg[0] == '-': print "ERROR: unknown option '%s'!" % arg sys.exit(-1) file_list.append(arg) if debug_opt: verbose_opt = 255 if outdir_par and not os.path.isdir(outdir_par): print "ERROR: '%s' is not directory!" % outdir_par sys.exit(-1) if not act: print_usage_and_exit() if len(file_list) == 0: file_list = os.listdir('.') for path_name in file_list: (dir_name, jpeg_name) = os.path.split(path_name) (photo_name, ext) = os.path.splitext(jpeg_name) if not os.path.exists(path_name): print path_name, 'UNREADABLE_FILE' continue if ext == '.jpg' or ext == '.JPG' or ext == '.jpeg' or ext == '.JPEG': if verbose_opt: repeat = 5 if verbose_opt > 1: repeat = 6 print "-------------" * repeat print "%30s: %s" % ("PhotoName", photo_name) tags = exif.parse(path_name, verbose_opt, mode_opt) if tags == {}: print path_name, 'NO_EXIF_HEADER' continue tags['PhotoName'] = photo_name if rename_opt: new_name = date_name(tags['DateTime']) print path_name, if new_name == '0000.00.00-00.00.00': print 'NO_VALID_TIMESTAMP' else: new_jpeg = new_name + '.jpg' if outdir_par: new_path_name = os.path.join(outdir_par, new_jpeg) else: new_path_name = os.path.join(dir_name, new_jpeg) if not noact_opt: tags['PhotoName'] = new_name new_path_noext = new_path_name[:-4]+'-' count = 1 while new_path_name and os.path.exists(new_path_name): jpeg1 = read_file(path_name) jpeg2 = read_file(new_path_name) if jpeg1 == jpeg2: new_path_name = '' print 'IDENTICAL_FILE_EXISTS', else: new_path_name = new_path_noext+`count`+'.jpg' count = count + 1 if debug_opt: print '[file exists, incremented name]', if new_path_name: if outdir_par: shutil.copyfile(path_name, new_path_name) else: os.rename(path_name, new_path_name) print new_path_name print_tags = PrintMap(tags) if info_opt: print TEXT_FORMAT % print_tags if write_opt: html_name = photo_name + '.html' if outdir_par: html_name = os.path.join(outdir_par, html_name) print path_name, html_name if not noact_opt: write_file_format(html_name, HTML_FORMAT, print_tags) except Exception, error: if isinstance(error, IOError) and str(error) == '[Errno 32] Broken pipe': sys.exit(-1) if isinstance(error,KeyboardInterrupt): sys.exit(-1) raise ## ===========================================================================