pax_global_header00006660000000000000000000000064135161337660014525gustar00rootroot0000000000000052 comment=aef3e8e76b3b63b45e501c73625d57441fae3c5a exif-py-2.2.0/000077500000000000000000000000001351613376600131075ustar00rootroot00000000000000exif-py-2.2.0/.travis.yml000066400000000000000000000007021351613376600152170ustar00rootroot00000000000000language: python dist: xenial python: - "2.7" - "3.4" - "3.5" - "3.6" - "3.7" install: - "pip install ." # get the test images before_script: - "wget https://github.com/ianare/exif-samples/archive/master.tar.gz" - "tar -xzf master.tar.gz" # could be much improved ... script: - "find exif-samples-master -name *.tiff -o -name *.jpg | xargs EXIF.py" - "find exif-samples-master -name *.tiff -o -name *.jpg | xargs EXIF.py -dc" exif-py-2.2.0/CONTRIBUTING.rst000066400000000000000000000015251351613376600155530ustar00rootroot00000000000000************ Contributing ************ All contributions are welcome! Bug reports, feature requests, bug fixes, documentation updates, sample images, etc ... Nothing is too small or too big ;-) Please be aware that this is a purely spare time project, so don't be offended if it takes some time to answer. Code Contributions ****************** Please start from the ``develop`` branch for any code or documentation contributions. You may even find the work has already been done... Normally the ``master`` branch is only for stable, released code. Sample Images ************* Sample images are very important, allowing for validating new features and limiting regressions. On every build, the library is run on all images. The samples are kept in a separate repository for space and bandwidth concerns: https://github.com/ianare/exif-samples exif-py-2.2.0/ChangeLog.rst000066400000000000000000000163271351613376600155010ustar00rootroot00000000000000********** Change Log ********** 2.2.0 — 2019-07-24 * Add support for Python 3.5, 3.6, 3.7 * Drop official support for Python 2.6, 3.2, 3.3 * Fix for string count equals 0 (issue #67) * Rebasing of struct pull requests: closes #54, closes #60 (Christopher Chavez) * Raw images support by changing Tiff detection (xaumex) * Fix GPS information erroneously None #96 (Christopher Chavez) * Initial HEIC support (Sam Rushing) 2.1.2 — 2015-09-14 * Fix 90 CW (6) and Rotated 90 CCW (8) which were swapped with each other (Mark Hahnenberg) * Catch memory and overflow errors on file seek, print a warning * Put manufacturers' makernote definitions in separate files 2.1.1 — 2015-05-16 * Add a CONTRIBUTING file for Github. * Add some FujiFilm tags. * Revert Canon Makernote processing modifications 2.1.0 — 2015-05-15 * Bypass empty/unreadable Olympus MakerNote info (issue #42) * Suport Apple Makernote and Apple HDR details (Jesus Cea) * Correcty process the Makernote of some Canon models (Jesus Cea) * Support HDR in Canon cameras (Jesus Cea) 2.0.2 — 2015-03-29 * Fixed bug when importing as a module (issue #31) 2.0.1 — 2014-02-09 * Represent the IFD as a string to fix formatting errors (issue #45) * Fix unicode errors in python2 (issue #46) * Fix for tag name backwards compatibility with 1.X series 2.0.0 — 2014-11-27 * Drop support for Python 2.5 * Add support for Python 3.2, 3.3 and 3.4 (velis74) * Add Travis testing * Cleanup some tag definitions * Fix bug #30 (TypeError on invalid IFD) * Fix bug #33 (TypeError on invalid output characters) * Add basic coloring for debug mode * Add finding XMP tags (experimental, debug only) * Add some missing Exif tags * Use stdout for log output * Experimental support for dumping XMP data 1.4.2 — 2013-11-28 * A few new Canon tags * Python3 fixes (velis74 and leprechaun) * Fix for TypeError (issue #28) * Pylint & PEP8 fixes 1.4.1 — 2013-10-19 * Better version handling * Better PyPI packaging 1.4.0 — 2013-09-28 * Many new tags (big thanks to Rodolfo Puig, Paul Barton, Joe Beda) * Do not extract thumbnail in quick mode (issue #19) * Put tag definitions in separate module * Add more timing info & version info 1.3.3 — 2013-08-03 * Add timing info in debug mode and nicer message format * Fix for faster processing 1.3.2 — 2013-07-31 * Improve PyPI package * fix for DeprecationWarning: classic int division * Improvements to debug output * Add some Nikon makernote tags 1.3.1 — 2013-07-29 * More PEP8 & PEP257 improvements * Better logging 1.3.0 — 2013-07-27 * Set default values in case not set (ortsed) * PEP8 & PEP257 improvements * Better score in pylint * Ideas and some code from Samuele Santi's and Peter Reimer's forks * Replace print with logging * Package for PyPI 1.2.0 — 2013-02-08 * Port to Python 3 (DarkRedman) * Fix endless loop on broken images (Michael Bemmerl) * Rewrite of README.md * Fixed incoherent copyright notices 1.1.0 — 2012-11-30 - Gregory Dudek * Overflow error fixes added (related to 2**31 size) * GPS tags added. 1.0.10 — 2012-09-26 * Add GPS tags * Add better endian debug info 2012-06-13 * Support malformed last IFD (fhats) * Light source, Flash and Metering mode dictionaries (gryfik) 2008-07-31 * Wikipedia Commons hunt for suitable test case images, * testing new code additions. 2008-07-09 - Stephen H. Olson * Fix a problem with reading MakerNotes out of NEF files. * Add some more Nikon MakerNote tags. 2008-07-08 - Stephen H. Olson * An error check for large tags totally borked MakerNotes. With Nikon anyway, valid MakerNotes can be pretty big. * Add error check for a crash caused by nikon_ev_bias being called with the wrong args. * Drop any garbage after a null character in string (patch from Andrew McNabb ). 2008-02-12 * Fix crash on invalid MakerNote * Fix crash on huge Makernote (temp fix) * Add printIM tag 0xC4A5, needs decoding info * Add 0x9C9B-F range of tags * Add a bunch of tag definitions from: http://owl.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html * Add 'strict' variable and command line option 2008-01-18 - Gunter Ohrner * Add ``GPSDate`` tag 2007-12-12 * Fix quick option on certain image types * Add note on tag naming in documentation 2007-11-30 * Changed -s option to -t * Put changelog into separate file 2007-10-28 * Merged changes from ReimarBauer * Added command line option for debug, stop processing on tag. 2007-09-27 * Add some Olympus Makernote tags. 2007-09-26 - Stephen H. Olson * Don't error out on invalid Olympus 'SpecialMode'. * Add a few more Olympus/Minolta tags. 2007-09-22 - Stephen H. Olson * Don't error on invalid string * Improved Nikon MakerNote support 2007-05-03 - Martin Stone * Fix for inverted detailed flag and Photoshop header 2007-03-24 * Can now ignore MakerNotes Tags for faster processing. 2007-01-18 * Fixed a couple errors and assuming maintenance of the library. 2006-08-04 Reimar Bauer * Added an optional parameter name to process_file and dump_IFD. Using this parameter the loop is breaked after that tag_name is processed. * some PEP8 changes Original Notices **************** Contains code from "exifdump.py" originally written by Thierry Bousch and released into the public domain. Updated and turned into general-purpose library by Gene Cash Patch Contributors: * Simon J. Gerraty s2n fix & orientation decode * John T. Riedl Added support for newer Nikon type 3 Makernote format for D70 and some other Nikon cameras. * Joerg Schaefer Fixed subtle bug when faking an EXIF header, which affected maker notes using relative offsets, and a fix for Nikon D100. 2004-02-15 CEC * Finally fixed bit shift warning by converting Y to 0L. 2003-11-30 CEC * Fixed problem with canon_decode_tag() not creating an IFD_Tag() object. 2002-01-26 CEC * Added ability to extract TIFF thumbnails. * Added Nikon, Fujifilm, Casio MakerNotes. 2002-01-25 CEC * Discovered JPEG thumbnail in Olympus TIFF MakerNote. 2002-01-23 CEC * Trimmed nulls from end of string values. 2002-01-20 CEC Added MakerNote processing logic. * Added Olympus MakerNote. * Converted data structure to single-level dictionary, avoiding tag name collisions by prefixing with IFD name. This makes it much easier to use. 2002-01-19 CEC Added ability to read TIFFs and JFIF-format JPEGs. * Added ability to extract JPEG formatted thumbnail. * Added ability to read GPS IFD (not tested). * Converted IFD data structure to dictionaries indexed by tag name. * Factored into library returning dictionary of IFDs plus thumbnail, if any. 2002-01-17 CEC Discovered code on web. * Commented everything. * Made small code improvements. * Reformatted for readability. 1999-08-21 TB * Last update by Thierry Bousch to his code. exif-py-2.2.0/EXIF.py000077500000000000000000000075451351613376600142320ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # # Library to extract Exif information from digital camera image files. # https://github.com/ianare/exif-py # # # Copyright (c) 2002-2007 Gene Cash # Copyright (c) 2007-2014 Ianaré Sévi and contributors # # See LICENSE.txt file for licensing information # See ChangeLog.rst file for all contributors and changes # """ Runs Exif tag extraction in command line. """ import sys import getopt import logging import timeit from exifread.tags import DEFAULT_STOP_TAG, FIELD_TYPES from exifread import process_file, exif_log, __version__ logger = exif_log.get_logger() def usage(exit_status): """Show command line usage.""" msg = ('Usage: EXIF.py [OPTIONS] file1 [file2 ...]\n' 'Extract EXIF information from digital camera image files.\n\nOptions:\n' '-h --help Display usage information and exit.\n' '-v --version Display version information and exit.\n' '-q --quick Do not process MakerNotes.\n' '-t TAG --stop-tag TAG Stop processing when this tag is retrieved.\n' '-s --strict Run in strict mode (stop on errors).\n' '-d --debug Run in debug mode (display extra info).\n' '-c --color Output in color (only works with debug on POSIX).\n' ) print(msg) sys.exit(exit_status) def show_version(): """Show the program version.""" print('Version %s on Python%s' % (__version__, sys.version_info[0])) sys.exit(0) def main(): """Parse command line options/arguments and execute.""" try: arg_names = ["help", "version", "quick", "strict", "debug", "stop-tag="] opts, args = getopt.getopt(sys.argv[1:], "hvqsdct:v", arg_names) except getopt.GetoptError: usage(2) detailed = True stop_tag = DEFAULT_STOP_TAG debug = False strict = False color = False for option, arg in opts: if option in ("-h", "--help"): usage(0) if option in ("-v", "--version"): show_version() if option in ("-q", "--quick"): detailed = False if option in ("-t", "--stop-tag"): stop_tag = arg if option in ("-s", "--strict"): strict = True if option in ("-d", "--debug"): debug = True if option in ("-c", "--color"): color = True if not args: usage(2) exif_log.setup_logger(debug, color) # output info for each file for filename in args: file_start = timeit.default_timer() try: img_file = open(str(filename), 'rb') except IOError: logger.error("'%s' is unreadable", filename) continue logger.info("Opening: %s", filename) tag_start = timeit.default_timer() # get the tags data = process_file(img_file, stop_tag=stop_tag, details=detailed, strict=strict, debug=debug) tag_stop = timeit.default_timer() if not data: logger.warning("No EXIF information found\n") continue if 'JPEGThumbnail' in data: logger.info('File has JPEG thumbnail') del data['JPEGThumbnail'] if 'TIFFThumbnail' in data: logger.info('File has TIFF thumbnail') del data['TIFFThumbnail'] tag_keys = list(data.keys()) tag_keys.sort() for i in tag_keys: try: logger.info('%s (%s): %s', i, FIELD_TYPES[data[i].field_type][2], data[i].printable) except: logger.error("%s : %s", i, str(data[i])) file_stop = timeit.default_timer() logger.debug("Tags processed in %s seconds", tag_stop - tag_start) logger.debug("File processed in %s seconds", file_stop - file_start) print("") if __name__ == '__main__': main() exif-py-2.2.0/LICENSE.txt000066400000000000000000000030001351613376600147230ustar00rootroot00000000000000 Copyright (c) 2002-2007 Gene Cash Copyright (c) 2007-2015 Ianaré Sévi and contributors Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the authors nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. exif-py-2.2.0/MANIFEST.in000066400000000000000000000000251351613376600146420ustar00rootroot00000000000000include *.txt *.rst exif-py-2.2.0/README.rst000066400000000000000000000115131351613376600145770ustar00rootroot00000000000000******* EXIF.py ******* .. image:: https://travis-ci.org/ianare/exif-py.png :target: https://travis-ci.org/ianare/exif-py Easy to use Python module to extract Exif metadata from tiff and jpeg files. Originally written by Gene Cash & Thierry Bousch. Installation ************ PyPI ==== The recommended process is to install the `PyPI package `_, as it allows easily staying up to date:: $ pip install exifread See the `pip documentation `_ for more info. Archive ======= Download an archive from the project's `releases page `_. Extract and enjoy. Compatibility ************* EXIF.py is tested and officially supported on the following Python versions: - 2.7 - 3.4 - 3.5 - 3.6 - 3.7 Usage ***** Command line ============ Some examples:: $ EXIF.py image1.jpg $ EXIF.py image1.jpg image2.tiff $ find ~/Pictures -name "*.jpg" -name "*.tiff" | xargs EXIF.py Show command line options:: $ EXIF.py Python Script ============= :: import exifread # Open image file for reading (binary mode) f = open(path_name, 'rb') # Return Exif tags tags = exifread.process_file(f) *Note:* To use this library in your project as a Git submodule, you should:: from import exifread Returned tags will be a dictionary mapping names of Exif tags to their values in the file named by path_name. You can process the tags as you wish. In particular, you can iterate through all the tags with:: for tag in tags.keys(): if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename', 'EXIF MakerNote'): print "Key: %s, value %s" % (tag, tags[tag]) An ``if`` statement is used to avoid printing out a few of the tags that tend to be long or boring. The tags dictionary will include keys for all of the usual Exif tags, and will also include keys for Makernotes used by some cameras, for which we have a good specification. Note that the dictionary keys are the IFD name followed by the tag name. For example:: 'EXIF DateTimeOriginal', 'Image Orientation', 'MakerNote FocusMode' Tag Descriptions **************** Tags are divided into these main categories: - ``Image``: information related to the main image (IFD0 of the Exif data). - ``Thumbnail``: information related to the thumbnail image, if present (IFD1 of the Exif data). - ``EXIF``: Exif information (sub-IFD). - ``GPS``: GPS information (sub-IFD). - ``Interoperability``: Interoperability information (sub-IFD). - ``MakerNote``: Manufacturer specific information. There are no official published references for these tags. Processing Options ****************** These options can be used both in command line mode and within a script. Faster Processing ================= Don't process makernote tags, don't extract the thumbnail image (if any). Pass the ``-q`` or ``--quick`` command line arguments, or as:: tags = exifread.process_file(f, details=False) Stop at a Given Tag =================== To stop processing the file after a specified tag is retrieved. Pass the ``-t TAG`` or ``--stop-tag TAG`` argument, or as:: tags = exifread.process_file(f, stop_tag='TAG') where ``TAG`` is a valid tag name, ex ``'DateTimeOriginal'``. *The two above options are useful to speed up processing of large numbers of files.* Strict Processing ================= Return an error on invalid tags instead of silently ignoring. Pass the ``-s`` or ``--strict`` argument, or as:: tags = exifread.process_file(f, strict=True) Usage Example ============= This example shows how to use the library to correct the orientation of an image (using PIL for the transformation) before e.g. displaying it. :: import exifread from PIL import Image def _read_img_and_correct_exif_orientation(path): im = Image.open(path) tags = {} with open(path, 'rb') as f: tags = exifread.process_file(f, details=False) if "Image Orientation" in tags.keys(): orientation = tags["Image Orientation"] logging.debug("Orientation: %s (%s)", orientation, orientation.values) val = orientation.values if 5 in val: val += [4,8] if 7 in val: val += [4, 6] if 3 in val: logging.debug("Rotating by 180 degrees.") im = im.transpose(Image.ROTATE_180) if 4 in val: logging.debug("Mirroring horizontally.") im = im.transpose(Image.FLIP_TOP_BOTTOM) if 6 in val: logging.debug("Rotating by 270 degrees.") im = im.transpose(Image.ROTATE_270) if 8 in val: logging.debug("Rotating by 90 degrees.") im = im.transpose(Image.ROTATE_90) return im exif-py-2.2.0/__init__.py000066400000000000000000000000021351613376600152100ustar00rootroot00000000000000# exif-py-2.2.0/exifread/000077500000000000000000000000001351613376600146765ustar00rootroot00000000000000exif-py-2.2.0/exifread/__init__.py000066400000000000000000000257771351613376600170310ustar00rootroot00000000000000""" Read Exif metadata from tiff and jpeg files. """ from .exif_log import get_logger from .classes import * from .tags import * from .utils import ord_ from .heic import HEICExifFinder __version__ = '2.1.2' logger = get_logger() def increment_base(data, base): return ord_(data[base + 2]) * 256 + ord_(data[base + 3]) + 2 def process_file(f, stop_tag=DEFAULT_STOP_TAG, details=True, strict=False, debug=False, truncate_tags=True, auto_seek=True): """ Process an image file (expects an open file object). This is the function that has to deal with all the arbitrary nasty bits of the EXIF standard. """ if auto_seek: f.seek(0) # by default do not fake an EXIF beginning fake_exif = 0 # determine whether it's a JPEG or TIFF data = f.read(12) if data[0:2] in [b'II', b'MM']: # it's a TIFF file logger.debug("TIFF format recognized in data[0:2]") f.seek(0) endian = f.read(1) f.read(1) offset = 0 elif data[4:12] == b'ftypheic': f.seek(0) heic = HEICExifFinder (f) offset, endian = heic.find_exif() elif data[0:2] == b'\xFF\xD8': # it's a JPEG file logger.debug("JPEG format recognized data[0:2]=0x%X%X", ord_(data[0]), ord_(data[1])) base = 2 logger.debug("data[2]=0x%X data[3]=0x%X data[6:10]=%s", ord_(data[2]), ord_(data[3]), data[6:10]) while ord_(data[2]) == 0xFF and data[6:10] in (b'JFIF', b'JFXX', b'OLYM', b'Phot'): length = ord_(data[4]) * 256 + ord_(data[5]) logger.debug(" Length offset is %s", length) f.read(length - 8) # fake an EXIF beginning of file # I don't think this is used. --gd data = b'\xFF\x00' + f.read(10) fake_exif = 1 if base > 2: logger.debug(" Added to base") base = base + length + 4 - 2 else: logger.debug(" Added to zero") base = length + 4 logger.debug(" Set segment base to 0x%X", base) # Big ugly patch to deal with APP2 (or other) data coming before APP1 f.seek(0) # in theory, this could be insufficient since 64K is the maximum size--gd data = f.read(base + 4000) # base = 2 while 1: logger.debug(" Segment base 0x%X", base) if data[base:base + 2] == b'\xFF\xE1': # APP1 logger.debug(" APP1 at base 0x%X", base) logger.debug(" Length: 0x%X 0x%X", ord_(data[base + 2]), ord_(data[base + 3])) logger.debug(" Code: %s", data[base + 4:base + 8]) if data[base + 4:base + 8] == b"Exif": logger.debug(" Decrement base by 2 to get to pre-segment header (for compatibility with later code)") base -= 2 break increment = increment_base(data, base) logger.debug(" Increment base by %s", increment) base += increment elif data[base:base + 2] == b'\xFF\xE0': # APP0 logger.debug(" APP0 at base 0x%X", base) logger.debug(" Length: 0x%X 0x%X", ord_(data[base + 2]), ord_(data[base + 3])) logger.debug(" Code: %s", data[base + 4:base + 8]) increment = increment_base(data, base) logger.debug(" Increment base by %s", increment) base += increment elif data[base:base + 2] == b'\xFF\xE2': # APP2 logger.debug(" APP2 at base 0x%X", base) logger.debug(" Length: 0x%X 0x%X", ord_(data[base + 2]), ord_(data[base + 3])) logger.debug(" Code: %s", data[base + 4:base + 8]) increment = increment_base(data, base) logger.debug(" Increment base by %s", increment) base += increment elif data[base:base + 2] == b'\xFF\xEE': # APP14 logger.debug(" APP14 Adobe segment at base 0x%X", base) logger.debug(" Length: 0x%X 0x%X", ord_(data[base + 2]), ord_(data[base + 3])) logger.debug(" Code: %s", data[base + 4:base + 8]) increment = increment_base(data, base) logger.debug(" Increment base by %s", increment) base += increment logger.debug(" There is useful EXIF-like data here, but we have no parser for it.") elif data[base:base + 2] == b'\xFF\xDB': logger.debug(" JPEG image data at base 0x%X No more segments are expected.", base) break elif data[base:base + 2] == b'\xFF\xD8': # APP12 logger.debug(" FFD8 segment at base 0x%X", base) logger.debug(" Got 0x%X 0x%X and %s instead", ord_(data[base]), ord_(data[base + 1]), data[4 + base:10 + base]) logger.debug(" Length: 0x%X 0x%X", ord_(data[base + 2]), ord_(data[base + 3])) logger.debug(" Code: %s", data[base + 4:base + 8]) increment = increment_base(data, base) logger.debug(" Increment base by %s", increment) base += increment elif data[base:base + 2] == b'\xFF\xEC': # APP12 logger.debug(" APP12 XMP (Ducky) or Pictureinfo segment at base 0x%X", base) logger.debug(" Got 0x%X and 0x%X instead", ord_(data[base]), ord_(data[base + 1])) logger.debug(" Length: 0x%X 0x%X", ord_(data[base + 2]), ord_(data[base + 3])) logger.debug("Code: %s", data[base + 4:base + 8]) increment = increment_base(data, base) logger.debug(" Increment base by %s", increment) base += increment logger.debug( " There is useful EXIF-like data here (quality, comment, copyright), but we have no parser for it.") else: try: increment = increment_base(data, base) logger.debug(" Got 0x%X and 0x%X instead", ord_(data[base]), ord_(data[base + 1])) except IndexError: logger.debug(" Unexpected/unhandled segment type or file content.") return {} else: logger.debug(" Increment base by %s", increment) base += increment f.seek(base + 12) if ord_(data[2 + base]) == 0xFF and data[6 + base:10 + base] == b'Exif': # detected EXIF header offset = f.tell() endian = f.read(1) #HACK TEST: endian = 'M' elif ord_(data[2 + base]) == 0xFF and data[6 + base:10 + base + 1] == b'Ducky': # detected Ducky header. logger.debug("EXIF-like header (normally 0xFF and code): 0x%X and %s", ord_(data[2 + base]), data[6 + base:10 + base + 1]) offset = f.tell() endian = f.read(1) elif ord_(data[2 + base]) == 0xFF and data[6 + base:10 + base + 1] == b'Adobe': # detected APP14 (Adobe) logger.debug("EXIF-like header (normally 0xFF and code): 0x%X and %s", ord_(data[2 + base]), data[6 + base:10 + base + 1]) offset = f.tell() endian = f.read(1) else: # no EXIF information logger.debug("No EXIF header expected data[2+base]==0xFF and data[6+base:10+base]===Exif (or Duck)") logger.debug("Did get 0x%X and %s", ord_(data[2 + base]), data[6 + base:10 + base + 1]) return {} else: # file format not recognized logger.debug("File format not recognized.") return {} endian = chr(ord_(endian[0])) # deal with the EXIF info we found logger.debug("Endian format is %s (%s)", endian, { 'I': 'Intel', 'M': 'Motorola', '\x01': 'Adobe Ducky', 'd': 'XMP/Adobe unknown' }[endian]) hdr = ExifHeader(f, endian, offset, fake_exif, strict, debug, details, truncate_tags) ifd_list = hdr.list_ifd() thumb_ifd = False ctr = 0 for ifd in ifd_list: if ctr == 0: ifd_name = 'Image' elif ctr == 1: ifd_name = 'Thumbnail' thumb_ifd = ifd else: ifd_name = 'IFD %d' % ctr logger.debug('IFD %d (%s) at offset %s:', ctr, ifd_name, ifd) hdr.dump_ifd(ifd, ifd_name, stop_tag=stop_tag) ctr += 1 # EXIF IFD exif_off = hdr.tags.get('Image ExifOffset') if exif_off: logger.debug('Exif SubIFD at offset %s:', exif_off.values[0]) hdr.dump_ifd(exif_off.values[0], 'EXIF', stop_tag=stop_tag) # deal with MakerNote contained in EXIF IFD # (Some apps use MakerNote tags but do not use a format for which we # have a description, do not process these). if details and 'EXIF MakerNote' in hdr.tags and 'Image Make' in hdr.tags: hdr.decode_maker_note() # extract thumbnails if details and thumb_ifd: hdr.extract_tiff_thumbnail(thumb_ifd) hdr.extract_jpeg_thumbnail() # parse XMP tags (experimental) if debug and details: xmp_string = b'' # Easy we already have them if 'Image ApplicationNotes' in hdr.tags: logger.debug('XMP present in Exif') xmp_string = make_string(hdr.tags['Image ApplicationNotes'].values) # We need to look in the entire file for the XML else: logger.debug('XMP not in Exif, searching file for XMP info...') xml_started = False xml_finished = False for line in f: open_tag = line.find(b'') if open_tag != -1: xml_started = True line = line[open_tag:] logger.debug('XMP found opening tag at line position %s' % open_tag) if close_tag != -1: logger.debug('XMP found closing tag at line position %s' % close_tag) line_offset = 0 if open_tag != -1: line_offset = open_tag line = line[:(close_tag - line_offset) + 12] xml_finished = True if xml_started: xmp_string += line if xml_finished: break logger.debug('XMP Finished searching for info') if xmp_string: hdr.parse_xmp(xmp_string) return hdr.tags exif-py-2.2.0/exifread/classes.py000066400000000000000000000602671351613376600167200ustar00rootroot00000000000000import struct import re from .exif_log import get_logger from .utils import Ratio from .tags import * logger = get_logger() try: basestring except NameError: basestring = str class IfdTag: """ Eases dealing with tags. """ def __init__(self, printable, tag, field_type, values, field_offset, field_length): # printable version of data self.printable = printable # tag ID number self.tag = tag # field type as index into FIELD_TYPES self.field_type = field_type # offset of start of field in bytes from beginning of IFD self.field_offset = field_offset # length of data field in bytes self.field_length = field_length # either a string or array of data items self.values = values def __str__(self): return self.printable def __repr__(self): try: s = '(0x%04X) %s=%s @ %d' % (self.tag, FIELD_TYPES[self.field_type][2], self.printable, self.field_offset) except: s = '(%s) %s=%s @ %s' % (str(self.tag), FIELD_TYPES[self.field_type][2], self.printable, str(self.field_offset)) return s class ExifHeader: """ Handle an EXIF header. """ def __init__(self, file, endian, offset, fake_exif, strict, debug=False, detailed=True, truncate_tags=True): self.file = file self.endian = endian self.offset = offset self.fake_exif = fake_exif self.strict = strict self.debug = debug self.detailed = detailed self.truncate_tags = truncate_tags self.tags = {} def s2n(self, offset, length, signed=False): """ Convert slice to integer, based on sign and endian flags. Usually this offset is assumed to be relative to the beginning of the start of the EXIF information. For some cameras that use relative tags, this offset may be relative to some other starting point. """ # Little-endian if Intel, big-endian if Motorola fmt = '<' if self.endian == 'I' else '>' # Construct a format string from the requested length and signedness; # raise a ValueError if length is something silly like 3 try: fmt += { (1, False): 'B', (1, True): 'b', (2, False): 'H', (2, True): 'h', (4, False): 'I', (4, True): 'i', (8, False): 'L', (8, True): 'l', }[(length, signed)] except KeyError: raise ValueError('unexpected unpacking length: %d' % length) self.file.seek(self.offset + offset) buf = self.file.read(length) if buf: return struct.unpack(fmt, buf)[0] return 0 def n2s(self, offset, length): """Convert offset to string.""" s = '' for dummy in range(length): if self.endian == 'I': s += chr(offset & 0xFF) else: s = chr(offset & 0xFF) + s offset = offset >> 8 return s def _first_ifd(self): """Return first IFD.""" return self.s2n(4, 4) def _next_ifd(self, ifd): """Return the pointer to next IFD.""" entries = self.s2n(ifd, 2) next_ifd = self.s2n(ifd + 2 + 12 * entries, 4) if next_ifd == ifd: return 0 else: return next_ifd def list_ifd(self): """Return the list of IFDs in the header.""" i = self._first_ifd() ifds = [] while i: ifds.append(i) i = self._next_ifd(i) return ifds def dump_ifd(self, ifd, ifd_name, tag_dict=EXIF_TAGS, relative=0, stop_tag=DEFAULT_STOP_TAG): """ Return a list of entries in the given IFD. """ # make sure we can process the entries try: entries = self.s2n(ifd, 2) except TypeError: logger.warning("Possibly corrupted IFD: %s" % ifd) return for i in range(entries): # entry is index of start of this IFD in the file entry = ifd + 2 + 12 * i tag = self.s2n(entry, 2) # get tag name early to avoid errors, help debug tag_entry = tag_dict.get(tag) if tag_entry: tag_name = tag_entry[0] else: tag_name = 'Tag 0x%04X' % tag # ignore certain tags for faster processing if not (not self.detailed and tag in IGNORE_TAGS): field_type = self.s2n(entry + 2, 2) # unknown field type if not 0 < field_type < len(FIELD_TYPES): if not self.strict: continue else: raise ValueError('Unknown type %d in tag 0x%04X' % (field_type, tag)) type_length = FIELD_TYPES[field_type][0] count = self.s2n(entry + 4, 4) # Adjust for tag id/type/count (2+2+4 bytes) # Now we point at either the data or the 2nd level offset offset = entry + 8 # If the value fits in 4 bytes, it is inlined, else we # need to jump ahead again. if count * type_length > 4: # offset is not the value; it's a pointer to the value # if relative we set things up so s2n will seek to the right # place when it adds self.offset. Note that this 'relative' # is for the Nikon type 3 makernote. Other cameras may use # other relative offsets, which would have to be computed here # slightly differently. if relative: tmp_offset = self.s2n(offset, 4) offset = tmp_offset + ifd - 8 if self.fake_exif: offset += 18 else: offset = self.s2n(offset, 4) field_offset = offset values = None if field_type == 2: # special case: null-terminated ASCII string # XXX investigate # sometimes gets too big to fit in int value if count != 0: # and count < (2**31): # 2E31 is hardware dependent. --gd file_position = self.offset + offset try: self.file.seek(file_position) values = self.file.read(count) # Drop any garbage after a null. values = values.split(b'\x00', 1)[0] if isinstance(values, bytes): try: values = values.decode("utf-8") except UnicodeDecodeError: logger.warning("Possibly corrupted field %s in %s IFD", tag_name, ifd_name) except OverflowError: logger.warn('OverflowError at position: %s, length: %s', file_position, count) values = '' except MemoryError: logger.warn('MemoryError at position: %s, length: %s', file_position, count) values = '' else: values = '' else: values = [] signed = (field_type in [6, 8, 9, 10]) # XXX investigate # some entries get too big to handle could be malformed # file or problem with self.s2n if count < 1000: for dummy in range(count): if field_type in (5, 10): # a ratio value = Ratio(self.s2n(offset, 4, signed), self.s2n(offset + 4, 4, signed)) elif field_type in (11,12): # a float or double unpack_format = "" if self.endian == 'I': unpack_format += "<" else: unpack_format += ">" if field_type == 11: unpack_format += "f" else: unpack_format += "d" self.file.seek(self.offset + offset) byte_str = self.file.read(type_length) value = struct.unpack(unpack_format,byte_str) else: value = self.s2n(offset, type_length, signed) values.append(value) offset = offset + type_length # The test above causes problems with tags that are # supposed to have long values! Fix up one important case. elif tag_name in ('MakerNote', makernote.canon.CAMERA_INFO_TAG_NAME): for dummy in range(count): value = self.s2n(offset, type_length, signed) values.append(value) offset = offset + type_length # now 'values' is either a string or an array if count == 1 and field_type != 2: printable = str(values[0]) elif count > 50 and len(values) > 20 and not isinstance(values, basestring) : if self.truncate_tags : printable = str(values[0:20])[0:-1] + ", ... ]" else: printable = str(values[0:-1]) else: try: printable = str(values) except UnicodeEncodeError: printable = unicode(values) # compute printable version of values if tag_entry: # optional 2nd tag element is present if len(tag_entry) != 1: if callable(tag_entry[1]): # call mapping function printable = tag_entry[1](values) elif type(tag_entry[1]) is tuple: ifd_info = tag_entry[1] try: logger.debug('%s SubIFD at offset %d:', ifd_info[0], values[0]) self.dump_ifd(values[0], ifd_info[0], tag_dict=ifd_info[1], stop_tag=stop_tag) except IndexError: logger.warn('No values found for %s SubIFD', ifd_info[0]) else: printable = '' for i in values: # use lookup table for this tag printable += tag_entry[1].get(i, repr(i)) self.tags[ifd_name + ' ' + tag_name] = IfdTag(printable, tag, field_type, values, field_offset, count * type_length) try: tag_value = repr(self.tags[ifd_name + ' ' + tag_name]) # fix for python2's handling of unicode values except UnicodeEncodeError: tag_value = unicode(self.tags[ifd_name + ' ' + tag_name]) logger.debug(' %s: %s', tag_name, tag_value) if tag_name == stop_tag: break def extract_tiff_thumbnail(self, thumb_ifd): """ Extract uncompressed TIFF thumbnail. Take advantage of the pre-existing layout in the thumbnail IFD as much as possible """ thumb = self.tags.get('Thumbnail Compression') if not thumb or thumb.printable != 'Uncompressed TIFF': return entries = self.s2n(thumb_ifd, 2) # this is header plus offset to IFD ... if self.endian == 'M': tiff = 'MM\x00*\x00\x00\x00\x08' else: tiff = 'II*\x00\x08\x00\x00\x00' # ... plus thumbnail IFD data plus a null "next IFD" pointer self.file.seek(self.offset + thumb_ifd) tiff += self.file.read(entries * 12 + 2) + '\x00\x00\x00\x00' # fix up large value offset pointers into data area for i in range(entries): entry = thumb_ifd + 2 + 12 * i tag = self.s2n(entry, 2) field_type = self.s2n(entry + 2, 2) type_length = FIELD_TYPES[field_type][0] count = self.s2n(entry + 4, 4) old_offset = self.s2n(entry + 8, 4) # start of the 4-byte pointer area in entry ptr = i * 12 + 18 # remember strip offsets location if tag == 0x0111: strip_off = ptr strip_len = count * type_length # is it in the data area? if count * type_length > 4: # update offset pointer (nasty "strings are immutable" crap) # should be able to say "tiff[ptr:ptr+4]=newoff" newoff = len(tiff) tiff = tiff[:ptr] + self.n2s(newoff, 4) + tiff[ptr + 4:] # remember strip offsets location if tag == 0x0111: strip_off = newoff strip_len = 4 # get original data and store it self.file.seek(self.offset + old_offset) tiff += self.file.read(count * type_length) # add pixel strips and update strip offset info old_offsets = self.tags['Thumbnail StripOffsets'].values old_counts = self.tags['Thumbnail StripByteCounts'].values for i in range(len(old_offsets)): # update offset pointer (more nasty "strings are immutable" crap) offset = self.n2s(len(tiff), strip_len) tiff = tiff[:strip_off] + offset + tiff[strip_off + strip_len:] strip_off += strip_len # add pixel strip to end self.file.seek(self.offset + old_offsets[i]) tiff += self.file.read(old_counts[i]) self.tags['TIFFThumbnail'] = tiff def extract_jpeg_thumbnail(self): """ Extract JPEG thumbnail. (Thankfully the JPEG data is stored as a unit.) """ thumb_offset = self.tags.get('Thumbnail JPEGInterchangeFormat') if thumb_offset: self.file.seek(self.offset + thumb_offset.values[0]) size = self.tags['Thumbnail JPEGInterchangeFormatLength'].values[0] self.tags['JPEGThumbnail'] = self.file.read(size) # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote # since it's not allowed in a uncompressed TIFF IFD if 'JPEGThumbnail' not in self.tags: thumb_offset = self.tags.get('MakerNote JPEGThumbnail') if thumb_offset: self.file.seek(self.offset + thumb_offset.values[0]) self.tags['JPEGThumbnail'] = self.file.read(thumb_offset.field_length) def decode_maker_note(self): """ Decode all the camera-specific MakerNote formats Note is the data that comprises this MakerNote. The MakerNote will likely have pointers in it that point to other parts of the file. We'll use self.offset as the starting point for most of those pointers, since they are relative to the beginning of the file. If the MakerNote is in a newer format, it may use relative addressing within the MakerNote. In that case we'll use relative addresses for the pointers. As an aside: it's not just to be annoying that the manufacturers use relative offsets. It's so that if the makernote has to be moved by the picture software all of the offsets don't have to be adjusted. Overall, this is probably the right strategy for makernotes, though the spec is ambiguous. The spec does not appear to imagine that makernotes would follow EXIF format internally. Once they did, it's ambiguous whether the offsets should be from the header at the start of all the EXIF info, or from the header at the start of the makernote. """ note = self.tags['EXIF MakerNote'] # Some apps use MakerNote tags but do not use a format for which we # have a description, so just do a raw dump for these. make = self.tags['Image Make'].printable # Nikon # The maker note usually starts with the word Nikon, followed by the # type of the makernote (1 or 2, as a short). If the word Nikon is # not at the start of the makernote, it's probably type 2, since some # cameras work that way. if 'NIKON' in make: if note.values[0:7] == [78, 105, 107, 111, 110, 0, 1]: logger.debug("Looks like a type 1 Nikon MakerNote.") self.dump_ifd(note.field_offset + 8, 'MakerNote', tag_dict=makernote.nikon.TAGS_OLD) elif note.values[0:7] == [78, 105, 107, 111, 110, 0, 2]: logger.debug("Looks like a labeled type 2 Nikon MakerNote") if note.values[12:14] != [0, 42] and note.values[12:14] != [42, 0]: raise ValueError("Missing marker tag '42' in MakerNote.") # skip the Makernote label and the TIFF header self.dump_ifd(note.field_offset + 10 + 8, 'MakerNote', tag_dict=makernote.nikon.TAGS_NEW, relative=1) else: # E99x or D1 logger.debug("Looks like an unlabeled type 2 Nikon MakerNote") self.dump_ifd(note.field_offset, 'MakerNote', tag_dict=makernote.nikon.TAGS_NEW) return # Olympus if make.startswith('OLYMPUS'): self.dump_ifd(note.field_offset + 8, 'MakerNote', tag_dict=makernote.olympus.TAGS) # TODO #for i in (('MakerNote Tag 0x2020', makernote.OLYMPUS_TAG_0x2020),): # self.decode_olympus_tag(self.tags[i[0]].values, i[1]) #return # Casio if 'CASIO' in make or 'Casio' in make: self.dump_ifd(note.field_offset, 'MakerNote', tag_dict=makernote.casio.TAGS) return # Fujifilm if make == 'FUJIFILM': # bug: everything else is "Motorola" endian, but the MakerNote # is "Intel" endian endian = self.endian self.endian = 'I' # bug: IFD offsets are from beginning of MakerNote, not # beginning of file header offset = self.offset self.offset += note.field_offset # process note with bogus values (note is actually at offset 12) self.dump_ifd(12, 'MakerNote', tag_dict=makernote.fujifilm.TAGS) # reset to correct values self.endian = endian self.offset = offset return # Apple if make == 'Apple' and \ note.values[0:10] == [65, 112, 112, 108, 101, 32, 105, 79, 83, 0]: t = self.offset self.offset += note.field_offset+14 self.dump_ifd(0, 'MakerNote', tag_dict=makernote.apple.TAGS) self.offset = t return # Canon if make == 'Canon': self.dump_ifd(note.field_offset, 'MakerNote', tag_dict=makernote.canon.TAGS) for i in (('MakerNote Tag 0x0001', makernote.canon.CAMERA_SETTINGS), ('MakerNote Tag 0x0002', makernote.canon.FOCAL_LENGTH), ('MakerNote Tag 0x0004', makernote.canon.SHOT_INFO), ('MakerNote Tag 0x0026', makernote.canon.AF_INFO_2), ('MakerNote Tag 0x0093', makernote.canon.FILE_INFO)): if i[0] in self.tags: logger.debug('Canon ' + i[0]) self._canon_decode_tag(self.tags[i[0]].values, i[1]) del self.tags[i[0]] if makernote.canon.CAMERA_INFO_TAG_NAME in self.tags: tag = self.tags[makernote.canon.CAMERA_INFO_TAG_NAME] logger.debug('Canon CameraInfo') self._canon_decode_camera_info(tag) del self.tags[makernote.canon.CAMERA_INFO_TAG_NAME] return def _olympus_decode_tag(self, value, mn_tags): """ TODO Decode Olympus MakerNote tag based on offset within tag.""" pass def _canon_decode_tag(self, value, mn_tags): """ Decode Canon MakerNote tag based on offset within tag. See http://www.burren.cx/david/canon.html by David Burren """ for i in range(1, len(value)): tag = mn_tags.get(i, ('Unknown', )) name = tag[0] if len(tag) > 1: val = tag[1].get(value[i], 'Unknown') else: val = value[i] try: logger.debug(" %s %s %s", i, name, hex(value[i])) except TypeError: logger.debug(" %s %s %s", i, name, value[i]) # it's not a real IFD Tag but we fake one to make everybody # happy. this will have a "proprietary" type self.tags['MakerNote ' + name] = IfdTag(str(val), None, 0, None, None, None) def _canon_decode_camera_info(self, camera_info_tag): """ Decode the variable length encoded camera info section. """ model = self.tags.get('Image Model', None) if not model: return model = str(model.values) camera_info_tags = None for (model_name_re, tag_desc) in makernote.canon.CAMERA_INFO_MODEL_MAP.items(): if re.search(model_name_re, model): camera_info_tags = tag_desc break else: return # We are assuming here that these are all unsigned bytes (Byte or # Unknown) if camera_info_tag.field_type not in (1, 7): return camera_info = struct.pack('<%dB' % len(camera_info_tag.values), *camera_info_tag.values) # Look for each data value and decode it appropriately. for offset, tag in camera_info_tags.items(): tag_format = tag[1] tag_size = struct.calcsize(tag_format) if len(camera_info) < offset + tag_size: continue packed_tag_value = camera_info[offset:offset + tag_size] tag_value = struct.unpack(tag_format, packed_tag_value)[0] tag_name = tag[0] if len(tag) > 2: if callable(tag[2]): tag_value = tag[2](tag_value) else: tag_value = tag[2].get(tag_value, tag_value) logger.debug(" %s %s", tag_name, tag_value) self.tags['MakerNote ' + tag_name] = IfdTag(str(tag_value), None, 0, None, None, None) def parse_xmp(self, xmp_string): import xml.dom.minidom logger.debug('XMP cleaning data') xml = xml.dom.minidom.parseString(xmp_string) pretty = xml.toprettyxml() cleaned = [] for line in pretty.splitlines(): if line.strip(): cleaned.append(line) self.tags['Image ApplicationNotes'] = IfdTag('\n'.join(cleaned), None, 1, None, None, None) exif-py-2.2.0/exifread/exif_log.py000066400000000000000000000037561351613376600170570ustar00rootroot00000000000000""" Custom log output """ import sys import logging TEXT_NORMAL = 0 TEXT_BOLD = 1 TEXT_RED = 31 TEXT_GREEN = 32 TEXT_YELLOW = 33 TEXT_BLUE = 34 TEXT_MAGENTA = 35 TEXT_CYAN = 36 def get_logger(): return logging.getLogger('exifread') def setup_logger(debug, color): """Configure the logger.""" if debug: log_level = logging.DEBUG else: log_level = logging.INFO logger = logging.getLogger('exifread') stream = Handler(log_level, debug, color) logger.addHandler(stream) logger.setLevel(log_level) class Formatter(logging.Formatter): def __init__(self, debug=False, color=False): self.color = color self.debug = debug if self.debug: log_format = '%(levelname)-6s %(message)s' else: log_format = '%(message)s' logging.Formatter.__init__(self, log_format) def format(self, record): if self.debug and self.color: if record.levelno >= logging.CRITICAL: color = TEXT_RED elif record.levelno >= logging.ERROR: color = TEXT_RED elif record.levelno >= logging.WARNING: color = TEXT_YELLOW elif record.levelno >= logging.INFO: color = TEXT_GREEN elif record.levelno >= logging.DEBUG: color = TEXT_CYAN else: color = TEXT_NORMAL record.levelname = "\x1b[%sm%s\x1b[%sm" % (color, record.levelname, TEXT_NORMAL) return logging.Formatter.format(self, record) class Handler(logging.StreamHandler): def __init__(self, log_level, debug=False, color=False): self.color = color self.debug = debug logging.StreamHandler.__init__(self, sys.stdout) self.setFormatter(Formatter(debug, color)) self.setLevel(log_level) """ def emit(self, record): record.msg = "\x1b[%sm%s\x1b[%sm" % (TEXT_BOLD, record.msg, TEXT_NORMAL) logging.StreamHandler.emit(self, record) """exif-py-2.2.0/exifread/heic.py000066400000000000000000000165171351613376600161720ustar00rootroot00000000000000# -*- Mode: Python -*- # Find Exif data in an HEIC file. # As of 2019, the latest standard seems to be "ISO/IEC 14496-12:2015" # There are many different related standards. (quicktime, mov, mp4, etc...) # See https://en.wikipedia.org/wiki/ISO_base_media_file_format for more details. # We parse just enough of the iso format to locate the Exif data in the file. # Inside the 'meta' box are two directories we need: # 1) the 'iinf' box contains 'infe' records, we look for the item_ID for 'Exif'. # 2) once we have the item_ID, we find a matching entry in the 'iloc' box, which # gives us position and size information. import struct from .exif_log import get_logger logger = get_logger() class WrongBox (Exception): pass class NoParser (Exception): pass class BoxVersion (Exception): pass class BadSize (Exception): pass class Box: def __init__ (self, name): self.name = name def __repr__ (self): return "" % (self.name,) class HEICExifFinder: def __init__ (self, file): self.file = file def get (self, nbytes): r = self.file.read (nbytes) if not r: raise EOFError else: return r def get16 (self): return struct.unpack ('>H', self.get (2))[0] def get32 (self): return struct.unpack ('>L', self.get (4))[0] def get64 (self): return struct.unpack ('>Q', self.get (8))[0] def get_int4x2 (self): n = struct.unpack ('>B', self.get(1))[0] n0 = n >> 4 n1 = n & 0xf return n0, n1 # some fields have variant-sized data. def get_int (self, size): if size == 2: return self.get16() elif size == 4: return self.get32() elif size == 8: return self.get64() elif size == 0: return 0 else: raise BadSize (size) def get_string (self): r = [] while 1: ch = self.get (1) if ch == b'\x00': break else: r.append (ch) return b''.join (r) def next_box (self, depth=0): pos = self.file.tell() size = self.get32() kind = self.get(4).decode('ascii') b = Box (kind) if size == 0: # signifies 'to the end of the file', we shouldn't see this. raise NotImplementedError elif size == 1: # 64-bit size follows type. size = self.get64() b.size = size - 16 b.after = pos + size else: b.size = size - 8 b.after = pos + size b.pos = self.file.tell() return b def get_full (self, box): # iso boxes come in 'old' and 'full' variants. the 'full' variant # contains version and flags information. vflags = self.get32() box.version = vflags >> 24 box.flags = vflags & 0x00ffffff def skip (self, box): self.file.seek (box.after) def expect_parse (self, name): b = self.next_box() if b.name == name: return self.parse_box (b) else: raise WrongBox (name, b.name) def get_parser (self, box): method = 'parse_%s' % (box.name,) return getattr (self, method, None) def parse_box (self, b): probe = self.get_parser (b) if probe is None: raise NoParser (b.name) else: probe (b) # in case anything is left unread self.file.seek (b.after) return b def parse_ftyp (self, box): box.major_brand = self.get(4) box.minor_version = self.get32() box.compat = [] size = box.size - 8 while size > 0: box.compat.append (self.get (4)) size -= 4 def parse_meta (self, meta): self.get_full (meta) # this is full of boxes, but not in a predictable order. meta.subs = {} while self.file.tell() < meta.after: box = self.next_box() psub = self.get_parser (box) if psub is not None: psub (box) meta.subs[box.name] = box else: logger.debug("HEIC: skipping %r" % (box,)) # skip any unparsed data self.skip (box) def parse_infe (self, box): self.get_full (box) if box.version >= 2: if box.version == 2: box.item_ID = self.get16() elif box.version == 3: box.item_ID = self.get32() box.item_protection_index = self.get16() box.item_type = self.get(4) box.item_name = self.get_string() # ignore the rest else: box.item_type = '' def parse_iinf (self, box): self.get_full (box) count = self.get16() box.exif_infe = None for _ in range (count): infe = self.expect_parse ('infe') if infe.item_type == b'Exif': logger.debug("HEIC: found Exif 'infe' box") box.exif_infe = infe break def parse_iloc (self, box): self.get_full (box) s0, s1 = self.get_int4x2() s2, s3 = self.get_int4x2() box.offset_size = s0 box.length_size = s1 box.base_offset_size = s2 box.index_size = s3 if box.version < 2: box.item_count = self.get16() elif box.version == 2: box.item_count = self.get32() else: raise BoxVersion (2, box.version) box.locs = {} logger.debug("HEIC: %d iloc items" % (box.item_count,)) for i in range (box.item_count): if box.version < 2: item_ID = self.get16() elif box.version == 2: item_ID = self.get32() else: # notreached raise BoxVersion (2, box.version) if box.version in (1, 2): # ignore construction_method _ = self.get16() data_reference_index = self.get16() box.base_offset = self.get_int (box.base_offset_size) extent_count = self.get16() extents = [] for _ in range (extent_count): if box.version in (1, 2) and box.index_size > 0: extent_index = self.get_int (box.index_size) else: extent_index = -1 extent_offset = self.get_int (box.offset_size) extent_length = self.get_int (box.length_size) extents.append ((extent_offset, extent_length)) box.locs[item_ID] = extents def find_exif (self): ftyp = self.expect_parse ('ftyp') assert ftyp.major_brand == b'heic' assert ftyp.minor_version == 0 meta = self.expect_parse ('meta') item_ID = meta.subs['iinf'].exif_infe.item_ID extents = meta.subs['iloc'].locs[item_ID] logger.debug("HEIC: found Exif location.") # we expect the Exif data to be in one piece. assert len(extents) == 1 pos, size = extents[0] # looks like there's a kind of pseudo-box here. self.file.seek (pos) size1 = self.get32() assert self.get(size1)[0:4] == b'Exif' offset = self.file.tell() endian = self.file.read(1) return offset, endian exif-py-2.2.0/exifread/tags/000077500000000000000000000000001351613376600156345ustar00rootroot00000000000000exif-py-2.2.0/exifread/tags/__init__.py000066400000000000000000000013501351613376600177440ustar00rootroot00000000000000""" Tag definitions """ from .exif import * from .makernote import * DEFAULT_STOP_TAG = 'UNDEF' # field type descriptions as (length, abbreviation, full name) tuples FIELD_TYPES = ( (0, 'X', 'Proprietary'), # no such type (1, 'B', 'Byte'), (1, 'A', 'ASCII'), (2, 'S', 'Short'), (4, 'L', 'Long'), (8, 'R', 'Ratio'), (1, 'SB', 'Signed Byte'), (1, 'U', 'Undefined'), (2, 'SS', 'Signed Short'), (4, 'SL', 'Signed Long'), (8, 'SR', 'Signed Ratio'), (4, 'F32', 'Single-Precision Floating Point (32-bit)'), (8, 'F64', 'Double-Precision Floating Point (64-bit)'), ) # To ignore when quick processing IGNORE_TAGS = ( 0x9286, # user comment 0x927C, # MakerNote Tags 0x02BC, # XPM ) exif-py-2.2.0/exifread/tags/exif.py000066400000000000000000000324071351613376600171470ustar00rootroot00000000000000""" Standard tag definitions. """ from ..utils import make_string, make_string_uc # Interoperability tags INTEROP_TAGS = { 0x0001: ('InteroperabilityIndex', ), 0x0002: ('InteroperabilityVersion', ), 0x1000: ('RelatedImageFileFormat', ), 0x1001: ('RelatedImageWidth', ), 0x1002: ('RelatedImageLength', ), } INTEROP_INFO = ( 'Interoperability', INTEROP_TAGS ) # GPS tags GPS_TAGS = { 0x0000: ('GPSVersionID', ), 0x0001: ('GPSLatitudeRef', ), 0x0002: ('GPSLatitude', ), 0x0003: ('GPSLongitudeRef', ), 0x0004: ('GPSLongitude', ), 0x0005: ('GPSAltitudeRef', ), 0x0006: ('GPSAltitude', ), 0x0007: ('GPSTimeStamp', ), 0x0008: ('GPSSatellites', ), 0x0009: ('GPSStatus', ), 0x000A: ('GPSMeasureMode', ), 0x000B: ('GPSDOP', ), 0x000C: ('GPSSpeedRef', ), 0x000D: ('GPSSpeed', ), 0x000E: ('GPSTrackRef', ), 0x000F: ('GPSTrack', ), 0x0010: ('GPSImgDirectionRef', ), 0x0011: ('GPSImgDirection', ), 0x0012: ('GPSMapDatum', ), 0x0013: ('GPSDestLatitudeRef', ), 0x0014: ('GPSDestLatitude', ), 0x0015: ('GPSDestLongitudeRef', ), 0x0016: ('GPSDestLongitude', ), 0x0017: ('GPSDestBearingRef', ), 0x0018: ('GPSDestBearing', ), 0x0019: ('GPSDestDistanceRef', ), 0x001A: ('GPSDestDistance', ), 0x001B: ('GPSProcessingMethod', ), 0x001C: ('GPSAreaInformation', ), 0x001D: ('GPSDate', ), 0x001E: ('GPSDifferential', ), } GPS_INFO = ( 'GPS', GPS_TAGS ) # Main Exif tag names EXIF_TAGS = { 0x00FE: ('SubfileType', { 0x0: 'Full-resolution Image', 0x1: 'Reduced-resolution image', 0x2: 'Single page of multi-page image', 0x3: 'Single page of multi-page reduced-resolution image', 0x4: 'Transparency mask', 0x5: 'Transparency mask of reduced-resolution image', 0x6: 'Transparency mask of multi-page image', 0x7: 'Transparency mask of reduced-resolution multi-page image', 0x10001: 'Alternate reduced-resolution image', 0xffffffff: 'invalid ', }), 0x00FF: ('OldSubfileType', { 1: 'Full-resolution image', 2: 'Reduced-resolution image', 3: 'Single page of multi-page image', }), 0x0100: ('ImageWidth', ), 0x0101: ('ImageLength', ), 0x0102: ('BitsPerSample', ), 0x0103: ('Compression', { 1: 'Uncompressed', 2: 'CCITT 1D', 3: 'T4/Group 3 Fax', 4: 'T6/Group 4 Fax', 5: 'LZW', 6: 'JPEG (old-style)', 7: 'JPEG', 8: 'Adobe Deflate', 9: 'JBIG B&W', 10: 'JBIG Color', 32766: 'Next', 32769: 'Epson ERF Compressed', 32771: 'CCIRLEW', 32773: 'PackBits', 32809: 'Thunderscan', 32895: 'IT8CTPAD', 32896: 'IT8LW', 32897: 'IT8MP', 32898: 'IT8BL', 32908: 'PixarFilm', 32909: 'PixarLog', 32946: 'Deflate', 32947: 'DCS', 34661: 'JBIG', 34676: 'SGILog', 34677: 'SGILog24', 34712: 'JPEG 2000', 34713: 'Nikon NEF Compressed', 65000: 'Kodak DCR Compressed', 65535: 'Pentax PEF Compressed' }), 0x0106: ('PhotometricInterpretation', ), 0x0107: ('Thresholding', ), 0x0108: ('CellWidth', ), 0x0109: ('CellLength', ), 0x010A: ('FillOrder', ), 0x010D: ('DocumentName', ), 0x010E: ('ImageDescription', ), 0x010F: ('Make', ), 0x0110: ('Model', ), 0x0111: ('StripOffsets', ), 0x0112: ('Orientation', { 1: 'Horizontal (normal)', 2: 'Mirrored horizontal', 3: 'Rotated 180', 4: 'Mirrored vertical', 5: 'Mirrored horizontal then rotated 90 CCW', 6: 'Rotated 90 CW', 7: 'Mirrored horizontal then rotated 90 CW', 8: 'Rotated 90 CCW' }), 0x0115: ('SamplesPerPixel', ), 0x0116: ('RowsPerStrip', ), 0x0117: ('StripByteCounts', ), 0x0118: ('MinSampleValue', ), 0x0119: ('MaxSampleValue', ), 0x011A: ('XResolution', ), 0x011B: ('YResolution', ), 0x011C: ('PlanarConfiguration', ), 0x011D: ('PageName', make_string), 0x011E: ('XPosition', ), 0x011F: ('YPosition', ), 0x0122: ('GrayResponseUnit', { 1: '0.1', 2: '0.001', 3: '0.0001', 4: '1e-05', 5: '1e-06', }), 0x0123: ('GrayResponseCurve', ), 0x0124: ('T4Options', ), 0x0125: ('T6Options', ), 0x0128: ('ResolutionUnit', { 1: 'Not Absolute', 2: 'Pixels/Inch', 3: 'Pixels/Centimeter' }), 0x0129: ('PageNumber', ), 0x012C: ('ColorResponseUnit', ), 0x012D: ('TransferFunction', ), 0x0131: ('Software', ), 0x0132: ('DateTime', ), 0x013B: ('Artist', ), 0x013C: ('HostComputer', ), 0x013D: ('Predictor', { 1: 'None', 2: 'Horizontal differencing' }), 0x013E: ('WhitePoint', ), 0x013F: ('PrimaryChromaticities', ), 0x0140: ('ColorMap', ), 0x0141: ('HalftoneHints', ), 0x0142: ('TileWidth', ), 0x0143: ('TileLength', ), 0x0144: ('TileOffsets', ), 0x0145: ('TileByteCounts', ), 0x0146: ('BadFaxLines', ), 0x0147: ('CleanFaxData', { 0: 'Clean', 1: 'Regenerated', 2: 'Unclean' }), 0x0148: ('ConsecutiveBadFaxLines', ), 0x014C: ('InkSet', { 1: 'CMYK', 2: 'Not CMYK' }), 0x014D: ('InkNames', ), 0x014E: ('NumberofInks', ), 0x0150: ('DotRange', ), 0x0151: ('TargetPrinter', ), 0x0152: ('ExtraSamples', { 0: 'Unspecified', 1: 'Associated Alpha', 2: 'Unassociated Alpha' }), 0x0153: ('SampleFormat', { 1: 'Unsigned', 2: 'Signed', 3: 'Float', 4: 'Undefined', 5: 'Complex int', 6: 'Complex float' }), 0x0154: ('SMinSampleValue', ), 0x0155: ('SMaxSampleValue', ), 0x0156: ('TransferRange', ), 0x0157: ('ClipPath', ), 0x0200: ('JPEGProc', ), 0x0201: ('JPEGInterchangeFormat', ), 0x0202: ('JPEGInterchangeFormatLength', ), 0x0211: ('YCbCrCoefficients', ), 0x0212: ('YCbCrSubSampling', ), 0x0213: ('YCbCrPositioning', { 1: 'Centered', 2: 'Co-sited' }), 0x0214: ('ReferenceBlackWhite', ), 0x02BC: ('ApplicationNotes', ), # XPM Info 0x4746: ('Rating', ), 0x828D: ('CFARepeatPatternDim', ), 0x828E: ('CFAPattern', ), 0x828F: ('BatteryLevel', ), 0x8298: ('Copyright', ), 0x829A: ('ExposureTime', ), 0x829D: ('FNumber', ), 0x83BB: ('IPTC/NAA', ), 0x8769: ('ExifOffset', ), # Exif Tags 0x8773: ('InterColorProfile', ), 0x8822: ('ExposureProgram', { 0: 'Unidentified', 1: 'Manual', 2: 'Program Normal', 3: 'Aperture Priority', 4: 'Shutter Priority', 5: 'Program Creative', 6: 'Program Action', 7: 'Portrait Mode', 8: 'Landscape Mode' }), 0x8824: ('SpectralSensitivity', ), 0x8825: ('GPSInfo', GPS_INFO), # GPS tags 0x8827: ('ISOSpeedRatings', ), 0x8828: ('OECF', ), 0x8830: ('SensitivityType', { 0: 'Unknown', 1: 'Standard Output Sensitivity', 2: 'Recommended Exposure Index', 3: 'ISO Speed', 4: 'Standard Output Sensitivity and Recommended Exposure Index', 5: 'Standard Output Sensitivity and ISO Speed', 6: 'Recommended Exposure Index and ISO Speed', 7: 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed' }), 0x8832: ('RecommendedExposureIndex', ), 0x8833: ('ISOSpeed', ), 0x9000: ('ExifVersion', make_string), 0x9003: ('DateTimeOriginal', ), 0x9004: ('DateTimeDigitized', ), 0x9101: ('ComponentsConfiguration', { 0: '', 1: 'Y', 2: 'Cb', 3: 'Cr', 4: 'Red', 5: 'Green', 6: 'Blue' }), 0x9102: ('CompressedBitsPerPixel', ), 0x9201: ('ShutterSpeedValue', ), 0x9202: ('ApertureValue', ), 0x9203: ('BrightnessValue', ), 0x9204: ('ExposureBiasValue', ), 0x9205: ('MaxApertureValue', ), 0x9206: ('SubjectDistance', ), 0x9207: ('MeteringMode', { 0: 'Unidentified', 1: 'Average', 2: 'CenterWeightedAverage', 3: 'Spot', 4: 'MultiSpot', 5: 'Pattern', 6: 'Partial', 255: 'other' }), 0x9208: ('LightSource', { 0: 'Unknown', 1: 'Daylight', 2: 'Fluorescent', 3: 'Tungsten (incandescent light)', 4: 'Flash', 9: 'Fine weather', 10: 'Cloudy weather', 11: 'Shade', 12: 'Daylight fluorescent (D 5700 - 7100K)', 13: 'Day white fluorescent (N 4600 - 5400K)', 14: 'Cool white fluorescent (W 3900 - 4500K)', 15: 'White fluorescent (WW 3200 - 3700K)', 17: 'Standard light A', 18: 'Standard light B', 19: 'Standard light C', 20: 'D55', 21: 'D65', 22: 'D75', 23: 'D50', 24: 'ISO studio tungsten', 255: 'other light source' }), 0x9209: ('Flash', { 0: 'Flash did not fire', 1: 'Flash fired', 5: 'Strobe return light not detected', 7: 'Strobe return light detected', 9: 'Flash fired, compulsory flash mode', 13: 'Flash fired, compulsory flash mode, return light not detected', 15: 'Flash fired, compulsory flash mode, return light detected', 16: 'Flash did not fire, compulsory flash mode', 24: 'Flash did not fire, auto mode', 25: 'Flash fired, auto mode', 29: 'Flash fired, auto mode, return light not detected', 31: 'Flash fired, auto mode, return light detected', 32: 'No flash function', 65: 'Flash fired, red-eye reduction mode', 69: 'Flash fired, red-eye reduction mode, return light not detected', 71: 'Flash fired, red-eye reduction mode, return light detected', 73: 'Flash fired, compulsory flash mode, red-eye reduction mode', 77: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected', 79: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected', 89: 'Flash fired, auto mode, red-eye reduction mode', 93: 'Flash fired, auto mode, return light not detected, red-eye reduction mode', 95: 'Flash fired, auto mode, return light detected, red-eye reduction mode' }), 0x920A: ('FocalLength', ), 0x9214: ('SubjectArea', ), 0x927C: ('MakerNote', ), 0x9286: ('UserComment', make_string_uc), 0x9290: ('SubSecTime', ), 0x9291: ('SubSecTimeOriginal', ), 0x9292: ('SubSecTimeDigitized', ), # used by Windows Explorer 0x9C9B: ('XPTitle', ), 0x9C9C: ('XPComment', ), 0x9C9D: ('XPAuthor', make_string), # (ignored by Windows Explorer if Artist exists) 0x9C9E: ('XPKeywords', ), 0x9C9F: ('XPSubject', ), 0xA000: ('FlashPixVersion', make_string), 0xA001: ('ColorSpace', { 1: 'sRGB', 2: 'Adobe RGB', 65535: 'Uncalibrated' }), 0xA002: ('ExifImageWidth', ), 0xA003: ('ExifImageLength', ), 0xA004: ('RelatedSoundFile', ), 0xA005: ('InteroperabilityOffset', INTEROP_INFO), 0xA20B: ('FlashEnergy', ), # 0x920B in TIFF/EP 0xA20C: ('SpatialFrequencyResponse', ), # 0x920C 0xA20E: ('FocalPlaneXResolution', ), # 0x920E 0xA20F: ('FocalPlaneYResolution', ), # 0x920F 0xA210: ('FocalPlaneResolutionUnit', ), # 0x9210 0xA214: ('SubjectLocation', ), # 0x9214 0xA215: ('ExposureIndex', ), # 0x9215 0xA217: ('SensingMethod', { # 0x9217 1: 'Not defined', 2: 'One-chip color area', 3: 'Two-chip color area', 4: 'Three-chip color area', 5: 'Color sequential area', 7: 'Trilinear', 8: 'Color sequential linear' }), 0xA300: ('FileSource', { 1: 'Film Scanner', 2: 'Reflection Print Scanner', 3: 'Digital Camera' }), 0xA301: ('SceneType', { 1: 'Directly Photographed' }), 0xA302: ('CVAPattern', ), 0xA401: ('CustomRendered', { 0: 'Normal', 1: 'Custom' }), 0xA402: ('ExposureMode', { 0: 'Auto Exposure', 1: 'Manual Exposure', 2: 'Auto Bracket' }), 0xA403: ('WhiteBalance', { 0: 'Auto', 1: 'Manual' }), 0xA404: ('DigitalZoomRatio', ), 0xA405: ('FocalLengthIn35mmFilm', ), 0xA406: ('SceneCaptureType', { 0: 'Standard', 1: 'Landscape', 2: 'Portrait', 3: 'Night' }), 0xA407: ('GainControl', { 0: 'None', 1: 'Low gain up', 2: 'High gain up', 3: 'Low gain down', 4: 'High gain down' }), 0xA408: ('Contrast', { 0: 'Normal', 1: 'Soft', 2: 'Hard' }), 0xA409: ('Saturation', { 0: 'Normal', 1: 'Soft', 2: 'Hard' }), 0xA40A: ('Sharpness', { 0: 'Normal', 1: 'Soft', 2: 'Hard' }), 0xA40B: ('DeviceSettingDescription', ), 0xA40C: ('SubjectDistanceRange', ), 0xA420: ('ImageUniqueID', ), 0xA430: ('CameraOwnerName', ), 0xA431: ('BodySerialNumber', ), 0xA432: ('LensSpecification', ), 0xA433: ('LensMake', ), 0xA434: ('LensModel', ), 0xA435: ('LensSerialNumber', ), 0xA500: ('Gamma', ), 0xC4A5: ('PrintIM', ), 0xEA1C: ('Padding', ), 0xEA1D: ('OffsetSchema', ), 0xFDE8: ('OwnerName', ), 0xFDE9: ('SerialNumber', ), } exif-py-2.2.0/exifread/tags/makernote/000077500000000000000000000000001351613376600176215ustar00rootroot00000000000000exif-py-2.2.0/exifread/tags/makernote/__init__.py000066400000000000000000000002411351613376600217270ustar00rootroot00000000000000""" Makernote tag definitions. """ from . import apple from . import canon from . import casio from . import fujifilm from . import nikon from . import olympus exif-py-2.2.0/exifread/tags/makernote/apple.py000066400000000000000000000004161351613376600212750ustar00rootroot00000000000000""" Makernote (proprietary) tag definitions for Apple iOS Based on version 1.01 of ExifTool -> Image/ExifTool/Apple.pm http://owl.phy.queensu.ca/~phil/exiftool/ """ TAGS = { 0x000a: ('HDRImageType', { 3: 'HDR Image', 4: 'Original Image', }), } exif-py-2.2.0/exifread/tags/makernote/canon.py000066400000000000000000000545731351613376600213070ustar00rootroot00000000000000""" Makernote (proprietary) tag definitions for Canon. http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html """ TAGS = { 0x0003: ('FlashInfo',), 0x0006: ('ImageType', ), 0x0007: ('FirmwareVersion', ), 0x0008: ('ImageNumber', ), 0x0009: ('OwnerName', ), 0x000c: ('SerialNumber', ), 0x000e: ('FileLength', ), 0x0010: ('ModelID', { 0x1010000: 'PowerShot A30', 0x1040000: 'PowerShot S300 / Digital IXUS 300 / IXY Digital 300', 0x1060000: 'PowerShot A20', 0x1080000: 'PowerShot A10', 0x1090000: 'PowerShot S110 / Digital IXUS v / IXY Digital 200', 0x1100000: 'PowerShot G2', 0x1110000: 'PowerShot S40', 0x1120000: 'PowerShot S30', 0x1130000: 'PowerShot A40', 0x1140000: 'EOS D30', 0x1150000: 'PowerShot A100', 0x1160000: 'PowerShot S200 / Digital IXUS v2 / IXY Digital 200a', 0x1170000: 'PowerShot A200', 0x1180000: 'PowerShot S330 / Digital IXUS 330 / IXY Digital 300a', 0x1190000: 'PowerShot G3', 0x1210000: 'PowerShot S45', 0x1230000: 'PowerShot SD100 / Digital IXUS II / IXY Digital 30', 0x1240000: 'PowerShot S230 / Digital IXUS v3 / IXY Digital 320', 0x1250000: 'PowerShot A70', 0x1260000: 'PowerShot A60', 0x1270000: 'PowerShot S400 / Digital IXUS 400 / IXY Digital 400', 0x1290000: 'PowerShot G5', 0x1300000: 'PowerShot A300', 0x1310000: 'PowerShot S50', 0x1340000: 'PowerShot A80', 0x1350000: 'PowerShot SD10 / Digital IXUS i / IXY Digital L', 0x1360000: 'PowerShot S1 IS', 0x1370000: 'PowerShot Pro1', 0x1380000: 'PowerShot S70', 0x1390000: 'PowerShot S60', 0x1400000: 'PowerShot G6', 0x1410000: 'PowerShot S500 / Digital IXUS 500 / IXY Digital 500', 0x1420000: 'PowerShot A75', 0x1440000: 'PowerShot SD110 / Digital IXUS IIs / IXY Digital 30a', 0x1450000: 'PowerShot A400', 0x1470000: 'PowerShot A310', 0x1490000: 'PowerShot A85', 0x1520000: 'PowerShot S410 / Digital IXUS 430 / IXY Digital 450', 0x1530000: 'PowerShot A95', 0x1540000: 'PowerShot SD300 / Digital IXUS 40 / IXY Digital 50', 0x1550000: 'PowerShot SD200 / Digital IXUS 30 / IXY Digital 40', 0x1560000: 'PowerShot A520', 0x1570000: 'PowerShot A510', 0x1590000: 'PowerShot SD20 / Digital IXUS i5 / IXY Digital L2', 0x1640000: 'PowerShot S2 IS', 0x1650000: 'PowerShot SD430 / Digital IXUS Wireless / IXY Digital Wireless', 0x1660000: 'PowerShot SD500 / Digital IXUS 700 / IXY Digital 600', 0x1668000: 'EOS D60', 0x1700000: 'PowerShot SD30 / Digital IXUS i Zoom / IXY Digital L3', 0x1740000: 'PowerShot A430', 0x1750000: 'PowerShot A410', 0x1760000: 'PowerShot S80', 0x1780000: 'PowerShot A620', 0x1790000: 'PowerShot A610', 0x1800000: 'PowerShot SD630 / Digital IXUS 65 / IXY Digital 80', 0x1810000: 'PowerShot SD450 / Digital IXUS 55 / IXY Digital 60', 0x1820000: 'PowerShot TX1', 0x1870000: 'PowerShot SD400 / Digital IXUS 50 / IXY Digital 55', 0x1880000: 'PowerShot A420', 0x1890000: 'PowerShot SD900 / Digital IXUS 900 Ti / IXY Digital 1000', 0x1900000: 'PowerShot SD550 / Digital IXUS 750 / IXY Digital 700', 0x1920000: 'PowerShot A700', 0x1940000: 'PowerShot SD700 IS / Digital IXUS 800 IS / IXY Digital 800 IS', 0x1950000: 'PowerShot S3 IS', 0x1960000: 'PowerShot A540', 0x1970000: 'PowerShot SD600 / Digital IXUS 60 / IXY Digital 70', 0x1980000: 'PowerShot G7', 0x1990000: 'PowerShot A530', 0x2000000: 'PowerShot SD800 IS / Digital IXUS 850 IS / IXY Digital 900 IS', 0x2010000: 'PowerShot SD40 / Digital IXUS i7 / IXY Digital L4', 0x2020000: 'PowerShot A710 IS', 0x2030000: 'PowerShot A640', 0x2040000: 'PowerShot A630', 0x2090000: 'PowerShot S5 IS', 0x2100000: 'PowerShot A460', 0x2120000: 'PowerShot SD850 IS / Digital IXUS 950 IS / IXY Digital 810 IS', 0x2130000: 'PowerShot A570 IS', 0x2140000: 'PowerShot A560', 0x2150000: 'PowerShot SD750 / Digital IXUS 75 / IXY Digital 90', 0x2160000: 'PowerShot SD1000 / Digital IXUS 70 / IXY Digital 10', 0x2180000: 'PowerShot A550', 0x2190000: 'PowerShot A450', 0x2230000: 'PowerShot G9', 0x2240000: 'PowerShot A650 IS', 0x2260000: 'PowerShot A720 IS', 0x2290000: 'PowerShot SX100 IS', 0x2300000: 'PowerShot SD950 IS / Digital IXUS 960 IS / IXY Digital 2000 IS', 0x2310000: 'PowerShot SD870 IS / Digital IXUS 860 IS / IXY Digital 910 IS', 0x2320000: 'PowerShot SD890 IS / Digital IXUS 970 IS / IXY Digital 820 IS', 0x2360000: 'PowerShot SD790 IS / Digital IXUS 90 IS / IXY Digital 95 IS', 0x2370000: 'PowerShot SD770 IS / Digital IXUS 85 IS / IXY Digital 25 IS', 0x2380000: 'PowerShot A590 IS', 0x2390000: 'PowerShot A580', 0x2420000: 'PowerShot A470', 0x2430000: 'PowerShot SD1100 IS / Digital IXUS 80 IS / IXY Digital 20 IS', 0x2460000: 'PowerShot SX1 IS', 0x2470000: 'PowerShot SX10 IS', 0x2480000: 'PowerShot A1000 IS', 0x2490000: 'PowerShot G10', 0x2510000: 'PowerShot A2000 IS', 0x2520000: 'PowerShot SX110 IS', 0x2530000: 'PowerShot SD990 IS / Digital IXUS 980 IS / IXY Digital 3000 IS', 0x2540000: 'PowerShot SD880 IS / Digital IXUS 870 IS / IXY Digital 920 IS', 0x2550000: 'PowerShot E1', 0x2560000: 'PowerShot D10', 0x2570000: 'PowerShot SD960 IS / Digital IXUS 110 IS / IXY Digital 510 IS', 0x2580000: 'PowerShot A2100 IS', 0x2590000: 'PowerShot A480', 0x2600000: 'PowerShot SX200 IS', 0x2610000: 'PowerShot SD970 IS / Digital IXUS 990 IS / IXY Digital 830 IS', 0x2620000: 'PowerShot SD780 IS / Digital IXUS 100 IS / IXY Digital 210 IS', 0x2630000: 'PowerShot A1100 IS', 0x2640000: 'PowerShot SD1200 IS / Digital IXUS 95 IS / IXY Digital 110 IS', 0x2700000: 'PowerShot G11', 0x2710000: 'PowerShot SX120 IS', 0x2720000: 'PowerShot S90', 0x2750000: 'PowerShot SX20 IS', 0x2760000: 'PowerShot SD980 IS / Digital IXUS 200 IS / IXY Digital 930 IS', 0x2770000: 'PowerShot SD940 IS / Digital IXUS 120 IS / IXY Digital 220 IS', 0x2800000: 'PowerShot A495', 0x2810000: 'PowerShot A490', 0x2820000: 'PowerShot A3100 IS / A3150 IS', 0x2830000: 'PowerShot A3000 IS', 0x2840000: 'PowerShot SD1400 IS / IXUS 130 / IXY 400F', 0x2850000: 'PowerShot SD1300 IS / IXUS 105 / IXY 200F', 0x2860000: 'PowerShot SD3500 IS / IXUS 210 / IXY 10S', 0x2870000: 'PowerShot SX210 IS', 0x2880000: 'PowerShot SD4000 IS / IXUS 300 HS / IXY 30S', 0x2890000: 'PowerShot SD4500 IS / IXUS 1000 HS / IXY 50S', 0x2920000: 'PowerShot G12', 0x2930000: 'PowerShot SX30 IS', 0x2940000: 'PowerShot SX130 IS', 0x2950000: 'PowerShot S95', 0x2980000: 'PowerShot A3300 IS', 0x2990000: 'PowerShot A3200 IS', 0x3000000: 'PowerShot ELPH 500 HS / IXUS 310 HS / IXY 31S', 0x3010000: 'PowerShot Pro90 IS', 0x3010001: 'PowerShot A800', 0x3020000: 'PowerShot ELPH 100 HS / IXUS 115 HS / IXY 210F', 0x3030000: 'PowerShot SX230 HS', 0x3040000: 'PowerShot ELPH 300 HS / IXUS 220 HS / IXY 410F', 0x3050000: 'PowerShot A2200', 0x3060000: 'PowerShot A1200', 0x3070000: 'PowerShot SX220 HS', 0x3080000: 'PowerShot G1 X', 0x3090000: 'PowerShot SX150 IS', 0x3100000: 'PowerShot ELPH 510 HS / IXUS 1100 HS / IXY 51S', 0x3110000: 'PowerShot S100 (new)', 0x3130000: 'PowerShot SX40 HS', 0x3120000: 'PowerShot ELPH 310 HS / IXUS 230 HS / IXY 600F', 0x3160000: 'PowerShot A1300', 0x3170000: 'PowerShot A810', 0x3180000: 'PowerShot ELPH 320 HS / IXUS 240 HS / IXY 420F', 0x3190000: 'PowerShot ELPH 110 HS / IXUS 125 HS / IXY 220F', 0x3200000: 'PowerShot D20', 0x3210000: 'PowerShot A4000 IS', 0x3220000: 'PowerShot SX260 HS', 0x3230000: 'PowerShot SX240 HS', 0x3240000: 'PowerShot ELPH 530 HS / IXUS 510 HS / IXY 1', 0x3250000: 'PowerShot ELPH 520 HS / IXUS 500 HS / IXY 3', 0x3260000: 'PowerShot A3400 IS', 0x3270000: 'PowerShot A2400 IS', 0x3280000: 'PowerShot A2300', 0x3330000: 'PowerShot G15', 0x3340000: 'PowerShot SX50', 0x3350000: 'PowerShot SX160 IS', 0x3360000: 'PowerShot S110 (new)', 0x3370000: 'PowerShot SX500 IS', 0x3380000: 'PowerShot N', 0x3390000: 'IXUS 245 HS / IXY 430F', 0x3400000: 'PowerShot SX280 HS', 0x3410000: 'PowerShot SX270 HS', 0x3420000: 'PowerShot A3500 IS', 0x3430000: 'PowerShot A2600', 0x3450000: 'PowerShot A1400', 0x3460000: 'PowerShot ELPH 130 IS / IXUS 140 / IXY 110F', 0x3470000: 'PowerShot ELPH 115/120 IS / IXUS 132/135 / IXY 90F/100F', 0x3490000: 'PowerShot ELPH 330 HS / IXUS 255 HS / IXY 610F', 0x3510000: 'PowerShot A2500', 0x3540000: 'PowerShot G16', 0x3550000: 'PowerShot S120', 0x3560000: 'PowerShot SX170 IS', 0x3580000: 'PowerShot SX510 HS', 0x3590000: 'PowerShot S200 (new)', 0x3600000: 'IXY 620F', 0x3610000: 'PowerShot N100', 0x3640000: 'PowerShot G1 X Mark II', 0x3650000: 'PowerShot D30', 0x3660000: 'PowerShot SX700 HS', 0x3670000: 'PowerShot SX600 HS', 0x3680000: 'PowerShot ELPH 140 IS / IXUS 150 / IXY 130', 0x3690000: 'PowerShot ELPH 135 / IXUS 145 / IXY 120', 0x3700000: 'PowerShot ELPH 340 HS / IXUS 265 HS / IXY 630', 0x3710000: 'PowerShot ELPH 150 IS / IXUS 155 / IXY 140', 0x3740000: 'EOS M3', 0x3750000: 'PowerShot SX60 HS', 0x3760000: 'PowerShot SX520 HS', 0x3770000: 'PowerShot SX400 IS', 0x3780000: 'PowerShot G7 X', 0x3790000: 'PowerShot N2', 0x3800000: 'PowerShot SX530 HS', 0x3820000: 'PowerShot SX710 HS', 0x3830000: 'PowerShot SX610 HS', 0x3870000: 'PowerShot ELPH 160 / IXUS 160', 0x3890000: 'PowerShot ELPH 170 IS / IXUS 170', 0x3910000: 'PowerShot SX410 IS', 0x4040000: 'PowerShot G1', 0x6040000: 'PowerShot S100 / Digital IXUS / IXY Digital', 0x4007d673: 'DC19/DC21/DC22', 0x4007d674: 'XH A1', 0x4007d675: 'HV10', 0x4007d676: 'MD130/MD140/MD150/MD160/ZR850', 0x4007d777: 'DC50', 0x4007d778: 'HV20', 0x4007d779: 'DC211', 0x4007d77a: 'HG10', 0x4007d77b: 'HR10', 0x4007d77d: 'MD255/ZR950', 0x4007d81c: 'HF11', 0x4007d878: 'HV30', 0x4007d87c: 'XH A1S', 0x4007d87e: 'DC301/DC310/DC311/DC320/DC330', 0x4007d87f: 'FS100', 0x4007d880: 'HF10', 0x4007d882: 'HG20/HG21', 0x4007d925: 'HF21', 0x4007d926: 'HF S11', 0x4007d978: 'HV40', 0x4007d987: 'DC410/DC411/DC420', 0x4007d988: 'FS19/FS20/FS21/FS22/FS200', 0x4007d989: 'HF20/HF200', 0x4007d98a: 'HF S10/S100', 0x4007da8e: 'HF R10/R16/R17/R18/R100/R106', 0x4007da8f: 'HF M30/M31/M36/M300/M306', 0x4007da90: 'HF S20/S21/S200', 0x4007da92: 'FS31/FS36/FS37/FS300/FS305/FS306/FS307', 0x4007dda9: 'HF G25', 0x80000001: 'EOS-1D', 0x80000167: 'EOS-1DS', 0x80000168: 'EOS 10D', 0x80000169: 'EOS-1D Mark III', 0x80000170: 'EOS Digital Rebel / 300D / Kiss Digital', 0x80000174: 'EOS-1D Mark II', 0x80000175: 'EOS 20D', 0x80000176: 'EOS Digital Rebel XSi / 450D / Kiss X2', 0x80000188: 'EOS-1Ds Mark II', 0x80000189: 'EOS Digital Rebel XT / 350D / Kiss Digital N', 0x80000190: 'EOS 40D', 0x80000213: 'EOS 5D', 0x80000215: 'EOS-1Ds Mark III', 0x80000218: 'EOS 5D Mark II', 0x80000219: 'WFT-E1', 0x80000232: 'EOS-1D Mark II N', 0x80000234: 'EOS 30D', 0x80000236: 'EOS Digital Rebel XTi / 400D / Kiss Digital X', 0x80000241: 'WFT-E2', 0x80000246: 'WFT-E3', 0x80000250: 'EOS 7D', 0x80000252: 'EOS Rebel T1i / 500D / Kiss X3', 0x80000254: 'EOS Rebel XS / 1000D / Kiss F', 0x80000261: 'EOS 50D', 0x80000269: 'EOS-1D X', 0x80000270: 'EOS Rebel T2i / 550D / Kiss X4', 0x80000271: 'WFT-E4', 0x80000273: 'WFT-E5', 0x80000281: 'EOS-1D Mark IV', 0x80000285: 'EOS 5D Mark III', 0x80000286: 'EOS Rebel T3i / 600D / Kiss X5', 0x80000287: 'EOS 60D', 0x80000288: 'EOS Rebel T3 / 1100D / Kiss X50', 0x80000289: 'EOS 7D Mark II', 0x80000297: 'WFT-E2 II', 0x80000298: 'WFT-E4 II', 0x80000301: 'EOS Rebel T4i / 650D / Kiss X6i', 0x80000302: 'EOS 6D', 0x80000324: 'EOS-1D C', 0x80000325: 'EOS 70D', 0x80000326: 'EOS Rebel T5i / 700D / Kiss X7i', 0x80000327: 'EOS Rebel T5 / 1200D / Kiss X70', 0x80000331: 'EOS M', 0x80000355: 'EOS M2', 0x80000346: 'EOS Rebel SL1 / 100D / Kiss X7', 0x80000347: 'EOS Rebel T6s / 760D / 8000D', 0x80000382: 'EOS 5DS', 0x80000393: 'EOS Rebel T6i / 750D / Kiss X8i', 0x80000401: 'EOS 5DS R', }), 0x0013: ('ThumbnailImageValidArea', ), 0x0015: ('SerialNumberFormat', { 0x90000000: 'Format 1', 0xA0000000: 'Format 2' }), 0x001a: ('SuperMacro', { 0: 'Off', 1: 'On (1)', 2: 'On (2)' }), 0x001c: ('DateStampMode', { 0: 'Off', 1: 'Date', 2: 'Date & Time', }), 0x001e: ('FirmwareRevision', ), 0x0028: ('ImageUniqueID', ), 0x0095: ('LensModel', ), 0x0096: ('InternalSerialNumber ', ), 0x0097: ('DustRemovalData ', ), 0x0098: ('CropInfo ', ), 0x009a: ('AspectInfo', ), 0x00b4: ('ColorSpace', { 1: 'sRGB', 2: 'Adobe RGB' }), } # this is in element offset, name, optional value dictionary format # 0x0001 CAMERA_SETTINGS = { 1: ('Macromode', { 1: 'Macro', 2: 'Normal' }), 2: ('SelfTimer', ), 3: ('Quality', { 1: 'Economy', 2: 'Normal', 3: 'Fine', 5: 'Superfine' }), 4: ('FlashMode', { 0: 'Flash Not Fired', 1: 'Auto', 2: 'On', 3: 'Red-Eye Reduction', 4: 'Slow Synchro', 5: 'Auto + Red-Eye Reduction', 6: 'On + Red-Eye Reduction', 16: 'external flash' }), 5: ('ContinuousDriveMode', { 0: 'Single Or Timer', 1: 'Continuous', 2: 'Movie', }), 7: ('FocusMode', { 0: 'One-Shot', 1: 'AI Servo', 2: 'AI Focus', 3: 'MF', 4: 'Single', 5: 'Continuous', 6: 'MF' }), 9: ('RecordMode', { 1: 'JPEG', 2: 'CRW+THM', 3: 'AVI+THM', 4: 'TIF', 5: 'TIF+JPEG', 6: 'CR2', 7: 'CR2+JPEG', 9: 'Video' }), 10: ('ImageSize', { 0: 'Large', 1: 'Medium', 2: 'Small' }), 11: ('EasyShootingMode', { 0: 'Full Auto', 1: 'Manual', 2: 'Landscape', 3: 'Fast Shutter', 4: 'Slow Shutter', 5: 'Night', 6: 'B&W', 7: 'Sepia', 8: 'Portrait', 9: 'Sports', 10: 'Macro/Close-Up', 11: 'Pan Focus', 51: 'High Dynamic Range', }), 12: ('DigitalZoom', { 0: 'None', 1: '2x', 2: '4x', 3: 'Other' }), 13: ('Contrast', { 0xFFFF: 'Low', 0: 'Normal', 1: 'High' }), 14: ('Saturation', { 0xFFFF: 'Low', 0: 'Normal', 1: 'High' }), 15: ('Sharpness', { 0xFFFF: 'Low', 0: 'Normal', 1: 'High' }), 16: ('ISO', { 0: 'See ISOSpeedRatings Tag', 15: 'Auto', 16: '50', 17: '100', 18: '200', 19: '400' }), 17: ('MeteringMode', { 0: 'Default', 1: 'Spot', 2: 'Average', 3: 'Evaluative', 4: 'Partial', 5: 'Center-weighted' }), 18: ('FocusType', { 0: 'Manual', 1: 'Auto', 3: 'Close-Up (Macro)', 8: 'Locked (Pan Mode)' }), 19: ('AFPointSelected', { 0x3000: 'None (MF)', 0x3001: 'Auto-Selected', 0x3002: 'Right', 0x3003: 'Center', 0x3004: 'Left' }), 20: ('ExposureMode', { 0: 'Easy Shooting', 1: 'Program', 2: 'Tv-priority', 3: 'Av-priority', 4: 'Manual', 5: 'A-DEP' }), 22: ('LensType', ), 23: ('LongFocalLengthOfLensInFocalUnits', ), 24: ('ShortFocalLengthOfLensInFocalUnits', ), 25: ('FocalUnitsPerMM', ), 28: ('FlashActivity', { 0: 'Did Not Fire', 1: 'Fired' }), 29: ('FlashDetails', { 0: 'Manual', 1: 'TTL', 2: 'A-TTL', 3: 'E-TTL', 4: 'FP Sync Enabled', 7: '2nd("Rear")-Curtain Sync Used', 11: 'FP Sync Used', 13: 'Internal Flash', 14: 'External E-TTL' }), 32: ('FocusMode', { 0: 'Single', 1: 'Continuous', 8: 'Manual' }), 33: ('AESetting', { 0: 'Normal AE', 1: 'Exposure Compensation', 2: 'AE Lock', 3: 'AE Lock + Exposure Comp.', 4: 'No AE' }), 34: ('ImageStabilization', { 0: 'Off', 1: 'On', 2: 'Shoot Only', 3: 'Panning', 4: 'Dynamic', 256: 'Off', 257: 'On', 258: 'Shoot Only', 259: 'Panning', 260: 'Dynamic' }), 39: ('SpotMeteringMode', { 0: 'Center', 1: 'AF Point' }), 41: ('ManualFlashOutput', { 0x0: 'n/a', 0x500: 'Full', 0x502: 'Medium', 0x504: 'Low', 0x7fff: 'n/a' }), } # 0x0002 FOCAL_LENGTH = { 1: ('FocalType', { 1: 'Fixed', 2: 'Zoom', }), 2: ('FocalLength', ), } # 0x0004 SHOT_INFO = { 7: ('WhiteBalance', { 0: 'Auto', 1: 'Sunny', 2: 'Cloudy', 3: 'Tungsten', 4: 'Fluorescent', 5: 'Flash', 6: 'Custom' }), 8: ('SlowShutter', { -1: 'n/a', 0: 'Off', 1: 'Night Scene', 2: 'On', 3: 'None' }), 9: ('SequenceNumber', ), 14: ('AFPointUsed', ), 15: ('FlashBias', { 0xFFC0: '-2 EV', 0xFFCC: '-1.67 EV', 0xFFD0: '-1.50 EV', 0xFFD4: '-1.33 EV', 0xFFE0: '-1 EV', 0xFFEC: '-0.67 EV', 0xFFF0: '-0.50 EV', 0xFFF4: '-0.33 EV', 0x0000: '0 EV', 0x000c: '0.33 EV', 0x0010: '0.50 EV', 0x0014: '0.67 EV', 0x0020: '1 EV', 0x002c: '1.33 EV', 0x0030: '1.50 EV', 0x0034: '1.67 EV', 0x0040: '2 EV' }), 19: ('SubjectDistance', ), } # 0x0026 AF_INFO_2 = { 2: ('AFAreaMode', { 0: 'Off (Manual Focus)', 2: 'Single-point AF', 4: 'Multi-point AF or AI AF', 5: 'Face Detect AF', 6: 'Face + Tracking', 7: 'Zone AF', 8: 'AF Point Expansion', 9: 'Spot AF', 11: 'Flexizone Multi', 13: 'Flexizone Single', }), 3: ('NumAFPoints', ), 4: ('ValidAFPoints', ), 5: ('CanonImageWidth', ), } # 0x0093 FILE_INFO = { 1: ('FileNumber', ), 3: ('BracketMode', { 0: 'Off', 1: 'AEB', 2: 'FEB', 3: 'ISO', 4: 'WB', }), 4: ('BracketValue', ), 5: ('BracketShotNumber', ), 6: ('RawJpgQuality', { 0xFFFF: 'n/a', 1: 'Economy', 2: 'Normal', 3: 'Fine', 4: 'RAW', 5: 'Superfine', 130: 'Normal Movie' }), 7: ('RawJpgSize', { 0: 'Large', 1: 'Medium', 2: 'Small', 5: 'Medium 1', 6: 'Medium 2', 7: 'Medium 3', 8: 'Postcard', 9: 'Widescreen', 10: 'Medium Widescreen', 14: 'Small 1', 15: 'Small 2', 16: 'Small 3', 128: '640x480 Movie', 129: 'Medium Movie', 130: 'Small Movie', 137: '1280x720 Movie', 142: '1920x1080 Movie', }), 8: ('LongExposureNoiseReduction2', { 0: 'Off', 1: 'On (1D)', 2: 'On', 3: 'Auto' }), 9: ('WBBracketMode', { 0: 'Off', 1: 'On (shift AB)', 2: 'On (shift GM)' }), 12: ('WBBracketValueAB', ), 13: ('WBBracketValueGM', ), 14: ('FilterEffect', { 0: 'None', 1: 'Yellow', 2: 'Orange', 3: 'Red', 4: 'Green' }), 15: ('ToningEffect', { 0: 'None', 1: 'Sepia', 2: 'Blue', 3: 'Purple', 4: 'Green', }), 16: ('MacroMagnification', ), 19: ('LiveViewShooting', { 0: 'Off', 1: 'On' }), 25: ('FlashExposureLock', { 0: 'Off', 1: 'On' }) } def add_one(value): return value + 1 def subtract_one(value): return value - 1 def convert_temp(value): return '%d C' % (value - 128) # CameraInfo data structures have variable sized members. Each entry here is: # byte offset: (item name, data item type, decoding map). # Note that the data item type is fed directly to struct.unpack at the # specified offset. CAMERA_INFO_TAG_NAME = 'MakerNote Tag 0x000D' CAMERA_INFO_5D = { 23: ('CameraTemperature', ' 127: a = 256 - a ret_str = '-' else: ret_str = '+' step = seq[2] # Assume third value means the step size whole = a / step a = a % step if whole != 0: ret_str = '%s%s ' % (ret_str, str(whole)) if a == 0: ret_str += 'EV' else: r = Ratio(a, step) ret_str = ret_str + r.__repr__() + ' EV' return ret_str # Nikon E99x MakerNote Tags TAGS_NEW = { 0x0001: ('MakernoteVersion', make_string), # Sometimes binary 0x0002: ('ISOSetting', make_string), 0x0003: ('ColorMode', ), 0x0004: ('Quality', ), 0x0005: ('Whitebalance', ), 0x0006: ('ImageSharpening', ), 0x0007: ('FocusMode', ), 0x0008: ('FlashSetting', ), 0x0009: ('AutoFlashMode', ), 0x000B: ('WhiteBalanceBias', ), 0x000C: ('WhiteBalanceRBCoeff', ), 0x000D: ('ProgramShift', ev_bias), # Nearly the same as the other EV vals, but step size is 1/12 EV (?) 0x000E: ('ExposureDifference', ev_bias), 0x000F: ('ISOSelection', ), 0x0010: ('DataDump', ), 0x0011: ('NikonPreview', ), 0x0012: ('FlashCompensation', ev_bias), 0x0013: ('ISOSpeedRequested', ), 0x0016: ('PhotoCornerCoordinates', ), 0x0017: ('ExternalFlashExposureComp', ev_bias), 0x0018: ('FlashBracketCompensationApplied', ev_bias), 0x0019: ('AEBracketCompensationApplied', ), 0x001A: ('ImageProcessing', ), 0x001B: ('CropHiSpeed', ), 0x001C: ('ExposureTuning', ), 0x001D: ('SerialNumber', ), # Conflict with 0x00A0 ? 0x001E: ('ColorSpace', ), 0x001F: ('VRInfo', ), 0x0020: ('ImageAuthentication', ), 0x0022: ('ActiveDLighting', ), 0x0023: ('PictureControl', ), 0x0024: ('WorldTime', ), 0x0025: ('ISOInfo', ), 0x0080: ('ImageAdjustment', ), 0x0081: ('ToneCompensation', ), 0x0082: ('AuxiliaryLens', ), 0x0083: ('LensType', ), 0x0084: ('LensMinMaxFocalMaxAperture', ), 0x0085: ('ManualFocusDistance', ), 0x0086: ('DigitalZoomFactor', ), 0x0087: ('FlashMode', { 0x00: 'Did Not Fire', 0x01: 'Fired, Manual', 0x07: 'Fired, External', 0x08: 'Fired, Commander Mode ', 0x09: 'Fired, TTL Mode', }), 0x0088: ('AFFocusPosition', { 0x0000: 'Center', 0x0100: 'Top', 0x0200: 'Bottom', 0x0300: 'Left', 0x0400: 'Right', }), 0x0089: ('BracketingMode', { 0x00: 'Single frame, no bracketing', 0x01: 'Continuous, no bracketing', 0x02: 'Timer, no bracketing', 0x10: 'Single frame, exposure bracketing', 0x11: 'Continuous, exposure bracketing', 0x12: 'Timer, exposure bracketing', 0x40: 'Single frame, white balance bracketing', 0x41: 'Continuous, white balance bracketing', 0x42: 'Timer, white balance bracketing' }), 0x008A: ('AutoBracketRelease', ), 0x008B: ('LensFStops', ), 0x008C: ('NEFCurve1', ), # ExifTool calls this 'ContrastCurve' 0x008D: ('ColorMode', ), 0x008F: ('SceneMode', ), 0x0090: ('LightingType', ), 0x0091: ('ShotInfo', ), # First 4 bytes are a version number in ASCII 0x0092: ('HueAdjustment', ), # ExifTool calls this 'NEFCompression', should be 1-4 0x0093: ('Compression', ), 0x0094: ('Saturation', { -3: 'B&W', -2: '-2', -1: '-1', 0: '0', 1: '1', 2: '2', }), 0x0095: ('NoiseReduction', ), 0x0096: ('NEFCurve2', ), # ExifTool calls this 'LinearizationTable' 0x0097: ('ColorBalance', ), # First 4 bytes are a version number in ASCII 0x0098: ('LensData', ), # First 4 bytes are a version number in ASCII 0x0099: ('RawImageCenter', ), 0x009A: ('SensorPixelSize', ), 0x009C: ('Scene Assist', ), 0x009E: ('RetouchHistory', ), 0x00A0: ('SerialNumber', ), 0x00A2: ('ImageDataSize', ), # 00A3: unknown - a single byte 0 # 00A4: In NEF, looks like a 4 byte ASCII version number ('0200') 0x00A5: ('ImageCount', ), 0x00A6: ('DeletedImageCount', ), 0x00A7: ('TotalShutterReleases', ), # First 4 bytes are a version number in ASCII, with version specific # info to follow. Its hard to treat it as a string due to embedded nulls. 0x00A8: ('FlashInfo', ), 0x00A9: ('ImageOptimization', ), 0x00AA: ('Saturation', ), 0x00AB: ('DigitalVariProgram', ), 0x00AC: ('ImageStabilization', ), 0x00AD: ('AFResponse', ), 0x00B0: ('MultiExposure', ), 0x00B1: ('HighISONoiseReduction', ), 0x00B6: ('PowerUpTime', ), 0x00B7: ('AFInfo2', ), 0x00B8: ('FileInfo', ), 0x00B9: ('AFTune', ), 0x0100: ('DigitalICE', ), 0x0103: ('PreviewCompression', { 1: 'Uncompressed', 2: 'CCITT 1D', 3: 'T4/Group 3 Fax', 4: 'T6/Group 4 Fax', 5: 'LZW', 6: 'JPEG (old-style)', 7: 'JPEG', 8: 'Adobe Deflate', 9: 'JBIG B&W', 10: 'JBIG Color', 32766: 'Next', 32769: 'Epson ERF Compressed', 32771: 'CCIRLEW', 32773: 'PackBits', 32809: 'Thunderscan', 32895: 'IT8CTPAD', 32896: 'IT8LW', 32897: 'IT8MP', 32898: 'IT8BL', 32908: 'PixarFilm', 32909: 'PixarLog', 32946: 'Deflate', 32947: 'DCS', 34661: 'JBIG', 34676: 'SGILog', 34677: 'SGILog24', 34712: 'JPEG 2000', 34713: 'Nikon NEF Compressed', 65000: 'Kodak DCR Compressed', 65535: 'Pentax PEF Compressed', }), 0x0201: ('PreviewImageStart', ), 0x0202: ('PreviewImageLength', ), 0x0213: ('PreviewYCbCrPositioning', { 1: 'Centered', 2: 'Co-sited', }), 0x0E09: ('NikonCaptureVersion', ), 0x0E0E: ('NikonCaptureOffsets', ), 0x0E10: ('NikonScan', ), 0x0E22: ('NEFBitDepth', ), } TAGS_OLD = { 0x0003: ('Quality', { 1: 'VGA Basic', 2: 'VGA Normal', 3: 'VGA Fine', 4: 'SXGA Basic', 5: 'SXGA Normal', 6: 'SXGA Fine', }), 0x0004: ('ColorMode', { 1: 'Color', 2: 'Monochrome', }), 0x0005: ('ImageAdjustment', { 0: 'Normal', 1: 'Bright+', 2: 'Bright-', 3: 'Contrast+', 4: 'Contrast-', }), 0x0006: ('CCDSpeed', { 0: 'ISO 80', 2: 'ISO 160', 4: 'ISO 320', 5: 'ISO 100', }), 0x0007: ('WhiteBalance', { 0: 'Auto', 1: 'Preset', 2: 'Daylight', 3: 'Incandescent', 4: 'Fluorescent', 5: 'Cloudy', 6: 'Speed Light', }), } exif-py-2.2.0/exifread/tags/makernote/olympus.py000066400000000000000000000171461351613376600217140ustar00rootroot00000000000000 from ...utils import make_string def special_mode(v): """decode Olympus SpecialMode tag in MakerNote""" mode1 = { 0: 'Normal', 1: 'Unknown', 2: 'Fast', 3: 'Panorama', } mode2 = { 0: 'Non-panoramic', 1: 'Left to right', 2: 'Right to left', 3: 'Bottom to top', 4: 'Top to bottom', } if not v or (v[0] not in mode1 or v[2] not in mode2): return v return '%s - sequence %d - %s' % (mode1[v[0]], v[1], mode2[v[2]]) TAGS = { # ah HAH! those sneeeeeaky bastids! this is how they get past the fact # that a JPEG thumbnail is not allowed in an uncompressed TIFF file 0x0100: ('JPEGThumbnail', ), 0x0200: ('SpecialMode', special_mode), 0x0201: ('JPEGQual', { 1: 'SQ', 2: 'HQ', 3: 'SHQ', }), 0x0202: ('Macro', { 0: 'Normal', 1: 'Macro', 2: 'SuperMacro' }), 0x0203: ('BWMode', { 0: 'Off', 1: 'On' }), 0x0204: ('DigitalZoom', ), 0x0205: ('FocalPlaneDiagonal', ), 0x0206: ('LensDistortionParams', ), 0x0207: ('SoftwareRelease', ), 0x0208: ('PictureInfo', ), 0x0209: ('CameraID', make_string), # print as string 0x0F00: ('DataDump', ), 0x0300: ('PreCaptureFrames', ), 0x0404: ('SerialNumber', ), 0x1000: ('ShutterSpeedValue', ), 0x1001: ('ISOValue', ), 0x1002: ('ApertureValue', ), 0x1003: ('BrightnessValue', ), 0x1004: ('FlashMode', { 2: 'On', 3: 'Off' }), 0x1005: ('FlashDevice', { 0: 'None', 1: 'Internal', 4: 'External', 5: 'Internal + External' }), 0x1006: ('ExposureCompensation', ), 0x1007: ('SensorTemperature', ), 0x1008: ('LensTemperature', ), 0x100b: ('FocusMode', { 0: 'Auto', 1: 'Manual' }), 0x1017: ('RedBalance', ), 0x1018: ('BlueBalance', ), 0x101a: ('SerialNumber', ), 0x1023: ('FlashExposureComp', ), 0x1026: ('ExternalFlashBounce', { 0: 'No', 1: 'Yes' }), 0x1027: ('ExternalFlashZoom', ), 0x1028: ('ExternalFlashMode', ), 0x1029: ('Contrast int16u', { 0: 'High', 1: 'Normal', 2: 'Low' }), 0x102a: ('SharpnessFactor', ), 0x102b: ('ColorControl', ), 0x102c: ('ValidBits', ), 0x102d: ('CoringFilter', ), 0x102e: ('OlympusImageWidth', ), 0x102f: ('OlympusImageHeight', ), 0x1034: ('CompressionRatio', ), 0x1035: ('PreviewImageValid', { 0: 'No', 1: 'Yes' }), 0x1036: ('PreviewImageStart', ), 0x1037: ('PreviewImageLength', ), 0x1039: ('CCDScanMode', { 0: 'Interlaced', 1: 'Progressive' }), 0x103a: ('NoiseReduction', { 0: 'Off', 1: 'On' }), 0x103b: ('InfinityLensStep', ), 0x103c: ('NearLensStep', ), # TODO - these need extra definitions # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html 0x2010: ('Equipment', ), 0x2020: ('CameraSettings', ), 0x2030: ('RawDevelopment', ), 0x2040: ('ImageProcessing', ), 0x2050: ('FocusInfo', ), 0x3000: ('RawInfo ', ), } # 0x2020 CameraSettings TAG_0x2020 = { 0x0100: ('PreviewImageValid', { 0: 'No', 1: 'Yes' }), 0x0101: ('PreviewImageStart', ), 0x0102: ('PreviewImageLength', ), 0x0200: ('ExposureMode', { 1: 'Manual', 2: 'Program', 3: 'Aperture-priority AE', 4: 'Shutter speed priority AE', 5: 'Program-shift' }), 0x0201: ('AELock', { 0: 'Off', 1: 'On' }), 0x0202: ('MeteringMode', { 2: 'Center Weighted', 3: 'Spot', 5: 'ESP', 261: 'Pattern+AF', 515: 'Spot+Highlight control', 1027: 'Spot+Shadow control' }), 0x0300: ('MacroMode', { 0: 'Off', 1: 'On' }), 0x0301: ('FocusMode', { 0: 'Single AF', 1: 'Sequential shooting AF', 2: 'Continuous AF', 3: 'Multi AF', 10: 'MF' }), 0x0302: ('FocusProcess', { 0: 'AF Not Used', 1: 'AF Used' }), 0x0303: ('AFSearch', { 0: 'Not Ready', 1: 'Ready' }), 0x0304: ('AFAreas', ), 0x0401: ('FlashExposureCompensation', ), 0x0500: ('WhiteBalance2', { 0: 'Auto', 16: '7500K (Fine Weather with Shade)', 17: '6000K (Cloudy)', 18: '5300K (Fine Weather)', 20: '3000K (Tungsten light)', 21: '3600K (Tungsten light-like)', 33: '6600K (Daylight fluorescent)', 34: '4500K (Neutral white fluorescent)', 35: '4000K (Cool white fluorescent)', 48: '3600K (Tungsten light-like)', 256: 'Custom WB 1', 257: 'Custom WB 2', 258: 'Custom WB 3', 259: 'Custom WB 4', 512: 'Custom WB 5400K', 513: 'Custom WB 2900K', 514: 'Custom WB 8000K', }), 0x0501: ('WhiteBalanceTemperature', ), 0x0502: ('WhiteBalanceBracket', ), 0x0503: ('CustomSaturation', ), # (3 numbers: 1. CS Value, 2. Min, 3. Max) 0x0504: ('ModifiedSaturation', { 0: 'Off', 1: 'CM1 (Red Enhance)', 2: 'CM2 (Green Enhance)', 3: 'CM3 (Blue Enhance)', 4: 'CM4 (Skin Tones)', }), 0x0505: ('ContrastSetting', ), # (3 numbers: 1. Contrast, 2. Min, 3. Max) 0x0506: ('SharpnessSetting', ), # (3 numbers: 1. Sharpness, 2. Min, 3. Max) 0x0507: ('ColorSpace', { 0: 'sRGB', 1: 'Adobe RGB', 2: 'Pro Photo RGB' }), 0x0509: ('SceneMode', { 0: 'Standard', 6: 'Auto', 7: 'Sport', 8: 'Portrait', 9: 'Landscape+Portrait', 10: 'Landscape', 11: 'Night scene', 13: 'Panorama', 16: 'Landscape+Portrait', 17: 'Night+Portrait', 19: 'Fireworks', 20: 'Sunset', 22: 'Macro', 25: 'Documents', 26: 'Museum', 28: 'Beach&Snow', 30: 'Candle', 35: 'Underwater Wide1', 36: 'Underwater Macro', 39: 'High Key', 40: 'Digital Image Stabilization', 44: 'Underwater Wide2', 45: 'Low Key', 46: 'Children', 48: 'Nature Macro', }), 0x050a: ('NoiseReduction', { 0: 'Off', 1: 'Noise Reduction', 2: 'Noise Filter', 3: 'Noise Reduction + Noise Filter', 4: 'Noise Filter (ISO Boost)', 5: 'Noise Reduction + Noise Filter (ISO Boost)' }), 0x050b: ('DistortionCorrection', { 0: 'Off', 1: 'On' }), 0x050c: ('ShadingCompensation', { 0: 'Off', 1: 'On' }), 0x050d: ('CompressionFactor', ), 0x050f: ('Gradation', { '-1 -1 1': 'Low Key', '0 -1 1': 'Normal', '1 -1 1': 'High Key' }), 0x0520: ('PictureMode', { 1: 'Vivid', 2: 'Natural', 3: 'Muted', 256: 'Monotone', 512: 'Sepia' }), 0x0521: ('PictureModeSaturation', ), 0x0522: ('PictureModeHue?', ), 0x0523: ('PictureModeContrast', ), 0x0524: ('PictureModeSharpness', ), 0x0525: ('PictureModeBWFilter', { 0: 'n/a', 1: 'Neutral', 2: 'Yellow', 3: 'Orange', 4: 'Red', 5: 'Green' }), 0x0526: ('PictureModeTone', { 0: 'n/a', 1: 'Neutral', 2: 'Sepia', 3: 'Blue', 4: 'Purple', 5: 'Green' }), 0x0600: ('Sequence', ), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits 0x0601: ('PanoramaMode', ), # (2 numbers: 1. Mode, 2. Shot number) 0x0603: ('ImageQuality2', { 1: 'SQ', 2: 'HQ', 3: 'SHQ', 4: 'RAW', }), 0x0901: ('ManometerReading', ), } exif-py-2.2.0/exifread/utils.py000066400000000000000000000050161351613376600164120ustar00rootroot00000000000000""" Misc utilities. """ from fractions import Fraction def ord_(dta): if isinstance(dta, str): return ord(dta) return dta def make_string(seq): """ Don't throw an exception when given an out of range character. """ string = '' for c in seq: # Screen out non-printing characters try: if 32 <= c and c < 256: string += chr(c) except TypeError: pass # If no printing chars if not string: return str(seq) return string def make_string_uc(seq): """ Special version to deal with the code in the first 8 bytes of a user comment. First 8 bytes gives coding system e.g. ASCII vs. JIS vs Unicode. """ seq = seq[8:] # Of course, this is only correct if ASCII, and the standard explicitly # allows JIS and Unicode. return make_string(seq) def get_gps_coords(tags): lng_ref_tag_name = "GPS GPSLongitudeRef" lng_tag_name = "GPS GPSLongitude" lat_ref_tag_name = "GPS GPSLatitudeRef" lat_tag_name = "GPS GPSLatitude" # Check if these tags are present gps_tags = [lng_ref_tag_name,lng_tag_name,lat_tag_name,lat_tag_name] for tag in gps_tags: if not tag in tags.keys(): return None lng_ref_val = tags[lng_ref_tag_name].values lng_coord_val = [c.decimal() for c in tags[lng_tag_name].values] lat_ref_val = tags[lat_ref_tag_name].values lat_coord_val = [c.decimal() for c in tags[lat_tag_name].values] lng_coord = sum([c/60**i for i,c in enumerate(lng_coord_val)]) lng_coord *= (-1)**(lng_ref_val=="W") lat_coord = sum([c/60**i for i,c in enumerate(lat_coord_val)]) lat_coord *= (-1)**(lat_ref_val=="S") return (lat_coord, lng_coord) class Ratio(Fraction): """ Ratio object that eventually will be able to reduce itself to lowest common denominator for printing. """ # We're immutable, so use __new__ not __init__ def __new__(cls, numerator=0, denominator=None): try: self = super(Ratio, cls).__new__(cls, numerator, denominator) except ZeroDivisionError: self = super(Ratio, cls).__new__(cls) self._numerator = numerator self._denominator = denominator return self __new__.doc = Fraction.__new__.__doc__ def __repr__(self): return str(self) @property def num(self): return self.numerator @property def den(self): return self.denominator def decimal(self): return float(self) exif-py-2.2.0/setup.py000066400000000000000000000021141351613376600146170ustar00rootroot00000000000000# -*- coding: utf-8 -*- from setuptools import setup, find_packages import exifread readme_file = open("README.rst", "rt").read() setup( name="ExifRead", version=exifread.__version__, author="Ianaré Sévi", author_email="ianare@gmail.com", packages=find_packages(), scripts=["EXIF.py"], url="https://github.com/ianare/exif-py", license="BSD", keywords="exif image metadata photo", description=" ".join(exifread.__doc__.splitlines()).strip(), long_description=readme_file, classifiers=( "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Utilities", ), )