Source code for ebu_tt_live.bindings

# -*- coding: utf-8 -*-
from .raw import *
from . import raw

# Let's import customizations
from . import _ebuttdt as ebuttdt
from . import _ebuttm as ebuttm
from . import _ebuttlm as ebuttlm
from . import _ebuttp as ebuttp
from . import _ebutts as ebutts
from . import _ttm as ttm
from . import _ttp as ttp
from . import _tts as tts
from .pyxb_utils import xml_parsing_context, get_xml_parsing_context
from .validation.base import SemanticDocumentMixin, SemanticValidationMixin, IDMixin
from ebu_tt_live.bindings.validation.presentation import SizingValidationMixin, StyledElementMixin, RegionedElementMixin
from ebu_tt_live.bindings.validation.timing import TimingValidationMixin, BodyTimingValidationMixin
from ebu_tt_live.bindings.validation.content import SubtitleContentContainer, ContentContainerMixin
from .validation.validator import SemanticValidator
from ebu_tt_live.errors import SemanticValidationError, OutsideSegmentError
from ebu_tt_live.strings import ERR_SEMANTIC_VALIDATION_MISSING_ATTRIBUTES, \
    ERR_SEMANTIC_VALIDATION_INVALID_ATTRIBUTES, ERR_SEMANTIC_STYLE_CIRCLE, ERR_SEMANTIC_STYLE_MISSING, \
    ERR_SEMANTIC_ELEMENT_BY_ID_MISSING, ERR_SEMANTIC_VALIDATION_EXPECTED
from pyxb.exceptions_ import SimpleTypeValueError
from pyxb.utils.domutils import BindingDOMSupport
from pyxb.binding.basis import ElementContent, NonElementContent
from datetime import timedelta
import threading
import copy
import logging


log = logging.getLogger(__name__)

# This mapping controls the namespace aliases used in the generated XML content.
# Not having these results in default mapping to ns1 ns2 ns3..., which should not be a problem
# but many downstream tools may have terrible custom XML parsing typically by using regular expressions
# to find `tt:head` for instance. So controlling these to match the spec namespaces helps interoperability.
namespace_prefix_map = {
    'tt': raw.Namespace,
    'ebuttdt': ebuttdt.Namespace,
    'ttp': ttp.Namespace,
    'tts': tts.Namespace,
    'ttm': ttm.Namespace,
    'ebuttm': ebuttm.Namespace,
    'ebutts': ebutts.Namespace,
    'ebuttp': ebuttp.Namespace,
    'ebuttlm': ebuttlm.Namespace
}


[docs]def CreateFromDocument(*args, **kwargs): """ Resetting the parsing context on start :return: """ with xml_parsing_context(): result = raw.CreateFromDocument(*args, **kwargs) return result
[docs]def CreateFromDOM(*args, **kwargs): """ Resetting the parsing context on start :return: """ with xml_parsing_context(): result = raw.CreateFromDOM(*args, **kwargs) return result
# Customizing validation mixins before application # ================================================
[docs]class style_type(StyledElementMixin, IDMixin, SizingValidationMixin, SemanticValidationMixin, raw.style): # This helps us detecting infinite loops. _styling_lock = None # ordered styles cached _ordered_styles = None # This mapping is meant to simplify things. In case anything needs special calculation that value should be # lifted out to its own function. _simple_attr_defaults = { 'backgroundColor': 'transparent', 'padding': '0px', 'unicodeBidi': 'normal' } _inherited_attr_defaults = { 'color': None, # See: https://www.w3.org/TR/ttaf1-dfxp/#style-attribute-color 'direction': 'ltr', 'fontFamily': 'default', 'fontStyle': 'normal', 'fontWeight': 'normal', 'linePadding': '0c', 'multiRowAlign': 'auto', 'textAlign': 'start', 'textDecoration': 'none', 'wrapOption': 'wrap' } _default_attrs = None def __repr__(self): return u'<style ID: {id} at {addr}>'.format( id=self.id, addr=hex(id(self)) ) def _semantic_copy(self, dataset): copied_style = style_type( id=self.id, style=self.style, # there is no ordering requirement in styling so too soon to deconflict here direction=self.direction, fontFamily=self.fontFamily, fontSize=self.fontSize, lineHeight=self.lineHeight, textAlign=self.textAlign, color=self.color, backgroundColor=self.backgroundColor, fontStyle=self.fontStyle, fontWeight=self.fontWeight, textDecoration=self.textDecoration, unicodeBidi=self.unicodeBidi, wrapOption=self.wrapOption, padding=self.padding, linePadding=self.linePadding, _strict_keywords=False ) return copied_style @property def validated_styles(self): # The style element itself is not meant to implement this. raise NotImplementedError()
[docs] def ordered_styles(self, dataset): """ This function figures out the chain of styles. WARNING: Do not call this before the semantic validation of tt/head/styling is finished. Otherwise your style may not have been found yet! :param dataset: Semantic dataset :return: a list of styles applicable in order """ if self._styling_lock.locked(): raise SemanticValidationError(ERR_SEMANTIC_STYLE_CIRCLE.format( style=self.id )) with self._styling_lock: if self._ordered_styles is not None: return self._ordered_styles ordered_styles = [self] if self.style is not None: for style_id in self.style: try: style_elem = dataset['tt_element'].get_element_by_id(elem_id=style_id, elem_type=style_type) cascading_styles = style_elem.ordered_styles(dataset=dataset) for style_elem in cascading_styles: if style_elem in ordered_styles: continue ordered_styles.append(style_elem) except LookupError: raise SemanticValidationError(ERR_SEMANTIC_STYLE_MISSING.format( style=style_id )) self._ordered_styles = ordered_styles return ordered_styles
[docs] def add(self, other): if self.direction is None and other.direction is not None: self.direction = other.direction if self.fontFamily is None and other.fontFamily is not None: self.fontFamily = other.fontFamily if self.fontSize is None and other.fontSize is not None: self.fontSize = other.fontSize if self.lineHeight is None and other.lineHeight is not None: self.lineHeight = other.lineHeight if self.textAlign is None and other.textAlign is not None: self.textAlign = other.textAlign if self.color is None and other.color is not None: self.color = other.color if self.backgroundColor is None and other.backgroundColor is not None: self.backgroundColor = other.backgroundColor if self.fontStyle is None and other.fontStyle is not None: self.fontStyle = other.fontStyle if self.fontWeight is None and other.fontWeight is not None: self.fontWeight = other.fontWeight if self.textDecoration is None and other.textDecoration is not None: self.textDecoration = other.textDecoration if self.unicodeBidi is None and other.unicodeBidi is not None: self.unicodeBidi = other.unicodeBidi if self.wrapOption is None and other.wrapOption is not None: self.wrapOption = other.wrapOption if self.padding is None and other.padding is not None: self.padding = other.padding if self.linePadding is None and other.linePadding is not None: self.linePadding = other.linePadding if self.multiRowAlign is None and other.multiRowAlign is not None: self.multiRowAlign = other.multiRowAlign return self
[docs] @classmethod def resolve_styles(cls, referenced_styles): """ Resolve the style attributes in inheritance chain :param referenced_styles: :return: """ instance = cls() for item in referenced_styles: instance.add(item) return instance
[docs] @classmethod def compute_font_size(cls, specified_style, parent_computed_style, region_computed_style, dataset, defer=False): spec_font_size = specified_style.fontSize default_font_size = ebuttdt.CellFontSizeType('1c') result_font_size = None if spec_font_size is not None: # Check relativeness if isinstance(spec_font_size, ebuttdt.PercentageFontSizeType): if parent_computed_style is not None and parent_computed_style.fontSize is not None: result_font_size = parent_computed_style.fontSize * spec_font_size elif region_computed_style is not None and region_computed_style.fontSize is not None: result_font_size = region_computed_style.fontSize * spec_font_size else: if region_computed_style is None and defer is True: # This is an edge-case. body or div can have styles attached with fontSize but may still have no # region assigned so if they are percentage based the calculation needs to be deferred. # In this case and in this case only we save percentage in the computed fontSize value result_font_size = spec_font_size else: # This means the default font size needs to be modulated by the percentage result_font_size = default_font_size * spec_font_size if isinstance(result_font_size, ebuttdt.PercentageFontSizeType) and defer is False: # We cannot defer any longer so now it is time to resolve it. result_font_size *= default_font_size else: # TODO: control the type here result_font_size = spec_font_size else: if region_computed_style is not None and region_computed_style.fontSize is not None: result_font_size = region_computed_style.fontSize if parent_computed_style is not None and parent_computed_style.fontSize is not None: if isinstance(parent_computed_style.fontSize, ebuttdt.PercentageFontSizeType): if result_font_size is not None: # There is a region we can proceed result_font_size *= parent_computed_style.fontSize else: result_font_size = parent_computed_style.fontSize else: result_font_size = parent_computed_style.fontSize if defer is False: if isinstance(result_font_size, ebuttdt.PercentageFontSizeType): result_font_size *= default_font_size if result_font_size is not None: if isinstance(result_font_size, ebuttdt.pixelFontSizeType): result_font_size = ebuttdt.CellFontSizeType( *ebuttdt.pixels_to_cells( result_font_size, dataset['tt_element'].extent, dataset['tt_element'].cellResolution ) ) elif defer is not True: result_font_size = default_font_size return result_font_size
@property def default_attrs(self): """ This property function gives back a set in which we find the unspecified style attributes. :return: set for attribute names that were inheriting the default in the computed style. Important at inheritance override """ if self._default_attrs is None: self._default_attrs = set() return self._default_attrs
[docs] def set_default_value(self, attr_name, default_value=None): # We must cater for the case when default computed values would override specified region style values # With fontSize the defaults are vital for computing relative values. At override the next element down the # line would not be able to tell if the parent computed an actually intended value or just the # inheritance of the default value. if default_value is None: if attr_name in self._simple_attr_defaults: default_value = self._simple_attr_defaults[attr_name] elif attr_name in self._inherited_attr_defaults: default_value = self._inherited_attr_defaults[attr_name] else: raise LookupError() # This is the extra step: register default value usage self.default_attrs.add(attr_name) setattr( self, attr_name, default_value )
[docs] @classmethod def compute_inherited_attribute(cls, attr_name, specified_style, parent_computed_style, region_computed_style): fallback_order = [specified_style, parent_computed_style, region_computed_style] for item in fallback_order: if item is not None and attr_name not in item.default_attrs: attr_value = getattr(item, attr_name) if attr_value is not None: return attr_value return None
[docs] @classmethod def compute_simple_attribute(cls, attr_name, specified_style): if specified_style is not None: attr_value = getattr(specified_style, attr_name) if attr_value is not None: return attr_value return None
[docs] @classmethod def compute_line_height(cls, specified_style, parent_computed_style, region_computed_style, dataset, font_size): fallback_order = [specified_style, parent_computed_style, region_computed_style] for item in fallback_order: if item is not None and item.lineHeight is not None and 'lineHeight' not in item.default_attrs: selected_value = item.lineHeight # NOTE: the return value should be cell based except when 'normal' is used if isinstance(selected_value, ebuttdt.PixelLineHeightType): selected_value = ebuttdt.CellLineHeightType( *ebuttdt.pixels_to_cells( selected_value, dataset['tt_element'].extent, dataset['tt_element'].cellResolution ) ) elif isinstance(selected_value, ebuttdt.PercentageLineHeightType) and isinstance(font_size, ebuttdt.cellFontSizeType): # We only need to deal with this case if fontSize was not deferred selected_value *= font_size return selected_value return None
[docs] @classmethod def compute_style(cls, specified_style, parent_computed_style, region_computed_style, dataset, defer_font_size): """ This function holds the styling semantics of containers considering direct reference, inheritance and containment variables :param specified_style: Directly referenced resolved styles :param parent_computed_style: Inherited styling information from parent container :param region_computed_style: Default region styling information :param dataset: Semantic dataset needed for conversion context :return: """ computed = cls() # Here we need to check for multiple things for each style attribute: # 1: If specified # 2: If specified value is relative # 3: If not specified and there is parent style attr # 4: If no parent style attr but there is region style attr # 5: If none of the above assume the default computed.fontSize = cls.compute_font_size( specified_style=specified_style, parent_computed_style=parent_computed_style, region_computed_style=region_computed_style, dataset=dataset, defer=defer_font_size ) computed_line_height = cls.compute_line_height( specified_style=specified_style, parent_computed_style=parent_computed_style, region_computed_style=region_computed_style, dataset=dataset, font_size=computed.fontSize ) if computed_line_height is None: computed.set_default_value('lineHeight', default_value='normal') else: computed.lineHeight = computed_line_height for attr_name in cls._simple_attr_defaults.keys(): comp_attr_value = cls.compute_simple_attribute( attr_name=attr_name, specified_style=specified_style ) if comp_attr_value is None: computed.set_default_value(attr_name) else: setattr( computed, attr_name, comp_attr_value ) for attr_name in cls._inherited_attr_defaults.keys(): comp_attr_value = cls.compute_inherited_attribute( attr_name=attr_name, specified_style=specified_style, parent_computed_style=parent_computed_style, region_computed_style=region_computed_style ) if comp_attr_value is None: computed.set_default_value(attr_name) else: setattr( computed, attr_name, comp_attr_value ) return computed
def _semantic_before_traversal(self, dataset, element_content=None, parent_binding=None): self._semantic_register_id(dataset=dataset) self._semantic_check_sizing_type(self.fontSize, dataset=dataset) self._semantic_check_sizing_type(self.lineHeight, dataset=dataset) # Init recursion loop detection lock self._styling_lock = threading.Lock() self._ordered_styles = None def _semantic_before_copy(self, dataset, element_content=None): if self not in dataset['affected_elements']: raise OutsideSegmentError()
# For the requirements of the StyledElementMixin style_type._compatible_style_type = style_type raw.style._SetSupersedingClass(style_type)
[docs]class LiveStyledElementMixin(StyledElementMixin): _compatible_style_type = style_type
# EBU TT Live element types # ========================= # NOTE: Some of the code below includes handling of SMPTE time base, which was removed from version 1.0 of the specification.
[docs]class tt_type(SemanticDocumentMixin, raw.tt_type): def __post_time_base_set_attribute(self, attr_use): context = get_xml_parsing_context() if context is not None: # This means we are in XML parsing mode context['timeBase'] = self.timeBase _attr_en_post = { (pyxb.namespace.ExpandedName(ttp.Namespace, 'timeBase')).uriTuple(): __post_time_base_set_attribute } _elements_by_id = None _validator_class = SemanticValidator def __copy__(self): copied_tt = tt_type( lang=self.lang, extent=self.extent, timeBase=self.timeBase, frameRate=self.frameRate, frameRateMultiplier=self.frameRateMultiplier, markerMode=self.markerMode, dropMode=self.dropMode, clockMode=self.clockMode, cellResolution=self.cellResolution, sequenceIdentifier=self.sequenceIdentifier, sequenceNumber=self.sequenceNumber, authoringDelay=self.authoringDelay, authorsGroupIdentifier=self.authorsGroupIdentifier, authorsGroupControlToken=self.authorsGroupControlToken, authorsGroupSelectedSequenceIdentifier=self.authorsGroupSelectedSequenceIdentifier, referenceClockIdentifier=self.referenceClockIdentifier, _strict_keywords=False ) return copied_tt
[docs] def merge(self, other, dataset): # TODO: compatibility check, rules of merging TBD # merged_tt = tt_type( # lang=self.lang, # extent=self.extent, # timeBase=self.timeBase, # frameRate=self.frameRate, # frameRateMultiplier=self.frameRateMultiplier, # markerMode=self.markerMode, # dropMode=self.dropMode, # clockMode=self.clockMode, # cellResolution=self.cellResolution, # sequenceIdentifier=self.sequenceIdentifier, # sequenceNumber=self.sequenceNumber, # authoringDelay=self.authoringDelay, # authorsGroupIdentifier=self.authorsGroupIdentifier, # authorsGroupControlToken=self.authorsGroupControlToken, # authorsGroupControlRequest=self.authorsGroupControlRequest, # referenceClockIdentifier=self.referenceClockIdentifier, # _strict_keywords=False # ) return self
@classmethod def __check_bds(cls, bds): if bds: return bds else: return BindingDOMSupport( namespace_prefix_map=namespace_prefix_map )
[docs] def toDOM(self, bds=None, parent=None, element_name=None): return super(tt_type, self).toDOM( bds=self.__check_bds(bds), parent=parent, element_name=element_name )
[docs] def toxml(self, encoding=None, bds=None, root_only=False, element_name=None): dom = self.toDOM(self.__check_bds(bds), element_name=element_name) if root_only: dom = dom.documentElement return dom.toprettyxml( encoding=encoding, indent=' ' )
def _semantic_after_subtree_copy(self, copied_instance, dataset, element_content=None): # This one does not have another parent to link with but it can make itself an element copied_instance._setElement(raw.tt) def __semantic_test_smpte_attrs_present(self): smpte_attrs = [ 'frameRate', # 'frameRateMultiplier', 'dropMode', 'markerMode' ] missing_attrs = self._semantic_attributes_missing(smpte_attrs) if missing_attrs: raise SemanticValidationError( ERR_SEMANTIC_VALIDATION_MISSING_ATTRIBUTES.format( elem_name='tt:tt', attr_names=missing_attrs ) ) def __semantic_test_smpte_attrs_absent(self): smpte_attrs = [ 'dropMode', 'markerMode' ] extra_attrs = self._semantic_attributes_present(smpte_attrs) if extra_attrs: raise SemanticValidationError( ERR_SEMANTIC_VALIDATION_INVALID_ATTRIBUTES.format( elem_name='tt:tt', attr_names=extra_attrs ) ) def __semantic_test_time_base_clock_attrs_present(self): clock_attrs = [ 'clockMode' ] missing_attrs = self._semantic_attributes_missing(clock_attrs) if missing_attrs: raise SemanticValidationError( ERR_SEMANTIC_VALIDATION_MISSING_ATTRIBUTES.format( elem_name='tt:tt', attr_names=missing_attrs ) ) def __semantic_test_time_base_clock_attrs_absent(self): clock_attrs = [ 'clockMode' ] extra_attrs = self._semantic_attributes_present(clock_attrs) if extra_attrs: raise SemanticValidationError( ERR_SEMANTIC_VALIDATION_MISSING_ATTRIBUTES.format( elem_name='tt:tt', attr_names=extra_attrs ) ) def __semantic_test_smpte_attr_combinations(self): # TODO: SMPTE validation(low priority) #52 pass def _semantic_before_validation(self): """ Here before anything semantic happens I check some SYNTACTIC errors. :raises ComplexTypeValidationError, SimpleTypeValueError """ # The following edge case is ruined by the XSD associating the same extent type to this extent element. if self.extent is not None and not isinstance(self.extent, ebuttdt.pixelExtentType): raise SimpleTypeValueError(type(self.extent), self.extent) # This little gem is correcting a bug in PyXB and defult attribute values being instantiated to the old type # and not the customized one # e.g: instead of ebuttdt.CellResolutionType it creates raw._ebuttdt.cellResolutionType, which is a bug # NOTE: As a side effect however this monkey patch will cause cellResolution to be defined on all generated # documents' tt element. if isinstance(self.cellResolution, ebuttdt.cellResolutionType): self.cellResolution = ebuttdt.CellResolutionType(self.cellResolution) def _semantic_before_traversal(self, dataset, element_content=None, parent_binding=None): # The tt element adds itself to the semantic dataset to help classes lower down the line to locate constraining # attributes. dataset['timing_begin_stack'] = [] dataset['timing_end_stack'] = [] dataset['timing_syncbase'] = timedelta() dataset['tt_element'] = self dataset['styles_stack'] = [] self._elements_by_id = {} dataset['elements_by_id'] = self._elements_by_id if self.timeBase == 'smpte': self.__semantic_test_smpte_attrs_present() else: self.__semantic_test_smpte_attrs_absent() if self.timeBase == 'clock': self.__semantic_test_time_base_clock_attrs_present() else: self.__semantic_test_time_base_clock_attrs_absent() def _semantic_after_traversal(self, dataset, element_content=None, parent_binding=None): # Save this for id lookup. self._elements_by_id = dataset['elements_by_id']
[docs] def get_element_by_id(self, elem_id, elem_type=None): """ Lookup an element and return it. Optionally type is checked as well. :param elem_id: :param elem_type: :return: """ if self._elements_by_id is None: raise SemanticValidationError(ERR_SEMANTIC_VALIDATION_EXPECTED) element = self._elements_by_id.get(elem_id, None) if element is None or elem_type is not None and not isinstance(element, elem_type): raise LookupError(ERR_SEMANTIC_ELEMENT_BY_ID_MISSING.format(id=elem_id)) return element
[docs] def get_timing_type(self, timedelta_in): if self.timeBase == 'clock': return ebuttdt.LimitedClockTimingType(timedelta_in) if self.timeBase == 'media': return ebuttdt.FullClockTimingType(timedelta_in) if self.timeBase == 'smpte': return ebuttdt.SMPTETimingType(timedelta_in)
raw.tt_type._SetSupersedingClass(tt_type) # Head classes # ============
[docs]class head_type(SemanticValidationMixin, raw.head_type): def __copy__(self): copied_head = head_type() return copied_head
[docs] def merge(self, other_elem, dataset): return self
raw.head_type._SetSupersedingClass(head_type) # Body classes # ============
[docs]class p_type(RegionedElementMixin, LiveStyledElementMixin, SubtitleContentContainer, raw.p_type): _attr_en_pre = { (pyxb.namespace.ExpandedName(None, 'begin')).uriTuple(): TimingValidationMixin._pre_timing_set_attribute, (pyxb.namespace.ExpandedName(None, 'end')).uriTuple(): TimingValidationMixin._pre_timing_set_attribute } def _semantic_copy(self, dataset): copied_p = p_type( id=self.id, space=self.space, lang=self.lang, region=self._semantic_deconflicted_ids(attr_name='region', dataset=dataset), style=self._semantic_deconflicted_ids(attr_name='style', dataset=dataset), begin=self.begin, end=self.end, agent=self.agent, role=self.role, _strict_keywords=False ) return copied_p def __copy__(self): copied_p = p_type( id=self.id, space=self.space, lang=self.lang, region=self.region, style=self.style, begin=self.begin, end=self.end, agent=self.agent, role=self.role, _strict_keywords=False ) return copied_p def _semantic_before_traversal(self, dataset, element_content=None, parent_binding=None): self._semantic_register_id(dataset=dataset) self._semantic_timebase_validation(dataset=dataset, element_content=element_content) self._semantic_preprocess_timing(dataset=dataset, element_content=element_content) self._semantic_set_region(dataset=dataset, region_type=region_type) self._semantic_collect_applicable_styles(dataset=dataset, style_type=style_type, parent_binding=parent_binding) self._semantic_push_styles(dataset=dataset) def _semantic_after_traversal(self, dataset, element_content=None, parent_binding=None): self._semantic_postprocess_timing(dataset=dataset, element_content=element_content) self._semantic_manage_timeline(dataset=dataset, element_content=element_content) self._semantic_unset_region(dataset=dataset) self._semantic_pop_styles(dataset=dataset) def _semantic_before_copy(self, dataset, element_content=None): self._assert_in_segment(dataset=dataset, element_content=element_content)
[docs] def is_timed_leaf(self): if len(self.span): return False else: return True
def _semantic_after_subtree_copy(self, copied_instance, dataset, element_content=None): copied_instance._assert_empty_container() self._semantic_copy_apply_leaf_timing( copied_instance=copied_instance, dataset=dataset, element_content=element_content) self._semantic_copy_verify_referenced_styles(dataset=dataset) self._semantic_copy_verify_referenced_region(dataset=dataset)
raw.p_type._SetSupersedingClass(p_type)
[docs]class span_type(LiveStyledElementMixin, SubtitleContentContainer, raw.span_type): _attr_en_pre = { (pyxb.namespace.ExpandedName(None, 'begin')).uriTuple(): TimingValidationMixin._pre_timing_set_attribute, (pyxb.namespace.ExpandedName(None, 'end')).uriTuple(): TimingValidationMixin._pre_timing_set_attribute } def _semantic_copy(self, dataset): copied_span = span_type( id=self.id, style=self._semantic_deconflicted_ids(attr_name='style', dataset=dataset), begin=self.begin, end=self.end, space=self.space, lang=self.lang, agent=self.agent, role=self.role, _strict_keywords=False ) return copied_span def __copy__(self): copied_span = span_type( id=self.id, style=self.style, begin=self.begin, end=self.end, space=self.space, lang=self.lang, agent=self.agent, role=self.role, _strict_keywords=False ) return copied_span def _semantic_before_traversal(self, dataset, element_content=None, parent_binding=None): self._semantic_register_id(dataset=dataset) self._semantic_timebase_validation(dataset=dataset, element_content=element_content) self._semantic_preprocess_timing(dataset=dataset, element_content=element_content) self._semantic_collect_applicable_styles(dataset=dataset, style_type=style_type, parent_binding=parent_binding) self._semantic_push_styles(dataset=dataset) def _semantic_after_traversal(self, dataset, element_content=None, parent_binding=None): self._semantic_postprocess_timing(dataset=dataset, element_content=element_content) self._semantic_manage_timeline(dataset=dataset, element_content=element_content) self._semantic_pop_styles(dataset=dataset) def _semantic_before_copy(self, dataset, element_content=None): self._assert_in_segment(dataset=dataset, element_content=element_content)
[docs] def is_timed_leaf(self): if len(self.span): return False else: return True
def _semantic_after_subtree_copy(self, copied_instance, dataset, element_content=None): copied_instance._assert_empty_container() self._semantic_copy_apply_leaf_timing( copied_instance=copied_instance, dataset=dataset, element_content=element_content) self._semantic_copy_verify_referenced_styles(dataset=dataset)
raw.span_type._SetSupersedingClass(span_type)
[docs]class br_type(SemanticValidationMixin, raw.br_type): def __copy__(self): return br_type()
[docs] def content_to_string(self, begin=None, end=None): return '<br />'
raw.br_type._SetSupersedingClass(br_type)
[docs]class div_type(ContentContainerMixin, IDMixin, RegionedElementMixin, LiveStyledElementMixin, TimingValidationMixin, SemanticValidationMixin, raw.div_type): _attr_en_pre = { (pyxb.namespace.ExpandedName(None, 'begin')).uriTuple(): TimingValidationMixin._pre_timing_set_attribute, (pyxb.namespace.ExpandedName(None, 'end')).uriTuple(): TimingValidationMixin._pre_timing_set_attribute } def _semantic_copy(self, dataset): copied_div = div_type( id=self.id, region=self._semantic_deconflicted_ids(attr_name='region', dataset=dataset), style=self._semantic_deconflicted_ids(attr_name='style', dataset=dataset), agent=self.agent, role=self.role, begin=self.begin, end=self.end, _strict_keywords=False ) return copied_div def __copy__(self): copied_div = div_type( id=self.id, region=self.region, style=self.style, agent=self.agent, role=self.role, begin=self.begin, end=self.end, _strict_keywords=False ) return copied_div def _semantic_before_traversal(self, dataset, element_content=None, parent_binding=None): self._semantic_register_id(dataset=dataset) self._semantic_timebase_validation(dataset=dataset, element_content=element_content) self._semantic_preprocess_timing(dataset=dataset, element_content=element_content) self._semantic_set_region(dataset=dataset, region_type=region_type) self._semantic_collect_applicable_styles( dataset=dataset, style_type=style_type, parent_binding=parent_binding, defer_font_size=True ) self._semantic_push_styles(dataset=dataset) def _semantic_after_traversal(self, dataset, element_content=None, parent_binding=None): self._semantic_postprocess_timing(dataset=dataset, element_content=element_content) self._semantic_unset_region(dataset=dataset) def _semantic_before_copy(self, dataset, element_content=None): self._assert_in_segment(dataset=dataset, element_content=element_content)
[docs] def is_empty(self): if len(self.div): return False if len(self.p): return False return True
def _semantic_after_subtree_copy(self, copied_instance, dataset, element_content=None): copied_instance._assert_empty_container() self._semantic_copy_apply_leaf_timing( copied_instance=copied_instance, dataset=dataset, element_content=element_content) self._semantic_copy_verify_referenced_styles(dataset=dataset) self._semantic_copy_verify_referenced_region(dataset=dataset)
raw.div_type._SetSupersedingClass(div_type)
[docs]class body_type(LiveStyledElementMixin, BodyTimingValidationMixin, SemanticValidationMixin, raw.body_type): _attr_en_pre = { (pyxb.namespace.ExpandedName(None, 'begin')).uriTuple(): BodyTimingValidationMixin._pre_timing_set_attribute, (pyxb.namespace.ExpandedName(None, 'dur')).uriTuple(): BodyTimingValidationMixin._pre_timing_set_attribute, (pyxb.namespace.ExpandedName(None, 'end')).uriTuple(): BodyTimingValidationMixin._pre_timing_set_attribute } def _semantic_copy(self, dataset): copied_body = body_type( agent = self.agent, role = self.role, begin=self.begin, dur=self.dur, end=self.end, style=self._semantic_deconflicted_ids(attr_name='style', dataset=dataset), _strict_keywords=False ) return copied_body def __copy__(self): copied_body = body_type( agent=self.agent, role=self.role, begin=self.begin, dur=self.dur, end=self.end, style=self.style, _strict_keywords=False ) return copied_body @classmethod def _merge_deconflict_ids(cls, element, dest, ids): """ Deconflict ids of body elements :param element: :return: """ children = element.orderedContent() output = [] for item in children: log.debug('processing child: {} of {}'.format(item.value, element)) if isinstance(item, NonElementContent): copied_stuff = copy.copy(item.value) output.append(copied_stuff) elif isinstance(item, ElementContent): copied_elem = copy.copy(item.value) copied_elem._resetContent() cls._merge_deconflict_ids(item.value, copied_elem, ids) if isinstance(copied_elem, IDMixin): if copied_elem.id is not None and copied_elem.id in ids: next_try = copied_elem.id while next_try in ids: next_try = '{}.1'.format(next_try) copied_elem.id = next_try ids.add(copied_elem.id) output.append(copied_elem) for item in output: dest.append(item) return dest
[docs] def merge(self, other_elem, dataset=None): # TODO: Sort out timing and styling merging rules merged_body = copy.copy(self) merged_body.begin = None merged_body.dur = None merged_body.end = None # The same recursive ID collision issue... DAMN! ids = dataset['ids'] self._merge_deconflict_ids(element=self, dest=merged_body, ids=ids) self._merge_deconflict_ids(element=other_elem, dest=merged_body, ids=ids) return merged_body
def _semantic_before_traversal(self, dataset, element_content=None, parent_binding=None): self._semantic_timebase_validation(dataset=dataset, element_content=element_content) self._semantic_preprocess_timing(dataset=dataset, element_content=element_content) self._semantic_collect_applicable_styles( dataset=dataset, style_type=style_type, parent_binding=parent_binding, defer_font_size=True ) self._semantic_push_styles(dataset=dataset) def _semantic_after_traversal(self, dataset, element_content=None, parent_binding=None): self._semantic_postprocess_timing(dataset=dataset, element_content=element_content) self._semantic_pop_styles(dataset=dataset) def _semantic_before_copy(self, dataset, element_content=None): self._assert_in_segment(dataset=dataset, element_content=element_content) def _semantic_after_subtree_copy(self, copied_instance, dataset, element_content=None): self._semantic_copy_apply_leaf_timing( copied_instance=copied_instance, dataset=dataset, element_content=element_content) self._semantic_copy_verify_referenced_styles(dataset=dataset)
raw.body_type._SetSupersedingClass(body_type)
[docs]class styling(SemanticValidationMixin, raw.styling): def __copy__(self): copied_styling = styling() return copied_styling
[docs] def merge(self, other_elem, dataset): style_ids = dataset['ids'] for item in self.orderedContent(): style_ids.add(item.value.id) if other_elem: for item in other_elem.orderedContent(): copied_style = copy.copy(item.value) if item.value.id in style_ids: copied_style.id = '{}.1'.format(copied_style.id) self.append(copied_style) return self
def _semantic_after_subtree_copy(self, copied_instance, dataset, element_content=None): # The styles are not ordered by inheritance so they need an extra step here # to get their style ID resolutions sorted for style_elem in \ [ item.value for item in self.orderedContent() if isinstance(item, ElementContent) and isinstance(item.value, style_type) ]: copied_style_elem = dataset['instance_mapping'].get(style_elem) # The style may not have been copied at all because it isn't used in the requested segment if copied_style_elem is not None: style_elem_styles = style_elem._semantic_deconflicted_ids(attr_name='style', dataset=dataset) if style_elem_styles: copied_style_elem.style = style_elem_styles
raw.styling._SetSupersedingClass(styling)
[docs]class region_type(IDMixin, LiveStyledElementMixin, SizingValidationMixin, SemanticValidationMixin, raw.region): def _semantic_copy(self, dataset): copied_region = region_type( id=self.id, origin=self.origin, extent=self.extent, style=self._semantic_deconflicted_ids(attr_name='style', dataset=dataset), displayAlign=self.displayAlign, padding=self.padding, writingMode=self.writingMode, showBackground=self.showBackground, overflow=self.overflow, _strict_keywords=False ) return copied_region def _semantic_before_traversal(self, dataset, element_content=None, parent_binding=None): self._semantic_register_id(dataset=dataset) self._semantic_check_sizing_type(self.origin, dataset=dataset) self._semantic_check_sizing_type(self.extent, dataset=dataset) self._semantic_collect_applicable_styles( dataset=dataset, style_type=self._compatible_style_type, parent_binding=parent_binding, extra_referenced_styles=[ self._compatible_style_type( padding=self.padding, _strict_keywords=False ) ] ) def _semantic_before_copy(self, dataset, element_content=None): if self not in dataset['affected_elements']: raise OutsideSegmentError()
raw.region._SetSupersedingClass(region_type)
[docs]class layout(SemanticValidationMixin, raw.layout): def __copy__(self): copied_layout = layout() return copied_layout
[docs] def merge(self, other_elem, dataset): region_ids = dataset['ids'] for item in self.orderedContent(): region_ids.add(item.value.id) if other_elem: for item in other_elem.orderedContent(): copied_region = copy.copy(item.value) if copied_region.id in region_ids: copied_region.id = '{}.1'.format(copied_region.id) region_ids.add(copied_region.id) self.append(copied_region) return self
raw.layout._SetSupersedingClass(layout) # EBU TT D classes # ================
[docs]class d_tt_type(raw.d_tt_type): @classmethod def __check_bds(cls, bds): if bds: return bds else: return BindingDOMSupport( namespace_prefix_map=namespace_prefix_map )
[docs] def toDOM(self, bds=None, parent=None, element_name=None): bds = self.__check_bds(bds) xml_dom = super(d_tt_type, self).toDOM( bds=bds, parent=parent, element_name=element_name ) # Nasty workaround for the namespace collision EBU-TT-D and EBU-TT Live are causing by both defining the same # tt element in the ttml namespace if bds.defaultNamespace() != Namespace: xml_dom.documentElement.tagName = 'tt:tt' else: xml_dom.documentElement.tagName = 'tt' return xml_dom
[docs] def toxml(self, encoding=None, bds=None, root_only=False, element_name=None): dom = self.toDOM(self.__check_bds(bds), element_name=element_name) if root_only: dom = dom.documentElement return dom.toprettyxml( encoding=encoding, indent=' ' )
def _validateBinding_vx(self): if self.timeBase != 'media': raise SimpleTypeValueError(type(self.timeBase), self.timeBase) super(d_tt_type, self)._validateBinding_vx()
raw.d_tt_type._SetSupersedingClass(d_tt_type)
[docs]class d_layout_type(raw.d_layout_type):
[docs] @classmethod def create_default_value(cls): instance = cls( d_region_type.create_default_value() ) return instance
raw.d_layout_type._SetSupersedingClass(d_layout_type)
[docs]class d_region_type(raw.d_region_type):
[docs] @classmethod def create_default_value(cls): instance = cls( id='region.default', origin='0% 0%', extent='100% 100%' ) return instance
raw.d_region_type._SetSupersedingClass(d_region_type)
[docs]class d_styling_type(raw.d_styling_type):
[docs] @classmethod def create_default_value(cls): instance = cls( d_style_type.create_default_value() ) return instance
raw.d_styling_type._SetSupersedingClass(d_styling_type)
[docs]class d_style_type(raw.d_style_type):
[docs] @classmethod def create_default_value(cls): instance = cls( id='style.default' ) return instance
raw.d_style_type._SetSupersedingClass(d_style_type)