Refactor box validation when wrapping
These became functions only at the behest of pylint. I don't really agree with that justification anymore, and flake8 is perfectly happy with them as methods, so back to methods they go.
This commit is contained in:
parent
858ecbbac8
commit
d7a3c4df27
1 changed files with 265 additions and 279 deletions
544
glymur/jp2k.py
544
glymur/jp2k.py
|
|
@ -840,7 +840,7 @@ class Jp2k(Jp2kBox):
|
|||
if boxes is None:
|
||||
boxes = self._get_default_jp2_boxes()
|
||||
|
||||
_validate_jp2_box_sequence(boxes)
|
||||
self._validate_jp2_box_sequence(boxes)
|
||||
|
||||
with open(filename, 'wb') as ofile:
|
||||
for box in boxes:
|
||||
|
|
@ -1486,7 +1486,7 @@ class Jp2k(Jp2kBox):
|
|||
dtypes, nrows, ncols = [], [], []
|
||||
for k in range(raw_image.contents.numcomps):
|
||||
component = raw_image.contents.comps[k]
|
||||
dtypes.append(_component2dtype(component))
|
||||
dtypes.append(self._component2dtype(component))
|
||||
nrows.append(component.h)
|
||||
ncols.append(component.w)
|
||||
is_cube = all(r == nrows[0] and c == ncols[0] and d == dtypes[0]
|
||||
|
|
@ -1500,7 +1500,7 @@ class Jp2k(Jp2kBox):
|
|||
for k in range(raw_image.contents.numcomps):
|
||||
component = raw_image.contents.comps[k]
|
||||
|
||||
_validate_nonzero_image_size(nrows[k], ncols[k], k)
|
||||
self._validate_nonzero_image_size(nrows[k], ncols[k], k)
|
||||
|
||||
addr = ctypes.addressof(component.data.contents)
|
||||
with warnings.catch_warnings():
|
||||
|
|
@ -1517,6 +1517,38 @@ class Jp2k(Jp2kBox):
|
|||
|
||||
return image
|
||||
|
||||
def _component2dtype(self, component):
|
||||
"""Determin the appropriate numpy datatype for an OpenJPEG component.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
component : ctypes pointer to ImageCompType (image_comp_t)
|
||||
single image component structure.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dtype : builtins.type
|
||||
numpy datatype to be used to construct an image array.
|
||||
"""
|
||||
if component.sgnd:
|
||||
if component.prec <= 8:
|
||||
dtype = np.int8
|
||||
elif component.prec <= 16:
|
||||
dtype = np.int16
|
||||
else:
|
||||
msg = "Unhandled precision: {0} bits.".format(component.prec)
|
||||
raise RuntimeError(msg)
|
||||
else:
|
||||
if component.prec <= 8:
|
||||
dtype = np.uint8
|
||||
elif component.prec <= 16:
|
||||
dtype = np.uint16
|
||||
else:
|
||||
msg = "Unhandled precision: {0} bits.".format(component.prec)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
return dtype
|
||||
|
||||
def get_codestream(self, header_only=True):
|
||||
"""Returns a codestream object.
|
||||
|
||||
|
|
@ -1645,306 +1677,260 @@ class Jp2k(Jp2kBox):
|
|||
|
||||
self._comptparms = comptparms
|
||||
|
||||
|
||||
def _component2dtype(component):
|
||||
"""Take an OpenJPEG component structure and determine the numpy datatype.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
component : ctypes pointer to ImageCompType (image_comp_t)
|
||||
single image component structure.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dtype : builtins.type
|
||||
numpy datatype to be used to construct an image array.
|
||||
"""
|
||||
if component.sgnd:
|
||||
if component.prec <= 8:
|
||||
dtype = np.int8
|
||||
elif component.prec <= 16:
|
||||
dtype = np.int16
|
||||
else:
|
||||
raise RuntimeError("Unhandled precision, datatype")
|
||||
else:
|
||||
if component.prec <= 8:
|
||||
dtype = np.uint8
|
||||
elif component.prec <= 16:
|
||||
dtype = np.uint16
|
||||
else:
|
||||
raise RuntimeError("Unhandled precision, datatype")
|
||||
|
||||
return dtype
|
||||
|
||||
|
||||
def _validate_nonzero_image_size(nrows, ncols, component_index):
|
||||
"""The image cannot have area of zero.
|
||||
"""
|
||||
if nrows == 0 or ncols == 0:
|
||||
# Letting this situation continue would segfault Python.
|
||||
msg = "Component {0} has dimensions {1} x {2}"
|
||||
msg = msg.format(component_index, nrows, ncols)
|
||||
raise IOError(msg)
|
||||
|
||||
|
||||
JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ',
|
||||
'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ',
|
||||
'uuid']
|
||||
|
||||
|
||||
def _validate_jp2_box_sequence(boxes):
|
||||
"""Run through series of tests for JP2 box legality.
|
||||
|
||||
This is non-exhaustive.
|
||||
"""
|
||||
_validate_signature_compatibility(boxes)
|
||||
_validate_jp2h(boxes)
|
||||
_validate_jp2c(boxes)
|
||||
if boxes[1].brand == 'jpx ':
|
||||
_validate_jpx_box_sequence(boxes)
|
||||
else:
|
||||
# Validate the JP2 box IDs.
|
||||
count = _collect_box_count(boxes)
|
||||
for box_id in count.keys():
|
||||
if box_id not in JP2_IDS:
|
||||
msg = "The presence of a '{0}' box requires that the file "
|
||||
msg += "type brand be set to 'jpx '."
|
||||
raise IOError(msg.format(box_id))
|
||||
|
||||
_validate_jp2_colr(boxes)
|
||||
|
||||
|
||||
def _validate_jp2_colr(boxes):
|
||||
"""
|
||||
Validate JP2 requirements on colour specification boxes.
|
||||
"""
|
||||
lst = [box for box in boxes if box.box_id == 'jp2h']
|
||||
jp2h = lst[0]
|
||||
for colr in [box for box in jp2h.box if box.box_id == 'colr']:
|
||||
if colr.approximation != 0:
|
||||
msg = "A JP2 colr box cannot have a non-zero approximation field."
|
||||
def _validate_nonzero_image_size(self, nrows, ncols, component_index):
|
||||
"""The image cannot have area of zero.
|
||||
"""
|
||||
if nrows == 0 or ncols == 0:
|
||||
# Letting this situation continue would segfault openjpeg.
|
||||
msg = "Component {0} has dimensions {1} x {2}"
|
||||
msg = msg.format(component_index, nrows, ncols)
|
||||
raise IOError(msg)
|
||||
|
||||
def _validate_jp2_box_sequence(self, boxes):
|
||||
"""Run through series of tests for JP2 box legality.
|
||||
|
||||
def _validate_jpx_box_sequence(boxes):
|
||||
"""Run through series of tests for JPX box legality."""
|
||||
_validate_label(boxes)
|
||||
_validate_jpx_brand(boxes, boxes[1].brand)
|
||||
_validate_jpx_compatibility(boxes, boxes[1].compatibility_list)
|
||||
_validate_singletons(boxes)
|
||||
_validate_top_level(boxes)
|
||||
This is non-exhaustive.
|
||||
"""
|
||||
JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h',
|
||||
'jP ', 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst',
|
||||
'uinf', 'url ', 'uuid']
|
||||
|
||||
self._validate_signature_compatibility(boxes)
|
||||
self._validate_jp2h(boxes)
|
||||
self._validate_jp2c(boxes)
|
||||
if boxes[1].brand == 'jpx ':
|
||||
self._validate_jpx_box_sequence(boxes)
|
||||
else:
|
||||
# Validate the JP2 box IDs.
|
||||
count = self._collect_box_count(boxes)
|
||||
for box_id in count.keys():
|
||||
if box_id not in JP2_IDS:
|
||||
msg = "The presence of a '{0}' box requires that the file "
|
||||
msg += "type brand be set to 'jpx '."
|
||||
raise IOError(msg.format(box_id))
|
||||
|
||||
def _validate_signature_compatibility(boxes):
|
||||
"""Validate the file signature and compatibility status."""
|
||||
# Check for a bad sequence of boxes.
|
||||
# 1st two boxes must be 'jP ' and 'ftyp'
|
||||
if boxes[0].box_id != 'jP ' or boxes[1].box_id != 'ftyp':
|
||||
msg = "The first box must be the signature box and the second "
|
||||
msg += "must be the file type box."
|
||||
raise IOError(msg)
|
||||
self._validate_jp2_colr(boxes)
|
||||
|
||||
# The compatibility list must contain at a minimum 'jp2 '.
|
||||
if 'jp2 ' not in boxes[1].compatibility_list:
|
||||
msg = "The ftyp box must contain 'jp2 ' in the compatibility list."
|
||||
raise IOError(msg)
|
||||
|
||||
|
||||
def _validate_jp2c(boxes):
|
||||
"""Validate the codestream box in relation to other boxes."""
|
||||
# jp2c must be preceeded by jp2h
|
||||
jp2h_lst = [idx for (idx, box) in enumerate(boxes)
|
||||
if box.box_id == 'jp2h']
|
||||
jp2h_idx = jp2h_lst[0]
|
||||
jp2c_lst = [idx for (idx, box) in enumerate(boxes)
|
||||
if box.box_id == 'jp2c']
|
||||
if len(jp2c_lst) == 0:
|
||||
msg = "A codestream box must be defined in the outermost "
|
||||
msg += "list of boxes."
|
||||
raise IOError(msg)
|
||||
|
||||
jp2c_idx = jp2c_lst[0]
|
||||
if jp2h_idx >= jp2c_idx:
|
||||
msg = "The codestream box must be preceeded by a jp2 header box."
|
||||
raise IOError(msg)
|
||||
|
||||
|
||||
def _validate_jp2h(boxes):
|
||||
"""Validate the JP2 Header box."""
|
||||
_check_jp2h_child_boxes(boxes, 'top-level')
|
||||
|
||||
jp2h_lst = [box for box in boxes if box.box_id == 'jp2h']
|
||||
jp2h = jp2h_lst[0]
|
||||
|
||||
# 1st jp2 header box cannot be empty.
|
||||
if len(jp2h.box) == 0:
|
||||
msg = "The JP2 header superbox cannot be empty."
|
||||
raise IOError(msg)
|
||||
|
||||
# 1st jp2 header box must be ihdr
|
||||
if jp2h.box[0].box_id != 'ihdr':
|
||||
msg = "The first box in the jp2 header box must be the image "
|
||||
msg += "header box."
|
||||
raise IOError(msg)
|
||||
|
||||
# colr must be present in jp2 header box.
|
||||
colr_lst = [j for (j, box) in enumerate(jp2h.box)
|
||||
if box.box_id == 'colr']
|
||||
if len(colr_lst) == 0:
|
||||
msg = "The jp2 header box must contain a color definition box."
|
||||
raise IOError(msg)
|
||||
colr = jp2h.box[colr_lst[0]]
|
||||
|
||||
_validate_channel_definition(jp2h, colr)
|
||||
|
||||
|
||||
def _validate_channel_definition(jp2h, colr):
|
||||
"""Validate the channel definition box."""
|
||||
cdef_lst = [j for (j, box) in enumerate(jp2h.box) if box.box_id == 'cdef']
|
||||
if len(cdef_lst) > 1:
|
||||
msg = "Only one channel definition box is allowed in the "
|
||||
msg += "JP2 header."
|
||||
raise IOError(msg)
|
||||
elif len(cdef_lst) == 1:
|
||||
cdef = jp2h.box[cdef_lst[0]]
|
||||
if colr.colorspace == core.SRGB:
|
||||
if any([chan + 1 not in cdef.association
|
||||
or cdef.channel_type[chan] != 0
|
||||
for chan in [0, 1, 2]]):
|
||||
msg = "All color channels must be defined in the "
|
||||
msg += "channel definition box."
|
||||
raise IOError(msg)
|
||||
elif colr.colorspace == core.GREYSCALE:
|
||||
if 0 not in cdef.channel_type:
|
||||
msg = "All color channels must be defined in the "
|
||||
msg += "channel definition box."
|
||||
def _validate_jp2_colr(self, boxes):
|
||||
"""
|
||||
Validate JP2 requirements on colour specification boxes.
|
||||
"""
|
||||
lst = [box for box in boxes if box.box_id == 'jp2h']
|
||||
jp2h = lst[0]
|
||||
for colr in [box for box in jp2h.box if box.box_id == 'colr']:
|
||||
if colr.approximation != 0:
|
||||
msg = "A JP2 colr box cannot have a non-zero approximation "
|
||||
msg += "field."
|
||||
raise IOError(msg)
|
||||
|
||||
def _validate_jpx_box_sequence(self, boxes):
|
||||
"""Run through series of tests for JPX box legality."""
|
||||
self._validate_label(boxes)
|
||||
self._validate_jpx_brand(boxes, boxes[1].brand)
|
||||
self._validate_jpx_compatibility(boxes, boxes[1].compatibility_list)
|
||||
self._validate_singletons(boxes)
|
||||
self._validate_top_level(boxes)
|
||||
|
||||
JP2H_CHILDREN = set(['bpcc', 'cdef', 'cmap', 'ihdr', 'pclr'])
|
||||
def _validate_signature_compatibility(self, boxes):
|
||||
"""Validate the file signature and compatibility status."""
|
||||
# Check for a bad sequence of boxes.
|
||||
# 1st two boxes must be 'jP ' and 'ftyp'
|
||||
if boxes[0].box_id != 'jP ' or boxes[1].box_id != 'ftyp':
|
||||
msg = "The first box must be the signature box and the second "
|
||||
msg += "must be the file type box."
|
||||
raise IOError(msg)
|
||||
|
||||
# The compatibility list must contain at a minimum 'jp2 '.
|
||||
if 'jp2 ' not in boxes[1].compatibility_list:
|
||||
msg = "The ftyp box must contain 'jp2 ' in the compatibility list."
|
||||
raise IOError(msg)
|
||||
|
||||
def _check_jp2h_child_boxes(boxes, parent_box_name):
|
||||
"""Certain boxes can only reside in the JP2 header."""
|
||||
box_ids = set([box.box_id for box in boxes])
|
||||
intersection = box_ids.intersection(JP2H_CHILDREN)
|
||||
if len(intersection) > 0 and parent_box_name not in ['jp2h', 'jpch']:
|
||||
msg = "A '{0}' box can only be nested in a JP2 header box."
|
||||
raise IOError(msg.format(list(intersection)[0]))
|
||||
def _validate_jp2c(self, boxes):
|
||||
"""Validate the codestream box in relation to other boxes."""
|
||||
# jp2c must be preceeded by jp2h
|
||||
jp2h_lst = [idx for (idx, box) in enumerate(boxes)
|
||||
if box.box_id == 'jp2h']
|
||||
jp2h_idx = jp2h_lst[0]
|
||||
jp2c_lst = [idx for (idx, box) in enumerate(boxes)
|
||||
if box.box_id == 'jp2c']
|
||||
if len(jp2c_lst) == 0:
|
||||
msg = "A codestream box must be defined in the outermost "
|
||||
msg += "list of boxes."
|
||||
raise IOError(msg)
|
||||
|
||||
# Recursively check any contained superboxes.
|
||||
for box in boxes:
|
||||
if hasattr(box, 'box'):
|
||||
_check_jp2h_child_boxes(box.box, box.box_id)
|
||||
jp2c_idx = jp2c_lst[0]
|
||||
if jp2h_idx >= jp2c_idx:
|
||||
msg = "The codestream box must be preceeded by a jp2 header box."
|
||||
raise IOError(msg)
|
||||
|
||||
def _validate_jp2h(self, boxes):
|
||||
"""Validate the JP2 Header box."""
|
||||
self._check_jp2h_child_boxes(boxes, 'top-level')
|
||||
|
||||
def _collect_box_count(boxes):
|
||||
"""Count the occurences of each box type."""
|
||||
count = Counter([box.box_id for box in boxes])
|
||||
jp2h_lst = [box for box in boxes if box.box_id == 'jp2h']
|
||||
jp2h = jp2h_lst[0]
|
||||
|
||||
# Add the counts in the superboxes.
|
||||
for box in boxes:
|
||||
if hasattr(box, 'box'):
|
||||
count.update(_collect_box_count(box.box))
|
||||
# 1st jp2 header box cannot be empty.
|
||||
if len(jp2h.box) == 0:
|
||||
msg = "The JP2 header superbox cannot be empty."
|
||||
raise IOError(msg)
|
||||
|
||||
return count
|
||||
# 1st jp2 header box must be ihdr
|
||||
if jp2h.box[0].box_id != 'ihdr':
|
||||
msg = "The first box in the jp2 header box must be the image "
|
||||
msg += "header box."
|
||||
raise IOError(msg)
|
||||
|
||||
TOP_LEVEL_ONLY_BOXES = set(['dtbl'])
|
||||
# colr must be present in jp2 header box.
|
||||
colr_lst = [j for (j, box) in enumerate(jp2h.box)
|
||||
if box.box_id == 'colr']
|
||||
if len(colr_lst) == 0:
|
||||
msg = "The jp2 header box must contain a color definition box."
|
||||
raise IOError(msg)
|
||||
colr = jp2h.box[colr_lst[0]]
|
||||
|
||||
self._validate_channel_definition(jp2h, colr)
|
||||
|
||||
def _check_superbox_for_top_levels(boxes):
|
||||
"""Several boxes can only occur at the top level."""
|
||||
# We are only looking at the boxes contained in a superbox, so if any of
|
||||
# the blacklisted boxes show up here, it's an error.
|
||||
box_ids = set([box.box_id for box in boxes])
|
||||
intersection = box_ids.intersection(TOP_LEVEL_ONLY_BOXES)
|
||||
if len(intersection) > 0:
|
||||
msg = "A '{0}' box cannot be nested in a superbox."
|
||||
raise IOError(msg.format(list(intersection)[0]))
|
||||
def _validate_channel_definition(self, jp2h, colr):
|
||||
"""Validate the channel definition box."""
|
||||
cdef_lst = [j for (j, box) in enumerate(jp2h.box)
|
||||
if box.box_id == 'cdef']
|
||||
if len(cdef_lst) > 1:
|
||||
msg = "Only one channel definition box is allowed in the "
|
||||
msg += "JP2 header."
|
||||
raise IOError(msg)
|
||||
elif len(cdef_lst) == 1:
|
||||
cdef = jp2h.box[cdef_lst[0]]
|
||||
if colr.colorspace == core.SRGB:
|
||||
if any([chan + 1 not in cdef.association
|
||||
or cdef.channel_type[chan] != 0
|
||||
for chan in [0, 1, 2]]):
|
||||
msg = "All color channels must be defined in the "
|
||||
msg += "channel definition box."
|
||||
raise IOError(msg)
|
||||
elif colr.colorspace == core.GREYSCALE:
|
||||
if 0 not in cdef.channel_type:
|
||||
msg = "All color channels must be defined in the "
|
||||
msg += "channel definition box."
|
||||
raise IOError(msg)
|
||||
|
||||
# Recursively check any contained superboxes.
|
||||
for box in boxes:
|
||||
if hasattr(box, 'box'):
|
||||
_check_superbox_for_top_levels(box.box)
|
||||
def _check_jp2h_child_boxes(self, boxes, parent_box_name):
|
||||
"""Certain boxes can only reside in the JP2 header."""
|
||||
JP2H_CHILDREN = set(['bpcc', 'cdef', 'cmap', 'ihdr', 'pclr'])
|
||||
|
||||
box_ids = set([box.box_id for box in boxes])
|
||||
intersection = box_ids.intersection(JP2H_CHILDREN)
|
||||
if len(intersection) > 0 and parent_box_name not in ['jp2h', 'jpch']:
|
||||
msg = "A '{0}' box can only be nested in a JP2 header box."
|
||||
raise IOError(msg.format(list(intersection)[0]))
|
||||
|
||||
def _validate_top_level(boxes):
|
||||
"""Several boxes can only occur at the top level."""
|
||||
# Add the counts in the superboxes.
|
||||
for box in boxes:
|
||||
if hasattr(box, 'box'):
|
||||
_check_superbox_for_top_levels(box.box)
|
||||
|
||||
count = _collect_box_count(boxes)
|
||||
# Which boxes occur more than once?
|
||||
multiples = [box_id for box_id, bcount in count.items() if bcount > 1]
|
||||
if 'dtbl' in multiples:
|
||||
raise IOError('There can only be one dtbl box in a file.')
|
||||
|
||||
# If there is one data reference box, then there must also be one ftbl.
|
||||
if 'dtbl' in count and 'ftbl' not in count:
|
||||
msg = 'The presence of a data reference box requires the presence of '
|
||||
msg += 'a fragment table box as well.'
|
||||
raise IOError(msg)
|
||||
|
||||
|
||||
def _validate_singletons(boxes):
|
||||
"""Several boxes can only occur once."""
|
||||
count = _collect_box_count(boxes)
|
||||
# Which boxes occur more than once?
|
||||
multiples = [box_id for box_id, bcount in count.items() if bcount > 1]
|
||||
if 'dtbl' in multiples:
|
||||
raise IOError('There can only be one dtbl box in a file.')
|
||||
|
||||
JPX_IDS = ['asoc', 'nlst']
|
||||
|
||||
|
||||
def _validate_jpx_brand(boxes, brand):
|
||||
"""
|
||||
If there is a JPX box then the brand must be 'jpx '.
|
||||
"""
|
||||
for box in boxes:
|
||||
if box.box_id in JPX_IDS:
|
||||
if brand != 'jpx ':
|
||||
msg = "A JPX box requires that the file type box brand be "
|
||||
msg += "'jpx '."
|
||||
raise RuntimeError(msg)
|
||||
if hasattr(box, 'box') != 0:
|
||||
# Same set of checks on any child boxes.
|
||||
_validate_jpx_brand(box.box, brand)
|
||||
|
||||
|
||||
def _validate_jpx_compatibility(boxes, compatibility_list):
|
||||
"""
|
||||
If there is a JPX box then the compatibility list must also contain 'jpx '.
|
||||
"""
|
||||
jpx_cl = set(compatibility_list)
|
||||
for box in boxes:
|
||||
if box.box_id in JPX_IDS:
|
||||
if len(set(['jpx ', 'jpxb']).intersection(jpx_cl)) == 0:
|
||||
msg = "A JPX box requires that either 'jpx ' or 'jpxb' be "
|
||||
msg += "present in the ftype compatibility list."
|
||||
raise RuntimeError(msg)
|
||||
if hasattr(box, 'box') != 0:
|
||||
# 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':
|
||||
# Recursively check any contained superboxes.
|
||||
for box in boxes:
|
||||
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)
|
||||
self._check_jp2h_child_boxes(box.box, box.box_id)
|
||||
|
||||
def _collect_box_count(self, boxes):
|
||||
"""Count the occurences of each box type."""
|
||||
count = Counter([box.box_id for box in boxes])
|
||||
|
||||
# Add the counts in the superboxes.
|
||||
for box in boxes:
|
||||
if hasattr(box, 'box'):
|
||||
count.update(self._collect_box_count(box.box))
|
||||
|
||||
return count
|
||||
|
||||
def _check_superbox_for_top_levels(self, boxes):
|
||||
"""Several boxes can only occur at the top level."""
|
||||
# We are only looking at the boxes contained in a superbox, so if any
|
||||
# of the blacklisted boxes show up here, it's an error.
|
||||
TOP_LEVEL_ONLY_BOXES = set(['dtbl'])
|
||||
box_ids = set([box.box_id for box in boxes])
|
||||
intersection = box_ids.intersection(TOP_LEVEL_ONLY_BOXES)
|
||||
if len(intersection) > 0:
|
||||
msg = "A '{0}' box cannot be nested in a superbox."
|
||||
raise IOError(msg.format(list(intersection)[0]))
|
||||
|
||||
# Recursively check any contained superboxes.
|
||||
for box in boxes:
|
||||
if hasattr(box, 'box'):
|
||||
self._check_superbox_for_top_levels(box.box)
|
||||
|
||||
def _validate_top_level(self, boxes):
|
||||
"""Several boxes can only occur at the top level."""
|
||||
# Add the counts in the superboxes.
|
||||
for box in boxes:
|
||||
if hasattr(box, 'box'):
|
||||
self._check_superbox_for_top_levels(box.box)
|
||||
|
||||
count = self._collect_box_count(boxes)
|
||||
# Which boxes occur more than once?
|
||||
multiples = [box_id for box_id, bcount in count.items() if bcount > 1]
|
||||
if 'dtbl' in multiples:
|
||||
raise IOError('There can only be one dtbl box in a file.')
|
||||
|
||||
# If there is one data reference box, then there must also be one ftbl.
|
||||
if 'dtbl' in count and 'ftbl' not in count:
|
||||
msg = 'The presence of a data reference box requires the presence '
|
||||
msg += 'of a fragment table box as well.'
|
||||
raise IOError(msg)
|
||||
|
||||
def _validate_singletons(self, boxes):
|
||||
"""Several boxes can only occur once."""
|
||||
count = self._collect_box_count(boxes)
|
||||
# Which boxes occur more than once?
|
||||
multiples = [box_id for box_id, bcount in count.items() if bcount > 1]
|
||||
if 'dtbl' in multiples:
|
||||
raise IOError('There can only be one dtbl box in a file.')
|
||||
|
||||
def _validate_jpx_brand(self, boxes, brand):
|
||||
"""
|
||||
If there is a JPX box then the brand must be 'jpx '.
|
||||
"""
|
||||
JPX_IDS = ['asoc', 'nlst']
|
||||
for box in boxes:
|
||||
if box.box_id in JPX_IDS:
|
||||
if brand != 'jpx ':
|
||||
msg = "A JPX box requires that the file type box brand be "
|
||||
msg += "'jpx '."
|
||||
raise RuntimeError(msg)
|
||||
if hasattr(box, 'box') != 0:
|
||||
# Same set of checks on any child boxes.
|
||||
_validate_label(box.box)
|
||||
self._validate_jpx_brand(box.box, brand)
|
||||
|
||||
def _validate_jpx_compatibility(self, boxes, compatibility_list):
|
||||
"""
|
||||
If there is a JPX box then the compatibility list must also contain
|
||||
'jpx '.
|
||||
"""
|
||||
JPX_IDS = ['asoc', 'nlst']
|
||||
jpx_cl = set(compatibility_list)
|
||||
for box in boxes:
|
||||
if box.box_id in JPX_IDS:
|
||||
if len(set(['jpx ', 'jpxb']).intersection(jpx_cl)) == 0:
|
||||
msg = "A JPX box requires that either 'jpx ' or 'jpxb' be "
|
||||
msg += "present in the ftype compatibility list."
|
||||
raise RuntimeError(msg)
|
||||
if hasattr(box, 'box') != 0:
|
||||
# Same set of checks on any child boxes.
|
||||
self._validate_jpx_compatibility(box.box, compatibility_list)
|
||||
|
||||
def _validate_label(self, 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 "
|
||||
msg += "{0} box."
|
||||
msg = msg.format(box.box_id)
|
||||
raise IOError(msg)
|
||||
# Same set of checks on any child boxes.
|
||||
self._validate_label(box.box)
|
||||
|
||||
|
||||
# Setup the default callback handlers. See the callback functions subsection
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue