Merge our EXIF.py with upstream version 1.0.2
authorJoel Rosdahl <joel@rosdahl.net>
Tue, 24 Jul 2007 19:32:44 +0000 (21:32 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Tue, 24 Jul 2007 19:32:44 +0000 (21:32 +0200)
src/packages/kofoto/EXIF.py

index 16e29f5..0fa74ca 100644 (file)
@@ -1,18 +1,44 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
 # pylint: disable-msg=W0311, W0622, C0103, W0402, C0322, W0141, C0302
 # pylint: disable-msg=R0913, W0621, W0612, R0914, W0102, W0702, R0912, R0915
 # pylint: disable-msg=W0702
 
 # Library to extract EXIF information in digital camera image files
 #
 # pylint: disable-msg=W0311, W0622, C0103, W0402, C0322, W0141, C0302
 # pylint: disable-msg=R0913, W0621, W0612, R0914, W0102, W0702, R0912, R0915
 # pylint: disable-msg=W0702
 
 # Library to extract EXIF information in digital camera image files
 #
+# To use this library call with:
+#    f=open(path_name, 'rb')
+#    tags=EXIF.process_file(f)
+#
+# If you want to ignore makerNote, pass the -q or --quick
+# command line arguments, or as
+#    tags=EXIF.process_file(f, details=False)
+#
+# This is usefull when you are retrieving a large list of images
+#
+# 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])
+# (This code uses the if statement 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.
+#
 # Contains code from "exifdump.py" originally written by Thierry Bousch
 # <bousch@topo.math.u-psud.fr> and released into the public domain.
 #
 # Updated and turned into general-purpose library by Gene Cash
 # Contains code from "exifdump.py" originally written by Thierry Bousch
 # <bousch@topo.math.u-psud.fr> and released into the public domain.
 #
 # Updated and turned into general-purpose library by Gene Cash
-# <email gcash at cfl.rr.com>
 #
 #
-# This copyright license is intended to be similar to the FreeBSD license. 
+# This copyright license is intended to be similar to the FreeBSD license.
 #
 #
-# Copyright 2002 Gene Cash All rights reserved. 
+# Copyright (c) 2002 Gene Cash All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions are
 # This means you may do anything you want with this code, except claim you
 # wrote it. Also, if it breaks you get to keep both pieces.
 #
 # This means you may do anything you want with this code, except claim you
 # wrote it. Also, if it breaks you get to keep both pieces.
 #
+# Patch Contributors:
+# * Simon J. Gerraty <sjg@crufty.net>
+#   s2n fix & orientation decode
+# * John T. Riedl <riedl@cs.umn.edu>
+#   Added support for newer Nikon type 3 Makernote format for D70 and some
+#   other Nikon cameras.
+# * Joerg Schaefer <schaeferj@gmx.net>
+#   Fixed subtle bug when faking an EXIF header, which affected maker notes
+#   using relative offsets, and a fix for Nikon D100.
+#
 # 21-AUG-99 TB  Last update by Thierry Bousch to his code.
 # 17-JAN-02 CEC Discovered code on web.
 #               Commented everything.
 # 21-AUG-99 TB  Last update by Thierry Bousch to his code.
 # 17-JAN-02 CEC Discovered code on web.
 #               Commented everything.
 #               IFD_Tag() object.
 # 15-FEB-04 CEC Finally fixed bit shift warning by converting Y to 0L.
 #
 #               IFD_Tag() object.
 # 15-FEB-04 CEC Finally fixed bit shift warning by converting Y to 0L.
 #
-# To do:
-# * Better printing of ratios
+# ---------------------------- End original notices ------------------------- #
+
+# 2007-18-01 - Ianaré Sévi <ianare@gmail.com>
+# Fixed a couple errors and assuming maintenance of the library.
+
+# 2007-24-03 - Ianaré Sévi
+# Can now ignore MakerNotes Tags for faster processing.
+
+# 2007-03-05 - Martin Stone <mj_stone@users.sourceforge.net>
+# Fix for inverted detailed flag and Photoshop header
+
 
 
-import string
-import struct
+
+
+
+# ===== CODE START ==== #
 
 # field type descriptions as (length, abbreviation, full name) tuples
 FIELD_TYPES=(
 
 # 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'),
+    (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, 'SB', 'Signed Byte'),
-    (1, 'U',  'Undefined'),
+    (1, 'U', 'Undefined'),
     (2, 'SS', 'Signed Short'),
     (4, 'SL', 'Signed Long'),
     (8, 'SR', 'Signed Ratio')
     (2, 'SS', 'Signed Short'),
     (4, 'SL', 'Signed Long'),
     (8, 'SR', 'Signed Ratio')
@@ -103,7 +150,15 @@ EXIF_TAGS={
     0x010F: ('Make', ),
     0x0110: ('Model', ),
     0x0111: ('StripOffsets', ),
     0x010F: ('Make', ),
     0x0110: ('Model', ),
     0x0111: ('StripOffsets', ),
-    0x0112: ('Orientation', ),
+    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', ),
     0x0115: ('SamplesPerPixel', ),
     0x0116: ('RowsPerStrip', ),
     0x0117: ('StripByteCounts', ),
@@ -227,6 +282,7 @@ EXIF_TAGS={
              {3: 'Digital Camera'}),
     0xA301: ('SceneType',
              {1: 'Directly Photographed'}),
              {3: 'Digital Camera'}),
     0xA301: ('SceneType',
              {1: 'Directly Photographed'}),
+    0xA302: ('CVAPattern',),
     }
 
 # interoperability tags
     }
 
 # interoperability tags
@@ -269,6 +325,12 @@ GPS_TAGS={
     0x001A: ('GPSDestDistance', )
     }
 
     0x001A: ('GPSDestDistance', )
     }
 
+# ignore these tags when quick processing
+# 0x927C is MakerNote Tags
+# 0x9286 is user comment
+IGNORE_TAGS=(0x9286, 0x927C)
+
+
 # Nikon E99x MakerNote Tags
 # http://members.tripod.com/~tawba/990exif.htm
 MAKERNOTE_NIKON_NEWER_TAGS={
 # Nikon E99x MakerNote Tags
 # http://members.tripod.com/~tawba/990exif.htm
 MAKERNOTE_NIKON_NEWER_TAGS={
@@ -279,9 +341,20 @@ MAKERNOTE_NIKON_NEWER_TAGS={
     0x0006: ('ImageSharpening', ),
     0x0007: ('FocusMode', ),
     0x0008: ('FlashSetting', ),
     0x0006: ('ImageSharpening', ),
     0x0007: ('FocusMode', ),
     0x0008: ('FlashSetting', ),
+    0x0009: ('AutoFlashMode', ),
+    0x000B: ('WhiteBalanceBias', ),
+    0x000C: ('WhiteBalanceRBCoeff', ),
     0x000F: ('ISOSelection', ),
     0x000F: ('ISOSelection', ),
+    0x0012: ('FlashCompensation', ),
+    0x0013: ('ISOSpeedRequested', ),
+    0x0016: ('PhotoCornerCoordinates', ),
+    0x0018: ('FlashBracketCompensationApplied', ),
+    0x0019: ('AEBracketCompensationApplied', ),
     0x0080: ('ImageAdjustment', ),
     0x0080: ('ImageAdjustment', ),
+    0x0081: ('ToneCompensation', ),
     0x0082: ('AuxiliaryLens', ),
     0x0082: ('AuxiliaryLens', ),
+    0x0083: ('LensType', ),
+    0x0084: ('LensMinMaxFocalMaxAperture', ),
     0x0085: ('ManualFocusDistance', ),
     0x0086: ('DigitalZoomFactor', ),
     0x0088: ('AFFocusPosition',
     0x0085: ('ManualFocusDistance', ),
     0x0086: ('DigitalZoomFactor', ),
     0x0088: ('AFFocusPosition',
@@ -290,6 +363,20 @@ MAKERNOTE_NIKON_NEWER_TAGS={
               0x0200: 'Bottom',
               0x0300: 'Left',
               0x0400: 'Right'}),
               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'}),
+    0x008D: ('ColorMode', ),
+    0x008F: ('SceneMode?', ),
+    0x0090: ('LightingType', ),
+    0x0092: ('HueAdjustment', ),
     0x0094: ('Saturation',
              {-3: 'B&W',
               -2: '-2',
     0x0094: ('Saturation',
              {-3: 'B&W',
               -2: '-2',
@@ -298,6 +385,10 @@ MAKERNOTE_NIKON_NEWER_TAGS={
               1:  '1',
               2:  '2'}),
     0x0095: ('NoiseReduction', ),
               1:  '1',
               2:  '2'}),
     0x0095: ('NoiseReduction', ),
+    0x00A7: ('TotalShutterReleases', ),
+    0x00A9: ('ImageOptimization', ),
+    0x00AA: ('Saturation', ),
+    0x00AB: ('DigitalVariProgram', ),
     0x0010: ('DataDump', )
     }
 
     0x0010: ('DataDump', )
     }
 
@@ -346,12 +437,8 @@ def olympus_special_mode(v):
         2: 'Right to left',
         3: 'Bottom to top',
         4: 'Top to bottom'}
         2: 'Right to left',
         3: 'Bottom to top',
         4: 'Top to bottom'}
-    # handle broken Olympus MakerNote
-    try:
-        return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
-    except KeyError:
-        return ''
-
+    return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
+        
 MAKERNOTE_OLYMPUS_TAGS={
     # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
     # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
 MAKERNOTE_OLYMPUS_TAGS={
     # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
     # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
@@ -368,7 +455,7 @@ MAKERNOTE_OLYMPUS_TAGS={
     0x0207: ('SoftwareRelease',  ),
     0x0208: ('PictureInfo',  ),
     # print as string
     0x0207: ('SoftwareRelease',  ),
     0x0208: ('PictureInfo',  ),
     # print as string
-    0x0209: ('CameraID', lambda x: ''.join(map(chr, x))), 
+    0x0209: ('CameraID', lambda x: ''.join(map(chr, x))),
     0x0F00: ('DataDump',  )
     }
 
     0x0F00: ('DataDump',  )
     }
 
@@ -636,10 +723,26 @@ MAKERNOTE_CANON_TAG_0x004={
          0X002C: '1.33 EV',
          0X0030: '1.50 EV',
          0X0034: '1.67 EV',
          0X002C: '1.33 EV',
          0X0030: '1.50 EV',
          0X0034: '1.67 EV',
-         0X0040: '2 EV'}), 
+         0X0040: '2 EV'}),
     19: ('SubjectDistance', )
     }
 
     19: ('SubjectDistance', )
     }
 
+# extract multibyte integer in Motorola format (little endian)
+def s2n_motorola(str):
+    x=0
+    for c in str:
+        x=(x << 8) | ord(c)
+    return x
+
+# extract multibyte integer in Intel format (big endian)
+def s2n_intel(str):
+    x=0
+    y=0L
+    for c in str:
+        x=x | (ord(c) << y)
+        y=y+8
+    return x
+
 # ratio object that eventually will be able to reduce itself to lowest
 # common denominator for printing
 def gcd(a, b):
 # ratio object that eventually will be able to reduce itself to lowest
 # common denominator for printing
 def gcd(a, b):
@@ -693,32 +796,31 @@ class IFD_Tag:
 
 # class that handles an EXIF header
 class EXIF_header:
 
 # class that handles an EXIF header
 class EXIF_header:
-    def __init__(self, file, endian, offset, debug=0):
+    def __init__(self, file, endian, offset, fake_exif, debug=0):
         self.file=file
         self.endian=endian
         self.offset=offset
         self.file=file
         self.endian=endian
         self.offset=offset
+        self.fake_exif=fake_exif
         self.debug=debug
         self.tags={}
         
     # convert slice to integer, based on sign and endian flags
         self.debug=debug
         self.tags={}
         
     # 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.
     def s2n(self, offset, length, signed=0):
         self.file.seek(self.offset+offset)
         slice=self.file.read(length)
         if self.endian == 'I':
     def s2n(self, offset, length, signed=0):
         self.file.seek(self.offset+offset)
         slice=self.file.read(length)
         if self.endian == 'I':
-            endian_sign='<'
-        else:
-            endian_sign='>'
-        if length == 1:
-            size_char='b'
-        elif length == 2:
-            size_char='h'
-        elif length == 4:
-            size_char='i'
+            val=s2n_intel(slice)
         else:
         else:
-            raise ValueError, ('bad slice length: %s' % length)
-        if not signed:
-            size_char=string.upper(size_char)
-        return struct.unpack(endian_sign + size_char, slice)[0]
+            val=s2n_motorola(slice)
+        # Sign extension ?
+        if signed:
+            msb=1L << (8*length-1)
+            if val & msb:
+                val=val-(msb << 1)
+        return val
 
     # convert offset to string
     def n2s(self, offset, length):
 
     # convert offset to string
     def n2s(self, offset, length):
@@ -750,74 +852,96 @@ class EXIF_header:
         return a
 
     # return list of entries in this IFD
         return a
 
     # return list of entries in this IFD
-    def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS):
+    def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0):
         entries=self.s2n(ifd, 2)
         for i in range(entries):
         entries=self.s2n(ifd, 2)
         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)
             entry=ifd+2+12*i
             tag=self.s2n(entry, 2)
-            field_type=self.s2n(entry+2, 2)
-            if not 0 < field_type < len(FIELD_TYPES):
-                # unknown field type
-                raise ValueError, \
-                      'unknown type %d in tag 0x%04X' % (field_type, tag)
-            typelen=FIELD_TYPES[field_type][0]
-            count=self.s2n(entry+4, 4)
-            offset=entry+8
-            if count*typelen > 4:
-                # not the value, it's a pointer to the value
-                offset=self.s2n(offset, 4)
-            field_offset=offset
-            if field_type == 2:
-                # special case: null-terminated ASCII string
-                if count != 0:
-                    self.file.seek(self.offset+offset)
-                    values=self.file.read(count).strip().replace('\x00','')
+            
+            #if not (tag in IGNORE_TAGS and detailed == False):
+            #    pass
+            # ignore certain tags for faster processing
+            if not (tag in IGNORE_TAGS and detailed == False):
+                # get tag name.  We do it early to make debugging easier
+                tag_entry=dict.get(tag)
+                if tag_entry:
+                    tag_name=tag_entry[0]
                 else:
                 else:
-                    values=''
-            else:
-                values=[]
-                signed=(field_type in [6, 8, 9, 10])
-                for j in range(count):
-                    if field_type in (5, 10):
-                        # a ratio
-                        value_j=Ratio(self.s2n(offset,   4, signed),
-                                      self.s2n(offset+4, 4, signed))
+                    tag_name='Tag 0x%04X' % tag
+                
+                field_type=self.s2n(entry+2, 2)
+                if not 0 < field_type < len(FIELD_TYPES):
+                    # unknown field type
+                    raise ValueError, \
+                          'unknown type %d in tag 0x%04X' % (field_type, tag)
+                typelen=FIELD_TYPES[field_type][0]
+                count=self.s2n(entry+4, 4)
+                offset=entry+8
+                if count*typelen > 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-self.offset+4
+                        if self.fake_exif:
+                            offset=offset+18
                     else:
                     else:
-                        value_j=self.s2n(offset, typelen, signed)
-                    values.append(value_j)
-                    offset=offset+typelen
-            # now "values" is either a string or an array
-            if count == 1 and field_type != 2:
-                printable=str(values[0])
-            else:
-                printable=str(values)
-            # figure out tag name
-            tag_entry=dict.get(tag)
-            if tag_entry:
-                tag_name=tag_entry[0]
-                if len(tag_entry) != 1:
-                    # optional 2nd tag element is present
-                    if callable(tag_entry[1]):
-                        # call mapping function
-                        try:
-                            printable=tag_entry[1](values)
-                        except:
-                            # Ugly work-around for now. /Joel 2005-01-27
-                            continue
+                        offset=self.s2n(offset, 4)
+                field_offset=offset
+                if field_type == 2:
+                    # special case: null-terminated ASCII string
+                    if count != 0:
+                        self.file.seek(self.offset+offset)
+                        values=self.file.read(count)
+                        values=values.strip().replace('\x00','')
                     else:
                     else:
-                        printable=''
-                        for i in values:
-                            # use LUT for this tag
-                            printable+=tag_entry[1].get(i, repr(i))
-            else:
-                tag_name='Tag 0x%04X' % (tag & 0xFFFFL)
-            self.tags[ifd_name+' '+tag_name]=IFD_Tag(printable, tag,
-                                                     field_type,
-                                                     values, field_offset,
-                                                     count*typelen)
-            if self.debug:
-                print '    %s: %s' % (tag_name,
-                                      repr(self.tags[ifd_name+' '+tag_name]))
+                        values=''
+                else:
+                    values=[]
+                    signed=(field_type in [6, 8, 9, 10])
+                    for j in range(count):
+                        if field_type in (5, 10):
+                            # a ratio
+                            value_j=Ratio(self.s2n(offset,   4, signed),
+                                          self.s2n(offset+4, 4, signed))
+                        else:
+                            value_j=self.s2n(offset, typelen, signed)
+                        values.append(value_j)
+                        offset=offset+typelen
+                # now "values" is either a string or an array
+                if count == 1 and field_type != 2:
+                    printable=str(values[0])
+                else:
+                    printable=str(values)
+                # compute printable version of values
+                if tag_entry:
+                    if len(tag_entry) != 1:
+                        # optional 2nd tag element is present
+                        if callable(tag_entry[1]):
+                            # call mapping function
+                            try:
+                                printable=tag_entry[1](values)
+                            except:
+                                # Ugly work-around for now. /Joel 2005-01-27
+                                continue
+                        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]=IFD_Tag(printable, tag,
+                                                         field_type,
+                                                         values, field_offset,
+                                                         count*typelen)
+                if self.debug:
+                    print ' debug:   %s: %s' % (tag_name,
+                                                repr(self.tags[ifd_name+' '+tag_name]))
 
     # extract uncompressed TIFF thumbnail (like pulling teeth)
     # we take advantage of the pre-existing layout in the thumbnail IFD as
 
     # extract uncompressed TIFF thumbnail (like pulling teeth)
     # we take advantage of the pre-existing layout in the thumbnail IFD as
@@ -876,6 +1000,24 @@ class EXIF_header:
         self.tags['TIFFThumbnail']=tiff
         
     # decode all the camera-specific MakerNote formats
         self.tags['TIFFThumbnail']=tiff
         
     # 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.)
     def decode_maker_note(self):
         note=self.tags['EXIF MakerNote']
         make=self.tags['Image Make'].printable
     def decode_maker_note(self):
         note=self.tags['EXIF MakerNote']
         make=self.tags['Image Make'].printable
@@ -885,13 +1027,28 @@ class EXIF_header:
             model='TAG_UNAVAILABLE'
 
         # Nikon
             model='TAG_UNAVAILABLE'
 
         # Nikon
-        if make == 'NIKON':
-            if note.values[0:5] == [78, 105, 107, 111, 110]: # "Nikon"
-                # older model
+        # 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 make in ('NIKON', 'NIKON CORPORATION'):
+            if note.values[0:7] == [78, 105, 107, 111, 110, 00, 01]:
+                if self.debug:
+                    print "Looks like a type 1 Nikon MakerNote."
                 self.dump_IFD(note.field_offset+8, 'MakerNote',
                               dict=MAKERNOTE_NIKON_OLDER_TAGS)
                 self.dump_IFD(note.field_offset+8, 'MakerNote',
                               dict=MAKERNOTE_NIKON_OLDER_TAGS)
+            elif note.values[0:7] == [78, 105, 107, 111, 110, 00, 02]:
+                if self.debug:
+                    print "Looks like a labeled type 2 Nikon MakerNote"
+                if note.values[12:14] != [0, 42] and note.values[12:14] != [42L, 0L]:
+                    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',
+                              dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1)
             else:
             else:
-                # newer model (E99x or D1)
+                # E99x or D1
+                if self.debug:
+                    print "Looks like an unlabeled type 2 Nikon MakerNote"
                 self.dump_IFD(note.field_offset, 'MakerNote',
                               dict=MAKERNOTE_NIKON_NEWER_TAGS)
             return
                 self.dump_IFD(note.field_offset, 'MakerNote',
                               dict=MAKERNOTE_NIKON_NEWER_TAGS)
             return
@@ -911,7 +1068,7 @@ class EXIF_header:
         # Fujifilm
         if make == 'FUJIFILM':
             # bug: everything else is "Motorola" endian, but the MakerNote
         # Fujifilm
         if make == 'FUJIFILM':
             # bug: everything else is "Motorola" endian, but the MakerNote
-            # is "Intel" endian 
+            # is "Intel" endian
             endian=self.endian
             self.endian='I'
             # bug: IFD offsets are from beginning of MakerNote, not
             endian=self.endian
             self.endian='I'
             # bug: IFD offsets are from beginning of MakerNote, not
@@ -954,7 +1111,14 @@ class EXIF_header:
 # 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
 # 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
-def process_file(file, debug=0):
+def process_file(file, details=True, debug=0):
+    # yah it's cheesy...
+    global detailed
+    detailed = details
+    
+    # by default do not fake an EXIF beginning
+    fake_exif=0
+    
     # determine whether it's a JPEG or TIFF
     data=file.read(12)
     if data[0:4] in ['II*\x00', 'MM\x00*']:
     # determine whether it's a JPEG or TIFF
     data=file.read(12)
     if data[0:4] in ['II*\x00', 'MM\x00*']:
@@ -965,12 +1129,12 @@ def process_file(file, debug=0):
         offset=0
     elif data[0:2] == '\xFF\xD8':
         # it's a JPEG file
         offset=0
     elif data[0:2] == '\xFF\xD8':
         # it's a JPEG file
-        # skip JFIF style header(s)
-        while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM'):
+        while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'):
             length=ord(data[4])*256+ord(data[5])
             file.read(length-8)
             # fake an EXIF beginning of file
             data='\xFF\x00'+file.read(10)
             length=ord(data[4])*256+ord(data[5])
             file.read(length-8)
             # fake an EXIF beginning of file
             data='\xFF\x00'+file.read(10)
+            fake_exif=1
         if data[2] == '\xFF' and data[6:10] == 'Exif':
             # detected EXIF header
             offset=file.tell()
         if data[2] == '\xFF' and data[6:10] == 'Exif':
             # detected EXIF header
             offset=file.tell()
@@ -985,7 +1149,7 @@ def process_file(file, debug=0):
     # deal with the EXIF info we found
     if debug:
         print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format'
     # deal with the EXIF info we found
     if debug:
         print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format'
-    hdr=EXIF_header(file, endian, offset, debug)
+    hdr=EXIF_header(file, endian, offset, fake_exif, debug)
     ifd_list=hdr.list_IFDs()
     ctr=0
     for i in ifd_list:
     ifd_list=hdr.list_IFDs()
     ctr=0
     for i in ifd_list:
@@ -1034,7 +1198,7 @@ def process_file(file, debug=0):
         hdr.tags['JPEGThumbnail']=file.read(size)
         
     # deal with MakerNote contained in EXIF IFD
         hdr.tags['JPEGThumbnail']=file.read(size)
         
     # deal with MakerNote contained in EXIF IFD
-    if hdr.tags.has_key('EXIF MakerNote'):
+    if hdr.tags.has_key('EXIF MakerNote') and detailed:
         hdr.decode_maker_note()
 
     # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
         hdr.decode_maker_note()
 
     # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
@@ -1047,15 +1211,23 @@ def process_file(file, debug=0):
             
     return hdr.tags
 
             
     return hdr.tags
 
+
 # library test/debug function (dump given files)
 if __name__ == '__main__':
     import sys
     
     if len(sys.argv) < 2:
 # library test/debug function (dump given files)
 if __name__ == '__main__':
     import sys
     
     if len(sys.argv) < 2:
-        print 'Usage: %s files...\n' % sys.argv[0]
+        print "Usage: %s [options] file1 [file2 ...]\n\nOptions:\n-q --quick : do not process MakerNotes for faster processing\n" % sys.argv[0]
         sys.exit(0)
         sys.exit(0)
-        
-    for filename in sys.argv[1:]:
+    
+    if sys.argv[1] == "-q" or sys.argv[1] == "--quick":
+        fileNamesStart = 2
+        detailed = False
+    else:
+        fileNamesStart = 1
+        detailed = True
+    
+    for filename in sys.argv[fileNamesStart:]:
         try:
             file=open(filename, 'rb')
         except:
         try:
             file=open(filename, 'rb')
         except:
@@ -1063,8 +1235,8 @@ if __name__ == '__main__':
             print
             continue
         print filename+':'
             print
             continue
         print filename+':'
-        # data=process_file(file, 1) # with debug info
-        data=process_file(file)
+        # data=process_file(file, debug=1) # with debug info and all tags
+        data=process_file(file, details=detailed)
         if not data:
             print 'No EXIF information found'
             continue
         if not data:
             print 'No EXIF information found'
             continue
@@ -1082,3 +1254,4 @@ if __name__ == '__main__':
         if data.has_key('JPEGThumbnail'):
             print 'File has JPEG thumbnail'
         print
         if data.has_key('JPEGThumbnail'):
             print 'File has JPEG thumbnail'
         print
+