Commit | Line | Data |
---|---|---|
9bf7563d JW |
1 | #!/usr/bin/env python |
2 | # -*- coding: utf-8 -*- | |
3 | # | |
9bf7563d | 4 | # |
6e60238b OHO |
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 | |
9bf7563d JW |
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 | # | |
9bf7563d JW |
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 | |
6e60238b | 52 | # Copyright (c) 2007-2012 Ianaré Sévi All rights reserved |
9bf7563d JW |
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. | |
6e60238b | 106 | return make_string( make_string(seq) ) |
9bf7563d JW |
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', | |
ab7281fe | 175 | 6: 'Rotated 90 CCW', |
9bf7563d | 176 | 7: 'Mirrored horizontal then rotated 90 CW', |
ab7281fe | 177 | 8: 'Rotated 90 CW'}), |
9bf7563d JW |
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', | |
ab7281fe SS |
255 | 5: 'Pattern', |
256 | 6: 'Partial', | |
257 | 255: 'other'}), | |
9bf7563d JW |
258 | 0x9208: ('LightSource', |
259 | {0: 'Unknown', | |
260 | 1: 'Daylight', | |
261 | 2: 'Fluorescent', | |
ab7281fe SS |
262 | 3: 'Tungsten (incandescent light)', |
263 | 4: 'Flash', | |
264 | 9: 'Fine weather', | |
265 | 10: 'Cloudy weather', | |
9bf7563d | 266 | 11: 'Shade', |
ab7281fe SS |
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', | |
9bf7563d JW |
274 | 20: 'D55', |
275 | 21: 'D65', | |
276 | 22: 'D75', | |
ab7281fe SS |
277 | 23: 'D50', |
278 | 24: 'ISO studio tungsten', | |
279 | 255: 'other light source',}), | |
9bf7563d | 280 | 0x9209: ('Flash', |
ab7281fe SS |
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'}), | |
9bf7563d JW |
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', ), | |
ab7281fe SS |
428 | 0x001B: ('GPSProcessingMethod', ), |
429 | 0x001C: ('GPSAreaInformation', ), | |
9bf7563d | 430 | 0x001D: ('GPSDate', ), |
ab7281fe | 431 | 0x001E: ('GPSDifferential', ), |
9bf7563d JW |
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): | |
6e60238b OHO |
1252 | try: |
1253 | s= '(0x%04X) %s=%s @ %d' % (self.tag, | |
9bf7563d JW |
1254 | FIELD_TYPES[self.field_type][2], |
1255 | self.printable, | |
1256 | self.field_offset) | |
6e60238b OHO |
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 | |
9bf7563d JW |
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) | |
ab7281fe SS |
1311 | next_ifd = self.s2n(ifd+2+12*entries, 4) |
1312 | if next_ifd == ifd: | |
1313 | return 0 | |
1314 | else: | |
1315 | return next_ifd | |
9bf7563d JW |
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 | |
6e60238b OHO |
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 = '' | |
9bf7563d JW |
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)): | |
ab7281fe SS |
1600 | if i[0] in self.tags: |
1601 | self.canon_decode_tag(self.tags[i[0]].values, i[1]) | |
9bf7563d JW |
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 | |
6e60238b OHO |
1647 | if debug: print "JPEG format recognized data[0:2] == '0xFFD8'." |
1648 | base = 2 | |
9bf7563d | 1649 | while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'): |
6e60238b | 1650 | if debug: print "data[2] == 0xxFF data[3]==%x and data[6:10] = %s"%(ord(data[3]),data[6:10]) |
9bf7563d | 1651 | length = ord(data[4])*256+ord(data[5]) |
6e60238b | 1652 | if debug: print "Length offset is",length |
9bf7563d JW |
1653 | f.read(length-8) |
1654 | # fake an EXIF beginning of file | |
6e60238b | 1655 | # I don't think this is used. --gd |
9bf7563d JW |
1656 | data = '\xFF\x00'+f.read(10) |
1657 | fake_exif = 1 | |
6e60238b OHO |
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': | |
9bf7563d JW |
1737 | # detected EXIF header |
1738 | offset = f.tell() | |
1739 | endian = f.read(1) | |
6e60238b OHO |
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) | |
9bf7563d JW |
1751 | else: |
1752 | # no EXIF information | |
6e60238b OHO |
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] | |
9bf7563d JW |
1755 | return {} |
1756 | else: | |
1757 | # file format not recognized | |
6e60238b | 1758 | if debug: print "file format not recognized" |
9bf7563d JW |
1759 | return {} |
1760 | ||
1761 | # deal with the EXIF info we found | |
1762 | if debug: | |
ab7281fe SS |
1763 | print "Endian format is ",endian |
1764 | print {'I': 'Intel', 'M': 'Motorola', '\x01':'Adobe Ducky', 'd':'XMP/Adobe unknown' }[endian], 'format' | |
9bf7563d JW |
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 | ||
1896 |