From 9b4e0a10fbef257399292ca469013bcc91d88540 Mon Sep 17 00:00:00 2001 From: jevans Date: Tue, 25 Mar 2014 21:17:29 -0400 Subject: [PATCH] More testing for jpx wrapping. Needs some refactoring, though. #206 --- glymur/jp2k.py | 39 ++++++++++++++++++++-- glymur/test/test_jp2box.py | 61 ++++++++++++++++++++++++++++++++++ glymur/test/test_jp2box_jpx.py | 15 --------- 3 files changed, 97 insertions(+), 18 deletions(-) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index fa2dfee..38e6973 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -607,7 +607,12 @@ class Jp2k(Jp2kBox): self.parse() 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 ---------- @@ -617,6 +622,8 @@ class Jp2k(Jp2kBox): JP2 box definitions to define the JP2 file format. If not provided, a default ""jacket" is assumed, consisting of JP2 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 ------- @@ -675,20 +682,46 @@ class Jp2k(Jp2kBox): with open(self.filename, 'rb') as ifile: ofile.write(ifile.read()) 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. offset = box.offset length = box.length 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. jp2c = [box for box in self.box if box.box_id == 'jp2c'] offset = jp2c[0].offset length = jp2c[0].length + # Verify that the specified codestream is right. with open(self.filename, 'rb') as ifile: 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() diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 6f3bac9..f61bafc 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -858,6 +858,67 @@ class TestWrap(unittest.TestCase): with self.assertRaises(IOError): 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): """Tests for canonical JP2 boxes.""" diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index f7cfec2..73e8f1b 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -45,21 +45,6 @@ class TestJPXWrap(unittest.TestCase): def tearDown(self): 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): """Can have a jpx with no codestream.""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: