~mblayman/entertainer/clean-up-1.0

« back to all changes in this revision

Viewing changes to src/utils/EXIF.py

  • Committer: Paul Hummer
  • Date: 2008-06-21 06:10:59 UTC
  • mto: This revision was merged to the branch mainline in revision 256.
  • Revision ID: paul@ubuntu.com-20080621061059-a55dg8p5hlr0k1jr
Moves many files and directories

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# pylint: disable-msg=E0011,W0311,C0301,C0302,C0111,C0322,W0621
 
2
# Library to extract EXIF information in digital camera image files
 
3
#
 
4
# To use this library call with:
 
5
#    f=open(path_name, 'rb')
 
6
#    tags=EXIF.process_file(f)
 
7
# tags will now be a dictionary mapping names of EXIF tags to their
 
8
# values in the file named by path_name.  You can process the tags
 
9
# as you wish.  In particular, you can iterate through all the tags with:
 
10
#     for tag in tags.keys():
 
11
#         if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename',
 
12
#                        'EXIF MakerNote'):
 
13
#             print "Key: %s, value %s" % (tag, tags[tag])
 
14
# (This code uses the if statement to avoid printing out a few of the
 
15
# tags that tend to be long or boring.)
 
16
#
 
17
# The tags dictionary will include keys for all of the usual EXIF
 
18
# tags, and will also include keys for Makernotes used by some
 
19
# cameras, for which we have a good specification.
 
20
#
 
21
# Contains code from "exifdump.py" originally written by Thierry Bousch
 
22
# <bousch@topo.math.u-psud.fr> and released into the public domain.
 
23
#
 
24
# Updated and turned into general-purpose library by Gene Cash
 
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