Introducing python-xmp-toolkit requirement. #104
Down to 3 failures and 1 error.
This commit is contained in:
parent
32760a6ecc
commit
ffe17f12cb
9 changed files with 67 additions and 195 deletions
|
|
@ -13,76 +13,27 @@ if sys.hexversion < 0x02070000:
|
||||||
else:
|
else:
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
class UUIDExif(object):
|
def tiff_header(read_buffer):
|
||||||
"""
|
"""
|
||||||
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.
|
# Ignore the first six bytes.
|
||||||
# Next 8 should be (73, 73, 42, 8) or (77, 77, 42, 8)
|
# Next 8 should be (73, 73, 42, 8) or (77, 77, 42, 8)
|
||||||
data = struct.unpack('<BB', read_buffer[6:8])
|
data = struct.unpack('<BB', read_buffer[6:8])
|
||||||
if data[0] == 73 and data[1] == 73:
|
if data[0] == 73 and data[1] == 73:
|
||||||
# little endian
|
# little endian
|
||||||
self.endian = '<'
|
endian = '<'
|
||||||
elif data[0] == 77 and data[1] == 77:
|
elif data[0] == 77 and data[1] == 77:
|
||||||
# big endian
|
# big endian
|
||||||
self.endian = '>'
|
endian = '>'
|
||||||
else:
|
else:
|
||||||
msg = "Bad byte order indication: {0}".format(read_buffer[6:8])
|
msg = "Bad byte order indication: {0}".format(read_buffer[6:8])
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
_, offset = struct.unpack(self.endian + 'HI', read_buffer[8:14])
|
_, offset = struct.unpack(endian + 'HI', read_buffer[8:14])
|
||||||
|
|
||||||
# This is the 'Exif Image' portion.
|
# This is the 'Exif Image' portion.
|
||||||
exif = _ExifImageIfd(self.endian, read_buffer[6:], offset)
|
exif = _ExifImageIfd(endian, read_buffer[6:], offset)
|
||||||
exif_image = exif.processed_ifd
|
return 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):
|
class _Ifd(object):
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
# -*- 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)
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
"""
|
"""
|
||||||
Sub package for handling various UUIDs.
|
Sub package for handling various types of UUIDs.
|
||||||
"""
|
"""
|
||||||
from .Exif import UUIDExif
|
from .Exif import tiff_header
|
||||||
from .XMP import UUIDXMP
|
|
||||||
from .generic import UUIDGeneric
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
# -*- 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))
|
|
||||||
|
|
||||||
|
|
@ -33,6 +33,12 @@ else:
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
try:
|
||||||
|
from libxmp import XMPMeta
|
||||||
|
_HAS_PYTHON_XMP_TOOLKIT = True
|
||||||
|
except ImportError:
|
||||||
|
_HAS_PYTHON_XMP_TOOLKIT = False
|
||||||
|
|
||||||
from .codestream import Codestream
|
from .codestream import Codestream
|
||||||
from .core import _COLORSPACE_MAP_DISPLAY
|
from .core import _COLORSPACE_MAP_DISPLAY
|
||||||
from .core import _COLOR_TYPE_MAP_DISPLAY
|
from .core import _COLOR_TYPE_MAP_DISPLAY
|
||||||
|
|
@ -2199,7 +2205,7 @@ class UUIDBox(Jp2kBox):
|
||||||
the_uuid : uuid.UUID
|
the_uuid : uuid.UUID
|
||||||
Identifies the type of UUID box.
|
Identifies the type of UUID box.
|
||||||
raw_data : byte array
|
raw_data : byte array
|
||||||
Sequence of uninterpreted bytes as read from the file.
|
Sequence of uninterpreted bytes as read from the UUID box.
|
||||||
length : int
|
length : int
|
||||||
length of the box in bytes.
|
length of the box in bytes.
|
||||||
offset : int
|
offset : int
|
||||||
|
|
@ -2208,64 +2214,53 @@ class UUIDBox(Jp2kBox):
|
||||||
Jp2kBox.__init__(self, box_id='uuid', longname='UUID')
|
Jp2kBox.__init__(self, box_id='uuid', longname='UUID')
|
||||||
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'):
|
|
||||||
self.data = _uuid_io.UUIDXMP(raw_data)
|
|
||||||
self._type = 'XMP'
|
|
||||||
elif the_uuid.bytes == b'JpgTiffExif->JP2':
|
|
||||||
self.data = _uuid_io.UUIDExif(raw_data)
|
|
||||||
self._type = 'Exif'
|
|
||||||
else:
|
|
||||||
self.data = _uuid_io.UUIDGeneric(raw_data)
|
|
||||||
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()))
|
|
||||||
|
|
||||||
self.raw_data = raw_data
|
|
||||||
|
|
||||||
self.length = length
|
self.length = length
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
self.data = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._parse_raw_data()
|
||||||
|
except Exception as e:
|
||||||
|
warnings.warn(str(e))
|
||||||
|
|
||||||
|
def _parse_raw_data(self):
|
||||||
|
"""
|
||||||
|
Private function for parsing UUID payloads if possible.
|
||||||
|
"""
|
||||||
|
if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
|
||||||
|
xmp = XMPMeta()
|
||||||
|
xmp.parse_from_str(self.raw_data.decode('utf-8'),
|
||||||
|
xmpmeta_wrap=False)
|
||||||
|
self.data = xmp
|
||||||
|
elif self.uuid.bytes == b'JpgTiffExif->JP2':
|
||||||
|
self.data = _uuid_io.tiff_header(self.raw_data)
|
||||||
|
else:
|
||||||
|
self.data = self.raw_data
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
msg = "glymur.jp2box.UUIDBox(the_uuid={0}, "
|
msg = "glymur.jp2box.UUIDBox(the_uuid={0}, "
|
||||||
msg += "raw_data=<byte array {1} elements>)"
|
msg += "raw_data=<byte array {1} elements>)"
|
||||||
return msg.format(repr(self.uuid), len(self.raw_data))
|
return msg.format(repr(self.uuid), len(self.data))
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = '{0}\n'
|
msg = '{0}\n'
|
||||||
msg += ' UUID: {1} ({2})\n'
|
msg += ' UUID: {1}\n'
|
||||||
msg += ' UUID Data: {3}'
|
msg += ' UUID Data: {2}'
|
||||||
|
|
||||||
msg = msg.format(Jp2kBox.__str__(self),
|
msg = msg.format(Jp2kBox.__str__(self), self.uuid, str(self.data))
|
||||||
self.uuid,
|
|
||||||
self._type,
|
|
||||||
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 to file.
|
||||||
"""
|
"""
|
||||||
if self._type != 'XMP':
|
if self.uuid != uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'):
|
||||||
msg = "Only XMP UUID boxes can currently be written."
|
msg = "Only XMP UUID boxes can currently be written."
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
serialized = b'<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>'
|
write_buffer = struct.pack('>I4s', self.length, b'uuid')
|
||||||
serialized += ET.tostring(self.data.packet.getroot(), encoding='utf-8')
|
fptr.write(write_buffer)
|
||||||
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(self.uuid.bytes)
|
fptr.write(self.uuid.bytes)
|
||||||
fptr.write(serialized)
|
fptr.write(self.raw_data)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import ctypes
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
from uuid import UUID
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
@ -526,7 +527,8 @@ class Jp2k(Jp2kBox):
|
||||||
raise IOError(msg)
|
raise IOError(msg)
|
||||||
|
|
||||||
if not ((box.box_id == 'xml ') or
|
if not ((box.box_id == 'xml ') or
|
||||||
(box.box_id == 'uuid' and box._type == 'XMP')):
|
(box.box_id == 'uuid' and
|
||||||
|
box.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'))):
|
||||||
msg = "Only XML boxes and XMP UUID boxes can currently be appended."
|
msg = "Only XML boxes and XMP UUID boxes can currently be appended."
|
||||||
raise IOError(msg)
|
raise IOError(msg)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ if sys.hexversion <= 0x03030000:
|
||||||
else:
|
else:
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from libxmp import XMPMeta
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
from glymur import Jp2k
|
from glymur import Jp2k
|
||||||
from .fixtures import OPJ_DATA_ROOT, opj_data_file, SimpleRDF
|
from .fixtures import OPJ_DATA_ROOT, opj_data_file, SimpleRDF
|
||||||
|
|
@ -66,8 +68,7 @@ class TestUUIDXMP(unittest.TestCase):
|
||||||
|
|
||||||
# The data should be an XMP packet, which gets interpreted as
|
# The data should be an XMP packet, which gets interpreted as
|
||||||
# an ElementTree.
|
# an ElementTree.
|
||||||
self.assertTrue(isinstance(jp2.box[-1].data.packet,
|
self.assertTrue(isinstance(jp2.box[-1].data, XMPMeta))
|
||||||
ET.ElementTree))
|
|
||||||
|
|
||||||
class TestUUIDExif(unittest.TestCase):
|
class TestUUIDExif(unittest.TestCase):
|
||||||
"""Tests for UUIDs of Exif type."""
|
"""Tests for UUIDs of Exif type."""
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ import warnings
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
|
import libxmp
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
from glymur import Jp2k
|
from glymur import Jp2k
|
||||||
|
|
||||||
|
|
@ -362,13 +364,9 @@ 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[3].data.packet
|
xmp = j.box[3].data
|
||||||
ns0 = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}'
|
creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool')
|
||||||
ns2 = '{http://ns.adobe.com/xap/1.0/}'
|
self.assertEqual(creator_tool, 'Google')
|
||||||
name = '{0}RDF/{0}Description/{1}CreatorTool'.format(ns0, ns2)
|
|
||||||
elt = xmp.find(name)
|
|
||||||
self.assertEqual(elt.text, 'Google')
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version),
|
@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version),
|
||||||
"Requires at least version 1.5")
|
"Requires at least version 1.5")
|
||||||
|
|
|
||||||
2
setup.py
2
setup.py
|
|
@ -16,7 +16,7 @@ kwargs = {'name': 'Glymur',
|
||||||
'license': 'MIT',
|
'license': 'MIT',
|
||||||
'test_suite': 'glymur.test'}
|
'test_suite': 'glymur.test'}
|
||||||
|
|
||||||
instllrqrs = ['numpy>=1.4.1']
|
instllrqrs = ['numpy>=1.4.1', 'python-xmp-toolkit>=2.0.0']
|
||||||
if sys.hexversion < 0x03030000:
|
if sys.hexversion < 0x03030000:
|
||||||
instllrqrs.append('contextlib2>=0.4')
|
instllrqrs.append('contextlib2>=0.4')
|
||||||
instllrqrs.append('mock>=1.0.1')
|
instllrqrs.append('mock>=1.0.1')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue