degal/lib/exif_data.py
branchnew-exif
changeset 108 f74d8cf678ce
parent 106 a4f605bd122c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/degal/lib/exif_data.py	Sun Jun 14 16:10:30 2009 +0300
@@ -0,0 +1,1304 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+    EXIF file format data, including tag names, types, etc.
+
+    Most of this was copied with modifications from EXIFpy:
+        # Library to extract EXIF information from digital camera image files
+        # http://sourceforge.net/projects/exif-py/
+        #
+        # VERSION 1.1.0
+        #
+        # Copyright (c) 2002-2007 Gene Cash All rights reserved
+        # Copyright (c) 2007-2008 Ianaré Sévi All rights reserved
+        #
+        # Redistribution and use in source and binary forms, with or without
+        # modification, are permitted provided that the following conditions
+        # are met:
+        #
+        #  1. Redistributions of source code must retain the above copyright
+        #     notice, this list of conditions and the following disclaimer.
+        #
+        #  2. Redistributions in binary form must reproduce the above
+        #     copyright notice, this list of conditions and the following
+        #     disclaimer in the documentation and/or other materials provided
+        #     with the distribution.
+        #
+        #  3. Neither the name of the authors nor the names of its contributors
+        #     may be used to endorse or promote products derived from this
+        #     software without specific prior written permission.
+        #
+        # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+        # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+        # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+        # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+        # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+        # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+        # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+        # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+        # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+        # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+        # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+
+import decimal, itertools
+
+def filter_ascii (values) :
+    """
+        Default post-filter for ASCII values.
+
+        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.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) :
+    """
+        Default post-filter for Ratio values.
+
+        This takes the pairs of numerator/denominator values and builds Decimals out of them
+    """
+
+    return [build_ratio(values[i], values[i + 1]) for i in xrange(0, len(values), 2)]
+
+
+# IFD Tag type information, indexed by code
+#  { type_code: (type_fmt, name, filter_func) }
+#
+# type_fmt's that are one char will be prefixed with the count for use with struct.unpack, those with more chars will
+# be repeated as many times for use with struct.unpack.
+FIELD_TYPES = {
+#    0x0000: (None,  'Proprietary'   ), # ??? no such type
+    0x0001: ('B',   'Byte',         None            ),
+    0x0002: ('s',   'ASCII',        filter_ascii    ),
+    0x0003: ('H',   'Short',        None            ),
+    0x0004: ('L',   'Long',         None            ),
+    0x0005: ('LL',  'Ratio',        filter_ratio    ),
+    0x0006: ('b',   'Signed Byte',  None            ),
+    0x0007: ('s',   'Undefined',    None            ),
+    0x0008: ('h',   'Signed Short', None            ),
+    0x0009: ('l',   'Signed Long',  None            ),
+    0x000A: ('ll',  'Signed Ratio', filter_ratio    ),
+}
+
+# magic value to indicate sub-IFDs
+SUB_IFD_MAGIC = object()
+
+
+class Tag (object) :
+    """
+        Represents an Exif Tag
+    """
+
+    def __init__ (self, name) :
+        """
+            Build Exif tag with given name, and optional external values-filter function.
+        """
+
+        self.name = name
+
+    def map_values (self, values) :
+        """
+            Map the given tag value to a printable string using the given value spec.
+        """
+
+        # default value-mapping
+        return ", ".join(str(value) for value in values)
+
+class TagDict (Tag) :
+    """
+        A tag with a dict mapping values to names
+    """
+
+    def __init__ (self, name, values_dict) :
+        super(TagDict, self).__init__(name)
+
+        self.values_dict = values_dict
+
+    def map_values (self, values) :
+        """
+            Map the values through our dict, defaulting to the repr.
+        """
+
+        return ", ".join(self.values_dict.get(value, repr(value)) for value in values)
+
+class TagFunc (Tag) :
+    """
+        A tag with a simple function mapping values to names
+    """
+
+    def __init__ (self, name, values_func) :
+        super(TagFunc, self).__init__(name)
+
+        self.values_func = values_func
+
+    def map_values (self, values) :
+        """
+            Map the values through our func
+        """
+
+        return self.values_func(values)
+
+class IFDTag (Tag) :
+    """
+        A tag that references another IFD
+    """
+
+    def __init__ (self, name, ifd_tags=None) :
+        """
+            A tag that points to another IFD block. `ifd_tags`, if given, lists the tags for that block, otherwise,
+            the same tags as for the current block are used.
+        """
+
+        super(IFDTag, self).__init__(name)
+
+        self.ifd_tags = ifd_tags
+
+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) :
+    """
+        A UserComment field starts with an eight-byte encoding designator.
+    """
+
+    # single binary string
+    value, = values
+    
+    # split up
+    charset, comment_raw = value[:8], value[8:]
+
+    # 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/SUB_IFD_MAGIC) }
+#
+# 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?
+# SUB_IFD_MAGIC signifies that this IFD points to 
+# otherwise, the value is left as-is.
+# interoperability tags
+INTR_TAGS = {
+    0x0001: Tag('InteroperabilityIndex'),
+    0x0002: Tag('InteroperabilityVersion'),
+    0x1000: Tag('RelatedImageFileFormat'),
+    0x1001: Tag('RelatedImageWidth'),
+    0x1002: Tag('RelatedImageLength'),
+    }
+
+# GPS tags (not used yet, haven't seen camera with GPS)
+GPS_TAGS = {
+    0x0000: Tag('GPSVersionID'),
+    0x0001: Tag('GPSLatitudeRef'),
+    0x0002: Tag('GPSLatitude'),
+    0x0003: Tag('GPSLongitudeRef'),
+    0x0004: Tag('GPSLongitude'),
+    0x0005: Tag('GPSAltitudeRef'),
+    0x0006: Tag('GPSAltitude'),
+    0x0007: Tag('GPSTimeStamp'),
+    0x0008: Tag('GPSSatellites'),
+    0x0009: Tag('GPSStatus'),
+    0x000A: Tag('GPSMeasureMode'),
+    0x000B: Tag('GPSDOP'),
+    0x000C: Tag('GPSSpeedRef'),
+    0x000D: Tag('GPSSpeed'),
+    0x000E: Tag('GPSTrackRef'),
+    0x000F: Tag('GPSTrack'),
+    0x0010: Tag('GPSImgDirectionRef'),
+    0x0011: Tag('GPSImgDirection'),
+    0x0012: Tag('GPSMapDatum'),
+    0x0013: Tag('GPSDestLatitudeRef'),
+    0x0014: Tag('GPSDestLatitude'),
+    0x0015: Tag('GPSDestLongitudeRef'),
+    0x0016: Tag('GPSDestLongitude'),
+    0x0017: Tag('GPSDestBearingRef'),
+    0x0018: Tag('GPSDestBearing'),
+    0x0019: Tag('GPSDestDistanceRef'),
+    0x001A: Tag('GPSDestDistance'),
+    0x001D: Tag('GPSDate'),
+    }
+
+
+EXIF_TAGS = {
+    0x0100: Tag('ImageWidth'),
+    0x0101: Tag('ImageLength'),
+    0x0102: Tag('BitsPerSample'),
+    0x0103: TagDict('Compression',
+             {1: 'Uncompressed',
+              2: 'CCITT 1D',
+              3: 'T4/Group 3 Fax',
+              4: 'T6/Group 4 Fax',
+              5: 'LZW',
+              6: 'JPEG (old-style)',
+              7: 'JPEG',
+              8: 'Adobe Deflate',
+              9: 'JBIG B&W',
+              10: 'JBIG Color',
+              32766: 'Next',
+              32769: 'Epson ERF Compressed',
+              32771: 'CCIRLEW',
+              32773: 'PackBits',
+              32809: 'Thunderscan',
+              32895: 'IT8CTPAD',
+              32896: 'IT8LW',
+              32897: 'IT8MP',
+              32898: 'IT8BL',
+              32908: 'PixarFilm',
+              32909: 'PixarLog',
+              32946: 'Deflate',
+              32947: 'DCS',
+              34661: 'JBIG',
+              34676: 'SGILog',
+              34677: 'SGILog24',
+              34712: 'JPEG 2000',
+              34713: 'Nikon NEF Compressed',
+              65000: 'Kodak DCR Compressed',
+              65535: 'Pentax PEF Compressed'}),
+    0x0106: Tag('PhotometricInterpretation'),
+    0x0107: Tag('Thresholding'),
+    0x010A: Tag('FillOrder'),
+    0x010D: Tag('DocumentName'),
+    0x010E: Tag('ImageDescription'),
+    0x010F: Tag('Make'),
+    0x0110: Tag('Model'),
+    0x0111: Tag('StripOffsets'),
+    0x0112: TagDict('Orientation',
+             {1: 'Horizontal (normal)',
+              2: 'Mirrored horizontal',
+              3: 'Rotated 180',
+              4: 'Mirrored vertical',
+              5: 'Mirrored horizontal then rotated 90 CCW',
+              6: 'Rotated 90 CW',
+              7: 'Mirrored horizontal then rotated 90 CW',
+              8: 'Rotated 90 CCW'}),
+    0x0115: Tag('SamplesPerPixel'),
+    0x0116: Tag('RowsPerStrip'),
+    0x0117: Tag('StripByteCounts'),
+    0x011A: Tag('XResolution'),
+    0x011B: Tag('YResolution'),
+    0x011C: Tag('PlanarConfiguration'),
+    0x011D: Tag('PageName'),
+    0x0128: TagDict('ResolutionUnit',
+             {1: 'Not Absolute',
+              2: 'Pixels/Inch',
+              3: 'Pixels/Centimeter'}),
+    0x012D: Tag('TransferFunction'),
+    0x0131: Tag('Software'),
+    0x0132: Tag('DateTime'),
+    0x013B: Tag('Artist'),
+    0x013E: Tag('WhitePoint'),
+    0x013F: Tag('PrimaryChromaticities'),
+    0x0156: Tag('TransferRange'),
+    0x0200: Tag('JPEGProc'),
+    0x0201: Tag('JPEGInterchangeFormat'),
+    0x0202: Tag('JPEGInterchangeFormatLength'),
+    0x0211: Tag('YCbCrCoefficients'),
+    0x0212: Tag('YCbCrSubSampling'),
+    0x0213: TagDict('YCbCrPositioning',
+             {1: 'Centered',
+              2: 'Co-sited'}),
+    0x0214: Tag('ReferenceBlackWhite'),
+    
+    0x4746: Tag('Rating'),
+    
+    0x828D: Tag('CFARepeatPatternDim'),
+    0x828E: Tag('CFAPattern'),
+    0x828F: Tag('BatteryLevel'),
+    0x8298: Tag('Copyright'),
+    0x829A: Tag('ExposureTime'),
+    0x829D: Tag('FNumber'),
+    0x83BB: Tag('IPTC/NAA'),
+    0x8769: IFDTag('ExifOffset', None),
+    0x8773: Tag('InterColorProfile'),
+    0x8822: TagDict('ExposureProgram',
+             {0: 'Unidentified',
+              1: 'Manual',
+              2: 'Program Normal',
+              3: 'Aperture Priority',
+              4: 'Shutter Priority',
+              5: 'Program Creative',
+              6: 'Program Action',
+              7: 'Portrait Mode',
+              8: 'Landscape Mode'}),
+    0x8824: Tag('SpectralSensitivity'),
+    0x8825: IFDTag('GPSInfo', GPS_TAGS),
+    0x8827: Tag('ISOSpeedRatings'),
+    0x8828: Tag('OECF'),
+    0x9000: Tag('ExifVersion'),
+    0x9003: Tag('DateTimeOriginal'),
+    0x9004: Tag('DateTimeDigitized'),
+    0x9101: TagDict('ComponentsConfiguration',
+             {0: '',
+              1: 'Y',
+              2: 'Cb',
+              3: 'Cr',
+              4: 'Red',
+              5: 'Green',
+              6: 'Blue'}),
+    0x9102: Tag('CompressedBitsPerPixel'),
+    0x9201: Tag('ShutterSpeedValue'),
+    0x9202: Tag('ApertureValue'),
+    0x9203: Tag('BrightnessValue'),
+    0x9204: Tag('ExposureBiasValue'),
+    0x9205: Tag('MaxApertureValue'),
+    0x9206: Tag('SubjectDistance'),
+    0x9207: TagDict('MeteringMode',
+             {0: 'Unidentified',
+              1: 'Average',
+              2: 'CenterWeightedAverage',
+              3: 'Spot',
+              4: 'MultiSpot',
+              5: 'Pattern'}),
+    0x9208: TagDict('LightSource',
+             {0: 'Unknown',
+              1: 'Daylight',
+              2: 'Fluorescent',
+              3: 'Tungsten',
+              9: 'Fine Weather',
+              10: 'Flash',
+              11: 'Shade',
+              12: 'Daylight Fluorescent',
+              13: 'Day White Fluorescent',
+              14: 'Cool White Fluorescent',
+              15: 'White Fluorescent',
+              17: 'Standard Light A',
+              18: 'Standard Light B',
+              19: 'Standard Light C',
+              20: 'D55',
+              21: 'D65',
+              22: 'D75',
+              255: 'Other'}),
+    0x9209: TagDict('Flash',
+             {0: 'No',
+              1: 'Fired',
+              5: 'Fired (?)', # no return sensed
+              7: 'Fired (!)', # return sensed
+              9: 'Fill Fired',
+              13: 'Fill Fired (?)',
+              15: 'Fill Fired (!)',
+              16: 'Off',
+              24: 'Auto Off',
+              25: 'Auto Fired',
+              29: 'Auto Fired (?)',
+              31: 'Auto Fired (!)',
+              32: 'Not Available'}),
+    0x920A: Tag('FocalLength'),
+    0x9214: Tag('SubjectArea'),
+    0x927C: Tag('MakerNote'),
+    0x9286: TagFunc('UserComment', decode_UserComment),
+    0x9290: Tag('SubSecTime'),
+    0x9291: Tag('SubSecTimeOriginal'),
+    0x9292: Tag('SubSecTimeDigitized'),
+    
+    # used by Windows Explorer
+    0x9C9B: Tag('XPTitle'),
+    0x9C9C: Tag('XPComment'),
+    0x9C9D: Tag('XPAuthor'), #(ignored by Windows Explorer if Artist exists)
+    0x9C9E: Tag('XPKeywords'),
+    0x9C9F: Tag('XPSubject'),
+
+    0xA000: Tag('FlashPixVersion'),
+    0xA001: TagDict('ColorSpace',
+             {1: 'sRGB',
+              2: 'Adobe RGB',
+              65535: 'Uncalibrated'}),
+    0xA002: Tag('ExifImageWidth'),
+    0xA003: Tag('ExifImageLength'),
+    0xA005: IFDTag('InteroperabilityOffset', INTR_TAGS),
+    0xA20B: Tag('FlashEnergy'),               # 0x920B in TIFF/EP
+    0xA20C: Tag('SpatialFrequencyResponse'),  # 0x920C
+    0xA20E: Tag('FocalPlaneXResolution'),     # 0x920E
+    0xA20F: Tag('FocalPlaneYResolution'),     # 0x920F
+    0xA210: Tag('FocalPlaneResolutionUnit'),  # 0x9210
+    0xA214: Tag('SubjectLocation'),           # 0x9214
+    0xA215: Tag('ExposureIndex'),             # 0x9215
+    0xA217: TagDict('SensingMethod',                # 0x9217
+             {1: 'Not defined',
+              2: 'One-chip color area',
+              3: 'Two-chip color area',
+              4: 'Three-chip color area',
+              5: 'Color sequential area',
+              7: 'Trilinear',
+              8: 'Color sequential linear'}),             
+    0xA300: TagDict('FileSource',
+             {1: 'Film Scanner',
+              2: 'Reflection Print Scanner',
+              3: 'Digital Camera'}),
+    0xA301: TagDict('SceneType',
+             {1: 'Directly Photographed'}),
+    0xA302: Tag('CVAPattern'),
+    0xA401: TagDict('CustomRendered',
+             {0: 'Normal',
+              1: 'Custom'}),
+    0xA402: TagDict('ExposureMode',
+             {0: 'Auto Exposure',
+              1: 'Manual Exposure',
+              2: 'Auto Bracket'}),
+    0xA403: TagDict('WhiteBalance',
+             {0: 'Auto',
+              1: 'Manual'}),
+    0xA404: Tag('DigitalZoomRatio'),
+    0xA405: ('FocalLengthIn35mmFilm', None),
+    0xA406: TagDict('SceneCaptureType',
+             {0: 'Standard',
+              1: 'Landscape',
+              2: 'Portrait',
+              3: 'Night)'}),
+    0xA407: TagDict('GainControl',
+             {0: 'None',
+              1: 'Low gain up',
+              2: 'High gain up',
+              3: 'Low gain down',
+              4: 'High gain down'}),
+    0xA408: TagDict('Contrast',
+             {0: 'Normal',
+              1: 'Soft',
+              2: 'Hard'}),
+    0xA409: TagDict('Saturation',
+             {0: 'Normal',
+              1: 'Soft',
+              2: 'Hard'}),
+    0xA40A: TagDict('Sharpness',
+             {0: 'Normal',
+              1: 'Soft',
+              2: 'Hard'}),
+    0xA40B: Tag('DeviceSettingDescription'),
+    0xA40C: Tag('SubjectDistanceRange'),
+    0xA500: Tag('Gamma'),
+    0xC4A5: Tag('PrintIM'),
+    0xEA1C:	('Padding', None),
+    }
+
+# 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
+    # reading the Nikon MakerNote.
+    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.
+    a = seq[0]
+
+    # 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
+
+    whole = a / b
+
+    a = a % b
+
+    if whole != 0 :
+        ret_str = ret_str + str(whole) + " "
+
+    if a == 0 :
+        ret_str = ret_str + "EV"
+    else :
+        r = Ratio(a, b)
+        ret_str = ret_str + r.__repr__() + " EV"
+
+    return ret_str
+
+# Nikon E99x MakerNote Tags
+MAKERNOTE_NIKON_NEWER_TAGS={
+    0x0001: Tag('MakernoteVersion'),	# Sometimes binary
+    0x0002: Tag('ISOSetting'),
+    0x0003: Tag('ColorMode'),
+    0x0004: Tag('Quality'),
+    0x0005: Tag('Whitebalance'),
+    0x0006: Tag('ImageSharpening'),
+    0x0007: Tag('FocusMode'),
+    0x0008: Tag('FlashSetting'),
+    0x0009: Tag('AutoFlashMode'),
+    0x000B: Tag('WhiteBalanceBias'),
+    0x000C: Tag('WhiteBalanceRBCoeff'),
+    0x000D: TagFunc('ProgramShift', nikon_ev_bias),
+    # Nearly the same as the other EV vals, but step size is 1/12 EV (?)
+    0x000E: TagFunc('ExposureDifference', nikon_ev_bias),
+    0x000F: Tag('ISOSelection'),
+    0x0011: Tag('NikonPreview'),
+    0x0012: TagFunc('FlashCompensation', nikon_ev_bias),
+    0x0013: Tag('ISOSpeedRequested'),
+    0x0016: Tag('PhotoCornerCoordinates'),
+    # 0x0017: Unknown, but most likely an EV value
+    0x0018: TagFunc('FlashBracketCompensationApplied', nikon_ev_bias),
+    0x0019: Tag('AEBracketCompensationApplied'),
+    0x001A: Tag('ImageProcessing'),
+    0x001B: Tag('CropHiSpeed'),
+    0x001D: Tag('SerialNumber'),	# Conflict with 0x00A0 ?
+    0x001E: Tag('ColorSpace'),
+    0x001F: Tag('VRInfo'),
+    0x0020: Tag('ImageAuthentication'),
+    0x0022: Tag('ActiveDLighting'),
+    0x0023: Tag('PictureControl'),
+    0x0024: Tag('WorldTime'),
+    0x0025: Tag('ISOInfo'),
+    0x0080: Tag('ImageAdjustment'),
+    0x0081: Tag('ToneCompensation'),
+    0x0082: Tag('AuxiliaryLens'),
+    0x0083: Tag('LensType'),
+    0x0084: Tag('LensMinMaxFocalMaxAperture'),
+    0x0085: Tag('ManualFocusDistance'),
+    0x0086: Tag('DigitalZoomFactor'),
+    0x0087: TagDict('FlashMode',
+             {0x00: 'Did Not Fire',
+              0x01: 'Fired, Manual',
+              0x07: 'Fired, External',
+              0x08: 'Fired, Commander Mode ',
+              0x09: 'Fired, TTL Mode'}),
+    0x0088: TagDict('AFFocusPosition',
+             {0x0000: 'Center',
+              0x0100: 'Top',
+              0x0200: 'Bottom',
+              0x0300: 'Left',
+              0x0400: 'Right'}),
+    0x0089: TagDict('BracketingMode',
+             {0x00: 'Single frame, no bracketing',
+              0x01: 'Continuous, no bracketing',
+              0x02: 'Timer, no bracketing',
+              0x10: 'Single frame, exposure bracketing',
+              0x11: 'Continuous, exposure bracketing',
+              0x12: 'Timer, exposure bracketing',
+              0x40: 'Single frame, white balance bracketing',
+              0x41: 'Continuous, white balance bracketing',
+              0x42: 'Timer, white balance bracketing'}),
+    0x008A: Tag('AutoBracketRelease'),
+    0x008B: Tag('LensFStops'),
+    0x008C: ('NEFCurve1', None),	# ExifTool calls this 'ContrastCurve'
+    0x008D: Tag('ColorMode'),
+    0x008F: Tag('SceneMode'),
+    0x0090: Tag('LightingType'),
+    0x0091: Tag('ShotInfo'),	# First 4 bytes are a version number in ASCII
+    0x0092: Tag('HueAdjustment'),
+    # ExifTool calls this 'NEFCompression', should be 1-4
+    0x0093: Tag('Compression'),
+    0x0094: TagDict('Saturation',
+             {-3: 'B&W',
+              -2: '-2',
+              -1: '-1',
+              0: '0',
+              1: '1',
+              2: '2'}),
+    0x0095: Tag('NoiseReduction'),
+    0x0096: ('NEFCurve2', None),	# ExifTool calls this 'LinearizationTable'
+    0x0097: Tag('ColorBalance'),	# First 4 bytes are a version number in ASCII
+    0x0098: Tag('LensData'),	# First 4 bytes are a version number in ASCII
+    0x0099: Tag('RawImageCenter'),
+    0x009A: Tag('SensorPixelSize'),
+    0x009C: Tag('Scene Assist'),
+    0x009E: Tag('RetouchHistory'),
+    0x00A0: Tag('SerialNumber'),
+    0x00A2: Tag('ImageDataSize'),
+    # 00A3: unknown - a single byte 0
+    # 00A4: In NEF, looks like a 4 byte ASCII version number ('0200')
+    0x00A5: Tag('ImageCount'),
+    0x00A6: Tag('DeletedImageCount'),
+    0x00A7: Tag('TotalShutterReleases'),
+    # 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: Tag('FlashInfo'),
+    0x00A9: Tag('ImageOptimization'),
+    0x00AA: Tag('Saturation'),
+    0x00AB: Tag('DigitalVariProgram'),
+    0x00AC: Tag('ImageStabilization'),
+    0x00AD: Tag('Responsive AF'),	# 'AFResponse'
+    0x00B0: Tag('MultiExposure'),
+    0x00B1: Tag('HighISONoiseReduction'),
+    0x00B7: Tag('AFInfo'),
+    0x00B8: Tag('FileInfo'),
+    # 00B9: unknown
+    0x0100: Tag('DigitalICE'),
+    0x0103: TagDict('PreviewCompression',
+             {1: 'Uncompressed',
+              2: 'CCITT 1D',
+              3: 'T4/Group 3 Fax',
+              4: 'T6/Group 4 Fax',
+              5: 'LZW',
+              6: 'JPEG (old-style)',
+              7: 'JPEG',
+              8: 'Adobe Deflate',
+              9: 'JBIG B&W',
+              10: 'JBIG Color',
+              32766: 'Next',
+              32769: 'Epson ERF Compressed',
+              32771: 'CCIRLEW',
+              32773: 'PackBits',
+              32809: 'Thunderscan',
+              32895: 'IT8CTPAD',
+              32896: 'IT8LW',
+              32897: 'IT8MP',
+              32898: 'IT8BL',
+              32908: 'PixarFilm',
+              32909: 'PixarLog',
+              32946: 'Deflate',
+              32947: 'DCS',
+              34661: 'JBIG',
+              34676: 'SGILog',
+              34677: 'SGILog24',
+              34712: 'JPEG 2000',
+              34713: 'Nikon NEF Compressed',
+              65000: 'Kodak DCR Compressed',
+              65535: 'Pentax PEF Compressed',}),
+    0x0201: Tag('PreviewImageStart'),
+    0x0202: Tag('PreviewImageLength'),
+    0x0213: TagDict('PreviewYCbCrPositioning',
+             {1: 'Centered',
+              2: 'Co-sited'}), 
+    0x0010: Tag('DataDump'),
+    }
+
+MAKERNOTE_NIKON_OLDER_TAGS = {
+    0x0003: TagDict('Quality',
+             {1: 'VGA Basic',
+              2: 'VGA Normal',
+              3: 'VGA Fine',
+              4: 'SXGA Basic',
+              5: 'SXGA Normal',
+              6: 'SXGA Fine'}),
+    0x0004: TagDict('ColorMode',
+             {1: 'Color',
+              2: 'Monochrome'}),
+    0x0005: TagDict('ImageAdjustment',
+             {0: 'Normal',
+              1: 'Bright+',
+              2: 'Bright-',
+              3: 'Contrast+',
+              4: 'Contrast-'}),
+    0x0006: TagDict('CCDSpeed',
+             {0: 'ISO 80',
+              2: 'ISO 160',
+              4: 'ISO 320',
+              5: 'ISO 100'}),
+    0x0007: TagDict('WhiteBalance',
+             {0: 'Auto',
+              1: 'Preset',
+              2: 'Daylight',
+              3: 'Incandescent',
+              4: 'Fluorescent',
+              5: 'Cloudy',
+              6: 'Speed Light'}),
+    }
+
+def olympus_special_mode (values) :
+    """
+        Decode Olympus SpecialMode tag in MakerNote
+    """
+
+    a = {
+        0: 'Normal',
+        1: 'Unknown',
+        2: 'Fast',
+        3: 'Panorama'
+    }
+
+    b = {
+        0: 'Non-panoramic',
+        1: 'Left to right',
+        2: 'Right to left',
+        3: 'Bottom to top',
+        4: 'Top to bottom'
+    }
+
+    if v[0] not in a or v[2] not in b:
+        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: Tag('JPEGThumbnail'),
+    0x0200: TagFunc('SpecialMode', olympus_special_mode),
+    0x0201: TagDict('JPEGQual',
+             {1: 'SQ',
+              2: 'HQ',
+              3: 'SHQ'}),
+    0x0202: TagDict('Macro',
+             {0: 'Normal',
+             1: 'Macro',
+             2: 'SuperMacro'}),
+    0x0203: TagDict('BWMode',
+             {0: 'Off',
+             1: 'On'}),
+    0x0204: Tag('DigitalZoom'),
+    0x0205: Tag('FocalPlaneDiagonal'),
+    0x0206: Tag('LensDistortionParams'),
+    0x0207: Tag('SoftwareRelease'),
+    0x0208: Tag('PictureInfo'),
+    0x0209: Tag('CameraID'), # print as string
+    0x0F00: Tag('DataDump'),
+    0x0300: Tag('PreCaptureFrames'),
+    0x0404: Tag('SerialNumber'),
+    0x1000: Tag('ShutterSpeedValue'),
+    0x1001: Tag('ISOValue'),
+    0x1002: Tag('ApertureValue'),
+    0x1003: Tag('BrightnessValue'),
+    0x1004: Tag('FlashMode'),
+    0x1004: TagDict('FlashMode',
+       {2: 'On',
+        3: 'Off'}),
+    0x1005: TagDict('FlashDevice',
+       {0: 'None',
+        1: 'Internal',
+        4: 'External',
+        5: 'Internal + External'}),
+    0x1006: Tag('ExposureCompensation'),
+    0x1007: Tag('SensorTemperature'),
+    0x1008: Tag('LensTemperature'),
+    0x100b: TagDict('FocusMode',
+       {0: 'Auto',
+        1: 'Manual'}),
+    0x1017: Tag('RedBalance'),
+    0x1018: Tag('BlueBalance'),
+    0x101a: Tag('SerialNumber'),
+    0x1023: Tag('FlashExposureComp'),
+    0x1026: TagDict('ExternalFlashBounce',
+       {0: 'No',
+        1: 'Yes'}),
+    0x1027: Tag('ExternalFlashZoom'),
+    0x1028: Tag('ExternalFlashMode'),
+    0x1029: ('Contrast 	int16u',
+       {0: 'High',
+        1: 'Normal',
+        2: 'Low'}),
+    0x102a: Tag('SharpnessFactor'),
+    0x102b: Tag('ColorControl'),
+    0x102c: Tag('ValidBits'),
+    0x102d: Tag('CoringFilter'),
+    0x102e: Tag('OlympusImageWidth'),
+    0x102f: Tag('OlympusImageHeight'),
+    0x1034: Tag('CompressionRatio'),
+    0x1035: TagDict('PreviewImageValid',
+       {0: 'No',
+        1: 'Yes'}),
+    0x1036: Tag('PreviewImageStart'),
+    0x1037: Tag('PreviewImageLength'),
+    0x1039: TagDict('CCDScanMode',
+       {0: 'Interlaced',
+        1: 'Progressive'}),
+    0x103a: TagDict('NoiseReduction',
+       {0: 'Off',
+        1: 'On'}),
+    0x103b: Tag('InfinityLensStep'),
+    0x103c: Tag('NearLensStep'),
+
+    # TODO - these need extra definitions
+    # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html
+    0x2010: Tag('Equipment'),
+    0x2020: Tag('CameraSettings'),
+    0x2030: Tag('RawDevelopment'),
+    0x2040: Tag('ImageProcessing'),
+    0x2050: Tag('FocusInfo'),
+    0x3000: Tag('RawInfo '),
+    }
+
+# 0x2020 CameraSettings
+MAKERNOTE_OLYMPUS_TAG_0x2020={
+    0x0100: TagDict('PreviewImageValid',
+             {0: 'No',
+              1: 'Yes'}),
+    0x0101: Tag('PreviewImageStart'),
+    0x0102: Tag('PreviewImageLength'),
+    0x0200: TagDict('ExposureMode',
+             {1: 'Manual',
+              2: 'Program',
+              3: 'Aperture-priority AE',
+              4: 'Shutter speed priority AE',
+              5: 'Program-shift'}),
+    0x0201: TagDict('AELock',
+             {0: 'Off',
+              1: 'On'}),
+    0x0202: TagDict('MeteringMode',
+             {2: 'Center Weighted',
+              3: 'Spot',
+              5: 'ESP',
+              261: 'Pattern+AF',
+              515: 'Spot+Highlight control',
+              1027: 'Spot+Shadow control'}),
+    0x0300: TagDict('MacroMode',
+             {0: 'Off',
+              1: 'On'}),
+    0x0301: TagDict('FocusMode',
+             {0: 'Single AF',
+              1: 'Sequential shooting AF',
+              2: 'Continuous AF',
+              3: 'Multi AF',
+              10: 'MF'}),
+    0x0302: TagDict('FocusProcess',
+             {0: 'AF Not Used',
+              1: 'AF Used'}),
+    0x0303: TagDict('AFSearch',
+             {0: 'Not Ready',
+              1: 'Ready'}),
+    0x0304: Tag('AFAreas'),
+    0x0401: Tag('FlashExposureCompensation'),
+    0x0500: ('WhiteBalance2',
+             {0: 'Auto',
+             16: '7500K (Fine Weather with Shade)',
+             17: '6000K (Cloudy)',
+             18: '5300K (Fine Weather)',
+             20: '3000K (Tungsten light)',
+             21: '3600K (Tungsten light-like)',
+             33: '6600K (Daylight fluorescent)',
+             34: '4500K (Neutral white fluorescent)',
+             35: '4000K (Cool white fluorescent)',
+             48: '3600K (Tungsten light-like)',
+             256: 'Custom WB 1',
+             257: 'Custom WB 2',
+             258: 'Custom WB 3',
+             259: 'Custom WB 4',
+             512: 'Custom WB 5400K',
+             513: 'Custom WB 2900K',
+             514: 'Custom WB 8000K', }),
+    0x0501: Tag('WhiteBalanceTemperature'),
+    0x0502: Tag('WhiteBalanceBracket'),
+    0x0503: Tag('CustomSaturation'), # (3 numbers: 1. CS Value, 2. Min, 3. Max)
+    0x0504: TagDict('ModifiedSaturation',
+             {0: 'Off',
+              1: 'CM1 (Red Enhance)',
+              2: 'CM2 (Green Enhance)',
+              3: 'CM3 (Blue Enhance)',
+              4: 'CM4 (Skin Tones)'}),
+    0x0505: Tag('ContrastSetting'), # (3 numbers: 1. Contrast, 2. Min, 3. Max)
+    0x0506: Tag('SharpnessSetting'), # (3 numbers: 1. Sharpness, 2. Min, 3. Max)
+    0x0507: TagDict('ColorSpace',
+             {0: 'sRGB',
+              1: 'Adobe RGB',
+              2: 'Pro Photo RGB'}),
+    0x0509: TagDict('SceneMode',
+             {0: 'Standard',
+              6: 'Auto',
+              7: 'Sport',
+              8: 'Portrait',
+              9: 'Landscape+Portrait',
+             10: 'Landscape',
+             11: 'Night scene',
+             13: 'Panorama',
+             16: 'Landscape+Portrait',
+             17: 'Night+Portrait',
+             19: 'Fireworks',
+             20: 'Sunset',
+             22: 'Macro',
+             25: 'Documents',
+             26: 'Museum',
+             28: 'Beach&Snow',
+             30: 'Candle',
+             35: 'Underwater Wide1',
+             36: 'Underwater Macro',
+             39: 'High Key',
+             40: 'Digital Image Stabilization',
+             44: 'Underwater Wide2',
+             45: 'Low Key',
+             46: 'Children',
+             48: 'Nature Macro'}),
+    0x050a: TagDict('NoiseReduction',
+             {0: 'Off',
+              1: 'Noise Reduction',
+              2: 'Noise Filter',
+              3: 'Noise Reduction + Noise Filter',
+              4: 'Noise Filter (ISO Boost)',
+              5: 'Noise Reduction + Noise Filter (ISO Boost)'}),
+    0x050b: TagDict('DistortionCorrection',
+             {0: 'Off',
+              1: 'On'}),
+    0x050c: TagDict('ShadingCompensation',
+             {0: 'Off',
+              1: 'On'}),
+    0x050d: Tag('CompressionFactor'),
+    0x050f: TagDict('Gradation',
+             {'-1 -1 1': 'Low Key',
+              '0 -1 1': 'Normal',
+              '1 -1 1': 'High Key'}),
+    0x0520: TagDict('PictureMode',
+             {1: 'Vivid',
+              2: 'Natural',
+              3: 'Muted',
+              256: 'Monotone',
+              512: 'Sepia'}),
+    0x0521: Tag('PictureModeSaturation'),
+    0x0522: Tag('PictureModeHue?'),
+    0x0523: Tag('PictureModeContrast'),
+    0x0524: Tag('PictureModeSharpness'),
+    0x0525: TagDict('PictureModeBWFilter',
+             {0: 'n/a',
+              1: 'Neutral',
+              2: 'Yellow',
+              3: 'Orange',
+              4: 'Red',
+              5: 'Green'}),
+    0x0526: TagDict('PictureModeTone',
+             {0: 'n/a',
+              1: 'Neutral',
+              2: 'Sepia',
+              3: 'Blue',
+              4: 'Purple',
+              5: 'Green'}),
+    0x0600: Tag('Sequence'), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits
+    0x0601: Tag('PanoramaMode'), # (2 numbers: 1. Mode, 2. Shot number)
+    0x0603: ('ImageQuality2',
+             {1: 'SQ',
+              2: 'HQ',
+              3: 'SHQ',
+              4: 'RAW'}),
+    0x0901: Tag('ManometerReading'),
+    }
+
+
+MAKERNOTE_CASIO_TAGS={
+    0x0001: TagDict('RecordingMode',
+             {1: 'Single Shutter',
+              2: 'Panorama',
+              3: 'Night Scene',
+              4: 'Portrait',
+              5: 'Landscape'}),
+    0x0002: TagDict('Quality',
+             {1: 'Economy',
+              2: 'Normal',
+              3: 'Fine'}),
+    0x0003: TagDict('FocusingMode',
+             {2: 'Macro',
+              3: 'Auto Focus',
+              4: 'Manual Focus',
+              5: 'Infinity'}),
+    0x0004: TagDict('FlashMode',
+             {1: 'Auto',
+              2: 'On',
+              3: 'Off',
+              4: 'Red Eye Reduction'}),
+    0x0005: TagDict('FlashIntensity',
+             {11: 'Weak',
+              13: 'Normal',
+              15: 'Strong'}),
+    0x0006: Tag('Object Distance'),
+    0x0007: TagDict('WhiteBalance',
+             {1: 'Auto',
+              2: 'Tungsten',
+              3: 'Daylight',
+              4: 'Fluorescent',
+              5: 'Shade',
+              129: 'Manual'}),
+    0x000B: TagDict('Sharpness',
+             {0: 'Normal',
+              1: 'Soft',
+              2: 'Hard'}),
+    0x000C: TagDict('Contrast',
+             {0: 'Normal',
+              1: 'Low',
+              2: 'High'}),
+    0x000D: TagDict('Saturation',
+             {0: 'Normal',
+              1: 'Low',
+              2: 'High'}),
+    0x0014: TagDict('CCDSpeed',
+             {64: 'Normal',
+              80: 'Normal',
+              100: 'High',
+              125: '+1.0',
+              244: '+3.0',
+              250: '+2.0'}),
+    }
+
+MAKERNOTE_FUJIFILM_TAGS={
+    0x0000: Tag('NoteVersion'),
+    0x1000: Tag('Quality'),
+    0x1001: TagDict('Sharpness',
+             {1: 'Soft',
+              2: 'Soft',
+              3: 'Normal',
+              4: 'Hard',
+              5: 'Hard'}),
+    0x1002: TagDict('WhiteBalance',
+             {0: 'Auto',
+              256: 'Daylight',
+              512: 'Cloudy',
+              768: 'DaylightColor-Fluorescent',
+              769: 'DaywhiteColor-Fluorescent',
+              770: 'White-Fluorescent',
+              1024: 'Incandescent',
+              3840: 'Custom'}),
+    0x1003: TagDict('Color',
+             {0: 'Normal',
+              256: 'High',
+              512: 'Low'}),
+    0x1004: TagDict('Tone',
+             {0: 'Normal',
+              256: 'High',
+              512: 'Low'}),
+    0x1010: TagDict('FlashMode',
+             {0: 'Auto',
+              1: 'On',
+              2: 'Off',
+              3: 'Red Eye Reduction'}),
+    0x1011: Tag('FlashStrength'),
+    0x1020: TagDict('Macro',
+             {0: 'Off',
+              1: 'On'}),
+    0x1021: TagDict('FocusMode',
+             {0: 'Auto',
+              1: 'Manual'}),
+    0x1030: TagDict('SlowSync',
+             {0: 'Off',
+              1: 'On'}),
+    0x1031: TagDict('PictureMode',
+             {0: 'Auto',
+              1: 'Portrait',
+              2: 'Landscape',
+              4: 'Sports',
+              5: 'Night',
+              6: 'Program AE',
+              256: 'Aperture Priority AE',
+              512: 'Shutter Priority AE',
+              768: 'Manual Exposure'}),
+    0x1100: TagDict('MotorOrBracket',
+             {0: 'Off',
+              1: 'On'}),
+    0x1300: TagDict('BlurWarning',
+             {0: 'Off',
+              1: 'On'}),
+    0x1301: TagDict('FocusWarning',
+             {0: 'Off',
+              1: 'On'}),
+    0x1302: TagDict('AEWarning',
+             {0: 'Off',
+              1: 'On'}),
+    }
+
+MAKERNOTE_CANON_TAGS = {
+    0x0006: Tag('ImageType'),
+    0x0007: Tag('FirmwareVersion'),
+    0x0008: Tag('ImageNumber'),
+    0x0009: Tag('OwnerName'),
+    }
+
+# this is in element offset, name, optional value dictionary format
+MAKERNOTE_CANON_TAG_0x001 = {
+    1: TagDict('Macromode',
+        {1: 'Macro',
+         2: 'Normal'}),
+    2: Tag('SelfTimer'),
+    3: TagDict('Quality',
+        {2: 'Normal',
+         3: 'Fine',
+         5: 'Superfine'}),
+    4: TagDict('FlashMode',
+        {0: 'Flash Not Fired',
+         1: 'Auto',
+         2: 'On',
+         3: 'Red-Eye Reduction',
+         4: 'Slow Synchro',
+         5: 'Auto + Red-Eye Reduction',
+         6: 'On + Red-Eye Reduction',
+         16: 'external flash'}),
+    5: TagDict('ContinuousDriveMode',
+        {0: 'Single Or Timer',
+         1: 'Continuous'}),
+    7: TagDict('FocusMode',
+        {0: 'One-Shot',
+         1: 'AI Servo',
+         2: 'AI Focus',
+         3: 'MF',
+         4: 'Single',
+         5: 'Continuous',
+         6: 'MF'}),
+    10: TagDict('ImageSize',
+         {0: 'Large',
+          1: 'Medium',
+          2: 'Small'}),
+    11: TagDict('EasyShootingMode',
+         {0: 'Full Auto',
+          1: 'Manual',
+          2: 'Landscape',
+          3: 'Fast Shutter',
+          4: 'Slow Shutter',
+          5: 'Night',
+          6: 'B&W',
+          7: 'Sepia',
+          8: 'Portrait',
+          9: 'Sports',
+          10: 'Macro/Close-Up',
+          11: 'Pan Focus'}),
+    12: TagDict('DigitalZoom',
+         {0: 'None',
+          1: '2x',
+          2: '4x'}),
+    13: TagDict('Contrast',
+         {0xFFFF: 'Low',
+          0: 'Normal',
+          1: 'High'}),
+    14: TagDict('Saturation',
+         {0xFFFF: 'Low',
+          0: 'Normal',
+          1: 'High'}),
+    15: TagDict('Sharpness',
+         {0xFFFF: 'Low',
+          0: 'Normal',
+          1: 'High'}),
+    16: TagDict('ISO',
+         {0: 'See ISOSpeedRatings Tag',
+          15: 'Auto',
+          16: '50',
+          17: '100',
+          18: '200',
+          19: '400'}),
+    17: TagDict('MeteringMode',
+         {3: 'Evaluative',
+          4: 'Partial',
+          5: 'Center-weighted'}),
+    18: TagDict('FocusType',
+         {0: 'Manual',
+          1: 'Auto',
+          3: 'Close-Up (Macro)',
+          8: 'Locked (Pan Mode)'}),
+    19: TagDict('AFPointSelected',
+         {0x3000: 'None (MF)',
+          0x3001: 'Auto-Selected',
+          0x3002: 'Right',
+          0x3003: 'Center',
+          0x3004: 'Left'}),
+    20: TagDict('ExposureMode',
+         {0: 'Easy Shooting',
+          1: 'Program',
+          2: 'Tv-priority',
+          3: 'Av-priority',
+          4: 'Manual',
+          5: 'A-DEP'}),
+    23: Tag('LongFocalLengthOfLensInFocalUnits'),
+    24: Tag('ShortFocalLengthOfLensInFocalUnits'),
+    25: Tag('FocalUnitsPerMM'),
+    28: TagDict('FlashActivity',
+         {0: 'Did Not Fire',
+          1: 'Fired'}),
+    29: TagDict('FlashDetails',
+         {14: 'External E-TTL',
+          13: 'Internal Flash',
+          11: 'FP Sync Used',
+          7: '2nd("Rear")-Curtain Sync Used',
+          4: 'FP Sync Enabled'}),
+    32: TagDict('FocusMode',
+         {0: 'Single',
+          1: 'Continuous'}),
+    }
+
+MAKERNOTE_CANON_TAG_0x004 = {
+    7: TagDict('WhiteBalance',
+        {0: 'Auto',
+         1: 'Sunny',
+         2: 'Cloudy',
+         3: 'Tungsten',
+         4: 'Fluorescent',
+         5: 'Flash',
+         6: 'Custom'}),
+    9: Tag('SequenceNumber'),
+    14: Tag('AFPointUsed'),
+    15: TagDict('FlashBias',
+         {0xFFC0: '-2 EV',
+          0xFFCC: '-1.67 EV',
+          0xFFD0: '-1.50 EV',
+          0xFFD4: '-1.33 EV',
+          0xFFE0: '-1 EV',
+          0xFFEC: '-0.67 EV',
+          0xFFF0: '-0.50 EV',
+          0xFFF4: '-0.33 EV',
+          0x0000: '0 EV',
+          0x000C: '0.33 EV',
+          0x0010: '0.50 EV',
+          0x0014: '0.67 EV',
+          0x0020: '1 EV',
+          0x002C: '1.33 EV',
+          0x0030: '1.50 EV',
+          0x0034: '1.67 EV',
+          0x0040: '2 EV'}),
+    19: Tag('SubjectDistance'),
+    }
+
+# 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
+    else:
+        return gcd(b, a % b)
+
+class Ratio:
+    def __init__(self, num, den):
+        self.num = num
+        self.den = den
+
+    def __repr__(self):
+        self.reduce()
+        if self.den == 1:
+            return str(self.num)
+        return '%d/%d' % (self.num, self.den)
+
+    def reduce(self):
+        div = gcd(self.num, self.den)
+        if div > 1:
+            self.num = self.num / div
+            self.den = self.den / div
+
+