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