diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 07ddb6e..a6ea821 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -1837,10 +1837,10 @@ class ReaderRequirementsBox(Jp2kBox): Jp2kBox.__init__(self, box_id='rreq', longname='Reader Requirements') self.fuam = fuam self.dcm = dcm - self.standard_flag = standard_flag - self.standard_mask = standard_mask - self.vendor_feature = vendor_feature - self.vendor_mask = vendor_mask + self.standard_flag = tuple(standard_flag) + self.standard_mask = tuple(standard_mask) + self.vendor_feature = tuple(vendor_feature) + self.vendor_mask = tuple(vendor_mask) self.length = length self.offset = offset @@ -1902,6 +1902,9 @@ class ReaderRequirementsBox(Jp2kBox): read_buffer = fptr.read(1) mask_length, = struct.unpack('>B', read_buffer) + if mask_length == 3: + return _parse_rreq3(fptr, length, offset) + # Fully Understands Aspect Mask # Decodes Completely Mask read_buffer = fptr.read(2 * mask_length) @@ -1911,6 +1914,7 @@ class ReaderRequirementsBox(Jp2kBox): # The mask length tells us the format string to use when unpacking # from the buffer read from file. + try: mask_format = {1: 'B', 2: 'H', 4: 'I', 8: 'Q'}[mask_length] fuam, dcm = struct.unpack('>' + mask_format * 2, read_buffer) @@ -1931,6 +1935,62 @@ class ReaderRequirementsBox(Jp2kBox): return box +def _parse_rreq3(fptr, length, offset): + """Parse a reader requirements box. Special case when mask length is 3.""" + # Fully Understands Aspect Mask + # Decodes Completely Mask + read_buffer = fptr.read(2 * 3) + + fuam = dcm = standard_flag = standard_mask = [] + vendor_feature = vendor_mask = [] + + # The mask length tells us the format string to use when unpacking + # from the buffer read from file. + lst = struct.unpack('>BBBBBB', read_buffer) + fuam = lst[0] << 16 | lst[1] << 8 | lst[2] + dcm = lst[3] << 16 | lst[4] << 8 | lst[5] + + read_buffer = fptr.read(2) + num_standard_features, = struct.unpack('>H', read_buffer) + + fmt = '>' + 'HBBB' * num_standard_features + read_buffer = fptr.read(num_standard_features * 5) + lst = struct.unpack(fmt, read_buffer) + + standard_flag = lst[0::4] + standard_mask = [] + for j in range(num_standard_features): + items = lst[slice(j * 4 + 1, j * 4 + 4)] + mask = items[0] << 16 | items[1] << 8 | items[2] + standard_mask.append(mask) + + read_buffer = fptr.read(2) + num_vendor_features, = struct.unpack('>H', read_buffer) + + fmt = '>' + 'HBBB' * num_vendor_features + read_buffer = fptr.read(num_vendor_features * 5) + lst = struct.unpack(fmt, read_buffer) + + # Each vendor feature consists of a 16-byte UUID plus a mask whose + # length is specified by, you guessed it, "mask_length". + entry_length = 16 + 3 + read_buffer = fptr.read(num_vendor_features * entry_length) + vendor_feature = [] + vendor_mask = [] + for j in range(num_vendor_features): + ubuffer = read_buffer[j * entry_length:(j + 1) * entry_length] + vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16])) + + lst = struct.unpack('>BBB', ubuffer[16:]) + vmask = lst[0] << 16 | lst[1] << 8 | lst[2] + vendor_mask.append(vmask) + + box = ReaderRequirementsBox(fuam, dcm, standard_flag, standard_mask, + vendor_feature, vendor_mask, + length=length, offset=offset) + return box + + def _parse_standard_flag(fptr, mask_length): """Construct standard flag, standard mask data from the file. diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index ac1b2a3..ea1fddb 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -580,10 +580,7 @@ class TestWrap(unittest.TestCase): def test_jpx_to_jp2(self): """basic test for rewrapping a jpx file""" - with warnings.catch_warnings(): - # This file has a rreq mask length that we do not recognize. - warnings.simplefilter("ignore") - jpx = Jp2k(self.jpxfile) + jpx = Jp2k(self.jpxfile) idx = [0, 1, 3, 6] boxes = [jpx.box[idx] for idx in [0, 1, 3, 6]] with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 571afbc..93b821f 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -135,7 +135,6 @@ class TestJPXWrap(unittest.TestCase): jpx = jp2.wrap(tfile.name, boxes=boxes) -@unittest.skipIf(sys.hexversion < 0x03000000, "Warning assert on 2.x.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestJPX(unittest.TestCase): """Test suite for other JPX boxes.""" @@ -146,22 +145,18 @@ class TestJPX(unittest.TestCase): def tearDown(self): pass - def test_rreq_box_strange_mask_length(self): - """The standard says that the mask length should be 1, 2, 4, or 8.""" - with warnings.catch_warnings(): - # This file has a rreq mask length that we do not recognize. - warnings.simplefilter("ignore") - j = Jp2k(self.jpxfile) - self.assertEqual(j.box[2].box_id, 'rreq') - self.assertEqual(type(j.box[2]), + def test_jpx_rreq_mask_length_3(self): + """There are some JPX files with rreq mask length of 3.""" + jpx = Jp2k(self.jpxfile) + self.assertEqual(jpx.box[2].box_id, 'rreq') + self.assertEqual(type(jpx.box[2]), glymur.jp2box.ReaderRequirementsBox) + self.assertEqual(jpx.box[2].standard_flag, + (5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20)) def test_free_box(self): """Verify that we can handle a free box.""" - with warnings.catch_warnings(): - # This file has a rreq mask length that we do not recognize. - warnings.simplefilter("ignore") - j = Jp2k(self.jpxfile) + j = Jp2k(self.jpxfile) self.assertEqual(j.box[16].box[0].box_id, 'free') self.assertEqual(type(j.box[16].box[0]), glymur.jp2box.FreeBox) @@ -183,10 +178,7 @@ class TestJPX(unittest.TestCase): tfile.flush() - 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(len(jpx.box[-1].DR), 2) @@ -212,8 +204,7 @@ class TestJPX(unittest.TestCase): tfile.flush() - with self.assertWarns(UserWarning): - jpx = Jp2k(tfile.name) + jpx = Jp2k(tfile.name) self.assertEqual(jpx.box[-1].box_id, 'ftbl') self.assertEqual(jpx.box[-1].box[0].box_id, 'flst') @@ -223,10 +214,7 @@ class TestJPX(unittest.TestCase): def test_nlst(self): """Verify that we can handle a free box.""" - with warnings.catch_warnings(): - # This file has a rreq mask length that we do not recognize. - warnings.simplefilter("ignore") - j = Jp2k(self.jpxfile) + j = Jp2k(self.jpxfile) self.assertEqual(j.box[16].box[1].box[0].box_id, 'nlst') self.assertEqual(type(j.box[16].box[1].box[0]), glymur.jp2box.NumberListBox) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index a36d42c..386f077 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -379,9 +379,7 @@ class TestJp2k(unittest.TestCase): def test_jpx_mult_codestreams_jp2_brand(self): """Read JPX codestream when jp2-compatible.""" # The file in question has multiple codestreams. - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jpx = Jp2k(self.jpxfile) + jpx = Jp2k(self.jpxfile) data = jpx.read() if re.match(r"""1\.[0123]""", glymur.version.openjpeg_version): # openjpeg 1.3 doesn't apply the palette, so it's a 2D image here