refactor exif_data to use a Tag class for each tag, instead of a magic tuple new-exif
authorTero Marttila <terom@fixme.fi>
Sat, 13 Jun 2009 20:59:53 +0300
branchnew-exif
changeset 105 effae6f38749
parent 104 6afe59e5ffae
child 106 a4f605bd122c
refactor exif_data to use a Tag class for each tag, instead of a magic tuple
degal/exif.py
degal/exif_data.py
--- a/degal/exif.py	Sat Jun 13 20:31:51 2009 +0300
+++ b/degal/exif.py	Sat Jun 13 20:59:53 2009 +0300
@@ -4,6 +4,8 @@
 
 import struct, mmap, os
 
+from utils import lazy_load, lazy_load_iter
+
 def read_struct (file, fmt) :
     """
         Utility function to read data from the a file using struct
@@ -107,11 +109,12 @@
         Represents a single Tag in an IFD
     """
 
-    def __init__ (self, offset, tag, type, count, data_raw) :
+    def __init__ (self, ifd, offset, tag, type, count, data_raw) :
         """
             Build a Tag with the given binary items from the IFD entry
         """
         
+        self.ifd = ifd
         self.offset = offset
         self.tag = tag
         self.type = type
@@ -126,13 +129,8 @@
             self.type_format, self.type_name, self.type_func = self.type_data
     
         # lookup the tag data for this tag
-        self.tag_data = exif_data.EXIF_TAGS.get(tag)
+        self.tag_data = self.ifd.tag_dict.get(tag)
         
-        # unpack it
-        if self.tag_data :
-            # the EXIF tag name
-            self.tag_name, self.tag_value_spec = self.tag_data
-            
     @property
     def name (self) :
         """
@@ -140,7 +138,7 @@
         """
 
         if self.tag_data :
-            return self.tag_name
+            return self.tag_data.name
 
         else :
             return None
@@ -166,14 +164,12 @@
         """
 
         if self.tag_data :
-            spec = self.tag_value_spec
+            # map it
+            return self.tag_data.map_values(values)
 
         else :
-            # fallback to default
-            spec = None
-
-        # map it
-        return exif_data.map_values(spec, values)
+            # default value-mapping
+            return ", ".join(str(value) for value in values)
 
 # size of an IFD entry in bytes
 IFD_ENTRY_SIZE = 12
@@ -183,7 +179,7 @@
         Represents an IFD (Image file directory) region in EXIF data.
     """
 
-    def __init__ (self, buffer, **buffer_opts) :
+    def __init__ (self, buffer, tag_dict, **buffer_opts) :
         """
             Access the IFD data from the given bufferable object with given buffer opts.
 
@@ -192,6 +188,9 @@
 
         # init
         super(IFD, self).__init__(buffer, **buffer_opts)
+
+        # store
+        self.tag_dict = tag_dict
         
         # read header
         self.count = self.pread_item(0, 'H')
@@ -199,7 +198,8 @@
         # read next-offset
         self.next_offset = self.pread_item(0x02 + self.count * IFD_ENTRY_SIZE, 'I')
     
-    def iter_tags (self) :
+    @lazy_load_iter
+    def tags (self) :
         """
             Iterate over all the Tag objects in this IFD
         """
@@ -210,7 +210,7 @@
             tag, type, count, data_raw = self.pread_struct(offset, 'HHI4s')
             
             # yield the new Tag
-            yield Tag(offset, tag, type, count, data_raw)
+            yield Tag(self, offset, tag, type, count, data_raw)
 
 class EXIF (Buffer) :
     """
@@ -230,7 +230,8 @@
         # store
         self.buffer = buffer
     
-    def iter_ifds (self) :
+    @lazy_load_iter
+    def ifds (self) :
         """
             Iterate over the primary IFDs in this EXIF.
         """
@@ -240,7 +241,7 @@
 
         while offset :
             # create and read the IFD, operating on the right sub-buffer
-            ifd = IFD(self.buf, offset=offset)
+            ifd = IFD(self.buf, exif_data.EXIF_TAGS, offset=offset)
 
             # yield it
             yield ifd
@@ -252,8 +253,6 @@
         """
             Iterate over all of the IFDs contained within this EXIF, or within other IFDs.
         """
-
-    __iter__ = iter_ifds
     
     def tag_data_info (self, tag) :
         """
@@ -500,7 +499,7 @@
 
     print "EXIF offset=%#08x, size=%d:" % (exif.offset, exif.size)
 
-    for i, ifd in enumerate(exif.iter_ifds()) :
+    for i, ifd in enumerate(exif.ifds) :
         print "\tIFD:%d offset=%#04x(%#08x), count=%d, next=%d:" % (
             i, 
             ifd.offset, ifd.offset + exif.offset,
@@ -508,7 +507,7 @@
             ifd.next_offset
         )
         
-        for i, tag in enumerate(ifd.iter_tags()) :
+        for i, tag in enumerate(ifd.tags) :
             data_info = exif.tag_data_info(tag)
 
             if data_info :
--- a/degal/exif_data.py	Sat Jun 13 20:31:51 2009 +0300
+++ b/degal/exif_data.py	Sat Jun 13 20:59:53 2009 +0300
@@ -93,28 +93,63 @@
     0x000A: ('ll',  'Signed Ratio', filter_ratio    ),
 }
 
-def map_values (spec, values) :
+# magic value to indicate sub-IFDs
+SUB_IFD_MAGIC = object()
+
+
+class Tag (object) :
     """
-        Map the given tag value to a printable string using the given value spec.
+        Represents an Exif Tag
     """
 
-    # XXX: ensure that this always returns a str/unicode
-    
-    if spec is None :
-        # nothing to map
+    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)
 
-    elif callable(spec):
-        # call mapping function
-        return spec(values)
+class TagDict (Tag) :
+    """
+        A tag with a dict mapping values to names
+    """
 
-    elif isinstance(spec, dict) :
-        # lookup value, default to repr
-        return ", ".join(spec.get(value, repr(value)) for value in values)
+    def __init__ (self, name, values_dict) :
+        super(TagDict, self).__init__(name)
 
-    else :
-        # unknown kind of spec
-        raise ValueError(spec)
+        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)
 
 USER_COMMENT_CHARSETS = {
     'ASCII':    ('ascii',   'replace'   ),
@@ -148,18 +183,19 @@
     return [comment_raw.decode(encoding, replace)]
 
 # Mappings of Exif tag codes to name and decoding information.
-# { tag : (name, value_dict/value_func/None) }
+# { 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.
 EXIF_TAGS = {
-    0x0100: ('ImageWidth', None),
-    0x0101: ('ImageLength', None),
-    0x0102: ('BitsPerSample', None),
-    0x0103: ('Compression',
+    0x0100: Tag('ImageWidth'),
+    0x0101: Tag('ImageLength'),
+    0x0102: Tag('BitsPerSample'),
+    0x0103: TagDict('Compression',
              {1: 'Uncompressed',
               2: 'CCITT 1D',
               3: 'T4/Group 3 Fax',
@@ -190,15 +226,15 @@
               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',
+    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',
@@ -207,46 +243,46 @@
               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',
+    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: ('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',
+    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: ('ReferenceBlackWhite', None),
-    
-    0x4746: ('Rating', None),
+    0x0214: Tag('ReferenceBlackWhite'),
     
-    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',
+    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: Tag('ExifOffset'),
+    0x8773: Tag('InterColorProfile'),
+    0x8822: TagDict('ExposureProgram',
              {0: 'Unidentified',
               1: 'Manual',
               2: 'Program Normal',
@@ -256,14 +292,14 @@
               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',
+    0x8824: Tag('SpectralSensitivity'),
+    0x8825: Tag('GPSInfo'),
+    0x8827: Tag('ISOSpeedRatings'),
+    0x8828: Tag('OECF'),
+    0x9000: Tag('ExifVersion'),
+    0x9003: Tag('DateTimeOriginal'),
+    0x9004: Tag('DateTimeDigitized'),
+    0x9101: TagDict('ComponentsConfiguration',
              {0: '',
               1: 'Y',
               2: 'Cb',
@@ -271,21 +307,21 @@
               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',
+    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: ('LightSource',
+    0x9208: TagDict('LightSource',
              {0: 'Unknown',
               1: 'Daylight',
               2: 'Fluorescent',
@@ -304,7 +340,7 @@
               21: 'D65',
               22: 'D75',
               255: 'Other'}),
-    0x9209: ('Flash',
+    0x9209: TagDict('Flash',
              {0: 'No',
               1: 'Fired',
               5: 'Fired (?)', # no return sensed
@@ -318,37 +354,37 @@
               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),
+    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: ('XPTitle', None),
-    0x9C9C: ('XPComment', None),
-    0x9C9D: ('XPAuthor', None), #(ignored by Windows Explorer if Artist exists)
-    0x9C9E: ('XPKeywords', None),
-    0x9C9F: ('XPSubject', None),
+    0x9C9B: Tag('XPTitle'),
+    0x9C9C: Tag('XPComment'),
+    0x9C9D: Tag('XPAuthor'), #(ignored by Windows Explorer if Artist exists)
+    0x9C9E: Tag('XPKeywords'),
+    0x9C9F: Tag('XPSubject'),
 
-    0xA000: ('FlashPixVersion', None),
-    0xA001: ('ColorSpace',
+    0xA000: Tag('FlashPixVersion'),
+    0xA001: TagDict('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
+    0xA002: Tag('ExifImageWidth'),
+    0xA003: Tag('ExifImageLength'),
+    0xA005: Tag('InteroperabilityOffset'),
+    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',
@@ -356,94 +392,94 @@
               5: 'Color sequential area',
               7: 'Trilinear',
               8: 'Color sequential linear'}),             
-    0xA300: ('FileSource',
+    0xA300: TagDict('FileSource',
              {1: 'Film Scanner',
               2: 'Reflection Print Scanner',
               3: 'Digital Camera'}),
-    0xA301: ('SceneType',
+    0xA301: TagDict('SceneType',
              {1: 'Directly Photographed'}),
-    0xA302: ('CVAPattern', None),
-    0xA401: ('CustomRendered',
+    0xA302: Tag('CVAPattern'),
+    0xA401: TagDict('CustomRendered',
              {0: 'Normal',
               1: 'Custom'}),
-    0xA402: ('ExposureMode',
+    0xA402: TagDict('ExposureMode',
              {0: 'Auto Exposure',
               1: 'Manual Exposure',
               2: 'Auto Bracket'}),
-    0xA403: ('WhiteBalance',
+    0xA403: TagDict('WhiteBalance',
              {0: 'Auto',
               1: 'Manual'}),
-    0xA404: ('DigitalZoomRatio', None),
+    0xA404: Tag('DigitalZoomRatio'),
     0xA405: ('FocalLengthIn35mmFilm', None),
-    0xA406: ('SceneCaptureType',
+    0xA406: TagDict('SceneCaptureType',
              {0: 'Standard',
               1: 'Landscape',
               2: 'Portrait',
               3: 'Night)'}),
-    0xA407: ('GainControl',
+    0xA407: TagDict('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',
+    0xA408: TagDict('Contrast',
              {0: 'Normal',
               1: 'Soft',
               2: 'Hard'}),
-    0xA40A: ('Sharpness',
+    0xA409: TagDict('Saturation',
              {0: 'Normal',
               1: 'Soft',
               2: 'Hard'}),
-    0xA40B: ('DeviceSettingDescription', None),
-    0xA40C: ('SubjectDistanceRange', None),
-    0xA500: ('Gamma', None),
-    0xC4A5: ('PrintIM', None),
+    0xA40A: TagDict('Sharpness',
+             {0: 'Normal',
+              1: 'Soft',
+              2: 'Hard'}),
+    0xA40B: Tag('DeviceSettingDescription'),
+    0xA40C: Tag('SubjectDistanceRange'),
+    0xA500: Tag('Gamma'),
+    0xC4A5: Tag('PrintIM'),
     0xEA1C:	('Padding', None),
     }
 
 # interoperability tags
 INTR_TAGS = {
-    0x0001: ('InteroperabilityIndex', None),
-    0x0002: ('InteroperabilityVersion', None),
-    0x1000: ('RelatedImageFileFormat', None),
-    0x1001: ('RelatedImageWidth', None),
-    0x1002: ('RelatedImageLength', None),
+    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: ('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),
+    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'),
     }
 
 # http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp
@@ -513,58 +549,58 @@
 
 # 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),
+    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: ('ExposureDifference', nikon_ev_bias),
-    0x000F: ('ISOSelection', None),
-    0x0011: ('NikonPreview', None),
-    0x0012: ('FlashCompensation', nikon_ev_bias),
-    0x0013: ('ISOSpeedRequested', None),
-    0x0016: ('PhotoCornerCoordinates', None),
+    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: ('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',
+    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: ('AFFocusPosition',
+    0x0088: TagDict('AFFocusPosition',
              {0x0000: 'Center',
               0x0100: 'Top',
               0x0200: 'Bottom',
               0x0300: 'Left',
               0x0400: 'Right'}),
-    0x0089: ('BracketingMode',
+    0x0089: TagDict('BracketingMode',
              {0x00: 'Single frame, no bracketing',
               0x01: 'Continuous, no bracketing',
               0x02: 'Timer, no bracketing',
@@ -574,53 +610,53 @@
               0x40: 'Single frame, white balance bracketing',
               0x41: 'Continuous, white balance bracketing',
               0x42: 'Timer, white balance bracketing'}),
-    0x008A: ('AutoBracketRelease', None),
-    0x008B: ('LensFStops', None),
+    0x008A: Tag('AutoBracketRelease'),
+    0x008B: Tag('LensFStops'),
     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),
+    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: ('Compression', None),
-    0x0094: ('Saturation',
+    0x0093: Tag('Compression'),
+    0x0094: TagDict('Saturation',
              {-3: 'B&W',
               -2: '-2',
               -1: '-1',
               0: '0',
               1: '1',
               2: '2'}),
-    0x0095: ('NoiseReduction', None),
+    0x0095: Tag('NoiseReduction'),
     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),
+    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: ('ImageCount', None),
-    0x00A6: ('DeletedImageCount', None),
-    0x00A7: ('TotalShutterReleases', None),
+    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: ('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),
+    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: ('DigitalICE', None),
-    0x0103: ('PreviewCompression',
+    0x0100: Tag('DigitalICE'),
+    0x0103: TagDict('PreviewCompression',
              {1: 'Uncompressed',
               2: 'CCITT 1D',
               3: 'T4/Group 3 Fax',
@@ -651,37 +687,37 @@
               34713: 'Nikon NEF Compressed',
               65000: 'Kodak DCR Compressed',
               65535: 'Pentax PEF Compressed',}),
-    0x0201: ('PreviewImageStart', None),
-    0x0202: ('PreviewImageLength', None),
-    0x0213: ('PreviewYCbCrPositioning',
+    0x0201: Tag('PreviewImageStart'),
+    0x0202: Tag('PreviewImageLength'),
+    0x0213: TagDict('PreviewYCbCrPositioning',
              {1: 'Centered',
               2: 'Co-sited'}), 
-    0x0010: ('DataDump', None),
+    0x0010: Tag('DataDump'),
     }
 
 MAKERNOTE_NIKON_OLDER_TAGS = {
-    0x0003: ('Quality',
+    0x0003: TagDict('Quality',
              {1: 'VGA Basic',
               2: 'VGA Normal',
               3: 'VGA Fine',
               4: 'SXGA Basic',
               5: 'SXGA Normal',
               6: 'SXGA Fine'}),
-    0x0004: ('ColorMode',
+    0x0004: TagDict('ColorMode',
              {1: 'Color',
               2: 'Monochrome'}),
-    0x0005: ('ImageAdjustment',
+    0x0005: TagDict('ImageAdjustment',
              {0: 'Normal',
               1: 'Bright+',
               2: 'Bright-',
               3: 'Contrast+',
               4: 'Contrast-'}),
-    0x0006: ('CCDSpeed',
+    0x0006: TagDict('CCDSpeed',
              {0: 'ISO 80',
               2: 'ISO 160',
               4: 'ISO 320',
               5: 'ISO 100'}),
-    0x0007: ('WhiteBalance',
+    0x0007: TagDict('WhiteBalance',
              {0: 'Auto',
               1: 'Preset',
               2: 'Daylight',
@@ -719,131 +755,131 @@
 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',
+    0x0100: Tag('JPEGThumbnail'),
+    0x0200: TagFunc('SpecialMode', olympus_special_mode),
+    0x0201: TagDict('JPEGQual',
              {1: 'SQ',
               2: 'HQ',
               3: 'SHQ'}),
-    0x0202: ('Macro',
+    0x0202: TagDict('Macro',
              {0: 'Normal',
              1: 'Macro',
              2: 'SuperMacro'}),
-    0x0203: ('BWMode',
+    0x0203: TagDict('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',
+    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: ('FlashDevice',
+    0x1005: TagDict('FlashDevice',
        {0: 'None',
         1: 'Internal',
         4: 'External',
         5: 'Internal + External'}),
-    0x1006: ('ExposureCompensation', None),
-    0x1007: ('SensorTemperature', None),
-    0x1008: ('LensTemperature', None),
-    0x100b: ('FocusMode',
+    0x1006: Tag('ExposureCompensation'),
+    0x1007: Tag('SensorTemperature'),
+    0x1008: Tag('LensTemperature'),
+    0x100b: TagDict('FocusMode',
        {0: 'Auto',
         1: 'Manual'}),
-    0x1017: ('RedBalance', None),
-    0x1018: ('BlueBalance', None),
-    0x101a: ('SerialNumber', None),
-    0x1023: ('FlashExposureComp', None),
-    0x1026: ('ExternalFlashBounce',
+    0x1017: Tag('RedBalance'),
+    0x1018: Tag('BlueBalance'),
+    0x101a: Tag('SerialNumber'),
+    0x1023: Tag('FlashExposureComp'),
+    0x1026: TagDict('ExternalFlashBounce',
        {0: 'No',
         1: 'Yes'}),
-    0x1027: ('ExternalFlashZoom', None),
-    0x1028: ('ExternalFlashMode', None),
+    0x1027: Tag('ExternalFlashZoom'),
+    0x1028: Tag('ExternalFlashMode'),
     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',
+    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: ('PreviewImageStart', None),
-    0x1037: ('PreviewImageLength', None),
-    0x1039: ('CCDScanMode',
+    0x1036: Tag('PreviewImageStart'),
+    0x1037: Tag('PreviewImageLength'),
+    0x1039: TagDict('CCDScanMode',
        {0: 'Interlaced',
         1: 'Progressive'}),
-    0x103a: ('NoiseReduction',
+    0x103a: TagDict('NoiseReduction',
        {0: 'Off',
         1: 'On'}),
-    0x103b: ('InfinityLensStep', None),
-    0x103c: ('NearLensStep', None),
+    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: ('Equipment', None),
-    0x2020: ('CameraSettings', None),
-    0x2030: ('RawDevelopment', None),
-    0x2040: ('ImageProcessing', None),
-    0x2050: ('FocusInfo', None),
-    0x3000: ('RawInfo ', None),
+    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: ('PreviewImageValid',
+    0x0100: TagDict('PreviewImageValid',
              {0: 'No',
               1: 'Yes'}),
-    0x0101: ('PreviewImageStart', None),
-    0x0102: ('PreviewImageLength', None),
-    0x0200: ('ExposureMode',
+    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: ('AELock',
+    0x0201: TagDict('AELock',
              {0: 'Off',
               1: 'On'}),
-    0x0202: ('MeteringMode',
+    0x0202: TagDict('MeteringMode',
              {2: 'Center Weighted',
               3: 'Spot',
               5: 'ESP',
               261: 'Pattern+AF',
               515: 'Spot+Highlight control',
               1027: 'Spot+Shadow control'}),
-    0x0300: ('MacroMode',
+    0x0300: TagDict('MacroMode',
              {0: 'Off',
               1: 'On'}),
-    0x0301: ('FocusMode',
+    0x0301: TagDict('FocusMode',
              {0: 'Single AF',
               1: 'Sequential shooting AF',
               2: 'Continuous AF',
               3: 'Multi AF',
               10: 'MF'}),
-    0x0302: ('FocusProcess',
+    0x0302: TagDict('FocusProcess',
              {0: 'AF Not Used',
               1: 'AF Used'}),
-    0x0303: ('AFSearch',
+    0x0303: TagDict('AFSearch',
              {0: 'Not Ready',
               1: 'Ready'}),
-    0x0304: ('AFAreas', None),
-    0x0401: ('FlashExposureCompensation', None),
+    0x0304: Tag('AFAreas'),
+    0x0401: Tag('FlashExposureCompensation'),
     0x0500: ('WhiteBalance2',
              {0: 'Auto',
              16: '7500K (Fine Weather with Shade)',
@@ -862,22 +898,22 @@
              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',
+    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: ('ContrastSetting', None), # (3 numbers: 1. Contrast, 2. Min, 3. Max)
-    0x0506: ('SharpnessSetting', None), # (3 numbers: 1. Sharpness, 2. Min, 3. Max)
-    0x0507: ('ColorSpace',
+    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: ('SceneMode',
+    0x0509: TagDict('SceneMode',
              {0: 'Standard',
               6: 'Auto',
               7: 'Sport',
@@ -903,105 +939,105 @@
              45: 'Low Key',
              46: 'Children',
              48: 'Nature Macro'}),
-    0x050a: ('NoiseReduction',
+    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: ('DistortionCorrection',
+    0x050b: TagDict('DistortionCorrection',
              {0: 'Off',
               1: 'On'}),
-    0x050c: ('ShadingCompensation',
+    0x050c: TagDict('ShadingCompensation',
              {0: 'Off',
               1: 'On'}),
-    0x050d: ('CompressionFactor', None),
-    0x050f: ('Gradation',
+    0x050d: Tag('CompressionFactor'),
+    0x050f: TagDict('Gradation',
              {'-1 -1 1': 'Low Key',
               '0 -1 1': 'Normal',
               '1 -1 1': 'High Key'}),
-    0x0520: ('PictureMode',
+    0x0520: TagDict('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',
+    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: ('PictureModeTone',
+    0x0526: TagDict('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)
+    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: ('ManometerReading', None),
+    0x0901: Tag('ManometerReading'),
     }
 
 
 MAKERNOTE_CASIO_TAGS={
-    0x0001: ('RecordingMode',
+    0x0001: TagDict('RecordingMode',
              {1: 'Single Shutter',
               2: 'Panorama',
               3: 'Night Scene',
               4: 'Portrait',
               5: 'Landscape'}),
-    0x0002: ('Quality',
+    0x0002: TagDict('Quality',
              {1: 'Economy',
               2: 'Normal',
               3: 'Fine'}),
-    0x0003: ('FocusingMode',
+    0x0003: TagDict('FocusingMode',
              {2: 'Macro',
               3: 'Auto Focus',
               4: 'Manual Focus',
               5: 'Infinity'}),
-    0x0004: ('FlashMode',
+    0x0004: TagDict('FlashMode',
              {1: 'Auto',
               2: 'On',
               3: 'Off',
               4: 'Red Eye Reduction'}),
-    0x0005: ('FlashIntensity',
+    0x0005: TagDict('FlashIntensity',
              {11: 'Weak',
               13: 'Normal',
               15: 'Strong'}),
-    0x0006: ('Object Distance', None),
-    0x0007: ('WhiteBalance',
+    0x0006: Tag('Object Distance'),
+    0x0007: TagDict('WhiteBalance',
              {1: 'Auto',
               2: 'Tungsten',
               3: 'Daylight',
               4: 'Fluorescent',
               5: 'Shade',
               129: 'Manual'}),
-    0x000B: ('Sharpness',
+    0x000B: TagDict('Sharpness',
              {0: 'Normal',
               1: 'Soft',
               2: 'Hard'}),
-    0x000C: ('Contrast',
+    0x000C: TagDict('Contrast',
              {0: 'Normal',
               1: 'Low',
               2: 'High'}),
-    0x000D: ('Saturation',
+    0x000D: TagDict('Saturation',
              {0: 'Normal',
               1: 'Low',
               2: 'High'}),
-    0x0014: ('CCDSpeed',
+    0x0014: TagDict('CCDSpeed',
              {64: 'Normal',
               80: 'Normal',
               100: 'High',
@@ -1011,15 +1047,15 @@
     }
 
 MAKERNOTE_FUJIFILM_TAGS={
-    0x0000: ('NoteVersion', None),
-    0x1000: ('Quality', None),
-    0x1001: ('Sharpness',
+    0x0000: Tag('NoteVersion'),
+    0x1000: Tag('Quality'),
+    0x1001: TagDict('Sharpness',
              {1: 'Soft',
               2: 'Soft',
               3: 'Normal',
               4: 'Hard',
               5: 'Hard'}),
-    0x1002: ('WhiteBalance',
+    0x1002: TagDict('WhiteBalance',
              {0: 'Auto',
               256: 'Daylight',
               512: 'Cloudy',
@@ -1028,30 +1064,30 @@
               770: 'White-Fluorescent',
               1024: 'Incandescent',
               3840: 'Custom'}),
-    0x1003: ('Color',
+    0x1003: TagDict('Color',
              {0: 'Normal',
               256: 'High',
               512: 'Low'}),
-    0x1004: ('Tone',
+    0x1004: TagDict('Tone',
              {0: 'Normal',
               256: 'High',
               512: 'Low'}),
-    0x1010: ('FlashMode',
+    0x1010: TagDict('FlashMode',
              {0: 'Auto',
               1: 'On',
               2: 'Off',
               3: 'Red Eye Reduction'}),
-    0x1011: ('FlashStrength', None),
-    0x1020: ('Macro',
+    0x1011: Tag('FlashStrength'),
+    0x1020: TagDict('Macro',
              {0: 'Off',
               1: 'On'}),
-    0x1021: ('FocusMode',
+    0x1021: TagDict('FocusMode',
              {0: 'Auto',
               1: 'Manual'}),
-    0x1030: ('SlowSync',
+    0x1030: TagDict('SlowSync',
              {0: 'Off',
               1: 'On'}),
-    0x1031: ('PictureMode',
+    0x1031: TagDict('PictureMode',
              {0: 'Auto',
               1: 'Portrait',
               2: 'Landscape',
@@ -1061,38 +1097,38 @@
               256: 'Aperture Priority AE',
               512: 'Shutter Priority AE',
               768: 'Manual Exposure'}),
-    0x1100: ('MotorOrBracket',
+    0x1100: TagDict('MotorOrBracket',
              {0: 'Off',
               1: 'On'}),
-    0x1300: ('BlurWarning',
+    0x1300: TagDict('BlurWarning',
              {0: 'Off',
               1: 'On'}),
-    0x1301: ('FocusWarning',
+    0x1301: TagDict('FocusWarning',
              {0: 'Off',
               1: 'On'}),
-    0x1302: ('AEWarning',
+    0x1302: TagDict('AEWarning',
              {0: 'Off',
               1: 'On'}),
     }
 
 MAKERNOTE_CANON_TAGS = {
-    0x0006: ('ImageType', None),
-    0x0007: ('FirmwareVersion', None),
-    0x0008: ('ImageNumber', None),
-    0x0009: ('OwnerName', None),
+    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: ('Macromode',
+    1: TagDict('Macromode',
         {1: 'Macro',
          2: 'Normal'}),
-    2: ('SelfTimer', None),
-    3: ('Quality',
+    2: Tag('SelfTimer'),
+    3: TagDict('Quality',
         {2: 'Normal',
          3: 'Fine',
          5: 'Superfine'}),
-    4: ('FlashMode',
+    4: TagDict('FlashMode',
         {0: 'Flash Not Fired',
          1: 'Auto',
          2: 'On',
@@ -1101,10 +1137,10 @@
          5: 'Auto + Red-Eye Reduction',
          6: 'On + Red-Eye Reduction',
          16: 'external flash'}),
-    5: ('ContinuousDriveMode',
+    5: TagDict('ContinuousDriveMode',
         {0: 'Single Or Timer',
          1: 'Continuous'}),
-    7: ('FocusMode',
+    7: TagDict('FocusMode',
         {0: 'One-Shot',
          1: 'AI Servo',
          2: 'AI Focus',
@@ -1112,11 +1148,11 @@
          4: 'Single',
          5: 'Continuous',
          6: 'MF'}),
-    10: ('ImageSize',
+    10: TagDict('ImageSize',
          {0: 'Large',
           1: 'Medium',
           2: 'Small'}),
-    11: ('EasyShootingMode',
+    11: TagDict('EasyShootingMode',
          {0: 'Full Auto',
           1: 'Manual',
           2: 'Landscape',
@@ -1129,70 +1165,70 @@
           9: 'Sports',
           10: 'Macro/Close-Up',
           11: 'Pan Focus'}),
-    12: ('DigitalZoom',
+    12: TagDict('DigitalZoom',
          {0: 'None',
           1: '2x',
           2: '4x'}),
-    13: ('Contrast',
+    13: TagDict('Contrast',
          {0xFFFF: 'Low',
           0: 'Normal',
           1: 'High'}),
-    14: ('Saturation',
+    14: TagDict('Saturation',
          {0xFFFF: 'Low',
           0: 'Normal',
           1: 'High'}),
-    15: ('Sharpness',
+    15: TagDict('Sharpness',
          {0xFFFF: 'Low',
           0: 'Normal',
           1: 'High'}),
-    16: ('ISO',
+    16: TagDict('ISO',
          {0: 'See ISOSpeedRatings Tag',
           15: 'Auto',
           16: '50',
           17: '100',
           18: '200',
           19: '400'}),
-    17: ('MeteringMode',
+    17: TagDict('MeteringMode',
          {3: 'Evaluative',
           4: 'Partial',
           5: 'Center-weighted'}),
-    18: ('FocusType',
+    18: TagDict('FocusType',
          {0: 'Manual',
           1: 'Auto',
           3: 'Close-Up (Macro)',
           8: 'Locked (Pan Mode)'}),
-    19: ('AFPointSelected',
+    19: TagDict('AFPointSelected',
          {0x3000: 'None (MF)',
           0x3001: 'Auto-Selected',
           0x3002: 'Right',
           0x3003: 'Center',
           0x3004: 'Left'}),
-    20: ('ExposureMode',
+    20: TagDict('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',
+    23: Tag('LongFocalLengthOfLensInFocalUnits'),
+    24: Tag('ShortFocalLengthOfLensInFocalUnits'),
+    25: Tag('FocalUnitsPerMM'),
+    28: TagDict('FlashActivity',
          {0: 'Did Not Fire',
           1: 'Fired'}),
-    29: ('FlashDetails',
+    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: ('FocusMode',
+    32: TagDict('FocusMode',
          {0: 'Single',
           1: 'Continuous'}),
     }
 
 MAKERNOTE_CANON_TAG_0x004 = {
-    7: ('WhiteBalance',
+    7: TagDict('WhiteBalance',
         {0: 'Auto',
          1: 'Sunny',
          2: 'Cloudy',
@@ -1200,9 +1236,9 @@
          4: 'Fluorescent',
          5: 'Flash',
          6: 'Custom'}),
-    9: ('SequenceNumber', None),
-    14: ('AFPointUsed', None),
-    15: ('FlashBias',
+    9: Tag('SequenceNumber'),
+    14: Tag('AFPointUsed'),
+    15: TagDict('FlashBias',
          {0xFFC0: '-2 EV',
           0xFFCC: '-1.67 EV',
           0xFFD0: '-1.50 EV',
@@ -1220,7 +1256,7 @@
           0x0030: '1.50 EV',
           0x0034: '1.67 EV',
           0x0040: '2 EV'}),
-    19: ('SubjectDistance', None),
+    19: Tag('SubjectDistance'),
     }
 
 # ratio object that eventually will be able to reduce itself to lowest