1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 FIELD_TYPES=(
93 (0, 'X', 'Proprietary'),
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')
104 )
105
106
107
108
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
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 (?)',
220 7: 'Fired (!)',
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
233 0x9286: ('UserComment', lambda x: ''.join(map(chr, x))),
234 0x9290: ('SubSecTime', ),
235 0x9291: ('SubSecTimeOriginal', ),
236 0x9292: ('SubSecTimeDigitized', ),
237
238 0xA000: ('FlashPixVersion', lambda x: ''.join(map(chr, x))),
239 0xA001: ('ColorSpace', ),
240 0xA002: ('ExifImageWidth', ),
241 0xA003: ('ExifImageLength', ),
242 0xA005: ('InteroperabilityOffset', ),
243 0xA20B: ('FlashEnergy', ),
244 0xA20C: ('SpatialFrequencyResponse', ),
245 0xA20E: ('FocalPlaneXResolution', ),
246 0xA20F: ('FocalPlaneYResolution', ),
247 0xA210: ('FocalPlaneResolutionUnit', ),
248 0xA214: ('SubjectLocation', ),
249 0xA215: ('ExposureIndex', ),
250 0xA217: ('SensingMethod', ),
251 0xA300: ('FileSource',
252 {3: 'Digital Camera'}),
253 0xA301: ('SceneType',
254 {1: 'Directly Photographed'}),
255 0xA302: ('CVAPattern',),
256 }
257
258
259 INTR_TAGS={
260 0x0001: ('InteroperabilityIndex', ),
261 0x0002: ('InteroperabilityVersion', ),
262 0x1000: ('RelatedImageFileFormat', ),
263 0x1001: ('RelatedImageWidth', ),
264 0x1002: ('RelatedImageLength', ),
265 }
266
267
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', )
296 }
297
298
299
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', )
357 }
358
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'})
389 }
390
391
393 a={
394 0: 'Normal',
395 1: 'Unknown',
396 2: 'Fast',
397 3: 'Panorama'}
398 b={
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]])
405
406 MAKERNOTE_OLYMPUS_TAGS={
407
408
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
422 0x0209: ('CameraID', lambda x: ''.join(map(chr, x))),
423 0x0F00: ('DataDump', )
424 }
425
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',})
478 }
479
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'})
543 }
544
545 MAKERNOTE_CANON_TAGS={
546 0x0006: ('ImageType', ),
547 0x0007: ('FirmwareVersion', ),
548 0x0008: ('ImageNumber', ),
549 0x0009: ('OwnerName', )
550 }
551
552
553
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'})
660 }
661
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', )
692 }
693
694
696 x=0
697 for c in str:
698 x=(x << 8) | ord(c)
699 return x
700
701
703 x=0
704 y=0L
705 for c in str:
706 x=x | (ord(c) << y)
707 y=y+8
708 return x
709
710
711
713 if b == 0:
714 return a
715 else:
716 return gcd(b, a % b)
717
720 self.num=num
721 self.den=den
722
724 self.reduce()
725 if self.den == 1:
726 return str(self.num)
727 return '%d/%d' % (self.num, self.den)
728
730 div=gcd(self.num, self.den)
731 if div > 1:
732 self.num=self.num/div
733 self.den=self.den/div
734
735
737 - def __init__(self, printable, tag, field_type, values, field_offset,
738 field_length):
739
740 self.printable=printable
741
742 self.tag=tag
743
744 self.field_type=field_type
745
746 self.field_offset=field_offset
747
748 self.field_length=field_length
749
750 self.values=values
751
753 return self.printable
754
756 return '(0x%04X) %s=%s @ %d' % (self.tag,
757 FIELD_TYPES[self.field_type][2],
758 self.printable,
759 self.field_offset)
760
761
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={}
770
771
772
773
774
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
783 if signed:
784 msb=1L << (8*length-1)
785 if val & msb:
786 val=val-(msb << 1)
787 return val
788
789
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
799
800
802 return self.s2n(4, 4)
803
804
806 entries=self.s2n(ifd, 2)
807 return self.s2n(ifd+2+12*entries, 4)
808
809
817
818
820 entries=self.s2n(ifd, 2)
821 for i in range(entries):
822
823 entry=ifd+2+12*i
824 tag=self.s2n(entry, 2)
825
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
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
841
842
843
844
845
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
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
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
875 if count == 1 and field_type != 2:
876 printable=str(values[0])
877 else:
878 printable=str(values)
879
880 if tag_entry:
881 if len(tag_entry) != 1:
882
883 if callable(tag_entry[1]):
884
885 printable=tag_entry[1](values)
886 else:
887 printable=''
888 for i in values:
889
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]))
898
899
900
901
903 entries=self.s2n(thumb_ifd, 2)
904
905 if self.endian == 'M':
906 tiff='MM\x00*\x00\x00\x00\x08'
907 else:
908 tiff='II*\x00\x08\x00\x00\x00'
909
910 self.file.seek(self.offset+thumb_ifd)
911 tiff+=self.file.read(entries*12+2)+'\x00\x00\x00\x00'
912
913
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
922 ptr=i*12+18
923
924 if tag == 0x0111:
925 strip_off=ptr
926 strip_len=count*typelen
927
928 if count*typelen > 4:
929
930
931 newoff=len(tiff)
932 tiff=tiff[:ptr]+self.n2s(newoff, 4)+tiff[ptr+4:]
933
934 if tag == 0x0111:
935 strip_off=newoff
936 strip_len=4
937
938 self.file.seek(self.offset+oldoff)
939 tiff+=self.file.read(count*typelen)
940
941
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
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
950 self.file.seek(self.offset+old_offsets[i])
951 tiff+=self.file.read(old_counts[i])
952
953 self.tags['TIFFThumbnail']=tiff
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
975 note=self.tags['EXIF MakerNote']
976 make=self.tags['Image Make'].printable
977 model=self.tags['Image Model'].printable
978
979
980
981
982
983
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
996 self.dump_IFD(note.field_offset+10+8, 'MakerNote',
997 dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1)
998 else:
999
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
1005
1006
1007 if make[:7] == 'OLYMPUS':
1008 self.dump_IFD(note.field_offset+8, 'MakerNote',
1009 dict=MAKERNOTE_OLYMPUS_TAGS)
1010 return
1011
1012
1013 if make == 'Casio':
1014 self.dump_IFD(note.field_offset, 'MakerNote',
1015 dict=MAKERNOTE_CASIO_TAGS)
1016 return
1017
1018
1019 if make == 'FUJIFILM':
1020
1021
1022 endian=self.endian
1023 self.endian='I'
1024
1025
1026 offset=self.offset
1027 self.offset+=note.field_offset
1028
1029 self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS)
1030
1031 self.endian=endian
1032 self.offset=offset
1033 return
1034
1035
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
1043
1044
1045
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
1057
1058 self.tags['MakerNote '+name]=IFD_Tag(str(val), None, 0, None,
1059 None, None)
1060
1061
1062
1063
1065
1066 data=file.read(12)
1067 if data[0:4] in ['II*\x00', 'MM\x00*']:
1068
1069 file.seek(0)
1070 endian=file.read(1)
1071 file.read(1)
1072 offset=0
1073 elif data[0:2] == '\xFF\xD8':
1074
1075
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
1081 data='\xFF\x00'+file.read(10)
1082 fake_exif=1
1083 if data[2] == '\xFF' and data[6:10] == 'Exif':
1084
1085 offset=file.tell()
1086 endian=file.read(1)
1087 else:
1088
1089 return {}
1090 else:
1091
1092 return {}
1093
1094
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
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
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
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
1132
1133
1134 thumb=hdr.tags.get('Thumbnail Compression')
1135 if thumb and thumb.printable == 'Uncompressed TIFF':
1136 hdr.extract_TIFF_thumbnail(thumb_ifd)
1137
1138
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)
1144
1145
1146 if hdr.tags.has_key('EXIF MakerNote'):
1147 hdr.decode_maker_note()
1148
1149
1150
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)
1156
1157 return hdr.tags
1158
1159
1160 if __name__ == '__main__':
1161 import sys
1162
1163 if len(sys.argv) < 2:
1164 print 'Usage: %s files...\n' % sys.argv[0]
1165 sys.exit(0)
1166
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
1176 data=process_file(file)
1177 if not data:
1178 print 'No EXIF information found'
1179 continue
1180
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
1194