degal/exif_data.py
branchnew-exif
changeset 104 6afe59e5ffae
parent 103 63e89dc2d6f1
child 105 effae6f38749
--- 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