Package elisa :: Package extern :: Module EXIF
[hide private]
[frames] | no frames]

Source Code for Module elisa.extern.EXIF

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