Added append capability, #103
This commit is contained in:
parent
16ba90a372
commit
08ff86e4a2
2 changed files with 138 additions and 3 deletions
|
|
@ -432,6 +432,42 @@ class Jp2k(Jp2kBox):
|
|||
# Refresh the metadata.
|
||||
self.parse()
|
||||
|
||||
def append(self, box):
|
||||
"""Append a JP2 box to the file in-place.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
box : Jp2Box
|
||||
Instance of a JP2 box. Currently only XML boxes are allowed.
|
||||
"""
|
||||
if self._codec_format == opj2.CODEC_J2K:
|
||||
msg = "Only JP2 files can currently have boxes appended to them."
|
||||
raise IOError(msg)
|
||||
|
||||
if box.box_id != 'xml ':
|
||||
raise IOError("Only XML boxes can currently be appended.")
|
||||
|
||||
# Check the last box. If the length field is zero, then rewrite
|
||||
# the length field to reflect the true length of the box.
|
||||
with open(self.filename, 'rb') as ifile:
|
||||
offset = self.box[-1].offset
|
||||
ifile.seek(offset)
|
||||
read_buffer = ifile.read(4)
|
||||
box_length, = struct.unpack('>I', read_buffer)
|
||||
if box_length == 0:
|
||||
# Reopen the file in write mode and rewrite the length field.
|
||||
true_box_length = os.path.getsize(ifile.name) - offset
|
||||
with open(self.filename, 'r+b') as ofile:
|
||||
ofile.seek(offset)
|
||||
write_buffer = struct.pack('>I', true_box_length)
|
||||
ofile.write(write_buffer)
|
||||
|
||||
# Can now safely append the box.
|
||||
with open(self.filename, 'ab') as ofile:
|
||||
box.write(ofile)
|
||||
|
||||
self.parse()
|
||||
|
||||
def wrap(self, filename, boxes=None):
|
||||
"""Write the codestream back out to file, wrapped in new JP2 jacket.
|
||||
|
||||
|
|
@ -1133,6 +1169,7 @@ def _unpack_colorspace(colorspace, img_array, cparams):
|
|||
|
||||
return colorspace
|
||||
|
||||
|
||||
def _populate_comptparms(img_array, cparams):
|
||||
"""Instantiate and populate comptparms structure.
|
||||
|
||||
|
|
@ -1171,6 +1208,7 @@ def _populate_comptparms(img_array, cparams):
|
|||
|
||||
return comptparms
|
||||
|
||||
|
||||
def _populate_image_struct(cparams, image, imgdata):
|
||||
"""Populates image struct needed for compression.
|
||||
|
||||
|
|
@ -1203,6 +1241,7 @@ def _populate_image_struct(cparams, image, imgdata):
|
|||
|
||||
return image
|
||||
|
||||
|
||||
def _validate_compression_params(img_array, cparams):
|
||||
"""Check that the compression parameters are valid.
|
||||
|
||||
|
|
@ -1313,4 +1352,3 @@ class LibraryNotFoundError(IOError):
|
|||
"""
|
||||
def __init__(self, msg):
|
||||
IOError.__init__(self, msg)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,11 @@ Test suite specifically targeting JP2 box layout.
|
|||
|
||||
import doctest
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
import uuid
|
||||
import xml.etree.cElementTree as ET
|
||||
|
||||
if sys.hexversion < 0x02070000:
|
||||
|
|
@ -502,6 +505,100 @@ class TestColourSpecificationBox(unittest.TestCase):
|
|||
approximation=approx)
|
||||
|
||||
|
||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
||||
"Missing openjp2 library.")
|
||||
class TestAppend(unittest.TestCase):
|
||||
"""Tests for append method."""
|
||||
|
||||
def setUp(self):
|
||||
self.j2kfile = glymur.data.goodstuff()
|
||||
self.jp2file = glymur.data.nemo()
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_append_xml(self):
|
||||
"""Should be able to append an XML box."""
|
||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||
shutil.copyfile(self.jp2file, tfile.name)
|
||||
|
||||
jp2 = Jp2k(tfile.name)
|
||||
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
||||
xmlbox = glymur.jp2box.XMLBox(xml=the_xml)
|
||||
jp2.append(xmlbox)
|
||||
|
||||
# The sequence of box IDs should be the same as before, but with an
|
||||
# xml box at the end.
|
||||
box_ids = [box.box_id for box in jp2.box]
|
||||
expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'uuid', 'jp2c', 'xml ']
|
||||
self.assertEqual(box_ids, expected)
|
||||
self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()),
|
||||
b'<data>0</data>')
|
||||
|
||||
def test_only_jp2_allowed_to_append(self):
|
||||
"""Only JP2 files are allowed to be appended."""
|
||||
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||
shutil.copyfile(self.j2kfile, tfile.name)
|
||||
|
||||
jp2 = Jp2k(tfile.name)
|
||||
|
||||
# Make a UUID box.
|
||||
uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000')
|
||||
data = b'0123456789'
|
||||
uuidbox = glymur.jp2box.UUIDBox(uuid_instance, data)
|
||||
with self.assertRaises(IOError):
|
||||
jp2.append(uuidbox)
|
||||
|
||||
def test_length_field_is_zero(self):
|
||||
"""L=0 (length field in box header) is handled.
|
||||
|
||||
L=0 implies that the containing box is the last box. If this is not
|
||||
handled properly, the appended box is never seen.
|
||||
"""
|
||||
baseline_jp2 = Jp2k(self.jp2file)
|
||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||
with open(self.jp2file, 'rb') as ifile:
|
||||
# Everything up until the jp2c box.
|
||||
offset = baseline_jp2.box[-1].offset
|
||||
tfile.write(ifile.read(offset))
|
||||
|
||||
# Write the L, T fields of the jp2c box such that L == 0
|
||||
write_buffer = struct.pack('>I4s', int(0), b'jp2c')
|
||||
tfile.write(write_buffer)
|
||||
|
||||
# Write out the rest of the codestream.
|
||||
ifile.seek(offset+8)
|
||||
tfile.write(ifile.read())
|
||||
tfile.flush()
|
||||
|
||||
jp2 = Jp2k(tfile.name)
|
||||
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
||||
xmlbox = glymur.jp2box.XMLBox(xml=the_xml)
|
||||
jp2.append(xmlbox)
|
||||
|
||||
# The sequence of box IDs should be the same as before, but with an
|
||||
# xml box at the end.
|
||||
box_ids = [box.box_id for box in jp2.box]
|
||||
expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'uuid', 'jp2c', 'xml ']
|
||||
self.assertEqual(box_ids, expected)
|
||||
self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()),
|
||||
b'<data>0</data>')
|
||||
|
||||
def test_only_xml_allowed_to_append(self):
|
||||
"""Only XML boxes are allowed to be appended."""
|
||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||
shutil.copyfile(self.jp2file, tfile.name)
|
||||
|
||||
jp2 = Jp2k(tfile.name)
|
||||
|
||||
# Make a UUID box.
|
||||
uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000')
|
||||
data = b'0123456789'
|
||||
uuidbox = glymur.jp2box.UUIDBox(uuid_instance, data)
|
||||
with self.assertRaises(IOError):
|
||||
jp2.append(uuidbox)
|
||||
|
||||
|
||||
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
||||
"Missing openjp2 library.")
|
||||
class TestWrap(unittest.TestCase):
|
||||
|
|
@ -703,7 +800,7 @@ class TestJp2Boxes(unittest.TestCase):
|
|||
def test_default_ihdr(self):
|
||||
"""Should be able to instantiate an image header box."""
|
||||
ihdr = glymur.jp2box.ImageHeaderBox(height=512, width=256,
|
||||
num_components=3)
|
||||
num_components=3)
|
||||
self.assertEqual(ihdr.height, 512)
|
||||
self.assertEqual(ihdr.width, 256)
|
||||
self.assertEqual(ihdr.num_components, 3)
|
||||
|
|
@ -715,7 +812,7 @@ class TestJp2Boxes(unittest.TestCase):
|
|||
"""Should be able to set jp2h boxes."""
|
||||
box = JP2HeaderBox()
|
||||
box.box = [ImageHeaderBox(height=512, width=256),
|
||||
ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)]
|
||||
ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)]
|
||||
self.assertTrue(True)
|
||||
|
||||
def test_default_ccodestreambox(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue