Merge branch 'issue104' of github.com:quintusdias/glymur into issue104

Conflicts:
	CHANGES.txt
This commit is contained in:
John Evans 2013-10-27 14:10:53 -04:00
commit f3d43cef7f
14 changed files with 1026 additions and 736 deletions

View file

@ -1,5 +1,7 @@
Oct 22, 2013 - v0.5.7 Super box constructors now take optional box list Oct 24, 2013 - v0.6.0 Palette box palette changed to 2D numpy array. Removed
argument. Removed ssiz attribute from SIZsegment class. nemo Exif and simple XMP UUIDs in favor of larger XMP UUID. Refactored
Exif UUID code into _uuid_io sub package. Superbox constructors now take
optional box list argument. Removed ssiz attribute from SIZsegment class.
Oct 13, 2013 - v0.5.6 Fixed handling of non-ascii chars in XML boxes. Fixed Oct 13, 2013 - v0.5.6 Fixed handling of non-ascii chars in XML boxes. Fixed
some docstring errors in jp2box module. some docstring errors in jp2box module.

540
glymur/_uuid_io/Exif.py Normal file
View file

@ -0,0 +1,540 @@
# -*- coding: utf-8 -*-
"""
Handlers for Exif UUIDs. Be nice if we would find a standard for this.
"""
import pprint
import struct
import sys
import warnings
if sys.hexversion < 0x02070000:
# pylint: disable=F0401,E0611
from ordereddict import OrderedDict
else:
from collections import OrderedDict
class UUIDExif(object):
"""
Attributes
----------
read_buffer : bytes
Raw byte stream consisting of the UUID data.
endian : str
Either '<' for big-endian, or '>' for little-endian.
"""
def __init__(self, read_buffer):
"""Interpret raw buffer consisting of Exif IFD.
"""
exif_image = None
exif_photo = None
exif_gpsinfo = None
exif_iop = None
self.read_buffer = read_buffer
# Ignore the first six bytes.
# Next 8 should be (73, 73, 42, 8) or (77, 77, 42, 8)
data = struct.unpack('<BB', read_buffer[6:8])
if data[0] == 73 and data[1] == 73:
# little endian
self.endian = '<'
elif data[0] == 77 and data[1] == 77:
# big endian
self.endian = '>'
else:
msg = "Bad byte order indication: {0}".format(read_buffer[6:8])
raise RuntimeError(msg)
_, offset = struct.unpack(self.endian + 'HI', read_buffer[8:14])
# This is the 'Exif Image' portion.
exif = _ExifImageIfd(self.endian, read_buffer[6:], offset)
exif_image = exif.processed_ifd
if 'ExifTag' in exif_image.keys():
offset = exif_image['ExifTag']
photo_ifd = _ExifPhotoIfd(self.endian, read_buffer[6:], offset)
exif_photo = photo_ifd.processed_ifd
if 'InteroperabilityTag' in exif_photo.keys():
offset = exif_photo['InteroperabilityTag']
interop = _ExifInteroperabilityIfd(self.endian,
read_buffer[6:],
offset)
exif_iop = interop.processed_ifd
if 'GPSTag' in exif_image.keys():
offset = exif_image['GPSTag']
gps = _ExifGPSInfoIfd(self.endian, read_buffer[6:], offset)
exif_gpsinfo = gps.processed_ifd
self.ifds = OrderedDict()
self.ifds['Image'] = exif_image
self.ifds['Photo'] = exif_photo
self.ifds['GPSInfo'] = exif_gpsinfo
self.ifds['Iop'] = exif_iop
def __str__(self):
# 2.7 has trouble pretty-printing ordered dicts, so print them
# as regular dicts. Not ideal, but at least it's good on 3.3+.
if sys.hexversion < 0x03000000:
data = dict(self.ifds)
else:
data = self.ifds
return '\n' + pprint.pformat(data)
class _Ifd(object):
"""
Attributes
----------
read_buffer : bytes
Raw byte stream consisting of the UUID data.
datatype2fmt : dictionary
Class attribute, maps the TIFF enumerated datatype to the python
datatype and data width.
endian : str
Either '<' for big-endian, or '>' for little-endian.
num_tags : int
Number of tags in the IFD.
raw_ifd : dictionary
Maps tag number to "mildly-interpreted" tag value.
processed_ifd : dictionary
Maps tag name 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, read_buffer, offset):
self.endian = endian
self.read_buffer = read_buffer
self.processed_ifd = OrderedDict()
self.num_tags, = struct.unpack(endian + 'H',
read_buffer[offset:offset + 2])
fmt = self.endian + 'HHII' * self.num_tags
ifd_buffer = read_buffer[offset + 2:offset + 2 + self.num_tags * 12]
data = struct.unpack(fmt, ifd_buffer)
self.raw_ifd = OrderedDict()
for j, tag in enumerate(data[0::4]):
# The offset to the tag offset/payload is the offset to the IFD
# plus 2 bytes for the number of tags plus 12 bytes for each
# tag entry plus 8 bytes to the offset/payload itself.
toffp = read_buffer[offset + 10 + j * 12:offset + 10 + j * 12 + 4]
tag_data = self.parse_tag(data[j * 4 + 1],
data[j * 4 + 2],
toffp)
self.raw_ifd[tag] = tag_data
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.read_buffer[offset:offset + payload_size]
if dtype == 2:
# ASCII
if sys.hexversion < 0x03000000:
payload = target_buffer.rstrip('\x00')
else:
payload = target_buffer.decode('utf-8').rstrip('\x00')
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
def post_process(self, tagnum2name):
"""Map the tag name instead of tag number to the tag value.
"""
for tag, value in self.raw_ifd.items():
try:
tag_name = tagnum2name[tag]
except KeyError:
# Ok, we don't recognize this tag. Just use the numeric id.
msg = 'Unrecognized Exif tag "{0}".'.format(tag)
warnings.warn(msg, UserWarning)
tag_name = tag
self.processed_ifd[tag_name] = value
class _ExifImageIfd(_Ifd):
"""
Attributes
----------
tagnum2name : dict
Maps Exif image tag numbers to the tag names.
ifd : dict
Maps tag names to tag values.
"""
tagnum2name = {11: 'ProcessingSoftware',
254: 'NewSubfileType',
255: 'SubfileType',
256: 'ImageWidth',
257: 'ImageLength',
258: 'BitsPerSample',
259: 'Compression',
262: 'PhotometricInterpretation',
263: 'Threshholding',
264: 'CellWidth',
265: 'CellLength',
266: 'FillOrder',
269: 'DocumentName',
270: 'ImageDescription',
271: 'Make',
272: 'Model',
273: 'StripOffsets',
274: 'Orientation',
277: 'SamplesPerPixel',
278: 'RowsPerStrip',
279: 'StripByteCounts',
282: 'XResolution',
283: 'YResolution',
284: 'PlanarConfiguration',
290: 'GrayResponseUnit',
291: 'GrayResponseCurve',
292: 'T4Options',
293: 'T6Options',
296: 'ResolutionUnit',
301: 'TransferFunction',
305: 'Software',
306: 'DateTime',
315: 'Artist',
316: 'HostComputer',
317: 'Predictor',
318: 'WhitePoint',
319: 'PrimaryChromaticities',
320: 'ColorMap',
321: 'HalftoneHints',
322: 'TileWidth',
323: 'TileLength',
324: 'TileOffsets',
325: 'TileByteCounts',
330: 'SubIFDs',
332: 'InkSet',
333: 'InkNames',
334: 'NumberOfInks',
336: 'DotRange',
337: 'TargetPrinter',
338: 'ExtraSamples',
339: 'SampleFormat',
340: 'SMinSampleValue',
341: 'SMaxSampleValue',
342: 'TransferRange',
343: 'ClipPath',
344: 'XClipPathUnits',
345: 'YClipPathUnits',
346: 'Indexed',
347: 'JPEGTables',
351: 'OPIProxy',
512: 'JPEGProc',
513: 'JPEGInterchangeFormat',
514: 'JPEGInterchangeFormatLength',
515: 'JPEGRestartInterval',
517: 'JPEGLosslessPredictors',
518: 'JPEGPointTransforms',
519: 'JPEGQTables',
520: 'JPEGDCTables',
521: 'JPEGACTables',
529: 'YCbCrCoefficients',
530: 'YCbCrSubSampling',
531: 'YCbCrPositioning',
532: 'ReferenceBlackWhite',
700: 'XMLPacket',
18246: 'Rating',
18249: 'RatingPercent',
32781: 'ImageID',
33421: 'CFARepeatPatternDim',
33422: 'CFAPattern',
33423: 'BatteryLevel',
33432: 'Copyright',
33434: 'ExposureTime',
33437: 'FNumber',
33723: 'IPTCNAA',
34377: 'ImageResources',
34665: 'ExifTag',
34675: 'InterColorProfile',
34850: 'ExposureProgram',
34852: 'SpectralSensitivity',
34853: 'GPSTag',
34855: 'ISOSpeedRatings',
34856: 'OECF',
34857: 'Interlace',
34858: 'TimeZoneOffset',
34859: 'SelfTimerMode',
36867: 'DateTimeOriginal',
37122: 'CompressedBitsPerPixel',
37377: 'ShutterSpeedValue',
37378: 'ApertureValue',
37379: 'BrightnessValue',
37380: 'ExposureBiasValue',
37381: 'MaxApertureValue',
37382: 'SubjectDistance',
37383: 'MeteringMode',
37384: 'LightSource',
37385: 'Flash',
37386: 'FocalLength',
37387: 'FlashEnergy',
37388: 'SpatialFrequencyResponse',
37389: 'Noise',
37390: 'FocalPlaneXResolution',
37391: 'FocalPlaneYResolution',
37392: 'FocalPlaneResolutionUnit',
37393: 'ImageNumber',
37394: 'SecurityClassification',
37395: 'ImageHistory',
37396: 'SubjectLocation',
37397: 'ExposureIndex',
37398: 'TIFFEPStandardID',
37399: 'SensingMethod',
40091: 'XPTitle',
40092: 'XPComment',
40093: 'XPAuthor',
40094: 'XPKeywords',
40095: 'XPSubject',
50341: 'PrintImageMatching',
50706: 'DNGVersion',
50707: 'DNGBackwardVersion',
50708: 'UniqueCameraModel',
50709: 'LocalizedCameraModel',
50710: 'CFAPlaneColor',
50711: 'CFALayout',
50712: 'LinearizationTable',
50713: 'BlackLevelRepeatDim',
50714: 'BlackLevel',
50715: 'BlackLevelDeltaH',
50716: 'BlackLevelDeltaV',
50717: 'WhiteLevel',
50718: 'DefaultScale',
50719: 'DefaultCropOrigin',
50720: 'DefaultCropSize',
50721: 'ColorMatrix1',
50722: 'ColorMatrix2',
50723: 'CameraCalibration1',
50724: 'CameraCalibration2',
50725: 'ReductionMatrix1',
50726: 'ReductionMatrix2',
50727: 'AnalogBalance',
50728: 'AsShotNeutral',
50729: 'AsShotWhiteXY',
50730: 'BaselineExposure',
50731: 'BaselineNoise',
50732: 'BaselineSharpness',
50733: 'BayerGreenSplit',
50734: 'LinearResponseLimit',
50735: 'CameraSerialNumber',
50736: 'LensInfo',
50737: 'ChromaBlurRadius',
50738: 'AntiAliasStrength',
50739: 'ShadowScale',
50740: 'DNGPrivateData',
50741: 'MakerNoteSafety',
50778: 'CalibrationIlluminant1',
50779: 'CalibrationIlluminant2',
50780: 'BestQualityScale',
50781: 'RawDataUniqueID',
50827: 'OriginalRawFileName',
50828: 'OriginalRawFileData',
50829: 'ActiveArea',
50830: 'MaskedAreas',
50831: 'AsShotICCProfile',
50832: 'AsShotPreProfileMatrix',
50833: 'CurrentICCProfile',
50834: 'CurrentPreProfileMatrix',
50879: 'ColorimetricReference',
50931: 'CameraCalibrationSignature',
50932: 'ProfileCalibrationSignature',
50934: 'AsShotProfileName',
50935: 'NoiseReductionApplied',
50936: 'ProfileName',
50937: 'ProfileHueSatMapDims',
50938: 'ProfileHueSatMapData1',
50939: 'ProfileHueSatMapData2',
50940: 'ProfileToneCurve',
50941: 'ProfileEmbedPolicy',
50942: 'ProfileCopyright',
50964: 'ForwardMatrix1',
50965: 'ForwardMatrix2',
50966: 'PreviewApplicationName',
50967: 'PreviewApplicationVersion',
50968: 'PreviewSettingsName',
50969: 'PreviewSettingsDigest',
50970: 'PreviewColorSpace',
50971: 'PreviewDateTime',
50972: 'RawImageDigest',
50973: 'OriginalRawFileDigest',
50974: 'SubTileBlockSize',
50975: 'RowInterleaveFactor',
50981: 'ProfileLookTableDims',
50982: 'ProfileLookTableData',
51008: 'OpcodeList1',
51009: 'OpcodeList2',
51022: 'OpcodeList3',
51041: 'NoiseProfile'}
def __init__(self, endian, read_buffer, offset):
_Ifd.__init__(self, endian, read_buffer, offset)
self.post_process(self.tagnum2name)
class _ExifPhotoIfd(_Ifd):
"""Represents tags found in the Exif sub ifd.
"""
tagnum2name = {33434: 'ExposureTime',
33437: 'FNumber',
34850: 'ExposureProgram',
34852: 'SpectralSensitivity',
34855: 'ISOSpeedRatings',
34856: 'OECF',
34864: 'SensitivityType',
34865: 'StandardOutputSensitivity',
34866: 'RecommendedExposureIndex',
34867: 'ISOSpeed',
34868: 'ISOSpeedLatitudeyyy',
34869: 'ISOSpeedLatitudezzz',
36864: 'ExifVersion',
36867: 'DateTimeOriginal',
36868: 'DateTimeDigitized',
37121: 'ComponentsConfiguration',
37122: 'CompressedBitsPerPixel',
37377: 'ShutterSpeedValue',
37378: 'ApertureValue',
37379: 'BrightnessValue',
37380: 'ExposureBiasValue',
37381: 'MaxApertureValue',
37382: 'SubjectDistance',
37383: 'MeteringMode',
37384: 'LightSource',
37385: 'Flash',
37386: 'FocalLength',
37396: 'SubjectArea',
37500: 'MakerNote',
37510: 'UserComment',
37520: 'SubSecTime',
37521: 'SubSecTimeOriginal',
37522: 'SubSecTimeDigitized',
40960: 'FlashpixVersion',
40961: 'ColorSpace',
40962: 'PixelXDimension',
40963: 'PixelYDimension',
40964: 'RelatedSoundFile',
40965: 'InteroperabilityTag',
41483: 'FlashEnergy',
41484: 'SpatialFrequencyResponse',
41486: 'FocalPlaneXResolution',
41487: 'FocalPlaneYResolution',
41488: 'FocalPlaneResolutionUnit',
41492: 'SubjectLocation',
41493: 'ExposureIndex',
41495: 'SensingMethod',
41728: 'FileSource',
41729: 'SceneType',
41730: 'CFAPattern',
41985: 'CustomRendered',
41986: 'ExposureMode',
41987: 'WhiteBalance',
41988: 'DigitalZoomRatio',
41989: 'FocalLengthIn35mmFilm',
41990: 'SceneCaptureType',
41991: 'GainControl',
41992: 'Contrast',
41993: 'Saturation',
41994: 'Sharpness',
41995: 'DeviceSettingDescription',
41996: 'SubjectDistanceRange',
42016: 'ImageUniqueID',
42032: 'CameraOwnerName',
42033: 'BodySerialNumber',
42034: 'LensSpecification',
42035: 'LensMake',
42036: 'LensModel',
42037: 'LensSerialNumber'}
def __init__(self, endian, read_buffer, offset):
_Ifd.__init__(self, endian, read_buffer, offset)
self.post_process(self.tagnum2name)
class _ExifGPSInfoIfd(_Ifd):
"""Represents information found in the GPSInfo sub 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, read_buffer, offset):
_Ifd.__init__(self, endian, read_buffer, offset)
self.post_process(self.tagnum2name)
class _ExifInteroperabilityIfd(_Ifd):
"""Represents tags found in the Interoperability sub IFD.
"""
tagnum2name = {1: 'InteroperabilityIndex',
2: 'InteroperabilityVersion',
4096: 'RelatedImageFileFormat',
4097: 'RelatedImageWidth',
4098: 'RelatedImageLength'}
def __init__(self, endian, read_buffer, offset):
_Ifd.__init__(self, endian, read_buffer, offset)
self.post_process(self.tagnum2name)

46
glymur/_uuid_io/XMP.py Normal file
View file

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
"""
Handler for a UUID for XMP.
"""
import sys
from xml.etree import cElementTree as ET
from ..core import _pretty_print_xml
class UUIDXMP(object):
"""
Handler for a UUID for XMP.
Attributes
----------
packet : ElementTree
XML conforming to the XMP specifications.
References
----------
.. [XMP] International Organization for Standardication. ISO/IEC
16684-1:2012 - Graphic technology -- Extensible metadata platform (XMP)
specification -- Part 1: Data model, serialization and core properties
"""
def __init__(self, read_buffer):
"""
Parameters
----------
read_buffer : byte array
sequence of bytes that can be decoded into an XMP packet.
"""
# XMP data. Parse as XML.
if sys.hexversion < 0x03000000:
# 2.x strings same as bytes
elt = ET.fromstring(read_buffer)
else:
# 3.x takes strings, not bytes.
text = read_buffer.decode('utf-8')
elt = ET.fromstring(text)
self.packet = ET.ElementTree(elt)
def __str__(self):
return _pretty_print_xml(self.packet)

View file

@ -0,0 +1,6 @@
"""
Sub package for handling various UUIDs.
"""
from .Exif import UUIDExif
from .XMP import UUIDXMP
from .generic import UUIDGeneric

View file

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
"""
Handler for a generic UUID.
"""
class UUIDGeneric(object):
"""
Handler for a generic UUID that is not currently recognized.
Attributes
----------
data : byte array
Sequence of uninterpreted bytes as read from the file.
"""
def __init__(self, read_buffer):
"""
Parameters
----------
read_buffer : byte array
sequence of bytes as read from the file.
"""
self.data = read_buffer
def __str__(self):
return '{0} bytes'.format(len(self.data))

View file

@ -1,5 +1,8 @@
"""Core definitions to be shared amongst the modules. """Core definitions to be shared amongst the modules.
""" """
import copy
import xml.etree.cElementTree as ET
# Progression order # Progression order
LRCP = 0 LRCP = 0
RLCP = 1 RLCP = 1
@ -73,3 +76,45 @@ _CAPABILITIES_DISPLAY = {
1: '0', 1: '0',
2: '1', 2: '1',
3: '3'} 3: '3'}
def _pretty_print_xml(xml, level=0):
"""Pretty print XML data.
"""
xml = copy.deepcopy(xml)
_indent(xml.getroot(), level=level)
xmltext = ET.tostring(xml.getroot(), encoding='utf-8').decode('utf-8')
# Indent it a bit.
lst = [(' ' + x) for x in xmltext.split('\n')]
try:
xml = '\n'.join(lst)
return '\n{0}'.format(xml)
except UnicodeEncodeError:
# This can happen on python 2.x if the character set contains certain
# non-ascii characters. Just print out the corresponding xml char
# entities instead.
xml = u'\n'.join(lst)
text = u'\n{0}'.format(xml)
text = text.encode('ascii', 'xmlcharrefreplace')
return text
def _indent(elem, level=0):
"""Recipe for pretty printing XML. Please see
http://effbot.org/zone/element-lib.htm#prettyprint
"""
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
_indent(elem, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i

Binary file not shown.

View file

@ -13,13 +13,13 @@ References
# pylint: disable=C0302,R0903,R0913 # pylint: disable=C0302,R0903,R0913
import copy
import datetime import datetime
import math import math
import os import os
import pprint import pprint
import struct import struct
import sys import sys
import traceback
import uuid import uuid
import warnings import warnings
import xml.etree.cElementTree as ET import xml.etree.cElementTree as ET
@ -38,6 +38,9 @@ from .core import _COLORSPACE_MAP_DISPLAY
from .core import _COLOR_TYPE_MAP_DISPLAY from .core import _COLOR_TYPE_MAP_DISPLAY
from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE
from .core import ANY_ICC_PROFILE, VENDOR_COLOR_METHOD from .core import ANY_ICC_PROFILE, VENDOR_COLOR_METHOD
from .core import _pretty_print_xml
from . import _uuid_io
_METHOD_DISPLAY = { _METHOD_DISPLAY = {
ENUMERATED_COLORSPACE: 'enumerated colorspace', ENUMERATED_COLORSPACE: 'enumerated colorspace',
@ -2192,8 +2195,11 @@ class UUIDBox(Jp2kBox):
---------- ----------
the_uuid : uuid.UUID the_uuid : uuid.UUID
Identifies the type of UUID box. Identifies the type of UUID box.
data : object
Specific to each type of UUID. There are handlers for XMP, Exif,
and unknown UUIDs.
raw_data : byte array raw_data : byte array
This is the "payload" of data for the specified UUID. Sequence of uninterpreted bytes as read from the file.
length : int length : int
length of the box in bytes. length of the box in bytes.
offset : int offset : int
@ -2203,34 +2209,25 @@ class UUIDBox(Jp2kBox):
self.uuid = the_uuid self.uuid = the_uuid
self.raw_data = raw_data self.raw_data = raw_data
try:
if the_uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): if the_uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
# XMP data. Parse as XML. Seems to be a difference between self.data = _uuid_io.UUIDXMP(raw_data)
# ElementTree in version 2.7 and 3.3.
if sys.hexversion < 0x03000000:
elt = ET.fromstring(raw_data)
else:
text = raw_data.decode('utf-8')
elt = ET.fromstring(text)
self.data = ET.ElementTree(elt)
self._type = 'XMP' self._type = 'XMP'
elif the_uuid.bytes == b'JpgTiffExif->JP2': elif the_uuid.bytes == b'JpgTiffExif->JP2':
exif_obj = Exif(raw_data) self.data = _uuid_io.UUIDExif(raw_data)
ifds = OrderedDict()
ifds['Image'] = exif_obj.exif_image
ifds['Photo'] = exif_obj.exif_photo
ifds['GPSInfo'] = exif_obj.exif_gpsinfo
ifds['Iop'] = exif_obj.exif_iop
self.data = ifds
self._type = 'Exif' self._type = 'Exif'
else: else:
self.data = raw_data self.data = _uuid_io.UUIDGeneric(raw_data)
self._type = 'unknown' self._type = 'unknown'
except Exception:
# In case of any exception, create the generic UUID.
self.data = _uuid_io.UUIDGeneric(raw_data)
self._type = 'unknown'
msg = "Error encountered during UUID processing, "
msg += "the UUID will be treated as generic.\n\n{0}"
warnings.warn(msg.format(traceback.format_exc()))
if length == 0: self.raw_data = raw_data
# Need to compute the length.
# The length is 8 (L and T fields) + 16 (length of UUID identifier)
# + length of uuid data.
length = 24 + len(self.data)
self.length = length self.length = length
self.offset = offset self.offset = offset
@ -2243,41 +2240,32 @@ class UUIDBox(Jp2kBox):
def __str__(self): def __str__(self):
msg = '{0}\n' msg = '{0}\n'
msg += ' UUID: {1}{2}\n' msg += ' UUID: {1} ({2})\n'
msg += ' UUID Data: {3}' msg += ' UUID Data: {3}'
if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
uuid_type = ' (XMP)'
uuid_data = _pretty_print_xml(self.data)
elif self.uuid.bytes == b'JpgTiffExif->JP2':
uuid_type = ' (Exif)'
# 2.7 has trouble pretty-printing ordered dicts, so print them
# as regular dicts. Not ideal, but at least it's good on 3.3+.
if sys.hexversion < 0x03000000:
data = dict(self.data)
else:
data = self.data
uuid_data = '\n' + pprint.pformat(data)
else:
uuid_type = ''
uuid_data = '{0} bytes'.format(len(self.data))
msg = msg.format(Jp2kBox.__str__(self), msg = msg.format(Jp2kBox.__str__(self),
self.uuid, self.uuid,
uuid_type, self._type,
uuid_data) str(self.data))
return msg return msg
def write(self, fptr): def write(self, fptr):
"""Write a UUID box box to file. """Write a UUID box box to file.
""" """
if self._type != 'XMP': if self._type != 'XMP':
msg = "Only XMP UUID boxes can currently be written." msg = "Only XMP UUID boxes can currently be written."
raise NotImplementedError(msg) raise NotImplementedError(msg)
read_buffer = struct.pack('>I4s', self.length, 'uuid') serialized = b'<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>'
serialized += ET.tostring(self.data.packet.getroot(), encoding='utf-8')
serialized += b'<?xpacket end="w"?>'
if self.length == 0:
self.length = 24 + len(serialized)
read_buffer = struct.pack('>I4s', self.length, b'uuid')
fptr.write(read_buffer) fptr.write(read_buffer)
fptr.write(self.data) fptr.write(self.uuid.bytes)
fptr.write(serialized)
@staticmethod @staticmethod
def parse(fptr, offset, length): def parse(fptr, offset, length):
@ -2306,511 +2294,6 @@ class UUIDBox(Jp2kBox):
return box return box
class Exif(object):
"""
Attributes
----------
read_buffer : bytes
Raw byte stream consisting of the UUID data.
endian : str
Either '<' for big-endian, or '>' for little-endian.
"""
def __init__(self, read_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.read_buffer = read_buffer
# Ignore the first six bytes.
# Next 8 should be (73, 73, 42, 8)
data = struct.unpack('<BBHI', read_buffer[6:14])
if data[0] == 73 and data[1] == 73:
# little endian
self.endian = '<'
else:
# big endian
self.endian = '>'
offset = data[3]
# This is the 'Exif Image' portion.
exif = _ExifImageIfd(self.endian, read_buffer[6:], offset)
self.exif_image = exif.processed_ifd
if 'ExifTag' in self.exif_image.keys():
offset = self.exif_image['ExifTag']
photo = _ExifPhotoIfd(self.endian, read_buffer[6:], offset)
self.exif_photo = photo.processed_ifd
if 'InteroperabilityTag' in self.exif_photo.keys():
offset = self.exif_photo['InteroperabilityTag']
interop = _ExifInteroperabilityIfd(self.endian,
read_buffer[6:],
offset)
self.iop = interop.processed_ifd
if 'GPSTag' in self.exif_image.keys():
offset = self.exif_image['GPSTag']
gps = _ExifGPSInfoIfd(self.endian, read_buffer[6:], offset)
self.exif_gpsinfo = gps.processed_ifd
class _Ifd(object):
"""
Attributes
----------
read_buffer : bytes
Raw byte stream consisting of the UUID data.
datatype2fmt : dictionary
Class attribute, maps the TIFF enumerated datatype to the python
datatype and data width.
endian : str
Either '<' for big-endian, or '>' for little-endian.
num_tags : int
Number of tags in the IFD.
raw_ifd : dictionary
Maps tag number to "mildly-interpreted" tag value.
processed_ifd : dictionary
Maps tag name 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, read_buffer, offset):
self.endian = endian
self.read_buffer = read_buffer
self.processed_ifd = OrderedDict()
self.num_tags, = struct.unpack(endian + 'H',
read_buffer[offset:offset + 2])
fmt = self.endian + 'HHII' * self.num_tags
ifd_buffer = read_buffer[offset + 2:offset + 2 + self.num_tags * 12]
data = struct.unpack(fmt, ifd_buffer)
self.raw_ifd = OrderedDict()
for j, tag in enumerate(data[0::4]):
# The offset to the tag offset/payload is the offset to the IFD
# plus 2 bytes for the number of tags plus 12 bytes for each
# tag entry plus 8 bytes to the offset/payload itself.
toffp = read_buffer[offset + 10 + j * 12:offset + 10 + j * 12 + 4]
tag_data = self.parse_tag(data[j * 4 + 1],
data[j * 4 + 2],
toffp)
self.raw_ifd[tag] = tag_data
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.read_buffer[offset:offset + payload_size]
if dtype == 2:
# ASCII
if sys.hexversion < 0x03000000:
payload = target_buffer.rstrip('\x00')
else:
payload = target_buffer.decode('utf-8').rstrip('\x00')
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
def post_process(self, tagnum2name):
"""Map the tag name instead of tag number to the tag value.
"""
for tag, value in self.raw_ifd.items():
try:
tag_name = tagnum2name[tag]
except KeyError:
# Ok, we don't recognize this tag. Just use the numeric id.
msg = 'Unrecognized Exif tag "{0}".'.format(tag)
warnings.warn(msg, UserWarning)
tag_name = tag
self.processed_ifd[tag_name] = value
class _ExifImageIfd(_Ifd):
"""
Attributes
----------
tagnum2name : dict
Maps Exif image tag numbers to the tag names.
ifd : dict
Maps tag names to tag values.
"""
tagnum2name = {11: 'ProcessingSoftware',
254: 'NewSubfileType',
255: 'SubfileType',
256: 'ImageWidth',
257: 'ImageLength',
258: 'BitsPerSample',
259: 'Compression',
262: 'PhotometricInterpretation',
263: 'Threshholding',
264: 'CellWidth',
265: 'CellLength',
266: 'FillOrder',
269: 'DocumentName',
270: 'ImageDescription',
271: 'Make',
272: 'Model',
273: 'StripOffsets',
274: 'Orientation',
277: 'SamplesPerPixel',
278: 'RowsPerStrip',
279: 'StripByteCounts',
282: 'XResolution',
283: 'YResolution',
284: 'PlanarConfiguration',
290: 'GrayResponseUnit',
291: 'GrayResponseCurve',
292: 'T4Options',
293: 'T6Options',
296: 'ResolutionUnit',
301: 'TransferFunction',
305: 'Software',
306: 'DateTime',
315: 'Artist',
316: 'HostComputer',
317: 'Predictor',
318: 'WhitePoint',
319: 'PrimaryChromaticities',
320: 'ColorMap',
321: 'HalftoneHints',
322: 'TileWidth',
323: 'TileLength',
324: 'TileOffsets',
325: 'TileByteCounts',
330: 'SubIFDs',
332: 'InkSet',
333: 'InkNames',
334: 'NumberOfInks',
336: 'DotRange',
337: 'TargetPrinter',
338: 'ExtraSamples',
339: 'SampleFormat',
340: 'SMinSampleValue',
341: 'SMaxSampleValue',
342: 'TransferRange',
343: 'ClipPath',
344: 'XClipPathUnits',
345: 'YClipPathUnits',
346: 'Indexed',
347: 'JPEGTables',
351: 'OPIProxy',
512: 'JPEGProc',
513: 'JPEGInterchangeFormat',
514: 'JPEGInterchangeFormatLength',
515: 'JPEGRestartInterval',
517: 'JPEGLosslessPredictors',
518: 'JPEGPointTransforms',
519: 'JPEGQTables',
520: 'JPEGDCTables',
521: 'JPEGACTables',
529: 'YCbCrCoefficients',
530: 'YCbCrSubSampling',
531: 'YCbCrPositioning',
532: 'ReferenceBlackWhite',
700: 'XMLPacket',
18246: 'Rating',
18249: 'RatingPercent',
32781: 'ImageID',
33421: 'CFARepeatPatternDim',
33422: 'CFAPattern',
33423: 'BatteryLevel',
33432: 'Copyright',
33434: 'ExposureTime',
33437: 'FNumber',
33723: 'IPTCNAA',
34377: 'ImageResources',
34665: 'ExifTag',
34675: 'InterColorProfile',
34850: 'ExposureProgram',
34852: 'SpectralSensitivity',
34853: 'GPSTag',
34855: 'ISOSpeedRatings',
34856: 'OECF',
34857: 'Interlace',
34858: 'TimeZoneOffset',
34859: 'SelfTimerMode',
36867: 'DateTimeOriginal',
37122: 'CompressedBitsPerPixel',
37377: 'ShutterSpeedValue',
37378: 'ApertureValue',
37379: 'BrightnessValue',
37380: 'ExposureBiasValue',
37381: 'MaxApertureValue',
37382: 'SubjectDistance',
37383: 'MeteringMode',
37384: 'LightSource',
37385: 'Flash',
37386: 'FocalLength',
37387: 'FlashEnergy',
37388: 'SpatialFrequencyResponse',
37389: 'Noise',
37390: 'FocalPlaneXResolution',
37391: 'FocalPlaneYResolution',
37392: 'FocalPlaneResolutionUnit',
37393: 'ImageNumber',
37394: 'SecurityClassification',
37395: 'ImageHistory',
37396: 'SubjectLocation',
37397: 'ExposureIndex',
37398: 'TIFFEPStandardID',
37399: 'SensingMethod',
40091: 'XPTitle',
40092: 'XPComment',
40093: 'XPAuthor',
40094: 'XPKeywords',
40095: 'XPSubject',
50341: 'PrintImageMatching',
50706: 'DNGVersion',
50707: 'DNGBackwardVersion',
50708: 'UniqueCameraModel',
50709: 'LocalizedCameraModel',
50710: 'CFAPlaneColor',
50711: 'CFALayout',
50712: 'LinearizationTable',
50713: 'BlackLevelRepeatDim',
50714: 'BlackLevel',
50715: 'BlackLevelDeltaH',
50716: 'BlackLevelDeltaV',
50717: 'WhiteLevel',
50718: 'DefaultScale',
50719: 'DefaultCropOrigin',
50720: 'DefaultCropSize',
50721: 'ColorMatrix1',
50722: 'ColorMatrix2',
50723: 'CameraCalibration1',
50724: 'CameraCalibration2',
50725: 'ReductionMatrix1',
50726: 'ReductionMatrix2',
50727: 'AnalogBalance',
50728: 'AsShotNeutral',
50729: 'AsShotWhiteXY',
50730: 'BaselineExposure',
50731: 'BaselineNoise',
50732: 'BaselineSharpness',
50733: 'BayerGreenSplit',
50734: 'LinearResponseLimit',
50735: 'CameraSerialNumber',
50736: 'LensInfo',
50737: 'ChromaBlurRadius',
50738: 'AntiAliasStrength',
50739: 'ShadowScale',
50740: 'DNGPrivateData',
50741: 'MakerNoteSafety',
50778: 'CalibrationIlluminant1',
50779: 'CalibrationIlluminant2',
50780: 'BestQualityScale',
50781: 'RawDataUniqueID',
50827: 'OriginalRawFileName',
50828: 'OriginalRawFileData',
50829: 'ActiveArea',
50830: 'MaskedAreas',
50831: 'AsShotICCProfile',
50832: 'AsShotPreProfileMatrix',
50833: 'CurrentICCProfile',
50834: 'CurrentPreProfileMatrix',
50879: 'ColorimetricReference',
50931: 'CameraCalibrationSignature',
50932: 'ProfileCalibrationSignature',
50934: 'AsShotProfileName',
50935: 'NoiseReductionApplied',
50936: 'ProfileName',
50937: 'ProfileHueSatMapDims',
50938: 'ProfileHueSatMapData1',
50939: 'ProfileHueSatMapData2',
50940: 'ProfileToneCurve',
50941: 'ProfileEmbedPolicy',
50942: 'ProfileCopyright',
50964: 'ForwardMatrix1',
50965: 'ForwardMatrix2',
50966: 'PreviewApplicationName',
50967: 'PreviewApplicationVersion',
50968: 'PreviewSettingsName',
50969: 'PreviewSettingsDigest',
50970: 'PreviewColorSpace',
50971: 'PreviewDateTime',
50972: 'RawImageDigest',
50973: 'OriginalRawFileDigest',
50974: 'SubTileBlockSize',
50975: 'RowInterleaveFactor',
50981: 'ProfileLookTableDims',
50982: 'ProfileLookTableData',
51008: 'OpcodeList1',
51009: 'OpcodeList2',
51022: 'OpcodeList3',
51041: 'NoiseProfile'}
def __init__(self, endian, read_buffer, offset):
_Ifd.__init__(self, endian, read_buffer, offset)
self.post_process(self.tagnum2name)
class _ExifPhotoIfd(_Ifd):
"""Represents tags found in the Exif sub ifd.
"""
tagnum2name = {33434: 'ExposureTime',
33437: 'FNumber',
34850: 'ExposureProgram',
34852: 'SpectralSensitivity',
34855: 'ISOSpeedRatings',
34856: 'OECF',
34864: 'SensitivityType',
34865: 'StandardOutputSensitivity',
34866: 'RecommendedExposureIndex',
34867: 'ISOSpeed',
34868: 'ISOSpeedLatitudeyyy',
34869: 'ISOSpeedLatitudezzz',
36864: 'ExifVersion',
36867: 'DateTimeOriginal',
36868: 'DateTimeDigitized',
37121: 'ComponentsConfiguration',
37122: 'CompressedBitsPerPixel',
37377: 'ShutterSpeedValue',
37378: 'ApertureValue',
37379: 'BrightnessValue',
37380: 'ExposureBiasValue',
37381: 'MaxApertureValue',
37382: 'SubjectDistance',
37383: 'MeteringMode',
37384: 'LightSource',
37385: 'Flash',
37386: 'FocalLength',
37396: 'SubjectArea',
37500: 'MakerNote',
37510: 'UserComment',
37520: 'SubSecTime',
37521: 'SubSecTimeOriginal',
37522: 'SubSecTimeDigitized',
40960: 'FlashpixVersion',
40961: 'ColorSpace',
40962: 'PixelXDimension',
40963: 'PixelYDimension',
40964: 'RelatedSoundFile',
40965: 'InteroperabilityTag',
41483: 'FlashEnergy',
41484: 'SpatialFrequencyResponse',
41486: 'FocalPlaneXResolution',
41487: 'FocalPlaneYResolution',
41488: 'FocalPlaneResolutionUnit',
41492: 'SubjectLocation',
41493: 'ExposureIndex',
41495: 'SensingMethod',
41728: 'FileSource',
41729: 'SceneType',
41730: 'CFAPattern',
41985: 'CustomRendered',
41986: 'ExposureMode',
41987: 'WhiteBalance',
41988: 'DigitalZoomRatio',
41989: 'FocalLengthIn35mmFilm',
41990: 'SceneCaptureType',
41991: 'GainControl',
41992: 'Contrast',
41993: 'Saturation',
41994: 'Sharpness',
41995: 'DeviceSettingDescription',
41996: 'SubjectDistanceRange',
42016: 'ImageUniqueID',
42032: 'CameraOwnerName',
42033: 'BodySerialNumber',
42034: 'LensSpecification',
42035: 'LensMake',
42036: 'LensModel',
42037: 'LensSerialNumber'}
def __init__(self, endian, read_buffer, offset):
_Ifd.__init__(self, endian, read_buffer, offset)
self.post_process(self.tagnum2name)
class _ExifGPSInfoIfd(_Ifd):
"""Represents information found in the GPSInfo sub 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, read_buffer, offset):
_Ifd.__init__(self, endian, read_buffer, offset)
self.post_process(self.tagnum2name)
class _ExifInteroperabilityIfd(_Ifd):
"""Represents tags found in the Interoperability sub IFD.
"""
tagnum2name = {1: 'InteroperabilityIndex',
2: 'InteroperabilityVersion',
4096: 'RelatedImageFileFormat',
4097: 'RelatedImageWidth',
4098: 'RelatedImageLength'}
def __init__(self, endian, read_buffer, offset):
_Ifd.__init__(self, endian, read_buffer, offset)
self.post_process(self.tagnum2name)
# Map each box ID to the corresponding class. # Map each box ID to the corresponding class.
_BOX_WITH_ID = { _BOX_WITH_ID = {
'asoc': AssociationBox, 'asoc': AssociationBox,
@ -2835,45 +2318,3 @@ _BOX_WITH_ID = {
'url ': DataEntryURLBox, 'url ': DataEntryURLBox,
'uuid': UUIDBox, 'uuid': UUIDBox,
'xml ': XMLBox} 'xml ': XMLBox}
def _indent(elem, level=0):
"""Recipe for pretty printing XML. Please see
http://effbot.org/zone/element-lib.htm#prettyprint
"""
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
_indent(elem, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def _pretty_print_xml(xml, level=0):
"""Pretty print XML data.
"""
xml = copy.deepcopy(xml)
_indent(xml.getroot(), level=level)
xmltext = ET.tostring(xml.getroot(), encoding='utf-8').decode('utf-8')
# Indent it a bit.
lst = [(' ' + x) for x in xmltext.split('\n')]
try:
xml = '\n'.join(lst)
return '\n{0}'.format(xml)
except UnicodeEncodeError:
# This can happen on python 2.x if the character set contains certain
# non-ascii characters. Just print out the corresponding xml char
# entities instead.
xml = u'\n'.join(lst)
text = u'\n{0}'.format(xml)
text = text.encode('ascii', 'xmlcharrefreplace')
return text

View file

@ -1017,7 +1017,7 @@ class Jp2k(Jp2kBox):
>>> jp2 = glymur.Jp2k(jfile) >>> jp2 = glymur.Jp2k(jfile)
>>> codestream = jp2.get_codestream() >>> codestream = jp2.get_codestream()
>>> print(codestream.segment[1]) >>> print(codestream.segment[1])
SIZ marker segment @ (3137, 47) SIZ marker segment @ (3233, 47)
Profile: 2 Profile: 2
Reference Grid Height, Width: (1456 x 2592) Reference Grid Height, Width: (1456 x 2592)
Vertical, Horizontal Reference Grid Offset: (0 x 0) Vertical, Horizontal Reference Grid Offset: (0 x 0)

View file

@ -167,3 +167,90 @@ def read_pgx_header(pgx_file):
header = header.rstrip() header = header.rstrip()
return header, pos return header, pos
nemo_xmp_box = """UUID Box (uuid) @ (77, 3146)
UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)
UUID Data:
<ns0:xmpmeta xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:ns0="adobe:ns:meta/" xmlns:ns2="http://ns.adobe.com/xap/1.0/" xmlns:ns3="http://ns.adobe.com/tiff/1.0/" xmlns:ns4="http://ns.adobe.com/exif/1.0/" xmlns:ns5="http://ns.adobe.com/photoshop/1.0/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" ns0:xmptk="Exempi + XMP Core 5.1.2">
<rdf:RDF>
<rdf:Description rdf:about="">
<ns2:CreatorTool>Google</ns2:CreatorTool>
<ns2:CreateDate>2013-02-09T14:47:53</ns2:CreateDate>
</rdf:Description>
<rdf:Description rdf:about="">
<ns3:YCbCrPositioning>1</ns3:YCbCrPositioning>
<ns3:XResolution>72/1</ns3:XResolution>
<ns3:YResolution>72/1</ns3:YResolution>
<ns3:ResolutionUnit>2</ns3:ResolutionUnit>
<ns3:Make>HTC</ns3:Make>
<ns3:Model>HTC Glacier</ns3:Model>
<ns3:ImageWidth>2592</ns3:ImageWidth>
<ns3:ImageLength>1456</ns3:ImageLength>
<ns3:BitsPerSample>
<rdf:Seq>
<rdf:li>8</rdf:li>
<rdf:li>8</rdf:li>
<rdf:li>8</rdf:li>
</rdf:Seq>
</ns3:BitsPerSample>
<ns3:PhotometricInterpretation>2</ns3:PhotometricInterpretation>
<ns3:SamplesPerPixel>3</ns3:SamplesPerPixel>
<ns3:WhitePoint>
<rdf:Seq>
<rdf:li>1343036288/4294967295</rdf:li>
<rdf:li>1413044224/4294967295</rdf:li>
</rdf:Seq>
</ns3:WhitePoint>
<ns3:PrimaryChromaticities>
<rdf:Seq>
<rdf:li>2748779008/4294967295</rdf:li>
<rdf:li>1417339264/4294967295</rdf:li>
<rdf:li>1288490240/4294967295</rdf:li>
<rdf:li>2576980480/4294967295</rdf:li>
<rdf:li>644245120/4294967295</rdf:li>
<rdf:li>257698032/4294967295</rdf:li>
</rdf:Seq>
</ns3:PrimaryChromaticities>
</rdf:Description>
<rdf:Description rdf:about="">
<ns4:ColorSpace>1</ns4:ColorSpace>
<ns4:PixelXDimension>2528</ns4:PixelXDimension>
<ns4:PixelYDimension>1424</ns4:PixelYDimension>
<ns4:FocalLength>353/100</ns4:FocalLength>
<ns4:GPSAltitudeRef>0</ns4:GPSAltitudeRef>
<ns4:GPSAltitude>0/1</ns4:GPSAltitude>
<ns4:GPSMapDatum>WGS-84</ns4:GPSMapDatum>
<ns4:DateTimeOriginal>2013-02-09T14:47:53</ns4:DateTimeOriginal>
<ns4:ISOSpeedRatings>
<rdf:Seq>
<rdf:li>76</rdf:li>
</rdf:Seq>
</ns4:ISOSpeedRatings>
<ns4:ExifVersion>0220</ns4:ExifVersion>
<ns4:FlashpixVersion>0100</ns4:FlashpixVersion>
<ns4:ComponentsConfiguration>
<rdf:Seq>
<rdf:li>1</rdf:li>
<rdf:li>2</rdf:li>
<rdf:li>3</rdf:li>
<rdf:li>0</rdf:li>
</rdf:Seq>
</ns4:ComponentsConfiguration>
<ns4:GPSLatitude>42,20.56N</ns4:GPSLatitude>
<ns4:GPSLongitude>71,5.29W</ns4:GPSLongitude>
<ns4:GPSTimeStamp>2013-02-09T19:47:53Z</ns4:GPSTimeStamp>
<ns4:GPSProcessingMethod>NETWORK</ns4:GPSProcessingMethod>
</rdf:Description>
<rdf:Description rdf:about="">
<ns5:DateCreated>2013-02-09T14:47:53</ns5:DateCreated>
</rdf:Description>
<rdf:Description rdf:about="">
<dc:Creator>
<rdf:Seq>
<rdf:li>Glymur</rdf:li>
<rdf:li>Python XMP Toolkit</rdf:li>
</rdf:Seq>
</dc:Creator>
</rdf:Description>
</rdf:RDF>
</ns0:xmpmeta>"""

View file

@ -421,7 +421,7 @@ class TestAppend(unittest.TestCase):
# The sequence of box IDs should be the same as before, but with an # The sequence of box IDs should be the same as before, but with an
# xml box at the end. # xml box at the end.
box_ids = [box.box_id for box in jp2.box] box_ids = [box.box_id for box in jp2.box]
expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'uuid', 'jp2c', 'xml '] expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'jp2c', 'xml ']
self.assertEqual(box_ids, expected) self.assertEqual(box_ids, expected)
self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()), self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()),
b'<data>0</data>') b'<data>0</data>')
@ -470,7 +470,7 @@ class TestAppend(unittest.TestCase):
# The sequence of box IDs should be the same as before, but with an # The sequence of box IDs should be the same as before, but with an
# xml box at the end. # xml box at the end.
box_ids = [box.box_id for box in jp2.box] box_ids = [box.box_id for box in jp2.box]
expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'uuid', 'jp2c', 'xml '] expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'jp2c', 'xml ']
self.assertEqual(box_ids, expected) self.assertEqual(box_ids, expected)
self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()), self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()),
b'<data>0</data>') b'<data>0</data>')

View file

@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
"""Test suite for printing.
"""
# C0302: don't care too much about having too many lines in a test module
# pylint: disable=C0302
# E061: unittest.mock introduced in 3.3 (python-2.7/pylint issue)
# pylint: disable=E0611,F0401
# R0904: Not too many methods in unittest.
# pylint: disable=R0904
import os
import re
import struct
import sys
import tempfile
import warnings
from xml.etree import cElementTree as ET
if sys.hexversion < 0x02070000:
import unittest2 as unittest
else:
import unittest
if sys.hexversion < 0x03000000:
from StringIO import StringIO
else:
from io import StringIO
if sys.hexversion <= 0x03030000:
from mock import patch
else:
from unittest.mock import patch
import glymur
from glymur import Jp2k
from .fixtures import OPJ_DATA_ROOT, opj_data_file, nemo_xmp_box
class TestUUIDExif(unittest.TestCase):
"""Tests for UUIDs of Exif type."""
def setUp(self):
self.jp2file = glymur.data.nemo()
def tearDown(self):
pass
@unittest.skipIf(sys.hexversion < 0x03000000, "Requires assertWarns, 3.2+")
def test_unrecognized_exif_tag(self):
"""Verify warning in case of unrecognized tag."""
with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile:
with open(self.jp2file, 'rb') as ifptr:
tfile.write(ifptr.read())
# Write L, T, UUID identifier.
tfile.write(struct.pack('>I4s', 52, b'uuid'))
tfile.write(b'JpgTiffExif->JP2')
tfile.write(b'Exif\x00\x00')
xbuffer = struct.pack('<BBHI', 73, 73, 42, 8)
tfile.write(xbuffer)
# We will write just a single tag.
tfile.write(struct.pack('<H', 1))
# The "Make" tag is tag no. 271. Corrupt it to 171.
tfile.write(struct.pack('<HHI4s', 171, 2, 3, b'HTC\x00'))
tfile.flush()
with self.assertWarns(UserWarning):
j = glymur.Jp2k(tfile.name)
@unittest.skipIf(sys.hexversion < 0x03000000, "Requires assertWarns, 3.2+")
def test_bad_tag_datatype(self):
"""Only certain datatypes are allowable"""
with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile:
with open(self.jp2file, 'rb') as ifptr:
tfile.write(ifptr.read())
# Write L, T, UUID identifier.
tfile.write(struct.pack('>I4s', 52, b'uuid'))
tfile.write(b'JpgTiffExif->JP2')
tfile.write(b'Exif\x00\x00')
xbuffer = struct.pack('<BBHI', 73, 73, 42, 8)
tfile.write(xbuffer)
# We will write just a single tag.
tfile.write(struct.pack('<H', 1))
# 2000 is not an allowable TIFF datatype.
tfile.write(struct.pack('<HHI4s', 271, 2000, 3, b'HTC\x00'))
tfile.flush()
with self.assertWarns(UserWarning):
j = glymur.Jp2k(tfile.name)
self.assertEqual(j.box[-1].box_id, 'uuid')
@unittest.skipIf(sys.hexversion < 0x03000000, "Requires assertWarns, 3.2+")
def test_bad_tiff_header_byte_order_indication(self):
"""Only b'II' and b'MM' are allowed."""
with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile:
with open(self.jp2file, 'rb') as ifptr:
tfile.write(ifptr.read())
# Write L, T, UUID identifier.
tfile.write(struct.pack('>I4s', 52, b'uuid'))
tfile.write(b'JpgTiffExif->JP2')
tfile.write(b'Exif\x00\x00')
xbuffer = struct.pack('<BBHI', 74, 73, 42, 8)
tfile.write(xbuffer)
# We will write just a single tag.
tfile.write(struct.pack('<H', 1))
# 271 is the Make.
tfile.write(struct.pack('<HHI4s', 271, 2, 3, b'HTC\x00'))
tfile.flush()
with self.assertWarns(UserWarning):
j = glymur.Jp2k(tfile.name)
self.assertEqual(j.box[-1].box_id, 'uuid')
def test_big_endian(self):
"""Verify read of big-endian IFD."""
with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile:
with open(self.jp2file, 'rb') as ifptr:
tfile.write(ifptr.read())
# Write L, T, UUID identifier.
tfile.write(struct.pack('>I4s', 52, b'uuid'))
tfile.write(b'JpgTiffExif->JP2')
tfile.write(b'Exif\x00\x00')
xbuffer = struct.pack('>BBHI', 77, 77, 42, 8)
tfile.write(xbuffer)
# We will write just a single tag.
tfile.write(struct.pack('>H', 1))
# The "Make" tag is tag no. 271.
tfile.write(struct.pack('>HHI4s', 271, 2, 3, b'HTC\x00'))
tfile.flush()
jp2 = glymur.Jp2k(tfile.name)
self.assertEqual(jp2.box[-1].data.ifds['Image']['Make'], "HTC")
if __name__ == "__main__":
unittest.main()

View file

@ -109,7 +109,7 @@ class TestJp2k(unittest.TestCase):
jp2k = Jp2k(self.jp2file) jp2k = Jp2k(self.jp2file)
# top-level boxes # top-level boxes
self.assertEqual(len(jp2k.box), 6) self.assertEqual(len(jp2k.box), 5)
self.assertEqual(jp2k.box[0].box_id, 'jP ') self.assertEqual(jp2k.box[0].box_id, 'jP ')
self.assertEqual(jp2k.box[0].offset, 0) self.assertEqual(jp2k.box[0].offset, 0)
@ -128,15 +128,11 @@ class TestJp2k(unittest.TestCase):
self.assertEqual(jp2k.box[3].box_id, 'uuid') self.assertEqual(jp2k.box[3].box_id, 'uuid')
self.assertEqual(jp2k.box[3].offset, 77) self.assertEqual(jp2k.box[3].offset, 77)
self.assertEqual(jp2k.box[3].length, 638) self.assertEqual(jp2k.box[3].length, 3146)
self.assertEqual(jp2k.box[4].box_id, 'uuid') self.assertEqual(jp2k.box[4].box_id, 'jp2c')
self.assertEqual(jp2k.box[4].offset, 715) self.assertEqual(jp2k.box[4].offset, 3223)
self.assertEqual(jp2k.box[4].length, 2412) self.assertEqual(jp2k.box[4].length, 1132296)
self.assertEqual(jp2k.box[5].box_id, 'jp2c')
self.assertEqual(jp2k.box[5].offset, 3127)
self.assertEqual(jp2k.box[5].length, 1132296)
# jp2h super box # jp2h super box
self.assertEqual(len(jp2k.box[2].box), 2) self.assertEqual(len(jp2k.box[2].box), 2)
@ -178,7 +174,7 @@ class TestJp2k(unittest.TestCase):
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
with open(self.jp2file, 'rb') as ifile: with open(self.jp2file, 'rb') as ifile:
# Everything up until the jp2c box. # Everything up until the jp2c box.
write_buffer = ifile.read(3127) write_buffer = ifile.read(3223)
tfile.write(write_buffer) tfile.write(write_buffer)
# The L field must be 1 in order to signal the presence of the # The L field must be 1 in order to signal the presence of the
@ -199,9 +195,9 @@ class TestJp2k(unittest.TestCase):
jp2k = Jp2k(tfile.name) jp2k = Jp2k(tfile.name)
self.assertEqual(jp2k.box[5].box_id, 'jp2c') self.assertEqual(jp2k.box[4].box_id, 'jp2c')
self.assertEqual(jp2k.box[5].offset, 3127) self.assertEqual(jp2k.box[4].offset, 3223)
self.assertEqual(jp2k.box[5].length, 1133427 + 8) self.assertEqual(jp2k.box[4].length, 1133427 + 8)
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_length_field_is_zero(self): def test_length_field_is_zero(self):
@ -366,45 +362,12 @@ class TestJp2k(unittest.TestCase):
def test_xmp_attribute(self): def test_xmp_attribute(self):
"""Verify the XMP packet in the shipping example file can be read.""" """Verify the XMP packet in the shipping example file can be read."""
j = Jp2k(self.jp2file) j = Jp2k(self.jp2file)
xmp = j.box[4].data xmp = j.box[3].data.packet
ns0 = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}' ns0 = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}'
ns1 = '{http://ns.adobe.com/xap/1.0/}' ns2 = '{http://ns.adobe.com/xap/1.0/}'
name = '{0}RDF/{0}Description'.format(ns0) name = '{0}RDF/{0}Description/{1}CreatorTool'.format(ns0, ns2)
elt = xmp.find(name) elt = xmp.find(name)
attr_value = elt.attrib['{0}CreatorTool'.format(ns1)] self.assertEqual(elt.text, 'Google')
self.assertEqual(attr_value, 'glymur')
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
def test_unrecognized_exif_tag(self):
"""An unrecognized exif tag should be handled gracefully."""
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
shutil.copyfile(self.jp2file, tfile.name)
# The Exif UUID starts at byte 77. There are 8 bytes for the L and
# T fields, then 16 bytes for the UUID identifier, then 6 exif
# header bytes, then 8 bytes for the TIFF header, then 2 bytes
# the the Image IFD number of tags, where we finally find the first
# tag, "Make" (271). We'll corrupt it by changing it into 171,
# which does not correspond to any known Exif Image tag.
with open(tfile.name, 'r+b') as fptr:
fptr.seek(117)
write_buffer = struct.pack('<H', int(171))
fptr.write(write_buffer)
# Verify that a warning is issued, but only on python3.
# On python2, just suppress the warning.
if sys.hexversion < 0x03030000:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
j = Jp2k(tfile.name)
else:
with self.assertWarns(UserWarning):
j = Jp2k(tfile.name)
exif = j.box[3].data
# Were the tag == 271, 'Make' would be in the keys instead.
self.assertTrue(171 in exif['Image'].keys())
self.assertFalse('Make' in exif['Image'].keys())
@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), @unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version),
@ -742,19 +705,22 @@ class TestJp2k_2_1(unittest.TestCase):
with open(self.jp2file, 'rb') as fptr: with open(self.jp2file, 'rb') as fptr:
data = fptr.read() data = fptr.read()
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
# Codestream starts at byte 3127. SIZ marker at 3137. # Codestream starts at byte 3323. SIZ marker at 3233.
# COD marker at 3186. Subsampling at 3180. # COD marker at 3282. Subsampling at 3276.
tfile.write(data[0:3179]) offset = 3223
tfile.write(data[0:offset+52])
# Make the DY bytes of the SIZ segment zero. That means that # Make the DY bytes of the SIZ segment zero. That means that
# a subsampling factor is zero, which is illegal. # a subsampling factor is zero, which is illegal.
tfile.write(b'\x00') tfile.write(b'\x00')
tfile.write(data[3180:3182]) tfile.write(data[offset+53:offset+55])
tfile.write(b'\x00') tfile.write(b'\x00')
tfile.write(data[3184:3186]) tfile.write(data[offset+57:offset+59])
#tfile.write(data[3184:3186])
tfile.write(b'\x00') tfile.write(b'\x00')
tfile.write(data[3186:]) tfile.write(data[offset+59:])
#tfile.write(data[3186:])
tfile.flush() tfile.flush()
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.simplefilter("ignore") warnings.simplefilter("ignore")

View file

@ -35,7 +35,7 @@ else:
import glymur import glymur
from glymur import Jp2k from glymur import Jp2k
from .fixtures import OPJ_DATA_ROOT, opj_data_file from .fixtures import OPJ_DATA_ROOT, opj_data_file, nemo_xmp_box
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
@ -230,7 +230,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[6]) print(codestream.segment[6])
actual = fake_out.getvalue().strip() actual = fake_out.getvalue().strip()
lines = ['COC marker segment @ (3260, 9)', lines = ['COC marker segment @ (3356, 9)',
' Associated component: 1', ' Associated component: 1',
' Coding style for this component: ' ' Coding style for this component: '
+ 'Entropy coder, PARTITION = 0', + 'Entropy coder, PARTITION = 0',
@ -258,7 +258,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[2]) print(codestream.segment[2])
actual = fake_out.getvalue().strip() actual = fake_out.getvalue().strip()
lines = ['COD marker segment @ (3186, 12)', lines = ['COD marker segment @ (3282, 12)',
' Coding style:', ' Coding style:',
' Entropy coder, without partitions', ' Entropy coder, without partitions',
' SOP marker segments: False', ' SOP marker segments: False',
@ -424,7 +424,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[-1]) print(codestream.segment[-1])
actual = fake_out.getvalue().strip() actual = fake_out.getvalue().strip()
lines = ['EOC marker segment @ (1135421, 0)'] lines = ['EOC marker segment @ (1135517, 0)']
expected = '\n'.join(lines) expected = '\n'.join(lines)
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@ -521,7 +521,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[7]) print(codestream.segment[7])
actual = fake_out.getvalue().strip() actual = fake_out.getvalue().strip()
lines = ['QCC marker segment @ (3271, 8)', lines = ['QCC marker segment @ (3367, 8)',
' Associated Component: 1', ' Associated Component: 1',
' Quantization style: no quantization, 2 guard bits', ' Quantization style: no quantization, 2 guard bits',
' Step size: [(0, 8), (0, 9), (0, 9), (0, 10)]'] ' Step size: [(0, 8), (0, 9), (0, 9), (0, 10)]']
@ -537,7 +537,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[3]) print(codestream.segment[3])
actual = fake_out.getvalue().strip() actual = fake_out.getvalue().strip()
lines = ['QCD marker segment @ (3200, 7)', lines = ['QCD marker segment @ (3296, 7)',
' Quantization style: no quantization, 2 guard bits', ' Quantization style: no quantization, 2 guard bits',
' Step size: [(0, 8), (0, 9), (0, 9), (0, 10)]'] ' Step size: [(0, 8), (0, 9), (0, 9), (0, 10)]']
@ -552,7 +552,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[1]) print(codestream.segment[1])
actual = fake_out.getvalue().strip() actual = fake_out.getvalue().strip()
lines = ['SIZ marker segment @ (3137, 47)', lines = ['SIZ marker segment @ (3233, 47)',
' Profile: 2', ' Profile: 2',
' Reference Grid Height, Width: (1456 x 2592)', ' Reference Grid Height, Width: (1456 x 2592)',
' Vertical, Horizontal Reference Grid Offset: (0 x 0)', ' Vertical, Horizontal Reference Grid Offset: (0 x 0)',
@ -574,7 +574,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[0]) print(codestream.segment[0])
actual = fake_out.getvalue().strip() actual = fake_out.getvalue().strip()
lines = ['SOC marker segment @ (3135, 0)'] lines = ['SOC marker segment @ (3231, 0)']
expected = '\n'.join(lines) expected = '\n'.join(lines)
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@ -586,7 +586,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[10]) print(codestream.segment[10])
actual = fake_out.getvalue().strip() actual = fake_out.getvalue().strip()
lines = ['SOD marker segment @ (3302, 0)'] lines = ['SOD marker segment @ (3398, 0)']
expected = '\n'.join(lines) expected = '\n'.join(lines)
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@ -598,7 +598,7 @@ class TestPrinting(unittest.TestCase):
print(codestream.segment[5]) print(codestream.segment[5])
actual = fake_out.getvalue().strip() actual = fake_out.getvalue().strip()
lines = ['SOT marker segment @ (3248, 10)', lines = ['SOT marker segment @ (3344, 10)',
' Tile part index: 0', ' Tile part index: 0',
' Tile part length: 1132173', ' Tile part length: 1132173',
' Tile part instance: 0', ' Tile part instance: 0',
@ -632,22 +632,11 @@ class TestPrinting(unittest.TestCase):
"""Verify the printing of a UUID/XMP box.""" """Verify the printing of a UUID/XMP box."""
j = glymur.Jp2k(self.jp2file) j = glymur.Jp2k(self.jp2file)
with patch('sys.stdout', new=StringIO()) as fake_out: with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[4]) print(j.box[3])
actual = fake_out.getvalue().strip() actual = fake_out.getvalue().strip()
lst = ['UUID Box (uuid) @ (715, 2412)', expected = nemo_xmp_box
' UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)', self.maxDiff = None
' UUID Data: ',
' <ns0:xmpmeta xmlns:ns0="adobe:ns:meta/" '
+ 'xmlns:ns2="http://ns.adobe.com/xap/1.0/" '
+ 'xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" '
+ 'ns0:xmptk="XMP Core 4.4.0-Exiv2">',
' <rdf:RDF>',
' <rdf:Description ns2:CreatorTool="glymur" '
+ 'rdf:about="" />',
' </rdf:RDF>',
' </ns0:xmpmeta>']
expected = '\n'.join(lst)
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
def test_codestream(self): def test_codestream(self):
@ -657,8 +646,8 @@ class TestPrinting(unittest.TestCase):
print(j.get_codestream()) print(j.get_codestream())
actual = fake_out.getvalue().strip() actual = fake_out.getvalue().strip()
lst = ['Codestream:', lst = ['Codestream:',
' SOC marker segment @ (3135, 0)', ' SOC marker segment @ (3231, 0)',
' SIZ marker segment @ (3137, 47)', ' SIZ marker segment @ (3233, 47)',
' Profile: 2', ' Profile: 2',
' Reference Grid Height, Width: (1456 x 2592)', ' Reference Grid Height, Width: (1456 x 2592)',
' Vertical, Horizontal Reference Grid Offset: (0 x 0)', ' Vertical, Horizontal Reference Grid Offset: (0 x 0)',
@ -668,7 +657,7 @@ class TestPrinting(unittest.TestCase):
' Signed: (False, False, False)', ' Signed: (False, False, False)',
' Vertical, Horizontal Subsampling: ' ' Vertical, Horizontal Subsampling: '
+ '((1, 1), (1, 1), (1, 1))', + '((1, 1), (1, 1), (1, 1))',
' COD marker segment @ (3186, 12)', ' COD marker segment @ (3282, 12)',
' Coding style:', ' Coding style:',
' Entropy coder, without partitions', ' Entropy coder, without partitions',
' SOP marker segments: False', ' SOP marker segments: False',
@ -690,11 +679,11 @@ class TestPrinting(unittest.TestCase):
' Vertically stripe causal context: False', ' Vertically stripe causal context: False',
' Predictable termination: False', ' Predictable termination: False',
' Segmentation symbols: False', ' Segmentation symbols: False',
' QCD marker segment @ (3200, 7)', ' QCD marker segment @ (3296, 7)',
' Quantization style: no quantization, ' ' Quantization style: no quantization, '
+ '2 guard bits', + '2 guard bits',
' Step size: [(0, 8), (0, 9), (0, 9), (0, 10)]', ' Step size: [(0, 8), (0, 9), (0, 9), (0, 10)]',
' CME marker segment @ (3209, 37)', ' CME marker segment @ (3305, 37)',
' "Created by OpenJPEG version 2.0.0"'] ' "Created by OpenJPEG version 2.0.0"']
expected = '\n'.join(lst) expected = '\n'.join(lst)
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
@ -1036,7 +1025,7 @@ class TestPrinting(unittest.TestCase):
print(jp2.box[4]) print(jp2.box[4])
actual = fake_out.getvalue().strip() actual = fake_out.getvalue().strip()
lines = ['UUID Box (uuid) @ (1544, 25)', lines = ['UUID Box (uuid) @ (1544, 25)',
' UUID: 3a0d0218-0ae9-4115-b376-4bca41ce0e71', ' UUID: 3a0d0218-0ae9-4115-b376-4bca41ce0e71 (unknown)',
' UUID Data: 1 bytes'] ' UUID Data: 1 bytes']
expected = '\n'.join(lines) expected = '\n'.join(lines)
@ -1046,59 +1035,42 @@ class TestPrinting(unittest.TestCase):
"Ordered dicts not printing well in 2.7") "Ordered dicts not printing well in 2.7")
def test_exif_uuid(self): def test_exif_uuid(self):
"""Verify printing of exif information""" """Verify printing of exif information"""
j = glymur.Jp2k(self.jp2file) with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile:
with open(self.jp2file, 'rb') as ifptr:
tfile.write(ifptr.read())
# Write L, T, UUID identifier.
tfile.write(struct.pack('>I4s', 76, b'uuid'))
tfile.write(b'JpgTiffExif->JP2')
tfile.write(b'Exif\x00\x00')
xbuffer = struct.pack('<BBHI', 73, 73, 42, 8)
tfile.write(xbuffer)
# We will write just three tags.
tfile.write(struct.pack('<H', 3))
# The "Make" tag is tag no. 271.
tfile.write(struct.pack('<HHII', 256, 4, 1, 256))
tfile.write(struct.pack('<HHII', 257, 4, 1, 512))
tfile.write(struct.pack('<HHI4s', 271, 2, 3, b'HTC\x00'))
tfile.flush()
j = glymur.Jp2k(tfile.name)
with patch('sys.stdout', new=StringIO()) as fake_out: with patch('sys.stdout', new=StringIO()) as fake_out:
print(j.box[3]) print(j.box[5])
actual = fake_out.getvalue().strip() actual = fake_out.getvalue().strip()
lines = ["UUID Box (uuid) @ (77, 638)", lines = ["UUID Box (uuid) @ (1135519, 76)",
" UUID: 4a706754-6966-6645-7869-662d3e4a5032 (Exif)", " UUID: 4a706754-6966-6645-7869-662d3e4a5032 (Exif)",
" UUID Data: ", " UUID Data: ",
"{'Image': {'Make': 'HTC',", "{'Image': {'ImageWidth': 256,",
" 'Model': 'HTC Glacier',", " 'ImageLength': 512,",
" 'XResolution': 72.0,", " 'Make': 'HTC'},",
" 'YResolution': 72.0,", " 'Photo': None,",
" 'ResolutionUnit': 2,", " 'GPSInfo': None,",
" 'YCbCrPositioning': 1,",
" 'ExifTag': 138,",
" 'GPSTag': 354},",
" 'Photo': {'ISOSpeedRatings': 76,",
" 'ExifVersion': (48, 50, 50, 48),",
" 'DateTimeOriginal': '2013:02:09 14:47:53',",
" 'DateTimeDigitized': '2013:02:09 14:47:53',",
" 'ComponentsConfiguration': (1, 2, 3, 0),",
" 'FocalLength': 3.53,",
" 'FlashpixVersion': (48, 49, 48, 48),",
" 'ColorSpace': 1,",
" 'PixelXDimension': 2528,",
" 'PixelYDimension': 1424,",
" 'InteroperabilityTag': 324},",
" 'GPSInfo': {'GPSVersionID': (2, 2, 0),",
" 'GPSLatitudeRef': 'N',",
" 'GPSLatitude': [42.0, 20.0, 33.61],",
" 'GPSLongitudeRef': 'W',",
" 'GPSLongitude': [71.0, 5.0, 17.32],",
" 'GPSAltitudeRef': 0,",
" 'GPSAltitude': 0.0,",
" 'GPSTimeStamp': [19.0, 47.0, 53.0],",
" 'GPSMapDatum': 'WGS-84',",
" 'GPSProcessingMethod': (65,",
" 83,",
" 67,",
" 73,",
" 73,",
" 0,",
" 0,",
" 0,",
" 78,",
" 69,",
" 84,",
" 87,",
" 79,",
" 82,",
" 75),",
" 'GPSDateStamp': '2013:02:09'},",
" 'Iop': None}"] " 'Iop': None}"]
expected = '\n'.join(lines) expected = '\n'.join(lines)