Timing resolution and consumer logic

EBU-TT part 3 deals with sequences of documents and creates a single timeline/sequence where document begin and end events are kept. Resolving this takes resolving the document timings first. After each document’s internal timing relationships have been worked out the document is inserted into the timeline where possible collisions are detected and resolved with the possible discarding of documents in the process.

Document Timings

The document timing resolution logic is built using the validation framework and mostly is in ebu_tt_live.bindings.validation.TimingValidationMixin

The approach is that the element timing semantics require parent and children derived information to calculate begin and end events on an absolute timeline. For this we hook into the Depth First Search that the validation framework uses to process element validation. It is very important to note that the begin and end times can be calculated either as the algorithm cascades down or on its way up in case information derived from children is required.

digraph shells {
    size="7,8";
    node [fontsize=24, shape = plaintext];
    "Document level" -> L1 -> L2 -> L3 -> L4;

    node [fontsize=20, shape = ellipse];
    { rank = same; "Document level" tt; }
    { rank = same; L1 body; }
    { rank = same; L2 div; }
    { rank = same; L3 p1 p2; }
    { rank = same; L4 span1 span2 span3; }

    /* 'visible' edges */
    tt -> body;
    body -> div;
    div -> {p1 p2};
    p1 -> {span1 span2};
    p2 -> span3;

    /* ’invisible’ edges to adjust node placement */
    edge [style=invis];
    L4 -> span1;
}

The classes involved in the timing resolution are in the diagram below. For every type of element in the XSD there is a python binding class defined. For the sake of simplicity the diagram only contains a fraction of them to provide enough context but omits the parts that are irrelevant to the timing resolution logic.

@startuml

class tt_type {
  #_semantic_before_validation()
  #_semantic_after_validation()
  #_semantic_before_traversal()
  #_semantic_after_traversal()
}
class body_type {
  #_semantic_before_traversal()
  #_semantic_after_traversal()
}
class div_type {
  #_semantic_before_traversal()
  #_semantic_after_traversal()
}
class p_type {
  #_semantic_before_traversal()
  #_semantic_after_traversal()
}
class span_type {
  #_semantic_before_traversal()
  #_semantic_after_traversal()
}

class SemanticValidationMixin {
  #_semantic_before_traversal()
  #_semantic_after_traversal()
}
class SemanticDocumentMixin {
  #_semantic_before_validation()
  #_semantic_after_validation()
  +validateBinding()
}
class TimingValidationMixin {
  #_semantic_preprocess_timing()
  #_semantic_postprocess_timing()
}
class RecursiveOperation {
  #{abstract}_process_element()
  #{abstract}_process_non_element()
  #_before_element()
  #_after_element()
  +proceed()
}
class SemanticValidator {
  #_process_element()
  #_process_non_element()
  #_before_element()
  #_after_element()
}

RecursiveOperation <|-- SemanticValidator
SemanticValidationMixin --> SemanticValidator
SemanticDocumentMixin <|-- SemanticValidationMixin
SemanticDocumentMixin <|-- tt_type
SemanticValidationMixin <|-- body_type
SemanticValidationMixin <|-- div_type
SemanticValidationMixin <|-- p_type
SemanticValidationMixin <|-- span_type
TimingValidationMixin <|-- body_type
TimingValidationMixin <|-- div_type
TimingValidationMixin <|-- p_type
TimingValidationMixin <|-- span_type

@enduml

Classes involved in timing resolution (Simplified)

The functions ebu_tt_live.bindings.validation.base.SemanticDocumentMixin._semantic_before_validation() and ebu_tt_live.bindings.validation.base.SemanticDocumentMixin._semantic_after_validation() are hooks that run before and after the Depth First Search traversal of the content tree. The ebu_tt_live.bindings.validation.base.SemanticValidationMixin._semantic_before_traversal() and ebu_tt_live.bindings.validation.base.SemanticValidationMixin._semantic_after_traversal() are element hooks, which are called before the traversal logic gets to the element and after the traversal logic has finished processing the children of the element in question. Types that subclass these mixins should override these functions to provide their own customized hook behaviour. In the case of timing resolution another mixin, ebu_tt_live.bindings.validation.base.TimingValidationMixin is involved, which encapsulates the functions used to process begin and end attributes. In order for a particular element type to gain timing resolution capability it needs to subclass TimingValidationMixin and SemanticValidationMixin and it must have begin and end attributes capability. Then it should implement _semantic_before_traversal and _semantic_after_traversal functions and call the ebu_tt_live.bindings.validation.timing.TimingValidationMixin._semantic_preprocess_timing in the before traversal and ebu_tt_live.bindings.validation.timing.TimingValidationMixin._semantic_postprocess_timing in the after traversal hook. The following code sample is from the implementation of the div_type class.

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)

In the following sequence diagram we traverse the document structure depicted in the first figure of this page and process the timings.

@startuml

actor User
User --> "root : tt_type" as root: validateBinding()
activate root

create "validator : SemanticValidator" as validator
root -> validator : <<create>>
root -> validator : process()
activate validator

validator -> "body : body_type" as body: _semantic_before_traversal()
activate body

body -> body : _semantic_preprocess_timing()
activate body
deactivate body

deactivate body

validator -> "div : div_type" as div: _semantic_before_traversal()
activate div

div -> div : _semantic_preprocess_timing()
activate div
deactivate div

deactivate div

validator -> "p1 : p_type" as p1 : _semantic_before_traversal()
activate p1

p1 -> p1 : _semantic_preprocess_timing()
activate p1
deactivate p1

deactivate p1

validator -> "span1 : span_type" as span1 : _semantic_before_traversal()
activate span1

span1 -> span1 : _semantic_preprocess_timing()
activate span1
deactivate span1

deactivate span1

validator -> span1 : _semantic_after_traversal()
activate span1

span1 -> span1: _semantic_postprocess_timing()
activate span1
deactivate span1

deactivate span1

validator -> "span2 : span_type" as span2 : _semantic_before_traversal()
activate span2

span2 -> span2 : _semantic_preprocess_timing()
activate span2
deactivate span2

deactivate span2

validator -> span2 : _semantic_after_traversal()
activate span2

span2 -> span2 : _semantic_postprocess_timing()
activate span2
deactivate span2

deactivate span2

validator -> p1 : _semantic_after_traversal()
activate p1

p1 -> p1 : _semantic_postprocess_timing()
activate p1
deactivate p1

deactivate p1

validator -> "p2 : p_type" as p2 : _semantic_before_traversal()
activate p2

p2 -> p2 : _semantic_preprocess_timing()
activate p2
deactivate p2

deactivate p2

validator -> "span3 : span_type" as span3 : _semantic_before_traversal()
activate span3

span3 -> span3 : _semantic_preprocess_timing()
activate span3
deactivate span3

deactivate span3

validator -> span3 : _semantic_after_traversal()
activate span3

span3 -> span3 : _semantic_postprocess_timing()
activate span3
deactivate span3

deactivate span3

validator -> p2 : _semantic_after_traversal()
activate p2

p2 -> p2 : _semantic_postprocess_timing()
activate p2
deactivate p2

deactivate p2

validator -> div : _semantic_after_traversal()
activate div

div -> div : _semantic_postprocess_timing()
activate div
deactivate div

deactivate div

validator -> body : _semantic_after_traversal()
activate body

body -> body : _semantic_postprocess_timing()
activate body
deactivate body

deactivate body

root <- validator
destroy validator
User <- root

deactivate root

@enduml

Timing validation of document tree (head element omitted as it is irrelevant to timing)

Sequence Timings

The sequence timings are handled by the ebu_tt_live.documents.ebutt3.EBUTT3DocumentSequence class. The document is inserted into the sequence after it is validated. The sequence looks at the computed begin and end times and detects collisions. If there are any, the collisions are resolved by the logic starting in ebu_tt_live.documents.ebutt3.EBUTT3DocumentSequence._insert_or_discard()