annotate photologue/utils/EXIF.py @ 203:075cc90f6f86

Update sizes on t-shirts.
author Brian Neal <bgneal@gmail.com>
date Mon, 15 Feb 2021 13:40:15 -0600
parents e2868ad47a1e
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