~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/filter/EXIF.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mfrom: (0.9.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20080622211713-fpo2zrq3s5dfecxg
Tags: 1.7.0-3
Simplify /etc/moin/wikilist format: "USER URL" (drop unneeded middle
CONFIG_DIR that was wrongly advertised as DATA_DIR).  Make
moin-mass-migrate handle both formats and warn about deprecation of
the old one.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: utf-8 -*-
 
3
#
1
4
# Library to extract EXIF information in digital camera image files
 
5
# http://sourceforge.net/projects/exif-py/
2
6
#
3
7
# To use this library call with:
4
8
#    f=open(path_name, 'rb')
5
9
#    tags=EXIF.process_file(f)
6
 
# tags will now be a dictionary mapping names of EXIF tags to their
 
10
#
 
11
# If you want to ignore makerNote, pass the -q or --quick
 
12
# command line arguments, or as
 
13
#    tags=EXIF.process_file(f, details=False)
 
14
#
 
15
# This is useful when you are retrieving a large list of images
 
16
#
 
17
# Returned tags will be a dictionary mapping names of EXIF tags to their
7
18
# values in the file named by path_name.  You can process the tags
8
19
# as you wish.  In particular, you can iterate through all the tags with:
9
20
#     for tag in tags.keys():
24
35
#
25
36
# This copyright license is intended to be similar to the FreeBSD license.
26
37
#
27
 
# Copyright 2002 Gene Cash All rights reserved.
 
38
# Copyright (c) 2002 Gene Cash All rights reserved.
28
39
#
29
40
# Redistribution and use in source and binary forms, with or without
30
41
# modification, are permitted provided that the following conditions are
62
73
#   Fixed subtle bug when faking an EXIF header, which affected maker notes
63
74
#   using relative offsets, and a fix for Nikon D100.
64
75
#
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.
 
76
# 1999-08-21 TB  Last update by Thierry Bousch to his code.
 
77
# 2002-01-17 CEC Discovered code on web.
 
78
#                Commented everything.
 
79
#                Made small code improvements.
 
80
#                Reformatted for readability.
 
81
# 2002-01-19 CEC Added ability to read TIFFs and JFIF-format JPEGs.
 
82
#                Added ability to extract JPEG formatted thumbnail.
 
83
#                Added ability to read GPS IFD (not tested).
 
84
#                Converted IFD data structure to dictionaries indexed by
 
85
#                tag name.
 
86
#                Factored into library returning dictionary of IFDs plus
 
87
#                thumbnail, if any.
 
88
# 2002-01-20 CEC Added MakerNote processing logic.
 
89
#                Added Olympus MakerNote.
 
90
#                Converted data structure to single-level dictionary, avoiding
 
91
#                tag name collisions by prefixing with IFD name.  This makes
 
92
#                it much easier to use.
 
93
# 2002-01-23 CEC Trimmed nulls from end of string values.
 
94
# 2002-01-25 CEC Discovered JPEG thumbnail in Olympus TIFF MakerNote.
 
95
# 2002-01-26 CEC Added ability to extract TIFF thumbnails.
85
96
#               Added Nikon, Fujifilm, Casio MakerNotes.
86
 
# 30-NOV-03 CEC Fixed problem with canon_decode_tag() not creating an
 
97
# 2003-11-30 CEC Fixed problem with canon_decode_tag() not creating an
87
98
#               IFD_Tag() object.
88
 
# 15-FEB-04 CEC Finally fixed bit shift warning by converting Y to 0L.
89
 
#
 
99
# 2004-02-15 CEC Finally fixed bit shift warning by converting Y to 0L.
 
100
#
 
101
# ---------------------------- End original notices ------------------------- #
 
102
 
 
103
# 2006-08-04 MoinMoin:ReimarBauer
 
104
# added an optional parameter name to process_file and dump_IFD. Using this parameter the
 
105
# loop is breaked after that tag_name is processed.
 
106
# Example:
 
107
#    f = open(infile, 'rb')
 
108
#    tags = EXIF.process_file(f,'DateTimeOriginal')
 
109
#    f.close()
 
110
# some PEP8 changes
 
111
 
 
112
# 2007-01-18 - Ianaré Sévi <ianare@gmail.com>
 
113
# Fixed a couple errors and assuming maintenance of the library.
 
114
 
 
115
# 2007-03-24 - Ianaré Sévi
 
116
# Can now ignore MakerNotes Tags for faster processing.
 
117
 
 
118
# 2007-05-03 - Martin Stone <mj_stone@users.sourceforge.net>
 
119
# Fix for inverted detailed flag and Photoshop header
 
120
 
 
121
# 2007-09-22 - Stephen H. Olson
 
122
# Don't error on invalid string
 
123
# Improved Nikon MakerNote support
 
124
 
 
125
# 2007-09-26 - Stephen H. Olson
 
126
# Don't error on invalid Olympus 'SpecialMode'.
 
127
# Add a few more Olympus/Minolta tags.
 
128
 
 
129
# 2007-09-27 - Ianaré Sévi
 
130
# Add Olympus Makernote tags.
 
131
 
 
132
 
 
133
# ===== CODE START ==== #
 
134
 
 
135
# Don't throw an exception when given an out of range character.
 
136
#
 
137
# lambda x: ''.join(map(chr, x))
 
138
#
 
139
def make_string(seq):
 
140
    str = ""
 
141
    for c in seq:
 
142
        # Screen out non-printing characters
 
143
        if 32 <= c and c < 256:
 
144
            str += chr(c)
 
145
    # If no printing chars
 
146
    if not str:
 
147
        return seq
 
148
    return str
 
149
 
 
150
# Special version to deal with the code in the first 8 bytes of a user comment.
 
151
def make_string_uc(seq):
 
152
    code = seq[0:8]
 
153
    seq = seq[8:]
 
154
    # Of course, this is only correct if ASCII, and the standard explicitly
 
155
    # allows JIS and Unicode.
 
156
    return make_string(seq)
90
157
 
91
158
# 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'),
 
159
FIELD_TYPES = (
 
160
    (0, 'X', 'Proprietary'), # no such type
 
161
    (1, 'B', 'Byte'),
 
162
    (1, 'A', 'ASCII'),
 
163
    (2, 'S', 'Short'),
 
164
    (4, 'L', 'Long'),
 
165
    (8, 'R', 'Ratio'),
99
166
    (1, 'SB', 'Signed Byte'),
100
 
    (1, 'U',  'Undefined'),
 
167
    (1, 'U', 'Undefined'),
101
168
    (2, 'SS', 'Signed Short'),
102
169
    (4, 'SL', 'Signed Long'),
103
 
    (8, 'SR', 'Signed Ratio')
 
170
    (8, 'SR', 'Signed Ratio'),
104
171
    )
105
172
 
106
173
# dictionary of main EXIF tag names
107
174
# first element of tuple is tag name, optional second element is
108
175
# another dictionary giving names to values
109
 
EXIF_TAGS={
 
176
EXIF_TAGS = {
110
177
    0x0100: ('ImageWidth', ),
111
178
    0x0101: ('ImageLength', ),
112
179
    0x0102: ('BitsPerSample', ),
177
244
    0x8827: ('ISOSpeedRatings', ),
178
245
    0x8828: ('OECF', ),
179
246
    # print as string
180
 
    0x9000: ('ExifVersion', lambda x: ''.join(map(chr, x))),
 
247
    0x9000: ('ExifVersion', make_string),
181
248
    0x9003: ('DateTimeOriginal', ),
182
249
    0x9004: ('DateTimeDigitized', ),
183
250
    0x9101: ('ComponentsConfiguration',
202
269
              3: 'Spot',
203
270
              4: 'MultiSpot'}),
204
271
    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',
 
272
             {0: 'Unknown',
 
273
              1: 'Daylight',
 
274
              2: 'Fluorescent',
 
275
              3: 'Tungsten',
 
276
              10: 'Flash',
 
277
              17: 'Standard Light A',
 
278
              18: 'Standard Light B',
 
279
              19: 'Standard Light C',
 
280
              20: 'D55',
 
281
              21: 'D65',
 
282
              22: 'D75',
216
283
              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',
 
284
    0x9209: ('Flash', {0: 'No',
 
285
                       1: 'Fired',
 
286
                       5: 'Fired (?)', # no return sensed
 
287
                       7: 'Fired (!)', # return sensed
 
288
                       9: 'Fill Fired',
222
289
                       13: 'Fill Fired (?)',
223
290
                       15: 'Fill Fired (!)',
224
291
                       16: 'Off',
228
295
                       31: 'Auto Fired (!)',
229
296
                       32: 'Not Available'}),
230
297
    0x920A: ('FocalLength', ),
 
298
    0x9214: ('SubjectArea', ),
231
299
    0x927C: ('MakerNote', ),
232
300
    # print as string
233
 
    0x9286: ('UserComment', lambda x: ''.join(map(chr, x))),
 
301
    0x9286: ('UserComment', make_string_uc),    # First 8 bytes gives coding system e.g. ASCII vs. JIS vs Unicode
234
302
    0x9290: ('SubSecTime', ),
235
303
    0x9291: ('SubSecTimeOriginal', ),
236
304
    0x9292: ('SubSecTimeDigitized', ),
237
305
    # print as string
238
 
    0xA000: ('FlashPixVersion', lambda x: ''.join(map(chr, x))),
 
306
    0xA000: ('FlashPixVersion', make_string),
239
307
    0xA001: ('ColorSpace', ),
240
308
    0xA002: ('ExifImageWidth', ),
241
309
    0xA003: ('ExifImageLength', ),
252
320
             {3: 'Digital Camera'}),
253
321
    0xA301: ('SceneType',
254
322
             {1: 'Directly Photographed'}),
255
 
    0xA302: ('CVAPattern',),
 
323
    0xA302: ('CVAPattern', ),
 
324
    0xA401: ('CustomRendered', ),
 
325
    0xA402: ('ExposureMode',
 
326
             {0: 'Auto Exposure',
 
327
              1: 'Manual Exposure',
 
328
              2: 'Auto Bracket'}),
 
329
    0xA403: ('WhiteBalance',
 
330
             {0: 'Auto',
 
331
              1: 'Manual'}),
 
332
    0xA404: ('DigitalZoomRatio', ),
 
333
    0xA405: ('FocalLengthIn35mm', ),
 
334
    0xA406: ('SceneCaptureType', ),
 
335
    0xA407: ('GainControl', ),
 
336
    0xA408: ('Contrast', ),
 
337
    0xA409: ('Saturation', ),
 
338
    0xA40A: ('Sharpness', ),
 
339
    0xA40C: ('SubjectDistanceRange', ),
256
340
    }
257
341
 
258
342
# interoperability tags
259
 
INTR_TAGS={
 
343
INTR_TAGS = {
260
344
    0x0001: ('InteroperabilityIndex', ),
261
345
    0x0002: ('InteroperabilityVersion', ),
262
346
    0x1000: ('RelatedImageFileFormat', ),
265
349
    }
266
350
 
267
351
# GPS tags (not used yet, haven't seen camera with GPS)
268
 
GPS_TAGS={
 
352
GPS_TAGS = {
269
353
    0x0000: ('GPSVersionID', ),
270
354
    0x0001: ('GPSLatitudeRef', ),
271
355
    0x0002: ('GPSLatitude', ),
292
376
    0x0017: ('GPSDestBearingRef', ),
293
377
    0x0018: ('GPSDestBearing', ),
294
378
    0x0019: ('GPSDestDistanceRef', ),
295
 
    0x001A: ('GPSDestDistance', )
 
379
    0x001A: ('GPSDestDistance', ),
296
380
    }
297
381
 
 
382
# ignore these tags when quick processing
 
383
# 0x927C is MakerNote Tags
 
384
# 0x9286 is user comment
 
385
IGNORE_TAGS=(0x9286, 0x927C)
 
386
 
 
387
# http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp
 
388
def nikon_ev_bias(seq):
 
389
    # First digit seems to be in steps of 1/6 EV.
 
390
    # Does the third value mean the step size?  It is usually 6,
 
391
    # but it is 12 for the ExposureDifference.
 
392
    #
 
393
    if seq == [252, 1, 6, 0]:
 
394
        return "-2/3 EV"
 
395
    if seq == [253, 1, 6, 0]:
 
396
        return "-1/2 EV"
 
397
    if seq == [254, 1, 6, 0]:
 
398
        return "-1/3 EV"
 
399
    if seq == [0, 1, 6, 0]:
 
400
        return "0 EV"
 
401
    if seq == [2, 1, 6, 0]:
 
402
        return "+1/3 EV"
 
403
    if seq == [3, 1, 6, 0]:
 
404
        return "+1/2 EV"
 
405
    if seq == [4, 1, 6, 0]:
 
406
        return "+2/3 EV"
 
407
    # Handle combinations not in the table.
 
408
    a = seq[0]
 
409
    # Causes headaches for the +/- logic, so special case it.
 
410
    if a == 0:
 
411
        return "0 EV"
 
412
    if a > 127:
 
413
        a = 256 - a
 
414
        ret_str = "-"
 
415
    else:
 
416
        ret_str = "+"
 
417
    b = seq[2]  # Assume third value means the step size
 
418
    whole = a / b
 
419
    a = a % b
 
420
    if whole != 0:
 
421
        ret_str = ret_str + str(whole) + " "
 
422
    if a == 0:
 
423
        ret_str = ret_str + "EV"
 
424
    else:
 
425
        r = Ratio(a, b)
 
426
        ret_str = ret_str + r.__repr__() + " EV"
 
427
    return ret_str
 
428
 
298
429
# Nikon E99x MakerNote Tags
299
 
# http://members.tripod.com/~tawba/990exif.htm
300
430
MAKERNOTE_NIKON_NEWER_TAGS={
 
431
    0x0001: ('MakernoteVersion', make_string),  # Sometimes binary
301
432
    0x0002: ('ISOSetting', ),
302
433
    0x0003: ('ColorMode', ),
303
434
    0x0004: ('Quality', ),
308
439
    0x0009: ('AutoFlashMode', ),
309
440
    0x000B: ('WhiteBalanceBias', ),
310
441
    0x000C: ('WhiteBalanceRBCoeff', ),
 
442
    0x000D: ('ProgramShift', nikon_ev_bias),
 
443
    # Nearly the same as the other EV vals, but step size is 1/12 EV (?)
 
444
    0x000E: ('ExposureDifference', nikon_ev_bias),
311
445
    0x000F: ('ISOSelection', ),
312
 
    0x0012: ('FlashCompensation', ),
 
446
    0x0011: ('NikonPreview', ),
 
447
    0x0012: ('FlashCompensation', nikon_ev_bias),
313
448
    0x0013: ('ISOSpeedRequested', ),
314
449
    0x0016: ('PhotoCornerCoordinates', ),
315
 
    0x0018: ('FlashBracketCompensationApplied', ),
 
450
    # 0x0017: Unknown, but most likely an EV value
 
451
    0x0018: ('FlashBracketCompensationApplied', nikon_ev_bias),
316
452
    0x0019: ('AEBracketCompensationApplied', ),
 
453
    0x001A: ('ImageProcessing', ),
317
454
    0x0080: ('ImageAdjustment', ),
318
455
    0x0081: ('ToneCompensation', ),
319
456
    0x0082: ('AuxiliaryLens', ),
321
458
    0x0084: ('LensMinMaxFocalMaxAperture', ),
322
459
    0x0085: ('ManualFocusDistance', ),
323
460
    0x0086: ('DigitalZoomFactor', ),
 
461
    0x0087: ('FlashMode',
 
462
             {0x00: 'Did Not Fire',
 
463
              0x01: 'Fired, Manual',
 
464
              0x07: 'Fired, External',
 
465
              0x08: 'Fired, Commander Mode ',
 
466
              0x09: 'Fired, TTL Mode'}),
324
467
    0x0088: ('AFFocusPosition',
325
468
             {0x0000: 'Center',
326
469
              0x0100: 'Top',
337
480
              0x40: 'Single frame, white balance bracketing',
338
481
              0x41: 'Continuous, white balance bracketing',
339
482
              0x42: 'Timer, white balance bracketing'}),
 
483
    0x008A: ('AutoBracketRelease', ),
 
484
    0x008B: ('LensFStops', ),
 
485
    0x008C: ('NEFCurve2', ),
340
486
    0x008D: ('ColorMode', ),
341
 
    0x008F: ('SceneMode?', ),
 
487
    0x008F: ('SceneMode', ),
342
488
    0x0090: ('LightingType', ),
 
489
    0x0091: ('ShotInfo', ),     # First 4 bytes are probably a version number in ASCII
343
490
    0x0092: ('HueAdjustment', ),
344
 
    0x0094: ('Saturation',
 
491
    # 0x0093: ('SaturationAdjustment', ),
 
492
    0x0094: ('Saturation',      # Name conflict with 0x00AA !!
345
493
             {-3: 'B&W',
346
494
              -2: '-2',
347
495
              -1: '-1',
348
 
              0:  '0',
349
 
              1:  '1',
350
 
              2:  '2'}),
 
496
              0: '0',
 
497
              1: '1',
 
498
              2: '2'}),
351
499
    0x0095: ('NoiseReduction', ),
 
500
    0x0096: ('NEFCurve2', ),
 
501
    0x0097: ('ColorBalance', ),
 
502
    0x0098: ('LensData', ),     # First 4 bytes are a version number in ASCII
 
503
    0x0099: ('RawImageCenter', ),
 
504
    0x009A: ('SensorPixelSize', ),
 
505
    0x009C: ('Scene Assist', ),
 
506
    0x00A0: ('SerialNumber', ),
 
507
    0x00A2: ('ImageDataSize', ),
 
508
    # A4: In NEF, looks like a 4 byte ASCII version number
 
509
    0x00A5: ('ImageCount', ),
 
510
    0x00A6: ('DeletedImageCount', ),
352
511
    0x00A7: ('TotalShutterReleases', ),
 
512
    # A8: ExposureMode?  JPG: First 4 bytes are probably a version number in ASCII
 
513
    # But in a sample NEF, its 8 zeros, then the string "NORMAL"
353
514
    0x00A9: ('ImageOptimization', ),
354
515
    0x00AA: ('Saturation', ),
355
516
    0x00AB: ('DigitalVariProgram', ),
356
 
    0x0010: ('DataDump', )
 
517
    0x00AC: ('ImageStabilization', ),
 
518
    0x00AD: ('Responsive AF', ),        # 'AFResponse'
 
519
    0x0010: ('DataDump', ),
357
520
    }
358
521
 
359
 
MAKERNOTE_NIKON_OLDER_TAGS={
 
522
MAKERNOTE_NIKON_OLDER_TAGS = {
360
523
    0x0003: ('Quality',
361
524
             {1: 'VGA Basic',
362
525
              2: 'VGA Normal',
385
548
              3: 'Incandescent',
386
549
              4: 'Fluorescent',
387
550
              5: 'Cloudy',
388
 
              6: 'Speed Light'})
 
551
              6: 'Speed Light'}),
389
552
    }
390
553
 
391
554
# decode Olympus SpecialMode tag in MakerNote
401
564
        2: 'Right to left',
402
565
        3: 'Bottom to top',
403
566
        4: 'Top to bottom'}
 
567
    if v[0] not in a or v[2] not in b:
 
568
        return v
404
569
    return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
405
 
        
 
570
 
406
571
MAKERNOTE_OLYMPUS_TAGS={
407
572
    # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
408
573
    # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
414
579
              3: 'SHQ'}),
415
580
    0x0202: ('Macro',
416
581
             {0: 'Normal',
417
 
              1: 'Macro'}),
 
582
             1: 'Macro',
 
583
             2: 'SuperMacro'}),
 
584
    0x0203: ('BWMode',
 
585
             {0: 'Off',
 
586
             1: 'On'}),
418
587
    0x0204: ('DigitalZoom', ),
419
 
    0x0207: ('SoftwareRelease',  ),
420
 
    0x0208: ('PictureInfo',  ),
421
 
    # print as string
422
 
    0x0209: ('CameraID', lambda x: ''.join(map(chr, x))),
423
 
    0x0F00: ('DataDump',  )
424
 
    }
 
588
    0x0205: ('FocalPlaneDiagonal', ),
 
589
    0x0206: ('LensDistortionParams', ),
 
590
    0x0207: ('SoftwareRelease', ),
 
591
    0x0208: ('PictureInfo', ),
 
592
    0x0209: ('CameraID', make_string), # print as string
 
593
    0x0F00: ('DataDump', ),
 
594
    0x0300: ('PreCaptureFrames', ),
 
595
    0x0404: ('SerialNumber', ),
 
596
    0x1000: ('ShutterSpeedValue', ),
 
597
    0x1001: ('ISOValue', ),
 
598
    0x1002: ('ApertureValue', ),
 
599
    0x1003: ('BrightnessValue', ),
 
600
    0x1004: ('FlashMode', ),
 
601
    0x1004: ('FlashMode',
 
602
       {2: 'On',
 
603
        3: 'Off'}),
 
604
    0x1005: ('FlashDevice',
 
605
       {0: 'None',
 
606
        1: 'Internal',
 
607
        4: 'External',
 
608
        5: 'Internal + External'}),
 
609
    0x1006: ('ExposureCompensation', ),
 
610
    0x1007: ('SensorTemperature', ),
 
611
    0x1008: ('LensTemperature', ),
 
612
    0x100b: ('FocusMode',
 
613
       {0: 'Auto',
 
614
        1: 'Manual'}),
 
615
    0x1017: ('RedBalance', ),
 
616
    0x1018: ('BlueBalance', ),
 
617
    0x101a: ('SerialNumber', ),
 
618
    0x1023: ('FlashExposureComp', ),
 
619
    0x1026: ('ExternalFlashBounce',
 
620
       {0: 'No',
 
621
        1: 'Yes'}),
 
622
    0x1027: ('ExternalFlashZoom', ),
 
623
    0x1028: ('ExternalFlashMode', ),
 
624
    0x1029: ('Contrast  int16u',
 
625
       {0: 'High',
 
626
        1: 'Normal',
 
627
        2: 'Low'}),
 
628
    0x102a: ('SharpnessFactor', ),
 
629
    0x102b: ('ColorControl', ),
 
630
    0x102c: ('ValidBits', ),
 
631
    0x102d: ('CoringFilter', ),
 
632
    0x102e: ('OlympusImageWidth', ),
 
633
    0x102f: ('OlympusImageHeight', ),
 
634
    0x1034: ('CompressionRatio', ),
 
635
    0x1035: ('PreviewImageValid',
 
636
       {0: 'No',
 
637
        1: 'Yes'}),
 
638
    0x1036: ('PreviewImageStart', ),
 
639
    0x1037: ('PreviewImageLength', ),
 
640
    0x1039: ('CCDScanMode',
 
641
       {0: 'Interlaced',
 
642
        1: 'Progressive'}),
 
643
    0x103a: ('NoiseReduction',
 
644
       {0: 'Off',
 
645
        1: 'On'}),
 
646
    0x103b: ('InfinityLensStep', ),
 
647
    0x103c: ('NearLensStep', ),
 
648
 
 
649
    # TODO - these need extra definitions
 
650
    # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html
 
651
    0x2010: ('Equipment', ),
 
652
    0x2020: ('CameraSettings', ),
 
653
    0x2030: ('RawDevelopment', ),
 
654
    0x2040: ('ImageProcessing', ),
 
655
    0x2050: ('FocusInfo', ),
 
656
    0x3000: ('RawInfo ', ),
 
657
    }
 
658
 
 
659
# 0x2020 CameraSettings
 
660
MAKERNOTE_OLYMPUS_TAG_0x2020={
 
661
    0x0100: ('PreviewImageValid',
 
662
        {0: 'No',
 
663
         1: 'Yes'}),
 
664
    0x0101: ('PreviewImageStart', ),
 
665
    0x0102: ('PreviewImageLength', ),
 
666
    0x0200: ('ExposureMode', {
 
667
        1: 'Manual',
 
668
        2: 'Program',
 
669
        3: 'Aperture-priority AE',
 
670
        4: 'Shutter speed priority AE',
 
671
        5: 'Program-shift'}),
 
672
    0x0201: ('AELock',
 
673
       {0: 'Off',
 
674
        1: 'On'}),
 
675
    0x0202: ('MeteringMode',
 
676
       {2: 'Center Weighted',
 
677
        3: 'Spot',
 
678
        5: 'ESP',
 
679
        261: 'Pattern+AF',
 
680
        515: 'Spot+Highlight control',
 
681
        1027: 'Spot+Shadow control'}),
 
682
    0x0300: ('MacroMode',
 
683
       {0: 'Off',
 
684
        1: 'On'}),
 
685
    0x0301: ('FocusMode',
 
686
       {0: 'Single AF',
 
687
        1: 'Sequential shooting AF',
 
688
        2: 'Continuous AF',
 
689
        3: 'Multi AF',
 
690
        10: 'MF'}),
 
691
    0x0302: ('FocusProcess',
 
692
       {0: 'AF Not Used',
 
693
        1: 'AF Used'}),
 
694
    0x0303: ('AFSearch',
 
695
       {0: 'Not Ready',
 
696
        1: 'Ready'}),
 
697
    0x0304: ('AFAreas', ),
 
698
    0x0401: ('FlashExposureCompensation', ),
 
699
    0x0500: ('WhiteBalance2',
 
700
       {0: 'Auto',
 
701
        16: '7500K (Fine Weather with Shade)',
 
702
        17: '6000K (Cloudy)',
 
703
        18: '5300K (Fine Weather)',
 
704
        20: '3000K (Tungsten light)',
 
705
        21: '3600K (Tungsten light-like)',
 
706
        33: '6600K (Daylight fluorescent)',
 
707
        34: '4500K (Neutral white fluorescent)',
 
708
        35: '4000K (Cool white fluorescent)',
 
709
        48: '3600K (Tungsten light-like)',
 
710
        256: 'Custom WB 1',
 
711
        257: 'Custom WB 2',
 
712
        258: 'Custom WB 3',
 
713
        259: 'Custom WB 4',
 
714
        512: 'Custom WB 5400K',
 
715
        513: 'Custom WB 2900K',
 
716
        514: 'Custom WB 8000K', }),
 
717
    0x0501: ('WhiteBalanceTemperature', ),
 
718
    0x0502: ('WhiteBalanceBracket', ),
 
719
    0x0503: ('CustomSaturation', ), # (3 numbers: 1. CS Value, 2. Min, 3. Max)
 
720
    0x0504: ('ModifiedSaturation',
 
721
       {0: 'Off',
 
722
        1: 'CM1 (Red Enhance)',
 
723
        2: 'CM2 (Green Enhance)',
 
724
        3: 'CM3 (Blue Enhance)',
 
725
        4: 'CM4 (Skin Tones)'}),
 
726
    0x0505: ('ContrastSetting', ), # (3 numbers: 1. Contrast, 2. Min, 3. Max)
 
727
    0x0506: ('SharpnessSetting', ), # (3 numbers: 1. Sharpness, 2. Min, 3. Max)
 
728
    0x0507: ('ColorSpace',
 
729
       {0: 'sRGB',
 
730
        1: 'Adobe RGB',
 
731
        2: 'Pro Photo RGB'}),
 
732
    0x0509: ('SceneMode',
 
733
       {0: 'Standard',
 
734
        6: 'Auto',
 
735
        7: 'Sport',
 
736
        8: 'Portrait',
 
737
        9: 'Landscape+Portrait',
 
738
        10: 'Landscape',
 
739
        11: 'Night scene',
 
740
        13: 'Panorama',
 
741
        16: 'Landscape+Portrait',
 
742
        17: 'Night+Portrait',
 
743
        19: 'Fireworks',
 
744
        20: 'Sunset',
 
745
        22: 'Macro',
 
746
        25: 'Documents',
 
747
        26: 'Museum',
 
748
        28: 'Beach&Snow',
 
749
        30: 'Candle',
 
750
        35: 'Underwater Wide1',
 
751
        36: 'Underwater Macro',
 
752
        39: 'High Key',
 
753
        40: 'Digital Image Stabilization',
 
754
        44: 'Underwater Wide2',
 
755
        45: 'Low Key',
 
756
        46: 'Children',
 
757
        48: 'Nature Macro'}),
 
758
    0x050a: ('NoiseReduction',
 
759
       {0: 'Off',
 
760
        1: 'Noise Reduction',
 
761
        2: 'Noise Filter',
 
762
        3: 'Noise Reduction + Noise Filter',
 
763
        4: 'Noise Filter (ISO Boost)',
 
764
        5: 'Noise Reduction + Noise Filter (ISO Boost)'}),
 
765
    0x050b: ('DistortionCorrection',
 
766
       {0: 'Off',
 
767
        1: 'On'}),
 
768
    0x050c: ('ShadingCompensation',
 
769
       {0: 'Off',
 
770
        1: 'On'}),
 
771
    0x050d: ('CompressionFactor', ),
 
772
    0x050f: ('Gradation',
 
773
       {'-1 -1 1': 'Low Key',
 
774
        '0 -1 1': 'Normal',
 
775
        '1 -1 1': 'High Key'}),
 
776
    0x0520: ('PictureMode',
 
777
       {1: 'Vivid',
 
778
        2: 'Natural',
 
779
        3: 'Muted',
 
780
        256: 'Monotone',
 
781
        512: 'Sepia'}),
 
782
    0x0521: ('PictureModeSaturation', ),
 
783
    0x0522: ('PictureModeHue?', ),
 
784
    0x0523: ('PictureModeContrast', ),
 
785
    0x0524: ('PictureModeSharpness', ),
 
786
    0x0525: ('PictureModeBWFilter',
 
787
       {0: 'n/a',
 
788
        1: 'Neutral',
 
789
        2: 'Yellow',
 
790
        3: 'Orange',
 
791
        4: 'Red',
 
792
        5: 'Green'}),
 
793
    0x0526: ('PictureModeTone',
 
794
       {0: 'n/a',
 
795
        1: 'Neutral',
 
796
        2: 'Sepia',
 
797
        3: 'Blue',
 
798
        4: 'Purple',
 
799
        5: 'Green'}),
 
800
    0x0600: ('Sequence', ), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits
 
801
    0x0601: ('PanoramaMode', ), # (2 numbers: 1. Mode, 2. Shot number)
 
802
    0x0603: ('ImageQuality2',
 
803
       {1: 'SQ',
 
804
        2: 'HQ',
 
805
        3: 'SHQ',
 
806
        4: 'RAW'}),
 
807
    0x0901: ('ManometerReading', ),
 
808
    }
 
809
 
425
810
 
426
811
MAKERNOTE_CASIO_TAGS={
427
812
    0x0001: ('RecordingMode',
450
835
              15: 'Strong'}),
451
836
    0x0006: ('Object Distance', ),
452
837
    0x0007: ('WhiteBalance',
453
 
             {1:   'Auto',
454
 
              2:   'Tungsten',
455
 
              3:   'Daylight',
456
 
              4:   'Fluorescent',
457
 
              5:   'Shade',
 
838
             {1: 'Auto',
 
839
              2: 'Tungsten',
 
840
              3: 'Daylight',
 
841
              4: 'Fluorescent',
 
842
              5: 'Shade',
458
843
              129: 'Manual'}),
459
844
    0x000B: ('Sharpness',
460
845
             {0: 'Normal',
469
854
              1: 'Low',
470
855
              2: 'High'}),
471
856
    0x0014: ('CCDSpeed',
472
 
             {64:  'Normal',
473
 
              80:  'Normal',
 
857
             {64: 'Normal',
 
858
              80: 'Normal',
474
859
              100: 'High',
475
860
              125: '+1.0',
476
861
              244: '+3.0',
477
 
              250: '+2.0',})
 
862
              250: '+2.0'}),
478
863
    }
479
864
 
480
865
MAKERNOTE_FUJIFILM_TAGS={
481
 
    0x0000: ('NoteVersion', lambda x: ''.join(map(chr, x))),
 
866
    0x0000: ('NoteVersion', make_string),
482
867
    0x1000: ('Quality', ),
483
868
    0x1001: ('Sharpness',
484
869
             {1: 'Soft',
487
872
              4: 'Hard',
488
873
              5: 'Hard'}),
489
874
    0x1002: ('WhiteBalance',
490
 
             {0:    'Auto',
491
 
              256:  'Daylight',
492
 
              512:  'Cloudy',
493
 
              768:  'DaylightColor-Fluorescent',
494
 
              769:  'DaywhiteColor-Fluorescent',
495
 
              770:  'White-Fluorescent',
 
875
             {0: 'Auto',
 
876
              256: 'Daylight',
 
877
              512: 'Cloudy',
 
878
              768: 'DaylightColor-Fluorescent',
 
879
              769: 'DaywhiteColor-Fluorescent',
 
880
              770: 'White-Fluorescent',
496
881
              1024: 'Incandescent',
497
882
              3840: 'Custom'}),
498
883
    0x1003: ('Color',
499
 
             {0:   'Normal',
 
884
             {0: 'Normal',
500
885
              256: 'High',
501
886
              512: 'Low'}),
502
887
    0x1004: ('Tone',
503
 
             {0:   'Normal',
 
888
             {0: 'Normal',
504
889
              256: 'High',
505
890
              512: 'Low'}),
506
891
    0x1010: ('FlashMode',
519
904
             {0: 'Off',
520
905
              1: 'On'}),
521
906
    0x1031: ('PictureMode',
522
 
             {0:   'Auto',
523
 
              1:   'Portrait',
524
 
              2:   'Landscape',
525
 
              4:   'Sports',
526
 
              5:   'Night',
527
 
              6:   'Program AE',
 
907
             {0: 'Auto',
 
908
              1: 'Portrait',
 
909
              2: 'Landscape',
 
910
              4: 'Sports',
 
911
              5: 'Night',
 
912
              6: 'Program AE',
528
913
              256: 'Aperture Priority AE',
529
914
              512: 'Shutter Priority AE',
530
915
              768: 'Manual Exposure'}),
539
924
              1: 'On'}),
540
925
    0x1302: ('AEWarning',
541
926
             {0: 'Off',
542
 
              1: 'On'})
 
927
              1: 'On'}),
543
928
    }
544
929
 
545
 
MAKERNOTE_CANON_TAGS={
 
930
MAKERNOTE_CANON_TAGS = {
546
931
    0x0006: ('ImageType', ),
547
932
    0x0007: ('FirmwareVersion', ),
548
933
    0x0008: ('ImageNumber', ),
549
 
    0x0009: ('OwnerName', )
 
934
    0x0009: ('OwnerName', ),
550
935
    }
551
936
 
552
 
# see http://www.burren.cx/david/canon.html by David Burren
553
937
# this is in element offset, name, optional value dictionary format
554
 
MAKERNOTE_CANON_TAG_0x001={
 
938
MAKERNOTE_CANON_TAG_0x001 = {
555
939
    1: ('Macromode',
556
940
        {1: 'Macro',
557
941
         2: 'Normal'}),
656
1040
          4: 'FP Sync Enabled'}),
657
1041
    32: ('FocusMode',
658
1042
         {0: 'Single',
659
 
          1: 'Continuous'})
 
1043
          1: 'Continuous'}),
660
1044
    }
661
1045
 
662
 
MAKERNOTE_CANON_TAG_0x004={
 
1046
MAKERNOTE_CANON_TAG_0x004 = {
663
1047
    7: ('WhiteBalance',
664
1048
        {0: 'Auto',
665
1049
         1: 'Sunny',
688
1072
         0X0030: '1.50 EV',
689
1073
         0X0034: '1.67 EV',
690
1074
         0X0040: '2 EV'}),
691
 
    19: ('SubjectDistance', )
 
1075
    19: ('SubjectDistance', ),
692
1076
    }
693
1077
 
694
1078
# extract multibyte integer in Motorola format (little endian)
695
1079
def s2n_motorola(str):
696
 
    x=0
 
1080
    x = 0
697
1081
    for c in str:
698
 
        x=(x << 8) | ord(c)
 
1082
        x = (x << 8) | ord(c)
699
1083
    return x
700
1084
 
701
1085
# extract multibyte integer in Intel format (big endian)
702
1086
def s2n_intel(str):
703
 
    x=0
704
 
    y=0L
 
1087
    x = 0
 
1088
    y = 0L
705
1089
    for c in str:
706
 
        x=x | (ord(c) << y)
707
 
        y=y+8
 
1090
        x = x | (ord(c) << y)
 
1091
        y = y + 8
708
1092
    return x
709
1093
 
710
1094
# ratio object that eventually will be able to reduce itself to lowest
711
1095
# common denominator for printing
712
1096
def gcd(a, b):
713
 
   if b == 0:
714
 
      return a
715
 
   else:
716
 
      return gcd(b, a % b)
 
1097
    if b == 0:
 
1098
        return a
 
1099
    else:
 
1100
        return gcd(b, a % b)
717
1101
 
718
1102
class Ratio:
719
1103
    def __init__(self, num, den):
720
 
        self.num=num
721
 
        self.den=den
 
1104
        self.num = num
 
1105
        self.den = den
722
1106
 
723
1107
    def __repr__(self):
724
1108
        self.reduce()
727
1111
        return '%d/%d' % (self.num, self.den)
728
1112
 
729
1113
    def reduce(self):
730
 
        div=gcd(self.num, self.den)
 
1114
        div = gcd(self.num, self.den)
731
1115
        if div > 1:
732
 
            self.num=self.num/div
733
 
            self.den=self.den/div
 
1116
            self.num = self.num / div
 
1117
            self.den = self.den / div
734
1118
 
735
1119
# for ease of dealing with tags
736
1120
class IFD_Tag:
737
1121
    def __init__(self, printable, tag, field_type, values, field_offset,
738
1122
                 field_length):
739
1123
        # printable version of data
740
 
        self.printable=printable
 
1124
        self.printable = printable
741
1125
        # tag ID number
742
 
        self.tag=tag
 
1126
        self.tag = tag
743
1127
        # field type as index into FIELD_TYPES
744
 
        self.field_type=field_type
 
1128
        self.field_type = field_type
745
1129
        # offset of start of field in bytes from beginning of IFD
746
 
        self.field_offset=field_offset
 
1130
        self.field_offset = field_offset
747
1131
        # length of data field in bytes
748
 
        self.field_length=field_length
 
1132
        self.field_length = field_length
749
1133
        # either a string or array of data items
750
 
        self.values=values
751
 
        
 
1134
        self.values = values
 
1135
 
752
1136
    def __str__(self):
753
1137
        return self.printable
754
 
    
 
1138
 
755
1139
    def __repr__(self):
756
1140
        return '(0x%04X) %s=%s @ %d' % (self.tag,
757
1141
                                        FIELD_TYPES[self.field_type][2],
761
1145
# class that handles an EXIF header
762
1146
class EXIF_header:
763
1147
    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={}
770
 
        
 
1148
        self.file = file
 
1149
        self.endian = endian
 
1150
        self.offset = offset
 
1151
        self.fake_exif = fake_exif
 
1152
        self.debug = debug
 
1153
        self.tags = {}
 
1154
 
771
1155
    # convert slice to integer, based on sign and endian flags
772
1156
    # usually this offset is assumed to be relative to the beginning of the
773
1157
    # start of the EXIF information.  For some cameras that use relative tags,
788
1172
 
789
1173
    # convert offset to string
790
1174
    def n2s(self, offset, length):
791
 
        s=''
792
 
        for i in range(length):
 
1175
        s = ''
 
1176
        for dummy in range(length):
793
1177
            if self.endian == 'I':
794
 
                s=s+chr(offset & 0xFF)
 
1178
                s = s + chr(offset & 0xFF)
795
1179
            else:
796
 
                s=chr(offset & 0xFF)+s
797
 
            offset=offset >> 8
 
1180
                s = chr(offset & 0xFF) + s
 
1181
            offset = offset >> 8
798
1182
        return s
799
 
    
 
1183
 
800
1184
    # return first IFD
801
1185
    def first_IFD(self):
802
1186
        return self.s2n(4, 4)
816
1200
        return a
817
1201
 
818
1202
    # return list of entries in this IFD
819
 
    def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0):
 
1203
    def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0, name='UNDEF'):
820
1204
        entries=self.s2n(ifd, 2)
821
1205
        for i in range(entries):
822
1206
            # 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]))
 
1207
            entry = ifd + 2 + 12 * i
 
1208
            tag = self.s2n(entry, 2)
 
1209
 
 
1210
            # ignore certain tags for faster processing
 
1211
            if not (tag in IGNORE_TAGS and not detailed):
 
1212
                # get tag name.  We do it early to make debugging easier
 
1213
                tag_entry = dict.get(tag)
 
1214
                if tag_entry:
 
1215
                    tag_name = tag_entry[0]
 
1216
                else:
 
1217
                    tag_name = 'Tag 0x%04X' % tag
 
1218
 
 
1219
                field_type = self.s2n(entry + 2, 2)
 
1220
                if not 0 < field_type < len(FIELD_TYPES):
 
1221
                    # unknown field type
 
1222
                    raise ValueError('unknown type %d in tag 0x%04X' % (field_type, tag))
 
1223
                typelen = FIELD_TYPES[field_type][0]
 
1224
                count = self.s2n(entry + 4, 4)
 
1225
                offset = entry + 8
 
1226
                if count * typelen > 4:
 
1227
                    # offset is not the value; it's a pointer to the value
 
1228
                    # if relative we set things up so s2n will seek to the right
 
1229
                    # place when it adds self.offset.  Note that this 'relative'
 
1230
                    # is for the Nikon type 3 makernote.  Other cameras may use
 
1231
                    # other relative offsets, which would have to be computed here
 
1232
                    # slightly differently.
 
1233
                    if relative:
 
1234
                        tmp_offset = self.s2n(offset, 4)
 
1235
                        offset = tmp_offset + ifd - self.offset + 4
 
1236
                        if self.fake_exif:
 
1237
                            offset = offset + 18
 
1238
                    else:
 
1239
                        offset = self.s2n(offset, 4)
 
1240
                field_offset = offset
 
1241
                if field_type == 2:
 
1242
                    # special case: null-terminated ASCII string
 
1243
                    if count != 0:
 
1244
                        self.file.seek(self.offset + offset)
 
1245
                        values = self.file.read(count)
 
1246
                        values = values.strip().replace('\x00', '')
 
1247
                    else:
 
1248
                        values = ''
 
1249
                else:
 
1250
                    values = []
 
1251
                    signed = (field_type in [6, 8, 9, 10])
 
1252
                    for dummy in range(count):
 
1253
                        if field_type in (5, 10):
 
1254
                            # a ratio
 
1255
                            value = Ratio(self.s2n(offset, 4, signed),
 
1256
                                          self.s2n(offset + 4, 4, signed))
 
1257
                        else:
 
1258
                            value = self.s2n(offset, typelen, signed)
 
1259
                        values.append(value)
 
1260
                        offset = offset + typelen
 
1261
                # now "values" is either a string or an array
 
1262
                if count == 1 and field_type != 2:
 
1263
                    printable=str(values[0])
 
1264
                else:
 
1265
                    printable=str(values)
 
1266
                # compute printable version of values
 
1267
                if tag_entry:
 
1268
                    if len(tag_entry) != 1:
 
1269
                        # optional 2nd tag element is present
 
1270
                        if callable(tag_entry[1]):
 
1271
                            # call mapping function
 
1272
                            printable = tag_entry[1](values)
 
1273
                        else:
 
1274
                            printable = ''
 
1275
                            for i in values:
 
1276
                                # use lookup table for this tag
 
1277
                                printable += tag_entry[1].get(i, repr(i))
 
1278
 
 
1279
                self.tags[ifd_name + ' ' + tag_name] = IFD_Tag(printable, tag,
 
1280
                                                          field_type,
 
1281
                                                          values, field_offset,
 
1282
                                                          count * typelen)
 
1283
                if self.debug:
 
1284
                    print ' debug:   %s: %s' % (tag_name,
 
1285
                                                repr(self.tags[ifd_name + ' ' + tag_name]))
 
1286
 
 
1287
            if tag_name == name:
 
1288
                break
898
1289
 
899
1290
    # extract uncompressed TIFF thumbnail (like pulling teeth)
900
1291
    # we take advantage of the pre-existing layout in the thumbnail IFD as
901
1292
    # much as possible
902
1293
    def extract_TIFF_thumbnail(self, thumb_ifd):
903
 
        entries=self.s2n(thumb_ifd, 2)
 
1294
        entries = self.s2n(thumb_ifd, 2)
904
1295
        # this is header plus offset to IFD ...
905
1296
        if self.endian == 'M':
906
 
            tiff='MM\x00*\x00\x00\x00\x08'
 
1297
            tiff = 'MM\x00*\x00\x00\x00\x08'
907
1298
        else:
908
 
            tiff='II*\x00\x08\x00\x00\x00'
 
1299
            tiff = 'II*\x00\x08\x00\x00\x00'
909
1300
        # ... plus thumbnail IFD data plus a null "next IFD" pointer
910
1301
        self.file.seek(self.offset+thumb_ifd)
911
 
        tiff+=self.file.read(entries*12+2)+'\x00\x00\x00\x00'
912
 
        
 
1302
        tiff += self.file.read(entries*12+2)+'\x00\x00\x00\x00'
 
1303
 
913
1304
        # fix up large value offset pointers into data area
914
1305
        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)
 
1306
            entry = thumb_ifd + 2 + 12 * i
 
1307
            tag = self.s2n(entry, 2)
 
1308
            field_type = self.s2n(entry+2, 2)
 
1309
            typelen = FIELD_TYPES[field_type][0]
 
1310
            count = self.s2n(entry+4, 4)
 
1311
            oldoff = self.s2n(entry+8, 4)
921
1312
            # start of the 4-byte pointer area in entry
922
 
            ptr=i*12+18
 
1313
            ptr = i * 12 + 18
923
1314
            # remember strip offsets location
924
1315
            if tag == 0x0111:
925
 
                strip_off=ptr
926
 
                strip_len=count*typelen
 
1316
                strip_off = ptr
 
1317
                strip_len = count * typelen
927
1318
            # is it in the data area?
928
 
            if count*typelen > 4:
 
1319
            if count * typelen > 4:
929
1320
                # update offset pointer (nasty "strings are immutable" crap)
930
1321
                # 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:]
 
1322
                newoff = len(tiff)
 
1323
                tiff = tiff[:ptr] + self.n2s(newoff, 4) + tiff[ptr+4:]
933
1324
                # remember strip offsets location
934
1325
                if tag == 0x0111:
935
 
                    strip_off=newoff
936
 
                    strip_len=4
 
1326
                    strip_off = newoff
 
1327
                    strip_len = 4
937
1328
                # get original data and store it
938
 
                self.file.seek(self.offset+oldoff)
939
 
                tiff+=self.file.read(count*typelen)
940
 
                
 
1329
                self.file.seek(self.offset + oldoff)
 
1330
                tiff += self.file.read(count * typelen)
 
1331
 
941
1332
        # add pixel strips and update strip offset info
942
 
        old_offsets=self.tags['Thumbnail StripOffsets'].values
943
 
        old_counts=self.tags['Thumbnail StripByteCounts'].values
 
1333
        old_offsets = self.tags['Thumbnail StripOffsets'].values
 
1334
        old_counts = self.tags['Thumbnail StripByteCounts'].values
944
1335
        for i in range(len(old_offsets)):
945
1336
            # 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
 
1337
            offset = self.n2s(len(tiff), strip_len)
 
1338
            tiff = tiff[:strip_off] + offset + tiff[strip_off + strip_len:]
 
1339
            strip_off += strip_len
949
1340
            # add pixel strip to end
950
 
            self.file.seek(self.offset+old_offsets[i])
951
 
            tiff+=self.file.read(old_counts[i])
952
 
            
953
 
        self.tags['TIFFThumbnail']=tiff
954
 
        
 
1341
            self.file.seek(self.offset + old_offsets[i])
 
1342
            tiff += self.file.read(old_counts[i])
 
1343
 
 
1344
        self.tags['TIFFThumbnail'] = tiff
 
1345
 
955
1346
    # decode all the camera-specific MakerNote formats
956
1347
 
957
1348
    # Note is the data that comprises this MakerNote.  The MakerNote will
972
1363
    # the offsets should be from the header at the start of all the EXIF info,
973
1364
    # or from the header at the start of the makernote.)
974
1365
    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
 
1366
        note = self.tags['EXIF MakerNote']
 
1367
        make = self.tags['Image Make'].printable
 
1368
        # model = self.tags['Image Model'].printable # unused
978
1369
 
979
1370
        # Nikon
980
1371
        # The maker note usually starts with the word Nikon, followed by the
982
1373
        # not at the start of the makernote, it's probably type 2, since some
983
1374
        # cameras work that way.
984
1375
        if make in ('NIKON', 'NIKON CORPORATION'):
985
 
            if note.values[0:7] == [78, 105, 107, 111, 110, 00, 01]:
 
1376
            if note.values[0:7] == [78, 105, 107, 111, 110, 0, 1]:
986
1377
                if self.debug:
987
1378
                    print "Looks like a type 1 Nikon MakerNote."
988
1379
                self.dump_IFD(note.field_offset+8, 'MakerNote',
989
1380
                              dict=MAKERNOTE_NIKON_OLDER_TAGS)
990
 
            elif note.values[0:7] == [78, 105, 107, 111, 110, 00, 02]:
 
1381
            elif note.values[0:7] == [78, 105, 107, 111, 110, 0, 2]:
991
1382
                if self.debug:
992
1383
                    print "Looks like a labeled type 2 Nikon MakerNote"
993
1384
                if note.values[12:14] != [0, 42] and note.values[12:14] != [42L, 0L]:
994
 
                    raise ValueError, "Missing marker tag '42' in MakerNote."
 
1385
                    raise ValueError("Missing marker tag '42' in MakerNote.")
995
1386
                # skip the Makernote label and the TIFF header
996
1387
                self.dump_IFD(note.field_offset+10+8, 'MakerNote',
997
1388
                              dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1)
1004
1395
            return
1005
1396
 
1006
1397
        # Olympus
1007
 
        if make[:7] == 'OLYMPUS':
 
1398
        if make.startswith('OLYMPUS'):
1008
1399
            self.dump_IFD(note.field_offset+8, 'MakerNote',
1009
1400
                          dict=MAKERNOTE_OLYMPUS_TAGS)
1010
 
            return
 
1401
            # TODO
 
1402
            #for i in (('MakerNote Tag 0x2020', MAKERNOTE_OLYMPUS_TAG_0x2020),):
 
1403
            #    self.decode_olympus_tag(self.tags[i[0]].values, i[1])
 
1404
            #return
1011
1405
 
1012
1406
        # Casio
1013
1407
        if make == 'Casio':
1014
1408
            self.dump_IFD(note.field_offset, 'MakerNote',
1015
1409
                          dict=MAKERNOTE_CASIO_TAGS)
1016
1410
            return
1017
 
        
 
1411
 
1018
1412
        # Fujifilm
1019
1413
        if make == 'FUJIFILM':
1020
1414
            # bug: everything else is "Motorola" endian, but the MakerNote
1021
1415
            # is "Intel" endian
1022
 
            endian=self.endian
1023
 
            self.endian='I'
 
1416
            endian = self.endian
 
1417
            self.endian = 'I'
1024
1418
            # bug: IFD offsets are from beginning of MakerNote, not
1025
1419
            # beginning of file header
1026
 
            offset=self.offset
1027
 
            self.offset+=note.field_offset
 
1420
            offset = self.offset
 
1421
            self.offset += note.field_offset
1028
1422
            # process note with bogus values (note is actually at offset 12)
1029
1423
            self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS)
1030
1424
            # reset to correct values
1031
 
            self.endian=endian
1032
 
            self.offset=offset
 
1425
            self.endian = endian
 
1426
            self.offset = offset
1033
1427
            return
1034
 
        
 
1428
 
1035
1429
        # Canon
1036
1430
        if make == 'Canon':
1037
1431
            self.dump_IFD(note.field_offset, 'MakerNote',
1041
1435
                self.canon_decode_tag(self.tags[i[0]].values, i[1])
1042
1436
            return
1043
1437
 
 
1438
    # decode Olympus MakerNote tag based on offset within tag
 
1439
    def olympus_decode_tag(self, value, dict):
 
1440
        pass
 
1441
 
1044
1442
    # decode Canon MakerNote tag based on offset within tag
1045
1443
    # see http://www.burren.cx/david/canon.html by David Burren
1046
1444
    def canon_decode_tag(self, value, dict):
1061
1459
# process an image file (expects an open file object)
1062
1460
# this is the function that has to deal with all the arbitrary nasty bits
1063
1461
# of the EXIF standard
1064
 
def process_file(file, debug=0):
 
1462
def process_file(f, name='UNDEF', details=True, debug=0):
 
1463
    # yah it's cheesy...
 
1464
    global detailed
 
1465
    detailed = details
 
1466
 
 
1467
    # by default do not fake an EXIF beginning
 
1468
    fake_exif = 0
 
1469
 
1065
1470
    # determine whether it's a JPEG or TIFF
1066
 
    data=file.read(12)
 
1471
    data = f.read(12)
1067
1472
    if data[0:4] in ['II*\x00', 'MM\x00*']:
1068
1473
        # it's a TIFF file
1069
 
        file.seek(0)
1070
 
        endian=file.read(1)
1071
 
        file.read(1)
1072
 
        offset=0
 
1474
        f.seek(0)
 
1475
        endian = f.read(1)
 
1476
        f.read(1)
 
1477
        offset = 0
1073
1478
    elif data[0:2] == '\xFF\xD8':
1074
1479
        # 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)
 
1480
        while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'):
 
1481
            length = ord(data[4])*256+ord(data[5])
 
1482
            f.read(length-8)
1080
1483
            # fake an EXIF beginning of file
1081
 
            data='\xFF\x00'+file.read(10)
1082
 
            fake_exif=1
 
1484
            data = '\xFF\x00'+f.read(10)
 
1485
            fake_exif = 1
1083
1486
        if data[2] == '\xFF' and data[6:10] == 'Exif':
1084
1487
            # detected EXIF header
1085
 
            offset=file.tell()
1086
 
            endian=file.read(1)
 
1488
            offset = f.tell()
 
1489
            endian = f.read(1)
1087
1490
        else:
1088
1491
            # no EXIF information
1089
1492
            return {}
1094
1497
    # deal with the EXIF info we found
1095
1498
    if debug:
1096
1499
        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
 
1500
    hdr = EXIF_header(f, endian, offset, fake_exif, debug)
 
1501
    ifd_list = hdr.list_IFDs()
 
1502
    ctr = 0
1100
1503
    for i in ifd_list:
1101
1504
        if ctr == 0:
1102
 
            IFD_name='Image'
 
1505
            IFD_name = 'Image'
1103
1506
        elif ctr == 1:
1104
 
            IFD_name='Thumbnail'
1105
 
            thumb_ifd=i
 
1507
            IFD_name = 'Thumbnail'
 
1508
            thumb_ifd = i
1106
1509
        else:
1107
 
            IFD_name='IFD %d' % ctr
 
1510
            IFD_name = 'IFD %d' % ctr
1108
1511
        if debug:
1109
1512
            print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i)
1110
 
        hdr.dump_IFD(i, IFD_name)
 
1513
        hdr.dump_IFD(i, IFD_name, name=name)
1111
1514
        # EXIF IFD
1112
 
        exif_off=hdr.tags.get(IFD_name+' ExifOffset')
 
1515
        exif_off = hdr.tags.get(IFD_name+' ExifOffset')
1113
1516
        if exif_off:
1114
1517
            if debug:
1115
1518
                print ' EXIF SubIFD at offset %d:' % exif_off.values[0]
1116
 
            hdr.dump_IFD(exif_off.values[0], 'EXIF')
 
1519
            hdr.dump_IFD(exif_off.values[0], 'EXIF', name=name)
1117
1520
            # Interoperability IFD contained in EXIF IFD
1118
 
            intr_off=hdr.tags.get('EXIF SubIFD InteroperabilityOffset')
 
1521
            intr_off = hdr.tags.get('EXIF SubIFD InteroperabilityOffset')
1119
1522
            if intr_off:
1120
1523
                if debug:
1121
1524
                    print ' EXIF Interoperability SubSubIFD at offset %d:' \
1122
1525
                          % intr_off.values[0]
1123
1526
                hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability',
1124
 
                             dict=INTR_TAGS)
 
1527
                             dict=INTR_TAGS, name=name)
1125
1528
        # GPS IFD
1126
 
        gps_off=hdr.tags.get(IFD_name+' GPSInfo')
 
1529
        gps_off = hdr.tags.get(IFD_name+' GPSInfo')
1127
1530
        if gps_off:
1128
1531
            if debug:
1129
1532
                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
 
1533
            hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS, name=name)
 
1534
        ctr += 1
1132
1535
 
1133
1536
    # extract uncompressed TIFF thumbnail
1134
 
    thumb=hdr.tags.get('Thumbnail Compression')
 
1537
    thumb = hdr.tags.get('Thumbnail Compression')
1135
1538
    if thumb and thumb.printable == 'Uncompressed TIFF':
1136
1539
        hdr.extract_TIFF_thumbnail(thumb_ifd)
1137
 
        
 
1540
 
1138
1541
    # JPEG thumbnail (thankfully the JPEG data is stored as a unit)
1139
 
    thumb_off=hdr.tags.get('Thumbnail JPEGInterchangeFormat')
 
1542
    thumb_off = hdr.tags.get('Thumbnail JPEGInterchangeFormat')
1140
1543
    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
 
        
 
1544
        f.seek(offset+thumb_off.values[0])
 
1545
        size = hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0]
 
1546
        hdr.tags['JPEGThumbnail'] = f.read(size)
 
1547
 
1145
1548
    # deal with MakerNote contained in EXIF IFD
1146
 
    if hdr.tags.has_key('EXIF MakerNote'):
 
1549
    if 'EXIF MakerNote' in hdr.tags and detailed:
1147
1550
        hdr.decode_maker_note()
1148
1551
 
1149
1552
    # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
1150
1553
    # since it's not allowed in a uncompressed TIFF IFD
1151
 
    if not hdr.tags.has_key('JPEGThumbnail'):
 
1554
    if 'JPEGThumbnail' not in hdr.tags:
1152
1555
        thumb_off=hdr.tags.get('MakerNote JPEGThumbnail')
1153
1556
        if thumb_off:
1154
 
            file.seek(offset+thumb_off.values[0])
 
1557
            f.seek(offset+thumb_off.values[0])
1155
1558
            hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length)
1156
 
            
 
1559
 
1157
1560
    return hdr.tags
1158
1561
 
 
1562
 
1159
1563
# library test/debug function (dump given files)
1160
1564
if __name__ == '__main__':
1161
1565
    import sys
1162
 
    
 
1566
 
1163
1567
    if len(sys.argv) < 2:
1164
 
        print 'Usage: %s files...\n' % sys.argv[0]
 
1568
        print "Usage: %s [options] file1 [file2 ...]\n\nOptions:\n-q --quick : do not process MakerNotes for faster processing\n" % sys.argv[0]
1165
1569
        sys.exit(0)
1166
 
        
1167
 
    for filename in sys.argv[1:]:
 
1570
 
 
1571
    if sys.argv[1] == "-q" or sys.argv[1] == "--quick":
 
1572
        fileNamesStart = 2
 
1573
        detailed = False
 
1574
    else:
 
1575
        fileNamesStart = 1
 
1576
        detailed = True
 
1577
 
 
1578
    for filename in sys.argv[fileNamesStart:]:
1168
1579
        try:
1169
1580
            file=open(filename, 'rb')
1170
1581
        except:
1171
1582
            print filename, 'unreadable'
1172
1583
            print
1173
1584
            continue
1174
 
        print filename+':'
1175
 
        # data=process_file(file, 1) # with debug info
1176
 
        data=process_file(file)
 
1585
        print filename + ':'
 
1586
        # data=process_file(file, debug=1) # with debug info and all tags
 
1587
        data=process_file(file, details=detailed)
1177
1588
        if not data:
1178
1589
            print 'No EXIF information found'
1179
1590
            continue
1188
1599
                      (i, FIELD_TYPES[data[i].field_type][2], data[i].printable)
1189
1600
            except:
1190
1601
                print 'error', i, '"', data[i], '"'
1191
 
        if data.has_key('JPEGThumbnail'):
 
1602
        if 'JPEGThumbnail' in data:
1192
1603
            print 'File has JPEG thumbnail'
1193
1604
        print
 
1605