# HG changeset patch # User Tero Marttila # Date 1244914311 -10800 # Node ID 6afe59e5ffaea9fad817bc6f0e071272dc70fc46 # Parent 63e89dc2d6f17b0485e56a0b13fa293fe08098a6 tidy up exif_data a bit diff -r 63e89dc2d6f1 -r 6afe59e5ffae degal/exif.py --- a/degal/exif.py Sat Jun 13 19:21:12 2009 +0300 +++ b/degal/exif.py Sat Jun 13 20:31:51 2009 +0300 @@ -131,15 +131,8 @@ # unpack it if self.tag_data : # the EXIF tag name - self.tag_name = self.tag_data[0] + self.tag_name, self.tag_value_spec = self.tag_data - # the optional value formatting specification - if len(self.tag_data) > 1 : - self.tag_value_spec = self.tag_data[1] - - else : - self.tag_value_spec = None - @property def name (self) : """ @@ -165,20 +158,22 @@ # nada, just leave them return raw_values - def readable_value (self, value) : + def readable_value (self, values) : """ - Convert the given value for this tag into a human-readable string. + Convert the given values for this tag into a human-readable string. - Returns the value itself by default. + Returns the comma-separated values by default. """ - if self.tag_data and self.tag_value_spec : - # map it - return exif_data.map_value(self.tag_value_spec, value) + if self.tag_data : + spec = self.tag_value_spec else : - # nope... - return value + # fallback to default + spec = None + + # map it + return exif_data.map_values(spec, values) # size of an IFD entry in bytes IFD_ENTRY_SIZE = 12 @@ -237,7 +232,7 @@ def iter_ifds (self) : """ - Iterate over all of the IFD objects in this EXIF. + Iterate over the primary IFDs in this EXIF. """ # starting offset @@ -253,6 +248,11 @@ # skip to next offset offset = ifd.next_offset + def iter_all_ifds (self) : + """ + Iterate over all of the IFDs contained within this EXIF, or within other IFDs. + """ + __iter__ = iter_ifds def tag_data_info (self, tag) : @@ -329,7 +329,7 @@ return "" # return as comma-separated formatted string, yes - return ", ".join(tag.readable_value(value) for value in values) + return tag.readable_value(values) # mapping from two-byte TIFF byte order marker to struct prefix TIFF_BYTE_ORDER = { @@ -525,11 +525,15 @@ tag.count, data_fmt, data_offset, data_size, ) + + values = exif.tag_values(tag) - for i, value in enumerate(exif.tag_values(tag)) : - print "\t\t\t%02d: %r -> %s" % (i, value, tag.readable_value(value)) + for i, value in enumerate(values) : + print "\t\t\t%02d: %r" % (i, value) -def main (path) : + print "\t\t\t-> %s" % (tag.readable_value(values), ) + +def main (path, quiet=False) : """ Load and dump EXIF data from the given path """ @@ -540,14 +544,15 @@ if not exif : raise Exception("No EXIF data found") - # dump it - print "%s: " % path - print + if not quiet : + # dump it + print "%s: " % path + print - dump_exif(exif) + dump_exif(exif) if __name__ == '__main__' : from sys import argv - main(argv[1]) + main(argv[1], '-q' in argv) diff -r 63e89dc2d6f1 -r 6afe59e5ffae degal/exif_data.py --- a/degal/exif_data.py Sat Jun 13 19:21:12 2009 +0300 +++ b/degal/exif_data.py Sat Jun 13 20:31:51 2009 +0300 @@ -49,17 +49,19 @@ """ Default post-filter for ASCII values. - This takes a single item of string data, splits it up into strings by ASCII-NUL, and trims the induvidual strings + This takes a single item of string data, splits it up into strings by ASCII-NUL. + + These sub-strings are then decoded into unicode as ASCII, and stripped. """ - return [string.rstrip() for string in values[0].split('\x00') if string] - + return [string.decode('ascii', 'replace').rstrip() for string in values[0].split('\x00') if string] def build_ratio (num, denom) : """ Builds a Decimal ratio out of the given numerator and denominator """ - + + # XXX: this may be slow return decimal.Decimal(num) / decimal.Decimal(denom) def filter_ratio (values) : @@ -91,58 +93,72 @@ 0x000A: ('ll', 'Signed Ratio', filter_ratio ), } -def map_value (spec, value) : +def map_values (spec, values) : """ Map the given tag value to a printable string using the given value spec. """ - - if callable(spec): - # call mapping function - return spec(value) - - else: - return spec.get(value, repr(value)) - -def make_string (seq): - """ - Filter a string to strip out non-printing chars - """ + # XXX: ensure that this always returns a str/unicode - # screen out non-printing characters - str = ''.join(c for c in seq if 32 <= c < 256) + if spec is None : + # nothing to map + return ", ".join(str(value) for value in values) - if not str: - # no printing chars - return seq + elif callable(spec): + # call mapping function + return spec(values) + + elif isinstance(spec, dict) : + # lookup value, default to repr + return ", ".join(spec.get(value, repr(value)) for value in values) else : - return str + # unknown kind of spec + raise ValueError(spec) -def make_string_uc (seq) : +USER_COMMENT_CHARSETS = { + 'ASCII': ('ascii', 'replace' ), + 'JIS': ('jis', 'error' ), + + # XXX: WTF? What kind of charset is 'Unicode' supposed to be? + # UTF-16? Little-endian? Big-endian? + # Confusing reigns: http://www.cpanforum.com/threads/7329 + 'UNICODE': ('utf16', 'error' ), +} + + +def decode_UserComment (values) : """ - # Special version to deal with the code in the first 8 bytes of a user comment. - # First 8 bytes gives coding system e.g. ASCII vs. JIS vs Unicode - - XXX: decode? + A UserComment field starts with an eight-byte encoding designator. """ - code = seq[0:8] - seq = seq[8:] - - ## Of course, this is only correct if ASCII, and the standard explicitly - ## allows JIS and Unicode. - return make_string(seq) - + # single binary string + value, = values + + # split up + charset, comment_raw = value[:8], value[8:] -# dictionary of main EXIF tag names -# first element of tuple is tag name, optional second element is -# another dictionary giving names to values -# { tag_type : (name, spec?) } + # strip NILs + charset = charset.rstrip('\x00') + + # map + encoding, replace = USER_COMMENT_CHARSETS.get(charset, ('ascii', 'replace')) + + # decode + return [comment_raw.decode(encoding, replace)] + +# Mappings of Exif tag codes to name and decoding information. +# { tag : (name, value_dict/value_func/None) } +# +# name is the official Exif tag name +# value_dict is a { value: value_name } mapping for human-readable values +# value_func is a `(values) -> values` mapping function which *overrides* the tag's type_func. +# XXX: or does it? +# otherwise, the value is left as-is. EXIF_TAGS = { - 0x0100: ('ImageWidth', ), - 0x0101: ('ImageLength', ), - 0x0102: ('BitsPerSample', ), + 0x0100: ('ImageWidth', None), + 0x0101: ('ImageLength', None), + 0x0102: ('BitsPerSample', None), 0x0103: ('Compression', {1: 'Uncompressed', 2: 'CCITT 1D', @@ -174,14 +190,14 @@ 34713: 'Nikon NEF Compressed', 65000: 'Kodak DCR Compressed', 65535: 'Pentax PEF Compressed'}), - 0x0106: ('PhotometricInterpretation', ), - 0x0107: ('Thresholding', ), - 0x010A: ('FillOrder', ), - 0x010D: ('DocumentName', ), - 0x010E: ('ImageDescription', ), - 0x010F: ('Make', ), - 0x0110: ('Model', ), - 0x0111: ('StripOffsets', ), + 0x0106: ('PhotometricInterpretation', None), + 0x0107: ('Thresholding', None), + 0x010A: ('FillOrder', None), + 0x010D: ('DocumentName', None), + 0x010E: ('ImageDescription', None), + 0x010F: ('Make', None), + 0x0110: ('Model', None), + 0x0111: ('StripOffsets', None), 0x0112: ('Orientation', {1: 'Horizontal (normal)', 2: 'Mirrored horizontal', @@ -191,45 +207,45 @@ 6: 'Rotated 90 CW', 7: 'Mirrored horizontal then rotated 90 CW', 8: 'Rotated 90 CCW'}), - 0x0115: ('SamplesPerPixel', ), - 0x0116: ('RowsPerStrip', ), - 0x0117: ('StripByteCounts', ), - 0x011A: ('XResolution', ), - 0x011B: ('YResolution', ), - 0x011C: ('PlanarConfiguration', ), - 0x011D: ('PageName', make_string), + 0x0115: ('SamplesPerPixel', None), + 0x0116: ('RowsPerStrip', None), + 0x0117: ('StripByteCounts', None), + 0x011A: ('XResolution', None), + 0x011B: ('YResolution', None), + 0x011C: ('PlanarConfiguration', None), + 0x011D: ('PageName', None), 0x0128: ('ResolutionUnit', {1: 'Not Absolute', 2: 'Pixels/Inch', 3: 'Pixels/Centimeter'}), - 0x012D: ('TransferFunction', ), - 0x0131: ('Software', ), - 0x0132: ('DateTime', ), - 0x013B: ('Artist', ), - 0x013E: ('WhitePoint', ), - 0x013F: ('PrimaryChromaticities', ), - 0x0156: ('TransferRange', ), - 0x0200: ('JPEGProc', ), - 0x0201: ('JPEGInterchangeFormat', ), - 0x0202: ('JPEGInterchangeFormatLength', ), - 0x0211: ('YCbCrCoefficients', ), - 0x0212: ('YCbCrSubSampling', ), + 0x012D: ('TransferFunction', None), + 0x0131: ('Software', None), + 0x0132: ('DateTime', None), + 0x013B: ('Artist', None), + 0x013E: ('WhitePoint', None), + 0x013F: ('PrimaryChromaticities', None), + 0x0156: ('TransferRange', None), + 0x0200: ('JPEGProc', None), + 0x0201: ('JPEGInterchangeFormat', None), + 0x0202: ('JPEGInterchangeFormatLength', None), + 0x0211: ('YCbCrCoefficients', None), + 0x0212: ('YCbCrSubSampling', None), 0x0213: ('YCbCrPositioning', {1: 'Centered', 2: 'Co-sited'}), - 0x0214: ('ReferenceBlackWhite', ), - - 0x4746: ('Rating', ), + 0x0214: ('ReferenceBlackWhite', None), - 0x828D: ('CFARepeatPatternDim', ), - 0x828E: ('CFAPattern', ), - 0x828F: ('BatteryLevel', ), - 0x8298: ('Copyright', ), - 0x829A: ('ExposureTime', ), - 0x829D: ('FNumber', ), - 0x83BB: ('IPTC/NAA', ), - 0x8769: ('ExifOffset', ), - 0x8773: ('InterColorProfile', ), + 0x4746: ('Rating', None), + + 0x828D: ('CFARepeatPatternDim', None), + 0x828E: ('CFAPattern', None), + 0x828F: ('BatteryLevel', None), + 0x8298: ('Copyright', None), + 0x829A: ('ExposureTime', None), + 0x829D: ('FNumber', None), + 0x83BB: ('IPTC/NAA', None), + 0x8769: ('ExifOffset', None), + 0x8773: ('InterColorProfile', None), 0x8822: ('ExposureProgram', {0: 'Unidentified', 1: 'Manual', @@ -240,13 +256,13 @@ 6: 'Program Action', 7: 'Portrait Mode', 8: 'Landscape Mode'}), - 0x8824: ('SpectralSensitivity', ), - 0x8825: ('GPSInfo', ), - 0x8827: ('ISOSpeedRatings', ), - 0x8828: ('OECF', ), - 0x9000: ('ExifVersion', make_string), - 0x9003: ('DateTimeOriginal', ), - 0x9004: ('DateTimeDigitized', ), + 0x8824: ('SpectralSensitivity', None), + 0x8825: ('GPSInfo', None), + 0x8827: ('ISOSpeedRatings', None), + 0x8828: ('OECF', None), + 0x9000: ('ExifVersion', None), + 0x9003: ('DateTimeOriginal', None), + 0x9004: ('DateTimeDigitized', None), 0x9101: ('ComponentsConfiguration', {0: '', 1: 'Y', @@ -255,13 +271,13 @@ 4: 'Red', 5: 'Green', 6: 'Blue'}), - 0x9102: ('CompressedBitsPerPixel', ), - 0x9201: ('ShutterSpeedValue', ), - 0x9202: ('ApertureValue', ), - 0x9203: ('BrightnessValue', ), - 0x9204: ('ExposureBiasValue', ), - 0x9205: ('MaxApertureValue', ), - 0x9206: ('SubjectDistance', ), + 0x9102: ('CompressedBitsPerPixel', None), + 0x9201: ('ShutterSpeedValue', None), + 0x9202: ('ApertureValue', None), + 0x9203: ('BrightnessValue', None), + 0x9204: ('ExposureBiasValue', None), + 0x9205: ('MaxApertureValue', None), + 0x9206: ('SubjectDistance', None), 0x9207: ('MeteringMode', {0: 'Unidentified', 1: 'Average', @@ -302,36 +318,36 @@ 29: 'Auto Fired (?)', 31: 'Auto Fired (!)', 32: 'Not Available'}), - 0x920A: ('FocalLength', ), - 0x9214: ('SubjectArea', ), - 0x927C: ('MakerNote', ), - 0x9286: ('UserComment', make_string_uc), - 0x9290: ('SubSecTime', ), - 0x9291: ('SubSecTimeOriginal', ), - 0x9292: ('SubSecTimeDigitized', ), + 0x920A: ('FocalLength', None), + 0x9214: ('SubjectArea', None), + 0x927C: ('MakerNote', None), + 0x9286: ('UserComment', decode_UserComment), + 0x9290: ('SubSecTime', None), + 0x9291: ('SubSecTimeOriginal', None), + 0x9292: ('SubSecTimeDigitized', None), # used by Windows Explorer - 0x9C9B: ('XPTitle', ), - 0x9C9C: ('XPComment', ), - 0x9C9D: ('XPAuthor', ), #(ignored by Windows Explorer if Artist exists) - 0x9C9E: ('XPKeywords', ), - 0x9C9F: ('XPSubject', ), + 0x9C9B: ('XPTitle', None), + 0x9C9C: ('XPComment', None), + 0x9C9D: ('XPAuthor', None), #(ignored by Windows Explorer if Artist exists) + 0x9C9E: ('XPKeywords', None), + 0x9C9F: ('XPSubject', None), - 0xA000: ('FlashPixVersion', make_string), + 0xA000: ('FlashPixVersion', None), 0xA001: ('ColorSpace', {1: 'sRGB', 2: 'Adobe RGB', 65535: 'Uncalibrated'}), - 0xA002: ('ExifImageWidth', ), - 0xA003: ('ExifImageLength', ), - 0xA005: ('InteroperabilityOffset', ), - 0xA20B: ('FlashEnergy', ), # 0x920B in TIFF/EP - 0xA20C: ('SpatialFrequencyResponse', ), # 0x920C - 0xA20E: ('FocalPlaneXResolution', ), # 0x920E - 0xA20F: ('FocalPlaneYResolution', ), # 0x920F - 0xA210: ('FocalPlaneResolutionUnit', ), # 0x9210 - 0xA214: ('SubjectLocation', ), # 0x9214 - 0xA215: ('ExposureIndex', ), # 0x9215 + 0xA002: ('ExifImageWidth', None), + 0xA003: ('ExifImageLength', None), + 0xA005: ('InteroperabilityOffset', None), + 0xA20B: ('FlashEnergy', None), # 0x920B in TIFF/EP + 0xA20C: ('SpatialFrequencyResponse', None), # 0x920C + 0xA20E: ('FocalPlaneXResolution', None), # 0x920E + 0xA20F: ('FocalPlaneYResolution', None), # 0x920F + 0xA210: ('FocalPlaneResolutionUnit', None), # 0x9210 + 0xA214: ('SubjectLocation', None), # 0x9214 + 0xA215: ('ExposureIndex', None), # 0x9215 0xA217: ('SensingMethod', # 0x9217 {1: 'Not defined', 2: 'One-chip color area', @@ -346,7 +362,7 @@ 3: 'Digital Camera'}), 0xA301: ('SceneType', {1: 'Directly Photographed'}), - 0xA302: ('CVAPattern', ), + 0xA302: ('CVAPattern', None), 0xA401: ('CustomRendered', {0: 'Normal', 1: 'Custom'}), @@ -357,8 +373,8 @@ 0xA403: ('WhiteBalance', {0: 'Auto', 1: 'Manual'}), - 0xA404: ('DigitalZoomRatio', ), - 0xA405: ('FocalLengthIn35mmFilm', ), + 0xA404: ('DigitalZoomRatio', None), + 0xA405: ('FocalLengthIn35mmFilm', None), 0xA406: ('SceneCaptureType', {0: 'Standard', 1: 'Landscape', @@ -382,147 +398,160 @@ {0: 'Normal', 1: 'Soft', 2: 'Hard'}), - 0xA40B: ('DeviceSettingDescription', ), - 0xA40C: ('SubjectDistanceRange', ), - 0xA500: ('Gamma', ), - 0xC4A5: ('PrintIM', ), - 0xEA1C: ('Padding', ), + 0xA40B: ('DeviceSettingDescription', None), + 0xA40C: ('SubjectDistanceRange', None), + 0xA500: ('Gamma', None), + 0xC4A5: ('PrintIM', None), + 0xEA1C: ('Padding', None), } # interoperability tags INTR_TAGS = { - 0x0001: ('InteroperabilityIndex', ), - 0x0002: ('InteroperabilityVersion', ), - 0x1000: ('RelatedImageFileFormat', ), - 0x1001: ('RelatedImageWidth', ), - 0x1002: ('RelatedImageLength', ), + 0x0001: ('InteroperabilityIndex', None), + 0x0002: ('InteroperabilityVersion', None), + 0x1000: ('RelatedImageFileFormat', None), + 0x1001: ('RelatedImageWidth', None), + 0x1002: ('RelatedImageLength', None), } # GPS tags (not used yet, haven't seen camera with GPS) GPS_TAGS = { - 0x0000: ('GPSVersionID', ), - 0x0001: ('GPSLatitudeRef', ), - 0x0002: ('GPSLatitude', ), - 0x0003: ('GPSLongitudeRef', ), - 0x0004: ('GPSLongitude', ), - 0x0005: ('GPSAltitudeRef', ), - 0x0006: ('GPSAltitude', ), - 0x0007: ('GPSTimeStamp', ), - 0x0008: ('GPSSatellites', ), - 0x0009: ('GPSStatus', ), - 0x000A: ('GPSMeasureMode', ), - 0x000B: ('GPSDOP', ), - 0x000C: ('GPSSpeedRef', ), - 0x000D: ('GPSSpeed', ), - 0x000E: ('GPSTrackRef', ), - 0x000F: ('GPSTrack', ), - 0x0010: ('GPSImgDirectionRef', ), - 0x0011: ('GPSImgDirection', ), - 0x0012: ('GPSMapDatum', ), - 0x0013: ('GPSDestLatitudeRef', ), - 0x0014: ('GPSDestLatitude', ), - 0x0015: ('GPSDestLongitudeRef', ), - 0x0016: ('GPSDestLongitude', ), - 0x0017: ('GPSDestBearingRef', ), - 0x0018: ('GPSDestBearing', ), - 0x0019: ('GPSDestDistanceRef', ), - 0x001A: ('GPSDestDistance', ), - 0x001D: ('GPSDate', ), + 0x0000: ('GPSVersionID', None), + 0x0001: ('GPSLatitudeRef', None), + 0x0002: ('GPSLatitude', None), + 0x0003: ('GPSLongitudeRef', None), + 0x0004: ('GPSLongitude', None), + 0x0005: ('GPSAltitudeRef', None), + 0x0006: ('GPSAltitude', None), + 0x0007: ('GPSTimeStamp', None), + 0x0008: ('GPSSatellites', None), + 0x0009: ('GPSStatus', None), + 0x000A: ('GPSMeasureMode', None), + 0x000B: ('GPSDOP', None), + 0x000C: ('GPSSpeedRef', None), + 0x000D: ('GPSSpeed', None), + 0x000E: ('GPSTrackRef', None), + 0x000F: ('GPSTrack', None), + 0x0010: ('GPSImgDirectionRef', None), + 0x0011: ('GPSImgDirection', None), + 0x0012: ('GPSMapDatum', None), + 0x0013: ('GPSDestLatitudeRef', None), + 0x0014: ('GPSDestLatitude', None), + 0x0015: ('GPSDestLongitudeRef', None), + 0x0016: ('GPSDestLongitude', None), + 0x0017: ('GPSDestBearingRef', None), + 0x0018: ('GPSDestBearing', None), + 0x0019: ('GPSDestDistanceRef', None), + 0x001A: ('GPSDestDistance', None), + 0x001D: ('GPSDate', None), } -# Ignore these tags when quick processing -# 0x927C is MakerNote Tags -# 0x9286 is user comment -IGNORE_TAGS=(0x9286, 0x927C) - # http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp -def nikon_ev_bias(seq): - # First digit seems to be in steps of 1/6 EV. - # Does the third value mean the step size? It is usually 6, - # but it is 12 for the ExposureDifference. - # - # Check for an error condition that could cause a crash. - # This only happens if something has gone really wrong in +def nikon_ev_bias (seq) : + """ + # First digit seems to be in steps of 1/6 EV. + # Does the third value mean the step size? It is usually 6, + # but it is 12 for the ExposureDifference. + """ + + # check for an error condition that could cause a crash. + # this only happens if something has gone really wrong in # reading the Nikon MakerNote. - if len( seq ) < 4 : return "" - # + if len(seq) < 4 : + return "" + if seq == [252, 1, 6, 0]: return "-2/3 EV" + if seq == [253, 1, 6, 0]: return "-1/2 EV" + if seq == [254, 1, 6, 0]: return "-1/3 EV" + if seq == [0, 1, 6, 0]: return "0 EV" + if seq == [2, 1, 6, 0]: return "+1/3 EV" + if seq == [3, 1, 6, 0]: return "+1/2 EV" + if seq == [4, 1, 6, 0]: return "+2/3 EV" - # Handle combinations not in the table. + + # handle combinations not in the table. a = seq[0] - # Causes headaches for the +/- logic, so special case it. + + # causes headaches for the +/- logic, so special case it. if a == 0: return "0 EV" + if a > 127: a = 256 - a ret_str = "-" else: ret_str = "+" - b = seq[2] # Assume third value means the step size + + b = seq[2] # assume third value means the step size + whole = a / b + a = a % b - if whole != 0: + + if whole != 0 : ret_str = ret_str + str(whole) + " " - if a == 0: + + if a == 0 : ret_str = ret_str + "EV" - else: + else : r = Ratio(a, b) ret_str = ret_str + r.__repr__() + " EV" + return ret_str # Nikon E99x MakerNote Tags MAKERNOTE_NIKON_NEWER_TAGS={ - 0x0001: ('MakernoteVersion', make_string), # Sometimes binary - 0x0002: ('ISOSetting', make_string), - 0x0003: ('ColorMode', ), - 0x0004: ('Quality', ), - 0x0005: ('Whitebalance', ), - 0x0006: ('ImageSharpening', ), - 0x0007: ('FocusMode', ), - 0x0008: ('FlashSetting', ), - 0x0009: ('AutoFlashMode', ), - 0x000B: ('WhiteBalanceBias', ), - 0x000C: ('WhiteBalanceRBCoeff', ), + 0x0001: ('MakernoteVersion', None), # Sometimes binary + 0x0002: ('ISOSetting', None), + 0x0003: ('ColorMode', None), + 0x0004: ('Quality', None), + 0x0005: ('Whitebalance', None), + 0x0006: ('ImageSharpening', None), + 0x0007: ('FocusMode', None), + 0x0008: ('FlashSetting', None), + 0x0009: ('AutoFlashMode', None), + 0x000B: ('WhiteBalanceBias', None), + 0x000C: ('WhiteBalanceRBCoeff', None), 0x000D: ('ProgramShift', nikon_ev_bias), # Nearly the same as the other EV vals, but step size is 1/12 EV (?) 0x000E: ('ExposureDifference', nikon_ev_bias), - 0x000F: ('ISOSelection', ), - 0x0011: ('NikonPreview', ), + 0x000F: ('ISOSelection', None), + 0x0011: ('NikonPreview', None), 0x0012: ('FlashCompensation', nikon_ev_bias), - 0x0013: ('ISOSpeedRequested', ), - 0x0016: ('PhotoCornerCoordinates', ), + 0x0013: ('ISOSpeedRequested', None), + 0x0016: ('PhotoCornerCoordinates', None), # 0x0017: Unknown, but most likely an EV value 0x0018: ('FlashBracketCompensationApplied', nikon_ev_bias), - 0x0019: ('AEBracketCompensationApplied', ), - 0x001A: ('ImageProcessing', ), - 0x001B: ('CropHiSpeed', ), - 0x001D: ('SerialNumber', ), # Conflict with 0x00A0 ? - 0x001E: ('ColorSpace', ), - 0x001F: ('VRInfo', ), - 0x0020: ('ImageAuthentication', ), - 0x0022: ('ActiveDLighting', ), - 0x0023: ('PictureControl', ), - 0x0024: ('WorldTime', ), - 0x0025: ('ISOInfo', ), - 0x0080: ('ImageAdjustment', ), - 0x0081: ('ToneCompensation', ), - 0x0082: ('AuxiliaryLens', ), - 0x0083: ('LensType', ), - 0x0084: ('LensMinMaxFocalMaxAperture', ), - 0x0085: ('ManualFocusDistance', ), - 0x0086: ('DigitalZoomFactor', ), + 0x0019: ('AEBracketCompensationApplied', None), + 0x001A: ('ImageProcessing', None), + 0x001B: ('CropHiSpeed', None), + 0x001D: ('SerialNumber', None), # Conflict with 0x00A0 ? + 0x001E: ('ColorSpace', None), + 0x001F: ('VRInfo', None), + 0x0020: ('ImageAuthentication', None), + 0x0022: ('ActiveDLighting', None), + 0x0023: ('PictureControl', None), + 0x0024: ('WorldTime', None), + 0x0025: ('ISOInfo', None), + 0x0080: ('ImageAdjustment', None), + 0x0081: ('ToneCompensation', None), + 0x0082: ('AuxiliaryLens', None), + 0x0083: ('LensType', None), + 0x0084: ('LensMinMaxFocalMaxAperture', None), + 0x0085: ('ManualFocusDistance', None), + 0x0086: ('DigitalZoomFactor', None), 0x0087: ('FlashMode', {0x00: 'Did Not Fire', 0x01: 'Fired, Manual', @@ -545,16 +574,16 @@ 0x40: 'Single frame, white balance bracketing', 0x41: 'Continuous, white balance bracketing', 0x42: 'Timer, white balance bracketing'}), - 0x008A: ('AutoBracketRelease', ), - 0x008B: ('LensFStops', ), - 0x008C: ('NEFCurve1', ), # ExifTool calls this 'ContrastCurve' - 0x008D: ('ColorMode', ), - 0x008F: ('SceneMode', ), - 0x0090: ('LightingType', ), - 0x0091: ('ShotInfo', ), # First 4 bytes are a version number in ASCII - 0x0092: ('HueAdjustment', ), + 0x008A: ('AutoBracketRelease', None), + 0x008B: ('LensFStops', None), + 0x008C: ('NEFCurve1', None), # ExifTool calls this 'ContrastCurve' + 0x008D: ('ColorMode', None), + 0x008F: ('SceneMode', None), + 0x0090: ('LightingType', None), + 0x0091: ('ShotInfo', None), # First 4 bytes are a version number in ASCII + 0x0092: ('HueAdjustment', None), # ExifTool calls this 'NEFCompression', should be 1-4 - 0x0093: ('Compression', ), + 0x0093: ('Compression', None), 0x0094: ('Saturation', {-3: 'B&W', -2: '-2', @@ -562,35 +591,35 @@ 0: '0', 1: '1', 2: '2'}), - 0x0095: ('NoiseReduction', ), - 0x0096: ('NEFCurve2', ), # ExifTool calls this 'LinearizationTable' - 0x0097: ('ColorBalance', ), # First 4 bytes are a version number in ASCII - 0x0098: ('LensData', ), # First 4 bytes are a version number in ASCII - 0x0099: ('RawImageCenter', ), - 0x009A: ('SensorPixelSize', ), - 0x009C: ('Scene Assist', ), - 0x009E: ('RetouchHistory', ), - 0x00A0: ('SerialNumber', ), - 0x00A2: ('ImageDataSize', ), + 0x0095: ('NoiseReduction', None), + 0x0096: ('NEFCurve2', None), # ExifTool calls this 'LinearizationTable' + 0x0097: ('ColorBalance', None), # First 4 bytes are a version number in ASCII + 0x0098: ('LensData', None), # First 4 bytes are a version number in ASCII + 0x0099: ('RawImageCenter', None), + 0x009A: ('SensorPixelSize', None), + 0x009C: ('Scene Assist', None), + 0x009E: ('RetouchHistory', None), + 0x00A0: ('SerialNumber', None), + 0x00A2: ('ImageDataSize', None), # 00A3: unknown - a single byte 0 # 00A4: In NEF, looks like a 4 byte ASCII version number ('0200') - 0x00A5: ('ImageCount', ), - 0x00A6: ('DeletedImageCount', ), - 0x00A7: ('TotalShutterReleases', ), + 0x00A5: ('ImageCount', None), + 0x00A6: ('DeletedImageCount', None), + 0x00A7: ('TotalShutterReleases', None), # First 4 bytes are a version number in ASCII, with version specific # info to follow. Its hard to treat it as a string due to embedded nulls. - 0x00A8: ('FlashInfo', ), - 0x00A9: ('ImageOptimization', ), - 0x00AA: ('Saturation', ), - 0x00AB: ('DigitalVariProgram', ), - 0x00AC: ('ImageStabilization', ), - 0x00AD: ('Responsive AF', ), # 'AFResponse' - 0x00B0: ('MultiExposure', ), - 0x00B1: ('HighISONoiseReduction', ), - 0x00B7: ('AFInfo', ), - 0x00B8: ('FileInfo', ), + 0x00A8: ('FlashInfo', None), + 0x00A9: ('ImageOptimization', None), + 0x00AA: ('Saturation', None), + 0x00AB: ('DigitalVariProgram', None), + 0x00AC: ('ImageStabilization', None), + 0x00AD: ('Responsive AF', None), # 'AFResponse' + 0x00B0: ('MultiExposure', None), + 0x00B1: ('HighISONoiseReduction', None), + 0x00B7: ('AFInfo', None), + 0x00B8: ('FileInfo', None), # 00B9: unknown - 0x0100: ('DigitalICE', ), + 0x0100: ('DigitalICE', None), 0x0103: ('PreviewCompression', {1: 'Uncompressed', 2: 'CCITT 1D', @@ -622,12 +651,12 @@ 34713: 'Nikon NEF Compressed', 65000: 'Kodak DCR Compressed', 65535: 'Pentax PEF Compressed',}), - 0x0201: ('PreviewImageStart', ), - 0x0202: ('PreviewImageLength', ), + 0x0201: ('PreviewImageStart', None), + 0x0202: ('PreviewImageLength', None), 0x0213: ('PreviewYCbCrPositioning', {1: 'Centered', 2: 'Co-sited'}), - 0x0010: ('DataDump', ), + 0x0010: ('DataDump', None), } MAKERNOTE_NIKON_OLDER_TAGS = { @@ -662,27 +691,35 @@ 6: 'Speed Light'}), } -# decode Olympus SpecialMode tag in MakerNote -def olympus_special_mode(v): - a={ +def olympus_special_mode (values) : + """ + Decode Olympus SpecialMode tag in MakerNote + """ + + a = { 0: 'Normal', 1: 'Unknown', 2: 'Fast', - 3: 'Panorama'} - b={ + 3: 'Panorama' + } + + b = { 0: 'Non-panoramic', 1: 'Left to right', 2: 'Right to left', 3: 'Bottom to top', - 4: 'Top to bottom'} + 4: 'Top to bottom' + } + if v[0] not in a or v[2] not in b: - return v + return values + return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]]) MAKERNOTE_OLYMPUS_TAGS={ # ah HAH! those sneeeeeaky bastids! this is how they get past the fact # that a JPEG thumbnail is not allowed in an uncompressed TIFF file - 0x0100: ('JPEGThumbnail', ), + 0x0100: ('JPEGThumbnail', None), 0x0200: ('SpecialMode', olympus_special_mode), 0x0201: ('JPEGQual', {1: 'SQ', @@ -695,20 +732,20 @@ 0x0203: ('BWMode', {0: 'Off', 1: 'On'}), - 0x0204: ('DigitalZoom', ), - 0x0205: ('FocalPlaneDiagonal', ), - 0x0206: ('LensDistortionParams', ), - 0x0207: ('SoftwareRelease', ), - 0x0208: ('PictureInfo', ), - 0x0209: ('CameraID', make_string), # print as string - 0x0F00: ('DataDump', ), - 0x0300: ('PreCaptureFrames', ), - 0x0404: ('SerialNumber', ), - 0x1000: ('ShutterSpeedValue', ), - 0x1001: ('ISOValue', ), - 0x1002: ('ApertureValue', ), - 0x1003: ('BrightnessValue', ), - 0x1004: ('FlashMode', ), + 0x0204: ('DigitalZoom', None), + 0x0205: ('FocalPlaneDiagonal', None), + 0x0206: ('LensDistortionParams', None), + 0x0207: ('SoftwareRelease', None), + 0x0208: ('PictureInfo', None), + 0x0209: ('CameraID', None), # print as string + 0x0F00: ('DataDump', None), + 0x0300: ('PreCaptureFrames', None), + 0x0404: ('SerialNumber', None), + 0x1000: ('ShutterSpeedValue', None), + 0x1001: ('ISOValue', None), + 0x1002: ('ApertureValue', None), + 0x1003: ('BrightnessValue', None), + 0x1004: ('FlashMode', None), 0x1004: ('FlashMode', {2: 'On', 3: 'Off'}), @@ -717,54 +754,54 @@ 1: 'Internal', 4: 'External', 5: 'Internal + External'}), - 0x1006: ('ExposureCompensation', ), - 0x1007: ('SensorTemperature', ), - 0x1008: ('LensTemperature', ), + 0x1006: ('ExposureCompensation', None), + 0x1007: ('SensorTemperature', None), + 0x1008: ('LensTemperature', None), 0x100b: ('FocusMode', {0: 'Auto', 1: 'Manual'}), - 0x1017: ('RedBalance', ), - 0x1018: ('BlueBalance', ), - 0x101a: ('SerialNumber', ), - 0x1023: ('FlashExposureComp', ), + 0x1017: ('RedBalance', None), + 0x1018: ('BlueBalance', None), + 0x101a: ('SerialNumber', None), + 0x1023: ('FlashExposureComp', None), 0x1026: ('ExternalFlashBounce', {0: 'No', 1: 'Yes'}), - 0x1027: ('ExternalFlashZoom', ), - 0x1028: ('ExternalFlashMode', ), + 0x1027: ('ExternalFlashZoom', None), + 0x1028: ('ExternalFlashMode', None), 0x1029: ('Contrast int16u', {0: 'High', 1: 'Normal', 2: 'Low'}), - 0x102a: ('SharpnessFactor', ), - 0x102b: ('ColorControl', ), - 0x102c: ('ValidBits', ), - 0x102d: ('CoringFilter', ), - 0x102e: ('OlympusImageWidth', ), - 0x102f: ('OlympusImageHeight', ), - 0x1034: ('CompressionRatio', ), + 0x102a: ('SharpnessFactor', None), + 0x102b: ('ColorControl', None), + 0x102c: ('ValidBits', None), + 0x102d: ('CoringFilter', None), + 0x102e: ('OlympusImageWidth', None), + 0x102f: ('OlympusImageHeight', None), + 0x1034: ('CompressionRatio', None), 0x1035: ('PreviewImageValid', {0: 'No', 1: 'Yes'}), - 0x1036: ('PreviewImageStart', ), - 0x1037: ('PreviewImageLength', ), + 0x1036: ('PreviewImageStart', None), + 0x1037: ('PreviewImageLength', None), 0x1039: ('CCDScanMode', {0: 'Interlaced', 1: 'Progressive'}), 0x103a: ('NoiseReduction', {0: 'Off', 1: 'On'}), - 0x103b: ('InfinityLensStep', ), - 0x103c: ('NearLensStep', ), + 0x103b: ('InfinityLensStep', None), + 0x103c: ('NearLensStep', None), # TODO - these need extra definitions # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html - 0x2010: ('Equipment', ), - 0x2020: ('CameraSettings', ), - 0x2030: ('RawDevelopment', ), - 0x2040: ('ImageProcessing', ), - 0x2050: ('FocusInfo', ), - 0x3000: ('RawInfo ', ), + 0x2010: ('Equipment', None), + 0x2020: ('CameraSettings', None), + 0x2030: ('RawDevelopment', None), + 0x2040: ('ImageProcessing', None), + 0x2050: ('FocusInfo', None), + 0x3000: ('RawInfo ', None), } # 0x2020 CameraSettings @@ -772,8 +809,8 @@ 0x0100: ('PreviewImageValid', {0: 'No', 1: 'Yes'}), - 0x0101: ('PreviewImageStart', ), - 0x0102: ('PreviewImageLength', ), + 0x0101: ('PreviewImageStart', None), + 0x0102: ('PreviewImageLength', None), 0x0200: ('ExposureMode', {1: 'Manual', 2: 'Program', @@ -805,8 +842,8 @@ 0x0303: ('AFSearch', {0: 'Not Ready', 1: 'Ready'}), - 0x0304: ('AFAreas', ), - 0x0401: ('FlashExposureCompensation', ), + 0x0304: ('AFAreas', None), + 0x0401: ('FlashExposureCompensation', None), 0x0500: ('WhiteBalance2', {0: 'Auto', 16: '7500K (Fine Weather with Shade)', @@ -825,17 +862,17 @@ 512: 'Custom WB 5400K', 513: 'Custom WB 2900K', 514: 'Custom WB 8000K', }), - 0x0501: ('WhiteBalanceTemperature', ), - 0x0502: ('WhiteBalanceBracket', ), - 0x0503: ('CustomSaturation', ), # (3 numbers: 1. CS Value, 2. Min, 3. Max) + 0x0501: ('WhiteBalanceTemperature', None), + 0x0502: ('WhiteBalanceBracket', None), + 0x0503: ('CustomSaturation', None), # (3 numbers: 1. CS Value, 2. Min, 3. Max) 0x0504: ('ModifiedSaturation', {0: 'Off', 1: 'CM1 (Red Enhance)', 2: 'CM2 (Green Enhance)', 3: 'CM3 (Blue Enhance)', 4: 'CM4 (Skin Tones)'}), - 0x0505: ('ContrastSetting', ), # (3 numbers: 1. Contrast, 2. Min, 3. Max) - 0x0506: ('SharpnessSetting', ), # (3 numbers: 1. Sharpness, 2. Min, 3. Max) + 0x0505: ('ContrastSetting', None), # (3 numbers: 1. Contrast, 2. Min, 3. Max) + 0x0506: ('SharpnessSetting', None), # (3 numbers: 1. Sharpness, 2. Min, 3. Max) 0x0507: ('ColorSpace', {0: 'sRGB', 1: 'Adobe RGB', @@ -879,7 +916,7 @@ 0x050c: ('ShadingCompensation', {0: 'Off', 1: 'On'}), - 0x050d: ('CompressionFactor', ), + 0x050d: ('CompressionFactor', None), 0x050f: ('Gradation', {'-1 -1 1': 'Low Key', '0 -1 1': 'Normal', @@ -890,10 +927,10 @@ 3: 'Muted', 256: 'Monotone', 512: 'Sepia'}), - 0x0521: ('PictureModeSaturation', ), - 0x0522: ('PictureModeHue?', ), - 0x0523: ('PictureModeContrast', ), - 0x0524: ('PictureModeSharpness', ), + 0x0521: ('PictureModeSaturation', None), + 0x0522: ('PictureModeHue?', None), + 0x0523: ('PictureModeContrast', None), + 0x0524: ('PictureModeSharpness', None), 0x0525: ('PictureModeBWFilter', {0: 'n/a', 1: 'Neutral', @@ -908,14 +945,14 @@ 3: 'Blue', 4: 'Purple', 5: 'Green'}), - 0x0600: ('Sequence', ), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits - 0x0601: ('PanoramaMode', ), # (2 numbers: 1. Mode, 2. Shot number) + 0x0600: ('Sequence', None), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits + 0x0601: ('PanoramaMode', None), # (2 numbers: 1. Mode, 2. Shot number) 0x0603: ('ImageQuality2', {1: 'SQ', 2: 'HQ', 3: 'SHQ', 4: 'RAW'}), - 0x0901: ('ManometerReading', ), + 0x0901: ('ManometerReading', None), } @@ -944,7 +981,7 @@ {11: 'Weak', 13: 'Normal', 15: 'Strong'}), - 0x0006: ('Object Distance', ), + 0x0006: ('Object Distance', None), 0x0007: ('WhiteBalance', {1: 'Auto', 2: 'Tungsten', @@ -974,8 +1011,8 @@ } MAKERNOTE_FUJIFILM_TAGS={ - 0x0000: ('NoteVersion', make_string), - 0x1000: ('Quality', ), + 0x0000: ('NoteVersion', None), + 0x1000: ('Quality', None), 0x1001: ('Sharpness', {1: 'Soft', 2: 'Soft', @@ -1004,7 +1041,7 @@ 1: 'On', 2: 'Off', 3: 'Red Eye Reduction'}), - 0x1011: ('FlashStrength', ), + 0x1011: ('FlashStrength', None), 0x1020: ('Macro', {0: 'Off', 1: 'On'}), @@ -1039,10 +1076,10 @@ } MAKERNOTE_CANON_TAGS = { - 0x0006: ('ImageType', ), - 0x0007: ('FirmwareVersion', ), - 0x0008: ('ImageNumber', ), - 0x0009: ('OwnerName', ), + 0x0006: ('ImageType', None), + 0x0007: ('FirmwareVersion', None), + 0x0008: ('ImageNumber', None), + 0x0009: ('OwnerName', None), } # this is in element offset, name, optional value dictionary format @@ -1050,7 +1087,7 @@ 1: ('Macromode', {1: 'Macro', 2: 'Normal'}), - 2: ('SelfTimer', ), + 2: ('SelfTimer', None), 3: ('Quality', {2: 'Normal', 3: 'Fine', @@ -1137,9 +1174,9 @@ 3: 'Av-priority', 4: 'Manual', 5: 'A-DEP'}), - 23: ('LongFocalLengthOfLensInFocalUnits', ), - 24: ('ShortFocalLengthOfLensInFocalUnits', ), - 25: ('FocalUnitsPerMM', ), + 23: ('LongFocalLengthOfLensInFocalUnits', None), + 24: ('ShortFocalLengthOfLensInFocalUnits', None), + 25: ('FocalUnitsPerMM', None), 28: ('FlashActivity', {0: 'Did Not Fire', 1: 'Fired'}), @@ -1163,8 +1200,8 @@ 4: 'Fluorescent', 5: 'Flash', 6: 'Custom'}), - 9: ('SequenceNumber', ), - 14: ('AFPointUsed', ), + 9: ('SequenceNumber', None), + 14: ('AFPointUsed', None), 15: ('FlashBias', {0xFFC0: '-2 EV', 0xFFCC: '-1.67 EV', @@ -1183,11 +1220,12 @@ 0x0030: '1.50 EV', 0x0034: '1.67 EV', 0x0040: '2 EV'}), - 19: ('SubjectDistance', ), + 19: ('SubjectDistance', None), } # ratio object that eventually will be able to reduce itself to lowest # common denominator for printing +# XXX: unused def gcd(a, b): if b == 0: return a