Attachment 'EXIF.py'

Download

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

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2006-07-27 13:28:42, 41.0 KB) [[attachment:EXIF.py]]
  • [get | view] (2006-07-27 18:28:39, 2.8 KB) [[attachment:EXIF_patch.patch]]
  • [get | view] (2006-07-27 18:29:41, 50.5 KB) [[attachment:Gallery.patch]]
  • [get | view] (2006-07-27 19:15:39, 25.6 KB) [[attachment:Gallery2.patch]]
  • [get | view] (2006-07-27 13:33:03, 3175.6 KB) [[attachment:Gallery2_additional_files_win32.zip]]
  • [get | view] (2006-07-27 18:25:50, 4.5 KB) [[attachment:if_modified_since.patch]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.