More testing for jpx wrapping. Needs some refactoring, though. #206
This commit is contained in:
parent
9ecbc81e7a
commit
9b4e0a10fb
3 changed files with 97 additions and 18 deletions
|
|
@ -607,7 +607,12 @@ class Jp2k(Jp2kBox):
|
||||||
self.parse()
|
self.parse()
|
||||||
|
|
||||||
def wrap(self, filename, boxes=None):
|
def wrap(self, filename, boxes=None):
|
||||||
"""Create a new JP2/JPX file wrapped in a new jacket.
|
"""Create a new JP2/JPX file wrapped in a new set of JP2 boxes.
|
||||||
|
|
||||||
|
This method is primarily aimed at wrapping a raw codestream in a set of
|
||||||
|
of JP2 boxes (turning it into a JP2 file instead of just a raw
|
||||||
|
codestream), or rewrapping a codestream in a JP2 file in a new "jacket"
|
||||||
|
of JP2 boxes.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
|
@ -617,6 +622,8 @@ class Jp2k(Jp2kBox):
|
||||||
JP2 box definitions to define the JP2 file format. If not
|
JP2 box definitions to define the JP2 file format. If not
|
||||||
provided, a default ""jacket" is assumed, consisting of JP2
|
provided, a default ""jacket" is assumed, consisting of JP2
|
||||||
signature, file type, JP2 header, and contiguous codestream boxes.
|
signature, file type, JP2 header, and contiguous codestream boxes.
|
||||||
|
A JPX file rewrapped without the boxes argument results in a JP2
|
||||||
|
file encompassing the first codestream.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
|
@ -675,20 +682,46 @@ class Jp2k(Jp2kBox):
|
||||||
with open(self.filename, 'rb') as ifile:
|
with open(self.filename, 'rb') as ifile:
|
||||||
ofile.write(ifile.read())
|
ofile.write(ifile.read())
|
||||||
else:
|
else:
|
||||||
# OK, I'm a jp2 file. Need to find out where the
|
# OK, I'm a jp2/jpx file. Need to find out where the
|
||||||
# raw codestream actually starts.
|
# raw codestream actually starts.
|
||||||
offset = box.offset
|
offset = box.offset
|
||||||
length = box.length
|
length = box.length
|
||||||
if offset == -1:
|
if offset == -1:
|
||||||
|
if self.box[1].brand == 'jpx ':
|
||||||
|
msg = "The codestream box must have its offset "
|
||||||
|
msg += "and length attributes fully specified "
|
||||||
|
msg += "if the file type brand is JPX."
|
||||||
|
raise IOError(msg)
|
||||||
|
|
||||||
# Find the first codestream in the file.
|
# Find the first codestream in the file.
|
||||||
jp2c = [box for box in self.box
|
jp2c = [box for box in self.box
|
||||||
if box.box_id == 'jp2c']
|
if box.box_id == 'jp2c']
|
||||||
offset = jp2c[0].offset
|
offset = jp2c[0].offset
|
||||||
length = jp2c[0].length
|
length = jp2c[0].length
|
||||||
|
|
||||||
|
# Verify that the specified codestream is right.
|
||||||
with open(self.filename, 'rb') as ifile:
|
with open(self.filename, 'rb') as ifile:
|
||||||
ifile.seek(offset)
|
ifile.seek(offset)
|
||||||
ofile.write(ifile.read(length))
|
read_buffer = ifile.read(8)
|
||||||
|
L, T = struct.unpack_from('>I4s', read_buffer, 0)
|
||||||
|
if T != b'jp2c':
|
||||||
|
msg = "Unable to locate the specified codestream."
|
||||||
|
raise IOError(msg)
|
||||||
|
if L == 0:
|
||||||
|
# The length of the box is presumed to last
|
||||||
|
# until the end of the file. Compute the
|
||||||
|
# effective length of the box.
|
||||||
|
L = os.path.getsize(ifile.name) - fptr.tell() + 8
|
||||||
|
|
||||||
|
elif L == 1:
|
||||||
|
# The length of the box is in the XL field, a
|
||||||
|
# 64-bit value.
|
||||||
|
read_buffer = ifile.read(8)
|
||||||
|
L, = struct.unpack('>Q', read_buffer)
|
||||||
|
|
||||||
|
ifile.seek(offset)
|
||||||
|
read_buffer = ifile.read(L)
|
||||||
|
ofile.write(read_buffer)
|
||||||
|
|
||||||
ofile.flush()
|
ofile.flush()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -858,6 +858,67 @@ class TestWrap(unittest.TestCase):
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
j2k.wrap(tfile.name, boxes=boxes)
|
j2k.wrap(tfile.name, boxes=boxes)
|
||||||
|
|
||||||
|
def test_wrap_jpx_to_jp2_with_unadorned_jpch(self):
|
||||||
|
"""A JPX file rewrapped with plain jpch is not allowed."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1:
|
||||||
|
jpx = Jp2k(self.jpxfile)
|
||||||
|
boxes = [jpx.box[0], jpx.box[1], jpx.box[2],
|
||||||
|
glymur.jp2box.ContiguousCodestreamBox()]
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
jpx.wrap(tfile1.name, boxes=boxes)
|
||||||
|
|
||||||
|
def test_wrap_jpx_to_jp2_with_incorrect_jp2c_offset(self):
|
||||||
|
"""Reject A JPX file rewrapped with bad jp2c offset."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1:
|
||||||
|
jpx = Jp2k(self.jpxfile)
|
||||||
|
jpch = jpx.box[5]
|
||||||
|
|
||||||
|
# The offset should be 902.
|
||||||
|
jpch.offset = 901
|
||||||
|
jpch.length = 313274
|
||||||
|
boxes = [jpx.box[0], jpx.box[1], jpx.box[2], jpch]
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
jpx.wrap(tfile1.name, boxes=boxes)
|
||||||
|
|
||||||
|
def test_wrap_jpx_to_jp2_with_correctly_specified_jp2c(self):
|
||||||
|
"""Accept A JPX file rewrapped with good jp2c."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1:
|
||||||
|
jpx = Jp2k(self.jpxfile)
|
||||||
|
jpch = jpx.box[5]
|
||||||
|
|
||||||
|
# This time get it right.
|
||||||
|
jpch.offset = 903
|
||||||
|
jpch.length = 313274
|
||||||
|
boxes = [jpx.box[0], jpx.box[1], jpx.box[2], jpch]
|
||||||
|
jp2 = jpx.wrap(tfile1.name, boxes=boxes)
|
||||||
|
|
||||||
|
act_ids = [box.box_id for box in jp2.box]
|
||||||
|
exp_ids = ['jP ', 'ftyp', 'jp2h', 'jp2c']
|
||||||
|
self.assertEqual(act_ids, exp_ids)
|
||||||
|
|
||||||
|
act_offsets = [box.offset for box in jp2.box]
|
||||||
|
exp_offsets = [0, 12, 40, 887]
|
||||||
|
self.assertEqual(act_offsets, exp_offsets)
|
||||||
|
|
||||||
|
act_lengths = [box.length for box in jp2.box]
|
||||||
|
exp_lengths = [12, 28, 847, 313274]
|
||||||
|
self.assertEqual(act_lengths, exp_lengths)
|
||||||
|
|
||||||
|
def test_full_blown_jpx(self):
|
||||||
|
"""Rewrap a jpx file."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1:
|
||||||
|
jpx = Jp2k(self.jpxfile)
|
||||||
|
idx = list(range(5)) + list(range(9, 12)) + list(range(6, 9)) + [12]
|
||||||
|
boxes = [jpx.box[j] for j in idx]
|
||||||
|
jpx2 = jpx.wrap(tfile1.name, boxes=boxes)
|
||||||
|
exp_ids = [box.box_id for box in boxes]
|
||||||
|
lengths = [box.length for box in jpx.box]
|
||||||
|
exp_lengths = [lengths[j] for j in idx]
|
||||||
|
act_ids = [box.box_id for box in jpx2.box]
|
||||||
|
act_lengths = [box.length for box in jpx2.box]
|
||||||
|
self.assertEqual(exp_ids, act_ids)
|
||||||
|
self.assertEqual(exp_lengths, act_lengths)
|
||||||
|
|
||||||
|
|
||||||
class TestJp2Boxes(unittest.TestCase):
|
class TestJp2Boxes(unittest.TestCase):
|
||||||
"""Tests for canonical JP2 boxes."""
|
"""Tests for canonical JP2 boxes."""
|
||||||
|
|
|
||||||
|
|
@ -45,21 +45,6 @@ class TestJPXWrap(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
os.unlink(self.xmlfile)
|
os.unlink(self.xmlfile)
|
||||||
|
|
||||||
def test_full_blown_jpx(self):
|
|
||||||
"""Rewrap a jpx file."""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1:
|
|
||||||
jpx = Jp2k(self.jpxfile)
|
|
||||||
idx = list(range(5)) + list(range(9, 12)) + list(range(6, 9)) + [12]
|
|
||||||
boxes = [jpx.box[j] for j in idx]
|
|
||||||
jpx2 = jpx.wrap(tfile1.name, boxes=boxes)
|
|
||||||
exp_ids = [box.box_id for box in boxes]
|
|
||||||
lengths = [box.length for box in jpx.box]
|
|
||||||
exp_lengths = [lengths[j] for j in idx]
|
|
||||||
act_ids = [box.box_id for box in jpx2.box]
|
|
||||||
act_lengths = [box.length for box in jpx2.box]
|
|
||||||
self.assertEqual(exp_ids, act_ids)
|
|
||||||
self.assertEqual(exp_lengths, act_lengths)
|
|
||||||
|
|
||||||
def test_jpx_ftbl_no_codestream(self):
|
def test_jpx_ftbl_no_codestream(self):
|
||||||
"""Can have a jpx with no codestream."""
|
"""Can have a jpx with no codestream."""
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1:
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue