62
73
# Fixed subtle bug when faking an EXIF header, which affected maker notes
63
74
# 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.
76
# 1999-08-21 TB Last update by Thierry Bousch to his code.
77
# 2002-01-17 CEC Discovered code on web.
78
# Commented everything.
79
# Made small code improvements.
80
# Reformatted for readability.
81
# 2002-01-19 CEC Added ability to read TIFFs and JFIF-format JPEGs.
82
# Added ability to extract JPEG formatted thumbnail.
83
# Added ability to read GPS IFD (not tested).
84
# Converted IFD data structure to dictionaries indexed by
86
# Factored into library returning dictionary of IFDs plus
88
# 2002-01-20 CEC Added MakerNote processing logic.
89
# Added Olympus MakerNote.
90
# Converted data structure to single-level dictionary, avoiding
91
# tag name collisions by prefixing with IFD name. This makes
92
# it much easier to use.
93
# 2002-01-23 CEC Trimmed nulls from end of string values.
94
# 2002-01-25 CEC Discovered JPEG thumbnail in Olympus TIFF MakerNote.
95
# 2002-01-26 CEC Added ability to extract TIFF thumbnails.
85
96
# Added Nikon, Fujifilm, Casio MakerNotes.
86
# 30-NOV-03 CEC Fixed problem with canon_decode_tag() not creating an
97
# 2003-11-30 CEC Fixed problem with canon_decode_tag() not creating an
87
98
# IFD_Tag() object.
88
# 15-FEB-04 CEC Finally fixed bit shift warning by converting Y to 0L.
99
# 2004-02-15 CEC Finally fixed bit shift warning by converting Y to 0L.
101
# ---------------------------- End original notices ------------------------- #
103
# 2006-08-04 MoinMoin:ReimarBauer
104
# added an optional parameter name to process_file and dump_IFD. Using this parameter the
105
# loop is breaked after that tag_name is processed.
107
# f = open(infile, 'rb')
108
# tags = EXIF.process_file(f,'DateTimeOriginal')
112
# 2007-01-18 - Ianaré Sévi <ianare@gmail.com>
113
# Fixed a couple errors and assuming maintenance of the library.
115
# 2007-03-24 - Ianaré Sévi
116
# Can now ignore MakerNotes Tags for faster processing.
118
# 2007-05-03 - Martin Stone <mj_stone@users.sourceforge.net>
119
# Fix for inverted detailed flag and Photoshop header
121
# 2007-09-22 - Stephen H. Olson
122
# Don't error on invalid string
123
# Improved Nikon MakerNote support
125
# 2007-09-26 - Stephen H. Olson
126
# Don't error on invalid Olympus 'SpecialMode'.
127
# Add a few more Olympus/Minolta tags.
129
# 2007-09-27 - Ianaré Sévi
130
# Add Olympus Makernote tags.
133
# ===== CODE START ==== #
135
# Don't throw an exception when given an out of range character.
137
# lambda x: ''.join(map(chr, x))
139
def make_string(seq):
142
# Screen out non-printing characters
143
if 32 <= c and c < 256:
145
# If no printing chars
150
# Special version to deal with the code in the first 8 bytes of a user comment.
151
def make_string_uc(seq):
154
# Of course, this is only correct if ASCII, and the standard explicitly
155
# allows JIS and Unicode.
156
return make_string(seq)
91
158
# field type descriptions as (length, abbreviation, full name) tuples
93
(0, 'X', 'Proprietary'), # no such type
160
(0, 'X', 'Proprietary'), # no such type
99
166
(1, 'SB', 'Signed Byte'),
100
(1, 'U', 'Undefined'),
167
(1, 'U', 'Undefined'),
101
168
(2, 'SS', 'Signed Short'),
102
169
(4, 'SL', 'Signed Long'),
103
(8, 'SR', 'Signed Ratio')
170
(8, 'SR', 'Signed Ratio'),
106
173
# dictionary of main EXIF tag names
107
174
# first element of tuple is tag name, optional second element is
108
175
# another dictionary giving names to values
110
177
0x0100: ('ImageWidth', ),
111
178
0x0101: ('ImageLength', ),
112
179
0x0102: ('BitsPerSample', ),
292
376
0x0017: ('GPSDestBearingRef', ),
293
377
0x0018: ('GPSDestBearing', ),
294
378
0x0019: ('GPSDestDistanceRef', ),
295
0x001A: ('GPSDestDistance', )
379
0x001A: ('GPSDestDistance', ),
382
# ignore these tags when quick processing
383
# 0x927C is MakerNote Tags
384
# 0x9286 is user comment
385
IGNORE_TAGS=(0x9286, 0x927C)
387
# http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp
388
def nikon_ev_bias(seq):
389
# First digit seems to be in steps of 1/6 EV.
390
# Does the third value mean the step size? It is usually 6,
391
# but it is 12 for the ExposureDifference.
393
if seq == [252, 1, 6, 0]:
395
if seq == [253, 1, 6, 0]:
397
if seq == [254, 1, 6, 0]:
399
if seq == [0, 1, 6, 0]:
401
if seq == [2, 1, 6, 0]:
403
if seq == [3, 1, 6, 0]:
405
if seq == [4, 1, 6, 0]:
407
# Handle combinations not in the table.
409
# Causes headaches for the +/- logic, so special case it.
417
b = seq[2] # Assume third value means the step size
421
ret_str = ret_str + str(whole) + " "
423
ret_str = ret_str + "EV"
426
ret_str = ret_str + r.__repr__() + " EV"
298
429
# Nikon E99x MakerNote Tags
299
# http://members.tripod.com/~tawba/990exif.htm
300
430
MAKERNOTE_NIKON_NEWER_TAGS={
431
0x0001: ('MakernoteVersion', make_string), # Sometimes binary
301
432
0x0002: ('ISOSetting', ),
302
433
0x0003: ('ColorMode', ),
303
434
0x0004: ('Quality', ),
308
439
0x0009: ('AutoFlashMode', ),
309
440
0x000B: ('WhiteBalanceBias', ),
310
441
0x000C: ('WhiteBalanceRBCoeff', ),
442
0x000D: ('ProgramShift', nikon_ev_bias),
443
# Nearly the same as the other EV vals, but step size is 1/12 EV (?)
444
0x000E: ('ExposureDifference', nikon_ev_bias),
311
445
0x000F: ('ISOSelection', ),
312
0x0012: ('FlashCompensation', ),
446
0x0011: ('NikonPreview', ),
447
0x0012: ('FlashCompensation', nikon_ev_bias),
313
448
0x0013: ('ISOSpeedRequested', ),
314
449
0x0016: ('PhotoCornerCoordinates', ),
315
0x0018: ('FlashBracketCompensationApplied', ),
450
# 0x0017: Unknown, but most likely an EV value
451
0x0018: ('FlashBracketCompensationApplied', nikon_ev_bias),
316
452
0x0019: ('AEBracketCompensationApplied', ),
453
0x001A: ('ImageProcessing', ),
317
454
0x0080: ('ImageAdjustment', ),
318
455
0x0081: ('ToneCompensation', ),
319
456
0x0082: ('AuxiliaryLens', ),
337
480
0x40: 'Single frame, white balance bracketing',
338
481
0x41: 'Continuous, white balance bracketing',
339
482
0x42: 'Timer, white balance bracketing'}),
483
0x008A: ('AutoBracketRelease', ),
484
0x008B: ('LensFStops', ),
485
0x008C: ('NEFCurve2', ),
340
486
0x008D: ('ColorMode', ),
341
0x008F: ('SceneMode?', ),
487
0x008F: ('SceneMode', ),
342
488
0x0090: ('LightingType', ),
489
0x0091: ('ShotInfo', ), # First 4 bytes are probably a version number in ASCII
343
490
0x0092: ('HueAdjustment', ),
344
0x0094: ('Saturation',
491
# 0x0093: ('SaturationAdjustment', ),
492
0x0094: ('Saturation', # Name conflict with 0x00AA !!
351
499
0x0095: ('NoiseReduction', ),
500
0x0096: ('NEFCurve2', ),
501
0x0097: ('ColorBalance', ),
502
0x0098: ('LensData', ), # First 4 bytes are a version number in ASCII
503
0x0099: ('RawImageCenter', ),
504
0x009A: ('SensorPixelSize', ),
505
0x009C: ('Scene Assist', ),
506
0x00A0: ('SerialNumber', ),
507
0x00A2: ('ImageDataSize', ),
508
# A4: In NEF, looks like a 4 byte ASCII version number
509
0x00A5: ('ImageCount', ),
510
0x00A6: ('DeletedImageCount', ),
352
511
0x00A7: ('TotalShutterReleases', ),
512
# A8: ExposureMode? JPG: First 4 bytes are probably a version number in ASCII
513
# But in a sample NEF, its 8 zeros, then the string "NORMAL"
353
514
0x00A9: ('ImageOptimization', ),
354
515
0x00AA: ('Saturation', ),
355
516
0x00AB: ('DigitalVariProgram', ),
356
0x0010: ('DataDump', )
517
0x00AC: ('ImageStabilization', ),
518
0x00AD: ('Responsive AF', ), # 'AFResponse'
519
0x0010: ('DataDump', ),
359
MAKERNOTE_NIKON_OLDER_TAGS={
522
MAKERNOTE_NIKON_OLDER_TAGS = {
360
523
0x0003: ('Quality',
415
580
0x0202: ('Macro',
418
587
0x0204: ('DigitalZoom', ),
419
0x0207: ('SoftwareRelease', ),
420
0x0208: ('PictureInfo', ),
422
0x0209: ('CameraID', lambda x: ''.join(map(chr, x))),
423
0x0F00: ('DataDump', )
588
0x0205: ('FocalPlaneDiagonal', ),
589
0x0206: ('LensDistortionParams', ),
590
0x0207: ('SoftwareRelease', ),
591
0x0208: ('PictureInfo', ),
592
0x0209: ('CameraID', make_string), # print as string
593
0x0F00: ('DataDump', ),
594
0x0300: ('PreCaptureFrames', ),
595
0x0404: ('SerialNumber', ),
596
0x1000: ('ShutterSpeedValue', ),
597
0x1001: ('ISOValue', ),
598
0x1002: ('ApertureValue', ),
599
0x1003: ('BrightnessValue', ),
600
0x1004: ('FlashMode', ),
601
0x1004: ('FlashMode',
604
0x1005: ('FlashDevice',
608
5: 'Internal + External'}),
609
0x1006: ('ExposureCompensation', ),
610
0x1007: ('SensorTemperature', ),
611
0x1008: ('LensTemperature', ),
612
0x100b: ('FocusMode',
615
0x1017: ('RedBalance', ),
616
0x1018: ('BlueBalance', ),
617
0x101a: ('SerialNumber', ),
618
0x1023: ('FlashExposureComp', ),
619
0x1026: ('ExternalFlashBounce',
622
0x1027: ('ExternalFlashZoom', ),
623
0x1028: ('ExternalFlashMode', ),
624
0x1029: ('Contrast int16u',
628
0x102a: ('SharpnessFactor', ),
629
0x102b: ('ColorControl', ),
630
0x102c: ('ValidBits', ),
631
0x102d: ('CoringFilter', ),
632
0x102e: ('OlympusImageWidth', ),
633
0x102f: ('OlympusImageHeight', ),
634
0x1034: ('CompressionRatio', ),
635
0x1035: ('PreviewImageValid',
638
0x1036: ('PreviewImageStart', ),
639
0x1037: ('PreviewImageLength', ),
640
0x1039: ('CCDScanMode',
643
0x103a: ('NoiseReduction',
646
0x103b: ('InfinityLensStep', ),
647
0x103c: ('NearLensStep', ),
649
# TODO - these need extra definitions
650
# http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html
651
0x2010: ('Equipment', ),
652
0x2020: ('CameraSettings', ),
653
0x2030: ('RawDevelopment', ),
654
0x2040: ('ImageProcessing', ),
655
0x2050: ('FocusInfo', ),
656
0x3000: ('RawInfo ', ),
659
# 0x2020 CameraSettings
660
MAKERNOTE_OLYMPUS_TAG_0x2020={
661
0x0100: ('PreviewImageValid',
664
0x0101: ('PreviewImageStart', ),
665
0x0102: ('PreviewImageLength', ),
666
0x0200: ('ExposureMode', {
669
3: 'Aperture-priority AE',
670
4: 'Shutter speed priority AE',
671
5: 'Program-shift'}),
675
0x0202: ('MeteringMode',
676
{2: 'Center Weighted',
680
515: 'Spot+Highlight control',
681
1027: 'Spot+Shadow control'}),
682
0x0300: ('MacroMode',
685
0x0301: ('FocusMode',
687
1: 'Sequential shooting AF',
691
0x0302: ('FocusProcess',
697
0x0304: ('AFAreas', ),
698
0x0401: ('FlashExposureCompensation', ),
699
0x0500: ('WhiteBalance2',
701
16: '7500K (Fine Weather with Shade)',
702
17: '6000K (Cloudy)',
703
18: '5300K (Fine Weather)',
704
20: '3000K (Tungsten light)',
705
21: '3600K (Tungsten light-like)',
706
33: '6600K (Daylight fluorescent)',
707
34: '4500K (Neutral white fluorescent)',
708
35: '4000K (Cool white fluorescent)',
709
48: '3600K (Tungsten light-like)',
714
512: 'Custom WB 5400K',
715
513: 'Custom WB 2900K',
716
514: 'Custom WB 8000K', }),
717
0x0501: ('WhiteBalanceTemperature', ),
718
0x0502: ('WhiteBalanceBracket', ),
719
0x0503: ('CustomSaturation', ), # (3 numbers: 1. CS Value, 2. Min, 3. Max)
720
0x0504: ('ModifiedSaturation',
722
1: 'CM1 (Red Enhance)',
723
2: 'CM2 (Green Enhance)',
724
3: 'CM3 (Blue Enhance)',
725
4: 'CM4 (Skin Tones)'}),
726
0x0505: ('ContrastSetting', ), # (3 numbers: 1. Contrast, 2. Min, 3. Max)
727
0x0506: ('SharpnessSetting', ), # (3 numbers: 1. Sharpness, 2. Min, 3. Max)
728
0x0507: ('ColorSpace',
731
2: 'Pro Photo RGB'}),
732
0x0509: ('SceneMode',
737
9: 'Landscape+Portrait',
741
16: 'Landscape+Portrait',
742
17: 'Night+Portrait',
750
35: 'Underwater Wide1',
751
36: 'Underwater Macro',
753
40: 'Digital Image Stabilization',
754
44: 'Underwater Wide2',
757
48: 'Nature Macro'}),
758
0x050a: ('NoiseReduction',
760
1: 'Noise Reduction',
762
3: 'Noise Reduction + Noise Filter',
763
4: 'Noise Filter (ISO Boost)',
764
5: 'Noise Reduction + Noise Filter (ISO Boost)'}),
765
0x050b: ('DistortionCorrection',
768
0x050c: ('ShadingCompensation',
771
0x050d: ('CompressionFactor', ),
772
0x050f: ('Gradation',
773
{'-1 -1 1': 'Low Key',
775
'1 -1 1': 'High Key'}),
776
0x0520: ('PictureMode',
782
0x0521: ('PictureModeSaturation', ),
783
0x0522: ('PictureModeHue?', ),
784
0x0523: ('PictureModeContrast', ),
785
0x0524: ('PictureModeSharpness', ),
786
0x0525: ('PictureModeBWFilter',
793
0x0526: ('PictureModeTone',
800
0x0600: ('Sequence', ), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits
801
0x0601: ('PanoramaMode', ), # (2 numbers: 1. Mode, 2. Shot number)
802
0x0603: ('ImageQuality2',
807
0x0901: ('ManometerReading', ),
426
811
MAKERNOTE_CASIO_TAGS={
427
812
0x0001: ('RecordingMode',
818
1202
# return list of entries in this IFD
819
def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0):
1203
def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0, name='UNDEF'):
820
1204
entries=self.s2n(ifd, 2)
821
1205
for i in range(entries):
822
1206
# 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]))
1207
entry = ifd + 2 + 12 * i
1208
tag = self.s2n(entry, 2)
1210
# ignore certain tags for faster processing
1211
if not (tag in IGNORE_TAGS and not detailed):
1212
# get tag name. We do it early to make debugging easier
1213
tag_entry = dict.get(tag)
1215
tag_name = tag_entry[0]
1217
tag_name = 'Tag 0x%04X' % tag
1219
field_type = self.s2n(entry + 2, 2)
1220
if not 0 < field_type < len(FIELD_TYPES):
1221
# unknown field type
1222
raise ValueError('unknown type %d in tag 0x%04X' % (field_type, tag))
1223
typelen = FIELD_TYPES[field_type][0]
1224
count = self.s2n(entry + 4, 4)
1226
if count * typelen > 4:
1227
# offset is not the value; it's a pointer to the value
1228
# if relative we set things up so s2n will seek to the right
1229
# place when it adds self.offset. Note that this 'relative'
1230
# is for the Nikon type 3 makernote. Other cameras may use
1231
# other relative offsets, which would have to be computed here
1232
# slightly differently.
1234
tmp_offset = self.s2n(offset, 4)
1235
offset = tmp_offset + ifd - self.offset + 4
1237
offset = offset + 18
1239
offset = self.s2n(offset, 4)
1240
field_offset = offset
1242
# special case: null-terminated ASCII string
1244
self.file.seek(self.offset + offset)
1245
values = self.file.read(count)
1246
values = values.strip().replace('\x00', '')
1251
signed = (field_type in [6, 8, 9, 10])
1252
for dummy in range(count):
1253
if field_type in (5, 10):
1255
value = Ratio(self.s2n(offset, 4, signed),
1256
self.s2n(offset + 4, 4, signed))
1258
value = self.s2n(offset, typelen, signed)
1259
values.append(value)
1260
offset = offset + typelen
1261
# now "values" is either a string or an array
1262
if count == 1 and field_type != 2:
1263
printable=str(values[0])
1265
printable=str(values)
1266
# compute printable version of values
1268
if len(tag_entry) != 1:
1269
# optional 2nd tag element is present
1270
if callable(tag_entry[1]):
1271
# call mapping function
1272
printable = tag_entry[1](values)
1276
# use lookup table for this tag
1277
printable += tag_entry[1].get(i, repr(i))
1279
self.tags[ifd_name + ' ' + tag_name] = IFD_Tag(printable, tag,
1281
values, field_offset,
1284
print ' debug: %s: %s' % (tag_name,
1285
repr(self.tags[ifd_name + ' ' + tag_name]))
1287
if tag_name == name:
899
1290
# extract uncompressed TIFF thumbnail (like pulling teeth)
900
1291
# we take advantage of the pre-existing layout in the thumbnail IFD as
901
1292
# much as possible
902
1293
def extract_TIFF_thumbnail(self, thumb_ifd):
903
entries=self.s2n(thumb_ifd, 2)
1294
entries = self.s2n(thumb_ifd, 2)
904
1295
# this is header plus offset to IFD ...
905
1296
if self.endian == 'M':
906
tiff='MM\x00*\x00\x00\x00\x08'
1297
tiff = 'MM\x00*\x00\x00\x00\x08'
908
tiff='II*\x00\x08\x00\x00\x00'
1299
tiff = 'II*\x00\x08\x00\x00\x00'
909
1300
# ... plus thumbnail IFD data plus a null "next IFD" pointer
910
1301
self.file.seek(self.offset+thumb_ifd)
911
tiff+=self.file.read(entries*12+2)+'\x00\x00\x00\x00'
1302
tiff += self.file.read(entries*12+2)+'\x00\x00\x00\x00'
913
1304
# fix up large value offset pointers into data area
914
1305
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)
1306
entry = thumb_ifd + 2 + 12 * i
1307
tag = self.s2n(entry, 2)
1308
field_type = self.s2n(entry+2, 2)
1309
typelen = FIELD_TYPES[field_type][0]
1310
count = self.s2n(entry+4, 4)
1311
oldoff = self.s2n(entry+8, 4)
921
1312
# start of the 4-byte pointer area in entry
923
1314
# remember strip offsets location
924
1315
if tag == 0x0111:
926
strip_len=count*typelen
1317
strip_len = count * typelen
927
1318
# is it in the data area?
928
if count*typelen > 4:
1319
if count * typelen > 4:
929
1320
# update offset pointer (nasty "strings are immutable" crap)
930
1321
# should be able to say "tiff[ptr:ptr+4]=newoff"
932
tiff=tiff[:ptr]+self.n2s(newoff, 4)+tiff[ptr+4:]
1323
tiff = tiff[:ptr] + self.n2s(newoff, 4) + tiff[ptr+4:]
933
1324
# remember strip offsets location
934
1325
if tag == 0x0111:
937
1328
# get original data and store it
938
self.file.seek(self.offset+oldoff)
939
tiff+=self.file.read(count*typelen)
1329
self.file.seek(self.offset + oldoff)
1330
tiff += self.file.read(count * typelen)
941
1332
# add pixel strips and update strip offset info
942
old_offsets=self.tags['Thumbnail StripOffsets'].values
943
old_counts=self.tags['Thumbnail StripByteCounts'].values
1333
old_offsets = self.tags['Thumbnail StripOffsets'].values
1334
old_counts = self.tags['Thumbnail StripByteCounts'].values
944
1335
for i in range(len(old_offsets)):
945
1336
# 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:]
1337
offset = self.n2s(len(tiff), strip_len)
1338
tiff = tiff[:strip_off] + offset + tiff[strip_off + strip_len:]
1339
strip_off += strip_len
949
1340
# 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
1341
self.file.seek(self.offset + old_offsets[i])
1342
tiff += self.file.read(old_counts[i])
1344
self.tags['TIFFThumbnail'] = tiff
955
1346
# decode all the camera-specific MakerNote formats
957
1348
# Note is the data that comprises this MakerNote. The MakerNote will
982
1373
# not at the start of the makernote, it's probably type 2, since some
983
1374
# cameras work that way.
984
1375
if make in ('NIKON', 'NIKON CORPORATION'):
985
if note.values[0:7] == [78, 105, 107, 111, 110, 00, 01]:
1376
if note.values[0:7] == [78, 105, 107, 111, 110, 0, 1]:
987
1378
print "Looks like a type 1 Nikon MakerNote."
988
1379
self.dump_IFD(note.field_offset+8, 'MakerNote',
989
1380
dict=MAKERNOTE_NIKON_OLDER_TAGS)
990
elif note.values[0:7] == [78, 105, 107, 111, 110, 00, 02]:
1381
elif note.values[0:7] == [78, 105, 107, 111, 110, 0, 2]:
992
1383
print "Looks like a labeled type 2 Nikon MakerNote"
993
1384
if note.values[12:14] != [0, 42] and note.values[12:14] != [42L, 0L]:
994
raise ValueError, "Missing marker tag '42' in MakerNote."
1385
raise ValueError("Missing marker tag '42' in MakerNote.")
995
1386
# skip the Makernote label and the TIFF header
996
1387
self.dump_IFD(note.field_offset+10+8, 'MakerNote',
997
1388
dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1)
1094
1497
# deal with the EXIF info we found
1096
1499
print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format'
1097
hdr=EXIF_header(file, endian, offset, fake_exif, debug)
1098
ifd_list=hdr.list_IFDs()
1500
hdr = EXIF_header(f, endian, offset, fake_exif, debug)
1501
ifd_list = hdr.list_IFDs()
1100
1503
for i in ifd_list:
1104
IFD_name='Thumbnail'
1507
IFD_name = 'Thumbnail'
1107
IFD_name='IFD %d' % ctr
1510
IFD_name = 'IFD %d' % ctr
1109
1512
print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i)
1110
hdr.dump_IFD(i, IFD_name)
1513
hdr.dump_IFD(i, IFD_name, name=name)
1112
exif_off=hdr.tags.get(IFD_name+' ExifOffset')
1515
exif_off = hdr.tags.get(IFD_name+' ExifOffset')
1115
1518
print ' EXIF SubIFD at offset %d:' % exif_off.values[0]
1116
hdr.dump_IFD(exif_off.values[0], 'EXIF')
1519
hdr.dump_IFD(exif_off.values[0], 'EXIF', name=name)
1117
1520
# Interoperability IFD contained in EXIF IFD
1118
intr_off=hdr.tags.get('EXIF SubIFD InteroperabilityOffset')
1521
intr_off = hdr.tags.get('EXIF SubIFD InteroperabilityOffset')
1121
1524
print ' EXIF Interoperability SubSubIFD at offset %d:' \
1122
1525
% intr_off.values[0]
1123
1526
hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability',
1527
dict=INTR_TAGS, name=name)
1126
gps_off=hdr.tags.get(IFD_name+' GPSInfo')
1529
gps_off = hdr.tags.get(IFD_name+' GPSInfo')
1129
1532
print ' GPS SubIFD at offset %d:' % gps_off.values[0]
1130
hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS)
1533
hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS, name=name)
1133
1536
# extract uncompressed TIFF thumbnail
1134
thumb=hdr.tags.get('Thumbnail Compression')
1537
thumb = hdr.tags.get('Thumbnail Compression')
1135
1538
if thumb and thumb.printable == 'Uncompressed TIFF':
1136
1539
hdr.extract_TIFF_thumbnail(thumb_ifd)
1138
1541
# JPEG thumbnail (thankfully the JPEG data is stored as a unit)
1139
thumb_off=hdr.tags.get('Thumbnail JPEGInterchangeFormat')
1542
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)
1544
f.seek(offset+thumb_off.values[0])
1545
size = hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0]
1546
hdr.tags['JPEGThumbnail'] = f.read(size)
1145
1548
# deal with MakerNote contained in EXIF IFD
1146
if hdr.tags.has_key('EXIF MakerNote'):
1549
if 'EXIF MakerNote' in hdr.tags and detailed:
1147
1550
hdr.decode_maker_note()
1149
1552
# Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
1150
1553
# since it's not allowed in a uncompressed TIFF IFD
1151
if not hdr.tags.has_key('JPEGThumbnail'):
1554
if 'JPEGThumbnail' not in hdr.tags:
1152
1555
thumb_off=hdr.tags.get('MakerNote JPEGThumbnail')
1154
file.seek(offset+thumb_off.values[0])
1557
f.seek(offset+thumb_off.values[0])
1155
1558
hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length)
1157
1560
return hdr.tags
1159
1563
# library test/debug function (dump given files)
1160
1564
if __name__ == '__main__':
1163
1567
if len(sys.argv) < 2:
1164
print 'Usage: %s files...\n' % sys.argv[0]
1568
print "Usage: %s [options] file1 [file2 ...]\n\nOptions:\n-q --quick : do not process MakerNotes for faster processing\n" % sys.argv[0]
1167
for filename in sys.argv[1:]:
1571
if sys.argv[1] == "-q" or sys.argv[1] == "--quick":
1578
for filename in sys.argv[fileNamesStart:]:
1169
1580
file=open(filename, 'rb')
1171
1582
print filename, 'unreadable'
1175
# data=process_file(file, 1) # with debug info
1176
data=process_file(file)
1585
print filename + ':'
1586
# data=process_file(file, debug=1) # with debug info and all tags
1587
data=process_file(file, details=detailed)
1178
1589
print 'No EXIF information found'