diff --git a/glymur/jp2box.py b/glymur/jp2box.py index d5e79e1..a599f84 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2286,6 +2286,14 @@ class LabelBox(Jp2kBox): msg = 'glymur.jp2box.LabelBox("{0}")'.format(self.label) return msg + def write(self, fptr): + """Write a Label box to file. + """ + length = 8 + len(self.label.encode()) + fptr.write(struct.pack('>I', length)) + fptr.write(self.box_id.encode()) + fptr.write(self.label.encode()) + @staticmethod def parse(fptr, offset, length): """Parse Label box. diff --git a/glymur/jp2k.py b/glymur/jp2k.py index d159fcc..96b6e68 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -1119,6 +1119,7 @@ def _validate_jp2_box_sequence(boxes): _validate_jp2h(boxes) _validate_jp2c(boxes) _validate_association(boxes) + _validate_label(boxes) _validate_jpx_brand(boxes, boxes[1].brand) _validate_jpx_compatibility(boxes, boxes[1].compatibility_list) _validate_singletons(boxes) @@ -1304,6 +1305,21 @@ def _validate_jpx_compatibility(boxes, compatibility_list): # Same set of checks on any child boxes. _validate_jpx_compatibility(box.box, compatibility_list) +def _validate_label(boxes): + """ + Label boxes can only be inside association, codestream headers, or + compositing layer header boxes. + """ + for box in boxes: + if box.box_id != 'asoc': + if hasattr(box, 'box'): + for boxi in box.box: + if boxi.box_id == 'lbl ': + msg = "A label box cannot be nested inside a {0} box." + msg = msg.format(box.box_id) + raise IOError(msg) + # Same set of checks on any child boxes. + _validate_label(box.box) def _validate_association(boxes): """ diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 2ebeb1f..8b5e075 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -67,8 +67,8 @@ class TestJPXWrap(unittest.TestCase): self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()), b'0') - def test_association_box(self): - """Wrap JP2 to JPX with asoc(nlst, xml)""" + def test_association_label_box(self): + """Wrap JP2 to JPX with asoc, label, and nlst boxes""" jp2 = Jp2k(self.jp2file) boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] @@ -76,11 +76,13 @@ class TestJPXWrap(unittest.TestCase): boxes[1].brand = 'jpx ' boxes[1].compatibility_list = ['jp2 ', 'jpx '] + label = 'this is a test' + lblb = glymur.jp2box.LabelBox(label) numbers = (0, 1) nlst = glymur.jp2box.NumberListBox(numbers) the_xml = ET.fromstring('0') xmlb = glymur.jp2box.XMLBox(xml=the_xml) - asoc = glymur.jp2box.AssociationBox([nlst, xmlb]) + asoc = glymur.jp2box.AssociationBox([nlst, xmlb, lblb]) boxes.append(asoc) with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: @@ -89,10 +91,12 @@ class TestJPXWrap(unittest.TestCase): self.assertEqual(jpx.box[1].compatibility_list, ['jp2 ', 'jpx ']) self.assertEqual(jpx.box[-1].box_id, 'asoc') self.assertEqual(jpx.box[-1].box[0].box_id, 'nlst') - self.assertEqual(jpx.box[-1].box[1].box_id, 'xml ') self.assertEqual(jpx.box[-1].box[0].associations, numbers) + self.assertEqual(jpx.box[-1].box[1].box_id, 'xml ') self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()), b'0') + self.assertEqual(jpx.box[-1].box[2].box_id, 'lbl ') + self.assertEqual(jpx.box[-1].box[2].label, label) def test_only_one_data_reference(self): """Data reference boxes cannot be inside a superbox .""" @@ -111,6 +115,23 @@ class TestJPXWrap(unittest.TestCase): with self.assertRaises(IOError): jpx = jp2.wrap(tfile.name, boxes=boxes) + def test_lbl_at_top_level(self): + """Label boxes can only be inside a asoc box .""" + jp2 = Jp2k(self.jp2file) + boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] + + flag = 0 + version = (0, 0, 0) + url = 'file:////usr/local/bin' + lblb = glymur.jp2box.LabelBox('hi there') + + # Put it inside the jp2 header box. + boxes[2].box.append(lblb) + + 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)