annotate mysite/photologue/utils/EXIF.py @ 88:7245c769e31e django1.3

Close this branch. I'm not sure if I merged it correctly to the default branch, because the graphlog doesn't look right. But the changes were made to default somehow. So closing this off to prevent future confusion.
author Brian Neal <bgneal@gmail.com>
date Sat, 13 Apr 2013 18:08:19 -0500
parents 0dcfcdf50c62
children
rev   line source
bgneal@1 1 #!/usr/bin/env python
bgneal@1 2 # -*- coding: utf-8 -*-
bgneal@1 3 #
bgneal@1 4 # Library to extract EXIF information from digital camera image files
bgneal@1 5 # http://sourceforge.net/projects/exif-py/
bgneal@1 6 #
bgneal@1 7 # VERSION 1.0.7
bgneal@1 8 #
bgneal@1 9 # To use this library call with:
bgneal@1 10 # f = open(path_name, 'rb')
bgneal@1 11 # tags = EXIF.process_file(f)
bgneal@1 12 #
bgneal@1 13 # To ignore makerNote tags, pass the -q or --quick
bgneal@1 14 # command line arguments, or as
bgneal@1 15 # f = open(path_name, 'rb')
bgneal@1 16 # tags = EXIF.process_file(f, details=False)
bgneal@1 17 #
bgneal@1 18 # To stop processing after a certain tag is retrieved,
bgneal@1 19 # pass the -t TAG or --stop-tag TAG argument, or as
bgneal@1 20 # f = open(path_name, 'rb')
bgneal@1 21 # tags = EXIF.process_file(f, stop_tag='TAG')
bgneal@1 22 #
bgneal@1 23 # where TAG is a valid tag name, ex 'DateTimeOriginal'
bgneal@1 24 #
bgneal@1 25 # These are useful when you are retrieving a large list of images
bgneal@1 26 #
bgneal@1 27 # Returned tags will be a dictionary mapping names of EXIF tags to their
bgneal@1 28 # values in the file named by path_name. You can process the tags
bgneal@1 29 # as you wish. In particular, you can iterate through all the tags with:
bgneal@1 30 # for tag in tags.keys():
bgneal@1 31 # if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename',
bgneal@1 32 # 'EXIF MakerNote'):
bgneal@1 33 # print "Key: %s, value %s" % (tag, tags[tag])
bgneal@1 34 # (This code uses the if statement to avoid printing out a few of the
bgneal@1 35 # tags that tend to be long or boring.)
bgneal@1 36 #
bgneal@1 37 # The tags dictionary will include keys for all of the usual EXIF
bgneal@1 38 # tags, and will also include keys for Makernotes used by some
bgneal@1 39 # cameras, for which we have a good specification.
bgneal@1 40 #
bgneal@1 41 # Note that the dictionary keys are the IFD name followed by the
bgneal@1 42 # tag name. For example:
bgneal@1 43 # 'EXIF DateTimeOriginal', 'Image Orientation', 'MakerNote FocusMode'
bgneal@1 44 #
bgneal@1 45 # Copyright (c) 2002-2007 Gene Cash All rights reserved
bgneal@1 46 # Copyright (c) 2007 Ianaré Sévi All rights reserved
bgneal@1 47 #
bgneal@1 48 # Redistribution and use in source and binary forms, with or without
bgneal@1 49 # modification, are permitted provided that the following conditions
bgneal@1 50 # are met:
bgneal@1 51 #
bgneal@1 52 # 1. Redistributions of source code must retain the above copyright
bgneal@1 53 # notice, this list of conditions and the following disclaimer.
bgneal@1 54 #
bgneal@1 55 # 2. Redistributions in binary form must reproduce the above
bgneal@1 56 # copyright notice, this list of conditions and the following
bgneal@1 57 # disclaimer in the documentation and/or other materials provided
bgneal@1 58 # with the distribution.
bgneal@1 59 #
bgneal@1 60 # 3. Neither the name of the authors nor the names of its contributors
bgneal@1 61 # may be used to endorse or promote products derived from this
bgneal@1 62 # software without specific prior written permission.
bgneal@1 63 #
bgneal@1 64 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
bgneal@1 65 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
bgneal@1 66 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
bgneal@1 67 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
bgneal@1 68 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
bgneal@1 69 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
bgneal@1 70 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
bgneal@1 71 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
bgneal@1 72 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
bgneal@1 73 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
bgneal@1 74 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
bgneal@1 75 #
bgneal@1 76 #
bgneal@1 77 # ----- See 'changes.txt' file for all contributors and changes ----- #
bgneal@1 78 #
bgneal@1 79
bgneal@1 80
bgneal@1 81 # Don't throw an exception when given an out of range character.
bgneal@1 82 def make_string(seq):
bgneal@1 83 str = ""
bgneal@1 84 for c in seq:
bgneal@1 85 # Screen out non-printing characters
bgneal@1 86 if 32 <= c and c < 256:
bgneal@1 87 str += chr(c)
bgneal@1 88 # If no printing chars
bgneal@1 89 if not str:
bgneal@1 90 return seq
bgneal@1 91 return str
bgneal@1 92
bgneal@1 93 # Special version to deal with the code in the first 8 bytes of a user comment.
bgneal@1 94 def make_string_uc(seq):
bgneal@1 95 code = seq[0:8]
bgneal@1 96 seq = seq[8:]
bgneal@1 97 # Of course, this is only correct if ASCII, and the standard explicitly
bgneal@1 98 # allows JIS and Unicode.
bgneal@1 99 return make_string(seq)
bgneal@1 100
bgneal@1 101 # field type descriptions as (length, abbreviation, full name) tuples
bgneal@1 102 FIELD_TYPES = (
bgneal@1 103 (0, 'X', 'Proprietary'), # no such type
bgneal@1 104 (1, 'B', 'Byte'),
bgneal@1 105 (1, 'A', 'ASCII'),
bgneal@1 106 (2, 'S', 'Short'),
bgneal@1 107 (4, 'L', 'Long'),
bgneal@1 108 (8, 'R', 'Ratio'),
bgneal@1 109 (1, 'SB', 'Signed Byte'),
bgneal@1 110 (1, 'U', 'Undefined'),
bgneal@1 111 (2, 'SS', 'Signed Short'),
bgneal@1 112 (4, 'SL', 'Signed Long'),
bgneal@1 113 (8, 'SR', 'Signed Ratio'),
bgneal@1 114 )
bgneal@1 115
bgneal@1 116 # dictionary of main EXIF tag names
bgneal@1 117 # first element of tuple is tag name, optional second element is
bgneal@1 118 # another dictionary giving names to values
bgneal@1 119 EXIF_TAGS = {
bgneal@1 120 0x0100: ('ImageWidth', ),
bgneal@1 121 0x0101: ('ImageLength', ),
bgneal@1 122 0x0102: ('BitsPerSample', ),
bgneal@1 123 0x0103: ('Compression',
bgneal@1 124 {1: 'Uncompressed TIFF',
bgneal@1 125 6: 'JPEG Compressed'}),
bgneal@1 126 0x0106: ('PhotometricInterpretation', ),
bgneal@1 127 0x010A: ('FillOrder', ),
bgneal@1 128 0x010D: ('DocumentName', ),
bgneal@1 129 0x010E: ('ImageDescription', ),
bgneal@1 130 0x010F: ('Make', ),
bgneal@1 131 0x0110: ('Model', ),
bgneal@1 132 0x0111: ('StripOffsets', ),
bgneal@1 133 0x0112: ('Orientation',
bgneal@1 134 {1: 'Horizontal (normal)',
bgneal@1 135 2: 'Mirrored horizontal',
bgneal@1 136 3: 'Rotated 180',
bgneal@1 137 4: 'Mirrored vertical',
bgneal@1 138 5: 'Mirrored horizontal then rotated 90 CCW',
bgneal@1 139 6: 'Rotated 90 CW',
bgneal@1 140 7: 'Mirrored horizontal then rotated 90 CW',
bgneal@1 141 8: 'Rotated 90 CCW'}),
bgneal@1 142 0x0115: ('SamplesPerPixel', ),
bgneal@1 143 0x0116: ('RowsPerStrip', ),
bgneal@1 144 0x0117: ('StripByteCounts', ),
bgneal@1 145 0x011A: ('XResolution', ),
bgneal@1 146 0x011B: ('YResolution', ),
bgneal@1 147 0x011C: ('PlanarConfiguration', ),
bgneal@1 148 0x0128: ('ResolutionUnit',
bgneal@1 149 {1: 'Not Absolute',
bgneal@1 150 2: 'Pixels/Inch',
bgneal@1 151 3: 'Pixels/Centimeter'}),
bgneal@1 152 0x012D: ('TransferFunction', ),
bgneal@1 153 0x0131: ('Software', ),
bgneal@1 154 0x0132: ('DateTime', ),
bgneal@1 155 0x013B: ('Artist', ),
bgneal@1 156 0x013E: ('WhitePoint', ),
bgneal@1 157 0x013F: ('PrimaryChromaticities', ),
bgneal@1 158 0x0156: ('TransferRange', ),
bgneal@1 159 0x0200: ('JPEGProc', ),
bgneal@1 160 0x0201: ('JPEGInterchangeFormat', ),
bgneal@1 161 0x0202: ('JPEGInterchangeFormatLength', ),
bgneal@1 162 0x0211: ('YCbCrCoefficients', ),
bgneal@1 163 0x0212: ('YCbCrSubSampling', ),
bgneal@1 164 0x0213: ('YCbCrPositioning', ),
bgneal@1 165 0x0214: ('ReferenceBlackWhite', ),
bgneal@1 166 0x828D: ('CFARepeatPatternDim', ),
bgneal@1 167 0x828E: ('CFAPattern', ),
bgneal@1 168 0x828F: ('BatteryLevel', ),
bgneal@1 169 0x8298: ('Copyright', ),
bgneal@1 170 0x829A: ('ExposureTime', ),
bgneal@1 171 0x829D: ('FNumber', ),
bgneal@1 172 0x83BB: ('IPTC/NAA', ),
bgneal@1 173 0x8769: ('ExifOffset', ),
bgneal@1 174 0x8773: ('InterColorProfile', ),
bgneal@1 175 0x8822: ('ExposureProgram',
bgneal@1 176 {0: 'Unidentified',
bgneal@1 177 1: 'Manual',
bgneal@1 178 2: 'Program Normal',
bgneal@1 179 3: 'Aperture Priority',
bgneal@1 180 4: 'Shutter Priority',
bgneal@1 181 5: 'Program Creative',
bgneal@1 182 6: 'Program Action',
bgneal@1 183 7: 'Portrait Mode',
bgneal@1 184 8: 'Landscape Mode'}),
bgneal@1 185 0x8824: ('SpectralSensitivity', ),
bgneal@1 186 0x8825: ('GPSInfo', ),
bgneal@1 187 0x8827: ('ISOSpeedRatings', ),
bgneal@1 188 0x8828: ('OECF', ),
bgneal@1 189 # print as string
bgneal@1 190 0x9000: ('ExifVersion', make_string),
bgneal@1 191 0x9003: ('DateTimeOriginal', ),
bgneal@1 192 0x9004: ('DateTimeDigitized', ),
bgneal@1 193 0x9101: ('ComponentsConfiguration',
bgneal@1 194 {0: '',
bgneal@1 195 1: 'Y',
bgneal@1 196 2: 'Cb',
bgneal@1 197 3: 'Cr',
bgneal@1 198 4: 'Red',
bgneal@1 199 5: 'Green',
bgneal@1 200 6: 'Blue'}),
bgneal@1 201 0x9102: ('CompressedBitsPerPixel', ),
bgneal@1 202 0x9201: ('ShutterSpeedValue', ),
bgneal@1 203 0x9202: ('ApertureValue', ),
bgneal@1 204 0x9203: ('BrightnessValue', ),
bgneal@1 205 0x9204: ('ExposureBiasValue', ),
bgneal@1 206 0x9205: ('MaxApertureValue', ),
bgneal@1 207 0x9206: ('SubjectDistance', ),
bgneal@1 208 0x9207: ('MeteringMode',
bgneal@1 209 {0: 'Unidentified',
bgneal@1 210 1: 'Average',
bgneal@1 211 2: 'CenterWeightedAverage',
bgneal@1 212 3: 'Spot',
bgneal@1 213 4: 'MultiSpot'}),
bgneal@1 214 0x9208: ('LightSource',
bgneal@1 215 {0: 'Unknown',
bgneal@1 216 1: 'Daylight',
bgneal@1 217 2: 'Fluorescent',
bgneal@1 218 3: 'Tungsten',
bgneal@1 219 10: 'Flash',
bgneal@1 220 17: 'Standard Light A',
bgneal@1 221 18: 'Standard Light B',
bgneal@1 222 19: 'Standard Light C',
bgneal@1 223 20: 'D55',
bgneal@1 224 21: 'D65',
bgneal@1 225 22: 'D75',
bgneal@1 226 255: 'Other'}),
bgneal@1 227 0x9209: ('Flash', {0: 'No',
bgneal@1 228 1: 'Fired',
bgneal@1 229 5: 'Fired (?)', # no return sensed
bgneal@1 230 7: 'Fired (!)', # return sensed
bgneal@1 231 9: 'Fill Fired',
bgneal@1 232 13: 'Fill Fired (?)',
bgneal@1 233 15: 'Fill Fired (!)',
bgneal@1 234 16: 'Off',
bgneal@1 235 24: 'Auto Off',
bgneal@1 236 25: 'Auto Fired',
bgneal@1 237 29: 'Auto Fired (?)',
bgneal@1 238 31: 'Auto Fired (!)',
bgneal@1 239 32: 'Not Available'}),
bgneal@1 240 0x920A: ('FocalLength', ),
bgneal@1 241 0x9214: ('SubjectArea', ),
bgneal@1 242 0x927C: ('MakerNote', ),
bgneal@1 243 # print as string
bgneal@1 244 0x9286: ('UserComment', make_string_uc), # First 8 bytes gives coding system e.g. ASCII vs. JIS vs Unicode
bgneal@1 245 0x9290: ('SubSecTime', ),
bgneal@1 246 0x9291: ('SubSecTimeOriginal', ),
bgneal@1 247 0x9292: ('SubSecTimeDigitized', ),
bgneal@1 248 # print as string
bgneal@1 249 0xA000: ('FlashPixVersion', make_string),
bgneal@1 250 0xA001: ('ColorSpace', ),
bgneal@1 251 0xA002: ('ExifImageWidth', ),
bgneal@1 252 0xA003: ('ExifImageLength', ),
bgneal@1 253 0xA005: ('InteroperabilityOffset', ),
bgneal@1 254 0xA20B: ('FlashEnergy', ), # 0x920B in TIFF/EP
bgneal@1 255 0xA20C: ('SpatialFrequencyResponse', ), # 0x920C - -
bgneal@1 256 0xA20E: ('FocalPlaneXResolution', ), # 0x920E - -
bgneal@1 257 0xA20F: ('FocalPlaneYResolution', ), # 0x920F - -
bgneal@1 258 0xA210: ('FocalPlaneResolutionUnit', ), # 0x9210 - -
bgneal@1 259 0xA214: ('SubjectLocation', ), # 0x9214 - -
bgneal@1 260 0xA215: ('ExposureIndex', ), # 0x9215 - -
bgneal@1 261 0xA217: ('SensingMethod', ), # 0x9217 - -
bgneal@1 262 0xA300: ('FileSource',
bgneal@1 263 {3: 'Digital Camera'}),
bgneal@1 264 0xA301: ('SceneType',
bgneal@1 265 {1: 'Directly Photographed'}),
bgneal@1 266 0xA302: ('CVAPattern', ),
bgneal@1 267 0xA401: ('CustomRendered', ),
bgneal@1 268 0xA402: ('ExposureMode',
bgneal@1 269 {0: 'Auto Exposure',
bgneal@1 270 1: 'Manual Exposure',
bgneal@1 271 2: 'Auto Bracket'}),
bgneal@1 272 0xA403: ('WhiteBalance',
bgneal@1 273 {0: 'Auto',
bgneal@1 274 1: 'Manual'}),
bgneal@1 275 0xA404: ('DigitalZoomRatio', ),
bgneal@1 276 0xA405: ('FocalLengthIn35mm', ),
bgneal@1 277 0xA406: ('SceneCaptureType', ),
bgneal@1 278 0xA407: ('GainControl', ),
bgneal@1 279 0xA408: ('Contrast', ),
bgneal@1 280 0xA409: ('Saturation', ),
bgneal@1 281 0xA40A: ('Sharpness', ),
bgneal@1 282 0xA40C: ('SubjectDistanceRange', ),
bgneal@1 283 }
bgneal@1 284
bgneal@1 285 # interoperability tags
bgneal@1 286 INTR_TAGS = {
bgneal@1 287 0x0001: ('InteroperabilityIndex', ),
bgneal@1 288 0x0002: ('InteroperabilityVersion', ),
bgneal@1 289 0x1000: ('RelatedImageFileFormat', ),
bgneal@1 290 0x1001: ('RelatedImageWidth', ),
bgneal@1 291 0x1002: ('RelatedImageLength', ),
bgneal@1 292 }
bgneal@1 293
bgneal@1 294 # GPS tags (not used yet, haven't seen camera with GPS)
bgneal@1 295 GPS_TAGS = {
bgneal@1 296 0x0000: ('GPSVersionID', ),
bgneal@1 297 0x0001: ('GPSLatitudeRef', ),
bgneal@1 298 0x0002: ('GPSLatitude', ),
bgneal@1 299 0x0003: ('GPSLongitudeRef', ),
bgneal@1 300 0x0004: ('GPSLongitude', ),
bgneal@1 301 0x0005: ('GPSAltitudeRef', ),
bgneal@1 302 0x0006: ('GPSAltitude', ),
bgneal@1 303 0x0007: ('GPSTimeStamp', ),
bgneal@1 304 0x0008: ('GPSSatellites', ),
bgneal@1 305 0x0009: ('GPSStatus', ),
bgneal@1 306 0x000A: ('GPSMeasureMode', ),
bgneal@1 307 0x000B: ('GPSDOP', ),
bgneal@1 308 0x000C: ('GPSSpeedRef', ),
bgneal@1 309 0x000D: ('GPSSpeed', ),
bgneal@1 310 0x000E: ('GPSTrackRef', ),
bgneal@1 311 0x000F: ('GPSTrack', ),
bgneal@1 312 0x0010: ('GPSImgDirectionRef', ),
bgneal@1 313 0x0011: ('GPSImgDirection', ),
bgneal@1 314 0x0012: ('GPSMapDatum', ),
bgneal@1 315 0x0013: ('GPSDestLatitudeRef', ),
bgneal@1 316 0x0014: ('GPSDestLatitude', ),
bgneal@1 317 0x0015: ('GPSDestLongitudeRef', ),
bgneal@1 318 0x0016: ('GPSDestLongitude', ),
bgneal@1 319 0x0017: ('GPSDestBearingRef', ),
bgneal@1 320 0x0018: ('GPSDestBearing', ),
bgneal@1 321 0x0019: ('GPSDestDistanceRef', ),
bgneal@1 322 0x001A: ('GPSDestDistance', ),
bgneal@1 323 }
bgneal@1 324
bgneal@1 325 # Ignore these tags when quick processing
bgneal@1 326 # 0x927C is MakerNote Tags
bgneal@1 327 # 0x9286 is user comment
bgneal@1 328 IGNORE_TAGS=(0x9286, 0x927C)
bgneal@1 329
bgneal@1 330 # http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp
bgneal@1 331 def nikon_ev_bias(seq):
bgneal@1 332 # First digit seems to be in steps of 1/6 EV.
bgneal@1 333 # Does the third value mean the step size? It is usually 6,
bgneal@1 334 # but it is 12 for the ExposureDifference.
bgneal@1 335 #
bgneal@1 336 if seq == [252, 1, 6, 0]:
bgneal@1 337 return "-2/3 EV"
bgneal@1 338 if seq == [253, 1, 6, 0]:
bgneal@1 339 return "-1/2 EV"
bgneal@1 340 if seq == [254, 1, 6, 0]:
bgneal@1 341 return "-1/3 EV"
bgneal@1 342 if seq == [0, 1, 6, 0]:
bgneal@1 343 return "0 EV"
bgneal@1 344 if seq == [2, 1, 6, 0]:
bgneal@1 345 return "+1/3 EV"
bgneal@1 346 if seq == [3, 1, 6, 0]:
bgneal@1 347 return "+1/2 EV"
bgneal@1 348 if seq == [4, 1, 6, 0]:
bgneal@1 349 return "+2/3 EV"
bgneal@1 350 # Handle combinations not in the table.
bgneal@1 351 a = seq[0]
bgneal@1 352 # Causes headaches for the +/- logic, so special case it.
bgneal@1 353 if a == 0:
bgneal@1 354 return "0 EV"
bgneal@1 355 if a > 127:
bgneal@1 356 a = 256 - a
bgneal@1 357 ret_str = "-"
bgneal@1 358 else:
bgneal@1 359 ret_str = "+"
bgneal@1 360 b = seq[2] # Assume third value means the step size
bgneal@1 361 whole = a / b
bgneal@1 362 a = a % b
bgneal@1 363 if whole != 0:
bgneal@1 364 ret_str = ret_str + str(whole) + " "
bgneal@1 365 if a == 0:
bgneal@1 366 ret_str = ret_str + "EV"
bgneal@1 367 else:
bgneal@1 368 r = Ratio(a, b)
bgneal@1 369 ret_str = ret_str + r.__repr__() + " EV"
bgneal@1 370 return ret_str
bgneal@1 371
bgneal@1 372 # Nikon E99x MakerNote Tags
bgneal@1 373 MAKERNOTE_NIKON_NEWER_TAGS={
bgneal@1 374 0x0001: ('MakernoteVersion', make_string), # Sometimes binary
bgneal@1 375 0x0002: ('ISOSetting', ),
bgneal@1 376 0x0003: ('ColorMode', ),
bgneal@1 377 0x0004: ('Quality', ),
bgneal@1 378 0x0005: ('Whitebalance', ),
bgneal@1 379 0x0006: ('ImageSharpening', ),
bgneal@1 380 0x0007: ('FocusMode', ),
bgneal@1 381 0x0008: ('FlashSetting', ),
bgneal@1 382 0x0009: ('AutoFlashMode', ),
bgneal@1 383 0x000B: ('WhiteBalanceBias', ),
bgneal@1 384 0x000C: ('WhiteBalanceRBCoeff', ),
bgneal@1 385 0x000D: ('ProgramShift', nikon_ev_bias),
bgneal@1 386 # Nearly the same as the other EV vals, but step size is 1/12 EV (?)
bgneal@1 387 0x000E: ('ExposureDifference', nikon_ev_bias),
bgneal@1 388 0x000F: ('ISOSelection', ),
bgneal@1 389 0x0011: ('NikonPreview', ),
bgneal@1 390 0x0012: ('FlashCompensation', nikon_ev_bias),
bgneal@1 391 0x0013: ('ISOSpeedRequested', ),
bgneal@1 392 0x0016: ('PhotoCornerCoordinates', ),
bgneal@1 393 # 0x0017: Unknown, but most likely an EV value
bgneal@1 394 0x0018: ('FlashBracketCompensationApplied', nikon_ev_bias),
bgneal@1 395 0x0019: ('AEBracketCompensationApplied', ),
bgneal@1 396 0x001A: ('ImageProcessing', ),
bgneal@1 397 0x0080: ('ImageAdjustment', ),
bgneal@1 398 0x0081: ('ToneCompensation', ),
bgneal@1 399 0x0082: ('AuxiliaryLens', ),
bgneal@1 400 0x0083: ('LensType', ),
bgneal@1 401 0x0084: ('LensMinMaxFocalMaxAperture', ),
bgneal@1 402 0x0085: ('ManualFocusDistance', ),
bgneal@1 403 0x0086: ('DigitalZoomFactor', ),
bgneal@1 404 0x0087: ('FlashMode',
bgneal@1 405 {0x00: 'Did Not Fire',
bgneal@1 406 0x01: 'Fired, Manual',
bgneal@1 407 0x07: 'Fired, External',
bgneal@1 408 0x08: 'Fired, Commander Mode ',
bgneal@1 409 0x09: 'Fired, TTL Mode'}),
bgneal@1 410 0x0088: ('AFFocusPosition',
bgneal@1 411 {0x0000: 'Center',
bgneal@1 412 0x0100: 'Top',
bgneal@1 413 0x0200: 'Bottom',
bgneal@1 414 0x0300: 'Left',
bgneal@1 415 0x0400: 'Right'}),
bgneal@1 416 0x0089: ('BracketingMode',
bgneal@1 417 {0x00: 'Single frame, no bracketing',
bgneal@1 418 0x01: 'Continuous, no bracketing',
bgneal@1 419 0x02: 'Timer, no bracketing',
bgneal@1 420 0x10: 'Single frame, exposure bracketing',
bgneal@1 421 0x11: 'Continuous, exposure bracketing',
bgneal@1 422 0x12: 'Timer, exposure bracketing',
bgneal@1 423 0x40: 'Single frame, white balance bracketing',
bgneal@1 424 0x41: 'Continuous, white balance bracketing',
bgneal@1 425 0x42: 'Timer, white balance bracketing'}),
bgneal@1 426 0x008A: ('AutoBracketRelease', ),
bgneal@1 427 0x008B: ('LensFStops', ),
bgneal@1 428 0x008C: ('NEFCurve2', ),
bgneal@1 429 0x008D: ('ColorMode', ),
bgneal@1 430 0x008F: ('SceneMode', ),
bgneal@1 431 0x0090: ('LightingType', ),
bgneal@1 432 0x0091: ('ShotInfo', ), # First 4 bytes are probably a version number in ASCII
bgneal@1 433 0x0092: ('HueAdjustment', ),
bgneal@1 434 # 0x0093: ('SaturationAdjustment', ),
bgneal@1 435 0x0094: ('Saturation', # Name conflict with 0x00AA !!
bgneal@1 436 {-3: 'B&W',
bgneal@1 437 -2: '-2',
bgneal@1 438 -1: '-1',
bgneal@1 439 0: '0',
bgneal@1 440 1: '1',
bgneal@1 441 2: '2'}),
bgneal@1 442 0x0095: ('NoiseReduction', ),
bgneal@1 443 0x0096: ('NEFCurve2', ),
bgneal@1 444 0x0097: ('ColorBalance', ),
bgneal@1 445 0x0098: ('LensData', ), # First 4 bytes are a version number in ASCII
bgneal@1 446 0x0099: ('RawImageCenter', ),
bgneal@1 447 0x009A: ('SensorPixelSize', ),
bgneal@1 448 0x009C: ('Scene Assist', ),
bgneal@1 449 0x00A0: ('SerialNumber', ),
bgneal@1 450 0x00A2: ('ImageDataSize', ),
bgneal@1 451 # A4: In NEF, looks like a 4 byte ASCII version number
bgneal@1 452 0x00A5: ('ImageCount', ),
bgneal@1 453 0x00A6: ('DeletedImageCount', ),
bgneal@1 454 0x00A7: ('TotalShutterReleases', ),
bgneal@1 455 # A8: ExposureMode? JPG: First 4 bytes are probably a version number in ASCII
bgneal@1 456 # But in a sample NEF, its 8 zeros, then the string "NORMAL"
bgneal@1 457 0x00A9: ('ImageOptimization', ),
bgneal@1 458 0x00AA: ('Saturation', ),
bgneal@1 459 0x00AB: ('DigitalVariProgram', ),
bgneal@1 460 0x00AC: ('ImageStabilization', ),
bgneal@1 461 0x00AD: ('Responsive AF', ), # 'AFResponse'
bgneal@1 462 0x0010: ('DataDump', ),
bgneal@1 463 }
bgneal@1 464
bgneal@1 465 MAKERNOTE_NIKON_OLDER_TAGS = {
bgneal@1 466 0x0003: ('Quality',
bgneal@1 467 {1: 'VGA Basic',
bgneal@1 468 2: 'VGA Normal',
bgneal@1 469 3: 'VGA Fine',
bgneal@1 470 4: 'SXGA Basic',
bgneal@1 471 5: 'SXGA Normal',
bgneal@1 472 6: 'SXGA Fine'}),
bgneal@1 473 0x0004: ('ColorMode',
bgneal@1 474 {1: 'Color',
bgneal@1 475 2: 'Monochrome'}),
bgneal@1 476 0x0005: ('ImageAdjustment',
bgneal@1 477 {0: 'Normal',
bgneal@1 478 1: 'Bright+',
bgneal@1 479 2: 'Bright-',
bgneal@1 480 3: 'Contrast+',
bgneal@1 481 4: 'Contrast-'}),
bgneal@1 482 0x0006: ('CCDSpeed',
bgneal@1 483 {0: 'ISO 80',
bgneal@1 484 2: 'ISO 160',
bgneal@1 485 4: 'ISO 320',
bgneal@1 486 5: 'ISO 100'}),
bgneal@1 487 0x0007: ('WhiteBalance',
bgneal@1 488 {0: 'Auto',
bgneal@1 489 1: 'Preset',
bgneal@1 490 2: 'Daylight',
bgneal@1 491 3: 'Incandescent',
bgneal@1 492 4: 'Fluorescent',
bgneal@1 493 5: 'Cloudy',
bgneal@1 494 6: 'Speed Light'}),
bgneal@1 495 }
bgneal@1 496
bgneal@1 497 # decode Olympus SpecialMode tag in MakerNote
bgneal@1 498 def olympus_special_mode(v):
bgneal@1 499 a={
bgneal@1 500 0: 'Normal',
bgneal@1 501 1: 'Unknown',
bgneal@1 502 2: 'Fast',
bgneal@1 503 3: 'Panorama'}
bgneal@1 504 b={
bgneal@1 505 0: 'Non-panoramic',
bgneal@1 506 1: 'Left to right',
bgneal@1 507 2: 'Right to left',
bgneal@1 508 3: 'Bottom to top',
bgneal@1 509 4: 'Top to bottom'}
bgneal@1 510 if v[0] not in a or v[2] not in b:
bgneal@1 511 return v
bgneal@1 512 return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]])
bgneal@1 513
bgneal@1 514 MAKERNOTE_OLYMPUS_TAGS={
bgneal@1 515 # ah HAH! those sneeeeeaky bastids! this is how they get past the fact
bgneal@1 516 # that a JPEG thumbnail is not allowed in an uncompressed TIFF file
bgneal@1 517 0x0100: ('JPEGThumbnail', ),
bgneal@1 518 0x0200: ('SpecialMode', olympus_special_mode),
bgneal@1 519 0x0201: ('JPEGQual',
bgneal@1 520 {1: 'SQ',
bgneal@1 521 2: 'HQ',
bgneal@1 522 3: 'SHQ'}),
bgneal@1 523 0x0202: ('Macro',
bgneal@1 524 {0: 'Normal',
bgneal@1 525 1: 'Macro',
bgneal@1 526 2: 'SuperMacro'}),
bgneal@1 527 0x0203: ('BWMode',
bgneal@1 528 {0: 'Off',
bgneal@1 529 1: 'On'}),
bgneal@1 530 0x0204: ('DigitalZoom', ),
bgneal@1 531 0x0205: ('FocalPlaneDiagonal', ),
bgneal@1 532 0x0206: ('LensDistortionParams', ),
bgneal@1 533 0x0207: ('SoftwareRelease', ),
bgneal@1 534 0x0208: ('PictureInfo', ),
bgneal@1 535 0x0209: ('CameraID', make_string), # print as string
bgneal@1 536 0x0F00: ('DataDump', ),
bgneal@1 537 0x0300: ('PreCaptureFrames', ),
bgneal@1 538 0x0404: ('SerialNumber', ),
bgneal@1 539 0x1000: ('ShutterSpeedValue', ),
bgneal@1 540 0x1001: ('ISOValue', ),
bgneal@1 541 0x1002: ('ApertureValue', ),
bgneal@1 542 0x1003: ('BrightnessValue', ),
bgneal@1 543 0x1004: ('FlashMode', ),
bgneal@1 544 0x1004: ('FlashMode',
bgneal@1 545 {2: 'On',
bgneal@1 546 3: 'Off'}),
bgneal@1 547 0x1005: ('FlashDevice',
bgneal@1 548 {0: 'None',
bgneal@1 549 1: 'Internal',
bgneal@1 550 4: 'External',
bgneal@1 551 5: 'Internal + External'}),
bgneal@1 552 0x1006: ('ExposureCompensation', ),
bgneal@1 553 0x1007: ('SensorTemperature', ),
bgneal@1 554 0x1008: ('LensTemperature', ),
bgneal@1 555 0x100b: ('FocusMode',
bgneal@1 556 {0: 'Auto',
bgneal@1 557 1: 'Manual'}),
bgneal@1 558 0x1017: ('RedBalance', ),
bgneal@1 559 0x1018: ('BlueBalance', ),
bgneal@1 560 0x101a: ('SerialNumber', ),
bgneal@1 561 0x1023: ('FlashExposureComp', ),
bgneal@1 562 0x1026: ('ExternalFlashBounce',
bgneal@1 563 {0: 'No',
bgneal@1 564 1: 'Yes'}),
bgneal@1 565 0x1027: ('ExternalFlashZoom', ),
bgneal@1 566 0x1028: ('ExternalFlashMode', ),
bgneal@1 567 0x1029: ('Contrast int16u',
bgneal@1 568 {0: 'High',
bgneal@1 569 1: 'Normal',
bgneal@1 570 2: 'Low'}),
bgneal@1 571 0x102a: ('SharpnessFactor', ),
bgneal@1 572 0x102b: ('ColorControl', ),
bgneal@1 573 0x102c: ('ValidBits', ),
bgneal@1 574 0x102d: ('CoringFilter', ),
bgneal@1 575 0x102e: ('OlympusImageWidth', ),
bgneal@1 576 0x102f: ('OlympusImageHeight', ),
bgneal@1 577 0x1034: ('CompressionRatio', ),
bgneal@1 578 0x1035: ('PreviewImageValid',
bgneal@1 579 {0: 'No',
bgneal@1 580 1: 'Yes'}),
bgneal@1 581 0x1036: ('PreviewImageStart', ),
bgneal@1 582 0x1037: ('PreviewImageLength', ),
bgneal@1 583 0x1039: ('CCDScanMode',
bgneal@1 584 {0: 'Interlaced',
bgneal@1 585 1: 'Progressive'}),
bgneal@1 586 0x103a: ('NoiseReduction',
bgneal@1 587 {0: 'Off',
bgneal@1 588 1: 'On'}),
bgneal@1 589 0x103b: ('InfinityLensStep', ),
bgneal@1 590 0x103c: ('NearLensStep', ),
bgneal@1 591
bgneal@1 592 # TODO - these need extra definitions
bgneal@1 593 # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html
bgneal@1 594 0x2010: ('Equipment', ),
bgneal@1 595 0x2020: ('CameraSettings', ),
bgneal@1 596 0x2030: ('RawDevelopment', ),
bgneal@1 597 0x2040: ('ImageProcessing', ),
bgneal@1 598 0x2050: ('FocusInfo', ),
bgneal@1 599 0x3000: ('RawInfo ', ),
bgneal@1 600 }
bgneal@1 601
bgneal@1 602 # 0x2020 CameraSettings
bgneal@1 603 MAKERNOTE_OLYMPUS_TAG_0x2020={
bgneal@1 604 0x0100: ('PreviewImageValid',
bgneal@1 605 {0: 'No',
bgneal@1 606 1: 'Yes'}),
bgneal@1 607 0x0101: ('PreviewImageStart', ),
bgneal@1 608 0x0102: ('PreviewImageLength', ),
bgneal@1 609 0x0200: ('ExposureMode', {
bgneal@1 610 1: 'Manual',
bgneal@1 611 2: 'Program',
bgneal@1 612 3: 'Aperture-priority AE',
bgneal@1 613 4: 'Shutter speed priority AE',
bgneal@1 614 5: 'Program-shift'}),
bgneal@1 615 0x0201: ('AELock',
bgneal@1 616 {0: 'Off',
bgneal@1 617 1: 'On'}),
bgneal@1 618 0x0202: ('MeteringMode',
bgneal@1 619 {2: 'Center Weighted',
bgneal@1 620 3: 'Spot',
bgneal@1 621 5: 'ESP',
bgneal@1 622 261: 'Pattern+AF',
bgneal@1 623 515: 'Spot+Highlight control',
bgneal@1 624 1027: 'Spot+Shadow control'}),
bgneal@1 625 0x0300: ('MacroMode',
bgneal@1 626 {0: 'Off',
bgneal@1 627 1: 'On'}),
bgneal@1 628 0x0301: ('FocusMode',
bgneal@1 629 {0: 'Single AF',
bgneal@1 630 1: 'Sequential shooting AF',
bgneal@1 631 2: 'Continuous AF',
bgneal@1 632 3: 'Multi AF',
bgneal@1 633 10: 'MF'}),
bgneal@1 634 0x0302: ('FocusProcess',
bgneal@1 635 {0: 'AF Not Used',
bgneal@1 636 1: 'AF Used'}),
bgneal@1 637 0x0303: ('AFSearch',
bgneal@1 638 {0: 'Not Ready',
bgneal@1 639 1: 'Ready'}),
bgneal@1 640 0x0304: ('AFAreas', ),
bgneal@1 641 0x0401: ('FlashExposureCompensation', ),
bgneal@1 642 0x0500: ('WhiteBalance2',
bgneal@1 643 {0: 'Auto',
bgneal@1 644 16: '7500K (Fine Weather with Shade)',
bgneal@1 645 17: '6000K (Cloudy)',
bgneal@1 646 18: '5300K (Fine Weather)',
bgneal@1 647 20: '3000K (Tungsten light)',
bgneal@1 648 21: '3600K (Tungsten light-like)',
bgneal@1 649 33: '6600K (Daylight fluorescent)',
bgneal@1 650 34: '4500K (Neutral white fluorescent)',
bgneal@1 651 35: '4000K (Cool white fluorescent)',
bgneal@1 652 48: '3600K (Tungsten light-like)',
bgneal@1 653 256: 'Custom WB 1',
bgneal@1 654 257: 'Custom WB 2',
bgneal@1 655 258: 'Custom WB 3',
bgneal@1 656 259: 'Custom WB 4',
bgneal@1 657 512: 'Custom WB 5400K',
bgneal@1 658 513: 'Custom WB 2900K',
bgneal@1 659 514: 'Custom WB 8000K', }),
bgneal@1 660 0x0501: ('WhiteBalanceTemperature', ),
bgneal@1 661 0x0502: ('WhiteBalanceBracket', ),
bgneal@1 662 0x0503: ('CustomSaturation', ), # (3 numbers: 1. CS Value, 2. Min, 3. Max)
bgneal@1 663 0x0504: ('ModifiedSaturation',
bgneal@1 664 {0: 'Off',
bgneal@1 665 1: 'CM1 (Red Enhance)',
bgneal@1 666 2: 'CM2 (Green Enhance)',
bgneal@1 667 3: 'CM3 (Blue Enhance)',
bgneal@1 668 4: 'CM4 (Skin Tones)'}),
bgneal@1 669 0x0505: ('ContrastSetting', ), # (3 numbers: 1. Contrast, 2. Min, 3. Max)
bgneal@1 670 0x0506: ('SharpnessSetting', ), # (3 numbers: 1. Sharpness, 2. Min, 3. Max)
bgneal@1 671 0x0507: ('ColorSpace',
bgneal@1 672 {0: 'sRGB',
bgneal@1 673 1: 'Adobe RGB',
bgneal@1 674 2: 'Pro Photo RGB'}),
bgneal@1 675 0x0509: ('SceneMode',
bgneal@1 676 {0: 'Standard',
bgneal@1 677 6: 'Auto',
bgneal@1 678 7: 'Sport',
bgneal@1 679 8: 'Portrait',
bgneal@1 680 9: 'Landscape+Portrait',
bgneal@1 681 10: 'Landscape',
bgneal@1 682 11: 'Night scene',
bgneal@1 683 13: 'Panorama',
bgneal@1 684 16: 'Landscape+Portrait',
bgneal@1 685 17: 'Night+Portrait',
bgneal@1 686 19: 'Fireworks',
bgneal@1 687 20: 'Sunset',
bgneal@1 688 22: 'Macro',
bgneal@1 689 25: 'Documents',
bgneal@1 690 26: 'Museum',
bgneal@1 691 28: 'Beach&Snow',
bgneal@1 692 30: 'Candle',
bgneal@1 693 35: 'Underwater Wide1',
bgneal@1 694 36: 'Underwater Macro',
bgneal@1 695 39: 'High Key',
bgneal@1 696 40: 'Digital Image Stabilization',
bgneal@1 697 44: 'Underwater Wide2',
bgneal@1 698 45: 'Low Key',
bgneal@1 699 46: 'Children',
bgneal@1 700 48: 'Nature Macro'}),
bgneal@1 701 0x050a: ('NoiseReduction',
bgneal@1 702 {0: 'Off',
bgneal@1 703 1: 'Noise Reduction',
bgneal@1 704 2: 'Noise Filter',
bgneal@1 705 3: 'Noise Reduction + Noise Filter',
bgneal@1 706 4: 'Noise Filter (ISO Boost)',
bgneal@1 707 5: 'Noise Reduction + Noise Filter (ISO Boost)'}),
bgneal@1 708 0x050b: ('DistortionCorrection',
bgneal@1 709 {0: 'Off',
bgneal@1 710 1: 'On'}),
bgneal@1 711 0x050c: ('ShadingCompensation',
bgneal@1 712 {0: 'Off',
bgneal@1 713 1: 'On'}),
bgneal@1 714 0x050d: ('CompressionFactor', ),
bgneal@1 715 0x050f: ('Gradation',
bgneal@1 716 {'-1 -1 1': 'Low Key',
bgneal@1 717 '0 -1 1': 'Normal',
bgneal@1 718 '1 -1 1': 'High Key'}),
bgneal@1 719 0x0520: ('PictureMode',
bgneal@1 720 {1: 'Vivid',
bgneal@1 721 2: 'Natural',
bgneal@1 722 3: 'Muted',
bgneal@1 723 256: 'Monotone',
bgneal@1 724 512: 'Sepia'}),
bgneal@1 725 0x0521: ('PictureModeSaturation', ),
bgneal@1 726 0x0522: ('PictureModeHue?', ),
bgneal@1 727 0x0523: ('PictureModeContrast', ),
bgneal@1 728 0x0524: ('PictureModeSharpness', ),
bgneal@1 729 0x0525: ('PictureModeBWFilter',
bgneal@1 730 {0: 'n/a',
bgneal@1 731 1: 'Neutral',
bgneal@1 732 2: 'Yellow',
bgneal@1 733 3: 'Orange',
bgneal@1 734 4: 'Red',
bgneal@1 735 5: 'Green'}),
bgneal@1 736 0x0526: ('PictureModeTone',
bgneal@1 737 {0: 'n/a',
bgneal@1 738 1: 'Neutral',
bgneal@1 739 2: 'Sepia',
bgneal@1 740 3: 'Blue',
bgneal@1 741 4: 'Purple',
bgneal@1 742 5: 'Green'}),
bgneal@1 743 0x0600: ('Sequence', ), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits
bgneal@1 744 0x0601: ('PanoramaMode', ), # (2 numbers: 1. Mode, 2. Shot number)
bgneal@1 745 0x0603: ('ImageQuality2',
bgneal@1 746 {1: 'SQ',
bgneal@1 747 2: 'HQ',
bgneal@1 748 3: 'SHQ',
bgneal@1 749 4: 'RAW'}),
bgneal@1 750 0x0901: ('ManometerReading', ),
bgneal@1 751 }
bgneal@1 752
bgneal@1 753
bgneal@1 754 MAKERNOTE_CASIO_TAGS={
bgneal@1 755 0x0001: ('RecordingMode',
bgneal@1 756 {1: 'Single Shutter',
bgneal@1 757 2: 'Panorama',
bgneal@1 758 3: 'Night Scene',
bgneal@1 759 4: 'Portrait',
bgneal@1 760 5: 'Landscape'}),
bgneal@1 761 0x0002: ('Quality',
bgneal@1 762 {1: 'Economy',
bgneal@1 763 2: 'Normal',
bgneal@1 764 3: 'Fine'}),
bgneal@1 765 0x0003: ('FocusingMode',
bgneal@1 766 {2: 'Macro',
bgneal@1 767 3: 'Auto Focus',
bgneal@1 768 4: 'Manual Focus',
bgneal@1 769 5: 'Infinity'}),
bgneal@1 770 0x0004: ('FlashMode',
bgneal@1 771 {1: 'Auto',
bgneal@1 772 2: 'On',
bgneal@1 773 3: 'Off',
bgneal@1 774 4: 'Red Eye Reduction'}),
bgneal@1 775 0x0005: ('FlashIntensity',
bgneal@1 776 {11: 'Weak',
bgneal@1 777 13: 'Normal',
bgneal@1 778 15: 'Strong'}),
bgneal@1 779 0x0006: ('Object Distance', ),
bgneal@1 780 0x0007: ('WhiteBalance',
bgneal@1 781 {1: 'Auto',
bgneal@1 782 2: 'Tungsten',
bgneal@1 783 3: 'Daylight',
bgneal@1 784 4: 'Fluorescent',
bgneal@1 785 5: 'Shade',
bgneal@1 786 129: 'Manual'}),
bgneal@1 787 0x000B: ('Sharpness',
bgneal@1 788 {0: 'Normal',
bgneal@1 789 1: 'Soft',
bgneal@1 790 2: 'Hard'}),
bgneal@1 791 0x000C: ('Contrast',
bgneal@1 792 {0: 'Normal',
bgneal@1 793 1: 'Low',
bgneal@1 794 2: 'High'}),
bgneal@1 795 0x000D: ('Saturation',
bgneal@1 796 {0: 'Normal',
bgneal@1 797 1: 'Low',
bgneal@1 798 2: 'High'}),
bgneal@1 799 0x0014: ('CCDSpeed',
bgneal@1 800 {64: 'Normal',
bgneal@1 801 80: 'Normal',
bgneal@1 802 100: 'High',
bgneal@1 803 125: '+1.0',
bgneal@1 804 244: '+3.0',
bgneal@1 805 250: '+2.0'}),
bgneal@1 806 }
bgneal@1 807
bgneal@1 808 MAKERNOTE_FUJIFILM_TAGS={
bgneal@1 809 0x0000: ('NoteVersion', make_string),
bgneal@1 810 0x1000: ('Quality', ),
bgneal@1 811 0x1001: ('Sharpness',
bgneal@1 812 {1: 'Soft',
bgneal@1 813 2: 'Soft',
bgneal@1 814 3: 'Normal',
bgneal@1 815 4: 'Hard',
bgneal@1 816 5: 'Hard'}),
bgneal@1 817 0x1002: ('WhiteBalance',
bgneal@1 818 {0: 'Auto',
bgneal@1 819 256: 'Daylight',
bgneal@1 820 512: 'Cloudy',
bgneal@1 821 768: 'DaylightColor-Fluorescent',
bgneal@1 822 769: 'DaywhiteColor-Fluorescent',
bgneal@1 823 770: 'White-Fluorescent',
bgneal@1 824 1024: 'Incandescent',
bgneal@1 825 3840: 'Custom'}),
bgneal@1 826 0x1003: ('Color',
bgneal@1 827 {0: 'Normal',
bgneal@1 828 256: 'High',
bgneal@1 829 512: 'Low'}),
bgneal@1 830 0x1004: ('Tone',
bgneal@1 831 {0: 'Normal',
bgneal@1 832 256: 'High',
bgneal@1 833 512: 'Low'}),
bgneal@1 834 0x1010: ('FlashMode',
bgneal@1 835 {0: 'Auto',
bgneal@1 836 1: 'On',
bgneal@1 837 2: 'Off',
bgneal@1 838 3: 'Red Eye Reduction'}),
bgneal@1 839 0x1011: ('FlashStrength', ),
bgneal@1 840 0x1020: ('Macro',
bgneal@1 841 {0: 'Off',
bgneal@1 842 1: 'On'}),
bgneal@1 843 0x1021: ('FocusMode',
bgneal@1 844 {0: 'Auto',
bgneal@1 845 1: 'Manual'}),
bgneal@1 846 0x1030: ('SlowSync',
bgneal@1 847 {0: 'Off',
bgneal@1 848 1: 'On'}),
bgneal@1 849 0x1031: ('PictureMode',
bgneal@1 850 {0: 'Auto',
bgneal@1 851 1: 'Portrait',
bgneal@1 852 2: 'Landscape',
bgneal@1 853 4: 'Sports',
bgneal@1 854 5: 'Night',
bgneal@1 855 6: 'Program AE',
bgneal@1 856 256: 'Aperture Priority AE',
bgneal@1 857 512: 'Shutter Priority AE',
bgneal@1 858 768: 'Manual Exposure'}),
bgneal@1 859 0x1100: ('MotorOrBracket',
bgneal@1 860 {0: 'Off',
bgneal@1 861 1: 'On'}),
bgneal@1 862 0x1300: ('BlurWarning',
bgneal@1 863 {0: 'Off',
bgneal@1 864 1: 'On'}),
bgneal@1 865 0x1301: ('FocusWarning',
bgneal@1 866 {0: 'Off',
bgneal@1 867 1: 'On'}),
bgneal@1 868 0x1302: ('AEWarning',
bgneal@1 869 {0: 'Off',
bgneal@1 870 1: 'On'}),
bgneal@1 871 }
bgneal@1 872
bgneal@1 873 MAKERNOTE_CANON_TAGS = {
bgneal@1 874 0x0006: ('ImageType', ),
bgneal@1 875 0x0007: ('FirmwareVersion', ),
bgneal@1 876 0x0008: ('ImageNumber', ),
bgneal@1 877 0x0009: ('OwnerName', ),
bgneal@1 878 }
bgneal@1 879
bgneal@1 880 # this is in element offset, name, optional value dictionary format
bgneal@1 881 MAKERNOTE_CANON_TAG_0x001 = {
bgneal@1 882 1: ('Macromode',
bgneal@1 883 {1: 'Macro',
bgneal@1 884 2: 'Normal'}),
bgneal@1 885 2: ('SelfTimer', ),
bgneal@1 886 3: ('Quality',
bgneal@1 887 {2: 'Normal',
bgneal@1 888 3: 'Fine',
bgneal@1 889 5: 'Superfine'}),
bgneal@1 890 4: ('FlashMode',
bgneal@1 891 {0: 'Flash Not Fired',
bgneal@1 892 1: 'Auto',
bgneal@1 893 2: 'On',
bgneal@1 894 3: 'Red-Eye Reduction',
bgneal@1 895 4: 'Slow Synchro',
bgneal@1 896 5: 'Auto + Red-Eye Reduction',
bgneal@1 897 6: 'On + Red-Eye Reduction',
bgneal@1 898 16: 'external flash'}),
bgneal@1 899 5: ('ContinuousDriveMode',
bgneal@1 900 {0: 'Single Or Timer',
bgneal@1 901 1: 'Continuous'}),
bgneal@1 902 7: ('FocusMode',
bgneal@1 903 {0: 'One-Shot',
bgneal@1 904 1: 'AI Servo',
bgneal@1 905 2: 'AI Focus',
bgneal@1 906 3: 'MF',
bgneal@1 907 4: 'Single',
bgneal@1 908 5: 'Continuous',
bgneal@1 909 6: 'MF'}),
bgneal@1 910 10: ('ImageSize',
bgneal@1 911 {0: 'Large',
bgneal@1 912 1: 'Medium',
bgneal@1 913 2: 'Small'}),
bgneal@1 914 11: ('EasyShootingMode',
bgneal@1 915 {0: 'Full Auto',
bgneal@1 916 1: 'Manual',
bgneal@1 917 2: 'Landscape',
bgneal@1 918 3: 'Fast Shutter',
bgneal@1 919 4: 'Slow Shutter',
bgneal@1 920 5: 'Night',
bgneal@1 921 6: 'B&W',
bgneal@1 922 7: 'Sepia',
bgneal@1 923 8: 'Portrait',
bgneal@1 924 9: 'Sports',
bgneal@1 925 10: 'Macro/Close-Up',
bgneal@1 926 11: 'Pan Focus'}),
bgneal@1 927 12: ('DigitalZoom',
bgneal@1 928 {0: 'None',
bgneal@1 929 1: '2x',
bgneal@1 930 2: '4x'}),
bgneal@1 931 13: ('Contrast',
bgneal@1 932 {0xFFFF: 'Low',
bgneal@1 933 0: 'Normal',
bgneal@1 934 1: 'High'}),
bgneal@1 935 14: ('Saturation',
bgneal@1 936 {0xFFFF: 'Low',
bgneal@1 937 0: 'Normal',
bgneal@1 938 1: 'High'}),
bgneal@1 939 15: ('Sharpness',
bgneal@1 940 {0xFFFF: 'Low',
bgneal@1 941 0: 'Normal',
bgneal@1 942 1: 'High'}),
bgneal@1 943 16: ('ISO',
bgneal@1 944 {0: 'See ISOSpeedRatings Tag',
bgneal@1 945 15: 'Auto',
bgneal@1 946 16: '50',
bgneal@1 947 17: '100',
bgneal@1 948 18: '200',
bgneal@1 949 19: '400'}),
bgneal@1 950 17: ('MeteringMode',
bgneal@1 951 {3: 'Evaluative',
bgneal@1 952 4: 'Partial',
bgneal@1 953 5: 'Center-weighted'}),
bgneal@1 954 18: ('FocusType',
bgneal@1 955 {0: 'Manual',
bgneal@1 956 1: 'Auto',
bgneal@1 957 3: 'Close-Up (Macro)',
bgneal@1 958 8: 'Locked (Pan Mode)'}),
bgneal@1 959 19: ('AFPointSelected',
bgneal@1 960 {0x3000: 'None (MF)',
bgneal@1 961 0x3001: 'Auto-Selected',
bgneal@1 962 0x3002: 'Right',
bgneal@1 963 0x3003: 'Center',
bgneal@1 964 0x3004: 'Left'}),
bgneal@1 965 20: ('ExposureMode',
bgneal@1 966 {0: 'Easy Shooting',
bgneal@1 967 1: 'Program',
bgneal@1 968 2: 'Tv-priority',
bgneal@1 969 3: 'Av-priority',
bgneal@1 970 4: 'Manual',
bgneal@1 971 5: 'A-DEP'}),
bgneal@1 972 23: ('LongFocalLengthOfLensInFocalUnits', ),
bgneal@1 973 24: ('ShortFocalLengthOfLensInFocalUnits', ),
bgneal@1 974 25: ('FocalUnitsPerMM', ),
bgneal@1 975 28: ('FlashActivity',
bgneal@1 976 {0: 'Did Not Fire',
bgneal@1 977 1: 'Fired'}),
bgneal@1 978 29: ('FlashDetails',
bgneal@1 979 {14: 'External E-TTL',
bgneal@1 980 13: 'Internal Flash',
bgneal@1 981 11: 'FP Sync Used',
bgneal@1 982 7: '2nd("Rear")-Curtain Sync Used',
bgneal@1 983 4: 'FP Sync Enabled'}),
bgneal@1 984 32: ('FocusMode',
bgneal@1 985 {0: 'Single',
bgneal@1 986 1: 'Continuous'}),
bgneal@1 987 }
bgneal@1 988
bgneal@1 989 MAKERNOTE_CANON_TAG_0x004 = {
bgneal@1 990 7: ('WhiteBalance',
bgneal@1 991 {0: 'Auto',
bgneal@1 992 1: 'Sunny',
bgneal@1 993 2: 'Cloudy',
bgneal@1 994 3: 'Tungsten',
bgneal@1 995 4: 'Fluorescent',
bgneal@1 996 5: 'Flash',
bgneal@1 997 6: 'Custom'}),
bgneal@1 998 9: ('SequenceNumber', ),
bgneal@1 999 14: ('AFPointUsed', ),
bgneal@1 1000 15: ('FlashBias',
bgneal@1 1001 {0XFFC0: '-2 EV',
bgneal@1 1002 0XFFCC: '-1.67 EV',
bgneal@1 1003 0XFFD0: '-1.50 EV',
bgneal@1 1004 0XFFD4: '-1.33 EV',
bgneal@1 1005 0XFFE0: '-1 EV',
bgneal@1 1006 0XFFEC: '-0.67 EV',
bgneal@1 1007 0XFFF0: '-0.50 EV',
bgneal@1 1008 0XFFF4: '-0.33 EV',
bgneal@1 1009 0X0000: '0 EV',
bgneal@1 1010 0X000C: '0.33 EV',
bgneal@1 1011 0X0010: '0.50 EV',
bgneal@1 1012 0X0014: '0.67 EV',
bgneal@1 1013 0X0020: '1 EV',
bgneal@1 1014 0X002C: '1.33 EV',
bgneal@1 1015 0X0030: '1.50 EV',
bgneal@1 1016 0X0034: '1.67 EV',
bgneal@1 1017 0X0040: '2 EV'}),
bgneal@1 1018 19: ('SubjectDistance', ),
bgneal@1 1019 }
bgneal@1 1020
bgneal@1 1021 # extract multibyte integer in Motorola format (little endian)
bgneal@1 1022 def s2n_motorola(str):
bgneal@1 1023 x = 0
bgneal@1 1024 for c in str:
bgneal@1 1025 x = (x << 8) | ord(c)
bgneal@1 1026 return x
bgneal@1 1027
bgneal@1 1028 # extract multibyte integer in Intel format (big endian)
bgneal@1 1029 def s2n_intel(str):
bgneal@1 1030 x = 0
bgneal@1 1031 y = 0L
bgneal@1 1032 for c in str:
bgneal@1 1033 x = x | (ord(c) << y)
bgneal@1 1034 y = y + 8
bgneal@1 1035 return x
bgneal@1 1036
bgneal@1 1037 # ratio object that eventually will be able to reduce itself to lowest
bgneal@1 1038 # common denominator for printing
bgneal@1 1039 def gcd(a, b):
bgneal@1 1040 if b == 0:
bgneal@1 1041 return a
bgneal@1 1042 else:
bgneal@1 1043 return gcd(b, a % b)
bgneal@1 1044
bgneal@1 1045 class Ratio:
bgneal@1 1046 def __init__(self, num, den):
bgneal@1 1047 self.num = num
bgneal@1 1048 self.den = den
bgneal@1 1049
bgneal@1 1050 def __repr__(self):
bgneal@1 1051 self.reduce()
bgneal@1 1052 if self.den == 1:
bgneal@1 1053 return str(self.num)
bgneal@1 1054 return '%d/%d' % (self.num, self.den)
bgneal@1 1055
bgneal@1 1056 def reduce(self):
bgneal@1 1057 div = gcd(self.num, self.den)
bgneal@1 1058 if div > 1:
bgneal@1 1059 self.num = self.num / div
bgneal@1 1060 self.den = self.den / div
bgneal@1 1061
bgneal@1 1062 # for ease of dealing with tags
bgneal@1 1063 class IFD_Tag:
bgneal@1 1064 def __init__(self, printable, tag, field_type, values, field_offset,
bgneal@1 1065 field_length):
bgneal@1 1066 # printable version of data
bgneal@1 1067 self.printable = printable
bgneal@1 1068 # tag ID number
bgneal@1 1069 self.tag = tag
bgneal@1 1070 # field type as index into FIELD_TYPES
bgneal@1 1071 self.field_type = field_type
bgneal@1 1072 # offset of start of field in bytes from beginning of IFD
bgneal@1 1073 self.field_offset = field_offset
bgneal@1 1074 # length of data field in bytes
bgneal@1 1075 self.field_length = field_length
bgneal@1 1076 # either a string or array of data items
bgneal@1 1077 self.values = values
bgneal@1 1078
bgneal@1 1079 def __str__(self):
bgneal@1 1080 return self.printable
bgneal@1 1081
bgneal@1 1082 def __repr__(self):
bgneal@1 1083 return '(0x%04X) %s=%s @ %d' % (self.tag,
bgneal@1 1084 FIELD_TYPES[self.field_type][2],
bgneal@1 1085 self.printable,
bgneal@1 1086 self.field_offset)
bgneal@1 1087
bgneal@1 1088 # class that handles an EXIF header
bgneal@1 1089 class EXIF_header:
bgneal@1 1090 def __init__(self, file, endian, offset, fake_exif, debug=0):
bgneal@1 1091 self.file = file
bgneal@1 1092 self.endian = endian
bgneal@1 1093 self.offset = offset
bgneal@1 1094 self.fake_exif = fake_exif
bgneal@1 1095 self.debug = debug
bgneal@1 1096 self.tags = {}
bgneal@1 1097
bgneal@1 1098 # convert slice to integer, based on sign and endian flags
bgneal@1 1099 # usually this offset is assumed to be relative to the beginning of the
bgneal@1 1100 # start of the EXIF information. For some cameras that use relative tags,
bgneal@1 1101 # this offset may be relative to some other starting point.
bgneal@1 1102 def s2n(self, offset, length, signed=0):
bgneal@1 1103 self.file.seek(self.offset+offset)
bgneal@1 1104 slice=self.file.read(length)
bgneal@1 1105 if self.endian == 'I':
bgneal@1 1106 val=s2n_intel(slice)
bgneal@1 1107 else:
bgneal@1 1108 val=s2n_motorola(slice)
bgneal@1 1109 # Sign extension ?
bgneal@1 1110 if signed:
bgneal@1 1111 msb=1L << (8*length-1)
bgneal@1 1112 if val & msb:
bgneal@1 1113 val=val-(msb << 1)
bgneal@1 1114 return val
bgneal@1 1115
bgneal@1 1116 # convert offset to string
bgneal@1 1117 def n2s(self, offset, length):
bgneal@1 1118 s = ''
bgneal@1 1119 for dummy in range(length):
bgneal@1 1120 if self.endian == 'I':
bgneal@1 1121 s = s + chr(offset & 0xFF)
bgneal@1 1122 else:
bgneal@1 1123 s = chr(offset & 0xFF) + s
bgneal@1 1124 offset = offset >> 8
bgneal@1 1125 return s
bgneal@1 1126
bgneal@1 1127 # return first IFD
bgneal@1 1128 def first_IFD(self):
bgneal@1 1129 return self.s2n(4, 4)
bgneal@1 1130
bgneal@1 1131 # return pointer to next IFD
bgneal@1 1132 def next_IFD(self, ifd):
bgneal@1 1133 entries=self.s2n(ifd, 2)
bgneal@1 1134 return self.s2n(ifd+2+12*entries, 4)
bgneal@1 1135
bgneal@1 1136 # return list of IFDs in header
bgneal@1 1137 def list_IFDs(self):
bgneal@1 1138 i=self.first_IFD()
bgneal@1 1139 a=[]
bgneal@1 1140 while i:
bgneal@1 1141 a.append(i)
bgneal@1 1142 i=self.next_IFD(i)
bgneal@1 1143 return a
bgneal@1 1144
bgneal@1 1145 # return list of entries in this IFD
bgneal@1 1146 def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0, stop_tag='UNDEF'):
bgneal@1 1147 entries=self.s2n(ifd, 2)
bgneal@1 1148 for i in range(entries):
bgneal@1 1149 # entry is index of start of this IFD in the file
bgneal@1 1150 entry = ifd + 2 + 12 * i
bgneal@1 1151 tag = self.s2n(entry, 2)
bgneal@1 1152
bgneal@1 1153 # get tag name early to avoid errors, help debug
bgneal@1 1154 tag_entry = dict.get(tag)
bgneal@1 1155 if tag_entry:
bgneal@1 1156 tag_name = tag_entry[0]
bgneal@1 1157 else:
bgneal@1 1158 tag_name = 'Tag 0x%04X' % tag
bgneal@1 1159
bgneal@1 1160 # ignore certain tags for faster processing
bgneal@1 1161 if not (not detailed and tag in IGNORE_TAGS):
bgneal@1 1162 field_type = self.s2n(entry + 2, 2)
bgneal@1 1163 if not 0 < field_type < len(FIELD_TYPES):
bgneal@1 1164 # unknown field type
bgneal@1 1165 raise ValueError('unknown type %d in tag 0x%04X' % (field_type, tag))
bgneal@1 1166 typelen = FIELD_TYPES[field_type][0]
bgneal@1 1167 count = self.s2n(entry + 4, 4)
bgneal@1 1168 offset = entry + 8
bgneal@1 1169 if count * typelen > 4:
bgneal@1 1170 # offset is not the value; it's a pointer to the value
bgneal@1 1171 # if relative we set things up so s2n will seek to the right
bgneal@1 1172 # place when it adds self.offset. Note that this 'relative'
bgneal@1 1173 # is for the Nikon type 3 makernote. Other cameras may use
bgneal@1 1174 # other relative offsets, which would have to be computed here
bgneal@1 1175 # slightly differently.
bgneal@1 1176 if relative:
bgneal@1 1177 tmp_offset = self.s2n(offset, 4)
bgneal@1 1178 offset = tmp_offset + ifd - self.offset + 4
bgneal@1 1179 if self.fake_exif:
bgneal@1 1180 offset = offset + 18
bgneal@1 1181 else:
bgneal@1 1182 offset = self.s2n(offset, 4)
bgneal@1 1183 field_offset = offset
bgneal@1 1184 if field_type == 2:
bgneal@1 1185 # special case: null-terminated ASCII string
bgneal@1 1186 if count != 0:
bgneal@1 1187 self.file.seek(self.offset + offset)
bgneal@1 1188 values = self.file.read(count)
bgneal@1 1189 values = values.strip().replace('\x00', '')
bgneal@1 1190 else:
bgneal@1 1191 values = ''
bgneal@1 1192 else:
bgneal@1 1193 values = []
bgneal@1 1194 signed = (field_type in [6, 8, 9, 10])
bgneal@1 1195 for dummy in range(count):
bgneal@1 1196 if field_type in (5, 10):
bgneal@1 1197 # a ratio
bgneal@1 1198 value = Ratio(self.s2n(offset, 4, signed),
bgneal@1 1199 self.s2n(offset + 4, 4, signed))
bgneal@1 1200 else:
bgneal@1 1201 value = self.s2n(offset, typelen, signed)
bgneal@1 1202 values.append(value)
bgneal@1 1203 offset = offset + typelen
bgneal@1 1204 # now "values" is either a string or an array
bgneal@1 1205 if count == 1 and field_type != 2:
bgneal@1 1206 printable=str(values[0])
bgneal@1 1207 else:
bgneal@1 1208 printable=str(values)
bgneal@1 1209 # compute printable version of values
bgneal@1 1210 if tag_entry:
bgneal@1 1211 if len(tag_entry) != 1:
bgneal@1 1212 # optional 2nd tag element is present
bgneal@1 1213 if callable(tag_entry[1]):
bgneal@1 1214 # call mapping function
bgneal@1 1215 printable = tag_entry[1](values)
bgneal@1 1216 else:
bgneal@1 1217 printable = ''
bgneal@1 1218 for i in values:
bgneal@1 1219 # use lookup table for this tag
bgneal@1 1220 printable += tag_entry[1].get(i, repr(i))
bgneal@1 1221
bgneal@1 1222 self.tags[ifd_name + ' ' + tag_name] = IFD_Tag(printable, tag,
bgneal@1 1223 field_type,
bgneal@1 1224 values, field_offset,
bgneal@1 1225 count * typelen)
bgneal@1 1226 if self.debug:
bgneal@1 1227 print ' debug: %s: %s' % (tag_name,
bgneal@1 1228 repr(self.tags[ifd_name + ' ' + tag_name]))
bgneal@1 1229
bgneal@1 1230 if tag_name == stop_tag:
bgneal@1 1231 break
bgneal@1 1232
bgneal@1 1233 # extract uncompressed TIFF thumbnail (like pulling teeth)
bgneal@1 1234 # we take advantage of the pre-existing layout in the thumbnail IFD as
bgneal@1 1235 # much as possible
bgneal@1 1236 def extract_TIFF_thumbnail(self, thumb_ifd):
bgneal@1 1237 entries = self.s2n(thumb_ifd, 2)
bgneal@1 1238 # this is header plus offset to IFD ...
bgneal@1 1239 if self.endian == 'M':
bgneal@1 1240 tiff = 'MM\x00*\x00\x00\x00\x08'
bgneal@1 1241 else:
bgneal@1 1242 tiff = 'II*\x00\x08\x00\x00\x00'
bgneal@1 1243 # ... plus thumbnail IFD data plus a null "next IFD" pointer
bgneal@1 1244 self.file.seek(self.offset+thumb_ifd)
bgneal@1 1245 tiff += self.file.read(entries*12+2)+'\x00\x00\x00\x00'
bgneal@1 1246
bgneal@1 1247 # fix up large value offset pointers into data area
bgneal@1 1248 for i in range(entries):
bgneal@1 1249 entry = thumb_ifd + 2 + 12 * i
bgneal@1 1250 tag = self.s2n(entry, 2)
bgneal@1 1251 field_type = self.s2n(entry+2, 2)
bgneal@1 1252 typelen = FIELD_TYPES[field_type][0]
bgneal@1 1253 count = self.s2n(entry+4, 4)
bgneal@1 1254 oldoff = self.s2n(entry+8, 4)
bgneal@1 1255 # start of the 4-byte pointer area in entry
bgneal@1 1256 ptr = i * 12 + 18
bgneal@1 1257 # remember strip offsets location
bgneal@1 1258 if tag == 0x0111:
bgneal@1 1259 strip_off = ptr
bgneal@1 1260 strip_len = count * typelen
bgneal@1 1261 # is it in the data area?
bgneal@1 1262 if count * typelen > 4:
bgneal@1 1263 # update offset pointer (nasty "strings are immutable" crap)
bgneal@1 1264 # should be able to say "tiff[ptr:ptr+4]=newoff"
bgneal@1 1265 newoff = len(tiff)
bgneal@1 1266 tiff = tiff[:ptr] + self.n2s(newoff, 4) + tiff[ptr+4:]
bgneal@1 1267 # remember strip offsets location
bgneal@1 1268 if tag == 0x0111:
bgneal@1 1269 strip_off = newoff
bgneal@1 1270 strip_len = 4
bgneal@1 1271 # get original data and store it
bgneal@1 1272 self.file.seek(self.offset + oldoff)
bgneal@1 1273 tiff += self.file.read(count * typelen)
bgneal@1 1274
bgneal@1 1275 # add pixel strips and update strip offset info
bgneal@1 1276 old_offsets = self.tags['Thumbnail StripOffsets'].values
bgneal@1 1277 old_counts = self.tags['Thumbnail StripByteCounts'].values
bgneal@1 1278 for i in range(len(old_offsets)):
bgneal@1 1279 # update offset pointer (more nasty "strings are immutable" crap)
bgneal@1 1280 offset = self.n2s(len(tiff), strip_len)
bgneal@1 1281 tiff = tiff[:strip_off] + offset + tiff[strip_off + strip_len:]
bgneal@1 1282 strip_off += strip_len
bgneal@1 1283 # add pixel strip to end
bgneal@1 1284 self.file.seek(self.offset + old_offsets[i])
bgneal@1 1285 tiff += self.file.read(old_counts[i])
bgneal@1 1286
bgneal@1 1287 self.tags['TIFFThumbnail'] = tiff
bgneal@1 1288
bgneal@1 1289 # decode all the camera-specific MakerNote formats
bgneal@1 1290
bgneal@1 1291 # Note is the data that comprises this MakerNote. The MakerNote will
bgneal@1 1292 # likely have pointers in it that point to other parts of the file. We'll
bgneal@1 1293 # use self.offset as the starting point for most of those pointers, since
bgneal@1 1294 # they are relative to the beginning of the file.
bgneal@1 1295 #
bgneal@1 1296 # If the MakerNote is in a newer format, it may use relative addressing
bgneal@1 1297 # within the MakerNote. In that case we'll use relative addresses for the
bgneal@1 1298 # pointers.
bgneal@1 1299 #
bgneal@1 1300 # As an aside: it's not just to be annoying that the manufacturers use
bgneal@1 1301 # relative offsets. It's so that if the makernote has to be moved by the
bgneal@1 1302 # picture software all of the offsets don't have to be adjusted. Overall,
bgneal@1 1303 # this is probably the right strategy for makernotes, though the spec is
bgneal@1 1304 # ambiguous. (The spec does not appear to imagine that makernotes would
bgneal@1 1305 # follow EXIF format internally. Once they did, it's ambiguous whether
bgneal@1 1306 # the offsets should be from the header at the start of all the EXIF info,
bgneal@1 1307 # or from the header at the start of the makernote.)
bgneal@1 1308 def decode_maker_note(self):
bgneal@1 1309 note = self.tags['EXIF MakerNote']
bgneal@1 1310 make = self.tags['Image Make'].printable
bgneal@1 1311 # model = self.tags['Image Model'].printable # unused
bgneal@1 1312
bgneal@1 1313 # Nikon
bgneal@1 1314 # The maker note usually starts with the word Nikon, followed by the
bgneal@1 1315 # type of the makernote (1 or 2, as a short). If the word Nikon is
bgneal@1 1316 # not at the start of the makernote, it's probably type 2, since some
bgneal@1 1317 # cameras work that way.
bgneal@1 1318 if make in ('NIKON', 'NIKON CORPORATION'):
bgneal@1 1319 if note.values[0:7] == [78, 105, 107, 111, 110, 0, 1]:
bgneal@1 1320 if self.debug:
bgneal@1 1321 print "Looks like a type 1 Nikon MakerNote."
bgneal@1 1322 self.dump_IFD(note.field_offset+8, 'MakerNote',
bgneal@1 1323 dict=MAKERNOTE_NIKON_OLDER_TAGS)
bgneal@1 1324 elif note.values[0:7] == [78, 105, 107, 111, 110, 0, 2]:
bgneal@1 1325 if self.debug:
bgneal@1 1326 print "Looks like a labeled type 2 Nikon MakerNote"
bgneal@1 1327 if note.values[12:14] != [0, 42] and note.values[12:14] != [42L, 0L]:
bgneal@1 1328 raise ValueError("Missing marker tag '42' in MakerNote.")
bgneal@1 1329 # skip the Makernote label and the TIFF header
bgneal@1 1330 self.dump_IFD(note.field_offset+10+8, 'MakerNote',
bgneal@1 1331 dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1)
bgneal@1 1332 else:
bgneal@1 1333 # E99x or D1
bgneal@1 1334 if self.debug:
bgneal@1 1335 print "Looks like an unlabeled type 2 Nikon MakerNote"
bgneal@1 1336 self.dump_IFD(note.field_offset, 'MakerNote',
bgneal@1 1337 dict=MAKERNOTE_NIKON_NEWER_TAGS)
bgneal@1 1338 return
bgneal@1 1339
bgneal@1 1340 # Olympus
bgneal@1 1341 if make.startswith('OLYMPUS'):
bgneal@1 1342 self.dump_IFD(note.field_offset+8, 'MakerNote',
bgneal@1 1343 dict=MAKERNOTE_OLYMPUS_TAGS)
bgneal@1 1344 # TODO
bgneal@1 1345 #for i in (('MakerNote Tag 0x2020', MAKERNOTE_OLYMPUS_TAG_0x2020),):
bgneal@1 1346 # self.decode_olympus_tag(self.tags[i[0]].values, i[1])
bgneal@1 1347 #return
bgneal@1 1348
bgneal@1 1349 # Casio
bgneal@1 1350 if make == 'Casio':
bgneal@1 1351 self.dump_IFD(note.field_offset, 'MakerNote',
bgneal@1 1352 dict=MAKERNOTE_CASIO_TAGS)
bgneal@1 1353 return
bgneal@1 1354
bgneal@1 1355 # Fujifilm
bgneal@1 1356 if make == 'FUJIFILM':
bgneal@1 1357 # bug: everything else is "Motorola" endian, but the MakerNote
bgneal@1 1358 # is "Intel" endian
bgneal@1 1359 endian = self.endian
bgneal@1 1360 self.endian = 'I'
bgneal@1 1361 # bug: IFD offsets are from beginning of MakerNote, not
bgneal@1 1362 # beginning of file header
bgneal@1 1363 offset = self.offset
bgneal@1 1364 self.offset += note.field_offset
bgneal@1 1365 # process note with bogus values (note is actually at offset 12)
bgneal@1 1366 self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS)
bgneal@1 1367 # reset to correct values
bgneal@1 1368 self.endian = endian
bgneal@1 1369 self.offset = offset
bgneal@1 1370 return
bgneal@1 1371
bgneal@1 1372 # Canon
bgneal@1 1373 if make == 'Canon':
bgneal@1 1374 self.dump_IFD(note.field_offset, 'MakerNote',
bgneal@1 1375 dict=MAKERNOTE_CANON_TAGS)
bgneal@1 1376 for i in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001),
bgneal@1 1377 ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004)):
bgneal@1 1378 self.canon_decode_tag(self.tags[i[0]].values, i[1])
bgneal@1 1379 return
bgneal@1 1380
bgneal@1 1381 # decode Olympus MakerNote tag based on offset within tag
bgneal@1 1382 def olympus_decode_tag(self, value, dict):
bgneal@1 1383 pass
bgneal@1 1384
bgneal@1 1385 # decode Canon MakerNote tag based on offset within tag
bgneal@1 1386 # see http://www.burren.cx/david/canon.html by David Burren
bgneal@1 1387 def canon_decode_tag(self, value, dict):
bgneal@1 1388 for i in range(1, len(value)):
bgneal@1 1389 x=dict.get(i, ('Unknown', ))
bgneal@1 1390 if self.debug:
bgneal@1 1391 print i, x
bgneal@1 1392 name=x[0]
bgneal@1 1393 if len(x) > 1:
bgneal@1 1394 val=x[1].get(value[i], 'Unknown')
bgneal@1 1395 else:
bgneal@1 1396 val=value[i]
bgneal@1 1397 # it's not a real IFD Tag but we fake one to make everybody
bgneal@1 1398 # happy. this will have a "proprietary" type
bgneal@1 1399 self.tags['MakerNote '+name]=IFD_Tag(str(val), None, 0, None,
bgneal@1 1400 None, None)
bgneal@1 1401
bgneal@1 1402 # process an image file (expects an open file object)
bgneal@1 1403 # this is the function that has to deal with all the arbitrary nasty bits
bgneal@1 1404 # of the EXIF standard
bgneal@1 1405 def process_file(f, stop_tag='UNDEF', details=True, debug=False):
bgneal@1 1406 # yah it's cheesy...
bgneal@1 1407 global detailed
bgneal@1 1408 detailed = details
bgneal@1 1409
bgneal@1 1410 # by default do not fake an EXIF beginning
bgneal@1 1411 fake_exif = 0
bgneal@1 1412
bgneal@1 1413 # determine whether it's a JPEG or TIFF
bgneal@1 1414 data = f.read(12)
bgneal@1 1415 if data[0:4] in ['II*\x00', 'MM\x00*']:
bgneal@1 1416 # it's a TIFF file
bgneal@1 1417 f.seek(0)
bgneal@1 1418 endian = f.read(1)
bgneal@1 1419 f.read(1)
bgneal@1 1420 offset = 0
bgneal@1 1421 elif data[0:2] == '\xFF\xD8':
bgneal@1 1422 # it's a JPEG file
bgneal@1 1423 while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'):
bgneal@1 1424 length = ord(data[4])*256+ord(data[5])
bgneal@1 1425 f.read(length-8)
bgneal@1 1426 # fake an EXIF beginning of file
bgneal@1 1427 data = '\xFF\x00'+f.read(10)
bgneal@1 1428 fake_exif = 1
bgneal@1 1429 if data[2] == '\xFF' and data[6:10] == 'Exif':
bgneal@1 1430 # detected EXIF header
bgneal@1 1431 offset = f.tell()
bgneal@1 1432 endian = f.read(1)
bgneal@1 1433 else:
bgneal@1 1434 # no EXIF information
bgneal@1 1435 return {}
bgneal@1 1436 else:
bgneal@1 1437 # file format not recognized
bgneal@1 1438 return {}
bgneal@1 1439
bgneal@1 1440 # deal with the EXIF info we found
bgneal@1 1441 if debug:
bgneal@1 1442 print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format'
bgneal@1 1443 hdr = EXIF_header(f, endian, offset, fake_exif, debug)
bgneal@1 1444 ifd_list = hdr.list_IFDs()
bgneal@1 1445 ctr = 0
bgneal@1 1446 for i in ifd_list:
bgneal@1 1447 if ctr == 0:
bgneal@1 1448 IFD_name = 'Image'
bgneal@1 1449 elif ctr == 1:
bgneal@1 1450 IFD_name = 'Thumbnail'
bgneal@1 1451 thumb_ifd = i
bgneal@1 1452 else:
bgneal@1 1453 IFD_name = 'IFD %d' % ctr
bgneal@1 1454 if debug:
bgneal@1 1455 print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i)
bgneal@1 1456 hdr.dump_IFD(i, IFD_name, stop_tag=stop_tag)
bgneal@1 1457 # EXIF IFD
bgneal@1 1458 exif_off = hdr.tags.get(IFD_name+' ExifOffset')
bgneal@1 1459 if exif_off:
bgneal@1 1460 if debug:
bgneal@1 1461 print ' EXIF SubIFD at offset %d:' % exif_off.values[0]
bgneal@1 1462 hdr.dump_IFD(exif_off.values[0], 'EXIF', stop_tag=stop_tag)
bgneal@1 1463 # Interoperability IFD contained in EXIF IFD
bgneal@1 1464 intr_off = hdr.tags.get('EXIF SubIFD InteroperabilityOffset')
bgneal@1 1465 if intr_off:
bgneal@1 1466 if debug:
bgneal@1 1467 print ' EXIF Interoperability SubSubIFD at offset %d:' \
bgneal@1 1468 % intr_off.values[0]
bgneal@1 1469 hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability',
bgneal@1 1470 dict=INTR_TAGS, stop_tag=stop_tag)
bgneal@1 1471 # GPS IFD
bgneal@1 1472 gps_off = hdr.tags.get(IFD_name+' GPSInfo')
bgneal@1 1473 if gps_off:
bgneal@1 1474 if debug:
bgneal@1 1475 print ' GPS SubIFD at offset %d:' % gps_off.values[0]
bgneal@1 1476 hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS, stop_tag=stop_tag)
bgneal@1 1477 ctr += 1
bgneal@1 1478
bgneal@1 1479 # extract uncompressed TIFF thumbnail
bgneal@1 1480 thumb = hdr.tags.get('Thumbnail Compression')
bgneal@1 1481 if thumb and thumb.printable == 'Uncompressed TIFF':
bgneal@1 1482 hdr.extract_TIFF_thumbnail(thumb_ifd)
bgneal@1 1483
bgneal@1 1484 # JPEG thumbnail (thankfully the JPEG data is stored as a unit)
bgneal@1 1485 thumb_off = hdr.tags.get('Thumbnail JPEGInterchangeFormat')
bgneal@1 1486 if thumb_off:
bgneal@1 1487 f.seek(offset+thumb_off.values[0])
bgneal@1 1488 size = hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0]
bgneal@1 1489 hdr.tags['JPEGThumbnail'] = f.read(size)
bgneal@1 1490
bgneal@1 1491 # deal with MakerNote contained in EXIF IFD
bgneal@1 1492 if 'EXIF MakerNote' in hdr.tags and detailed:
bgneal@1 1493 hdr.decode_maker_note()
bgneal@1 1494
bgneal@1 1495 # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote
bgneal@1 1496 # since it's not allowed in a uncompressed TIFF IFD
bgneal@1 1497 if 'JPEGThumbnail' not in hdr.tags:
bgneal@1 1498 thumb_off=hdr.tags.get('MakerNote JPEGThumbnail')
bgneal@1 1499 if thumb_off:
bgneal@1 1500 f.seek(offset+thumb_off.values[0])
bgneal@1 1501 hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length)
bgneal@1 1502
bgneal@1 1503 return hdr.tags
bgneal@1 1504
bgneal@1 1505
bgneal@1 1506 # show command line usage
bgneal@1 1507 def usage(exit_status):
bgneal@1 1508 msg = 'Usage: EXIF.py [OPTIONS] file1 [file2 ...]\n'
bgneal@1 1509 msg += 'Extract EXIF information from digital camera image files.\n\nOptions:\n'
bgneal@1 1510 msg += '-q --quick Do not process MakerNotes.\n'
bgneal@1 1511 msg += '-t TAG --stop-tag TAG Stop processing when this tag is retrieved.\n'
bgneal@1 1512 msg += '-d --debug Run in debug mode.\n'
bgneal@1 1513 print msg
bgneal@1 1514 sys.exit(exit_status)
bgneal@1 1515
bgneal@1 1516 # library test/debug function (dump given files)
bgneal@1 1517 if __name__ == '__main__':
bgneal@1 1518 import sys
bgneal@1 1519 import getopt
bgneal@1 1520
bgneal@1 1521 # parse command line options/arguments
bgneal@1 1522 try:
bgneal@1 1523 opts, args = getopt.getopt(sys.argv[1:], "hqdt:v", ["help", "quick", "debug", "stop-tag="])
bgneal@1 1524 except getopt.GetoptError:
bgneal@1 1525 usage(2)
bgneal@1 1526 if args == []:
bgneal@1 1527 usage(2)
bgneal@1 1528 detailed = True
bgneal@1 1529 stop_tag = 'UNDEF'
bgneal@1 1530 debug = False
bgneal@1 1531 for o, a in opts:
bgneal@1 1532 if o in ("-h", "--help"):
bgneal@1 1533 usage(0)
bgneal@1 1534 if o in ("-q", "--quick"):
bgneal@1 1535 detailed = False
bgneal@1 1536 if o in ("-t", "--stop-tag"):
bgneal@1 1537 stop_tag = a
bgneal@1 1538 if o in ("-d", "--debug"):
bgneal@1 1539 debug = True
bgneal@1 1540
bgneal@1 1541 # output info for each file
bgneal@1 1542 for filename in args:
bgneal@1 1543 try:
bgneal@1 1544 file=open(filename, 'rb')
bgneal@1 1545 except:
bgneal@1 1546 print "'%s' is unreadable\n"%filename
bgneal@1 1547 continue
bgneal@1 1548 print filename + ':'
bgneal@1 1549 # get the tags
bgneal@1 1550 data = process_file(file, stop_tag=stop_tag, details=detailed, debug=debug)
bgneal@1 1551 if not data:
bgneal@1 1552 print 'No EXIF information found'
bgneal@1 1553 continue
bgneal@1 1554
bgneal@1 1555 x=data.keys()
bgneal@1 1556 x.sort()
bgneal@1 1557 for i in x:
bgneal@1 1558 if i in ('JPEGThumbnail', 'TIFFThumbnail'):
bgneal@1 1559 continue
bgneal@1 1560 try:
bgneal@1 1561 print ' %s (%s): %s' % \
bgneal@1 1562 (i, FIELD_TYPES[data[i].field_type][2], data[i].printable)
bgneal@1 1563 except:
bgneal@1 1564 print 'error', i, '"', data[i], '"'
bgneal@1 1565 if 'JPEGThumbnail' in data:
bgneal@1 1566 print 'File has JPEG thumbnail'
bgneal@1 1567 print
bgneal@1 1568