--- a/degal/exif.py Sun Jun 14 16:09:04 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,669 +0,0 @@
-"""
- A custom EXIF parsing module, aimed at high performance.
-"""
-
-import struct, mmap, os
-
-from utils import lazy_load, lazy_load_iter
-
-def read_struct (file, fmt) :
- """
- Utility function to read data from the a file using struct
- """
-
- # length of data
- fmt_size = struct.calcsize(fmt)
-
- # get data
- file_data = file.read(fmt_size)
-
- # unpack single item, this should raise an error if file_data is too short
- return struct.unpack(fmt, file_data)
-
-class Buffer (object) :
- """
- Wraps a buffer object (anything that supports the python buffer protocol) for read-only access.
-
- Includes an offset for relative values, and an endianess for reading binary data.
- """
-
- def __init__ (self, obj, offset=None, size=None, struct_prefix='=') :
- """
- Create a new Buffer object with a new underlying buffer, created from the given object, offset and size.
-
- The endiannes is given in the form of a struct-module prefix, which should be one of '<' or '>'.
- Standard size/alignment are assumed.
- """
-
- # store
- self.buf = buffer(obj, *(arg for arg in (offset, size) if arg is not None))
- self.offset = offset
- self.size = size
- self.prefix = struct_prefix
-
- def subregion (self, offset, length=None) :
- """
- Create a new sub-Buffer referencing a view of this buffer, at the given offset, and with the given
- length, if any, and the same struct_prefix.
- """
-
- return Buffer(self.buf, offset, length, struct_prefix=self.prefix)
-
- def pread (self, offset, length) :
- """
- Read a random-access region of raw data
- """
-
- return self.buf[offset:offset + length]
-
- def pread_struct (self, offset, fmt) :
- """
- Read structured data using the given struct format from the given offset.
- """
-
- return struct.unpack_from(self.prefix + fmt, self.buf, offset=offset)
-
- def pread_item (self, offset, fmt) :
- """
- Read a single item of structured data from the given offset.
- """
-
- value, = self.pread_struct(offset, fmt)
-
- return value
-
- def iter_offsets (self, count, size, offset=0) :
- """
- Yield a series of offsets for `count` items of `size` bytes, beginning at `offset`.
- """
-
- return xrange(offset, offset + count * size, size)
-
- def item_size (self, fmt) :
- """
- Returns the size in bytes of the given item format
- """
-
- return struct.calcsize(self.prefix + fmt)
-
- def unpack_item (self, fmt, data) :
- """
- Unpacks a single item from the given data
- """
-
- value, = struct.unpack(self.prefix + fmt, data)
-
- return value
-
-def mmap_buffer (file, size) :
- """
- Create and return a new read-only mmap'd region
- """
-
- return mmap.mmap(file.fileno(), size, access=mmap.ACCESS_READ)
-
-import exif_data
-
-class Tag (object) :
- """
- Represents a single Tag in an IFD
- """
-
- def __init__ (self, ifd, offset, tag, type, count, data_raw) :
- """
- Build a Tag with the given binary items from the IFD entry
- """
-
- self.ifd = ifd
- self.offset = offset
- self.tag = tag
- self.type = type
- self.count = count
- self.data_raw = data_raw
-
- # lookup the type for this tag
- self.type_data = exif_data.FIELD_TYPES.get(type)
-
- # unpack it
- if self.type_data :
- self.type_format, self.type_name, self.type_func = self.type_data
-
- # lookup the tag data for this tag
- self.tag_data = self.ifd.tag_dict.get(tag)
-
- @property
- def name (self) :
- """
- Lookup the name of this tag via its code, returns None if unknown.
- """
-
- if self.tag_data :
- return self.tag_data.name
-
- else :
- return None
-
- def is_subifd (self) :
- """
- Tests if this Tag is of a IFDTag type
- """
-
- return self.tag_data and isinstance(self.tag_data, exif_data.IFDTag)
-
- @lazy_load
- def subifd (self) :
- """
- Load the sub-IFD for this tag
- """
-
- # the tag_dict to use
- tag_dict = self.tag_data.ifd_tags or self.ifd.tag_dict
-
- # construct, return
- return self.ifd.exif._load_subifd(self, tag_dict)
-
- def process_values (self, raw_values) :
- """
- Process the given raw values unpacked from the file.
- """
-
- if self.type_data and self.type_func :
- # use the filter func
- return self.type_func(raw_values)
-
- else :
- # nada, just leave them
- return raw_values
-
- def readable_value (self, values) :
- """
- Convert the given values for this tag into a human-readable string.
-
- Returns the comma-separated values by default.
- """
-
- if self.tag_data :
- # map it
- return self.tag_data.map_values(values)
-
- else :
- # default value-mapping
- return ", ".join(str(value) for value in values)
-
-# size of an IFD entry in bytes
-IFD_ENTRY_SIZE = 12
-
-class IFD (Buffer) :
- """
- Represents an IFD (Image file directory) region in EXIF data.
- """
-
- def __init__ (self, exif, buffer, tag_dict, **buffer_opts) :
- """
- Access the IFD data from the given bufferable object with given buffer opts.
-
- This will read the `count` and `next_offset` values.
- """
-
- # init
- super(IFD, self).__init__(buffer, **buffer_opts)
-
- # store
- self.exif = exif
- self.tag_dict = tag_dict
-
- # read header
- self.count = self.pread_item(0, 'H')
-
- # read next-offset
- self.next_offset = self.pread_item(0x02 + self.count * IFD_ENTRY_SIZE, 'I')
-
- @lazy_load_iter
- def tags (self) :
- """
- Iterate over all the Tag objects in this IFD
- """
-
- # read each tag
- for offset in self.iter_offsets(self.count, IFD_ENTRY_SIZE, 0x02) :
- # read the tag data
- tag, type, count, data_raw = self.pread_struct(offset, 'HHI4s')
-
- # yield the new Tag
- yield Tag(self, self.offset + offset, tag, type, count, data_raw)
-
- def get_tags (self, filter=None) :
- """
- Yield a series of tag objects for this IFD and all sub-IFDs.
- """
-
- for tag in self.tags :
- if tag.is_subifd() :
- # recurse
- for subtag in tag.subifd.get_tags(filter=filter) :
- yield subtag
-
- else :
- # normal tag
- yield tag
-
-class EXIF (Buffer) :
- """
- Represents the EXIF data embedded in some image file in the form of a Region.
- """
-
- def __init__ (self, buffer, **buffer_opts) :
- """
- Access the EXIF data from the given bufferable object with the given buffer options.
- """
-
- # init Buffer
- super(EXIF, self).__init__(buffer, **buffer_opts)
-
- # store
- self.buffer = buffer
-
- @lazy_load_iter
- def ifds (self) :
- """
- Iterate over the primary IFDs in this EXIF.
- """
-
- # starting offset
- offset = self.pread_item(0x04, 'I')
-
- while offset :
- # create and read the IFD, operating on the right sub-buffer
- ifd = IFD(self, self.buf, exif_data.EXIF_TAGS, offset=offset)
-
- # yield it
- yield ifd
-
- # skip to next offset
- offset = ifd.next_offset
-
- def _load_subifd (self, tag, tag_dict) :
- """
- Creates and returns a sub-IFD for the given tag.
- """
-
- # locate it
- offset, = self.tag_values_raw(tag)
-
- # construct the new IFD
- return IFD(self, self.buf, tag_dict, offset=offset)
-
- def tag_data_info (self, tag) :
- """
- Calculate the location, format and size of the given tag's data.
-
- Returns a (fmt, offset, size) tuple.
- """
- # unknown tag?
- if not tag.type_data :
- return None
-
- # data format
- if len(tag.type_format) == 1 :
- # let struct handle the count
- fmt = "%d%s" % (tag.count, tag.type_format)
-
- else :
- # handle the count ourselves
- fmt = tag.type_format * tag.count
-
- # size of the data
- size = self.item_size(fmt)
-
- # inline or external?
- if size > 0x04 :
- # point at the external data
- offset = self.unpack_item('I', tag.data_raw)
-
- else :
- # point at the inline data
- offset = tag.offset + 0x08
-
- return fmt, offset, size
-
- def tag_values_raw (self, tag) :
- """
- Get the raw values for the given tag as a tuple.
-
- Returns None if the tag could not be recognized.
- """
-
- # find the data
- data_info = self.tag_data_info(tag)
-
- # not found?
- if not data_info :
- return None
-
- # unpack
- data_fmt, data_offset, data_size = data_info
-
- # read values
- return self.pread_struct(data_offset, data_fmt)
-
- def tag_values (self, tag) :
- """
- Gets the processed values for the given tag as a list.
- """
-
- # read + process
- return tag.process_values(self.tag_values_raw(tag))
-
- def tag_value (self, tag) :
- """
- Return the human-readable string value for the given tag.
- """
-
- # load the raw values
- values = self.tag_values(tag)
-
- # unknown?
- if not values :
- return ""
-
- # return as comma-separated formatted string, yes
- return tag.readable_value(values)
-
- def get_main_tags (self, **opts) :
- """
- Get the tags for the main image's IFD as a dict.
- """
-
- if not self.ifds :
- # weird case
- raise Exception("No IFD for main image found")
-
- # the main IFD is always the first one
- main_ifd = self.ifds[0]
-
- # do it
- return dict((tag.name, self.tag_value(tag)) for tag in main_ifd.get_tags(**opts))
-
-# mapping from two-byte TIFF byte order marker to struct prefix
-TIFF_BYTE_ORDER = {
- 'II': '<',
- 'MM': '>',
-}
-
-# "An arbitrary but carefully chosen number (42) that further identifies the file as a TIFF file"
-TIFF_BYTEORDER_MAGIC = 42
-
-def tiff_load (file, length=0, **opts) :
- """
- Load the Exif/TIFF data from the given file at its current position with optional length, using exif_load.
- """
-
- # all Exif data offsets are relative to the beginning of this TIFF header
- offset = file.tell()
-
- # mmap the region for the EXIF data
- buffer = mmap_buffer(file, length)
-
- # read byte-order header
- byte_order = file.read(2)
-
- # map to struct prefix
- struct_prefix = TIFF_BYTE_ORDER[byte_order]
-
- # validate
- check_value, = read_struct(file, struct_prefix + 'H')
-
- if check_value != TIFF_BYTEORDER_MAGIC :
- raise Exception("Invalid byte-order for TIFF: %2c -> %d" % (byte_order, check_value))
-
- # build and return the EXIF object with the correct offset/size from the mmap region
- return EXIF(buffer, offset=offset, size=length, **opts)
-
-# the JPEG markers that don't have any data
-JPEG_NOSIZE_MARKERS = (0xD8, 0xD9)
-
-# the first marker in a JPEG File
-JPEG_START_MARKER = 0xD8
-
-# the JPEG APP1 marker used for EXIF
-JPEG_EXIF_MARKER = 0xE1
-
-# the JPEG APP1 Exif header
-JPEG_EXIF_HEADER = "Exif\x00\x00"
-
-def jpeg_markers (file) :
- """
- Iterate over the JPEG markers in the given file, yielding (type_byte, size) tuples.
-
- The size fields will be 0 for markers with no data. The file will be positioned at the beginning of the data
- region, and may be seek'd around if needed.
-
- XXX: find a real implementation of this somewhere?
- """
-
- while True :
- # read type
- marker_byte, marker_type = read_struct(file, '!BB')
-
- # validate
- if marker_byte != 0xff :
- raise Exception("Not a JPEG marker: %x%x" % (marker_byte, marker_type))
-
- # special cases for no data
- if marker_type in JPEG_NOSIZE_MARKERS :
- size = 0
-
- else :
- # read size field
- size, = read_struct(file, '!H')
-
- # validate
- if size < 0x02 :
- raise Exception("Invalid size for marker %x%x: %x" % (marker_byte, marker_type, size))
-
- else :
- # do not count the size field itself
- size = size - 2
-
- # ok, data is at current position
- offset = file.tell()
-
- # yield
- yield marker_type, size
-
- # absolute seek to next marker
- file.seek(offset + size)
-
-def jpeg_find_exif (file) :
- """
- Find the Exif/TIFF section in the given JPEG file.
-
- 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
- be returned.
-
- Returns None if no EXIF section was found.
- """
-
- for count, (marker, size) in enumerate(jpeg_markers(file)) :
- # verify that it's a JPEG file
- if count == 0 :
- # must start with the right marker
- if marker != JPEG_START_MARKER :
- raise Exception("JPEG file must start with 0xFF%02x marker" % (marker, ))
-
- # look for APP1 marker (0xE1) with EXIF signature
- elif marker == JPEG_EXIF_MARKER and file.read(len(JPEG_EXIF_HEADER)) == JPEG_EXIF_HEADER:
- # skipped the initial Exif marker signature
- return size - len(JPEG_EXIF_HEADER)
-
- # nothing
- return None
-
-def jpeg_load (file, **opts) :
- """
- Loads the embedded Exif TIFF data from the given JPEG file using tiff_load.
-
- Returns None if no EXIF data could be found.
- """
-
- # look for the right section
- size = jpeg_find_exif(file)
-
- # not found?
- if not size :
- # nothing
- return
-
- else :
- # load it as TIFF data
- return tiff_load(file, size, **opts)
-
-def load_path (path, **opts) :
- """
- Loads an EXIF object from the given filesystem path.
-
- Returns None if it could not be parsed.
- """
-
- # file extension
- root, fext = os.path.splitext(path)
-
- # map
- func = {
- '.jpeg': jpeg_load,
- '.jpg': jpeg_load,
- '.tiff': tiff_load, # XXX: untested
- }.get(fext.lower())
-
- # not recognized?
- if not func :
- # XXX: sniff the file
- return None
-
- # open it
- file = open(path, 'rb')
-
- # try and load it
- return func(file, **opts)
-
-def dump_tag (exif, i, tag, indent=2) :
- """
- Dump the given tag
- """
-
- data_info = exif.tag_data_info(tag)
-
- if data_info :
- data_fmt, data_offset, data_size = data_info
-
- else :
- data_fmt = data_offset = data_size = None
-
- print "%sTag:%d offset=%#04x(%#08x), tag=%d/%s, type=%d/%s, count=%d, fmt=%s, offset=%#04x, size=%s, is_subifd=%s:" % (
- '\t'*indent,
- i,
- tag.offset, tag.offset + exif.offset,
- tag.tag, tag.name or '???',
- tag.type, tag.type_name if tag.type_data else '???',
- tag.count,
- data_fmt, data_offset, data_size,
- tag.is_subifd(),
- )
-
- if tag.is_subifd() :
- # recurse
- dump_ifd(exif, 0, tag.subifd, indent + 1)
-
- else :
- # dump each value
- values = exif.tag_values(tag)
-
- for i, value in enumerate(values) :
- print "%s\t%02d: %.120r" % ('\t'*indent, i, value)
-
- # and then the readable one
- print "%s\t-> %.120s" % ('\t'*indent, tag.readable_value(values), )
-
-
-def dump_ifd (exif, i, ifd, indent=1) :
- """
- Dump the given IFD, recursively
- """
-
- print "%sIFD:%d offset=%#04x(%#08x), count=%d, next=%d:" % (
- '\t'*indent,
- i,
- ifd.offset, ifd.offset + exif.offset,
- ifd.count,
- ifd.next_offset
- )
-
- for i, tag in enumerate(ifd.tags) :
- # dump
- dump_tag(exif, i, tag, indent + 1)
-
-
-def dump_exif (exif) :
- """
- Dump all tags from the given EXIF object to stdout
- """
-
- print "EXIF offset=%#08x, size=%d:" % (exif.offset, exif.size)
-
- for i, ifd in enumerate(exif.ifds) :
- # dump
- dump_ifd(exif, i, ifd)
-
-
-def list_tags (exif) :
- """
- Print a neat listing of tags to stdout
- """
-
- for k, v in exif.get_main_tags().iteritems() :
- print "%30s: %s" % (k, v)
-
-def main_path (path, dump) :
- # dump path
- print "%s: " % path
-
- # try and load it
- exif = load_path(path)
-
- if not exif :
- raise Exception("No EXIF data found")
-
- if dump :
- # dump everything
- dump_exif(exif)
-
- else :
- # list them
- list_tags(exif)
-
-
-def main (paths, dump=False) :
- """
- Load and dump EXIF data from the given path
- """
-
- # handle each one
- for path in paths :
- main_path(path, dump=dump)
-
-if __name__ == '__main__' :
- import getopt
- from sys import argv
-
- # defaults
- dump = False
-
- # parse args
- opts, args = getopt.getopt(argv[1:], "d", ["dump"])
-
- for opt, val in opts :
- if opt in ('-d', "--dump") :
- dump = True
-
- main(args, dump=dump)
-
--- a/degal/exif_data.py Sun Jun 14 16:09:04 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1304 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
- EXIF file format data, including tag names, types, etc.
-
- Most of this was copied with modifications from EXIFpy:
- # Library to extract EXIF information from digital camera image files
- # http://sourceforge.net/projects/exif-py/
- #
- # VERSION 1.1.0
- #
- # Copyright (c) 2002-2007 Gene Cash All rights reserved
- # Copyright (c) 2007-2008 Ianaré Sévi All rights reserved
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions
- # are met:
- #
- # 1. Redistributions of source code must retain the above copyright
- # notice, this list of conditions and the following disclaimer.
- #
- # 2. Redistributions in binary form must reproduce the above
- # copyright notice, this list of conditions and the following
- # disclaimer in the documentation and/or other materials provided
- # with the distribution.
- #
- # 3. Neither the name of the authors nor the names of its contributors
- # may be used to endorse or promote products derived from this
- # software without specific prior written permission.
- #
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-"""
-
-import decimal, itertools
-
-def filter_ascii (values) :
- """
- Default post-filter for ASCII values.
-
- This takes a single item of string data, splits it up into strings by ASCII-NUL.
-
- These sub-strings are then decoded into unicode as ASCII, and stripped.
- """
-
- return [string.decode('ascii', 'replace').rstrip() for string in values[0].split('\x00') if string]
-
-def build_ratio (num, denom) :
- """
- Builds a Decimal ratio out of the given numerator and denominator
- """
-
- # XXX: this may be slow
- return decimal.Decimal(num) / decimal.Decimal(denom)
-
-def filter_ratio (values) :
- """
- Default post-filter for Ratio values.
-
- This takes the pairs of numerator/denominator values and builds Decimals out of them
- """
-
- return [build_ratio(values[i], values[i + 1]) for i in xrange(0, len(values), 2)]
-
-
-# IFD Tag type information, indexed by code
-# { type_code: (type_fmt, name, filter_func) }
-#
-# type_fmt's that are one char will be prefixed with the count for use with struct.unpack, those with more chars will
-# be repeated as many times for use with struct.unpack.
-FIELD_TYPES = {
-# 0x0000: (None, 'Proprietary' ), # ??? no such type
- 0x0001: ('B', 'Byte', None ),
- 0x0002: ('s', 'ASCII', filter_ascii ),
- 0x0003: ('H', 'Short', None ),
- 0x0004: ('L', 'Long', None ),
- 0x0005: ('LL', 'Ratio', filter_ratio ),
- 0x0006: ('b', 'Signed Byte', None ),
- 0x0007: ('s', 'Undefined', None ),
- 0x0008: ('h', 'Signed Short', None ),
- 0x0009: ('l', 'Signed Long', None ),
- 0x000A: ('ll', 'Signed Ratio', filter_ratio ),
-}
-
-# magic value to indicate sub-IFDs
-SUB_IFD_MAGIC = object()
-
-
-class Tag (object) :
- """
- Represents an Exif Tag
- """
-
- def __init__ (self, name) :
- """
- Build Exif tag with given name, and optional external values-filter function.
- """
-
- self.name = name
-
- def map_values (self, values) :
- """
- Map the given tag value to a printable string using the given value spec.
- """
-
- # default value-mapping
- return ", ".join(str(value) for value in values)
-
-class TagDict (Tag) :
- """
- A tag with a dict mapping values to names
- """
-
- def __init__ (self, name, values_dict) :
- super(TagDict, self).__init__(name)
-
- self.values_dict = values_dict
-
- def map_values (self, values) :
- """
- Map the values through our dict, defaulting to the repr.
- """
-
- return ", ".join(self.values_dict.get(value, repr(value)) for value in values)
-
-class TagFunc (Tag) :
- """
- A tag with a simple function mapping values to names
- """
-
- def __init__ (self, name, values_func) :
- super(TagFunc, self).__init__(name)
-
- self.values_func = values_func
-
- def map_values (self, values) :
- """
- Map the values through our func
- """
-
- return self.values_func(values)
-
-class IFDTag (Tag) :
- """
- A tag that references another IFD
- """
-
- def __init__ (self, name, ifd_tags=None) :
- """
- A tag that points to another IFD block. `ifd_tags`, if given, lists the tags for that block, otherwise,
- the same tags as for the current block are used.
- """
-
- super(IFDTag, self).__init__(name)
-
- self.ifd_tags = ifd_tags
-
-USER_COMMENT_CHARSETS = {
- 'ASCII': ('ascii', 'replace' ),
- 'JIS': ('jis', 'error' ),
-
- # XXX: WTF? What kind of charset is 'Unicode' supposed to be?
- # UTF-16? Little-endian? Big-endian?
- # Confusing reigns: http://www.cpanforum.com/threads/7329
- 'UNICODE': ('utf16', 'error' ),
-}
-
-
-def decode_UserComment (values) :
- """
- A UserComment field starts with an eight-byte encoding designator.
- """
-
- # single binary string
- value, = values
-
- # split up
- charset, comment_raw = value[:8], value[8:]
-
- # strip NILs
- charset = charset.rstrip('\x00')
-
- # map
- encoding, replace = USER_COMMENT_CHARSETS.get(charset, ('ascii', 'replace'))
-
- # decode
- return [comment_raw.decode(encoding, replace)]
-
-# Mappings of Exif tag codes to name and decoding information.
-# { tag : (name, value_dict/value_func/None/SUB_IFD_MAGIC) }
-#
-# name is the official Exif tag name
-# value_dict is a { value: value_name } mapping for human-readable values
-# value_func is a `(values) -> values` mapping function which *overrides* the tag's type_func.
-# XXX: or does it?
-# SUB_IFD_MAGIC signifies that this IFD points to
-# otherwise, the value is left as-is.
-# interoperability tags
-INTR_TAGS = {
- 0x0001: Tag('InteroperabilityIndex'),
- 0x0002: Tag('InteroperabilityVersion'),
- 0x1000: Tag('RelatedImageFileFormat'),
- 0x1001: Tag('RelatedImageWidth'),
- 0x1002: Tag('RelatedImageLength'),
- }
-
-# GPS tags (not used yet, haven't seen camera with GPS)
-GPS_TAGS = {
- 0x0000: Tag('GPSVersionID'),
- 0x0001: Tag('GPSLatitudeRef'),
- 0x0002: Tag('GPSLatitude'),
- 0x0003: Tag('GPSLongitudeRef'),
- 0x0004: Tag('GPSLongitude'),
- 0x0005: Tag('GPSAltitudeRef'),
- 0x0006: Tag('GPSAltitude'),
- 0x0007: Tag('GPSTimeStamp'),
- 0x0008: Tag('GPSSatellites'),
- 0x0009: Tag('GPSStatus'),
- 0x000A: Tag('GPSMeasureMode'),
- 0x000B: Tag('GPSDOP'),
- 0x000C: Tag('GPSSpeedRef'),
- 0x000D: Tag('GPSSpeed'),
- 0x000E: Tag('GPSTrackRef'),
- 0x000F: Tag('GPSTrack'),
- 0x0010: Tag('GPSImgDirectionRef'),
- 0x0011: Tag('GPSImgDirection'),
- 0x0012: Tag('GPSMapDatum'),
- 0x0013: Tag('GPSDestLatitudeRef'),
- 0x0014: Tag('GPSDestLatitude'),
- 0x0015: Tag('GPSDestLongitudeRef'),
- 0x0016: Tag('GPSDestLongitude'),
- 0x0017: Tag('GPSDestBearingRef'),
- 0x0018: Tag('GPSDestBearing'),
- 0x0019: Tag('GPSDestDistanceRef'),
- 0x001A: Tag('GPSDestDistance'),
- 0x001D: Tag('GPSDate'),
- }
-
-
-EXIF_TAGS = {
- 0x0100: Tag('ImageWidth'),
- 0x0101: Tag('ImageLength'),
- 0x0102: Tag('BitsPerSample'),
- 0x0103: TagDict('Compression',
- {1: 'Uncompressed',
- 2: 'CCITT 1D',
- 3: 'T4/Group 3 Fax',
- 4: 'T6/Group 4 Fax',
- 5: 'LZW',
- 6: 'JPEG (old-style)',
- 7: 'JPEG',
- 8: 'Adobe Deflate',
- 9: 'JBIG B&W',
- 10: 'JBIG Color',
- 32766: 'Next',
- 32769: 'Epson ERF Compressed',
- 32771: 'CCIRLEW',
- 32773: 'PackBits',
- 32809: 'Thunderscan',
- 32895: 'IT8CTPAD',
- 32896: 'IT8LW',
- 32897: 'IT8MP',
- 32898: 'IT8BL',
- 32908: 'PixarFilm',
- 32909: 'PixarLog',
- 32946: 'Deflate',
- 32947: 'DCS',
- 34661: 'JBIG',
- 34676: 'SGILog',
- 34677: 'SGILog24',
- 34712: 'JPEG 2000',
- 34713: 'Nikon NEF Compressed',
- 65000: 'Kodak DCR Compressed',
- 65535: 'Pentax PEF Compressed'}),
- 0x0106: Tag('PhotometricInterpretation'),
- 0x0107: Tag('Thresholding'),
- 0x010A: Tag('FillOrder'),
- 0x010D: Tag('DocumentName'),
- 0x010E: Tag('ImageDescription'),
- 0x010F: Tag('Make'),
- 0x0110: Tag('Model'),
- 0x0111: Tag('StripOffsets'),
- 0x0112: TagDict('Orientation',
- {1: 'Horizontal (normal)',
- 2: 'Mirrored horizontal',
- 3: 'Rotated 180',
- 4: 'Mirrored vertical',
- 5: 'Mirrored horizontal then rotated 90 CCW',
- 6: 'Rotated 90 CW',
- 7: 'Mirrored horizontal then rotated 90 CW',
- 8: 'Rotated 90 CCW'}),
- 0x0115: Tag('SamplesPerPixel'),
- 0x0116: Tag('RowsPerStrip'),
- 0x0117: Tag('StripByteCounts'),
- 0x011A: Tag('XResolution'),
- 0x011B: Tag('YResolution'),
- 0x011C: Tag('PlanarConfiguration'),
- 0x011D: Tag('PageName'),
- 0x0128: TagDict('ResolutionUnit',
- {1: 'Not Absolute',
- 2: 'Pixels/Inch',
- 3: 'Pixels/Centimeter'}),
- 0x012D: Tag('TransferFunction'),
- 0x0131: Tag('Software'),
- 0x0132: Tag('DateTime'),
- 0x013B: Tag('Artist'),
- 0x013E: Tag('WhitePoint'),
- 0x013F: Tag('PrimaryChromaticities'),
- 0x0156: Tag('TransferRange'),
- 0x0200: Tag('JPEGProc'),
- 0x0201: Tag('JPEGInterchangeFormat'),
- 0x0202: Tag('JPEGInterchangeFormatLength'),
- 0x0211: Tag('YCbCrCoefficients'),
- 0x0212: Tag('YCbCrSubSampling'),
- 0x0213: TagDict('YCbCrPositioning',
- {1: 'Centered',
- 2: 'Co-sited'}),
- 0x0214: Tag('ReferenceBlackWhite'),
-
- 0x4746: Tag('Rating'),
-
- 0x828D: Tag('CFARepeatPatternDim'),
- 0x828E: Tag('CFAPattern'),
- 0x828F: Tag('BatteryLevel'),
- 0x8298: Tag('Copyright'),
- 0x829A: Tag('ExposureTime'),
- 0x829D: Tag('FNumber'),
- 0x83BB: Tag('IPTC/NAA'),
- 0x8769: IFDTag('ExifOffset', None),
- 0x8773: Tag('InterColorProfile'),
- 0x8822: TagDict('ExposureProgram',
- {0: 'Unidentified',
- 1: 'Manual',
- 2: 'Program Normal',
- 3: 'Aperture Priority',
- 4: 'Shutter Priority',
- 5: 'Program Creative',
- 6: 'Program Action',
- 7: 'Portrait Mode',
- 8: 'Landscape Mode'}),
- 0x8824: Tag('SpectralSensitivity'),
- 0x8825: IFDTag('GPSInfo', GPS_TAGS),
- 0x8827: Tag('ISOSpeedRatings'),
- 0x8828: Tag('OECF'),
- 0x9000: Tag('ExifVersion'),
- 0x9003: Tag('DateTimeOriginal'),
- 0x9004: Tag('DateTimeDigitized'),
- 0x9101: TagDict('ComponentsConfiguration',
- {0: '',
- 1: 'Y',
- 2: 'Cb',
- 3: 'Cr',
- 4: 'Red',
- 5: 'Green',
- 6: 'Blue'}),
- 0x9102: Tag('CompressedBitsPerPixel'),
- 0x9201: Tag('ShutterSpeedValue'),
- 0x9202: Tag('ApertureValue'),
- 0x9203: Tag('BrightnessValue'),
- 0x9204: Tag('ExposureBiasValue'),
- 0x9205: Tag('MaxApertureValue'),
- 0x9206: Tag('SubjectDistance'),
- 0x9207: TagDict('MeteringMode',
- {0: 'Unidentified',
- 1: 'Average',
- 2: 'CenterWeightedAverage',
- 3: 'Spot',
- 4: 'MultiSpot',
- 5: 'Pattern'}),
- 0x9208: TagDict('LightSource',
- {0: 'Unknown',
- 1: 'Daylight',
- 2: 'Fluorescent',
- 3: 'Tungsten',
- 9: 'Fine Weather',
- 10: 'Flash',
- 11: 'Shade',
- 12: 'Daylight Fluorescent',
- 13: 'Day White Fluorescent',
- 14: 'Cool White Fluorescent',
- 15: 'White Fluorescent',
- 17: 'Standard Light A',
- 18: 'Standard Light B',
- 19: 'Standard Light C',
- 20: 'D55',
- 21: 'D65',
- 22: 'D75',
- 255: 'Other'}),
- 0x9209: TagDict('Flash',
- {0: 'No',
- 1: 'Fired',
- 5: 'Fired (?)', # no return sensed
- 7: 'Fired (!)', # return sensed
- 9: 'Fill Fired',
- 13: 'Fill Fired (?)',
- 15: 'Fill Fired (!)',
- 16: 'Off',
- 24: 'Auto Off',
- 25: 'Auto Fired',
- 29: 'Auto Fired (?)',
- 31: 'Auto Fired (!)',
- 32: 'Not Available'}),
- 0x920A: Tag('FocalLength'),
- 0x9214: Tag('SubjectArea'),
- 0x927C: Tag('MakerNote'),
- 0x9286: TagFunc('UserComment', decode_UserComment),
- 0x9290: Tag('SubSecTime'),
- 0x9291: Tag('SubSecTimeOriginal'),
- 0x9292: Tag('SubSecTimeDigitized'),
-
- # used by Windows Explorer
- 0x9C9B: Tag('XPTitle'),
- 0x9C9C: Tag('XPComment'),
- 0x9C9D: Tag('XPAuthor'), #(ignored by Windows Explorer if Artist exists)
- 0x9C9E: Tag('XPKeywords'),
- 0x9C9F: Tag('XPSubject'),
-
- 0xA000: Tag('FlashPixVersion'),
- 0xA001: TagDict('ColorSpace',
- {1: 'sRGB',
- 2: 'Adobe RGB',
- 65535: 'Uncalibrated'}),
- 0xA002: Tag('ExifImageWidth'),
- 0xA003: Tag('ExifImageLength'),
- 0xA005: IFDTag('InteroperabilityOffset', INTR_TAGS),
- 0xA20B: Tag('FlashEnergy'), # 0x920B in TIFF/EP
- 0xA20C: Tag('SpatialFrequencyResponse'), # 0x920C
- 0xA20E: Tag('FocalPlaneXResolution'), # 0x920E
- 0xA20F: Tag('FocalPlaneYResolution'), # 0x920F
- 0xA210: Tag('FocalPlaneResolutionUnit'), # 0x9210
- 0xA214: Tag('SubjectLocation'), # 0x9214
- 0xA215: Tag('ExposureIndex'), # 0x9215
- 0xA217: TagDict('SensingMethod', # 0x9217
- {1: 'Not defined',
- 2: 'One-chip color area',
- 3: 'Two-chip color area',
- 4: 'Three-chip color area',
- 5: 'Color sequential area',
- 7: 'Trilinear',
- 8: 'Color sequential linear'}),
- 0xA300: TagDict('FileSource',
- {1: 'Film Scanner',
- 2: 'Reflection Print Scanner',
- 3: 'Digital Camera'}),
- 0xA301: TagDict('SceneType',
- {1: 'Directly Photographed'}),
- 0xA302: Tag('CVAPattern'),
- 0xA401: TagDict('CustomRendered',
- {0: 'Normal',
- 1: 'Custom'}),
- 0xA402: TagDict('ExposureMode',
- {0: 'Auto Exposure',
- 1: 'Manual Exposure',
- 2: 'Auto Bracket'}),
- 0xA403: TagDict('WhiteBalance',
- {0: 'Auto',
- 1: 'Manual'}),
- 0xA404: Tag('DigitalZoomRatio'),
- 0xA405: ('FocalLengthIn35mmFilm', None),
- 0xA406: TagDict('SceneCaptureType',
- {0: 'Standard',
- 1: 'Landscape',
- 2: 'Portrait',
- 3: 'Night)'}),
- 0xA407: TagDict('GainControl',
- {0: 'None',
- 1: 'Low gain up',
- 2: 'High gain up',
- 3: 'Low gain down',
- 4: 'High gain down'}),
- 0xA408: TagDict('Contrast',
- {0: 'Normal',
- 1: 'Soft',
- 2: 'Hard'}),
- 0xA409: TagDict('Saturation',
- {0: 'Normal',
- 1: 'Soft',
- 2: 'Hard'}),
- 0xA40A: TagDict('Sharpness',
- {0: 'Normal',
- 1: 'Soft',
- 2: 'Hard'}),
- 0xA40B: Tag('DeviceSettingDescription'),
- 0xA40C: Tag('SubjectDistanceRange'),
- 0xA500: Tag('Gamma'),
- 0xC4A5: Tag('PrintIM'),
- 0xEA1C: ('Padding', None),
- }
-
-# http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp
-def nikon_ev_bias (seq) :
- """
- # First digit seems to be in steps of 1/6 EV.
- # Does the third value mean the step size? It is usually 6,
- # but it is 12 for the ExposureDifference.
- """
-
- # check for an error condition that could cause a crash.
- # this only happens if something has gone really wrong in
- # reading the Nikon MakerNote.
- if len(seq) < 4 :
- return ""
-
- if seq == [252, 1, 6, 0]:
- return "-2/3 EV"
-
- if seq == [253, 1, 6, 0]:
- return "-1/2 EV"
-
- if seq == [254, 1, 6, 0]:
- return "-1/3 EV"
-
- if seq == [0, 1, 6, 0]:
- return "0 EV"
-
- if seq == [2, 1, 6, 0]:
- return "+1/3 EV"
-
- if seq == [3, 1, 6, 0]:
- return "+1/2 EV"
-
- if seq == [4, 1, 6, 0]:
- return "+2/3 EV"
-
- # handle combinations not in the table.
- a = seq[0]
-
- # causes headaches for the +/- logic, so special case it.
- if a == 0:
- return "0 EV"
-
- if a > 127:
- a = 256 - a
- ret_str = "-"
- else:
- ret_str = "+"
-
- b = seq[2] # assume third value means the step size
-
- whole = a / b
-
- a = a % b
-
- if whole != 0 :
- ret_str = ret_str + str(whole) + " "
-
- if a == 0 :
- ret_str = ret_str + "EV"
- else :
- r = Ratio(a, b)
- ret_str = ret_str + r.__repr__() + " EV"
-
- return ret_str
-
-# Nikon E99x MakerNote Tags
-MAKERNOTE_NIKON_NEWER_TAGS={
- 0x0001: Tag('MakernoteVersion'), # Sometimes binary
- 0x0002: Tag('ISOSetting'),
- 0x0003: Tag('ColorMode'),
- 0x0004: Tag('Quality'),
- 0x0005: Tag('Whitebalance'),
- 0x0006: Tag('ImageSharpening'),
- 0x0007: Tag('FocusMode'),
- 0x0008: Tag('FlashSetting'),
- 0x0009: Tag('AutoFlashMode'),
- 0x000B: Tag('WhiteBalanceBias'),
- 0x000C: Tag('WhiteBalanceRBCoeff'),
- 0x000D: TagFunc('ProgramShift', nikon_ev_bias),
- # Nearly the same as the other EV vals, but step size is 1/12 EV (?)
- 0x000E: TagFunc('ExposureDifference', nikon_ev_bias),
- 0x000F: Tag('ISOSelection'),
- 0x0011: Tag('NikonPreview'),
- 0x0012: TagFunc('FlashCompensation', nikon_ev_bias),
- 0x0013: Tag('ISOSpeedRequested'),
- 0x0016: Tag('PhotoCornerCoordinates'),
- # 0x0017: Unknown, but most likely an EV value
- 0x0018: TagFunc('FlashBracketCompensationApplied', nikon_ev_bias),
- 0x0019: Tag('AEBracketCompensationApplied'),
- 0x001A: Tag('ImageProcessing'),
- 0x001B: Tag('CropHiSpeed'),
- 0x001D: Tag('SerialNumber'), # Conflict with 0x00A0 ?
- 0x001E: Tag('ColorSpace'),
- 0x001F: Tag('VRInfo'),
- 0x0020: Tag('ImageAuthentication'),
- 0x0022: Tag('ActiveDLighting'),
- 0x0023: Tag('PictureControl'),
- 0x0024: Tag('WorldTime'),
- 0x0025: Tag('ISOInfo'),
- 0x0080: Tag('ImageAdjustment'),
- 0x0081: Tag('ToneCompensation'),
- 0x0082: Tag('AuxiliaryLens'),
- 0x0083: Tag('LensType'),
- 0x0084: Tag('LensMinMaxFocalMaxAperture'),
- 0x0085: Tag('ManualFocusDistance'),
- 0x0086: Tag('DigitalZoomFactor'),
- 0x0087: TagDict('FlashMode',
- {0x00: 'Did Not Fire',
- 0x01: 'Fired, Manual',
- 0x07: 'Fired, External',
- 0x08: 'Fired, Commander Mode ',
- 0x09: 'Fired, TTL Mode'}),
- 0x0088: TagDict('AFFocusPosition',
- {0x0000: 'Center',
- 0x0100: 'Top',
- 0x0200: 'Bottom',
- 0x0300: 'Left',
- 0x0400: 'Right'}),
- 0x0089: TagDict('BracketingMode',
- {0x00: 'Single frame, no bracketing',
- 0x01: 'Continuous, no bracketing',
- 0x02: 'Timer, no bracketing',
- 0x10: 'Single frame, exposure bracketing',
- 0x11: 'Continuous, exposure bracketing',
- 0x12: 'Timer, exposure bracketing',
- 0x40: 'Single frame, white balance bracketing',
- 0x41: 'Continuous, white balance bracketing',
- 0x42: 'Timer, white balance bracketing'}),
- 0x008A: Tag('AutoBracketRelease'),
- 0x008B: Tag('LensFStops'),
- 0x008C: ('NEFCurve1', None), # ExifTool calls this 'ContrastCurve'
- 0x008D: Tag('ColorMode'),
- 0x008F: Tag('SceneMode'),
- 0x0090: Tag('LightingType'),
- 0x0091: Tag('ShotInfo'), # First 4 bytes are a version number in ASCII
- 0x0092: Tag('HueAdjustment'),
- # ExifTool calls this 'NEFCompression', should be 1-4
- 0x0093: Tag('Compression'),
- 0x0094: TagDict('Saturation',
- {-3: 'B&W',
- -2: '-2',
- -1: '-1',
- 0: '0',
- 1: '1',
- 2: '2'}),
- 0x0095: Tag('NoiseReduction'),
- 0x0096: ('NEFCurve2', None), # ExifTool calls this 'LinearizationTable'
- 0x0097: Tag('ColorBalance'), # First 4 bytes are a version number in ASCII
- 0x0098: Tag('LensData'), # First 4 bytes are a version number in ASCII
- 0x0099: Tag('RawImageCenter'),
- 0x009A: Tag('SensorPixelSize'),
- 0x009C: Tag('Scene Assist'),
- 0x009E: Tag('RetouchHistory'),
- 0x00A0: Tag('SerialNumber'),
- 0x00A2: Tag('ImageDataSize'),
- # 00A3: unknown - a single byte 0
- # 00A4: In NEF, looks like a 4 byte ASCII version number ('0200')
- 0x00A5: Tag('ImageCount'),
- 0x00A6: Tag('DeletedImageCount'),
- 0x00A7: Tag('TotalShutterReleases'),
- # First 4 bytes are a version number in ASCII, with version specific
- # info to follow. Its hard to treat it as a string due to embedded nulls.
- 0x00A8: Tag('FlashInfo'),
- 0x00A9: Tag('ImageOptimization'),
- 0x00AA: Tag('Saturation'),
- 0x00AB: Tag('DigitalVariProgram'),
- 0x00AC: Tag('ImageStabilization'),
- 0x00AD: Tag('Responsive AF'), # 'AFResponse'
- 0x00B0: Tag('MultiExposure'),
- 0x00B1: Tag('HighISONoiseReduction'),
- 0x00B7: Tag('AFInfo'),
- 0x00B8: Tag('FileInfo'),
- # 00B9: unknown
- 0x0100: Tag('DigitalICE'),
- 0x0103: TagDict('PreviewCompression',
- {1: 'Uncompressed',
- 2: 'CCITT 1D',
- 3: 'T4/Group 3 Fax',
- 4: 'T6/Group 4 Fax',
- 5: 'LZW',
- 6: 'JPEG (old-style)',
- 7: 'JPEG',
- 8: 'Adobe Deflate',
- 9: 'JBIG B&W',
- 10: 'JBIG Color',
- 32766: 'Next',
- 32769: 'Epson ERF Compressed',
- 32771: 'CCIRLEW',
- 32773: 'PackBits',
- 32809: 'Thunderscan',
- 32895: 'IT8CTPAD',
- 32896: 'IT8LW',
- 32897: 'IT8MP',
- 32898: 'IT8BL',
- 32908: 'PixarFilm',
- 32909: 'PixarLog',
- 32946: 'Deflate',
- 32947: 'DCS',
- 34661: 'JBIG',
- 34676: 'SGILog',
- 34677: 'SGILog24',
- 34712: 'JPEG 2000',
- 34713: 'Nikon NEF Compressed',
- 65000: 'Kodak DCR Compressed',
- 65535: 'Pentax PEF Compressed',}),
- 0x0201: Tag('PreviewImageStart'),
- 0x0202: Tag('PreviewImageLength'),
- 0x0213: TagDict('PreviewYCbCrPositioning',
- {1: 'Centered',
- 2: 'Co-sited'}),
- 0x0010: Tag('DataDump'),
- }
-
-MAKERNOTE_NIKON_OLDER_TAGS = {
- 0x0003: TagDict('Quality',
- {1: 'VGA Basic',
- 2: 'VGA Normal',
- 3: 'VGA Fine',
- 4: 'SXGA Basic',
- 5: 'SXGA Normal',
- 6: 'SXGA Fine'}),
- 0x0004: TagDict('ColorMode',
- {1: 'Color',
- 2: 'Monochrome'}),
- 0x0005: TagDict('ImageAdjustment',
- {0: 'Normal',
- 1: 'Bright+',
- 2: 'Bright-',
- 3: 'Contrast+',
- 4: 'Contrast-'}),
- 0x0006: TagDict('CCDSpeed',
- {0: 'ISO 80',
- 2: 'ISO 160',
- 4: 'ISO 320',
- 5: 'ISO 100'}),
- 0x0007: TagDict('WhiteBalance',
- {0: 'Auto',
- 1: 'Preset',
- 2: 'Daylight',
- 3: 'Incandescent',
- 4: 'Fluorescent',
- 5: 'Cloudy',
- 6: 'Speed Light'}),
- }
-
-def olympus_special_mode (values) :
- """
- Decode Olympus SpecialMode tag in MakerNote
- """
-
- a = {
- 0: 'Normal',
- 1: 'Unknown',
- 2: 'Fast',
- 3: 'Panorama'
- }
-
- b = {
- 0: 'Non-panoramic',
- 1: 'Left to right',
- 2: 'Right to left',
- 3: 'Bottom to top',
- 4: 'Top to bottom'
- }
-
- if v[0] not in a or v[2] not in b:
- return values
-
- return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
-
-MAKERNOTE_OLYMPUS_TAGS={
- # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
- # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
- 0x0100: Tag('JPEGThumbnail'),
- 0x0200: TagFunc('SpecialMode', olympus_special_mode),
- 0x0201: TagDict('JPEGQual',
- {1: 'SQ',
- 2: 'HQ',
- 3: 'SHQ'}),
- 0x0202: TagDict('Macro',
- {0: 'Normal',
- 1: 'Macro',
- 2: 'SuperMacro'}),
- 0x0203: TagDict('BWMode',
- {0: 'Off',
- 1: 'On'}),
- 0x0204: Tag('DigitalZoom'),
- 0x0205: Tag('FocalPlaneDiagonal'),
- 0x0206: Tag('LensDistortionParams'),
- 0x0207: Tag('SoftwareRelease'),
- 0x0208: Tag('PictureInfo'),
- 0x0209: Tag('CameraID'), # print as string
- 0x0F00: Tag('DataDump'),
- 0x0300: Tag('PreCaptureFrames'),
- 0x0404: Tag('SerialNumber'),
- 0x1000: Tag('ShutterSpeedValue'),
- 0x1001: Tag('ISOValue'),
- 0x1002: Tag('ApertureValue'),
- 0x1003: Tag('BrightnessValue'),
- 0x1004: Tag('FlashMode'),
- 0x1004: TagDict('FlashMode',
- {2: 'On',
- 3: 'Off'}),
- 0x1005: TagDict('FlashDevice',
- {0: 'None',
- 1: 'Internal',
- 4: 'External',
- 5: 'Internal + External'}),
- 0x1006: Tag('ExposureCompensation'),
- 0x1007: Tag('SensorTemperature'),
- 0x1008: Tag('LensTemperature'),
- 0x100b: TagDict('FocusMode',
- {0: 'Auto',
- 1: 'Manual'}),
- 0x1017: Tag('RedBalance'),
- 0x1018: Tag('BlueBalance'),
- 0x101a: Tag('SerialNumber'),
- 0x1023: Tag('FlashExposureComp'),
- 0x1026: TagDict('ExternalFlashBounce',
- {0: 'No',
- 1: 'Yes'}),
- 0x1027: Tag('ExternalFlashZoom'),
- 0x1028: Tag('ExternalFlashMode'),
- 0x1029: ('Contrast int16u',
- {0: 'High',
- 1: 'Normal',
- 2: 'Low'}),
- 0x102a: Tag('SharpnessFactor'),
- 0x102b: Tag('ColorControl'),
- 0x102c: Tag('ValidBits'),
- 0x102d: Tag('CoringFilter'),
- 0x102e: Tag('OlympusImageWidth'),
- 0x102f: Tag('OlympusImageHeight'),
- 0x1034: Tag('CompressionRatio'),
- 0x1035: TagDict('PreviewImageValid',
- {0: 'No',
- 1: 'Yes'}),
- 0x1036: Tag('PreviewImageStart'),
- 0x1037: Tag('PreviewImageLength'),
- 0x1039: TagDict('CCDScanMode',
- {0: 'Interlaced',
- 1: 'Progressive'}),
- 0x103a: TagDict('NoiseReduction',
- {0: 'Off',
- 1: 'On'}),
- 0x103b: Tag('InfinityLensStep'),
- 0x103c: Tag('NearLensStep'),
-
- # TODO - these need extra definitions
- # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html
- 0x2010: Tag('Equipment'),
- 0x2020: Tag('CameraSettings'),
- 0x2030: Tag('RawDevelopment'),
- 0x2040: Tag('ImageProcessing'),
- 0x2050: Tag('FocusInfo'),
- 0x3000: Tag('RawInfo '),
- }
-
-# 0x2020 CameraSettings
-MAKERNOTE_OLYMPUS_TAG_0x2020={
- 0x0100: TagDict('PreviewImageValid',
- {0: 'No',
- 1: 'Yes'}),
- 0x0101: Tag('PreviewImageStart'),
- 0x0102: Tag('PreviewImageLength'),
- 0x0200: TagDict('ExposureMode',
- {1: 'Manual',
- 2: 'Program',
- 3: 'Aperture-priority AE',
- 4: 'Shutter speed priority AE',
- 5: 'Program-shift'}),
- 0x0201: TagDict('AELock',
- {0: 'Off',
- 1: 'On'}),
- 0x0202: TagDict('MeteringMode',
- {2: 'Center Weighted',
- 3: 'Spot',
- 5: 'ESP',
- 261: 'Pattern+AF',
- 515: 'Spot+Highlight control',
- 1027: 'Spot+Shadow control'}),
- 0x0300: TagDict('MacroMode',
- {0: 'Off',
- 1: 'On'}),
- 0x0301: TagDict('FocusMode',
- {0: 'Single AF',
- 1: 'Sequential shooting AF',
- 2: 'Continuous AF',
- 3: 'Multi AF',
- 10: 'MF'}),
- 0x0302: TagDict('FocusProcess',
- {0: 'AF Not Used',
- 1: 'AF Used'}),
- 0x0303: TagDict('AFSearch',
- {0: 'Not Ready',
- 1: 'Ready'}),
- 0x0304: Tag('AFAreas'),
- 0x0401: Tag('FlashExposureCompensation'),
- 0x0500: ('WhiteBalance2',
- {0: 'Auto',
- 16: '7500K (Fine Weather with Shade)',
- 17: '6000K (Cloudy)',
- 18: '5300K (Fine Weather)',
- 20: '3000K (Tungsten light)',
- 21: '3600K (Tungsten light-like)',
- 33: '6600K (Daylight fluorescent)',
- 34: '4500K (Neutral white fluorescent)',
- 35: '4000K (Cool white fluorescent)',
- 48: '3600K (Tungsten light-like)',
- 256: 'Custom WB 1',
- 257: 'Custom WB 2',
- 258: 'Custom WB 3',
- 259: 'Custom WB 4',
- 512: 'Custom WB 5400K',
- 513: 'Custom WB 2900K',
- 514: 'Custom WB 8000K', }),
- 0x0501: Tag('WhiteBalanceTemperature'),
- 0x0502: Tag('WhiteBalanceBracket'),
- 0x0503: Tag('CustomSaturation'), # (3 numbers: 1. CS Value, 2. Min, 3. Max)
- 0x0504: TagDict('ModifiedSaturation',
- {0: 'Off',
- 1: 'CM1 (Red Enhance)',
- 2: 'CM2 (Green Enhance)',
- 3: 'CM3 (Blue Enhance)',
- 4: 'CM4 (Skin Tones)'}),
- 0x0505: Tag('ContrastSetting'), # (3 numbers: 1. Contrast, 2. Min, 3. Max)
- 0x0506: Tag('SharpnessSetting'), # (3 numbers: 1. Sharpness, 2. Min, 3. Max)
- 0x0507: TagDict('ColorSpace',
- {0: 'sRGB',
- 1: 'Adobe RGB',
- 2: 'Pro Photo RGB'}),
- 0x0509: TagDict('SceneMode',
- {0: 'Standard',
- 6: 'Auto',
- 7: 'Sport',
- 8: 'Portrait',
- 9: 'Landscape+Portrait',
- 10: 'Landscape',
- 11: 'Night scene',
- 13: 'Panorama',
- 16: 'Landscape+Portrait',
- 17: 'Night+Portrait',
- 19: 'Fireworks',
- 20: 'Sunset',
- 22: 'Macro',
- 25: 'Documents',
- 26: 'Museum',
- 28: 'Beach&Snow',
- 30: 'Candle',
- 35: 'Underwater Wide1',
- 36: 'Underwater Macro',
- 39: 'High Key',
- 40: 'Digital Image Stabilization',
- 44: 'Underwater Wide2',
- 45: 'Low Key',
- 46: 'Children',
- 48: 'Nature Macro'}),
- 0x050a: TagDict('NoiseReduction',
- {0: 'Off',
- 1: 'Noise Reduction',
- 2: 'Noise Filter',
- 3: 'Noise Reduction + Noise Filter',
- 4: 'Noise Filter (ISO Boost)',
- 5: 'Noise Reduction + Noise Filter (ISO Boost)'}),
- 0x050b: TagDict('DistortionCorrection',
- {0: 'Off',
- 1: 'On'}),
- 0x050c: TagDict('ShadingCompensation',
- {0: 'Off',
- 1: 'On'}),
- 0x050d: Tag('CompressionFactor'),
- 0x050f: TagDict('Gradation',
- {'-1 -1 1': 'Low Key',
- '0 -1 1': 'Normal',
- '1 -1 1': 'High Key'}),
- 0x0520: TagDict('PictureMode',
- {1: 'Vivid',
- 2: 'Natural',
- 3: 'Muted',
- 256: 'Monotone',
- 512: 'Sepia'}),
- 0x0521: Tag('PictureModeSaturation'),
- 0x0522: Tag('PictureModeHue?'),
- 0x0523: Tag('PictureModeContrast'),
- 0x0524: Tag('PictureModeSharpness'),
- 0x0525: TagDict('PictureModeBWFilter',
- {0: 'n/a',
- 1: 'Neutral',
- 2: 'Yellow',
- 3: 'Orange',
- 4: 'Red',
- 5: 'Green'}),
- 0x0526: TagDict('PictureModeTone',
- {0: 'n/a',
- 1: 'Neutral',
- 2: 'Sepia',
- 3: 'Blue',
- 4: 'Purple',
- 5: 'Green'}),
- 0x0600: Tag('Sequence'), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits
- 0x0601: Tag('PanoramaMode'), # (2 numbers: 1. Mode, 2. Shot number)
- 0x0603: ('ImageQuality2',
- {1: 'SQ',
- 2: 'HQ',
- 3: 'SHQ',
- 4: 'RAW'}),
- 0x0901: Tag('ManometerReading'),
- }
-
-
-MAKERNOTE_CASIO_TAGS={
- 0x0001: TagDict('RecordingMode',
- {1: 'Single Shutter',
- 2: 'Panorama',
- 3: 'Night Scene',
- 4: 'Portrait',
- 5: 'Landscape'}),
- 0x0002: TagDict('Quality',
- {1: 'Economy',
- 2: 'Normal',
- 3: 'Fine'}),
- 0x0003: TagDict('FocusingMode',
- {2: 'Macro',
- 3: 'Auto Focus',
- 4: 'Manual Focus',
- 5: 'Infinity'}),
- 0x0004: TagDict('FlashMode',
- {1: 'Auto',
- 2: 'On',
- 3: 'Off',
- 4: 'Red Eye Reduction'}),
- 0x0005: TagDict('FlashIntensity',
- {11: 'Weak',
- 13: 'Normal',
- 15: 'Strong'}),
- 0x0006: Tag('Object Distance'),
- 0x0007: TagDict('WhiteBalance',
- {1: 'Auto',
- 2: 'Tungsten',
- 3: 'Daylight',
- 4: 'Fluorescent',
- 5: 'Shade',
- 129: 'Manual'}),
- 0x000B: TagDict('Sharpness',
- {0: 'Normal',
- 1: 'Soft',
- 2: 'Hard'}),
- 0x000C: TagDict('Contrast',
- {0: 'Normal',
- 1: 'Low',
- 2: 'High'}),
- 0x000D: TagDict('Saturation',
- {0: 'Normal',
- 1: 'Low',
- 2: 'High'}),
- 0x0014: TagDict('CCDSpeed',
- {64: 'Normal',
- 80: 'Normal',
- 100: 'High',
- 125: '+1.0',
- 244: '+3.0',
- 250: '+2.0'}),
- }
-
-MAKERNOTE_FUJIFILM_TAGS={
- 0x0000: Tag('NoteVersion'),
- 0x1000: Tag('Quality'),
- 0x1001: TagDict('Sharpness',
- {1: 'Soft',
- 2: 'Soft',
- 3: 'Normal',
- 4: 'Hard',
- 5: 'Hard'}),
- 0x1002: TagDict('WhiteBalance',
- {0: 'Auto',
- 256: 'Daylight',
- 512: 'Cloudy',
- 768: 'DaylightColor-Fluorescent',
- 769: 'DaywhiteColor-Fluorescent',
- 770: 'White-Fluorescent',
- 1024: 'Incandescent',
- 3840: 'Custom'}),
- 0x1003: TagDict('Color',
- {0: 'Normal',
- 256: 'High',
- 512: 'Low'}),
- 0x1004: TagDict('Tone',
- {0: 'Normal',
- 256: 'High',
- 512: 'Low'}),
- 0x1010: TagDict('FlashMode',
- {0: 'Auto',
- 1: 'On',
- 2: 'Off',
- 3: 'Red Eye Reduction'}),
- 0x1011: Tag('FlashStrength'),
- 0x1020: TagDict('Macro',
- {0: 'Off',
- 1: 'On'}),
- 0x1021: TagDict('FocusMode',
- {0: 'Auto',
- 1: 'Manual'}),
- 0x1030: TagDict('SlowSync',
- {0: 'Off',
- 1: 'On'}),
- 0x1031: TagDict('PictureMode',
- {0: 'Auto',
- 1: 'Portrait',
- 2: 'Landscape',
- 4: 'Sports',
- 5: 'Night',
- 6: 'Program AE',
- 256: 'Aperture Priority AE',
- 512: 'Shutter Priority AE',
- 768: 'Manual Exposure'}),
- 0x1100: TagDict('MotorOrBracket',
- {0: 'Off',
- 1: 'On'}),
- 0x1300: TagDict('BlurWarning',
- {0: 'Off',
- 1: 'On'}),
- 0x1301: TagDict('FocusWarning',
- {0: 'Off',
- 1: 'On'}),
- 0x1302: TagDict('AEWarning',
- {0: 'Off',
- 1: 'On'}),
- }
-
-MAKERNOTE_CANON_TAGS = {
- 0x0006: Tag('ImageType'),
- 0x0007: Tag('FirmwareVersion'),
- 0x0008: Tag('ImageNumber'),
- 0x0009: Tag('OwnerName'),
- }
-
-# this is in element offset, name, optional value dictionary format
-MAKERNOTE_CANON_TAG_0x001 = {
- 1: TagDict('Macromode',
- {1: 'Macro',
- 2: 'Normal'}),
- 2: Tag('SelfTimer'),
- 3: TagDict('Quality',
- {2: 'Normal',
- 3: 'Fine',
- 5: 'Superfine'}),
- 4: TagDict('FlashMode',
- {0: 'Flash Not Fired',
- 1: 'Auto',
- 2: 'On',
- 3: 'Red-Eye Reduction',
- 4: 'Slow Synchro',
- 5: 'Auto + Red-Eye Reduction',
- 6: 'On + Red-Eye Reduction',
- 16: 'external flash'}),
- 5: TagDict('ContinuousDriveMode',
- {0: 'Single Or Timer',
- 1: 'Continuous'}),
- 7: TagDict('FocusMode',
- {0: 'One-Shot',
- 1: 'AI Servo',
- 2: 'AI Focus',
- 3: 'MF',
- 4: 'Single',
- 5: 'Continuous',
- 6: 'MF'}),
- 10: TagDict('ImageSize',
- {0: 'Large',
- 1: 'Medium',
- 2: 'Small'}),
- 11: TagDict('EasyShootingMode',
- {0: 'Full Auto',
- 1: 'Manual',
- 2: 'Landscape',
- 3: 'Fast Shutter',
- 4: 'Slow Shutter',
- 5: 'Night',
- 6: 'B&W',
- 7: 'Sepia',
- 8: 'Portrait',
- 9: 'Sports',
- 10: 'Macro/Close-Up',
- 11: 'Pan Focus'}),
- 12: TagDict('DigitalZoom',
- {0: 'None',
- 1: '2x',
- 2: '4x'}),
- 13: TagDict('Contrast',
- {0xFFFF: 'Low',
- 0: 'Normal',
- 1: 'High'}),
- 14: TagDict('Saturation',
- {0xFFFF: 'Low',
- 0: 'Normal',
- 1: 'High'}),
- 15: TagDict('Sharpness',
- {0xFFFF: 'Low',
- 0: 'Normal',
- 1: 'High'}),
- 16: TagDict('ISO',
- {0: 'See ISOSpeedRatings Tag',
- 15: 'Auto',
- 16: '50',
- 17: '100',
- 18: '200',
- 19: '400'}),
- 17: TagDict('MeteringMode',
- {3: 'Evaluative',
- 4: 'Partial',
- 5: 'Center-weighted'}),
- 18: TagDict('FocusType',
- {0: 'Manual',
- 1: 'Auto',
- 3: 'Close-Up (Macro)',
- 8: 'Locked (Pan Mode)'}),
- 19: TagDict('AFPointSelected',
- {0x3000: 'None (MF)',
- 0x3001: 'Auto-Selected',
- 0x3002: 'Right',
- 0x3003: 'Center',
- 0x3004: 'Left'}),
- 20: TagDict('ExposureMode',
- {0: 'Easy Shooting',
- 1: 'Program',
- 2: 'Tv-priority',
- 3: 'Av-priority',
- 4: 'Manual',
- 5: 'A-DEP'}),
- 23: Tag('LongFocalLengthOfLensInFocalUnits'),
- 24: Tag('ShortFocalLengthOfLensInFocalUnits'),
- 25: Tag('FocalUnitsPerMM'),
- 28: TagDict('FlashActivity',
- {0: 'Did Not Fire',
- 1: 'Fired'}),
- 29: TagDict('FlashDetails',
- {14: 'External E-TTL',
- 13: 'Internal Flash',
- 11: 'FP Sync Used',
- 7: '2nd("Rear")-Curtain Sync Used',
- 4: 'FP Sync Enabled'}),
- 32: TagDict('FocusMode',
- {0: 'Single',
- 1: 'Continuous'}),
- }
-
-MAKERNOTE_CANON_TAG_0x004 = {
- 7: TagDict('WhiteBalance',
- {0: 'Auto',
- 1: 'Sunny',
- 2: 'Cloudy',
- 3: 'Tungsten',
- 4: 'Fluorescent',
- 5: 'Flash',
- 6: 'Custom'}),
- 9: Tag('SequenceNumber'),
- 14: Tag('AFPointUsed'),
- 15: TagDict('FlashBias',
- {0xFFC0: '-2 EV',
- 0xFFCC: '-1.67 EV',
- 0xFFD0: '-1.50 EV',
- 0xFFD4: '-1.33 EV',
- 0xFFE0: '-1 EV',
- 0xFFEC: '-0.67 EV',
- 0xFFF0: '-0.50 EV',
- 0xFFF4: '-0.33 EV',
- 0x0000: '0 EV',
- 0x000C: '0.33 EV',
- 0x0010: '0.50 EV',
- 0x0014: '0.67 EV',
- 0x0020: '1 EV',
- 0x002C: '1.33 EV',
- 0x0030: '1.50 EV',
- 0x0034: '1.67 EV',
- 0x0040: '2 EV'}),
- 19: Tag('SubjectDistance'),
- }
-
-# ratio object that eventually will be able to reduce itself to lowest
-# common denominator for printing
-# XXX: unused
-def gcd(a, b):
- if b == 0:
- return a
- else:
- return gcd(b, a % b)
-
-class Ratio:
- def __init__(self, num, den):
- self.num = num
- self.den = den
-
- def __repr__(self):
- self.reduce()
- if self.den == 1:
- return str(self.num)
- return '%d/%d' % (self.num, self.den)
-
- def reduce(self):
- div = gcd(self.num, self.den)
- if div > 1:
- self.num = self.num / div
- self.den = self.den / div
-
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/degal/lib/exif.py Sun Jun 14 16:10:30 2009 +0300
@@ -0,0 +1,669 @@
+"""
+ A custom EXIF parsing module, aimed at high performance.
+"""
+
+import struct, mmap, os
+
+from utils import lazy_load, lazy_load_iter
+
+def read_struct (file, fmt) :
+ """
+ Utility function to read data from the a file using struct
+ """
+
+ # length of data
+ fmt_size = struct.calcsize(fmt)
+
+ # get data
+ file_data = file.read(fmt_size)
+
+ # unpack single item, this should raise an error if file_data is too short
+ return struct.unpack(fmt, file_data)
+
+class Buffer (object) :
+ """
+ Wraps a buffer object (anything that supports the python buffer protocol) for read-only access.
+
+ Includes an offset for relative values, and an endianess for reading binary data.
+ """
+
+ def __init__ (self, obj, offset=None, size=None, struct_prefix='=') :
+ """
+ Create a new Buffer object with a new underlying buffer, created from the given object, offset and size.
+
+ The endiannes is given in the form of a struct-module prefix, which should be one of '<' or '>'.
+ Standard size/alignment are assumed.
+ """
+
+ # store
+ self.buf = buffer(obj, *(arg for arg in (offset, size) if arg is not None))
+ self.offset = offset
+ self.size = size
+ self.prefix = struct_prefix
+
+ def subregion (self, offset, length=None) :
+ """
+ Create a new sub-Buffer referencing a view of this buffer, at the given offset, and with the given
+ length, if any, and the same struct_prefix.
+ """
+
+ return Buffer(self.buf, offset, length, struct_prefix=self.prefix)
+
+ def pread (self, offset, length) :
+ """
+ Read a random-access region of raw data
+ """
+
+ return self.buf[offset:offset + length]
+
+ def pread_struct (self, offset, fmt) :
+ """
+ Read structured data using the given struct format from the given offset.
+ """
+
+ return struct.unpack_from(self.prefix + fmt, self.buf, offset=offset)
+
+ def pread_item (self, offset, fmt) :
+ """
+ Read a single item of structured data from the given offset.
+ """
+
+ value, = self.pread_struct(offset, fmt)
+
+ return value
+
+ def iter_offsets (self, count, size, offset=0) :
+ """
+ Yield a series of offsets for `count` items of `size` bytes, beginning at `offset`.
+ """
+
+ return xrange(offset, offset + count * size, size)
+
+ def item_size (self, fmt) :
+ """
+ Returns the size in bytes of the given item format
+ """
+
+ return struct.calcsize(self.prefix + fmt)
+
+ def unpack_item (self, fmt, data) :
+ """
+ Unpacks a single item from the given data
+ """
+
+ value, = struct.unpack(self.prefix + fmt, data)
+
+ return value
+
+def mmap_buffer (file, size) :
+ """
+ Create and return a new read-only mmap'd region
+ """
+
+ return mmap.mmap(file.fileno(), size, access=mmap.ACCESS_READ)
+
+import exif_data
+
+class Tag (object) :
+ """
+ Represents a single Tag in an IFD
+ """
+
+ def __init__ (self, ifd, offset, tag, type, count, data_raw) :
+ """
+ Build a Tag with the given binary items from the IFD entry
+ """
+
+ self.ifd = ifd
+ self.offset = offset
+ self.tag = tag
+ self.type = type
+ self.count = count
+ self.data_raw = data_raw
+
+ # lookup the type for this tag
+ self.type_data = exif_data.FIELD_TYPES.get(type)
+
+ # unpack it
+ if self.type_data :
+ self.type_format, self.type_name, self.type_func = self.type_data
+
+ # lookup the tag data for this tag
+ self.tag_data = self.ifd.tag_dict.get(tag)
+
+ @property
+ def name (self) :
+ """
+ Lookup the name of this tag via its code, returns None if unknown.
+ """
+
+ if self.tag_data :
+ return self.tag_data.name
+
+ else :
+ return None
+
+ def is_subifd (self) :
+ """
+ Tests if this Tag is of a IFDTag type
+ """
+
+ return self.tag_data and isinstance(self.tag_data, exif_data.IFDTag)
+
+ @lazy_load
+ def subifd (self) :
+ """
+ Load the sub-IFD for this tag
+ """
+
+ # the tag_dict to use
+ tag_dict = self.tag_data.ifd_tags or self.ifd.tag_dict
+
+ # construct, return
+ return self.ifd.exif._load_subifd(self, tag_dict)
+
+ def process_values (self, raw_values) :
+ """
+ Process the given raw values unpacked from the file.
+ """
+
+ if self.type_data and self.type_func :
+ # use the filter func
+ return self.type_func(raw_values)
+
+ else :
+ # nada, just leave them
+ return raw_values
+
+ def readable_value (self, values) :
+ """
+ Convert the given values for this tag into a human-readable string.
+
+ Returns the comma-separated values by default.
+ """
+
+ if self.tag_data :
+ # map it
+ return self.tag_data.map_values(values)
+
+ else :
+ # default value-mapping
+ return ", ".join(str(value) for value in values)
+
+# size of an IFD entry in bytes
+IFD_ENTRY_SIZE = 12
+
+class IFD (Buffer) :
+ """
+ Represents an IFD (Image file directory) region in EXIF data.
+ """
+
+ def __init__ (self, exif, buffer, tag_dict, **buffer_opts) :
+ """
+ Access the IFD data from the given bufferable object with given buffer opts.
+
+ This will read the `count` and `next_offset` values.
+ """
+
+ # init
+ super(IFD, self).__init__(buffer, **buffer_opts)
+
+ # store
+ self.exif = exif
+ self.tag_dict = tag_dict
+
+ # read header
+ self.count = self.pread_item(0, 'H')
+
+ # read next-offset
+ self.next_offset = self.pread_item(0x02 + self.count * IFD_ENTRY_SIZE, 'I')
+
+ @lazy_load_iter
+ def tags (self) :
+ """
+ Iterate over all the Tag objects in this IFD
+ """
+
+ # read each tag
+ for offset in self.iter_offsets(self.count, IFD_ENTRY_SIZE, 0x02) :
+ # read the tag data
+ tag, type, count, data_raw = self.pread_struct(offset, 'HHI4s')
+
+ # yield the new Tag
+ yield Tag(self, self.offset + offset, tag, type, count, data_raw)
+
+ def get_tags (self, filter=None) :
+ """
+ Yield a series of tag objects for this IFD and all sub-IFDs.
+ """
+
+ for tag in self.tags :
+ if tag.is_subifd() :
+ # recurse
+ for subtag in tag.subifd.get_tags(filter=filter) :
+ yield subtag
+
+ else :
+ # normal tag
+ yield tag
+
+class EXIF (Buffer) :
+ """
+ Represents the EXIF data embedded in some image file in the form of a Region.
+ """
+
+ def __init__ (self, buffer, **buffer_opts) :
+ """
+ Access the EXIF data from the given bufferable object with the given buffer options.
+ """
+
+ # init Buffer
+ super(EXIF, self).__init__(buffer, **buffer_opts)
+
+ # store
+ self.buffer = buffer
+
+ @lazy_load_iter
+ def ifds (self) :
+ """
+ Iterate over the primary IFDs in this EXIF.
+ """
+
+ # starting offset
+ offset = self.pread_item(0x04, 'I')
+
+ while offset :
+ # create and read the IFD, operating on the right sub-buffer
+ ifd = IFD(self, self.buf, exif_data.EXIF_TAGS, offset=offset)
+
+ # yield it
+ yield ifd
+
+ # skip to next offset
+ offset = ifd.next_offset
+
+ def _load_subifd (self, tag, tag_dict) :
+ """
+ Creates and returns a sub-IFD for the given tag.
+ """
+
+ # locate it
+ offset, = self.tag_values_raw(tag)
+
+ # construct the new IFD
+ return IFD(self, self.buf, tag_dict, offset=offset)
+
+ def tag_data_info (self, tag) :
+ """
+ Calculate the location, format and size of the given tag's data.
+
+ Returns a (fmt, offset, size) tuple.
+ """
+ # unknown tag?
+ if not tag.type_data :
+ return None
+
+ # data format
+ if len(tag.type_format) == 1 :
+ # let struct handle the count
+ fmt = "%d%s" % (tag.count, tag.type_format)
+
+ else :
+ # handle the count ourselves
+ fmt = tag.type_format * tag.count
+
+ # size of the data
+ size = self.item_size(fmt)
+
+ # inline or external?
+ if size > 0x04 :
+ # point at the external data
+ offset = self.unpack_item('I', tag.data_raw)
+
+ else :
+ # point at the inline data
+ offset = tag.offset + 0x08
+
+ return fmt, offset, size
+
+ def tag_values_raw (self, tag) :
+ """
+ Get the raw values for the given tag as a tuple.
+
+ Returns None if the tag could not be recognized.
+ """
+
+ # find the data
+ data_info = self.tag_data_info(tag)
+
+ # not found?
+ if not data_info :
+ return None
+
+ # unpack
+ data_fmt, data_offset, data_size = data_info
+
+ # read values
+ return self.pread_struct(data_offset, data_fmt)
+
+ def tag_values (self, tag) :
+ """
+ Gets the processed values for the given tag as a list.
+ """
+
+ # read + process
+ return tag.process_values(self.tag_values_raw(tag))
+
+ def tag_value (self, tag) :
+ """
+ Return the human-readable string value for the given tag.
+ """
+
+ # load the raw values
+ values = self.tag_values(tag)
+
+ # unknown?
+ if not values :
+ return ""
+
+ # return as comma-separated formatted string, yes
+ return tag.readable_value(values)
+
+ def get_main_tags (self, **opts) :
+ """
+ Get the tags for the main image's IFD as a dict.
+ """
+
+ if not self.ifds :
+ # weird case
+ raise Exception("No IFD for main image found")
+
+ # the main IFD is always the first one
+ main_ifd = self.ifds[0]
+
+ # do it
+ return dict((tag.name, self.tag_value(tag)) for tag in main_ifd.get_tags(**opts))
+
+# mapping from two-byte TIFF byte order marker to struct prefix
+TIFF_BYTE_ORDER = {
+ 'II': '<',
+ 'MM': '>',
+}
+
+# "An arbitrary but carefully chosen number (42) that further identifies the file as a TIFF file"
+TIFF_BYTEORDER_MAGIC = 42
+
+def tiff_load (file, length=0, **opts) :
+ """
+ Load the Exif/TIFF data from the given file at its current position with optional length, using exif_load.
+ """
+
+ # all Exif data offsets are relative to the beginning of this TIFF header
+ offset = file.tell()
+
+ # mmap the region for the EXIF data
+ buffer = mmap_buffer(file, length)
+
+ # read byte-order header
+ byte_order = file.read(2)
+
+ # map to struct prefix
+ struct_prefix = TIFF_BYTE_ORDER[byte_order]
+
+ # validate
+ check_value, = read_struct(file, struct_prefix + 'H')
+
+ if check_value != TIFF_BYTEORDER_MAGIC :
+ raise Exception("Invalid byte-order for TIFF: %2c -> %d" % (byte_order, check_value))
+
+ # build and return the EXIF object with the correct offset/size from the mmap region
+ return EXIF(buffer, offset=offset, size=length, **opts)
+
+# the JPEG markers that don't have any data
+JPEG_NOSIZE_MARKERS = (0xD8, 0xD9)
+
+# the first marker in a JPEG File
+JPEG_START_MARKER = 0xD8
+
+# the JPEG APP1 marker used for EXIF
+JPEG_EXIF_MARKER = 0xE1
+
+# the JPEG APP1 Exif header
+JPEG_EXIF_HEADER = "Exif\x00\x00"
+
+def jpeg_markers (file) :
+ """
+ Iterate over the JPEG markers in the given file, yielding (type_byte, size) tuples.
+
+ The size fields will be 0 for markers with no data. The file will be positioned at the beginning of the data
+ region, and may be seek'd around if needed.
+
+ XXX: find a real implementation of this somewhere?
+ """
+
+ while True :
+ # read type
+ marker_byte, marker_type = read_struct(file, '!BB')
+
+ # validate
+ if marker_byte != 0xff :
+ raise Exception("Not a JPEG marker: %x%x" % (marker_byte, marker_type))
+
+ # special cases for no data
+ if marker_type in JPEG_NOSIZE_MARKERS :
+ size = 0
+
+ else :
+ # read size field
+ size, = read_struct(file, '!H')
+
+ # validate
+ if size < 0x02 :
+ raise Exception("Invalid size for marker %x%x: %x" % (marker_byte, marker_type, size))
+
+ else :
+ # do not count the size field itself
+ size = size - 2
+
+ # ok, data is at current position
+ offset = file.tell()
+
+ # yield
+ yield marker_type, size
+
+ # absolute seek to next marker
+ file.seek(offset + size)
+
+def jpeg_find_exif (file) :
+ """
+ Find the Exif/TIFF section in the given JPEG file.
+
+ 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
+ be returned.
+
+ Returns None if no EXIF section was found.
+ """
+
+ for count, (marker, size) in enumerate(jpeg_markers(file)) :
+ # verify that it's a JPEG file
+ if count == 0 :
+ # must start with the right marker
+ if marker != JPEG_START_MARKER :
+ raise Exception("JPEG file must start with 0xFF%02x marker" % (marker, ))
+
+ # look for APP1 marker (0xE1) with EXIF signature
+ elif marker == JPEG_EXIF_MARKER and file.read(len(JPEG_EXIF_HEADER)) == JPEG_EXIF_HEADER:
+ # skipped the initial Exif marker signature
+ return size - len(JPEG_EXIF_HEADER)
+
+ # nothing
+ return None
+
+def jpeg_load (file, **opts) :
+ """
+ Loads the embedded Exif TIFF data from the given JPEG file using tiff_load.
+
+ Returns None if no EXIF data could be found.
+ """
+
+ # look for the right section
+ size = jpeg_find_exif(file)
+
+ # not found?
+ if not size :
+ # nothing
+ return
+
+ else :
+ # load it as TIFF data
+ return tiff_load(file, size, **opts)
+
+def load_path (path, **opts) :
+ """
+ Loads an EXIF object from the given filesystem path.
+
+ Returns None if it could not be parsed.
+ """
+
+ # file extension
+ root, fext = os.path.splitext(path)
+
+ # map
+ func = {
+ '.jpeg': jpeg_load,
+ '.jpg': jpeg_load,
+ '.tiff': tiff_load, # XXX: untested
+ }.get(fext.lower())
+
+ # not recognized?
+ if not func :
+ # XXX: sniff the file
+ return None
+
+ # open it
+ file = open(path, 'rb')
+
+ # try and load it
+ return func(file, **opts)
+
+def dump_tag (exif, i, tag, indent=2) :
+ """
+ Dump the given tag
+ """
+
+ data_info = exif.tag_data_info(tag)
+
+ if data_info :
+ data_fmt, data_offset, data_size = data_info
+
+ else :
+ data_fmt = data_offset = data_size = None
+
+ print "%sTag:%d offset=%#04x(%#08x), tag=%d/%s, type=%d/%s, count=%d, fmt=%s, offset=%#04x, size=%s, is_subifd=%s:" % (
+ '\t'*indent,
+ i,
+ tag.offset, tag.offset + exif.offset,
+ tag.tag, tag.name or '???',
+ tag.type, tag.type_name if tag.type_data else '???',
+ tag.count,
+ data_fmt, data_offset, data_size,
+ tag.is_subifd(),
+ )
+
+ if tag.is_subifd() :
+ # recurse
+ dump_ifd(exif, 0, tag.subifd, indent + 1)
+
+ else :
+ # dump each value
+ values = exif.tag_values(tag)
+
+ for i, value in enumerate(values) :
+ print "%s\t%02d: %.120r" % ('\t'*indent, i, value)
+
+ # and then the readable one
+ print "%s\t-> %.120s" % ('\t'*indent, tag.readable_value(values), )
+
+
+def dump_ifd (exif, i, ifd, indent=1) :
+ """
+ Dump the given IFD, recursively
+ """
+
+ print "%sIFD:%d offset=%#04x(%#08x), count=%d, next=%d:" % (
+ '\t'*indent,
+ i,
+ ifd.offset, ifd.offset + exif.offset,
+ ifd.count,
+ ifd.next_offset
+ )
+
+ for i, tag in enumerate(ifd.tags) :
+ # dump
+ dump_tag(exif, i, tag, indent + 1)
+
+
+def dump_exif (exif) :
+ """
+ Dump all tags from the given EXIF object to stdout
+ """
+
+ print "EXIF offset=%#08x, size=%d:" % (exif.offset, exif.size)
+
+ for i, ifd in enumerate(exif.ifds) :
+ # dump
+ dump_ifd(exif, i, ifd)
+
+
+def list_tags (exif) :
+ """
+ Print a neat listing of tags to stdout
+ """
+
+ for k, v in exif.get_main_tags().iteritems() :
+ print "%30s: %s" % (k, v)
+
+def main_path (path, dump) :
+ # dump path
+ print "%s: " % path
+
+ # try and load it
+ exif = load_path(path)
+
+ if not exif :
+ raise Exception("No EXIF data found")
+
+ if dump :
+ # dump everything
+ dump_exif(exif)
+
+ else :
+ # list them
+ list_tags(exif)
+
+
+def main (paths, dump=False) :
+ """
+ Load and dump EXIF data from the given path
+ """
+
+ # handle each one
+ for path in paths :
+ main_path(path, dump=dump)
+
+if __name__ == '__main__' :
+ import getopt
+ from sys import argv
+
+ # defaults
+ dump = False
+
+ # parse args
+ opts, args = getopt.getopt(argv[1:], "d", ["dump"])
+
+ for opt, val in opts :
+ if opt in ('-d', "--dump") :
+ dump = True
+
+ main(args, dump=dump)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/degal/lib/exif_data.py Sun Jun 14 16:10:30 2009 +0300
@@ -0,0 +1,1304 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ EXIF file format data, including tag names, types, etc.
+
+ Most of this was copied with modifications from EXIFpy:
+ # Library to extract EXIF information from digital camera image files
+ # http://sourceforge.net/projects/exif-py/
+ #
+ # VERSION 1.1.0
+ #
+ # Copyright (c) 2002-2007 Gene Cash All rights reserved
+ # Copyright (c) 2007-2008 Ianaré Sévi All rights reserved
+ #
+ # Redistribution and use in source and binary forms, with or without
+ # modification, are permitted provided that the following conditions
+ # are met:
+ #
+ # 1. Redistributions of source code must retain the above copyright
+ # notice, this list of conditions and the following disclaimer.
+ #
+ # 2. Redistributions in binary form must reproduce the above
+ # copyright notice, this list of conditions and the following
+ # disclaimer in the documentation and/or other materials provided
+ # with the distribution.
+ #
+ # 3. Neither the name of the authors nor the names of its contributors
+ # may be used to endorse or promote products derived from this
+ # software without specific prior written permission.
+ #
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+
+import decimal, itertools
+
+def filter_ascii (values) :
+ """
+ Default post-filter for ASCII values.
+
+ This takes a single item of string data, splits it up into strings by ASCII-NUL.
+
+ These sub-strings are then decoded into unicode as ASCII, and stripped.
+ """
+
+ return [string.decode('ascii', 'replace').rstrip() for string in values[0].split('\x00') if string]
+
+def build_ratio (num, denom) :
+ """
+ Builds a Decimal ratio out of the given numerator and denominator
+ """
+
+ # XXX: this may be slow
+ return decimal.Decimal(num) / decimal.Decimal(denom)
+
+def filter_ratio (values) :
+ """
+ Default post-filter for Ratio values.
+
+ This takes the pairs of numerator/denominator values and builds Decimals out of them
+ """
+
+ return [build_ratio(values[i], values[i + 1]) for i in xrange(0, len(values), 2)]
+
+
+# IFD Tag type information, indexed by code
+# { type_code: (type_fmt, name, filter_func) }
+#
+# type_fmt's that are one char will be prefixed with the count for use with struct.unpack, those with more chars will
+# be repeated as many times for use with struct.unpack.
+FIELD_TYPES = {
+# 0x0000: (None, 'Proprietary' ), # ??? no such type
+ 0x0001: ('B', 'Byte', None ),
+ 0x0002: ('s', 'ASCII', filter_ascii ),
+ 0x0003: ('H', 'Short', None ),
+ 0x0004: ('L', 'Long', None ),
+ 0x0005: ('LL', 'Ratio', filter_ratio ),
+ 0x0006: ('b', 'Signed Byte', None ),
+ 0x0007: ('s', 'Undefined', None ),
+ 0x0008: ('h', 'Signed Short', None ),
+ 0x0009: ('l', 'Signed Long', None ),
+ 0x000A: ('ll', 'Signed Ratio', filter_ratio ),
+}
+
+# magic value to indicate sub-IFDs
+SUB_IFD_MAGIC = object()
+
+
+class Tag (object) :
+ """
+ Represents an Exif Tag
+ """
+
+ def __init__ (self, name) :
+ """
+ Build Exif tag with given name, and optional external values-filter function.
+ """
+
+ self.name = name
+
+ def map_values (self, values) :
+ """
+ Map the given tag value to a printable string using the given value spec.
+ """
+
+ # default value-mapping
+ return ", ".join(str(value) for value in values)
+
+class TagDict (Tag) :
+ """
+ A tag with a dict mapping values to names
+ """
+
+ def __init__ (self, name, values_dict) :
+ super(TagDict, self).__init__(name)
+
+ self.values_dict = values_dict
+
+ def map_values (self, values) :
+ """
+ Map the values through our dict, defaulting to the repr.
+ """
+
+ return ", ".join(self.values_dict.get(value, repr(value)) for value in values)
+
+class TagFunc (Tag) :
+ """
+ A tag with a simple function mapping values to names
+ """
+
+ def __init__ (self, name, values_func) :
+ super(TagFunc, self).__init__(name)
+
+ self.values_func = values_func
+
+ def map_values (self, values) :
+ """
+ Map the values through our func
+ """
+
+ return self.values_func(values)
+
+class IFDTag (Tag) :
+ """
+ A tag that references another IFD
+ """
+
+ def __init__ (self, name, ifd_tags=None) :
+ """
+ A tag that points to another IFD block. `ifd_tags`, if given, lists the tags for that block, otherwise,
+ the same tags as for the current block are used.
+ """
+
+ super(IFDTag, self).__init__(name)
+
+ self.ifd_tags = ifd_tags
+
+USER_COMMENT_CHARSETS = {
+ 'ASCII': ('ascii', 'replace' ),
+ 'JIS': ('jis', 'error' ),
+
+ # XXX: WTF? What kind of charset is 'Unicode' supposed to be?
+ # UTF-16? Little-endian? Big-endian?
+ # Confusing reigns: http://www.cpanforum.com/threads/7329
+ 'UNICODE': ('utf16', 'error' ),
+}
+
+
+def decode_UserComment (values) :
+ """
+ A UserComment field starts with an eight-byte encoding designator.
+ """
+
+ # single binary string
+ value, = values
+
+ # split up
+ charset, comment_raw = value[:8], value[8:]
+
+ # strip NILs
+ charset = charset.rstrip('\x00')
+
+ # map
+ encoding, replace = USER_COMMENT_CHARSETS.get(charset, ('ascii', 'replace'))
+
+ # decode
+ return [comment_raw.decode(encoding, replace)]
+
+# Mappings of Exif tag codes to name and decoding information.
+# { tag : (name, value_dict/value_func/None/SUB_IFD_MAGIC) }
+#
+# name is the official Exif tag name
+# value_dict is a { value: value_name } mapping for human-readable values
+# value_func is a `(values) -> values` mapping function which *overrides* the tag's type_func.
+# XXX: or does it?
+# SUB_IFD_MAGIC signifies that this IFD points to
+# otherwise, the value is left as-is.
+# interoperability tags
+INTR_TAGS = {
+ 0x0001: Tag('InteroperabilityIndex'),
+ 0x0002: Tag('InteroperabilityVersion'),
+ 0x1000: Tag('RelatedImageFileFormat'),
+ 0x1001: Tag('RelatedImageWidth'),
+ 0x1002: Tag('RelatedImageLength'),
+ }
+
+# GPS tags (not used yet, haven't seen camera with GPS)
+GPS_TAGS = {
+ 0x0000: Tag('GPSVersionID'),
+ 0x0001: Tag('GPSLatitudeRef'),
+ 0x0002: Tag('GPSLatitude'),
+ 0x0003: Tag('GPSLongitudeRef'),
+ 0x0004: Tag('GPSLongitude'),
+ 0x0005: Tag('GPSAltitudeRef'),
+ 0x0006: Tag('GPSAltitude'),
+ 0x0007: Tag('GPSTimeStamp'),
+ 0x0008: Tag('GPSSatellites'),
+ 0x0009: Tag('GPSStatus'),
+ 0x000A: Tag('GPSMeasureMode'),
+ 0x000B: Tag('GPSDOP'),
+ 0x000C: Tag('GPSSpeedRef'),
+ 0x000D: Tag('GPSSpeed'),
+ 0x000E: Tag('GPSTrackRef'),
+ 0x000F: Tag('GPSTrack'),
+ 0x0010: Tag('GPSImgDirectionRef'),
+ 0x0011: Tag('GPSImgDirection'),
+ 0x0012: Tag('GPSMapDatum'),
+ 0x0013: Tag('GPSDestLatitudeRef'),
+ 0x0014: Tag('GPSDestLatitude'),
+ 0x0015: Tag('GPSDestLongitudeRef'),
+ 0x0016: Tag('GPSDestLongitude'),
+ 0x0017: Tag('GPSDestBearingRef'),
+ 0x0018: Tag('GPSDestBearing'),
+ 0x0019: Tag('GPSDestDistanceRef'),
+ 0x001A: Tag('GPSDestDistance'),
+ 0x001D: Tag('GPSDate'),
+ }
+
+
+EXIF_TAGS = {
+ 0x0100: Tag('ImageWidth'),
+ 0x0101: Tag('ImageLength'),
+ 0x0102: Tag('BitsPerSample'),
+ 0x0103: TagDict('Compression',
+ {1: 'Uncompressed',
+ 2: 'CCITT 1D',
+ 3: 'T4/Group 3 Fax',
+ 4: 'T6/Group 4 Fax',
+ 5: 'LZW',
+ 6: 'JPEG (old-style)',
+ 7: 'JPEG',
+ 8: 'Adobe Deflate',
+ 9: 'JBIG B&W',
+ 10: 'JBIG Color',
+ 32766: 'Next',
+ 32769: 'Epson ERF Compressed',
+ 32771: 'CCIRLEW',
+ 32773: 'PackBits',
+ 32809: 'Thunderscan',
+ 32895: 'IT8CTPAD',
+ 32896: 'IT8LW',
+ 32897: 'IT8MP',
+ 32898: 'IT8BL',
+ 32908: 'PixarFilm',
+ 32909: 'PixarLog',
+ 32946: 'Deflate',
+ 32947: 'DCS',
+ 34661: 'JBIG',
+ 34676: 'SGILog',
+ 34677: 'SGILog24',
+ 34712: 'JPEG 2000',
+ 34713: 'Nikon NEF Compressed',
+ 65000: 'Kodak DCR Compressed',
+ 65535: 'Pentax PEF Compressed'}),
+ 0x0106: Tag('PhotometricInterpretation'),
+ 0x0107: Tag('Thresholding'),
+ 0x010A: Tag('FillOrder'),
+ 0x010D: Tag('DocumentName'),
+ 0x010E: Tag('ImageDescription'),
+ 0x010F: Tag('Make'),
+ 0x0110: Tag('Model'),
+ 0x0111: Tag('StripOffsets'),
+ 0x0112: TagDict('Orientation',
+ {1: 'Horizontal (normal)',
+ 2: 'Mirrored horizontal',
+ 3: 'Rotated 180',
+ 4: 'Mirrored vertical',
+ 5: 'Mirrored horizontal then rotated 90 CCW',
+ 6: 'Rotated 90 CW',
+ 7: 'Mirrored horizontal then rotated 90 CW',
+ 8: 'Rotated 90 CCW'}),
+ 0x0115: Tag('SamplesPerPixel'),
+ 0x0116: Tag('RowsPerStrip'),
+ 0x0117: Tag('StripByteCounts'),
+ 0x011A: Tag('XResolution'),
+ 0x011B: Tag('YResolution'),
+ 0x011C: Tag('PlanarConfiguration'),
+ 0x011D: Tag('PageName'),
+ 0x0128: TagDict('ResolutionUnit',
+ {1: 'Not Absolute',
+ 2: 'Pixels/Inch',
+ 3: 'Pixels/Centimeter'}),
+ 0x012D: Tag('TransferFunction'),
+ 0x0131: Tag('Software'),
+ 0x0132: Tag('DateTime'),
+ 0x013B: Tag('Artist'),
+ 0x013E: Tag('WhitePoint'),
+ 0x013F: Tag('PrimaryChromaticities'),
+ 0x0156: Tag('TransferRange'),
+ 0x0200: Tag('JPEGProc'),
+ 0x0201: Tag('JPEGInterchangeFormat'),
+ 0x0202: Tag('JPEGInterchangeFormatLength'),
+ 0x0211: Tag('YCbCrCoefficients'),
+ 0x0212: Tag('YCbCrSubSampling'),
+ 0x0213: TagDict('YCbCrPositioning',
+ {1: 'Centered',
+ 2: 'Co-sited'}),
+ 0x0214: Tag('ReferenceBlackWhite'),
+
+ 0x4746: Tag('Rating'),
+
+ 0x828D: Tag('CFARepeatPatternDim'),
+ 0x828E: Tag('CFAPattern'),
+ 0x828F: Tag('BatteryLevel'),
+ 0x8298: Tag('Copyright'),
+ 0x829A: Tag('ExposureTime'),
+ 0x829D: Tag('FNumber'),
+ 0x83BB: Tag('IPTC/NAA'),
+ 0x8769: IFDTag('ExifOffset', None),
+ 0x8773: Tag('InterColorProfile'),
+ 0x8822: TagDict('ExposureProgram',
+ {0: 'Unidentified',
+ 1: 'Manual',
+ 2: 'Program Normal',
+ 3: 'Aperture Priority',
+ 4: 'Shutter Priority',
+ 5: 'Program Creative',
+ 6: 'Program Action',
+ 7: 'Portrait Mode',
+ 8: 'Landscape Mode'}),
+ 0x8824: Tag('SpectralSensitivity'),
+ 0x8825: IFDTag('GPSInfo', GPS_TAGS),
+ 0x8827: Tag('ISOSpeedRatings'),
+ 0x8828: Tag('OECF'),
+ 0x9000: Tag('ExifVersion'),
+ 0x9003: Tag('DateTimeOriginal'),
+ 0x9004: Tag('DateTimeDigitized'),
+ 0x9101: TagDict('ComponentsConfiguration',
+ {0: '',
+ 1: 'Y',
+ 2: 'Cb',
+ 3: 'Cr',
+ 4: 'Red',
+ 5: 'Green',
+ 6: 'Blue'}),
+ 0x9102: Tag('CompressedBitsPerPixel'),
+ 0x9201: Tag('ShutterSpeedValue'),
+ 0x9202: Tag('ApertureValue'),
+ 0x9203: Tag('BrightnessValue'),
+ 0x9204: Tag('ExposureBiasValue'),
+ 0x9205: Tag('MaxApertureValue'),
+ 0x9206: Tag('SubjectDistance'),
+ 0x9207: TagDict('MeteringMode',
+ {0: 'Unidentified',
+ 1: 'Average',
+ 2: 'CenterWeightedAverage',
+ 3: 'Spot',
+ 4: 'MultiSpot',
+ 5: 'Pattern'}),
+ 0x9208: TagDict('LightSource',
+ {0: 'Unknown',
+ 1: 'Daylight',
+ 2: 'Fluorescent',
+ 3: 'Tungsten',
+ 9: 'Fine Weather',
+ 10: 'Flash',
+ 11: 'Shade',
+ 12: 'Daylight Fluorescent',
+ 13: 'Day White Fluorescent',
+ 14: 'Cool White Fluorescent',
+ 15: 'White Fluorescent',
+ 17: 'Standard Light A',
+ 18: 'Standard Light B',
+ 19: 'Standard Light C',
+ 20: 'D55',
+ 21: 'D65',
+ 22: 'D75',
+ 255: 'Other'}),
+ 0x9209: TagDict('Flash',
+ {0: 'No',
+ 1: 'Fired',
+ 5: 'Fired (?)', # no return sensed
+ 7: 'Fired (!)', # return sensed
+ 9: 'Fill Fired',
+ 13: 'Fill Fired (?)',
+ 15: 'Fill Fired (!)',
+ 16: 'Off',
+ 24: 'Auto Off',
+ 25: 'Auto Fired',
+ 29: 'Auto Fired (?)',
+ 31: 'Auto Fired (!)',
+ 32: 'Not Available'}),
+ 0x920A: Tag('FocalLength'),
+ 0x9214: Tag('SubjectArea'),
+ 0x927C: Tag('MakerNote'),
+ 0x9286: TagFunc('UserComment', decode_UserComment),
+ 0x9290: Tag('SubSecTime'),
+ 0x9291: Tag('SubSecTimeOriginal'),
+ 0x9292: Tag('SubSecTimeDigitized'),
+
+ # used by Windows Explorer
+ 0x9C9B: Tag('XPTitle'),
+ 0x9C9C: Tag('XPComment'),
+ 0x9C9D: Tag('XPAuthor'), #(ignored by Windows Explorer if Artist exists)
+ 0x9C9E: Tag('XPKeywords'),
+ 0x9C9F: Tag('XPSubject'),
+
+ 0xA000: Tag('FlashPixVersion'),
+ 0xA001: TagDict('ColorSpace',
+ {1: 'sRGB',
+ 2: 'Adobe RGB',
+ 65535: 'Uncalibrated'}),
+ 0xA002: Tag('ExifImageWidth'),
+ 0xA003: Tag('ExifImageLength'),
+ 0xA005: IFDTag('InteroperabilityOffset', INTR_TAGS),
+ 0xA20B: Tag('FlashEnergy'), # 0x920B in TIFF/EP
+ 0xA20C: Tag('SpatialFrequencyResponse'), # 0x920C
+ 0xA20E: Tag('FocalPlaneXResolution'), # 0x920E
+ 0xA20F: Tag('FocalPlaneYResolution'), # 0x920F
+ 0xA210: Tag('FocalPlaneResolutionUnit'), # 0x9210
+ 0xA214: Tag('SubjectLocation'), # 0x9214
+ 0xA215: Tag('ExposureIndex'), # 0x9215
+ 0xA217: TagDict('SensingMethod', # 0x9217
+ {1: 'Not defined',
+ 2: 'One-chip color area',
+ 3: 'Two-chip color area',
+ 4: 'Three-chip color area',
+ 5: 'Color sequential area',
+ 7: 'Trilinear',
+ 8: 'Color sequential linear'}),
+ 0xA300: TagDict('FileSource',
+ {1: 'Film Scanner',
+ 2: 'Reflection Print Scanner',
+ 3: 'Digital Camera'}),
+ 0xA301: TagDict('SceneType',
+ {1: 'Directly Photographed'}),
+ 0xA302: Tag('CVAPattern'),
+ 0xA401: TagDict('CustomRendered',
+ {0: 'Normal',
+ 1: 'Custom'}),
+ 0xA402: TagDict('ExposureMode',
+ {0: 'Auto Exposure',
+ 1: 'Manual Exposure',
+ 2: 'Auto Bracket'}),
+ 0xA403: TagDict('WhiteBalance',
+ {0: 'Auto',
+ 1: 'Manual'}),
+ 0xA404: Tag('DigitalZoomRatio'),
+ 0xA405: ('FocalLengthIn35mmFilm', None),
+ 0xA406: TagDict('SceneCaptureType',
+ {0: 'Standard',
+ 1: 'Landscape',
+ 2: 'Portrait',
+ 3: 'Night)'}),
+ 0xA407: TagDict('GainControl',
+ {0: 'None',
+ 1: 'Low gain up',
+ 2: 'High gain up',
+ 3: 'Low gain down',
+ 4: 'High gain down'}),
+ 0xA408: TagDict('Contrast',
+ {0: 'Normal',
+ 1: 'Soft',
+ 2: 'Hard'}),
+ 0xA409: TagDict('Saturation',
+ {0: 'Normal',
+ 1: 'Soft',
+ 2: 'Hard'}),
+ 0xA40A: TagDict('Sharpness',
+ {0: 'Normal',
+ 1: 'Soft',
+ 2: 'Hard'}),
+ 0xA40B: Tag('DeviceSettingDescription'),
+ 0xA40C: Tag('SubjectDistanceRange'),
+ 0xA500: Tag('Gamma'),
+ 0xC4A5: Tag('PrintIM'),
+ 0xEA1C: ('Padding', None),
+ }
+
+# http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp
+def nikon_ev_bias (seq) :
+ """
+ # First digit seems to be in steps of 1/6 EV.
+ # Does the third value mean the step size? It is usually 6,
+ # but it is 12 for the ExposureDifference.
+ """
+
+ # check for an error condition that could cause a crash.
+ # this only happens if something has gone really wrong in
+ # reading the Nikon MakerNote.
+ if len(seq) < 4 :
+ return ""
+
+ if seq == [252, 1, 6, 0]:
+ return "-2/3 EV"
+
+ if seq == [253, 1, 6, 0]:
+ return "-1/2 EV"
+
+ if seq == [254, 1, 6, 0]:
+ return "-1/3 EV"
+
+ if seq == [0, 1, 6, 0]:
+ return "0 EV"
+
+ if seq == [2, 1, 6, 0]:
+ return "+1/3 EV"
+
+ if seq == [3, 1, 6, 0]:
+ return "+1/2 EV"
+
+ if seq == [4, 1, 6, 0]:
+ return "+2/3 EV"
+
+ # handle combinations not in the table.
+ a = seq[0]
+
+ # causes headaches for the +/- logic, so special case it.
+ if a == 0:
+ return "0 EV"
+
+ if a > 127:
+ a = 256 - a
+ ret_str = "-"
+ else:
+ ret_str = "+"
+
+ b = seq[2] # assume third value means the step size
+
+ whole = a / b
+
+ a = a % b
+
+ if whole != 0 :
+ ret_str = ret_str + str(whole) + " "
+
+ if a == 0 :
+ ret_str = ret_str + "EV"
+ else :
+ r = Ratio(a, b)
+ ret_str = ret_str + r.__repr__() + " EV"
+
+ return ret_str
+
+# Nikon E99x MakerNote Tags
+MAKERNOTE_NIKON_NEWER_TAGS={
+ 0x0001: Tag('MakernoteVersion'), # Sometimes binary
+ 0x0002: Tag('ISOSetting'),
+ 0x0003: Tag('ColorMode'),
+ 0x0004: Tag('Quality'),
+ 0x0005: Tag('Whitebalance'),
+ 0x0006: Tag('ImageSharpening'),
+ 0x0007: Tag('FocusMode'),
+ 0x0008: Tag('FlashSetting'),
+ 0x0009: Tag('AutoFlashMode'),
+ 0x000B: Tag('WhiteBalanceBias'),
+ 0x000C: Tag('WhiteBalanceRBCoeff'),
+ 0x000D: TagFunc('ProgramShift', nikon_ev_bias),
+ # Nearly the same as the other EV vals, but step size is 1/12 EV (?)
+ 0x000E: TagFunc('ExposureDifference', nikon_ev_bias),
+ 0x000F: Tag('ISOSelection'),
+ 0x0011: Tag('NikonPreview'),
+ 0x0012: TagFunc('FlashCompensation', nikon_ev_bias),
+ 0x0013: Tag('ISOSpeedRequested'),
+ 0x0016: Tag('PhotoCornerCoordinates'),
+ # 0x0017: Unknown, but most likely an EV value
+ 0x0018: TagFunc('FlashBracketCompensationApplied', nikon_ev_bias),
+ 0x0019: Tag('AEBracketCompensationApplied'),
+ 0x001A: Tag('ImageProcessing'),
+ 0x001B: Tag('CropHiSpeed'),
+ 0x001D: Tag('SerialNumber'), # Conflict with 0x00A0 ?
+ 0x001E: Tag('ColorSpace'),
+ 0x001F: Tag('VRInfo'),
+ 0x0020: Tag('ImageAuthentication'),
+ 0x0022: Tag('ActiveDLighting'),
+ 0x0023: Tag('PictureControl'),
+ 0x0024: Tag('WorldTime'),
+ 0x0025: Tag('ISOInfo'),
+ 0x0080: Tag('ImageAdjustment'),
+ 0x0081: Tag('ToneCompensation'),
+ 0x0082: Tag('AuxiliaryLens'),
+ 0x0083: Tag('LensType'),
+ 0x0084: Tag('LensMinMaxFocalMaxAperture'),
+ 0x0085: Tag('ManualFocusDistance'),
+ 0x0086: Tag('DigitalZoomFactor'),
+ 0x0087: TagDict('FlashMode',
+ {0x00: 'Did Not Fire',
+ 0x01: 'Fired, Manual',
+ 0x07: 'Fired, External',
+ 0x08: 'Fired, Commander Mode ',
+ 0x09: 'Fired, TTL Mode'}),
+ 0x0088: TagDict('AFFocusPosition',
+ {0x0000: 'Center',
+ 0x0100: 'Top',
+ 0x0200: 'Bottom',
+ 0x0300: 'Left',
+ 0x0400: 'Right'}),
+ 0x0089: TagDict('BracketingMode',
+ {0x00: 'Single frame, no bracketing',
+ 0x01: 'Continuous, no bracketing',
+ 0x02: 'Timer, no bracketing',
+ 0x10: 'Single frame, exposure bracketing',
+ 0x11: 'Continuous, exposure bracketing',
+ 0x12: 'Timer, exposure bracketing',
+ 0x40: 'Single frame, white balance bracketing',
+ 0x41: 'Continuous, white balance bracketing',
+ 0x42: 'Timer, white balance bracketing'}),
+ 0x008A: Tag('AutoBracketRelease'),
+ 0x008B: Tag('LensFStops'),
+ 0x008C: ('NEFCurve1', None), # ExifTool calls this 'ContrastCurve'
+ 0x008D: Tag('ColorMode'),
+ 0x008F: Tag('SceneMode'),
+ 0x0090: Tag('LightingType'),
+ 0x0091: Tag('ShotInfo'), # First 4 bytes are a version number in ASCII
+ 0x0092: Tag('HueAdjustment'),
+ # ExifTool calls this 'NEFCompression', should be 1-4
+ 0x0093: Tag('Compression'),
+ 0x0094: TagDict('Saturation',
+ {-3: 'B&W',
+ -2: '-2',
+ -1: '-1',
+ 0: '0',
+ 1: '1',
+ 2: '2'}),
+ 0x0095: Tag('NoiseReduction'),
+ 0x0096: ('NEFCurve2', None), # ExifTool calls this 'LinearizationTable'
+ 0x0097: Tag('ColorBalance'), # First 4 bytes are a version number in ASCII
+ 0x0098: Tag('LensData'), # First 4 bytes are a version number in ASCII
+ 0x0099: Tag('RawImageCenter'),
+ 0x009A: Tag('SensorPixelSize'),
+ 0x009C: Tag('Scene Assist'),
+ 0x009E: Tag('RetouchHistory'),
+ 0x00A0: Tag('SerialNumber'),
+ 0x00A2: Tag('ImageDataSize'),
+ # 00A3: unknown - a single byte 0
+ # 00A4: In NEF, looks like a 4 byte ASCII version number ('0200')
+ 0x00A5: Tag('ImageCount'),
+ 0x00A6: Tag('DeletedImageCount'),
+ 0x00A7: Tag('TotalShutterReleases'),
+ # First 4 bytes are a version number in ASCII, with version specific
+ # info to follow. Its hard to treat it as a string due to embedded nulls.
+ 0x00A8: Tag('FlashInfo'),
+ 0x00A9: Tag('ImageOptimization'),
+ 0x00AA: Tag('Saturation'),
+ 0x00AB: Tag('DigitalVariProgram'),
+ 0x00AC: Tag('ImageStabilization'),
+ 0x00AD: Tag('Responsive AF'), # 'AFResponse'
+ 0x00B0: Tag('MultiExposure'),
+ 0x00B1: Tag('HighISONoiseReduction'),
+ 0x00B7: Tag('AFInfo'),
+ 0x00B8: Tag('FileInfo'),
+ # 00B9: unknown
+ 0x0100: Tag('DigitalICE'),
+ 0x0103: TagDict('PreviewCompression',
+ {1: 'Uncompressed',
+ 2: 'CCITT 1D',
+ 3: 'T4/Group 3 Fax',
+ 4: 'T6/Group 4 Fax',
+ 5: 'LZW',
+ 6: 'JPEG (old-style)',
+ 7: 'JPEG',
+ 8: 'Adobe Deflate',
+ 9: 'JBIG B&W',
+ 10: 'JBIG Color',
+ 32766: 'Next',
+ 32769: 'Epson ERF Compressed',
+ 32771: 'CCIRLEW',
+ 32773: 'PackBits',
+ 32809: 'Thunderscan',
+ 32895: 'IT8CTPAD',
+ 32896: 'IT8LW',
+ 32897: 'IT8MP',
+ 32898: 'IT8BL',
+ 32908: 'PixarFilm',
+ 32909: 'PixarLog',
+ 32946: 'Deflate',
+ 32947: 'DCS',
+ 34661: 'JBIG',
+ 34676: 'SGILog',
+ 34677: 'SGILog24',
+ 34712: 'JPEG 2000',
+ 34713: 'Nikon NEF Compressed',
+ 65000: 'Kodak DCR Compressed',
+ 65535: 'Pentax PEF Compressed',}),
+ 0x0201: Tag('PreviewImageStart'),
+ 0x0202: Tag('PreviewImageLength'),
+ 0x0213: TagDict('PreviewYCbCrPositioning',
+ {1: 'Centered',
+ 2: 'Co-sited'}),
+ 0x0010: Tag('DataDump'),
+ }
+
+MAKERNOTE_NIKON_OLDER_TAGS = {
+ 0x0003: TagDict('Quality',
+ {1: 'VGA Basic',
+ 2: 'VGA Normal',
+ 3: 'VGA Fine',
+ 4: 'SXGA Basic',
+ 5: 'SXGA Normal',
+ 6: 'SXGA Fine'}),
+ 0x0004: TagDict('ColorMode',
+ {1: 'Color',
+ 2: 'Monochrome'}),
+ 0x0005: TagDict('ImageAdjustment',
+ {0: 'Normal',
+ 1: 'Bright+',
+ 2: 'Bright-',
+ 3: 'Contrast+',
+ 4: 'Contrast-'}),
+ 0x0006: TagDict('CCDSpeed',
+ {0: 'ISO 80',
+ 2: 'ISO 160',
+ 4: 'ISO 320',
+ 5: 'ISO 100'}),
+ 0x0007: TagDict('WhiteBalance',
+ {0: 'Auto',
+ 1: 'Preset',
+ 2: 'Daylight',
+ 3: 'Incandescent',
+ 4: 'Fluorescent',
+ 5: 'Cloudy',
+ 6: 'Speed Light'}),
+ }
+
+def olympus_special_mode (values) :
+ """
+ Decode Olympus SpecialMode tag in MakerNote
+ """
+
+ a = {
+ 0: 'Normal',
+ 1: 'Unknown',
+ 2: 'Fast',
+ 3: 'Panorama'
+ }
+
+ b = {
+ 0: 'Non-panoramic',
+ 1: 'Left to right',
+ 2: 'Right to left',
+ 3: 'Bottom to top',
+ 4: 'Top to bottom'
+ }
+
+ if v[0] not in a or v[2] not in b:
+ return values
+
+ return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
+
+MAKERNOTE_OLYMPUS_TAGS={
+ # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
+ # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
+ 0x0100: Tag('JPEGThumbnail'),
+ 0x0200: TagFunc('SpecialMode', olympus_special_mode),
+ 0x0201: TagDict('JPEGQual',
+ {1: 'SQ',
+ 2: 'HQ',
+ 3: 'SHQ'}),
+ 0x0202: TagDict('Macro',
+ {0: 'Normal',
+ 1: 'Macro',
+ 2: 'SuperMacro'}),
+ 0x0203: TagDict('BWMode',
+ {0: 'Off',
+ 1: 'On'}),
+ 0x0204: Tag('DigitalZoom'),
+ 0x0205: Tag('FocalPlaneDiagonal'),
+ 0x0206: Tag('LensDistortionParams'),
+ 0x0207: Tag('SoftwareRelease'),
+ 0x0208: Tag('PictureInfo'),
+ 0x0209: Tag('CameraID'), # print as string
+ 0x0F00: Tag('DataDump'),
+ 0x0300: Tag('PreCaptureFrames'),
+ 0x0404: Tag('SerialNumber'),
+ 0x1000: Tag('ShutterSpeedValue'),
+ 0x1001: Tag('ISOValue'),
+ 0x1002: Tag('ApertureValue'),
+ 0x1003: Tag('BrightnessValue'),
+ 0x1004: Tag('FlashMode'),
+ 0x1004: TagDict('FlashMode',
+ {2: 'On',
+ 3: 'Off'}),
+ 0x1005: TagDict('FlashDevice',
+ {0: 'None',
+ 1: 'Internal',
+ 4: 'External',
+ 5: 'Internal + External'}),
+ 0x1006: Tag('ExposureCompensation'),
+ 0x1007: Tag('SensorTemperature'),
+ 0x1008: Tag('LensTemperature'),
+ 0x100b: TagDict('FocusMode',
+ {0: 'Auto',
+ 1: 'Manual'}),
+ 0x1017: Tag('RedBalance'),
+ 0x1018: Tag('BlueBalance'),
+ 0x101a: Tag('SerialNumber'),
+ 0x1023: Tag('FlashExposureComp'),
+ 0x1026: TagDict('ExternalFlashBounce',
+ {0: 'No',
+ 1: 'Yes'}),
+ 0x1027: Tag('ExternalFlashZoom'),
+ 0x1028: Tag('ExternalFlashMode'),
+ 0x1029: ('Contrast int16u',
+ {0: 'High',
+ 1: 'Normal',
+ 2: 'Low'}),
+ 0x102a: Tag('SharpnessFactor'),
+ 0x102b: Tag('ColorControl'),
+ 0x102c: Tag('ValidBits'),
+ 0x102d: Tag('CoringFilter'),
+ 0x102e: Tag('OlympusImageWidth'),
+ 0x102f: Tag('OlympusImageHeight'),
+ 0x1034: Tag('CompressionRatio'),
+ 0x1035: TagDict('PreviewImageValid',
+ {0: 'No',
+ 1: 'Yes'}),
+ 0x1036: Tag('PreviewImageStart'),
+ 0x1037: Tag('PreviewImageLength'),
+ 0x1039: TagDict('CCDScanMode',
+ {0: 'Interlaced',
+ 1: 'Progressive'}),
+ 0x103a: TagDict('NoiseReduction',
+ {0: 'Off',
+ 1: 'On'}),
+ 0x103b: Tag('InfinityLensStep'),
+ 0x103c: Tag('NearLensStep'),
+
+ # TODO - these need extra definitions
+ # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html
+ 0x2010: Tag('Equipment'),
+ 0x2020: Tag('CameraSettings'),
+ 0x2030: Tag('RawDevelopment'),
+ 0x2040: Tag('ImageProcessing'),
+ 0x2050: Tag('FocusInfo'),
+ 0x3000: Tag('RawInfo '),
+ }
+
+# 0x2020 CameraSettings
+MAKERNOTE_OLYMPUS_TAG_0x2020={
+ 0x0100: TagDict('PreviewImageValid',
+ {0: 'No',
+ 1: 'Yes'}),
+ 0x0101: Tag('PreviewImageStart'),
+ 0x0102: Tag('PreviewImageLength'),
+ 0x0200: TagDict('ExposureMode',
+ {1: 'Manual',
+ 2: 'Program',
+ 3: 'Aperture-priority AE',
+ 4: 'Shutter speed priority AE',
+ 5: 'Program-shift'}),
+ 0x0201: TagDict('AELock',
+ {0: 'Off',
+ 1: 'On'}),
+ 0x0202: TagDict('MeteringMode',
+ {2: 'Center Weighted',
+ 3: 'Spot',
+ 5: 'ESP',
+ 261: 'Pattern+AF',
+ 515: 'Spot+Highlight control',
+ 1027: 'Spot+Shadow control'}),
+ 0x0300: TagDict('MacroMode',
+ {0: 'Off',
+ 1: 'On'}),
+ 0x0301: TagDict('FocusMode',
+ {0: 'Single AF',
+ 1: 'Sequential shooting AF',
+ 2: 'Continuous AF',
+ 3: 'Multi AF',
+ 10: 'MF'}),
+ 0x0302: TagDict('FocusProcess',
+ {0: 'AF Not Used',
+ 1: 'AF Used'}),
+ 0x0303: TagDict('AFSearch',
+ {0: 'Not Ready',
+ 1: 'Ready'}),
+ 0x0304: Tag('AFAreas'),
+ 0x0401: Tag('FlashExposureCompensation'),
+ 0x0500: ('WhiteBalance2',
+ {0: 'Auto',
+ 16: '7500K (Fine Weather with Shade)',
+ 17: '6000K (Cloudy)',
+ 18: '5300K (Fine Weather)',
+ 20: '3000K (Tungsten light)',
+ 21: '3600K (Tungsten light-like)',
+ 33: '6600K (Daylight fluorescent)',
+ 34: '4500K (Neutral white fluorescent)',
+ 35: '4000K (Cool white fluorescent)',
+ 48: '3600K (Tungsten light-like)',
+ 256: 'Custom WB 1',
+ 257: 'Custom WB 2',
+ 258: 'Custom WB 3',
+ 259: 'Custom WB 4',
+ 512: 'Custom WB 5400K',
+ 513: 'Custom WB 2900K',
+ 514: 'Custom WB 8000K', }),
+ 0x0501: Tag('WhiteBalanceTemperature'),
+ 0x0502: Tag('WhiteBalanceBracket'),
+ 0x0503: Tag('CustomSaturation'), # (3 numbers: 1. CS Value, 2. Min, 3. Max)
+ 0x0504: TagDict('ModifiedSaturation',
+ {0: 'Off',
+ 1: 'CM1 (Red Enhance)',
+ 2: 'CM2 (Green Enhance)',
+ 3: 'CM3 (Blue Enhance)',
+ 4: 'CM4 (Skin Tones)'}),
+ 0x0505: Tag('ContrastSetting'), # (3 numbers: 1. Contrast, 2. Min, 3. Max)
+ 0x0506: Tag('SharpnessSetting'), # (3 numbers: 1. Sharpness, 2. Min, 3. Max)
+ 0x0507: TagDict('ColorSpace',
+ {0: 'sRGB',
+ 1: 'Adobe RGB',
+ 2: 'Pro Photo RGB'}),
+ 0x0509: TagDict('SceneMode',
+ {0: 'Standard',
+ 6: 'Auto',
+ 7: 'Sport',
+ 8: 'Portrait',
+ 9: 'Landscape+Portrait',
+ 10: 'Landscape',
+ 11: 'Night scene',
+ 13: 'Panorama',
+ 16: 'Landscape+Portrait',
+ 17: 'Night+Portrait',
+ 19: 'Fireworks',
+ 20: 'Sunset',
+ 22: 'Macro',
+ 25: 'Documents',
+ 26: 'Museum',
+ 28: 'Beach&Snow',
+ 30: 'Candle',
+ 35: 'Underwater Wide1',
+ 36: 'Underwater Macro',
+ 39: 'High Key',
+ 40: 'Digital Image Stabilization',
+ 44: 'Underwater Wide2',
+ 45: 'Low Key',
+ 46: 'Children',
+ 48: 'Nature Macro'}),
+ 0x050a: TagDict('NoiseReduction',
+ {0: 'Off',
+ 1: 'Noise Reduction',
+ 2: 'Noise Filter',
+ 3: 'Noise Reduction + Noise Filter',
+ 4: 'Noise Filter (ISO Boost)',
+ 5: 'Noise Reduction + Noise Filter (ISO Boost)'}),
+ 0x050b: TagDict('DistortionCorrection',
+ {0: 'Off',
+ 1: 'On'}),
+ 0x050c: TagDict('ShadingCompensation',
+ {0: 'Off',
+ 1: 'On'}),
+ 0x050d: Tag('CompressionFactor'),
+ 0x050f: TagDict('Gradation',
+ {'-1 -1 1': 'Low Key',
+ '0 -1 1': 'Normal',
+ '1 -1 1': 'High Key'}),
+ 0x0520: TagDict('PictureMode',
+ {1: 'Vivid',
+ 2: 'Natural',
+ 3: 'Muted',
+ 256: 'Monotone',
+ 512: 'Sepia'}),
+ 0x0521: Tag('PictureModeSaturation'),
+ 0x0522: Tag('PictureModeHue?'),
+ 0x0523: Tag('PictureModeContrast'),
+ 0x0524: Tag('PictureModeSharpness'),
+ 0x0525: TagDict('PictureModeBWFilter',
+ {0: 'n/a',
+ 1: 'Neutral',
+ 2: 'Yellow',
+ 3: 'Orange',
+ 4: 'Red',
+ 5: 'Green'}),
+ 0x0526: TagDict('PictureModeTone',
+ {0: 'n/a',
+ 1: 'Neutral',
+ 2: 'Sepia',
+ 3: 'Blue',
+ 4: 'Purple',
+ 5: 'Green'}),
+ 0x0600: Tag('Sequence'), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits
+ 0x0601: Tag('PanoramaMode'), # (2 numbers: 1. Mode, 2. Shot number)
+ 0x0603: ('ImageQuality2',
+ {1: 'SQ',
+ 2: 'HQ',
+ 3: 'SHQ',
+ 4: 'RAW'}),
+ 0x0901: Tag('ManometerReading'),
+ }
+
+
+MAKERNOTE_CASIO_TAGS={
+ 0x0001: TagDict('RecordingMode',
+ {1: 'Single Shutter',
+ 2: 'Panorama',
+ 3: 'Night Scene',
+ 4: 'Portrait',
+ 5: 'Landscape'}),
+ 0x0002: TagDict('Quality',
+ {1: 'Economy',
+ 2: 'Normal',
+ 3: 'Fine'}),
+ 0x0003: TagDict('FocusingMode',
+ {2: 'Macro',
+ 3: 'Auto Focus',
+ 4: 'Manual Focus',
+ 5: 'Infinity'}),
+ 0x0004: TagDict('FlashMode',
+ {1: 'Auto',
+ 2: 'On',
+ 3: 'Off',
+ 4: 'Red Eye Reduction'}),
+ 0x0005: TagDict('FlashIntensity',
+ {11: 'Weak',
+ 13: 'Normal',
+ 15: 'Strong'}),
+ 0x0006: Tag('Object Distance'),
+ 0x0007: TagDict('WhiteBalance',
+ {1: 'Auto',
+ 2: 'Tungsten',
+ 3: 'Daylight',
+ 4: 'Fluorescent',
+ 5: 'Shade',
+ 129: 'Manual'}),
+ 0x000B: TagDict('Sharpness',
+ {0: 'Normal',
+ 1: 'Soft',
+ 2: 'Hard'}),
+ 0x000C: TagDict('Contrast',
+ {0: 'Normal',
+ 1: 'Low',
+ 2: 'High'}),
+ 0x000D: TagDict('Saturation',
+ {0: 'Normal',
+ 1: 'Low',
+ 2: 'High'}),
+ 0x0014: TagDict('CCDSpeed',
+ {64: 'Normal',
+ 80: 'Normal',
+ 100: 'High',
+ 125: '+1.0',
+ 244: '+3.0',
+ 250: '+2.0'}),
+ }
+
+MAKERNOTE_FUJIFILM_TAGS={
+ 0x0000: Tag('NoteVersion'),
+ 0x1000: Tag('Quality'),
+ 0x1001: TagDict('Sharpness',
+ {1: 'Soft',
+ 2: 'Soft',
+ 3: 'Normal',
+ 4: 'Hard',
+ 5: 'Hard'}),
+ 0x1002: TagDict('WhiteBalance',
+ {0: 'Auto',
+ 256: 'Daylight',
+ 512: 'Cloudy',
+ 768: 'DaylightColor-Fluorescent',
+ 769: 'DaywhiteColor-Fluorescent',
+ 770: 'White-Fluorescent',
+ 1024: 'Incandescent',
+ 3840: 'Custom'}),
+ 0x1003: TagDict('Color',
+ {0: 'Normal',
+ 256: 'High',
+ 512: 'Low'}),
+ 0x1004: TagDict('Tone',
+ {0: 'Normal',
+ 256: 'High',
+ 512: 'Low'}),
+ 0x1010: TagDict('FlashMode',
+ {0: 'Auto',
+ 1: 'On',
+ 2: 'Off',
+ 3: 'Red Eye Reduction'}),
+ 0x1011: Tag('FlashStrength'),
+ 0x1020: TagDict('Macro',
+ {0: 'Off',
+ 1: 'On'}),
+ 0x1021: TagDict('FocusMode',
+ {0: 'Auto',
+ 1: 'Manual'}),
+ 0x1030: TagDict('SlowSync',
+ {0: 'Off',
+ 1: 'On'}),
+ 0x1031: TagDict('PictureMode',
+ {0: 'Auto',
+ 1: 'Portrait',
+ 2: 'Landscape',
+ 4: 'Sports',
+ 5: 'Night',
+ 6: 'Program AE',
+ 256: 'Aperture Priority AE',
+ 512: 'Shutter Priority AE',
+ 768: 'Manual Exposure'}),
+ 0x1100: TagDict('MotorOrBracket',
+ {0: 'Off',
+ 1: 'On'}),
+ 0x1300: TagDict('BlurWarning',
+ {0: 'Off',
+ 1: 'On'}),
+ 0x1301: TagDict('FocusWarning',
+ {0: 'Off',
+ 1: 'On'}),
+ 0x1302: TagDict('AEWarning',
+ {0: 'Off',
+ 1: 'On'}),
+ }
+
+MAKERNOTE_CANON_TAGS = {
+ 0x0006: Tag('ImageType'),
+ 0x0007: Tag('FirmwareVersion'),
+ 0x0008: Tag('ImageNumber'),
+ 0x0009: Tag('OwnerName'),
+ }
+
+# this is in element offset, name, optional value dictionary format
+MAKERNOTE_CANON_TAG_0x001 = {
+ 1: TagDict('Macromode',
+ {1: 'Macro',
+ 2: 'Normal'}),
+ 2: Tag('SelfTimer'),
+ 3: TagDict('Quality',
+ {2: 'Normal',
+ 3: 'Fine',
+ 5: 'Superfine'}),
+ 4: TagDict('FlashMode',
+ {0: 'Flash Not Fired',
+ 1: 'Auto',
+ 2: 'On',
+ 3: 'Red-Eye Reduction',
+ 4: 'Slow Synchro',
+ 5: 'Auto + Red-Eye Reduction',
+ 6: 'On + Red-Eye Reduction',
+ 16: 'external flash'}),
+ 5: TagDict('ContinuousDriveMode',
+ {0: 'Single Or Timer',
+ 1: 'Continuous'}),
+ 7: TagDict('FocusMode',
+ {0: 'One-Shot',
+ 1: 'AI Servo',
+ 2: 'AI Focus',
+ 3: 'MF',
+ 4: 'Single',
+ 5: 'Continuous',
+ 6: 'MF'}),
+ 10: TagDict('ImageSize',
+ {0: 'Large',
+ 1: 'Medium',
+ 2: 'Small'}),
+ 11: TagDict('EasyShootingMode',
+ {0: 'Full Auto',
+ 1: 'Manual',
+ 2: 'Landscape',
+ 3: 'Fast Shutter',
+ 4: 'Slow Shutter',
+ 5: 'Night',
+ 6: 'B&W',
+ 7: 'Sepia',
+ 8: 'Portrait',
+ 9: 'Sports',
+ 10: 'Macro/Close-Up',
+ 11: 'Pan Focus'}),
+ 12: TagDict('DigitalZoom',
+ {0: 'None',
+ 1: '2x',
+ 2: '4x'}),
+ 13: TagDict('Contrast',
+ {0xFFFF: 'Low',
+ 0: 'Normal',
+ 1: 'High'}),
+ 14: TagDict('Saturation',
+ {0xFFFF: 'Low',
+ 0: 'Normal',
+ 1: 'High'}),
+ 15: TagDict('Sharpness',
+ {0xFFFF: 'Low',
+ 0: 'Normal',
+ 1: 'High'}),
+ 16: TagDict('ISO',
+ {0: 'See ISOSpeedRatings Tag',
+ 15: 'Auto',
+ 16: '50',
+ 17: '100',
+ 18: '200',
+ 19: '400'}),
+ 17: TagDict('MeteringMode',
+ {3: 'Evaluative',
+ 4: 'Partial',
+ 5: 'Center-weighted'}),
+ 18: TagDict('FocusType',
+ {0: 'Manual',
+ 1: 'Auto',
+ 3: 'Close-Up (Macro)',
+ 8: 'Locked (Pan Mode)'}),
+ 19: TagDict('AFPointSelected',
+ {0x3000: 'None (MF)',
+ 0x3001: 'Auto-Selected',
+ 0x3002: 'Right',
+ 0x3003: 'Center',
+ 0x3004: 'Left'}),
+ 20: TagDict('ExposureMode',
+ {0: 'Easy Shooting',
+ 1: 'Program',
+ 2: 'Tv-priority',
+ 3: 'Av-priority',
+ 4: 'Manual',
+ 5: 'A-DEP'}),
+ 23: Tag('LongFocalLengthOfLensInFocalUnits'),
+ 24: Tag('ShortFocalLengthOfLensInFocalUnits'),
+ 25: Tag('FocalUnitsPerMM'),
+ 28: TagDict('FlashActivity',
+ {0: 'Did Not Fire',
+ 1: 'Fired'}),
+ 29: TagDict('FlashDetails',
+ {14: 'External E-TTL',
+ 13: 'Internal Flash',
+ 11: 'FP Sync Used',
+ 7: '2nd("Rear")-Curtain Sync Used',
+ 4: 'FP Sync Enabled'}),
+ 32: TagDict('FocusMode',
+ {0: 'Single',
+ 1: 'Continuous'}),
+ }
+
+MAKERNOTE_CANON_TAG_0x004 = {
+ 7: TagDict('WhiteBalance',
+ {0: 'Auto',
+ 1: 'Sunny',
+ 2: 'Cloudy',
+ 3: 'Tungsten',
+ 4: 'Fluorescent',
+ 5: 'Flash',
+ 6: 'Custom'}),
+ 9: Tag('SequenceNumber'),
+ 14: Tag('AFPointUsed'),
+ 15: TagDict('FlashBias',
+ {0xFFC0: '-2 EV',
+ 0xFFCC: '-1.67 EV',
+ 0xFFD0: '-1.50 EV',
+ 0xFFD4: '-1.33 EV',
+ 0xFFE0: '-1 EV',
+ 0xFFEC: '-0.67 EV',
+ 0xFFF0: '-0.50 EV',
+ 0xFFF4: '-0.33 EV',
+ 0x0000: '0 EV',
+ 0x000C: '0.33 EV',
+ 0x0010: '0.50 EV',
+ 0x0014: '0.67 EV',
+ 0x0020: '1 EV',
+ 0x002C: '1.33 EV',
+ 0x0030: '1.50 EV',
+ 0x0034: '1.67 EV',
+ 0x0040: '2 EV'}),
+ 19: Tag('SubjectDistance'),
+ }
+
+# ratio object that eventually will be able to reduce itself to lowest
+# common denominator for printing
+# XXX: unused
+def gcd(a, b):
+ if b == 0:
+ return a
+ else:
+ return gcd(b, a % b)
+
+class Ratio:
+ def __init__(self, num, den):
+ self.num = num
+ self.den = den
+
+ def __repr__(self):
+ self.reduce()
+ if self.den == 1:
+ return str(self.num)
+ return '%d/%d' % (self.num, self.den)
+
+ def reduce(self):
+ div = gcd(self.num, self.den)
+ if div > 1:
+ self.num = self.num / div
+ self.den = self.den / div
+
+