Back to index

moin  1.9.0~rc2
EXIF.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # -*- coding: utf-8 -*-
00003 #
00004 # Library to extract EXIF information in digital camera image files
00005 # http://sourceforge.net/projects/exif-py/
00006 #
00007 # To use this library call with:
00008 #    f=open(path_name, 'rb')
00009 #    tags=EXIF.process_file(f)
00010 #
00011 # If you want to ignore makerNote, pass the -q or --quick
00012 # command line arguments, or as
00013 #    tags=EXIF.process_file(f, details=False)
00014 #
00015 # This is useful when you are retrieving a large list of images
00016 #
00017 # Returned tags will be a dictionary mapping names of EXIF tags to their
00018 # values in the file named by path_name.  You can process the tags
00019 # as you wish.  In particular, you can iterate through all the tags with:
00020 #     for tag in tags.keys():
00021 #         if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename',
00022 #                        'EXIF MakerNote'):
00023 #             print "Key: %s, value %s" % (tag, tags[tag])
00024 # (This code uses the if statement to avoid printing out a few of the
00025 # tags that tend to be long or boring.)
00026 #
00027 # The tags dictionary will include keys for all of the usual EXIF
00028 # tags, and will also include keys for Makernotes used by some
00029 # cameras, for which we have a good specification.
00030 #
00031 # Contains code from "exifdump.py" originally written by Thierry Bousch
00032 # <bousch@topo.math.u-psud.fr> and released into the public domain.
00033 #
00034 # Updated and turned into general-purpose library by Gene Cash
00035 #
00036 # This copyright license is intended to be similar to the FreeBSD license.
00037 #
00038 # Copyright (c) 2002 Gene Cash All rights reserved.
00039 #
00040 # Redistribution and use in source and binary forms, with or without
00041 # modification, are permitted provided that the following conditions are
00042 # met:
00043 #
00044 #    1. Redistributions of source code must retain the above copyright
00045 #       notice, this list of conditions and the following disclaimer.
00046 #    2. Redistributions in binary form must reproduce the above copyright
00047 #       notice, this list of conditions and the following disclaimer in the
00048 #       documentation and/or other materials provided with the
00049 #       distribution.
00050 #
00051 # THIS SOFTWARE IS PROVIDED BY GENE CASH ``AS IS'' AND ANY EXPRESS OR
00052 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00053 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00054 # DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
00055 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
00056 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
00057 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
00058 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
00059 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00060 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00061 # POSSIBILITY OF SUCH DAMAGE.
00062 #
00063 # This means you may do anything you want with this code, except claim you
00064 # wrote it. Also, if it breaks you get to keep both pieces.
00065 #
00066 # Patch Contributors:
00067 # * Simon J. Gerraty <sjg@crufty.net>
00068 #   s2n fix & orientation decode
00069 # * John T. Riedl <riedl@cs.umn.edu>
00070 #   Added support for newer Nikon type 3 Makernote format for D70 and some
00071 #   other Nikon cameras.
00072 # * Joerg Schaefer <schaeferj@gmx.net>
00073 #   Fixed subtle bug when faking an EXIF header, which affected maker notes
00074 #   using relative offsets, and a fix for Nikon D100.
00075 #
00076 # 1999-08-21 TB  Last update by Thierry Bousch to his code.
00077 # 2002-01-17 CEC Discovered code on web.
00078 #                Commented everything.
00079 #                Made small code improvements.
00080 #                Reformatted for readability.
00081 # 2002-01-19 CEC Added ability to read TIFFs and JFIF-format JPEGs.
00082 #                Added ability to extract JPEG formatted thumbnail.
00083 #                Added ability to read GPS IFD (not tested).
00084 #                Converted IFD data structure to dictionaries indexed by
00085 #                tag name.
00086 #                Factored into library returning dictionary of IFDs plus
00087 #                thumbnail, if any.
00088 # 2002-01-20 CEC Added MakerNote processing logic.
00089 #                Added Olympus MakerNote.
00090 #                Converted data structure to single-level dictionary, avoiding
00091 #                tag name collisions by prefixing with IFD name.  This makes
00092 #                it much easier to use.
00093 # 2002-01-23 CEC Trimmed nulls from end of string values.
00094 # 2002-01-25 CEC Discovered JPEG thumbnail in Olympus TIFF MakerNote.
00095 # 2002-01-26 CEC Added ability to extract TIFF thumbnails.
00096 #               Added Nikon, Fujifilm, Casio MakerNotes.
00097 # 2003-11-30 CEC Fixed problem with canon_decode_tag() not creating an
00098 #               IFD_Tag() object.
00099 # 2004-02-15 CEC Finally fixed bit shift warning by converting Y to 0L.
00100 #
00101 # ---------------------------- End original notices ------------------------- #
00102 
00103 # 2006-08-04 MoinMoin:ReimarBauer
00104 # added an optional parameter name to process_file and dump_IFD. Using this parameter the
00105 # loop is breaked after that tag_name is processed.
00106 # Example:
00107 #    f = open(infile, 'rb')
00108 #    tags = EXIF.process_file(f,'DateTimeOriginal')
00109 #    f.close()
00110 # some PEP8 changes
00111 
00112 # 2007-01-18 - Ianaré Sévi <ianare@gmail.com>
00113 # Fixed a couple errors and assuming maintenance of the library.
00114 
00115 # 2007-03-24 - Ianaré Sévi
00116 # Can now ignore MakerNotes Tags for faster processing.
00117 
00118 # 2007-05-03 - Martin Stone <mj_stone@users.sourceforge.net>
00119 # Fix for inverted detailed flag and Photoshop header
00120 
00121 # 2007-09-22 - Stephen H. Olson
00122 # Don't error on invalid string
00123 # Improved Nikon MakerNote support
00124 
00125 # 2007-09-26 - Stephen H. Olson
00126 # Don't error on invalid Olympus 'SpecialMode'.
00127 # Add a few more Olympus/Minolta tags.
00128 
00129 # 2007-09-27 - Ianaré Sévi
00130 # Add Olympus Makernote tags.
00131 
00132 
00133 # ===== CODE START ==== #
00134 
00135 # Don't throw an exception when given an out of range character.
00136 #
00137 # lambda x: ''.join(map(chr, x))
00138 #
00139 def make_string(seq):
00140     str = ""
00141     for c in seq:
00142         # Screen out non-printing characters
00143         if 32 <= c and c < 256:
00144             str += chr(c)
00145     # If no printing chars
00146     if not str:
00147         return seq
00148     return str
00149 
00150 # Special version to deal with the code in the first 8 bytes of a user comment.
00151 def make_string_uc(seq):
00152     code = seq[0:8]
00153     seq = seq[8:]
00154     # Of course, this is only correct if ASCII, and the standard explicitly
00155     # allows JIS and Unicode.
00156     return make_string(seq)
00157 
00158 # field type descriptions as (length, abbreviation, full name) tuples
00159 FIELD_TYPES = (
00160     (0, 'X', 'Proprietary'), # no such type
00161     (1, 'B', 'Byte'),
00162     (1, 'A', 'ASCII'),
00163     (2, 'S', 'Short'),
00164     (4, 'L', 'Long'),
00165     (8, 'R', 'Ratio'),
00166     (1, 'SB', 'Signed Byte'),
00167     (1, 'U', 'Undefined'),
00168     (2, 'SS', 'Signed Short'),
00169     (4, 'SL', 'Signed Long'),
00170     (8, 'SR', 'Signed Ratio'),
00171     )
00172 
00173 # dictionary of main EXIF tag names
00174 # first element of tuple is tag name, optional second element is
00175 # another dictionary giving names to values
00176 EXIF_TAGS = {
00177     0x0100: ('ImageWidth', ),
00178     0x0101: ('ImageLength', ),
00179     0x0102: ('BitsPerSample', ),
00180     0x0103: ('Compression',
00181              {1: 'Uncompressed TIFF',
00182               6: 'JPEG Compressed'}),
00183     0x0106: ('PhotometricInterpretation', ),
00184     0x010A: ('FillOrder', ),
00185     0x010D: ('DocumentName', ),
00186     0x010E: ('ImageDescription', ),
00187     0x010F: ('Make', ),
00188     0x0110: ('Model', ),
00189     0x0111: ('StripOffsets', ),
00190     0x0112: ('Orientation',
00191              {1: 'Horizontal (normal)',
00192               2: 'Mirrored horizontal',
00193               3: 'Rotated 180',
00194               4: 'Mirrored vertical',
00195               5: 'Mirrored horizontal then rotated 90 CCW',
00196               6: 'Rotated 90 CW',
00197               7: 'Mirrored horizontal then rotated 90 CW',
00198               8: 'Rotated 90 CCW'}),
00199     0x0115: ('SamplesPerPixel', ),
00200     0x0116: ('RowsPerStrip', ),
00201     0x0117: ('StripByteCounts', ),
00202     0x011A: ('XResolution', ),
00203     0x011B: ('YResolution', ),
00204     0x011C: ('PlanarConfiguration', ),
00205     0x0128: ('ResolutionUnit',
00206              {1: 'Not Absolute',
00207               2: 'Pixels/Inch',
00208               3: 'Pixels/Centimeter'}),
00209     0x012D: ('TransferFunction', ),
00210     0x0131: ('Software', ),
00211     0x0132: ('DateTime', ),
00212     0x013B: ('Artist', ),
00213     0x013E: ('WhitePoint', ),
00214     0x013F: ('PrimaryChromaticities', ),
00215     0x0156: ('TransferRange', ),
00216     0x0200: ('JPEGProc', ),
00217     0x0201: ('JPEGInterchangeFormat', ),
00218     0x0202: ('JPEGInterchangeFormatLength', ),
00219     0x0211: ('YCbCrCoefficients', ),
00220     0x0212: ('YCbCrSubSampling', ),
00221     0x0213: ('YCbCrPositioning', ),
00222     0x0214: ('ReferenceBlackWhite', ),
00223     0x828D: ('CFARepeatPatternDim', ),
00224     0x828E: ('CFAPattern', ),
00225     0x828F: ('BatteryLevel', ),
00226     0x8298: ('Copyright', ),
00227     0x829A: ('ExposureTime', ),
00228     0x829D: ('FNumber', ),
00229     0x83BB: ('IPTC/NAA', ),
00230     0x8769: ('ExifOffset', ),
00231     0x8773: ('InterColorProfile', ),
00232     0x8822: ('ExposureProgram',
00233              {0: 'Unidentified',
00234               1: 'Manual',
00235               2: 'Program Normal',
00236               3: 'Aperture Priority',
00237               4: 'Shutter Priority',
00238               5: 'Program Creative',
00239               6: 'Program Action',
00240               7: 'Portrait Mode',
00241               8: 'Landscape Mode'}),
00242     0x8824: ('SpectralSensitivity', ),
00243     0x8825: ('GPSInfo', ),
00244     0x8827: ('ISOSpeedRatings', ),
00245     0x8828: ('OECF', ),
00246     # print as string
00247     0x9000: ('ExifVersion', make_string),
00248     0x9003: ('DateTimeOriginal', ),
00249     0x9004: ('DateTimeDigitized', ),
00250     0x9101: ('ComponentsConfiguration',
00251              {0: '',
00252               1: 'Y',
00253               2: 'Cb',
00254               3: 'Cr',
00255               4: 'Red',
00256               5: 'Green',
00257               6: 'Blue'}),
00258     0x9102: ('CompressedBitsPerPixel', ),
00259     0x9201: ('ShutterSpeedValue', ),
00260     0x9202: ('ApertureValue', ),
00261     0x9203: ('BrightnessValue', ),
00262     0x9204: ('ExposureBiasValue', ),
00263     0x9205: ('MaxApertureValue', ),
00264     0x9206: ('SubjectDistance', ),
00265     0x9207: ('MeteringMode',
00266              {0: 'Unidentified',
00267               1: 'Average',
00268               2: 'CenterWeightedAverage',
00269               3: 'Spot',
00270               4: 'MultiSpot'}),
00271     0x9208: ('LightSource',
00272              {0: 'Unknown',
00273               1: 'Daylight',
00274               2: 'Fluorescent',
00275               3: 'Tungsten',
00276               10: 'Flash',
00277               17: 'Standard Light A',
00278               18: 'Standard Light B',
00279               19: 'Standard Light C',
00280               20: 'D55',
00281               21: 'D65',
00282               22: 'D75',
00283               255: 'Other'}),
00284     0x9209: ('Flash', {0: 'No',
00285                        1: 'Fired',
00286                        5: 'Fired (?)', # no return sensed
00287                        7: 'Fired (!)', # return sensed
00288                        9: 'Fill Fired',
00289                        13: 'Fill Fired (?)',
00290                        15: 'Fill Fired (!)',
00291                        16: 'Off',
00292                        24: 'Auto Off',
00293                        25: 'Auto Fired',
00294                        29: 'Auto Fired (?)',
00295                        31: 'Auto Fired (!)',
00296                        32: 'Not Available'}),
00297     0x920A: ('FocalLength', ),
00298     0x9214: ('SubjectArea', ),
00299     0x927C: ('MakerNote', ),
00300     # print as string
00301     0x9286: ('UserComment', make_string_uc),     # First 8 bytes gives coding system e.g. ASCII vs. JIS vs Unicode
00302     0x9290: ('SubSecTime', ),
00303     0x9291: ('SubSecTimeOriginal', ),
00304     0x9292: ('SubSecTimeDigitized', ),
00305     # print as string
00306     0xA000: ('FlashPixVersion', make_string),
00307     0xA001: ('ColorSpace', ),
00308     0xA002: ('ExifImageWidth', ),
00309     0xA003: ('ExifImageLength', ),
00310     0xA005: ('InteroperabilityOffset', ),
00311     0xA20B: ('FlashEnergy', ),               # 0x920B in TIFF/EP
00312     0xA20C: ('SpatialFrequencyResponse', ),  # 0x920C    -  -
00313     0xA20E: ('FocalPlaneXResolution', ),     # 0x920E    -  -
00314     0xA20F: ('FocalPlaneYResolution', ),     # 0x920F    -  -
00315     0xA210: ('FocalPlaneResolutionUnit', ),  # 0x9210    -  -
00316     0xA214: ('SubjectLocation', ),           # 0x9214    -  -
00317     0xA215: ('ExposureIndex', ),             # 0x9215    -  -
00318     0xA217: ('SensingMethod', ),             # 0x9217    -  -
00319     0xA300: ('FileSource',
00320              {3: 'Digital Camera'}),
00321     0xA301: ('SceneType',
00322              {1: 'Directly Photographed'}),
00323     0xA302: ('CVAPattern', ),
00324     0xA401: ('CustomRendered', ),
00325     0xA402: ('ExposureMode',
00326              {0: 'Auto Exposure',
00327               1: 'Manual Exposure',
00328               2: 'Auto Bracket'}),
00329     0xA403: ('WhiteBalance',
00330              {0: 'Auto',
00331               1: 'Manual'}),
00332     0xA404: ('DigitalZoomRatio', ),
00333     0xA405: ('FocalLengthIn35mm', ),
00334     0xA406: ('SceneCaptureType', ),
00335     0xA407: ('GainControl', ),
00336     0xA408: ('Contrast', ),
00337     0xA409: ('Saturation', ),
00338     0xA40A: ('Sharpness', ),
00339     0xA40C: ('SubjectDistanceRange', ),
00340     }
00341 
00342 # interoperability tags
00343 INTR_TAGS = {
00344     0x0001: ('InteroperabilityIndex', ),
00345     0x0002: ('InteroperabilityVersion', ),
00346     0x1000: ('RelatedImageFileFormat', ),
00347     0x1001: ('RelatedImageWidth', ),
00348     0x1002: ('RelatedImageLength', ),
00349     }
00350 
00351 # GPS tags (not used yet, haven't seen camera with GPS)
00352 GPS_TAGS = {
00353     0x0000: ('GPSVersionID', ),
00354     0x0001: ('GPSLatitudeRef', ),
00355     0x0002: ('GPSLatitude', ),
00356     0x0003: ('GPSLongitudeRef', ),
00357     0x0004: ('GPSLongitude', ),
00358     0x0005: ('GPSAltitudeRef', ),
00359     0x0006: ('GPSAltitude', ),
00360     0x0007: ('GPSTimeStamp', ),
00361     0x0008: ('GPSSatellites', ),
00362     0x0009: ('GPSStatus', ),
00363     0x000A: ('GPSMeasureMode', ),
00364     0x000B: ('GPSDOP', ),
00365     0x000C: ('GPSSpeedRef', ),
00366     0x000D: ('GPSSpeed', ),
00367     0x000E: ('GPSTrackRef', ),
00368     0x000F: ('GPSTrack', ),
00369     0x0010: ('GPSImgDirectionRef', ),
00370     0x0011: ('GPSImgDirection', ),
00371     0x0012: ('GPSMapDatum', ),
00372     0x0013: ('GPSDestLatitudeRef', ),
00373     0x0014: ('GPSDestLatitude', ),
00374     0x0015: ('GPSDestLongitudeRef', ),
00375     0x0016: ('GPSDestLongitude', ),
00376     0x0017: ('GPSDestBearingRef', ),
00377     0x0018: ('GPSDestBearing', ),
00378     0x0019: ('GPSDestDistanceRef', ),
00379     0x001A: ('GPSDestDistance', ),
00380     }
00381 
00382 # ignore these tags when quick processing
00383 # 0x927C is MakerNote Tags
00384 # 0x9286 is user comment
00385 IGNORE_TAGS=(0x9286, 0x927C)
00386 
00387 # http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp
00388 def nikon_ev_bias(seq):
00389     # First digit seems to be in steps of 1/6 EV.
00390     # Does the third value mean the step size?  It is usually 6,
00391     # but it is 12 for the ExposureDifference.
00392     #
00393     if seq == [252, 1, 6, 0]:
00394         return "-2/3 EV"
00395     if seq == [253, 1, 6, 0]:
00396         return "-1/2 EV"
00397     if seq == [254, 1, 6, 0]:
00398         return "-1/3 EV"
00399     if seq == [0, 1, 6, 0]:
00400         return "0 EV"
00401     if seq == [2, 1, 6, 0]:
00402         return "+1/3 EV"
00403     if seq == [3, 1, 6, 0]:
00404         return "+1/2 EV"
00405     if seq == [4, 1, 6, 0]:
00406         return "+2/3 EV"
00407     # Handle combinations not in the table.
00408     a = seq[0]
00409     # Causes headaches for the +/- logic, so special case it.
00410     if a == 0:
00411         return "0 EV"
00412     if a > 127:
00413         a = 256 - a
00414         ret_str = "-"
00415     else:
00416         ret_str = "+"
00417     b = seq[2]       # Assume third value means the step size
00418     whole = a / b
00419     a = a % b
00420     if whole != 0:
00421         ret_str = ret_str + str(whole) + " "
00422     if a == 0:
00423         ret_str = ret_str + "EV"
00424     else:
00425         r = Ratio(a, b)
00426         ret_str = ret_str + r.__repr__() + " EV"
00427     return ret_str
00428 
00429 # Nikon E99x MakerNote Tags
00430 MAKERNOTE_NIKON_NEWER_TAGS={
00431     0x0001: ('MakernoteVersion', make_string),   # Sometimes binary
00432     0x0002: ('ISOSetting', ),
00433     0x0003: ('ColorMode', ),
00434     0x0004: ('Quality', ),
00435     0x0005: ('Whitebalance', ),
00436     0x0006: ('ImageSharpening', ),
00437     0x0007: ('FocusMode', ),
00438     0x0008: ('FlashSetting', ),
00439     0x0009: ('AutoFlashMode', ),
00440     0x000B: ('WhiteBalanceBias', ),
00441     0x000C: ('WhiteBalanceRBCoeff', ),
00442     0x000D: ('ProgramShift', nikon_ev_bias),
00443     # Nearly the same as the other EV vals, but step size is 1/12 EV (?)
00444     0x000E: ('ExposureDifference', nikon_ev_bias),
00445     0x000F: ('ISOSelection', ),
00446     0x0011: ('NikonPreview', ),
00447     0x0012: ('FlashCompensation', nikon_ev_bias),
00448     0x0013: ('ISOSpeedRequested', ),
00449     0x0016: ('PhotoCornerCoordinates', ),
00450     # 0x0017: Unknown, but most likely an EV value
00451     0x0018: ('FlashBracketCompensationApplied', nikon_ev_bias),
00452     0x0019: ('AEBracketCompensationApplied', ),
00453     0x001A: ('ImageProcessing', ),
00454     0x0080: ('ImageAdjustment', ),
00455     0x0081: ('ToneCompensation', ),
00456     0x0082: ('AuxiliaryLens', ),
00457     0x0083: ('LensType', ),
00458     0x0084: ('LensMinMaxFocalMaxAperture', ),
00459     0x0085: ('ManualFocusDistance', ),
00460     0x0086: ('DigitalZoomFactor', ),
00461     0x0087: ('FlashMode',
00462              {0x00: 'Did Not Fire',
00463               0x01: 'Fired, Manual',
00464               0x07: 'Fired, External',
00465               0x08: 'Fired, Commander Mode ',
00466               0x09: 'Fired, TTL Mode'}),
00467     0x0088: ('AFFocusPosition',
00468              {0x0000: 'Center',
00469               0x0100: 'Top',
00470               0x0200: 'Bottom',
00471               0x0300: 'Left',
00472               0x0400: 'Right'}),
00473     0x0089: ('BracketingMode',
00474              {0x00: 'Single frame, no bracketing',
00475               0x01: 'Continuous, no bracketing',
00476               0x02: 'Timer, no bracketing',
00477               0x10: 'Single frame, exposure bracketing',
00478               0x11: 'Continuous, exposure bracketing',
00479               0x12: 'Timer, exposure bracketing',
00480               0x40: 'Single frame, white balance bracketing',
00481               0x41: 'Continuous, white balance bracketing',
00482               0x42: 'Timer, white balance bracketing'}),
00483     0x008A: ('AutoBracketRelease', ),
00484     0x008B: ('LensFStops', ),
00485     0x008C: ('NEFCurve2', ),
00486     0x008D: ('ColorMode', ),
00487     0x008F: ('SceneMode', ),
00488     0x0090: ('LightingType', ),
00489     0x0091: ('ShotInfo', ), # First 4 bytes are probably a version number in ASCII
00490     0x0092: ('HueAdjustment', ),
00491     # 0x0093: ('SaturationAdjustment', ),
00492     0x0094: ('Saturation',  # Name conflict with 0x00AA !!
00493              {-3: 'B&W',
00494               -2: '-2',
00495               -1: '-1',
00496               0: '0',
00497               1: '1',
00498               2: '2'}),
00499     0x0095: ('NoiseReduction', ),
00500     0x0096: ('NEFCurve2', ),
00501     0x0097: ('ColorBalance', ),
00502     0x0098: ('LensData', ), # First 4 bytes are a version number in ASCII
00503     0x0099: ('RawImageCenter', ),
00504     0x009A: ('SensorPixelSize', ),
00505     0x009C: ('Scene Assist', ),
00506     0x00A0: ('SerialNumber', ),
00507     0x00A2: ('ImageDataSize', ),
00508     # A4: In NEF, looks like a 4 byte ASCII version number
00509     0x00A5: ('ImageCount', ),
00510     0x00A6: ('DeletedImageCount', ),
00511     0x00A7: ('TotalShutterReleases', ),
00512     # A8: ExposureMode?  JPG: First 4 bytes are probably a version number in ASCII
00513     # But in a sample NEF, its 8 zeros, then the string "NORMAL"
00514     0x00A9: ('ImageOptimization', ),
00515     0x00AA: ('Saturation', ),
00516     0x00AB: ('DigitalVariProgram', ),
00517     0x00AC: ('ImageStabilization', ),
00518     0x00AD: ('Responsive AF', ),   # 'AFResponse'
00519     0x0010: ('DataDump', ),
00520     }
00521 
00522 MAKERNOTE_NIKON_OLDER_TAGS = {
00523     0x0003: ('Quality',
00524              {1: 'VGA Basic',
00525               2: 'VGA Normal',
00526               3: 'VGA Fine',
00527               4: 'SXGA Basic',
00528               5: 'SXGA Normal',
00529               6: 'SXGA Fine'}),
00530     0x0004: ('ColorMode',
00531              {1: 'Color',
00532               2: 'Monochrome'}),
00533     0x0005: ('ImageAdjustment',
00534              {0: 'Normal',
00535               1: 'Bright+',
00536               2: 'Bright-',
00537               3: 'Contrast+',
00538               4: 'Contrast-'}),
00539     0x0006: ('CCDSpeed',
00540              {0: 'ISO 80',
00541               2: 'ISO 160',
00542               4: 'ISO 320',
00543               5: 'ISO 100'}),
00544     0x0007: ('WhiteBalance',
00545              {0: 'Auto',
00546               1: 'Preset',
00547               2: 'Daylight',
00548               3: 'Incandescent',
00549               4: 'Fluorescent',
00550               5: 'Cloudy',
00551               6: 'Speed Light'}),
00552     }
00553 
00554 # decode Olympus SpecialMode tag in MakerNote
00555 def olympus_special_mode(v):
00556     a={
00557         0: 'Normal',
00558         1: 'Unknown',
00559         2: 'Fast',
00560         3: 'Panorama'}
00561     b={
00562         0: 'Non-panoramic',
00563         1: 'Left to right',
00564         2: 'Right to left',
00565         3: 'Bottom to top',
00566         4: 'Top to bottom'}
00567     if v[0] not in a or v[2] not in b:
00568         return v
00569     return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
00570 
00571 MAKERNOTE_OLYMPUS_TAGS={
00572     # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
00573     # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
00574     0x0100: ('JPEGThumbnail', ),
00575     0x0200: ('SpecialMode', olympus_special_mode),
00576     0x0201: ('JPEGQual',
00577              {1: 'SQ',
00578               2: 'HQ',
00579               3: 'SHQ'}),
00580     0x0202: ('Macro',
00581              {0: 'Normal',
00582              1: 'Macro',
00583              2: 'SuperMacro'}),
00584     0x0203: ('BWMode',
00585              {0: 'Off',
00586              1: 'On'}),
00587     0x0204: ('DigitalZoom', ),
00588     0x0205: ('FocalPlaneDiagonal', ),
00589     0x0206: ('LensDistortionParams', ),
00590     0x0207: ('SoftwareRelease', ),
00591     0x0208: ('PictureInfo', ),
00592     0x0209: ('CameraID', make_string), # print as string
00593     0x0F00: ('DataDump', ),
00594     0x0300: ('PreCaptureFrames', ),
00595     0x0404: ('SerialNumber', ),
00596     0x1000: ('ShutterSpeedValue', ),
00597     0x1001: ('ISOValue', ),
00598     0x1002: ('ApertureValue', ),
00599     0x1003: ('BrightnessValue', ),
00600     0x1004: ('FlashMode', ),
00601     0x1004: ('FlashMode',
00602        {2: 'On',
00603         3: 'Off'}),
00604     0x1005: ('FlashDevice',
00605        {0: 'None',
00606         1: 'Internal',
00607         4: 'External',
00608         5: 'Internal + External'}),
00609     0x1006: ('ExposureCompensation', ),
00610     0x1007: ('SensorTemperature', ),
00611     0x1008: ('LensTemperature', ),
00612     0x100b: ('FocusMode',
00613        {0: 'Auto',
00614         1: 'Manual'}),
00615     0x1017: ('RedBalance', ),
00616     0x1018: ('BlueBalance', ),
00617     0x101a: ('SerialNumber', ),
00618     0x1023: ('FlashExposureComp', ),
00619     0x1026: ('ExternalFlashBounce',
00620        {0: 'No',
00621         1: 'Yes'}),
00622     0x1027: ('ExternalFlashZoom', ),
00623     0x1028: ('ExternalFlashMode', ),
00624     0x1029: ('Contrast      int16u',
00625        {0: 'High',
00626         1: 'Normal',
00627         2: 'Low'}),
00628     0x102a: ('SharpnessFactor', ),
00629     0x102b: ('ColorControl', ),
00630     0x102c: ('ValidBits', ),
00631     0x102d: ('CoringFilter', ),
00632     0x102e: ('OlympusImageWidth', ),
00633     0x102f: ('OlympusImageHeight', ),
00634     0x1034: ('CompressionRatio', ),
00635     0x1035: ('PreviewImageValid',
00636        {0: 'No',
00637         1: 'Yes'}),
00638     0x1036: ('PreviewImageStart', ),
00639     0x1037: ('PreviewImageLength', ),
00640     0x1039: ('CCDScanMode',
00641        {0: 'Interlaced',
00642         1: 'Progressive'}),
00643     0x103a: ('NoiseReduction',
00644        {0: 'Off',
00645         1: 'On'}),
00646     0x103b: ('InfinityLensStep', ),
00647     0x103c: ('NearLensStep', ),
00648 
00649     # TODO - these need extra definitions
00650     # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html
00651     0x2010: ('Equipment', ),
00652     0x2020: ('CameraSettings', ),
00653     0x2030: ('RawDevelopment', ),
00654     0x2040: ('ImageProcessing', ),
00655     0x2050: ('FocusInfo', ),
00656     0x3000: ('RawInfo ', ),
00657     }
00658 
00659 # 0x2020 CameraSettings
00660 MAKERNOTE_OLYMPUS_TAG_0x2020={
00661     0x0100: ('PreviewImageValid',
00662         {0: 'No',
00663          1: 'Yes'}),
00664     0x0101: ('PreviewImageStart', ),
00665     0x0102: ('PreviewImageLength', ),
00666     0x0200: ('ExposureMode', {
00667         1: 'Manual',
00668         2: 'Program',
00669         3: 'Aperture-priority AE',
00670         4: 'Shutter speed priority AE',
00671         5: 'Program-shift'}),
00672     0x0201: ('AELock',
00673        {0: 'Off',
00674         1: 'On'}),
00675     0x0202: ('MeteringMode',
00676        {2: 'Center Weighted',
00677         3: 'Spot',
00678         5: 'ESP',
00679         261: 'Pattern+AF',
00680         515: 'Spot+Highlight control',
00681         1027: 'Spot+Shadow control'}),
00682     0x0300: ('MacroMode',
00683        {0: 'Off',
00684         1: 'On'}),
00685     0x0301: ('FocusMode',
00686        {0: 'Single AF',
00687         1: 'Sequential shooting AF',
00688         2: 'Continuous AF',
00689         3: 'Multi AF',
00690         10: 'MF'}),
00691     0x0302: ('FocusProcess',
00692        {0: 'AF Not Used',
00693         1: 'AF Used'}),
00694     0x0303: ('AFSearch',
00695        {0: 'Not Ready',
00696         1: 'Ready'}),
00697     0x0304: ('AFAreas', ),
00698     0x0401: ('FlashExposureCompensation', ),
00699     0x0500: ('WhiteBalance2',
00700        {0: 'Auto',
00701         16: '7500K (Fine Weather with Shade)',
00702         17: '6000K (Cloudy)',
00703         18: '5300K (Fine Weather)',
00704         20: '3000K (Tungsten light)',
00705         21: '3600K (Tungsten light-like)',
00706         33: '6600K (Daylight fluorescent)',
00707         34: '4500K (Neutral white fluorescent)',
00708         35: '4000K (Cool white fluorescent)',
00709         48: '3600K (Tungsten light-like)',
00710         256: 'Custom WB 1',
00711         257: 'Custom WB 2',
00712         258: 'Custom WB 3',
00713         259: 'Custom WB 4',
00714         512: 'Custom WB 5400K',
00715         513: 'Custom WB 2900K',
00716         514: 'Custom WB 8000K', }),
00717     0x0501: ('WhiteBalanceTemperature', ),
00718     0x0502: ('WhiteBalanceBracket', ),
00719     0x0503: ('CustomSaturation', ), # (3 numbers: 1. CS Value, 2. Min, 3. Max)
00720     0x0504: ('ModifiedSaturation',
00721        {0: 'Off',
00722         1: 'CM1 (Red Enhance)',
00723         2: 'CM2 (Green Enhance)',
00724         3: 'CM3 (Blue Enhance)',
00725         4: 'CM4 (Skin Tones)'}),
00726     0x0505: ('ContrastSetting', ), # (3 numbers: 1. Contrast, 2. Min, 3. Max)
00727     0x0506: ('SharpnessSetting', ), # (3 numbers: 1. Sharpness, 2. Min, 3. Max)
00728     0x0507: ('ColorSpace',
00729        {0: 'sRGB',
00730         1: 'Adobe RGB',
00731         2: 'Pro Photo RGB'}),
00732     0x0509: ('SceneMode',
00733        {0: 'Standard',
00734         6: 'Auto',
00735         7: 'Sport',
00736         8: 'Portrait',
00737         9: 'Landscape+Portrait',
00738         10: 'Landscape',
00739         11: 'Night scene',
00740         13: 'Panorama',
00741         16: 'Landscape+Portrait',
00742         17: 'Night+Portrait',
00743         19: 'Fireworks',
00744         20: 'Sunset',
00745         22: 'Macro',
00746         25: 'Documents',
00747         26: 'Museum',
00748         28: 'Beach&Snow',
00749         30: 'Candle',
00750         35: 'Underwater Wide1',
00751         36: 'Underwater Macro',
00752         39: 'High Key',
00753         40: 'Digital Image Stabilization',
00754         44: 'Underwater Wide2',
00755         45: 'Low Key',
00756         46: 'Children',
00757         48: 'Nature Macro'}),
00758     0x050a: ('NoiseReduction',
00759        {0: 'Off',
00760         1: 'Noise Reduction',
00761         2: 'Noise Filter',
00762         3: 'Noise Reduction + Noise Filter',
00763         4: 'Noise Filter (ISO Boost)',
00764         5: 'Noise Reduction + Noise Filter (ISO Boost)'}),
00765     0x050b: ('DistortionCorrection',
00766        {0: 'Off',
00767         1: 'On'}),
00768     0x050c: ('ShadingCompensation',
00769        {0: 'Off',
00770         1: 'On'}),
00771     0x050d: ('CompressionFactor', ),
00772     0x050f: ('Gradation',
00773        {'-1 -1 1': 'Low Key',
00774         '0 -1 1': 'Normal',
00775         '1 -1 1': 'High Key'}),
00776     0x0520: ('PictureMode',
00777        {1: 'Vivid',
00778         2: 'Natural',
00779         3: 'Muted',
00780         256: 'Monotone',
00781         512: 'Sepia'}),
00782     0x0521: ('PictureModeSaturation', ),
00783     0x0522: ('PictureModeHue?', ),
00784     0x0523: ('PictureModeContrast', ),
00785     0x0524: ('PictureModeSharpness', ),
00786     0x0525: ('PictureModeBWFilter',
00787        {0: 'n/a',
00788         1: 'Neutral',
00789         2: 'Yellow',
00790         3: 'Orange',
00791         4: 'Red',
00792         5: 'Green'}),
00793     0x0526: ('PictureModeTone',
00794        {0: 'n/a',
00795         1: 'Neutral',
00796         2: 'Sepia',
00797         3: 'Blue',
00798         4: 'Purple',
00799         5: 'Green'}),
00800     0x0600: ('Sequence', ), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits
00801     0x0601: ('PanoramaMode', ), # (2 numbers: 1. Mode, 2. Shot number)
00802     0x0603: ('ImageQuality2',
00803        {1: 'SQ',
00804         2: 'HQ',
00805         3: 'SHQ',
00806         4: 'RAW'}),
00807     0x0901: ('ManometerReading', ),
00808     }
00809 
00810 
00811 MAKERNOTE_CASIO_TAGS={
00812     0x0001: ('RecordingMode',
00813              {1: 'Single Shutter',
00814               2: 'Panorama',
00815               3: 'Night Scene',
00816               4: 'Portrait',
00817               5: 'Landscape'}),
00818     0x0002: ('Quality',
00819              {1: 'Economy',
00820               2: 'Normal',
00821               3: 'Fine'}),
00822     0x0003: ('FocusingMode',
00823              {2: 'Macro',
00824               3: 'Auto Focus',
00825               4: 'Manual Focus',
00826               5: 'Infinity'}),
00827     0x0004: ('FlashMode',
00828              {1: 'Auto',
00829               2: 'On',
00830               3: 'Off',
00831               4: 'Red Eye Reduction'}),
00832     0x0005: ('FlashIntensity',
00833              {11: 'Weak',
00834               13: 'Normal',
00835               15: 'Strong'}),
00836     0x0006: ('Object Distance', ),
00837     0x0007: ('WhiteBalance',
00838              {1: 'Auto',
00839               2: 'Tungsten',
00840               3: 'Daylight',
00841               4: 'Fluorescent',
00842               5: 'Shade',
00843               129: 'Manual'}),
00844     0x000B: ('Sharpness',
00845              {0: 'Normal',
00846               1: 'Soft',
00847               2: 'Hard'}),
00848     0x000C: ('Contrast',
00849              {0: 'Normal',
00850               1: 'Low',
00851               2: 'High'}),
00852     0x000D: ('Saturation',
00853              {0: 'Normal',
00854               1: 'Low',
00855               2: 'High'}),
00856     0x0014: ('CCDSpeed',
00857              {64: 'Normal',
00858               80: 'Normal',
00859               100: 'High',
00860               125: '+1.0',
00861               244: '+3.0',
00862               250: '+2.0'}),
00863     }
00864 
00865 MAKERNOTE_FUJIFILM_TAGS={
00866     0x0000: ('NoteVersion', make_string),
00867     0x1000: ('Quality', ),
00868     0x1001: ('Sharpness',
00869              {1: 'Soft',
00870               2: 'Soft',
00871               3: 'Normal',
00872               4: 'Hard',
00873               5: 'Hard'}),
00874     0x1002: ('WhiteBalance',
00875              {0: 'Auto',
00876               256: 'Daylight',
00877               512: 'Cloudy',
00878               768: 'DaylightColor-Fluorescent',
00879               769: 'DaywhiteColor-Fluorescent',
00880               770: 'White-Fluorescent',
00881               1024: 'Incandescent',
00882               3840: 'Custom'}),
00883     0x1003: ('Color',
00884              {0: 'Normal',
00885               256: 'High',
00886               512: 'Low'}),
00887     0x1004: ('Tone',
00888              {0: 'Normal',
00889               256: 'High',
00890               512: 'Low'}),
00891     0x1010: ('FlashMode',
00892              {0: 'Auto',
00893               1: 'On',
00894               2: 'Off',
00895               3: 'Red Eye Reduction'}),
00896     0x1011: ('FlashStrength', ),
00897     0x1020: ('Macro',
00898              {0: 'Off',
00899               1: 'On'}),
00900     0x1021: ('FocusMode',
00901              {0: 'Auto',
00902               1: 'Manual'}),
00903     0x1030: ('SlowSync',
00904              {0: 'Off',
00905               1: 'On'}),
00906     0x1031: ('PictureMode',
00907              {0: 'Auto',
00908               1: 'Portrait',
00909               2: 'Landscape',
00910               4: 'Sports',
00911               5: 'Night',
00912               6: 'Program AE',
00913               256: 'Aperture Priority AE',
00914               512: 'Shutter Priority AE',
00915               768: 'Manual Exposure'}),
00916     0x1100: ('MotorOrBracket',
00917              {0: 'Off',
00918               1: 'On'}),
00919     0x1300: ('BlurWarning',
00920              {0: 'Off',
00921               1: 'On'}),
00922     0x1301: ('FocusWarning',
00923              {0: 'Off',
00924               1: 'On'}),
00925     0x1302: ('AEWarning',
00926              {0: 'Off',
00927               1: 'On'}),
00928     }
00929 
00930 MAKERNOTE_CANON_TAGS = {
00931     0x0006: ('ImageType', ),
00932     0x0007: ('FirmwareVersion', ),
00933     0x0008: ('ImageNumber', ),
00934     0x0009: ('OwnerName', ),
00935     }
00936 
00937 # this is in element offset, name, optional value dictionary format
00938 MAKERNOTE_CANON_TAG_0x001 = {
00939     1: ('Macromode',
00940         {1: 'Macro',
00941          2: 'Normal'}),
00942     2: ('SelfTimer', ),
00943     3: ('Quality',
00944         {2: 'Normal',
00945          3: 'Fine',
00946          5: 'Superfine'}),
00947     4: ('FlashMode',
00948         {0: 'Flash Not Fired',
00949          1: 'Auto',
00950          2: 'On',
00951          3: 'Red-Eye Reduction',
00952          4: 'Slow Synchro',
00953          5: 'Auto + Red-Eye Reduction',
00954          6: 'On + Red-Eye Reduction',
00955          16: 'external flash'}),
00956     5: ('ContinuousDriveMode',
00957         {0: 'Single Or Timer',
00958          1: 'Continuous'}),
00959     7: ('FocusMode',
00960         {0: 'One-Shot',
00961          1: 'AI Servo',
00962          2: 'AI Focus',
00963          3: 'MF',
00964          4: 'Single',
00965          5: 'Continuous',
00966          6: 'MF'}),
00967     10: ('ImageSize',
00968          {0: 'Large',
00969           1: 'Medium',
00970           2: 'Small'}),
00971     11: ('EasyShootingMode',
00972          {0: 'Full Auto',
00973           1: 'Manual',
00974           2: 'Landscape',
00975           3: 'Fast Shutter',
00976           4: 'Slow Shutter',
00977           5: 'Night',
00978           6: 'B&W',
00979           7: 'Sepia',
00980           8: 'Portrait',
00981           9: 'Sports',
00982           10: 'Macro/Close-Up',
00983           11: 'Pan Focus'}),
00984     12: ('DigitalZoom',
00985          {0: 'None',
00986           1: '2x',
00987           2: '4x'}),
00988     13: ('Contrast',
00989          {0xFFFF: 'Low',
00990           0: 'Normal',
00991           1: 'High'}),
00992     14: ('Saturation',
00993          {0xFFFF: 'Low',
00994           0: 'Normal',
00995           1: 'High'}),
00996     15: ('Sharpness',
00997          {0xFFFF: 'Low',
00998           0: 'Normal',
00999           1: 'High'}),
01000     16: ('ISO',
01001          {0: 'See ISOSpeedRatings Tag',
01002           15: 'Auto',
01003           16: '50',
01004           17: '100',
01005           18: '200',
01006           19: '400'}),
01007     17: ('MeteringMode',
01008          {3: 'Evaluative',
01009           4: 'Partial',
01010           5: 'Center-weighted'}),
01011     18: ('FocusType',
01012          {0: 'Manual',
01013           1: 'Auto',
01014           3: 'Close-Up (Macro)',
01015           8: 'Locked (Pan Mode)'}),
01016     19: ('AFPointSelected',
01017          {0x3000: 'None (MF)',
01018           0x3001: 'Auto-Selected',
01019           0x3002: 'Right',
01020           0x3003: 'Center',
01021           0x3004: 'Left'}),
01022     20: ('ExposureMode',
01023          {0: 'Easy Shooting',
01024           1: 'Program',
01025           2: 'Tv-priority',
01026           3: 'Av-priority',
01027           4: 'Manual',
01028           5: 'A-DEP'}),
01029     23: ('LongFocalLengthOfLensInFocalUnits', ),
01030     24: ('ShortFocalLengthOfLensInFocalUnits', ),
01031     25: ('FocalUnitsPerMM', ),
01032     28: ('FlashActivity',
01033          {0: 'Did Not Fire',
01034           1: 'Fired'}),
01035     29: ('FlashDetails',
01036          {14: 'External E-TTL',
01037           13: 'Internal Flash',
01038           11: 'FP Sync Used',
01039           7: '2nd("Rear")-Curtain Sync Used',
01040           4: 'FP Sync Enabled'}),
01041     32: ('FocusMode',
01042          {0: 'Single',
01043           1: 'Continuous'}),
01044     }
01045 
01046 MAKERNOTE_CANON_TAG_0x004 = {
01047     7: ('WhiteBalance',
01048         {0: 'Auto',
01049          1: 'Sunny',
01050          2: 'Cloudy',
01051          3: 'Tungsten',
01052          4: 'Fluorescent',
01053          5: 'Flash',
01054          6: 'Custom'}),
01055     9: ('SequenceNumber', ),
01056     14: ('AFPointUsed', ),
01057     15: ('FlashBias',
01058         {0XFFC0: '-2 EV',
01059          0XFFCC: '-1.67 EV',
01060          0XFFD0: '-1.50 EV',
01061          0XFFD4: '-1.33 EV',
01062          0XFFE0: '-1 EV',
01063          0XFFEC: '-0.67 EV',
01064          0XFFF0: '-0.50 EV',
01065          0XFFF4: '-0.33 EV',
01066          0X0000: '0 EV',
01067          0X000C: '0.33 EV',
01068          0X0010: '0.50 EV',
01069          0X0014: '0.67 EV',
01070          0X0020: '1 EV',
01071          0X002C: '1.33 EV',
01072          0X0030: '1.50 EV',
01073          0X0034: '1.67 EV',
01074          0X0040: '2 EV'}),
01075     19: ('SubjectDistance', ),
01076     }
01077 
01078 # extract multibyte integer in Motorola format (little endian)
01079 def s2n_motorola(str):
01080     x = 0
01081     for c in str:
01082         x = (x << 8) | ord(c)
01083     return x
01084 
01085 # extract multibyte integer in Intel format (big endian)
01086 def s2n_intel(str):
01087     x = 0
01088     y = 0L
01089     for c in str:
01090         x = x | (ord(c) << y)
01091         y = y + 8
01092     return x
01093 
01094 # ratio object that eventually will be able to reduce itself to lowest
01095 # common denominator for printing
01096 def gcd(a, b):
01097     if b == 0:
01098         return a
01099     else:
01100         return gcd(b, a % b)
01101 
01102 class Ratio:
01103     def __init__(self, num, den):
01104         self.num = num
01105         self.den = den
01106 
01107     def __repr__(self):
01108         self.reduce_size()
01109         if self.den == 1:
01110             return str(self.num)
01111         return '%d/%d' % (self.num, self.den)
01112 
01113     def reduce_size(self):
01114         div = gcd(self.num, self.den)
01115         if div > 1:
01116             self.num = self.num / div
01117             self.den = self.den / div
01118 
01119 # for ease of dealing with tags
01120 class IFD_Tag:
01121     def __init__(self, printable, tag, field_type, values, field_offset,
01122                  field_length):
01123         # printable version of data
01124         self.printable = printable
01125         # tag ID number
01126         self.tag = tag
01127         # field type as index into FIELD_TYPES
01128         self.field_type = field_type
01129         # offset of start of field in bytes from beginning of IFD
01130         self.field_offset = field_offset
01131         # length of data field in bytes
01132         self.field_length = field_length
01133         # either a string or array of data items
01134         self.values = values
01135 
01136     def __str__(self):
01137         return self.printable
01138 
01139     def __repr__(self):
01140         return '(0x%04X) %s=%s @ %d' % (self.tag,
01141                                         FIELD_TYPES[self.field_type][2],
01142                                         self.printable,
01143                                         self.field_offset)
01144 
01145 # class that handles an EXIF header
01146 class EXIF_header:
01147     def __init__(self, file, endian, offset, fake_exif, debug=0):
01148         self.file = file
01149         self.endian = endian
01150         self.offset = offset
01151         self.fake_exif = fake_exif
01152         self.debug = debug
01153         self.tags = {}
01154 
01155     # convert slice to integer, based on sign and endian flags
01156     # usually this offset is assumed to be relative to the beginning of the
01157     # start of the EXIF information.  For some cameras that use relative tags,
01158     # this offset may be relative to some other starting point.
01159     def s2n(self, offset, length, signed=0):
01160         self.file.seek(self.offset+offset)
01161         slice=self.file.read(length)
01162         if self.endian == 'I':
01163             val=s2n_intel(slice)
01164         else:
01165             val=s2n_motorola(slice)
01166         # Sign extension ?
01167         if signed:
01168             msb=1L << (8*length-1)
01169             if val & msb:
01170                 val=val-(msb << 1)
01171         return val
01172 
01173     # convert offset to string
01174     def n2s(self, offset, length):
01175         s = ''
01176         for dummy in range(length):
01177             if self.endian == 'I':
01178                 s = s + chr(offset & 0xFF)
01179             else:
01180                 s = chr(offset & 0xFF) + s
01181             offset = offset >> 8
01182         return s
01183 
01184     # return first IFD
01185     def first_IFD(self):
01186         return self.s2n(4, 4)
01187 
01188     # return pointer to next IFD
01189     def next_IFD(self, ifd):
01190         entries=self.s2n(ifd, 2)
01191         return self.s2n(ifd+2+12*entries, 4)
01192 
01193     # return list of IFDs in header
01194     def list_IFDs(self):
01195         i=self.first_IFD()
01196         a=[]
01197         while i:
01198             a.append(i)
01199             i=self.next_IFD(i)
01200         return a
01201 
01202     # return list of entries in this IFD
01203     def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0, name='UNDEF'):
01204         entries=self.s2n(ifd, 2)
01205         for i in range(entries):
01206             # entry is index of start of this IFD in the file
01207             entry = ifd + 2 + 12 * i
01208             tag = self.s2n(entry, 2)
01209 
01210             # ignore certain tags for faster processing
01211             if not (tag in IGNORE_TAGS and not detailed):
01212                 # get tag name.  We do it early to make debugging easier
01213                 tag_entry = dict.get(tag)
01214                 if tag_entry:
01215                     tag_name = tag_entry[0]
01216                 else:
01217                     tag_name = 'Tag 0x%04X' % tag
01218 
01219                 field_type = self.s2n(entry + 2, 2)
01220                 if not 0 < field_type < len(FIELD_TYPES):
01221                     # unknown field type
01222                     raise ValueError('unknown type %d in tag 0x%04X' % (field_type, tag))
01223                 typelen = FIELD_TYPES[field_type][0]
01224                 count = self.s2n(entry + 4, 4)
01225                 offset = entry + 8
01226                 if count * typelen > 4:
01227                     # offset is not the value; it's a pointer to the value
01228                     # if relative we set things up so s2n will seek to the right
01229                     # place when it adds self.offset.  Note that this 'relative'
01230                     # is for the Nikon type 3 makernote.  Other cameras may use
01231                     # other relative offsets, which would have to be computed here
01232                     # slightly differently.
01233                     if relative:
01234                         tmp_offset = self.s2n(offset, 4)
01235                         offset = tmp_offset + ifd - self.offset + 4
01236                         if self.fake_exif:
01237                             offset = offset + 18
01238                     else:
01239                         offset = self.s2n(offset, 4)
01240                 field_offset = offset
01241                 if field_type == 2:
01242                     # special case: null-terminated ASCII string
01243                     if count != 0:
01244                         self.file.seek(self.offset + offset)
01245                         values = self.file.read(count)
01246                         values = values.strip().replace('\x00', '')
01247                     else:
01248                         values = ''
01249                 else:
01250                     values = []
01251                     signed = (field_type in [6, 8, 9, 10])
01252                     for dummy in range(count):
01253                         if field_type in (5, 10):
01254                             # a ratio
01255                             value = Ratio(self.s2n(offset, 4, signed),
01256                                           self.s2n(offset + 4, 4, signed))
01257                         else:
01258                             value = self.s2n(offset, typelen, signed)
01259                         values.append(value)
01260                         offset = offset + typelen
01261                 # now "values" is either a string or an array
01262                 if count == 1 and field_type != 2:
01263                     printable=str(values[0])
01264                 else:
01265                     printable=str(values)
01266                 # compute printable version of values
01267                 if tag_entry:
01268                     if len(tag_entry) != 1:
01269                         # optional 2nd tag element is present
01270                         if callable(tag_entry[1]):
01271                             # call mapping function
01272                             printable = tag_entry[1](values)
01273                         else:
01274                             printable = ''
01275                             for i in values:
01276                                 # use lookup table for this tag
01277                                 printable += tag_entry[1].get(i, repr(i))
01278 
01279                 self.tags[ifd_name + ' ' + tag_name] = IFD_Tag(printable, tag,
01280                                                           field_type,
01281                                                           values, field_offset,
01282                                                           count * typelen)
01283                 if self.debug:
01284                     print ' debug:   %s: %s' % (tag_name,
01285                                                 repr(self.tags[ifd_name + ' ' + tag_name]))
01286 
01287             if tag_name == name:
01288                 break
01289 
01290     # extract uncompressed TIFF thumbnail (like pulling teeth)
01291     # we take advantage of the pre-existing layout in the thumbnail IFD as
01292     # much as possible
01293     def extract_TIFF_thumbnail(self, thumb_ifd):
01294         entries = self.s2n(thumb_ifd, 2)
01295         # this is header plus offset to IFD ...
01296         if self.endian == 'M':
01297             tiff = 'MM\x00*\x00\x00\x00\x08'
01298         else:
01299             tiff = 'II*\x00\x08\x00\x00\x00'
01300         # ... plus thumbnail IFD data plus a null "next IFD" pointer
01301         self.file.seek(self.offset+thumb_ifd)
01302         tiff += self.file.read(entries*12+2)+'\x00\x00\x00\x00'
01303 
01304         # fix up large value offset pointers into data area
01305         for i in range(entries):
01306             entry = thumb_ifd + 2 + 12 * i
01307             tag = self.s2n(entry, 2)
01308             field_type = self.s2n(entry+2, 2)
01309             typelen = FIELD_TYPES[field_type][0]
01310             count = self.s2n(entry+4, 4)
01311             oldoff = self.s2n(entry+8, 4)
01312             # start of the 4-byte pointer area in entry
01313             ptr = i * 12 + 18
01314             # remember strip offsets location
01315             if tag == 0x0111:
01316                 strip_off = ptr
01317                 strip_len = count * typelen
01318             # is it in the data area?
01319             if count * typelen > 4:
01320                 # update offset pointer (nasty "strings are immutable" crap)
01321                 # should be able to say "tiff[ptr:ptr+4]=newoff"
01322                 newoff = len(tiff)
01323                 tiff = tiff[:ptr] + self.n2s(newoff, 4) + tiff[ptr+4:]
01324                 # remember strip offsets location
01325                 if tag == 0x0111:
01326                     strip_off = newoff
01327                     strip_len = 4
01328                 # get original data and store it
01329                 self.file.seek(self.offset + oldoff)
01330                 tiff += self.file.read(count * typelen)
01331 
01332         # add pixel strips and update strip offset info
01333         old_offsets = self.tags['Thumbnail StripOffsets'].values
01334         old_counts = self.tags['Thumbnail StripByteCounts'].values
01335         for i in range(len(old_offsets)):
01336             # update offset pointer (more nasty "strings are immutable" crap)
01337             offset = self.n2s(len(tiff), strip_len)
01338             tiff = tiff[:strip_off] + offset + tiff[strip_off + strip_len:]
01339             strip_off += strip_len
01340             # add pixel strip to end
01341             self.file.seek(self.offset + old_offsets[i])
01342             tiff += self.file.read(old_counts[i])
01343 
01344         self.tags['TIFFThumbnail'] = tiff
01345 
01346     # decode all the camera-specific MakerNote formats
01347 
01348     # Note is the data that comprises this MakerNote.  The MakerNote will
01349     # likely have pointers in it that point to other parts of the file.  We'll
01350     # use self.offset as the starting point for most of those pointers, since
01351     # they are relative to the beginning of the file.
01352     #
01353     # If the MakerNote is in a newer format, it may use relative addressing
01354     # within the MakerNote.  In that case we'll use relative addresses for the
01355     # pointers.
01356     #
01357     # As an aside: it's not just to be annoying that the manufacturers use
01358     # relative offsets.  It's so that if the makernote has to be moved by the
01359     # picture software all of the offsets don't have to be adjusted.  Overall,
01360     # this is probably the right strategy for makernotes, though the spec is
01361     # ambiguous.  (The spec does not appear to imagine that makernotes would
01362     # follow EXIF format internally.  Once they did, it's ambiguous whether
01363     # the offsets should be from the header at the start of all the EXIF info,
01364     # or from the header at the start of the makernote.)
01365     def decode_maker_note(self):
01366         note = self.tags['EXIF MakerNote']
01367         make = self.tags['Image Make'].printable
01368         # model = self.tags['Image Model'].printable # unused
01369 
01370         # Nikon
01371         # The maker note usually starts with the word Nikon, followed by the
01372         # type of the makernote (1 or 2, as a short).  If the word Nikon is
01373         # not at the start of the makernote, it's probably type 2, since some
01374         # cameras work that way.
01375         if make in ('NIKON', 'NIKON CORPORATION'):
01376             if note.values[0:7] == [78, 105, 107, 111, 110, 0, 1]:
01377                 if self.debug:
01378                     print "Looks like a type 1 Nikon MakerNote."
01379                 self.dump_IFD(note.field_offset+8, 'MakerNote',
01380                               dict=MAKERNOTE_NIKON_OLDER_TAGS)
01381             elif note.values[0:7] == [78, 105, 107, 111, 110, 0, 2]:
01382                 if self.debug:
01383                     print "Looks like a labeled type 2 Nikon MakerNote"
01384                 if note.values[12:14] != [0, 42] and note.values[12:14] != [42L, 0L]:
01385                     raise ValueError("Missing marker tag '42' in MakerNote.")
01386                 # skip the Makernote label and the TIFF header
01387                 self.dump_IFD(note.field_offset+10+8, 'MakerNote',
01388                               dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1)
01389             else:
01390                 # E99x or D1
01391                 if self.debug:
01392                     print "Looks like an unlabeled type 2 Nikon MakerNote"
01393                 self.dump_IFD(note.field_offset, 'MakerNote',
01394                               dict=MAKERNOTE_NIKON_NEWER_TAGS)
01395             return
01396 
01397         # Olympus
01398         if make.startswith('OLYMPUS'):
01399             self.dump_IFD(note.field_offset+8, 'MakerNote',
01400                           dict=MAKERNOTE_OLYMPUS_TAGS)
01401             # TODO
01402             #for i in (('MakerNote Tag 0x2020', MAKERNOTE_OLYMPUS_TAG_0x2020),):
01403             #    self.decode_olympus_tag(self.tags[i[0]].values, i[1])
01404             #return
01405 
01406         # Casio
01407         if make == 'Casio':
01408             self.dump_IFD(note.field_offset, 'MakerNote',
01409                           dict=MAKERNOTE_CASIO_TAGS)
01410             return
01411 
01412         # Fujifilm
01413         if make == 'FUJIFILM':
01414             # bug: everything else is "Motorola" endian, but the MakerNote
01415             # is "Intel" endian
01416             endian = self.endian
01417             self.endian = 'I'
01418             # bug: IFD offsets are from beginning of MakerNote, not
01419             # beginning of file header
01420             offset = self.offset
01421             self.offset += note.field_offset
01422             # process note with bogus values (note is actually at offset 12)
01423             self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS)
01424             # reset to correct values
01425             self.endian = endian
01426             self.offset = offset
01427             return
01428 
01429         # Canon
01430         if make == 'Canon':
01431             self.dump_IFD(note.field_offset, 'MakerNote',
01432                           dict=MAKERNOTE_CANON_TAGS)
01433             for i in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001),
01434                       ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004)):
01435                 self.canon_decode_tag(self.tags[i[0]].values, i[1])
01436             return
01437 
01438     # decode Olympus MakerNote tag based on offset within tag
01439     def olympus_decode_tag(self, value, dict):
01440         pass
01441 
01442     # decode Canon MakerNote tag based on offset within tag
01443     # see http://www.burren.cx/david/canon.html by David Burren
01444     def canon_decode_tag(self, value, dict):
01445         for i in range(1, len(value)):
01446             x=dict.get(i, ('Unknown', ))
01447             if self.debug:
01448                 print i, x
01449             name=x[0]
01450             if len(x) > 1:
01451                 val=x[1].get(value[i], 'Unknown')
01452             else:
01453                 val=value[i]
01454             # it's not a real IFD Tag but we fake one to make everybody
01455             # happy. this will have a "proprietary" type
01456             self.tags['MakerNote '+name]=IFD_Tag(str(val), None, 0, None,
01457                                                  None, None)
01458 
01459 # process an image file (expects an open file object)
01460 # this is the function that has to deal with all the arbitrary nasty bits
01461 # of the EXIF standard
01462 def process_file(f, name='UNDEF', details=True, debug=0):
01463     # yah it's cheesy...
01464     global detailed
01465     detailed = details
01466 
01467     # by default do not fake an EXIF beginning
01468     fake_exif = 0
01469 
01470     # determine whether it's a JPEG or TIFF
01471     data = f.read(12)
01472     if data[0:4] in ['II*\x00', 'MM\x00*']:
01473         # it's a TIFF file
01474         f.seek(0)
01475         endian = f.read(1)
01476         f.read(1)
01477         offset = 0
01478     elif data[0:2] == '\xFF\xD8':
01479         # it's a JPEG file
01480         while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'):
01481             length = ord(data[4])*256+ord(data[5])
01482             f.read(length-8)
01483             # fake an EXIF beginning of file
01484             data = '\xFF\x00'+f.read(10)
01485             fake_exif = 1
01486         if data[2] == '\xFF' and data[6:10] == 'Exif':
01487             # detected EXIF header
01488             offset = f.tell()
01489             endian = f.read(1)
01490         else:
01491             # no EXIF information
01492             return {}
01493     else:
01494         # file format not recognized
01495         return {}
01496 
01497     # deal with the EXIF info we found
01498     if debug:
01499         print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format'
01500     hdr = EXIF_header(f, endian, offset, fake_exif, debug)
01501     ifd_list = hdr.list_IFDs()
01502     ctr = 0
01503     for i in ifd_list:
01504         if ctr == 0:
01505             IFD_name = 'Image'
01506         elif ctr == 1:
01507             IFD_name = 'Thumbnail'
01508             thumb_ifd = i
01509         else:
01510             IFD_name = 'IFD %d' % ctr
01511         if debug:
01512             print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i)
01513         hdr.dump_IFD(i, IFD_name, name=name)
01514         # EXIF IFD
01515         exif_off = hdr.tags.get(IFD_name+' ExifOffset')
01516         if exif_off:
01517             if debug:
01518                 print ' EXIF SubIFD at offset %d:' % exif_off.values[0]
01519             hdr.dump_IFD(exif_off.values[0], 'EXIF', name=name)
01520             # Interoperability IFD contained in EXIF IFD
01521             intr_off = hdr.tags.get('EXIF SubIFD InteroperabilityOffset')
01522             if intr_off:
01523                 if debug:
01524                     print ' EXIF Interoperability SubSubIFD at offset %d:' \
01525                           % intr_off.values[0]
01526                 hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability',
01527                              dict=INTR_TAGS, name=name)
01528         # GPS IFD
01529         gps_off = hdr.tags.get(IFD_name+' GPSInfo')
01530         if gps_off:
01531             if debug:
01532                 print ' GPS SubIFD at offset %d:' % gps_off.values[0]
01533             hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS, name=name)
01534         ctr += 1
01535 
01536     # extract uncompressed TIFF thumbnail
01537     thumb = hdr.tags.get('Thumbnail Compression')
01538     if thumb and thumb.printable == 'Uncompressed TIFF':
01539         hdr.extract_TIFF_thumbnail(thumb_ifd)
01540 
01541     # JPEG thumbnail (thankfully the JPEG data is stored as a unit)
01542     thumb_off = hdr.tags.get('Thumbnail JPEGInterchangeFormat')
01543     if thumb_off:
01544         f.seek(offset+thumb_off.values[0])
01545         size = hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0]
01546         hdr.tags['JPEGThumbnail'] = f.read(size)
01547 
01548     # deal with MakerNote contained in EXIF IFD
01549     if 'EXIF MakerNote' in hdr.tags and detailed:
01550         hdr.decode_maker_note()
01551 
01552     # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
01553     # since it's not allowed in a uncompressed TIFF IFD
01554     if 'JPEGThumbnail' not in hdr.tags:
01555         thumb_off=hdr.tags.get('MakerNote JPEGThumbnail')
01556         if thumb_off:
01557             f.seek(offset+thumb_off.values[0])
01558             hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length)
01559 
01560     return hdr.tags
01561 
01562 
01563 # library test/debug function (dump given files)
01564 if __name__ == '__main__':
01565     import sys
01566 
01567     if len(sys.argv) < 2:
01568         print "Usage: %s [options] file1 [file2 ...]\n\nOptions:\n-q --quick : do not process MakerNotes for faster processing\n" % sys.argv[0]
01569         sys.exit(0)
01570 
01571     if sys.argv[1] == "-q" or sys.argv[1] == "--quick":
01572         fileNamesStart = 2
01573         detailed = False
01574     else:
01575         fileNamesStart = 1
01576         detailed = True
01577 
01578     for filename in sys.argv[fileNamesStart:]:
01579         try:
01580             file=open(filename, 'rb')
01581         except:
01582             print filename, 'unreadable'
01583             print
01584             continue
01585         print filename + ':'
01586         # data=process_file(file, debug=1) # with debug info and all tags
01587         data=process_file(file, details=detailed)
01588         if not data:
01589             print 'No EXIF information found'
01590             continue
01591 
01592         x=data.keys()
01593         x.sort()
01594         for i in x:
01595             if i in ('JPEGThumbnail', 'TIFFThumbnail'):
01596                 continue
01597             try:
01598                 print '   %s (%s): %s' % \
01599                       (i, FIELD_TYPES[data[i].field_type][2], data[i].printable)
01600             except:
01601                 print 'error', i, '"', data[i], '"'
01602         if 'JPEGThumbnail' in data:
01603             print 'File has JPEG thumbnail'
01604         print
01605