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)