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 | | |