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

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