1 # Library to extract EXIF information in digital camera image files
3 # Contains code from "exifdump.py" originally written by Thierry Bousch
4 # <bousch@topo.math.u-psud.fr> and released into the public domain.
6 # Updated and turned into general-purpose library by Gene Cash
7 # <email gcash at cfl.rr.com>
9 # This copyright license is intended to be similar to the FreeBSD license.
11 # Copyright 2002 Gene Cash All rights reserved.
13 # Redistribution and use in source and binary forms, with or without
14 # modification, are permitted provided that the following conditions are
17 # 1. Redistributions of source code must retain the above copyright
18 # notice, this list of conditions and the following disclaimer.
19 # 2. Redistributions in binary form must reproduce the above copyright
20 # notice, this list of conditions and the following disclaimer in the
21 # documentation and/or other materials provided with the
24 # THIS SOFTWARE IS PROVIDED BY GENE CASH ``AS IS'' AND ANY EXPRESS OR
25 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27 # DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
28 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 # POSSIBILITY OF SUCH DAMAGE.
36 # This means you may do anything you want with this code, except claim you
37 # wrote it. Also, if it breaks you get to keep both pieces.
39 # 21-AUG-99 TB Last update by Thierry Bousch to his code.
40 # 17-JAN-02 CEC Discovered code on web.
41 # Commented everything.
42 # Made small code improvements.
43 # Reformatted for readability.
44 # 19-JAN-02 CEC Added ability to read TIFFs and JFIF-format JPEGs.
45 # Added ability to extract JPEG formatted thumbnail.
46 # Added ability to read GPS IFD (not tested).
47 # Converted IFD data structure to dictionaries indexed by
49 # Factored into library returning dictionary of IFDs plus
51 # 20-JAN-02 CEC Added MakerNote processing logic.
52 # Added Olympus MakerNote.
53 # Converted data structure to single-level dictionary, avoiding
54 # tag name collisions by prefixing with IFD name. This makes
55 # it much easier to use.
56 # 23-JAN-02 CEC Trimmed nulls from end of string values.
57 # 25-JAN-02 CEC Discovered JPEG thumbnail in Olympus TIFF MakerNote.
58 # 26-JAN-02 CEC Added ability to extract TIFF thumbnails.
59 # Added Nikon, Fujifilm, Casio MakerNotes.
60 # 30-NOV-03 CEC Fixed problem with canon_decode_tag() not creating an
62 # 15-FEB-04 CEC Finally fixed bit shift warning by converting Y to 0L.
65 # * Better printing of ratios
67 # field type descriptions as (length, abbreviation, full name) tuples
69 (0, 'X', 'Proprietary'), # no such type
75 (1, 'SB', 'Signed Byte'),
76 (1, 'U', 'Undefined'),
77 (2, 'SS', 'Signed Short'),
78 (4, 'SL', 'Signed Long'),
79 (8, 'SR', 'Signed Ratio')
82 # dictionary of main EXIF tag names
83 # first element of tuple is tag name, optional second element is
84 # another dictionary giving names to values
86 0x0100: ('ImageWidth', ),
87 0x0101: ('ImageLength', ),
88 0x0102: ('BitsPerSample', ),
89 0x0103: ('Compression',
90 {1: 'Uncompressed TIFF',
91 6: 'JPEG Compressed'}),
92 0x0106: ('PhotometricInterpretation', ),
93 0x010A: ('FillOrder', ),
94 0x010D: ('DocumentName', ),
95 0x010E: ('ImageDescription', ),
98 0x0111: ('StripOffsets', ),
99 0x0112: ('Orientation', ),
100 0x0115: ('SamplesPerPixel', ),
101 0x0116: ('RowsPerStrip', ),
102 0x0117: ('StripByteCounts', ),
103 0x011A: ('XResolution', ),
104 0x011B: ('YResolution', ),
105 0x011C: ('PlanarConfiguration', ),
106 0x0128: ('ResolutionUnit',
109 3: 'Pixels/Centimeter'}),
110 0x012D: ('TransferFunction', ),
111 0x0131: ('Software', ),
112 0x0132: ('DateTime', ),
113 0x013B: ('Artist', ),
114 0x013E: ('WhitePoint', ),
115 0x013F: ('PrimaryChromaticities', ),
116 0x0156: ('TransferRange', ),
117 0x0200: ('JPEGProc', ),
118 0x0201: ('JPEGInterchangeFormat', ),
119 0x0202: ('JPEGInterchangeFormatLength', ),
120 0x0211: ('YCbCrCoefficients', ),
121 0x0212: ('YCbCrSubSampling', ),
122 0x0213: ('YCbCrPositioning', ),
123 0x0214: ('ReferenceBlackWhite', ),
124 0x828D: ('CFARepeatPatternDim', ),
125 0x828E: ('CFAPattern', ),
126 0x828F: ('BatteryLevel', ),
127 0x8298: ('Copyright', ),
128 0x829A: ('ExposureTime', ),
129 0x829D: ('FNumber', ),
130 0x83BB: ('IPTC/NAA', ),
131 0x8769: ('ExifOffset', ),
132 0x8773: ('InterColorProfile', ),
133 0x8822: ('ExposureProgram',
137 3: 'Aperture Priority',
138 4: 'Shutter Priority',
139 5: 'Program Creative',
142 8: 'Landscape Mode'}),
143 0x8824: ('SpectralSensitivity', ),
144 0x8825: ('GPSInfo', ),
145 0x8827: ('ISOSpeedRatings', ),
148 0x9000: ('ExifVersion', lambda x: ''.join(map(chr, x))),
149 0x9003: ('DateTimeOriginal', ),
150 0x9004: ('DateTimeDigitized', ),
151 0x9101: ('ComponentsConfiguration',
159 0x9102: ('CompressedBitsPerPixel', ),
160 0x9201: ('ShutterSpeedValue', ),
161 0x9202: ('ApertureValue', ),
162 0x9203: ('BrightnessValue', ),
163 0x9204: ('ExposureBiasValue', ),
164 0x9205: ('MaxApertureValue', ),
165 0x9206: ('SubjectDistance', ),
166 0x9207: ('MeteringMode',
169 2: 'CenterWeightedAverage',
172 0x9208: ('LightSource',
178 17: 'Standard Light A',
179 18: 'Standard Light B',
180 19: 'Standard Light C',
185 0x9209: ('Flash', {0: 'No',
187 5: 'Fired (?)', # no return sensed
188 7: 'Fired (!)', # return sensed
190 13: 'Fill Fired (?)',
191 15: 'Fill Fired (!)',
195 29: 'Auto Fired (?)',
196 31: 'Auto Fired (!)',
197 32: 'Not Available'}),
198 0x920A: ('FocalLength', ),
199 0x927C: ('MakerNote', ),
201 0x9286: ('UserComment', lambda x: ''.join(map(chr, x))),
202 0x9290: ('SubSecTime', ),
203 0x9291: ('SubSecTimeOriginal', ),
204 0x9292: ('SubSecTimeDigitized', ),
206 0xA000: ('FlashPixVersion', lambda x: ''.join(map(chr, x))),
207 0xA001: ('ColorSpace', ),
208 0xA002: ('ExifImageWidth', ),
209 0xA003: ('ExifImageLength', ),
210 0xA005: ('InteroperabilityOffset', ),
211 0xA20B: ('FlashEnergy', ), # 0x920B in TIFF/EP
212 0xA20C: ('SpatialFrequencyResponse', ), # 0x920C - -
213 0xA20E: ('FocalPlaneXResolution', ), # 0x920E - -
214 0xA20F: ('FocalPlaneYResolution', ), # 0x920F - -
215 0xA210: ('FocalPlaneResolutionUnit', ), # 0x9210 - -
216 0xA214: ('SubjectLocation', ), # 0x9214 - -
217 0xA215: ('ExposureIndex', ), # 0x9215 - -
218 0xA217: ('SensingMethod', ), # 0x9217 - -
219 0xA300: ('FileSource',
220 {3: 'Digital Camera'}),
221 0xA301: ('SceneType',
222 {1: 'Directly Photographed'}),
225 # interoperability tags
227 0x0001: ('InteroperabilityIndex', ),
228 0x0002: ('InteroperabilityVersion', ),
229 0x1000: ('RelatedImageFileFormat', ),
230 0x1001: ('RelatedImageWidth', ),
231 0x1002: ('RelatedImageLength', ),
234 # GPS tags (not used yet, haven't seen camera with GPS)
236 0x0000: ('GPSVersionID', ),
237 0x0001: ('GPSLatitudeRef', ),
238 0x0002: ('GPSLatitude', ),
239 0x0003: ('GPSLongitudeRef', ),
240 0x0004: ('GPSLongitude', ),
241 0x0005: ('GPSAltitudeRef', ),
242 0x0006: ('GPSAltitude', ),
243 0x0007: ('GPSTimeStamp', ),
244 0x0008: ('GPSSatellites', ),
245 0x0009: ('GPSStatus', ),
246 0x000A: ('GPSMeasureMode', ),
247 0x000B: ('GPSDOP', ),
248 0x000C: ('GPSSpeedRef', ),
249 0x000D: ('GPSSpeed', ),
250 0x000E: ('GPSTrackRef', ),
251 0x000F: ('GPSTrack', ),
252 0x0010: ('GPSImgDirectionRef', ),
253 0x0011: ('GPSImgDirection', ),
254 0x0012: ('GPSMapDatum', ),
255 0x0013: ('GPSDestLatitudeRef', ),
256 0x0014: ('GPSDestLatitude', ),
257 0x0015: ('GPSDestLongitudeRef', ),
258 0x0016: ('GPSDestLongitude', ),
259 0x0017: ('GPSDestBearingRef', ),
260 0x0018: ('GPSDestBearing', ),
261 0x0019: ('GPSDestDistanceRef', ),
262 0x001A: ('GPSDestDistance', )
265 # Nikon E99x MakerNote Tags
266 # http://members.tripod.com/~tawba/990exif.htm
267 MAKERNOTE_NIKON_NEWER_TAGS={
268 0x0002: ('ISOSetting', ),
269 0x0003: ('ColorMode', ),
270 0x0004: ('Quality', ),
271 0x0005: ('Whitebalance', ),
272 0x0006: ('ImageSharpening', ),
273 0x0007: ('FocusMode', ),
274 0x0008: ('FlashSetting', ),
275 0x000F: ('ISOSelection', ),
276 0x0080: ('ImageAdjustment', ),
277 0x0082: ('AuxiliaryLens', ),
278 0x0085: ('ManualFocusDistance', ),
279 0x0086: ('DigitalZoomFactor', ),
280 0x0088: ('AFFocusPosition',
286 0x0094: ('Saturation',
293 0x0095: ('NoiseReduction', ),
294 0x0010: ('DataDump', )
297 MAKERNOTE_NIKON_OLDER_TAGS={
305 0x0004: ('ColorMode',
308 0x0005: ('ImageAdjustment',
319 0x0007: ('WhiteBalance',
329 # decode Olympus SpecialMode tag in MakerNote
330 def olympus_special_mode(v):
342 return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
344 MAKERNOTE_OLYMPUS_TAGS={
345 # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
346 # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
347 0x0100: ('JPEGThumbnail', ),
348 0x0200: ('SpecialMode', olympus_special_mode),
356 0x0204: ('DigitalZoom', ),
357 0x0207: ('SoftwareRelease', ),
358 0x0208: ('PictureInfo', ),
360 0x0209: ('CameraID', lambda x: ''.join(map(chr, x))),
361 0x0F00: ('DataDump', )
364 MAKERNOTE_CASIO_TAGS={
365 0x0001: ('RecordingMode',
366 {1: 'Single Shutter',
375 0x0003: ('FocusingMode',
380 0x0004: ('FlashMode',
384 4: 'Red Eye Reduction'}),
385 0x0005: ('FlashIntensity',
389 0x0006: ('Object Distance', ),
390 0x0007: ('WhiteBalance',
397 0x000B: ('Sharpness',
405 0x000D: ('Saturation',
418 MAKERNOTE_FUJIFILM_TAGS={
419 0x0000: ('NoteVersion', lambda x: ''.join(map(chr, x))),
420 0x1000: ('Quality', ),
421 0x1001: ('Sharpness',
427 0x1002: ('WhiteBalance',
431 768: 'DaylightColor-Fluorescent',
432 769: 'DaywhiteColor-Fluorescent',
433 770: 'White-Fluorescent',
434 1024: 'Incandescent',
444 0x1010: ('FlashMode',
448 3: 'Red Eye Reduction'}),
449 0x1011: ('FlashStrength', ),
453 0x1021: ('FocusMode',
459 0x1031: ('PictureMode',
466 256: 'Aperture Priority AE',
467 512: 'Shutter Priority AE',
468 768: 'Manual Exposure'}),
469 0x1100: ('MotorOrBracket',
472 0x1300: ('BlurWarning',
475 0x1301: ('FocusWarning',
478 0x1302: ('AEWarning',
483 MAKERNOTE_CANON_TAGS={
484 0x0006: ('ImageType', ),
485 0x0007: ('FirmwareVersion', ),
486 0x0008: ('ImageNumber', ),
487 0x0009: ('OwnerName', )
490 # see http://www.burren.cx/david/canon.html by David Burren
491 # this is in element offset, name, optional value dictionary format
492 MAKERNOTE_CANON_TAG_0x001={
502 {0: 'Flash Not Fired',
505 3: 'Red-Eye Reduction',
507 5: 'Auto + Red-Eye Reduction',
508 6: 'On + Red-Eye Reduction',
509 16: 'external flash'}),
510 5: ('ContinuousDriveMode',
511 {0: 'Single Or Timer',
525 11: ('EasyShootingMode',
536 10: 'Macro/Close-Up',
555 {0: 'See ISOSpeedRatings Tag',
564 5: 'Center-weighted'}),
568 3: 'Close-Up (Macro)',
569 8: 'Locked (Pan Mode)'}),
570 19: ('AFPointSelected',
571 {0x3000: 'None (MF)',
572 0x3001: 'Auto-Selected',
583 23: ('LongFocalLengthOfLensInFocalUnits', ),
584 24: ('ShortFocalLengthOfLensInFocalUnits', ),
585 25: ('FocalUnitsPerMM', ),
586 28: ('FlashActivity',
590 {14: 'External E-TTL',
591 13: 'Internal Flash',
593 7: '2nd("Rear")-Curtain Sync Used',
594 4: 'FP Sync Enabled'}),
600 MAKERNOTE_CANON_TAG_0x004={
609 9: ('SequenceNumber', ),
610 14: ('AFPointUsed', ),
629 19: ('SubjectDistance', )
632 # extract multibyte integer in Motorola format (little endian)
633 def s2n_motorola(str):
639 # extract multibyte integer in Intel format (big endian)
648 # ratio object that eventually will be able to reduce itself to lowest
649 # common denominator for printing
657 def __init__(self, num, den):
665 return '%d/%d' % (self.num, self.den)
668 div=gcd(self.num, self.den)
670 self.num=self.num/div
671 self.den=self.den/div
673 # for ease of dealing with tags
675 def __init__(self, printable, tag, field_type, values, field_offset,
677 # printable version of data
678 self.printable=printable
681 # field type as index into FIELD_TYPES
682 self.field_type=field_type
683 # offset of start of field in bytes from beginning of IFD
684 self.field_offset=field_offset
685 # length of data field in bytes
686 self.field_length=field_length
687 # either a string or array of data items
691 return self.printable
694 return '(0x%04X) %s=%s @ %d' % (self.tag,
695 FIELD_TYPES[self.field_type][2],
699 # class that handles an EXIF header
701 def __init__(self, file, endian, offset, debug=0):
708 # convert slice to integer, based on sign and endian flags
709 def s2n(self, offset, length, signed=0):
710 self.file.seek(self.offset+offset)
711 slice=self.file.read(length)
712 if self.endian == 'I':
715 val=s2n_motorola(slice)
718 #msb=1 << (8*length-1)
724 # convert offset to string
725 def n2s(self, offset, length):
727 for i in range(length):
728 if self.endian == 'I':
729 s=s+chr(offset & 0xFF)
731 s=chr(offset & 0xFF)+s
737 return self.s2n(4, 4)
739 # return pointer to next IFD
740 def next_IFD(self, ifd):
741 entries=self.s2n(ifd, 2)
742 return self.s2n(ifd+2+12*entries, 4)
744 # return list of IFDs in header
753 # return list of entries in this IFD
754 def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS):
755 entries=self.s2n(ifd, 2)
756 for i in range(entries):
758 tag=self.s2n(entry, 2)
759 field_type=self.s2n(entry+2, 2)
760 if not 0 < field_type < len(FIELD_TYPES):
763 'unknown type %d in tag 0x%04X' % (field_type, tag)
764 typelen=FIELD_TYPES[field_type][0]
765 count=self.s2n(entry+4, 4)
767 if count*typelen > 4:
768 # not the value, it's a pointer to the value
769 offset=self.s2n(offset, 4)
772 # special case: null-terminated ASCII string
774 self.file.seek(self.offset+offset)
775 values=self.file.read(count).strip().replace('\x00','')
780 signed=(field_type in [6, 8, 9, 10])
781 for j in range(count):
782 if field_type in (5, 10):
784 value_j=Ratio(self.s2n(offset, 4, signed),
785 self.s2n(offset+4, 4, signed))
787 value_j=self.s2n(offset, typelen, signed)
788 values.append(value_j)
789 offset=offset+typelen
790 # now "values" is either a string or an array
791 if count == 1 and field_type != 2:
792 printable=str(values[0])
794 printable=str(values)
795 # figure out tag name
796 tag_entry=dict.get(tag)
798 tag_name=tag_entry[0]
799 if len(tag_entry) != 1:
800 # optional 2nd tag element is present
801 if callable(tag_entry[1]):
802 # call mapping function
803 printable=tag_entry[1](values)
807 # use LUT for this tag
808 printable+=tag_entry[1].get(i, repr(i))
810 tag_name='Tag 0x%04X' % tag
811 self.tags[ifd_name+' '+tag_name]=IFD_Tag(printable, tag,
813 values, field_offset,
816 print ' %s: %s' % (tag_name,
817 repr(self.tags[ifd_name+' '+tag_name]))
819 # extract uncompressed TIFF thumbnail (like pulling teeth)
820 # we take advantage of the pre-existing layout in the thumbnail IFD as
822 def extract_TIFF_thumbnail(self, thumb_ifd):
823 entries=self.s2n(thumb_ifd, 2)
824 # this is header plus offset to IFD ...
825 if self.endian == 'M':
826 tiff='MM\x00*\x00\x00\x00\x08'
828 tiff='II*\x00\x08\x00\x00\x00'
829 # ... plus thumbnail IFD data plus a null "next IFD" pointer
830 self.file.seek(self.offset+thumb_ifd)
831 tiff+=self.file.read(entries*12+2)+'\x00\x00\x00\x00'
833 # fix up large value offset pointers into data area
834 for i in range(entries):
835 entry=thumb_ifd+2+12*i
836 tag=self.s2n(entry, 2)
837 field_type=self.s2n(entry+2, 2)
838 typelen=FIELD_TYPES[field_type][0]
839 count=self.s2n(entry+4, 4)
840 oldoff=self.s2n(entry+8, 4)
841 # start of the 4-byte pointer area in entry
843 # remember strip offsets location
846 strip_len=count*typelen
847 # is it in the data area?
848 if count*typelen > 4:
849 # update offset pointer (nasty "strings are immutable" crap)
850 # should be able to say "tiff[ptr:ptr+4]=newoff"
852 tiff=tiff[:ptr]+self.n2s(newoff, 4)+tiff[ptr+4:]
853 # remember strip offsets location
857 # get original data and store it
858 self.file.seek(self.offset+oldoff)
859 tiff+=self.file.read(count*typelen)
861 # add pixel strips and update strip offset info
862 old_offsets=self.tags['Thumbnail StripOffsets'].values
863 old_counts=self.tags['Thumbnail StripByteCounts'].values
864 for i in range(len(old_offsets)):
865 # update offset pointer (more nasty "strings are immutable" crap)
866 offset=self.n2s(len(tiff), strip_len)
867 tiff=tiff[:strip_off]+offset+tiff[strip_off+strip_len:]
869 # add pixel strip to end
870 self.file.seek(self.offset+old_offsets[i])
871 tiff+=self.file.read(old_counts[i])
873 self.tags['TIFFThumbnail']=tiff
875 # decode all the camera-specific MakerNote formats
876 def decode_maker_note(self):
877 note=self.tags['EXIF MakerNote']
878 make=self.tags['Image Make'].printable
879 model=self.tags['Image Model'].printable
883 if note.values[0:5] == [78, 105, 107, 111, 110]: # "Nikon"
885 self.dump_IFD(note.field_offset+8, 'MakerNote',
886 dict=MAKERNOTE_NIKON_OLDER_TAGS)
888 # newer model (E99x or D1)
889 self.dump_IFD(note.field_offset, 'MakerNote',
890 dict=MAKERNOTE_NIKON_NEWER_TAGS)
894 if make[:7] == 'OLYMPUS':
895 self.dump_IFD(note.field_offset+8, 'MakerNote',
896 dict=MAKERNOTE_OLYMPUS_TAGS)
901 self.dump_IFD(note.field_offset, 'MakerNote',
902 dict=MAKERNOTE_CASIO_TAGS)
906 if make == 'FUJIFILM':
907 # bug: everything else is "Motorola" endian, but the MakerNote
911 # bug: IFD offsets are from beginning of MakerNote, not
912 # beginning of file header
914 self.offset+=note.field_offset
915 # process note with bogus values (note is actually at offset 12)
916 self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS)
917 # reset to correct values
924 self.dump_IFD(note.field_offset, 'MakerNote',
925 dict=MAKERNOTE_CANON_TAGS)
926 for i in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001),
927 ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004)):
928 self.canon_decode_tag(self.tags[i[0]].values, i[1])
931 # decode Canon MakerNote tag based on offset within tag
932 # see http://www.burren.cx/david/canon.html by David Burren
933 def canon_decode_tag(self, value, dict):
934 for i in range(1, len(value)):
935 x=dict.get(i, ('Unknown', ))
940 val=x[1].get(value[i], 'Unknown')
943 # it's not a real IFD Tag but we fake one to make everybody
944 # happy. this will have a "proprietary" type
945 self.tags['MakerNote '+name]=IFD_Tag(str(val), None, 0, None,
948 # process an image file (expects an open file object)
949 # this is the function that has to deal with all the arbitrary nasty bits
950 # of the EXIF standard
951 def process_file(file, debug=0):
952 # determine whether it's a JPEG or TIFF
954 if data[0:4] in ['II*\x00', 'MM\x00*']:
960 elif data[0:2] == '\xFF\xD8':
962 # skip JFIF style header(s)
963 while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM'):
964 length=ord(data[4])*256+ord(data[5])
966 # fake an EXIF beginning of file
967 data='\xFF\x00'+file.read(10)
968 if data[2] == '\xFF' and data[6:10] == 'Exif':
969 # detected EXIF header
973 # no EXIF information
976 # file format not recognized
979 # deal with the EXIF info we found
981 print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format'
982 hdr=EXIF_header(file, endian, offset, debug)
983 ifd_list=hdr.list_IFDs()
992 IFD_name='IFD %d' % ctr
994 print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i)
995 hdr.dump_IFD(i, IFD_name)
997 exif_off=hdr.tags.get(IFD_name+' ExifOffset')
1000 print ' EXIF SubIFD at offset %d:' % exif_off.values[0]
1001 hdr.dump_IFD(exif_off.values[0], 'EXIF')
1002 # Interoperability IFD contained in EXIF IFD
1003 intr_off=hdr.tags.get('EXIF SubIFD InteroperabilityOffset')
1006 print ' EXIF Interoperability SubSubIFD at offset %d:' \
1007 % intr_off.values[0]
1008 hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability',
1011 gps_off=hdr.tags.get(IFD_name+' GPSInfo')
1014 print ' GPS SubIFD at offset %d:' % gps_off.values[0]
1015 hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS)
1018 # extract uncompressed TIFF thumbnail
1019 thumb=hdr.tags.get('Thumbnail Compression')
1020 if thumb and thumb.printable == 'Uncompressed TIFF':
1021 hdr.extract_TIFF_thumbnail(thumb_ifd)
1023 # JPEG thumbnail (thankfully the JPEG data is stored as a unit)
1024 thumb_off=hdr.tags.get('Thumbnail JPEGInterchangeFormat')
1026 file.seek(offset+thumb_off.values[0])
1027 size=hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0]
1028 hdr.tags['JPEGThumbnail']=file.read(size)
1030 # deal with MakerNote contained in EXIF IFD
1031 if hdr.tags.has_key('EXIF MakerNote'):
1032 hdr.decode_maker_note()
1034 # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
1035 # since it's not allowed in a uncompressed TIFF IFD
1036 if not hdr.tags.has_key('JPEGThumbnail'):
1037 thumb_off=hdr.tags.get('MakerNote JPEGThumbnail')
1039 file.seek(offset+thumb_off.values[0])
1040 hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length)
1044 # library test/debug function (dump given files)
1045 if __name__ == '__main__':
1048 if len(sys.argv) < 2:
1049 print 'Usage: %s files...\n' % sys.argv[0]
1052 for filename in sys.argv[1:]:
1054 file=open(filename, 'rb')
1056 print filename, 'unreadable'
1060 # data=process_file(file, 1) # with debug info
1061 data=process_file(file)
1063 print 'No EXIF information found'
1069 if i in ('JPEGThumbnail', 'TIFFThumbnail'):
1072 print ' %s (%s): %s' % \
1073 (i, FIELD_TYPES[data[i].field_type][2], data[i].printable)
1075 print 'error', i, '"', data[i], '"'
1076 if data.has_key('JPEGThumbnail'):
1077 print 'File has JPEG thumbnail'