Mercurial > public > madeira
comparison mysite/photologue/utils/EXIF.py @ 1:0dcfcdf50c62
Initial import of Madeira project from the private repository.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Mon, 06 Apr 2009 03:10:59 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
0:df0370bfe3f0 | 1:0dcfcdf50c62 |
---|---|
1 #!/usr/bin/env python | |
2 # -*- coding: utf-8 -*- | |
3 # | |
4 # Library to extract EXIF information from digital camera image files | |
5 # http://sourceforge.net/projects/exif-py/ | |
6 # | |
7 # VERSION 1.0.7 | |
8 # | |
9 # To use this library call with: | |
10 # f = open(path_name, 'rb') | |
11 # tags = EXIF.process_file(f) | |
12 # | |
13 # To ignore makerNote tags, pass the -q or --quick | |
14 # command line arguments, or as | |
15 # f = open(path_name, 'rb') | |
16 # tags = EXIF.process_file(f, details=False) | |
17 # | |
18 # To stop processing after a certain tag is retrieved, | |
19 # pass the -t TAG or --stop-tag TAG argument, or as | |
20 # f = open(path_name, 'rb') | |
21 # tags = EXIF.process_file(f, stop_tag='TAG') | |
22 # | |
23 # where TAG is a valid tag name, ex 'DateTimeOriginal' | |
24 # | |
25 # These are useful when you are retrieving a large list of images | |
26 # | |
27 # Returned tags will be a dictionary mapping names of EXIF tags to their | |
28 # values in the file named by path_name. You can process the tags | |
29 # as you wish. In particular, you can iterate through all the tags with: | |
30 # for tag in tags.keys(): | |
31 # if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename', | |
32 # 'EXIF MakerNote'): | |
33 # print "Key: %s, value %s" % (tag, tags[tag]) | |
34 # (This code uses the if statement to avoid printing out a few of the | |
35 # tags that tend to be long or boring.) | |
36 # | |
37 # The tags dictionary will include keys for all of the usual EXIF | |
38 # tags, and will also include keys for Makernotes used by some | |
39 # cameras, for which we have a good specification. | |
40 # | |
41 # Note that the dictionary keys are the IFD name followed by the | |
42 # tag name. For example: | |
43 # 'EXIF DateTimeOriginal', 'Image Orientation', 'MakerNote FocusMode' | |
44 # | |
45 # Copyright (c) 2002-2007 Gene Cash All rights reserved | |
46 # Copyright (c) 2007 Ianaré Sévi All rights reserved | |
47 # | |
48 # Redistribution and use in source and binary forms, with or without | |
49 # modification, are permitted provided that the following conditions | |
50 # are met: | |
51 # | |
52 # 1. Redistributions of source code must retain the above copyright | |
53 # notice, this list of conditions and the following disclaimer. | |
54 # | |
55 # 2. Redistributions in binary form must reproduce the above | |
56 # copyright notice, this list of conditions and the following | |
57 # disclaimer in the documentation and/or other materials provided | |
58 # with the distribution. | |
59 # | |
60 # 3. Neither the name of the authors nor the names of its contributors | |
61 # may be used to endorse or promote products derived from this | |
62 # software without specific prior written permission. | |
63 # | |
64 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
65 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
66 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
67 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
68 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
69 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
70 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
71 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
72 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
73 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
74 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
75 # | |
76 # | |
77 # ----- See 'changes.txt' file for all contributors and changes ----- # | |
78 # | |
79 | |
80 | |
81 # Don't throw an exception when given an out of range character. | |
82 def make_string(seq): | |
83 str = "" | |
84 for c in seq: | |
85 # Screen out non-printing characters | |
86 if 32 <= c and c < 256: | |
87 str += chr(c) | |
88 # If no printing chars | |
89 if not str: | |
90 return seq | |
91 return str | |
92 | |
93 # Special version to deal with the code in the first 8 bytes of a user comment. | |
94 def make_string_uc(seq): | |
95 code = seq[0:8] | |
96 seq = seq[8:] | |
97 # Of course, this is only correct if ASCII, and the standard explicitly | |
98 # allows JIS and Unicode. | |
99 return make_string(seq) | |
100 | |
101 # field type descriptions as (length, abbreviation, full name) tuples | |
102 FIELD_TYPES = ( | |
103 (0, 'X', 'Proprietary'), # no such type | |
104 (1, 'B', 'Byte'), | |
105 (1, 'A', 'ASCII'), | |
106 (2, 'S', 'Short'), | |
107 (4, 'L', 'Long'), | |
108 (8, 'R', 'Ratio'), | |
109 (1, 'SB', 'Signed Byte'), | |
110 (1, 'U', 'Undefined'), | |
111 (2, 'SS', 'Signed Short'), | |
112 (4, 'SL', 'Signed Long'), | |
113 (8, 'SR', 'Signed Ratio'), | |
114 ) | |
115 | |
116 # dictionary of main EXIF tag names | |
117 # first element of tuple is tag name, optional second element is | |
118 # another dictionary giving names to values | |
119 EXIF_TAGS = { | |
120 0x0100: ('ImageWidth', ), | |
121 0x0101: ('ImageLength', ), | |
122 0x0102: ('BitsPerSample', ), | |
123 0x0103: ('Compression', | |
124 {1: 'Uncompressed TIFF', | |
125 6: 'JPEG Compressed'}), | |
126 0x0106: ('PhotometricInterpretation', ), | |
127 0x010A: ('FillOrder', ), | |
128 0x010D: ('DocumentName', ), | |
129 0x010E: ('ImageDescription', ), | |
130 0x010F: ('Make', ), | |
131 0x0110: ('Model', ), | |
132 0x0111: ('StripOffsets', ), | |
133 0x0112: ('Orientation', | |
134 {1: 'Horizontal (normal)', | |
135 2: 'Mirrored horizontal', | |
136 3: 'Rotated 180', | |
137 4: 'Mirrored vertical', | |
138 5: 'Mirrored horizontal then rotated 90 CCW', | |
139 6: 'Rotated 90 CW', | |
140 7: 'Mirrored horizontal then rotated 90 CW', | |
141 8: 'Rotated 90 CCW'}), | |
142 0x0115: ('SamplesPerPixel', ), | |
143 0x0116: ('RowsPerStrip', ), | |
144 0x0117: ('StripByteCounts', ), | |
145 0x011A: ('XResolution', ), | |
146 0x011B: ('YResolution', ), | |
147 0x011C: ('PlanarConfiguration', ), | |
148 0x0128: ('ResolutionUnit', | |
149 {1: 'Not Absolute', | |
150 2: 'Pixels/Inch', | |
151 3: 'Pixels/Centimeter'}), | |
152 0x012D: ('TransferFunction', ), | |
153 0x0131: ('Software', ), | |
154 0x0132: ('DateTime', ), | |
155 0x013B: ('Artist', ), | |
156 0x013E: ('WhitePoint', ), | |
157 0x013F: ('PrimaryChromaticities', ), | |
158 0x0156: ('TransferRange', ), | |
159 0x0200: ('JPEGProc', ), | |
160 0x0201: ('JPEGInterchangeFormat', ), | |
161 0x0202: ('JPEGInterchangeFormatLength', ), | |
162 0x0211: ('YCbCrCoefficients', ), | |
163 0x0212: ('YCbCrSubSampling', ), | |
164 0x0213: ('YCbCrPositioning', ), | |
165 0x0214: ('ReferenceBlackWhite', ), | |
166 0x828D: ('CFARepeatPatternDim', ), | |
167 0x828E: ('CFAPattern', ), | |
168 0x828F: ('BatteryLevel', ), | |
169 0x8298: ('Copyright', ), | |
170 0x829A: ('ExposureTime', ), | |
171 0x829D: ('FNumber', ), | |
172 0x83BB: ('IPTC/NAA', ), | |
173 0x8769: ('ExifOffset', ), | |
174 0x8773: ('InterColorProfile', ), | |
175 0x8822: ('ExposureProgram', | |
176 {0: 'Unidentified', | |
177 1: 'Manual', | |
178 2: 'Program Normal', | |
179 3: 'Aperture Priority', | |
180 4: 'Shutter Priority', | |
181 5: 'Program Creative', | |
182 6: 'Program Action', | |
183 7: 'Portrait Mode', | |
184 8: 'Landscape Mode'}), | |
185 0x8824: ('SpectralSensitivity', ), | |
186 0x8825: ('GPSInfo', ), | |
187 0x8827: ('ISOSpeedRatings', ), | |
188 0x8828: ('OECF', ), | |
189 # print as string | |
190 0x9000: ('ExifVersion', make_string), | |
191 0x9003: ('DateTimeOriginal', ), | |
192 0x9004: ('DateTimeDigitized', ), | |
193 0x9101: ('ComponentsConfiguration', | |
194 {0: '', | |
195 1: 'Y', | |
196 2: 'Cb', | |
197 3: 'Cr', | |
198 4: 'Red', | |
199 5: 'Green', | |
200 6: 'Blue'}), | |
201 0x9102: ('CompressedBitsPerPixel', ), | |
202 0x9201: ('ShutterSpeedValue', ), | |
203 0x9202: ('ApertureValue', ), | |
204 0x9203: ('BrightnessValue', ), | |
205 0x9204: ('ExposureBiasValue', ), | |
206 0x9205: ('MaxApertureValue', ), | |
207 0x9206: ('SubjectDistance', ), | |
208 0x9207: ('MeteringMode', | |
209 {0: 'Unidentified', | |
210 1: 'Average', | |
211 2: 'CenterWeightedAverage', | |
212 3: 'Spot', | |
213 4: 'MultiSpot'}), | |
214 0x9208: ('LightSource', | |
215 {0: 'Unknown', | |
216 1: 'Daylight', | |
217 2: 'Fluorescent', | |
218 3: 'Tungsten', | |
219 10: 'Flash', | |
220 17: 'Standard Light A', | |
221 18: 'Standard Light B', | |
222 19: 'Standard Light C', | |
223 20: 'D55', | |
224 21: 'D65', | |
225 22: 'D75', | |
226 255: 'Other'}), | |
227 0x9209: ('Flash', {0: 'No', | |
228 1: 'Fired', | |
229 5: 'Fired (?)', # no return sensed | |
230 7: 'Fired (!)', # return sensed | |
231 9: 'Fill Fired', | |
232 13: 'Fill Fired (?)', | |
233 15: 'Fill Fired (!)', | |
234 16: 'Off', | |
235 24: 'Auto Off', | |
236 25: 'Auto Fired', | |
237 29: 'Auto Fired (?)', | |
238 31: 'Auto Fired (!)', | |
239 32: 'Not Available'}), | |
240 0x920A: ('FocalLength', ), | |
241 0x9214: ('SubjectArea', ), | |
242 0x927C: ('MakerNote', ), | |
243 # print as string | |
244 0x9286: ('UserComment', make_string_uc), # First 8 bytes gives coding system e.g. ASCII vs. JIS vs Unicode | |
245 0x9290: ('SubSecTime', ), | |
246 0x9291: ('SubSecTimeOriginal', ), | |
247 0x9292: ('SubSecTimeDigitized', ), | |
248 # print as string | |
249 0xA000: ('FlashPixVersion', make_string), | |
250 0xA001: ('ColorSpace', ), | |
251 0xA002: ('ExifImageWidth', ), | |
252 0xA003: ('ExifImageLength', ), | |
253 0xA005: ('InteroperabilityOffset', ), | |
254 0xA20B: ('FlashEnergy', ), # 0x920B in TIFF/EP | |
255 0xA20C: ('SpatialFrequencyResponse', ), # 0x920C - - | |
256 0xA20E: ('FocalPlaneXResolution', ), # 0x920E - - | |
257 0xA20F: ('FocalPlaneYResolution', ), # 0x920F - - | |
258 0xA210: ('FocalPlaneResolutionUnit', ), # 0x9210 - - | |
259 0xA214: ('SubjectLocation', ), # 0x9214 - - | |
260 0xA215: ('ExposureIndex', ), # 0x9215 - - | |
261 0xA217: ('SensingMethod', ), # 0x9217 - - | |
262 0xA300: ('FileSource', | |
263 {3: 'Digital Camera'}), | |
264 0xA301: ('SceneType', | |
265 {1: 'Directly Photographed'}), | |
266 0xA302: ('CVAPattern', ), | |
267 0xA401: ('CustomRendered', ), | |
268 0xA402: ('ExposureMode', | |
269 {0: 'Auto Exposure', | |
270 1: 'Manual Exposure', | |
271 2: 'Auto Bracket'}), | |
272 0xA403: ('WhiteBalance', | |
273 {0: 'Auto', | |
274 1: 'Manual'}), | |
275 0xA404: ('DigitalZoomRatio', ), | |
276 0xA405: ('FocalLengthIn35mm', ), | |
277 0xA406: ('SceneCaptureType', ), | |
278 0xA407: ('GainControl', ), | |
279 0xA408: ('Contrast', ), | |
280 0xA409: ('Saturation', ), | |
281 0xA40A: ('Sharpness', ), | |
282 0xA40C: ('SubjectDistanceRange', ), | |
283 } | |
284 | |
285 # interoperability tags | |
286 INTR_TAGS = { | |
287 0x0001: ('InteroperabilityIndex', ), | |
288 0x0002: ('InteroperabilityVersion', ), | |
289 0x1000: ('RelatedImageFileFormat', ), | |
290 0x1001: ('RelatedImageWidth', ), | |
291 0x1002: ('RelatedImageLength', ), | |
292 } | |
293 | |
294 # GPS tags (not used yet, haven't seen camera with GPS) | |
295 GPS_TAGS = { | |
296 0x0000: ('GPSVersionID', ), | |
297 0x0001: ('GPSLatitudeRef', ), | |
298 0x0002: ('GPSLatitude', ), | |
299 0x0003: ('GPSLongitudeRef', ), | |
300 0x0004: ('GPSLongitude', ), | |
301 0x0005: ('GPSAltitudeRef', ), | |
302 0x0006: ('GPSAltitude', ), | |
303 0x0007: ('GPSTimeStamp', ), | |
304 0x0008: ('GPSSatellites', ), | |
305 0x0009: ('GPSStatus', ), | |
306 0x000A: ('GPSMeasureMode', ), | |
307 0x000B: ('GPSDOP', ), | |
308 0x000C: ('GPSSpeedRef', ), | |
309 0x000D: ('GPSSpeed', ), | |
310 0x000E: ('GPSTrackRef', ), | |
311 0x000F: ('GPSTrack', ), | |
312 0x0010: ('GPSImgDirectionRef', ), | |
313 0x0011: ('GPSImgDirection', ), | |
314 0x0012: ('GPSMapDatum', ), | |
315 0x0013: ('GPSDestLatitudeRef', ), | |
316 0x0014: ('GPSDestLatitude', ), | |
317 0x0015: ('GPSDestLongitudeRef', ), | |
318 0x0016: ('GPSDestLongitude', ), | |
319 0x0017: ('GPSDestBearingRef', ), | |
320 0x0018: ('GPSDestBearing', ), | |
321 0x0019: ('GPSDestDistanceRef', ), | |
322 0x001A: ('GPSDestDistance', ), | |
323 } | |
324 | |
325 # Ignore these tags when quick processing | |
326 # 0x927C is MakerNote Tags | |
327 # 0x9286 is user comment | |
328 IGNORE_TAGS=(0x9286, 0x927C) | |
329 | |
330 # http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp | |
331 def nikon_ev_bias(seq): | |
332 # First digit seems to be in steps of 1/6 EV. | |
333 # Does the third value mean the step size? It is usually 6, | |
334 # but it is 12 for the ExposureDifference. | |
335 # | |
336 if seq == [252, 1, 6, 0]: | |
337 return "-2/3 EV" | |
338 if seq == [253, 1, 6, 0]: | |
339 return "-1/2 EV" | |
340 if seq == [254, 1, 6, 0]: | |
341 return "-1/3 EV" | |
342 if seq == [0, 1, 6, 0]: | |
343 return "0 EV" | |
344 if seq == [2, 1, 6, 0]: | |
345 return "+1/3 EV" | |
346 if seq == [3, 1, 6, 0]: | |
347 return "+1/2 EV" | |
348 if seq == [4, 1, 6, 0]: | |
349 return "+2/3 EV" | |
350 # Handle combinations not in the table. | |
351 a = seq[0] | |
352 # Causes headaches for the +/- logic, so special case it. | |
353 if a == 0: | |
354 return "0 EV" | |
355 if a > 127: | |
356 a = 256 - a | |
357 ret_str = "-" | |
358 else: | |
359 ret_str = "+" | |
360 b = seq[2] # Assume third value means the step size | |
361 whole = a / b | |
362 a = a % b | |
363 if whole != 0: | |
364 ret_str = ret_str + str(whole) + " " | |
365 if a == 0: | |
366 ret_str = ret_str + "EV" | |
367 else: | |
368 r = Ratio(a, b) | |
369 ret_str = ret_str + r.__repr__() + " EV" | |
370 return ret_str | |
371 | |
372 # Nikon E99x MakerNote Tags | |
373 MAKERNOTE_NIKON_NEWER_TAGS={ | |
374 0x0001: ('MakernoteVersion', make_string), # Sometimes binary | |
375 0x0002: ('ISOSetting', ), | |
376 0x0003: ('ColorMode', ), | |
377 0x0004: ('Quality', ), | |
378 0x0005: ('Whitebalance', ), | |
379 0x0006: ('ImageSharpening', ), | |
380 0x0007: ('FocusMode', ), | |
381 0x0008: ('FlashSetting', ), | |
382 0x0009: ('AutoFlashMode', ), | |
383 0x000B: ('WhiteBalanceBias', ), | |
384 0x000C: ('WhiteBalanceRBCoeff', ), | |
385 0x000D: ('ProgramShift', nikon_ev_bias), | |
386 # Nearly the same as the other EV vals, but step size is 1/12 EV (?) | |
387 0x000E: ('ExposureDifference', nikon_ev_bias), | |
388 0x000F: ('ISOSelection', ), | |
389 0x0011: ('NikonPreview', ), | |
390 0x0012: ('FlashCompensation', nikon_ev_bias), | |
391 0x0013: ('ISOSpeedRequested', ), | |
392 0x0016: ('PhotoCornerCoordinates', ), | |
393 # 0x0017: Unknown, but most likely an EV value | |
394 0x0018: ('FlashBracketCompensationApplied', nikon_ev_bias), | |
395 0x0019: ('AEBracketCompensationApplied', ), | |
396 0x001A: ('ImageProcessing', ), | |
397 0x0080: ('ImageAdjustment', ), | |
398 0x0081: ('ToneCompensation', ), | |
399 0x0082: ('AuxiliaryLens', ), | |
400 0x0083: ('LensType', ), | |
401 0x0084: ('LensMinMaxFocalMaxAperture', ), | |
402 0x0085: ('ManualFocusDistance', ), | |
403 0x0086: ('DigitalZoomFactor', ), | |
404 0x0087: ('FlashMode', | |
405 {0x00: 'Did Not Fire', | |
406 0x01: 'Fired, Manual', | |
407 0x07: 'Fired, External', | |
408 0x08: 'Fired, Commander Mode ', | |
409 0x09: 'Fired, TTL Mode'}), | |
410 0x0088: ('AFFocusPosition', | |
411 {0x0000: 'Center', | |
412 0x0100: 'Top', | |
413 0x0200: 'Bottom', | |
414 0x0300: 'Left', | |
415 0x0400: 'Right'}), | |
416 0x0089: ('BracketingMode', | |
417 {0x00: 'Single frame, no bracketing', | |
418 0x01: 'Continuous, no bracketing', | |
419 0x02: 'Timer, no bracketing', | |
420 0x10: 'Single frame, exposure bracketing', | |
421 0x11: 'Continuous, exposure bracketing', | |
422 0x12: 'Timer, exposure bracketing', | |
423 0x40: 'Single frame, white balance bracketing', | |
424 0x41: 'Continuous, white balance bracketing', | |
425 0x42: 'Timer, white balance bracketing'}), | |
426 0x008A: ('AutoBracketRelease', ), | |
427 0x008B: ('LensFStops', ), | |
428 0x008C: ('NEFCurve2', ), | |
429 0x008D: ('ColorMode', ), | |
430 0x008F: ('SceneMode', ), | |
431 0x0090: ('LightingType', ), | |
432 0x0091: ('ShotInfo', ), # First 4 bytes are probably a version number in ASCII | |
433 0x0092: ('HueAdjustment', ), | |
434 # 0x0093: ('SaturationAdjustment', ), | |
435 0x0094: ('Saturation', # Name conflict with 0x00AA !! | |
436 {-3: 'B&W', | |
437 -2: '-2', | |
438 -1: '-1', | |
439 0: '0', | |
440 1: '1', | |
441 2: '2'}), | |
442 0x0095: ('NoiseReduction', ), | |
443 0x0096: ('NEFCurve2', ), | |
444 0x0097: ('ColorBalance', ), | |
445 0x0098: ('LensData', ), # First 4 bytes are a version number in ASCII | |
446 0x0099: ('RawImageCenter', ), | |
447 0x009A: ('SensorPixelSize', ), | |
448 0x009C: ('Scene Assist', ), | |
449 0x00A0: ('SerialNumber', ), | |
450 0x00A2: ('ImageDataSize', ), | |
451 # A4: In NEF, looks like a 4 byte ASCII version number | |
452 0x00A5: ('ImageCount', ), | |
453 0x00A6: ('DeletedImageCount', ), | |
454 0x00A7: ('TotalShutterReleases', ), | |
455 # A8: ExposureMode? JPG: First 4 bytes are probably a version number in ASCII | |
456 # But in a sample NEF, its 8 zeros, then the string "NORMAL" | |
457 0x00A9: ('ImageOptimization', ), | |
458 0x00AA: ('Saturation', ), | |
459 0x00AB: ('DigitalVariProgram', ), | |
460 0x00AC: ('ImageStabilization', ), | |
461 0x00AD: ('Responsive AF', ), # 'AFResponse' | |
462 0x0010: ('DataDump', ), | |
463 } | |
464 | |
465 MAKERNOTE_NIKON_OLDER_TAGS = { | |
466 0x0003: ('Quality', | |
467 {1: 'VGA Basic', | |
468 2: 'VGA Normal', | |
469 3: 'VGA Fine', | |
470 4: 'SXGA Basic', | |
471 5: 'SXGA Normal', | |
472 6: 'SXGA Fine'}), | |
473 0x0004: ('ColorMode', | |
474 {1: 'Color', | |
475 2: 'Monochrome'}), | |
476 0x0005: ('ImageAdjustment', | |
477 {0: 'Normal', | |
478 1: 'Bright+', | |
479 2: 'Bright-', | |
480 3: 'Contrast+', | |
481 4: 'Contrast-'}), | |
482 0x0006: ('CCDSpeed', | |
483 {0: 'ISO 80', | |
484 2: 'ISO 160', | |
485 4: 'ISO 320', | |
486 5: 'ISO 100'}), | |
487 0x0007: ('WhiteBalance', | |
488 {0: 'Auto', | |
489 1: 'Preset', | |
490 2: 'Daylight', | |
491 3: 'Incandescent', | |
492 4: 'Fluorescent', | |
493 5: 'Cloudy', | |
494 6: 'Speed Light'}), | |
495 } | |
496 | |
497 # decode Olympus SpecialMode tag in MakerNote | |
498 def olympus_special_mode(v): | |
499 a={ | |
500 0: 'Normal', | |
501 1: 'Unknown', | |
502 2: 'Fast', | |
503 3: 'Panorama'} | |
504 b={ | |
505 0: 'Non-panoramic', | |
506 1: 'Left to right', | |
507 2: 'Right to left', | |
508 3: 'Bottom to top', | |
509 4: 'Top to bottom'} | |
510 if v[0] not in a or v[2] not in b: | |
511 return v | |
512 return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]]) | |
513 | |
514 MAKERNOTE_OLYMPUS_TAGS={ | |
515 # ah HAH! those sneeeeeaky bastids! this is how they get past the fact | |
516 # that a JPEG thumbnail is not allowed in an uncompressed TIFF file | |
517 0x0100: ('JPEGThumbnail', ), | |
518 0x0200: ('SpecialMode', olympus_special_mode), | |
519 0x0201: ('JPEGQual', | |
520 {1: 'SQ', | |
521 2: 'HQ', | |
522 3: 'SHQ'}), | |
523 0x0202: ('Macro', | |
524 {0: 'Normal', | |
525 1: 'Macro', | |
526 2: 'SuperMacro'}), | |
527 0x0203: ('BWMode', | |
528 {0: 'Off', | |
529 1: 'On'}), | |
530 0x0204: ('DigitalZoom', ), | |
531 0x0205: ('FocalPlaneDiagonal', ), | |
532 0x0206: ('LensDistortionParams', ), | |
533 0x0207: ('SoftwareRelease', ), | |
534 0x0208: ('PictureInfo', ), | |
535 0x0209: ('CameraID', make_string), # print as string | |
536 0x0F00: ('DataDump', ), | |
537 0x0300: ('PreCaptureFrames', ), | |
538 0x0404: ('SerialNumber', ), | |
539 0x1000: ('ShutterSpeedValue', ), | |
540 0x1001: ('ISOValue', ), | |
541 0x1002: ('ApertureValue', ), | |
542 0x1003: ('BrightnessValue', ), | |
543 0x1004: ('FlashMode', ), | |
544 0x1004: ('FlashMode', | |
545 {2: 'On', | |
546 3: 'Off'}), | |
547 0x1005: ('FlashDevice', | |
548 {0: 'None', | |
549 1: 'Internal', | |
550 4: 'External', | |
551 5: 'Internal + External'}), | |
552 0x1006: ('ExposureCompensation', ), | |
553 0x1007: ('SensorTemperature', ), | |
554 0x1008: ('LensTemperature', ), | |
555 0x100b: ('FocusMode', | |
556 {0: 'Auto', | |
557 1: 'Manual'}), | |
558 0x1017: ('RedBalance', ), | |
559 0x1018: ('BlueBalance', ), | |
560 0x101a: ('SerialNumber', ), | |
561 0x1023: ('FlashExposureComp', ), | |
562 0x1026: ('ExternalFlashBounce', | |
563 {0: 'No', | |
564 1: 'Yes'}), | |
565 0x1027: ('ExternalFlashZoom', ), | |
566 0x1028: ('ExternalFlashMode', ), | |
567 0x1029: ('Contrast int16u', | |
568 {0: 'High', | |
569 1: 'Normal', | |
570 2: 'Low'}), | |
571 0x102a: ('SharpnessFactor', ), | |
572 0x102b: ('ColorControl', ), | |
573 0x102c: ('ValidBits', ), | |
574 0x102d: ('CoringFilter', ), | |
575 0x102e: ('OlympusImageWidth', ), | |
576 0x102f: ('OlympusImageHeight', ), | |
577 0x1034: ('CompressionRatio', ), | |
578 0x1035: ('PreviewImageValid', | |
579 {0: 'No', | |
580 1: 'Yes'}), | |
581 0x1036: ('PreviewImageStart', ), | |
582 0x1037: ('PreviewImageLength', ), | |
583 0x1039: ('CCDScanMode', | |
584 {0: 'Interlaced', | |
585 1: 'Progressive'}), | |
586 0x103a: ('NoiseReduction', | |
587 {0: 'Off', | |
588 1: 'On'}), | |
589 0x103b: ('InfinityLensStep', ), | |
590 0x103c: ('NearLensStep', ), | |
591 | |
592 # TODO - these need extra definitions | |
593 # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html | |
594 0x2010: ('Equipment', ), | |
595 0x2020: ('CameraSettings', ), | |
596 0x2030: ('RawDevelopment', ), | |
597 0x2040: ('ImageProcessing', ), | |
598 0x2050: ('FocusInfo', ), | |
599 0x3000: ('RawInfo ', ), | |
600 } | |
601 | |
602 # 0x2020 CameraSettings | |
603 MAKERNOTE_OLYMPUS_TAG_0x2020={ | |
604 0x0100: ('PreviewImageValid', | |
605 {0: 'No', | |
606 1: 'Yes'}), | |
607 0x0101: ('PreviewImageStart', ), | |
608 0x0102: ('PreviewImageLength', ), | |
609 0x0200: ('ExposureMode', { | |
610 1: 'Manual', | |
611 2: 'Program', | |
612 3: 'Aperture-priority AE', | |
613 4: 'Shutter speed priority AE', | |
614 5: 'Program-shift'}), | |
615 0x0201: ('AELock', | |
616 {0: 'Off', | |
617 1: 'On'}), | |
618 0x0202: ('MeteringMode', | |
619 {2: 'Center Weighted', | |
620 3: 'Spot', | |
621 5: 'ESP', | |
622 261: 'Pattern+AF', | |
623 515: 'Spot+Highlight control', | |
624 1027: 'Spot+Shadow control'}), | |
625 0x0300: ('MacroMode', | |
626 {0: 'Off', | |
627 1: 'On'}), | |
628 0x0301: ('FocusMode', | |
629 {0: 'Single AF', | |
630 1: 'Sequential shooting AF', | |
631 2: 'Continuous AF', | |
632 3: 'Multi AF', | |
633 10: 'MF'}), | |
634 0x0302: ('FocusProcess', | |
635 {0: 'AF Not Used', | |
636 1: 'AF Used'}), | |
637 0x0303: ('AFSearch', | |
638 {0: 'Not Ready', | |
639 1: 'Ready'}), | |
640 0x0304: ('AFAreas', ), | |
641 0x0401: ('FlashExposureCompensation', ), | |
642 0x0500: ('WhiteBalance2', | |
643 {0: 'Auto', | |
644 16: '7500K (Fine Weather with Shade)', | |
645 17: '6000K (Cloudy)', | |
646 18: '5300K (Fine Weather)', | |
647 20: '3000K (Tungsten light)', | |
648 21: '3600K (Tungsten light-like)', | |
649 33: '6600K (Daylight fluorescent)', | |
650 34: '4500K (Neutral white fluorescent)', | |
651 35: '4000K (Cool white fluorescent)', | |
652 48: '3600K (Tungsten light-like)', | |
653 256: 'Custom WB 1', | |
654 257: 'Custom WB 2', | |
655 258: 'Custom WB 3', | |
656 259: 'Custom WB 4', | |
657 512: 'Custom WB 5400K', | |
658 513: 'Custom WB 2900K', | |
659 514: 'Custom WB 8000K', }), | |
660 0x0501: ('WhiteBalanceTemperature', ), | |
661 0x0502: ('WhiteBalanceBracket', ), | |
662 0x0503: ('CustomSaturation', ), # (3 numbers: 1. CS Value, 2. Min, 3. Max) | |
663 0x0504: ('ModifiedSaturation', | |
664 {0: 'Off', | |
665 1: 'CM1 (Red Enhance)', | |
666 2: 'CM2 (Green Enhance)', | |
667 3: 'CM3 (Blue Enhance)', | |
668 4: 'CM4 (Skin Tones)'}), | |
669 0x0505: ('ContrastSetting', ), # (3 numbers: 1. Contrast, 2. Min, 3. Max) | |
670 0x0506: ('SharpnessSetting', ), # (3 numbers: 1. Sharpness, 2. Min, 3. Max) | |
671 0x0507: ('ColorSpace', | |
672 {0: 'sRGB', | |
673 1: 'Adobe RGB', | |
674 2: 'Pro Photo RGB'}), | |
675 0x0509: ('SceneMode', | |
676 {0: 'Standard', | |
677 6: 'Auto', | |
678 7: 'Sport', | |
679 8: 'Portrait', | |
680 9: 'Landscape+Portrait', | |
681 10: 'Landscape', | |
682 11: 'Night scene', | |
683 13: 'Panorama', | |
684 16: 'Landscape+Portrait', | |
685 17: 'Night+Portrait', | |
686 19: 'Fireworks', | |
687 20: 'Sunset', | |
688 22: 'Macro', | |
689 25: 'Documents', | |
690 26: 'Museum', | |
691 28: 'Beach&Snow', | |
692 30: 'Candle', | |
693 35: 'Underwater Wide1', | |
694 36: 'Underwater Macro', | |
695 39: 'High Key', | |
696 40: 'Digital Image Stabilization', | |
697 44: 'Underwater Wide2', | |
698 45: 'Low Key', | |
699 46: 'Children', | |
700 48: 'Nature Macro'}), | |
701 0x050a: ('NoiseReduction', | |
702 {0: 'Off', | |
703 1: 'Noise Reduction', | |
704 2: 'Noise Filter', | |
705 3: 'Noise Reduction + Noise Filter', | |
706 4: 'Noise Filter (ISO Boost)', | |
707 5: 'Noise Reduction + Noise Filter (ISO Boost)'}), | |
708 0x050b: ('DistortionCorrection', | |
709 {0: 'Off', | |
710 1: 'On'}), | |
711 0x050c: ('ShadingCompensation', | |
712 {0: 'Off', | |
713 1: 'On'}), | |
714 0x050d: ('CompressionFactor', ), | |
715 0x050f: ('Gradation', | |
716 {'-1 -1 1': 'Low Key', | |
717 '0 -1 1': 'Normal', | |
718 '1 -1 1': 'High Key'}), | |
719 0x0520: ('PictureMode', | |
720 {1: 'Vivid', | |
721 2: 'Natural', | |
722 3: 'Muted', | |
723 256: 'Monotone', | |
724 512: 'Sepia'}), | |
725 0x0521: ('PictureModeSaturation', ), | |
726 0x0522: ('PictureModeHue?', ), | |
727 0x0523: ('PictureModeContrast', ), | |
728 0x0524: ('PictureModeSharpness', ), | |
729 0x0525: ('PictureModeBWFilter', | |
730 {0: 'n/a', | |
731 1: 'Neutral', | |
732 2: 'Yellow', | |
733 3: 'Orange', | |
734 4: 'Red', | |
735 5: 'Green'}), | |
736 0x0526: ('PictureModeTone', | |
737 {0: 'n/a', | |
738 1: 'Neutral', | |
739 2: 'Sepia', | |
740 3: 'Blue', | |
741 4: 'Purple', | |
742 5: 'Green'}), | |
743 0x0600: ('Sequence', ), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits | |
744 0x0601: ('PanoramaMode', ), # (2 numbers: 1. Mode, 2. Shot number) | |
745 0x0603: ('ImageQuality2', | |
746 {1: 'SQ', | |
747 2: 'HQ', | |
748 3: 'SHQ', | |
749 4: 'RAW'}), | |
750 0x0901: ('ManometerReading', ), | |
751 } | |
752 | |
753 | |
754 MAKERNOTE_CASIO_TAGS={ | |
755 0x0001: ('RecordingMode', | |
756 {1: 'Single Shutter', | |
757 2: 'Panorama', | |
758 3: 'Night Scene', | |
759 4: 'Portrait', | |
760 5: 'Landscape'}), | |
761 0x0002: ('Quality', | |
762 {1: 'Economy', | |
763 2: 'Normal', | |
764 3: 'Fine'}), | |
765 0x0003: ('FocusingMode', | |
766 {2: 'Macro', | |
767 3: 'Auto Focus', | |
768 4: 'Manual Focus', | |
769 5: 'Infinity'}), | |
770 0x0004: ('FlashMode', | |
771 {1: 'Auto', | |
772 2: 'On', | |
773 3: 'Off', | |
774 4: 'Red Eye Reduction'}), | |
775 0x0005: ('FlashIntensity', | |
776 {11: 'Weak', | |
777 13: 'Normal', | |
778 15: 'Strong'}), | |
779 0x0006: ('Object Distance', ), | |
780 0x0007: ('WhiteBalance', | |
781 {1: 'Auto', | |
782 2: 'Tungsten', | |
783 3: 'Daylight', | |
784 4: 'Fluorescent', | |
785 5: 'Shade', | |
786 129: 'Manual'}), | |
787 0x000B: ('Sharpness', | |
788 {0: 'Normal', | |
789 1: 'Soft', | |
790 2: 'Hard'}), | |
791 0x000C: ('Contrast', | |
792 {0: 'Normal', | |
793 1: 'Low', | |
794 2: 'High'}), | |
795 0x000D: ('Saturation', | |
796 {0: 'Normal', | |
797 1: 'Low', | |
798 2: 'High'}), | |
799 0x0014: ('CCDSpeed', | |
800 {64: 'Normal', | |
801 80: 'Normal', | |
802 100: 'High', | |
803 125: '+1.0', | |
804 244: '+3.0', | |
805 250: '+2.0'}), | |
806 } | |
807 | |
808 MAKERNOTE_FUJIFILM_TAGS={ | |
809 0x0000: ('NoteVersion', make_string), | |
810 0x1000: ('Quality', ), | |
811 0x1001: ('Sharpness', | |
812 {1: 'Soft', | |
813 2: 'Soft', | |
814 3: 'Normal', | |
815 4: 'Hard', | |
816 5: 'Hard'}), | |
817 0x1002: ('WhiteBalance', | |
818 {0: 'Auto', | |
819 256: 'Daylight', | |
820 512: 'Cloudy', | |
821 768: 'DaylightColor-Fluorescent', | |
822 769: 'DaywhiteColor-Fluorescent', | |
823 770: 'White-Fluorescent', | |
824 1024: 'Incandescent', | |
825 3840: 'Custom'}), | |
826 0x1003: ('Color', | |
827 {0: 'Normal', | |
828 256: 'High', | |
829 512: 'Low'}), | |
830 0x1004: ('Tone', | |
831 {0: 'Normal', | |
832 256: 'High', | |
833 512: 'Low'}), | |
834 0x1010: ('FlashMode', | |
835 {0: 'Auto', | |
836 1: 'On', | |
837 2: 'Off', | |
838 3: 'Red Eye Reduction'}), | |
839 0x1011: ('FlashStrength', ), | |
840 0x1020: ('Macro', | |
841 {0: 'Off', | |
842 1: 'On'}), | |
843 0x1021: ('FocusMode', | |
844 {0: 'Auto', | |
845 1: 'Manual'}), | |
846 0x1030: ('SlowSync', | |
847 {0: 'Off', | |
848 1: 'On'}), | |
849 0x1031: ('PictureMode', | |
850 {0: 'Auto', | |
851 1: 'Portrait', | |
852 2: 'Landscape', | |
853 4: 'Sports', | |
854 5: 'Night', | |
855 6: 'Program AE', | |
856 256: 'Aperture Priority AE', | |
857 512: 'Shutter Priority AE', | |
858 768: 'Manual Exposure'}), | |
859 0x1100: ('MotorOrBracket', | |
860 {0: 'Off', | |
861 1: 'On'}), | |
862 0x1300: ('BlurWarning', | |
863 {0: 'Off', | |
864 1: 'On'}), | |
865 0x1301: ('FocusWarning', | |
866 {0: 'Off', | |
867 1: 'On'}), | |
868 0x1302: ('AEWarning', | |
869 {0: 'Off', | |
870 1: 'On'}), | |
871 } | |
872 | |
873 MAKERNOTE_CANON_TAGS = { | |
874 0x0006: ('ImageType', ), | |
875 0x0007: ('FirmwareVersion', ), | |
876 0x0008: ('ImageNumber', ), | |
877 0x0009: ('OwnerName', ), | |
878 } | |
879 | |
880 # this is in element offset, name, optional value dictionary format | |
881 MAKERNOTE_CANON_TAG_0x001 = { | |
882 1: ('Macromode', | |
883 {1: 'Macro', | |
884 2: 'Normal'}), | |
885 2: ('SelfTimer', ), | |
886 3: ('Quality', | |
887 {2: 'Normal', | |
888 3: 'Fine', | |
889 5: 'Superfine'}), | |
890 4: ('FlashMode', | |
891 {0: 'Flash Not Fired', | |
892 1: 'Auto', | |
893 2: 'On', | |
894 3: 'Red-Eye Reduction', | |
895 4: 'Slow Synchro', | |
896 5: 'Auto + Red-Eye Reduction', | |
897 6: 'On + Red-Eye Reduction', | |
898 16: 'external flash'}), | |
899 5: ('ContinuousDriveMode', | |
900 {0: 'Single Or Timer', | |
901 1: 'Continuous'}), | |
902 7: ('FocusMode', | |
903 {0: 'One-Shot', | |
904 1: 'AI Servo', | |
905 2: 'AI Focus', | |
906 3: 'MF', | |
907 4: 'Single', | |
908 5: 'Continuous', | |
909 6: 'MF'}), | |
910 10: ('ImageSize', | |
911 {0: 'Large', | |
912 1: 'Medium', | |
913 2: 'Small'}), | |
914 11: ('EasyShootingMode', | |
915 {0: 'Full Auto', | |
916 1: 'Manual', | |
917 2: 'Landscape', | |
918 3: 'Fast Shutter', | |
919 4: 'Slow Shutter', | |
920 5: 'Night', | |
921 6: 'B&W', | |
922 7: 'Sepia', | |
923 8: 'Portrait', | |
924 9: 'Sports', | |
925 10: 'Macro/Close-Up', | |
926 11: 'Pan Focus'}), | |
927 12: ('DigitalZoom', | |
928 {0: 'None', | |
929 1: '2x', | |
930 2: '4x'}), | |
931 13: ('Contrast', | |
932 {0xFFFF: 'Low', | |
933 0: 'Normal', | |
934 1: 'High'}), | |
935 14: ('Saturation', | |
936 {0xFFFF: 'Low', | |
937 0: 'Normal', | |
938 1: 'High'}), | |
939 15: ('Sharpness', | |
940 {0xFFFF: 'Low', | |
941 0: 'Normal', | |
942 1: 'High'}), | |
943 16: ('ISO', | |
944 {0: 'See ISOSpeedRatings Tag', | |
945 15: 'Auto', | |
946 16: '50', | |
947 17: '100', | |
948 18: '200', | |
949 19: '400'}), | |
950 17: ('MeteringMode', | |
951 {3: 'Evaluative', | |
952 4: 'Partial', | |
953 5: 'Center-weighted'}), | |
954 18: ('FocusType', | |
955 {0: 'Manual', | |
956 1: 'Auto', | |
957 3: 'Close-Up (Macro)', | |
958 8: 'Locked (Pan Mode)'}), | |
959 19: ('AFPointSelected', | |
960 {0x3000: 'None (MF)', | |
961 0x3001: 'Auto-Selected', | |
962 0x3002: 'Right', | |
963 0x3003: 'Center', | |
964 0x3004: 'Left'}), | |
965 20: ('ExposureMode', | |
966 {0: 'Easy Shooting', | |
967 1: 'Program', | |
968 2: 'Tv-priority', | |
969 3: 'Av-priority', | |
970 4: 'Manual', | |
971 5: 'A-DEP'}), | |
972 23: ('LongFocalLengthOfLensInFocalUnits', ), | |
973 24: ('ShortFocalLengthOfLensInFocalUnits', ), | |
974 25: ('FocalUnitsPerMM', ), | |
975 28: ('FlashActivity', | |
976 {0: 'Did Not Fire', | |
977 1: 'Fired'}), | |
978 29: ('FlashDetails', | |
979 {14: 'External E-TTL', | |
980 13: 'Internal Flash', | |
981 11: 'FP Sync Used', | |
982 7: '2nd("Rear")-Curtain Sync Used', | |
983 4: 'FP Sync Enabled'}), | |
984 32: ('FocusMode', | |
985 {0: 'Single', | |
986 1: 'Continuous'}), | |
987 } | |
988 | |
989 MAKERNOTE_CANON_TAG_0x004 = { | |
990 7: ('WhiteBalance', | |
991 {0: 'Auto', | |
992 1: 'Sunny', | |
993 2: 'Cloudy', | |
994 3: 'Tungsten', | |
995 4: 'Fluorescent', | |
996 5: 'Flash', | |
997 6: 'Custom'}), | |
998 9: ('SequenceNumber', ), | |
999 14: ('AFPointUsed', ), | |
1000 15: ('FlashBias', | |
1001 {0XFFC0: '-2 EV', | |
1002 0XFFCC: '-1.67 EV', | |
1003 0XFFD0: '-1.50 EV', | |
1004 0XFFD4: '-1.33 EV', | |
1005 0XFFE0: '-1 EV', | |
1006 0XFFEC: '-0.67 EV', | |
1007 0XFFF0: '-0.50 EV', | |
1008 0XFFF4: '-0.33 EV', | |
1009 0X0000: '0 EV', | |
1010 0X000C: '0.33 EV', | |
1011 0X0010: '0.50 EV', | |
1012 0X0014: '0.67 EV', | |
1013 0X0020: '1 EV', | |
1014 0X002C: '1.33 EV', | |
1015 0X0030: '1.50 EV', | |
1016 0X0034: '1.67 EV', | |
1017 0X0040: '2 EV'}), | |
1018 19: ('SubjectDistance', ), | |
1019 } | |
1020 | |
1021 # extract multibyte integer in Motorola format (little endian) | |
1022 def s2n_motorola(str): | |
1023 x = 0 | |
1024 for c in str: | |
1025 x = (x << 8) | ord(c) | |
1026 return x | |
1027 | |
1028 # extract multibyte integer in Intel format (big endian) | |
1029 def s2n_intel(str): | |
1030 x = 0 | |
1031 y = 0L | |
1032 for c in str: | |
1033 x = x | (ord(c) << y) | |
1034 y = y + 8 | |
1035 return x | |
1036 | |
1037 # ratio object that eventually will be able to reduce itself to lowest | |
1038 # common denominator for printing | |
1039 def gcd(a, b): | |
1040 if b == 0: | |
1041 return a | |
1042 else: | |
1043 return gcd(b, a % b) | |
1044 | |
1045 class Ratio: | |
1046 def __init__(self, num, den): | |
1047 self.num = num | |
1048 self.den = den | |
1049 | |
1050 def __repr__(self): | |
1051 self.reduce() | |
1052 if self.den == 1: | |
1053 return str(self.num) | |
1054 return '%d/%d' % (self.num, self.den) | |
1055 | |
1056 def reduce(self): | |
1057 div = gcd(self.num, self.den) | |
1058 if div > 1: | |
1059 self.num = self.num / div | |
1060 self.den = self.den / div | |
1061 | |
1062 # for ease of dealing with tags | |
1063 class IFD_Tag: | |
1064 def __init__(self, printable, tag, field_type, values, field_offset, | |
1065 field_length): | |
1066 # printable version of data | |
1067 self.printable = printable | |
1068 # tag ID number | |
1069 self.tag = tag | |
1070 # field type as index into FIELD_TYPES | |
1071 self.field_type = field_type | |
1072 # offset of start of field in bytes from beginning of IFD | |
1073 self.field_offset = field_offset | |
1074 # length of data field in bytes | |
1075 self.field_length = field_length | |
1076 # either a string or array of data items | |
1077 self.values = values | |
1078 | |
1079 def __str__(self): | |
1080 return self.printable | |
1081 | |
1082 def __repr__(self): | |
1083 return '(0x%04X) %s=%s @ %d' % (self.tag, | |
1084 FIELD_TYPES[self.field_type][2], | |
1085 self.printable, | |
1086 self.field_offset) | |
1087 | |
1088 # class that handles an EXIF header | |
1089 class EXIF_header: | |
1090 def __init__(self, file, endian, offset, fake_exif, debug=0): | |
1091 self.file = file | |
1092 self.endian = endian | |
1093 self.offset = offset | |
1094 self.fake_exif = fake_exif | |
1095 self.debug = debug | |
1096 self.tags = {} | |
1097 | |
1098 # convert slice to integer, based on sign and endian flags | |
1099 # usually this offset is assumed to be relative to the beginning of the | |
1100 # start of the EXIF information. For some cameras that use relative tags, | |
1101 # this offset may be relative to some other starting point. | |
1102 def s2n(self, offset, length, signed=0): | |
1103 self.file.seek(self.offset+offset) | |
1104 slice=self.file.read(length) | |
1105 if self.endian == 'I': | |
1106 val=s2n_intel(slice) | |
1107 else: | |
1108 val=s2n_motorola(slice) | |
1109 # Sign extension ? | |
1110 if signed: | |
1111 msb=1L << (8*length-1) | |
1112 if val & msb: | |
1113 val=val-(msb << 1) | |
1114 return val | |
1115 | |
1116 # convert offset to string | |
1117 def n2s(self, offset, length): | |
1118 s = '' | |
1119 for dummy in range(length): | |
1120 if self.endian == 'I': | |
1121 s = s + chr(offset & 0xFF) | |
1122 else: | |
1123 s = chr(offset & 0xFF) + s | |
1124 offset = offset >> 8 | |
1125 return s | |
1126 | |
1127 # return first IFD | |
1128 def first_IFD(self): | |
1129 return self.s2n(4, 4) | |
1130 | |
1131 # return pointer to next IFD | |
1132 def next_IFD(self, ifd): | |
1133 entries=self.s2n(ifd, 2) | |
1134 return self.s2n(ifd+2+12*entries, 4) | |
1135 | |
1136 # return list of IFDs in header | |
1137 def list_IFDs(self): | |
1138 i=self.first_IFD() | |
1139 a=[] | |
1140 while i: | |
1141 a.append(i) | |
1142 i=self.next_IFD(i) | |
1143 return a | |
1144 | |
1145 # return list of entries in this IFD | |
1146 def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0, stop_tag='UNDEF'): | |
1147 entries=self.s2n(ifd, 2) | |
1148 for i in range(entries): | |
1149 # entry is index of start of this IFD in the file | |
1150 entry = ifd + 2 + 12 * i | |
1151 tag = self.s2n(entry, 2) | |
1152 | |
1153 # get tag name early to avoid errors, help debug | |
1154 tag_entry = dict.get(tag) | |
1155 if tag_entry: | |
1156 tag_name = tag_entry[0] | |
1157 else: | |
1158 tag_name = 'Tag 0x%04X' % tag | |
1159 | |
1160 # ignore certain tags for faster processing | |
1161 if not (not detailed and tag in IGNORE_TAGS): | |
1162 field_type = self.s2n(entry + 2, 2) | |
1163 if not 0 < field_type < len(FIELD_TYPES): | |
1164 # unknown field type | |
1165 raise ValueError('unknown type %d in tag 0x%04X' % (field_type, tag)) | |
1166 typelen = FIELD_TYPES[field_type][0] | |
1167 count = self.s2n(entry + 4, 4) | |
1168 offset = entry + 8 | |
1169 if count * typelen > 4: | |
1170 # offset is not the value; it's a pointer to the value | |
1171 # if relative we set things up so s2n will seek to the right | |
1172 # place when it adds self.offset. Note that this 'relative' | |
1173 # is for the Nikon type 3 makernote. Other cameras may use | |
1174 # other relative offsets, which would have to be computed here | |
1175 # slightly differently. | |
1176 if relative: | |
1177 tmp_offset = self.s2n(offset, 4) | |
1178 offset = tmp_offset + ifd - self.offset + 4 | |
1179 if self.fake_exif: | |
1180 offset = offset + 18 | |
1181 else: | |
1182 offset = self.s2n(offset, 4) | |
1183 field_offset = offset | |
1184 if field_type == 2: | |
1185 # special case: null-terminated ASCII string | |
1186 if count != 0: | |
1187 self.file.seek(self.offset + offset) | |
1188 values = self.file.read(count) | |
1189 values = values.strip().replace('\x00', '') | |
1190 else: | |
1191 values = '' | |
1192 else: | |
1193 values = [] | |
1194 signed = (field_type in [6, 8, 9, 10]) | |
1195 for dummy in range(count): | |
1196 if field_type in (5, 10): | |
1197 # a ratio | |
1198 value = Ratio(self.s2n(offset, 4, signed), | |
1199 self.s2n(offset + 4, 4, signed)) | |
1200 else: | |
1201 value = self.s2n(offset, typelen, signed) | |
1202 values.append(value) | |
1203 offset = offset + typelen | |
1204 # now "values" is either a string or an array | |
1205 if count == 1 and field_type != 2: | |
1206 printable=str(values[0]) | |
1207 else: | |
1208 printable=str(values) | |
1209 # compute printable version of values | |
1210 if tag_entry: | |
1211 if len(tag_entry) != 1: | |
1212 # optional 2nd tag element is present | |
1213 if callable(tag_entry[1]): | |
1214 # call mapping function | |
1215 printable = tag_entry[1](values) | |
1216 else: | |
1217 printable = '' | |
1218 for i in values: | |
1219 # use lookup table for this tag | |
1220 printable += tag_entry[1].get(i, repr(i)) | |
1221 | |
1222 self.tags[ifd_name + ' ' + tag_name] = IFD_Tag(printable, tag, | |
1223 field_type, | |
1224 values, field_offset, | |
1225 count * typelen) | |
1226 if self.debug: | |
1227 print ' debug: %s: %s' % (tag_name, | |
1228 repr(self.tags[ifd_name + ' ' + tag_name])) | |
1229 | |
1230 if tag_name == stop_tag: | |
1231 break | |
1232 | |
1233 # extract uncompressed TIFF thumbnail (like pulling teeth) | |
1234 # we take advantage of the pre-existing layout in the thumbnail IFD as | |
1235 # much as possible | |
1236 def extract_TIFF_thumbnail(self, thumb_ifd): | |
1237 entries = self.s2n(thumb_ifd, 2) | |
1238 # this is header plus offset to IFD ... | |
1239 if self.endian == 'M': | |
1240 tiff = 'MM\x00*\x00\x00\x00\x08' | |
1241 else: | |
1242 tiff = 'II*\x00\x08\x00\x00\x00' | |
1243 # ... plus thumbnail IFD data plus a null "next IFD" pointer | |
1244 self.file.seek(self.offset+thumb_ifd) | |
1245 tiff += self.file.read(entries*12+2)+'\x00\x00\x00\x00' | |
1246 | |
1247 # fix up large value offset pointers into data area | |
1248 for i in range(entries): | |
1249 entry = thumb_ifd + 2 + 12 * i | |
1250 tag = self.s2n(entry, 2) | |
1251 field_type = self.s2n(entry+2, 2) | |
1252 typelen = FIELD_TYPES[field_type][0] | |
1253 count = self.s2n(entry+4, 4) | |
1254 oldoff = self.s2n(entry+8, 4) | |
1255 # start of the 4-byte pointer area in entry | |
1256 ptr = i * 12 + 18 | |
1257 # remember strip offsets location | |
1258 if tag == 0x0111: | |
1259 strip_off = ptr | |
1260 strip_len = count * typelen | |
1261 # is it in the data area? | |
1262 if count * typelen > 4: | |
1263 # update offset pointer (nasty "strings are immutable" crap) | |
1264 # should be able to say "tiff[ptr:ptr+4]=newoff" | |
1265 newoff = len(tiff) | |
1266 tiff = tiff[:ptr] + self.n2s(newoff, 4) + tiff[ptr+4:] | |
1267 # remember strip offsets location | |
1268 if tag == 0x0111: | |
1269 strip_off = newoff | |
1270 strip_len = 4 | |
1271 # get original data and store it | |
1272 self.file.seek(self.offset + oldoff) | |
1273 tiff += self.file.read(count * typelen) | |
1274 | |
1275 # add pixel strips and update strip offset info | |
1276 old_offsets = self.tags['Thumbnail StripOffsets'].values | |
1277 old_counts = self.tags['Thumbnail StripByteCounts'].values | |
1278 for i in range(len(old_offsets)): | |
1279 # update offset pointer (more nasty "strings are immutable" crap) | |
1280 offset = self.n2s(len(tiff), strip_len) | |
1281 tiff = tiff[:strip_off] + offset + tiff[strip_off + strip_len:] | |
1282 strip_off += strip_len | |
1283 # add pixel strip to end | |
1284 self.file.seek(self.offset + old_offsets[i]) | |
1285 tiff += self.file.read(old_counts[i]) | |
1286 | |
1287 self.tags['TIFFThumbnail'] = tiff | |
1288 | |
1289 # decode all the camera-specific MakerNote formats | |
1290 | |
1291 # Note is the data that comprises this MakerNote. The MakerNote will | |
1292 # likely have pointers in it that point to other parts of the file. We'll | |
1293 # use self.offset as the starting point for most of those pointers, since | |
1294 # they are relative to the beginning of the file. | |
1295 # | |
1296 # If the MakerNote is in a newer format, it may use relative addressing | |
1297 # within the MakerNote. In that case we'll use relative addresses for the | |
1298 # pointers. | |
1299 # | |
1300 # As an aside: it's not just to be annoying that the manufacturers use | |
1301 # relative offsets. It's so that if the makernote has to be moved by the | |
1302 # picture software all of the offsets don't have to be adjusted. Overall, | |
1303 # this is probably the right strategy for makernotes, though the spec is | |
1304 # ambiguous. (The spec does not appear to imagine that makernotes would | |
1305 # follow EXIF format internally. Once they did, it's ambiguous whether | |
1306 # the offsets should be from the header at the start of all the EXIF info, | |
1307 # or from the header at the start of the makernote.) | |
1308 def decode_maker_note(self): | |
1309 note = self.tags['EXIF MakerNote'] | |
1310 make = self.tags['Image Make'].printable | |
1311 # model = self.tags['Image Model'].printable # unused | |
1312 | |
1313 # Nikon | |
1314 # The maker note usually starts with the word Nikon, followed by the | |
1315 # type of the makernote (1 or 2, as a short). If the word Nikon is | |
1316 # not at the start of the makernote, it's probably type 2, since some | |
1317 # cameras work that way. | |
1318 if make in ('NIKON', 'NIKON CORPORATION'): | |
1319 if note.values[0:7] == [78, 105, 107, 111, 110, 0, 1]: | |
1320 if self.debug: | |
1321 print "Looks like a type 1 Nikon MakerNote." | |
1322 self.dump_IFD(note.field_offset+8, 'MakerNote', | |
1323 dict=MAKERNOTE_NIKON_OLDER_TAGS) | |
1324 elif note.values[0:7] == [78, 105, 107, 111, 110, 0, 2]: | |
1325 if self.debug: | |
1326 print "Looks like a labeled type 2 Nikon MakerNote" | |
1327 if note.values[12:14] != [0, 42] and note.values[12:14] != [42L, 0L]: | |
1328 raise ValueError("Missing marker tag '42' in MakerNote.") | |
1329 # skip the Makernote label and the TIFF header | |
1330 self.dump_IFD(note.field_offset+10+8, 'MakerNote', | |
1331 dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1) | |
1332 else: | |
1333 # E99x or D1 | |
1334 if self.debug: | |
1335 print "Looks like an unlabeled type 2 Nikon MakerNote" | |
1336 self.dump_IFD(note.field_offset, 'MakerNote', | |
1337 dict=MAKERNOTE_NIKON_NEWER_TAGS) | |
1338 return | |
1339 | |
1340 # Olympus | |
1341 if make.startswith('OLYMPUS'): | |
1342 self.dump_IFD(note.field_offset+8, 'MakerNote', | |
1343 dict=MAKERNOTE_OLYMPUS_TAGS) | |
1344 # TODO | |
1345 #for i in (('MakerNote Tag 0x2020', MAKERNOTE_OLYMPUS_TAG_0x2020),): | |
1346 # self.decode_olympus_tag(self.tags[i[0]].values, i[1]) | |
1347 #return | |
1348 | |
1349 # Casio | |
1350 if make == 'Casio': | |
1351 self.dump_IFD(note.field_offset, 'MakerNote', | |
1352 dict=MAKERNOTE_CASIO_TAGS) | |
1353 return | |
1354 | |
1355 # Fujifilm | |
1356 if make == 'FUJIFILM': | |
1357 # bug: everything else is "Motorola" endian, but the MakerNote | |
1358 # is "Intel" endian | |
1359 endian = self.endian | |
1360 self.endian = 'I' | |
1361 # bug: IFD offsets are from beginning of MakerNote, not | |
1362 # beginning of file header | |
1363 offset = self.offset | |
1364 self.offset += note.field_offset | |
1365 # process note with bogus values (note is actually at offset 12) | |
1366 self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS) | |
1367 # reset to correct values | |
1368 self.endian = endian | |
1369 self.offset = offset | |
1370 return | |
1371 | |
1372 # Canon | |
1373 if make == 'Canon': | |
1374 self.dump_IFD(note.field_offset, 'MakerNote', | |
1375 dict=MAKERNOTE_CANON_TAGS) | |
1376 for i in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001), | |
1377 ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004)): | |
1378 self.canon_decode_tag(self.tags[i[0]].values, i[1]) | |
1379 return | |
1380 | |
1381 # decode Olympus MakerNote tag based on offset within tag | |
1382 def olympus_decode_tag(self, value, dict): | |
1383 pass | |
1384 | |
1385 # decode Canon MakerNote tag based on offset within tag | |
1386 # see http://www.burren.cx/david/canon.html by David Burren | |
1387 def canon_decode_tag(self, value, dict): | |
1388 for i in range(1, len(value)): | |
1389 x=dict.get(i, ('Unknown', )) | |
1390 if self.debug: | |
1391 print i, x | |
1392 name=x[0] | |
1393 if len(x) > 1: | |
1394 val=x[1].get(value[i], 'Unknown') | |
1395 else: | |
1396 val=value[i] | |
1397 # it's not a real IFD Tag but we fake one to make everybody | |
1398 # happy. this will have a "proprietary" type | |
1399 self.tags['MakerNote '+name]=IFD_Tag(str(val), None, 0, None, | |
1400 None, None) | |
1401 | |
1402 # process an image file (expects an open file object) | |
1403 # this is the function that has to deal with all the arbitrary nasty bits | |
1404 # of the EXIF standard | |
1405 def process_file(f, stop_tag='UNDEF', details=True, debug=False): | |
1406 # yah it's cheesy... | |
1407 global detailed | |
1408 detailed = details | |
1409 | |
1410 # by default do not fake an EXIF beginning | |
1411 fake_exif = 0 | |
1412 | |
1413 # determine whether it's a JPEG or TIFF | |
1414 data = f.read(12) | |
1415 if data[0:4] in ['II*\x00', 'MM\x00*']: | |
1416 # it's a TIFF file | |
1417 f.seek(0) | |
1418 endian = f.read(1) | |
1419 f.read(1) | |
1420 offset = 0 | |
1421 elif data[0:2] == '\xFF\xD8': | |
1422 # it's a JPEG file | |
1423 while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'): | |
1424 length = ord(data[4])*256+ord(data[5]) | |
1425 f.read(length-8) | |
1426 # fake an EXIF beginning of file | |
1427 data = '\xFF\x00'+f.read(10) | |
1428 fake_exif = 1 | |
1429 if data[2] == '\xFF' and data[6:10] == 'Exif': | |
1430 # detected EXIF header | |
1431 offset = f.tell() | |
1432 endian = f.read(1) | |
1433 else: | |
1434 # no EXIF information | |
1435 return {} | |
1436 else: | |
1437 # file format not recognized | |
1438 return {} | |
1439 | |
1440 # deal with the EXIF info we found | |
1441 if debug: | |
1442 print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format' | |
1443 hdr = EXIF_header(f, endian, offset, fake_exif, debug) | |
1444 ifd_list = hdr.list_IFDs() | |
1445 ctr = 0 | |
1446 for i in ifd_list: | |
1447 if ctr == 0: | |
1448 IFD_name = 'Image' | |
1449 elif ctr == 1: | |
1450 IFD_name = 'Thumbnail' | |
1451 thumb_ifd = i | |
1452 else: | |
1453 IFD_name = 'IFD %d' % ctr | |
1454 if debug: | |
1455 print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i) | |
1456 hdr.dump_IFD(i, IFD_name, stop_tag=stop_tag) | |
1457 # EXIF IFD | |
1458 exif_off = hdr.tags.get(IFD_name+' ExifOffset') | |
1459 if exif_off: | |
1460 if debug: | |
1461 print ' EXIF SubIFD at offset %d:' % exif_off.values[0] | |
1462 hdr.dump_IFD(exif_off.values[0], 'EXIF', stop_tag=stop_tag) | |
1463 # Interoperability IFD contained in EXIF IFD | |
1464 intr_off = hdr.tags.get('EXIF SubIFD InteroperabilityOffset') | |
1465 if intr_off: | |
1466 if debug: | |
1467 print ' EXIF Interoperability SubSubIFD at offset %d:' \ | |
1468 % intr_off.values[0] | |
1469 hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability', | |
1470 dict=INTR_TAGS, stop_tag=stop_tag) | |
1471 # GPS IFD | |
1472 gps_off = hdr.tags.get(IFD_name+' GPSInfo') | |
1473 if gps_off: | |
1474 if debug: | |
1475 print ' GPS SubIFD at offset %d:' % gps_off.values[0] | |
1476 hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS, stop_tag=stop_tag) | |
1477 ctr += 1 | |
1478 | |
1479 # extract uncompressed TIFF thumbnail | |
1480 thumb = hdr.tags.get('Thumbnail Compression') | |
1481 if thumb and thumb.printable == 'Uncompressed TIFF': | |
1482 hdr.extract_TIFF_thumbnail(thumb_ifd) | |
1483 | |
1484 # JPEG thumbnail (thankfully the JPEG data is stored as a unit) | |
1485 thumb_off = hdr.tags.get('Thumbnail JPEGInterchangeFormat') | |
1486 if thumb_off: | |
1487 f.seek(offset+thumb_off.values[0]) | |
1488 size = hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0] | |
1489 hdr.tags['JPEGThumbnail'] = f.read(size) | |
1490 | |
1491 # deal with MakerNote contained in EXIF IFD | |
1492 if 'EXIF MakerNote' in hdr.tags and detailed: | |
1493 hdr.decode_maker_note() | |
1494 | |
1495 # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote | |
1496 # since it's not allowed in a uncompressed TIFF IFD | |
1497 if 'JPEGThumbnail' not in hdr.tags: | |
1498 thumb_off=hdr.tags.get('MakerNote JPEGThumbnail') | |
1499 if thumb_off: | |
1500 f.seek(offset+thumb_off.values[0]) | |
1501 hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length) | |
1502 | |
1503 return hdr.tags | |
1504 | |
1505 | |
1506 # show command line usage | |
1507 def usage(exit_status): | |
1508 msg = 'Usage: EXIF.py [OPTIONS] file1 [file2 ...]\n' | |
1509 msg += 'Extract EXIF information from digital camera image files.\n\nOptions:\n' | |
1510 msg += '-q --quick Do not process MakerNotes.\n' | |
1511 msg += '-t TAG --stop-tag TAG Stop processing when this tag is retrieved.\n' | |
1512 msg += '-d --debug Run in debug mode.\n' | |
1513 print msg | |
1514 sys.exit(exit_status) | |
1515 | |
1516 # library test/debug function (dump given files) | |
1517 if __name__ == '__main__': | |
1518 import sys | |
1519 import getopt | |
1520 | |
1521 # parse command line options/arguments | |
1522 try: | |
1523 opts, args = getopt.getopt(sys.argv[1:], "hqdt:v", ["help", "quick", "debug", "stop-tag="]) | |
1524 except getopt.GetoptError: | |
1525 usage(2) | |
1526 if args == []: | |
1527 usage(2) | |
1528 detailed = True | |
1529 stop_tag = 'UNDEF' | |
1530 debug = False | |
1531 for o, a in opts: | |
1532 if o in ("-h", "--help"): | |
1533 usage(0) | |
1534 if o in ("-q", "--quick"): | |
1535 detailed = False | |
1536 if o in ("-t", "--stop-tag"): | |
1537 stop_tag = a | |
1538 if o in ("-d", "--debug"): | |
1539 debug = True | |
1540 | |
1541 # output info for each file | |
1542 for filename in args: | |
1543 try: | |
1544 file=open(filename, 'rb') | |
1545 except: | |
1546 print "'%s' is unreadable\n"%filename | |
1547 continue | |
1548 print filename + ':' | |
1549 # get the tags | |
1550 data = process_file(file, stop_tag=stop_tag, details=detailed, debug=debug) | |
1551 if not data: | |
1552 print 'No EXIF information found' | |
1553 continue | |
1554 | |
1555 x=data.keys() | |
1556 x.sort() | |
1557 for i in x: | |
1558 if i in ('JPEGThumbnail', 'TIFFThumbnail'): | |
1559 continue | |
1560 try: | |
1561 print ' %s (%s): %s' % \ | |
1562 (i, FIELD_TYPES[data[i].field_type][2], data[i].printable) | |
1563 except: | |
1564 print 'error', i, '"', data[i], '"' | |
1565 if 'JPEGThumbnail' in data: | |
1566 print 'File has JPEG thumbnail' | |
1567 print | |
1568 |