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