web/lib/photologue/utils/EXIF.py
changeset 5 10b1f6d8a5d2
equal deleted inserted replaced
4:b77683731f25 5:10b1f6d8a5d2
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 #
       
     4 # Library to extract EXIF information from digital camera image files
       
     5 # http://sourceforge.net/projects/exif-py/
       
     6 #
       
     7 # VERSION 1.0.7
       
     8 #
       
     9 # To use this library call with:
       
    10 #    f = open(path_name, 'rb')
       
    11 #    tags = EXIF.process_file(f)
       
    12 #
       
    13 # To ignore makerNote tags, pass the -q or --quick
       
    14 # command line arguments, or as
       
    15 #    f = open(path_name, 'rb')
       
    16 #    tags = EXIF.process_file(f, details=False)
       
    17 #
       
    18 # To stop processing after a certain tag is retrieved,
       
    19 # pass the -t TAG or --stop-tag TAG argument, or as
       
    20 #    f = open(path_name, 'rb')
       
    21 #    tags = EXIF.process_file(f, stop_tag='TAG')
       
    22 #
       
    23 # where TAG is a valid tag name, ex 'DateTimeOriginal'
       
    24 #
       
    25 # These are useful when you are retrieving a large list of images
       
    26 #
       
    27 # Returned tags will be a dictionary mapping names of EXIF tags to their
       
    28 # values in the file named by path_name.  You can process the tags
       
    29 # as you wish.  In particular, you can iterate through all the tags with:
       
    30 #     for tag in tags.keys():
       
    31 #         if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename',
       
    32 #                        'EXIF MakerNote'):
       
    33 #             print "Key: %s, value %s" % (tag, tags[tag])
       
    34 # (This code uses the if statement to avoid printing out a few of the
       
    35 # tags that tend to be long or boring.)
       
    36 #
       
    37 # The tags dictionary will include keys for all of the usual EXIF
       
    38 # tags, and will also include keys for Makernotes used by some
       
    39 # cameras, for which we have a good specification.
       
    40 #
       
    41 # Note that the dictionary keys are the IFD name followed by the
       
    42 # tag name. For example:
       
    43 # 'EXIF DateTimeOriginal', 'Image Orientation', 'MakerNote FocusMode'
       
    44 #
       
    45 # Copyright (c) 2002-2007 Gene Cash All rights reserved
       
    46 # Copyright (c) 2007 Ianaré Sévi All rights reserved
       
    47 #
       
    48 # Redistribution and use in source and binary forms, with or without
       
    49 # modification, are permitted provided that the following conditions
       
    50 # are met:
       
    51 #
       
    52 #  1. Redistributions of source code must retain the above copyright
       
    53 #     notice, this list of conditions and the following disclaimer.
       
    54 #
       
    55 #  2. Redistributions in binary form must reproduce the above
       
    56 #     copyright notice, this list of conditions and the following
       
    57 #     disclaimer in the documentation and/or other materials provided
       
    58 #     with the distribution.
       
    59 #
       
    60 #  3. Neither the name of the authors nor the names of its contributors
       
    61 #     may be used to endorse or promote products derived from this
       
    62 #     software without specific prior written permission.
       
    63 #
       
    64 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
       
    65 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
       
    66 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
       
    67 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
       
    68 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
       
    69 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
       
    70 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
       
    71 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
       
    72 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       
    73 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
       
    74 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    75 #
       
    76 #
       
    77 # ----- See 'changes.txt' file for all contributors and changes ----- #
       
    78 #
       
    79 
       
    80 
       
    81 # Don't throw an exception when given an out of range character.
       
    82 def make_string(seq):
       
    83     str = ""
       
    84     for c in seq:
       
    85         # Screen out non-printing characters
       
    86         if 32 <= c and c < 256:
       
    87             str += chr(c)
       
    88     # If no printing chars
       
    89     if not str:
       
    90         return seq
       
    91     return str
       
    92 
       
    93 # Special version to deal with the code in the first 8 bytes of a user comment.
       
    94 def make_string_uc(seq):
       
    95     code = seq[0:8]
       
    96     seq = seq[8:]
       
    97     # Of course, this is only correct if ASCII, and the standard explicitly
       
    98     # allows JIS and Unicode.
       
    99     return make_string(seq)
       
   100 
       
   101 # field type descriptions as (length, abbreviation, full name) tuples
       
   102 FIELD_TYPES = (
       
   103     (0, 'X', 'Proprietary'), # no such type
       
   104     (1, 'B', 'Byte'),
       
   105     (1, 'A', 'ASCII'),
       
   106     (2, 'S', 'Short'),
       
   107     (4, 'L', 'Long'),
       
   108     (8, 'R', 'Ratio'),
       
   109     (1, 'SB', 'Signed Byte'),
       
   110     (1, 'U', 'Undefined'),
       
   111     (2, 'SS', 'Signed Short'),
       
   112     (4, 'SL', 'Signed Long'),
       
   113     (8, 'SR', 'Signed Ratio'),
       
   114     )
       
   115 
       
   116 # dictionary of main EXIF tag names
       
   117 # first element of tuple is tag name, optional second element is
       
   118 # another dictionary giving names to values
       
   119 EXIF_TAGS = {
       
   120     0x0100: ('ImageWidth', ),
       
   121     0x0101: ('ImageLength', ),
       
   122     0x0102: ('BitsPerSample', ),
       
   123     0x0103: ('Compression',
       
   124              {1: 'Uncompressed TIFF',
       
   125               6: 'JPEG Compressed'}),
       
   126     0x0106: ('PhotometricInterpretation', ),
       
   127     0x010A: ('FillOrder', ),
       
   128     0x010D: ('DocumentName', ),
       
   129     0x010E: ('ImageDescription', ),
       
   130     0x010F: ('Make', ),
       
   131     0x0110: ('Model', ),
       
   132     0x0111: ('StripOffsets', ),
       
   133     0x0112: ('Orientation',
       
   134              {1: 'Horizontal (normal)',
       
   135               2: 'Mirrored horizontal',
       
   136               3: 'Rotated 180',
       
   137               4: 'Mirrored vertical',
       
   138               5: 'Mirrored horizontal then rotated 90 CCW',
       
   139               6: 'Rotated 90 CW',
       
   140               7: 'Mirrored horizontal then rotated 90 CW',
       
   141               8: 'Rotated 90 CCW'}),
       
   142     0x0115: ('SamplesPerPixel', ),
       
   143     0x0116: ('RowsPerStrip', ),
       
   144     0x0117: ('StripByteCounts', ),
       
   145     0x011A: ('XResolution', ),
       
   146     0x011B: ('YResolution', ),
       
   147     0x011C: ('PlanarConfiguration', ),
       
   148     0x0128: ('ResolutionUnit',
       
   149              {1: 'Not Absolute',
       
   150               2: 'Pixels/Inch',
       
   151               3: 'Pixels/Centimeter'}),
       
   152     0x012D: ('TransferFunction', ),
       
   153     0x0131: ('Software', ),
       
   154     0x0132: ('DateTime', ),
       
   155     0x013B: ('Artist', ),
       
   156     0x013E: ('WhitePoint', ),
       
   157     0x013F: ('PrimaryChromaticities', ),
       
   158     0x0156: ('TransferRange', ),
       
   159     0x0200: ('JPEGProc', ),
       
   160     0x0201: ('JPEGInterchangeFormat', ),
       
   161     0x0202: ('JPEGInterchangeFormatLength', ),
       
   162     0x0211: ('YCbCrCoefficients', ),
       
   163     0x0212: ('YCbCrSubSampling', ),
       
   164     0x0213: ('YCbCrPositioning', ),
       
   165     0x0214: ('ReferenceBlackWhite', ),
       
   166     0x828D: ('CFARepeatPatternDim', ),
       
   167     0x828E: ('CFAPattern', ),
       
   168     0x828F: ('BatteryLevel', ),
       
   169     0x8298: ('Copyright', ),
       
   170     0x829A: ('ExposureTime', ),
       
   171     0x829D: ('FNumber', ),
       
   172     0x83BB: ('IPTC/NAA', ),
       
   173     0x8769: ('ExifOffset', ),
       
   174     0x8773: ('InterColorProfile', ),
       
   175     0x8822: ('ExposureProgram',
       
   176              {0: 'Unidentified',
       
   177               1: 'Manual',
       
   178               2: 'Program Normal',
       
   179               3: 'Aperture Priority',
       
   180               4: 'Shutter Priority',
       
   181               5: 'Program Creative',
       
   182               6: 'Program Action',
       
   183               7: 'Portrait Mode',
       
   184               8: 'Landscape Mode'}),
       
   185     0x8824: ('SpectralSensitivity', ),
       
   186     0x8825: ('GPSInfo', ),
       
   187     0x8827: ('ISOSpeedRatings', ),
       
   188     0x8828: ('OECF', ),
       
   189     # print as string
       
   190     0x9000: ('ExifVersion', make_string),
       
   191     0x9003: ('DateTimeOriginal', ),
       
   192     0x9004: ('DateTimeDigitized', ),
       
   193     0x9101: ('ComponentsConfiguration',
       
   194              {0: '',
       
   195               1: 'Y',
       
   196               2: 'Cb',
       
   197               3: 'Cr',
       
   198               4: 'Red',
       
   199               5: 'Green',
       
   200               6: 'Blue'}),
       
   201     0x9102: ('CompressedBitsPerPixel', ),
       
   202     0x9201: ('ShutterSpeedValue', ),
       
   203     0x9202: ('ApertureValue', ),
       
   204     0x9203: ('BrightnessValue', ),
       
   205     0x9204: ('ExposureBiasValue', ),
       
   206     0x9205: ('MaxApertureValue', ),
       
   207     0x9206: ('SubjectDistance', ),
       
   208     0x9207: ('MeteringMode',
       
   209              {0: 'Unidentified',
       
   210               1: 'Average',
       
   211               2: 'CenterWeightedAverage',
       
   212               3: 'Spot',
       
   213               4: 'MultiSpot'}),
       
   214     0x9208: ('LightSource',
       
   215              {0: 'Unknown',
       
   216               1: 'Daylight',
       
   217               2: 'Fluorescent',
       
   218               3: 'Tungsten',
       
   219               10: 'Flash',
       
   220               17: 'Standard Light A',
       
   221               18: 'Standard Light B',
       
   222               19: 'Standard Light C',
       
   223               20: 'D55',
       
   224               21: 'D65',
       
   225               22: 'D75',
       
   226               255: 'Other'}),
       
   227     0x9209: ('Flash', {0: 'No',
       
   228                        1: 'Fired',
       
   229                        5: 'Fired (?)', # no return sensed
       
   230                        7: 'Fired (!)', # return sensed
       
   231                        9: 'Fill Fired',
       
   232                        13: 'Fill Fired (?)',
       
   233                        15: 'Fill Fired (!)',
       
   234                        16: 'Off',
       
   235                        24: 'Auto Off',
       
   236                        25: 'Auto Fired',
       
   237                        29: 'Auto Fired (?)',
       
   238                        31: 'Auto Fired (!)',
       
   239                        32: 'Not Available'}),
       
   240     0x920A: ('FocalLength', ),
       
   241     0x9214: ('SubjectArea', ),
       
   242     0x927C: ('MakerNote', ),
       
   243     # print as string
       
   244     0x9286: ('UserComment', make_string_uc),  # First 8 bytes gives coding system e.g. ASCII vs. JIS vs Unicode
       
   245     0x9290: ('SubSecTime', ),
       
   246     0x9291: ('SubSecTimeOriginal', ),
       
   247     0x9292: ('SubSecTimeDigitized', ),
       
   248     # print as string
       
   249     0xA000: ('FlashPixVersion', make_string),
       
   250     0xA001: ('ColorSpace', ),
       
   251     0xA002: ('ExifImageWidth', ),
       
   252     0xA003: ('ExifImageLength', ),
       
   253     0xA005: ('InteroperabilityOffset', ),
       
   254     0xA20B: ('FlashEnergy', ),               # 0x920B in TIFF/EP
       
   255     0xA20C: ('SpatialFrequencyResponse', ),  # 0x920C    -  -
       
   256     0xA20E: ('FocalPlaneXResolution', ),     # 0x920E    -  -
       
   257     0xA20F: ('FocalPlaneYResolution', ),     # 0x920F    -  -
       
   258     0xA210: ('FocalPlaneResolutionUnit', ),  # 0x9210    -  -
       
   259     0xA214: ('SubjectLocation', ),           # 0x9214    -  -
       
   260     0xA215: ('ExposureIndex', ),             # 0x9215    -  -
       
   261     0xA217: ('SensingMethod', ),             # 0x9217    -  -
       
   262     0xA300: ('FileSource',
       
   263              {3: 'Digital Camera'}),
       
   264     0xA301: ('SceneType',
       
   265              {1: 'Directly Photographed'}),
       
   266     0xA302: ('CVAPattern', ),
       
   267     0xA401: ('CustomRendered', ),
       
   268     0xA402: ('ExposureMode',
       
   269              {0: 'Auto Exposure',
       
   270               1: 'Manual Exposure',
       
   271               2: 'Auto Bracket'}),
       
   272     0xA403: ('WhiteBalance',
       
   273              {0: 'Auto',
       
   274               1: 'Manual'}),
       
   275     0xA404: ('DigitalZoomRatio', ),
       
   276     0xA405: ('FocalLengthIn35mm', ),
       
   277     0xA406: ('SceneCaptureType', ),
       
   278     0xA407: ('GainControl', ),
       
   279     0xA408: ('Contrast', ),
       
   280     0xA409: ('Saturation', ),
       
   281     0xA40A: ('Sharpness', ),
       
   282     0xA40C: ('SubjectDistanceRange', ),
       
   283     }
       
   284 
       
   285 # interoperability tags
       
   286 INTR_TAGS = {
       
   287     0x0001: ('InteroperabilityIndex', ),
       
   288     0x0002: ('InteroperabilityVersion', ),
       
   289     0x1000: ('RelatedImageFileFormat', ),
       
   290     0x1001: ('RelatedImageWidth', ),
       
   291     0x1002: ('RelatedImageLength', ),
       
   292     }
       
   293 
       
   294 # GPS tags (not used yet, haven't seen camera with GPS)
       
   295 GPS_TAGS = {
       
   296     0x0000: ('GPSVersionID', ),
       
   297     0x0001: ('GPSLatitudeRef', ),
       
   298     0x0002: ('GPSLatitude', ),
       
   299     0x0003: ('GPSLongitudeRef', ),
       
   300     0x0004: ('GPSLongitude', ),
       
   301     0x0005: ('GPSAltitudeRef', ),
       
   302     0x0006: ('GPSAltitude', ),
       
   303     0x0007: ('GPSTimeStamp', ),
       
   304     0x0008: ('GPSSatellites', ),
       
   305     0x0009: ('GPSStatus', ),
       
   306     0x000A: ('GPSMeasureMode', ),
       
   307     0x000B: ('GPSDOP', ),
       
   308     0x000C: ('GPSSpeedRef', ),
       
   309     0x000D: ('GPSSpeed', ),
       
   310     0x000E: ('GPSTrackRef', ),
       
   311     0x000F: ('GPSTrack', ),
       
   312     0x0010: ('GPSImgDirectionRef', ),
       
   313     0x0011: ('GPSImgDirection', ),
       
   314     0x0012: ('GPSMapDatum', ),
       
   315     0x0013: ('GPSDestLatitudeRef', ),
       
   316     0x0014: ('GPSDestLatitude', ),
       
   317     0x0015: ('GPSDestLongitudeRef', ),
       
   318     0x0016: ('GPSDestLongitude', ),
       
   319     0x0017: ('GPSDestBearingRef', ),
       
   320     0x0018: ('GPSDestBearing', ),
       
   321     0x0019: ('GPSDestDistanceRef', ),
       
   322     0x001A: ('GPSDestDistance', ),
       
   323     }
       
   324 
       
   325 # Ignore these tags when quick processing
       
   326 # 0x927C is MakerNote Tags
       
   327 # 0x9286 is user comment
       
   328 IGNORE_TAGS=(0x9286, 0x927C)
       
   329 
       
   330 # http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp
       
   331 def nikon_ev_bias(seq):
       
   332     # First digit seems to be in steps of 1/6 EV.
       
   333     # Does the third value mean the step size?  It is usually 6,
       
   334     # but it is 12 for the ExposureDifference.
       
   335     #
       
   336     if seq == [252, 1, 6, 0]:
       
   337         return "-2/3 EV"
       
   338     if seq == [253, 1, 6, 0]:
       
   339         return "-1/2 EV"
       
   340     if seq == [254, 1, 6, 0]:
       
   341         return "-1/3 EV"
       
   342     if seq == [0, 1, 6, 0]:
       
   343         return "0 EV"
       
   344     if seq == [2, 1, 6, 0]:
       
   345         return "+1/3 EV"
       
   346     if seq == [3, 1, 6, 0]:
       
   347         return "+1/2 EV"
       
   348     if seq == [4, 1, 6, 0]:
       
   349         return "+2/3 EV"
       
   350     # Handle combinations not in the table.
       
   351     a = seq[0]
       
   352     # Causes headaches for the +/- logic, so special case it.
       
   353     if a == 0:
       
   354         return "0 EV"
       
   355     if a > 127:
       
   356         a = 256 - a
       
   357         ret_str = "-"
       
   358     else:
       
   359         ret_str = "+"
       
   360     b = seq[2]	# Assume third value means the step size
       
   361     whole = a / b
       
   362     a = a % b
       
   363     if whole != 0:
       
   364         ret_str = ret_str + str(whole) + " "
       
   365     if a == 0:
       
   366         ret_str = ret_str + "EV"
       
   367     else:
       
   368         r = Ratio(a, b)
       
   369         ret_str = ret_str + r.__repr__() + " EV"
       
   370     return ret_str
       
   371 
       
   372 # Nikon E99x MakerNote Tags
       
   373 MAKERNOTE_NIKON_NEWER_TAGS={
       
   374     0x0001: ('MakernoteVersion', make_string),	# Sometimes binary
       
   375     0x0002: ('ISOSetting', ),
       
   376     0x0003: ('ColorMode', ),
       
   377     0x0004: ('Quality', ),
       
   378     0x0005: ('Whitebalance', ),
       
   379     0x0006: ('ImageSharpening', ),
       
   380     0x0007: ('FocusMode', ),
       
   381     0x0008: ('FlashSetting', ),
       
   382     0x0009: ('AutoFlashMode', ),
       
   383     0x000B: ('WhiteBalanceBias', ),
       
   384     0x000C: ('WhiteBalanceRBCoeff', ),
       
   385     0x000D: ('ProgramShift', nikon_ev_bias),
       
   386     # Nearly the same as the other EV vals, but step size is 1/12 EV (?)
       
   387     0x000E: ('ExposureDifference', nikon_ev_bias),
       
   388     0x000F: ('ISOSelection', ),
       
   389     0x0011: ('NikonPreview', ),
       
   390     0x0012: ('FlashCompensation', nikon_ev_bias),
       
   391     0x0013: ('ISOSpeedRequested', ),
       
   392     0x0016: ('PhotoCornerCoordinates', ),
       
   393     # 0x0017: Unknown, but most likely an EV value
       
   394     0x0018: ('FlashBracketCompensationApplied', nikon_ev_bias),
       
   395     0x0019: ('AEBracketCompensationApplied', ),
       
   396     0x001A: ('ImageProcessing', ),
       
   397     0x0080: ('ImageAdjustment', ),
       
   398     0x0081: ('ToneCompensation', ),
       
   399     0x0082: ('AuxiliaryLens', ),
       
   400     0x0083: ('LensType', ),
       
   401     0x0084: ('LensMinMaxFocalMaxAperture', ),
       
   402     0x0085: ('ManualFocusDistance', ),
       
   403     0x0086: ('DigitalZoomFactor', ),
       
   404     0x0087: ('FlashMode',
       
   405              {0x00: 'Did Not Fire',
       
   406               0x01: 'Fired, Manual',
       
   407               0x07: 'Fired, External',
       
   408               0x08: 'Fired, Commander Mode ',
       
   409               0x09: 'Fired, TTL Mode'}),
       
   410     0x0088: ('AFFocusPosition',
       
   411              {0x0000: 'Center',
       
   412               0x0100: 'Top',
       
   413               0x0200: 'Bottom',
       
   414               0x0300: 'Left',
       
   415               0x0400: 'Right'}),
       
   416     0x0089: ('BracketingMode',
       
   417              {0x00: 'Single frame, no bracketing',
       
   418               0x01: 'Continuous, no bracketing',
       
   419               0x02: 'Timer, no bracketing',
       
   420               0x10: 'Single frame, exposure bracketing',
       
   421               0x11: 'Continuous, exposure bracketing',
       
   422               0x12: 'Timer, exposure bracketing',
       
   423               0x40: 'Single frame, white balance bracketing',
       
   424               0x41: 'Continuous, white balance bracketing',
       
   425               0x42: 'Timer, white balance bracketing'}),
       
   426     0x008A: ('AutoBracketRelease', ),
       
   427     0x008B: ('LensFStops', ),
       
   428     0x008C: ('NEFCurve2', ),
       
   429     0x008D: ('ColorMode', ),
       
   430     0x008F: ('SceneMode', ),
       
   431     0x0090: ('LightingType', ),
       
   432     0x0091: ('ShotInfo', ),	# First 4 bytes are probably a version number in ASCII
       
   433     0x0092: ('HueAdjustment', ),
       
   434     # 0x0093: ('SaturationAdjustment', ),
       
   435     0x0094: ('Saturation',	# Name conflict with 0x00AA !!
       
   436              {-3: 'B&W',
       
   437               -2: '-2',
       
   438               -1: '-1',
       
   439               0: '0',
       
   440               1: '1',
       
   441               2: '2'}),
       
   442     0x0095: ('NoiseReduction', ),
       
   443     0x0096: ('NEFCurve2', ),
       
   444     0x0097: ('ColorBalance', ),
       
   445     0x0098: ('LensData', ),	# First 4 bytes are a version number in ASCII
       
   446     0x0099: ('RawImageCenter', ),
       
   447     0x009A: ('SensorPixelSize', ),
       
   448     0x009C: ('Scene Assist', ),
       
   449     0x00A0: ('SerialNumber', ),
       
   450     0x00A2: ('ImageDataSize', ),
       
   451     # A4: In NEF, looks like a 4 byte ASCII version number
       
   452     0x00A5: ('ImageCount', ),
       
   453     0x00A6: ('DeletedImageCount', ),
       
   454     0x00A7: ('TotalShutterReleases', ),
       
   455     # A8: ExposureMode?  JPG: First 4 bytes are probably a version number in ASCII
       
   456     # But in a sample NEF, its 8 zeros, then the string "NORMAL"
       
   457     0x00A9: ('ImageOptimization', ),
       
   458     0x00AA: ('Saturation', ),
       
   459     0x00AB: ('DigitalVariProgram', ),
       
   460     0x00AC: ('ImageStabilization', ),
       
   461     0x00AD: ('Responsive AF', ),	# 'AFResponse'
       
   462     0x0010: ('DataDump', ),
       
   463     }
       
   464 
       
   465 MAKERNOTE_NIKON_OLDER_TAGS = {
       
   466     0x0003: ('Quality',
       
   467              {1: 'VGA Basic',
       
   468               2: 'VGA Normal',
       
   469               3: 'VGA Fine',
       
   470               4: 'SXGA Basic',
       
   471               5: 'SXGA Normal',
       
   472               6: 'SXGA Fine'}),
       
   473     0x0004: ('ColorMode',
       
   474              {1: 'Color',
       
   475               2: 'Monochrome'}),
       
   476     0x0005: ('ImageAdjustment',
       
   477              {0: 'Normal',
       
   478               1: 'Bright+',
       
   479               2: 'Bright-',
       
   480               3: 'Contrast+',
       
   481               4: 'Contrast-'}),
       
   482     0x0006: ('CCDSpeed',
       
   483              {0: 'ISO 80',
       
   484               2: 'ISO 160',
       
   485               4: 'ISO 320',
       
   486               5: 'ISO 100'}),
       
   487     0x0007: ('WhiteBalance',
       
   488              {0: 'Auto',
       
   489               1: 'Preset',
       
   490               2: 'Daylight',
       
   491               3: 'Incandescent',
       
   492               4: 'Fluorescent',
       
   493               5: 'Cloudy',
       
   494               6: 'Speed Light'}),
       
   495     }
       
   496 
       
   497 # decode Olympus SpecialMode tag in MakerNote
       
   498 def olympus_special_mode(v):
       
   499     a={
       
   500         0: 'Normal',
       
   501         1: 'Unknown',
       
   502         2: 'Fast',
       
   503         3: 'Panorama'}
       
   504     b={
       
   505         0: 'Non-panoramic',
       
   506         1: 'Left to right',
       
   507         2: 'Right to left',
       
   508         3: 'Bottom to top',
       
   509         4: 'Top to bottom'}
       
   510     if v[0] not in a or v[2] not in b:
       
   511         return v
       
   512     return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
       
   513 
       
   514 MAKERNOTE_OLYMPUS_TAGS={
       
   515     # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
       
   516     # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
       
   517     0x0100: ('JPEGThumbnail', ),
       
   518     0x0200: ('SpecialMode', olympus_special_mode),
       
   519     0x0201: ('JPEGQual',
       
   520              {1: 'SQ',
       
   521               2: 'HQ',
       
   522               3: 'SHQ'}),
       
   523     0x0202: ('Macro',
       
   524              {0: 'Normal',
       
   525              1: 'Macro',
       
   526              2: 'SuperMacro'}),
       
   527     0x0203: ('BWMode',
       
   528              {0: 'Off',
       
   529              1: 'On'}),
       
   530     0x0204: ('DigitalZoom', ),
       
   531     0x0205: ('FocalPlaneDiagonal', ),
       
   532     0x0206: ('LensDistortionParams', ),
       
   533     0x0207: ('SoftwareRelease', ),
       
   534     0x0208: ('PictureInfo', ),
       
   535     0x0209: ('CameraID', make_string), # print as string
       
   536     0x0F00: ('DataDump', ),
       
   537     0x0300: ('PreCaptureFrames', ),
       
   538     0x0404: ('SerialNumber', ),
       
   539     0x1000: ('ShutterSpeedValue', ),
       
   540     0x1001: ('ISOValue', ),
       
   541     0x1002: ('ApertureValue', ),
       
   542     0x1003: ('BrightnessValue', ),
       
   543     0x1004: ('FlashMode', ),
       
   544     0x1004: ('FlashMode',
       
   545        {2: 'On',
       
   546         3: 'Off'}),
       
   547     0x1005: ('FlashDevice',
       
   548        {0: 'None',
       
   549         1: 'Internal',
       
   550         4: 'External',
       
   551         5: 'Internal + External'}),
       
   552     0x1006: ('ExposureCompensation', ),
       
   553     0x1007: ('SensorTemperature', ),
       
   554     0x1008: ('LensTemperature', ),
       
   555     0x100b: ('FocusMode',
       
   556        {0: 'Auto',
       
   557         1: 'Manual'}),
       
   558     0x1017: ('RedBalance', ),
       
   559     0x1018: ('BlueBalance', ),
       
   560     0x101a: ('SerialNumber', ),
       
   561     0x1023: ('FlashExposureComp', ),
       
   562     0x1026: ('ExternalFlashBounce',
       
   563        {0: 'No',
       
   564         1: 'Yes'}),
       
   565     0x1027: ('ExternalFlashZoom', ),
       
   566     0x1028: ('ExternalFlashMode', ),
       
   567     0x1029: ('Contrast 	int16u',
       
   568        {0: 'High',
       
   569         1: 'Normal',
       
   570         2: 'Low'}),
       
   571     0x102a: ('SharpnessFactor', ),
       
   572     0x102b: ('ColorControl', ),
       
   573     0x102c: ('ValidBits', ),
       
   574     0x102d: ('CoringFilter', ),
       
   575     0x102e: ('OlympusImageWidth', ),
       
   576     0x102f: ('OlympusImageHeight', ),
       
   577     0x1034: ('CompressionRatio', ),
       
   578     0x1035: ('PreviewImageValid',
       
   579        {0: 'No',
       
   580         1: 'Yes'}),
       
   581     0x1036: ('PreviewImageStart', ),
       
   582     0x1037: ('PreviewImageLength', ),
       
   583     0x1039: ('CCDScanMode',
       
   584        {0: 'Interlaced',
       
   585         1: 'Progressive'}),
       
   586     0x103a: ('NoiseReduction',
       
   587        {0: 'Off',
       
   588         1: 'On'}),
       
   589     0x103b: ('InfinityLensStep', ),
       
   590     0x103c: ('NearLensStep', ),
       
   591 
       
   592     # TODO - these need extra definitions
       
   593     # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html
       
   594     0x2010: ('Equipment', ),
       
   595     0x2020: ('CameraSettings', ),
       
   596     0x2030: ('RawDevelopment', ),
       
   597     0x2040: ('ImageProcessing', ),
       
   598     0x2050: ('FocusInfo', ),
       
   599     0x3000: ('RawInfo ', ),
       
   600     }
       
   601 
       
   602 # 0x2020 CameraSettings
       
   603 MAKERNOTE_OLYMPUS_TAG_0x2020={
       
   604     0x0100: ('PreviewImageValid',
       
   605         {0: 'No',
       
   606          1: 'Yes'}),
       
   607     0x0101: ('PreviewImageStart', ),
       
   608     0x0102: ('PreviewImageLength', ),
       
   609     0x0200: ('ExposureMode', {
       
   610         1: 'Manual',
       
   611         2: 'Program',
       
   612         3: 'Aperture-priority AE',
       
   613         4: 'Shutter speed priority AE',
       
   614         5: 'Program-shift'}),
       
   615     0x0201: ('AELock',
       
   616        {0: 'Off',
       
   617         1: 'On'}),
       
   618     0x0202: ('MeteringMode',
       
   619        {2: 'Center Weighted',
       
   620         3: 'Spot',
       
   621         5: 'ESP',
       
   622         261: 'Pattern+AF',
       
   623         515: 'Spot+Highlight control',
       
   624         1027: 'Spot+Shadow control'}),
       
   625     0x0300: ('MacroMode',
       
   626        {0: 'Off',
       
   627         1: 'On'}),
       
   628     0x0301: ('FocusMode',
       
   629        {0: 'Single AF',
       
   630         1: 'Sequential shooting AF',
       
   631         2: 'Continuous AF',
       
   632         3: 'Multi AF',
       
   633         10: 'MF'}),
       
   634     0x0302: ('FocusProcess',
       
   635        {0: 'AF Not Used',
       
   636         1: 'AF Used'}),
       
   637     0x0303: ('AFSearch',
       
   638        {0: 'Not Ready',
       
   639         1: 'Ready'}),
       
   640     0x0304: ('AFAreas', ),
       
   641     0x0401: ('FlashExposureCompensation', ),
       
   642     0x0500: ('WhiteBalance2',
       
   643        {0: 'Auto',
       
   644         16: '7500K (Fine Weather with Shade)',
       
   645         17: '6000K (Cloudy)',
       
   646         18: '5300K (Fine Weather)',
       
   647         20: '3000K (Tungsten light)',
       
   648         21: '3600K (Tungsten light-like)',
       
   649         33: '6600K (Daylight fluorescent)',
       
   650         34: '4500K (Neutral white fluorescent)',
       
   651         35: '4000K (Cool white fluorescent)',
       
   652         48: '3600K (Tungsten light-like)',
       
   653         256: 'Custom WB 1',
       
   654         257: 'Custom WB 2',
       
   655         258: 'Custom WB 3',
       
   656         259: 'Custom WB 4',
       
   657         512: 'Custom WB 5400K',
       
   658         513: 'Custom WB 2900K',
       
   659         514: 'Custom WB 8000K', }),
       
   660     0x0501: ('WhiteBalanceTemperature', ),
       
   661     0x0502: ('WhiteBalanceBracket', ),
       
   662     0x0503: ('CustomSaturation', ), # (3 numbers: 1. CS Value, 2. Min, 3. Max)
       
   663     0x0504: ('ModifiedSaturation',
       
   664        {0: 'Off',
       
   665         1: 'CM1 (Red Enhance)',
       
   666         2: 'CM2 (Green Enhance)',
       
   667         3: 'CM3 (Blue Enhance)',
       
   668         4: 'CM4 (Skin Tones)'}),
       
   669     0x0505: ('ContrastSetting', ), # (3 numbers: 1. Contrast, 2. Min, 3. Max)
       
   670     0x0506: ('SharpnessSetting', ), # (3 numbers: 1. Sharpness, 2. Min, 3. Max)
       
   671     0x0507: ('ColorSpace',
       
   672        {0: 'sRGB',
       
   673         1: 'Adobe RGB',
       
   674         2: 'Pro Photo RGB'}),
       
   675     0x0509: ('SceneMode',
       
   676        {0: 'Standard',
       
   677         6: 'Auto',
       
   678         7: 'Sport',
       
   679         8: 'Portrait',
       
   680         9: 'Landscape+Portrait',
       
   681         10: 'Landscape',
       
   682         11: 'Night scene',
       
   683         13: 'Panorama',
       
   684         16: 'Landscape+Portrait',
       
   685         17: 'Night+Portrait',
       
   686         19: 'Fireworks',
       
   687         20: 'Sunset',
       
   688         22: 'Macro',
       
   689         25: 'Documents',
       
   690         26: 'Museum',
       
   691         28: 'Beach&Snow',
       
   692         30: 'Candle',
       
   693         35: 'Underwater Wide1',
       
   694         36: 'Underwater Macro',
       
   695         39: 'High Key',
       
   696         40: 'Digital Image Stabilization',
       
   697         44: 'Underwater Wide2',
       
   698         45: 'Low Key',
       
   699         46: 'Children',
       
   700         48: 'Nature Macro'}),
       
   701     0x050a: ('NoiseReduction',
       
   702        {0: 'Off',
       
   703         1: 'Noise Reduction',
       
   704         2: 'Noise Filter',
       
   705         3: 'Noise Reduction + Noise Filter',
       
   706         4: 'Noise Filter (ISO Boost)',
       
   707         5: 'Noise Reduction + Noise Filter (ISO Boost)'}),
       
   708     0x050b: ('DistortionCorrection',
       
   709        {0: 'Off',
       
   710         1: 'On'}),
       
   711     0x050c: ('ShadingCompensation',
       
   712        {0: 'Off',
       
   713         1: 'On'}),
       
   714     0x050d: ('CompressionFactor', ),
       
   715     0x050f: ('Gradation',
       
   716        {'-1 -1 1': 'Low Key',
       
   717         '0 -1 1': 'Normal',
       
   718         '1 -1 1': 'High Key'}),
       
   719     0x0520: ('PictureMode',
       
   720        {1: 'Vivid',
       
   721         2: 'Natural',
       
   722         3: 'Muted',
       
   723         256: 'Monotone',
       
   724         512: 'Sepia'}),
       
   725     0x0521: ('PictureModeSaturation', ),
       
   726     0x0522: ('PictureModeHue?', ),
       
   727     0x0523: ('PictureModeContrast', ),
       
   728     0x0524: ('PictureModeSharpness', ),
       
   729     0x0525: ('PictureModeBWFilter',
       
   730        {0: 'n/a',
       
   731         1: 'Neutral',
       
   732         2: 'Yellow',
       
   733         3: 'Orange',
       
   734         4: 'Red',
       
   735         5: 'Green'}),
       
   736     0x0526: ('PictureModeTone',
       
   737        {0: 'n/a',
       
   738         1: 'Neutral',
       
   739         2: 'Sepia',
       
   740         3: 'Blue',
       
   741         4: 'Purple',
       
   742         5: 'Green'}),
       
   743     0x0600: ('Sequence', ), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits
       
   744     0x0601: ('PanoramaMode', ), # (2 numbers: 1. Mode, 2. Shot number)
       
   745     0x0603: ('ImageQuality2',
       
   746        {1: 'SQ',
       
   747         2: 'HQ',
       
   748         3: 'SHQ',
       
   749         4: 'RAW'}),
       
   750     0x0901: ('ManometerReading', ),
       
   751     }
       
   752 
       
   753 
       
   754 MAKERNOTE_CASIO_TAGS={
       
   755     0x0001: ('RecordingMode',
       
   756              {1: 'Single Shutter',
       
   757               2: 'Panorama',
       
   758               3: 'Night Scene',
       
   759               4: 'Portrait',
       
   760               5: 'Landscape'}),
       
   761     0x0002: ('Quality',
       
   762              {1: 'Economy',
       
   763               2: 'Normal',
       
   764               3: 'Fine'}),
       
   765     0x0003: ('FocusingMode',
       
   766              {2: 'Macro',
       
   767               3: 'Auto Focus',
       
   768               4: 'Manual Focus',
       
   769               5: 'Infinity'}),
       
   770     0x0004: ('FlashMode',
       
   771              {1: 'Auto',
       
   772               2: 'On',
       
   773               3: 'Off',
       
   774               4: 'Red Eye Reduction'}),
       
   775     0x0005: ('FlashIntensity',
       
   776              {11: 'Weak',
       
   777               13: 'Normal',
       
   778               15: 'Strong'}),
       
   779     0x0006: ('Object Distance', ),
       
   780     0x0007: ('WhiteBalance',
       
   781              {1: 'Auto',
       
   782               2: 'Tungsten',
       
   783               3: 'Daylight',
       
   784               4: 'Fluorescent',
       
   785               5: 'Shade',
       
   786               129: 'Manual'}),
       
   787     0x000B: ('Sharpness',
       
   788              {0: 'Normal',
       
   789               1: 'Soft',
       
   790               2: 'Hard'}),
       
   791     0x000C: ('Contrast',
       
   792              {0: 'Normal',
       
   793               1: 'Low',
       
   794               2: 'High'}),
       
   795     0x000D: ('Saturation',
       
   796              {0: 'Normal',
       
   797               1: 'Low',
       
   798               2: 'High'}),
       
   799     0x0014: ('CCDSpeed',
       
   800              {64: 'Normal',
       
   801               80: 'Normal',
       
   802               100: 'High',
       
   803               125: '+1.0',
       
   804               244: '+3.0',
       
   805               250: '+2.0'}),
       
   806     }
       
   807 
       
   808 MAKERNOTE_FUJIFILM_TAGS={
       
   809     0x0000: ('NoteVersion', make_string),
       
   810     0x1000: ('Quality', ),
       
   811     0x1001: ('Sharpness',
       
   812              {1: 'Soft',
       
   813               2: 'Soft',
       
   814               3: 'Normal',
       
   815               4: 'Hard',
       
   816               5: 'Hard'}),
       
   817     0x1002: ('WhiteBalance',
       
   818              {0: 'Auto',
       
   819               256: 'Daylight',
       
   820               512: 'Cloudy',
       
   821               768: 'DaylightColor-Fluorescent',
       
   822               769: 'DaywhiteColor-Fluorescent',
       
   823               770: 'White-Fluorescent',
       
   824               1024: 'Incandescent',
       
   825               3840: 'Custom'}),
       
   826     0x1003: ('Color',
       
   827              {0: 'Normal',
       
   828               256: 'High',
       
   829               512: 'Low'}),
       
   830     0x1004: ('Tone',
       
   831              {0: 'Normal',
       
   832               256: 'High',
       
   833               512: 'Low'}),
       
   834     0x1010: ('FlashMode',
       
   835              {0: 'Auto',
       
   836               1: 'On',
       
   837               2: 'Off',
       
   838               3: 'Red Eye Reduction'}),
       
   839     0x1011: ('FlashStrength', ),
       
   840     0x1020: ('Macro',
       
   841              {0: 'Off',
       
   842               1: 'On'}),
       
   843     0x1021: ('FocusMode',
       
   844              {0: 'Auto',
       
   845               1: 'Manual'}),
       
   846     0x1030: ('SlowSync',
       
   847              {0: 'Off',
       
   848               1: 'On'}),
       
   849     0x1031: ('PictureMode',
       
   850              {0: 'Auto',
       
   851               1: 'Portrait',
       
   852               2: 'Landscape',
       
   853               4: 'Sports',
       
   854               5: 'Night',
       
   855               6: 'Program AE',
       
   856               256: 'Aperture Priority AE',
       
   857               512: 'Shutter Priority AE',
       
   858               768: 'Manual Exposure'}),
       
   859     0x1100: ('MotorOrBracket',
       
   860              {0: 'Off',
       
   861               1: 'On'}),
       
   862     0x1300: ('BlurWarning',
       
   863              {0: 'Off',
       
   864               1: 'On'}),
       
   865     0x1301: ('FocusWarning',
       
   866              {0: 'Off',
       
   867               1: 'On'}),
       
   868     0x1302: ('AEWarning',
       
   869              {0: 'Off',
       
   870               1: 'On'}),
       
   871     }
       
   872 
       
   873 MAKERNOTE_CANON_TAGS = {
       
   874     0x0006: ('ImageType', ),
       
   875     0x0007: ('FirmwareVersion', ),
       
   876     0x0008: ('ImageNumber', ),
       
   877     0x0009: ('OwnerName', ),
       
   878     }
       
   879 
       
   880 # this is in element offset, name, optional value dictionary format
       
   881 MAKERNOTE_CANON_TAG_0x001 = {
       
   882     1: ('Macromode',
       
   883         {1: 'Macro',
       
   884          2: 'Normal'}),
       
   885     2: ('SelfTimer', ),
       
   886     3: ('Quality',
       
   887         {2: 'Normal',
       
   888          3: 'Fine',
       
   889          5: 'Superfine'}),
       
   890     4: ('FlashMode',
       
   891         {0: 'Flash Not Fired',
       
   892          1: 'Auto',
       
   893          2: 'On',
       
   894          3: 'Red-Eye Reduction',
       
   895          4: 'Slow Synchro',
       
   896          5: 'Auto + Red-Eye Reduction',
       
   897          6: 'On + Red-Eye Reduction',
       
   898          16: 'external flash'}),
       
   899     5: ('ContinuousDriveMode',
       
   900         {0: 'Single Or Timer',
       
   901          1: 'Continuous'}),
       
   902     7: ('FocusMode',
       
   903         {0: 'One-Shot',
       
   904          1: 'AI Servo',
       
   905          2: 'AI Focus',
       
   906          3: 'MF',
       
   907          4: 'Single',
       
   908          5: 'Continuous',
       
   909          6: 'MF'}),
       
   910     10: ('ImageSize',
       
   911          {0: 'Large',
       
   912           1: 'Medium',
       
   913           2: 'Small'}),
       
   914     11: ('EasyShootingMode',
       
   915          {0: 'Full Auto',
       
   916           1: 'Manual',
       
   917           2: 'Landscape',
       
   918           3: 'Fast Shutter',
       
   919           4: 'Slow Shutter',
       
   920           5: 'Night',
       
   921           6: 'B&W',
       
   922           7: 'Sepia',
       
   923           8: 'Portrait',
       
   924           9: 'Sports',
       
   925           10: 'Macro/Close-Up',
       
   926           11: 'Pan Focus'}),
       
   927     12: ('DigitalZoom',
       
   928          {0: 'None',
       
   929           1: '2x',
       
   930           2: '4x'}),
       
   931     13: ('Contrast',
       
   932          {0xFFFF: 'Low',
       
   933           0: 'Normal',
       
   934           1: 'High'}),
       
   935     14: ('Saturation',
       
   936          {0xFFFF: 'Low',
       
   937           0: 'Normal',
       
   938           1: 'High'}),
       
   939     15: ('Sharpness',
       
   940          {0xFFFF: 'Low',
       
   941           0: 'Normal',
       
   942           1: 'High'}),
       
   943     16: ('ISO',
       
   944          {0: 'See ISOSpeedRatings Tag',
       
   945           15: 'Auto',
       
   946           16: '50',
       
   947           17: '100',
       
   948           18: '200',
       
   949           19: '400'}),
       
   950     17: ('MeteringMode',
       
   951          {3: 'Evaluative',
       
   952           4: 'Partial',
       
   953           5: 'Center-weighted'}),
       
   954     18: ('FocusType',
       
   955          {0: 'Manual',
       
   956           1: 'Auto',
       
   957           3: 'Close-Up (Macro)',
       
   958           8: 'Locked (Pan Mode)'}),
       
   959     19: ('AFPointSelected',
       
   960          {0x3000: 'None (MF)',
       
   961           0x3001: 'Auto-Selected',
       
   962           0x3002: 'Right',
       
   963           0x3003: 'Center',
       
   964           0x3004: 'Left'}),
       
   965     20: ('ExposureMode',
       
   966          {0: 'Easy Shooting',
       
   967           1: 'Program',
       
   968           2: 'Tv-priority',
       
   969           3: 'Av-priority',
       
   970           4: 'Manual',
       
   971           5: 'A-DEP'}),
       
   972     23: ('LongFocalLengthOfLensInFocalUnits', ),
       
   973     24: ('ShortFocalLengthOfLensInFocalUnits', ),
       
   974     25: ('FocalUnitsPerMM', ),
       
   975     28: ('FlashActivity',
       
   976          {0: 'Did Not Fire',
       
   977           1: 'Fired'}),
       
   978     29: ('FlashDetails',
       
   979          {14: 'External E-TTL',
       
   980           13: 'Internal Flash',
       
   981           11: 'FP Sync Used',
       
   982           7: '2nd("Rear")-Curtain Sync Used',
       
   983           4: 'FP Sync Enabled'}),
       
   984     32: ('FocusMode',
       
   985          {0: 'Single',
       
   986           1: 'Continuous'}),
       
   987     }
       
   988 
       
   989 MAKERNOTE_CANON_TAG_0x004 = {
       
   990     7: ('WhiteBalance',
       
   991         {0: 'Auto',
       
   992          1: 'Sunny',
       
   993          2: 'Cloudy',
       
   994          3: 'Tungsten',
       
   995          4: 'Fluorescent',
       
   996          5: 'Flash',
       
   997          6: 'Custom'}),
       
   998     9: ('SequenceNumber', ),
       
   999     14: ('AFPointUsed', ),
       
  1000     15: ('FlashBias',
       
  1001         {0XFFC0: '-2 EV',
       
  1002          0XFFCC: '-1.67 EV',
       
  1003          0XFFD0: '-1.50 EV',
       
  1004          0XFFD4: '-1.33 EV',
       
  1005          0XFFE0: '-1 EV',
       
  1006          0XFFEC: '-0.67 EV',
       
  1007          0XFFF0: '-0.50 EV',
       
  1008          0XFFF4: '-0.33 EV',
       
  1009          0X0000: '0 EV',
       
  1010          0X000C: '0.33 EV',
       
  1011          0X0010: '0.50 EV',
       
  1012          0X0014: '0.67 EV',
       
  1013          0X0020: '1 EV',
       
  1014          0X002C: '1.33 EV',
       
  1015          0X0030: '1.50 EV',
       
  1016          0X0034: '1.67 EV',
       
  1017          0X0040: '2 EV'}),
       
  1018     19: ('SubjectDistance', ),
       
  1019     }
       
  1020 
       
  1021 # extract multibyte integer in Motorola format (little endian)
       
  1022 def s2n_motorola(str):
       
  1023     x = 0
       
  1024     for c in str:
       
  1025         x = (x << 8) | ord(c)
       
  1026     return x
       
  1027 
       
  1028 # extract multibyte integer in Intel format (big endian)
       
  1029 def s2n_intel(str):
       
  1030     x = 0
       
  1031     y = 0L
       
  1032     for c in str:
       
  1033         x = x | (ord(c) << y)
       
  1034         y = y + 8
       
  1035     return x
       
  1036 
       
  1037 # ratio object that eventually will be able to reduce itself to lowest
       
  1038 # common denominator for printing
       
  1039 def gcd(a, b):
       
  1040     if b == 0:
       
  1041         return a
       
  1042     else:
       
  1043         return gcd(b, a % b)
       
  1044 
       
  1045 class Ratio:
       
  1046     def __init__(self, num, den):
       
  1047         self.num = num
       
  1048         self.den = den
       
  1049 
       
  1050     def __repr__(self):
       
  1051         self.reduce()
       
  1052         if self.den == 1:
       
  1053             return str(self.num)
       
  1054         return '%d/%d' % (self.num, self.den)
       
  1055 
       
  1056     def reduce(self):
       
  1057         div = gcd(self.num, self.den)
       
  1058         if div > 1:
       
  1059             self.num = self.num / div
       
  1060             self.den = self.den / div
       
  1061 
       
  1062 # for ease of dealing with tags
       
  1063 class IFD_Tag:
       
  1064     def __init__(self, printable, tag, field_type, values, field_offset,
       
  1065                  field_length):
       
  1066         # printable version of data
       
  1067         self.printable = printable
       
  1068         # tag ID number
       
  1069         self.tag = tag
       
  1070         # field type as index into FIELD_TYPES
       
  1071         self.field_type = field_type
       
  1072         # offset of start of field in bytes from beginning of IFD
       
  1073         self.field_offset = field_offset
       
  1074         # length of data field in bytes
       
  1075         self.field_length = field_length
       
  1076         # either a string or array of data items
       
  1077         self.values = values
       
  1078 
       
  1079     def __str__(self):
       
  1080         return self.printable
       
  1081 
       
  1082     def __repr__(self):
       
  1083         return '(0x%04X) %s=%s @ %d' % (self.tag,
       
  1084                                         FIELD_TYPES[self.field_type][2],
       
  1085                                         self.printable,
       
  1086                                         self.field_offset)
       
  1087 
       
  1088 # class that handles an EXIF header
       
  1089 class EXIF_header:
       
  1090     def __init__(self, file, endian, offset, fake_exif, debug=0):
       
  1091         self.file = file
       
  1092         self.endian = endian
       
  1093         self.offset = offset
       
  1094         self.fake_exif = fake_exif
       
  1095         self.debug = debug
       
  1096         self.tags = {}
       
  1097 
       
  1098     # convert slice to integer, based on sign and endian flags
       
  1099     # usually this offset is assumed to be relative to the beginning of the
       
  1100     # start of the EXIF information.  For some cameras that use relative tags,
       
  1101     # this offset may be relative to some other starting point.
       
  1102     def s2n(self, offset, length, signed=0):
       
  1103         self.file.seek(self.offset+offset)
       
  1104         slice=self.file.read(length)
       
  1105         if self.endian == 'I':
       
  1106             val=s2n_intel(slice)
       
  1107         else:
       
  1108             val=s2n_motorola(slice)
       
  1109         # Sign extension ?
       
  1110         if signed:
       
  1111             msb=1L << (8*length-1)
       
  1112             if val & msb:
       
  1113                 val=val-(msb << 1)
       
  1114         return val
       
  1115 
       
  1116     # convert offset to string
       
  1117     def n2s(self, offset, length):
       
  1118         s = ''
       
  1119         for dummy in range(length):
       
  1120             if self.endian == 'I':
       
  1121                 s = s + chr(offset & 0xFF)
       
  1122             else:
       
  1123                 s = chr(offset & 0xFF) + s
       
  1124             offset = offset >> 8
       
  1125         return s
       
  1126 
       
  1127     # return first IFD
       
  1128     def first_IFD(self):
       
  1129         return self.s2n(4, 4)
       
  1130 
       
  1131     # return pointer to next IFD
       
  1132     def next_IFD(self, ifd):
       
  1133         entries=self.s2n(ifd, 2)
       
  1134         return self.s2n(ifd+2+12*entries, 4)
       
  1135 
       
  1136     # return list of IFDs in header
       
  1137     def list_IFDs(self):
       
  1138         i=self.first_IFD()
       
  1139         a=[]
       
  1140         while i:
       
  1141             a.append(i)
       
  1142             i=self.next_IFD(i)
       
  1143         return a
       
  1144 
       
  1145     # return list of entries in this IFD
       
  1146     def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0, stop_tag='UNDEF'):
       
  1147         entries=self.s2n(ifd, 2)
       
  1148         for i in range(entries):
       
  1149             # entry is index of start of this IFD in the file
       
  1150             entry = ifd + 2 + 12 * i
       
  1151             tag = self.s2n(entry, 2)
       
  1152 
       
  1153             # get tag name early to avoid errors, help debug
       
  1154             tag_entry = dict.get(tag)
       
  1155             if tag_entry:
       
  1156                 tag_name = tag_entry[0]
       
  1157             else:
       
  1158                 tag_name = 'Tag 0x%04X' % tag
       
  1159 
       
  1160             # ignore certain tags for faster processing
       
  1161             if not (not detailed and tag in IGNORE_TAGS):
       
  1162                 field_type = self.s2n(entry + 2, 2)
       
  1163                 if not 0 < field_type < len(FIELD_TYPES):
       
  1164                     # unknown field type
       
  1165                     raise ValueError('unknown type %d in tag 0x%04X' % (field_type, tag))
       
  1166                 typelen = FIELD_TYPES[field_type][0]
       
  1167                 count = self.s2n(entry + 4, 4)
       
  1168                 offset = entry + 8
       
  1169                 if count * typelen > 4:
       
  1170                     # offset is not the value; it's a pointer to the value
       
  1171                     # if relative we set things up so s2n will seek to the right
       
  1172                     # place when it adds self.offset.  Note that this 'relative'
       
  1173                     # is for the Nikon type 3 makernote.  Other cameras may use
       
  1174                     # other relative offsets, which would have to be computed here
       
  1175                     # slightly differently.
       
  1176                     if relative:
       
  1177                         tmp_offset = self.s2n(offset, 4)
       
  1178                         offset = tmp_offset + ifd - self.offset + 4
       
  1179                         if self.fake_exif:
       
  1180                             offset = offset + 18
       
  1181                     else:
       
  1182                         offset = self.s2n(offset, 4)
       
  1183                 field_offset = offset
       
  1184                 if field_type == 2:
       
  1185                     # special case: null-terminated ASCII string
       
  1186                     if count != 0:
       
  1187                         self.file.seek(self.offset + offset)
       
  1188                         values = self.file.read(count)
       
  1189                         values = values.strip().replace('\x00', '')
       
  1190                     else:
       
  1191                         values = ''
       
  1192                 else:
       
  1193                     values = []
       
  1194                     signed = (field_type in [6, 8, 9, 10])
       
  1195                     for dummy in range(count):
       
  1196                         if field_type in (5, 10):
       
  1197                             # a ratio
       
  1198                             value = Ratio(self.s2n(offset, 4, signed),
       
  1199                                           self.s2n(offset + 4, 4, signed))
       
  1200                         else:
       
  1201                             value = self.s2n(offset, typelen, signed)
       
  1202                         values.append(value)
       
  1203                         offset = offset + typelen
       
  1204                 # now "values" is either a string or an array
       
  1205                 if count == 1 and field_type != 2:
       
  1206                     printable=str(values[0])
       
  1207                 else:
       
  1208                     printable=str(values)
       
  1209                 # compute printable version of values
       
  1210                 if tag_entry:
       
  1211                     if len(tag_entry) != 1:
       
  1212                         # optional 2nd tag element is present
       
  1213                         if callable(tag_entry[1]):
       
  1214                             # call mapping function
       
  1215                             printable = tag_entry[1](values)
       
  1216                         else:
       
  1217                             printable = ''
       
  1218                             for i in values:
       
  1219                                 # use lookup table for this tag
       
  1220                                 printable += tag_entry[1].get(i, repr(i))
       
  1221 
       
  1222                 self.tags[ifd_name + ' ' + tag_name] = IFD_Tag(printable, tag,
       
  1223                                                           field_type,
       
  1224                                                           values, field_offset,
       
  1225                                                           count * typelen)
       
  1226                 if self.debug:
       
  1227                     print ' debug:   %s: %s' % (tag_name,
       
  1228                                                 repr(self.tags[ifd_name + ' ' + tag_name]))
       
  1229 
       
  1230             if tag_name == stop_tag:
       
  1231                 break
       
  1232 
       
  1233     # extract uncompressed TIFF thumbnail (like pulling teeth)
       
  1234     # we take advantage of the pre-existing layout in the thumbnail IFD as
       
  1235     # much as possible
       
  1236     def extract_TIFF_thumbnail(self, thumb_ifd):
       
  1237         entries = self.s2n(thumb_ifd, 2)
       
  1238         # this is header plus offset to IFD ...
       
  1239         if self.endian == 'M':
       
  1240             tiff = 'MM\x00*\x00\x00\x00\x08'
       
  1241         else:
       
  1242             tiff = 'II*\x00\x08\x00\x00\x00'
       
  1243         # ... plus thumbnail IFD data plus a null "next IFD" pointer
       
  1244         self.file.seek(self.offset+thumb_ifd)
       
  1245         tiff += self.file.read(entries*12+2)+'\x00\x00\x00\x00'
       
  1246 
       
  1247         # fix up large value offset pointers into data area
       
  1248         for i in range(entries):
       
  1249             entry = thumb_ifd + 2 + 12 * i
       
  1250             tag = self.s2n(entry, 2)
       
  1251             field_type = self.s2n(entry+2, 2)
       
  1252             typelen = FIELD_TYPES[field_type][0]
       
  1253             count = self.s2n(entry+4, 4)
       
  1254             oldoff = self.s2n(entry+8, 4)
       
  1255             # start of the 4-byte pointer area in entry
       
  1256             ptr = i * 12 + 18
       
  1257             # remember strip offsets location
       
  1258             if tag == 0x0111:
       
  1259                 strip_off = ptr
       
  1260                 strip_len = count * typelen
       
  1261             # is it in the data area?
       
  1262             if count * typelen > 4:
       
  1263                 # update offset pointer (nasty "strings are immutable" crap)
       
  1264                 # should be able to say "tiff[ptr:ptr+4]=newoff"
       
  1265                 newoff = len(tiff)
       
  1266                 tiff = tiff[:ptr] + self.n2s(newoff, 4) + tiff[ptr+4:]
       
  1267                 # remember strip offsets location
       
  1268                 if tag == 0x0111:
       
  1269                     strip_off = newoff
       
  1270                     strip_len = 4
       
  1271                 # get original data and store it
       
  1272                 self.file.seek(self.offset + oldoff)
       
  1273                 tiff += self.file.read(count * typelen)
       
  1274 
       
  1275         # add pixel strips and update strip offset info
       
  1276         old_offsets = self.tags['Thumbnail StripOffsets'].values
       
  1277         old_counts = self.tags['Thumbnail StripByteCounts'].values
       
  1278         for i in range(len(old_offsets)):
       
  1279             # update offset pointer (more nasty "strings are immutable" crap)
       
  1280             offset = self.n2s(len(tiff), strip_len)
       
  1281             tiff = tiff[:strip_off] + offset + tiff[strip_off + strip_len:]
       
  1282             strip_off += strip_len
       
  1283             # add pixel strip to end
       
  1284             self.file.seek(self.offset + old_offsets[i])
       
  1285             tiff += self.file.read(old_counts[i])
       
  1286 
       
  1287         self.tags['TIFFThumbnail'] = tiff
       
  1288 
       
  1289     # decode all the camera-specific MakerNote formats
       
  1290 
       
  1291     # Note is the data that comprises this MakerNote.  The MakerNote will
       
  1292     # likely have pointers in it that point to other parts of the file.  We'll
       
  1293     # use self.offset as the starting point for most of those pointers, since
       
  1294     # they are relative to the beginning of the file.
       
  1295     #
       
  1296     # If the MakerNote is in a newer format, it may use relative addressing
       
  1297     # within the MakerNote.  In that case we'll use relative addresses for the
       
  1298     # pointers.
       
  1299     #
       
  1300     # As an aside: it's not just to be annoying that the manufacturers use
       
  1301     # relative offsets.  It's so that if the makernote has to be moved by the
       
  1302     # picture software all of the offsets don't have to be adjusted.  Overall,
       
  1303     # this is probably the right strategy for makernotes, though the spec is
       
  1304     # ambiguous.  (The spec does not appear to imagine that makernotes would
       
  1305     # follow EXIF format internally.  Once they did, it's ambiguous whether
       
  1306     # the offsets should be from the header at the start of all the EXIF info,
       
  1307     # or from the header at the start of the makernote.)
       
  1308     def decode_maker_note(self):
       
  1309         note = self.tags['EXIF MakerNote']
       
  1310         make = self.tags['Image Make'].printable
       
  1311         # model = self.tags['Image Model'].printable # unused
       
  1312 
       
  1313         # Nikon
       
  1314         # The maker note usually starts with the word Nikon, followed by the
       
  1315         # type of the makernote (1 or 2, as a short).  If the word Nikon is
       
  1316         # not at the start of the makernote, it's probably type 2, since some
       
  1317         # cameras work that way.
       
  1318         if make in ('NIKON', 'NIKON CORPORATION'):
       
  1319             if note.values[0:7] == [78, 105, 107, 111, 110, 0, 1]:
       
  1320                 if self.debug:
       
  1321                     print "Looks like a type 1 Nikon MakerNote."
       
  1322                 self.dump_IFD(note.field_offset+8, 'MakerNote',
       
  1323                               dict=MAKERNOTE_NIKON_OLDER_TAGS)
       
  1324             elif note.values[0:7] == [78, 105, 107, 111, 110, 0, 2]:
       
  1325                 if self.debug:
       
  1326                     print "Looks like a labeled type 2 Nikon MakerNote"
       
  1327                 if note.values[12:14] != [0, 42] and note.values[12:14] != [42L, 0L]:
       
  1328                     raise ValueError("Missing marker tag '42' in MakerNote.")
       
  1329                 # skip the Makernote label and the TIFF header
       
  1330                 self.dump_IFD(note.field_offset+10+8, 'MakerNote',
       
  1331                               dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1)
       
  1332             else:
       
  1333                 # E99x or D1
       
  1334                 if self.debug:
       
  1335                     print "Looks like an unlabeled type 2 Nikon MakerNote"
       
  1336                 self.dump_IFD(note.field_offset, 'MakerNote',
       
  1337                               dict=MAKERNOTE_NIKON_NEWER_TAGS)
       
  1338             return
       
  1339 
       
  1340         # Olympus
       
  1341         if make.startswith('OLYMPUS'):
       
  1342             self.dump_IFD(note.field_offset+8, 'MakerNote',
       
  1343                           dict=MAKERNOTE_OLYMPUS_TAGS)
       
  1344             # TODO
       
  1345             #for i in (('MakerNote Tag 0x2020', MAKERNOTE_OLYMPUS_TAG_0x2020),):
       
  1346             #    self.decode_olympus_tag(self.tags[i[0]].values, i[1])
       
  1347             #return
       
  1348 
       
  1349         # Casio
       
  1350         if make == 'Casio':
       
  1351             self.dump_IFD(note.field_offset, 'MakerNote',
       
  1352                           dict=MAKERNOTE_CASIO_TAGS)
       
  1353             return
       
  1354 
       
  1355         # Fujifilm
       
  1356         if make == 'FUJIFILM':
       
  1357             # bug: everything else is "Motorola" endian, but the MakerNote
       
  1358             # is "Intel" endian
       
  1359             endian = self.endian
       
  1360             self.endian = 'I'
       
  1361             # bug: IFD offsets are from beginning of MakerNote, not
       
  1362             # beginning of file header
       
  1363             offset = self.offset
       
  1364             self.offset += note.field_offset
       
  1365             # process note with bogus values (note is actually at offset 12)
       
  1366             self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS)
       
  1367             # reset to correct values
       
  1368             self.endian = endian
       
  1369             self.offset = offset
       
  1370             return
       
  1371 
       
  1372         # Canon
       
  1373         if make == 'Canon':
       
  1374             self.dump_IFD(note.field_offset, 'MakerNote',
       
  1375                           dict=MAKERNOTE_CANON_TAGS)
       
  1376             for i in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001),
       
  1377                       ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004)):
       
  1378                 self.canon_decode_tag(self.tags[i[0]].values, i[1])
       
  1379             return
       
  1380 
       
  1381     # decode Olympus MakerNote tag based on offset within tag
       
  1382     def olympus_decode_tag(self, value, dict):
       
  1383         pass
       
  1384 
       
  1385     # decode Canon MakerNote tag based on offset within tag
       
  1386     # see http://www.burren.cx/david/canon.html by David Burren
       
  1387     def canon_decode_tag(self, value, dict):
       
  1388         for i in range(1, len(value)):
       
  1389             x=dict.get(i, ('Unknown', ))
       
  1390             if self.debug:
       
  1391                 print i, x
       
  1392             name=x[0]
       
  1393             if len(x) > 1:
       
  1394                 val=x[1].get(value[i], 'Unknown')
       
  1395             else:
       
  1396                 val=value[i]
       
  1397             # it's not a real IFD Tag but we fake one to make everybody
       
  1398             # happy. this will have a "proprietary" type
       
  1399             self.tags['MakerNote '+name]=IFD_Tag(str(val), None, 0, None,
       
  1400                                                  None, None)
       
  1401 
       
  1402 # process an image file (expects an open file object)
       
  1403 # this is the function that has to deal with all the arbitrary nasty bits
       
  1404 # of the EXIF standard
       
  1405 def process_file(f, stop_tag='UNDEF', details=True, debug=False):
       
  1406     # yah it's cheesy...
       
  1407     global detailed
       
  1408     detailed = details
       
  1409 
       
  1410     # by default do not fake an EXIF beginning
       
  1411     fake_exif = 0
       
  1412 
       
  1413     # determine whether it's a JPEG or TIFF
       
  1414     data = f.read(12)
       
  1415     if data[0:4] in ['II*\x00', 'MM\x00*']:
       
  1416         # it's a TIFF file
       
  1417         f.seek(0)
       
  1418         endian = f.read(1)
       
  1419         f.read(1)
       
  1420         offset = 0
       
  1421     elif data[0:2] == '\xFF\xD8':
       
  1422         # it's a JPEG file
       
  1423         while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'):
       
  1424             length = ord(data[4])*256+ord(data[5])
       
  1425             f.read(length-8)
       
  1426             # fake an EXIF beginning of file
       
  1427             data = '\xFF\x00'+f.read(10)
       
  1428             fake_exif = 1
       
  1429         if data[2] == '\xFF' and data[6:10] == 'Exif':
       
  1430             # detected EXIF header
       
  1431             offset = f.tell()
       
  1432             endian = f.read(1)
       
  1433         else:
       
  1434             # no EXIF information
       
  1435             return {}
       
  1436     else:
       
  1437         # file format not recognized
       
  1438         return {}
       
  1439 
       
  1440     # deal with the EXIF info we found
       
  1441     if debug:
       
  1442         print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format'
       
  1443     hdr = EXIF_header(f, endian, offset, fake_exif, debug)
       
  1444     ifd_list = hdr.list_IFDs()
       
  1445     ctr = 0
       
  1446     for i in ifd_list:
       
  1447         if ctr == 0:
       
  1448             IFD_name = 'Image'
       
  1449         elif ctr == 1:
       
  1450             IFD_name = 'Thumbnail'
       
  1451             thumb_ifd = i
       
  1452         else:
       
  1453             IFD_name = 'IFD %d' % ctr
       
  1454         if debug:
       
  1455             print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i)
       
  1456         hdr.dump_IFD(i, IFD_name, stop_tag=stop_tag)
       
  1457         # EXIF IFD
       
  1458         exif_off = hdr.tags.get(IFD_name+' ExifOffset')
       
  1459         if exif_off:
       
  1460             if debug:
       
  1461                 print ' EXIF SubIFD at offset %d:' % exif_off.values[0]
       
  1462             hdr.dump_IFD(exif_off.values[0], 'EXIF', stop_tag=stop_tag)
       
  1463             # Interoperability IFD contained in EXIF IFD
       
  1464             intr_off = hdr.tags.get('EXIF SubIFD InteroperabilityOffset')
       
  1465             if intr_off:
       
  1466                 if debug:
       
  1467                     print ' EXIF Interoperability SubSubIFD at offset %d:' \
       
  1468                           % intr_off.values[0]
       
  1469                 hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability',
       
  1470                              dict=INTR_TAGS, stop_tag=stop_tag)
       
  1471         # GPS IFD
       
  1472         gps_off = hdr.tags.get(IFD_name+' GPSInfo')
       
  1473         if gps_off:
       
  1474             if debug:
       
  1475                 print ' GPS SubIFD at offset %d:' % gps_off.values[0]
       
  1476             hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS, stop_tag=stop_tag)
       
  1477         ctr += 1
       
  1478 
       
  1479     # extract uncompressed TIFF thumbnail
       
  1480     thumb = hdr.tags.get('Thumbnail Compression')
       
  1481     if thumb and thumb.printable == 'Uncompressed TIFF':
       
  1482         hdr.extract_TIFF_thumbnail(thumb_ifd)
       
  1483 
       
  1484     # JPEG thumbnail (thankfully the JPEG data is stored as a unit)
       
  1485     thumb_off = hdr.tags.get('Thumbnail JPEGInterchangeFormat')
       
  1486     if thumb_off:
       
  1487         f.seek(offset+thumb_off.values[0])
       
  1488         size = hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0]
       
  1489         hdr.tags['JPEGThumbnail'] = f.read(size)
       
  1490 
       
  1491     # deal with MakerNote contained in EXIF IFD
       
  1492     if 'EXIF MakerNote' in hdr.tags and detailed:
       
  1493         hdr.decode_maker_note()
       
  1494 
       
  1495     # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
       
  1496     # since it's not allowed in a uncompressed TIFF IFD
       
  1497     if 'JPEGThumbnail' not in hdr.tags:
       
  1498         thumb_off=hdr.tags.get('MakerNote JPEGThumbnail')
       
  1499         if thumb_off:
       
  1500             f.seek(offset+thumb_off.values[0])
       
  1501             hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length)
       
  1502 
       
  1503     return hdr.tags
       
  1504 
       
  1505 
       
  1506 # show command line usage
       
  1507 def usage(exit_status):
       
  1508     msg = 'Usage: EXIF.py [OPTIONS] file1 [file2 ...]\n'
       
  1509     msg += 'Extract EXIF information from digital camera image files.\n\nOptions:\n'
       
  1510     msg += '-q --quick   Do not process MakerNotes.\n'
       
  1511     msg += '-t TAG --stop-tag TAG   Stop processing when this tag is retrieved.\n'
       
  1512     msg += '-d --debug   Run in debug mode.\n'
       
  1513     print msg
       
  1514     sys.exit(exit_status)
       
  1515 
       
  1516 # library test/debug function (dump given files)
       
  1517 if __name__ == '__main__':
       
  1518     import sys
       
  1519     import getopt
       
  1520 
       
  1521     # parse command line options/arguments
       
  1522     try:
       
  1523         opts, args = getopt.getopt(sys.argv[1:], "hqdt:v", ["help", "quick", "debug", "stop-tag="])
       
  1524     except getopt.GetoptError:
       
  1525         usage(2)
       
  1526     if args == []:
       
  1527         usage(2)
       
  1528     detailed = True
       
  1529     stop_tag = 'UNDEF'
       
  1530     debug = False
       
  1531     for o, a in opts:
       
  1532         if o in ("-h", "--help"):
       
  1533             usage(0)
       
  1534         if o in ("-q", "--quick"):
       
  1535             detailed = False
       
  1536         if o in ("-t", "--stop-tag"):
       
  1537             stop_tag = a
       
  1538         if o in ("-d", "--debug"):
       
  1539             debug = True
       
  1540 
       
  1541     # output info for each file
       
  1542     for filename in args:
       
  1543         try:
       
  1544             file=open(filename, 'rb')
       
  1545         except:
       
  1546             print "'%s' is unreadable\n"%filename
       
  1547             continue
       
  1548         print filename + ':'
       
  1549         # get the tags
       
  1550         data = process_file(file, stop_tag=stop_tag, details=detailed, debug=debug)
       
  1551         if not data:
       
  1552             print 'No EXIF information found'
       
  1553             continue
       
  1554 
       
  1555         x=data.keys()
       
  1556         x.sort()
       
  1557         for i in x:
       
  1558             if i in ('JPEGThumbnail', 'TIFFThumbnail'):
       
  1559                 continue
       
  1560             try:
       
  1561                 print '   %s (%s): %s' % \
       
  1562                       (i, FIELD_TYPES[data[i].field_type][2], data[i].printable)
       
  1563             except:
       
  1564                 print 'error', i, '"', data[i], '"'
       
  1565         if 'JPEGThumbnail' in data:
       
  1566             print 'File has JPEG thumbnail'
       
  1567         print
       
  1568