Introducing python-xmp-toolkit requirement. #104

Down to 3 failures and 1 error.
This commit is contained in:
jevans 2014-01-23 21:50:48 -05:00
commit ffe17f12cb
9 changed files with 67 additions and 195 deletions

View file

@ -13,76 +13,27 @@ if sys.hexversion < 0x02070000:
else:
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.
"""
# 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
endian = '<'
elif data[0] == 77 and data[1] == 77:
# big endian
endian = '>'
else:
msg = "Bad byte order indication: {0}".format(read_buffer[6:8])
raise RuntimeError(msg)
def __init__(self, read_buffer):
"""Interpret raw buffer consisting of Exif IFD.
"""
exif_image = None
exif_photo = None
exif_gpsinfo = None
exif_iop = None
_, offset = struct.unpack(endian + 'HI', read_buffer[8:14])
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)
# This is the 'Exif Image' portion.
exif = _ExifImageIfd(endian, read_buffer[6:], offset)
return exif.processed_ifd
class _Ifd(object):

View file

@ -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)

View file

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

View file

@ -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))

View file

@ -33,6 +33,12 @@ else:
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 .core import _COLORSPACE_MAP_DISPLAY
from .core import _COLOR_TYPE_MAP_DISPLAY
@ -2199,7 +2205,7 @@ class UUIDBox(Jp2kBox):
the_uuid : uuid.UUID
Identifies the type of UUID box.
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 of the box in bytes.
offset : int
@ -2208,64 +2214,53 @@ class UUIDBox(Jp2kBox):
Jp2kBox.__init__(self, box_id='uuid', longname='UUID')
self.uuid = the_uuid
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.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):
msg = "glymur.jp2box.UUIDBox(the_uuid={0}, "
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):
msg = '{0}\n'
msg += ' UUID: {1} ({2})\n'
msg += ' UUID Data: {3}'
msg += ' UUID: {1}\n'
msg += ' UUID Data: {2}'
msg = msg.format(Jp2kBox.__str__(self),
self.uuid,
self._type,
str(self.data))
msg = msg.format(Jp2kBox.__str__(self), self.uuid, str(self.data))
return msg
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."
raise NotImplementedError(msg)
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)
write_buffer = struct.pack('>I4s', self.length, b'uuid')
fptr.write(write_buffer)
fptr.write(self.uuid.bytes)
fptr.write(serialized)
fptr.write(self.raw_data)
@staticmethod
def parse(fptr, offset, length):

View file

@ -20,6 +20,7 @@ import ctypes
import math
import os
import struct
from uuid import UUID
import warnings
import numpy as np
@ -526,7 +527,8 @@ class Jp2k(Jp2kBox):
raise IOError(msg)
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."
raise IOError(msg)

View file

@ -35,6 +35,8 @@ if sys.hexversion <= 0x03030000:
else:
from unittest.mock import patch
from libxmp import XMPMeta
import glymur
from glymur import Jp2k
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
# an ElementTree.
self.assertTrue(isinstance(jp2.box[-1].data.packet,
ET.ElementTree))
self.assertTrue(isinstance(jp2.box[-1].data, XMPMeta))
class TestUUIDExif(unittest.TestCase):
"""Tests for UUIDs of Exif type."""

View file

@ -30,6 +30,8 @@ import warnings
import numpy as np
import pkg_resources
import libxmp
import glymur
from glymur import Jp2k
@ -362,13 +364,9 @@ class TestJp2k(unittest.TestCase):
def test_xmp_attribute(self):
"""Verify the XMP packet in the shipping example file can be read."""
j = Jp2k(self.jp2file)
xmp = j.box[3].data.packet
ns0 = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}'
ns2 = '{http://ns.adobe.com/xap/1.0/}'
name = '{0}RDF/{0}Description/{1}CreatorTool'.format(ns0, ns2)
elt = xmp.find(name)
self.assertEqual(elt.text, 'Google')
xmp = j.box[3].data
creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, 'CreatorTool')
self.assertEqual(creator_tool, 'Google')
@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version),
"Requires at least version 1.5")

View file

@ -16,7 +16,7 @@ kwargs = {'name': 'Glymur',
'license': 'MIT',
'test_suite': 'glymur.test'}
instllrqrs = ['numpy>=1.4.1']
instllrqrs = ['numpy>=1.4.1', 'python-xmp-toolkit>=2.0.0']
if sys.hexversion < 0x03030000:
instllrqrs.append('contextlib2>=0.4')
instllrqrs.append('mock>=1.0.1')