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