Merge branch 'issue156' into devel

This commit is contained in:
jevans 2014-02-08 14:57:26 -05:00
commit 6bfb98d550
4 changed files with 128 additions and 36 deletions

View file

@ -1,12 +1,12 @@
Feb 03, 2014 - Removed support for Python 2.6. Added write support for
Feb 08, 2014 - Removed support for Python 2.6. Added write support for
JP2 DataEntryURL, Palette and Component Mapping boxes, JPX
Association and NumberList boxes. Added read support for JPX
free, number list, data reference, fragment table, and fragment
list boxes. Palette box now a 2D numpy array instead of a list
of 1D arrays. JP2 super box constructors now take optional box
list argument. Fixed bug where JPX files with more than one
codestream but advertising jp2 compatibility were not being
read.
Association, NumberList and DataReference boxes. Added read
support for JPX free, number list, data reference, fragment
table, and fragment list boxes. Palette box now a 2D numpy
array instead of a list of 1D arrays. JP2 super box constructors
now take optional box list argument. Fixed bug where JPX files
with more than one codestream but advertising jp2 compatibility
were not being read.
Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask
length is unsupported.

View file

@ -863,6 +863,27 @@ class DataReferenceBox(Jp2kBox):
self.length = length
self.offset = offset
def write(self, fptr):
"""Write a Data Reference box to file.
"""
# Very similar to the say a superbox is written.
orig_pos = fptr.tell()
fptr.write(struct.pack('>I', 0))
fptr.write(self.box_id.encode())
# Write the number of data entry url boxes.
write_buffer = struct.pack('>H', len(self.DR))
fptr.write(write_buffer)
for box in self.DR:
box.write(fptr)
end_pos = fptr.tell()
fptr.seek(orig_pos)
fptr.write(struct.pack('>I', end_pos - orig_pos))
fptr.seek(end_pos)
def __str__(self):
msg = Jp2kBox.__str__(self)
for box in self.DR:

View file

@ -16,6 +16,7 @@ if sys.hexversion >= 0x03030000:
else:
from contextlib2 import ExitStack
from collections import Counter
import ctypes
import math
import os
@ -1190,6 +1191,57 @@ def _validate_jp2_box_sequence(boxes):
_asoc_check(boxes)
_jpx_brand(boxes, boxes[1].brand)
_jpx_compatibility(boxes, boxes[1].compatibility_list)
_check_for_singletons(boxes)
_check_top_level(boxes)
def _collect_box_count(boxes):
"""Count the occurences of each box type."""
count = Counter([box.box_id for box in boxes])
# Add the counts in the superboxes.
for box in boxes:
if hasattr(box, 'box'):
count.update(_collect_box_count(box.box))
return count
TOP_LEVEL_ONLY_BOXES = set(['dtbl'])
def _check_superbox_for_top_levels(boxes):
"""Several boxes can only occur at the top level."""
# We are only looking at the boxes contained in a superbox, so if any of
# the blacklisted boxes show up here, it's an error.
box_ids = set([box.box_id for box in boxes])
intersection = box_ids.intersection(TOP_LEVEL_ONLY_BOXES)
if len(intersection) > 0:
msg = "A '{0}' box cannot be nested in a superbox."
raise IOError(msg.format(list(intersection)[0]))
# Recursively check any contained superboxes.
for box in boxes:
if hasattr(box, 'box'):
_check_superbox_for_top_levels(box.box)
def _check_top_level(boxes):
"""Several boxes can only occur at the top level."""
# Add the counts in the superboxes.
for box in boxes:
if hasattr(box, 'box'):
_check_superbox_for_top_levels(box.box)
count = _collect_box_count(boxes)
# Which boxes occur more than once?
multiples = [box_id for box_id, bcount in count.items() if bcount > 1]
if 'dtbl' in multiples:
raise IOError('There can only be one dtbl box in a file.')
def _check_for_singletons(boxes):
"""Several boxes can only occur once."""
count = _collect_box_count(boxes)
# Which boxes occur more than once?
multiples = [box_id for box_id, bcount in count.items() if bcount > 1]
if 'dtbl' in multiples:
raise IOError('There can only be one dtbl box in a file.')
def _jpx_brand(boxes, brand):
"""

View file

@ -67,6 +67,41 @@ class TestJPXWrap(unittest.TestCase):
self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()),
b'<data>0</data>')
def test_only_one_data_reference(self):
"""Data reference boxes cannot be inside a superbox ."""
jp2 = Jp2k(self.jp2file)
boxes = [jp2.box[idx] for idx in [0, 1, 2, 5]]
flag = 0
version = (0, 0, 0)
url = 'file:////usr/local/bin'
deurl = glymur.jp2box.DataEntryURLBox(flag, version, url)
dref = glymur.jp2box.DataReferenceBox([deurl])
boxes.append(dref)
boxes.append(dref)
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
with self.assertRaises(IOError):
jpx = jp2.wrap(tfile.name, boxes=boxes)
def test_data_reference_not_at_top_level(self):
"""Data reference boxes cannot be inside a superbox ."""
jp2 = Jp2k(self.jp2file)
boxes = [jp2.box[idx] for idx in [0, 1, 2, 5]]
flag = 0
version = (0, 0, 0)
url = 'file:////usr/local/bin'
deurl = glymur.jp2box.DataEntryURLBox(flag, version, url)
dref = glymur.jp2box.DataReferenceBox([deurl])
# Put it inside the jp2 header box.
boxes[2].box.append(dref)
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
with self.assertRaises(IOError):
jpx = jp2.wrap(tfile.name, boxes=boxes)
def test_jp2_to_jpx_sans_jp2_compatibility(self):
"""jp2 wrapped to jpx not including jp2 compatibility is wrong."""
jp2 = Jp2k(self.jp2file)
@ -133,46 +168,30 @@ class TestJPX(unittest.TestCase):
def test_dtbl(self):
"""Verify that we can interpret Data Reference boxes."""
# Copy the existing JPX file, add a data reference box onto the end.
flag = 0
version = (0, 0, 0)
url1 = 'file:////usr/local/bin'
url2 = 'http://glymur.readthedocs.org' + chr(0) * 3
with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile:
with open(self.jpxfile, 'rb') as ifile:
tfile.write(ifile.read())
# 8 + 2 + 20 + 36
boxlen = 66
write_buffer = struct.pack('>I4s', boxlen, b'dtbl')
tfile.write(write_buffer)
# Just two boxes.
write_buffer = struct.pack('>H', 2)
tfile.write(write_buffer)
# First data entry url box.
# This one will have a URL with 3 null chars at the end.
# They should be stripped off.
write_buffer = struct.pack('>I4s', 36, b'url ')
tfile.write(write_buffer)
url1 = 'file:////usr/local/bin'
write_buffer = struct.pack('>BBBB24s', 0, 0, 0, 0,
(url1 + chr(0) * 3).encode())
tfile.write(write_buffer)
# 2nd data entry url box.
write_buffer = struct.pack('>I4s', 20, b'url ')
tfile.write(write_buffer)
url2 = 'file:///'
write_buffer = struct.pack('>BBBB8s', 0, 0, 0, 0, url2.encode())
tfile.write(write_buffer)
deurl1 = glymur.jp2box.DataEntryURLBox(flag, version, url1)
deurl2 = glymur.jp2box.DataEntryURLBox(flag, version, url2)
dref = glymur.jp2box.DataReferenceBox([deurl1, deurl2])
dref.write(tfile)
tfile.flush()
with self.assertWarns(UserWarning):
with warnings.catch_warnings():
# This file has a rreq mask length that we do not recognize.
warnings.simplefilter("ignore")
jpx = Jp2k(tfile.name)
self.assertEqual(jpx.box[-1].box_id, 'dtbl')
self.assertEqual(len(jpx.box[-1].DR), 2)
self.assertEqual(jpx.box[-1].DR[0].url, url1)
self.assertEqual(jpx.box[-1].DR[1].url, url2)
self.assertEqual(jpx.box[-1].DR[1].url, url2.rstrip('\0'))
def test_ftbl(self):
"""Verify that we can interpret Fragment Table boxes."""