2 # -*- coding: utf-8 -*-
5 # Library to extract EXIF information from digital camera image files.
6 # https://github.com/ianare/exif-py
11 # To use this library call with:
12 # f = open(path_name, 'rb')
13 # tags = EXIF.process_file(f)
15 # To ignore MakerNote tags, pass the -q or --quick
16 # command line arguments, or as
17 # tags = EXIF.process_file(f, details=False)
19 # To stop processing after a certain tag is retrieved,
20 # pass the -t TAG or --stop-tag TAG argument, or as
21 # tags = EXIF.process_file(f, stop_tag='TAG')
23 # where TAG is a valid tag name, ex 'DateTimeOriginal'
25 # These 2 are useful when you are retrieving a large list of images
27 # To return an error on invalid tags,
28 # pass the -s or --strict argument, or as
29 # tags = EXIF.process_file(f, strict=True)
31 # Otherwise these tags will be ignored
33 # Returned tags will be a dictionary mapping names of EXIF tags to their
34 # values in the file named by path_name. You can process the tags
35 # as you wish. In particular, you can iterate through all the tags with:
36 # for tag in tags.keys():
37 # if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename',
39 # print "Key: %s, value %s" % (tag, tags[tag])
40 # (This code uses the if statement to avoid printing out a few of the
41 # tags that tend to be long or boring.)
43 # The tags dictionary will include keys for all of the usual EXIF
44 # tags, and will also include keys for Makernotes used by some
45 # cameras, for which we have a good specification.
47 # Note that the dictionary keys are the IFD name followed by the
48 # tag name. For example:
49 # 'EXIF DateTimeOriginal', 'Image Orientation', 'MakerNote FocusMode'
51 # Copyright (c) 2002-2007 Gene Cash All rights reserved
52 # Copyright (c) 2007-2012 Ianaré Sévi All rights reserved
54 # Redistribution and use in source and binary forms, with or without
55 # modification, are permitted provided that the following conditions
58 # 1. Redistributions of source code must retain the above copyright
59 # notice, this list of conditions and the following disclaimer.
61 # 2. Redistributions in binary form must reproduce the above
62 # copyright notice, this list of conditions and the following
63 # disclaimer in the documentation and/or other materials provided
64 # with the distribution.
66 # 3. Neither the name of the authors nor the names of its contributors
67 # may be used to endorse or promote products derived from this
68 # software without specific prior written permission.
70 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
71 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
72 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
73 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
74 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
75 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
76 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
77 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
78 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
79 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
80 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
83 # ----- See 'changes.txt' file for all contributors and changes ----- #
87 # Don't throw an exception when given an out of range character.
91 # Screen out non-printing characters
92 if 32 <= c
and c
< 256:
94 # If no printing chars
99 # Special version to deal with the code in the first 8 bytes of a user comment.
100 # First 8 bytes gives coding system e.g. ASCII vs. JIS vs Unicode
101 def make_string_uc(seq
):
104 # Of course, this is only correct if ASCII, and the standard explicitly
105 # allows JIS and Unicode.
106 return make_string( make_string(seq
) )
108 # field type descriptions as (length, abbreviation, full name) tuples
110 (0, 'X', 'Proprietary'), # no such type
116 (1, 'SB', 'Signed Byte'),
117 (1, 'U', 'Undefined'),
118 (2, 'SS', 'Signed Short'),
119 (4, 'SL', 'Signed Long'),
120 (8, 'SR', 'Signed Ratio'),
123 # dictionary of main EXIF tag names
124 # first element of tuple is tag name, optional second element is
125 # another dictionary giving names to values
127 0x0100: ('ImageWidth', ),
128 0x0101: ('ImageLength', ),
129 0x0102: ('BitsPerSample', ),
130 0x0103: ('Compression',
136 6: 'JPEG (old-style)',
142 32769: 'Epson ERF Compressed',
145 32809: 'Thunderscan',
158 34713: 'Nikon NEF Compressed',
159 65000: 'Kodak DCR Compressed',
160 65535: 'Pentax PEF Compressed'}),
161 0x0106: ('PhotometricInterpretation', ),
162 0x0107: ('Thresholding', ),
163 0x010A: ('FillOrder', ),
164 0x010D: ('DocumentName', ),
165 0x010E: ('ImageDescription', ),
168 0x0111: ('StripOffsets', ),
169 0x0112: ('Orientation',
170 {1: 'Horizontal (normal)',
171 2: 'Mirrored horizontal',
173 4: 'Mirrored vertical',
174 5: 'Mirrored horizontal then rotated 90 CCW',
176 7: 'Mirrored horizontal then rotated 90 CW',
177 8: 'Rotated 90 CW'}),
178 0x0115: ('SamplesPerPixel', ),
179 0x0116: ('RowsPerStrip', ),
180 0x0117: ('StripByteCounts', ),
181 0x011A: ('XResolution', ),
182 0x011B: ('YResolution', ),
183 0x011C: ('PlanarConfiguration', ),
184 0x011D: ('PageName', make_string
),
185 0x0128: ('ResolutionUnit',
188 3: 'Pixels/Centimeter'}),
189 0x012D: ('TransferFunction', ),
190 0x0131: ('Software', ),
191 0x0132: ('DateTime', ),
192 0x013B: ('Artist', ),
193 0x013E: ('WhitePoint', ),
194 0x013F: ('PrimaryChromaticities', ),
195 0x0156: ('TransferRange', ),
196 0x0200: ('JPEGProc', ),
197 0x0201: ('JPEGInterchangeFormat', ),
198 0x0202: ('JPEGInterchangeFormatLength', ),
199 0x0211: ('YCbCrCoefficients', ),
200 0x0212: ('YCbCrSubSampling', ),
201 0x0213: ('YCbCrPositioning',
204 0x0214: ('ReferenceBlackWhite', ),
206 0x4746: ('Rating', ),
208 0x828D: ('CFARepeatPatternDim', ),
209 0x828E: ('CFAPattern', ),
210 0x828F: ('BatteryLevel', ),
211 0x8298: ('Copyright', ),
212 0x829A: ('ExposureTime', ),
213 0x829D: ('FNumber', ),
214 0x83BB: ('IPTC/NAA', ),
215 0x8769: ('ExifOffset', ),
216 0x8773: ('InterColorProfile', ),
217 0x8822: ('ExposureProgram',
221 3: 'Aperture Priority',
222 4: 'Shutter Priority',
223 5: 'Program Creative',
226 8: 'Landscape Mode'}),
227 0x8824: ('SpectralSensitivity', ),
228 0x8825: ('GPSInfo', ),
229 0x8827: ('ISOSpeedRatings', ),
231 0x9000: ('ExifVersion', make_string
),
232 0x9003: ('DateTimeOriginal', ),
233 0x9004: ('DateTimeDigitized', ),
234 0x9101: ('ComponentsConfiguration',
242 0x9102: ('CompressedBitsPerPixel', ),
243 0x9201: ('ShutterSpeedValue', ),
244 0x9202: ('ApertureValue', ),
245 0x9203: ('BrightnessValue', ),
246 0x9204: ('ExposureBiasValue', ),
247 0x9205: ('MaxApertureValue', ),
248 0x9206: ('SubjectDistance', ),
249 0x9207: ('MeteringMode',
252 2: 'CenterWeightedAverage',
258 0x9208: ('LightSource',
262 3: 'Tungsten (incandescent light)',
265 10: 'Cloudy weather',
267 12: 'Daylight fluorescent (D 5700 - 7100K)',
268 13: 'Day white fluorescent (N 4600 - 5400K)',
269 14: 'Cool white fluorescent (W 3900 - 4500K)',
270 15: 'White fluorescent (WW 3200 - 3700K)',
271 17: 'Standard light A',
272 18: 'Standard light B',
273 19: 'Standard light C',
278 24: 'ISO studio tungsten',
279 255: 'other light source',}),
281 {0: 'Flash did not fire',
283 5: 'Strobe return light not detected',
284 7: 'Strobe return light detected',
285 9: 'Flash fired, compulsory flash mode',
286 13: 'Flash fired, compulsory flash mode, return light not detected',
287 15: 'Flash fired, compulsory flash mode, return light detected',
288 16: 'Flash did not fire, compulsory flash mode',
289 24: 'Flash did not fire, auto mode',
290 25: 'Flash fired, auto mode',
291 29: 'Flash fired, auto mode, return light not detected',
292 31: 'Flash fired, auto mode, return light detected',
293 32: 'No flash function',
294 65: 'Flash fired, red-eye reduction mode',
295 69: 'Flash fired, red-eye reduction mode, return light not detected',
296 71: 'Flash fired, red-eye reduction mode, return light detected',
297 73: 'Flash fired, compulsory flash mode, red-eye reduction mode',
298 77: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
299 79: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
300 89: 'Flash fired, auto mode, red-eye reduction mode',
301 93: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
302 95: 'Flash fired, auto mode, return light detected, red-eye reduction mode'}),
303 0x920A: ('FocalLength', ),
304 0x9214: ('SubjectArea', ),
305 0x927C: ('MakerNote', ),
306 0x9286: ('UserComment', make_string_uc
),
307 0x9290: ('SubSecTime', ),
308 0x9291: ('SubSecTimeOriginal', ),
309 0x9292: ('SubSecTimeDigitized', ),
311 # used by Windows Explorer
312 0x9C9B: ('XPTitle', ),
313 0x9C9C: ('XPComment', ),
314 0x9C9D: ('XPAuthor', ), #(ignored by Windows Explorer if Artist exists)
315 0x9C9E: ('XPKeywords', ),
316 0x9C9F: ('XPSubject', ),
318 0xA000: ('FlashPixVersion', make_string
),
319 0xA001: ('ColorSpace',
322 65535: 'Uncalibrated'}),
323 0xA002: ('ExifImageWidth', ),
324 0xA003: ('ExifImageLength', ),
325 0xA005: ('InteroperabilityOffset', ),
326 0xA20B: ('FlashEnergy', ), # 0x920B in TIFF/EP
327 0xA20C: ('SpatialFrequencyResponse', ), # 0x920C
328 0xA20E: ('FocalPlaneXResolution', ), # 0x920E
329 0xA20F: ('FocalPlaneYResolution', ), # 0x920F
330 0xA210: ('FocalPlaneResolutionUnit', ), # 0x9210
331 0xA214: ('SubjectLocation', ), # 0x9214
332 0xA215: ('ExposureIndex', ), # 0x9215
333 0xA217: ('SensingMethod', # 0x9217
335 2: 'One-chip color area',
336 3: 'Two-chip color area',
337 4: 'Three-chip color area',
338 5: 'Color sequential area',
340 8: 'Color sequential linear'}),
341 0xA300: ('FileSource',
343 2: 'Reflection Print Scanner',
344 3: 'Digital Camera'}),
345 0xA301: ('SceneType',
346 {1: 'Directly Photographed'}),
347 0xA302: ('CVAPattern', ),
348 0xA401: ('CustomRendered',
351 0xA402: ('ExposureMode',
353 1: 'Manual Exposure',
355 0xA403: ('WhiteBalance',
358 0xA404: ('DigitalZoomRatio', ),
359 0xA405: ('FocalLengthIn35mmFilm', ),
360 0xA406: ('SceneCaptureType',
365 0xA407: ('GainControl',
370 4: 'High gain down'}),
375 0xA409: ('Saturation',
379 0xA40A: ('Sharpness',
383 0xA40B: ('DeviceSettingDescription', ),
384 0xA40C: ('SubjectDistanceRange', ),
386 0xC4A5: ('PrintIM', ),
387 0xEA1C: ('Padding', ),
390 # interoperability tags
392 0x0001: ('InteroperabilityIndex', ),
393 0x0002: ('InteroperabilityVersion', ),
394 0x1000: ('RelatedImageFileFormat', ),
395 0x1001: ('RelatedImageWidth', ),
396 0x1002: ('RelatedImageLength', ),
399 # GPS tags (not used yet, haven't seen camera with GPS)
401 0x0000: ('GPSVersionID', ),
402 0x0001: ('GPSLatitudeRef', ),
403 0x0002: ('GPSLatitude', ),
404 0x0003: ('GPSLongitudeRef', ),
405 0x0004: ('GPSLongitude', ),
406 0x0005: ('GPSAltitudeRef', ),
407 0x0006: ('GPSAltitude', ),
408 0x0007: ('GPSTimeStamp', ),
409 0x0008: ('GPSSatellites', ),
410 0x0009: ('GPSStatus', ),
411 0x000A: ('GPSMeasureMode', ),
412 0x000B: ('GPSDOP', ),
413 0x000C: ('GPSSpeedRef', ),
414 0x000D: ('GPSSpeed', ),
415 0x000E: ('GPSTrackRef', ),
416 0x000F: ('GPSTrack', ),
417 0x0010: ('GPSImgDirectionRef', ),
418 0x0011: ('GPSImgDirection', ),
419 0x0012: ('GPSMapDatum', ),
420 0x0013: ('GPSDestLatitudeRef', ),
421 0x0014: ('GPSDestLatitude', ),
422 0x0015: ('GPSDestLongitudeRef', ),
423 0x0016: ('GPSDestLongitude', ),
424 0x0017: ('GPSDestBearingRef', ),
425 0x0018: ('GPSDestBearing', ),
426 0x0019: ('GPSDestDistanceRef', ),
427 0x001A: ('GPSDestDistance', ),
428 0x001B: ('GPSProcessingMethod', ),
429 0x001C: ('GPSAreaInformation', ),
430 0x001D: ('GPSDate', ),
431 0x001E: ('GPSDifferential', ),
434 # Ignore these tags when quick processing
435 # 0x927C is MakerNote Tags
436 # 0x9286 is user comment
437 IGNORE_TAGS
=(0x9286, 0x927C)
439 # http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp
440 def nikon_ev_bias(seq
):
441 # First digit seems to be in steps of 1/6 EV.
442 # Does the third value mean the step size? It is usually 6,
443 # but it is 12 for the ExposureDifference.
445 # Check for an error condition that could cause a crash.
446 # This only happens if something has gone really wrong in
447 # reading the Nikon MakerNote.
448 if len( seq
) < 4 : return ""
450 if seq
== [252, 1, 6, 0]:
452 if seq
== [253, 1, 6, 0]:
454 if seq
== [254, 1, 6, 0]:
456 if seq
== [0, 1, 6, 0]:
458 if seq
== [2, 1, 6, 0]:
460 if seq
== [3, 1, 6, 0]:
462 if seq
== [4, 1, 6, 0]:
464 # Handle combinations not in the table.
466 # Causes headaches for the +/- logic, so special case it.
474 b
= seq
[2] # Assume third value means the step size
478 ret_str
= ret_str
+ str(whole
) + " "
480 ret_str
= ret_str
+ "EV"
483 ret_str
= ret_str
+ r
.__repr
__() + " EV"
486 # Nikon E99x MakerNote Tags
487 MAKERNOTE_NIKON_NEWER_TAGS
={
488 0x0001: ('MakernoteVersion', make_string
), # Sometimes binary
489 0x0002: ('ISOSetting', make_string
),
490 0x0003: ('ColorMode', ),
491 0x0004: ('Quality', ),
492 0x0005: ('Whitebalance', ),
493 0x0006: ('ImageSharpening', ),
494 0x0007: ('FocusMode', ),
495 0x0008: ('FlashSetting', ),
496 0x0009: ('AutoFlashMode', ),
497 0x000B: ('WhiteBalanceBias', ),
498 0x000C: ('WhiteBalanceRBCoeff', ),
499 0x000D: ('ProgramShift', nikon_ev_bias
),
500 # Nearly the same as the other EV vals, but step size is 1/12 EV (?)
501 0x000E: ('ExposureDifference', nikon_ev_bias
),
502 0x000F: ('ISOSelection', ),
503 0x0011: ('NikonPreview', ),
504 0x0012: ('FlashCompensation', nikon_ev_bias
),
505 0x0013: ('ISOSpeedRequested', ),
506 0x0016: ('PhotoCornerCoordinates', ),
507 # 0x0017: Unknown, but most likely an EV value
508 0x0018: ('FlashBracketCompensationApplied', nikon_ev_bias
),
509 0x0019: ('AEBracketCompensationApplied', ),
510 0x001A: ('ImageProcessing', ),
511 0x001B: ('CropHiSpeed', ),
512 0x001D: ('SerialNumber', ), # Conflict with 0x00A0 ?
513 0x001E: ('ColorSpace', ),
514 0x001F: ('VRInfo', ),
515 0x0020: ('ImageAuthentication', ),
516 0x0022: ('ActiveDLighting', ),
517 0x0023: ('PictureControl', ),
518 0x0024: ('WorldTime', ),
519 0x0025: ('ISOInfo', ),
520 0x0080: ('ImageAdjustment', ),
521 0x0081: ('ToneCompensation', ),
522 0x0082: ('AuxiliaryLens', ),
523 0x0083: ('LensType', ),
524 0x0084: ('LensMinMaxFocalMaxAperture', ),
525 0x0085: ('ManualFocusDistance', ),
526 0x0086: ('DigitalZoomFactor', ),
527 0x0087: ('FlashMode',
528 {0x00: 'Did Not Fire',
529 0x01: 'Fired, Manual',
530 0x07: 'Fired, External',
531 0x08: 'Fired, Commander Mode ',
532 0x09: 'Fired, TTL Mode'}),
533 0x0088: ('AFFocusPosition',
539 0x0089: ('BracketingMode',
540 {0x00: 'Single frame, no bracketing',
541 0x01: 'Continuous, no bracketing',
542 0x02: 'Timer, no bracketing',
543 0x10: 'Single frame, exposure bracketing',
544 0x11: 'Continuous, exposure bracketing',
545 0x12: 'Timer, exposure bracketing',
546 0x40: 'Single frame, white balance bracketing',
547 0x41: 'Continuous, white balance bracketing',
548 0x42: 'Timer, white balance bracketing'}),
549 0x008A: ('AutoBracketRelease', ),
550 0x008B: ('LensFStops', ),
551 0x008C: ('NEFCurve1', ), # ExifTool calls this 'ContrastCurve'
552 0x008D: ('ColorMode', ),
553 0x008F: ('SceneMode', ),
554 0x0090: ('LightingType', ),
555 0x0091: ('ShotInfo', ), # First 4 bytes are a version number in ASCII
556 0x0092: ('HueAdjustment', ),
557 # ExifTool calls this 'NEFCompression', should be 1-4
558 0x0093: ('Compression', ),
559 0x0094: ('Saturation',
566 0x0095: ('NoiseReduction', ),
567 0x0096: ('NEFCurve2', ), # ExifTool calls this 'LinearizationTable'
568 0x0097: ('ColorBalance', ), # First 4 bytes are a version number in ASCII
569 0x0098: ('LensData', ), # First 4 bytes are a version number in ASCII
570 0x0099: ('RawImageCenter', ),
571 0x009A: ('SensorPixelSize', ),
572 0x009C: ('Scene Assist', ),
573 0x009E: ('RetouchHistory', ),
574 0x00A0: ('SerialNumber', ),
575 0x00A2: ('ImageDataSize', ),
576 # 00A3: unknown - a single byte 0
577 # 00A4: In NEF, looks like a 4 byte ASCII version number ('0200')
578 0x00A5: ('ImageCount', ),
579 0x00A6: ('DeletedImageCount', ),
580 0x00A7: ('TotalShutterReleases', ),
581 # First 4 bytes are a version number in ASCII, with version specific
582 # info to follow. Its hard to treat it as a string due to embedded nulls.
583 0x00A8: ('FlashInfo', ),
584 0x00A9: ('ImageOptimization', ),
585 0x00AA: ('Saturation', ),
586 0x00AB: ('DigitalVariProgram', ),
587 0x00AC: ('ImageStabilization', ),
588 0x00AD: ('Responsive AF', ), # 'AFResponse'
589 0x00B0: ('MultiExposure', ),
590 0x00B1: ('HighISONoiseReduction', ),
591 0x00B7: ('AFInfo', ),
592 0x00B8: ('FileInfo', ),
594 0x0100: ('DigitalICE', ),
595 0x0103: ('PreviewCompression',
601 6: 'JPEG (old-style)',
607 32769: 'Epson ERF Compressed',
610 32809: 'Thunderscan',
623 34713: 'Nikon NEF Compressed',
624 65000: 'Kodak DCR Compressed',
625 65535: 'Pentax PEF Compressed',}),
626 0x0201: ('PreviewImageStart', ),
627 0x0202: ('PreviewImageLength', ),
628 0x0213: ('PreviewYCbCrPositioning',
631 0x0010: ('DataDump', ),
634 MAKERNOTE_NIKON_OLDER_TAGS
= {
642 0x0004: ('ColorMode',
645 0x0005: ('ImageAdjustment',
656 0x0007: ('WhiteBalance',
666 # decode Olympus SpecialMode tag in MakerNote
667 def olympus_special_mode(v
):
679 if v
[0] not in a
or v
[2] not in b
:
681 return '%s - sequence %d - %s' % (a
[v
[0]], v
[1], b
[v
[2]])
683 MAKERNOTE_OLYMPUS_TAGS
={
684 # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
685 # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
686 0x0100: ('JPEGThumbnail', ),
687 0x0200: ('SpecialMode', olympus_special_mode
),
699 0x0204: ('DigitalZoom', ),
700 0x0205: ('FocalPlaneDiagonal', ),
701 0x0206: ('LensDistortionParams', ),
702 0x0207: ('SoftwareRelease', ),
703 0x0208: ('PictureInfo', ),
704 0x0209: ('CameraID', make_string
), # print as string
705 0x0F00: ('DataDump', ),
706 0x0300: ('PreCaptureFrames', ),
707 0x0404: ('SerialNumber', ),
708 0x1000: ('ShutterSpeedValue', ),
709 0x1001: ('ISOValue', ),
710 0x1002: ('ApertureValue', ),
711 0x1003: ('BrightnessValue', ),
712 0x1004: ('FlashMode', ),
713 0x1004: ('FlashMode',
716 0x1005: ('FlashDevice',
720 5: 'Internal + External'}),
721 0x1006: ('ExposureCompensation', ),
722 0x1007: ('SensorTemperature', ),
723 0x1008: ('LensTemperature', ),
724 0x100b: ('FocusMode',
727 0x1017: ('RedBalance', ),
728 0x1018: ('BlueBalance', ),
729 0x101a: ('SerialNumber', ),
730 0x1023: ('FlashExposureComp', ),
731 0x1026: ('ExternalFlashBounce',
734 0x1027: ('ExternalFlashZoom', ),
735 0x1028: ('ExternalFlashMode', ),
736 0x1029: ('Contrast int16u',
740 0x102a: ('SharpnessFactor', ),
741 0x102b: ('ColorControl', ),
742 0x102c: ('ValidBits', ),
743 0x102d: ('CoringFilter', ),
744 0x102e: ('OlympusImageWidth', ),
745 0x102f: ('OlympusImageHeight', ),
746 0x1034: ('CompressionRatio', ),
747 0x1035: ('PreviewImageValid',
750 0x1036: ('PreviewImageStart', ),
751 0x1037: ('PreviewImageLength', ),
752 0x1039: ('CCDScanMode',
755 0x103a: ('NoiseReduction',
758 0x103b: ('InfinityLensStep', ),
759 0x103c: ('NearLensStep', ),
761 # TODO - these need extra definitions
762 # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html
763 0x2010: ('Equipment', ),
764 0x2020: ('CameraSettings', ),
765 0x2030: ('RawDevelopment', ),
766 0x2040: ('ImageProcessing', ),
767 0x2050: ('FocusInfo', ),
768 0x3000: ('RawInfo ', ),
771 # 0x2020 CameraSettings
772 MAKERNOTE_OLYMPUS_TAG_0x2020
={
773 0x0100: ('PreviewImageValid',
776 0x0101: ('PreviewImageStart', ),
777 0x0102: ('PreviewImageLength', ),
778 0x0200: ('ExposureMode',
781 3: 'Aperture-priority AE',
782 4: 'Shutter speed priority AE',
783 5: 'Program-shift'}),
787 0x0202: ('MeteringMode',
788 {2: 'Center Weighted',
792 515: 'Spot+Highlight control',
793 1027: 'Spot+Shadow control'}),
794 0x0300: ('MacroMode',
797 0x0301: ('FocusMode',
799 1: 'Sequential shooting AF',
803 0x0302: ('FocusProcess',
809 0x0304: ('AFAreas', ),
810 0x0401: ('FlashExposureCompensation', ),
811 0x0500: ('WhiteBalance2',
813 16: '7500K (Fine Weather with Shade)',
814 17: '6000K (Cloudy)',
815 18: '5300K (Fine Weather)',
816 20: '3000K (Tungsten light)',
817 21: '3600K (Tungsten light-like)',
818 33: '6600K (Daylight fluorescent)',
819 34: '4500K (Neutral white fluorescent)',
820 35: '4000K (Cool white fluorescent)',
821 48: '3600K (Tungsten light-like)',
826 512: 'Custom WB 5400K',
827 513: 'Custom WB 2900K',
828 514: 'Custom WB 8000K', }),
829 0x0501: ('WhiteBalanceTemperature', ),
830 0x0502: ('WhiteBalanceBracket', ),
831 0x0503: ('CustomSaturation', ), # (3 numbers: 1. CS Value, 2. Min, 3. Max)
832 0x0504: ('ModifiedSaturation',
834 1: 'CM1 (Red Enhance)',
835 2: 'CM2 (Green Enhance)',
836 3: 'CM3 (Blue Enhance)',
837 4: 'CM4 (Skin Tones)'}),
838 0x0505: ('ContrastSetting', ), # (3 numbers: 1. Contrast, 2. Min, 3. Max)
839 0x0506: ('SharpnessSetting', ), # (3 numbers: 1. Sharpness, 2. Min, 3. Max)
840 0x0507: ('ColorSpace',
843 2: 'Pro Photo RGB'}),
844 0x0509: ('SceneMode',
849 9: 'Landscape+Portrait',
853 16: 'Landscape+Portrait',
854 17: 'Night+Portrait',
862 35: 'Underwater Wide1',
863 36: 'Underwater Macro',
865 40: 'Digital Image Stabilization',
866 44: 'Underwater Wide2',
869 48: 'Nature Macro'}),
870 0x050a: ('NoiseReduction',
872 1: 'Noise Reduction',
874 3: 'Noise Reduction + Noise Filter',
875 4: 'Noise Filter (ISO Boost)',
876 5: 'Noise Reduction + Noise Filter (ISO Boost)'}),
877 0x050b: ('DistortionCorrection',
880 0x050c: ('ShadingCompensation',
883 0x050d: ('CompressionFactor', ),
884 0x050f: ('Gradation',
885 {'-1 -1 1': 'Low Key',
887 '1 -1 1': 'High Key'}),
888 0x0520: ('PictureMode',
894 0x0521: ('PictureModeSaturation', ),
895 0x0522: ('PictureModeHue?', ),
896 0x0523: ('PictureModeContrast', ),
897 0x0524: ('PictureModeSharpness', ),
898 0x0525: ('PictureModeBWFilter',
905 0x0526: ('PictureModeTone',
912 0x0600: ('Sequence', ), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits
913 0x0601: ('PanoramaMode', ), # (2 numbers: 1. Mode, 2. Shot number)
914 0x0603: ('ImageQuality2',
919 0x0901: ('ManometerReading', ),
923 MAKERNOTE_CASIO_TAGS
={
924 0x0001: ('RecordingMode',
925 {1: 'Single Shutter',
934 0x0003: ('FocusingMode',
939 0x0004: ('FlashMode',
943 4: 'Red Eye Reduction'}),
944 0x0005: ('FlashIntensity',
948 0x0006: ('Object Distance', ),
949 0x0007: ('WhiteBalance',
956 0x000B: ('Sharpness',
964 0x000D: ('Saturation',
977 MAKERNOTE_FUJIFILM_TAGS
={
978 0x0000: ('NoteVersion', make_string
),
979 0x1000: ('Quality', ),
980 0x1001: ('Sharpness',
986 0x1002: ('WhiteBalance',
990 768: 'DaylightColor-Fluorescent',
991 769: 'DaywhiteColor-Fluorescent',
992 770: 'White-Fluorescent',
993 1024: 'Incandescent',
1003 0x1010: ('FlashMode',
1007 3: 'Red Eye Reduction'}),
1008 0x1011: ('FlashStrength', ),
1012 0x1021: ('FocusMode',
1015 0x1030: ('SlowSync',
1018 0x1031: ('PictureMode',
1025 256: 'Aperture Priority AE',
1026 512: 'Shutter Priority AE',
1027 768: 'Manual Exposure'}),
1028 0x1100: ('MotorOrBracket',
1031 0x1300: ('BlurWarning',
1034 0x1301: ('FocusWarning',
1037 0x1302: ('AEWarning',
1042 MAKERNOTE_CANON_TAGS
= {
1043 0x0006: ('ImageType', ),
1044 0x0007: ('FirmwareVersion', ),
1045 0x0008: ('ImageNumber', ),
1046 0x0009: ('OwnerName', ),
1049 # this is in element offset, name, optional value dictionary format
1050 MAKERNOTE_CANON_TAG_0x001
= {
1060 {0: 'Flash Not Fired',
1063 3: 'Red-Eye Reduction',
1065 5: 'Auto + Red-Eye Reduction',
1066 6: 'On + Red-Eye Reduction',
1067 16: 'external flash'}),
1068 5: ('ContinuousDriveMode',
1069 {0: 'Single Or Timer',
1083 11: ('EasyShootingMode',
1094 10: 'Macro/Close-Up',
1113 {0: 'See ISOSpeedRatings Tag',
1119 17: ('MeteringMode',
1122 5: 'Center-weighted'}),
1126 3: 'Close-Up (Macro)',
1127 8: 'Locked (Pan Mode)'}),
1128 19: ('AFPointSelected',
1129 {0x3000: 'None (MF)',
1130 0x3001: 'Auto-Selected',
1134 20: ('ExposureMode',
1135 {0: 'Easy Shooting',
1141 23: ('LongFocalLengthOfLensInFocalUnits', ),
1142 24: ('ShortFocalLengthOfLensInFocalUnits', ),
1143 25: ('FocalUnitsPerMM', ),
1144 28: ('FlashActivity',
1147 29: ('FlashDetails',
1148 {14: 'External E-TTL',
1149 13: 'Internal Flash',
1151 7: '2nd("Rear")-Curtain Sync Used',
1152 4: 'FP Sync Enabled'}),
1158 MAKERNOTE_CANON_TAG_0x004
= {
1167 9: ('SequenceNumber', ),
1168 14: ('AFPointUsed', ),
1187 19: ('SubjectDistance', ),
1190 # extract multibyte integer in Motorola format (little endian)
1191 def s2n_motorola(str):
1194 x
= (x
<< 8) |
ord(c
)
1197 # extract multibyte integer in Intel format (big endian)
1202 x
= x |
(ord(c
) << y
)
1206 # ratio object that eventually will be able to reduce itself to lowest
1207 # common denominator for printing
1212 return gcd(b
, a
% b
)
1215 def __init__(self
, num
, den
):
1222 return str(self
.num
)
1223 return '%d/%d' % (self
.num
, self
.den
)
1226 div
= gcd(self
.num
, self
.den
)
1228 self
.num
= self
.num
/ div
1229 self
.den
= self
.den
/ div
1231 # for ease of dealing with tags
1233 def __init__(self
, printable
, tag
, field_type
, values
, field_offset
,
1235 # printable version of data
1236 self
.printable
= printable
1239 # field type as index into FIELD_TYPES
1240 self
.field_type
= field_type
1241 # offset of start of field in bytes from beginning of IFD
1242 self
.field_offset
= field_offset
1243 # length of data field in bytes
1244 self
.field_length
= field_length
1245 # either a string or array of data items
1246 self
.values
= values
1249 return self
.printable
1253 s
= '(0x%04X) %s=%s @ %d' % (self
.tag
,
1254 FIELD_TYPES
[self
.field_type
][2],
1258 s
= '(%s) %s=%s @ %s' % (str(self
.tag
),
1259 FIELD_TYPES
[self
.field_type
][2],
1261 str(self
.field_offset
))
1264 # class that handles an EXIF header
1266 def __init__(self
, file, endian
, offset
, fake_exif
, strict
, debug
=0):
1268 self
.endian
= endian
1269 self
.offset
= offset
1270 self
.fake_exif
= fake_exif
1271 self
.strict
= strict
1275 # convert slice to integer, based on sign and endian flags
1276 # usually this offset is assumed to be relative to the beginning of the
1277 # start of the EXIF information. For some cameras that use relative tags,
1278 # this offset may be relative to some other starting point.
1279 def s2n(self
, offset
, length
, signed
=0):
1280 self
.file.seek(self
.offset
+offset
)
1281 slice=self
.file.read(length
)
1282 if self
.endian
== 'I':
1283 val
=s2n_intel(slice)
1285 val
=s2n_motorola(slice)
1288 msb
=1L << (8*length
-1)
1293 # convert offset to string
1294 def n2s(self
, offset
, length
):
1296 for dummy
in range(length
):
1297 if self
.endian
== 'I':
1298 s
= s
+ chr(offset
& 0xFF)
1300 s
= chr(offset
& 0xFF) + s
1301 offset
= offset
>> 8
1305 def first_IFD(self
):
1306 return self
.s2n(4, 4)
1308 # return pointer to next IFD
1309 def next_IFD(self
, ifd
):
1310 entries
=self
.s2n(ifd
, 2)
1311 next_ifd
= self
.s2n(ifd
+2+12*entries
, 4)
1317 # return list of IFDs in header
1318 def list_IFDs(self
):
1326 # return list of entries in this IFD
1327 def dump_IFD(self
, ifd
, ifd_name
, dict=EXIF_TAGS
, relative
=0, stop_tag
='UNDEF'):
1328 entries
=self
.s2n(ifd
, 2)
1329 for i
in range(entries
):
1330 # entry is index of start of this IFD in the file
1331 entry
= ifd
+ 2 + 12 * i
1332 tag
= self
.s2n(entry
, 2)
1334 # get tag name early to avoid errors, help debug
1335 tag_entry
= dict.get(tag
)
1337 tag_name
= tag_entry
[0]
1339 tag_name
= 'Tag 0x%04X' % tag
1341 # ignore certain tags for faster processing
1342 if not (not detailed
and tag
in IGNORE_TAGS
):
1343 field_type
= self
.s2n(entry
+ 2, 2)
1345 # unknown field type
1346 if not 0 < field_type
< len(FIELD_TYPES
):
1350 raise ValueError('unknown type %d in tag 0x%04X' % (field_type
, tag
))
1352 typelen
= FIELD_TYPES
[field_type
][0]
1353 count
= self
.s2n(entry
+ 4, 4)
1354 # Adjust for tag id/type/count (2+2+4 bytes)
1355 # Now we point at either the data or the 2nd level offset
1358 # If the value fits in 4 bytes, it is inlined, else we
1359 # need to jump ahead again.
1360 if count
* typelen
> 4:
1361 # offset is not the value; it's a pointer to the value
1362 # if relative we set things up so s2n will seek to the right
1363 # place when it adds self.offset. Note that this 'relative'
1364 # is for the Nikon type 3 makernote. Other cameras may use
1365 # other relative offsets, which would have to be computed here
1366 # slightly differently.
1368 tmp_offset
= self
.s2n(offset
, 4)
1369 offset
= tmp_offset
+ ifd
- 8
1371 offset
= offset
+ 18
1373 offset
= self
.s2n(offset
, 4)
1375 field_offset
= offset
1377 # special case: null-terminated ASCII string
1379 # sometimes gets too big to fit in int value
1380 if count
!= 0: # and count < (2**31): # 2E31 is hardware dependant. --gd
1382 self
.file.seek(self
.offset
+ offset
)
1383 values
= self
.file.read(count
)
1385 # Drop any garbage after a null.
1386 values
= values
.split('\x00', 1)[0]
1387 except OverflowError:
1391 signed
= (field_type
in [6, 8, 9, 10])
1394 # some entries get too big to handle could be malformed
1395 # file or problem with self.s2n
1397 for dummy
in range(count
):
1398 if field_type
in (5, 10):
1400 value
= Ratio(self
.s2n(offset
, 4, signed
),
1401 self
.s2n(offset
+ 4, 4, signed
))
1403 value
= self
.s2n(offset
, typelen
, signed
)
1404 values
.append(value
)
1405 offset
= offset
+ typelen
1406 # The test above causes problems with tags that are
1407 # supposed to have long values! Fix up one important case.
1408 elif tag_name
== 'MakerNote' :
1409 for dummy
in range(count
):
1410 value
= self
.s2n(offset
, typelen
, signed
)
1411 values
.append(value
)
1412 offset
= offset
+ typelen
1414 # print "Warning: dropping large tag:", tag, tag_name
1416 # now 'values' is either a string or an array
1417 if count
== 1 and field_type
!= 2:
1418 printable
=str(values
[0])
1419 elif count
> 50 and len(values
) > 20 :
1420 printable
=str( values
[0:20] )[0:-1] + ", ... ]"
1422 printable
=str(values
)
1424 # compute printable version of values
1426 if len(tag_entry
) != 1:
1427 # optional 2nd tag element is present
1428 if callable(tag_entry
[1]):
1429 # call mapping function
1430 printable
= tag_entry
[1](values
)
1434 # use lookup table for this tag
1435 printable
+= tag_entry
[1].get(i
, repr(i
))
1437 self
.tags
[ifd_name
+ ' ' + tag_name
] = IFD_Tag(printable
, tag
,
1439 values
, field_offset
,
1442 print ' debug: %s: %s' % (tag_name
,
1443 repr(self
.tags
[ifd_name
+ ' ' + tag_name
]))
1445 if tag_name
== stop_tag
:
1448 # extract uncompressed TIFF thumbnail (like pulling teeth)
1449 # we take advantage of the pre-existing layout in the thumbnail IFD as
1451 def extract_TIFF_thumbnail(self
, thumb_ifd
):
1452 entries
= self
.s2n(thumb_ifd
, 2)
1453 # this is header plus offset to IFD ...
1454 if self
.endian
== 'M':
1455 tiff
= 'MM\x00*\x00\x00\x00\x08'
1457 tiff
= 'II*\x00\x08\x00\x00\x00'
1458 # ... plus thumbnail IFD data plus a null "next IFD" pointer
1459 self
.file.seek(self
.offset
+thumb_ifd
)
1460 tiff
+= self
.file.read(entries
*12+2)+'\x00\x00\x00\x00'
1462 # fix up large value offset pointers into data area
1463 for i
in range(entries
):
1464 entry
= thumb_ifd
+ 2 + 12 * i
1465 tag
= self
.s2n(entry
, 2)
1466 field_type
= self
.s2n(entry
+2, 2)
1467 typelen
= FIELD_TYPES
[field_type
][0]
1468 count
= self
.s2n(entry
+4, 4)
1469 oldoff
= self
.s2n(entry
+8, 4)
1470 # start of the 4-byte pointer area in entry
1472 # remember strip offsets location
1475 strip_len
= count
* typelen
1476 # is it in the data area?
1477 if count
* typelen
> 4:
1478 # update offset pointer (nasty "strings are immutable" crap)
1479 # should be able to say "tiff[ptr:ptr+4]=newoff"
1481 tiff
= tiff
[:ptr
] + self
.n2s(newoff
, 4) + tiff
[ptr
+4:]
1482 # remember strip offsets location
1486 # get original data and store it
1487 self
.file.seek(self
.offset
+ oldoff
)
1488 tiff
+= self
.file.read(count
* typelen
)
1490 # add pixel strips and update strip offset info
1491 old_offsets
= self
.tags
['Thumbnail StripOffsets'].values
1492 old_counts
= self
.tags
['Thumbnail StripByteCounts'].values
1493 for i
in range(len(old_offsets
)):
1494 # update offset pointer (more nasty "strings are immutable" crap)
1495 offset
= self
.n2s(len(tiff
), strip_len
)
1496 tiff
= tiff
[:strip_off
] + offset
+ tiff
[strip_off
+ strip_len
:]
1497 strip_off
+= strip_len
1498 # add pixel strip to end
1499 self
.file.seek(self
.offset
+ old_offsets
[i
])
1500 tiff
+= self
.file.read(old_counts
[i
])
1502 self
.tags
['TIFFThumbnail'] = tiff
1504 # decode all the camera-specific MakerNote formats
1506 # Note is the data that comprises this MakerNote. The MakerNote will
1507 # likely have pointers in it that point to other parts of the file. We'll
1508 # use self.offset as the starting point for most of those pointers, since
1509 # they are relative to the beginning of the file.
1511 # If the MakerNote is in a newer format, it may use relative addressing
1512 # within the MakerNote. In that case we'll use relative addresses for the
1515 # As an aside: it's not just to be annoying that the manufacturers use
1516 # relative offsets. It's so that if the makernote has to be moved by the
1517 # picture software all of the offsets don't have to be adjusted. Overall,
1518 # this is probably the right strategy for makernotes, though the spec is
1519 # ambiguous. (The spec does not appear to imagine that makernotes would
1520 # follow EXIF format internally. Once they did, it's ambiguous whether
1521 # the offsets should be from the header at the start of all the EXIF info,
1522 # or from the header at the start of the makernote.)
1523 def decode_maker_note(self
):
1524 note
= self
.tags
['EXIF MakerNote']
1526 # Some apps use MakerNote tags but do not use a format for which we
1527 # have a description, so just do a raw dump for these.
1528 #if self.tags.has_key('Image Make'):
1529 make
= self
.tags
['Image Make'].printable
1533 # model = self.tags['Image Model'].printable # unused
1536 # The maker note usually starts with the word Nikon, followed by the
1537 # type of the makernote (1 or 2, as a short). If the word Nikon is
1538 # not at the start of the makernote, it's probably type 2, since some
1539 # cameras work that way.
1541 if note
.values
[0:7] == [78, 105, 107, 111, 110, 0, 1]:
1543 print "Looks like a type 1 Nikon MakerNote."
1544 self
.dump_IFD(note
.field_offset
+8, 'MakerNote',
1545 dict=MAKERNOTE_NIKON_OLDER_TAGS
)
1546 elif note
.values
[0:7] == [78, 105, 107, 111, 110, 0, 2]:
1548 print "Looks like a labeled type 2 Nikon MakerNote"
1549 if note
.values
[12:14] != [0, 42] and note
.values
[12:14] != [42L, 0L]:
1550 raise ValueError("Missing marker tag '42' in MakerNote.")
1551 # skip the Makernote label and the TIFF header
1552 self
.dump_IFD(note
.field_offset
+10+8, 'MakerNote',
1553 dict=MAKERNOTE_NIKON_NEWER_TAGS
, relative
=1)
1557 print "Looks like an unlabeled type 2 Nikon MakerNote"
1558 self
.dump_IFD(note
.field_offset
, 'MakerNote',
1559 dict=MAKERNOTE_NIKON_NEWER_TAGS
)
1563 if make
.startswith('OLYMPUS'):
1564 self
.dump_IFD(note
.field_offset
+8, 'MakerNote',
1565 dict=MAKERNOTE_OLYMPUS_TAGS
)
1567 #for i in (('MakerNote Tag 0x2020', MAKERNOTE_OLYMPUS_TAG_0x2020),):
1568 # self.decode_olympus_tag(self.tags[i[0]].values, i[1])
1572 if 'CASIO' in make
or 'Casio' in make
:
1573 self
.dump_IFD(note
.field_offset
, 'MakerNote',
1574 dict=MAKERNOTE_CASIO_TAGS
)
1578 if make
== 'FUJIFILM':
1579 # bug: everything else is "Motorola" endian, but the MakerNote
1581 endian
= self
.endian
1583 # bug: IFD offsets are from beginning of MakerNote, not
1584 # beginning of file header
1585 offset
= self
.offset
1586 self
.offset
+= note
.field_offset
1587 # process note with bogus values (note is actually at offset 12)
1588 self
.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS
)
1589 # reset to correct values
1590 self
.endian
= endian
1591 self
.offset
= offset
1596 self
.dump_IFD(note
.field_offset
, 'MakerNote',
1597 dict=MAKERNOTE_CANON_TAGS
)
1598 for i
in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001
),
1599 ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004
)):
1600 if i
[0] in self
.tags
:
1601 self
.canon_decode_tag(self
.tags
[i
[0]].values
, i
[1])
1605 # XXX TODO decode Olympus MakerNote tag based on offset within tag
1606 def olympus_decode_tag(self
, value
, dict):
1609 # decode Canon MakerNote tag based on offset within tag
1610 # see http://www.burren.cx/david/canon.html by David Burren
1611 def canon_decode_tag(self
, value
, dict):
1612 for i
in range(1, len(value
)):
1613 x
=dict.get(i
, ('Unknown', ))
1618 val
=x
[1].get(value
[i
], 'Unknown')
1621 # it's not a real IFD Tag but we fake one to make everybody
1622 # happy. this will have a "proprietary" type
1623 self
.tags
['MakerNote '+name
]=IFD_Tag(str(val
), None, 0, None,
1626 # process an image file (expects an open file object)
1627 # this is the function that has to deal with all the arbitrary nasty bits
1628 # of the EXIF standard
1629 def process_file(f
, stop_tag
='UNDEF', details
=True, strict
=False, debug
=False):
1630 # yah it's cheesy...
1634 # by default do not fake an EXIF beginning
1637 # determine whether it's a JPEG or TIFF
1639 if data
[0:4] in ['II*\x00', 'MM\x00*']:
1645 elif data
[0:2] == '\xFF\xD8':
1647 if debug
: print "JPEG format recognized data[0:2] == '0xFFD8'."
1649 while data
[2] == '\xFF' and data
[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'):
1650 if debug
: print "data[2] == 0xxFF data[3]==%x and data[6:10] = %s"%(ord(data
[3]),data
[6:10])
1651 length
= ord(data
[4])*256+ord(data
[5])
1652 if debug
: print "Length offset is",length
1654 # fake an EXIF beginning of file
1655 # I don't think this is used. --gd
1656 data
= '\xFF\x00'+f
.read(10)
1659 if debug
: print "added to base "
1660 base
= base
+ length
+ 4 -2
1662 if debug
: print "added to zero "
1664 if debug
: print "Set segment base to",base
1666 # Big ugly patch to deal with APP2 (or other) data coming before APP1
1668 data
= f
.read(base
+4000) # in theory, this could be insufficient since 64K is the maximum size--gd
1671 if debug
: print "Segment base 0x%X" % base
1672 if data
[base
:base
+2]=='\xFF\xE1':
1674 if debug
: print "APP1 at base",hex(base
)
1675 if debug
: print "Length",hex(ord(data
[base
+2])), hex(ord(data
[base
+3]))
1676 if debug
: print "Code",data
[base
+4:base
+8]
1677 if data
[base
+4:base
+8] == "Exif":
1678 if debug
: print "Decrement base by",2,"to get to pre-segment header (for compatibility with later code)"
1681 if debug
: print "Increment base by",ord(data
[base
+2])*256+ord(data
[base
+3])+2
1682 base
=base
+ord(data
[base
+2])*256+ord(data
[base
+3])+2
1683 elif data
[base
:base
+2]=='\xFF\xE0':
1685 if debug
: print "APP0 at base",hex(base
)
1686 if debug
: print "Length",hex(ord(data
[base
+2])), hex(ord(data
[base
+3]))
1687 if debug
: print "Code",data
[base
+4:base
+8]
1688 if debug
: print "Increment base by",ord(data
[base
+2])*256+ord(data
[base
+3])+2
1689 base
=base
+ord(data
[base
+2])*256+ord(data
[base
+3])+2
1690 elif data
[base
:base
+2]=='\xFF\xE2':
1692 if debug
: print "APP2 at base",hex(base
)
1693 if debug
: print "Length",hex(ord(data
[base
+2])), hex(ord(data
[base
+3]))
1694 if debug
: print "Code",data
[base
+4:base
+8]
1695 if debug
: print "Increment base by",ord(data
[base
+2])*256+ord(data
[base
+3])+2
1696 base
=base
+ord(data
[base
+2])*256+ord(data
[base
+3])+2
1697 elif data
[base
:base
+2]=='\xFF\xEE':
1699 if debug
: print "APP14 Adobe segment at base",hex(base
)
1700 if debug
: print "Length",hex(ord(data
[base
+2])), hex(ord(data
[base
+3]))
1701 if debug
: print "Code",data
[base
+4:base
+8]
1702 if debug
: print "Increment base by",ord(data
[base
+2])*256+ord(data
[base
+3])+2
1703 print "There is useful EXIF-like data here, but we have no parser for it."
1704 base
=base
+ord(data
[base
+2])*256+ord(data
[base
+3])+2
1705 elif data
[base
:base
+2]=='\xFF\xDB':
1706 if debug
: print "JPEG image data at base",hex(base
),"No more segments are expected."
1709 elif data
[base
:base
+2]=='\xFF\xD8':
1711 if debug
: print "FFD8 segment at base",hex(base
)
1712 if debug
: print "Got",hex(ord(data
[base
])), hex(ord(data
[base
+1])),"and", data
[4+base
:10+base
], "instead."
1713 if debug
: print "Length",hex(ord(data
[base
+2])), hex(ord(data
[base
+3]))
1714 if debug
: print "Code",data
[base
+4:base
+8]
1715 if debug
: print "Increment base by",ord(data
[base
+2])*256+ord(data
[base
+3])+2
1716 base
=base
+ord(data
[base
+2])*256+ord(data
[base
+3])+2
1717 elif data
[base
:base
+2]=='\xFF\xEC':
1719 if debug
: print "APP12 XMP (Ducky) or Pictureinfo segment at base",hex(base
)
1720 if debug
: print "Got",hex(ord(data
[base
])), hex(ord(data
[base
+1])),"and", data
[4+base
:10+base
], "instead."
1721 if debug
: print "Length",hex(ord(data
[base
+2])), hex(ord(data
[base
+3]))
1722 if debug
: print "Code",data
[base
+4:base
+8]
1723 if debug
: print "Increment base by",ord(data
[base
+2])*256+ord(data
[base
+3])+2
1724 print "There is useful EXIF-like data here (quality, comment, copyright), but we have no parser for it."
1725 base
=base
+ord(data
[base
+2])*256+ord(data
[base
+3])+2
1728 if debug
: print "Unexpected/unhandled segment type or file content."
1729 if debug
: print "Got",hex(ord(data
[base
])), hex(ord(data
[base
+1])),"and", data
[4+base
:10+base
], "instead."
1730 if debug
: print "Increment base by",ord(data
[base
+2])*256+ord(data
[base
+3])+2
1732 try: base
=base
+ord(data
[base
+2])*256+ord(data
[base
+3])+2
1736 if data
[2+base
] == '\xFF' and data
[6+base
:10+base
] == 'Exif':
1737 # detected EXIF header
1740 #HACK TEST: endian = 'M'
1741 elif data
[2+base
] == '\xFF' and data
[6+base
:10+base
+1] == 'Ducky':
1742 # detected Ducky header.
1743 if debug
: print "EXIF-like header (normally 0xFF and code):",hex(ord(data
[2+base
])) , "and", data
[6+base
:10+base
+1]
1746 elif data
[2+base
] == '\xFF' and data
[6+base
:10+base
+1] == 'Adobe':
1747 # detected APP14 (Adobe)
1748 if debug
: print "EXIF-like header (normally 0xFF and code):",hex(ord(data
[2+base
])) , "and", data
[6+base
:10+base
+1]
1752 # no EXIF information
1753 if debug
: print "No EXIF header expected data[2+base]==0xFF and data[6+base:10+base]===Exif (or Duck)"
1754 if debug
: print " but got",hex(ord(data
[2+base
])) , "and", data
[6+base
:10+base
+1]
1757 # file format not recognized
1758 if debug
: print "file format not recognized"
1761 # deal with the EXIF info we found
1763 print "Endian format is ",endian
1764 print {'I': 'Intel', 'M': 'Motorola', '\x01':'Adobe Ducky', 'd':'XMP/Adobe unknown' }[endian
], 'format'
1765 hdr
= EXIF_header(f
, endian
, offset
, fake_exif
, strict
, debug
)
1766 ifd_list
= hdr
.list_IFDs()
1772 IFD_name
= 'Thumbnail'
1775 IFD_name
= 'IFD %d' % ctr
1777 print ' IFD %d (%s) at offset %d:' % (ctr
, IFD_name
, i
)
1778 hdr
.dump_IFD(i
, IFD_name
, stop_tag
=stop_tag
)
1780 exif_off
= hdr
.tags
.get(IFD_name
+' ExifOffset')
1783 print ' EXIF SubIFD at offset %d:' % exif_off
.values
[0]
1784 hdr
.dump_IFD(exif_off
.values
[0], 'EXIF', stop_tag
=stop_tag
)
1785 # Interoperability IFD contained in EXIF IFD
1786 intr_off
= hdr
.tags
.get('EXIF SubIFD InteroperabilityOffset')
1789 print ' EXIF Interoperability SubSubIFD at offset %d:' \
1790 % intr_off
.values
[0]
1791 hdr
.dump_IFD(intr_off
.values
[0], 'EXIF Interoperability',
1792 dict=INTR_TAGS
, stop_tag
=stop_tag
)
1794 gps_off
= hdr
.tags
.get(IFD_name
+' GPSInfo')
1797 print ' GPS SubIFD at offset %d:' % gps_off
.values
[0]
1798 hdr
.dump_IFD(gps_off
.values
[0], 'GPS', dict=GPS_TAGS
, stop_tag
=stop_tag
)
1801 # extract uncompressed TIFF thumbnail
1802 thumb
= hdr
.tags
.get('Thumbnail Compression')
1803 if thumb
and thumb
.printable
== 'Uncompressed TIFF':
1804 hdr
.extract_TIFF_thumbnail(thumb_ifd
)
1806 # JPEG thumbnail (thankfully the JPEG data is stored as a unit)
1807 thumb_off
= hdr
.tags
.get('Thumbnail JPEGInterchangeFormat')
1809 f
.seek(offset
+thumb_off
.values
[0])
1810 size
= hdr
.tags
['Thumbnail JPEGInterchangeFormatLength'].values
[0]
1811 hdr
.tags
['JPEGThumbnail'] = f
.read(size
)
1813 # deal with MakerNote contained in EXIF IFD
1814 # (Some apps use MakerNote tags but do not use a format for which we
1815 # have a description, do not process these).
1816 if 'EXIF MakerNote' in hdr
.tags
and 'Image Make' in hdr
.tags
and detailed
:
1817 hdr
.decode_maker_note()
1819 # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
1820 # since it's not allowed in a uncompressed TIFF IFD
1821 if 'JPEGThumbnail' not in hdr
.tags
:
1822 thumb_off
=hdr
.tags
.get('MakerNote JPEGThumbnail')
1824 f
.seek(offset
+thumb_off
.values
[0])
1825 hdr
.tags
['JPEGThumbnail']=file.read(thumb_off
.field_length
)
1830 # show command line usage
1831 def usage(exit_status
):
1832 msg
= 'Usage: EXIF.py [OPTIONS] file1 [file2 ...]\n'
1833 msg
+= 'Extract EXIF information from digital camera image files.\n\nOptions:\n'
1834 msg
+= '-q --quick Do not process MakerNotes.\n'
1835 msg
+= '-t TAG --stop-tag TAG Stop processing when this tag is retrieved.\n'
1836 msg
+= '-s --strict Run in strict mode (stop on errors).\n'
1837 msg
+= '-d --debug Run in debug mode (display extra info).\n'
1839 sys
.exit(exit_status
)
1841 # library test/debug function (dump given files)
1842 if __name__
== '__main__':
1846 # parse command line options/arguments
1848 opts
, args
= getopt
.getopt(sys
.argv
[1:], "hqsdt:v", ["help", "quick", "strict", "debug", "stop-tag="])
1849 except getopt
.GetoptError
:
1858 if o
in ("-h", "--help"):
1860 if o
in ("-q", "--quick"):
1862 if o
in ("-t", "--stop-tag"):
1864 if o
in ("-s", "--strict"):
1866 if o
in ("-d", "--debug"):
1869 # output info for each file
1870 for filename
in args
:
1872 file=open(filename
, 'rb')
1874 print "'%s' is unreadable\n"%filename
1876 print filename
+ ':'
1878 data
= process_file(file, stop_tag
=stop_tag
, details
=detailed
, strict
=strict
, debug
=debug
)
1880 print 'No EXIF information found'
1886 if i
in ('JPEGThumbnail', 'TIFFThumbnail'):
1889 print ' %s (%s): %s' % \
1890 (i
, FIELD_TYPES
[data
[i
].field_type
][2], data
[i
].printable
)
1892 print 'error', i
, '"', data
[i
], '"'
1893 if 'JPEGThumbnail' in data
:
1894 print 'File has JPEG thumbnail'