1 | # Library to extract EXIF information in digital camera image files |
---|
2 | # |
---|
3 | # To use this library call with: |
---|
4 | # f=open(path_name, 'rb') |
---|
5 | # tags=EXIF.process_file(f) |
---|
6 | # tags will now be a dictionary mapping names of EXIF tags to their |
---|
7 | # values in the file named by path_name. You can process the tags |
---|
8 | # as you wish. In particular, you can iterate through all the tags with: |
---|
9 | # for tag in tags.keys(): |
---|
10 | # if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename', |
---|
11 | # 'EXIF MakerNote'): |
---|
12 | # print "Key: %s, value %s" % (tag, tags[tag]) |
---|
13 | # (This code uses the if statement to avoid printing out a few of the |
---|
14 | # tags that tend to be long or boring.) |
---|
15 | # |
---|
16 | # The tags dictionary will include keys for all of the usual EXIF |
---|
17 | # tags, and will also include keys for Makernotes used by some |
---|
18 | # cameras, for which we have a good specification. |
---|
19 | # |
---|
20 | # Contains code from "exifdump.py" originally written by Thierry Bousch |
---|
21 | # <bousch@topo.math.u-psud.fr> and released into the public domain. |
---|
22 | # |
---|
23 | # Updated and turned into general-purpose library by Gene Cash |
---|
24 | # <email gcash at cfl.rr.com> |
---|
25 | # |
---|
26 | # This copyright license is intended to be similar to the FreeBSD license. |
---|
27 | # |
---|
28 | # Copyright 2002 Gene Cash All rights reserved. |
---|
29 | # |
---|
30 | # Redistribution and use in source and binary forms, with or without |
---|
31 | # modification, are permitted provided that the following conditions are |
---|
32 | # met: |
---|
33 | # |
---|
34 | # 1. Redistributions of source code must retain the above copyright |
---|
35 | # notice, this list of conditions and the following disclaimer. |
---|
36 | # 2. Redistributions in binary form must reproduce the above copyright |
---|
37 | # notice, this list of conditions and the following disclaimer in the |
---|
38 | # documentation and/or other materials provided with the |
---|
39 | # distribution. |
---|
40 | # |
---|
41 | # THIS SOFTWARE IS PROVIDED BY GENE CASH ``AS IS'' AND ANY EXPRESS OR |
---|
42 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
---|
43 | # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
---|
44 | # DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR |
---|
45 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
---|
46 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
---|
47 | # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
---|
48 | # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
---|
49 | # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
---|
50 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
---|
51 | # POSSIBILITY OF SUCH DAMAGE. |
---|
52 | # |
---|
53 | # This means you may do anything you want with this code, except claim you |
---|
54 | # wrote it. Also, if it breaks you get to keep both pieces. |
---|
55 | # |
---|
56 | # Patch Contributors: |
---|
57 | # * Simon J. Gerraty <sjg@crufty.net> |
---|
58 | # s2n fix & orientation decode |
---|
59 | # * John T. Riedl <riedl@cs.umn.edu> |
---|
60 | # Added support for newer Nikon type 3 Makernote format for D70 and some |
---|
61 | # other Nikon cameras. |
---|
62 | # * Joerg Schaefer <schaeferj@gmx.net> |
---|
63 | # Fixed subtle bug when faking an EXIF header, which affected maker notes |
---|
64 | # using relative offsets, and a fix for Nikon D100. |
---|
65 | # |
---|
66 | # 21-AUG-99 TB Last update by Thierry Bousch to his code. |
---|
67 | # 17-JAN-02 CEC Discovered code on web. |
---|
68 | # Commented everything. |
---|
69 | # Made small code improvements. |
---|
70 | # Reformatted for readability. |
---|
71 | # 19-JAN-02 CEC Added ability to read TIFFs and JFIF-format JPEGs. |
---|
72 | # Added ability to extract JPEG formatted thumbnail. |
---|
73 | # Added ability to read GPS IFD (not tested). |
---|
74 | # Converted IFD data structure to dictionaries indexed by |
---|
75 | # tag name. |
---|
76 | # Factored into library returning dictionary of IFDs plus |
---|
77 | # thumbnail, if any. |
---|
78 | # 20-JAN-02 CEC Added MakerNote processing logic. |
---|
79 | # Added Olympus MakerNote. |
---|
80 | # Converted data structure to single-level dictionary, avoiding |
---|
81 | # tag name collisions by prefixing with IFD name. This makes |
---|
82 | # it much easier to use. |
---|
83 | # 23-JAN-02 CEC Trimmed nulls from end of string values. |
---|
84 | # 25-JAN-02 CEC Discovered JPEG thumbnail in Olympus TIFF MakerNote. |
---|
85 | # 26-JAN-02 CEC Added ability to extract TIFF thumbnails. |
---|
86 | # Added Nikon, Fujifilm, Casio MakerNotes. |
---|
87 | # 30-NOV-03 CEC Fixed problem with canon_decode_tag() not creating an |
---|
88 | # IFD_Tag() object. |
---|
89 | # 15-FEB-04 CEC Finally fixed bit shift warning by converting Y to 0L. |
---|
90 | # |
---|
91 | |
---|
92 | # field type descriptions as (length, abbreviation, full name) tuples |
---|
93 | FIELD_TYPES=( |
---|
94 | (0, 'X', 'Proprietary'), # no such type |
---|
95 | (1, 'B', 'Byte'), |
---|
96 | (1, 'A', 'ASCII'), |
---|
97 | (2, 'S', 'Short'), |
---|
98 | (4, 'L', 'Long'), |
---|
99 | (8, 'R', 'Ratio'), |
---|
100 | (1, 'SB', 'Signed Byte'), |
---|
101 | (1, 'U', 'Undefined'), |
---|
102 | (2, 'SS', 'Signed Short'), |
---|
103 | (4, 'SL', 'Signed Long'), |
---|
104 | (8, 'SR', 'Signed Ratio') |
---|
105 | ) |
---|
106 | |
---|
107 | # dictionary of main EXIF tag names |
---|
108 | # first element of tuple is tag name, optional second element is |
---|
109 | # another dictionary giving names to values |
---|
110 | EXIF_TAGS={ |
---|
111 | 0x0100: ('ImageWidth', ), |
---|
112 | 0x0101: ('ImageLength', ), |
---|
113 | 0x0102: ('BitsPerSample', ), |
---|
114 | 0x0103: ('Compression', |
---|
115 | {1: 'Uncompressed TIFF', |
---|
116 | 6: 'JPEG Compressed'}), |
---|
117 | 0x0106: ('PhotometricInterpretation', ), |
---|
118 | 0x010A: ('FillOrder', ), |
---|
119 | 0x010D: ('DocumentName', ), |
---|
120 | 0x010E: ('ImageDescription', ), |
---|
121 | 0x010F: ('Make', ), |
---|
122 | 0x0110: ('Model', ), |
---|
123 | 0x0111: ('StripOffsets', ), |
---|
124 | 0x0112: ('Orientation', |
---|
125 | {1: 'Horizontal (normal)', |
---|
126 | 2: 'Mirrored horizontal', |
---|
127 | 3: 'Rotated 180', |
---|
128 | 4: 'Mirrored vertical', |
---|
129 | 5: 'Mirrored horizontal then rotated 90 CCW', |
---|
130 | 6: 'Rotated 90 CW', |
---|
131 | 7: 'Mirrored horizontal then rotated 90 CW', |
---|
132 | 8: 'Rotated 90 CCW'}), |
---|
133 | 0x0115: ('SamplesPerPixel', ), |
---|
134 | 0x0116: ('RowsPerStrip', ), |
---|
135 | 0x0117: ('StripByteCounts', ), |
---|
136 | 0x011A: ('XResolution', ), |
---|
137 | 0x011B: ('YResolution', ), |
---|
138 | 0x011C: ('PlanarConfiguration', ), |
---|
139 | 0x0128: ('ResolutionUnit', |
---|
140 | {1: 'Not Absolute', |
---|
141 | 2: 'Pixels/Inch', |
---|
142 | 3: 'Pixels/Centimeter'}), |
---|
143 | 0x012D: ('TransferFunction', ), |
---|
144 | 0x0131: ('Software', ), |
---|
145 | 0x0132: ('DateTime', ), |
---|
146 | 0x013B: ('Artist', ), |
---|
147 | 0x013E: ('WhitePoint', ), |
---|
148 | 0x013F: ('PrimaryChromaticities', ), |
---|
149 | 0x0156: ('TransferRange', ), |
---|
150 | 0x0200: ('JPEGProc', ), |
---|
151 | 0x0201: ('JPEGInterchangeFormat', ), |
---|
152 | 0x0202: ('JPEGInterchangeFormatLength', ), |
---|
153 | 0x0211: ('YCbCrCoefficients', ), |
---|
154 | 0x0212: ('YCbCrSubSampling', ), |
---|
155 | 0x0213: ('YCbCrPositioning', ), |
---|
156 | 0x0214: ('ReferenceBlackWhite', ), |
---|
157 | 0x828D: ('CFARepeatPatternDim', ), |
---|
158 | 0x828E: ('CFAPattern', ), |
---|
159 | 0x828F: ('BatteryLevel', ), |
---|
160 | 0x8298: ('Copyright', ), |
---|
161 | 0x829A: ('ExposureTime', ), |
---|
162 | 0x829D: ('FNumber', ), |
---|
163 | 0x83BB: ('IPTC/NAA', ), |
---|
164 | 0x8769: ('ExifOffset', ), |
---|
165 | 0x8773: ('InterColorProfile', ), |
---|
166 | 0x8822: ('ExposureProgram', |
---|
167 | {0: 'Unidentified', |
---|
168 | 1: 'Manual', |
---|
169 | 2: 'Program Normal', |
---|
170 | 3: 'Aperture Priority', |
---|
171 | 4: 'Shutter Priority', |
---|
172 | 5: 'Program Creative', |
---|
173 | 6: 'Program Action', |
---|
174 | 7: 'Portrait Mode', |
---|
175 | 8: 'Landscape Mode'}), |
---|
176 | 0x8824: ('SpectralSensitivity', ), |
---|
177 | 0x8825: ('GPSInfo', ), |
---|
178 | 0x8827: ('ISOSpeedRatings', ), |
---|
179 | 0x8828: ('OECF', ), |
---|
180 | # print as string |
---|
181 | 0x9000: ('ExifVersion', lambda x: ''.join(map(chr, x))), |
---|
182 | 0x9003: ('DateTimeOriginal', ), |
---|
183 | 0x9004: ('DateTimeDigitized', ), |
---|
184 | 0x9101: ('ComponentsConfiguration', |
---|
185 | {0: '', |
---|
186 | 1: 'Y', |
---|
187 | 2: 'Cb', |
---|
188 | 3: 'Cr', |
---|
189 | 4: 'Red', |
---|
190 | 5: 'Green', |
---|
191 | 6: 'Blue'}), |
---|
192 | 0x9102: ('CompressedBitsPerPixel', ), |
---|
193 | 0x9201: ('ShutterSpeedValue', ), |
---|
194 | 0x9202: ('ApertureValue', ), |
---|
195 | 0x9203: ('BrightnessValue', ), |
---|
196 | 0x9204: ('ExposureBiasValue', ), |
---|
197 | 0x9205: ('MaxApertureValue', ), |
---|
198 | 0x9206: ('SubjectDistance', ), |
---|
199 | 0x9207: ('MeteringMode', |
---|
200 | {0: 'Unidentified', |
---|
201 | 1: 'Average', |
---|
202 | 2: 'CenterWeightedAverage', |
---|
203 | 3: 'Spot', |
---|
204 | 4: 'MultiSpot'}), |
---|
205 | 0x9208: ('LightSource', |
---|
206 | {0: 'Unknown', |
---|
207 | 1: 'Daylight', |
---|
208 | 2: 'Fluorescent', |
---|
209 | 3: 'Tungsten', |
---|
210 | 10: 'Flash', |
---|
211 | 17: 'Standard Light A', |
---|
212 | 18: 'Standard Light B', |
---|
213 | 19: 'Standard Light C', |
---|
214 | 20: 'D55', |
---|
215 | 21: 'D65', |
---|
216 | 22: 'D75', |
---|
217 | 255: 'Other'}), |
---|
218 | 0x9209: ('Flash', {0: 'No', |
---|
219 | 1: 'Fired', |
---|
220 | 5: 'Fired (?)', # no return sensed |
---|
221 | 7: 'Fired (!)', # return sensed |
---|
222 | 9: 'Fill Fired', |
---|
223 | 13: 'Fill Fired (?)', |
---|
224 | 15: 'Fill Fired (!)', |
---|
225 | 16: 'Off', |
---|
226 | 24: 'Auto Off', |
---|
227 | 25: 'Auto Fired', |
---|
228 | 29: 'Auto Fired (?)', |
---|
229 | 31: 'Auto Fired (!)', |
---|
230 | 32: 'Not Available'}), |
---|
231 | 0x920A: ('FocalLength', ), |
---|
232 | 0x927C: ('MakerNote', ), |
---|
233 | # print as string |
---|
234 | 0x9286: ('UserComment', lambda x: ''.join(map(chr, x))), |
---|
235 | 0x9290: ('SubSecTime', ), |
---|
236 | 0x9291: ('SubSecTimeOriginal', ), |
---|
237 | 0x9292: ('SubSecTimeDigitized', ), |
---|
238 | # print as string |
---|
239 | 0xA000: ('FlashPixVersion', lambda x: ''.join(map(chr, x))), |
---|
240 | 0xA001: ('ColorSpace', ), |
---|
241 | 0xA002: ('ExifImageWidth', ), |
---|
242 | 0xA003: ('ExifImageLength', ), |
---|
243 | 0xA005: ('InteroperabilityOffset', ), |
---|
244 | 0xA20B: ('FlashEnergy', ), # 0x920B in TIFF/EP |
---|
245 | 0xA20C: ('SpatialFrequencyResponse', ), # 0x920C - - |
---|
246 | 0xA20E: ('FocalPlaneXResolution', ), # 0x920E - - |
---|
247 | 0xA20F: ('FocalPlaneYResolution', ), # 0x920F - - |
---|
248 | 0xA210: ('FocalPlaneResolutionUnit', ), # 0x9210 - - |
---|
249 | 0xA214: ('SubjectLocation', ), # 0x9214 - - |
---|
250 | 0xA215: ('ExposureIndex', ), # 0x9215 - - |
---|
251 | 0xA217: ('SensingMethod', ), # 0x9217 - - |
---|
252 | 0xA300: ('FileSource', |
---|
253 | {3: 'Digital Camera'}), |
---|
254 | 0xA301: ('SceneType', |
---|
255 | {1: 'Directly Photographed'}), |
---|
256 | 0xA302: ('CVAPattern',), |
---|
257 | } |
---|
258 | |
---|
259 | # interoperability tags |
---|
260 | INTR_TAGS={ |
---|
261 | 0x0001: ('InteroperabilityIndex', ), |
---|
262 | 0x0002: ('InteroperabilityVersion', ), |
---|
263 | 0x1000: ('RelatedImageFileFormat', ), |
---|
264 | 0x1001: ('RelatedImageWidth', ), |
---|
265 | 0x1002: ('RelatedImageLength', ), |
---|
266 | } |
---|
267 | |
---|
268 | # GPS tags (not used yet, haven't seen camera with GPS) |
---|
269 | GPS_TAGS={ |
---|
270 | 0x0000: ('GPSVersionID', ), |
---|
271 | 0x0001: ('GPSLatitudeRef', ), |
---|
272 | 0x0002: ('GPSLatitude', ), |
---|
273 | 0x0003: ('GPSLongitudeRef', ), |
---|
274 | 0x0004: ('GPSLongitude', ), |
---|
275 | 0x0005: ('GPSAltitudeRef', ), |
---|
276 | 0x0006: ('GPSAltitude', ), |
---|
277 | 0x0007: ('GPSTimeStamp', ), |
---|
278 | 0x0008: ('GPSSatellites', ), |
---|
279 | 0x0009: ('GPSStatus', ), |
---|
280 | 0x000A: ('GPSMeasureMode', ), |
---|
281 | 0x000B: ('GPSDOP', ), |
---|
282 | 0x000C: ('GPSSpeedRef', ), |
---|
283 | 0x000D: ('GPSSpeed', ), |
---|
284 | 0x000E: ('GPSTrackRef', ), |
---|
285 | 0x000F: ('GPSTrack', ), |
---|
286 | 0x0010: ('GPSImgDirectionRef', ), |
---|
287 | 0x0011: ('GPSImgDirection', ), |
---|
288 | 0x0012: ('GPSMapDatum', ), |
---|
289 | 0x0013: ('GPSDestLatitudeRef', ), |
---|
290 | 0x0014: ('GPSDestLatitude', ), |
---|
291 | 0x0015: ('GPSDestLongitudeRef', ), |
---|
292 | 0x0016: ('GPSDestLongitude', ), |
---|
293 | 0x0017: ('GPSDestBearingRef', ), |
---|
294 | 0x0018: ('GPSDestBearing', ), |
---|
295 | 0x0019: ('GPSDestDistanceRef', ), |
---|
296 | 0x001A: ('GPSDestDistance', ) |
---|
297 | } |
---|
298 | |
---|
299 | # Nikon E99x MakerNote Tags |
---|
300 | # http://members.tripod.com/~tawba/990exif.htm |
---|
301 | MAKERNOTE_NIKON_NEWER_TAGS={ |
---|
302 | 0x0002: ('ISOSetting', ), |
---|
303 | 0x0003: ('ColorMode', ), |
---|
304 | 0x0004: ('Quality', ), |
---|
305 | 0x0005: ('Whitebalance', ), |
---|
306 | 0x0006: ('ImageSharpening', ), |
---|
307 | 0x0007: ('FocusMode', ), |
---|
308 | 0x0008: ('FlashSetting', ), |
---|
309 | 0x0009: ('AutoFlashMode', ), |
---|
310 | 0x000B: ('WhiteBalanceBias', ), |
---|
311 | 0x000C: ('WhiteBalanceRBCoeff', ), |
---|
312 | 0x000F: ('ISOSelection', ), |
---|
313 | 0x0012: ('FlashCompensation', ), |
---|
314 | 0x0013: ('ISOSpeedRequested', ), |
---|
315 | 0x0016: ('PhotoCornerCoordinates', ), |
---|
316 | 0x0018: ('FlashBracketCompensationApplied', ), |
---|
317 | 0x0019: ('AEBracketCompensationApplied', ), |
---|
318 | 0x0080: ('ImageAdjustment', ), |
---|
319 | 0x0081: ('ToneCompensation', ), |
---|
320 | 0x0082: ('AuxiliaryLens', ), |
---|
321 | 0x0083: ('LensType', ), |
---|
322 | 0x0084: ('LensMinMaxFocalMaxAperture', ), |
---|
323 | 0x0085: ('ManualFocusDistance', ), |
---|
324 | 0x0086: ('DigitalZoomFactor', ), |
---|
325 | 0x0088: ('AFFocusPosition', |
---|
326 | {0x0000: 'Center', |
---|
327 | 0x0100: 'Top', |
---|
328 | 0x0200: 'Bottom', |
---|
329 | 0x0300: 'Left', |
---|
330 | 0x0400: 'Right'}), |
---|
331 | 0x0089: ('BracketingMode', |
---|
332 | {0x00: 'Single frame, no bracketing', |
---|
333 | 0x01: 'Continuous, no bracketing', |
---|
334 | 0x02: 'Timer, no bracketing', |
---|
335 | 0x10: 'Single frame, exposure bracketing', |
---|
336 | 0x11: 'Continuous, exposure bracketing', |
---|
337 | 0x12: 'Timer, exposure bracketing', |
---|
338 | 0x40: 'Single frame, white balance bracketing', |
---|
339 | 0x41: 'Continuous, white balance bracketing', |
---|
340 | 0x42: 'Timer, white balance bracketing'}), |
---|
341 | 0x008D: ('ColorMode', ), |
---|
342 | 0x008F: ('SceneMode?', ), |
---|
343 | 0x0090: ('LightingType', ), |
---|
344 | 0x0092: ('HueAdjustment', ), |
---|
345 | 0x0094: ('Saturation', |
---|
346 | {-3: 'B&W', |
---|
347 | -2: '-2', |
---|
348 | -1: '-1', |
---|
349 | 0: '0', |
---|
350 | 1: '1', |
---|
351 | 2: '2'}), |
---|
352 | 0x0095: ('NoiseReduction', ), |
---|
353 | 0x00A7: ('TotalShutterReleases', ), |
---|
354 | 0x00A9: ('ImageOptimization', ), |
---|
355 | 0x00AA: ('Saturation', ), |
---|
356 | 0x00AB: ('DigitalVariProgram', ), |
---|
357 | 0x0010: ('DataDump', ) |
---|
358 | } |
---|
359 | |
---|
360 | MAKERNOTE_NIKON_OLDER_TAGS={ |
---|
361 | 0x0003: ('Quality', |
---|
362 | {1: 'VGA Basic', |
---|
363 | 2: 'VGA Normal', |
---|
364 | 3: 'VGA Fine', |
---|
365 | 4: 'SXGA Basic', |
---|
366 | 5: 'SXGA Normal', |
---|
367 | 6: 'SXGA Fine'}), |
---|
368 | 0x0004: ('ColorMode', |
---|
369 | {1: 'Color', |
---|
370 | 2: 'Monochrome'}), |
---|
371 | 0x0005: ('ImageAdjustment', |
---|
372 | {0: 'Normal', |
---|
373 | 1: 'Bright+', |
---|
374 | 2: 'Bright-', |
---|
375 | 3: 'Contrast+', |
---|
376 | 4: 'Contrast-'}), |
---|
377 | 0x0006: ('CCDSpeed', |
---|
378 | {0: 'ISO 80', |
---|
379 | 2: 'ISO 160', |
---|
380 | 4: 'ISO 320', |
---|
381 | 5: 'ISO 100'}), |
---|
382 | 0x0007: ('WhiteBalance', |
---|
383 | {0: 'Auto', |
---|
384 | 1: 'Preset', |
---|
385 | 2: 'Daylight', |
---|
386 | 3: 'Incandescent', |
---|
387 | 4: 'Fluorescent', |
---|
388 | 5: 'Cloudy', |
---|
389 | 6: 'Speed Light'}) |
---|
390 | } |
---|
391 | |
---|
392 | # decode Olympus SpecialMode tag in MakerNote |
---|
393 | def olympus_special_mode(v): |
---|
394 | a={ |
---|
395 | 0: 'Normal', |
---|
396 | 1: 'Unknown', |
---|
397 | 2: 'Fast', |
---|
398 | 3: 'Panorama'} |
---|
399 | b={ |
---|
400 | 0: 'Non-panoramic', |
---|
401 | 1: 'Left to right', |
---|
402 | 2: 'Right to left', |
---|
403 | 3: 'Bottom to top', |
---|
404 | 4: 'Top to bottom'} |
---|
405 | return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]]) |
---|
406 | |
---|
407 | MAKERNOTE_OLYMPUS_TAGS={ |
---|
408 | # ah HAH! those sneeeeeaky bastids! this is how they get past the fact |
---|
409 | # that a JPEG thumbnail is not allowed in an uncompressed TIFF file |
---|
410 | 0x0100: ('JPEGThumbnail', ), |
---|
411 | 0x0200: ('SpecialMode', olympus_special_mode), |
---|
412 | 0x0201: ('JPEGQual', |
---|
413 | {1: 'SQ', |
---|
414 | 2: 'HQ', |
---|
415 | 3: 'SHQ'}), |
---|
416 | 0x0202: ('Macro', |
---|
417 | {0: 'Normal', |
---|
418 | 1: 'Macro'}), |
---|
419 | 0x0204: ('DigitalZoom', ), |
---|
420 | 0x0207: ('SoftwareRelease', ), |
---|
421 | 0x0208: ('PictureInfo', ), |
---|
422 | # print as string |
---|
423 | 0x0209: ('CameraID', lambda x: ''.join(map(chr, x))), |
---|
424 | 0x0F00: ('DataDump', ) |
---|
425 | } |
---|
426 | |
---|
427 | MAKERNOTE_CASIO_TAGS={ |
---|
428 | 0x0001: ('RecordingMode', |
---|
429 | {1: 'Single Shutter', |
---|
430 | 2: 'Panorama', |
---|
431 | 3: 'Night Scene', |
---|
432 | 4: 'Portrait', |
---|
433 | 5: 'Landscape'}), |
---|
434 | 0x0002: ('Quality', |
---|
435 | {1: 'Economy', |
---|
436 | 2: 'Normal', |
---|
437 | 3: 'Fine'}), |
---|
438 | 0x0003: ('FocusingMode', |
---|
439 | {2: 'Macro', |
---|
440 | 3: 'Auto Focus', |
---|
441 | 4: 'Manual Focus', |
---|
442 | 5: 'Infinity'}), |
---|
443 | 0x0004: ('FlashMode', |
---|
444 | {1: 'Auto', |
---|
445 | 2: 'On', |
---|
446 | 3: 'Off', |
---|
447 | 4: 'Red Eye Reduction'}), |
---|
448 | 0x0005: ('FlashIntensity', |
---|
449 | {11: 'Weak', |
---|
450 | 13: 'Normal', |
---|
451 | 15: 'Strong'}), |
---|
452 | 0x0006: ('Object Distance', ), |
---|
453 | 0x0007: ('WhiteBalance', |
---|
454 | {1: 'Auto', |
---|
455 | 2: 'Tungsten', |
---|
456 | 3: 'Daylight', |
---|
457 | 4: 'Fluorescent', |
---|
458 | 5: 'Shade', |
---|
459 | 129: 'Manual'}), |
---|
460 | 0x000B: ('Sharpness', |
---|
461 | {0: 'Normal', |
---|
462 | 1: 'Soft', |
---|
463 | 2: 'Hard'}), |
---|
464 | 0x000C: ('Contrast', |
---|
465 | {0: 'Normal', |
---|
466 | 1: 'Low', |
---|
467 | 2: 'High'}), |
---|
468 | 0x000D: ('Saturation', |
---|
469 | {0: 'Normal', |
---|
470 | 1: 'Low', |
---|
471 | 2: 'High'}), |
---|
472 | 0x0014: ('CCDSpeed', |
---|
473 | {64: 'Normal', |
---|
474 | 80: 'Normal', |
---|
475 | 100: 'High', |
---|
476 | 125: '+1.0', |
---|
477 | 244: '+3.0', |
---|
478 | 250: '+2.0',}) |
---|
479 | } |
---|
480 | |
---|
481 | MAKERNOTE_FUJIFILM_TAGS={ |
---|
482 | 0x0000: ('NoteVersion', lambda x: ''.join(map(chr, x))), |
---|
483 | 0x1000: ('Quality', ), |
---|
484 | 0x1001: ('Sharpness', |
---|
485 | {1: 'Soft', |
---|
486 | 2: 'Soft', |
---|
487 | 3: 'Normal', |
---|
488 | 4: 'Hard', |
---|
489 | 5: 'Hard'}), |
---|
490 | 0x1002: ('WhiteBalance', |
---|
491 | {0: 'Auto', |
---|
492 | 256: 'Daylight', |
---|
493 | 512: 'Cloudy', |
---|
494 | 768: 'DaylightColor-Fluorescent', |
---|
495 | 769: 'DaywhiteColor-Fluorescent', |
---|
496 | 770: 'White-Fluorescent', |
---|
497 | 1024: 'Incandescent', |
---|
498 | 3840: 'Custom'}), |
---|
499 | 0x1003: ('Color', |
---|
500 | {0: 'Normal', |
---|
501 | 256: 'High', |
---|
502 | 512: 'Low'}), |
---|
503 | 0x1004: ('Tone', |
---|
504 | {0: 'Normal', |
---|
505 | 256: 'High', |
---|
506 | 512: 'Low'}), |
---|
507 | 0x1010: ('FlashMode', |
---|
508 | {0: 'Auto', |
---|
509 | 1: 'On', |
---|
510 | 2: 'Off', |
---|
511 | 3: 'Red Eye Reduction'}), |
---|
512 | 0x1011: ('FlashStrength', ), |
---|
513 | 0x1020: ('Macro', |
---|
514 | {0: 'Off', |
---|
515 | 1: 'On'}), |
---|
516 | 0x1021: ('FocusMode', |
---|
517 | {0: 'Auto', |
---|
518 | 1: 'Manual'}), |
---|
519 | 0x1030: ('SlowSync', |
---|
520 | {0: 'Off', |
---|
521 | 1: 'On'}), |
---|
522 | 0x1031: ('PictureMode', |
---|
523 | {0: 'Auto', |
---|
524 | 1: 'Portrait', |
---|
525 | 2: 'Landscape', |
---|
526 | 4: 'Sports', |
---|
527 | 5: 'Night', |
---|
528 | 6: 'Program AE', |
---|
529 | 256: 'Aperture Priority AE', |
---|
530 | 512: 'Shutter Priority AE', |
---|
531 | 768: 'Manual Exposure'}), |
---|
532 | 0x1100: ('MotorOrBracket', |
---|
533 | {0: 'Off', |
---|
534 | 1: 'On'}), |
---|
535 | 0x1300: ('BlurWarning', |
---|
536 | {0: 'Off', |
---|
537 | 1: 'On'}), |
---|
538 | 0x1301: ('FocusWarning', |
---|
539 | {0: 'Off', |
---|
540 | 1: 'On'}), |
---|
541 | 0x1302: ('AEWarning', |
---|
542 | {0: 'Off', |
---|
543 | 1: 'On'}) |
---|
544 | } |
---|
545 | |
---|
546 | MAKERNOTE_CANON_TAGS={ |
---|
547 | 0x0006: ('ImageType', ), |
---|
548 | 0x0007: ('FirmwareVersion', ), |
---|
549 | 0x0008: ('ImageNumber', ), |
---|
550 | 0x0009: ('OwnerName', ) |
---|
551 | } |
---|
552 | |
---|
553 | # see http://www.burren.cx/david/canon.html by David Burren |
---|
554 | # this is in element offset, name, optional value dictionary format |
---|
555 | MAKERNOTE_CANON_TAG_0x001={ |
---|
556 | 1: ('Macromode', |
---|
557 | {1: 'Macro', |
---|
558 | 2: 'Normal'}), |
---|
559 | 2: ('SelfTimer', ), |
---|
560 | 3: ('Quality', |
---|
561 | {2: 'Normal', |
---|
562 | 3: 'Fine', |
---|
563 | 5: 'Superfine'}), |
---|
564 | 4: ('FlashMode', |
---|
565 | {0: 'Flash Not Fired', |
---|
566 | 1: 'Auto', |
---|
567 | 2: 'On', |
---|
568 | 3: 'Red-Eye Reduction', |
---|
569 | 4: 'Slow Synchro', |
---|
570 | 5: 'Auto + Red-Eye Reduction', |
---|
571 | 6: 'On + Red-Eye Reduction', |
---|
572 | 16: 'external flash'}), |
---|
573 | 5: ('ContinuousDriveMode', |
---|
574 | {0: 'Single Or Timer', |
---|
575 | 1: 'Continuous'}), |
---|
576 | 7: ('FocusMode', |
---|
577 | {0: 'One-Shot', |
---|
578 | 1: 'AI Servo', |
---|
579 | 2: 'AI Focus', |
---|
580 | 3: 'MF', |
---|
581 | 4: 'Single', |
---|
582 | 5: 'Continuous', |
---|
583 | 6: 'MF'}), |
---|
584 | 10: ('ImageSize', |
---|
585 | {0: 'Large', |
---|
586 | 1: 'Medium', |
---|
587 | 2: 'Small'}), |
---|
588 | 11: ('EasyShootingMode', |
---|
589 | {0: 'Full Auto', |
---|
590 | 1: 'Manual', |
---|
591 | 2: 'Landscape', |
---|
592 | 3: 'Fast Shutter', |
---|
593 | 4: 'Slow Shutter', |
---|
594 | 5: 'Night', |
---|
595 | 6: 'B&W', |
---|
596 | 7: 'Sepia', |
---|
597 | 8: 'Portrait', |
---|
598 | 9: 'Sports', |
---|
599 | 10: 'Macro/Close-Up', |
---|
600 | 11: 'Pan Focus'}), |
---|
601 | 12: ('DigitalZoom', |
---|
602 | {0: 'None', |
---|
603 | 1: '2x', |
---|
604 | 2: '4x'}), |
---|
605 | 13: ('Contrast', |
---|
606 | {0xFFFF: 'Low', |
---|
607 | 0: 'Normal', |
---|
608 | 1: 'High'}), |
---|
609 | 14: ('Saturation', |
---|
610 | {0xFFFF: 'Low', |
---|
611 | 0: 'Normal', |
---|
612 | 1: 'High'}), |
---|
613 | 15: ('Sharpness', |
---|
614 | {0xFFFF: 'Low', |
---|
615 | 0: 'Normal', |
---|
616 | 1: 'High'}), |
---|
617 | 16: ('ISO', |
---|
618 | {0: 'See ISOSpeedRatings Tag', |
---|
619 | 15: 'Auto', |
---|
620 | 16: '50', |
---|
621 | 17: '100', |
---|
622 | 18: '200', |
---|
623 | 19: '400'}), |
---|
624 | 17: ('MeteringMode', |
---|
625 | {3: 'Evaluative', |
---|
626 | 4: 'Partial', |
---|
627 | 5: 'Center-weighted'}), |
---|
628 | 18: ('FocusType', |
---|
629 | {0: 'Manual', |
---|
630 | 1: 'Auto', |
---|
631 | 3: 'Close-Up (Macro)', |
---|
632 | 8: 'Locked (Pan Mode)'}), |
---|
633 | 19: ('AFPointSelected', |
---|
634 | {0x3000: 'None (MF)', |
---|
635 | 0x3001: 'Auto-Selected', |
---|
636 | 0x3002: 'Right', |
---|
637 | 0x3003: 'Center', |
---|
638 | 0x3004: 'Left'}), |
---|
639 | 20: ('ExposureMode', |
---|
640 | {0: 'Easy Shooting', |
---|
641 | 1: 'Program', |
---|
642 | 2: 'Tv-priority', |
---|
643 | 3: 'Av-priority', |
---|
644 | 4: 'Manual', |
---|
645 | 5: 'A-DEP'}), |
---|
646 | 23: ('LongFocalLengthOfLensInFocalUnits', ), |
---|
647 | 24: ('ShortFocalLengthOfLensInFocalUnits', ), |
---|
648 | 25: ('FocalUnitsPerMM', ), |
---|
649 | 28: ('FlashActivity', |
---|
650 | {0: 'Did Not Fire', |
---|
651 | 1: 'Fired'}), |
---|
652 | 29: ('FlashDetails', |
---|
653 | {14: 'External E-TTL', |
---|
654 | 13: 'Internal Flash', |
---|
655 | 11: 'FP Sync Used', |
---|
656 | 7: '2nd("Rear")-Curtain Sync Used', |
---|
657 | 4: 'FP Sync Enabled'}), |
---|
658 | 32: ('FocusMode', |
---|
659 | {0: 'Single', |
---|
660 | 1: 'Continuous'}) |
---|
661 | } |
---|
662 | |
---|
663 | MAKERNOTE_CANON_TAG_0x004={ |
---|
664 | 7: ('WhiteBalance', |
---|
665 | {0: 'Auto', |
---|
666 | 1: 'Sunny', |
---|
667 | 2: 'Cloudy', |
---|
668 | 3: 'Tungsten', |
---|
669 | 4: 'Fluorescent', |
---|
670 | 5: 'Flash', |
---|
671 | 6: 'Custom'}), |
---|
672 | 9: ('SequenceNumber', ), |
---|
673 | 14: ('AFPointUsed', ), |
---|
674 | 15: ('FlashBias', |
---|
675 | {0XFFC0: '-2 EV', |
---|
676 | 0XFFCC: '-1.67 EV', |
---|
677 | 0XFFD0: '-1.50 EV', |
---|
678 | 0XFFD4: '-1.33 EV', |
---|
679 | 0XFFE0: '-1 EV', |
---|
680 | 0XFFEC: '-0.67 EV', |
---|
681 | 0XFFF0: '-0.50 EV', |
---|
682 | 0XFFF4: '-0.33 EV', |
---|
683 | 0X0000: '0 EV', |
---|
684 | 0X000C: '0.33 EV', |
---|
685 | 0X0010: '0.50 EV', |
---|
686 | 0X0014: '0.67 EV', |
---|
687 | 0X0020: '1 EV', |
---|
688 | 0X002C: '1.33 EV', |
---|
689 | 0X0030: '1.50 EV', |
---|
690 | 0X0034: '1.67 EV', |
---|
691 | 0X0040: '2 EV'}), |
---|
692 | 19: ('SubjectDistance', ) |
---|
693 | } |
---|
694 | |
---|
695 | # extract multibyte integer in Motorola format (little endian) |
---|
696 | def s2n_motorola(str): |
---|
697 | x=0 |
---|
698 | for c in str: |
---|
699 | x=(x << 8) | ord(c) |
---|
700 | return x |
---|
701 | |
---|
702 | # extract multibyte integer in Intel format (big endian) |
---|
703 | def s2n_intel(str): |
---|
704 | x=0 |
---|
705 | y=0L |
---|
706 | for c in str: |
---|
707 | x=x | (ord(c) << y) |
---|
708 | y=y+8 |
---|
709 | return x |
---|
710 | |
---|
711 | # ratio object that eventually will be able to reduce itself to lowest |
---|
712 | # common denominator for printing |
---|
713 | def gcd(a, b): |
---|
714 | if b == 0: |
---|
715 | return a |
---|
716 | else: |
---|
717 | return gcd(b, a % b) |
---|
718 | |
---|
719 | class Ratio: |
---|
720 | def __init__(self, num, den): |
---|
721 | self.num=num |
---|
722 | self.den=den |
---|
723 | |
---|
724 | def __repr__(self): |
---|
725 | self.reduce() |
---|
726 | if self.den == 1: |
---|
727 | return str(self.num) |
---|
728 | return '%d/%d' % (self.num, self.den) |
---|
729 | |
---|
730 | def reduce(self): |
---|
731 | div=gcd(self.num, self.den) |
---|
732 | if div > 1: |
---|
733 | self.num=self.num/div |
---|
734 | self.den=self.den/div |
---|
735 | |
---|
736 | # for ease of dealing with tags |
---|
737 | class IFD_Tag: |
---|
738 | def __init__(self, printable, tag, field_type, values, field_offset, |
---|
739 | field_length): |
---|
740 | # printable version of data |
---|
741 | self.printable=printable |
---|
742 | # tag ID number |
---|
743 | self.tag=tag |
---|
744 | # field type as index into FIELD_TYPES |
---|
745 | self.field_type=field_type |
---|
746 | # offset of start of field in bytes from beginning of IFD |
---|
747 | self.field_offset=field_offset |
---|
748 | # length of data field in bytes |
---|
749 | self.field_length=field_length |
---|
750 | # either a string or array of data items |
---|
751 | self.values=values |
---|
752 | |
---|
753 | def __str__(self): |
---|
754 | return self.printable |
---|
755 | |
---|
756 | def __repr__(self): |
---|
757 | return '(0x%04X) %s=%s @ %d' % (self.tag, |
---|
758 | FIELD_TYPES[self.field_type][2], |
---|
759 | self.printable, |
---|
760 | self.field_offset) |
---|
761 | |
---|
762 | # class that handles an EXIF header |
---|
763 | class EXIF_header: |
---|
764 | def __init__(self, file, endian, offset, fake_exif, debug=0): |
---|
765 | self.file=file |
---|
766 | self.endian=endian |
---|
767 | self.offset=offset |
---|
768 | self.fake_exif=fake_exif |
---|
769 | self.debug=debug |
---|
770 | self.tags={} |
---|
771 | |
---|
772 | # convert slice to integer, based on sign and endian flags |
---|
773 | # usually this offset is assumed to be relative to the beginning of the |
---|
774 | # start of the EXIF information. For some cameras that use relative tags, |
---|
775 | # this offset may be relative to some other starting point. |
---|
776 | def s2n(self, offset, length, signed=0): |
---|
777 | self.file.seek(self.offset+offset) |
---|
778 | slice=self.file.read(length) |
---|
779 | if self.endian == 'I': |
---|
780 | val=s2n_intel(slice) |
---|
781 | else: |
---|
782 | val=s2n_motorola(slice) |
---|
783 | # Sign extension ? |
---|
784 | if signed: |
---|
785 | msb=1L << (8*length-1) |
---|
786 | if val & msb: |
---|
787 | val=val-(msb << 1) |
---|
788 | return val |
---|
789 | |
---|
790 | # convert offset to string |
---|
791 | def n2s(self, offset, length): |
---|
792 | s='' |
---|
793 | for i in range(length): |
---|
794 | if self.endian == 'I': |
---|
795 | s=s+chr(offset & 0xFF) |
---|
796 | else: |
---|
797 | s=chr(offset & 0xFF)+s |
---|
798 | offset=offset >> 8 |
---|
799 | return s |
---|
800 | |
---|
801 | # return first IFD |
---|
802 | def first_IFD(self): |
---|
803 | return self.s2n(4, 4) |
---|
804 | |
---|
805 | # return pointer to next IFD |
---|
806 | def next_IFD(self, ifd): |
---|
807 | entries=self.s2n(ifd, 2) |
---|
808 | return self.s2n(ifd+2+12*entries, 4) |
---|
809 | |
---|
810 | # return list of IFDs in header |
---|
811 | def list_IFDs(self): |
---|
812 | i=self.first_IFD() |
---|
813 | a=[] |
---|
814 | while i: |
---|
815 | a.append(i) |
---|
816 | i=self.next_IFD(i) |
---|
817 | return a |
---|
818 | |
---|
819 | # return list of entries in this IFD |
---|
820 | def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0): |
---|
821 | entries=self.s2n(ifd, 2) |
---|
822 | for i in range(entries): |
---|
823 | # entry is index of start of this IFD in the file |
---|
824 | entry=ifd+2+12*i |
---|
825 | tag=self.s2n(entry, 2) |
---|
826 | # get tag name. We do it early to make debugging easier |
---|
827 | tag_entry=dict.get(tag) |
---|
828 | if tag_entry: |
---|
829 | tag_name=tag_entry[0] |
---|
830 | else: |
---|
831 | tag_name='Tag 0x%04X' % tag |
---|
832 | field_type=self.s2n(entry+2, 2) |
---|
833 | if not 0 < field_type < len(FIELD_TYPES): |
---|
834 | # unknown field type |
---|
835 | raise ValueError, \ |
---|
836 | 'unknown type %d in tag 0x%04X' % (field_type, tag) |
---|
837 | typelen=FIELD_TYPES[field_type][0] |
---|
838 | count=self.s2n(entry+4, 4) |
---|
839 | offset=entry+8 |
---|
840 | if count*typelen > 4: |
---|
841 | # offset is not the value; it's a pointer to the value |
---|
842 | # if relative we set things up so s2n will seek to the right |
---|
843 | # place when it adds self.offset. Note that this 'relative' |
---|
844 | # is for the Nikon type 3 makernote. Other cameras may use |
---|
845 | # other relative offsets, which would have to be computed here |
---|
846 | # slightly differently. |
---|
847 | if relative: |
---|
848 | tmp_offset=self.s2n(offset, 4) |
---|
849 | offset=tmp_offset+ifd-self.offset+4 |
---|
850 | if self.fake_exif: |
---|
851 | offset=offset+18 |
---|
852 | else: |
---|
853 | offset=self.s2n(offset, 4) |
---|
854 | field_offset=offset |
---|
855 | if field_type == 2: |
---|
856 | # special case: null-terminated ASCII string |
---|
857 | if count != 0: |
---|
858 | self.file.seek(self.offset+offset) |
---|
859 | values=self.file.read(count) |
---|
860 | values=values.strip().replace('\x00','') |
---|
861 | else: |
---|
862 | values='' |
---|
863 | else: |
---|
864 | values=[] |
---|
865 | signed=(field_type in [6, 8, 9, 10]) |
---|
866 | for j in range(count): |
---|
867 | if field_type in (5, 10): |
---|
868 | # a ratio |
---|
869 | value_j=Ratio(self.s2n(offset, 4, signed), |
---|
870 | self.s2n(offset+4, 4, signed)) |
---|
871 | else: |
---|
872 | value_j=self.s2n(offset, typelen, signed) |
---|
873 | values.append(value_j) |
---|
874 | offset=offset+typelen |
---|
875 | # now "values" is either a string or an array |
---|
876 | if count == 1 and field_type != 2: |
---|
877 | printable=str(values[0]) |
---|
878 | else: |
---|
879 | printable=str(values) |
---|
880 | # compute printable version of values |
---|
881 | if tag_entry: |
---|
882 | if len(tag_entry) != 1: |
---|
883 | # optional 2nd tag element is present |
---|
884 | if callable(tag_entry[1]): |
---|
885 | # call mapping function |
---|
886 | printable=tag_entry[1](values) |
---|
887 | else: |
---|
888 | printable='' |
---|
889 | for i in values: |
---|
890 | # use lookup table for this tag |
---|
891 | printable+=tag_entry[1].get(i, repr(i)) |
---|
892 | self.tags[ifd_name+' '+tag_name]=IFD_Tag(printable, tag, |
---|
893 | field_type, |
---|
894 | values, field_offset, |
---|
895 | count*typelen) |
---|
896 | if self.debug: |
---|
897 | print ' debug: %s: %s' % (tag_name, |
---|
898 | repr(self.tags[ifd_name+' '+tag_name])) |
---|
899 | |
---|
900 | # extract uncompressed TIFF thumbnail (like pulling teeth) |
---|
901 | # we take advantage of the pre-existing layout in the thumbnail IFD as |
---|
902 | # much as possible |
---|
903 | def extract_TIFF_thumbnail(self, thumb_ifd): |
---|
904 | entries=self.s2n(thumb_ifd, 2) |
---|
905 | # this is header plus offset to IFD ... |
---|
906 | if self.endian == 'M': |
---|
907 | tiff='MM\x00*\x00\x00\x00\x08' |
---|
908 | else: |
---|
909 | tiff='II*\x00\x08\x00\x00\x00' |
---|
910 | # ... plus thumbnail IFD data plus a null "next IFD" pointer |
---|
911 | self.file.seek(self.offset+thumb_ifd) |
---|
912 | tiff+=self.file.read(entries*12+2)+'\x00\x00\x00\x00' |
---|
913 | |
---|
914 | # fix up large value offset pointers into data area |
---|
915 | for i in range(entries): |
---|
916 | entry=thumb_ifd+2+12*i |
---|
917 | tag=self.s2n(entry, 2) |
---|
918 | field_type=self.s2n(entry+2, 2) |
---|
919 | typelen=FIELD_TYPES[field_type][0] |
---|
920 | count=self.s2n(entry+4, 4) |
---|
921 | oldoff=self.s2n(entry+8, 4) |
---|
922 | # start of the 4-byte pointer area in entry |
---|
923 | ptr=i*12+18 |
---|
924 | # remember strip offsets location |
---|
925 | if tag == 0x0111: |
---|
926 | strip_off=ptr |
---|
927 | strip_len=count*typelen |
---|
928 | # is it in the data area? |
---|
929 | if count*typelen > 4: |
---|
930 | # update offset pointer (nasty "strings are immutable" crap) |
---|
931 | # should be able to say "tiff[ptr:ptr+4]=newoff" |
---|
932 | newoff=len(tiff) |
---|
933 | tiff=tiff[:ptr]+self.n2s(newoff, 4)+tiff[ptr+4:] |
---|
934 | # remember strip offsets location |
---|
935 | if tag == 0x0111: |
---|
936 | strip_off=newoff |
---|
937 | strip_len=4 |
---|
938 | # get original data and store it |
---|
939 | self.file.seek(self.offset+oldoff) |
---|
940 | tiff+=self.file.read(count*typelen) |
---|
941 | |
---|
942 | # add pixel strips and update strip offset info |
---|
943 | old_offsets=self.tags['Thumbnail StripOffsets'].values |
---|
944 | old_counts=self.tags['Thumbnail StripByteCounts'].values |
---|
945 | for i in range(len(old_offsets)): |
---|
946 | # update offset pointer (more nasty "strings are immutable" crap) |
---|
947 | offset=self.n2s(len(tiff), strip_len) |
---|
948 | tiff=tiff[:strip_off]+offset+tiff[strip_off+strip_len:] |
---|
949 | strip_off+=strip_len |
---|
950 | # add pixel strip to end |
---|
951 | self.file.seek(self.offset+old_offsets[i]) |
---|
952 | tiff+=self.file.read(old_counts[i]) |
---|
953 | |
---|
954 | self.tags['TIFFThumbnail']=tiff |
---|
955 | |
---|
956 | # decode all the camera-specific MakerNote formats |
---|
957 | |
---|
958 | # Note is the data that comprises this MakerNote. The MakerNote will |
---|
959 | # likely have pointers in it that point to other parts of the file. We'll |
---|
960 | # use self.offset as the starting point for most of those pointers, since |
---|
961 | # they are relative to the beginning of the file. |
---|
962 | # |
---|
963 | # If the MakerNote is in a newer format, it may use relative addressing |
---|
964 | # within the MakerNote. In that case we'll use relative addresses for the |
---|
965 | # pointers. |
---|
966 | # |
---|
967 | # As an aside: it's not just to be annoying that the manufacturers use |
---|
968 | # relative offsets. It's so that if the makernote has to be moved by the |
---|
969 | # picture software all of the offsets don't have to be adjusted. Overall, |
---|
970 | # this is probably the right strategy for makernotes, though the spec is |
---|
971 | # ambiguous. (The spec does not appear to imagine that makernotes would |
---|
972 | # follow EXIF format internally. Once they did, it's ambiguous whether |
---|
973 | # the offsets should be from the header at the start of all the EXIF info, |
---|
974 | # or from the header at the start of the makernote.) |
---|
975 | def decode_maker_note(self): |
---|
976 | note=self.tags['EXIF MakerNote'] |
---|
977 | make=self.tags['Image Make'].printable |
---|
978 | model=self.tags['Image Model'].printable |
---|
979 | |
---|
980 | # Nikon |
---|
981 | # The maker note usually starts with the word Nikon, followed by the |
---|
982 | # type of the makernote (1 or 2, as a short). If the word Nikon is |
---|
983 | # not at the start of the makernote, it's probably type 2, since some |
---|
984 | # cameras work that way. |
---|
985 | if make in ('NIKON', 'NIKON CORPORATION'): |
---|
986 | if note.values[0:7] == [78, 105, 107, 111, 110, 00, 01]: |
---|
987 | if self.debug: |
---|
988 | print "Looks like a type 1 Nikon MakerNote." |
---|
989 | self.dump_IFD(note.field_offset+8, 'MakerNote', |
---|
990 | dict=MAKERNOTE_NIKON_OLDER_TAGS) |
---|
991 | elif note.values[0:7] == [78, 105, 107, 111, 110, 00, 02]: |
---|
992 | if self.debug: |
---|
993 | print "Looks like a labeled type 2 Nikon MakerNote" |
---|
994 | if note.values[12:14] != [0, 42] and note.values[12:14] != [42L, 0L]: |
---|
995 | raise ValueError, "Missing marker tag '42' in MakerNote." |
---|
996 | # skip the Makernote label and the TIFF header |
---|
997 | self.dump_IFD(note.field_offset+10+8, 'MakerNote', |
---|
998 | dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1) |
---|
999 | else: |
---|
1000 | # E99x or D1 |
---|
1001 | if self.debug: |
---|
1002 | print "Looks like an unlabeled type 2 Nikon MakerNote" |
---|
1003 | self.dump_IFD(note.field_offset, 'MakerNote', |
---|
1004 | dict=MAKERNOTE_NIKON_NEWER_TAGS) |
---|
1005 | return |
---|
1006 | |
---|
1007 | # Olympus |
---|
1008 | if make[:7] == 'OLYMPUS': |
---|
1009 | self.dump_IFD(note.field_offset+8, 'MakerNote', |
---|
1010 | dict=MAKERNOTE_OLYMPUS_TAGS) |
---|
1011 | return |
---|
1012 | |
---|
1013 | # Casio |
---|
1014 | if make == 'Casio': |
---|
1015 | self.dump_IFD(note.field_offset, 'MakerNote', |
---|
1016 | dict=MAKERNOTE_CASIO_TAGS) |
---|
1017 | return |
---|
1018 | |
---|
1019 | # Fujifilm |
---|
1020 | if make == 'FUJIFILM': |
---|
1021 | # bug: everything else is "Motorola" endian, but the MakerNote |
---|
1022 | # is "Intel" endian |
---|
1023 | endian=self.endian |
---|
1024 | self.endian='I' |
---|
1025 | # bug: IFD offsets are from beginning of MakerNote, not |
---|
1026 | # beginning of file header |
---|
1027 | offset=self.offset |
---|
1028 | self.offset+=note.field_offset |
---|
1029 | # process note with bogus values (note is actually at offset 12) |
---|
1030 | self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS) |
---|
1031 | # reset to correct values |
---|
1032 | self.endian=endian |
---|
1033 | self.offset=offset |
---|
1034 | return |
---|
1035 | |
---|
1036 | # Canon |
---|
1037 | if make == 'Canon': |
---|
1038 | self.dump_IFD(note.field_offset, 'MakerNote', |
---|
1039 | dict=MAKERNOTE_CANON_TAGS) |
---|
1040 | for i in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001), |
---|
1041 | ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004)): |
---|
1042 | self.canon_decode_tag(self.tags[i[0]].values, i[1]) |
---|
1043 | return |
---|
1044 | |
---|
1045 | # decode Canon MakerNote tag based on offset within tag |
---|
1046 | # see http://www.burren.cx/david/canon.html by David Burren |
---|
1047 | def canon_decode_tag(self, value, dict): |
---|
1048 | for i in range(1, len(value)): |
---|
1049 | x=dict.get(i, ('Unknown', )) |
---|
1050 | if self.debug: |
---|
1051 | print i, x |
---|
1052 | name=x[0] |
---|
1053 | if len(x) > 1: |
---|
1054 | val=x[1].get(value[i], 'Unknown') |
---|
1055 | else: |
---|
1056 | val=value[i] |
---|
1057 | # it's not a real IFD Tag but we fake one to make everybody |
---|
1058 | # happy. this will have a "proprietary" type |
---|
1059 | self.tags['MakerNote '+name]=IFD_Tag(str(val), None, 0, None, |
---|
1060 | None, None) |
---|
1061 | |
---|
1062 | # process an image file (expects an open file object) |
---|
1063 | # this is the function that has to deal with all the arbitrary nasty bits |
---|
1064 | # of the EXIF standard |
---|
1065 | def process_file(file, debug=0): |
---|
1066 | # determine whether it's a JPEG or TIFF |
---|
1067 | data=file.read(12) |
---|
1068 | if data[0:4] in ['II*\x00', 'MM\x00*']: |
---|
1069 | # it's a TIFF file |
---|
1070 | file.seek(0) |
---|
1071 | endian=file.read(1) |
---|
1072 | file.read(1) |
---|
1073 | offset=0 |
---|
1074 | elif data[0:2] == '\xFF\xD8': |
---|
1075 | # it's a JPEG file |
---|
1076 | # skip JFIF style header(s) |
---|
1077 | fake_exif=0 |
---|
1078 | while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM'): |
---|
1079 | length=ord(data[4])*256+ord(data[5]) |
---|
1080 | file.read(length-8) |
---|
1081 | # fake an EXIF beginning of file |
---|
1082 | data='\xFF\x00'+file.read(10) |
---|
1083 | fake_exif=1 |
---|
1084 | if data[2] == '\xFF' and data[6:10] == 'Exif': |
---|
1085 | # detected EXIF header |
---|
1086 | offset=file.tell() |
---|
1087 | endian=file.read(1) |
---|
1088 | else: |
---|
1089 | # no EXIF information |
---|
1090 | return {} |
---|
1091 | else: |
---|
1092 | # file format not recognized |
---|
1093 | return {} |
---|
1094 | |
---|
1095 | # deal with the EXIF info we found |
---|
1096 | if debug: |
---|
1097 | print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format' |
---|
1098 | hdr=EXIF_header(file, endian, offset, fake_exif, debug) |
---|
1099 | ifd_list=hdr.list_IFDs() |
---|
1100 | ctr=0 |
---|
1101 | for i in ifd_list: |
---|
1102 | if ctr == 0: |
---|
1103 | IFD_name='Image' |
---|
1104 | elif ctr == 1: |
---|
1105 | IFD_name='Thumbnail' |
---|
1106 | thumb_ifd=i |
---|
1107 | else: |
---|
1108 | IFD_name='IFD %d' % ctr |
---|
1109 | if debug: |
---|
1110 | print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i) |
---|
1111 | hdr.dump_IFD(i, IFD_name) |
---|
1112 | # EXIF IFD |
---|
1113 | exif_off=hdr.tags.get(IFD_name+' ExifOffset') |
---|
1114 | if exif_off: |
---|
1115 | if debug: |
---|
1116 | print ' EXIF SubIFD at offset %d:' % exif_off.values[0] |
---|
1117 | hdr.dump_IFD(exif_off.values[0], 'EXIF') |
---|
1118 | # Interoperability IFD contained in EXIF IFD |
---|
1119 | intr_off=hdr.tags.get('EXIF SubIFD InteroperabilityOffset') |
---|
1120 | if intr_off: |
---|
1121 | if debug: |
---|
1122 | print ' EXIF Interoperability SubSubIFD at offset %d:' \ |
---|
1123 | % intr_off.values[0] |
---|
1124 | hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability', |
---|
1125 | dict=INTR_TAGS) |
---|
1126 | # GPS IFD |
---|
1127 | gps_off=hdr.tags.get(IFD_name+' GPSInfo') |
---|
1128 | if gps_off: |
---|
1129 | if debug: |
---|
1130 | print ' GPS SubIFD at offset %d:' % gps_off.values[0] |
---|
1131 | hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS) |
---|
1132 | ctr+=1 |
---|
1133 | |
---|
1134 | # extract uncompressed TIFF thumbnail |
---|
1135 | thumb=hdr.tags.get('Thumbnail Compression') |
---|
1136 | if thumb and thumb.printable == 'Uncompressed TIFF': |
---|
1137 | hdr.extract_TIFF_thumbnail(thumb_ifd) |
---|
1138 | |
---|
1139 | # JPEG thumbnail (thankfully the JPEG data is stored as a unit) |
---|
1140 | thumb_off=hdr.tags.get('Thumbnail JPEGInterchangeFormat') |
---|
1141 | if thumb_off: |
---|
1142 | file.seek(offset+thumb_off.values[0]) |
---|
1143 | size=hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0] |
---|
1144 | hdr.tags['JPEGThumbnail']=file.read(size) |
---|
1145 | |
---|
1146 | # deal with MakerNote contained in EXIF IFD |
---|
1147 | if hdr.tags.has_key('EXIF MakerNote'): |
---|
1148 | hdr.decode_maker_note() |
---|
1149 | |
---|
1150 | # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote |
---|
1151 | # since it's not allowed in a uncompressed TIFF IFD |
---|
1152 | if not hdr.tags.has_key('JPEGThumbnail'): |
---|
1153 | thumb_off=hdr.tags.get('MakerNote JPEGThumbnail') |
---|
1154 | if thumb_off: |
---|
1155 | file.seek(offset+thumb_off.values[0]) |
---|
1156 | hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length) |
---|
1157 | |
---|
1158 | return hdr.tags |
---|
1159 | |
---|
1160 | # library test/debug function (dump given files) |
---|
1161 | if __name__ == '__main__': |
---|
1162 | import sys |
---|
1163 | |
---|
1164 | if len(sys.argv) < 2: |
---|
1165 | print 'Usage: %s files...\n' % sys.argv[0] |
---|
1166 | sys.exit(0) |
---|
1167 | |
---|
1168 | for filename in sys.argv[1:]: |
---|
1169 | try: |
---|
1170 | file=open(filename, 'rb') |
---|
1171 | except: |
---|
1172 | print filename, 'unreadable' |
---|
1173 | print |
---|
1174 | continue |
---|
1175 | print filename+':' |
---|
1176 | # data=process_file(file, 1) # with debug info |
---|
1177 | data=process_file(file) |
---|
1178 | if not data: |
---|
1179 | print 'No EXIF information found' |
---|
1180 | continue |
---|
1181 | |
---|
1182 | x=data.keys() |
---|
1183 | x.sort() |
---|
1184 | for i in x: |
---|
1185 | if i in ('JPEGThumbnail', 'TIFFThumbnail'): |
---|
1186 | continue |
---|
1187 | try: |
---|
1188 | print ' %s (%s): %s' % \ |
---|
1189 | (i, FIELD_TYPES[data[i].field_type][2], data[i].printable) |
---|
1190 | except: |
---|
1191 | print 'error', i, '"', data[i], '"' |
---|
1192 | if data.has_key('JPEGThumbnail'): |
---|
1193 | print 'File has JPEG thumbnail' |
---|
1194 | print |
---|