relocate the new exif module to lib/, as it's not really part of degal as such new-exif
authorTero Marttila <terom@fixme.fi>
Sun, 14 Jun 2009 16:10:30 +0300
branchnew-exif
changeset 108f74d8cf678ce
parent 107 2e2ef5c99985
relocate the new exif module to lib/, as it's not really part of degal as such
degal/exif.py
degal/exif_data.py
degal/lib/exif.py
degal/lib/exif_data.py
     1.1 --- a/degal/exif.py	Sun Jun 14 16:09:04 2009 +0300
     1.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.3 @@ -1,669 +0,0 @@
     1.4 -"""
     1.5 -    A custom EXIF parsing module, aimed at high performance.
     1.6 -"""
     1.7 -
     1.8 -import struct, mmap, os
     1.9 -
    1.10 -from utils import lazy_load, lazy_load_iter
    1.11 -
    1.12 -def read_struct (file, fmt) :
    1.13 -    """
    1.14 -        Utility function to read data from the a file using struct
    1.15 -    """
    1.16 -    
    1.17 -    # length of data
    1.18 -    fmt_size = struct.calcsize(fmt)
    1.19 -    
    1.20 -    # get data
    1.21 -    file_data = file.read(fmt_size)
    1.22 -    
    1.23 -    # unpack single item, this should raise an error if file_data is too short
    1.24 -    return struct.unpack(fmt, file_data)
    1.25 -
    1.26 -class Buffer (object) :
    1.27 -    """
    1.28 -        Wraps a buffer object (anything that supports the python buffer protocol) for read-only access.
    1.29 -        
    1.30 -        Includes an offset for relative values, and an endianess for reading binary data.
    1.31 -    """
    1.32 -    
    1.33 -    def __init__ (self, obj, offset=None, size=None, struct_prefix='=') :
    1.34 -        """
    1.35 -            Create a new Buffer object with a new underlying buffer, created from the given object, offset and size.
    1.36 -            
    1.37 -            The endiannes is given in the form of a struct-module prefix, which should be one of '<' or '>'.
    1.38 -            Standard size/alignment are assumed.
    1.39 -        """
    1.40 -
    1.41 -        # store
    1.42 -        self.buf = buffer(obj, *(arg for arg in (offset, size) if arg is not None))
    1.43 -        self.offset = offset
    1.44 -        self.size = size
    1.45 -        self.prefix = struct_prefix
    1.46 -    
    1.47 -    def subregion (self, offset, length=None) :
    1.48 -        """
    1.49 -            Create a new sub-Buffer referencing a view of this buffer, at the given offset, and with the given
    1.50 -            length, if any, and the same struct_prefix.
    1.51 -        """
    1.52 -
    1.53 -        return Buffer(self.buf, offset, length, struct_prefix=self.prefix)
    1.54 -    
    1.55 -    def pread (self, offset, length) :
    1.56 -        """
    1.57 -            Read a random-access region of raw data
    1.58 -        """
    1.59 -
    1.60 -        return self.buf[offset:offset + length]
    1.61 -    
    1.62 -    def pread_struct (self, offset, fmt) :
    1.63 -        """
    1.64 -            Read structured data using the given struct format from the given offset.
    1.65 -        """
    1.66 -
    1.67 -        return struct.unpack_from(self.prefix + fmt, self.buf, offset=offset)
    1.68 -
    1.69 -    def pread_item (self, offset, fmt) :
    1.70 -        """
    1.71 -            Read a single item of structured data from the given offset.
    1.72 -        """
    1.73 -
    1.74 -        value, = self.pread_struct(offset, fmt)
    1.75 -
    1.76 -        return value
    1.77 -
    1.78 -    def iter_offsets (self, count, size, offset=0) :
    1.79 -        """
    1.80 -            Yield a series of offsets for `count` items of `size` bytes, beginning at `offset`.
    1.81 -        """
    1.82 -
    1.83 -        return xrange(offset, offset + count * size, size)
    1.84 -    
    1.85 -    def item_size (self, fmt) :
    1.86 -        """
    1.87 -            Returns the size in bytes of the given item format
    1.88 -        """
    1.89 -
    1.90 -        return struct.calcsize(self.prefix + fmt)
    1.91 -
    1.92 -    def unpack_item (self, fmt, data) :
    1.93 -        """
    1.94 -            Unpacks a single item from the given data
    1.95 -        """
    1.96 -
    1.97 -        value, = struct.unpack(self.prefix + fmt, data)
    1.98 -        
    1.99 -        return value
   1.100 -
   1.101 -def mmap_buffer (file, size) :
   1.102 -    """
   1.103 -        Create and return a new read-only mmap'd region
   1.104 -    """
   1.105 -
   1.106 -    return mmap.mmap(file.fileno(), size, access=mmap.ACCESS_READ)
   1.107 -
   1.108 -import exif_data
   1.109 -
   1.110 -class Tag (object) :
   1.111 -    """
   1.112 -        Represents a single Tag in an IFD
   1.113 -    """
   1.114 -
   1.115 -    def __init__ (self, ifd, offset, tag, type, count, data_raw) :
   1.116 -        """
   1.117 -            Build a Tag with the given binary items from the IFD entry
   1.118 -        """
   1.119 -        
   1.120 -        self.ifd = ifd
   1.121 -        self.offset = offset
   1.122 -        self.tag = tag
   1.123 -        self.type = type
   1.124 -        self.count = count
   1.125 -        self.data_raw = data_raw
   1.126 -        
   1.127 -        # lookup the type for this tag
   1.128 -        self.type_data = exif_data.FIELD_TYPES.get(type)
   1.129 -
   1.130 -        # unpack it
   1.131 -        if self.type_data :
   1.132 -            self.type_format, self.type_name, self.type_func = self.type_data
   1.133 -    
   1.134 -        # lookup the tag data for this tag
   1.135 -        self.tag_data = self.ifd.tag_dict.get(tag)
   1.136 -        
   1.137 -    @property
   1.138 -    def name (self) :
   1.139 -        """
   1.140 -            Lookup the name of this tag via its code, returns None if unknown.
   1.141 -        """
   1.142 -
   1.143 -        if self.tag_data :
   1.144 -            return self.tag_data.name
   1.145 -
   1.146 -        else :
   1.147 -            return None
   1.148 -    
   1.149 -    def is_subifd (self) :
   1.150 -        """
   1.151 -            Tests if this Tag is of a IFDTag type
   1.152 -        """
   1.153 -
   1.154 -        return self.tag_data and isinstance(self.tag_data, exif_data.IFDTag)
   1.155 -    
   1.156 -    @lazy_load
   1.157 -    def subifd (self) :
   1.158 -        """
   1.159 -            Load the sub-IFD for this tag
   1.160 -        """
   1.161 -
   1.162 -        # the tag_dict to use
   1.163 -        tag_dict = self.tag_data.ifd_tags or self.ifd.tag_dict
   1.164 -        
   1.165 -        # construct, return
   1.166 -        return self.ifd.exif._load_subifd(self, tag_dict)
   1.167 -
   1.168 -    def process_values (self, raw_values) :
   1.169 -        """
   1.170 -            Process the given raw values unpacked from the file.
   1.171 -        """
   1.172 -
   1.173 -        if self.type_data and self.type_func :
   1.174 -            # use the filter func
   1.175 -            return self.type_func(raw_values)
   1.176 -
   1.177 -        else :
   1.178 -            # nada, just leave them
   1.179 -            return raw_values
   1.180 -
   1.181 -    def readable_value (self, values) :
   1.182 -        """
   1.183 -            Convert the given values for this tag into a human-readable string.
   1.184 -
   1.185 -            Returns the comma-separated values by default.
   1.186 -        """
   1.187 -
   1.188 -        if self.tag_data :
   1.189 -            # map it
   1.190 -            return self.tag_data.map_values(values)
   1.191 -
   1.192 -        else :
   1.193 -            # default value-mapping
   1.194 -            return ", ".join(str(value) for value in values)
   1.195 -
   1.196 -# size of an IFD entry in bytes
   1.197 -IFD_ENTRY_SIZE = 12
   1.198 -
   1.199 -class IFD (Buffer) :
   1.200 -    """
   1.201 -        Represents an IFD (Image file directory) region in EXIF data.
   1.202 -    """
   1.203 -
   1.204 -    def __init__ (self, exif, buffer, tag_dict, **buffer_opts) :
   1.205 -        """
   1.206 -            Access the IFD data from the given bufferable object with given buffer opts.
   1.207 -
   1.208 -            This will read the `count` and `next_offset` values.
   1.209 -        """
   1.210 -
   1.211 -        # init
   1.212 -        super(IFD, self).__init__(buffer, **buffer_opts)
   1.213 -
   1.214 -        # store
   1.215 -        self.exif = exif
   1.216 -        self.tag_dict = tag_dict
   1.217 -        
   1.218 -        # read header
   1.219 -        self.count = self.pread_item(0, 'H')
   1.220 -
   1.221 -        # read next-offset
   1.222 -        self.next_offset = self.pread_item(0x02 + self.count * IFD_ENTRY_SIZE, 'I')
   1.223 -    
   1.224 -    @lazy_load_iter
   1.225 -    def tags (self) :
   1.226 -        """
   1.227 -            Iterate over all the Tag objects in this IFD
   1.228 -        """
   1.229 -        
   1.230 -        # read each tag
   1.231 -        for offset in self.iter_offsets(self.count, IFD_ENTRY_SIZE, 0x02) :
   1.232 -            # read the tag data
   1.233 -            tag, type, count, data_raw = self.pread_struct(offset, 'HHI4s')
   1.234 -            
   1.235 -            # yield the new Tag
   1.236 -            yield Tag(self, self.offset + offset, tag, type, count, data_raw)
   1.237 -
   1.238 -    def get_tags (self, filter=None) :
   1.239 -        """
   1.240 -            Yield a series of tag objects for this IFD and all sub-IFDs.
   1.241 -        """
   1.242 -        
   1.243 -        for tag in self.tags :
   1.244 -            if tag.is_subifd() :
   1.245 -                # recurse
   1.246 -                for subtag in tag.subifd.get_tags(filter=filter) :
   1.247 -                    yield subtag
   1.248 -            
   1.249 -            else :
   1.250 -                # normal tag
   1.251 -                yield tag
   1.252 -
   1.253 -class EXIF (Buffer) :
   1.254 -    """
   1.255 -        Represents the EXIF data embedded in some image file in the form of a Region.
   1.256 -    """
   1.257 -
   1.258 -    def __init__ (self, buffer, **buffer_opts) :
   1.259 -        """
   1.260 -            Access the EXIF data from the given bufferable object with the given buffer options.
   1.261 -        """
   1.262 -
   1.263 -        # init Buffer
   1.264 -        super(EXIF, self).__init__(buffer, **buffer_opts)
   1.265 -
   1.266 -        # store
   1.267 -        self.buffer = buffer
   1.268 -    
   1.269 -    @lazy_load_iter
   1.270 -    def ifds (self) :
   1.271 -        """
   1.272 -            Iterate over the primary IFDs in this EXIF.
   1.273 -        """
   1.274 -
   1.275 -        # starting offset
   1.276 -        offset = self.pread_item(0x04, 'I')
   1.277 -
   1.278 -        while offset :
   1.279 -            # create and read the IFD, operating on the right sub-buffer
   1.280 -            ifd = IFD(self, self.buf, exif_data.EXIF_TAGS, offset=offset)
   1.281 -
   1.282 -            # yield it
   1.283 -            yield ifd
   1.284 -
   1.285 -            # skip to next offset
   1.286 -            offset = ifd.next_offset
   1.287 -    
   1.288 -    def _load_subifd (self, tag, tag_dict) :
   1.289 -        """
   1.290 -            Creates and returns a sub-IFD for the given tag.
   1.291 -        """
   1.292 -
   1.293 -        # locate it
   1.294 -        offset, = self.tag_values_raw(tag)
   1.295 -
   1.296 -        # construct the new IFD
   1.297 -        return IFD(self, self.buf, tag_dict, offset=offset)
   1.298 -
   1.299 -    def tag_data_info (self, tag) :
   1.300 -        """
   1.301 -            Calculate the location, format and size of the given tag's data.
   1.302 -
   1.303 -            Returns a (fmt, offset, size) tuple.
   1.304 -        """
   1.305 -        # unknown tag?
   1.306 -        if not tag.type_data :
   1.307 -            return None
   1.308 -
   1.309 -        # data format
   1.310 -        if len(tag.type_format) == 1 :
   1.311 -            # let struct handle the count
   1.312 -            fmt = "%d%s" % (tag.count, tag.type_format)
   1.313 -
   1.314 -        else :
   1.315 -            # handle the count ourselves
   1.316 -            fmt = tag.type_format * tag.count
   1.317 -
   1.318 -        # size of the data
   1.319 -        size = self.item_size(fmt)
   1.320 -
   1.321 -        # inline or external?
   1.322 -        if size > 0x04 :
   1.323 -            # point at the external data
   1.324 -            offset = self.unpack_item('I', tag.data_raw)
   1.325 -
   1.326 -        else :
   1.327 -            # point at the inline data
   1.328 -            offset = tag.offset + 0x08
   1.329 -        
   1.330 -        return fmt, offset, size
   1.331 -
   1.332 -    def tag_values_raw (self, tag) :
   1.333 -        """
   1.334 -            Get the raw values for the given tag as a tuple.
   1.335 -
   1.336 -            Returns None if the tag could not be recognized.
   1.337 -        """
   1.338 -
   1.339 -        # find the data
   1.340 -        data_info = self.tag_data_info(tag)
   1.341 -
   1.342 -        # not found?
   1.343 -        if not data_info :
   1.344 -            return None
   1.345 -        
   1.346 -        # unpack
   1.347 -        data_fmt, data_offset, data_size = data_info
   1.348 -        
   1.349 -        # read values
   1.350 -        return self.pread_struct(data_offset, data_fmt)
   1.351 -    
   1.352 -    def tag_values (self, tag) :
   1.353 -        """
   1.354 -            Gets the processed values for the given tag as a list.
   1.355 -        """
   1.356 -
   1.357 -        # read + process
   1.358 -        return tag.process_values(self.tag_values_raw(tag))
   1.359 -
   1.360 -    def tag_value (self, tag) :
   1.361 -        """
   1.362 -            Return the human-readable string value for the given tag.
   1.363 -        """
   1.364 -        
   1.365 -        # load the raw values
   1.366 -        values = self.tag_values(tag)
   1.367 -
   1.368 -        # unknown?
   1.369 -        if not values :
   1.370 -            return ""
   1.371 -
   1.372 -        # return as comma-separated formatted string, yes
   1.373 -        return tag.readable_value(values)
   1.374 -    
   1.375 -    def get_main_tags (self, **opts) :
   1.376 -        """
   1.377 -            Get the tags for the main image's IFD as a dict.
   1.378 -        """
   1.379 -
   1.380 -        if not self.ifds :
   1.381 -            # weird case
   1.382 -            raise Exception("No IFD for main image found")
   1.383 -
   1.384 -        # the main IFD is always the first one
   1.385 -        main_ifd = self.ifds[0]
   1.386 -
   1.387 -        # do it
   1.388 -        return dict((tag.name, self.tag_value(tag)) for tag in main_ifd.get_tags(**opts))
   1.389 -
   1.390 -# mapping from two-byte TIFF byte order marker to struct prefix
   1.391 -TIFF_BYTE_ORDER = {
   1.392 -    'II': '<',
   1.393 -    'MM': '>',
   1.394 -}
   1.395 -
   1.396 -# "An arbitrary but carefully chosen number (42) that further identifies the file as a TIFF file"
   1.397 -TIFF_BYTEORDER_MAGIC = 42 
   1.398 -
   1.399 -def tiff_load (file, length=0, **opts) :
   1.400 -    """
   1.401 -        Load the Exif/TIFF data from the given file at its current position with optional length, using exif_load.
   1.402 -    """
   1.403 -
   1.404 -    # all Exif data offsets are relative to the beginning of this TIFF header
   1.405 -    offset = file.tell()
   1.406 -
   1.407 -    # mmap the region for the EXIF data
   1.408 -    buffer = mmap_buffer(file, length)
   1.409 -
   1.410 -    # read byte-order header
   1.411 -    byte_order = file.read(2)
   1.412 -
   1.413 -    # map to struct prefix
   1.414 -    struct_prefix = TIFF_BYTE_ORDER[byte_order]
   1.415 -
   1.416 -    # validate
   1.417 -    check_value, = read_struct(file, struct_prefix + 'H')
   1.418 -
   1.419 -    if check_value != TIFF_BYTEORDER_MAGIC  :
   1.420 -        raise Exception("Invalid byte-order for TIFF: %2c -> %d" % (byte_order, check_value))
   1.421 -
   1.422 -    # build and return the EXIF object with the correct offset/size from the mmap region
   1.423 -    return EXIF(buffer, offset=offset, size=length, **opts)
   1.424 -
   1.425 -# the JPEG markers that don't have any data
   1.426 -JPEG_NOSIZE_MARKERS = (0xD8, 0xD9)
   1.427 -
   1.428 -# the first marker in a JPEG File
   1.429 -JPEG_START_MARKER = 0xD8
   1.430 -
   1.431 -# the JPEG APP1 marker used for EXIF
   1.432 -JPEG_EXIF_MARKER = 0xE1
   1.433 -
   1.434 -# the JPEG APP1 Exif header
   1.435 -JPEG_EXIF_HEADER = "Exif\x00\x00"
   1.436 -
   1.437 -def jpeg_markers (file) :
   1.438 -    """
   1.439 -        Iterate over the JPEG markers in the given file, yielding (type_byte, size) tuples.
   1.440 -
   1.441 -        The size fields will be 0 for markers with no data. The file will be positioned at the beginning of the data
   1.442 -        region, and may be seek'd around if needed.
   1.443 -        
   1.444 -        XXX: find a real implementation of this somewhere?
   1.445 -    """
   1.446 -
   1.447 -    while True :
   1.448 -        # read type
   1.449 -        marker_byte, marker_type = read_struct(file, '!BB')
   1.450 -        
   1.451 -        # validate
   1.452 -        if marker_byte != 0xff :
   1.453 -            raise Exception("Not a JPEG marker: %x%x" % (marker_byte, marker_type))
   1.454 -
   1.455 -        # special cases for no data
   1.456 -        if marker_type in JPEG_NOSIZE_MARKERS :
   1.457 -            size = 0
   1.458 -
   1.459 -        else :
   1.460 -            # read size field
   1.461 -            size, = read_struct(file, '!H')
   1.462 -            
   1.463 -            # validate
   1.464 -            if size < 0x02 :
   1.465 -                raise Exception("Invalid size for marker %x%x: %x" % (marker_byte, marker_type, size))
   1.466 -            
   1.467 -            else :
   1.468 -                # do not count the size field itself
   1.469 -                size = size - 2
   1.470 -            
   1.471 -        # ok, data is at current position
   1.472 -        offset = file.tell()
   1.473 -        
   1.474 -        # yield
   1.475 -        yield marker_type, size
   1.476 -
   1.477 -        # absolute seek to next marker
   1.478 -        file.seek(offset + size)
   1.479 -
   1.480 -def jpeg_find_exif (file) :
   1.481 -    """
   1.482 -        Find the Exif/TIFF section in the given JPEG file.
   1.483 -
   1.484 -        If found, the file will be seek'd to the start of the Exif/TIFF header, and the size of the Exif/TIFF data will
   1.485 -        be returned.
   1.486 -        
   1.487 -        Returns None if no EXIF section was found.
   1.488 -    """
   1.489 -
   1.490 -    for count, (marker, size) in enumerate(jpeg_markers(file)) :
   1.491 -        # verify that it's a JPEG file
   1.492 -        if count == 0 :
   1.493 -            # must start with the right marker
   1.494 -            if marker != JPEG_START_MARKER :
   1.495 -                raise Exception("JPEG file must start with 0xFF%02x marker" % (marker, ))
   1.496 -
   1.497 -        # look for APP1 marker (0xE1) with EXIF signature
   1.498 -        elif marker == JPEG_EXIF_MARKER and file.read(len(JPEG_EXIF_HEADER)) == JPEG_EXIF_HEADER:
   1.499 -            # skipped the initial Exif marker signature
   1.500 -            return size - len(JPEG_EXIF_HEADER)
   1.501 -
   1.502 -    # nothing
   1.503 -    return None
   1.504 -
   1.505 -def jpeg_load (file, **opts) :
   1.506 -    """
   1.507 -        Loads the embedded Exif TIFF data from the given JPEG file using tiff_load.
   1.508 -
   1.509 -        Returns None if no EXIF data could be found.
   1.510 -    """
   1.511 -        
   1.512 -    # look for the right section
   1.513 -    size = jpeg_find_exif(file)
   1.514 -    
   1.515 -    # not found?
   1.516 -    if not size :
   1.517 -        # nothing
   1.518 -        return
   1.519 -
   1.520 -    else :    
   1.521 -        # load it as TIFF data
   1.522 -        return tiff_load(file, size, **opts)
   1.523 -
   1.524 -def load_path (path, **opts) :
   1.525 -    """
   1.526 -        Loads an EXIF object from the given filesystem path.
   1.527 -
   1.528 -        Returns None if it could not be parsed.
   1.529 -    """
   1.530 -    
   1.531 -    # file extension
   1.532 -    root, fext = os.path.splitext(path)
   1.533 -
   1.534 -    # map
   1.535 -    func = {
   1.536 -        '.jpeg':    jpeg_load,
   1.537 -        '.jpg':     jpeg_load,
   1.538 -        '.tiff':    tiff_load,  # XXX: untested
   1.539 -    }.get(fext.lower())
   1.540 -    
   1.541 -    # not recognized?
   1.542 -    if not func :
   1.543 -        # XXX: sniff the file
   1.544 -        return None
   1.545 -
   1.546 -    # open it
   1.547 -    file = open(path, 'rb')
   1.548 -
   1.549 -    # try and load it
   1.550 -    return func(file, **opts)
   1.551 -
   1.552 -def dump_tag (exif, i, tag, indent=2) :
   1.553 -    """
   1.554 -        Dump the given tag
   1.555 -    """
   1.556 -
   1.557 -    data_info = exif.tag_data_info(tag)
   1.558 -
   1.559 -    if data_info :
   1.560 -        data_fmt, data_offset, data_size = data_info
   1.561 -
   1.562 -    else :
   1.563 -        data_fmt = data_offset = data_size = None
   1.564 -
   1.565 -    print "%sTag:%d offset=%#04x(%#08x), tag=%d/%s, type=%d/%s, count=%d, fmt=%s, offset=%#04x, size=%s, is_subifd=%s:" % (
   1.566 -        '\t'*indent,
   1.567 -        i, 
   1.568 -        tag.offset, tag.offset + exif.offset,
   1.569 -        tag.tag, tag.name or '???',
   1.570 -        tag.type, tag.type_name if tag.type_data else '???',
   1.571 -        tag.count,
   1.572 -        data_fmt, data_offset, data_size,
   1.573 -        tag.is_subifd(),
   1.574 -    )
   1.575 -    
   1.576 -    if tag.is_subifd() :
   1.577 -        # recurse
   1.578 -        dump_ifd(exif, 0, tag.subifd, indent + 1)
   1.579 -
   1.580 -    else :
   1.581 -        # dump each value
   1.582 -        values = exif.tag_values(tag)
   1.583 -        
   1.584 -        for i, value in enumerate(values) :
   1.585 -            print "%s\t%02d: %.120r" % ('\t'*indent, i, value)
   1.586 -        
   1.587 -        # and then the readable one
   1.588 -        print "%s\t->  %.120s" % ('\t'*indent, tag.readable_value(values), )
   1.589 -
   1.590 -
   1.591 -def dump_ifd (exif, i, ifd, indent=1) :
   1.592 -    """
   1.593 -        Dump the given IFD, recursively
   1.594 -    """
   1.595 -
   1.596 -    print "%sIFD:%d offset=%#04x(%#08x), count=%d, next=%d:" % (
   1.597 -        '\t'*indent,
   1.598 -        i, 
   1.599 -        ifd.offset, ifd.offset + exif.offset,
   1.600 -        ifd.count, 
   1.601 -        ifd.next_offset
   1.602 -    )
   1.603 -    
   1.604 -    for i, tag in enumerate(ifd.tags) :
   1.605 -        # dump
   1.606 -        dump_tag(exif, i, tag, indent + 1)
   1.607 -
   1.608 -
   1.609 -def dump_exif (exif) :
   1.610 -    """
   1.611 -        Dump all tags from the given EXIF object to stdout
   1.612 -    """
   1.613 -
   1.614 -    print "EXIF offset=%#08x, size=%d:" % (exif.offset, exif.size)
   1.615 -
   1.616 -    for i, ifd in enumerate(exif.ifds) :
   1.617 -        # dump
   1.618 -        dump_ifd(exif, i, ifd)
   1.619 -
   1.620 -
   1.621 -def list_tags (exif) :
   1.622 -    """
   1.623 -        Print a neat listing of tags to stdout
   1.624 -    """
   1.625 -
   1.626 -    for k, v in exif.get_main_tags().iteritems() :
   1.627 -        print "%30s: %s" % (k, v)
   1.628 -
   1.629 -def main_path (path, dump) :
   1.630 -    # dump path
   1.631 -    print "%s: " % path
   1.632 -
   1.633 -    # try and load it
   1.634 -    exif = load_path(path)
   1.635 - 
   1.636 -    if not exif :
   1.637 -        raise Exception("No EXIF data found")
   1.638 -
   1.639 -    if dump :
   1.640 -        # dump everything
   1.641 -        dump_exif(exif)
   1.642 -    
   1.643 -    else :
   1.644 -        # list them
   1.645 -        list_tags(exif)   
   1.646 -
   1.647 -
   1.648 -def main (paths, dump=False) :
   1.649 -    """
   1.650 -        Load and dump EXIF data from the given path
   1.651 -    """
   1.652 -    
   1.653 -    # handle each one
   1.654 -    for path in paths :
   1.655 -        main_path(path, dump=dump)
   1.656 -
   1.657 -if __name__ == '__main__' :
   1.658 -    import getopt
   1.659 -    from sys import argv
   1.660 -    
   1.661 -    # defaults
   1.662 -    dump = False
   1.663 -
   1.664 -    # parse args
   1.665 -    opts, args = getopt.getopt(argv[1:], "d", ["dump"])
   1.666 -
   1.667 -    for opt, val in opts :
   1.668 -        if opt in ('-d', "--dump") :
   1.669 -            dump = True
   1.670 -
   1.671 -    main(args, dump=dump)
   1.672 -
     2.1 --- a/degal/exif_data.py	Sun Jun 14 16:09:04 2009 +0300
     2.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.3 @@ -1,1304 +0,0 @@
     2.4 -#!/usr/bin/env python
     2.5 -# -*- coding: utf-8 -*-
     2.6 -
     2.7 -"""
     2.8 -    EXIF file format data, including tag names, types, etc.
     2.9 -
    2.10 -    Most of this was copied with modifications from EXIFpy:
    2.11 -        # Library to extract EXIF information from digital camera image files
    2.12 -        # http://sourceforge.net/projects/exif-py/
    2.13 -        #
    2.14 -        # VERSION 1.1.0
    2.15 -        #
    2.16 -        # Copyright (c) 2002-2007 Gene Cash All rights reserved
    2.17 -        # Copyright (c) 2007-2008 Ianaré Sévi All rights reserved
    2.18 -        #
    2.19 -        # Redistribution and use in source and binary forms, with or without
    2.20 -        # modification, are permitted provided that the following conditions
    2.21 -        # are met:
    2.22 -        #
    2.23 -        #  1. Redistributions of source code must retain the above copyright
    2.24 -        #     notice, this list of conditions and the following disclaimer.
    2.25 -        #
    2.26 -        #  2. Redistributions in binary form must reproduce the above
    2.27 -        #     copyright notice, this list of conditions and the following
    2.28 -        #     disclaimer in the documentation and/or other materials provided
    2.29 -        #     with the distribution.
    2.30 -        #
    2.31 -        #  3. Neither the name of the authors nor the names of its contributors
    2.32 -        #     may be used to endorse or promote products derived from this
    2.33 -        #     software without specific prior written permission.
    2.34 -        #
    2.35 -        # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    2.36 -        # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    2.37 -        # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    2.38 -        # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    2.39 -        # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    2.40 -        # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    2.41 -        # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    2.42 -        # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    2.43 -        # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    2.44 -        # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    2.45 -        # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    2.46 -
    2.47 -"""
    2.48 -
    2.49 -import decimal, itertools
    2.50 -
    2.51 -def filter_ascii (values) :
    2.52 -    """
    2.53 -        Default post-filter for ASCII values.
    2.54 -
    2.55 -        This takes a single item of string data, splits it up into strings by ASCII-NUL.
    2.56 -
    2.57 -        These sub-strings are then decoded into unicode as ASCII, and stripped.
    2.58 -    """
    2.59 -
    2.60 -    return [string.decode('ascii', 'replace').rstrip() for string in values[0].split('\x00') if string]
    2.61 -
    2.62 -def build_ratio (num, denom) :
    2.63 -    """
    2.64 -        Builds a Decimal ratio out of the given numerator and denominator
    2.65 -    """
    2.66 -    
    2.67 -    # XXX: this may be slow
    2.68 -    return decimal.Decimal(num) / decimal.Decimal(denom)
    2.69 -
    2.70 -def filter_ratio (values) :
    2.71 -    """
    2.72 -        Default post-filter for Ratio values.
    2.73 -
    2.74 -        This takes the pairs of numerator/denominator values and builds Decimals out of them
    2.75 -    """
    2.76 -
    2.77 -    return [build_ratio(values[i], values[i + 1]) for i in xrange(0, len(values), 2)]
    2.78 -
    2.79 -
    2.80 -# IFD Tag type information, indexed by code
    2.81 -#  { type_code: (type_fmt, name, filter_func) }
    2.82 -#
    2.83 -# type_fmt's that are one char will be prefixed with the count for use with struct.unpack, those with more chars will
    2.84 -# be repeated as many times for use with struct.unpack.
    2.85 -FIELD_TYPES = {
    2.86 -#    0x0000: (None,  'Proprietary'   ), # ??? no such type
    2.87 -    0x0001: ('B',   'Byte',         None            ),
    2.88 -    0x0002: ('s',   'ASCII',        filter_ascii    ),
    2.89 -    0x0003: ('H',   'Short',        None            ),
    2.90 -    0x0004: ('L',   'Long',         None            ),
    2.91 -    0x0005: ('LL',  'Ratio',        filter_ratio    ),
    2.92 -    0x0006: ('b',   'Signed Byte',  None            ),
    2.93 -    0x0007: ('s',   'Undefined',    None            ),
    2.94 -    0x0008: ('h',   'Signed Short', None            ),
    2.95 -    0x0009: ('l',   'Signed Long',  None            ),
    2.96 -    0x000A: ('ll',  'Signed Ratio', filter_ratio    ),
    2.97 -}
    2.98 -
    2.99 -# magic value to indicate sub-IFDs
   2.100 -SUB_IFD_MAGIC = object()
   2.101 -
   2.102 -
   2.103 -class Tag (object) :
   2.104 -    """
   2.105 -        Represents an Exif Tag
   2.106 -    """
   2.107 -
   2.108 -    def __init__ (self, name) :
   2.109 -        """
   2.110 -            Build Exif tag with given name, and optional external values-filter function.
   2.111 -        """
   2.112 -
   2.113 -        self.name = name
   2.114 -
   2.115 -    def map_values (self, values) :
   2.116 -        """
   2.117 -            Map the given tag value to a printable string using the given value spec.
   2.118 -        """
   2.119 -
   2.120 -        # default value-mapping
   2.121 -        return ", ".join(str(value) for value in values)
   2.122 -
   2.123 -class TagDict (Tag) :
   2.124 -    """
   2.125 -        A tag with a dict mapping values to names
   2.126 -    """
   2.127 -
   2.128 -    def __init__ (self, name, values_dict) :
   2.129 -        super(TagDict, self).__init__(name)
   2.130 -
   2.131 -        self.values_dict = values_dict
   2.132 -
   2.133 -    def map_values (self, values) :
   2.134 -        """
   2.135 -            Map the values through our dict, defaulting to the repr.
   2.136 -        """
   2.137 -
   2.138 -        return ", ".join(self.values_dict.get(value, repr(value)) for value in values)
   2.139 -
   2.140 -class TagFunc (Tag) :
   2.141 -    """
   2.142 -        A tag with a simple function mapping values to names
   2.143 -    """
   2.144 -
   2.145 -    def __init__ (self, name, values_func) :
   2.146 -        super(TagFunc, self).__init__(name)
   2.147 -
   2.148 -        self.values_func = values_func
   2.149 -
   2.150 -    def map_values (self, values) :
   2.151 -        """
   2.152 -            Map the values through our func
   2.153 -        """
   2.154 -
   2.155 -        return self.values_func(values)
   2.156 -
   2.157 -class IFDTag (Tag) :
   2.158 -    """
   2.159 -        A tag that references another IFD
   2.160 -    """
   2.161 -
   2.162 -    def __init__ (self, name, ifd_tags=None) :
   2.163 -        """
   2.164 -            A tag that points to another IFD block. `ifd_tags`, if given, lists the tags for that block, otherwise,
   2.165 -            the same tags as for the current block are used.
   2.166 -        """
   2.167 -
   2.168 -        super(IFDTag, self).__init__(name)
   2.169 -
   2.170 -        self.ifd_tags = ifd_tags
   2.171 -
   2.172 -USER_COMMENT_CHARSETS = {
   2.173 -    'ASCII':    ('ascii',   'replace'   ),
   2.174 -    'JIS':      ('jis',     'error'     ),
   2.175 -
   2.176 -    # XXX: WTF? What kind of charset is 'Unicode' supposed to be?
   2.177 -    # UTF-16? Little-endian? Big-endian?
   2.178 -    # Confusing reigns: http://www.cpanforum.com/threads/7329
   2.179 -    'UNICODE':  ('utf16',   'error'     ),
   2.180 -}
   2.181 -
   2.182 -
   2.183 -def decode_UserComment (values) :
   2.184 -    """
   2.185 -        A UserComment field starts with an eight-byte encoding designator.
   2.186 -    """
   2.187 -
   2.188 -    # single binary string
   2.189 -    value, = values
   2.190 -    
   2.191 -    # split up
   2.192 -    charset, comment_raw = value[:8], value[8:]
   2.193 -
   2.194 -    # strip NILs
   2.195 -    charset = charset.rstrip('\x00')
   2.196 -
   2.197 -    # map
   2.198 -    encoding, replace = USER_COMMENT_CHARSETS.get(charset, ('ascii', 'replace'))
   2.199 -    
   2.200 -    # decode
   2.201 -    return [comment_raw.decode(encoding, replace)]
   2.202 -
   2.203 -# Mappings of Exif tag codes to name and decoding information.
   2.204 -# { tag : (name, value_dict/value_func/None/SUB_IFD_MAGIC) }
   2.205 -#
   2.206 -# name is the official Exif tag name
   2.207 -# value_dict is a { value: value_name } mapping for human-readable values
   2.208 -# value_func is a `(values) -> values` mapping function which *overrides* the tag's type_func.
   2.209 -#   XXX: or does it?
   2.210 -# SUB_IFD_MAGIC signifies that this IFD points to 
   2.211 -# otherwise, the value is left as-is.
   2.212 -# interoperability tags
   2.213 -INTR_TAGS = {
   2.214 -    0x0001: Tag('InteroperabilityIndex'),
   2.215 -    0x0002: Tag('InteroperabilityVersion'),
   2.216 -    0x1000: Tag('RelatedImageFileFormat'),
   2.217 -    0x1001: Tag('RelatedImageWidth'),
   2.218 -    0x1002: Tag('RelatedImageLength'),
   2.219 -    }
   2.220 -
   2.221 -# GPS tags (not used yet, haven't seen camera with GPS)
   2.222 -GPS_TAGS = {
   2.223 -    0x0000: Tag('GPSVersionID'),
   2.224 -    0x0001: Tag('GPSLatitudeRef'),
   2.225 -    0x0002: Tag('GPSLatitude'),
   2.226 -    0x0003: Tag('GPSLongitudeRef'),
   2.227 -    0x0004: Tag('GPSLongitude'),
   2.228 -    0x0005: Tag('GPSAltitudeRef'),
   2.229 -    0x0006: Tag('GPSAltitude'),
   2.230 -    0x0007: Tag('GPSTimeStamp'),
   2.231 -    0x0008: Tag('GPSSatellites'),
   2.232 -    0x0009: Tag('GPSStatus'),
   2.233 -    0x000A: Tag('GPSMeasureMode'),
   2.234 -    0x000B: Tag('GPSDOP'),
   2.235 -    0x000C: Tag('GPSSpeedRef'),
   2.236 -    0x000D: Tag('GPSSpeed'),
   2.237 -    0x000E: Tag('GPSTrackRef'),
   2.238 -    0x000F: Tag('GPSTrack'),
   2.239 -    0x0010: Tag('GPSImgDirectionRef'),
   2.240 -    0x0011: Tag('GPSImgDirection'),
   2.241 -    0x0012: Tag('GPSMapDatum'),
   2.242 -    0x0013: Tag('GPSDestLatitudeRef'),
   2.243 -    0x0014: Tag('GPSDestLatitude'),
   2.244 -    0x0015: Tag('GPSDestLongitudeRef'),
   2.245 -    0x0016: Tag('GPSDestLongitude'),
   2.246 -    0x0017: Tag('GPSDestBearingRef'),
   2.247 -    0x0018: Tag('GPSDestBearing'),
   2.248 -    0x0019: Tag('GPSDestDistanceRef'),
   2.249 -    0x001A: Tag('GPSDestDistance'),
   2.250 -    0x001D: Tag('GPSDate'),
   2.251 -    }
   2.252 -
   2.253 -
   2.254 -EXIF_TAGS = {
   2.255 -    0x0100: Tag('ImageWidth'),
   2.256 -    0x0101: Tag('ImageLength'),
   2.257 -    0x0102: Tag('BitsPerSample'),
   2.258 -    0x0103: TagDict('Compression',
   2.259 -             {1: 'Uncompressed',
   2.260 -              2: 'CCITT 1D',
   2.261 -              3: 'T4/Group 3 Fax',
   2.262 -              4: 'T6/Group 4 Fax',
   2.263 -              5: 'LZW',
   2.264 -              6: 'JPEG (old-style)',
   2.265 -              7: 'JPEG',
   2.266 -              8: 'Adobe Deflate',
   2.267 -              9: 'JBIG B&W',
   2.268 -              10: 'JBIG Color',
   2.269 -              32766: 'Next',
   2.270 -              32769: 'Epson ERF Compressed',
   2.271 -              32771: 'CCIRLEW',
   2.272 -              32773: 'PackBits',
   2.273 -              32809: 'Thunderscan',
   2.274 -              32895: 'IT8CTPAD',
   2.275 -              32896: 'IT8LW',
   2.276 -              32897: 'IT8MP',
   2.277 -              32898: 'IT8BL',
   2.278 -              32908: 'PixarFilm',
   2.279 -              32909: 'PixarLog',
   2.280 -              32946: 'Deflate',
   2.281 -              32947: 'DCS',
   2.282 -              34661: 'JBIG',
   2.283 -              34676: 'SGILog',
   2.284 -              34677: 'SGILog24',
   2.285 -              34712: 'JPEG 2000',
   2.286 -              34713: 'Nikon NEF Compressed',
   2.287 -              65000: 'Kodak DCR Compressed',
   2.288 -              65535: 'Pentax PEF Compressed'}),
   2.289 -    0x0106: Tag('PhotometricInterpretation'),
   2.290 -    0x0107: Tag('Thresholding'),
   2.291 -    0x010A: Tag('FillOrder'),
   2.292 -    0x010D: Tag('DocumentName'),
   2.293 -    0x010E: Tag('ImageDescription'),
   2.294 -    0x010F: Tag('Make'),
   2.295 -    0x0110: Tag('Model'),
   2.296 -    0x0111: Tag('StripOffsets'),
   2.297 -    0x0112: TagDict('Orientation',
   2.298 -             {1: 'Horizontal (normal)',
   2.299 -              2: 'Mirrored horizontal',
   2.300 -              3: 'Rotated 180',
   2.301 -              4: 'Mirrored vertical',
   2.302 -              5: 'Mirrored horizontal then rotated 90 CCW',
   2.303 -              6: 'Rotated 90 CW',
   2.304 -              7: 'Mirrored horizontal then rotated 90 CW',
   2.305 -              8: 'Rotated 90 CCW'}),
   2.306 -    0x0115: Tag('SamplesPerPixel'),
   2.307 -    0x0116: Tag('RowsPerStrip'),
   2.308 -    0x0117: Tag('StripByteCounts'),
   2.309 -    0x011A: Tag('XResolution'),
   2.310 -    0x011B: Tag('YResolution'),
   2.311 -    0x011C: Tag('PlanarConfiguration'),
   2.312 -    0x011D: Tag('PageName'),
   2.313 -    0x0128: TagDict('ResolutionUnit',
   2.314 -             {1: 'Not Absolute',
   2.315 -              2: 'Pixels/Inch',
   2.316 -              3: 'Pixels/Centimeter'}),
   2.317 -    0x012D: Tag('TransferFunction'),
   2.318 -    0x0131: Tag('Software'),
   2.319 -    0x0132: Tag('DateTime'),
   2.320 -    0x013B: Tag('Artist'),
   2.321 -    0x013E: Tag('WhitePoint'),
   2.322 -    0x013F: Tag('PrimaryChromaticities'),
   2.323 -    0x0156: Tag('TransferRange'),
   2.324 -    0x0200: Tag('JPEGProc'),
   2.325 -    0x0201: Tag('JPEGInterchangeFormat'),
   2.326 -    0x0202: Tag('JPEGInterchangeFormatLength'),
   2.327 -    0x0211: Tag('YCbCrCoefficients'),
   2.328 -    0x0212: Tag('YCbCrSubSampling'),
   2.329 -    0x0213: TagDict('YCbCrPositioning',
   2.330 -             {1: 'Centered',
   2.331 -              2: 'Co-sited'}),
   2.332 -    0x0214: Tag('ReferenceBlackWhite'),
   2.333 -    
   2.334 -    0x4746: Tag('Rating'),
   2.335 -    
   2.336 -    0x828D: Tag('CFARepeatPatternDim'),
   2.337 -    0x828E: Tag('CFAPattern'),
   2.338 -    0x828F: Tag('BatteryLevel'),
   2.339 -    0x8298: Tag('Copyright'),
   2.340 -    0x829A: Tag('ExposureTime'),
   2.341 -    0x829D: Tag('FNumber'),
   2.342 -    0x83BB: Tag('IPTC/NAA'),
   2.343 -    0x8769: IFDTag('ExifOffset', None),
   2.344 -    0x8773: Tag('InterColorProfile'),
   2.345 -    0x8822: TagDict('ExposureProgram',
   2.346 -             {0: 'Unidentified',
   2.347 -              1: 'Manual',
   2.348 -              2: 'Program Normal',
   2.349 -              3: 'Aperture Priority',
   2.350 -              4: 'Shutter Priority',
   2.351 -              5: 'Program Creative',
   2.352 -              6: 'Program Action',
   2.353 -              7: 'Portrait Mode',
   2.354 -              8: 'Landscape Mode'}),
   2.355 -    0x8824: Tag('SpectralSensitivity'),
   2.356 -    0x8825: IFDTag('GPSInfo', GPS_TAGS),
   2.357 -    0x8827: Tag('ISOSpeedRatings'),
   2.358 -    0x8828: Tag('OECF'),
   2.359 -    0x9000: Tag('ExifVersion'),
   2.360 -    0x9003: Tag('DateTimeOriginal'),
   2.361 -    0x9004: Tag('DateTimeDigitized'),
   2.362 -    0x9101: TagDict('ComponentsConfiguration',
   2.363 -             {0: '',
   2.364 -              1: 'Y',
   2.365 -              2: 'Cb',
   2.366 -              3: 'Cr',
   2.367 -              4: 'Red',
   2.368 -              5: 'Green',
   2.369 -              6: 'Blue'}),
   2.370 -    0x9102: Tag('CompressedBitsPerPixel'),
   2.371 -    0x9201: Tag('ShutterSpeedValue'),
   2.372 -    0x9202: Tag('ApertureValue'),
   2.373 -    0x9203: Tag('BrightnessValue'),
   2.374 -    0x9204: Tag('ExposureBiasValue'),
   2.375 -    0x9205: Tag('MaxApertureValue'),
   2.376 -    0x9206: Tag('SubjectDistance'),
   2.377 -    0x9207: TagDict('MeteringMode',
   2.378 -             {0: 'Unidentified',
   2.379 -              1: 'Average',
   2.380 -              2: 'CenterWeightedAverage',
   2.381 -              3: 'Spot',
   2.382 -              4: 'MultiSpot',
   2.383 -              5: 'Pattern'}),
   2.384 -    0x9208: TagDict('LightSource',
   2.385 -             {0: 'Unknown',
   2.386 -              1: 'Daylight',
   2.387 -              2: 'Fluorescent',
   2.388 -              3: 'Tungsten',
   2.389 -              9: 'Fine Weather',
   2.390 -              10: 'Flash',
   2.391 -              11: 'Shade',
   2.392 -              12: 'Daylight Fluorescent',
   2.393 -              13: 'Day White Fluorescent',
   2.394 -              14: 'Cool White Fluorescent',
   2.395 -              15: 'White Fluorescent',
   2.396 -              17: 'Standard Light A',
   2.397 -              18: 'Standard Light B',
   2.398 -              19: 'Standard Light C',
   2.399 -              20: 'D55',
   2.400 -              21: 'D65',
   2.401 -              22: 'D75',
   2.402 -              255: 'Other'}),
   2.403 -    0x9209: TagDict('Flash',
   2.404 -             {0: 'No',
   2.405 -              1: 'Fired',
   2.406 -              5: 'Fired (?)', # no return sensed
   2.407 -              7: 'Fired (!)', # return sensed
   2.408 -              9: 'Fill Fired',
   2.409 -              13: 'Fill Fired (?)',
   2.410 -              15: 'Fill Fired (!)',
   2.411 -              16: 'Off',
   2.412 -              24: 'Auto Off',
   2.413 -              25: 'Auto Fired',
   2.414 -              29: 'Auto Fired (?)',
   2.415 -              31: 'Auto Fired (!)',
   2.416 -              32: 'Not Available'}),
   2.417 -    0x920A: Tag('FocalLength'),
   2.418 -    0x9214: Tag('SubjectArea'),
   2.419 -    0x927C: Tag('MakerNote'),
   2.420 -    0x9286: TagFunc('UserComment', decode_UserComment),
   2.421 -    0x9290: Tag('SubSecTime'),
   2.422 -    0x9291: Tag('SubSecTimeOriginal'),
   2.423 -    0x9292: Tag('SubSecTimeDigitized'),
   2.424 -    
   2.425 -    # used by Windows Explorer
   2.426 -    0x9C9B: Tag('XPTitle'),
   2.427 -    0x9C9C: Tag('XPComment'),
   2.428 -    0x9C9D: Tag('XPAuthor'), #(ignored by Windows Explorer if Artist exists)
   2.429 -    0x9C9E: Tag('XPKeywords'),
   2.430 -    0x9C9F: Tag('XPSubject'),
   2.431 -
   2.432 -    0xA000: Tag('FlashPixVersion'),
   2.433 -    0xA001: TagDict('ColorSpace',
   2.434 -             {1: 'sRGB',
   2.435 -              2: 'Adobe RGB',
   2.436 -              65535: 'Uncalibrated'}),
   2.437 -    0xA002: Tag('ExifImageWidth'),
   2.438 -    0xA003: Tag('ExifImageLength'),
   2.439 -    0xA005: IFDTag('InteroperabilityOffset', INTR_TAGS),
   2.440 -    0xA20B: Tag('FlashEnergy'),               # 0x920B in TIFF/EP
   2.441 -    0xA20C: Tag('SpatialFrequencyResponse'),  # 0x920C
   2.442 -    0xA20E: Tag('FocalPlaneXResolution'),     # 0x920E
   2.443 -    0xA20F: Tag('FocalPlaneYResolution'),     # 0x920F
   2.444 -    0xA210: Tag('FocalPlaneResolutionUnit'),  # 0x9210
   2.445 -    0xA214: Tag('SubjectLocation'),           # 0x9214
   2.446 -    0xA215: Tag('ExposureIndex'),             # 0x9215
   2.447 -    0xA217: TagDict('SensingMethod',                # 0x9217
   2.448 -             {1: 'Not defined',
   2.449 -              2: 'One-chip color area',
   2.450 -              3: 'Two-chip color area',
   2.451 -              4: 'Three-chip color area',
   2.452 -              5: 'Color sequential area',
   2.453 -              7: 'Trilinear',
   2.454 -              8: 'Color sequential linear'}),             
   2.455 -    0xA300: TagDict('FileSource',
   2.456 -             {1: 'Film Scanner',
   2.457 -              2: 'Reflection Print Scanner',
   2.458 -              3: 'Digital Camera'}),
   2.459 -    0xA301: TagDict('SceneType',
   2.460 -             {1: 'Directly Photographed'}),
   2.461 -    0xA302: Tag('CVAPattern'),
   2.462 -    0xA401: TagDict('CustomRendered',
   2.463 -             {0: 'Normal',
   2.464 -              1: 'Custom'}),
   2.465 -    0xA402: TagDict('ExposureMode',
   2.466 -             {0: 'Auto Exposure',
   2.467 -              1: 'Manual Exposure',
   2.468 -              2: 'Auto Bracket'}),
   2.469 -    0xA403: TagDict('WhiteBalance',
   2.470 -             {0: 'Auto',
   2.471 -              1: 'Manual'}),
   2.472 -    0xA404: Tag('DigitalZoomRatio'),
   2.473 -    0xA405: ('FocalLengthIn35mmFilm', None),
   2.474 -    0xA406: TagDict('SceneCaptureType',
   2.475 -             {0: 'Standard',
   2.476 -              1: 'Landscape',
   2.477 -              2: 'Portrait',
   2.478 -              3: 'Night)'}),
   2.479 -    0xA407: TagDict('GainControl',
   2.480 -             {0: 'None',
   2.481 -              1: 'Low gain up',
   2.482 -              2: 'High gain up',
   2.483 -              3: 'Low gain down',
   2.484 -              4: 'High gain down'}),
   2.485 -    0xA408: TagDict('Contrast',
   2.486 -             {0: 'Normal',
   2.487 -              1: 'Soft',
   2.488 -              2: 'Hard'}),
   2.489 -    0xA409: TagDict('Saturation',
   2.490 -             {0: 'Normal',
   2.491 -              1: 'Soft',
   2.492 -              2: 'Hard'}),
   2.493 -    0xA40A: TagDict('Sharpness',
   2.494 -             {0: 'Normal',
   2.495 -              1: 'Soft',
   2.496 -              2: 'Hard'}),
   2.497 -    0xA40B: Tag('DeviceSettingDescription'),
   2.498 -    0xA40C: Tag('SubjectDistanceRange'),
   2.499 -    0xA500: Tag('Gamma'),
   2.500 -    0xC4A5: Tag('PrintIM'),
   2.501 -    0xEA1C:	('Padding', None),
   2.502 -    }
   2.503 -
   2.504 -# http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp
   2.505 -def nikon_ev_bias (seq) :
   2.506 -    """
   2.507 -        # First digit seems to be in steps of 1/6 EV.
   2.508 -        # Does the third value mean the step size?  It is usually 6,
   2.509 -        # but it is 12 for the ExposureDifference.
   2.510 -    """
   2.511 -
   2.512 -    # check for an error condition that could cause a crash.
   2.513 -    # this only happens if something has gone really wrong in
   2.514 -    # reading the Nikon MakerNote.
   2.515 -    if len(seq) < 4 : 
   2.516 -        return ""
   2.517 -
   2.518 -    if seq == [252, 1, 6, 0]:
   2.519 -        return "-2/3 EV"
   2.520 -
   2.521 -    if seq == [253, 1, 6, 0]:
   2.522 -        return "-1/2 EV"
   2.523 -
   2.524 -    if seq == [254, 1, 6, 0]:
   2.525 -        return "-1/3 EV"
   2.526 -
   2.527 -    if seq == [0, 1, 6, 0]:
   2.528 -        return "0 EV"
   2.529 -
   2.530 -    if seq == [2, 1, 6, 0]:
   2.531 -        return "+1/3 EV"
   2.532 -
   2.533 -    if seq == [3, 1, 6, 0]:
   2.534 -        return "+1/2 EV"
   2.535 -
   2.536 -    if seq == [4, 1, 6, 0]:
   2.537 -        return "+2/3 EV"
   2.538 -
   2.539 -    # handle combinations not in the table.
   2.540 -    a = seq[0]
   2.541 -
   2.542 -    # causes headaches for the +/- logic, so special case it.
   2.543 -    if a == 0:
   2.544 -        return "0 EV"
   2.545 -
   2.546 -    if a > 127:
   2.547 -        a = 256 - a
   2.548 -        ret_str = "-"
   2.549 -    else:
   2.550 -        ret_str = "+"
   2.551 -
   2.552 -    b = seq[2]	# assume third value means the step size
   2.553 -
   2.554 -    whole = a / b
   2.555 -
   2.556 -    a = a % b
   2.557 -
   2.558 -    if whole != 0 :
   2.559 -        ret_str = ret_str + str(whole) + " "
   2.560 -
   2.561 -    if a == 0 :
   2.562 -        ret_str = ret_str + "EV"
   2.563 -    else :
   2.564 -        r = Ratio(a, b)
   2.565 -        ret_str = ret_str + r.__repr__() + " EV"
   2.566 -
   2.567 -    return ret_str
   2.568 -
   2.569 -# Nikon E99x MakerNote Tags
   2.570 -MAKERNOTE_NIKON_NEWER_TAGS={
   2.571 -    0x0001: Tag('MakernoteVersion'),	# Sometimes binary
   2.572 -    0x0002: Tag('ISOSetting'),
   2.573 -    0x0003: Tag('ColorMode'),
   2.574 -    0x0004: Tag('Quality'),
   2.575 -    0x0005: Tag('Whitebalance'),
   2.576 -    0x0006: Tag('ImageSharpening'),
   2.577 -    0x0007: Tag('FocusMode'),
   2.578 -    0x0008: Tag('FlashSetting'),
   2.579 -    0x0009: Tag('AutoFlashMode'),
   2.580 -    0x000B: Tag('WhiteBalanceBias'),
   2.581 -    0x000C: Tag('WhiteBalanceRBCoeff'),
   2.582 -    0x000D: TagFunc('ProgramShift', nikon_ev_bias),
   2.583 -    # Nearly the same as the other EV vals, but step size is 1/12 EV (?)
   2.584 -    0x000E: TagFunc('ExposureDifference', nikon_ev_bias),
   2.585 -    0x000F: Tag('ISOSelection'),
   2.586 -    0x0011: Tag('NikonPreview'),
   2.587 -    0x0012: TagFunc('FlashCompensation', nikon_ev_bias),
   2.588 -    0x0013: Tag('ISOSpeedRequested'),
   2.589 -    0x0016: Tag('PhotoCornerCoordinates'),
   2.590 -    # 0x0017: Unknown, but most likely an EV value
   2.591 -    0x0018: TagFunc('FlashBracketCompensationApplied', nikon_ev_bias),
   2.592 -    0x0019: Tag('AEBracketCompensationApplied'),
   2.593 -    0x001A: Tag('ImageProcessing'),
   2.594 -    0x001B: Tag('CropHiSpeed'),
   2.595 -    0x001D: Tag('SerialNumber'),	# Conflict with 0x00A0 ?
   2.596 -    0x001E: Tag('ColorSpace'),
   2.597 -    0x001F: Tag('VRInfo'),
   2.598 -    0x0020: Tag('ImageAuthentication'),
   2.599 -    0x0022: Tag('ActiveDLighting'),
   2.600 -    0x0023: Tag('PictureControl'),
   2.601 -    0x0024: Tag('WorldTime'),
   2.602 -    0x0025: Tag('ISOInfo'),
   2.603 -    0x0080: Tag('ImageAdjustment'),
   2.604 -    0x0081: Tag('ToneCompensation'),
   2.605 -    0x0082: Tag('AuxiliaryLens'),
   2.606 -    0x0083: Tag('LensType'),
   2.607 -    0x0084: Tag('LensMinMaxFocalMaxAperture'),
   2.608 -    0x0085: Tag('ManualFocusDistance'),
   2.609 -    0x0086: Tag('DigitalZoomFactor'),
   2.610 -    0x0087: TagDict('FlashMode',
   2.611 -             {0x00: 'Did Not Fire',
   2.612 -              0x01: 'Fired, Manual',
   2.613 -              0x07: 'Fired, External',
   2.614 -              0x08: 'Fired, Commander Mode ',
   2.615 -              0x09: 'Fired, TTL Mode'}),
   2.616 -    0x0088: TagDict('AFFocusPosition',
   2.617 -             {0x0000: 'Center',
   2.618 -              0x0100: 'Top',
   2.619 -              0x0200: 'Bottom',
   2.620 -              0x0300: 'Left',
   2.621 -              0x0400: 'Right'}),
   2.622 -    0x0089: TagDict('BracketingMode',
   2.623 -             {0x00: 'Single frame, no bracketing',
   2.624 -              0x01: 'Continuous, no bracketing',
   2.625 -              0x02: 'Timer, no bracketing',
   2.626 -              0x10: 'Single frame, exposure bracketing',
   2.627 -              0x11: 'Continuous, exposure bracketing',
   2.628 -              0x12: 'Timer, exposure bracketing',
   2.629 -              0x40: 'Single frame, white balance bracketing',
   2.630 -              0x41: 'Continuous, white balance bracketing',
   2.631 -              0x42: 'Timer, white balance bracketing'}),
   2.632 -    0x008A: Tag('AutoBracketRelease'),
   2.633 -    0x008B: Tag('LensFStops'),
   2.634 -    0x008C: ('NEFCurve1', None),	# ExifTool calls this 'ContrastCurve'
   2.635 -    0x008D: Tag('ColorMode'),
   2.636 -    0x008F: Tag('SceneMode'),
   2.637 -    0x0090: Tag('LightingType'),
   2.638 -    0x0091: Tag('ShotInfo'),	# First 4 bytes are a version number in ASCII
   2.639 -    0x0092: Tag('HueAdjustment'),
   2.640 -    # ExifTool calls this 'NEFCompression', should be 1-4
   2.641 -    0x0093: Tag('Compression'),
   2.642 -    0x0094: TagDict('Saturation',
   2.643 -             {-3: 'B&W',
   2.644 -              -2: '-2',
   2.645 -              -1: '-1',
   2.646 -              0: '0',
   2.647 -              1: '1',
   2.648 -              2: '2'}),
   2.649 -    0x0095: Tag('NoiseReduction'),
   2.650 -    0x0096: ('NEFCurve2', None),	# ExifTool calls this 'LinearizationTable'
   2.651 -    0x0097: Tag('ColorBalance'),	# First 4 bytes are a version number in ASCII
   2.652 -    0x0098: Tag('LensData'),	# First 4 bytes are a version number in ASCII
   2.653 -    0x0099: Tag('RawImageCenter'),
   2.654 -    0x009A: Tag('SensorPixelSize'),
   2.655 -    0x009C: Tag('Scene Assist'),
   2.656 -    0x009E: Tag('RetouchHistory'),
   2.657 -    0x00A0: Tag('SerialNumber'),
   2.658 -    0x00A2: Tag('ImageDataSize'),
   2.659 -    # 00A3: unknown - a single byte 0
   2.660 -    # 00A4: In NEF, looks like a 4 byte ASCII version number ('0200')
   2.661 -    0x00A5: Tag('ImageCount'),
   2.662 -    0x00A6: Tag('DeletedImageCount'),
   2.663 -    0x00A7: Tag('TotalShutterReleases'),
   2.664 -    # First 4 bytes are a version number in ASCII, with version specific
   2.665 -    # info to follow.  Its hard to treat it as a string due to embedded nulls.
   2.666 -    0x00A8: Tag('FlashInfo'),
   2.667 -    0x00A9: Tag('ImageOptimization'),
   2.668 -    0x00AA: Tag('Saturation'),
   2.669 -    0x00AB: Tag('DigitalVariProgram'),
   2.670 -    0x00AC: Tag('ImageStabilization'),
   2.671 -    0x00AD: Tag('Responsive AF'),	# 'AFResponse'
   2.672 -    0x00B0: Tag('MultiExposure'),
   2.673 -    0x00B1: Tag('HighISONoiseReduction'),
   2.674 -    0x00B7: Tag('AFInfo'),
   2.675 -    0x00B8: Tag('FileInfo'),
   2.676 -    # 00B9: unknown
   2.677 -    0x0100: Tag('DigitalICE'),
   2.678 -    0x0103: TagDict('PreviewCompression',
   2.679 -             {1: 'Uncompressed',
   2.680 -              2: 'CCITT 1D',
   2.681 -              3: 'T4/Group 3 Fax',
   2.682 -              4: 'T6/Group 4 Fax',
   2.683 -              5: 'LZW',
   2.684 -              6: 'JPEG (old-style)',
   2.685 -              7: 'JPEG',
   2.686 -              8: 'Adobe Deflate',
   2.687 -              9: 'JBIG B&W',
   2.688 -              10: 'JBIG Color',
   2.689 -              32766: 'Next',
   2.690 -              32769: 'Epson ERF Compressed',
   2.691 -              32771: 'CCIRLEW',
   2.692 -              32773: 'PackBits',
   2.693 -              32809: 'Thunderscan',
   2.694 -              32895: 'IT8CTPAD',
   2.695 -              32896: 'IT8LW',
   2.696 -              32897: 'IT8MP',
   2.697 -              32898: 'IT8BL',
   2.698 -              32908: 'PixarFilm',
   2.699 -              32909: 'PixarLog',
   2.700 -              32946: 'Deflate',
   2.701 -              32947: 'DCS',
   2.702 -              34661: 'JBIG',
   2.703 -              34676: 'SGILog',
   2.704 -              34677: 'SGILog24',
   2.705 -              34712: 'JPEG 2000',
   2.706 -              34713: 'Nikon NEF Compressed',
   2.707 -              65000: 'Kodak DCR Compressed',
   2.708 -              65535: 'Pentax PEF Compressed',}),
   2.709 -    0x0201: Tag('PreviewImageStart'),
   2.710 -    0x0202: Tag('PreviewImageLength'),
   2.711 -    0x0213: TagDict('PreviewYCbCrPositioning',
   2.712 -             {1: 'Centered',
   2.713 -              2: 'Co-sited'}), 
   2.714 -    0x0010: Tag('DataDump'),
   2.715 -    }
   2.716 -
   2.717 -MAKERNOTE_NIKON_OLDER_TAGS = {
   2.718 -    0x0003: TagDict('Quality',
   2.719 -             {1: 'VGA Basic',
   2.720 -              2: 'VGA Normal',
   2.721 -              3: 'VGA Fine',
   2.722 -              4: 'SXGA Basic',
   2.723 -              5: 'SXGA Normal',
   2.724 -              6: 'SXGA Fine'}),
   2.725 -    0x0004: TagDict('ColorMode',
   2.726 -             {1: 'Color',
   2.727 -              2: 'Monochrome'}),
   2.728 -    0x0005: TagDict('ImageAdjustment',
   2.729 -             {0: 'Normal',
   2.730 -              1: 'Bright+',
   2.731 -              2: 'Bright-',
   2.732 -              3: 'Contrast+',
   2.733 -              4: 'Contrast-'}),
   2.734 -    0x0006: TagDict('CCDSpeed',
   2.735 -             {0: 'ISO 80',
   2.736 -              2: 'ISO 160',
   2.737 -              4: 'ISO 320',
   2.738 -              5: 'ISO 100'}),
   2.739 -    0x0007: TagDict('WhiteBalance',
   2.740 -             {0: 'Auto',
   2.741 -              1: 'Preset',
   2.742 -              2: 'Daylight',
   2.743 -              3: 'Incandescent',
   2.744 -              4: 'Fluorescent',
   2.745 -              5: 'Cloudy',
   2.746 -              6: 'Speed Light'}),
   2.747 -    }
   2.748 -
   2.749 -def olympus_special_mode (values) :
   2.750 -    """
   2.751 -        Decode Olympus SpecialMode tag in MakerNote
   2.752 -    """
   2.753 -
   2.754 -    a = {
   2.755 -        0: 'Normal',
   2.756 -        1: 'Unknown',
   2.757 -        2: 'Fast',
   2.758 -        3: 'Panorama'
   2.759 -    }
   2.760 -
   2.761 -    b = {
   2.762 -        0: 'Non-panoramic',
   2.763 -        1: 'Left to right',
   2.764 -        2: 'Right to left',
   2.765 -        3: 'Bottom to top',
   2.766 -        4: 'Top to bottom'
   2.767 -    }
   2.768 -
   2.769 -    if v[0] not in a or v[2] not in b:
   2.770 -        return values
   2.771 -
   2.772 -    return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
   2.773 -
   2.774 -MAKERNOTE_OLYMPUS_TAGS={
   2.775 -    # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
   2.776 -    # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
   2.777 -    0x0100: Tag('JPEGThumbnail'),
   2.778 -    0x0200: TagFunc('SpecialMode', olympus_special_mode),
   2.779 -    0x0201: TagDict('JPEGQual',
   2.780 -             {1: 'SQ',
   2.781 -              2: 'HQ',
   2.782 -              3: 'SHQ'}),
   2.783 -    0x0202: TagDict('Macro',
   2.784 -             {0: 'Normal',
   2.785 -             1: 'Macro',
   2.786 -             2: 'SuperMacro'}),
   2.787 -    0x0203: TagDict('BWMode',
   2.788 -             {0: 'Off',
   2.789 -             1: 'On'}),
   2.790 -    0x0204: Tag('DigitalZoom'),
   2.791 -    0x0205: Tag('FocalPlaneDiagonal'),
   2.792 -    0x0206: Tag('LensDistortionParams'),
   2.793 -    0x0207: Tag('SoftwareRelease'),
   2.794 -    0x0208: Tag('PictureInfo'),
   2.795 -    0x0209: Tag('CameraID'), # print as string
   2.796 -    0x0F00: Tag('DataDump'),
   2.797 -    0x0300: Tag('PreCaptureFrames'),
   2.798 -    0x0404: Tag('SerialNumber'),
   2.799 -    0x1000: Tag('ShutterSpeedValue'),
   2.800 -    0x1001: Tag('ISOValue'),
   2.801 -    0x1002: Tag('ApertureValue'),
   2.802 -    0x1003: Tag('BrightnessValue'),
   2.803 -    0x1004: Tag('FlashMode'),
   2.804 -    0x1004: TagDict('FlashMode',
   2.805 -       {2: 'On',
   2.806 -        3: 'Off'}),
   2.807 -    0x1005: TagDict('FlashDevice',
   2.808 -       {0: 'None',
   2.809 -        1: 'Internal',
   2.810 -        4: 'External',
   2.811 -        5: 'Internal + External'}),
   2.812 -    0x1006: Tag('ExposureCompensation'),
   2.813 -    0x1007: Tag('SensorTemperature'),
   2.814 -    0x1008: Tag('LensTemperature'),
   2.815 -    0x100b: TagDict('FocusMode',
   2.816 -       {0: 'Auto',
   2.817 -        1: 'Manual'}),
   2.818 -    0x1017: Tag('RedBalance'),
   2.819 -    0x1018: Tag('BlueBalance'),
   2.820 -    0x101a: Tag('SerialNumber'),
   2.821 -    0x1023: Tag('FlashExposureComp'),
   2.822 -    0x1026: TagDict('ExternalFlashBounce',
   2.823 -       {0: 'No',
   2.824 -        1: 'Yes'}),
   2.825 -    0x1027: Tag('ExternalFlashZoom'),
   2.826 -    0x1028: Tag('ExternalFlashMode'),
   2.827 -    0x1029: ('Contrast 	int16u',
   2.828 -       {0: 'High',
   2.829 -        1: 'Normal',
   2.830 -        2: 'Low'}),
   2.831 -    0x102a: Tag('SharpnessFactor'),
   2.832 -    0x102b: Tag('ColorControl'),
   2.833 -    0x102c: Tag('ValidBits'),
   2.834 -    0x102d: Tag('CoringFilter'),
   2.835 -    0x102e: Tag('OlympusImageWidth'),
   2.836 -    0x102f: Tag('OlympusImageHeight'),
   2.837 -    0x1034: Tag('CompressionRatio'),
   2.838 -    0x1035: TagDict('PreviewImageValid',
   2.839 -       {0: 'No',
   2.840 -        1: 'Yes'}),
   2.841 -    0x1036: Tag('PreviewImageStart'),
   2.842 -    0x1037: Tag('PreviewImageLength'),
   2.843 -    0x1039: TagDict('CCDScanMode',
   2.844 -       {0: 'Interlaced',
   2.845 -        1: 'Progressive'}),
   2.846 -    0x103a: TagDict('NoiseReduction',
   2.847 -       {0: 'Off',
   2.848 -        1: 'On'}),
   2.849 -    0x103b: Tag('InfinityLensStep'),
   2.850 -    0x103c: Tag('NearLensStep'),
   2.851 -
   2.852 -    # TODO - these need extra definitions
   2.853 -    # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html
   2.854 -    0x2010: Tag('Equipment'),
   2.855 -    0x2020: Tag('CameraSettings'),
   2.856 -    0x2030: Tag('RawDevelopment'),
   2.857 -    0x2040: Tag('ImageProcessing'),
   2.858 -    0x2050: Tag('FocusInfo'),
   2.859 -    0x3000: Tag('RawInfo '),
   2.860 -    }
   2.861 -
   2.862 -# 0x2020 CameraSettings
   2.863 -MAKERNOTE_OLYMPUS_TAG_0x2020={
   2.864 -    0x0100: TagDict('PreviewImageValid',
   2.865 -             {0: 'No',
   2.866 -              1: 'Yes'}),
   2.867 -    0x0101: Tag('PreviewImageStart'),
   2.868 -    0x0102: Tag('PreviewImageLength'),
   2.869 -    0x0200: TagDict('ExposureMode',
   2.870 -             {1: 'Manual',
   2.871 -              2: 'Program',
   2.872 -              3: 'Aperture-priority AE',
   2.873 -              4: 'Shutter speed priority AE',
   2.874 -              5: 'Program-shift'}),
   2.875 -    0x0201: TagDict('AELock',
   2.876 -             {0: 'Off',
   2.877 -              1: 'On'}),
   2.878 -    0x0202: TagDict('MeteringMode',
   2.879 -             {2: 'Center Weighted',
   2.880 -              3: 'Spot',
   2.881 -              5: 'ESP',
   2.882 -              261: 'Pattern+AF',
   2.883 -              515: 'Spot+Highlight control',
   2.884 -              1027: 'Spot+Shadow control'}),
   2.885 -    0x0300: TagDict('MacroMode',
   2.886 -             {0: 'Off',
   2.887 -              1: 'On'}),
   2.888 -    0x0301: TagDict('FocusMode',
   2.889 -             {0: 'Single AF',
   2.890 -              1: 'Sequential shooting AF',
   2.891 -              2: 'Continuous AF',
   2.892 -              3: 'Multi AF',
   2.893 -              10: 'MF'}),
   2.894 -    0x0302: TagDict('FocusProcess',
   2.895 -             {0: 'AF Not Used',
   2.896 -              1: 'AF Used'}),
   2.897 -    0x0303: TagDict('AFSearch',
   2.898 -             {0: 'Not Ready',
   2.899 -              1: 'Ready'}),
   2.900 -    0x0304: Tag('AFAreas'),
   2.901 -    0x0401: Tag('FlashExposureCompensation'),
   2.902 -    0x0500: ('WhiteBalance2',
   2.903 -             {0: 'Auto',
   2.904 -             16: '7500K (Fine Weather with Shade)',
   2.905 -             17: '6000K (Cloudy)',
   2.906 -             18: '5300K (Fine Weather)',
   2.907 -             20: '3000K (Tungsten light)',
   2.908 -             21: '3600K (Tungsten light-like)',
   2.909 -             33: '6600K (Daylight fluorescent)',
   2.910 -             34: '4500K (Neutral white fluorescent)',
   2.911 -             35: '4000K (Cool white fluorescent)',
   2.912 -             48: '3600K (Tungsten light-like)',
   2.913 -             256: 'Custom WB 1',
   2.914 -             257: 'Custom WB 2',
   2.915 -             258: 'Custom WB 3',
   2.916 -             259: 'Custom WB 4',
   2.917 -             512: 'Custom WB 5400K',
   2.918 -             513: 'Custom WB 2900K',
   2.919 -             514: 'Custom WB 8000K', }),
   2.920 -    0x0501: Tag('WhiteBalanceTemperature'),
   2.921 -    0x0502: Tag('WhiteBalanceBracket'),
   2.922 -    0x0503: Tag('CustomSaturation'), # (3 numbers: 1. CS Value, 2. Min, 3. Max)
   2.923 -    0x0504: TagDict('ModifiedSaturation',
   2.924 -             {0: 'Off',
   2.925 -              1: 'CM1 (Red Enhance)',
   2.926 -              2: 'CM2 (Green Enhance)',
   2.927 -              3: 'CM3 (Blue Enhance)',
   2.928 -              4: 'CM4 (Skin Tones)'}),
   2.929 -    0x0505: Tag('ContrastSetting'), # (3 numbers: 1. Contrast, 2. Min, 3. Max)
   2.930 -    0x0506: Tag('SharpnessSetting'), # (3 numbers: 1. Sharpness, 2. Min, 3. Max)
   2.931 -    0x0507: TagDict('ColorSpace',
   2.932 -             {0: 'sRGB',
   2.933 -              1: 'Adobe RGB',
   2.934 -              2: 'Pro Photo RGB'}),
   2.935 -    0x0509: TagDict('SceneMode',
   2.936 -             {0: 'Standard',
   2.937 -              6: 'Auto',
   2.938 -              7: 'Sport',
   2.939 -              8: 'Portrait',
   2.940 -              9: 'Landscape+Portrait',
   2.941 -             10: 'Landscape',
   2.942 -             11: 'Night scene',
   2.943 -             13: 'Panorama',
   2.944 -             16: 'Landscape+Portrait',
   2.945 -             17: 'Night+Portrait',
   2.946 -             19: 'Fireworks',
   2.947 -             20: 'Sunset',
   2.948 -             22: 'Macro',
   2.949 -             25: 'Documents',
   2.950 -             26: 'Museum',
   2.951 -             28: 'Beach&Snow',
   2.952 -             30: 'Candle',
   2.953 -             35: 'Underwater Wide1',
   2.954 -             36: 'Underwater Macro',
   2.955 -             39: 'High Key',
   2.956 -             40: 'Digital Image Stabilization',
   2.957 -             44: 'Underwater Wide2',
   2.958 -             45: 'Low Key',
   2.959 -             46: 'Children',
   2.960 -             48: 'Nature Macro'}),
   2.961 -    0x050a: TagDict('NoiseReduction',
   2.962 -             {0: 'Off',
   2.963 -              1: 'Noise Reduction',
   2.964 -              2: 'Noise Filter',
   2.965 -              3: 'Noise Reduction + Noise Filter',
   2.966 -              4: 'Noise Filter (ISO Boost)',
   2.967 -              5: 'Noise Reduction + Noise Filter (ISO Boost)'}),
   2.968 -    0x050b: TagDict('DistortionCorrection',
   2.969 -             {0: 'Off',
   2.970 -              1: 'On'}),
   2.971 -    0x050c: TagDict('ShadingCompensation',
   2.972 -             {0: 'Off',
   2.973 -              1: 'On'}),
   2.974 -    0x050d: Tag('CompressionFactor'),
   2.975 -    0x050f: TagDict('Gradation',
   2.976 -             {'-1 -1 1': 'Low Key',
   2.977 -              '0 -1 1': 'Normal',
   2.978 -              '1 -1 1': 'High Key'}),
   2.979 -    0x0520: TagDict('PictureMode',
   2.980 -             {1: 'Vivid',
   2.981 -              2: 'Natural',
   2.982 -              3: 'Muted',
   2.983 -              256: 'Monotone',
   2.984 -              512: 'Sepia'}),
   2.985 -    0x0521: Tag('PictureModeSaturation'),
   2.986 -    0x0522: Tag('PictureModeHue?'),
   2.987 -    0x0523: Tag('PictureModeContrast'),
   2.988 -    0x0524: Tag('PictureModeSharpness'),
   2.989 -    0x0525: TagDict('PictureModeBWFilter',
   2.990 -             {0: 'n/a',
   2.991 -              1: 'Neutral',
   2.992 -              2: 'Yellow',
   2.993 -              3: 'Orange',
   2.994 -              4: 'Red',
   2.995 -              5: 'Green'}),
   2.996 -    0x0526: TagDict('PictureModeTone',
   2.997 -             {0: 'n/a',
   2.998 -              1: 'Neutral',
   2.999 -              2: 'Sepia',
  2.1000 -              3: 'Blue',
  2.1001 -              4: 'Purple',
  2.1002 -              5: 'Green'}),
  2.1003 -    0x0600: Tag('Sequence'), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits
  2.1004 -    0x0601: Tag('PanoramaMode'), # (2 numbers: 1. Mode, 2. Shot number)
  2.1005 -    0x0603: ('ImageQuality2',
  2.1006 -             {1: 'SQ',
  2.1007 -              2: 'HQ',
  2.1008 -              3: 'SHQ',
  2.1009 -              4: 'RAW'}),
  2.1010 -    0x0901: Tag('ManometerReading'),
  2.1011 -    }
  2.1012 -
  2.1013 -
  2.1014 -MAKERNOTE_CASIO_TAGS={
  2.1015 -    0x0001: TagDict('RecordingMode',
  2.1016 -             {1: 'Single Shutter',
  2.1017 -              2: 'Panorama',
  2.1018 -              3: 'Night Scene',
  2.1019 -              4: 'Portrait',
  2.1020 -              5: 'Landscape'}),
  2.1021 -    0x0002: TagDict('Quality',
  2.1022 -             {1: 'Economy',
  2.1023 -              2: 'Normal',
  2.1024 -              3: 'Fine'}),
  2.1025 -    0x0003: TagDict('FocusingMode',
  2.1026 -             {2: 'Macro',
  2.1027 -              3: 'Auto Focus',
  2.1028 -              4: 'Manual Focus',
  2.1029 -              5: 'Infinity'}),
  2.1030 -    0x0004: TagDict('FlashMode',
  2.1031 -             {1: 'Auto',
  2.1032 -              2: 'On',
  2.1033 -              3: 'Off',
  2.1034 -              4: 'Red Eye Reduction'}),
  2.1035 -    0x0005: TagDict('FlashIntensity',
  2.1036 -             {11: 'Weak',
  2.1037 -              13: 'Normal',
  2.1038 -              15: 'Strong'}),
  2.1039 -    0x0006: Tag('Object Distance'),
  2.1040 -    0x0007: TagDict('WhiteBalance',
  2.1041 -             {1: 'Auto',
  2.1042 -              2: 'Tungsten',
  2.1043 -              3: 'Daylight',
  2.1044 -              4: 'Fluorescent',
  2.1045 -              5: 'Shade',
  2.1046 -              129: 'Manual'}),
  2.1047 -    0x000B: TagDict('Sharpness',
  2.1048 -             {0: 'Normal',
  2.1049 -              1: 'Soft',
  2.1050 -              2: 'Hard'}),
  2.1051 -    0x000C: TagDict('Contrast',
  2.1052 -             {0: 'Normal',
  2.1053 -              1: 'Low',
  2.1054 -              2: 'High'}),
  2.1055 -    0x000D: TagDict('Saturation',
  2.1056 -             {0: 'Normal',
  2.1057 -              1: 'Low',
  2.1058 -              2: 'High'}),
  2.1059 -    0x0014: TagDict('CCDSpeed',
  2.1060 -             {64: 'Normal',
  2.1061 -              80: 'Normal',
  2.1062 -              100: 'High',
  2.1063 -              125: '+1.0',
  2.1064 -              244: '+3.0',
  2.1065 -              250: '+2.0'}),
  2.1066 -    }
  2.1067 -
  2.1068 -MAKERNOTE_FUJIFILM_TAGS={
  2.1069 -    0x0000: Tag('NoteVersion'),
  2.1070 -    0x1000: Tag('Quality'),
  2.1071 -    0x1001: TagDict('Sharpness',
  2.1072 -             {1: 'Soft',
  2.1073 -              2: 'Soft',
  2.1074 -              3: 'Normal',
  2.1075 -              4: 'Hard',
  2.1076 -              5: 'Hard'}),
  2.1077 -    0x1002: TagDict('WhiteBalance',
  2.1078 -             {0: 'Auto',
  2.1079 -              256: 'Daylight',
  2.1080 -              512: 'Cloudy',
  2.1081 -              768: 'DaylightColor-Fluorescent',
  2.1082 -              769: 'DaywhiteColor-Fluorescent',
  2.1083 -              770: 'White-Fluorescent',
  2.1084 -              1024: 'Incandescent',
  2.1085 -              3840: 'Custom'}),
  2.1086 -    0x1003: TagDict('Color',
  2.1087 -             {0: 'Normal',
  2.1088 -              256: 'High',
  2.1089 -              512: 'Low'}),
  2.1090 -    0x1004: TagDict('Tone',
  2.1091 -             {0: 'Normal',
  2.1092 -              256: 'High',
  2.1093 -              512: 'Low'}),
  2.1094 -    0x1010: TagDict('FlashMode',
  2.1095 -             {0: 'Auto',
  2.1096 -              1: 'On',
  2.1097 -              2: 'Off',
  2.1098 -              3: 'Red Eye Reduction'}),
  2.1099 -    0x1011: Tag('FlashStrength'),
  2.1100 -    0x1020: TagDict('Macro',
  2.1101 -             {0: 'Off',
  2.1102 -              1: 'On'}),
  2.1103 -    0x1021: TagDict('FocusMode',
  2.1104 -             {0: 'Auto',
  2.1105 -              1: 'Manual'}),
  2.1106 -    0x1030: TagDict('SlowSync',
  2.1107 -             {0: 'Off',
  2.1108 -              1: 'On'}),
  2.1109 -    0x1031: TagDict('PictureMode',
  2.1110 -             {0: 'Auto',
  2.1111 -              1: 'Portrait',
  2.1112 -              2: 'Landscape',
  2.1113 -              4: 'Sports',
  2.1114 -              5: 'Night',
  2.1115 -              6: 'Program AE',
  2.1116 -              256: 'Aperture Priority AE',
  2.1117 -              512: 'Shutter Priority AE',
  2.1118 -              768: 'Manual Exposure'}),
  2.1119 -    0x1100: TagDict('MotorOrBracket',
  2.1120 -             {0: 'Off',
  2.1121 -              1: 'On'}),
  2.1122 -    0x1300: TagDict('BlurWarning',
  2.1123 -             {0: 'Off',
  2.1124 -              1: 'On'}),
  2.1125 -    0x1301: TagDict('FocusWarning',
  2.1126 -             {0: 'Off',
  2.1127 -              1: 'On'}),
  2.1128 -    0x1302: TagDict('AEWarning',
  2.1129 -             {0: 'Off',
  2.1130 -              1: 'On'}),
  2.1131 -    }
  2.1132 -
  2.1133 -MAKERNOTE_CANON_TAGS = {
  2.1134 -    0x0006: Tag('ImageType'),
  2.1135 -    0x0007: Tag('FirmwareVersion'),
  2.1136 -    0x0008: Tag('ImageNumber'),
  2.1137 -    0x0009: Tag('OwnerName'),
  2.1138 -    }
  2.1139 -
  2.1140 -# this is in element offset, name, optional value dictionary format
  2.1141 -MAKERNOTE_CANON_TAG_0x001 = {
  2.1142 -    1: TagDict('Macromode',
  2.1143 -        {1: 'Macro',
  2.1144 -         2: 'Normal'}),
  2.1145 -    2: Tag('SelfTimer'),
  2.1146 -    3: TagDict('Quality',
  2.1147 -        {2: 'Normal',
  2.1148 -         3: 'Fine',
  2.1149 -         5: 'Superfine'}),
  2.1150 -    4: TagDict('FlashMode',
  2.1151 -        {0: 'Flash Not Fired',
  2.1152 -         1: 'Auto',
  2.1153 -         2: 'On',
  2.1154 -         3: 'Red-Eye Reduction',
  2.1155 -         4: 'Slow Synchro',
  2.1156 -         5: 'Auto + Red-Eye Reduction',
  2.1157 -         6: 'On + Red-Eye Reduction',
  2.1158 -         16: 'external flash'}),
  2.1159 -    5: TagDict('ContinuousDriveMode',
  2.1160 -        {0: 'Single Or Timer',
  2.1161 -         1: 'Continuous'}),
  2.1162 -    7: TagDict('FocusMode',
  2.1163 -        {0: 'One-Shot',
  2.1164 -         1: 'AI Servo',
  2.1165 -         2: 'AI Focus',
  2.1166 -         3: 'MF',
  2.1167 -         4: 'Single',
  2.1168 -         5: 'Continuous',
  2.1169 -         6: 'MF'}),
  2.1170 -    10: TagDict('ImageSize',
  2.1171 -         {0: 'Large',
  2.1172 -          1: 'Medium',
  2.1173 -          2: 'Small'}),
  2.1174 -    11: TagDict('EasyShootingMode',
  2.1175 -         {0: 'Full Auto',
  2.1176 -          1: 'Manual',
  2.1177 -          2: 'Landscape',
  2.1178 -          3: 'Fast Shutter',
  2.1179 -          4: 'Slow Shutter',
  2.1180 -          5: 'Night',
  2.1181 -          6: 'B&W',
  2.1182 -          7: 'Sepia',
  2.1183 -          8: 'Portrait',
  2.1184 -          9: 'Sports',
  2.1185 -          10: 'Macro/Close-Up',
  2.1186 -          11: 'Pan Focus'}),
  2.1187 -    12: TagDict('DigitalZoom',
  2.1188 -         {0: 'None',
  2.1189 -          1: '2x',
  2.1190 -          2: '4x'}),
  2.1191 -    13: TagDict('Contrast',
  2.1192 -         {0xFFFF: 'Low',
  2.1193 -          0: 'Normal',
  2.1194 -          1: 'High'}),
  2.1195 -    14: TagDict('Saturation',
  2.1196 -         {0xFFFF: 'Low',
  2.1197 -          0: 'Normal',
  2.1198 -          1: 'High'}),
  2.1199 -    15: TagDict('Sharpness',
  2.1200 -         {0xFFFF: 'Low',
  2.1201 -          0: 'Normal',
  2.1202 -          1: 'High'}),
  2.1203 -    16: TagDict('ISO',
  2.1204 -         {0: 'See ISOSpeedRatings Tag',
  2.1205 -          15: 'Auto',
  2.1206 -          16: '50',
  2.1207 -          17: '100',
  2.1208 -          18: '200',
  2.1209 -          19: '400'}),
  2.1210 -    17: TagDict('MeteringMode',
  2.1211 -         {3: 'Evaluative',
  2.1212 -          4: 'Partial',
  2.1213 -          5: 'Center-weighted'}),
  2.1214 -    18: TagDict('FocusType',
  2.1215 -         {0: 'Manual',
  2.1216 -          1: 'Auto',
  2.1217 -          3: 'Close-Up (Macro)',
  2.1218 -          8: 'Locked (Pan Mode)'}),
  2.1219 -    19: TagDict('AFPointSelected',
  2.1220 -         {0x3000: 'None (MF)',
  2.1221 -          0x3001: 'Auto-Selected',
  2.1222 -          0x3002: 'Right',
  2.1223 -          0x3003: 'Center',
  2.1224 -          0x3004: 'Left'}),
  2.1225 -    20: TagDict('ExposureMode',
  2.1226 -         {0: 'Easy Shooting',
  2.1227 -          1: 'Program',
  2.1228 -          2: 'Tv-priority',
  2.1229 -          3: 'Av-priority',
  2.1230 -          4: 'Manual',
  2.1231 -          5: 'A-DEP'}),
  2.1232 -    23: Tag('LongFocalLengthOfLensInFocalUnits'),
  2.1233 -    24: Tag('ShortFocalLengthOfLensInFocalUnits'),
  2.1234 -    25: Tag('FocalUnitsPerMM'),
  2.1235 -    28: TagDict('FlashActivity',
  2.1236 -         {0: 'Did Not Fire',
  2.1237 -          1: 'Fired'}),
  2.1238 -    29: TagDict('FlashDetails',
  2.1239 -         {14: 'External E-TTL',
  2.1240 -          13: 'Internal Flash',
  2.1241 -          11: 'FP Sync Used',
  2.1242 -          7: '2nd("Rear")-Curtain Sync Used',
  2.1243 -          4: 'FP Sync Enabled'}),
  2.1244 -    32: TagDict('FocusMode',
  2.1245 -         {0: 'Single',
  2.1246 -          1: 'Continuous'}),
  2.1247 -    }
  2.1248 -
  2.1249 -MAKERNOTE_CANON_TAG_0x004 = {
  2.1250 -    7: TagDict('WhiteBalance',
  2.1251 -        {0: 'Auto',
  2.1252 -         1: 'Sunny',
  2.1253 -         2: 'Cloudy',
  2.1254 -         3: 'Tungsten',
  2.1255 -         4: 'Fluorescent',
  2.1256 -         5: 'Flash',
  2.1257 -         6: 'Custom'}),
  2.1258 -    9: Tag('SequenceNumber'),
  2.1259 -    14: Tag('AFPointUsed'),
  2.1260 -    15: TagDict('FlashBias',
  2.1261 -         {0xFFC0: '-2 EV',
  2.1262 -          0xFFCC: '-1.67 EV',
  2.1263 -          0xFFD0: '-1.50 EV',
  2.1264 -          0xFFD4: '-1.33 EV',
  2.1265 -          0xFFE0: '-1 EV',
  2.1266 -          0xFFEC: '-0.67 EV',
  2.1267 -          0xFFF0: '-0.50 EV',
  2.1268 -          0xFFF4: '-0.33 EV',
  2.1269 -          0x0000: '0 EV',
  2.1270 -          0x000C: '0.33 EV',
  2.1271 -          0x0010: '0.50 EV',
  2.1272 -          0x0014: '0.67 EV',
  2.1273 -          0x0020: '1 EV',
  2.1274 -          0x002C: '1.33 EV',
  2.1275 -          0x0030: '1.50 EV',
  2.1276 -          0x0034: '1.67 EV',
  2.1277 -          0x0040: '2 EV'}),
  2.1278 -    19: Tag('SubjectDistance'),
  2.1279 -    }
  2.1280 -
  2.1281 -# ratio object that eventually will be able to reduce itself to lowest
  2.1282 -# common denominator for printing
  2.1283 -# XXX: unused
  2.1284 -def gcd(a, b):
  2.1285 -    if b == 0:
  2.1286 -        return a
  2.1287 -    else:
  2.1288 -        return gcd(b, a % b)
  2.1289 -
  2.1290 -class Ratio:
  2.1291 -    def __init__(self, num, den):
  2.1292 -        self.num = num
  2.1293 -        self.den = den
  2.1294 -
  2.1295 -    def __repr__(self):
  2.1296 -        self.reduce()
  2.1297 -        if self.den == 1:
  2.1298 -            return str(self.num)
  2.1299 -        return '%d/%d' % (self.num, self.den)
  2.1300 -
  2.1301 -    def reduce(self):
  2.1302 -        div = gcd(self.num, self.den)
  2.1303 -        if div > 1:
  2.1304 -            self.num = self.num / div
  2.1305 -            self.den = self.den / div
  2.1306 -
  2.1307 -
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/degal/lib/exif.py	Sun Jun 14 16:10:30 2009 +0300
     3.3 @@ -0,0 +1,669 @@
     3.4 +"""
     3.5 +    A custom EXIF parsing module, aimed at high performance.
     3.6 +"""
     3.7 +
     3.8 +import struct, mmap, os
     3.9 +
    3.10 +from utils import lazy_load, lazy_load_iter
    3.11 +
    3.12 +def read_struct (file, fmt) :
    3.13 +    """
    3.14 +        Utility function to read data from the a file using struct
    3.15 +    """
    3.16 +    
    3.17 +    # length of data
    3.18 +    fmt_size = struct.calcsize(fmt)
    3.19 +    
    3.20 +    # get data
    3.21 +    file_data = file.read(fmt_size)
    3.22 +    
    3.23 +    # unpack single item, this should raise an error if file_data is too short
    3.24 +    return struct.unpack(fmt, file_data)
    3.25 +
    3.26 +class Buffer (object) :
    3.27 +    """
    3.28 +        Wraps a buffer object (anything that supports the python buffer protocol) for read-only access.
    3.29 +        
    3.30 +        Includes an offset for relative values, and an endianess for reading binary data.
    3.31 +    """
    3.32 +    
    3.33 +    def __init__ (self, obj, offset=None, size=None, struct_prefix='=') :
    3.34 +        """
    3.35 +            Create a new Buffer object with a new underlying buffer, created from the given object, offset and size.
    3.36 +            
    3.37 +            The endiannes is given in the form of a struct-module prefix, which should be one of '<' or '>'.
    3.38 +            Standard size/alignment are assumed.
    3.39 +        """
    3.40 +
    3.41 +        # store
    3.42 +        self.buf = buffer(obj, *(arg for arg in (offset, size) if arg is not None))
    3.43 +        self.offset = offset
    3.44 +        self.size = size
    3.45 +        self.prefix = struct_prefix
    3.46 +    
    3.47 +    def subregion (self, offset, length=None) :
    3.48 +        """
    3.49 +            Create a new sub-Buffer referencing a view of this buffer, at the given offset, and with the given
    3.50 +            length, if any, and the same struct_prefix.
    3.51 +        """
    3.52 +
    3.53 +        return Buffer(self.buf, offset, length, struct_prefix=self.prefix)
    3.54 +    
    3.55 +    def pread (self, offset, length) :
    3.56 +        """
    3.57 +            Read a random-access region of raw data
    3.58 +        """
    3.59 +
    3.60 +        return self.buf[offset:offset + length]
    3.61 +    
    3.62 +    def pread_struct (self, offset, fmt) :
    3.63 +        """
    3.64 +            Read structured data using the given struct format from the given offset.
    3.65 +        """
    3.66 +
    3.67 +        return struct.unpack_from(self.prefix + fmt, self.buf, offset=offset)
    3.68 +
    3.69 +    def pread_item (self, offset, fmt) :
    3.70 +        """
    3.71 +            Read a single item of structured data from the given offset.
    3.72 +        """
    3.73 +
    3.74 +        value, = self.pread_struct(offset, fmt)
    3.75 +
    3.76 +        return value
    3.77 +
    3.78 +    def iter_offsets (self, count, size, offset=0) :
    3.79 +        """
    3.80 +            Yield a series of offsets for `count` items of `size` bytes, beginning at `offset`.
    3.81 +        """
    3.82 +
    3.83 +        return xrange(offset, offset + count * size, size)
    3.84 +    
    3.85 +    def item_size (self, fmt) :
    3.86 +        """
    3.87 +            Returns the size in bytes of the given item format
    3.88 +        """
    3.89 +
    3.90 +        return struct.calcsize(self.prefix + fmt)
    3.91 +
    3.92 +    def unpack_item (self, fmt, data) :
    3.93 +        """
    3.94 +            Unpacks a single item from the given data
    3.95 +        """
    3.96 +
    3.97 +        value, = struct.unpack(self.prefix + fmt, data)
    3.98 +        
    3.99 +        return value
   3.100 +
   3.101 +def mmap_buffer (file, size) :
   3.102 +    """
   3.103 +        Create and return a new read-only mmap'd region
   3.104 +    """
   3.105 +
   3.106 +    return mmap.mmap(file.fileno(), size, access=mmap.ACCESS_READ)
   3.107 +
   3.108 +import exif_data
   3.109 +
   3.110 +class Tag (object) :
   3.111 +    """
   3.112 +        Represents a single Tag in an IFD
   3.113 +    """
   3.114 +
   3.115 +    def __init__ (self, ifd, offset, tag, type, count, data_raw) :
   3.116 +        """
   3.117 +            Build a Tag with the given binary items from the IFD entry
   3.118 +        """
   3.119 +        
   3.120 +        self.ifd = ifd
   3.121 +        self.offset = offset
   3.122 +        self.tag = tag
   3.123 +        self.type = type
   3.124 +        self.count = count
   3.125 +        self.data_raw = data_raw
   3.126 +        
   3.127 +        # lookup the type for this tag
   3.128 +        self.type_data = exif_data.FIELD_TYPES.get(type)
   3.129 +
   3.130 +        # unpack it
   3.131 +        if self.type_data :
   3.132 +            self.type_format, self.type_name, self.type_func = self.type_data
   3.133 +    
   3.134 +        # lookup the tag data for this tag
   3.135 +        self.tag_data = self.ifd.tag_dict.get(tag)
   3.136 +        
   3.137 +    @property
   3.138 +    def name (self) :
   3.139 +        """
   3.140 +            Lookup the name of this tag via its code, returns None if unknown.
   3.141 +        """
   3.142 +
   3.143 +        if self.tag_data :
   3.144 +            return self.tag_data.name
   3.145 +
   3.146 +        else :
   3.147 +            return None
   3.148 +    
   3.149 +    def is_subifd (self) :
   3.150 +        """
   3.151 +            Tests if this Tag is of a IFDTag type
   3.152 +        """
   3.153 +
   3.154 +        return self.tag_data and isinstance(self.tag_data, exif_data.IFDTag)
   3.155 +    
   3.156 +    @lazy_load
   3.157 +    def subifd (self) :
   3.158 +        """
   3.159 +            Load the sub-IFD for this tag
   3.160 +        """
   3.161 +
   3.162 +        # the tag_dict to use
   3.163 +        tag_dict = self.tag_data.ifd_tags or self.ifd.tag_dict
   3.164 +        
   3.165 +        # construct, return
   3.166 +        return self.ifd.exif._load_subifd(self, tag_dict)
   3.167 +
   3.168 +    def process_values (self, raw_values) :
   3.169 +        """
   3.170 +            Process the given raw values unpacked from the file.
   3.171 +        """
   3.172 +
   3.173 +        if self.type_data and self.type_func :
   3.174 +            # use the filter func
   3.175 +            return self.type_func(raw_values)
   3.176 +
   3.177 +        else :
   3.178 +            # nada, just leave them
   3.179 +            return raw_values
   3.180 +
   3.181 +    def readable_value (self, values) :
   3.182 +        """
   3.183 +            Convert the given values for this tag into a human-readable string.
   3.184 +
   3.185 +            Returns the comma-separated values by default.
   3.186 +        """
   3.187 +
   3.188 +        if self.tag_data :
   3.189 +            # map it
   3.190 +            return self.tag_data.map_values(values)
   3.191 +
   3.192 +        else :
   3.193 +            # default value-mapping
   3.194 +            return ", ".join(str(value) for value in values)
   3.195 +
   3.196 +# size of an IFD entry in bytes
   3.197 +IFD_ENTRY_SIZE = 12
   3.198 +
   3.199 +class IFD (Buffer) :
   3.200 +    """
   3.201 +        Represents an IFD (Image file directory) region in EXIF data.
   3.202 +    """
   3.203 +
   3.204 +    def __init__ (self, exif, buffer, tag_dict, **buffer_opts) :
   3.205 +        """
   3.206 +            Access the IFD data from the given bufferable object with given buffer opts.
   3.207 +
   3.208 +            This will read the `count` and `next_offset` values.
   3.209 +        """
   3.210 +
   3.211 +        # init
   3.212 +        super(IFD, self).__init__(buffer, **buffer_opts)
   3.213 +
   3.214 +        # store
   3.215 +        self.exif = exif
   3.216 +        self.tag_dict = tag_dict
   3.217 +        
   3.218 +        # read header
   3.219 +        self.count = self.pread_item(0, 'H')
   3.220 +
   3.221 +        # read next-offset
   3.222 +        self.next_offset = self.pread_item(0x02 + self.count * IFD_ENTRY_SIZE, 'I')
   3.223 +    
   3.224 +    @lazy_load_iter
   3.225 +    def tags (self) :
   3.226 +        """
   3.227 +            Iterate over all the Tag objects in this IFD
   3.228 +        """
   3.229 +        
   3.230 +        # read each tag
   3.231 +        for offset in self.iter_offsets(self.count, IFD_ENTRY_SIZE, 0x02) :
   3.232 +            # read the tag data
   3.233 +            tag, type, count, data_raw = self.pread_struct(offset, 'HHI4s')
   3.234 +            
   3.235 +            # yield the new Tag
   3.236 +            yield Tag(self, self.offset + offset, tag, type, count, data_raw)
   3.237 +
   3.238 +    def get_tags (self, filter=None) :
   3.239 +        """
   3.240 +            Yield a series of tag objects for this IFD and all sub-IFDs.
   3.241 +        """
   3.242 +        
   3.243 +        for tag in self.tags :
   3.244 +            if tag.is_subifd() :
   3.245 +                # recurse
   3.246 +                for subtag in tag.subifd.get_tags(filter=filter) :
   3.247 +                    yield subtag
   3.248 +            
   3.249 +            else :
   3.250 +                # normal tag
   3.251 +                yield tag
   3.252 +
   3.253 +class EXIF (Buffer) :
   3.254 +    """
   3.255 +        Represents the EXIF data embedded in some image file in the form of a Region.
   3.256 +    """
   3.257 +
   3.258 +    def __init__ (self, buffer, **buffer_opts) :
   3.259 +        """
   3.260 +            Access the EXIF data from the given bufferable object with the given buffer options.
   3.261 +        """
   3.262 +
   3.263 +        # init Buffer
   3.264 +        super(EXIF, self).__init__(buffer, **buffer_opts)
   3.265 +
   3.266 +        # store
   3.267 +        self.buffer = buffer
   3.268 +    
   3.269 +    @lazy_load_iter
   3.270 +    def ifds (self) :
   3.271 +        """
   3.272 +            Iterate over the primary IFDs in this EXIF.
   3.273 +        """
   3.274 +
   3.275 +        # starting offset
   3.276 +        offset = self.pread_item(0x04, 'I')
   3.277 +
   3.278 +        while offset :
   3.279 +            # create and read the IFD, operating on the right sub-buffer
   3.280 +            ifd = IFD(self, self.buf, exif_data.EXIF_TAGS, offset=offset)
   3.281 +
   3.282 +            # yield it
   3.283 +            yield ifd
   3.284 +
   3.285 +            # skip to next offset
   3.286 +            offset = ifd.next_offset
   3.287 +    
   3.288 +    def _load_subifd (self, tag, tag_dict) :
   3.289 +        """
   3.290 +            Creates and returns a sub-IFD for the given tag.
   3.291 +        """
   3.292 +
   3.293 +        # locate it
   3.294 +        offset, = self.tag_values_raw(tag)
   3.295 +
   3.296 +        # construct the new IFD
   3.297 +        return IFD(self, self.buf, tag_dict, offset=offset)
   3.298 +
   3.299 +    def tag_data_info (self, tag) :
   3.300 +        """
   3.301 +            Calculate the location, format and size of the given tag's data.
   3.302 +
   3.303 +            Returns a (fmt, offset, size) tuple.
   3.304 +        """
   3.305 +        # unknown tag?
   3.306 +        if not tag.type_data :
   3.307 +            return None
   3.308 +
   3.309 +        # data format
   3.310 +        if len(tag.type_format) == 1 :
   3.311 +            # let struct handle the count
   3.312 +            fmt = "%d%s" % (tag.count, tag.type_format)
   3.313 +
   3.314 +        else :
   3.315 +            # handle the count ourselves
   3.316 +            fmt = tag.type_format * tag.count
   3.317 +
   3.318 +        # size of the data
   3.319 +        size = self.item_size(fmt)
   3.320 +
   3.321 +        # inline or external?
   3.322 +        if size > 0x04 :
   3.323 +            # point at the external data
   3.324 +            offset = self.unpack_item('I', tag.data_raw)
   3.325 +
   3.326 +        else :
   3.327 +            # point at the inline data
   3.328 +            offset = tag.offset + 0x08
   3.329 +        
   3.330 +        return fmt, offset, size
   3.331 +
   3.332 +    def tag_values_raw (self, tag) :
   3.333 +        """
   3.334 +            Get the raw values for the given tag as a tuple.
   3.335 +
   3.336 +            Returns None if the tag could not be recognized.
   3.337 +        """
   3.338 +
   3.339 +        # find the data
   3.340 +        data_info = self.tag_data_info(tag)
   3.341 +
   3.342 +        # not found?
   3.343 +        if not data_info :
   3.344 +            return None
   3.345 +        
   3.346 +        # unpack
   3.347 +        data_fmt, data_offset, data_size = data_info
   3.348 +        
   3.349 +        # read values
   3.350 +        return self.pread_struct(data_offset, data_fmt)
   3.351 +    
   3.352 +    def tag_values (self, tag) :
   3.353 +        """
   3.354 +            Gets the processed values for the given tag as a list.
   3.355 +        """
   3.356 +
   3.357 +        # read + process
   3.358 +        return tag.process_values(self.tag_values_raw(tag))
   3.359 +
   3.360 +    def tag_value (self, tag) :
   3.361 +        """
   3.362 +            Return the human-readable string value for the given tag.
   3.363 +        """
   3.364 +        
   3.365 +        # load the raw values
   3.366 +        values = self.tag_values(tag)
   3.367 +
   3.368 +        # unknown?
   3.369 +        if not values :
   3.370 +            return ""
   3.371 +
   3.372 +        # return as comma-separated formatted string, yes
   3.373 +        return tag.readable_value(values)
   3.374 +    
   3.375 +    def get_main_tags (self, **opts) :
   3.376 +        """
   3.377 +            Get the tags for the main image's IFD as a dict.
   3.378 +        """
   3.379 +
   3.380 +        if not self.ifds :
   3.381 +            # weird case
   3.382 +            raise Exception("No IFD for main image found")
   3.383 +
   3.384 +        # the main IFD is always the first one
   3.385 +        main_ifd = self.ifds[0]
   3.386 +
   3.387 +        # do it
   3.388 +        return dict((tag.name, self.tag_value(tag)) for tag in main_ifd.get_tags(**opts))
   3.389 +
   3.390 +# mapping from two-byte TIFF byte order marker to struct prefix
   3.391 +TIFF_BYTE_ORDER = {
   3.392 +    'II': '<',
   3.393 +    'MM': '>',
   3.394 +}
   3.395 +
   3.396 +# "An arbitrary but carefully chosen number (42) that further identifies the file as a TIFF file"
   3.397 +TIFF_BYTEORDER_MAGIC = 42 
   3.398 +
   3.399 +def tiff_load (file, length=0, **opts) :
   3.400 +    """
   3.401 +        Load the Exif/TIFF data from the given file at its current position with optional length, using exif_load.
   3.402 +    """
   3.403 +
   3.404 +    # all Exif data offsets are relative to the beginning of this TIFF header
   3.405 +    offset = file.tell()
   3.406 +
   3.407 +    # mmap the region for the EXIF data
   3.408 +    buffer = mmap_buffer(file, length)
   3.409 +
   3.410 +    # read byte-order header
   3.411 +    byte_order = file.read(2)
   3.412 +
   3.413 +    # map to struct prefix
   3.414 +    struct_prefix = TIFF_BYTE_ORDER[byte_order]
   3.415 +
   3.416 +    # validate
   3.417 +    check_value, = read_struct(file, struct_prefix + 'H')
   3.418 +
   3.419 +    if check_value != TIFF_BYTEORDER_MAGIC  :
   3.420 +        raise Exception("Invalid byte-order for TIFF: %2c -> %d" % (byte_order, check_value))
   3.421 +
   3.422 +    # build and return the EXIF object with the correct offset/size from the mmap region
   3.423 +    return EXIF(buffer, offset=offset, size=length, **opts)
   3.424 +
   3.425 +# the JPEG markers that don't have any data
   3.426 +JPEG_NOSIZE_MARKERS = (0xD8, 0xD9)
   3.427 +
   3.428 +# the first marker in a JPEG File
   3.429 +JPEG_START_MARKER = 0xD8
   3.430 +
   3.431 +# the JPEG APP1 marker used for EXIF
   3.432 +JPEG_EXIF_MARKER = 0xE1
   3.433 +
   3.434 +# the JPEG APP1 Exif header
   3.435 +JPEG_EXIF_HEADER = "Exif\x00\x00"
   3.436 +
   3.437 +def jpeg_markers (file) :
   3.438 +    """
   3.439 +        Iterate over the JPEG markers in the given file, yielding (type_byte, size) tuples.
   3.440 +
   3.441 +        The size fields will be 0 for markers with no data. The file will be positioned at the beginning of the data
   3.442 +        region, and may be seek'd around if needed.
   3.443 +        
   3.444 +        XXX: find a real implementation of this somewhere?
   3.445 +    """
   3.446 +
   3.447 +    while True :
   3.448 +        # read type
   3.449 +        marker_byte, marker_type = read_struct(file, '!BB')
   3.450 +        
   3.451 +        # validate
   3.452 +        if marker_byte != 0xff :
   3.453 +            raise Exception("Not a JPEG marker: %x%x" % (marker_byte, marker_type))
   3.454 +
   3.455 +        # special cases for no data
   3.456 +        if marker_type in JPEG_NOSIZE_MARKERS :
   3.457 +            size = 0
   3.458 +
   3.459 +        else :
   3.460 +            # read size field
   3.461 +            size, = read_struct(file, '!H')
   3.462 +            
   3.463 +            # validate
   3.464 +            if size < 0x02 :
   3.465 +                raise Exception("Invalid size for marker %x%x: %x" % (marker_byte, marker_type, size))
   3.466 +            
   3.467 +            else :
   3.468 +                # do not count the size field itself
   3.469 +                size = size - 2
   3.470 +            
   3.471 +        # ok, data is at current position
   3.472 +        offset = file.tell()
   3.473 +        
   3.474 +        # yield
   3.475 +        yield marker_type, size
   3.476 +
   3.477 +        # absolute seek to next marker
   3.478 +        file.seek(offset + size)
   3.479 +
   3.480 +def jpeg_find_exif (file) :
   3.481 +    """
   3.482 +        Find the Exif/TIFF section in the given JPEG file.
   3.483 +
   3.484 +        If found, the file will be seek'd to the start of the Exif/TIFF header, and the size of the Exif/TIFF data will
   3.485 +        be returned.
   3.486 +        
   3.487 +        Returns None if no EXIF section was found.
   3.488 +    """
   3.489 +
   3.490 +    for count, (marker, size) in enumerate(jpeg_markers(file)) :
   3.491 +        # verify that it's a JPEG file
   3.492 +        if count == 0 :
   3.493 +            # must start with the right marker
   3.494 +            if marker != JPEG_START_MARKER :
   3.495 +                raise Exception("JPEG file must start with 0xFF%02x marker" % (marker, ))
   3.496 +
   3.497 +        # look for APP1 marker (0xE1) with EXIF signature
   3.498 +        elif marker == JPEG_EXIF_MARKER and file.read(len(JPEG_EXIF_HEADER)) == JPEG_EXIF_HEADER:
   3.499 +            # skipped the initial Exif marker signature
   3.500 +            return size - len(JPEG_EXIF_HEADER)
   3.501 +
   3.502 +    # nothing
   3.503 +    return None
   3.504 +
   3.505 +def jpeg_load (file, **opts) :
   3.506 +    """
   3.507 +        Loads the embedded Exif TIFF data from the given JPEG file using tiff_load.
   3.508 +
   3.509 +        Returns None if no EXIF data could be found.
   3.510 +    """
   3.511 +        
   3.512 +    # look for the right section
   3.513 +    size = jpeg_find_exif(file)
   3.514 +    
   3.515 +    # not found?
   3.516 +    if not size :
   3.517 +        # nothing
   3.518 +        return
   3.519 +
   3.520 +    else :    
   3.521 +        # load it as TIFF data
   3.522 +        return tiff_load(file, size, **opts)
   3.523 +
   3.524 +def load_path (path, **opts) :
   3.525 +    """
   3.526 +        Loads an EXIF object from the given filesystem path.
   3.527 +
   3.528 +        Returns None if it could not be parsed.
   3.529 +    """
   3.530 +    
   3.531 +    # file extension
   3.532 +    root, fext = os.path.splitext(path)
   3.533 +
   3.534 +    # map
   3.535 +    func = {
   3.536 +        '.jpeg':    jpeg_load,
   3.537 +        '.jpg':     jpeg_load,
   3.538 +        '.tiff':    tiff_load,  # XXX: untested
   3.539 +    }.get(fext.lower())
   3.540 +    
   3.541 +    # not recognized?
   3.542 +    if not func :
   3.543 +        # XXX: sniff the file
   3.544 +        return None
   3.545 +
   3.546 +    # open it
   3.547 +    file = open(path, 'rb')
   3.548 +
   3.549 +    # try and load it
   3.550 +    return func(file, **opts)
   3.551 +
   3.552 +def dump_tag (exif, i, tag, indent=2) :
   3.553 +    """
   3.554 +        Dump the given tag
   3.555 +    """
   3.556 +
   3.557 +    data_info = exif.tag_data_info(tag)
   3.558 +
   3.559 +    if data_info :
   3.560 +        data_fmt, data_offset, data_size = data_info
   3.561 +
   3.562 +    else :
   3.563 +        data_fmt = data_offset = data_size = None
   3.564 +
   3.565 +    print "%sTag:%d offset=%#04x(%#08x), tag=%d/%s, type=%d/%s, count=%d, fmt=%s, offset=%#04x, size=%s, is_subifd=%s:" % (
   3.566 +        '\t'*indent,
   3.567 +        i, 
   3.568 +        tag.offset, tag.offset + exif.offset,
   3.569 +        tag.tag, tag.name or '???',
   3.570 +        tag.type, tag.type_name if tag.type_data else '???',
   3.571 +        tag.count,
   3.572 +        data_fmt, data_offset, data_size,
   3.573 +        tag.is_subifd(),
   3.574 +    )
   3.575 +    
   3.576 +    if tag.is_subifd() :
   3.577 +        # recurse
   3.578 +        dump_ifd(exif, 0, tag.subifd, indent + 1)
   3.579 +
   3.580 +    else :
   3.581 +        # dump each value
   3.582 +        values = exif.tag_values(tag)
   3.583 +        
   3.584 +        for i, value in enumerate(values) :
   3.585 +            print "%s\t%02d: %.120r" % ('\t'*indent, i, value)
   3.586 +        
   3.587 +        # and then the readable one
   3.588 +        print "%s\t->  %.120s" % ('\t'*indent, tag.readable_value(values), )
   3.589 +
   3.590 +
   3.591 +def dump_ifd (exif, i, ifd, indent=1) :
   3.592 +    """
   3.593 +        Dump the given IFD, recursively
   3.594 +    """
   3.595 +
   3.596 +    print "%sIFD:%d offset=%#04x(%#08x), count=%d, next=%d:" % (
   3.597 +        '\t'*indent,
   3.598 +        i, 
   3.599 +        ifd.offset, ifd.offset + exif.offset,
   3.600 +        ifd.count, 
   3.601 +        ifd.next_offset
   3.602 +    )
   3.603 +    
   3.604 +    for i, tag in enumerate(ifd.tags) :
   3.605 +        # dump
   3.606 +        dump_tag(exif, i, tag, indent + 1)
   3.607 +
   3.608 +
   3.609 +def dump_exif (exif) :
   3.610 +    """
   3.611 +        Dump all tags from the given EXIF object to stdout
   3.612 +    """
   3.613 +
   3.614 +    print "EXIF offset=%#08x, size=%d:" % (exif.offset, exif.size)
   3.615 +
   3.616 +    for i, ifd in enumerate(exif.ifds) :
   3.617 +        # dump
   3.618 +        dump_ifd(exif, i, ifd)
   3.619 +
   3.620 +
   3.621 +def list_tags (exif) :
   3.622 +    """
   3.623 +        Print a neat listing of tags to stdout
   3.624 +    """
   3.625 +
   3.626 +    for k, v in exif.get_main_tags().iteritems() :
   3.627 +        print "%30s: %s" % (k, v)
   3.628 +
   3.629 +def main_path (path, dump) :
   3.630 +    # dump path
   3.631 +    print "%s: " % path
   3.632 +
   3.633 +    # try and load it
   3.634 +    exif = load_path(path)
   3.635 + 
   3.636 +    if not exif :
   3.637 +        raise Exception("No EXIF data found")
   3.638 +
   3.639 +    if dump :
   3.640 +        # dump everything
   3.641 +        dump_exif(exif)
   3.642 +    
   3.643 +    else :
   3.644 +        # list them
   3.645 +        list_tags(exif)   
   3.646 +
   3.647 +
   3.648 +def main (paths, dump=False) :
   3.649 +    """
   3.650 +        Load and dump EXIF data from the given path
   3.651 +    """
   3.652 +    
   3.653 +    # handle each one
   3.654 +    for path in paths :
   3.655 +        main_path(path, dump=dump)
   3.656 +
   3.657 +if __name__ == '__main__' :
   3.658 +    import getopt
   3.659 +    from sys import argv
   3.660 +    
   3.661 +    # defaults
   3.662 +    dump = False
   3.663 +
   3.664 +    # parse args
   3.665 +    opts, args = getopt.getopt(argv[1:], "d", ["dump"])
   3.666 +
   3.667 +    for opt, val in opts :
   3.668 +        if opt in ('-d', "--dump") :
   3.669 +            dump = True
   3.670 +
   3.671 +    main(args, dump=dump)
   3.672 +
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/degal/lib/exif_data.py	Sun Jun 14 16:10:30 2009 +0300
     4.3 @@ -0,0 +1,1304 @@
     4.4 +#!/usr/bin/env python
     4.5 +# -*- coding: utf-8 -*-
     4.6 +
     4.7 +"""
     4.8 +    EXIF file format data, including tag names, types, etc.
     4.9 +
    4.10 +    Most of this was copied with modifications from EXIFpy:
    4.11 +        # Library to extract EXIF information from digital camera image files
    4.12 +        # http://sourceforge.net/projects/exif-py/
    4.13 +        #
    4.14 +        # VERSION 1.1.0
    4.15 +        #
    4.16 +        # Copyright (c) 2002-2007 Gene Cash All rights reserved
    4.17 +        # Copyright (c) 2007-2008 Ianaré Sévi All rights reserved
    4.18 +        #
    4.19 +        # Redistribution and use in source and binary forms, with or without
    4.20 +        # modification, are permitted provided that the following conditions
    4.21 +        # are met:
    4.22 +        #
    4.23 +        #  1. Redistributions of source code must retain the above copyright
    4.24 +        #     notice, this list of conditions and the following disclaimer.
    4.25 +        #
    4.26 +        #  2. Redistributions in binary form must reproduce the above
    4.27 +        #     copyright notice, this list of conditions and the following
    4.28 +        #     disclaimer in the documentation and/or other materials provided
    4.29 +        #     with the distribution.
    4.30 +        #
    4.31 +        #  3. Neither the name of the authors nor the names of its contributors
    4.32 +        #     may be used to endorse or promote products derived from this
    4.33 +        #     software without specific prior written permission.
    4.34 +        #
    4.35 +        # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    4.36 +        # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    4.37 +        # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    4.38 +        # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    4.39 +        # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    4.40 +        # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    4.41 +        # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    4.42 +        # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    4.43 +        # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    4.44 +        # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    4.45 +        # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    4.46 +
    4.47 +"""
    4.48 +
    4.49 +import decimal, itertools
    4.50 +
    4.51 +def filter_ascii (values) :
    4.52 +    """
    4.53 +        Default post-filter for ASCII values.
    4.54 +
    4.55 +        This takes a single item of string data, splits it up into strings by ASCII-NUL.
    4.56 +
    4.57 +        These sub-strings are then decoded into unicode as ASCII, and stripped.
    4.58 +    """
    4.59 +
    4.60 +    return [string.decode('ascii', 'replace').rstrip() for string in values[0].split('\x00') if string]
    4.61 +
    4.62 +def build_ratio (num, denom) :
    4.63 +    """
    4.64 +        Builds a Decimal ratio out of the given numerator and denominator
    4.65 +    """
    4.66 +    
    4.67 +    # XXX: this may be slow
    4.68 +    return decimal.Decimal(num) / decimal.Decimal(denom)
    4.69 +
    4.70 +def filter_ratio (values) :
    4.71 +    """
    4.72 +        Default post-filter for Ratio values.
    4.73 +
    4.74 +        This takes the pairs of numerator/denominator values and builds Decimals out of them
    4.75 +    """
    4.76 +
    4.77 +    return [build_ratio(values[i], values[i + 1]) for i in xrange(0, len(values), 2)]
    4.78 +
    4.79 +
    4.80 +# IFD Tag type information, indexed by code
    4.81 +#  { type_code: (type_fmt, name, filter_func) }
    4.82 +#
    4.83 +# type_fmt's that are one char will be prefixed with the count for use with struct.unpack, those with more chars will
    4.84 +# be repeated as many times for use with struct.unpack.
    4.85 +FIELD_TYPES = {
    4.86 +#    0x0000: (None,  'Proprietary'   ), # ??? no such type
    4.87 +    0x0001: ('B',   'Byte',         None            ),
    4.88 +    0x0002: ('s',   'ASCII',        filter_ascii    ),
    4.89 +    0x0003: ('H',   'Short',        None            ),
    4.90 +    0x0004: ('L',   'Long',         None            ),
    4.91 +    0x0005: ('LL',  'Ratio',        filter_ratio    ),
    4.92 +    0x0006: ('b',   'Signed Byte',  None            ),
    4.93 +    0x0007: ('s',   'Undefined',    None            ),
    4.94 +    0x0008: ('h',   'Signed Short', None            ),
    4.95 +    0x0009: ('l',   'Signed Long',  None            ),
    4.96 +    0x000A: ('ll',  'Signed Ratio', filter_ratio    ),
    4.97 +}
    4.98 +
    4.99 +# magic value to indicate sub-IFDs
   4.100 +SUB_IFD_MAGIC = object()
   4.101 +
   4.102 +
   4.103 +class Tag (object) :
   4.104 +    """
   4.105 +        Represents an Exif Tag
   4.106 +    """
   4.107 +
   4.108 +    def __init__ (self, name) :
   4.109 +        """
   4.110 +            Build Exif tag with given name, and optional external values-filter function.
   4.111 +        """
   4.112 +
   4.113 +        self.name = name
   4.114 +
   4.115 +    def map_values (self, values) :
   4.116 +        """
   4.117 +            Map the given tag value to a printable string using the given value spec.
   4.118 +        """
   4.119 +
   4.120 +        # default value-mapping
   4.121 +        return ", ".join(str(value) for value in values)
   4.122 +
   4.123 +class TagDict (Tag) :
   4.124 +    """
   4.125 +        A tag with a dict mapping values to names
   4.126 +    """
   4.127 +
   4.128 +    def __init__ (self, name, values_dict) :
   4.129 +        super(TagDict, self).__init__(name)
   4.130 +
   4.131 +        self.values_dict = values_dict
   4.132 +
   4.133 +    def map_values (self, values) :
   4.134 +        """
   4.135 +            Map the values through our dict, defaulting to the repr.
   4.136 +        """
   4.137 +
   4.138 +        return ", ".join(self.values_dict.get(value, repr(value)) for value in values)
   4.139 +
   4.140 +class TagFunc (Tag) :
   4.141 +    """
   4.142 +        A tag with a simple function mapping values to names
   4.143 +    """
   4.144 +
   4.145 +    def __init__ (self, name, values_func) :
   4.146 +        super(TagFunc, self).__init__(name)
   4.147 +
   4.148 +        self.values_func = values_func
   4.149 +
   4.150 +    def map_values (self, values) :
   4.151 +        """
   4.152 +            Map the values through our func
   4.153 +        """
   4.154 +
   4.155 +        return self.values_func(values)
   4.156 +
   4.157 +class IFDTag (Tag) :
   4.158 +    """
   4.159 +        A tag that references another IFD
   4.160 +    """
   4.161 +
   4.162 +    def __init__ (self, name, ifd_tags=None) :
   4.163 +        """
   4.164 +            A tag that points to another IFD block. `ifd_tags`, if given, lists the tags for that block, otherwise,
   4.165 +            the same tags as for the current block are used.
   4.166 +        """
   4.167 +
   4.168 +        super(IFDTag, self).__init__(name)
   4.169 +
   4.170 +        self.ifd_tags = ifd_tags
   4.171 +
   4.172 +USER_COMMENT_CHARSETS = {
   4.173 +    'ASCII':    ('ascii',   'replace'   ),
   4.174 +    'JIS':      ('jis',     'error'     ),
   4.175 +
   4.176 +    # XXX: WTF? What kind of charset is 'Unicode' supposed to be?
   4.177 +    # UTF-16? Little-endian? Big-endian?
   4.178 +    # Confusing reigns: http://www.cpanforum.com/threads/7329
   4.179 +    'UNICODE':  ('utf16',   'error'     ),
   4.180 +}
   4.181 +
   4.182 +
   4.183 +def decode_UserComment (values) :
   4.184 +    """
   4.185 +        A UserComment field starts with an eight-byte encoding designator.
   4.186 +    """
   4.187 +
   4.188 +    # single binary string
   4.189 +    value, = values
   4.190 +    
   4.191 +    # split up
   4.192 +    charset, comment_raw = value[:8], value[8:]
   4.193 +
   4.194 +    # strip NILs
   4.195 +    charset = charset.rstrip('\x00')
   4.196 +
   4.197 +    # map
   4.198 +    encoding, replace = USER_COMMENT_CHARSETS.get(charset, ('ascii', 'replace'))
   4.199 +    
   4.200 +    # decode
   4.201 +    return [comment_raw.decode(encoding, replace)]
   4.202 +
   4.203 +# Mappings of Exif tag codes to name and decoding information.
   4.204 +# { tag : (name, value_dict/value_func/None/SUB_IFD_MAGIC) }
   4.205 +#
   4.206 +# name is the official Exif tag name
   4.207 +# value_dict is a { value: value_name } mapping for human-readable values
   4.208 +# value_func is a `(values) -> values` mapping function which *overrides* the tag's type_func.
   4.209 +#   XXX: or does it?
   4.210 +# SUB_IFD_MAGIC signifies that this IFD points to 
   4.211 +# otherwise, the value is left as-is.
   4.212 +# interoperability tags
   4.213 +INTR_TAGS = {
   4.214 +    0x0001: Tag('InteroperabilityIndex'),
   4.215 +    0x0002: Tag('InteroperabilityVersion'),
   4.216 +    0x1000: Tag('RelatedImageFileFormat'),
   4.217 +    0x1001: Tag('RelatedImageWidth'),
   4.218 +    0x1002: Tag('RelatedImageLength'),
   4.219 +    }
   4.220 +
   4.221 +# GPS tags (not used yet, haven't seen camera with GPS)
   4.222 +GPS_TAGS = {
   4.223 +    0x0000: Tag('GPSVersionID'),
   4.224 +    0x0001: Tag('GPSLatitudeRef'),
   4.225 +    0x0002: Tag('GPSLatitude'),
   4.226 +    0x0003: Tag('GPSLongitudeRef'),
   4.227 +    0x0004: Tag('GPSLongitude'),
   4.228 +    0x0005: Tag('GPSAltitudeRef'),
   4.229 +    0x0006: Tag('GPSAltitude'),
   4.230 +    0x0007: Tag('GPSTimeStamp'),
   4.231 +    0x0008: Tag('GPSSatellites'),
   4.232 +    0x0009: Tag('GPSStatus'),
   4.233 +    0x000A: Tag('GPSMeasureMode'),
   4.234 +    0x000B: Tag('GPSDOP'),
   4.235 +    0x000C: Tag('GPSSpeedRef'),
   4.236 +    0x000D: Tag('GPSSpeed'),
   4.237 +    0x000E: Tag('GPSTrackRef'),
   4.238 +    0x000F: Tag('GPSTrack'),
   4.239 +    0x0010: Tag('GPSImgDirectionRef'),
   4.240 +    0x0011: Tag('GPSImgDirection'),
   4.241 +    0x0012: Tag('GPSMapDatum'),
   4.242 +    0x0013: Tag('GPSDestLatitudeRef'),
   4.243 +    0x0014: Tag('GPSDestLatitude'),
   4.244 +    0x0015: Tag('GPSDestLongitudeRef'),
   4.245 +    0x0016: Tag('GPSDestLongitude'),
   4.246 +    0x0017: Tag('GPSDestBearingRef'),
   4.247 +    0x0018: Tag('GPSDestBearing'),
   4.248 +    0x0019: Tag('GPSDestDistanceRef'),
   4.249 +    0x001A: Tag('GPSDestDistance'),
   4.250 +    0x001D: Tag('GPSDate'),
   4.251 +    }
   4.252 +
   4.253 +
   4.254 +EXIF_TAGS = {
   4.255 +    0x0100: Tag('ImageWidth'),
   4.256 +    0x0101: Tag('ImageLength'),
   4.257 +    0x0102: Tag('BitsPerSample'),
   4.258 +    0x0103: TagDict('Compression',
   4.259 +             {1: 'Uncompressed',
   4.260 +              2: 'CCITT 1D',
   4.261 +              3: 'T4/Group 3 Fax',
   4.262 +              4: 'T6/Group 4 Fax',
   4.263 +              5: 'LZW',
   4.264 +              6: 'JPEG (old-style)',
   4.265 +              7: 'JPEG',
   4.266 +              8: 'Adobe Deflate',
   4.267 +              9: 'JBIG B&W',
   4.268 +              10: 'JBIG Color',
   4.269 +              32766: 'Next',
   4.270 +              32769: 'Epson ERF Compressed',
   4.271 +              32771: 'CCIRLEW',
   4.272 +              32773: 'PackBits',
   4.273 +              32809: 'Thunderscan',
   4.274 +              32895: 'IT8CTPAD',
   4.275 +              32896: 'IT8LW',
   4.276 +              32897: 'IT8MP',
   4.277 +              32898: 'IT8BL',
   4.278 +              32908: 'PixarFilm',
   4.279 +              32909: 'PixarLog',
   4.280 +              32946: 'Deflate',
   4.281 +              32947: 'DCS',
   4.282 +              34661: 'JBIG',
   4.283 +              34676: 'SGILog',
   4.284 +              34677: 'SGILog24',
   4.285 +              34712: 'JPEG 2000',
   4.286 +              34713: 'Nikon NEF Compressed',
   4.287 +              65000: 'Kodak DCR Compressed',
   4.288 +              65535: 'Pentax PEF Compressed'}),
   4.289 +    0x0106: Tag('PhotometricInterpretation'),
   4.290 +    0x0107: Tag('Thresholding'),
   4.291 +    0x010A: Tag('FillOrder'),
   4.292 +    0x010D: Tag('DocumentName'),
   4.293 +    0x010E: Tag('ImageDescription'),
   4.294 +    0x010F: Tag('Make'),
   4.295 +    0x0110: Tag('Model'),
   4.296 +    0x0111: Tag('StripOffsets'),
   4.297 +    0x0112: TagDict('Orientation',
   4.298 +             {1: 'Horizontal (normal)',
   4.299 +              2: 'Mirrored horizontal',
   4.300 +              3: 'Rotated 180',
   4.301 +              4: 'Mirrored vertical',
   4.302 +              5: 'Mirrored horizontal then rotated 90 CCW',
   4.303 +              6: 'Rotated 90 CW',
   4.304 +              7: 'Mirrored horizontal then rotated 90 CW',
   4.305 +              8: 'Rotated 90 CCW'}),
   4.306 +    0x0115: Tag('SamplesPerPixel'),
   4.307 +    0x0116: Tag('RowsPerStrip'),
   4.308 +    0x0117: Tag('StripByteCounts'),
   4.309 +    0x011A: Tag('XResolution'),
   4.310 +    0x011B: Tag('YResolution'),
   4.311 +    0x011C: Tag('PlanarConfiguration'),
   4.312 +    0x011D: Tag('PageName'),
   4.313 +    0x0128: TagDict('ResolutionUnit',
   4.314 +             {1: 'Not Absolute',
   4.315 +              2: 'Pixels/Inch',
   4.316 +              3: 'Pixels/Centimeter'}),
   4.317 +    0x012D: Tag('TransferFunction'),
   4.318 +    0x0131: Tag('Software'),
   4.319 +    0x0132: Tag('DateTime'),
   4.320 +    0x013B: Tag('Artist'),
   4.321 +    0x013E: Tag('WhitePoint'),
   4.322 +    0x013F: Tag('PrimaryChromaticities'),
   4.323 +    0x0156: Tag('TransferRange'),
   4.324 +    0x0200: Tag('JPEGProc'),
   4.325 +    0x0201: Tag('JPEGInterchangeFormat'),
   4.326 +    0x0202: Tag('JPEGInterchangeFormatLength'),
   4.327 +    0x0211: Tag('YCbCrCoefficients'),
   4.328 +    0x0212: Tag('YCbCrSubSampling'),
   4.329 +    0x0213: TagDict('YCbCrPositioning',
   4.330 +             {1: 'Centered',
   4.331 +              2: 'Co-sited'}),
   4.332 +    0x0214: Tag('ReferenceBlackWhite'),
   4.333 +    
   4.334 +    0x4746: Tag('Rating'),
   4.335 +    
   4.336 +    0x828D: Tag('CFARepeatPatternDim'),
   4.337 +    0x828E: Tag('CFAPattern'),
   4.338 +    0x828F: Tag('BatteryLevel'),
   4.339 +    0x8298: Tag('Copyright'),
   4.340 +    0x829A: Tag('ExposureTime'),
   4.341 +    0x829D: Tag('FNumber'),
   4.342 +    0x83BB: Tag('IPTC/NAA'),
   4.343 +    0x8769: IFDTag('ExifOffset', None),
   4.344 +    0x8773: Tag('InterColorProfile'),
   4.345 +    0x8822: TagDict('ExposureProgram',
   4.346 +             {0: 'Unidentified',
   4.347 +              1: 'Manual',
   4.348 +              2: 'Program Normal',
   4.349 +              3: 'Aperture Priority',
   4.350 +              4: 'Shutter Priority',
   4.351 +              5: 'Program Creative',
   4.352 +              6: 'Program Action',
   4.353 +              7: 'Portrait Mode',
   4.354 +              8: 'Landscape Mode'}),
   4.355 +    0x8824: Tag('SpectralSensitivity'),
   4.356 +    0x8825: IFDTag('GPSInfo', GPS_TAGS),
   4.357 +    0x8827: Tag('ISOSpeedRatings'),
   4.358 +    0x8828: Tag('OECF'),
   4.359 +    0x9000: Tag('ExifVersion'),
   4.360 +    0x9003: Tag('DateTimeOriginal'),
   4.361 +    0x9004: Tag('DateTimeDigitized'),
   4.362 +    0x9101: TagDict('ComponentsConfiguration',
   4.363 +             {0: '',
   4.364 +              1: 'Y',
   4.365 +              2: 'Cb',
   4.366 +              3: 'Cr',
   4.367 +              4: 'Red',
   4.368 +              5: 'Green',
   4.369 +              6: 'Blue'}),
   4.370 +    0x9102: Tag('CompressedBitsPerPixel'),
   4.371 +    0x9201: Tag('ShutterSpeedValue'),
   4.372 +    0x9202: Tag('ApertureValue'),
   4.373 +    0x9203: Tag('BrightnessValue'),
   4.374 +    0x9204: Tag('ExposureBiasValue'),
   4.375 +    0x9205: Tag('MaxApertureValue'),
   4.376 +    0x9206: Tag('SubjectDistance'),
   4.377 +    0x9207: TagDict('MeteringMode',
   4.378 +             {0: 'Unidentified',
   4.379 +              1: 'Average',
   4.380 +              2: 'CenterWeightedAverage',
   4.381 +              3: 'Spot',
   4.382 +              4: 'MultiSpot',
   4.383 +              5: 'Pattern'}),
   4.384 +    0x9208: TagDict('LightSource',
   4.385 +             {0: 'Unknown',
   4.386 +              1: 'Daylight',
   4.387 +              2: 'Fluorescent',
   4.388 +              3: 'Tungsten',
   4.389 +              9: 'Fine Weather',
   4.390 +              10: 'Flash',
   4.391 +              11: 'Shade',
   4.392 +              12: 'Daylight Fluorescent',
   4.393 +              13: 'Day White Fluorescent',
   4.394 +              14: 'Cool White Fluorescent',
   4.395 +              15: 'White Fluorescent',
   4.396 +              17: 'Standard Light A',
   4.397 +              18: 'Standard Light B',
   4.398 +              19: 'Standard Light C',
   4.399 +              20: 'D55',
   4.400 +              21: 'D65',
   4.401 +              22: 'D75',
   4.402 +              255: 'Other'}),
   4.403 +    0x9209: TagDict('Flash',
   4.404 +             {0: 'No',
   4.405 +              1: 'Fired',
   4.406 +              5: 'Fired (?)', # no return sensed
   4.407 +              7: 'Fired (!)', # return sensed
   4.408 +              9: 'Fill Fired',
   4.409 +              13: 'Fill Fired (?)',
   4.410 +              15: 'Fill Fired (!)',
   4.411 +              16: 'Off',
   4.412 +              24: 'Auto Off',
   4.413 +              25: 'Auto Fired',
   4.414 +              29: 'Auto Fired (?)',
   4.415 +              31: 'Auto Fired (!)',
   4.416 +              32: 'Not Available'}),
   4.417 +    0x920A: Tag('FocalLength'),
   4.418 +    0x9214: Tag('SubjectArea'),
   4.419 +    0x927C: Tag('MakerNote'),
   4.420 +    0x9286: TagFunc('UserComment', decode_UserComment),
   4.421 +    0x9290: Tag('SubSecTime'),
   4.422 +    0x9291: Tag('SubSecTimeOriginal'),
   4.423 +    0x9292: Tag('SubSecTimeDigitized'),
   4.424 +    
   4.425 +    # used by Windows Explorer
   4.426 +    0x9C9B: Tag('XPTitle'),
   4.427 +    0x9C9C: Tag('XPComment'),
   4.428 +    0x9C9D: Tag('XPAuthor'), #(ignored by Windows Explorer if Artist exists)
   4.429 +    0x9C9E: Tag('XPKeywords'),
   4.430 +    0x9C9F: Tag('XPSubject'),
   4.431 +
   4.432 +    0xA000: Tag('FlashPixVersion'),
   4.433 +    0xA001: TagDict('ColorSpace',
   4.434 +             {1: 'sRGB',
   4.435 +              2: 'Adobe RGB',
   4.436 +              65535: 'Uncalibrated'}),
   4.437 +    0xA002: Tag('ExifImageWidth'),
   4.438 +    0xA003: Tag('ExifImageLength'),
   4.439 +    0xA005: IFDTag('InteroperabilityOffset', INTR_TAGS),
   4.440 +    0xA20B: Tag('FlashEnergy'),               # 0x920B in TIFF/EP
   4.441 +    0xA20C: Tag('SpatialFrequencyResponse'),  # 0x920C
   4.442 +    0xA20E: Tag('FocalPlaneXResolution'),     # 0x920E
   4.443 +    0xA20F: Tag('FocalPlaneYResolution'),     # 0x920F
   4.444 +    0xA210: Tag('FocalPlaneResolutionUnit'),  # 0x9210
   4.445 +    0xA214: Tag('SubjectLocation'),           # 0x9214
   4.446 +    0xA215: Tag('ExposureIndex'),             # 0x9215
   4.447 +    0xA217: TagDict('SensingMethod',                # 0x9217
   4.448 +             {1: 'Not defined',
   4.449 +              2: 'One-chip color area',
   4.450 +              3: 'Two-chip color area',
   4.451 +              4: 'Three-chip color area',
   4.452 +              5: 'Color sequential area',
   4.453 +              7: 'Trilinear',
   4.454 +              8: 'Color sequential linear'}),             
   4.455 +    0xA300: TagDict('FileSource',
   4.456 +             {1: 'Film Scanner',
   4.457 +              2: 'Reflection Print Scanner',
   4.458 +              3: 'Digital Camera'}),
   4.459 +    0xA301: TagDict('SceneType',
   4.460 +             {1: 'Directly Photographed'}),
   4.461 +    0xA302: Tag('CVAPattern'),
   4.462 +    0xA401: TagDict('CustomRendered',
   4.463 +             {0: 'Normal',
   4.464 +              1: 'Custom'}),
   4.465 +    0xA402: TagDict('ExposureMode',
   4.466 +             {0: 'Auto Exposure',
   4.467 +              1: 'Manual Exposure',
   4.468 +              2: 'Auto Bracket'}),
   4.469 +    0xA403: TagDict('WhiteBalance',
   4.470 +             {0: 'Auto',
   4.471 +              1: 'Manual'}),
   4.472 +    0xA404: Tag('DigitalZoomRatio'),
   4.473 +    0xA405: ('FocalLengthIn35mmFilm', None),
   4.474 +    0xA406: TagDict('SceneCaptureType',
   4.475 +             {0: 'Standard',
   4.476 +              1: 'Landscape',
   4.477 +              2: 'Portrait',
   4.478 +              3: 'Night)'}),
   4.479 +    0xA407: TagDict('GainControl',
   4.480 +             {0: 'None',
   4.481 +              1: 'Low gain up',
   4.482 +              2: 'High gain up',
   4.483 +              3: 'Low gain down',
   4.484 +              4: 'High gain down'}),
   4.485 +    0xA408: TagDict('Contrast',
   4.486 +             {0: 'Normal',
   4.487 +              1: 'Soft',
   4.488 +              2: 'Hard'}),
   4.489 +    0xA409: TagDict('Saturation',
   4.490 +             {0: 'Normal',
   4.491 +              1: 'Soft',
   4.492 +              2: 'Hard'}),
   4.493 +    0xA40A: TagDict('Sharpness',
   4.494 +             {0: 'Normal',
   4.495 +              1: 'Soft',
   4.496 +              2: 'Hard'}),
   4.497 +    0xA40B: Tag('DeviceSettingDescription'),
   4.498 +    0xA40C: Tag('SubjectDistanceRange'),
   4.499 +    0xA500: Tag('Gamma'),
   4.500 +    0xC4A5: Tag('PrintIM'),
   4.501 +    0xEA1C:	('Padding', None),
   4.502 +    }
   4.503 +
   4.504 +# http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp
   4.505 +def nikon_ev_bias (seq) :
   4.506 +    """
   4.507 +        # First digit seems to be in steps of 1/6 EV.
   4.508 +        # Does the third value mean the step size?  It is usually 6,
   4.509 +        # but it is 12 for the ExposureDifference.
   4.510 +    """
   4.511 +
   4.512 +    # check for an error condition that could cause a crash.
   4.513 +    # this only happens if something has gone really wrong in
   4.514 +    # reading the Nikon MakerNote.
   4.515 +    if len(seq) < 4 : 
   4.516 +        return ""
   4.517 +
   4.518 +    if seq == [252, 1, 6, 0]:
   4.519 +        return "-2/3 EV"
   4.520 +
   4.521 +    if seq == [253, 1, 6, 0]:
   4.522 +        return "-1/2 EV"
   4.523 +
   4.524 +    if seq == [254, 1, 6, 0]:
   4.525 +        return "-1/3 EV"
   4.526 +
   4.527 +    if seq == [0, 1, 6, 0]:
   4.528 +        return "0 EV"
   4.529 +
   4.530 +    if seq == [2, 1, 6, 0]:
   4.531 +        return "+1/3 EV"
   4.532 +
   4.533 +    if seq == [3, 1, 6, 0]:
   4.534 +        return "+1/2 EV"
   4.535 +
   4.536 +    if seq == [4, 1, 6, 0]:
   4.537 +        return "+2/3 EV"
   4.538 +
   4.539 +    # handle combinations not in the table.
   4.540 +    a = seq[0]
   4.541 +
   4.542 +    # causes headaches for the +/- logic, so special case it.
   4.543 +    if a == 0:
   4.544 +        return "0 EV"
   4.545 +
   4.546 +    if a > 127:
   4.547 +        a = 256 - a
   4.548 +        ret_str = "-"
   4.549 +    else:
   4.550 +        ret_str = "+"
   4.551 +
   4.552 +    b = seq[2]	# assume third value means the step size
   4.553 +
   4.554 +    whole = a / b
   4.555 +
   4.556 +    a = a % b
   4.557 +
   4.558 +    if whole != 0 :
   4.559 +        ret_str = ret_str + str(whole) + " "
   4.560 +
   4.561 +    if a == 0 :
   4.562 +        ret_str = ret_str + "EV"
   4.563 +    else :
   4.564 +        r = Ratio(a, b)
   4.565 +        ret_str = ret_str + r.__repr__() + " EV"
   4.566 +
   4.567 +    return ret_str
   4.568 +
   4.569 +# Nikon E99x MakerNote Tags
   4.570 +MAKERNOTE_NIKON_NEWER_TAGS={
   4.571 +    0x0001: Tag('MakernoteVersion'),	# Sometimes binary
   4.572 +    0x0002: Tag('ISOSetting'),
   4.573 +    0x0003: Tag('ColorMode'),
   4.574 +    0x0004: Tag('Quality'),
   4.575 +    0x0005: Tag('Whitebalance'),
   4.576 +    0x0006: Tag('ImageSharpening'),
   4.577 +    0x0007: Tag('FocusMode'),
   4.578 +    0x0008: Tag('FlashSetting'),
   4.579 +    0x0009: Tag('AutoFlashMode'),
   4.580 +    0x000B: Tag('WhiteBalanceBias'),
   4.581 +    0x000C: Tag('WhiteBalanceRBCoeff'),
   4.582 +    0x000D: TagFunc('ProgramShift', nikon_ev_bias),
   4.583 +    # Nearly the same as the other EV vals, but step size is 1/12 EV (?)
   4.584 +    0x000E: TagFunc('ExposureDifference', nikon_ev_bias),
   4.585 +    0x000F: Tag('ISOSelection'),
   4.586 +    0x0011: Tag('NikonPreview'),
   4.587 +    0x0012: TagFunc('FlashCompensation', nikon_ev_bias),
   4.588 +    0x0013: Tag('ISOSpeedRequested'),
   4.589 +    0x0016: Tag('PhotoCornerCoordinates'),
   4.590 +    # 0x0017: Unknown, but most likely an EV value
   4.591 +    0x0018: TagFunc('FlashBracketCompensationApplied', nikon_ev_bias),
   4.592 +    0x0019: Tag('AEBracketCompensationApplied'),
   4.593 +    0x001A: Tag('ImageProcessing'),
   4.594 +    0x001B: Tag('CropHiSpeed'),
   4.595 +    0x001D: Tag('SerialNumber'),	# Conflict with 0x00A0 ?
   4.596 +    0x001E: Tag('ColorSpace'),
   4.597 +    0x001F: Tag('VRInfo'),
   4.598 +    0x0020: Tag('ImageAuthentication'),
   4.599 +    0x0022: Tag('ActiveDLighting'),
   4.600 +    0x0023: Tag('PictureControl'),
   4.601 +    0x0024: Tag('WorldTime'),
   4.602 +    0x0025: Tag('ISOInfo'),
   4.603 +    0x0080: Tag('ImageAdjustment'),
   4.604 +    0x0081: Tag('ToneCompensation'),
   4.605 +    0x0082: Tag('AuxiliaryLens'),
   4.606 +    0x0083: Tag('LensType'),
   4.607 +    0x0084: Tag('LensMinMaxFocalMaxAperture'),
   4.608 +    0x0085: Tag('ManualFocusDistance'),
   4.609 +    0x0086: Tag('DigitalZoomFactor'),
   4.610 +    0x0087: TagDict('FlashMode',
   4.611 +             {0x00: 'Did Not Fire',
   4.612 +              0x01: 'Fired, Manual',
   4.613 +              0x07: 'Fired, External',
   4.614 +              0x08: 'Fired, Commander Mode ',
   4.615 +              0x09: 'Fired, TTL Mode'}),
   4.616 +    0x0088: TagDict('AFFocusPosition',
   4.617 +             {0x0000: 'Center',
   4.618 +              0x0100: 'Top',
   4.619 +              0x0200: 'Bottom',
   4.620 +              0x0300: 'Left',
   4.621 +              0x0400: 'Right'}),
   4.622 +    0x0089: TagDict('BracketingMode',
   4.623 +             {0x00: 'Single frame, no bracketing',
   4.624 +              0x01: 'Continuous, no bracketing',
   4.625 +              0x02: 'Timer, no bracketing',
   4.626 +              0x10: 'Single frame, exposure bracketing',
   4.627 +              0x11: 'Continuous, exposure bracketing',
   4.628 +              0x12: 'Timer, exposure bracketing',
   4.629 +              0x40: 'Single frame, white balance bracketing',
   4.630 +              0x41: 'Continuous, white balance bracketing',
   4.631 +              0x42: 'Timer, white balance bracketing'}),
   4.632 +    0x008A: Tag('AutoBracketRelease'),
   4.633 +    0x008B: Tag('LensFStops'),
   4.634 +    0x008C: ('NEFCurve1', None),	# ExifTool calls this 'ContrastCurve'
   4.635 +    0x008D: Tag('ColorMode'),
   4.636 +    0x008F: Tag('SceneMode'),
   4.637 +    0x0090: Tag('LightingType'),
   4.638 +    0x0091: Tag('ShotInfo'),	# First 4 bytes are a version number in ASCII
   4.639 +    0x0092: Tag('HueAdjustment'),
   4.640 +    # ExifTool calls this 'NEFCompression', should be 1-4
   4.641 +    0x0093: Tag('Compression'),
   4.642 +    0x0094: TagDict('Saturation',
   4.643 +             {-3: 'B&W',
   4.644 +              -2: '-2',
   4.645 +              -1: '-1',
   4.646 +              0: '0',
   4.647 +              1: '1',
   4.648 +              2: '2'}),
   4.649 +    0x0095: Tag('NoiseReduction'),
   4.650 +    0x0096: ('NEFCurve2', None),	# ExifTool calls this 'LinearizationTable'
   4.651 +    0x0097: Tag('ColorBalance'),	# First 4 bytes are a version number in ASCII
   4.652 +    0x0098: Tag('LensData'),	# First 4 bytes are a version number in ASCII
   4.653 +    0x0099: Tag('RawImageCenter'),
   4.654 +    0x009A: Tag('SensorPixelSize'),
   4.655 +    0x009C: Tag('Scene Assist'),
   4.656 +    0x009E: Tag('RetouchHistory'),
   4.657 +    0x00A0: Tag('SerialNumber'),
   4.658 +    0x00A2: Tag('ImageDataSize'),
   4.659 +    # 00A3: unknown - a single byte 0
   4.660 +    # 00A4: In NEF, looks like a 4 byte ASCII version number ('0200')
   4.661 +    0x00A5: Tag('ImageCount'),
   4.662 +    0x00A6: Tag('DeletedImageCount'),
   4.663 +    0x00A7: Tag('TotalShutterReleases'),
   4.664 +    # First 4 bytes are a version number in ASCII, with version specific
   4.665 +    # info to follow.  Its hard to treat it as a string due to embedded nulls.
   4.666 +    0x00A8: Tag('FlashInfo'),
   4.667 +    0x00A9: Tag('ImageOptimization'),
   4.668 +    0x00AA: Tag('Saturation'),
   4.669 +    0x00AB: Tag('DigitalVariProgram'),
   4.670 +    0x00AC: Tag('ImageStabilization'),
   4.671 +    0x00AD: Tag('Responsive AF'),	# 'AFResponse'
   4.672 +    0x00B0: Tag('MultiExposure'),
   4.673 +    0x00B1: Tag('HighISONoiseReduction'),
   4.674 +    0x00B7: Tag('AFInfo'),
   4.675 +    0x00B8: Tag('FileInfo'),
   4.676 +    # 00B9: unknown
   4.677 +    0x0100: Tag('DigitalICE'),
   4.678 +    0x0103: TagDict('PreviewCompression',
   4.679 +             {1: 'Uncompressed',
   4.680 +              2: 'CCITT 1D',
   4.681 +              3: 'T4/Group 3 Fax',
   4.682 +              4: 'T6/Group 4 Fax',
   4.683 +              5: 'LZW',
   4.684 +              6: 'JPEG (old-style)',
   4.685 +              7: 'JPEG',
   4.686 +              8: 'Adobe Deflate',
   4.687 +              9: 'JBIG B&W',
   4.688 +              10: 'JBIG Color',
   4.689 +              32766: 'Next',
   4.690 +              32769: 'Epson ERF Compressed',
   4.691 +              32771: 'CCIRLEW',
   4.692 +              32773: 'PackBits',
   4.693 +              32809: 'Thunderscan',
   4.694 +              32895: 'IT8CTPAD',
   4.695 +              32896: 'IT8LW',
   4.696 +              32897: 'IT8MP',
   4.697 +              32898: 'IT8BL',
   4.698 +              32908: 'PixarFilm',
   4.699 +              32909: 'PixarLog',
   4.700 +              32946: 'Deflate',
   4.701 +              32947: 'DCS',
   4.702 +              34661: 'JBIG',
   4.703 +              34676: 'SGILog',
   4.704 +              34677: 'SGILog24',
   4.705 +              34712: 'JPEG 2000',
   4.706 +              34713: 'Nikon NEF Compressed',
   4.707 +              65000: 'Kodak DCR Compressed',
   4.708 +              65535: 'Pentax PEF Compressed',}),
   4.709 +    0x0201: Tag('PreviewImageStart'),
   4.710 +    0x0202: Tag('PreviewImageLength'),
   4.711 +    0x0213: TagDict('PreviewYCbCrPositioning',
   4.712 +             {1: 'Centered',
   4.713 +              2: 'Co-sited'}), 
   4.714 +    0x0010: Tag('DataDump'),
   4.715 +    }
   4.716 +
   4.717 +MAKERNOTE_NIKON_OLDER_TAGS = {
   4.718 +    0x0003: TagDict('Quality',
   4.719 +             {1: 'VGA Basic',
   4.720 +              2: 'VGA Normal',
   4.721 +              3: 'VGA Fine',
   4.722 +              4: 'SXGA Basic',
   4.723 +              5: 'SXGA Normal',
   4.724 +              6: 'SXGA Fine'}),
   4.725 +    0x0004: TagDict('ColorMode',
   4.726 +             {1: 'Color',
   4.727 +              2: 'Monochrome'}),
   4.728 +    0x0005: TagDict('ImageAdjustment',
   4.729 +             {0: 'Normal',
   4.730 +              1: 'Bright+',
   4.731 +              2: 'Bright-',
   4.732 +              3: 'Contrast+',
   4.733 +              4: 'Contrast-'}),
   4.734 +    0x0006: TagDict('CCDSpeed',
   4.735 +             {0: 'ISO 80',
   4.736 +              2: 'ISO 160',
   4.737 +              4: 'ISO 320',
   4.738 +              5: 'ISO 100'}),
   4.739 +    0x0007: TagDict('WhiteBalance',
   4.740 +             {0: 'Auto',
   4.741 +              1: 'Preset',
   4.742 +              2: 'Daylight',
   4.743 +              3: 'Incandescent',
   4.744 +              4: 'Fluorescent',
   4.745 +              5: 'Cloudy',
   4.746 +              6: 'Speed Light'}),
   4.747 +    }
   4.748 +
   4.749 +def olympus_special_mode (values) :
   4.750 +    """
   4.751 +        Decode Olympus SpecialMode tag in MakerNote
   4.752 +    """
   4.753 +
   4.754 +    a = {
   4.755 +        0: 'Normal',
   4.756 +        1: 'Unknown',
   4.757 +        2: 'Fast',
   4.758 +        3: 'Panorama'
   4.759 +    }
   4.760 +
   4.761 +    b = {
   4.762 +        0: 'Non-panoramic',
   4.763 +        1: 'Left to right',
   4.764 +        2: 'Right to left',
   4.765 +        3: 'Bottom to top',
   4.766 +        4: 'Top to bottom'
   4.767 +    }
   4.768 +
   4.769 +    if v[0] not in a or v[2] not in b:
   4.770 +        return values
   4.771 +
   4.772 +    return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
   4.773 +
   4.774 +MAKERNOTE_OLYMPUS_TAGS={
   4.775 +    # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
   4.776 +    # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
   4.777 +    0x0100: Tag('JPEGThumbnail'),
   4.778 +    0x0200: TagFunc('SpecialMode', olympus_special_mode),
   4.779 +    0x0201: TagDict('JPEGQual',
   4.780 +             {1: 'SQ',
   4.781 +              2: 'HQ',
   4.782 +              3: 'SHQ'}),
   4.783 +    0x0202: TagDict('Macro',
   4.784 +             {0: 'Normal',
   4.785 +             1: 'Macro',
   4.786 +             2: 'SuperMacro'}),
   4.787 +    0x0203: TagDict('BWMode',
   4.788 +             {0: 'Off',
   4.789 +             1: 'On'}),
   4.790 +    0x0204: Tag('DigitalZoom'),
   4.791 +    0x0205: Tag('FocalPlaneDiagonal'),
   4.792 +    0x0206: Tag('LensDistortionParams'),
   4.793 +    0x0207: Tag('SoftwareRelease'),
   4.794 +    0x0208: Tag('PictureInfo'),
   4.795 +    0x0209: Tag('CameraID'), # print as string
   4.796 +    0x0F00: Tag('DataDump'),
   4.797 +    0x0300: Tag('PreCaptureFrames'),
   4.798 +    0x0404: Tag('SerialNumber'),
   4.799 +    0x1000: Tag('ShutterSpeedValue'),
   4.800 +    0x1001: Tag('ISOValue'),
   4.801 +    0x1002: Tag('ApertureValue'),
   4.802 +    0x1003: Tag('BrightnessValue'),
   4.803 +    0x1004: Tag('FlashMode'),
   4.804 +    0x1004: TagDict('FlashMode',
   4.805 +       {2: 'On',
   4.806 +        3: 'Off'}),
   4.807 +    0x1005: TagDict('FlashDevice',
   4.808 +       {0: 'None',
   4.809 +        1: 'Internal',
   4.810 +        4: 'External',
   4.811 +        5: 'Internal + External'}),
   4.812 +    0x1006: Tag('ExposureCompensation'),
   4.813 +    0x1007: Tag('SensorTemperature'),
   4.814 +    0x1008: Tag('LensTemperature'),
   4.815 +    0x100b: TagDict('FocusMode',
   4.816 +       {0: 'Auto',
   4.817 +        1: 'Manual'}),
   4.818 +    0x1017: Tag('RedBalance'),
   4.819 +    0x1018: Tag('BlueBalance'),
   4.820 +    0x101a: Tag('SerialNumber'),
   4.821 +    0x1023: Tag('FlashExposureComp'),
   4.822 +    0x1026: TagDict('ExternalFlashBounce',
   4.823 +       {0: 'No',
   4.824 +        1: 'Yes'}),
   4.825 +    0x1027: Tag('ExternalFlashZoom'),
   4.826 +    0x1028: Tag('ExternalFlashMode'),
   4.827 +    0x1029: ('Contrast 	int16u',
   4.828 +       {0: 'High',
   4.829 +        1: 'Normal',
   4.830 +        2: 'Low'}),
   4.831 +    0x102a: Tag('SharpnessFactor'),
   4.832 +    0x102b: Tag('ColorControl'),
   4.833 +    0x102c: Tag('ValidBits'),
   4.834 +    0x102d: Tag('CoringFilter'),
   4.835 +    0x102e: Tag('OlympusImageWidth'),
   4.836 +    0x102f: Tag('OlympusImageHeight'),
   4.837 +    0x1034: Tag('CompressionRatio'),
   4.838 +    0x1035: TagDict('PreviewImageValid',
   4.839 +       {0: 'No',
   4.840 +        1: 'Yes'}),
   4.841 +    0x1036: Tag('PreviewImageStart'),
   4.842 +    0x1037: Tag('PreviewImageLength'),
   4.843 +    0x1039: TagDict('CCDScanMode',
   4.844 +       {0: 'Interlaced',
   4.845 +        1: 'Progressive'}),
   4.846 +    0x103a: TagDict('NoiseReduction',
   4.847 +       {0: 'Off',
   4.848 +        1: 'On'}),
   4.849 +    0x103b: Tag('InfinityLensStep'),
   4.850 +    0x103c: Tag('NearLensStep'),
   4.851 +
   4.852 +    # TODO - these need extra definitions
   4.853 +    # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html
   4.854 +    0x2010: Tag('Equipment'),
   4.855 +    0x2020: Tag('CameraSettings'),
   4.856 +    0x2030: Tag('RawDevelopment'),
   4.857 +    0x2040: Tag('ImageProcessing'),
   4.858 +    0x2050: Tag('FocusInfo'),
   4.859 +    0x3000: Tag('RawInfo '),
   4.860 +    }
   4.861 +
   4.862 +# 0x2020 CameraSettings
   4.863 +MAKERNOTE_OLYMPUS_TAG_0x2020={
   4.864 +    0x0100: TagDict('PreviewImageValid',
   4.865 +             {0: 'No',
   4.866 +              1: 'Yes'}),
   4.867 +    0x0101: Tag('PreviewImageStart'),
   4.868 +    0x0102: Tag('PreviewImageLength'),
   4.869 +    0x0200: TagDict('ExposureMode',
   4.870 +             {1: 'Manual',
   4.871 +              2: 'Program',
   4.872 +              3: 'Aperture-priority AE',
   4.873 +              4: 'Shutter speed priority AE',
   4.874 +              5: 'Program-shift'}),
   4.875 +    0x0201: TagDict('AELock',
   4.876 +             {0: 'Off',
   4.877 +              1: 'On'}),
   4.878 +    0x0202: TagDict('MeteringMode',
   4.879 +             {2: 'Center Weighted',
   4.880 +              3: 'Spot',
   4.881 +              5: 'ESP',
   4.882 +              261: 'Pattern+AF',
   4.883 +              515: 'Spot+Highlight control',
   4.884 +              1027: 'Spot+Shadow control'}),
   4.885 +    0x0300: TagDict('MacroMode',
   4.886 +             {0: 'Off',
   4.887 +              1: 'On'}),
   4.888 +    0x0301: TagDict('FocusMode',
   4.889 +             {0: 'Single AF',
   4.890 +              1: 'Sequential shooting AF',
   4.891 +              2: 'Continuous AF',
   4.892 +              3: 'Multi AF',
   4.893 +              10: 'MF'}),
   4.894 +    0x0302: TagDict('FocusProcess',
   4.895 +             {0: 'AF Not Used',
   4.896 +              1: 'AF Used'}),
   4.897 +    0x0303: TagDict('AFSearch',
   4.898 +             {0: 'Not Ready',
   4.899 +              1: 'Ready'}),
   4.900 +    0x0304: Tag('AFAreas'),
   4.901 +    0x0401: Tag('FlashExposureCompensation'),
   4.902 +    0x0500: ('WhiteBalance2',
   4.903 +             {0: 'Auto',
   4.904 +             16: '7500K (Fine Weather with Shade)',
   4.905 +             17: '6000K (Cloudy)',
   4.906 +             18: '5300K (Fine Weather)',
   4.907 +             20: '3000K (Tungsten light)',
   4.908 +             21: '3600K (Tungsten light-like)',
   4.909 +             33: '6600K (Daylight fluorescent)',
   4.910 +             34: '4500K (Neutral white fluorescent)',
   4.911 +             35: '4000K (Cool white fluorescent)',
   4.912 +             48: '3600K (Tungsten light-like)',
   4.913 +             256: 'Custom WB 1',
   4.914 +             257: 'Custom WB 2',
   4.915 +             258: 'Custom WB 3',
   4.916 +             259: 'Custom WB 4',
   4.917 +             512: 'Custom WB 5400K',
   4.918 +             513: 'Custom WB 2900K',
   4.919 +             514: 'Custom WB 8000K', }),
   4.920 +    0x0501: Tag('WhiteBalanceTemperature'),
   4.921 +    0x0502: Tag('WhiteBalanceBracket'),
   4.922 +    0x0503: Tag('CustomSaturation'), # (3 numbers: 1. CS Value, 2. Min, 3. Max)
   4.923 +    0x0504: TagDict('ModifiedSaturation',
   4.924 +             {0: 'Off',
   4.925 +              1: 'CM1 (Red Enhance)',
   4.926 +              2: 'CM2 (Green Enhance)',
   4.927 +              3: 'CM3 (Blue Enhance)',
   4.928 +              4: 'CM4 (Skin Tones)'}),
   4.929 +    0x0505: Tag('ContrastSetting'), # (3 numbers: 1. Contrast, 2. Min, 3. Max)
   4.930 +    0x0506: Tag('SharpnessSetting'), # (3 numbers: 1. Sharpness, 2. Min, 3. Max)
   4.931 +    0x0507: TagDict('ColorSpace',
   4.932 +             {0: 'sRGB',
   4.933 +              1: 'Adobe RGB',
   4.934 +              2: 'Pro Photo RGB'}),
   4.935 +    0x0509: TagDict('SceneMode',
   4.936 +             {0: 'Standard',
   4.937 +              6: 'Auto',
   4.938 +              7: 'Sport',
   4.939 +              8: 'Portrait',
   4.940 +              9: 'Landscape+Portrait',
   4.941 +             10: 'Landscape',
   4.942 +             11: 'Night scene',
   4.943 +             13: 'Panorama',
   4.944 +             16: 'Landscape+Portrait',
   4.945 +             17: 'Night+Portrait',
   4.946 +             19: 'Fireworks',
   4.947 +             20: 'Sunset',
   4.948 +             22: 'Macro',
   4.949 +             25: 'Documents',
   4.950 +             26: 'Museum',
   4.951 +             28: 'Beach&Snow',
   4.952 +             30: 'Candle',
   4.953 +             35: 'Underwater Wide1',
   4.954 +             36: 'Underwater Macro',
   4.955 +             39: 'High Key',
   4.956 +             40: 'Digital Image Stabilization',
   4.957 +             44: 'Underwater Wide2',
   4.958 +             45: 'Low Key',
   4.959 +             46: 'Children',
   4.960 +             48: 'Nature Macro'}),
   4.961 +    0x050a: TagDict('NoiseReduction',
   4.962 +             {0: 'Off',
   4.963 +              1: 'Noise Reduction',
   4.964 +              2: 'Noise Filter',
   4.965 +              3: 'Noise Reduction + Noise Filter',
   4.966 +              4: 'Noise Filter (ISO Boost)',
   4.967 +              5: 'Noise Reduction + Noise Filter (ISO Boost)'}),
   4.968 +    0x050b: TagDict('DistortionCorrection',
   4.969 +             {0: 'Off',
   4.970 +              1: 'On'}),
   4.971 +    0x050c: TagDict('ShadingCompensation',
   4.972 +             {0: 'Off',
   4.973 +              1: 'On'}),
   4.974 +    0x050d: Tag('CompressionFactor'),
   4.975 +    0x050f: TagDict('Gradation',
   4.976 +             {'-1 -1 1': 'Low Key',
   4.977 +              '0 -1 1': 'Normal',
   4.978 +              '1 -1 1': 'High Key'}),
   4.979 +    0x0520: TagDict('PictureMode',
   4.980 +             {1: 'Vivid',
   4.981 +              2: 'Natural',
   4.982 +              3: 'Muted',
   4.983 +              256: 'Monotone',
   4.984 +              512: 'Sepia'}),
   4.985 +    0x0521: Tag('PictureModeSaturation'),
   4.986 +    0x0522: Tag('PictureModeHue?'),
   4.987 +    0x0523: Tag('PictureModeContrast'),
   4.988 +    0x0524: Tag('PictureModeSharpness'),
   4.989 +    0x0525: TagDict('PictureModeBWFilter',
   4.990 +             {0: 'n/a',
   4.991 +              1: 'Neutral',
   4.992 +              2: 'Yellow',
   4.993 +              3: 'Orange',
   4.994 +              4: 'Red',
   4.995 +              5: 'Green'}),
   4.996 +    0x0526: TagDict('PictureModeTone',
   4.997 +             {0: 'n/a',
   4.998 +              1: 'Neutral',
   4.999 +              2: 'Sepia',
  4.1000 +              3: 'Blue',
  4.1001 +              4: 'Purple',
  4.1002 +              5: 'Green'}),
  4.1003 +    0x0600: Tag('Sequence'), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits
  4.1004 +    0x0601: Tag('PanoramaMode'), # (2 numbers: 1. Mode, 2. Shot number)
  4.1005 +    0x0603: ('ImageQuality2',
  4.1006 +             {1: 'SQ',
  4.1007 +              2: 'HQ',
  4.1008 +              3: 'SHQ',
  4.1009 +              4: 'RAW'}),
  4.1010 +    0x0901: Tag('ManometerReading'),
  4.1011 +    }
  4.1012 +
  4.1013 +
  4.1014 +MAKERNOTE_CASIO_TAGS={
  4.1015 +    0x0001: TagDict('RecordingMode',
  4.1016 +             {1: 'Single Shutter',
  4.1017 +              2: 'Panorama',
  4.1018 +              3: 'Night Scene',
  4.1019 +              4: 'Portrait',
  4.1020 +              5: 'Landscape'}),
  4.1021 +    0x0002: TagDict('Quality',
  4.1022 +             {1: 'Economy',
  4.1023 +              2: 'Normal',
  4.1024 +              3: 'Fine'}),
  4.1025 +    0x0003: TagDict('FocusingMode',
  4.1026 +             {2: 'Macro',
  4.1027 +              3: 'Auto Focus',
  4.1028 +              4: 'Manual Focus',
  4.1029 +              5: 'Infinity'}),
  4.1030 +    0x0004: TagDict('FlashMode',
  4.1031 +             {1: 'Auto',
  4.1032 +              2: 'On',
  4.1033 +              3: 'Off',
  4.1034 +              4: 'Red Eye Reduction'}),
  4.1035 +    0x0005: TagDict('FlashIntensity',
  4.1036 +             {11: 'Weak',
  4.1037 +              13: 'Normal',
  4.1038 +              15: 'Strong'}),
  4.1039 +    0x0006: Tag('Object Distance'),
  4.1040 +    0x0007: TagDict('WhiteBalance',
  4.1041 +             {1: 'Auto',
  4.1042 +              2: 'Tungsten',
  4.1043 +              3: 'Daylight',
  4.1044 +              4: 'Fluorescent',
  4.1045 +              5: 'Shade',
  4.1046 +              129: 'Manual'}),
  4.1047 +    0x000B: TagDict('Sharpness',
  4.1048 +             {0: 'Normal',
  4.1049 +              1: 'Soft',
  4.1050 +              2: 'Hard'}),
  4.1051 +    0x000C: TagDict('Contrast',
  4.1052 +             {0: 'Normal',
  4.1053 +              1: 'Low',
  4.1054 +              2: 'High'}),
  4.1055 +    0x000D: TagDict('Saturation',
  4.1056 +             {0: 'Normal',
  4.1057 +              1: 'Low',
  4.1058 +              2: 'High'}),
  4.1059 +    0x0014: TagDict('CCDSpeed',
  4.1060 +             {64: 'Normal',
  4.1061 +              80: 'Normal',
  4.1062 +              100: 'High',
  4.1063 +              125: '+1.0',
  4.1064 +              244: '+3.0',
  4.1065 +              250: '+2.0'}),
  4.1066 +    }
  4.1067 +
  4.1068 +MAKERNOTE_FUJIFILM_TAGS={
  4.1069 +    0x0000: Tag('NoteVersion'),
  4.1070 +    0x1000: Tag('Quality'),
  4.1071 +    0x1001: TagDict('Sharpness',
  4.1072 +             {1: 'Soft',
  4.1073 +              2: 'Soft',
  4.1074 +              3: 'Normal',
  4.1075 +              4: 'Hard',
  4.1076 +              5: 'Hard'}),
  4.1077 +    0x1002: TagDict('WhiteBalance',
  4.1078 +             {0: 'Auto',
  4.1079 +              256: 'Daylight',
  4.1080 +              512: 'Cloudy',
  4.1081 +              768: 'DaylightColor-Fluorescent',
  4.1082 +              769: 'DaywhiteColor-Fluorescent',
  4.1083 +              770: 'White-Fluorescent',
  4.1084 +              1024: 'Incandescent',
  4.1085 +              3840: 'Custom'}),
  4.1086 +    0x1003: TagDict('Color',
  4.1087 +             {0: 'Normal',
  4.1088 +              256: 'High',
  4.1089 +              512: 'Low'}),
  4.1090 +    0x1004: TagDict('Tone',
  4.1091 +             {0: 'Normal',
  4.1092 +              256: 'High',
  4.1093 +              512: 'Low'}),
  4.1094 +    0x1010: TagDict('FlashMode',
  4.1095 +             {0: 'Auto',
  4.1096 +              1: 'On',
  4.1097 +              2: 'Off',
  4.1098 +              3: 'Red Eye Reduction'}),
  4.1099 +    0x1011: Tag('FlashStrength'),
  4.1100 +    0x1020: TagDict('Macro',
  4.1101 +             {0: 'Off',
  4.1102 +              1: 'On'}),
  4.1103 +    0x1021: TagDict('FocusMode',
  4.1104 +             {0: 'Auto',
  4.1105 +              1: 'Manual'}),
  4.1106 +    0x1030: TagDict('SlowSync',
  4.1107 +             {0: 'Off',
  4.1108 +              1: 'On'}),
  4.1109 +    0x1031: TagDict('PictureMode',
  4.1110 +             {0: 'Auto',
  4.1111 +              1: 'Portrait',
  4.1112 +              2: 'Landscape',
  4.1113 +              4: 'Sports',
  4.1114 +              5: 'Night',
  4.1115 +              6: 'Program AE',
  4.1116 +              256: 'Aperture Priority AE',
  4.1117 +              512: 'Shutter Priority AE',
  4.1118 +              768: 'Manual Exposure'}),
  4.1119 +    0x1100: TagDict('MotorOrBracket',
  4.1120 +             {0: 'Off',
  4.1121 +              1: 'On'}),
  4.1122 +    0x1300: TagDict('BlurWarning',
  4.1123 +             {0: 'Off',
  4.1124 +              1: 'On'}),
  4.1125 +    0x1301: TagDict('FocusWarning',
  4.1126 +             {0: 'Off',
  4.1127 +              1: 'On'}),
  4.1128 +    0x1302: TagDict('AEWarning',
  4.1129 +             {0: 'Off',
  4.1130 +              1: 'On'}),
  4.1131 +    }
  4.1132 +
  4.1133 +MAKERNOTE_CANON_TAGS = {
  4.1134 +    0x0006: Tag('ImageType'),
  4.1135 +    0x0007: Tag('FirmwareVersion'),
  4.1136 +    0x0008: Tag('ImageNumber'),
  4.1137 +    0x0009: Tag('OwnerName'),
  4.1138 +    }
  4.1139 +
  4.1140 +# this is in element offset, name, optional value dictionary format
  4.1141 +MAKERNOTE_CANON_TAG_0x001 = {
  4.1142 +    1: TagDict('Macromode',
  4.1143 +        {1: 'Macro',
  4.1144 +         2: 'Normal'}),
  4.1145 +    2: Tag('SelfTimer'),
  4.1146 +    3: TagDict('Quality',
  4.1147 +        {2: 'Normal',
  4.1148 +         3: 'Fine',
  4.1149 +         5: 'Superfine'}),
  4.1150 +    4: TagDict('FlashMode',
  4.1151 +        {0: 'Flash Not Fired',
  4.1152 +         1: 'Auto',
  4.1153 +         2: 'On',
  4.1154 +         3: 'Red-Eye Reduction',
  4.1155 +         4: 'Slow Synchro',
  4.1156 +         5: 'Auto + Red-Eye Reduction',
  4.1157 +         6: 'On + Red-Eye Reduction',
  4.1158 +         16: 'external flash'}),
  4.1159 +    5: TagDict('ContinuousDriveMode',
  4.1160 +        {0: 'Single Or Timer',
  4.1161 +         1: 'Continuous'}),
  4.1162 +    7: TagDict('FocusMode',
  4.1163 +        {0: 'One-Shot',
  4.1164 +         1: 'AI Servo',
  4.1165 +         2: 'AI Focus',
  4.1166 +         3: 'MF',
  4.1167 +         4: 'Single',
  4.1168 +         5: 'Continuous',
  4.1169 +         6: 'MF'}),
  4.1170 +    10: TagDict('ImageSize',
  4.1171 +         {0: 'Large',
  4.1172 +          1: 'Medium',
  4.1173 +          2: 'Small'}),
  4.1174 +    11: TagDict('EasyShootingMode',
  4.1175 +         {0: 'Full Auto',
  4.1176 +          1: 'Manual',
  4.1177 +          2: 'Landscape',
  4.1178 +          3: 'Fast Shutter',
  4.1179 +          4: 'Slow Shutter',
  4.1180 +          5: 'Night',
  4.1181 +          6: 'B&W',
  4.1182 +          7: 'Sepia',
  4.1183 +          8: 'Portrait',
  4.1184 +          9: 'Sports',
  4.1185 +          10: 'Macro/Close-Up',
  4.1186 +          11: 'Pan Focus'}),
  4.1187 +    12: TagDict('DigitalZoom',
  4.1188 +         {0: 'None',
  4.1189 +          1: '2x',
  4.1190 +          2: '4x'}),
  4.1191 +    13: TagDict('Contrast',
  4.1192 +         {0xFFFF: 'Low',
  4.1193 +          0: 'Normal',
  4.1194 +          1: 'High'}),
  4.1195 +    14: TagDict('Saturation',
  4.1196 +         {0xFFFF: 'Low',
  4.1197 +          0: 'Normal',
  4.1198 +          1: 'High'}),
  4.1199 +    15: TagDict('Sharpness',
  4.1200 +         {0xFFFF: 'Low',
  4.1201 +          0: 'Normal',
  4.1202 +          1: 'High'}),
  4.1203 +    16: TagDict('ISO',
  4.1204 +         {0: 'See ISOSpeedRatings Tag',
  4.1205 +          15: 'Auto',
  4.1206 +          16: '50',
  4.1207 +          17: '100',
  4.1208 +          18: '200',
  4.1209 +          19: '400'}),
  4.1210 +    17: TagDict('MeteringMode',
  4.1211 +         {3: 'Evaluative',
  4.1212 +          4: 'Partial',
  4.1213 +          5: 'Center-weighted'}),
  4.1214 +    18: TagDict('FocusType',
  4.1215 +         {0: 'Manual',
  4.1216 +          1: 'Auto',
  4.1217 +          3: 'Close-Up (Macro)',
  4.1218 +          8: 'Locked (Pan Mode)'}),
  4.1219 +    19: TagDict('AFPointSelected',
  4.1220 +         {0x3000: 'None (MF)',
  4.1221 +          0x3001: 'Auto-Selected',
  4.1222 +          0x3002: 'Right',
  4.1223 +          0x3003: 'Center',
  4.1224 +          0x3004: 'Left'}),
  4.1225 +    20: TagDict('ExposureMode',
  4.1226 +         {0: 'Easy Shooting',
  4.1227 +          1: 'Program',
  4.1228 +          2: 'Tv-priority',
  4.1229 +          3: 'Av-priority',
  4.1230 +          4: 'Manual',
  4.1231 +          5: 'A-DEP'}),
  4.1232 +    23: Tag('LongFocalLengthOfLensInFocalUnits'),
  4.1233 +    24: Tag('ShortFocalLengthOfLensInFocalUnits'),
  4.1234 +    25: Tag('FocalUnitsPerMM'),
  4.1235 +    28: TagDict('FlashActivity',
  4.1236 +         {0: 'Did Not Fire',
  4.1237 +          1: 'Fired'}),
  4.1238 +    29: TagDict('FlashDetails',
  4.1239 +         {14: 'External E-TTL',
  4.1240 +          13: 'Internal Flash',
  4.1241 +          11: 'FP Sync Used',
  4.1242 +          7: '2nd("Rear")-Curtain Sync Used',
  4.1243 +          4: 'FP Sync Enabled'}),
  4.1244 +    32: TagDict('FocusMode',
  4.1245 +         {0: 'Single',
  4.1246 +          1: 'Continuous'}),
  4.1247 +    }
  4.1248 +
  4.1249 +MAKERNOTE_CANON_TAG_0x004 = {
  4.1250 +    7: TagDict('WhiteBalance',
  4.1251 +        {0: 'Auto',
  4.1252 +         1: 'Sunny',
  4.1253 +         2: 'Cloudy',
  4.1254 +         3: 'Tungsten',
  4.1255 +         4: 'Fluorescent',
  4.1256 +         5: 'Flash',
  4.1257 +         6: 'Custom'}),
  4.1258 +    9: Tag('SequenceNumber'),
  4.1259 +    14: Tag('AFPointUsed'),
  4.1260 +    15: TagDict('FlashBias',
  4.1261 +         {0xFFC0: '-2 EV',
  4.1262 +          0xFFCC: '-1.67 EV',
  4.1263 +          0xFFD0: '-1.50 EV',
  4.1264 +          0xFFD4: '-1.33 EV',
  4.1265 +          0xFFE0: '-1 EV',
  4.1266 +          0xFFEC: '-0.67 EV',
  4.1267 +          0xFFF0: '-0.50 EV',
  4.1268 +          0xFFF4: '-0.33 EV',
  4.1269 +          0x0000: '0 EV',
  4.1270 +          0x000C: '0.33 EV',
  4.1271 +          0x0010: '0.50 EV',
  4.1272 +          0x0014: '0.67 EV',
  4.1273 +          0x0020: '1 EV',
  4.1274 +          0x002C: '1.33 EV',
  4.1275 +          0x0030: '1.50 EV',
  4.1276 +          0x0034: '1.67 EV',
  4.1277 +          0x0040: '2 EV'}),
  4.1278 +    19: Tag('SubjectDistance'),
  4.1279 +    }
  4.1280 +
  4.1281 +# ratio object that eventually will be able to reduce itself to lowest
  4.1282 +# common denominator for printing
  4.1283 +# XXX: unused
  4.1284 +def gcd(a, b):
  4.1285 +    if b == 0:
  4.1286 +        return a
  4.1287 +    else:
  4.1288 +        return gcd(b, a % b)
  4.1289 +
  4.1290 +class Ratio:
  4.1291 +    def __init__(self, num, den):
  4.1292 +        self.num = num
  4.1293 +        self.den = den
  4.1294 +
  4.1295 +    def __repr__(self):
  4.1296 +        self.reduce()
  4.1297 +        if self.den == 1:
  4.1298 +            return str(self.num)
  4.1299 +        return '%d/%d' % (self.num, self.den)
  4.1300 +
  4.1301 +    def reduce(self):
  4.1302 +        div = gcd(self.num, self.den)
  4.1303 +        if div > 1:
  4.1304 +            self.num = self.num / div
  4.1305 +            self.den = self.den / div
  4.1306 +
  4.1307 +