1 # Library to extract EXIF information in digital camera image files
3 # To use this library call with:
4 # f=open(path_name, 'rb')
5 # tags=EXIF.process_file(f)
6 # tags will now be a dictionary mapping names of EXIF tags to their
7 # values in the file named by path_name. You can process the tags
8 # as you wish. In particular, you can iterate through all the tags with:
9 # for tag in tags.keys():
10 # if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename',
12 # print "Key: %s, value %s" % (tag, tags[tag])
13 # (This code uses the if statement to avoid printing out a few of the
14 # tags that tend to be long or boring.)
16 # The tags dictionary will include keys for all of the usual EXIF
17 # tags, and will also include keys for Makernotes used by some
18 # cameras, for which we have a good specification.
20 # Contains code from "exifdump.py" originally written by Thierry Bousch
21 # <bousch@topo.math.u-psud.fr> and released into the public domain.
23 # Updated and turned into general-purpose library by Gene Cash
25 # This copyright license is intended to be similar to the FreeBSD license.
27 # Copyright 2002 Gene Cash All rights reserved.
29 # Redistribution and use in source and binary forms, with or without
30 # modification, are permitted provided that the following conditions are
33 # 1. Redistributions of source code must retain the above copyright
34 # notice, this list of conditions and the following disclaimer.
35 # 2. Redistributions in binary form must reproduce the above copyright
36 # notice, this list of conditions and the following disclaimer in the
37 # documentation and/or other materials provided with the
40 # THIS SOFTWARE IS PROVIDED BY GENE CASH ``AS IS'' AND ANY EXPRESS OR
41 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
42 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
43 # DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
44 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
45 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
46 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
47 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
48 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
49 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
50 # POSSIBILITY OF SUCH DAMAGE.
52 # This means you may do anything you want with this code, except claim you
53 # wrote it. Also, if it breaks you get to keep both pieces.
56 # * Simon J. Gerraty <sjg@crufty.net>
57 # s2n fix & orientation decode
58 # * John T. Riedl <riedl@cs.umn.edu>
59 # Added support for newer Nikon type 3 Makernote format for D70 and some
60 # other Nikon cameras.
61 # * Joerg Schaefer <schaeferj@gmx.net>
62 # Fixed subtle bug when faking an EXIF header, which affected maker notes
63 # using relative offsets, and a fix for Nikon D100.
65 # 21-AUG-99 TB Last update by Thierry Bousch to his code.
66 # 17-JAN-02 CEC Discovered code on web.
67 # Commented everything.
68 # Made small code improvements.
69 # Reformatted for readability.
70 # 19-JAN-02 CEC Added ability to read TIFFs and JFIF-format JPEGs.
71 # Added ability to extract JPEG formatted thumbnail.
72 # Added ability to read GPS IFD (not tested).
73 # Converted IFD data structure to dictionaries indexed by
75 # Factored into library returning dictionary of IFDs plus
77 # 20-JAN-02 CEC Added MakerNote processing logic.
78 # Added Olympus MakerNote.
79 # Converted data structure to single-level dictionary, avoiding
80 # tag name collisions by prefixing with IFD name. This makes
81 # it much easier to use.
82 # 23-JAN-02 CEC Trimmed nulls from end of string values.
83 # 25-JAN-02 CEC Discovered JPEG thumbnail in Olympus TIFF MakerNote.
84 # 26-JAN-02 CEC Added ability to extract TIFF thumbnails.
85 # Added Nikon, Fujifilm, Casio MakerNotes.
86 # 30-NOV-03 CEC Fixed problem with canon_decode_tag() not creating an
88 # 15-FEB-04 CEC Finally fixed bit shift warning by converting Y to 0L.
91 # field type descriptions as (length, abbreviation, full name) tuples
93 (0, 'X', 'Proprietary'), # no such type
99 (1, 'SB', 'Signed Byte'),
100 (1, 'U', 'Undefined'),
101 (2, 'SS', 'Signed Short'),
102 (4, 'SL', 'Signed Long'),
103 (8, 'SR', 'Signed Ratio')
106 # dictionary of main EXIF tag names
107 # first element of tuple is tag name, optional second element is
108 # another dictionary giving names to values
110 0x0100: ('ImageWidth', ),
111 0x0101: ('ImageLength', ),
112 0x0102: ('BitsPerSample', ),
113 0x0103: ('Compression',
114 {1: 'Uncompressed TIFF',
115 6: 'JPEG Compressed'}),
116 0x0106: ('PhotometricInterpretation', ),
117 0x010A: ('FillOrder', ),
118 0x010D: ('DocumentName', ),
119 0x010E: ('ImageDescription', ),
122 0x0111: ('StripOffsets', ),
123 0x0112: ('Orientation',
124 {1: 'Horizontal (normal)',
125 2: 'Mirrored horizontal',
127 4: 'Mirrored vertical',
128 5: 'Mirrored horizontal then rotated 90 CCW',
130 7: 'Mirrored horizontal then rotated 90 CW',
131 8: 'Rotated 90 CCW'}),
132 0x0115: ('SamplesPerPixel', ),
133 0x0116: ('RowsPerStrip', ),
134 0x0117: ('StripByteCounts', ),
135 0x011A: ('XResolution', ),
136 0x011B: ('YResolution', ),
137 0x011C: ('PlanarConfiguration', ),
138 0x0128: ('ResolutionUnit',
141 3: 'Pixels/Centimeter'}),
142 0x012D: ('TransferFunction', ),
143 0x0131: ('Software', ),
144 0x0132: ('DateTime', ),
145 0x013B: ('Artist', ),
146 0x013E: ('WhitePoint', ),
147 0x013F: ('PrimaryChromaticities', ),
148 0x0156: ('TransferRange', ),
149 0x0200: ('JPEGProc', ),
150 0x0201: ('JPEGInterchangeFormat', ),
151 0x0202: ('JPEGInterchangeFormatLength', ),
152 0x0211: ('YCbCrCoefficients', ),
153 0x0212: ('YCbCrSubSampling', ),
154 0x0213: ('YCbCrPositioning', ),
155 0x0214: ('ReferenceBlackWhite', ),
156 0x828D: ('CFARepeatPatternDim', ),
157 0x828E: ('CFAPattern', ),
158 0x828F: ('BatteryLevel', ),
159 0x8298: ('Copyright', ),
160 0x829A: ('ExposureTime', ),
161 0x829D: ('FNumber', ),
162 0x83BB: ('IPTC/NAA', ),
163 0x8769: ('ExifOffset', ),
164 0x8773: ('InterColorProfile', ),
165 0x8822: ('ExposureProgram',
169 3: 'Aperture Priority',
170 4: 'Shutter Priority',
171 5: 'Program Creative',
174 8: 'Landscape Mode'}),
175 0x8824: ('SpectralSensitivity', ),
176 0x8825: ('GPSInfo', ),
177 0x8827: ('ISOSpeedRatings', ),
180 0x9000: ('ExifVersion', lambda x
: ''.join(map(chr, x
))),
181 0x9003: ('DateTimeOriginal', ),
182 0x9004: ('DateTimeDigitized', ),
183 0x9101: ('ComponentsConfiguration',
191 0x9102: ('CompressedBitsPerPixel', ),
192 0x9201: ('ShutterSpeedValue', ),
193 0x9202: ('ApertureValue', ),
194 0x9203: ('BrightnessValue', ),
195 0x9204: ('ExposureBiasValue', ),
196 0x9205: ('MaxApertureValue', ),
197 0x9206: ('SubjectDistance', ),
198 0x9207: ('MeteringMode',
201 2: 'CenterWeightedAverage',
204 0x9208: ('LightSource',
210 17: 'Standard Light A',
211 18: 'Standard Light B',
212 19: 'Standard Light C',
217 0x9209: ('Flash', {0: 'No',
219 5: 'Fired (?)', # no return sensed
220 7: 'Fired (!)', # return sensed
222 13: 'Fill Fired (?)',
223 15: 'Fill Fired (!)',
227 29: 'Auto Fired (?)',
228 31: 'Auto Fired (!)',
229 32: 'Not Available'}),
230 0x920A: ('FocalLength', ),
231 0x927C: ('MakerNote', ),
233 0x9286: ('UserComment', lambda x
: ''.join(map(chr, x
))),
234 0x9290: ('SubSecTime', ),
235 0x9291: ('SubSecTimeOriginal', ),
236 0x9292: ('SubSecTimeDigitized', ),
238 0xA000: ('FlashPixVersion', lambda x
: ''.join(map(chr, x
))),
239 0xA001: ('ColorSpace', ),
240 0xA002: ('ExifImageWidth', ),
241 0xA003: ('ExifImageLength', ),
242 0xA005: ('InteroperabilityOffset', ),
243 0xA20B: ('FlashEnergy', ), # 0x920B in TIFF/EP
244 0xA20C: ('SpatialFrequencyResponse', ), # 0x920C - -
245 0xA20E: ('FocalPlaneXResolution', ), # 0x920E - -
246 0xA20F: ('FocalPlaneYResolution', ), # 0x920F - -
247 0xA210: ('FocalPlaneResolutionUnit', ), # 0x9210 - -
248 0xA214: ('SubjectLocation', ), # 0x9214 - -
249 0xA215: ('ExposureIndex', ), # 0x9215 - -
250 0xA217: ('SensingMethod', ), # 0x9217 - -
251 0xA300: ('FileSource',
252 {3: 'Digital Camera'}),
253 0xA301: ('SceneType',
254 {1: 'Directly Photographed'}),
255 0xA302: ('CVAPattern',),
258 # interoperability tags
260 0x0001: ('InteroperabilityIndex', ),
261 0x0002: ('InteroperabilityVersion', ),
262 0x1000: ('RelatedImageFileFormat', ),
263 0x1001: ('RelatedImageWidth', ),
264 0x1002: ('RelatedImageLength', ),
267 # GPS tags (not used yet, haven't seen camera with GPS)
269 0x0000: ('GPSVersionID', ),
270 0x0001: ('GPSLatitudeRef', ),
271 0x0002: ('GPSLatitude', ),
272 0x0003: ('GPSLongitudeRef', ),
273 0x0004: ('GPSLongitude', ),
274 0x0005: ('GPSAltitudeRef', ),
275 0x0006: ('GPSAltitude', ),
276 0x0007: ('GPSTimeStamp', ),
277 0x0008: ('GPSSatellites', ),
278 0x0009: ('GPSStatus', ),
279 0x000A: ('GPSMeasureMode', ),
280 0x000B: ('GPSDOP', ),
281 0x000C: ('GPSSpeedRef', ),
282 0x000D: ('GPSSpeed', ),
283 0x000E: ('GPSTrackRef', ),
284 0x000F: ('GPSTrack', ),
285 0x0010: ('GPSImgDirectionRef', ),
286 0x0011: ('GPSImgDirection', ),
287 0x0012: ('GPSMapDatum', ),
288 0x0013: ('GPSDestLatitudeRef', ),
289 0x0014: ('GPSDestLatitude', ),
290 0x0015: ('GPSDestLongitudeRef', ),
291 0x0016: ('GPSDestLongitude', ),
292 0x0017: ('GPSDestBearingRef', ),
293 0x0018: ('GPSDestBearing', ),
294 0x0019: ('GPSDestDistanceRef', ),
295 0x001A: ('GPSDestDistance', )
298 # Nikon E99x MakerNote Tags
299 # http://members.tripod.com/~tawba/990exif.htm
300 MAKERNOTE_NIKON_NEWER_TAGS
={
301 0x0002: ('ISOSetting', ),
302 0x0003: ('ColorMode', ),
303 0x0004: ('Quality', ),
304 0x0005: ('Whitebalance', ),
305 0x0006: ('ImageSharpening', ),
306 0x0007: ('FocusMode', ),
307 0x0008: ('FlashSetting', ),
308 0x0009: ('AutoFlashMode', ),
309 0x000B: ('WhiteBalanceBias', ),
310 0x000C: ('WhiteBalanceRBCoeff', ),
311 0x000F: ('ISOSelection', ),
312 0x0012: ('FlashCompensation', ),
313 0x0013: ('ISOSpeedRequested', ),
314 0x0016: ('PhotoCornerCoordinates', ),
315 0x0018: ('FlashBracketCompensationApplied', ),
316 0x0019: ('AEBracketCompensationApplied', ),
317 0x0080: ('ImageAdjustment', ),
318 0x0081: ('ToneCompensation', ),
319 0x0082: ('AuxiliaryLens', ),
320 0x0083: ('LensType', ),
321 0x0084: ('LensMinMaxFocalMaxAperture', ),
322 0x0085: ('ManualFocusDistance', ),
323 0x0086: ('DigitalZoomFactor', ),
324 0x0088: ('AFFocusPosition',
330 0x0089: ('BracketingMode',
331 {0x00: 'Single frame, no bracketing',
332 0x01: 'Continuous, no bracketing',
333 0x02: 'Timer, no bracketing',
334 0x10: 'Single frame, exposure bracketing',
335 0x11: 'Continuous, exposure bracketing',
336 0x12: 'Timer, exposure bracketing',
337 0x40: 'Single frame, white balance bracketing',
338 0x41: 'Continuous, white balance bracketing',
339 0x42: 'Timer, white balance bracketing'}),
340 0x008D: ('ColorMode', ),
341 0x008F: ('SceneMode?', ),
342 0x0090: ('LightingType', ),
343 0x0092: ('HueAdjustment', ),
344 0x0094: ('Saturation',
351 0x0095: ('NoiseReduction', ),
352 0x00A7: ('TotalShutterReleases', ),
353 0x00A9: ('ImageOptimization', ),
354 0x00AA: ('Saturation', ),
355 0x00AB: ('DigitalVariProgram', ),
356 0x0010: ('DataDump', )
359 MAKERNOTE_NIKON_OLDER_TAGS
={
367 0x0004: ('ColorMode',
370 0x0005: ('ImageAdjustment',
381 0x0007: ('WhiteBalance',
391 # decode Olympus SpecialMode tag in MakerNote
392 def olympus_special_mode(v
):
404 return '%s - sequence %d - %s' % (a
[v
[0]], v
[1], b
[v
[2]])
406 MAKERNOTE_OLYMPUS_TAGS
={
407 # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
408 # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
409 0x0100: ('JPEGThumbnail', ),
410 0x0200: ('SpecialMode', olympus_special_mode
),
418 0x0204: ('DigitalZoom', ),
419 0x0207: ('SoftwareRelease', ),
420 0x0208: ('PictureInfo', ),
422 0x0209: ('CameraID', lambda x
: ''.join(map(chr, x
))),
423 0x0F00: ('DataDump', )
426 MAKERNOTE_CASIO_TAGS
={
427 0x0001: ('RecordingMode',
428 {1: 'Single Shutter',
437 0x0003: ('FocusingMode',
442 0x0004: ('FlashMode',
446 4: 'Red Eye Reduction'}),
447 0x0005: ('FlashIntensity',
451 0x0006: ('Object Distance', ),
452 0x0007: ('WhiteBalance',
459 0x000B: ('Sharpness',
467 0x000D: ('Saturation',
480 MAKERNOTE_FUJIFILM_TAGS
={
481 0x0000: ('NoteVersion', lambda x
: ''.join(map(chr, x
))),
482 0x1000: ('Quality', ),
483 0x1001: ('Sharpness',
489 0x1002: ('WhiteBalance',
493 768: 'DaylightColor-Fluorescent',
494 769: 'DaywhiteColor-Fluorescent',
495 770: 'White-Fluorescent',
496 1024: 'Incandescent',
506 0x1010: ('FlashMode',
510 3: 'Red Eye Reduction'}),
511 0x1011: ('FlashStrength', ),
515 0x1021: ('FocusMode',
521 0x1031: ('PictureMode',
528 256: 'Aperture Priority AE',
529 512: 'Shutter Priority AE',
530 768: 'Manual Exposure'}),
531 0x1100: ('MotorOrBracket',
534 0x1300: ('BlurWarning',
537 0x1301: ('FocusWarning',
540 0x1302: ('AEWarning',
545 MAKERNOTE_CANON_TAGS
={
546 0x0006: ('ImageType', ),
547 0x0007: ('FirmwareVersion', ),
548 0x0008: ('ImageNumber', ),
549 0x0009: ('OwnerName', )
552 # see http://www.burren.cx/david/canon.html by David Burren
553 # this is in element offset, name, optional value dictionary format
554 MAKERNOTE_CANON_TAG_0x001
={
564 {0: 'Flash Not Fired',
567 3: 'Red-Eye Reduction',
569 5: 'Auto + Red-Eye Reduction',
570 6: 'On + Red-Eye Reduction',
571 16: 'external flash'}),
572 5: ('ContinuousDriveMode',
573 {0: 'Single Or Timer',
587 11: ('EasyShootingMode',
598 10: 'Macro/Close-Up',
617 {0: 'See ISOSpeedRatings Tag',
626 5: 'Center-weighted'}),
630 3: 'Close-Up (Macro)',
631 8: 'Locked (Pan Mode)'}),
632 19: ('AFPointSelected',
633 {0x3000: 'None (MF)',
634 0x3001: 'Auto-Selected',
645 23: ('LongFocalLengthOfLensInFocalUnits', ),
646 24: ('ShortFocalLengthOfLensInFocalUnits', ),
647 25: ('FocalUnitsPerMM', ),
648 28: ('FlashActivity',
652 {14: 'External E-TTL',
653 13: 'Internal Flash',
655 7: '2nd("Rear")-Curtain Sync Used',
656 4: 'FP Sync Enabled'}),
662 MAKERNOTE_CANON_TAG_0x004
={
671 9: ('SequenceNumber', ),
672 14: ('AFPointUsed', ),
691 19: ('SubjectDistance', )
694 # extract multibyte integer in Motorola format (little endian)
695 def s2n_motorola(str):
701 # extract multibyte integer in Intel format (big endian)
710 # ratio object that eventually will be able to reduce itself to lowest
711 # common denominator for printing
719 def __init__(self
, num
, den
):
727 return '%d/%d' % (self
.num
, self
.den
)
730 div
=gcd(self
.num
, self
.den
)
732 self
.num
=self
.num
/div
733 self
.den
=self
.den
/div
735 # for ease of dealing with tags
737 def __init__(self
, printable
, tag
, field_type
, values
, field_offset
,
739 # printable version of data
740 self
.printable
=printable
743 # field type as index into FIELD_TYPES
744 self
.field_type
=field_type
745 # offset of start of field in bytes from beginning of IFD
746 self
.field_offset
=field_offset
747 # length of data field in bytes
748 self
.field_length
=field_length
749 # either a string or array of data items
753 return self
.printable
756 return '(0x%04X) %s=%s @ %d' % (self
.tag
,
757 FIELD_TYPES
[self
.field_type
][2],
761 # class that handles an EXIF header
763 def __init__(self
, file, endian
, offset
, fake_exif
, debug
=0):
767 self
.fake_exif
=fake_exif
771 # convert slice to integer, based on sign and endian flags
772 # usually this offset is assumed to be relative to the beginning of the
773 # start of the EXIF information. For some cameras that use relative tags,
774 # this offset may be relative to some other starting point.
775 def s2n(self
, offset
, length
, signed
=0):
776 self
.file.seek(self
.offset
+offset
)
777 slice=self
.file.read(length
)
778 if self
.endian
== 'I':
781 val
=s2n_motorola(slice)
784 msb
=1L << (8*length
-1)
789 # convert offset to string
790 def n2s(self
, offset
, length
):
792 for i
in range(length
):
793 if self
.endian
== 'I':
794 s
=s
+chr(offset
& 0xFF)
796 s
=chr(offset
& 0xFF)+s
802 return self
.s2n(4, 4)
804 # return pointer to next IFD
805 def next_IFD(self
, ifd
):
806 entries
=self
.s2n(ifd
, 2)
807 return self
.s2n(ifd
+2+12*entries
, 4)
809 # return list of IFDs in header
818 # return list of entries in this IFD
819 def dump_IFD(self
, ifd
, ifd_name
, dict=EXIF_TAGS
, relative
=0):
820 entries
=self
.s2n(ifd
, 2)
821 for i
in range(entries
):
822 # entry is index of start of this IFD in the file
824 tag
=self
.s2n(entry
, 2)
825 # get tag name. We do it early to make debugging easier
826 tag_entry
=dict.get(tag
)
828 tag_name
=tag_entry
[0]
830 tag_name
='Tag 0x%04X' % tag
831 field_type
=self
.s2n(entry
+2, 2)
832 if not 0 < field_type
< len(FIELD_TYPES
):
835 'unknown type %d in tag 0x%04X' % (field_type
, tag
)
836 typelen
=FIELD_TYPES
[field_type
][0]
837 count
=self
.s2n(entry
+4, 4)
839 if count
*typelen
> 4:
840 # offset is not the value; it's a pointer to the value
841 # if relative we set things up so s2n will seek to the right
842 # place when it adds self.offset. Note that this 'relative'
843 # is for the Nikon type 3 makernote. Other cameras may use
844 # other relative offsets, which would have to be computed here
845 # slightly differently.
847 tmp_offset
=self
.s2n(offset
, 4)
848 offset
=tmp_offset
+ifd
-self
.offset
+4
852 offset
=self
.s2n(offset
, 4)
855 # special case: null-terminated ASCII string
857 self
.file.seek(self
.offset
+offset
)
858 values
=self
.file.read(count
)
859 values
=values
.strip().replace('\x00','')
864 signed
=(field_type
in [6, 8, 9, 10])
865 for j
in range(count
):
866 if field_type
in (5, 10):
868 value_j
=Ratio(self
.s2n(offset
, 4, signed
),
869 self
.s2n(offset
+4, 4, signed
))
871 value_j
=self
.s2n(offset
, typelen
, signed
)
872 values
.append(value_j
)
873 offset
=offset
+typelen
874 # now "values" is either a string or an array
875 if count
== 1 and field_type
!= 2:
876 printable
=str(values
[0])
878 printable
=str(values
)
879 # compute printable version of values
881 if len(tag_entry
) != 1:
882 # optional 2nd tag element is present
883 if callable(tag_entry
[1]):
884 # call mapping function
885 printable
=tag_entry
[1](values
)
889 # use lookup table for this tag
890 printable
+=tag_entry
[1].get(i
, repr(i
))
891 self
.tags
[ifd_name
+' '+tag_name
]=IFD_Tag(printable
, tag
,
893 values
, field_offset
,
896 print ' debug: %s: %s' % (tag_name
,
897 repr(self
.tags
[ifd_name
+' '+tag_name
]))
899 # extract uncompressed TIFF thumbnail (like pulling teeth)
900 # we take advantage of the pre-existing layout in the thumbnail IFD as
902 def extract_TIFF_thumbnail(self
, thumb_ifd
):
903 entries
=self
.s2n(thumb_ifd
, 2)
904 # this is header plus offset to IFD ...
905 if self
.endian
== 'M':
906 tiff
='MM\x00*\x00\x00\x00\x08'
908 tiff
='II*\x00\x08\x00\x00\x00'
909 # ... plus thumbnail IFD data plus a null "next IFD" pointer
910 self
.file.seek(self
.offset
+thumb_ifd
)
911 tiff
+=self
.file.read(entries
*12+2)+'\x00\x00\x00\x00'
913 # fix up large value offset pointers into data area
914 for i
in range(entries
):
915 entry
=thumb_ifd
+2+12*i
916 tag
=self
.s2n(entry
, 2)
917 field_type
=self
.s2n(entry
+2, 2)
918 typelen
=FIELD_TYPES
[field_type
][0]
919 count
=self
.s2n(entry
+4, 4)
920 oldoff
=self
.s2n(entry
+8, 4)
921 # start of the 4-byte pointer area in entry
923 # remember strip offsets location
926 strip_len
=count
*typelen
927 # is it in the data area?
928 if count
*typelen
> 4:
929 # update offset pointer (nasty "strings are immutable" crap)
930 # should be able to say "tiff[ptr:ptr+4]=newoff"
932 tiff
=tiff
[:ptr
]+self
.n2s(newoff
, 4)+tiff
[ptr
+4:]
933 # remember strip offsets location
937 # get original data and store it
938 self
.file.seek(self
.offset
+oldoff
)
939 tiff
+=self
.file.read(count
*typelen
)
941 # add pixel strips and update strip offset info
942 old_offsets
=self
.tags
['Thumbnail StripOffsets'].values
943 old_counts
=self
.tags
['Thumbnail StripByteCounts'].values
944 for i
in range(len(old_offsets
)):
945 # update offset pointer (more nasty "strings are immutable" crap)
946 offset
=self
.n2s(len(tiff
), strip_len
)
947 tiff
=tiff
[:strip_off
]+offset
+tiff
[strip_off
+strip_len
:]
949 # add pixel strip to end
950 self
.file.seek(self
.offset
+old_offsets
[i
])
951 tiff
+=self
.file.read(old_counts
[i
])
953 self
.tags
['TIFFThumbnail']=tiff
955 # decode all the camera-specific MakerNote formats
957 # Note is the data that comprises this MakerNote. The MakerNote will
958 # likely have pointers in it that point to other parts of the file. We'll
959 # use self.offset as the starting point for most of those pointers, since
960 # they are relative to the beginning of the file.
962 # If the MakerNote is in a newer format, it may use relative addressing
963 # within the MakerNote. In that case we'll use relative addresses for the
966 # As an aside: it's not just to be annoying that the manufacturers use
967 # relative offsets. It's so that if the makernote has to be moved by the
968 # picture software all of the offsets don't have to be adjusted. Overall,
969 # this is probably the right strategy for makernotes, though the spec is
970 # ambiguous. (The spec does not appear to imagine that makernotes would
971 # follow EXIF format internally. Once they did, it's ambiguous whether
972 # the offsets should be from the header at the start of all the EXIF info,
973 # or from the header at the start of the makernote.)
974 def decode_maker_note(self
):
975 note
=self
.tags
['EXIF MakerNote']
976 make
=self
.tags
['Image Make'].printable
977 model
=self
.tags
['Image Model'].printable
980 # The maker note usually starts with the word Nikon, followed by the
981 # type of the makernote (1 or 2, as a short). If the word Nikon is
982 # not at the start of the makernote, it's probably type 2, since some
983 # cameras work that way.
984 if make
in ('NIKON', 'NIKON CORPORATION'):
985 if note
.values
[0:7] == [78, 105, 107, 111, 110, 00, 01]:
987 print "Looks like a type 1 Nikon MakerNote."
988 self
.dump_IFD(note
.field_offset
+8, 'MakerNote',
989 dict=MAKERNOTE_NIKON_OLDER_TAGS
)
990 elif note
.values
[0:7] == [78, 105, 107, 111, 110, 00, 02]:
992 print "Looks like a labeled type 2 Nikon MakerNote"
993 if note
.values
[12:14] != [0, 42] and note
.values
[12:14] != [42L, 0L]:
994 raise ValueError, "Missing marker tag '42' in MakerNote."
995 # skip the Makernote label and the TIFF header
996 self
.dump_IFD(note
.field_offset
+10+8, 'MakerNote',
997 dict=MAKERNOTE_NIKON_NEWER_TAGS
, relative
=1)
1001 print "Looks like an unlabeled type 2 Nikon MakerNote"
1002 self
.dump_IFD(note
.field_offset
, 'MakerNote',
1003 dict=MAKERNOTE_NIKON_NEWER_TAGS
)
1007 if make
[:7] == 'OLYMPUS':
1008 self
.dump_IFD(note
.field_offset
+8, 'MakerNote',
1009 dict=MAKERNOTE_OLYMPUS_TAGS
)
1014 self
.dump_IFD(note
.field_offset
, 'MakerNote',
1015 dict=MAKERNOTE_CASIO_TAGS
)
1019 if make
== 'FUJIFILM':
1020 # bug: everything else is "Motorola" endian, but the MakerNote
1024 # bug: IFD offsets are from beginning of MakerNote, not
1025 # beginning of file header
1027 self
.offset
+=note
.field_offset
1028 # process note with bogus values (note is actually at offset 12)
1029 self
.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS
)
1030 # reset to correct values
1037 self
.dump_IFD(note
.field_offset
, 'MakerNote',
1038 dict=MAKERNOTE_CANON_TAGS
)
1039 for i
in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001
),
1040 ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004
)):
1041 self
.canon_decode_tag(self
.tags
[i
[0]].values
, i
[1])
1044 # decode Canon MakerNote tag based on offset within tag
1045 # see http://www.burren.cx/david/canon.html by David Burren
1046 def canon_decode_tag(self
, value
, dict):
1047 for i
in range(1, len(value
)):
1048 x
=dict.get(i
, ('Unknown', ))
1053 val
=x
[1].get(value
[i
], 'Unknown')
1056 # it's not a real IFD Tag but we fake one to make everybody
1057 # happy. this will have a "proprietary" type
1058 self
.tags
['MakerNote '+name
]=IFD_Tag(str(val
), None, 0, None,
1061 # process an image file (expects an open file object)
1062 # this is the function that has to deal with all the arbitrary nasty bits
1063 # of the EXIF standard
1064 def process_file(file, debug
=0):
1065 # determine whether it's a JPEG or TIFF
1067 if data
[0:4] in ['II*\x00', 'MM\x00*']:
1073 elif data
[0:2] == '\xFF\xD8':
1075 # skip JFIF style header(s)
1077 while data
[2] == '\xFF' and data
[6:10] in ('JFIF', 'JFXX', 'OLYM'):
1078 length
=ord(data
[4])*256+ord(data
[5])
1080 # fake an EXIF beginning of file
1081 data
='\xFF\x00'+file.read(10)
1083 if data
[2] == '\xFF' and data
[6:10] == 'Exif':
1084 # detected EXIF header
1088 # no EXIF information
1091 # file format not recognized
1094 # deal with the EXIF info we found
1096 print {'I': 'Intel', 'M': 'Motorola'}[endian
], 'format'
1097 hdr
=EXIF_header(file, endian
, offset
, fake_exif
, debug
)
1098 ifd_list
=hdr
.list_IFDs()
1104 IFD_name
='Thumbnail'
1107 IFD_name
='IFD %d' % ctr
1109 print ' IFD %d (%s) at offset %d:' % (ctr
, IFD_name
, i
)
1110 hdr
.dump_IFD(i
, IFD_name
)
1112 exif_off
=hdr
.tags
.get(IFD_name
+' ExifOffset')
1115 print ' EXIF SubIFD at offset %d:' % exif_off
.values
[0]
1116 hdr
.dump_IFD(exif_off
.values
[0], 'EXIF')
1117 # Interoperability IFD contained in EXIF IFD
1118 intr_off
=hdr
.tags
.get('EXIF SubIFD InteroperabilityOffset')
1121 print ' EXIF Interoperability SubSubIFD at offset %d:' \
1122 % intr_off
.values
[0]
1123 hdr
.dump_IFD(intr_off
.values
[0], 'EXIF Interoperability',
1126 gps_off
=hdr
.tags
.get(IFD_name
+' GPSInfo')
1129 print ' GPS SubIFD at offset %d:' % gps_off
.values
[0]
1130 hdr
.dump_IFD(gps_off
.values
[0], 'GPS', dict=GPS_TAGS
)
1133 # extract uncompressed TIFF thumbnail
1134 thumb
=hdr
.tags
.get('Thumbnail Compression')
1135 if thumb
and thumb
.printable
== 'Uncompressed TIFF':
1136 hdr
.extract_TIFF_thumbnail(thumb_ifd
)
1138 # JPEG thumbnail (thankfully the JPEG data is stored as a unit)
1139 thumb_off
=hdr
.tags
.get('Thumbnail JPEGInterchangeFormat')
1141 file.seek(offset
+thumb_off
.values
[0])
1142 size
=hdr
.tags
['Thumbnail JPEGInterchangeFormatLength'].values
[0]
1143 hdr
.tags
['JPEGThumbnail']=file.read(size
)
1145 # deal with MakerNote contained in EXIF IFD
1146 if hdr
.tags
.has_key('EXIF MakerNote'):
1147 hdr
.decode_maker_note()
1149 # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
1150 # since it's not allowed in a uncompressed TIFF IFD
1151 if not hdr
.tags
.has_key('JPEGThumbnail'):
1152 thumb_off
=hdr
.tags
.get('MakerNote JPEGThumbnail')
1154 file.seek(offset
+thumb_off
.values
[0])
1155 hdr
.tags
['JPEGThumbnail']=file.read(thumb_off
.field_length
)
1159 # library test/debug function (dump given files)
1160 if __name__
== '__main__':
1163 if len(sys
.argv
) < 2:
1164 print 'Usage: %s files...\n' % sys
.argv
[0]
1167 for filename
in sys
.argv
[1:]:
1169 file=open(filename
, 'rb')
1171 print filename
, 'unreadable'
1175 # data=process_file(file, 1) # with debug info
1176 data
=process_file(file)
1178 print 'No EXIF information found'
1184 if i
in ('JPEGThumbnail', 'TIFFThumbnail'):
1187 print ' %s (%s): %s' % \
1188 (i
, FIELD_TYPES
[data
[i
].field_type
][2], data
[i
].printable
)
1190 print 'error', i
, '"', data
[i
], '"'
1191 if data
.has_key('JPEGThumbnail'):
1192 print 'File has JPEG thumbnail'