Merge branch 'issue156' into devel
This commit is contained in:
commit
6bfb98d550
4 changed files with 128 additions and 36 deletions
16
CHANGES.txt
16
CHANGES.txt
|
|
@ -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
|
JP2 DataEntryURL, Palette and Component Mapping boxes, JPX
|
||||||
Association and NumberList boxes. Added read support for JPX
|
Association, NumberList and DataReference boxes. Added read
|
||||||
free, number list, data reference, fragment table, and fragment
|
support for JPX free, number list, data reference, fragment
|
||||||
list boxes. Palette box now a 2D numpy array instead of a list
|
table, and fragment list boxes. Palette box now a 2D numpy
|
||||||
of 1D arrays. JP2 super box constructors now take optional box
|
array instead of a list of 1D arrays. JP2 super box constructors
|
||||||
list argument. Fixed bug where JPX files with more than one
|
now take optional box list argument. Fixed bug where JPX files
|
||||||
codestream but advertising jp2 compatibility were not being
|
with more than one codestream but advertising jp2 compatibility
|
||||||
read.
|
were not being read.
|
||||||
|
|
||||||
Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask
|
Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask
|
||||||
length is unsupported.
|
length is unsupported.
|
||||||
|
|
|
||||||
|
|
@ -863,6 +863,27 @@ class DataReferenceBox(Jp2kBox):
|
||||||
self.length = length
|
self.length = length
|
||||||
self.offset = offset
|
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):
|
def __str__(self):
|
||||||
msg = Jp2kBox.__str__(self)
|
msg = Jp2kBox.__str__(self)
|
||||||
for box in self.DR:
|
for box in self.DR:
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ if sys.hexversion >= 0x03030000:
|
||||||
else:
|
else:
|
||||||
from contextlib2 import ExitStack
|
from contextlib2 import ExitStack
|
||||||
|
|
||||||
|
from collections import Counter
|
||||||
import ctypes
|
import ctypes
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
|
|
@ -1190,6 +1191,57 @@ def _validate_jp2_box_sequence(boxes):
|
||||||
_asoc_check(boxes)
|
_asoc_check(boxes)
|
||||||
_jpx_brand(boxes, boxes[1].brand)
|
_jpx_brand(boxes, boxes[1].brand)
|
||||||
_jpx_compatibility(boxes, boxes[1].compatibility_list)
|
_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):
|
def _jpx_brand(boxes, brand):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,41 @@ class TestJPXWrap(unittest.TestCase):
|
||||||
self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()),
|
self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()),
|
||||||
b'<data>0</data>')
|
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):
|
def test_jp2_to_jpx_sans_jp2_compatibility(self):
|
||||||
"""jp2 wrapped to jpx not including jp2 compatibility is wrong."""
|
"""jp2 wrapped to jpx not including jp2 compatibility is wrong."""
|
||||||
jp2 = Jp2k(self.jp2file)
|
jp2 = Jp2k(self.jp2file)
|
||||||
|
|
@ -133,46 +168,30 @@ class TestJPX(unittest.TestCase):
|
||||||
def test_dtbl(self):
|
def test_dtbl(self):
|
||||||
"""Verify that we can interpret Data Reference boxes."""
|
"""Verify that we can interpret Data Reference boxes."""
|
||||||
# Copy the existing JPX file, add a data reference box onto the end.
|
# 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 tempfile.NamedTemporaryFile(suffix='.jpx') as tfile:
|
||||||
with open(self.jpxfile, 'rb') as ifile:
|
with open(self.jpxfile, 'rb') as ifile:
|
||||||
tfile.write(ifile.read())
|
tfile.write(ifile.read())
|
||||||
# 8 + 2 + 20 + 36
|
|
||||||
boxlen = 66
|
|
||||||
|
|
||||||
write_buffer = struct.pack('>I4s', boxlen, b'dtbl')
|
deurl1 = glymur.jp2box.DataEntryURLBox(flag, version, url1)
|
||||||
tfile.write(write_buffer)
|
deurl2 = glymur.jp2box.DataEntryURLBox(flag, version, url2)
|
||||||
|
dref = glymur.jp2box.DataReferenceBox([deurl1, deurl2])
|
||||||
# Just two boxes.
|
dref.write(tfile)
|
||||||
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)
|
|
||||||
|
|
||||||
tfile.flush()
|
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)
|
jpx = Jp2k(tfile.name)
|
||||||
|
|
||||||
self.assertEqual(jpx.box[-1].box_id, 'dtbl')
|
self.assertEqual(jpx.box[-1].box_id, 'dtbl')
|
||||||
self.assertEqual(len(jpx.box[-1].DR), 2)
|
self.assertEqual(len(jpx.box[-1].DR), 2)
|
||||||
self.assertEqual(jpx.box[-1].DR[0].url, url1)
|
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):
|
def test_ftbl(self):
|
||||||
"""Verify that we can interpret Fragment Table boxes."""
|
"""Verify that we can interpret Fragment Table boxes."""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue