From b0642a7fcb859d8173e9125b4fbc801e5ce50949 Mon Sep 17 00:00:00 2001 From: Kirill Kuzminykh Date: Thu, 23 Jan 2020 15:49:16 +0300 Subject: [PATCH 1/5] - Added new 'TAG' with code '-1' and name '_errors' to prevent some exceptions during parse of corrupted EXIF. - Fixed exception in ``_ExifReader.get_ifd_dict()`` during parse of EXIF with invalid IFD pointers. Instead it this method returns dictionary with '_errors' key that has description of error. --- doc/changes.rst | 9 + piexif/_exif.py | 614 +++++++++++++++++++------------------- piexif/_load.py | 22 +- tests/images/bad_exif.bin | Bin 0 -> 926 bytes tests/s_test.py | 42 ++- 5 files changed, 375 insertions(+), 312 deletions(-) create mode 100644 tests/images/bad_exif.bin diff --git a/doc/changes.rst b/doc/changes.rst index 12a06f7..8d71cd6 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -1,6 +1,15 @@ Changelog ========= +Unreleased +---------- + +- Added new ``TAG`` with code ``-1`` and name ``_errors`` to prevent some + exceptions during parse of corrupted EXIF. +- Fixed exception in ``_ExifReader.get_ifd_dict()`` during parse of EXIF + with invalid IFD pointers. Instead it this method returns dictionary with + ``_errors`` key that has description of error. + 1.1.3 ----- diff --git a/piexif/_exif.py b/piexif/_exif.py index abdf0bc..9baddea 100644 --- a/piexif/_exif.py +++ b/piexif/_exif.py @@ -14,307 +14,319 @@ class TYPES: TAGS = { - 'Image': {11: {'name': 'ProcessingSoftware', 'type': TYPES.Ascii}, - 254: {'name': 'NewSubfileType', 'type': TYPES.Long}, - 255: {'name': 'SubfileType', 'type': TYPES.Short}, - 256: {'name': 'ImageWidth', 'type': TYPES.Long}, - 257: {'name': 'ImageLength', 'type': TYPES.Long}, - 258: {'name': 'BitsPerSample', 'type': TYPES.Short}, - 259: {'name': 'Compression', 'type': TYPES.Short}, - 262: {'name': 'PhotometricInterpretation', 'type': TYPES.Short}, - 263: {'name': 'Threshholding', 'type': TYPES.Short}, - 264: {'name': 'CellWidth', 'type': TYPES.Short}, - 265: {'name': 'CellLength', 'type': TYPES.Short}, - 266: {'name': 'FillOrder', 'type': TYPES.Short}, - 269: {'name': 'DocumentName', 'type': TYPES.Ascii}, - 270: {'name': 'ImageDescription', 'type': TYPES.Ascii}, - 271: {'name': 'Make', 'type': TYPES.Ascii}, - 272: {'name': 'Model', 'type': TYPES.Ascii}, - 273: {'name': 'StripOffsets', 'type': TYPES.Long}, - 274: {'name': 'Orientation', 'type': TYPES.Short}, - 277: {'name': 'SamplesPerPixel', 'type': TYPES.Short}, - 278: {'name': 'RowsPerStrip', 'type': TYPES.Long}, - 279: {'name': 'StripByteCounts', 'type': TYPES.Long}, - 282: {'name': 'XResolution', 'type': TYPES.Rational}, - 283: {'name': 'YResolution', 'type': TYPES.Rational}, - 284: {'name': 'PlanarConfiguration', 'type': TYPES.Short}, - 290: {'name': 'GrayResponseUnit', 'type': TYPES.Short}, - 291: {'name': 'GrayResponseCurve', 'type': TYPES.Short}, - 292: {'name': 'T4Options', 'type': TYPES.Long}, - 293: {'name': 'T6Options', 'type': TYPES.Long}, - 296: {'name': 'ResolutionUnit', 'type': TYPES.Short}, - 301: {'name': 'TransferFunction', 'type': TYPES.Short}, - 305: {'name': 'Software', 'type': TYPES.Ascii}, - 306: {'name': 'DateTime', 'type': TYPES.Ascii}, - 315: {'name': 'Artist', 'type': TYPES.Ascii}, - 316: {'name': 'HostComputer', 'type': TYPES.Ascii}, - 317: {'name': 'Predictor', 'type': TYPES.Short}, - 318: {'name': 'WhitePoint', 'type': TYPES.Rational}, - 319: {'name': 'PrimaryChromaticities', 'type': TYPES.Rational}, - 320: {'name': 'ColorMap', 'type': TYPES.Short}, - 321: {'name': 'HalftoneHints', 'type': TYPES.Short}, - 322: {'name': 'TileWidth', 'type': TYPES.Short}, - 323: {'name': 'TileLength', 'type': TYPES.Short}, - 324: {'name': 'TileOffsets', 'type': TYPES.Short}, - 325: {'name': 'TileByteCounts', 'type': TYPES.Short}, - 330: {'name': 'SubIFDs', 'type': TYPES.Long}, - 332: {'name': 'InkSet', 'type': TYPES.Short}, - 333: {'name': 'InkNames', 'type': TYPES.Ascii}, - 334: {'name': 'NumberOfInks', 'type': TYPES.Short}, - 336: {'name': 'DotRange', 'type': TYPES.Byte}, - 337: {'name': 'TargetPrinter', 'type': TYPES.Ascii}, - 338: {'name': 'ExtraSamples', 'type': TYPES.Short}, - 339: {'name': 'SampleFormat', 'type': TYPES.Short}, - 340: {'name': 'SMinSampleValue', 'type': TYPES.Short}, - 341: {'name': 'SMaxSampleValue', 'type': TYPES.Short}, - 342: {'name': 'TransferRange', 'type': TYPES.Short}, - 343: {'name': 'ClipPath', 'type': TYPES.Byte}, - 344: {'name': 'XClipPathUnits', 'type': TYPES.Long}, - 345: {'name': 'YClipPathUnits', 'type': TYPES.Long}, - 346: {'name': 'Indexed', 'type': TYPES.Short}, - 347: {'name': 'JPEGTables', 'type': TYPES.Undefined}, - 351: {'name': 'OPIProxy', 'type': TYPES.Short}, - 512: {'name': 'JPEGProc', 'type': TYPES.Long}, - 513: {'name': 'JPEGInterchangeFormat', 'type': TYPES.Long}, - 514: {'name': 'JPEGInterchangeFormatLength', 'type': TYPES.Long}, - 515: {'name': 'JPEGRestartInterval', 'type': TYPES.Short}, - 517: {'name': 'JPEGLosslessPredictors', 'type': TYPES.Short}, - 518: {'name': 'JPEGPointTransforms', 'type': TYPES.Short}, - 519: {'name': 'JPEGQTables', 'type': TYPES.Long}, - 520: {'name': 'JPEGDCTables', 'type': TYPES.Long}, - 521: {'name': 'JPEGACTables', 'type': TYPES.Long}, - 529: {'name': 'YCbCrCoefficients', 'type': TYPES.Rational}, - 530: {'name': 'YCbCrSubSampling', 'type': TYPES.Short}, - 531: {'name': 'YCbCrPositioning', 'type': TYPES.Short}, - 532: {'name': 'ReferenceBlackWhite', 'type': TYPES.Rational}, - 700: {'name': 'XMLPacket', 'type': TYPES.Byte}, - 18246: {'name': 'Rating', 'type': TYPES.Short}, - 18249: {'name': 'RatingPercent', 'type': TYPES.Short}, - 32781: {'name': 'ImageID', 'type': TYPES.Ascii}, - 33421: {'name': 'CFARepeatPatternDim', 'type': TYPES.Short}, - 33422: {'name': 'CFAPattern', 'type': TYPES.Byte}, - 33423: {'name': 'BatteryLevel', 'type': TYPES.Rational}, - 33432: {'name': 'Copyright', 'type': TYPES.Ascii}, - 33434: {'name': 'ExposureTime', 'type': TYPES.Rational}, - 34377: {'name': 'ImageResources', 'type': TYPES.Byte}, - 34665: {'name': 'ExifTag', 'type': TYPES.Long}, - 34675: {'name': 'InterColorProfile', 'type': TYPES.Undefined}, - 34853: {'name': 'GPSTag', 'type': TYPES.Long}, - 34857: {'name': 'Interlace', 'type': TYPES.Short}, - 34858: {'name': 'TimeZoneOffset', 'type': TYPES.Long}, - 34859: {'name': 'SelfTimerMode', 'type': TYPES.Short}, - 37387: {'name': 'FlashEnergy', 'type': TYPES.Rational}, - 37388: {'name': 'SpatialFrequencyResponse', 'type': TYPES.Undefined}, - 37389: {'name': 'Noise', 'type': TYPES.Undefined}, - 37390: {'name': 'FocalPlaneXResolution', 'type': TYPES.Rational}, - 37391: {'name': 'FocalPlaneYResolution', 'type': TYPES.Rational}, - 37392: {'name': 'FocalPlaneResolutionUnit', 'type': TYPES.Short}, - 37393: {'name': 'ImageNumber', 'type': TYPES.Long}, - 37394: {'name': 'SecurityClassification', 'type': TYPES.Ascii}, - 37395: {'name': 'ImageHistory', 'type': TYPES.Ascii}, - 37397: {'name': 'ExposureIndex', 'type': TYPES.Rational}, - 37398: {'name': 'TIFFEPStandardID', 'type': TYPES.Byte}, - 37399: {'name': 'SensingMethod', 'type': TYPES.Short}, - 40091: {'name': 'XPTitle', 'type': TYPES.Byte}, - 40092: {'name': 'XPComment', 'type': TYPES.Byte}, - 40093: {'name': 'XPAuthor', 'type': TYPES.Byte}, - 40094: {'name': 'XPKeywords', 'type': TYPES.Byte}, - 40095: {'name': 'XPSubject', 'type': TYPES.Byte}, - 50341: {'name': 'PrintImageMatching', 'type': TYPES.Undefined}, - 50706: {'name': 'DNGVersion', 'type': TYPES.Byte}, - 50707: {'name': 'DNGBackwardVersion', 'type': TYPES.Byte}, - 50708: {'name': 'UniqueCameraModel', 'type': TYPES.Ascii}, - 50709: {'name': 'LocalizedCameraModel', 'type': TYPES.Byte}, - 50710: {'name': 'CFAPlaneColor', 'type': TYPES.Byte}, - 50711: {'name': 'CFALayout', 'type': TYPES.Short}, - 50712: {'name': 'LinearizationTable', 'type': TYPES.Short}, - 50713: {'name': 'BlackLevelRepeatDim', 'type': TYPES.Short}, - 50714: {'name': 'BlackLevel', 'type': TYPES.Rational}, - 50715: {'name': 'BlackLevelDeltaH', 'type': TYPES.SRational}, - 50716: {'name': 'BlackLevelDeltaV', 'type': TYPES.SRational}, - 50717: {'name': 'WhiteLevel', 'type': TYPES.Short}, - 50718: {'name': 'DefaultScale', 'type': TYPES.Rational}, - 50719: {'name': 'DefaultCropOrigin', 'type': TYPES.Short}, - 50720: {'name': 'DefaultCropSize', 'type': TYPES.Short}, - 50721: {'name': 'ColorMatrix1', 'type': TYPES.SRational}, - 50722: {'name': 'ColorMatrix2', 'type': TYPES.SRational}, - 50723: {'name': 'CameraCalibration1', 'type': TYPES.SRational}, - 50724: {'name': 'CameraCalibration2', 'type': TYPES.SRational}, - 50725: {'name': 'ReductionMatrix1', 'type': TYPES.SRational}, - 50726: {'name': 'ReductionMatrix2', 'type': TYPES.SRational}, - 50727: {'name': 'AnalogBalance', 'type': TYPES.Rational}, - 50728: {'name': 'AsShotNeutral', 'type': TYPES.Short}, - 50729: {'name': 'AsShotWhiteXY', 'type': TYPES.Rational}, - 50730: {'name': 'BaselineExposure', 'type': TYPES.SRational}, - 50731: {'name': 'BaselineNoise', 'type': TYPES.Rational}, - 50732: {'name': 'BaselineSharpness', 'type': TYPES.Rational}, - 50733: {'name': 'BayerGreenSplit', 'type': TYPES.Long}, - 50734: {'name': 'LinearResponseLimit', 'type': TYPES.Rational}, - 50735: {'name': 'CameraSerialNumber', 'type': TYPES.Ascii}, - 50736: {'name': 'LensInfo', 'type': TYPES.Rational}, - 50737: {'name': 'ChromaBlurRadius', 'type': TYPES.Rational}, - 50738: {'name': 'AntiAliasStrength', 'type': TYPES.Rational}, - 50739: {'name': 'ShadowScale', 'type': TYPES.SRational}, - 50740: {'name': 'DNGPrivateData', 'type': TYPES.Byte}, - 50741: {'name': 'MakerNoteSafety', 'type': TYPES.Short}, - 50778: {'name': 'CalibrationIlluminant1', 'type': TYPES.Short}, - 50779: {'name': 'CalibrationIlluminant2', 'type': TYPES.Short}, - 50780: {'name': 'BestQualityScale', 'type': TYPES.Rational}, - 50781: {'name': 'RawDataUniqueID', 'type': TYPES.Byte}, - 50827: {'name': 'OriginalRawFileName', 'type': TYPES.Byte}, - 50828: {'name': 'OriginalRawFileData', 'type': TYPES.Undefined}, - 50829: {'name': 'ActiveArea', 'type': TYPES.Short}, - 50830: {'name': 'MaskedAreas', 'type': TYPES.Short}, - 50831: {'name': 'AsShotICCProfile', 'type': TYPES.Undefined}, - 50832: {'name': 'AsShotPreProfileMatrix', 'type': TYPES.SRational}, - 50833: {'name': 'CurrentICCProfile', 'type': TYPES.Undefined}, - 50834: {'name': 'CurrentPreProfileMatrix', 'type': TYPES.SRational}, - 50879: {'name': 'ColorimetricReference', 'type': TYPES.Short}, - 50931: {'name': 'CameraCalibrationSignature', 'type': TYPES.Byte}, - 50932: {'name': 'ProfileCalibrationSignature', 'type': TYPES.Byte}, - 50934: {'name': 'AsShotProfileName', 'type': TYPES.Byte}, - 50935: {'name': 'NoiseReductionApplied', 'type': TYPES.Rational}, - 50936: {'name': 'ProfileName', 'type': TYPES.Byte}, - 50937: {'name': 'ProfileHueSatMapDims', 'type': TYPES.Long}, - 50938: {'name': 'ProfileHueSatMapData1', 'type': TYPES.Float}, - 50939: {'name': 'ProfileHueSatMapData2', 'type': TYPES.Float}, - 50940: {'name': 'ProfileToneCurve', 'type': TYPES.Float}, - 50941: {'name': 'ProfileEmbedPolicy', 'type': TYPES.Long}, - 50942: {'name': 'ProfileCopyright', 'type': TYPES.Byte}, - 50964: {'name': 'ForwardMatrix1', 'type': TYPES.SRational}, - 50965: {'name': 'ForwardMatrix2', 'type': TYPES.SRational}, - 50966: {'name': 'PreviewApplicationName', 'type': TYPES.Byte}, - 50967: {'name': 'PreviewApplicationVersion', 'type': TYPES.Byte}, - 50968: {'name': 'PreviewSettingsName', 'type': TYPES.Byte}, - 50969: {'name': 'PreviewSettingsDigest', 'type': TYPES.Byte}, - 50970: {'name': 'PreviewColorSpace', 'type': TYPES.Long}, - 50971: {'name': 'PreviewDateTime', 'type': TYPES.Ascii}, - 50972: {'name': 'RawImageDigest', 'type': TYPES.Undefined}, - 50973: {'name': 'OriginalRawFileDigest', 'type': TYPES.Undefined}, - 50974: {'name': 'SubTileBlockSize', 'type': TYPES.Long}, - 50975: {'name': 'RowInterleaveFactor', 'type': TYPES.Long}, - 50981: {'name': 'ProfileLookTableDims', 'type': TYPES.Long}, - 50982: {'name': 'ProfileLookTableData', 'type': TYPES.Float}, - 51008: {'name': 'OpcodeList1', 'type': TYPES.Undefined}, - 51009: {'name': 'OpcodeList2', 'type': TYPES.Undefined}, - 51022: {'name': 'OpcodeList3', 'type': TYPES.Undefined}, - 60606: {'name': 'ZZZTestSlong1', 'type': TYPES.SLong}, - 60607: {'name': 'ZZZTestSlong2', 'type': TYPES.SLong}, - 60608: {'name': 'ZZZTestSByte', 'type': TYPES.SByte}, - 60609: {'name': 'ZZZTestSShort', 'type': TYPES.SShort}, - 60610: {'name': 'ZZZTestDFloat', 'type': TYPES.DFloat},}, - 'Exif': {33434: {'name': 'ExposureTime', 'type': TYPES.Rational}, - 33437: {'name': 'FNumber', 'type': TYPES.Rational}, - 34850: {'name': 'ExposureProgram', 'type': TYPES.Short}, - 34852: {'name': 'SpectralSensitivity', 'type': TYPES.Ascii}, - 34855: {'name': 'ISOSpeedRatings', 'type': TYPES.Short}, - 34856: {'name': 'OECF', 'type': TYPES.Undefined}, - 34864: {'name': 'SensitivityType', 'type': TYPES.Short}, - 34865: {'name': 'StandardOutputSensitivity', 'type': TYPES.Long}, - 34866: {'name': 'RecommendedExposureIndex', 'type': TYPES.Long}, - 34867: {'name': 'ISOSpeed', 'type': TYPES.Long}, - 34868: {'name': 'ISOSpeedLatitudeyyy', 'type': TYPES.Long}, - 34869: {'name': 'ISOSpeedLatitudezzz', 'type': TYPES.Long}, - 36864: {'name': 'ExifVersion', 'type': TYPES.Undefined}, - 36867: {'name': 'DateTimeOriginal', 'type': TYPES.Ascii}, - 36868: {'name': 'DateTimeDigitized', 'type': TYPES.Ascii}, - 36880: {'name': 'OffsetTime', 'type': TYPES.Ascii}, - 36881: {'name': 'OffsetTimeOriginal', 'type': TYPES.Ascii}, - 36882: {'name': 'OffsetTimeDigitized', 'type': TYPES.Ascii}, - 37121: {'name': 'ComponentsConfiguration', 'type': TYPES.Undefined}, - 37122: {'name': 'CompressedBitsPerPixel', 'type': TYPES.Rational}, - 37377: {'name': 'ShutterSpeedValue', 'type': TYPES.SRational}, - 37378: {'name': 'ApertureValue', 'type': TYPES.Rational}, - 37379: {'name': 'BrightnessValue', 'type': TYPES.SRational}, - 37380: {'name': 'ExposureBiasValue', 'type': TYPES.SRational}, - 37381: {'name': 'MaxApertureValue', 'type': TYPES.Rational}, - 37382: {'name': 'SubjectDistance', 'type': TYPES.Rational}, - 37383: {'name': 'MeteringMode', 'type': TYPES.Short}, - 37384: {'name': 'LightSource', 'type': TYPES.Short}, - 37385: {'name': 'Flash', 'type': TYPES.Short}, - 37386: {'name': 'FocalLength', 'type': TYPES.Rational}, - 37396: {'name': 'SubjectArea', 'type': TYPES.Short}, - 37500: {'name': 'MakerNote', 'type': TYPES.Undefined}, - 37510: {'name': 'UserComment', 'type': TYPES.Undefined}, - 37520: {'name': 'SubSecTime', 'type': TYPES.Ascii}, - 37521: {'name': 'SubSecTimeOriginal', 'type': TYPES.Ascii}, - 37522: {'name': 'SubSecTimeDigitized', 'type': TYPES.Ascii}, - 37888: {'name': 'Temperature', 'type': TYPES.SRational}, - 37889: {'name': 'Humidity', 'type': TYPES.Rational}, - 37890: {'name': 'Pressure', 'type': TYPES.Rational}, - 37891: {'name': 'WaterDepth', 'type': TYPES.SRational}, - 37892: {'name': 'Acceleration', 'type': TYPES.Rational}, - 37893: {'name': 'CameraElevationAngle', 'type': TYPES.SRational}, - 40960: {'name': 'FlashpixVersion', 'type': TYPES.Undefined}, - 40961: {'name': 'ColorSpace', 'type': TYPES.Short}, - 40962: {'name': 'PixelXDimension', 'type': TYPES.Long}, - 40963: {'name': 'PixelYDimension', 'type': TYPES.Long}, - 40964: {'name': 'RelatedSoundFile', 'type': TYPES.Ascii}, - 40965: {'name': 'InteroperabilityTag', 'type': TYPES.Long}, - 41483: {'name': 'FlashEnergy', 'type': TYPES.Rational}, - 41484: {'name': 'SpatialFrequencyResponse', 'type': TYPES.Undefined}, - 41486: {'name': 'FocalPlaneXResolution', 'type': TYPES.Rational}, - 41487: {'name': 'FocalPlaneYResolution', 'type': TYPES.Rational}, - 41488: {'name': 'FocalPlaneResolutionUnit', 'type': TYPES.Short}, - 41492: {'name': 'SubjectLocation', 'type': TYPES.Short}, - 41493: {'name': 'ExposureIndex', 'type': TYPES.Rational}, - 41495: {'name': 'SensingMethod', 'type': TYPES.Short}, - 41728: {'name': 'FileSource', 'type': TYPES.Undefined}, - 41729: {'name': 'SceneType', 'type': TYPES.Undefined}, - 41730: {'name': 'CFAPattern', 'type': TYPES.Undefined}, - 41985: {'name': 'CustomRendered', 'type': TYPES.Short}, - 41986: {'name': 'ExposureMode', 'type': TYPES.Short}, - 41987: {'name': 'WhiteBalance', 'type': TYPES.Short}, - 41988: {'name': 'DigitalZoomRatio', 'type': TYPES.Rational}, - 41989: {'name': 'FocalLengthIn35mmFilm', 'type': TYPES.Short}, - 41990: {'name': 'SceneCaptureType', 'type': TYPES.Short}, - 41991: {'name': 'GainControl', 'type': TYPES.Short}, - 41992: {'name': 'Contrast', 'type': TYPES.Short}, - 41993: {'name': 'Saturation', 'type': TYPES.Short}, - 41994: {'name': 'Sharpness', 'type': TYPES.Short}, - 41995: {'name': 'DeviceSettingDescription', 'type': TYPES.Undefined}, - 41996: {'name': 'SubjectDistanceRange', 'type': TYPES.Short}, - 42016: {'name': 'ImageUniqueID', 'type': TYPES.Ascii}, - 42032: {'name': 'CameraOwnerName', 'type': TYPES.Ascii}, - 42033: {'name': 'BodySerialNumber', 'type': TYPES.Ascii}, - 42034: {'name': 'LensSpecification', 'type': TYPES.Rational}, - 42035: {'name': 'LensMake', 'type': TYPES.Ascii}, - 42036: {'name': 'LensModel', 'type': TYPES.Ascii}, - 42037: {'name': 'LensSerialNumber', 'type': TYPES.Ascii}, - 42240: {'name': 'Gamma', 'type': TYPES.Rational}}, - 'GPS': {0: {'name': 'GPSVersionID', 'type': TYPES.Byte}, - 1: {'name': 'GPSLatitudeRef', 'type': TYPES.Ascii}, - 2: {'name': 'GPSLatitude', 'type': TYPES.Rational}, - 3: {'name': 'GPSLongitudeRef', 'type': TYPES.Ascii}, - 4: {'name': 'GPSLongitude', 'type': TYPES.Rational}, - 5: {'name': 'GPSAltitudeRef', 'type': TYPES.Byte}, - 6: {'name': 'GPSAltitude', 'type': TYPES.Rational}, - 7: {'name': 'GPSTimeStamp', 'type': TYPES.Rational}, - 8: {'name': 'GPSSatellites', 'type': TYPES.Ascii}, - 9: {'name': 'GPSStatus', 'type': TYPES.Ascii}, - 10: {'name': 'GPSMeasureMode', 'type': TYPES.Ascii}, - 11: {'name': 'GPSDOP', 'type': TYPES.Rational}, - 12: {'name': 'GPSSpeedRef', 'type': TYPES.Ascii}, - 13: {'name': 'GPSSpeed', 'type': TYPES.Rational}, - 14: {'name': 'GPSTrackRef', 'type': TYPES.Ascii}, - 15: {'name': 'GPSTrack', 'type': TYPES.Rational}, - 16: {'name': 'GPSImgDirectionRef', 'type': TYPES.Ascii}, - 17: {'name': 'GPSImgDirection', 'type': TYPES.Rational}, - 18: {'name': 'GPSMapDatum', 'type': TYPES.Ascii}, - 19: {'name': 'GPSDestLatitudeRef', 'type': TYPES.Ascii}, - 20: {'name': 'GPSDestLatitude', 'type': TYPES.Rational}, - 21: {'name': 'GPSDestLongitudeRef', 'type': TYPES.Ascii}, - 22: {'name': 'GPSDestLongitude', 'type': TYPES.Rational}, - 23: {'name': 'GPSDestBearingRef', 'type': TYPES.Ascii}, - 24: {'name': 'GPSDestBearing', 'type': TYPES.Rational}, - 25: {'name': 'GPSDestDistanceRef', 'type': TYPES.Ascii}, - 26: {'name': 'GPSDestDistance', 'type': TYPES.Rational}, - 27: {'name': 'GPSProcessingMethod', 'type': TYPES.Undefined}, - 28: {'name': 'GPSAreaInformation', 'type': TYPES.Undefined}, - 29: {'name': 'GPSDateStamp', 'type': TYPES.Ascii}, - 30: {'name': 'GPSDifferential', 'type': TYPES.Short}, - 31: {'name': 'GPSHPositioningError', 'type': TYPES.Rational}}, - 'Interop': {1: {'name': 'InteroperabilityIndex', 'type': TYPES.Ascii}}, + 'Image': { + -1: {'name': '_errors', 'type': TYPES.Ascii}, + 11: {'name': 'ProcessingSoftware', 'type': TYPES.Ascii}, + 254: {'name': 'NewSubfileType', 'type': TYPES.Long}, + 255: {'name': 'SubfileType', 'type': TYPES.Short}, + 256: {'name': 'ImageWidth', 'type': TYPES.Long}, + 257: {'name': 'ImageLength', 'type': TYPES.Long}, + 258: {'name': 'BitsPerSample', 'type': TYPES.Short}, + 259: {'name': 'Compression', 'type': TYPES.Short}, + 262: {'name': 'PhotometricInterpretation', 'type': TYPES.Short}, + 263: {'name': 'Threshholding', 'type': TYPES.Short}, + 264: {'name': 'CellWidth', 'type': TYPES.Short}, + 265: {'name': 'CellLength', 'type': TYPES.Short}, + 266: {'name': 'FillOrder', 'type': TYPES.Short}, + 269: {'name': 'DocumentName', 'type': TYPES.Ascii}, + 270: {'name': 'ImageDescription', 'type': TYPES.Ascii}, + 271: {'name': 'Make', 'type': TYPES.Ascii}, + 272: {'name': 'Model', 'type': TYPES.Ascii}, + 273: {'name': 'StripOffsets', 'type': TYPES.Long}, + 274: {'name': 'Orientation', 'type': TYPES.Short}, + 277: {'name': 'SamplesPerPixel', 'type': TYPES.Short}, + 278: {'name': 'RowsPerStrip', 'type': TYPES.Long}, + 279: {'name': 'StripByteCounts', 'type': TYPES.Long}, + 282: {'name': 'XResolution', 'type': TYPES.Rational}, + 283: {'name': 'YResolution', 'type': TYPES.Rational}, + 284: {'name': 'PlanarConfiguration', 'type': TYPES.Short}, + 290: {'name': 'GrayResponseUnit', 'type': TYPES.Short}, + 291: {'name': 'GrayResponseCurve', 'type': TYPES.Short}, + 292: {'name': 'T4Options', 'type': TYPES.Long}, + 293: {'name': 'T6Options', 'type': TYPES.Long}, + 296: {'name': 'ResolutionUnit', 'type': TYPES.Short}, + 301: {'name': 'TransferFunction', 'type': TYPES.Short}, + 305: {'name': 'Software', 'type': TYPES.Ascii}, + 306: {'name': 'DateTime', 'type': TYPES.Ascii}, + 315: {'name': 'Artist', 'type': TYPES.Ascii}, + 316: {'name': 'HostComputer', 'type': TYPES.Ascii}, + 317: {'name': 'Predictor', 'type': TYPES.Short}, + 318: {'name': 'WhitePoint', 'type': TYPES.Rational}, + 319: {'name': 'PrimaryChromaticities', 'type': TYPES.Rational}, + 320: {'name': 'ColorMap', 'type': TYPES.Short}, + 321: {'name': 'HalftoneHints', 'type': TYPES.Short}, + 322: {'name': 'TileWidth', 'type': TYPES.Short}, + 323: {'name': 'TileLength', 'type': TYPES.Short}, + 324: {'name': 'TileOffsets', 'type': TYPES.Short}, + 325: {'name': 'TileByteCounts', 'type': TYPES.Short}, + 330: {'name': 'SubIFDs', 'type': TYPES.Long}, + 332: {'name': 'InkSet', 'type': TYPES.Short}, + 333: {'name': 'InkNames', 'type': TYPES.Ascii}, + 334: {'name': 'NumberOfInks', 'type': TYPES.Short}, + 336: {'name': 'DotRange', 'type': TYPES.Byte}, + 337: {'name': 'TargetPrinter', 'type': TYPES.Ascii}, + 338: {'name': 'ExtraSamples', 'type': TYPES.Short}, + 339: {'name': 'SampleFormat', 'type': TYPES.Short}, + 340: {'name': 'SMinSampleValue', 'type': TYPES.Short}, + 341: {'name': 'SMaxSampleValue', 'type': TYPES.Short}, + 342: {'name': 'TransferRange', 'type': TYPES.Short}, + 343: {'name': 'ClipPath', 'type': TYPES.Byte}, + 344: {'name': 'XClipPathUnits', 'type': TYPES.Long}, + 345: {'name': 'YClipPathUnits', 'type': TYPES.Long}, + 346: {'name': 'Indexed', 'type': TYPES.Short}, + 347: {'name': 'JPEGTables', 'type': TYPES.Undefined}, + 351: {'name': 'OPIProxy', 'type': TYPES.Short}, + 512: {'name': 'JPEGProc', 'type': TYPES.Long}, + 513: {'name': 'JPEGInterchangeFormat', 'type': TYPES.Long}, + 514: {'name': 'JPEGInterchangeFormatLength', 'type': TYPES.Long}, + 515: {'name': 'JPEGRestartInterval', 'type': TYPES.Short}, + 517: {'name': 'JPEGLosslessPredictors', 'type': TYPES.Short}, + 518: {'name': 'JPEGPointTransforms', 'type': TYPES.Short}, + 519: {'name': 'JPEGQTables', 'type': TYPES.Long}, + 520: {'name': 'JPEGDCTables', 'type': TYPES.Long}, + 521: {'name': 'JPEGACTables', 'type': TYPES.Long}, + 529: {'name': 'YCbCrCoefficients', 'type': TYPES.Rational}, + 530: {'name': 'YCbCrSubSampling', 'type': TYPES.Short}, + 531: {'name': 'YCbCrPositioning', 'type': TYPES.Short}, + 532: {'name': 'ReferenceBlackWhite', 'type': TYPES.Rational}, + 700: {'name': 'XMLPacket', 'type': TYPES.Byte}, + 18246: {'name': 'Rating', 'type': TYPES.Short}, + 18249: {'name': 'RatingPercent', 'type': TYPES.Short}, + 32781: {'name': 'ImageID', 'type': TYPES.Ascii}, + 33421: {'name': 'CFARepeatPatternDim', 'type': TYPES.Short}, + 33422: {'name': 'CFAPattern', 'type': TYPES.Byte}, + 33423: {'name': 'BatteryLevel', 'type': TYPES.Rational}, + 33432: {'name': 'Copyright', 'type': TYPES.Ascii}, + 33434: {'name': 'ExposureTime', 'type': TYPES.Rational}, + 34377: {'name': 'ImageResources', 'type': TYPES.Byte}, + 34665: {'name': 'ExifTag', 'type': TYPES.Long}, + 34675: {'name': 'InterColorProfile', 'type': TYPES.Undefined}, + 34853: {'name': 'GPSTag', 'type': TYPES.Long}, + 34857: {'name': 'Interlace', 'type': TYPES.Short}, + 34858: {'name': 'TimeZoneOffset', 'type': TYPES.Long}, + 34859: {'name': 'SelfTimerMode', 'type': TYPES.Short}, + 37387: {'name': 'FlashEnergy', 'type': TYPES.Rational}, + 37388: {'name': 'SpatialFrequencyResponse', 'type': TYPES.Undefined}, + 37389: {'name': 'Noise', 'type': TYPES.Undefined}, + 37390: {'name': 'FocalPlaneXResolution', 'type': TYPES.Rational}, + 37391: {'name': 'FocalPlaneYResolution', 'type': TYPES.Rational}, + 37392: {'name': 'FocalPlaneResolutionUnit', 'type': TYPES.Short}, + 37393: {'name': 'ImageNumber', 'type': TYPES.Long}, + 37394: {'name': 'SecurityClassification', 'type': TYPES.Ascii}, + 37395: {'name': 'ImageHistory', 'type': TYPES.Ascii}, + 37397: {'name': 'ExposureIndex', 'type': TYPES.Rational}, + 37398: {'name': 'TIFFEPStandardID', 'type': TYPES.Byte}, + 37399: {'name': 'SensingMethod', 'type': TYPES.Short}, + 40091: {'name': 'XPTitle', 'type': TYPES.Byte}, + 40092: {'name': 'XPComment', 'type': TYPES.Byte}, + 40093: {'name': 'XPAuthor', 'type': TYPES.Byte}, + 40094: {'name': 'XPKeywords', 'type': TYPES.Byte}, + 40095: {'name': 'XPSubject', 'type': TYPES.Byte}, + 50341: {'name': 'PrintImageMatching', 'type': TYPES.Undefined}, + 50706: {'name': 'DNGVersion', 'type': TYPES.Byte}, + 50707: {'name': 'DNGBackwardVersion', 'type': TYPES.Byte}, + 50708: {'name': 'UniqueCameraModel', 'type': TYPES.Ascii}, + 50709: {'name': 'LocalizedCameraModel', 'type': TYPES.Byte}, + 50710: {'name': 'CFAPlaneColor', 'type': TYPES.Byte}, + 50711: {'name': 'CFALayout', 'type': TYPES.Short}, + 50712: {'name': 'LinearizationTable', 'type': TYPES.Short}, + 50713: {'name': 'BlackLevelRepeatDim', 'type': TYPES.Short}, + 50714: {'name': 'BlackLevel', 'type': TYPES.Rational}, + 50715: {'name': 'BlackLevelDeltaH', 'type': TYPES.SRational}, + 50716: {'name': 'BlackLevelDeltaV', 'type': TYPES.SRational}, + 50717: {'name': 'WhiteLevel', 'type': TYPES.Short}, + 50718: {'name': 'DefaultScale', 'type': TYPES.Rational}, + 50719: {'name': 'DefaultCropOrigin', 'type': TYPES.Short}, + 50720: {'name': 'DefaultCropSize', 'type': TYPES.Short}, + 50721: {'name': 'ColorMatrix1', 'type': TYPES.SRational}, + 50722: {'name': 'ColorMatrix2', 'type': TYPES.SRational}, + 50723: {'name': 'CameraCalibration1', 'type': TYPES.SRational}, + 50724: {'name': 'CameraCalibration2', 'type': TYPES.SRational}, + 50725: {'name': 'ReductionMatrix1', 'type': TYPES.SRational}, + 50726: {'name': 'ReductionMatrix2', 'type': TYPES.SRational}, + 50727: {'name': 'AnalogBalance', 'type': TYPES.Rational}, + 50728: {'name': 'AsShotNeutral', 'type': TYPES.Short}, + 50729: {'name': 'AsShotWhiteXY', 'type': TYPES.Rational}, + 50730: {'name': 'BaselineExposure', 'type': TYPES.SRational}, + 50731: {'name': 'BaselineNoise', 'type': TYPES.Rational}, + 50732: {'name': 'BaselineSharpness', 'type': TYPES.Rational}, + 50733: {'name': 'BayerGreenSplit', 'type': TYPES.Long}, + 50734: {'name': 'LinearResponseLimit', 'type': TYPES.Rational}, + 50735: {'name': 'CameraSerialNumber', 'type': TYPES.Ascii}, + 50736: {'name': 'LensInfo', 'type': TYPES.Rational}, + 50737: {'name': 'ChromaBlurRadius', 'type': TYPES.Rational}, + 50738: {'name': 'AntiAliasStrength', 'type': TYPES.Rational}, + 50739: {'name': 'ShadowScale', 'type': TYPES.SRational}, + 50740: {'name': 'DNGPrivateData', 'type': TYPES.Byte}, + 50741: {'name': 'MakerNoteSafety', 'type': TYPES.Short}, + 50778: {'name': 'CalibrationIlluminant1', 'type': TYPES.Short}, + 50779: {'name': 'CalibrationIlluminant2', 'type': TYPES.Short}, + 50780: {'name': 'BestQualityScale', 'type': TYPES.Rational}, + 50781: {'name': 'RawDataUniqueID', 'type': TYPES.Byte}, + 50827: {'name': 'OriginalRawFileName', 'type': TYPES.Byte}, + 50828: {'name': 'OriginalRawFileData', 'type': TYPES.Undefined}, + 50829: {'name': 'ActiveArea', 'type': TYPES.Short}, + 50830: {'name': 'MaskedAreas', 'type': TYPES.Short}, + 50831: {'name': 'AsShotICCProfile', 'type': TYPES.Undefined}, + 50832: {'name': 'AsShotPreProfileMatrix', 'type': TYPES.SRational}, + 50833: {'name': 'CurrentICCProfile', 'type': TYPES.Undefined}, + 50834: {'name': 'CurrentPreProfileMatrix', 'type': TYPES.SRational}, + 50879: {'name': 'ColorimetricReference', 'type': TYPES.Short}, + 50931: {'name': 'CameraCalibrationSignature', 'type': TYPES.Byte}, + 50932: {'name': 'ProfileCalibrationSignature', 'type': TYPES.Byte}, + 50934: {'name': 'AsShotProfileName', 'type': TYPES.Byte}, + 50935: {'name': 'NoiseReductionApplied', 'type': TYPES.Rational}, + 50936: {'name': 'ProfileName', 'type': TYPES.Byte}, + 50937: {'name': 'ProfileHueSatMapDims', 'type': TYPES.Long}, + 50938: {'name': 'ProfileHueSatMapData1', 'type': TYPES.Float}, + 50939: {'name': 'ProfileHueSatMapData2', 'type': TYPES.Float}, + 50940: {'name': 'ProfileToneCurve', 'type': TYPES.Float}, + 50941: {'name': 'ProfileEmbedPolicy', 'type': TYPES.Long}, + 50942: {'name': 'ProfileCopyright', 'type': TYPES.Byte}, + 50964: {'name': 'ForwardMatrix1', 'type': TYPES.SRational}, + 50965: {'name': 'ForwardMatrix2', 'type': TYPES.SRational}, + 50966: {'name': 'PreviewApplicationName', 'type': TYPES.Byte}, + 50967: {'name': 'PreviewApplicationVersion', 'type': TYPES.Byte}, + 50968: {'name': 'PreviewSettingsName', 'type': TYPES.Byte}, + 50969: {'name': 'PreviewSettingsDigest', 'type': TYPES.Byte}, + 50970: {'name': 'PreviewColorSpace', 'type': TYPES.Long}, + 50971: {'name': 'PreviewDateTime', 'type': TYPES.Ascii}, + 50972: {'name': 'RawImageDigest', 'type': TYPES.Undefined}, + 50973: {'name': 'OriginalRawFileDigest', 'type': TYPES.Undefined}, + 50974: {'name': 'SubTileBlockSize', 'type': TYPES.Long}, + 50975: {'name': 'RowInterleaveFactor', 'type': TYPES.Long}, + 50981: {'name': 'ProfileLookTableDims', 'type': TYPES.Long}, + 50982: {'name': 'ProfileLookTableData', 'type': TYPES.Float}, + 51008: {'name': 'OpcodeList1', 'type': TYPES.Undefined}, + 51009: {'name': 'OpcodeList2', 'type': TYPES.Undefined}, + 51022: {'name': 'OpcodeList3', 'type': TYPES.Undefined}, + 60606: {'name': 'ZZZTestSlong1', 'type': TYPES.SLong}, + 60607: {'name': 'ZZZTestSlong2', 'type': TYPES.SLong}, + 60608: {'name': 'ZZZTestSByte', 'type': TYPES.SByte}, + 60609: {'name': 'ZZZTestSShort', 'type': TYPES.SShort}, + 60610: {'name': 'ZZZTestDFloat', 'type': TYPES.DFloat}, + }, + 'Exif': { + -1: {'name': '_errors', 'type': TYPES.Ascii}, + 33434: {'name': 'ExposureTime', 'type': TYPES.Rational}, + 33437: {'name': 'FNumber', 'type': TYPES.Rational}, + 34850: {'name': 'ExposureProgram', 'type': TYPES.Short}, + 34852: {'name': 'SpectralSensitivity', 'type': TYPES.Ascii}, + 34855: {'name': 'ISOSpeedRatings', 'type': TYPES.Short}, + 34856: {'name': 'OECF', 'type': TYPES.Undefined}, + 34864: {'name': 'SensitivityType', 'type': TYPES.Short}, + 34865: {'name': 'StandardOutputSensitivity', 'type': TYPES.Long}, + 34866: {'name': 'RecommendedExposureIndex', 'type': TYPES.Long}, + 34867: {'name': 'ISOSpeed', 'type': TYPES.Long}, + 34868: {'name': 'ISOSpeedLatitudeyyy', 'type': TYPES.Long}, + 34869: {'name': 'ISOSpeedLatitudezzz', 'type': TYPES.Long}, + 36864: {'name': 'ExifVersion', 'type': TYPES.Undefined}, + 36867: {'name': 'DateTimeOriginal', 'type': TYPES.Ascii}, + 36868: {'name': 'DateTimeDigitized', 'type': TYPES.Ascii}, + 36880: {'name': 'OffsetTime', 'type': TYPES.Ascii}, + 36881: {'name': 'OffsetTimeOriginal', 'type': TYPES.Ascii}, + 36882: {'name': 'OffsetTimeDigitized', 'type': TYPES.Ascii}, + 37121: {'name': 'ComponentsConfiguration', 'type': TYPES.Undefined}, + 37122: {'name': 'CompressedBitsPerPixel', 'type': TYPES.Rational}, + 37377: {'name': 'ShutterSpeedValue', 'type': TYPES.SRational}, + 37378: {'name': 'ApertureValue', 'type': TYPES.Rational}, + 37379: {'name': 'BrightnessValue', 'type': TYPES.SRational}, + 37380: {'name': 'ExposureBiasValue', 'type': TYPES.SRational}, + 37381: {'name': 'MaxApertureValue', 'type': TYPES.Rational}, + 37382: {'name': 'SubjectDistance', 'type': TYPES.Rational}, + 37383: {'name': 'MeteringMode', 'type': TYPES.Short}, + 37384: {'name': 'LightSource', 'type': TYPES.Short}, + 37385: {'name': 'Flash', 'type': TYPES.Short}, + 37386: {'name': 'FocalLength', 'type': TYPES.Rational}, + 37396: {'name': 'SubjectArea', 'type': TYPES.Short}, + 37500: {'name': 'MakerNote', 'type': TYPES.Undefined}, + 37510: {'name': 'UserComment', 'type': TYPES.Undefined}, + 37520: {'name': 'SubSecTime', 'type': TYPES.Ascii}, + 37521: {'name': 'SubSecTimeOriginal', 'type': TYPES.Ascii}, + 37522: {'name': 'SubSecTimeDigitized', 'type': TYPES.Ascii}, + 37888: {'name': 'Temperature', 'type': TYPES.SRational}, + 37889: {'name': 'Humidity', 'type': TYPES.Rational}, + 37890: {'name': 'Pressure', 'type': TYPES.Rational}, + 37891: {'name': 'WaterDepth', 'type': TYPES.SRational}, + 37892: {'name': 'Acceleration', 'type': TYPES.Rational}, + 37893: {'name': 'CameraElevationAngle', 'type': TYPES.SRational}, + 40960: {'name': 'FlashpixVersion', 'type': TYPES.Undefined}, + 40961: {'name': 'ColorSpace', 'type': TYPES.Short}, + 40962: {'name': 'PixelXDimension', 'type': TYPES.Long}, + 40963: {'name': 'PixelYDimension', 'type': TYPES.Long}, + 40964: {'name': 'RelatedSoundFile', 'type': TYPES.Ascii}, + 40965: {'name': 'InteroperabilityTag', 'type': TYPES.Long}, + 41483: {'name': 'FlashEnergy', 'type': TYPES.Rational}, + 41484: {'name': 'SpatialFrequencyResponse', 'type': TYPES.Undefined}, + 41486: {'name': 'FocalPlaneXResolution', 'type': TYPES.Rational}, + 41487: {'name': 'FocalPlaneYResolution', 'type': TYPES.Rational}, + 41488: {'name': 'FocalPlaneResolutionUnit', 'type': TYPES.Short}, + 41492: {'name': 'SubjectLocation', 'type': TYPES.Short}, + 41493: {'name': 'ExposureIndex', 'type': TYPES.Rational}, + 41495: {'name': 'SensingMethod', 'type': TYPES.Short}, + 41728: {'name': 'FileSource', 'type': TYPES.Undefined}, + 41729: {'name': 'SceneType', 'type': TYPES.Undefined}, + 41730: {'name': 'CFAPattern', 'type': TYPES.Undefined}, + 41985: {'name': 'CustomRendered', 'type': TYPES.Short}, + 41986: {'name': 'ExposureMode', 'type': TYPES.Short}, + 41987: {'name': 'WhiteBalance', 'type': TYPES.Short}, + 41988: {'name': 'DigitalZoomRatio', 'type': TYPES.Rational}, + 41989: {'name': 'FocalLengthIn35mmFilm', 'type': TYPES.Short}, + 41990: {'name': 'SceneCaptureType', 'type': TYPES.Short}, + 41991: {'name': 'GainControl', 'type': TYPES.Short}, + 41992: {'name': 'Contrast', 'type': TYPES.Short}, + 41993: {'name': 'Saturation', 'type': TYPES.Short}, + 41994: {'name': 'Sharpness', 'type': TYPES.Short}, + 41995: {'name': 'DeviceSettingDescription', 'type': TYPES.Undefined}, + 41996: {'name': 'SubjectDistanceRange', 'type': TYPES.Short}, + 42016: {'name': 'ImageUniqueID', 'type': TYPES.Ascii}, + 42032: {'name': 'CameraOwnerName', 'type': TYPES.Ascii}, + 42033: {'name': 'BodySerialNumber', 'type': TYPES.Ascii}, + 42034: {'name': 'LensSpecification', 'type': TYPES.Rational}, + 42035: {'name': 'LensMake', 'type': TYPES.Ascii}, + 42036: {'name': 'LensModel', 'type': TYPES.Ascii}, + 42037: {'name': 'LensSerialNumber', 'type': TYPES.Ascii}, + 42240: {'name': 'Gamma', 'type': TYPES.Rational}, + }, + 'GPS': { + -1: {'name': '_errors', 'type': TYPES.Ascii}, + 0: {'name': 'GPSVersionID', 'type': TYPES.Byte}, + 1: {'name': 'GPSLatitudeRef', 'type': TYPES.Ascii}, + 2: {'name': 'GPSLatitude', 'type': TYPES.Rational}, + 3: {'name': 'GPSLongitudeRef', 'type': TYPES.Ascii}, + 4: {'name': 'GPSLongitude', 'type': TYPES.Rational}, + 5: {'name': 'GPSAltitudeRef', 'type': TYPES.Byte}, + 6: {'name': 'GPSAltitude', 'type': TYPES.Rational}, + 7: {'name': 'GPSTimeStamp', 'type': TYPES.Rational}, + 8: {'name': 'GPSSatellites', 'type': TYPES.Ascii}, + 9: {'name': 'GPSStatus', 'type': TYPES.Ascii}, + 10: {'name': 'GPSMeasureMode', 'type': TYPES.Ascii}, + 11: {'name': 'GPSDOP', 'type': TYPES.Rational}, + 12: {'name': 'GPSSpeedRef', 'type': TYPES.Ascii}, + 13: {'name': 'GPSSpeed', 'type': TYPES.Rational}, + 14: {'name': 'GPSTrackRef', 'type': TYPES.Ascii}, + 15: {'name': 'GPSTrack', 'type': TYPES.Rational}, + 16: {'name': 'GPSImgDirectionRef', 'type': TYPES.Ascii}, + 17: {'name': 'GPSImgDirection', 'type': TYPES.Rational}, + 18: {'name': 'GPSMapDatum', 'type': TYPES.Ascii}, + 19: {'name': 'GPSDestLatitudeRef', 'type': TYPES.Ascii}, + 20: {'name': 'GPSDestLatitude', 'type': TYPES.Rational}, + 21: {'name': 'GPSDestLongitudeRef', 'type': TYPES.Ascii}, + 22: {'name': 'GPSDestLongitude', 'type': TYPES.Rational}, + 23: {'name': 'GPSDestBearingRef', 'type': TYPES.Ascii}, + 24: {'name': 'GPSDestBearing', 'type': TYPES.Rational}, + 25: {'name': 'GPSDestDistanceRef', 'type': TYPES.Ascii}, + 26: {'name': 'GPSDestDistance', 'type': TYPES.Rational}, + 27: {'name': 'GPSProcessingMethod', 'type': TYPES.Undefined}, + 28: {'name': 'GPSAreaInformation', 'type': TYPES.Undefined}, + 29: {'name': 'GPSDateStamp', 'type': TYPES.Ascii}, + 30: {'name': 'GPSDifferential', 'type': TYPES.Short}, + 31: {'name': 'GPSHPositioningError', 'type': TYPES.Rational}, + }, + 'Interop': { + -1: {'name': '_errors', 'type': TYPES.Ascii}, + 1: {'name': 'InteroperabilityIndex', 'type': TYPES.Ascii}, + }, } TAGS["0th"] = TAGS["Image"] diff --git a/piexif/_load.py b/piexif/_load.py index 6176028..d57b23f 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -105,8 +105,12 @@ def __init__(self, data): def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): ifd_dict = {} - tag_count = struct.unpack(self.endian_mark + "H", - self.tiftag[pointer: pointer+2])[0] + try: + tag_count = struct.unpack(self.endian_mark + "H", + self.tiftag[pointer: pointer + 2])[0] + except struct.error: + return {-1: b'Bad SubDirectory start.'} + offset = pointer + 2 if ifd_name in ["0th", "1st"]: t = "Image" @@ -260,11 +264,11 @@ def convert_value(self, val): def _get_key_name_dict(exif_dict): new_dict = { - "0th":{TAGS["Image"][n]["name"]:value for n, value in exif_dict["0th"].items()}, - "Exif":{TAGS["Exif"][n]["name"]:value for n, value in exif_dict["Exif"].items()}, - "1st":{TAGS["Image"][n]["name"]:value for n, value in exif_dict["1st"].items()}, - "GPS":{TAGS["GPS"][n]["name"]:value for n, value in exif_dict["GPS"].items()}, - "Interop":{TAGS["Interop"][n]["name"]:value for n, value in exif_dict["Interop"].items()}, - "thumbnail":exif_dict["thumbnail"], + ifd_name: { + TAGS[ifd_name][n]["name"]: value + for n, value in exif_dict[ifd_name].items() + } + for ifd_name in ("0th", "Exif", "1st", "GPS", "Interop") } - return new_dict \ No newline at end of file + new_dict["thumbnail"] = exif_dict["thumbnail"] + return new_dict diff --git a/tests/images/bad_exif.bin b/tests/images/bad_exif.bin new file mode 100644 index 0000000000000000000000000000000000000000..8a7aaf884e1675d415c35aa961291cfadd35c9f9 GIT binary patch literal 926 zcmZvbzi-qq6vyAQ(<>Cpo#+jybcpyN5E9_{lIt}rmEbCtQxOL{44l-VWo1I>7P26v znJOeubjpVK4_Fux6BCS#jEszY?byj7Vaf3~?|tl-_wqb`aWEsAOy~v?ouaecBLUUL ze9z}eVr5KXZS8%?k7C2wLB2kqz%k$FCzh%C>n)RA^^Dx*ONE;lb(}^q$`zmE<-I(xvGDsO z6#=c)wUYf(*8KV_>~$JG%4NfEm7-?)yI`C)O;HH{JyIPrtzuiiLQ=^!ZC&YK6o102cuf17SmjGR>>+5W5&8*T6-GAW!)mmU0HpT8HKCv|8O f-pl~`*CSwGPV_M^-G=rC$c&q=m#Byx|FrR6&U#fw literal 0 HcmV?d00001 diff --git a/tests/s_test.py b/tests/s_test.py index 5d105de..b4ea2a8 100644 --- a/tests/s_test.py +++ b/tests/s_test.py @@ -26,7 +26,7 @@ # JPEG without APP0 and APP1 segments NOAPP01_FILE = os.path.join("tests", "images", "noapp01.jpg") INPUT_FILE_TIF = os.path.join("tests", "images", "01.tif") - +BAD_EXIF_FILE = os.path.join("tests", "images", "bad_exif.bin") with open(INPUT_FILE1, "rb") as f: I1 = f.read() @@ -560,6 +560,44 @@ def test_insert_fail2(self): with self.assertRaises(ValueError): piexif.insert(exif_bytes, I1, False) + def test_invalid_ifd_pointer(self): + with open(BAD_EXIF_FILE, "rb") as exif_file: + data = exif_file.read() + exif = piexif.load(data, key_is_name=True) + + self.assertEqual(exif['Interop'], { + '_errors': 'Bad SubDirectory start.', + }) + + self.assertDictContainsSubset({ + 'ExifVersion': '0230', + 'PixelXDimension': 5184, + 'PixelYDimension': 3456, + 'DateTimeDigitized': '2018:06:02 15:56:53', + 'LensModel': 'EF-S24mm f/2.8 STM', + 'DateTimeOriginal': '2018:06:02 15:56:53', + 'ISOSpeedRatings': 400, + 'ExposureTime': (1, 80), + }, exif['Exif']) + + self.assertDictContainsSubset({ + 'XResolution': (72, 1), + 'YResolution': (72, 1), + 'ResolutionUnit': 2, + 'Make': 'Canon', + 'DateTime': '2019:02:06 21:05:32', + 'YCbCrPositioning': 2, + 'Model': 'Canon EOS 700D', + 'Orientation': 0, + }, exif['0th']) + + self.assertDictContainsSubset({ + 'XResolution': (72, 1), + 'YResolution': (72, 1), + 'ResolutionUnit': 2, + 'Compression': 6 + }, exif['1st']) + # ------ def test_print_exif(self): print("\n**********************************************") @@ -1030,7 +1068,7 @@ def test_insert(self): } } exif_bytes = piexif.dump(exif_dict) - + for filename in files: try: Image.open(IMAGE_DIR + filename) From 139ed2547d396f4cd7c9cb3833dc94dbaca3ca62 Mon Sep 17 00:00:00 2001 From: Kirill Kuzminykh Date: Thu, 23 Jan 2020 15:55:33 +0300 Subject: [PATCH 2/5] Fixed changelog. --- doc/changes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/changes.rst b/doc/changes.rst index 8d71cd6..b89a556 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -6,8 +6,8 @@ Unreleased - Added new ``TAG`` with code ``-1`` and name ``_errors`` to prevent some exceptions during parse of corrupted EXIF. -- Fixed exception in ``_ExifReader.get_ifd_dict()`` during parse of EXIF - with invalid IFD pointers. Instead it this method returns dictionary with +- Fixed exception in ``_ExifReader.get_ifd_dict()`` method during parse of EXIF + with invalid IFD pointers. Instead, the method returns dictionary with ``_errors`` key that has description of error. 1.1.3 From 0fb0923c5be74b2a5e10d0ddbecc31f0f3436c42 Mon Sep 17 00:00:00 2001 From: Kirill Kuzminykh Date: Thu, 23 Jan 2020 16:17:15 +0300 Subject: [PATCH 3/5] Fixed test 'test_invalid_ifd_pointer' for Python 3 --- tests/s_test.py | 70 +++++++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/tests/s_test.py b/tests/s_test.py index b4ea2a8..3efdffc 100644 --- a/tests/s_test.py +++ b/tests/s_test.py @@ -566,37 +566,49 @@ def test_invalid_ifd_pointer(self): exif = piexif.load(data, key_is_name=True) self.assertEqual(exif['Interop'], { - '_errors': 'Bad SubDirectory start.', + '_errors': b'Bad SubDirectory start.', }) - self.assertDictContainsSubset({ - 'ExifVersion': '0230', - 'PixelXDimension': 5184, - 'PixelYDimension': 3456, - 'DateTimeDigitized': '2018:06:02 15:56:53', - 'LensModel': 'EF-S24mm f/2.8 STM', - 'DateTimeOriginal': '2018:06:02 15:56:53', - 'ISOSpeedRatings': 400, - 'ExposureTime': (1, 80), - }, exif['Exif']) - - self.assertDictContainsSubset({ - 'XResolution': (72, 1), - 'YResolution': (72, 1), - 'ResolutionUnit': 2, - 'Make': 'Canon', - 'DateTime': '2019:02:06 21:05:32', - 'YCbCrPositioning': 2, - 'Model': 'Canon EOS 700D', - 'Orientation': 0, - }, exif['0th']) - - self.assertDictContainsSubset({ - 'XResolution': (72, 1), - 'YResolution': (72, 1), - 'ResolutionUnit': 2, - 'Compression': 6 - }, exif['1st']) + self.assertTrue( + set( + { + 'ExifVersion': b'0230', + 'PixelXDimension': 5184, + 'PixelYDimension': 3456, + 'DateTimeDigitized': b'2018:06:02 15:56:53', + 'LensModel': b'EF-S24mm f/2.8 STM', + 'DateTimeOriginal': b'2018:06:02 15:56:53', + 'ISOSpeedRatings': 400, + 'ExposureTime': (1, 80), + }.items() + ).issubset(set(exif['Exif'].items())) + ) + + self.assertTrue( + set( + { + 'XResolution': (72, 1), + 'YResolution': (72, 1), + 'ResolutionUnit': 2, + 'Make': b'Canon', + 'DateTime': b'2019:02:06 21:05:32', + 'YCbCrPositioning': 2, + 'Model': b'Canon EOS 700D', + 'Orientation': 0, + }.items() + ).issubset(set(exif['0th'].items())) + ) + + self.assertTrue( + set( + { + 'XResolution': (72, 1), + 'YResolution': (72, 1), + 'ResolutionUnit': 2, + 'Compression': 6 + }.items() + ).issubset(set(exif['1st'].items())) + ) # ------ def test_print_exif(self): From 4e8a5308e55fd5bdc378c6ec771bd4592407fb4a Mon Sep 17 00:00:00 2001 From: Kirill Kuzminykh Date: Fri, 13 Mar 2020 17:02:08 +0300 Subject: [PATCH 4/5] Added new function ``safe_load()`` that do not stop parsing of EXIF after any error in data. Instead it collects all errors into special list. As a result the function returns tuple contains a dict with parsed EXIF and list of all errors. --- doc/changes.rst | 9 ++-- piexif/__init__.py | 3 +- piexif/_exif.py | 5 +- piexif/_load.py | 116 +++++++++++++++++++++++++++++++-------------- tests/s_test.py | 41 +++++++--------- 5 files changed, 102 insertions(+), 72 deletions(-) diff --git a/doc/changes.rst b/doc/changes.rst index b89a556..7635db2 100644 --- a/doc/changes.rst +++ b/doc/changes.rst @@ -4,11 +4,10 @@ Changelog Unreleased ---------- -- Added new ``TAG`` with code ``-1`` and name ``_errors`` to prevent some - exceptions during parse of corrupted EXIF. -- Fixed exception in ``_ExifReader.get_ifd_dict()`` method during parse of EXIF - with invalid IFD pointers. Instead, the method returns dictionary with - ``_errors`` key that has description of error. +- Added new function ``safe_load()`` that do not stop parsing of EXIF + after any error in data. Instead it collects all errors into special + list. As a result the function returns tuple contains a dict with + parsed EXIF and list of all errors. 1.1.3 ----- diff --git a/piexif/__init__.py b/piexif/__init__.py index a863445..3c20434 100644 --- a/piexif/__init__.py +++ b/piexif/__init__.py @@ -1,5 +1,5 @@ from ._remove import remove -from ._load import load +from ._load import load, safe_load from ._dump import dump from ._transplant import transplant from ._insert import insert @@ -7,5 +7,4 @@ from ._exceptions import * - VERSION = '1.1.3' diff --git a/piexif/_exif.py b/piexif/_exif.py index 9baddea..2b40906 100644 --- a/piexif/_exif.py +++ b/piexif/_exif.py @@ -15,7 +15,6 @@ class TYPES: TAGS = { 'Image': { - -1: {'name': '_errors', 'type': TYPES.Ascii}, 11: {'name': 'ProcessingSoftware', 'type': TYPES.Ascii}, 254: {'name': 'NewSubfileType', 'type': TYPES.Long}, 255: {'name': 'SubfileType', 'type': TYPES.Short}, @@ -207,7 +206,6 @@ class TYPES: 60610: {'name': 'ZZZTestDFloat', 'type': TYPES.DFloat}, }, 'Exif': { - -1: {'name': '_errors', 'type': TYPES.Ascii}, 33434: {'name': 'ExposureTime', 'type': TYPES.Rational}, 33437: {'name': 'FNumber', 'type': TYPES.Rational}, 34850: {'name': 'ExposureProgram', 'type': TYPES.Short}, @@ -289,7 +287,6 @@ class TYPES: 42240: {'name': 'Gamma', 'type': TYPES.Rational}, }, 'GPS': { - -1: {'name': '_errors', 'type': TYPES.Ascii}, 0: {'name': 'GPSVersionID', 'type': TYPES.Byte}, 1: {'name': 'GPSLatitudeRef', 'type': TYPES.Ascii}, 2: {'name': 'GPSLatitude', 'type': TYPES.Rational}, @@ -324,7 +321,6 @@ class TYPES: 31: {'name': 'GPSHPositioningError', 'type': TYPES.Rational}, }, 'Interop': { - -1: {'name': '_errors', 'type': TYPES.Ascii}, 1: {'name': 'InteroperabilityIndex', 'type': TYPES.Ascii}, }, } @@ -332,6 +328,7 @@ class TYPES: TAGS["0th"] = TAGS["Image"] TAGS["1st"] = TAGS["Image"] + class ImageIFD: """Exif tag number reference - 0th IFD""" ProcessingSoftware = 11 diff --git a/piexif/_load.py b/piexif/_load.py index d57b23f..75232aa 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -1,10 +1,11 @@ import struct import sys -from ._common import * +from . import _webp +from ._common import get_exif_seg, read_exif_from_file, split_into_segments from ._exceptions import InvalidImageDataError -from ._exif import * -from piexif import _webp +from ._exif import ExifIFD, ImageIFD, TAGS, TYPES + LITTLE_ENDIAN = b"\x49\x49" @@ -13,46 +14,69 @@ def load(input_data, key_is_name=False): """ py:function:: piexif.load(filename) - Return exif data as dict. Keys(IFD name), be contained, are "0th", "Exif", "GPS", "Interop", "1st", and "thumbnail". Without "thumbnail", the value is dict(tag name/tag value). "thumbnail" value is JPEG as bytes. + Return exif data as dict. Keys(IFD name), be contained, are "0th", "Exif", "GPS", "Interop", + "1st", and "thumbnail". Without "thumbnail", the value is dict(tag name/tag value). + "thumbnail" value is JPEG as bytes. - :param str filename: JPEG or TIFF + :type input_data: str or bytes + :type key_is_name: bool :return: Exif data({"0th":dict, "Exif":dict, "GPS":dict, "Interop":dict, "1st":dict, "thumbnail":bytes}) :rtype: dict """ + exif_dict, _ = _load_and_parse(input_data, key_is_name) + return exif_dict + + +def safe_load(input_data, key_is_name=False): + """ + py:function:: piexif.safe_load(filename) + + Returns tuple with exif data as dict and list of errors. + Keys(IFD name) of dictionary, be contained, are "0th", "Exif", "GPS", "Interop", + "1st", and "thumbnail". Without "thumbnail", the value is dict(tag name/tag value). + "thumbnail" value is JPEG as bytes. + + :type input_data: str or bytes + :type key_is_name: bool + :rtype: tuple(dict, list) + """ + return _load_and_parse(input_data, key_is_name, raise_errors=False) + + +def _load_and_parse(input_data, key_is_name=False, raise_errors=True): exif_dict = {"0th":{}, "Exif":{}, "GPS":{}, "Interop":{}, "1st":{}, "thumbnail":None} - exifReader = _ExifReader(input_data) + exifReader = _ExifReader(input_data, raise_errors) if exifReader.tiftag is None: - return exif_dict - - if exifReader.tiftag[0:2] == LITTLE_ENDIAN: - exifReader.endian_mark = "<" - else: - exifReader.endian_mark = ">" + return exif_dict, [(None, None, 'EXIF has not found')] pointer = struct.unpack(exifReader.endian_mark + "L", exifReader.tiftag[4:8])[0] - exif_dict["0th"] = exifReader.get_ifd_dict(pointer, "0th") + exif_dict["0th"], all_errors = exifReader.get_ifd_dict(pointer, "0th") first_ifd_pointer = exif_dict["0th"].pop("first_ifd_pointer") if ImageIFD.ExifTag in exif_dict["0th"]: pointer = exif_dict["0th"][ImageIFD.ExifTag] - exif_dict["Exif"] = exifReader.get_ifd_dict(pointer, "Exif") + exif_dict["Exif"], errors = exifReader.get_ifd_dict(pointer, "Exif") + all_errors += errors if ImageIFD.GPSTag in exif_dict["0th"]: pointer = exif_dict["0th"][ImageIFD.GPSTag] - exif_dict["GPS"] = exifReader.get_ifd_dict(pointer, "GPS") + exif_dict["GPS"], errors = exifReader.get_ifd_dict(pointer, "GPS") + all_errors += errors if ExifIFD.InteroperabilityTag in exif_dict["Exif"]: pointer = exif_dict["Exif"][ExifIFD.InteroperabilityTag] - exif_dict["Interop"] = exifReader.get_ifd_dict(pointer, "Interop") + exif_dict["Interop"], errors = exifReader.get_ifd_dict(pointer, "Interop") + all_errors += errors if first_ifd_pointer != b"\x00\x00\x00\x00": pointer = struct.unpack(exifReader.endian_mark + "L", first_ifd_pointer)[0] - exif_dict["1st"] = exifReader.get_ifd_dict(pointer, "1st") + exif_dict["1st"], errors = exifReader.get_ifd_dict(pointer, "1st") + all_errors += errors if (ImageIFD.JPEGInterchangeFormat in exif_dict["1st"] and - ImageIFD.JPEGInterchangeFormatLength in exif_dict["1st"]): + ImageIFD.JPEGInterchangeFormatLength in exif_dict["1st"]): end = (exif_dict["1st"][ImageIFD.JPEGInterchangeFormat] + exif_dict["1st"][ImageIFD.JPEGInterchangeFormatLength]) thumb = exifReader.tiftag[exif_dict["1st"][ImageIFD.JPEGInterchangeFormat]:end] @@ -60,11 +84,12 @@ def load(input_data, key_is_name=False): if key_is_name: exif_dict = _get_key_name_dict(exif_dict) - return exif_dict + return exif_dict, all_errors class _ExifReader(object): - def __init__(self, data): + def __init__(self, data, raise_errors=True): + self.raise_errors = raise_errors # Prevents "UnicodeWarning: Unicode equal comparison failed" warnings on Python 2 maybe_image = sys.version_info >= (3,0,0) or isinstance(data, str) @@ -103,14 +128,22 @@ def __init__(self, data): else: raise InvalidImageDataError("Given file is neither JPEG nor TIFF.") + if self.tiftag and self.tiftag[0:2] == LITTLE_ENDIAN: + self.endian_mark = "<" + else: + self.endian_mark = ">" + def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): ifd_dict = {} try: tag_count = struct.unpack(self.endian_mark + "H", self.tiftag[pointer: pointer + 2])[0] except struct.error: - return {-1: b'Bad SubDirectory start.'} + if self.raise_errors: + raise + return ifd_dict, [(ifd_name, None, 'Bad SubDirectory start.')] + errors = [] offset = pointer + 2 if ifd_name in ["0th", "1st"]: t = "Image" @@ -119,27 +152,38 @@ def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): p_and_value = [] for x in range(tag_count): pointer = offset + 12 * x - tag = struct.unpack(self.endian_mark + "H", - self.tiftag[pointer: pointer+2])[0] - value_type = struct.unpack(self.endian_mark + "H", - self.tiftag[pointer + 2: pointer + 4])[0] - value_num = struct.unpack(self.endian_mark + "L", - self.tiftag[pointer + 4: pointer + 8] - )[0] - value = self.tiftag[pointer+8: pointer+12] + tag = None + try: + tag = struct.unpack(self.endian_mark + "H", + self.tiftag[pointer: pointer + 2])[0] + value_type = struct.unpack(self.endian_mark + "H", + self.tiftag[pointer + 2: pointer + 4])[0] + value_num = struct.unpack(self.endian_mark + "L", + self.tiftag[pointer + 4: pointer + 8] + )[0] + value = self.tiftag[pointer + 8: pointer + 12] + except Exception as e: + if self.raise_errors: + raise + errors.append((ifd_name, tag, str(e))) + continue + p_and_value.append((pointer, value_type, value_num, value)) v_set = (value_type, value_num, value, tag) - if tag in TAGS[t]: - ifd_dict[tag] = self.convert_value(v_set) - elif read_unknown: - ifd_dict[tag] = (v_set[0], v_set[1], v_set[2], self.tiftag) - #else: - # pass + try: + if tag in TAGS[t]: + ifd_dict[tag] = self.convert_value(v_set) + elif read_unknown: + ifd_dict[tag] = (v_set[0], v_set[1], v_set[2], self.tiftag) + except Exception as e: + if self.raise_errors: + raise + errors.append((ifd_name, tag, str(e))) if ifd_name == "0th": pointer = offset + 12 * tag_count ifd_dict["first_ifd_pointer"] = self.tiftag[pointer:pointer + 4] - return ifd_dict + return ifd_dict, errors def convert_value(self, val): data = None diff --git a/tests/s_test.py b/tests/s_test.py index 3efdffc..bf2ee8c 100644 --- a/tests/s_test.py +++ b/tests/s_test.py @@ -1,19 +1,15 @@ # -*- coding: utf-8 -*- - -import copy import glob import io import os import struct -import sys import time import unittest from PIL import Image + import piexif -from piexif import _common, ImageIFD, ExifIFD, GPSIFD, TAGS, InvalidImageDataError -from piexif import _webp -from piexif import helper +from piexif import ExifIFD, GPSIFD, ImageIFD, InvalidImageDataError, TAGS, _common, _webp, helper print("piexif version: {}".format(piexif.VERSION)) @@ -563,11 +559,10 @@ def test_insert_fail2(self): def test_invalid_ifd_pointer(self): with open(BAD_EXIF_FILE, "rb") as exif_file: data = exif_file.read() - exif = piexif.load(data, key_is_name=True) + self.assertRaises(struct.error, piexif.load, data, key_is_name=True) - self.assertEqual(exif['Interop'], { - '_errors': b'Bad SubDirectory start.', - }) + exif, errors = piexif.safe_load(data, key_is_name=True) + self.assertEqual(errors, [('Interop', None, 'Bad SubDirectory start.')]) self.assertTrue( set( @@ -696,11 +691,7 @@ def test_ExifReader_return_unknown(self): b1 = b"MM\x00\x2a\x00\x00\x00\x08" b2 = b"\x00\x01" + b"\xff\xff\x00\x00\x00\x00" + b"\x00\x00\x00\x00" er = piexif._load._ExifReader(b1 + b2) - if er.tiftag[0:2] == b"II": - er.endian_mark = "<" - else: - er.endian_mark = ">" - ifd = er.get_ifd_dict(8, "0th", True) + ifd, _ = er.get_ifd_dict(8, "0th", True) self.assertEqual(ifd[65535][0], 0) self.assertEqual(ifd[65535][1], 0) self.assertEqual(ifd[65535][2], b"\x00\x00") @@ -924,7 +915,7 @@ def test_merge_chunks(self): for filename in files: try: - Image.open(IMAGE_DIR + filename) + Image.open(IMAGE_DIR + filename).close() except: print("Pillow can't read {}".format(filename)) continue @@ -938,7 +929,7 @@ def test_merge_chunks(self): new_webp_bytes = file_header + merged with open(OUT_DIR + "raw_" + filename, "wb") as f: f.write(new_webp_bytes) - Image.open(OUT_DIR + "raw_" + filename) + Image.open(OUT_DIR + "raw_" + filename).close() def test_insert_exif(self): """Can PIL open WebP that is inserted exif?""" @@ -962,7 +953,7 @@ def test_insert_exif(self): for filename in files: try: - Image.open(IMAGE_DIR + filename) + Image.open(IMAGE_DIR + filename).close() except: print("Pillow can't read {}".format(filename)) continue @@ -973,7 +964,7 @@ def test_insert_exif(self): exif_inserted = _webp.insert(data, exif_bytes) with open(OUT_DIR + "i_" + filename, "wb") as f: f.write(exif_inserted) - Image.open(OUT_DIR + "i_" + filename) + Image.open(OUT_DIR + "i_" + filename).close() def test_remove_exif(self): """Can PIL open WebP that is removed exif?""" @@ -990,7 +981,7 @@ def test_remove_exif(self): for filename in files: try: - Image.open(IMAGE_DIR + filename) + Image.open(IMAGE_DIR + filename).close() except: print("Pillow can't read {}".format(filename)) continue @@ -1000,7 +991,7 @@ def test_remove_exif(self): exif_removed = _webp.remove(data) with open(OUT_DIR + "r_" + filename, "wb") as f: f.write(exif_removed) - Image.open(OUT_DIR + "r_" + filename) + Image.open(OUT_DIR + "r_" + filename).close() def test_get_exif(self): """Can we get exif from WebP?""" @@ -1053,12 +1044,12 @@ def test_remove(self): for filename in files: try: - Image.open(IMAGE_DIR + filename) + Image.open(IMAGE_DIR + filename).close() except: print("Pillow can't read {}".format(filename)) continue piexif.remove(IMAGE_DIR + filename, OUT_DIR + "rr_" + filename) - Image.open(OUT_DIR + "rr_" + filename) + Image.open(OUT_DIR + "rr_" + filename).close() def test_insert(self): """Can PIL open WebP that is inserted exif?""" @@ -1083,12 +1074,12 @@ def test_insert(self): for filename in files: try: - Image.open(IMAGE_DIR + filename) + Image.open(IMAGE_DIR + filename).close() except: print("Pillow can't read {}".format(filename)) continue piexif.insert(exif_bytes, IMAGE_DIR + filename, OUT_DIR + "ii_" + filename) - Image.open(OUT_DIR + "ii_" + filename) + Image.open(OUT_DIR + "ii_" + filename).close() def suite(): From 8f477292354d8884c4a921da2fee36a7fa549bae Mon Sep 17 00:00:00 2001 From: Kirill Kuzminykh Date: Fri, 13 Mar 2020 17:04:18 +0300 Subject: [PATCH 5/5] Added new function ``safe_load()`` that do not stop parsing of EXIF after any error in data. Instead it collects all errors into special list. As a result the function returns tuple contains a dict with parsed EXIF and list of all errors. --- piexif/_load.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/piexif/_load.py b/piexif/_load.py index 75232aa..db8e7b4 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -12,7 +12,7 @@ def load(input_data, key_is_name=False): """ - py:function:: piexif.load(filename) + py:function:: piexif.load(input_data, key_is_name=False) Return exif data as dict. Keys(IFD name), be contained, are "0th", "Exif", "GPS", "Interop", "1st", and "thumbnail". Without "thumbnail", the value is dict(tag name/tag value). @@ -29,7 +29,7 @@ def load(input_data, key_is_name=False): def safe_load(input_data, key_is_name=False): """ - py:function:: piexif.safe_load(filename) + py:function:: piexif.safe_load(input_data, key_is_name=False) Returns tuple with exif data as dict and list of errors. Keys(IFD name) of dictionary, be contained, are "0th", "Exif", "GPS", "Interop",