From fd2fd63b287288aaddbef30e11993d7f76dee9c3 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 4 Jun 2013 20:52:59 -0400 Subject: [PATCH] Added Exif classes for preliminary Exif image, photo, iop, gps support. --- glymur/jp2box.py | 295 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 227 insertions(+), 68 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index f11674e..9edf010 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1619,12 +1619,238 @@ class UUIDBox(Jp2kBox): text = buffer.decode('utf-8') kwargs['data'] = ET.fromstring(text) elif kwargs['uuid'].bytes == b'JpgTiffExif->JP2': - kwargs['data'] = _parse_exif(buffer) + kwargs['data'] = Exif(buffer).exif_image else: kwargs['data'] = buffer box = UUIDBox(**kwargs) return box +class Exif: + """ + Attributes + ---------- + buffer : bytes + Raw byte stream consisting of the UUID data. + endian : str + Either '<' for big-endian, or '>' for little-endian. + """ + + def __init__(self, buffer): + """Interpret raw buffer consisting of Exif IFD. + """ + self.exif_image = None + self.exif_photo = None + self.exif_gpsinfo = None + self.exif_iop = None + + self.buffer = buffer + + # Ignore the first six bytes. + # Next 8 should be (73, 73, 42, 8) + data = struct.unpack('' for little-endian. + num_tags : int + Number of tags in the IFD. + raw_ifd : dictionary + Maps tag number to "mildly-interpreted" tag value. + """ + datatype2fmt = {1: ('B', 1), + 2: ('B', 1), + 3: ('H', 2), + 4: ('I', 4), + 5: ('II', 8), + 7: ('B', 1), + 9: ('i', 4), + 10: ('ii', 8)} + + def __init__(self, endian, buffer, offset): + self.endian = endian + self.buffer = buffer + + self.num_tags, = struct.unpack(endian + 'H', buffer[offset:offset + 2]) + + fmt = self.endian + 'HHII' * self.num_tags + data = struct.unpack(fmt, buffer[offset + 2:offset + 2 + self.num_tags * 12]) + self.raw_ifd = {} + for j, tag in enumerate(data[0::4]): + tag_entry = buffer[offset + 2 + j * 12 + 8:offset + 2 + j * 12 + 8 + 4] + payload = self.parse_tag(data[j * 4 + 1], + data[j * 4 + 2], + tag_entry) + self.raw_ifd[tag] = payload + + def parse_tag(self, dtype, count, offset_buf): + """Interpret an Exif image tag data payload. + """ + fmt = self.datatype2fmt[dtype][0] * count + payload_size = self.datatype2fmt[dtype][1] * count + + if payload_size <= 4: + # Interpret the payload from the 4 bytes in the tag entry. + target_buffer = offset_buf[:payload_size] + else: + # Interpret the payload at the offset specified by the 4 bytes in the + # tag entry. + offset, = struct.unpack(self.endian + 'I', offset_buf) + target_buffer = self.buffer[offset:offset + payload_size] + + if dtype == 2: + # ASCII + payload = target_buffer.decode('utf-8').rstrip() + else: + payload = struct.unpack(self.endian + fmt, target_buffer) + if dtype == 5 or dtype == 10: + # Rational or Signed Rational. Construct the list of values. + rational_payload = [] + for j in range(count): + value = float(payload[j * 2]) / float(payload[j * 2 + 1]) + rational_payload.append(value) + payload = rational_payload + if count == 1: + # If just a single value, then return a scalar instead of a + # tuple. + payload = payload[0] + + return payload + + +class ExifImageIfd(Ifd): + tagnum2name = {271: 'Make', + 272: 'Model', + 282: 'XResolution', + 283: 'YResolution', + 296: 'ResolutionUnit', + 531: 'YCbCrPositioning', + 34665: 'ExifTag', + 34853: 'GPSTag'} + + def __init__(self, endian, buffer, offset): + Ifd.__init__(self, endian, buffer, offset) + + # Now post process the raw IFD. + self.ifd = {} + for tag, value in self.raw_ifd.items(): + tag_name = self.tagnum2name[tag] + self.ifd[tag_name] = value + +class ExifPhotoIfd(Ifd): + tagnum2name = {34855: 'ISOSpeedRatings', + 36864: 'ExifVersion', + 36867: 'DateTimeOriginal', + 36868: 'DateTimeDigitized', + 37121: 'ComponentsConfiguration', + 37386: 'FocalLength', + 40960: 'FlashpixVersion', + 40961: 'ColorSpace', + 40962: 'PixelXDimension', + 40963: 'PixelYDimension', + 40965: 'InteroperabilityTag'} + + def __init__(self, endian, buffer, offset): + Ifd.__init__(self, endian, buffer, offset) + + # Now post process the raw IFD. + self.ifd = {} + for tag, value in self.raw_ifd.items(): + tag_name = self.tagnum2name[tag] + self.ifd[tag_name] = value + +class ExifGPSInfoIfd(Ifd): + tagnum2name = {0: 'GPSVersionID', + 1: 'GPSLatitudeRef', + 2: 'GPSLatitude', + 3: 'GPSLongitudeRef', + 4: 'GPSLongitude', + 5: 'GPSAltitudeRef', + 6: 'GPSAltitude', + 7: 'GPSTimeStamp', + 8: 'GPSSatellites', + 9: 'GPSStatus', + 10: 'GPSMeasureMode', + 11: 'GPSDOP', + 12: 'GPSSpeedRef', + 13: 'GPSSpeed', + 14: 'GPSTrackRef', + 15: 'GPSTrack', + 16: 'GPSImgDirectionRef', + 17: 'GPSImgDirection', + 18: 'GPSMapDatum', + 19: 'GPSDestLatitudeRef', + 20: 'GPSDestLatitude', + 21: 'GPSDestLongitudeRef', + 22: 'GPSDestLongitude', + 23: 'GPSDestBearingRef', + 24: 'GPSDestBearing', + 25: 'GPSDestDistanceRef', + 26: 'GPSDestDistance', + 27: 'GPSProcessingMethod', + 28: 'GPSAreaInformation', + 29: 'GPSDateStamp', + 30: 'GPSDifferential'} + + def __init__(self, endian, buffer, offset): + Ifd.__init__(self, endian, buffer, offset) + + # Now post process the raw IFD. + self.ifd = {} + for tag, value in self.raw_ifd.items(): + tag_name = self.tagnum2name[tag] + self.ifd[tag_name] = value + +class ExifInteroperabilityIfd(Ifd): + tagnum2name = {1: 'InteroperabilityIndex', + 2: 'InteroperabilityVersion', + 4096: 'RelatedImageFileFormat', + 4097: 'RelatedImageWidth', + 4098: 'RelatedImageLength'} + + def __init__(self, endian, buffer, offset): + Ifd.__init__(self, endian, buffer, offset) + + # Now post process the raw IFD. + self.ifd = {} + for tag, value in self.raw_ifd.items(): + tag_name = self.tagnum2name[tag] + self.ifd[tag_name] = value # Map each box ID to the corresponding class. _box_with_id = { @@ -1668,73 +1894,6 @@ def _indent(elem, level=0): if level and (not elem.tail or not elem.tail.strip()): elem.tail = i -_tagnum2name = {271: 'Make', 272: 'Model', - 282: 'XResolution', 283: 'YResolution', - 296: 'ResolutionUnit', - 531: 'YCbCrPositioning', - 34665: 'ExifTag', - 34853: 'GPSTag'} - -def _parse_exif(buffer): - """Interpret raw buffer consisting of Exif IFD. - """ - # Ignore the first six bytes. - # Next six should be (73, 73, 42, 8, numtags) - data = struct.unpack('