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