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