degal/exif_data.py
author Tero Marttila <terom@fixme.fi>
Sat, 13 Jun 2009 20:31:51 +0300
branchnew-exif
changeset 104 6afe59e5ffae
parent 103 63e89dc2d6f1
child 105 effae6f38749
permissions -rw-r--r--
tidy up exif_data a bit
#!/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    ),
}

def map_values (spec, values) :
    """
        Map the given tag value to a printable string using the given value spec.
    """

    # XXX: ensure that this always returns a str/unicode
    
    if spec is None :
        # nothing to map
        return ", ".join(str(value) for value in values)

    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 :
        # unknown kind of spec
        raise ValueError(spec)

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) }
#
# 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', None),
    0x0101: ('ImageLength', None),
    0x0102: ('BitsPerSample', None),
    0x0103: ('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: ('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',
              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: ('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', 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', None),
    
    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',
              2: 'Program Normal',
              3: 'Aperture Priority',
              4: 'Shutter Priority',
              5: 'Program Creative',
              6: 'Program Action',
              7: 'Portrait Mode',
              8: 'Landscape Mode'}),
    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',
              2: 'Cb',
              3: 'Cr',
              4: 'Red',
              5: 'Green',
              6: 'Blue'}),
    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',
              2: 'CenterWeightedAverage',
              3: 'Spot',
              4: 'MultiSpot',
              5: 'Pattern'}),
    0x9208: ('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: ('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: ('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', None),
    0x9C9C: ('XPComment', None),
    0x9C9D: ('XPAuthor', None), #(ignored by Windows Explorer if Artist exists)
    0x9C9E: ('XPKeywords', None),
    0x9C9F: ('XPSubject', None),

    0xA000: ('FlashPixVersion', None),
    0xA001: ('ColorSpace',
             {1: 'sRGB',
              2: 'Adobe RGB',
              65535: 'Uncalibrated'}),
    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',
              3: 'Two-chip color area',
              4: 'Three-chip color area',
              5: 'Color sequential area',
              7: 'Trilinear',
              8: 'Color sequential linear'}),             
    0xA300: ('FileSource',
             {1: 'Film Scanner',
              2: 'Reflection Print Scanner',
              3: 'Digital Camera'}),
    0xA301: ('SceneType',
             {1: 'Directly Photographed'}),
    0xA302: ('CVAPattern', None),
    0xA401: ('CustomRendered',
             {0: 'Normal',
              1: 'Custom'}),
    0xA402: ('ExposureMode',
             {0: 'Auto Exposure',
              1: 'Manual Exposure',
              2: 'Auto Bracket'}),
    0xA403: ('WhiteBalance',
             {0: 'Auto',
              1: 'Manual'}),
    0xA404: ('DigitalZoomRatio', None),
    0xA405: ('FocalLengthIn35mmFilm', None),
    0xA406: ('SceneCaptureType',
             {0: 'Standard',
              1: 'Landscape',
              2: 'Portrait',
              3: 'Night)'}),
    0xA407: ('GainControl',
             {0: 'None',
              1: 'Low gain up',
              2: 'High gain up',
              3: 'Low gain down',
              4: 'High gain down'}),
    0xA408: ('Contrast',
             {0: 'Normal',
              1: 'Soft',
              2: 'Hard'}),
    0xA409: ('Saturation',
             {0: 'Normal',
              1: 'Soft',
              2: 'Hard'}),
    0xA40A: ('Sharpness',
             {0: 'Normal',
              1: 'Soft',
              2: 'Hard'}),
    0xA40B: ('DeviceSettingDescription', None),
    0xA40C: ('SubjectDistanceRange', None),
    0xA500: ('Gamma', None),
    0xC4A5: ('PrintIM', None),
    0xEA1C:	('Padding', None),
    }

# interoperability tags
INTR_TAGS = {
    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', 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),
    }

# 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: ('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', None),
    0x0011: ('NikonPreview', None),
    0x0012: ('FlashCompensation', nikon_ev_bias),
    0x0013: ('ISOSpeedRequested', None),
    0x0016: ('PhotoCornerCoordinates', None),
    # 0x0017: Unknown, but most likely an EV value
    0x0018: ('FlashBracketCompensationApplied', nikon_ev_bias),
    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',
              0x07: 'Fired, External',
              0x08: 'Fired, Commander Mode ',
              0x09: 'Fired, TTL Mode'}),
    0x0088: ('AFFocusPosition',
             {0x0000: 'Center',
              0x0100: 'Top',
              0x0200: 'Bottom',
              0x0300: 'Left',
              0x0400: 'Right'}),
    0x0089: ('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: ('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', None),
    0x0094: ('Saturation',
             {-3: 'B&W',
              -2: '-2',
              -1: '-1',
              0: '0',
              1: '1',
              2: '2'}),
    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', 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', 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', None),
    0x0103: ('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: ('PreviewImageStart', None),
    0x0202: ('PreviewImageLength', None),
    0x0213: ('PreviewYCbCrPositioning',
             {1: 'Centered',
              2: 'Co-sited'}), 
    0x0010: ('DataDump', None),
    }

MAKERNOTE_NIKON_OLDER_TAGS = {
    0x0003: ('Quality',
             {1: 'VGA Basic',
              2: 'VGA Normal',
              3: 'VGA Fine',
              4: 'SXGA Basic',
              5: 'SXGA Normal',
              6: 'SXGA Fine'}),
    0x0004: ('ColorMode',
             {1: 'Color',
              2: 'Monochrome'}),
    0x0005: ('ImageAdjustment',
             {0: 'Normal',
              1: 'Bright+',
              2: 'Bright-',
              3: 'Contrast+',
              4: 'Contrast-'}),
    0x0006: ('CCDSpeed',
             {0: 'ISO 80',
              2: 'ISO 160',
              4: 'ISO 320',
              5: 'ISO 100'}),
    0x0007: ('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: ('JPEGThumbnail', None),
    0x0200: ('SpecialMode', olympus_special_mode),
    0x0201: ('JPEGQual',
             {1: 'SQ',
              2: 'HQ',
              3: 'SHQ'}),
    0x0202: ('Macro',
             {0: 'Normal',
             1: 'Macro',
             2: 'SuperMacro'}),
    0x0203: ('BWMode',
             {0: 'Off',
             1: 'On'}),
    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'}),
    0x1005: ('FlashDevice',
       {0: 'None',
        1: 'Internal',
        4: 'External',
        5: 'Internal + External'}),
    0x1006: ('ExposureCompensation', None),
    0x1007: ('SensorTemperature', None),
    0x1008: ('LensTemperature', None),
    0x100b: ('FocusMode',
       {0: 'Auto',
        1: 'Manual'}),
    0x1017: ('RedBalance', None),
    0x1018: ('BlueBalance', None),
    0x101a: ('SerialNumber', None),
    0x1023: ('FlashExposureComp', None),
    0x1026: ('ExternalFlashBounce',
       {0: 'No',
        1: 'Yes'}),
    0x1027: ('ExternalFlashZoom', None),
    0x1028: ('ExternalFlashMode', None),
    0x1029: ('Contrast 	int16u',
       {0: 'High',
        1: 'Normal',
        2: 'Low'}),
    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', None),
    0x1037: ('PreviewImageLength', None),
    0x1039: ('CCDScanMode',
       {0: 'Interlaced',
        1: 'Progressive'}),
    0x103a: ('NoiseReduction',
       {0: 'Off',
        1: 'On'}),
    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', None),
    0x2020: ('CameraSettings', None),
    0x2030: ('RawDevelopment', None),
    0x2040: ('ImageProcessing', None),
    0x2050: ('FocusInfo', None),
    0x3000: ('RawInfo ', None),
    }

# 0x2020 CameraSettings
MAKERNOTE_OLYMPUS_TAG_0x2020={
    0x0100: ('PreviewImageValid',
             {0: 'No',
              1: 'Yes'}),
    0x0101: ('PreviewImageStart', None),
    0x0102: ('PreviewImageLength', None),
    0x0200: ('ExposureMode',
             {1: 'Manual',
              2: 'Program',
              3: 'Aperture-priority AE',
              4: 'Shutter speed priority AE',
              5: 'Program-shift'}),
    0x0201: ('AELock',
             {0: 'Off',
              1: 'On'}),
    0x0202: ('MeteringMode',
             {2: 'Center Weighted',
              3: 'Spot',
              5: 'ESP',
              261: 'Pattern+AF',
              515: 'Spot+Highlight control',
              1027: 'Spot+Shadow control'}),
    0x0300: ('MacroMode',
             {0: 'Off',
              1: 'On'}),
    0x0301: ('FocusMode',
             {0: 'Single AF',
              1: 'Sequential shooting AF',
              2: 'Continuous AF',
              3: 'Multi AF',
              10: 'MF'}),
    0x0302: ('FocusProcess',
             {0: 'AF Not Used',
              1: 'AF Used'}),
    0x0303: ('AFSearch',
             {0: 'Not Ready',
              1: 'Ready'}),
    0x0304: ('AFAreas', None),
    0x0401: ('FlashExposureCompensation', None),
    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: ('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', 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',
              2: 'Pro Photo RGB'}),
    0x0509: ('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: ('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: ('DistortionCorrection',
             {0: 'Off',
              1: 'On'}),
    0x050c: ('ShadingCompensation',
             {0: 'Off',
              1: 'On'}),
    0x050d: ('CompressionFactor', None),
    0x050f: ('Gradation',
             {'-1 -1 1': 'Low Key',
              '0 -1 1': 'Normal',
              '1 -1 1': 'High Key'}),
    0x0520: ('PictureMode',
             {1: 'Vivid',
              2: 'Natural',
              3: 'Muted',
              256: 'Monotone',
              512: 'Sepia'}),
    0x0521: ('PictureModeSaturation', None),
    0x0522: ('PictureModeHue?', None),
    0x0523: ('PictureModeContrast', None),
    0x0524: ('PictureModeSharpness', None),
    0x0525: ('PictureModeBWFilter',
             {0: 'n/a',
              1: 'Neutral',
              2: 'Yellow',
              3: 'Orange',
              4: 'Red',
              5: 'Green'}),
    0x0526: ('PictureModeTone',
             {0: 'n/a',
              1: 'Neutral',
              2: 'Sepia',
              3: 'Blue',
              4: 'Purple',
              5: 'Green'}),
    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', None),
    }


MAKERNOTE_CASIO_TAGS={
    0x0001: ('RecordingMode',
             {1: 'Single Shutter',
              2: 'Panorama',
              3: 'Night Scene',
              4: 'Portrait',
              5: 'Landscape'}),
    0x0002: ('Quality',
             {1: 'Economy',
              2: 'Normal',
              3: 'Fine'}),
    0x0003: ('FocusingMode',
             {2: 'Macro',
              3: 'Auto Focus',
              4: 'Manual Focus',
              5: 'Infinity'}),
    0x0004: ('FlashMode',
             {1: 'Auto',
              2: 'On',
              3: 'Off',
              4: 'Red Eye Reduction'}),
    0x0005: ('FlashIntensity',
             {11: 'Weak',
              13: 'Normal',
              15: 'Strong'}),
    0x0006: ('Object Distance', None),
    0x0007: ('WhiteBalance',
             {1: 'Auto',
              2: 'Tungsten',
              3: 'Daylight',
              4: 'Fluorescent',
              5: 'Shade',
              129: 'Manual'}),
    0x000B: ('Sharpness',
             {0: 'Normal',
              1: 'Soft',
              2: 'Hard'}),
    0x000C: ('Contrast',
             {0: 'Normal',
              1: 'Low',
              2: 'High'}),
    0x000D: ('Saturation',
             {0: 'Normal',
              1: 'Low',
              2: 'High'}),
    0x0014: ('CCDSpeed',
             {64: 'Normal',
              80: 'Normal',
              100: 'High',
              125: '+1.0',
              244: '+3.0',
              250: '+2.0'}),
    }

MAKERNOTE_FUJIFILM_TAGS={
    0x0000: ('NoteVersion', None),
    0x1000: ('Quality', None),
    0x1001: ('Sharpness',
             {1: 'Soft',
              2: 'Soft',
              3: 'Normal',
              4: 'Hard',
              5: 'Hard'}),
    0x1002: ('WhiteBalance',
             {0: 'Auto',
              256: 'Daylight',
              512: 'Cloudy',
              768: 'DaylightColor-Fluorescent',
              769: 'DaywhiteColor-Fluorescent',
              770: 'White-Fluorescent',
              1024: 'Incandescent',
              3840: 'Custom'}),
    0x1003: ('Color',
             {0: 'Normal',
              256: 'High',
              512: 'Low'}),
    0x1004: ('Tone',
             {0: 'Normal',
              256: 'High',
              512: 'Low'}),
    0x1010: ('FlashMode',
             {0: 'Auto',
              1: 'On',
              2: 'Off',
              3: 'Red Eye Reduction'}),
    0x1011: ('FlashStrength', None),
    0x1020: ('Macro',
             {0: 'Off',
              1: 'On'}),
    0x1021: ('FocusMode',
             {0: 'Auto',
              1: 'Manual'}),
    0x1030: ('SlowSync',
             {0: 'Off',
              1: 'On'}),
    0x1031: ('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: ('MotorOrBracket',
             {0: 'Off',
              1: 'On'}),
    0x1300: ('BlurWarning',
             {0: 'Off',
              1: 'On'}),
    0x1301: ('FocusWarning',
             {0: 'Off',
              1: 'On'}),
    0x1302: ('AEWarning',
             {0: 'Off',
              1: 'On'}),
    }

MAKERNOTE_CANON_TAGS = {
    0x0006: ('ImageType', None),
    0x0007: ('FirmwareVersion', None),
    0x0008: ('ImageNumber', None),
    0x0009: ('OwnerName', None),
    }

# this is in element offset, name, optional value dictionary format
MAKERNOTE_CANON_TAG_0x001 = {
    1: ('Macromode',
        {1: 'Macro',
         2: 'Normal'}),
    2: ('SelfTimer', None),
    3: ('Quality',
        {2: 'Normal',
         3: 'Fine',
         5: 'Superfine'}),
    4: ('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: ('ContinuousDriveMode',
        {0: 'Single Or Timer',
         1: 'Continuous'}),
    7: ('FocusMode',
        {0: 'One-Shot',
         1: 'AI Servo',
         2: 'AI Focus',
         3: 'MF',
         4: 'Single',
         5: 'Continuous',
         6: 'MF'}),
    10: ('ImageSize',
         {0: 'Large',
          1: 'Medium',
          2: 'Small'}),
    11: ('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: ('DigitalZoom',
         {0: 'None',
          1: '2x',
          2: '4x'}),
    13: ('Contrast',
         {0xFFFF: 'Low',
          0: 'Normal',
          1: 'High'}),
    14: ('Saturation',
         {0xFFFF: 'Low',
          0: 'Normal',
          1: 'High'}),
    15: ('Sharpness',
         {0xFFFF: 'Low',
          0: 'Normal',
          1: 'High'}),
    16: ('ISO',
         {0: 'See ISOSpeedRatings Tag',
          15: 'Auto',
          16: '50',
          17: '100',
          18: '200',
          19: '400'}),
    17: ('MeteringMode',
         {3: 'Evaluative',
          4: 'Partial',
          5: 'Center-weighted'}),
    18: ('FocusType',
         {0: 'Manual',
          1: 'Auto',
          3: 'Close-Up (Macro)',
          8: 'Locked (Pan Mode)'}),
    19: ('AFPointSelected',
         {0x3000: 'None (MF)',
          0x3001: 'Auto-Selected',
          0x3002: 'Right',
          0x3003: 'Center',
          0x3004: 'Left'}),
    20: ('ExposureMode',
         {0: 'Easy Shooting',
          1: 'Program',
          2: 'Tv-priority',
          3: 'Av-priority',
          4: 'Manual',
          5: 'A-DEP'}),
    23: ('LongFocalLengthOfLensInFocalUnits', None),
    24: ('ShortFocalLengthOfLensInFocalUnits', None),
    25: ('FocalUnitsPerMM', None),
    28: ('FlashActivity',
         {0: 'Did Not Fire',
          1: 'Fired'}),
    29: ('FlashDetails',
         {14: 'External E-TTL',
          13: 'Internal Flash',
          11: 'FP Sync Used',
          7: '2nd("Rear")-Curtain Sync Used',
          4: 'FP Sync Enabled'}),
    32: ('FocusMode',
         {0: 'Single',
          1: 'Continuous'}),
    }

MAKERNOTE_CANON_TAG_0x004 = {
    7: ('WhiteBalance',
        {0: 'Auto',
         1: 'Sunny',
         2: 'Cloudy',
         3: 'Tungsten',
         4: 'Fluorescent',
         5: 'Flash',
         6: 'Custom'}),
    9: ('SequenceNumber', None),
    14: ('AFPointUsed', None),
    15: ('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: ('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
    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