import struct import sys from . import _webp from ._common import get_exif_seg, read_exif_from_file, split_into_segments from ._exceptions import InvalidImageDataError from ._exif import TAGS, TYPES, ExifIFD, ImageIFD LITTLE_ENDIAN = b"\x49\x49" 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. :param str filename: JPEG or TIFF :return: Exif data({"0th":dict, "Exif":dict, "GPS":dict, "Interop":dict, "1st":dict, "thumbnail":bytes}) :rtype: dict """ exif_dict = { "0th": {}, "Exif": {}, "GPS": {}, "Interop": {}, "1st": {}, "thumbnail": None, } exifReader = _ExifReader(input_data) if exifReader.tiftag is None: return exif_dict if exifReader.tiftag[0:2] == LITTLE_ENDIAN: exifReader.endian_mark = "<" else: exifReader.endian_mark = ">" pointer = struct.unpack(exifReader.endian_mark + "L", exifReader.tiftag[4:8])[0] exif_dict["0th"] = 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") if ImageIFD.GPSTag in exif_dict["0th"]: pointer = exif_dict["0th"][ImageIFD.GPSTag] exif_dict["GPS"] = exifReader.get_ifd_dict(pointer, "GPS") if ExifIFD.InteroperabilityTag in exif_dict["Exif"]: pointer = exif_dict["Exif"][ExifIFD.InteroperabilityTag] exif_dict["Interop"] = exifReader.get_ifd_dict(pointer, "Interop") 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") if ( ImageIFD.JPEGInterchangeFormat in exif_dict["1st"] and 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 ] exif_dict["thumbnail"] = thumb if key_is_name: exif_dict = _get_key_name_dict(exif_dict) return exif_dict class _ExifReader(object): def __init__(self, data): # Prevents "UnicodeWarning: Unicode equal comparison failed" warnings on Python 2 maybe_image = sys.version_info >= (3, 0, 0) or isinstance(data, str) if maybe_image and data[0:2] == b"\xff\xd8": # JPEG segments = split_into_segments(data) app1 = get_exif_seg(segments) if app1: self.tiftag = app1[10:] else: self.tiftag = None elif maybe_image and data[0:2] in (b"\x49\x49", b"\x4d\x4d"): # TIFF self.tiftag = data elif maybe_image and data[0:4] == b"RIFF" and data[8:12] == b"WEBP": self.tiftag = _webp.get_exif(data) elif maybe_image and data[0:4] == b"Exif": # Exif self.tiftag = data[6:] else: with open(data, "rb") as f: magic_number = f.read(2) if magic_number == b"\xff\xd8": # JPEG app1 = read_exif_from_file(data) if app1: self.tiftag = app1[10:] else: self.tiftag = None elif magic_number in (b"\x49\x49", b"\x4d\x4d"): # TIFF with open(data, "rb") as f: self.tiftag = f.read() else: with open(data, "rb") as f: header = f.read(12) if header[0:4] == b"RIFF" and header[8:12] == b"WEBP": with open(data, "rb") as f: file_data = f.read() self.tiftag = _webp.get_exif(file_data) else: raise InvalidImageDataError("Given file is neither JPEG nor TIFF.") self.endian_mark = None 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] offset = pointer + 2 if ifd_name in ["0th", "1st"]: t = "Image" else: t = ifd_name 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] 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 if ifd_name == "0th": pointer = offset + 12 * tag_count ifd_dict["first_ifd_pointer"] = self.tiftag[pointer : pointer + 4] return ifd_dict def convert_value(self, val): data = None t = val[0] length = val[1] value = val[2] if t == TYPES.Byte: # BYTE if length > 4: pointer = struct.unpack(self.endian_mark + "L", value)[0] data = struct.unpack( "B" * length, self.tiftag[pointer : pointer + length] ) else: data = struct.unpack("B" * length, value[0:length]) elif t == TYPES.Ascii: # ASCII if length > 4: pointer = struct.unpack(self.endian_mark + "L", value)[0] data = self.tiftag[pointer : pointer + length - 1] else: data = value[0 : length - 1] elif t == TYPES.Short: # SHORT if length > 2: pointer = struct.unpack(self.endian_mark + "L", value)[0] data = struct.unpack( self.endian_mark + "H" * length, self.tiftag[pointer : pointer + length * 2], ) else: data = struct.unpack( self.endian_mark + "H" * length, value[0 : length * 2] ) elif t == TYPES.Long: # LONG if length > 1: pointer = struct.unpack(self.endian_mark + "L", value)[0] data = struct.unpack( self.endian_mark + "L" * length, self.tiftag[pointer : pointer + length * 4], ) else: data = struct.unpack(self.endian_mark + "L" * length, value) elif t == TYPES.Rational: # RATIONAL pointer = struct.unpack(self.endian_mark + "L", value)[0] if length > 1: data = tuple( ( struct.unpack( self.endian_mark + "L", self.tiftag[pointer + x * 8 : pointer + 4 + x * 8], )[0], struct.unpack( self.endian_mark + "L", self.tiftag[pointer + 4 + x * 8 : pointer + 8 + x * 8], )[0], ) for x in range(length) ) else: data = ( struct.unpack( self.endian_mark + "L", self.tiftag[pointer : pointer + 4] )[0], struct.unpack( self.endian_mark + "L", self.tiftag[pointer + 4 : pointer + 8] )[0], ) elif t == TYPES.SByte: # SIGNED BYTES if length > 4: pointer = struct.unpack(self.endian_mark + "L", value)[0] data = struct.unpack( "b" * length, self.tiftag[pointer : pointer + length] ) else: data = struct.unpack("b" * length, value[0:length]) elif t == TYPES.Undefined: # UNDEFINED BYTES if length > 4: pointer = struct.unpack(self.endian_mark + "L", value)[0] data = self.tiftag[pointer : pointer + length] else: data = value[0:length] elif t == TYPES.SShort: # SIGNED SHORT if length > 2: pointer = struct.unpack(self.endian_mark + "L", value)[0] data = struct.unpack( self.endian_mark + "h" * length, self.tiftag[pointer : pointer + length * 2], ) else: data = struct.unpack( self.endian_mark + "h" * length, value[0 : length * 2] ) elif t == TYPES.SLong: # SLONG if length > 1: pointer = struct.unpack(self.endian_mark + "L", value)[0] data = struct.unpack( self.endian_mark + "l" * length, self.tiftag[pointer : pointer + length * 4], ) else: data = struct.unpack(self.endian_mark + "l" * length, value) elif t == TYPES.SRational: # SRATIONAL pointer = struct.unpack(self.endian_mark + "L", value)[0] if length > 1: data = tuple( ( struct.unpack( self.endian_mark + "l", self.tiftag[pointer + x * 8 : pointer + 4 + x * 8], )[0], struct.unpack( self.endian_mark + "l", self.tiftag[pointer + 4 + x * 8 : pointer + 8 + x * 8], )[0], ) for x in range(length) ) else: data = ( struct.unpack( self.endian_mark + "l", self.tiftag[pointer : pointer + 4] )[0], struct.unpack( self.endian_mark + "l", self.tiftag[pointer + 4 : pointer + 8] )[0], ) elif t == TYPES.Float: # FLOAT if length > 1: pointer = struct.unpack(self.endian_mark + "L", value)[0] data = struct.unpack( self.endian_mark + "f" * length, self.tiftag[pointer : pointer + length * 4], ) else: data = struct.unpack(self.endian_mark + "f" * length, value) elif t == TYPES.DFloat: # DOUBLE pointer = struct.unpack(self.endian_mark + "L", value)[0] data = struct.unpack( self.endian_mark + "d" * length, self.tiftag[pointer : pointer + length * 8], ) else: raise ValueError( "Exif might be wrong. Got incorrect value " + "type to decode.\n" + "tag: " + str(val[3]) + "\ntype: " + str(t) ) if isinstance(data, tuple) and (len(data) == 1): return data[0] else: return data 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"], } return new_dict