terom@54: #!/usr/bin/env python terom@54: # -*- coding: utf-8 -*- terom@54: # terom@54: # Library to extract EXIF information from digital camera image files terom@54: # http://sourceforge.net/projects/exif-py/ terom@54: # terom@54: # VERSION 1.1.0 terom@54: # terom@54: # To use this library call with: terom@54: # f = open(path_name, 'rb') terom@54: # tags = EXIF.process_file(f) terom@54: # terom@54: # To ignore MakerNote tags, pass the -q or --quick terom@54: # command line arguments, or as terom@54: # tags = EXIF.process_file(f, details=False) terom@54: # terom@54: # To stop processing after a certain tag is retrieved, terom@54: # pass the -t TAG or --stop-tag TAG argument, or as terom@54: # tags = EXIF.process_file(f, stop_tag='TAG') terom@54: # terom@54: # where TAG is a valid tag name, ex 'DateTimeOriginal' terom@54: # terom@54: # These 2 are useful when you are retrieving a large list of images terom@54: # terom@54: # terom@54: # To return an error on invalid tags, terom@54: # pass the -s or --strict argument, or as terom@54: # tags = EXIF.process_file(f, strict=True) terom@54: # terom@54: # Otherwise these tags will be ignored terom@54: # terom@54: # Returned tags will be a dictionary mapping names of EXIF tags to their terom@54: # values in the file named by path_name. You can process the tags terom@54: # as you wish. In particular, you can iterate through all the tags with: terom@54: # for tag in tags.keys(): terom@54: # if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename', terom@54: # 'EXIF MakerNote'): terom@54: # print "Key: %s, value %s" % (tag, tags[tag]) terom@54: # (This code uses the if statement to avoid printing out a few of the terom@54: # tags that tend to be long or boring.) terom@54: # terom@54: # The tags dictionary will include keys for all of the usual EXIF terom@54: # tags, and will also include keys for Makernotes used by some terom@54: # cameras, for which we have a good specification. terom@54: # terom@54: # Note that the dictionary keys are the IFD name followed by the terom@54: # tag name. For example: terom@54: # 'EXIF DateTimeOriginal', 'Image Orientation', 'MakerNote FocusMode' terom@54: # terom@54: # Copyright (c) 2002-2007 Gene Cash All rights reserved terom@54: # Copyright (c) 2007-2008 Ianaré Sévi All rights reserved terom@54: # terom@54: # Redistribution and use in source and binary forms, with or without terom@54: # modification, are permitted provided that the following conditions terom@54: # are met: terom@54: # terom@54: # 1. Redistributions of source code must retain the above copyright terom@54: # notice, this list of conditions and the following disclaimer. terom@54: # terom@54: # 2. Redistributions in binary form must reproduce the above terom@54: # copyright notice, this list of conditions and the following terom@54: # disclaimer in the documentation and/or other materials provided terom@54: # with the distribution. terom@54: # terom@54: # 3. Neither the name of the authors nor the names of its contributors terom@54: # may be used to endorse or promote products derived from this terom@54: # software without specific prior written permission. terom@54: # terom@54: # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS terom@54: # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT terom@54: # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR terom@54: # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT terom@54: # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, terom@54: # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT terom@54: # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, terom@54: # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY terom@54: # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT terom@54: # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE terom@54: # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. terom@54: # terom@54: # terom@54: # ----- See 'changes.txt' file for all contributors and changes ----- # terom@54: # terom@98: import mmap terom@54: terom@54: # Don't throw an exception when given an out of range character. terom@54: def make_string(seq): terom@54: str = '' terom@54: for c in seq: terom@54: # Screen out non-printing characters terom@54: if 32 <= c and c < 256: terom@54: str += chr(c) terom@54: # If no printing chars terom@54: if not str: terom@54: return seq terom@54: return str terom@54: terom@54: # Special version to deal with the code in the first 8 bytes of a user comment. terom@54: # First 8 bytes gives coding system e.g. ASCII vs. JIS vs Unicode terom@54: def make_string_uc(seq): terom@54: code = seq[0:8] terom@54: seq = seq[8:] terom@54: # Of course, this is only correct if ASCII, and the standard explicitly terom@54: # allows JIS and Unicode. terom@54: return make_string(seq) terom@54: terom@54: # field type descriptions as (length, abbreviation, full name) tuples terom@54: FIELD_TYPES = ( terom@54: (0, 'X', 'Proprietary'), # no such type terom@54: (1, 'B', 'Byte'), terom@54: (1, 'A', 'ASCII'), terom@54: (2, 'S', 'Short'), terom@54: (4, 'L', 'Long'), terom@54: (8, 'R', 'Ratio'), terom@54: (1, 'SB', 'Signed Byte'), terom@54: (1, 'U', 'Undefined'), terom@54: (2, 'SS', 'Signed Short'), terom@54: (4, 'SL', 'Signed Long'), terom@54: (8, 'SR', 'Signed Ratio'), terom@54: ) terom@54: terom@54: # dictionary of main EXIF tag names terom@54: # first element of tuple is tag name, optional second element is terom@54: # another dictionary giving names to values terom@54: EXIF_TAGS = { terom@54: 0x0100: ('ImageWidth', ), terom@54: 0x0101: ('ImageLength', ), terom@54: 0x0102: ('BitsPerSample', ), terom@54: 0x0103: ('Compression', terom@54: {1: 'Uncompressed', terom@54: 2: 'CCITT 1D', terom@54: 3: 'T4/Group 3 Fax', terom@54: 4: 'T6/Group 4 Fax', terom@54: 5: 'LZW', terom@54: 6: 'JPEG (old-style)', terom@54: 7: 'JPEG', terom@54: 8: 'Adobe Deflate', terom@54: 9: 'JBIG B&W', terom@54: 10: 'JBIG Color', terom@54: 32766: 'Next', terom@54: 32769: 'Epson ERF Compressed', terom@54: 32771: 'CCIRLEW', terom@54: 32773: 'PackBits', terom@54: 32809: 'Thunderscan', terom@54: 32895: 'IT8CTPAD', terom@54: 32896: 'IT8LW', terom@54: 32897: 'IT8MP', terom@54: 32898: 'IT8BL', terom@54: 32908: 'PixarFilm', terom@54: 32909: 'PixarLog', terom@54: 32946: 'Deflate', terom@54: 32947: 'DCS', terom@54: 34661: 'JBIG', terom@54: 34676: 'SGILog', terom@54: 34677: 'SGILog24', terom@54: 34712: 'JPEG 2000', terom@54: 34713: 'Nikon NEF Compressed', terom@54: 65000: 'Kodak DCR Compressed', terom@54: 65535: 'Pentax PEF Compressed'}), terom@54: 0x0106: ('PhotometricInterpretation', ), terom@54: 0x0107: ('Thresholding', ), terom@54: 0x010A: ('FillOrder', ), terom@54: 0x010D: ('DocumentName', ), terom@54: 0x010E: ('ImageDescription', ), terom@54: 0x010F: ('Make', ), terom@54: 0x0110: ('Model', ), terom@54: 0x0111: ('StripOffsets', ), terom@54: 0x0112: ('Orientation', terom@54: {1: 'Horizontal (normal)', terom@54: 2: 'Mirrored horizontal', terom@54: 3: 'Rotated 180', terom@54: 4: 'Mirrored vertical', terom@54: 5: 'Mirrored horizontal then rotated 90 CCW', terom@54: 6: 'Rotated 90 CW', terom@54: 7: 'Mirrored horizontal then rotated 90 CW', terom@54: 8: 'Rotated 90 CCW'}), terom@54: 0x0115: ('SamplesPerPixel', ), terom@54: 0x0116: ('RowsPerStrip', ), terom@54: 0x0117: ('StripByteCounts', ), terom@54: 0x011A: ('XResolution', ), terom@54: 0x011B: ('YResolution', ), terom@54: 0x011C: ('PlanarConfiguration', ), terom@54: 0x011D: ('PageName', make_string), terom@54: 0x0128: ('ResolutionUnit', terom@54: {1: 'Not Absolute', terom@54: 2: 'Pixels/Inch', terom@54: 3: 'Pixels/Centimeter'}), terom@54: 0x012D: ('TransferFunction', ), terom@54: 0x0131: ('Software', ), terom@54: 0x0132: ('DateTime', ), terom@54: 0x013B: ('Artist', ), terom@54: 0x013E: ('WhitePoint', ), terom@54: 0x013F: ('PrimaryChromaticities', ), terom@54: 0x0156: ('TransferRange', ), terom@54: 0x0200: ('JPEGProc', ), terom@54: 0x0201: ('JPEGInterchangeFormat', ), terom@54: 0x0202: ('JPEGInterchangeFormatLength', ), terom@54: 0x0211: ('YCbCrCoefficients', ), terom@54: 0x0212: ('YCbCrSubSampling', ), terom@54: 0x0213: ('YCbCrPositioning', terom@54: {1: 'Centered', terom@54: 2: 'Co-sited'}), terom@54: 0x0214: ('ReferenceBlackWhite', ), terom@54: terom@54: 0x4746: ('Rating', ), terom@54: terom@54: 0x828D: ('CFARepeatPatternDim', ), terom@54: 0x828E: ('CFAPattern', ), terom@54: 0x828F: ('BatteryLevel', ), terom@54: 0x8298: ('Copyright', ), terom@54: 0x829A: ('ExposureTime', ), terom@54: 0x829D: ('FNumber', ), terom@54: 0x83BB: ('IPTC/NAA', ), terom@54: 0x8769: ('ExifOffset', ), terom@54: 0x8773: ('InterColorProfile', ), terom@54: 0x8822: ('ExposureProgram', terom@54: {0: 'Unidentified', terom@54: 1: 'Manual', terom@54: 2: 'Program Normal', terom@54: 3: 'Aperture Priority', terom@54: 4: 'Shutter Priority', terom@54: 5: 'Program Creative', terom@54: 6: 'Program Action', terom@54: 7: 'Portrait Mode', terom@54: 8: 'Landscape Mode'}), terom@54: 0x8824: ('SpectralSensitivity', ), terom@54: 0x8825: ('GPSInfo', ), terom@54: 0x8827: ('ISOSpeedRatings', ), terom@54: 0x8828: ('OECF', ), terom@54: 0x9000: ('ExifVersion', make_string), terom@54: 0x9003: ('DateTimeOriginal', ), terom@54: 0x9004: ('DateTimeDigitized', ), terom@54: 0x9101: ('ComponentsConfiguration', terom@54: {0: '', terom@54: 1: 'Y', terom@54: 2: 'Cb', terom@54: 3: 'Cr', terom@54: 4: 'Red', terom@54: 5: 'Green', terom@54: 6: 'Blue'}), terom@54: 0x9102: ('CompressedBitsPerPixel', ), terom@54: 0x9201: ('ShutterSpeedValue', ), terom@54: 0x9202: ('ApertureValue', ), terom@54: 0x9203: ('BrightnessValue', ), terom@54: 0x9204: ('ExposureBiasValue', ), terom@54: 0x9205: ('MaxApertureValue', ), terom@54: 0x9206: ('SubjectDistance', ), terom@54: 0x9207: ('MeteringMode', terom@54: {0: 'Unidentified', terom@54: 1: 'Average', terom@54: 2: 'CenterWeightedAverage', terom@54: 3: 'Spot', terom@54: 4: 'MultiSpot', terom@54: 5: 'Pattern'}), terom@54: 0x9208: ('LightSource', terom@54: {0: 'Unknown', terom@54: 1: 'Daylight', terom@54: 2: 'Fluorescent', terom@54: 3: 'Tungsten', terom@54: 9: 'Fine Weather', terom@54: 10: 'Flash', terom@54: 11: 'Shade', terom@54: 12: 'Daylight Fluorescent', terom@54: 13: 'Day White Fluorescent', terom@54: 14: 'Cool White Fluorescent', terom@54: 15: 'White Fluorescent', terom@54: 17: 'Standard Light A', terom@54: 18: 'Standard Light B', terom@54: 19: 'Standard Light C', terom@54: 20: 'D55', terom@54: 21: 'D65', terom@54: 22: 'D75', terom@54: 255: 'Other'}), terom@54: 0x9209: ('Flash', terom@54: {0: 'No', terom@54: 1: 'Fired', terom@54: 5: 'Fired (?)', # no return sensed terom@54: 7: 'Fired (!)', # return sensed terom@54: 9: 'Fill Fired', terom@54: 13: 'Fill Fired (?)', terom@54: 15: 'Fill Fired (!)', terom@54: 16: 'Off', terom@54: 24: 'Auto Off', terom@54: 25: 'Auto Fired', terom@54: 29: 'Auto Fired (?)', terom@54: 31: 'Auto Fired (!)', terom@54: 32: 'Not Available'}), terom@54: 0x920A: ('FocalLength', ), terom@54: 0x9214: ('SubjectArea', ), terom@54: 0x927C: ('MakerNote', ), terom@54: 0x9286: ('UserComment', make_string_uc), terom@54: 0x9290: ('SubSecTime', ), terom@54: 0x9291: ('SubSecTimeOriginal', ), terom@54: 0x9292: ('SubSecTimeDigitized', ), terom@54: terom@54: # used by Windows Explorer terom@54: 0x9C9B: ('XPTitle', ), terom@54: 0x9C9C: ('XPComment', ), terom@54: 0x9C9D: ('XPAuthor', ), #(ignored by Windows Explorer if Artist exists) terom@54: 0x9C9E: ('XPKeywords', ), terom@54: 0x9C9F: ('XPSubject', ), terom@54: terom@54: 0xA000: ('FlashPixVersion', make_string), terom@54: 0xA001: ('ColorSpace', terom@54: {1: 'sRGB', terom@54: 2: 'Adobe RGB', terom@54: 65535: 'Uncalibrated'}), terom@54: 0xA002: ('ExifImageWidth', ), terom@54: 0xA003: ('ExifImageLength', ), terom@54: 0xA005: ('InteroperabilityOffset', ), terom@54: 0xA20B: ('FlashEnergy', ), # 0x920B in TIFF/EP terom@54: 0xA20C: ('SpatialFrequencyResponse', ), # 0x920C terom@54: 0xA20E: ('FocalPlaneXResolution', ), # 0x920E terom@54: 0xA20F: ('FocalPlaneYResolution', ), # 0x920F terom@54: 0xA210: ('FocalPlaneResolutionUnit', ), # 0x9210 terom@54: 0xA214: ('SubjectLocation', ), # 0x9214 terom@54: 0xA215: ('ExposureIndex', ), # 0x9215 terom@54: 0xA217: ('SensingMethod', # 0x9217 terom@54: {1: 'Not defined', terom@54: 2: 'One-chip color area', terom@54: 3: 'Two-chip color area', terom@54: 4: 'Three-chip color area', terom@54: 5: 'Color sequential area', terom@54: 7: 'Trilinear', terom@54: 8: 'Color sequential linear'}), terom@54: 0xA300: ('FileSource', terom@54: {1: 'Film Scanner', terom@54: 2: 'Reflection Print Scanner', terom@54: 3: 'Digital Camera'}), terom@54: 0xA301: ('SceneType', terom@54: {1: 'Directly Photographed'}), terom@54: 0xA302: ('CVAPattern', ), terom@54: 0xA401: ('CustomRendered', terom@54: {0: 'Normal', terom@54: 1: 'Custom'}), terom@54: 0xA402: ('ExposureMode', terom@54: {0: 'Auto Exposure', terom@54: 1: 'Manual Exposure', terom@54: 2: 'Auto Bracket'}), terom@54: 0xA403: ('WhiteBalance', terom@54: {0: 'Auto', terom@54: 1: 'Manual'}), terom@54: 0xA404: ('DigitalZoomRatio', ), terom@54: 0xA405: ('FocalLengthIn35mmFilm', ), terom@54: 0xA406: ('SceneCaptureType', terom@54: {0: 'Standard', terom@54: 1: 'Landscape', terom@54: 2: 'Portrait', terom@54: 3: 'Night)'}), terom@54: 0xA407: ('GainControl', terom@54: {0: 'None', terom@54: 1: 'Low gain up', terom@54: 2: 'High gain up', terom@54: 3: 'Low gain down', terom@54: 4: 'High gain down'}), terom@54: 0xA408: ('Contrast', terom@54: {0: 'Normal', terom@54: 1: 'Soft', terom@54: 2: 'Hard'}), terom@54: 0xA409: ('Saturation', terom@54: {0: 'Normal', terom@54: 1: 'Soft', terom@54: 2: 'Hard'}), terom@54: 0xA40A: ('Sharpness', terom@54: {0: 'Normal', terom@54: 1: 'Soft', terom@54: 2: 'Hard'}), terom@54: 0xA40B: ('DeviceSettingDescription', ), terom@54: 0xA40C: ('SubjectDistanceRange', ), terom@54: 0xA500: ('Gamma', ), terom@54: 0xC4A5: ('PrintIM', ), terom@54: 0xEA1C: ('Padding', ), terom@54: } terom@54: terom@54: # interoperability tags terom@54: INTR_TAGS = { terom@54: 0x0001: ('InteroperabilityIndex', ), terom@54: 0x0002: ('InteroperabilityVersion', ), terom@54: 0x1000: ('RelatedImageFileFormat', ), terom@54: 0x1001: ('RelatedImageWidth', ), terom@54: 0x1002: ('RelatedImageLength', ), terom@54: } terom@54: terom@54: # GPS tags (not used yet, haven't seen camera with GPS) terom@54: GPS_TAGS = { terom@54: 0x0000: ('GPSVersionID', ), terom@54: 0x0001: ('GPSLatitudeRef', ), terom@54: 0x0002: ('GPSLatitude', ), terom@54: 0x0003: ('GPSLongitudeRef', ), terom@54: 0x0004: ('GPSLongitude', ), terom@54: 0x0005: ('GPSAltitudeRef', ), terom@54: 0x0006: ('GPSAltitude', ), terom@54: 0x0007: ('GPSTimeStamp', ), terom@54: 0x0008: ('GPSSatellites', ), terom@54: 0x0009: ('GPSStatus', ), terom@54: 0x000A: ('GPSMeasureMode', ), terom@54: 0x000B: ('GPSDOP', ), terom@54: 0x000C: ('GPSSpeedRef', ), terom@54: 0x000D: ('GPSSpeed', ), terom@54: 0x000E: ('GPSTrackRef', ), terom@54: 0x000F: ('GPSTrack', ), terom@54: 0x0010: ('GPSImgDirectionRef', ), terom@54: 0x0011: ('GPSImgDirection', ), terom@54: 0x0012: ('GPSMapDatum', ), terom@54: 0x0013: ('GPSDestLatitudeRef', ), terom@54: 0x0014: ('GPSDestLatitude', ), terom@54: 0x0015: ('GPSDestLongitudeRef', ), terom@54: 0x0016: ('GPSDestLongitude', ), terom@54: 0x0017: ('GPSDestBearingRef', ), terom@54: 0x0018: ('GPSDestBearing', ), terom@54: 0x0019: ('GPSDestDistanceRef', ), terom@54: 0x001A: ('GPSDestDistance', ), terom@54: 0x001D: ('GPSDate', ), terom@54: } terom@54: terom@54: # Ignore these tags when quick processing terom@54: # 0x927C is MakerNote Tags terom@54: # 0x9286 is user comment terom@54: IGNORE_TAGS=(0x9286, 0x927C) terom@54: terom@54: # http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp terom@54: def nikon_ev_bias(seq): terom@54: # First digit seems to be in steps of 1/6 EV. terom@54: # Does the third value mean the step size? It is usually 6, terom@54: # but it is 12 for the ExposureDifference. terom@54: # terom@54: # Check for an error condition that could cause a crash. terom@54: # This only happens if something has gone really wrong in terom@54: # reading the Nikon MakerNote. terom@54: if len( seq ) < 4 : return "" terom@54: # terom@54: if seq == [252, 1, 6, 0]: terom@54: return "-2/3 EV" terom@54: if seq == [253, 1, 6, 0]: terom@54: return "-1/2 EV" terom@54: if seq == [254, 1, 6, 0]: terom@54: return "-1/3 EV" terom@54: if seq == [0, 1, 6, 0]: terom@54: return "0 EV" terom@54: if seq == [2, 1, 6, 0]: terom@54: return "+1/3 EV" terom@54: if seq == [3, 1, 6, 0]: terom@54: return "+1/2 EV" terom@54: if seq == [4, 1, 6, 0]: terom@54: return "+2/3 EV" terom@54: # Handle combinations not in the table. terom@54: a = seq[0] terom@54: # Causes headaches for the +/- logic, so special case it. terom@54: if a == 0: terom@54: return "0 EV" terom@54: if a > 127: terom@54: a = 256 - a terom@54: ret_str = "-" terom@54: else: terom@54: ret_str = "+" terom@54: b = seq[2] # Assume third value means the step size terom@54: whole = a / b terom@54: a = a % b terom@54: if whole != 0: terom@54: ret_str = ret_str + str(whole) + " " terom@54: if a == 0: terom@54: ret_str = ret_str + "EV" terom@54: else: terom@54: r = Ratio(a, b) terom@54: ret_str = ret_str + r.__repr__() + " EV" terom@54: return ret_str terom@54: terom@54: # Nikon E99x MakerNote Tags terom@54: MAKERNOTE_NIKON_NEWER_TAGS={ terom@54: 0x0001: ('MakernoteVersion', make_string), # Sometimes binary terom@54: 0x0002: ('ISOSetting', make_string), terom@54: 0x0003: ('ColorMode', ), terom@54: 0x0004: ('Quality', ), terom@54: 0x0005: ('Whitebalance', ), terom@54: 0x0006: ('ImageSharpening', ), terom@54: 0x0007: ('FocusMode', ), terom@54: 0x0008: ('FlashSetting', ), terom@54: 0x0009: ('AutoFlashMode', ), terom@54: 0x000B: ('WhiteBalanceBias', ), terom@54: 0x000C: ('WhiteBalanceRBCoeff', ), terom@54: 0x000D: ('ProgramShift', nikon_ev_bias), terom@54: # Nearly the same as the other EV vals, but step size is 1/12 EV (?) terom@54: 0x000E: ('ExposureDifference', nikon_ev_bias), terom@54: 0x000F: ('ISOSelection', ), terom@54: 0x0011: ('NikonPreview', ), terom@54: 0x0012: ('FlashCompensation', nikon_ev_bias), terom@54: 0x0013: ('ISOSpeedRequested', ), terom@54: 0x0016: ('PhotoCornerCoordinates', ), terom@54: # 0x0017: Unknown, but most likely an EV value terom@54: 0x0018: ('FlashBracketCompensationApplied', nikon_ev_bias), terom@54: 0x0019: ('AEBracketCompensationApplied', ), terom@54: 0x001A: ('ImageProcessing', ), terom@54: 0x001B: ('CropHiSpeed', ), terom@54: 0x001D: ('SerialNumber', ), # Conflict with 0x00A0 ? terom@54: 0x001E: ('ColorSpace', ), terom@54: 0x001F: ('VRInfo', ), terom@54: 0x0020: ('ImageAuthentication', ), terom@54: 0x0022: ('ActiveDLighting', ), terom@54: 0x0023: ('PictureControl', ), terom@54: 0x0024: ('WorldTime', ), terom@54: 0x0025: ('ISOInfo', ), terom@54: 0x0080: ('ImageAdjustment', ), terom@54: 0x0081: ('ToneCompensation', ), terom@54: 0x0082: ('AuxiliaryLens', ), terom@54: 0x0083: ('LensType', ), terom@54: 0x0084: ('LensMinMaxFocalMaxAperture', ), terom@54: 0x0085: ('ManualFocusDistance', ), terom@54: 0x0086: ('DigitalZoomFactor', ), terom@54: 0x0087: ('FlashMode', terom@54: {0x00: 'Did Not Fire', terom@54: 0x01: 'Fired, Manual', terom@54: 0x07: 'Fired, External', terom@54: 0x08: 'Fired, Commander Mode ', terom@54: 0x09: 'Fired, TTL Mode'}), terom@54: 0x0088: ('AFFocusPosition', terom@54: {0x0000: 'Center', terom@54: 0x0100: 'Top', terom@54: 0x0200: 'Bottom', terom@54: 0x0300: 'Left', terom@54: 0x0400: 'Right'}), terom@54: 0x0089: ('BracketingMode', terom@54: {0x00: 'Single frame, no bracketing', terom@54: 0x01: 'Continuous, no bracketing', terom@54: 0x02: 'Timer, no bracketing', terom@54: 0x10: 'Single frame, exposure bracketing', terom@54: 0x11: 'Continuous, exposure bracketing', terom@54: 0x12: 'Timer, exposure bracketing', terom@54: 0x40: 'Single frame, white balance bracketing', terom@54: 0x41: 'Continuous, white balance bracketing', terom@54: 0x42: 'Timer, white balance bracketing'}), terom@54: 0x008A: ('AutoBracketRelease', ), terom@54: 0x008B: ('LensFStops', ), terom@54: 0x008C: ('NEFCurve1', ), # ExifTool calls this 'ContrastCurve' terom@54: 0x008D: ('ColorMode', ), terom@54: 0x008F: ('SceneMode', ), terom@54: 0x0090: ('LightingType', ), terom@54: 0x0091: ('ShotInfo', ), # First 4 bytes are a version number in ASCII terom@54: 0x0092: ('HueAdjustment', ), terom@54: # ExifTool calls this 'NEFCompression', should be 1-4 terom@54: 0x0093: ('Compression', ), terom@54: 0x0094: ('Saturation', terom@54: {-3: 'B&W', terom@54: -2: '-2', terom@54: -1: '-1', terom@54: 0: '0', terom@54: 1: '1', terom@54: 2: '2'}), terom@54: 0x0095: ('NoiseReduction', ), terom@54: 0x0096: ('NEFCurve2', ), # ExifTool calls this 'LinearizationTable' terom@54: 0x0097: ('ColorBalance', ), # First 4 bytes are a version number in ASCII terom@54: 0x0098: ('LensData', ), # First 4 bytes are a version number in ASCII terom@54: 0x0099: ('RawImageCenter', ), terom@54: 0x009A: ('SensorPixelSize', ), terom@54: 0x009C: ('Scene Assist', ), terom@54: 0x009E: ('RetouchHistory', ), terom@54: 0x00A0: ('SerialNumber', ), terom@54: 0x00A2: ('ImageDataSize', ), terom@54: # 00A3: unknown - a single byte 0 terom@54: # 00A4: In NEF, looks like a 4 byte ASCII version number ('0200') terom@54: 0x00A5: ('ImageCount', ), terom@54: 0x00A6: ('DeletedImageCount', ), terom@54: 0x00A7: ('TotalShutterReleases', ), terom@54: # First 4 bytes are a version number in ASCII, with version specific terom@54: # info to follow. Its hard to treat it as a string due to embedded nulls. terom@54: 0x00A8: ('FlashInfo', ), terom@54: 0x00A9: ('ImageOptimization', ), terom@54: 0x00AA: ('Saturation', ), terom@54: 0x00AB: ('DigitalVariProgram', ), terom@54: 0x00AC: ('ImageStabilization', ), terom@54: 0x00AD: ('Responsive AF', ), # 'AFResponse' terom@54: 0x00B0: ('MultiExposure', ), terom@54: 0x00B1: ('HighISONoiseReduction', ), terom@54: 0x00B7: ('AFInfo', ), terom@54: 0x00B8: ('FileInfo', ), terom@54: # 00B9: unknown terom@54: 0x0100: ('DigitalICE', ), terom@54: 0x0103: ('PreviewCompression', terom@54: {1: 'Uncompressed', terom@54: 2: 'CCITT 1D', terom@54: 3: 'T4/Group 3 Fax', terom@54: 4: 'T6/Group 4 Fax', terom@54: 5: 'LZW', terom@54: 6: 'JPEG (old-style)', terom@54: 7: 'JPEG', terom@54: 8: 'Adobe Deflate', terom@54: 9: 'JBIG B&W', terom@54: 10: 'JBIG Color', terom@54: 32766: 'Next', terom@54: 32769: 'Epson ERF Compressed', terom@54: 32771: 'CCIRLEW', terom@54: 32773: 'PackBits', terom@54: 32809: 'Thunderscan', terom@54: 32895: 'IT8CTPAD', terom@54: 32896: 'IT8LW', terom@54: 32897: 'IT8MP', terom@54: 32898: 'IT8BL', terom@54: 32908: 'PixarFilm', terom@54: 32909: 'PixarLog', terom@54: 32946: 'Deflate', terom@54: 32947: 'DCS', terom@54: 34661: 'JBIG', terom@54: 34676: 'SGILog', terom@54: 34677: 'SGILog24', terom@54: 34712: 'JPEG 2000', terom@54: 34713: 'Nikon NEF Compressed', terom@54: 65000: 'Kodak DCR Compressed', terom@54: 65535: 'Pentax PEF Compressed',}), terom@54: 0x0201: ('PreviewImageStart', ), terom@54: 0x0202: ('PreviewImageLength', ), terom@54: 0x0213: ('PreviewYCbCrPositioning', terom@54: {1: 'Centered', terom@54: 2: 'Co-sited'}), terom@54: 0x0010: ('DataDump', ), terom@54: } terom@54: terom@54: MAKERNOTE_NIKON_OLDER_TAGS = { terom@54: 0x0003: ('Quality', terom@54: {1: 'VGA Basic', terom@54: 2: 'VGA Normal', terom@54: 3: 'VGA Fine', terom@54: 4: 'SXGA Basic', terom@54: 5: 'SXGA Normal', terom@54: 6: 'SXGA Fine'}), terom@54: 0x0004: ('ColorMode', terom@54: {1: 'Color', terom@54: 2: 'Monochrome'}), terom@54: 0x0005: ('ImageAdjustment', terom@54: {0: 'Normal', terom@54: 1: 'Bright+', terom@54: 2: 'Bright-', terom@54: 3: 'Contrast+', terom@54: 4: 'Contrast-'}), terom@54: 0x0006: ('CCDSpeed', terom@54: {0: 'ISO 80', terom@54: 2: 'ISO 160', terom@54: 4: 'ISO 320', terom@54: 5: 'ISO 100'}), terom@54: 0x0007: ('WhiteBalance', terom@54: {0: 'Auto', terom@54: 1: 'Preset', terom@54: 2: 'Daylight', terom@54: 3: 'Incandescent', terom@54: 4: 'Fluorescent', terom@54: 5: 'Cloudy', terom@54: 6: 'Speed Light'}), terom@54: } terom@54: terom@54: # decode Olympus SpecialMode tag in MakerNote terom@54: def olympus_special_mode(v): terom@54: a={ terom@54: 0: 'Normal', terom@54: 1: 'Unknown', terom@54: 2: 'Fast', terom@54: 3: 'Panorama'} terom@54: b={ terom@54: 0: 'Non-panoramic', terom@54: 1: 'Left to right', terom@54: 2: 'Right to left', terom@54: 3: 'Bottom to top', terom@54: 4: 'Top to bottom'} terom@54: if v[0] not in a or v[2] not in b: terom@54: return v terom@54: return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]]) terom@54: terom@54: MAKERNOTE_OLYMPUS_TAGS={ terom@54: # ah HAH! those sneeeeeaky bastids! this is how they get past the fact terom@54: # that a JPEG thumbnail is not allowed in an uncompressed TIFF file terom@54: 0x0100: ('JPEGThumbnail', ), terom@54: 0x0200: ('SpecialMode', olympus_special_mode), terom@54: 0x0201: ('JPEGQual', terom@54: {1: 'SQ', terom@54: 2: 'HQ', terom@54: 3: 'SHQ'}), terom@54: 0x0202: ('Macro', terom@54: {0: 'Normal', terom@54: 1: 'Macro', terom@54: 2: 'SuperMacro'}), terom@54: 0x0203: ('BWMode', terom@54: {0: 'Off', terom@54: 1: 'On'}), terom@54: 0x0204: ('DigitalZoom', ), terom@54: 0x0205: ('FocalPlaneDiagonal', ), terom@54: 0x0206: ('LensDistortionParams', ), terom@54: 0x0207: ('SoftwareRelease', ), terom@54: 0x0208: ('PictureInfo', ), terom@54: 0x0209: ('CameraID', make_string), # print as string terom@54: 0x0F00: ('DataDump', ), terom@54: 0x0300: ('PreCaptureFrames', ), terom@54: 0x0404: ('SerialNumber', ), terom@54: 0x1000: ('ShutterSpeedValue', ), terom@54: 0x1001: ('ISOValue', ), terom@54: 0x1002: ('ApertureValue', ), terom@54: 0x1003: ('BrightnessValue', ), terom@54: 0x1004: ('FlashMode', ), terom@54: 0x1004: ('FlashMode', terom@54: {2: 'On', terom@54: 3: 'Off'}), terom@54: 0x1005: ('FlashDevice', terom@54: {0: 'None', terom@54: 1: 'Internal', terom@54: 4: 'External', terom@54: 5: 'Internal + External'}), terom@54: 0x1006: ('ExposureCompensation', ), terom@54: 0x1007: ('SensorTemperature', ), terom@54: 0x1008: ('LensTemperature', ), terom@54: 0x100b: ('FocusMode', terom@54: {0: 'Auto', terom@54: 1: 'Manual'}), terom@54: 0x1017: ('RedBalance', ), terom@54: 0x1018: ('BlueBalance', ), terom@54: 0x101a: ('SerialNumber', ), terom@54: 0x1023: ('FlashExposureComp', ), terom@54: 0x1026: ('ExternalFlashBounce', terom@54: {0: 'No', terom@54: 1: 'Yes'}), terom@54: 0x1027: ('ExternalFlashZoom', ), terom@54: 0x1028: ('ExternalFlashMode', ), terom@54: 0x1029: ('Contrast int16u', terom@54: {0: 'High', terom@54: 1: 'Normal', terom@54: 2: 'Low'}), terom@54: 0x102a: ('SharpnessFactor', ), terom@54: 0x102b: ('ColorControl', ), terom@54: 0x102c: ('ValidBits', ), terom@54: 0x102d: ('CoringFilter', ), terom@54: 0x102e: ('OlympusImageWidth', ), terom@54: 0x102f: ('OlympusImageHeight', ), terom@54: 0x1034: ('CompressionRatio', ), terom@54: 0x1035: ('PreviewImageValid', terom@54: {0: 'No', terom@54: 1: 'Yes'}), terom@54: 0x1036: ('PreviewImageStart', ), terom@54: 0x1037: ('PreviewImageLength', ), terom@54: 0x1039: ('CCDScanMode', terom@54: {0: 'Interlaced', terom@54: 1: 'Progressive'}), terom@54: 0x103a: ('NoiseReduction', terom@54: {0: 'Off', terom@54: 1: 'On'}), terom@54: 0x103b: ('InfinityLensStep', ), terom@54: 0x103c: ('NearLensStep', ), terom@54: terom@54: # TODO - these need extra definitions terom@54: # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html terom@54: 0x2010: ('Equipment', ), terom@54: 0x2020: ('CameraSettings', ), terom@54: 0x2030: ('RawDevelopment', ), terom@54: 0x2040: ('ImageProcessing', ), terom@54: 0x2050: ('FocusInfo', ), terom@54: 0x3000: ('RawInfo ', ), terom@54: } terom@54: terom@54: # 0x2020 CameraSettings terom@54: MAKERNOTE_OLYMPUS_TAG_0x2020={ terom@54: 0x0100: ('PreviewImageValid', terom@54: {0: 'No', terom@54: 1: 'Yes'}), terom@54: 0x0101: ('PreviewImageStart', ), terom@54: 0x0102: ('PreviewImageLength', ), terom@54: 0x0200: ('ExposureMode', terom@54: {1: 'Manual', terom@54: 2: 'Program', terom@54: 3: 'Aperture-priority AE', terom@54: 4: 'Shutter speed priority AE', terom@54: 5: 'Program-shift'}), terom@54: 0x0201: ('AELock', terom@54: {0: 'Off', terom@54: 1: 'On'}), terom@54: 0x0202: ('MeteringMode', terom@54: {2: 'Center Weighted', terom@54: 3: 'Spot', terom@54: 5: 'ESP', terom@54: 261: 'Pattern+AF', terom@54: 515: 'Spot+Highlight control', terom@54: 1027: 'Spot+Shadow control'}), terom@54: 0x0300: ('MacroMode', terom@54: {0: 'Off', terom@54: 1: 'On'}), terom@54: 0x0301: ('FocusMode', terom@54: {0: 'Single AF', terom@54: 1: 'Sequential shooting AF', terom@54: 2: 'Continuous AF', terom@54: 3: 'Multi AF', terom@54: 10: 'MF'}), terom@54: 0x0302: ('FocusProcess', terom@54: {0: 'AF Not Used', terom@54: 1: 'AF Used'}), terom@54: 0x0303: ('AFSearch', terom@54: {0: 'Not Ready', terom@54: 1: 'Ready'}), terom@54: 0x0304: ('AFAreas', ), terom@54: 0x0401: ('FlashExposureCompensation', ), terom@54: 0x0500: ('WhiteBalance2', terom@54: {0: 'Auto', terom@54: 16: '7500K (Fine Weather with Shade)', terom@54: 17: '6000K (Cloudy)', terom@54: 18: '5300K (Fine Weather)', terom@54: 20: '3000K (Tungsten light)', terom@54: 21: '3600K (Tungsten light-like)', terom@54: 33: '6600K (Daylight fluorescent)', terom@54: 34: '4500K (Neutral white fluorescent)', terom@54: 35: '4000K (Cool white fluorescent)', terom@54: 48: '3600K (Tungsten light-like)', terom@54: 256: 'Custom WB 1', terom@54: 257: 'Custom WB 2', terom@54: 258: 'Custom WB 3', terom@54: 259: 'Custom WB 4', terom@54: 512: 'Custom WB 5400K', terom@54: 513: 'Custom WB 2900K', terom@54: 514: 'Custom WB 8000K', }), terom@54: 0x0501: ('WhiteBalanceTemperature', ), terom@54: 0x0502: ('WhiteBalanceBracket', ), terom@54: 0x0503: ('CustomSaturation', ), # (3 numbers: 1. CS Value, 2. Min, 3. Max) terom@54: 0x0504: ('ModifiedSaturation', terom@54: {0: 'Off', terom@54: 1: 'CM1 (Red Enhance)', terom@54: 2: 'CM2 (Green Enhance)', terom@54: 3: 'CM3 (Blue Enhance)', terom@54: 4: 'CM4 (Skin Tones)'}), terom@54: 0x0505: ('ContrastSetting', ), # (3 numbers: 1. Contrast, 2. Min, 3. Max) terom@54: 0x0506: ('SharpnessSetting', ), # (3 numbers: 1. Sharpness, 2. Min, 3. Max) terom@54: 0x0507: ('ColorSpace', terom@54: {0: 'sRGB', terom@54: 1: 'Adobe RGB', terom@54: 2: 'Pro Photo RGB'}), terom@54: 0x0509: ('SceneMode', terom@54: {0: 'Standard', terom@54: 6: 'Auto', terom@54: 7: 'Sport', terom@54: 8: 'Portrait', terom@54: 9: 'Landscape+Portrait', terom@54: 10: 'Landscape', terom@54: 11: 'Night scene', terom@54: 13: 'Panorama', terom@54: 16: 'Landscape+Portrait', terom@54: 17: 'Night+Portrait', terom@54: 19: 'Fireworks', terom@54: 20: 'Sunset', terom@54: 22: 'Macro', terom@54: 25: 'Documents', terom@54: 26: 'Museum', terom@54: 28: 'Beach&Snow', terom@54: 30: 'Candle', terom@54: 35: 'Underwater Wide1', terom@54: 36: 'Underwater Macro', terom@54: 39: 'High Key', terom@54: 40: 'Digital Image Stabilization', terom@54: 44: 'Underwater Wide2', terom@54: 45: 'Low Key', terom@54: 46: 'Children', terom@54: 48: 'Nature Macro'}), terom@54: 0x050a: ('NoiseReduction', terom@54: {0: 'Off', terom@54: 1: 'Noise Reduction', terom@54: 2: 'Noise Filter', terom@54: 3: 'Noise Reduction + Noise Filter', terom@54: 4: 'Noise Filter (ISO Boost)', terom@54: 5: 'Noise Reduction + Noise Filter (ISO Boost)'}), terom@54: 0x050b: ('DistortionCorrection', terom@54: {0: 'Off', terom@54: 1: 'On'}), terom@54: 0x050c: ('ShadingCompensation', terom@54: {0: 'Off', terom@54: 1: 'On'}), terom@54: 0x050d: ('CompressionFactor', ), terom@54: 0x050f: ('Gradation', terom@54: {'-1 -1 1': 'Low Key', terom@54: '0 -1 1': 'Normal', terom@54: '1 -1 1': 'High Key'}), terom@54: 0x0520: ('PictureMode', terom@54: {1: 'Vivid', terom@54: 2: 'Natural', terom@54: 3: 'Muted', terom@54: 256: 'Monotone', terom@54: 512: 'Sepia'}), terom@54: 0x0521: ('PictureModeSaturation', ), terom@54: 0x0522: ('PictureModeHue?', ), terom@54: 0x0523: ('PictureModeContrast', ), terom@54: 0x0524: ('PictureModeSharpness', ), terom@54: 0x0525: ('PictureModeBWFilter', terom@54: {0: 'n/a', terom@54: 1: 'Neutral', terom@54: 2: 'Yellow', terom@54: 3: 'Orange', terom@54: 4: 'Red', terom@54: 5: 'Green'}), terom@54: 0x0526: ('PictureModeTone', terom@54: {0: 'n/a', terom@54: 1: 'Neutral', terom@54: 2: 'Sepia', terom@54: 3: 'Blue', terom@54: 4: 'Purple', terom@54: 5: 'Green'}), terom@54: 0x0600: ('Sequence', ), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits terom@54: 0x0601: ('PanoramaMode', ), # (2 numbers: 1. Mode, 2. Shot number) terom@54: 0x0603: ('ImageQuality2', terom@54: {1: 'SQ', terom@54: 2: 'HQ', terom@54: 3: 'SHQ', terom@54: 4: 'RAW'}), terom@54: 0x0901: ('ManometerReading', ), terom@54: } terom@54: terom@54: terom@54: MAKERNOTE_CASIO_TAGS={ terom@54: 0x0001: ('RecordingMode', terom@54: {1: 'Single Shutter', terom@54: 2: 'Panorama', terom@54: 3: 'Night Scene', terom@54: 4: 'Portrait', terom@54: 5: 'Landscape'}), terom@54: 0x0002: ('Quality', terom@54: {1: 'Economy', terom@54: 2: 'Normal', terom@54: 3: 'Fine'}), terom@54: 0x0003: ('FocusingMode', terom@54: {2: 'Macro', terom@54: 3: 'Auto Focus', terom@54: 4: 'Manual Focus', terom@54: 5: 'Infinity'}), terom@54: 0x0004: ('FlashMode', terom@54: {1: 'Auto', terom@54: 2: 'On', terom@54: 3: 'Off', terom@54: 4: 'Red Eye Reduction'}), terom@54: 0x0005: ('FlashIntensity', terom@54: {11: 'Weak', terom@54: 13: 'Normal', terom@54: 15: 'Strong'}), terom@54: 0x0006: ('Object Distance', ), terom@54: 0x0007: ('WhiteBalance', terom@54: {1: 'Auto', terom@54: 2: 'Tungsten', terom@54: 3: 'Daylight', terom@54: 4: 'Fluorescent', terom@54: 5: 'Shade', terom@54: 129: 'Manual'}), terom@54: 0x000B: ('Sharpness', terom@54: {0: 'Normal', terom@54: 1: 'Soft', terom@54: 2: 'Hard'}), terom@54: 0x000C: ('Contrast', terom@54: {0: 'Normal', terom@54: 1: 'Low', terom@54: 2: 'High'}), terom@54: 0x000D: ('Saturation', terom@54: {0: 'Normal', terom@54: 1: 'Low', terom@54: 2: 'High'}), terom@54: 0x0014: ('CCDSpeed', terom@54: {64: 'Normal', terom@54: 80: 'Normal', terom@54: 100: 'High', terom@54: 125: '+1.0', terom@54: 244: '+3.0', terom@54: 250: '+2.0'}), terom@54: } terom@54: terom@54: MAKERNOTE_FUJIFILM_TAGS={ terom@54: 0x0000: ('NoteVersion', make_string), terom@54: 0x1000: ('Quality', ), terom@54: 0x1001: ('Sharpness', terom@54: {1: 'Soft', terom@54: 2: 'Soft', terom@54: 3: 'Normal', terom@54: 4: 'Hard', terom@54: 5: 'Hard'}), terom@54: 0x1002: ('WhiteBalance', terom@54: {0: 'Auto', terom@54: 256: 'Daylight', terom@54: 512: 'Cloudy', terom@54: 768: 'DaylightColor-Fluorescent', terom@54: 769: 'DaywhiteColor-Fluorescent', terom@54: 770: 'White-Fluorescent', terom@54: 1024: 'Incandescent', terom@54: 3840: 'Custom'}), terom@54: 0x1003: ('Color', terom@54: {0: 'Normal', terom@54: 256: 'High', terom@54: 512: 'Low'}), terom@54: 0x1004: ('Tone', terom@54: {0: 'Normal', terom@54: 256: 'High', terom@54: 512: 'Low'}), terom@54: 0x1010: ('FlashMode', terom@54: {0: 'Auto', terom@54: 1: 'On', terom@54: 2: 'Off', terom@54: 3: 'Red Eye Reduction'}), terom@54: 0x1011: ('FlashStrength', ), terom@54: 0x1020: ('Macro', terom@54: {0: 'Off', terom@54: 1: 'On'}), terom@54: 0x1021: ('FocusMode', terom@54: {0: 'Auto', terom@54: 1: 'Manual'}), terom@54: 0x1030: ('SlowSync', terom@54: {0: 'Off', terom@54: 1: 'On'}), terom@54: 0x1031: ('PictureMode', terom@54: {0: 'Auto', terom@54: 1: 'Portrait', terom@54: 2: 'Landscape', terom@54: 4: 'Sports', terom@54: 5: 'Night', terom@54: 6: 'Program AE', terom@54: 256: 'Aperture Priority AE', terom@54: 512: 'Shutter Priority AE', terom@54: 768: 'Manual Exposure'}), terom@54: 0x1100: ('MotorOrBracket', terom@54: {0: 'Off', terom@54: 1: 'On'}), terom@54: 0x1300: ('BlurWarning', terom@54: {0: 'Off', terom@54: 1: 'On'}), terom@54: 0x1301: ('FocusWarning', terom@54: {0: 'Off', terom@54: 1: 'On'}), terom@54: 0x1302: ('AEWarning', terom@54: {0: 'Off', terom@54: 1: 'On'}), terom@54: } terom@54: terom@54: MAKERNOTE_CANON_TAGS = { terom@54: 0x0006: ('ImageType', ), terom@54: 0x0007: ('FirmwareVersion', ), terom@54: 0x0008: ('ImageNumber', ), terom@54: 0x0009: ('OwnerName', ), terom@54: } terom@54: terom@54: # this is in element offset, name, optional value dictionary format terom@54: MAKERNOTE_CANON_TAG_0x001 = { terom@54: 1: ('Macromode', terom@54: {1: 'Macro', terom@54: 2: 'Normal'}), terom@54: 2: ('SelfTimer', ), terom@54: 3: ('Quality', terom@54: {2: 'Normal', terom@54: 3: 'Fine', terom@54: 5: 'Superfine'}), terom@54: 4: ('FlashMode', terom@54: {0: 'Flash Not Fired', terom@54: 1: 'Auto', terom@54: 2: 'On', terom@54: 3: 'Red-Eye Reduction', terom@54: 4: 'Slow Synchro', terom@54: 5: 'Auto + Red-Eye Reduction', terom@54: 6: 'On + Red-Eye Reduction', terom@54: 16: 'external flash'}), terom@54: 5: ('ContinuousDriveMode', terom@54: {0: 'Single Or Timer', terom@54: 1: 'Continuous'}), terom@54: 7: ('FocusMode', terom@54: {0: 'One-Shot', terom@54: 1: 'AI Servo', terom@54: 2: 'AI Focus', terom@54: 3: 'MF', terom@54: 4: 'Single', terom@54: 5: 'Continuous', terom@54: 6: 'MF'}), terom@54: 10: ('ImageSize', terom@54: {0: 'Large', terom@54: 1: 'Medium', terom@54: 2: 'Small'}), terom@54: 11: ('EasyShootingMode', terom@54: {0: 'Full Auto', terom@54: 1: 'Manual', terom@54: 2: 'Landscape', terom@54: 3: 'Fast Shutter', terom@54: 4: 'Slow Shutter', terom@54: 5: 'Night', terom@54: 6: 'B&W', terom@54: 7: 'Sepia', terom@54: 8: 'Portrait', terom@54: 9: 'Sports', terom@54: 10: 'Macro/Close-Up', terom@54: 11: 'Pan Focus'}), terom@54: 12: ('DigitalZoom', terom@54: {0: 'None', terom@54: 1: '2x', terom@54: 2: '4x'}), terom@54: 13: ('Contrast', terom@54: {0xFFFF: 'Low', terom@54: 0: 'Normal', terom@54: 1: 'High'}), terom@54: 14: ('Saturation', terom@54: {0xFFFF: 'Low', terom@54: 0: 'Normal', terom@54: 1: 'High'}), terom@54: 15: ('Sharpness', terom@54: {0xFFFF: 'Low', terom@54: 0: 'Normal', terom@54: 1: 'High'}), terom@54: 16: ('ISO', terom@54: {0: 'See ISOSpeedRatings Tag', terom@54: 15: 'Auto', terom@54: 16: '50', terom@54: 17: '100', terom@54: 18: '200', terom@54: 19: '400'}), terom@54: 17: ('MeteringMode', terom@54: {3: 'Evaluative', terom@54: 4: 'Partial', terom@54: 5: 'Center-weighted'}), terom@54: 18: ('FocusType', terom@54: {0: 'Manual', terom@54: 1: 'Auto', terom@54: 3: 'Close-Up (Macro)', terom@54: 8: 'Locked (Pan Mode)'}), terom@54: 19: ('AFPointSelected', terom@54: {0x3000: 'None (MF)', terom@54: 0x3001: 'Auto-Selected', terom@54: 0x3002: 'Right', terom@54: 0x3003: 'Center', terom@54: 0x3004: 'Left'}), terom@54: 20: ('ExposureMode', terom@54: {0: 'Easy Shooting', terom@54: 1: 'Program', terom@54: 2: 'Tv-priority', terom@54: 3: 'Av-priority', terom@54: 4: 'Manual', terom@54: 5: 'A-DEP'}), terom@54: 23: ('LongFocalLengthOfLensInFocalUnits', ), terom@54: 24: ('ShortFocalLengthOfLensInFocalUnits', ), terom@54: 25: ('FocalUnitsPerMM', ), terom@54: 28: ('FlashActivity', terom@54: {0: 'Did Not Fire', terom@54: 1: 'Fired'}), terom@54: 29: ('FlashDetails', terom@54: {14: 'External E-TTL', terom@54: 13: 'Internal Flash', terom@54: 11: 'FP Sync Used', terom@54: 7: '2nd("Rear")-Curtain Sync Used', terom@54: 4: 'FP Sync Enabled'}), terom@54: 32: ('FocusMode', terom@54: {0: 'Single', terom@54: 1: 'Continuous'}), terom@54: } terom@54: terom@54: MAKERNOTE_CANON_TAG_0x004 = { terom@54: 7: ('WhiteBalance', terom@54: {0: 'Auto', terom@54: 1: 'Sunny', terom@54: 2: 'Cloudy', terom@54: 3: 'Tungsten', terom@54: 4: 'Fluorescent', terom@54: 5: 'Flash', terom@54: 6: 'Custom'}), terom@54: 9: ('SequenceNumber', ), terom@54: 14: ('AFPointUsed', ), terom@54: 15: ('FlashBias', terom@54: {0xFFC0: '-2 EV', terom@54: 0xFFCC: '-1.67 EV', terom@54: 0xFFD0: '-1.50 EV', terom@54: 0xFFD4: '-1.33 EV', terom@54: 0xFFE0: '-1 EV', terom@54: 0xFFEC: '-0.67 EV', terom@54: 0xFFF0: '-0.50 EV', terom@54: 0xFFF4: '-0.33 EV', terom@54: 0x0000: '0 EV', terom@54: 0x000C: '0.33 EV', terom@54: 0x0010: '0.50 EV', terom@54: 0x0014: '0.67 EV', terom@54: 0x0020: '1 EV', terom@54: 0x002C: '1.33 EV', terom@54: 0x0030: '1.50 EV', terom@54: 0x0034: '1.67 EV', terom@54: 0x0040: '2 EV'}), terom@54: 19: ('SubjectDistance', ), terom@54: } terom@54: terom@54: # extract multibyte integer in Motorola format (little endian) terom@54: def s2n_motorola(str): terom@54: x = 0 terom@54: for c in str: terom@54: x = (x << 8) | ord(c) terom@54: return x terom@54: terom@54: # extract multibyte integer in Intel format (big endian) terom@54: def s2n_intel(str): terom@54: x = 0 terom@54: y = 0L terom@54: for c in str: terom@54: x = x | (ord(c) << y) terom@54: y = y + 8 terom@54: return x terom@54: terom@54: # ratio object that eventually will be able to reduce itself to lowest terom@54: # common denominator for printing terom@54: def gcd(a, b): terom@54: if b == 0: terom@54: return a terom@54: else: terom@54: return gcd(b, a % b) terom@54: terom@54: class Ratio: terom@54: def __init__(self, num, den): terom@54: self.num = num terom@54: self.den = den terom@54: terom@54: def __repr__(self): terom@54: self.reduce() terom@54: if self.den == 1: terom@54: return str(self.num) terom@54: return '%d/%d' % (self.num, self.den) terom@54: terom@54: def reduce(self): terom@54: div = gcd(self.num, self.den) terom@54: if div > 1: terom@54: self.num = self.num / div terom@54: self.den = self.den / div terom@54: terom@54: # for ease of dealing with tags terom@54: class IFD_Tag: terom@54: def __init__(self, printable, tag, field_type, values, field_offset, terom@54: field_length): terom@54: # printable version of data terom@54: self.printable = printable terom@54: # tag ID number terom@54: self.tag = tag terom@54: # field type as index into FIELD_TYPES terom@54: self.field_type = field_type terom@54: # offset of start of field in bytes from beginning of IFD terom@54: self.field_offset = field_offset terom@54: # length of data field in bytes terom@54: self.field_length = field_length terom@54: # either a string or array of data items terom@54: self.values = values terom@54: terom@54: def __str__(self): terom@54: return self.printable terom@54: terom@54: def __repr__(self): terom@54: return '(0x%04X) %s=%s @ %d' % (self.tag, terom@54: FIELD_TYPES[self.field_type][2], terom@54: self.printable, terom@54: self.field_offset) terom@54: terom@54: # class that handles an EXIF header terom@54: class EXIF_header: terom@54: def __init__(self, file, endian, offset, fake_exif, strict, debug=0): terom@54: self.file = file terom@98: self.mmap = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) terom@54: self.endian = endian terom@54: self.offset = offset terom@54: self.fake_exif = fake_exif terom@54: self.strict = strict terom@54: self.debug = debug terom@54: self.tags = {} terom@98: terom@98: def pread(self, reloffset, length): terom@98: """Read bytes from self.file at relative offset """ terom@98: terom@98: offset = self.offset + reloffset terom@98: terom@98: return self.mmap[offset:offset + length] terom@54: terom@54: # convert slice to integer, based on sign and endian flags terom@54: # usually this offset is assumed to be relative to the beginning of the terom@54: # start of the EXIF information. For some cameras that use relative tags, terom@54: # this offset may be relative to some other starting point. terom@54: def s2n(self, offset, length, signed=0): terom@98: slice=self.pread(offset, length) terom@54: if self.endian == 'I': terom@54: val=s2n_intel(slice) terom@54: else: terom@54: val=s2n_motorola(slice) terom@54: # Sign extension ? terom@54: if signed: terom@54: msb=1L << (8*length-1) terom@54: if val & msb: terom@54: val=val-(msb << 1) terom@54: return val terom@54: terom@54: # convert offset to string terom@54: def n2s(self, offset, length): terom@54: s = '' terom@54: for dummy in range(length): terom@54: if self.endian == 'I': terom@54: s = s + chr(offset & 0xFF) terom@54: else: terom@54: s = chr(offset & 0xFF) + s terom@54: offset = offset >> 8 terom@54: return s terom@54: terom@54: # return first IFD terom@54: def first_IFD(self): terom@54: return self.s2n(4, 4) terom@54: terom@54: # return pointer to next IFD terom@54: def next_IFD(self, ifd): terom@54: entries=self.s2n(ifd, 2) terom@54: return self.s2n(ifd+2+12*entries, 4) terom@54: terom@54: # return list of IFDs in header terom@54: def list_IFDs(self): terom@54: i=self.first_IFD() terom@54: a=[] terom@54: while i: terom@54: a.append(i) terom@54: i=self.next_IFD(i) terom@54: return a terom@54: terom@54: # return list of entries in this IFD terom@54: def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0, stop_tag='UNDEF'): terom@54: entries=self.s2n(ifd, 2) terom@54: for i in range(entries): terom@54: # entry is index of start of this IFD in the file terom@54: entry = ifd + 2 + 12 * i terom@54: tag = self.s2n(entry, 2) terom@54: terom@54: # get tag name early to avoid errors, help debug terom@54: tag_entry = dict.get(tag) terom@54: if tag_entry: terom@54: tag_name = tag_entry[0] terom@54: else: terom@54: tag_name = 'Tag 0x%04X' % tag terom@54: terom@54: # ignore certain tags for faster processing terom@54: if not (not detailed and tag in IGNORE_TAGS): terom@54: field_type = self.s2n(entry + 2, 2) terom@54: terom@54: # unknown field type terom@54: if not 0 < field_type < len(FIELD_TYPES): terom@54: if not self.strict: terom@54: continue terom@54: else: terom@54: raise ValueError('unknown type %d in tag 0x%04X' % (field_type, tag)) terom@54: terom@54: typelen = FIELD_TYPES[field_type][0] terom@54: count = self.s2n(entry + 4, 4) terom@54: # Adjust for tag id/type/count (2+2+4 bytes) terom@54: # Now we point at either the data or the 2nd level offset terom@54: offset = entry + 8 terom@54: terom@54: # If the value fits in 4 bytes, it is inlined, else we terom@54: # need to jump ahead again. terom@54: if count * typelen > 4: terom@54: # offset is not the value; it's a pointer to the value terom@54: # if relative we set things up so s2n will seek to the right terom@54: # place when it adds self.offset. Note that this 'relative' terom@54: # is for the Nikon type 3 makernote. Other cameras may use terom@54: # other relative offsets, which would have to be computed here terom@54: # slightly differently. terom@54: if relative: terom@54: tmp_offset = self.s2n(offset, 4) terom@54: offset = tmp_offset + ifd - 8 terom@54: if self.fake_exif: terom@54: offset = offset + 18 terom@54: else: terom@54: offset = self.s2n(offset, 4) terom@54: terom@54: field_offset = offset terom@54: if field_type == 2: terom@54: # special case: null-terminated ASCII string terom@54: # XXX investigate terom@54: # sometimes gets too big to fit in int value terom@54: if count != 0 and count < (2**31): terom@98: values = self.pread(offset, count) terom@54: #print values terom@54: # Drop any garbage after a null. terom@54: values = values.split('\x00', 1)[0] terom@54: else: terom@54: values = '' terom@54: else: terom@54: values = [] terom@54: signed = (field_type in [6, 8, 9, 10]) terom@54: terom@54: # XXX investigate terom@54: # some entries get too big to handle could be malformed terom@54: # file or problem with self.s2n terom@54: if count < 1000: terom@54: for dummy in range(count): terom@54: if field_type in (5, 10): terom@54: # a ratio terom@54: value = Ratio(self.s2n(offset, 4, signed), terom@54: self.s2n(offset + 4, 4, signed)) terom@54: else: terom@54: value = self.s2n(offset, typelen, signed) terom@54: values.append(value) terom@54: offset = offset + typelen terom@54: # The test above causes problems with tags that are terom@54: # supposed to have long values! Fix up one important case. terom@54: elif tag_name == 'MakerNote' : terom@54: for dummy in range(count): terom@54: value = self.s2n(offset, typelen, signed) terom@54: values.append(value) terom@54: offset = offset + typelen terom@54: #else : terom@54: # print "Warning: dropping large tag:", tag, tag_name terom@54: terom@54: # now 'values' is either a string or an array terom@54: if count == 1 and field_type != 2: terom@54: printable=str(values[0]) terom@54: elif count > 50 and len(values) > 20 : terom@54: printable=str( values[0:20] )[0:-1] + ", ... ]" terom@54: else: terom@54: printable=str(values) terom@54: terom@54: # compute printable version of values terom@54: if tag_entry: terom@54: if len(tag_entry) != 1: terom@54: # optional 2nd tag element is present terom@54: if callable(tag_entry[1]): terom@54: # call mapping function terom@54: printable = tag_entry[1](values) terom@54: else: terom@54: printable = '' terom@54: for i in values: terom@54: # use lookup table for this tag terom@54: printable += tag_entry[1].get(i, repr(i)) terom@54: terom@54: self.tags[ifd_name + ' ' + tag_name] = IFD_Tag(printable, tag, terom@54: field_type, terom@54: values, field_offset, terom@54: count * typelen) terom@54: if self.debug: terom@54: print ' debug: %s: %s' % (tag_name, terom@54: repr(self.tags[ifd_name + ' ' + tag_name])) terom@54: terom@54: if tag_name == stop_tag: terom@54: break terom@54: terom@54: # extract uncompressed TIFF thumbnail (like pulling teeth) terom@54: # we take advantage of the pre-existing layout in the thumbnail IFD as terom@54: # much as possible terom@54: def extract_TIFF_thumbnail(self, thumb_ifd): terom@54: entries = self.s2n(thumb_ifd, 2) terom@54: # this is header plus offset to IFD ... terom@54: if self.endian == 'M': terom@54: tiff = 'MM\x00*\x00\x00\x00\x08' terom@54: else: terom@54: tiff = 'II*\x00\x08\x00\x00\x00' terom@54: # ... plus thumbnail IFD data plus a null "next IFD" pointer terom@98: tiff += self.pread(thumb_ifd, entries*12+2)+'\x00\x00\x00\x00' terom@54: terom@54: # fix up large value offset pointers into data area terom@54: for i in range(entries): terom@54: entry = thumb_ifd + 2 + 12 * i terom@54: tag = self.s2n(entry, 2) terom@54: field_type = self.s2n(entry+2, 2) terom@54: typelen = FIELD_TYPES[field_type][0] terom@54: count = self.s2n(entry+4, 4) terom@54: oldoff = self.s2n(entry+8, 4) terom@54: # start of the 4-byte pointer area in entry terom@54: ptr = i * 12 + 18 terom@54: # remember strip offsets location terom@54: if tag == 0x0111: terom@54: strip_off = ptr terom@54: strip_len = count * typelen terom@54: # is it in the data area? terom@54: if count * typelen > 4: terom@54: # update offset pointer (nasty "strings are immutable" crap) terom@54: # should be able to say "tiff[ptr:ptr+4]=newoff" terom@54: newoff = len(tiff) terom@54: tiff = tiff[:ptr] + self.n2s(newoff, 4) + tiff[ptr+4:] terom@54: # remember strip offsets location terom@54: if tag == 0x0111: terom@54: strip_off = newoff terom@54: strip_len = 4 terom@54: # get original data and store it terom@98: tiff += self.pread(oldoff, count * typelen) terom@54: terom@54: # add pixel strips and update strip offset info terom@54: old_offsets = self.tags['Thumbnail StripOffsets'].values terom@54: old_counts = self.tags['Thumbnail StripByteCounts'].values terom@54: for i in range(len(old_offsets)): terom@54: # update offset pointer (more nasty "strings are immutable" crap) terom@54: offset = self.n2s(len(tiff), strip_len) terom@54: tiff = tiff[:strip_off] + offset + tiff[strip_off + strip_len:] terom@54: strip_off += strip_len terom@54: # add pixel strip to end terom@98: tiff += self.pread(old_offsets[i], old_counts[i]) terom@54: terom@54: self.tags['TIFFThumbnail'] = tiff terom@54: terom@54: # decode all the camera-specific MakerNote formats terom@54: terom@54: # Note is the data that comprises this MakerNote. The MakerNote will terom@54: # likely have pointers in it that point to other parts of the file. We'll terom@54: # use self.offset as the starting point for most of those pointers, since terom@54: # they are relative to the beginning of the file. terom@54: # terom@54: # If the MakerNote is in a newer format, it may use relative addressing terom@54: # within the MakerNote. In that case we'll use relative addresses for the terom@54: # pointers. terom@54: # terom@54: # As an aside: it's not just to be annoying that the manufacturers use terom@54: # relative offsets. It's so that if the makernote has to be moved by the terom@54: # picture software all of the offsets don't have to be adjusted. Overall, terom@54: # this is probably the right strategy for makernotes, though the spec is terom@54: # ambiguous. (The spec does not appear to imagine that makernotes would terom@54: # follow EXIF format internally. Once they did, it's ambiguous whether terom@54: # the offsets should be from the header at the start of all the EXIF info, terom@54: # or from the header at the start of the makernote.) terom@54: def decode_maker_note(self): terom@54: note = self.tags['EXIF MakerNote'] terom@54: terom@54: # Some apps use MakerNote tags but do not use a format for which we terom@54: # have a description, so just do a raw dump for these. terom@54: #if self.tags.has_key('Image Make'): terom@54: make = self.tags['Image Make'].printable terom@54: #else: terom@54: # make = '' terom@54: terom@54: # model = self.tags['Image Model'].printable # unused terom@54: terom@54: # Nikon terom@54: # The maker note usually starts with the word Nikon, followed by the terom@54: # type of the makernote (1 or 2, as a short). If the word Nikon is terom@54: # not at the start of the makernote, it's probably type 2, since some terom@54: # cameras work that way. terom@54: if 'NIKON' in make: terom@54: if note.values[0:7] == [78, 105, 107, 111, 110, 0, 1]: terom@54: if self.debug: terom@54: print "Looks like a type 1 Nikon MakerNote." terom@54: self.dump_IFD(note.field_offset+8, 'MakerNote', terom@54: dict=MAKERNOTE_NIKON_OLDER_TAGS) terom@54: elif note.values[0:7] == [78, 105, 107, 111, 110, 0, 2]: terom@54: if self.debug: terom@54: print "Looks like a labeled type 2 Nikon MakerNote" terom@54: if note.values[12:14] != [0, 42] and note.values[12:14] != [42L, 0L]: terom@54: raise ValueError("Missing marker tag '42' in MakerNote.") terom@54: # skip the Makernote label and the TIFF header terom@54: self.dump_IFD(note.field_offset+10+8, 'MakerNote', terom@54: dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1) terom@54: else: terom@54: # E99x or D1 terom@54: if self.debug: terom@54: print "Looks like an unlabeled type 2 Nikon MakerNote" terom@54: self.dump_IFD(note.field_offset, 'MakerNote', terom@54: dict=MAKERNOTE_NIKON_NEWER_TAGS) terom@54: return terom@54: terom@54: # Olympus terom@54: if make.startswith('OLYMPUS'): terom@54: self.dump_IFD(note.field_offset+8, 'MakerNote', terom@54: dict=MAKERNOTE_OLYMPUS_TAGS) terom@54: # XXX TODO terom@54: #for i in (('MakerNote Tag 0x2020', MAKERNOTE_OLYMPUS_TAG_0x2020),): terom@54: # self.decode_olympus_tag(self.tags[i[0]].values, i[1]) terom@54: #return terom@54: terom@54: # Casio terom@54: if 'CASIO' in make or 'Casio' in make: terom@54: self.dump_IFD(note.field_offset, 'MakerNote', terom@54: dict=MAKERNOTE_CASIO_TAGS) terom@54: return terom@54: terom@54: # Fujifilm terom@54: if make == 'FUJIFILM': terom@54: # bug: everything else is "Motorola" endian, but the MakerNote terom@54: # is "Intel" endian terom@54: endian = self.endian terom@54: self.endian = 'I' terom@54: # bug: IFD offsets are from beginning of MakerNote, not terom@54: # beginning of file header terom@54: offset = self.offset terom@54: self.offset += note.field_offset terom@54: # process note with bogus values (note is actually at offset 12) terom@54: self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS) terom@54: # reset to correct values terom@54: self.endian = endian terom@54: self.offset = offset terom@54: return terom@54: terom@54: # Canon terom@54: if make == 'Canon': terom@54: self.dump_IFD(note.field_offset, 'MakerNote', terom@54: dict=MAKERNOTE_CANON_TAGS) terom@54: for i in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001), terom@54: ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004)): terom@54: self.canon_decode_tag(self.tags[i[0]].values, i[1]) terom@54: return terom@54: terom@54: terom@54: # XXX TODO decode Olympus MakerNote tag based on offset within tag terom@54: def olympus_decode_tag(self, value, dict): terom@54: pass terom@54: terom@54: # decode Canon MakerNote tag based on offset within tag terom@54: # see http://www.burren.cx/david/canon.html by David Burren terom@54: def canon_decode_tag(self, value, dict): terom@54: for i in range(1, len(value)): terom@54: x=dict.get(i, ('Unknown', )) terom@54: if self.debug: terom@54: print i, x terom@54: name=x[0] terom@54: if len(x) > 1: terom@54: val=x[1].get(value[i], 'Unknown') terom@54: else: terom@54: val=value[i] terom@54: # it's not a real IFD Tag but we fake one to make everybody terom@54: # happy. this will have a "proprietary" type terom@54: self.tags['MakerNote '+name]=IFD_Tag(str(val), None, 0, None, terom@54: None, None) terom@54: terom@54: # process an image file (expects an open file object) terom@54: # this is the function that has to deal with all the arbitrary nasty bits terom@54: # of the EXIF standard terom@54: def process_file(f, stop_tag='UNDEF', details=True, strict=False, debug=False): terom@54: # yah it's cheesy... terom@54: global detailed terom@54: detailed = details terom@54: terom@54: # by default do not fake an EXIF beginning terom@54: fake_exif = 0 terom@54: terom@54: # determine whether it's a JPEG or TIFF terom@54: data = f.read(12) terom@54: if data[0:4] in ['II*\x00', 'MM\x00*']: terom@54: # it's a TIFF file terom@54: f.seek(0) terom@54: endian = f.read(1) terom@54: f.read(1) terom@54: offset = 0 terom@54: elif data[0:2] == '\xFF\xD8': terom@54: # it's a JPEG file terom@54: while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'): terom@54: length = ord(data[4])*256+ord(data[5]) terom@54: f.read(length-8) terom@54: # fake an EXIF beginning of file terom@54: data = '\xFF\x00'+f.read(10) terom@54: fake_exif = 1 terom@54: if data[2] == '\xFF' and data[6:10] == 'Exif': terom@54: # detected EXIF header terom@54: offset = f.tell() terom@54: endian = f.read(1) terom@54: else: terom@54: # no EXIF information terom@54: return {} terom@54: else: terom@54: # file format not recognized terom@54: return {} terom@54: terom@54: # deal with the EXIF info we found terom@54: if debug: terom@54: print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format' terom@54: hdr = EXIF_header(f, endian, offset, fake_exif, strict, debug) terom@54: ifd_list = hdr.list_IFDs() terom@54: ctr = 0 terom@54: for i in ifd_list: terom@54: if ctr == 0: terom@54: IFD_name = 'Image' terom@54: elif ctr == 1: terom@54: IFD_name = 'Thumbnail' terom@54: thumb_ifd = i terom@54: else: terom@54: IFD_name = 'IFD %d' % ctr terom@54: if debug: terom@54: print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i) terom@54: hdr.dump_IFD(i, IFD_name, stop_tag=stop_tag) terom@54: # EXIF IFD terom@54: exif_off = hdr.tags.get(IFD_name+' ExifOffset') terom@54: if exif_off: terom@54: if debug: terom@54: print ' EXIF SubIFD at offset %d:' % exif_off.values[0] terom@54: hdr.dump_IFD(exif_off.values[0], 'EXIF', stop_tag=stop_tag) terom@54: # Interoperability IFD contained in EXIF IFD terom@54: intr_off = hdr.tags.get('EXIF SubIFD InteroperabilityOffset') terom@54: if intr_off: terom@54: if debug: terom@54: print ' EXIF Interoperability SubSubIFD at offset %d:' \ terom@54: % intr_off.values[0] terom@54: hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability', terom@54: dict=INTR_TAGS, stop_tag=stop_tag) terom@54: # GPS IFD terom@54: gps_off = hdr.tags.get(IFD_name+' GPSInfo') terom@54: if gps_off: terom@54: if debug: terom@54: print ' GPS SubIFD at offset %d:' % gps_off.values[0] terom@54: hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS, stop_tag=stop_tag) terom@54: ctr += 1 terom@54: terom@54: # extract uncompressed TIFF thumbnail terom@54: thumb = hdr.tags.get('Thumbnail Compression') terom@54: if thumb and thumb.printable == 'Uncompressed TIFF': terom@54: hdr.extract_TIFF_thumbnail(thumb_ifd) terom@54: terom@54: # JPEG thumbnail (thankfully the JPEG data is stored as a unit) terom@54: thumb_off = hdr.tags.get('Thumbnail JPEGInterchangeFormat') terom@54: if thumb_off: terom@54: f.seek(offset+thumb_off.values[0]) terom@54: size = hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0] terom@54: hdr.tags['JPEGThumbnail'] = f.read(size) terom@54: terom@54: # deal with MakerNote contained in EXIF IFD terom@54: # (Some apps use MakerNote tags but do not use a format for which we terom@54: # have a description, do not process these). terom@54: if 'EXIF MakerNote' in hdr.tags and 'Image Make' in hdr.tags and detailed: terom@54: hdr.decode_maker_note() terom@54: terom@54: # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote terom@54: # since it's not allowed in a uncompressed TIFF IFD terom@54: if 'JPEGThumbnail' not in hdr.tags: terom@54: thumb_off=hdr.tags.get('MakerNote JPEGThumbnail') terom@54: if thumb_off: terom@54: f.seek(offset+thumb_off.values[0]) terom@54: hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length) terom@54: terom@54: return hdr.tags terom@54: terom@54: terom@54: # show command line usage terom@54: def usage(exit_status): terom@54: msg = 'Usage: EXIF.py [OPTIONS] file1 [file2 ...]\n' terom@54: msg += 'Extract EXIF information from digital camera image files.\n\nOptions:\n' terom@54: msg += '-q --quick Do not process MakerNotes.\n' terom@54: msg += '-t TAG --stop-tag TAG Stop processing when this tag is retrieved.\n' terom@54: msg += '-s --strict Run in strict mode (stop on errors).\n' terom@54: msg += '-d --debug Run in debug mode (display extra info).\n' terom@54: print msg terom@54: sys.exit(exit_status) terom@54: terom@54: # library test/debug function (dump given files) terom@54: if __name__ == '__main__': terom@54: import sys terom@54: import getopt terom@54: terom@54: # parse command line options/arguments terom@54: try: terom@54: opts, args = getopt.getopt(sys.argv[1:], "hqsdt:v", ["help", "quick", "strict", "debug", "stop-tag="]) terom@54: except getopt.GetoptError: terom@54: usage(2) terom@54: if args == []: terom@54: usage(2) terom@54: detailed = True terom@54: stop_tag = 'UNDEF' terom@54: debug = False terom@54: strict = False terom@54: for o, a in opts: terom@54: if o in ("-h", "--help"): terom@54: usage(0) terom@54: if o in ("-q", "--quick"): terom@54: detailed = False terom@54: if o in ("-t", "--stop-tag"): terom@54: stop_tag = a terom@54: if o in ("-s", "--strict"): terom@54: strict = True terom@54: if o in ("-d", "--debug"): terom@54: debug = True terom@54: terom@54: # output info for each file terom@54: for filename in args: terom@54: try: terom@54: file=open(filename, 'rb') terom@54: except: terom@54: print "'%s' is unreadable\n"%filename terom@54: continue terom@54: print filename + ':' terom@54: # get the tags terom@54: data = process_file(file, stop_tag=stop_tag, details=detailed, strict=strict, debug=debug) terom@54: if not data: terom@54: print 'No EXIF information found' terom@54: continue terom@54: terom@54: x=data.keys() terom@54: x.sort() terom@54: for i in x: terom@54: if i in ('JPEGThumbnail', 'TIFFThumbnail'): terom@54: continue terom@54: try: terom@54: print ' %s (%s): %s' % \ terom@54: (i, FIELD_TYPES[data[i].field_type][2], data[i].printable) terom@54: except: terom@54: print 'error', i, '"', data[i], '"' terom@54: if 'JPEGThumbnail' in data: terom@54: print 'File has JPEG thumbnail' terom@54: print terom@54: