Fixed a small typo that was causing the mediagoblin.moderation.users_detail
[mediagoblin.git] / extlib / exif / EXIF.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 #
5 # Library to extract EXIF information from digital camera image files.
6 # https://github.com/ianare/exif-py
7 #
8 #
9 # VERSION 1.1.0
10 #
11 # To use this library call with:
12 # f = open(path_name, 'rb')
13 # tags = EXIF.process_file(f)
14 #
15 # To ignore MakerNote tags, pass the -q or --quick
16 # command line arguments, or as
17 # tags = EXIF.process_file(f, details=False)
18 #
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')
22 #
23 # where TAG is a valid tag name, ex 'DateTimeOriginal'
24 #
25 # These 2 are useful when you are retrieving a large list of images
26 #
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)
30 #
31 # Otherwise these tags will be ignored
32 #
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',
38 # 'EXIF MakerNote'):
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.)
42 #
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.
46 #
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'
50 #
51 # Copyright (c) 2002-2007 Gene Cash All rights reserved
52 # Copyright (c) 2007-2012 Ianaré Sévi All rights reserved
53 #
54 # Redistribution and use in source and binary forms, with or without
55 # modification, are permitted provided that the following conditions
56 # are met:
57 #
58 # 1. Redistributions of source code must retain the above copyright
59 # notice, this list of conditions and the following disclaimer.
60 #
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.
65 #
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.
69 #
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.
81 #
82 #
83 # ----- See 'changes.txt' file for all contributors and changes ----- #
84 #
85
86
87 # Don't throw an exception when given an out of range character.
88 def make_string(seq):
89 str = ''
90 for c in seq:
91 # Screen out non-printing characters
92 if 32 <= c and c < 256:
93 str += chr(c)
94 # If no printing chars
95 if not str:
96 return seq
97 return str
98
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):
102 code = seq[0:8]
103 seq = seq[8:]
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) )
107
108 # field type descriptions as (length, abbreviation, full name) tuples
109 FIELD_TYPES = (
110 (0, 'X', 'Proprietary'), # no such type
111 (1, 'B', 'Byte'),
112 (1, 'A', 'ASCII'),
113 (2, 'S', 'Short'),
114 (4, 'L', 'Long'),
115 (8, 'R', 'Ratio'),
116 (1, 'SB', 'Signed Byte'),
117 (1, 'U', 'Undefined'),
118 (2, 'SS', 'Signed Short'),
119 (4, 'SL', 'Signed Long'),
120 (8, 'SR', 'Signed Ratio'),
121 )
122
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
126 EXIF_TAGS = {
127 0x0100: ('ImageWidth', ),
128 0x0101: ('ImageLength', ),
129 0x0102: ('BitsPerSample', ),
130 0x0103: ('Compression',
131 {1: 'Uncompressed',
132 2: 'CCITT 1D',
133 3: 'T4/Group 3 Fax',
134 4: 'T6/Group 4 Fax',
135 5: 'LZW',
136 6: 'JPEG (old-style)',
137 7: 'JPEG',
138 8: 'Adobe Deflate',
139 9: 'JBIG B&W',
140 10: 'JBIG Color',
141 32766: 'Next',
142 32769: 'Epson ERF Compressed',
143 32771: 'CCIRLEW',
144 32773: 'PackBits',
145 32809: 'Thunderscan',
146 32895: 'IT8CTPAD',
147 32896: 'IT8LW',
148 32897: 'IT8MP',
149 32898: 'IT8BL',
150 32908: 'PixarFilm',
151 32909: 'PixarLog',
152 32946: 'Deflate',
153 32947: 'DCS',
154 34661: 'JBIG',
155 34676: 'SGILog',
156 34677: 'SGILog24',
157 34712: 'JPEG 2000',
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', ),
166 0x010F: ('Make', ),
167 0x0110: ('Model', ),
168 0x0111: ('StripOffsets', ),
169 0x0112: ('Orientation',
170 {1: 'Horizontal (normal)',
171 2: 'Mirrored horizontal',
172 3: 'Rotated 180',
173 4: 'Mirrored vertical',
174 5: 'Mirrored horizontal then rotated 90 CCW',
175 6: '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',
186 {1: 'Not Absolute',
187 2: 'Pixels/Inch',
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',
202 {1: 'Centered',
203 2: 'Co-sited'}),
204 0x0214: ('ReferenceBlackWhite', ),
205
206 0x4746: ('Rating', ),
207
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',
218 {0: 'Unidentified',
219 1: 'Manual',
220 2: 'Program Normal',
221 3: 'Aperture Priority',
222 4: 'Shutter Priority',
223 5: 'Program Creative',
224 6: 'Program Action',
225 7: 'Portrait Mode',
226 8: 'Landscape Mode'}),
227 0x8824: ('SpectralSensitivity', ),
228 0x8825: ('GPSInfo', ),
229 0x8827: ('ISOSpeedRatings', ),
230 0x8828: ('OECF', ),
231 0x9000: ('ExifVersion', make_string),
232 0x9003: ('DateTimeOriginal', ),
233 0x9004: ('DateTimeDigitized', ),
234 0x9101: ('ComponentsConfiguration',
235 {0: '',
236 1: 'Y',
237 2: 'Cb',
238 3: 'Cr',
239 4: 'Red',
240 5: 'Green',
241 6: 'Blue'}),
242 0x9102: ('CompressedBitsPerPixel', ),
243 0x9201: ('ShutterSpeedValue', ),
244 0x9202: ('ApertureValue', ),
245 0x9203: ('BrightnessValue', ),
246 0x9204: ('ExposureBiasValue', ),
247 0x9205: ('MaxApertureValue', ),
248 0x9206: ('SubjectDistance', ),
249 0x9207: ('MeteringMode',
250 {0: 'Unidentified',
251 1: 'Average',
252 2: 'CenterWeightedAverage',
253 3: 'Spot',
254 4: 'MultiSpot',
255 5: 'Pattern',
256 6: 'Partial',
257 255: 'other'}),
258 0x9208: ('LightSource',
259 {0: 'Unknown',
260 1: 'Daylight',
261 2: 'Fluorescent',
262 3: 'Tungsten (incandescent light)',
263 4: 'Flash',
264 9: 'Fine weather',
265 10: 'Cloudy weather',
266 11: 'Shade',
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',
274 20: 'D55',
275 21: 'D65',
276 22: 'D75',
277 23: 'D50',
278 24: 'ISO studio tungsten',
279 255: 'other light source',}),
280 0x9209: ('Flash',
281 {0: 'Flash did not fire',
282 1: 'Flash fired',
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', ),
310
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', ),
317
318 0xA000: ('FlashPixVersion', make_string),
319 0xA001: ('ColorSpace',
320 {1: 'sRGB',
321 2: 'Adobe RGB',
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
334 {1: 'Not defined',
335 2: 'One-chip color area',
336 3: 'Two-chip color area',
337 4: 'Three-chip color area',
338 5: 'Color sequential area',
339 7: 'Trilinear',
340 8: 'Color sequential linear'}),
341 0xA300: ('FileSource',
342 {1: 'Film Scanner',
343 2: 'Reflection Print Scanner',
344 3: 'Digital Camera'}),
345 0xA301: ('SceneType',
346 {1: 'Directly Photographed'}),
347 0xA302: ('CVAPattern', ),
348 0xA401: ('CustomRendered',
349 {0: 'Normal',
350 1: 'Custom'}),
351 0xA402: ('ExposureMode',
352 {0: 'Auto Exposure',
353 1: 'Manual Exposure',
354 2: 'Auto Bracket'}),
355 0xA403: ('WhiteBalance',
356 {0: 'Auto',
357 1: 'Manual'}),
358 0xA404: ('DigitalZoomRatio', ),
359 0xA405: ('FocalLengthIn35mmFilm', ),
360 0xA406: ('SceneCaptureType',
361 {0: 'Standard',
362 1: 'Landscape',
363 2: 'Portrait',
364 3: 'Night)'}),
365 0xA407: ('GainControl',
366 {0: 'None',
367 1: 'Low gain up',
368 2: 'High gain up',
369 3: 'Low gain down',
370 4: 'High gain down'}),
371 0xA408: ('Contrast',
372 {0: 'Normal',
373 1: 'Soft',
374 2: 'Hard'}),
375 0xA409: ('Saturation',
376 {0: 'Normal',
377 1: 'Soft',
378 2: 'Hard'}),
379 0xA40A: ('Sharpness',
380 {0: 'Normal',
381 1: 'Soft',
382 2: 'Hard'}),
383 0xA40B: ('DeviceSettingDescription', ),
384 0xA40C: ('SubjectDistanceRange', ),
385 0xA500: ('Gamma', ),
386 0xC4A5: ('PrintIM', ),
387 0xEA1C: ('Padding', ),
388 }
389
390 # interoperability tags
391 INTR_TAGS = {
392 0x0001: ('InteroperabilityIndex', ),
393 0x0002: ('InteroperabilityVersion', ),
394 0x1000: ('RelatedImageFileFormat', ),
395 0x1001: ('RelatedImageWidth', ),
396 0x1002: ('RelatedImageLength', ),
397 }
398
399 # GPS tags (not used yet, haven't seen camera with GPS)
400 GPS_TAGS = {
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', ),
432 }
433
434 # Ignore these tags when quick processing
435 # 0x927C is MakerNote Tags
436 # 0x9286 is user comment
437 IGNORE_TAGS=(0x9286, 0x927C)
438
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.
444 #
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 ""
449 #
450 if seq == [252, 1, 6, 0]:
451 return "-2/3 EV"
452 if seq == [253, 1, 6, 0]:
453 return "-1/2 EV"
454 if seq == [254, 1, 6, 0]:
455 return "-1/3 EV"
456 if seq == [0, 1, 6, 0]:
457 return "0 EV"
458 if seq == [2, 1, 6, 0]:
459 return "+1/3 EV"
460 if seq == [3, 1, 6, 0]:
461 return "+1/2 EV"
462 if seq == [4, 1, 6, 0]:
463 return "+2/3 EV"
464 # Handle combinations not in the table.
465 a = seq[0]
466 # Causes headaches for the +/- logic, so special case it.
467 if a == 0:
468 return "0 EV"
469 if a > 127:
470 a = 256 - a
471 ret_str = "-"
472 else:
473 ret_str = "+"
474 b = seq[2] # Assume third value means the step size
475 whole = a / b
476 a = a % b
477 if whole != 0:
478 ret_str = ret_str + str(whole) + " "
479 if a == 0:
480 ret_str = ret_str + "EV"
481 else:
482 r = Ratio(a, b)
483 ret_str = ret_str + r.__repr__() + " EV"
484 return ret_str
485
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',
534 {0x0000: 'Center',
535 0x0100: 'Top',
536 0x0200: 'Bottom',
537 0x0300: 'Left',
538 0x0400: 'Right'}),
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',
560 {-3: 'B&W',
561 -2: '-2',
562 -1: '-1',
563 0: '0',
564 1: '1',
565 2: '2'}),
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', ),
593 # 00B9: unknown
594 0x0100: ('DigitalICE', ),
595 0x0103: ('PreviewCompression',
596 {1: 'Uncompressed',
597 2: 'CCITT 1D',
598 3: 'T4/Group 3 Fax',
599 4: 'T6/Group 4 Fax',
600 5: 'LZW',
601 6: 'JPEG (old-style)',
602 7: 'JPEG',
603 8: 'Adobe Deflate',
604 9: 'JBIG B&W',
605 10: 'JBIG Color',
606 32766: 'Next',
607 32769: 'Epson ERF Compressed',
608 32771: 'CCIRLEW',
609 32773: 'PackBits',
610 32809: 'Thunderscan',
611 32895: 'IT8CTPAD',
612 32896: 'IT8LW',
613 32897: 'IT8MP',
614 32898: 'IT8BL',
615 32908: 'PixarFilm',
616 32909: 'PixarLog',
617 32946: 'Deflate',
618 32947: 'DCS',
619 34661: 'JBIG',
620 34676: 'SGILog',
621 34677: 'SGILog24',
622 34712: 'JPEG 2000',
623 34713: 'Nikon NEF Compressed',
624 65000: 'Kodak DCR Compressed',
625 65535: 'Pentax PEF Compressed',}),
626 0x0201: ('PreviewImageStart', ),
627 0x0202: ('PreviewImageLength', ),
628 0x0213: ('PreviewYCbCrPositioning',
629 {1: 'Centered',
630 2: 'Co-sited'}),
631 0x0010: ('DataDump', ),
632 }
633
634 MAKERNOTE_NIKON_OLDER_TAGS = {
635 0x0003: ('Quality',
636 {1: 'VGA Basic',
637 2: 'VGA Normal',
638 3: 'VGA Fine',
639 4: 'SXGA Basic',
640 5: 'SXGA Normal',
641 6: 'SXGA Fine'}),
642 0x0004: ('ColorMode',
643 {1: 'Color',
644 2: 'Monochrome'}),
645 0x0005: ('ImageAdjustment',
646 {0: 'Normal',
647 1: 'Bright+',
648 2: 'Bright-',
649 3: 'Contrast+',
650 4: 'Contrast-'}),
651 0x0006: ('CCDSpeed',
652 {0: 'ISO 80',
653 2: 'ISO 160',
654 4: 'ISO 320',
655 5: 'ISO 100'}),
656 0x0007: ('WhiteBalance',
657 {0: 'Auto',
658 1: 'Preset',
659 2: 'Daylight',
660 3: 'Incandescent',
661 4: 'Fluorescent',
662 5: 'Cloudy',
663 6: 'Speed Light'}),
664 }
665
666 # decode Olympus SpecialMode tag in MakerNote
667 def olympus_special_mode(v):
668 a={
669 0: 'Normal',
670 1: 'Unknown',
671 2: 'Fast',
672 3: 'Panorama'}
673 b={
674 0: 'Non-panoramic',
675 1: 'Left to right',
676 2: 'Right to left',
677 3: 'Bottom to top',
678 4: 'Top to bottom'}
679 if v[0] not in a or v[2] not in b:
680 return v
681 return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
682
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),
688 0x0201: ('JPEGQual',
689 {1: 'SQ',
690 2: 'HQ',
691 3: 'SHQ'}),
692 0x0202: ('Macro',
693 {0: 'Normal',
694 1: 'Macro',
695 2: 'SuperMacro'}),
696 0x0203: ('BWMode',
697 {0: 'Off',
698 1: 'On'}),
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',
714 {2: 'On',
715 3: 'Off'}),
716 0x1005: ('FlashDevice',
717 {0: 'None',
718 1: 'Internal',
719 4: 'External',
720 5: 'Internal + External'}),
721 0x1006: ('ExposureCompensation', ),
722 0x1007: ('SensorTemperature', ),
723 0x1008: ('LensTemperature', ),
724 0x100b: ('FocusMode',
725 {0: 'Auto',
726 1: 'Manual'}),
727 0x1017: ('RedBalance', ),
728 0x1018: ('BlueBalance', ),
729 0x101a: ('SerialNumber', ),
730 0x1023: ('FlashExposureComp', ),
731 0x1026: ('ExternalFlashBounce',
732 {0: 'No',
733 1: 'Yes'}),
734 0x1027: ('ExternalFlashZoom', ),
735 0x1028: ('ExternalFlashMode', ),
736 0x1029: ('Contrast int16u',
737 {0: 'High',
738 1: 'Normal',
739 2: 'Low'}),
740 0x102a: ('SharpnessFactor', ),
741 0x102b: ('ColorControl', ),
742 0x102c: ('ValidBits', ),
743 0x102d: ('CoringFilter', ),
744 0x102e: ('OlympusImageWidth', ),
745 0x102f: ('OlympusImageHeight', ),
746 0x1034: ('CompressionRatio', ),
747 0x1035: ('PreviewImageValid',
748 {0: 'No',
749 1: 'Yes'}),
750 0x1036: ('PreviewImageStart', ),
751 0x1037: ('PreviewImageLength', ),
752 0x1039: ('CCDScanMode',
753 {0: 'Interlaced',
754 1: 'Progressive'}),
755 0x103a: ('NoiseReduction',
756 {0: 'Off',
757 1: 'On'}),
758 0x103b: ('InfinityLensStep', ),
759 0x103c: ('NearLensStep', ),
760
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 ', ),
769 }
770
771 # 0x2020 CameraSettings
772 MAKERNOTE_OLYMPUS_TAG_0x2020={
773 0x0100: ('PreviewImageValid',
774 {0: 'No',
775 1: 'Yes'}),
776 0x0101: ('PreviewImageStart', ),
777 0x0102: ('PreviewImageLength', ),
778 0x0200: ('ExposureMode',
779 {1: 'Manual',
780 2: 'Program',
781 3: 'Aperture-priority AE',
782 4: 'Shutter speed priority AE',
783 5: 'Program-shift'}),
784 0x0201: ('AELock',
785 {0: 'Off',
786 1: 'On'}),
787 0x0202: ('MeteringMode',
788 {2: 'Center Weighted',
789 3: 'Spot',
790 5: 'ESP',
791 261: 'Pattern+AF',
792 515: 'Spot+Highlight control',
793 1027: 'Spot+Shadow control'}),
794 0x0300: ('MacroMode',
795 {0: 'Off',
796 1: 'On'}),
797 0x0301: ('FocusMode',
798 {0: 'Single AF',
799 1: 'Sequential shooting AF',
800 2: 'Continuous AF',
801 3: 'Multi AF',
802 10: 'MF'}),
803 0x0302: ('FocusProcess',
804 {0: 'AF Not Used',
805 1: 'AF Used'}),
806 0x0303: ('AFSearch',
807 {0: 'Not Ready',
808 1: 'Ready'}),
809 0x0304: ('AFAreas', ),
810 0x0401: ('FlashExposureCompensation', ),
811 0x0500: ('WhiteBalance2',
812 {0: 'Auto',
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)',
822 256: 'Custom WB 1',
823 257: 'Custom WB 2',
824 258: 'Custom WB 3',
825 259: 'Custom WB 4',
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',
833 {0: 'Off',
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',
841 {0: 'sRGB',
842 1: 'Adobe RGB',
843 2: 'Pro Photo RGB'}),
844 0x0509: ('SceneMode',
845 {0: 'Standard',
846 6: 'Auto',
847 7: 'Sport',
848 8: 'Portrait',
849 9: 'Landscape+Portrait',
850 10: 'Landscape',
851 11: 'Night scene',
852 13: 'Panorama',
853 16: 'Landscape+Portrait',
854 17: 'Night+Portrait',
855 19: 'Fireworks',
856 20: 'Sunset',
857 22: 'Macro',
858 25: 'Documents',
859 26: 'Museum',
860 28: 'Beach&Snow',
861 30: 'Candle',
862 35: 'Underwater Wide1',
863 36: 'Underwater Macro',
864 39: 'High Key',
865 40: 'Digital Image Stabilization',
866 44: 'Underwater Wide2',
867 45: 'Low Key',
868 46: 'Children',
869 48: 'Nature Macro'}),
870 0x050a: ('NoiseReduction',
871 {0: 'Off',
872 1: 'Noise Reduction',
873 2: 'Noise Filter',
874 3: 'Noise Reduction + Noise Filter',
875 4: 'Noise Filter (ISO Boost)',
876 5: 'Noise Reduction + Noise Filter (ISO Boost)'}),
877 0x050b: ('DistortionCorrection',
878 {0: 'Off',
879 1: 'On'}),
880 0x050c: ('ShadingCompensation',
881 {0: 'Off',
882 1: 'On'}),
883 0x050d: ('CompressionFactor', ),
884 0x050f: ('Gradation',
885 {'-1 -1 1': 'Low Key',
886 '0 -1 1': 'Normal',
887 '1 -1 1': 'High Key'}),
888 0x0520: ('PictureMode',
889 {1: 'Vivid',
890 2: 'Natural',
891 3: 'Muted',
892 256: 'Monotone',
893 512: 'Sepia'}),
894 0x0521: ('PictureModeSaturation', ),
895 0x0522: ('PictureModeHue?', ),
896 0x0523: ('PictureModeContrast', ),
897 0x0524: ('PictureModeSharpness', ),
898 0x0525: ('PictureModeBWFilter',
899 {0: 'n/a',
900 1: 'Neutral',
901 2: 'Yellow',
902 3: 'Orange',
903 4: 'Red',
904 5: 'Green'}),
905 0x0526: ('PictureModeTone',
906 {0: 'n/a',
907 1: 'Neutral',
908 2: 'Sepia',
909 3: 'Blue',
910 4: 'Purple',
911 5: 'Green'}),
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',
915 {1: 'SQ',
916 2: 'HQ',
917 3: 'SHQ',
918 4: 'RAW'}),
919 0x0901: ('ManometerReading', ),
920 }
921
922
923 MAKERNOTE_CASIO_TAGS={
924 0x0001: ('RecordingMode',
925 {1: 'Single Shutter',
926 2: 'Panorama',
927 3: 'Night Scene',
928 4: 'Portrait',
929 5: 'Landscape'}),
930 0x0002: ('Quality',
931 {1: 'Economy',
932 2: 'Normal',
933 3: 'Fine'}),
934 0x0003: ('FocusingMode',
935 {2: 'Macro',
936 3: 'Auto Focus',
937 4: 'Manual Focus',
938 5: 'Infinity'}),
939 0x0004: ('FlashMode',
940 {1: 'Auto',
941 2: 'On',
942 3: 'Off',
943 4: 'Red Eye Reduction'}),
944 0x0005: ('FlashIntensity',
945 {11: 'Weak',
946 13: 'Normal',
947 15: 'Strong'}),
948 0x0006: ('Object Distance', ),
949 0x0007: ('WhiteBalance',
950 {1: 'Auto',
951 2: 'Tungsten',
952 3: 'Daylight',
953 4: 'Fluorescent',
954 5: 'Shade',
955 129: 'Manual'}),
956 0x000B: ('Sharpness',
957 {0: 'Normal',
958 1: 'Soft',
959 2: 'Hard'}),
960 0x000C: ('Contrast',
961 {0: 'Normal',
962 1: 'Low',
963 2: 'High'}),
964 0x000D: ('Saturation',
965 {0: 'Normal',
966 1: 'Low',
967 2: 'High'}),
968 0x0014: ('CCDSpeed',
969 {64: 'Normal',
970 80: 'Normal',
971 100: 'High',
972 125: '+1.0',
973 244: '+3.0',
974 250: '+2.0'}),
975 }
976
977 MAKERNOTE_FUJIFILM_TAGS={
978 0x0000: ('NoteVersion', make_string),
979 0x1000: ('Quality', ),
980 0x1001: ('Sharpness',
981 {1: 'Soft',
982 2: 'Soft',
983 3: 'Normal',
984 4: 'Hard',
985 5: 'Hard'}),
986 0x1002: ('WhiteBalance',
987 {0: 'Auto',
988 256: 'Daylight',
989 512: 'Cloudy',
990 768: 'DaylightColor-Fluorescent',
991 769: 'DaywhiteColor-Fluorescent',
992 770: 'White-Fluorescent',
993 1024: 'Incandescent',
994 3840: 'Custom'}),
995 0x1003: ('Color',
996 {0: 'Normal',
997 256: 'High',
998 512: 'Low'}),
999 0x1004: ('Tone',
1000 {0: 'Normal',
1001 256: 'High',
1002 512: 'Low'}),
1003 0x1010: ('FlashMode',
1004 {0: 'Auto',
1005 1: 'On',
1006 2: 'Off',
1007 3: 'Red Eye Reduction'}),
1008 0x1011: ('FlashStrength', ),
1009 0x1020: ('Macro',
1010 {0: 'Off',
1011 1: 'On'}),
1012 0x1021: ('FocusMode',
1013 {0: 'Auto',
1014 1: 'Manual'}),
1015 0x1030: ('SlowSync',
1016 {0: 'Off',
1017 1: 'On'}),
1018 0x1031: ('PictureMode',
1019 {0: 'Auto',
1020 1: 'Portrait',
1021 2: 'Landscape',
1022 4: 'Sports',
1023 5: 'Night',
1024 6: 'Program AE',
1025 256: 'Aperture Priority AE',
1026 512: 'Shutter Priority AE',
1027 768: 'Manual Exposure'}),
1028 0x1100: ('MotorOrBracket',
1029 {0: 'Off',
1030 1: 'On'}),
1031 0x1300: ('BlurWarning',
1032 {0: 'Off',
1033 1: 'On'}),
1034 0x1301: ('FocusWarning',
1035 {0: 'Off',
1036 1: 'On'}),
1037 0x1302: ('AEWarning',
1038 {0: 'Off',
1039 1: 'On'}),
1040 }
1041
1042 MAKERNOTE_CANON_TAGS = {
1043 0x0006: ('ImageType', ),
1044 0x0007: ('FirmwareVersion', ),
1045 0x0008: ('ImageNumber', ),
1046 0x0009: ('OwnerName', ),
1047 }
1048
1049 # this is in element offset, name, optional value dictionary format
1050 MAKERNOTE_CANON_TAG_0x001 = {
1051 1: ('Macromode',
1052 {1: 'Macro',
1053 2: 'Normal'}),
1054 2: ('SelfTimer', ),
1055 3: ('Quality',
1056 {2: 'Normal',
1057 3: 'Fine',
1058 5: 'Superfine'}),
1059 4: ('FlashMode',
1060 {0: 'Flash Not Fired',
1061 1: 'Auto',
1062 2: 'On',
1063 3: 'Red-Eye Reduction',
1064 4: 'Slow Synchro',
1065 5: 'Auto + Red-Eye Reduction',
1066 6: 'On + Red-Eye Reduction',
1067 16: 'external flash'}),
1068 5: ('ContinuousDriveMode',
1069 {0: 'Single Or Timer',
1070 1: 'Continuous'}),
1071 7: ('FocusMode',
1072 {0: 'One-Shot',
1073 1: 'AI Servo',
1074 2: 'AI Focus',
1075 3: 'MF',
1076 4: 'Single',
1077 5: 'Continuous',
1078 6: 'MF'}),
1079 10: ('ImageSize',
1080 {0: 'Large',
1081 1: 'Medium',
1082 2: 'Small'}),
1083 11: ('EasyShootingMode',
1084 {0: 'Full Auto',
1085 1: 'Manual',
1086 2: 'Landscape',
1087 3: 'Fast Shutter',
1088 4: 'Slow Shutter',
1089 5: 'Night',
1090 6: 'B&W',
1091 7: 'Sepia',
1092 8: 'Portrait',
1093 9: 'Sports',
1094 10: 'Macro/Close-Up',
1095 11: 'Pan Focus'}),
1096 12: ('DigitalZoom',
1097 {0: 'None',
1098 1: '2x',
1099 2: '4x'}),
1100 13: ('Contrast',
1101 {0xFFFF: 'Low',
1102 0: 'Normal',
1103 1: 'High'}),
1104 14: ('Saturation',
1105 {0xFFFF: 'Low',
1106 0: 'Normal',
1107 1: 'High'}),
1108 15: ('Sharpness',
1109 {0xFFFF: 'Low',
1110 0: 'Normal',
1111 1: 'High'}),
1112 16: ('ISO',
1113 {0: 'See ISOSpeedRatings Tag',
1114 15: 'Auto',
1115 16: '50',
1116 17: '100',
1117 18: '200',
1118 19: '400'}),
1119 17: ('MeteringMode',
1120 {3: 'Evaluative',
1121 4: 'Partial',
1122 5: 'Center-weighted'}),
1123 18: ('FocusType',
1124 {0: 'Manual',
1125 1: 'Auto',
1126 3: 'Close-Up (Macro)',
1127 8: 'Locked (Pan Mode)'}),
1128 19: ('AFPointSelected',
1129 {0x3000: 'None (MF)',
1130 0x3001: 'Auto-Selected',
1131 0x3002: 'Right',
1132 0x3003: 'Center',
1133 0x3004: 'Left'}),
1134 20: ('ExposureMode',
1135 {0: 'Easy Shooting',
1136 1: 'Program',
1137 2: 'Tv-priority',
1138 3: 'Av-priority',
1139 4: 'Manual',
1140 5: 'A-DEP'}),
1141 23: ('LongFocalLengthOfLensInFocalUnits', ),
1142 24: ('ShortFocalLengthOfLensInFocalUnits', ),
1143 25: ('FocalUnitsPerMM', ),
1144 28: ('FlashActivity',
1145 {0: 'Did Not Fire',
1146 1: 'Fired'}),
1147 29: ('FlashDetails',
1148 {14: 'External E-TTL',
1149 13: 'Internal Flash',
1150 11: 'FP Sync Used',
1151 7: '2nd("Rear")-Curtain Sync Used',
1152 4: 'FP Sync Enabled'}),
1153 32: ('FocusMode',
1154 {0: 'Single',
1155 1: 'Continuous'}),
1156 }
1157
1158 MAKERNOTE_CANON_TAG_0x004 = {
1159 7: ('WhiteBalance',
1160 {0: 'Auto',
1161 1: 'Sunny',
1162 2: 'Cloudy',
1163 3: 'Tungsten',
1164 4: 'Fluorescent',
1165 5: 'Flash',
1166 6: 'Custom'}),
1167 9: ('SequenceNumber', ),
1168 14: ('AFPointUsed', ),
1169 15: ('FlashBias',
1170 {0xFFC0: '-2 EV',
1171 0xFFCC: '-1.67 EV',
1172 0xFFD0: '-1.50 EV',
1173 0xFFD4: '-1.33 EV',
1174 0xFFE0: '-1 EV',
1175 0xFFEC: '-0.67 EV',
1176 0xFFF0: '-0.50 EV',
1177 0xFFF4: '-0.33 EV',
1178 0x0000: '0 EV',
1179 0x000C: '0.33 EV',
1180 0x0010: '0.50 EV',
1181 0x0014: '0.67 EV',
1182 0x0020: '1 EV',
1183 0x002C: '1.33 EV',
1184 0x0030: '1.50 EV',
1185 0x0034: '1.67 EV',
1186 0x0040: '2 EV'}),
1187 19: ('SubjectDistance', ),
1188 }
1189
1190 # extract multibyte integer in Motorola format (little endian)
1191 def s2n_motorola(str):
1192 x = 0
1193 for c in str:
1194 x = (x << 8) | ord(c)
1195 return x
1196
1197 # extract multibyte integer in Intel format (big endian)
1198 def s2n_intel(str):
1199 x = 0
1200 y = 0L
1201 for c in str:
1202 x = x | (ord(c) << y)
1203 y = y + 8
1204 return x
1205
1206 # ratio object that eventually will be able to reduce itself to lowest
1207 # common denominator for printing
1208 def gcd(a, b):
1209 if b == 0:
1210 return a
1211 else:
1212 return gcd(b, a % b)
1213
1214 class Ratio:
1215 def __init__(self, num, den):
1216 self.num = num
1217 self.den = den
1218
1219 def __repr__(self):
1220 self.reduce()
1221 if self.den == 1:
1222 return str(self.num)
1223 return '%d/%d' % (self.num, self.den)
1224
1225 def reduce(self):
1226 div = gcd(self.num, self.den)
1227 if div > 1:
1228 self.num = self.num / div
1229 self.den = self.den / div
1230
1231 # for ease of dealing with tags
1232 class IFD_Tag:
1233 def __init__(self, printable, tag, field_type, values, field_offset,
1234 field_length):
1235 # printable version of data
1236 self.printable = printable
1237 # tag ID number
1238 self.tag = tag
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
1247
1248 def __str__(self):
1249 return self.printable
1250
1251 def __repr__(self):
1252 try:
1253 s= '(0x%04X) %s=%s @ %d' % (self.tag,
1254 FIELD_TYPES[self.field_type][2],
1255 self.printable,
1256 self.field_offset)
1257 except:
1258 s= '(%s) %s=%s @ %s' % (str(self.tag),
1259 FIELD_TYPES[self.field_type][2],
1260 self.printable,
1261 str(self.field_offset))
1262 return s
1263
1264 # class that handles an EXIF header
1265 class EXIF_header:
1266 def __init__(self, file, endian, offset, fake_exif, strict, debug=0):
1267 self.file = file
1268 self.endian = endian
1269 self.offset = offset
1270 self.fake_exif = fake_exif
1271 self.strict = strict
1272 self.debug = debug
1273 self.tags = {}
1274
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)
1284 else:
1285 val=s2n_motorola(slice)
1286 # Sign extension ?
1287 if signed:
1288 msb=1L << (8*length-1)
1289 if val & msb:
1290 val=val-(msb << 1)
1291 return val
1292
1293 # convert offset to string
1294 def n2s(self, offset, length):
1295 s = ''
1296 for dummy in range(length):
1297 if self.endian == 'I':
1298 s = s + chr(offset & 0xFF)
1299 else:
1300 s = chr(offset & 0xFF) + s
1301 offset = offset >> 8
1302 return s
1303
1304 # return first IFD
1305 def first_IFD(self):
1306 return self.s2n(4, 4)
1307
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)
1312 if next_ifd == ifd:
1313 return 0
1314 else:
1315 return next_ifd
1316
1317 # return list of IFDs in header
1318 def list_IFDs(self):
1319 i=self.first_IFD()
1320 a=[]
1321 while i:
1322 a.append(i)
1323 i=self.next_IFD(i)
1324 return a
1325
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)
1333
1334 # get tag name early to avoid errors, help debug
1335 tag_entry = dict.get(tag)
1336 if tag_entry:
1337 tag_name = tag_entry[0]
1338 else:
1339 tag_name = 'Tag 0x%04X' % tag
1340
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)
1344
1345 # unknown field type
1346 if not 0 < field_type < len(FIELD_TYPES):
1347 if not self.strict:
1348 continue
1349 else:
1350 raise ValueError('unknown type %d in tag 0x%04X' % (field_type, tag))
1351
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
1356 offset = entry + 8
1357
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.
1367 if relative:
1368 tmp_offset = self.s2n(offset, 4)
1369 offset = tmp_offset + ifd - 8
1370 if self.fake_exif:
1371 offset = offset + 18
1372 else:
1373 offset = self.s2n(offset, 4)
1374
1375 field_offset = offset
1376 if field_type == 2:
1377 # special case: null-terminated ASCII string
1378 # XXX investigate
1379 # sometimes gets too big to fit in int value
1380 if count != 0: # and count < (2**31): # 2E31 is hardware dependant. --gd
1381 try:
1382 self.file.seek(self.offset + offset)
1383 values = self.file.read(count)
1384 #print values
1385 # Drop any garbage after a null.
1386 values = values.split('\x00', 1)[0]
1387 except OverflowError:
1388 values = ''
1389 else:
1390 values = []
1391 signed = (field_type in [6, 8, 9, 10])
1392
1393 # XXX investigate
1394 # some entries get too big to handle could be malformed
1395 # file or problem with self.s2n
1396 if count < 1000:
1397 for dummy in range(count):
1398 if field_type in (5, 10):
1399 # a ratio
1400 value = Ratio(self.s2n(offset, 4, signed),
1401 self.s2n(offset + 4, 4, signed))
1402 else:
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
1413 #else :
1414 # print "Warning: dropping large tag:", tag, tag_name
1415
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] + ", ... ]"
1421 else:
1422 printable=str(values)
1423
1424 # compute printable version of values
1425 if tag_entry:
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)
1431 else:
1432 printable = ''
1433 for i in values:
1434 # use lookup table for this tag
1435 printable += tag_entry[1].get(i, repr(i))
1436
1437 self.tags[ifd_name + ' ' + tag_name] = IFD_Tag(printable, tag,
1438 field_type,
1439 values, field_offset,
1440 count * typelen)
1441 if self.debug:
1442 print ' debug: %s: %s' % (tag_name,
1443 repr(self.tags[ifd_name + ' ' + tag_name]))
1444
1445 if tag_name == stop_tag:
1446 break
1447
1448 # extract uncompressed TIFF thumbnail (like pulling teeth)
1449 # we take advantage of the pre-existing layout in the thumbnail IFD as
1450 # much as possible
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'
1456 else:
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'
1461
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
1471 ptr = i * 12 + 18
1472 # remember strip offsets location
1473 if tag == 0x0111:
1474 strip_off = ptr
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"
1480 newoff = len(tiff)
1481 tiff = tiff[:ptr] + self.n2s(newoff, 4) + tiff[ptr+4:]
1482 # remember strip offsets location
1483 if tag == 0x0111:
1484 strip_off = newoff
1485 strip_len = 4
1486 # get original data and store it
1487 self.file.seek(self.offset + oldoff)
1488 tiff += self.file.read(count * typelen)
1489
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])
1501
1502 self.tags['TIFFThumbnail'] = tiff
1503
1504 # decode all the camera-specific MakerNote formats
1505
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.
1510 #
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
1513 # pointers.
1514 #
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']
1525
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
1530 #else:
1531 # make = ''
1532
1533 # model = self.tags['Image Model'].printable # unused
1534
1535 # Nikon
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.
1540 if 'NIKON' in make:
1541 if note.values[0:7] == [78, 105, 107, 111, 110, 0, 1]:
1542 if self.debug:
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]:
1547 if self.debug:
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)
1554 else:
1555 # E99x or D1
1556 if self.debug:
1557 print "Looks like an unlabeled type 2 Nikon MakerNote"
1558 self.dump_IFD(note.field_offset, 'MakerNote',
1559 dict=MAKERNOTE_NIKON_NEWER_TAGS)
1560 return
1561
1562 # Olympus
1563 if make.startswith('OLYMPUS'):
1564 self.dump_IFD(note.field_offset+8, 'MakerNote',
1565 dict=MAKERNOTE_OLYMPUS_TAGS)
1566 # XXX TODO
1567 #for i in (('MakerNote Tag 0x2020', MAKERNOTE_OLYMPUS_TAG_0x2020),):
1568 # self.decode_olympus_tag(self.tags[i[0]].values, i[1])
1569 #return
1570
1571 # Casio
1572 if 'CASIO' in make or 'Casio' in make:
1573 self.dump_IFD(note.field_offset, 'MakerNote',
1574 dict=MAKERNOTE_CASIO_TAGS)
1575 return
1576
1577 # Fujifilm
1578 if make == 'FUJIFILM':
1579 # bug: everything else is "Motorola" endian, but the MakerNote
1580 # is "Intel" endian
1581 endian = self.endian
1582 self.endian = 'I'
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
1592 return
1593
1594 # Canon
1595 if make == 'Canon':
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])
1602 return
1603
1604
1605 # XXX TODO decode Olympus MakerNote tag based on offset within tag
1606 def olympus_decode_tag(self, value, dict):
1607 pass
1608
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', ))
1614 if self.debug:
1615 print i, x
1616 name=x[0]
1617 if len(x) > 1:
1618 val=x[1].get(value[i], 'Unknown')
1619 else:
1620 val=value[i]
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,
1624 None, None)
1625
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...
1631 global detailed
1632 detailed = details
1633
1634 # by default do not fake an EXIF beginning
1635 fake_exif = 0
1636
1637 # determine whether it's a JPEG or TIFF
1638 data = f.read(12)
1639 if data[0:4] in ['II*\x00', 'MM\x00*']:
1640 # it's a TIFF file
1641 f.seek(0)
1642 endian = f.read(1)
1643 f.read(1)
1644 offset = 0
1645 elif data[0:2] == '\xFF\xD8':
1646 # it's a JPEG file
1647 if debug: print "JPEG format recognized data[0:2] == '0xFFD8'."
1648 base = 2
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
1653 f.read(length-8)
1654 # fake an EXIF beginning of file
1655 # I don't think this is used. --gd
1656 data = '\xFF\x00'+f.read(10)
1657 fake_exif = 1
1658 if base>2:
1659 if debug: print "added to base "
1660 base = base + length + 4 -2
1661 else:
1662 if debug: print "added to zero "
1663 base = length + 4
1664 if debug: print "Set segment base to",base
1665
1666 # Big ugly patch to deal with APP2 (or other) data coming before APP1
1667 f.seek(0)
1668 data = f.read(base+4000) # in theory, this could be insufficient since 64K is the maximum size--gd
1669 # base = 2
1670 while 1:
1671 if debug: print "Segment base 0x%X" % base
1672 if data[base:base+2]=='\xFF\xE1':
1673 # APP1
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)"
1679 base = base-2
1680 break
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':
1684 # APP0
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':
1691 # APP2
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':
1698 # APP14
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."
1707 # sys.exit(0)
1708 break
1709 elif data[base:base+2]=='\xFF\xD8':
1710 # APP12
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':
1718 # APP12
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
1726 else:
1727 try:
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
1731 except: pass
1732 try: base=base+ord(data[base+2])*256+ord(data[base+3])+2
1733 except: pass
1734
1735 f.seek(base+12)
1736 if data[2+base] == '\xFF' and data[6+base:10+base] == 'Exif':
1737 # detected EXIF header
1738 offset = f.tell()
1739 endian = f.read(1)
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]
1744 offset = f.tell()
1745 endian = f.read(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]
1749 offset = f.tell()
1750 endian = f.read(1)
1751 else:
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]
1755 return {}
1756 else:
1757 # file format not recognized
1758 if debug: print "file format not recognized"
1759 return {}
1760
1761 # deal with the EXIF info we found
1762 if debug:
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()
1767 ctr = 0
1768 for i in ifd_list:
1769 if ctr == 0:
1770 IFD_name = 'Image'
1771 elif ctr == 1:
1772 IFD_name = 'Thumbnail'
1773 thumb_ifd = i
1774 else:
1775 IFD_name = 'IFD %d' % ctr
1776 if debug:
1777 print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i)
1778 hdr.dump_IFD(i, IFD_name, stop_tag=stop_tag)
1779 # EXIF IFD
1780 exif_off = hdr.tags.get(IFD_name+' ExifOffset')
1781 if exif_off:
1782 if debug:
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')
1787 if intr_off:
1788 if debug:
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)
1793 # GPS IFD
1794 gps_off = hdr.tags.get(IFD_name+' GPSInfo')
1795 if gps_off:
1796 if debug:
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)
1799 ctr += 1
1800
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)
1805
1806 # JPEG thumbnail (thankfully the JPEG data is stored as a unit)
1807 thumb_off = hdr.tags.get('Thumbnail JPEGInterchangeFormat')
1808 if thumb_off:
1809 f.seek(offset+thumb_off.values[0])
1810 size = hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0]
1811 hdr.tags['JPEGThumbnail'] = f.read(size)
1812
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()
1818
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')
1823 if thumb_off:
1824 f.seek(offset+thumb_off.values[0])
1825 hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length)
1826
1827 return hdr.tags
1828
1829
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'
1838 print msg
1839 sys.exit(exit_status)
1840
1841 # library test/debug function (dump given files)
1842 if __name__ == '__main__':
1843 import sys
1844 import getopt
1845
1846 # parse command line options/arguments
1847 try:
1848 opts, args = getopt.getopt(sys.argv[1:], "hqsdt:v", ["help", "quick", "strict", "debug", "stop-tag="])
1849 except getopt.GetoptError:
1850 usage(2)
1851 if args == []:
1852 usage(2)
1853 detailed = True
1854 stop_tag = 'UNDEF'
1855 debug = False
1856 strict = False
1857 for o, a in opts:
1858 if o in ("-h", "--help"):
1859 usage(0)
1860 if o in ("-q", "--quick"):
1861 detailed = False
1862 if o in ("-t", "--stop-tag"):
1863 stop_tag = a
1864 if o in ("-s", "--strict"):
1865 strict = True
1866 if o in ("-d", "--debug"):
1867 debug = True
1868
1869 # output info for each file
1870 for filename in args:
1871 try:
1872 file=open(filename, 'rb')
1873 except:
1874 print "'%s' is unreadable\n"%filename
1875 continue
1876 print filename + ':'
1877 # get the tags
1878 data = process_file(file, stop_tag=stop_tag, details=detailed, strict=strict, debug=debug)
1879 if not data:
1880 print 'No EXIF information found'
1881 continue
1882
1883 x=data.keys()
1884 x.sort()
1885 for i in x:
1886 if i in ('JPEGThumbnail', 'TIFFThumbnail'):
1887 continue
1888 try:
1889 print ' %s (%s): %s' % \
1890 (i, FIELD_TYPES[data[i].field_type][2], data[i].printable)
1891 except:
1892 print 'error', i, '"', data[i], '"'
1893 if 'JPEGThumbnail' in data:
1894 print 'File has JPEG thumbnail'
1895 print
1896