Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ebuttd/Unnesting divs, spans #38

Merged
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
9cbc921
Add test and implementation for p elements in div
mm326 Aug 20, 2019
c508fee
adding step defenitions
mm326 Aug 27, 2019
6e84e96
adding property for child elements to see parent div
mm326 Aug 28, 2019
1815de9
MSPA-458 WIP Making converter and tests
PhoebeWheelerCode Aug 28, 2019
c7138dc
MSPA-458-nested-styles WIP adding tests for denester
mm326 Aug 28, 2019
f35238f
Add test and impl to unnest divs and add test files
mm326 Aug 29, 2019
8f88955
Add test files for many divs
mm326 Aug 29, 2019
b298497
adding denester
mm326 Sep 3, 2019
3d7a976
MSPA-458 Adding code and tests for scenarios 3 and 4
PhoebeWheelerCode Sep 4, 2019
f12012a
MSPA-458 WIP Span tests and code
PhoebeWheelerCode Sep 5, 2019
5764173
MSPA-458 WIP Span tests and code
PhoebeWheelerCode Sep 6, 2019
a0da9bd
Unnest spans and add them to p element
mm326 Sep 9, 2019
9ca1f14
Adding None to style if merged styles is empty list
mm326 Sep 10, 2019
68d460e
merging begin and end times onto child divs
mm326 Sep 10, 2019
f87fbb9
adding tests and implementation for end time on unnested divs
mm326 Sep 11, 2019
5c1ff7b
Computing fontsize after denesting spans
mm326 Sep 13, 2019
a8d5b24
Renamed test_nested_elements to test_denester and moved it to the tes…
mm326 Sep 13, 2019
dfe5b8b
Adding new tests for new features (such as not creating functionally …
PhoebeWheelerCode Sep 30, 2019
ee8215f
Correcting some hex code mistakes and making capitalisation consistent
PhoebeWheelerCode Sep 30, 2019
fa0c29f
Remove unnecessary '.0' from some font size values
PhoebeWheelerCode Oct 1, 2019
09f2591
More tests for absolute fontsizes; px, c etc
PhoebeWheelerCode Oct 1, 2019
61c5469
Cause divs and spans to correctly inherit and calculate begin/end times
PhoebeWheelerCode Oct 4, 2019
9ad30e5
Adding tests for the timings of nested spans
PhoebeWheelerCode Oct 7, 2019
baf42b2
Splitting resolved timing on elements tests into two sets of tests (u…
PhoebeWheelerCode Oct 7, 2019
15c856c
Fix a bug where fractions of seconds were being interpreted as number…
nigelmegitt Oct 8, 2019
29a42b5
Test that as_timedelta works properly
nigelmegitt Oct 9, 2019
31c44d4
Address some but not all complex timing scenarios
nigelmegitt Oct 9, 2019
913c72b
Stuff that should have been in the previous commit
nigelmegitt Oct 9, 2019
c506b36
Cherry-pick travis build commits from the tt1-to-tt3-conversion branch
nigelmegitt Oct 9, 2019
a99c9be
Slightly dubious test change to allow div begin to float to match chi…
nigelmegitt Oct 9, 2019
e1707a8
Changed requested on the pull request
PhoebeWheelerCode Oct 15, 2019
8e6cfc9
Made adjustments to tests to allow greater reusability of pytest steps
PhoebeWheelerCode Oct 21, 2019
99f19ae
Splitting denesting and live-to-d conversion into separate test steps
PhoebeWheelerCode Oct 21, 2019
536db96
Removing unnecessary code
PhoebeWheelerCode Oct 21, 2019
0409b46
Remove code for handling facets/metadata
PhoebeWheelerCode Nov 4, 2019
3d05970
Refactoring Denester to make it into a node
PhoebeWheelerCode Nov 5, 2019
6a51fa7
Reindented functions within DenesterNode, modified references to acco…
PhoebeWheelerCode Nov 5, 2019
3aa8acf
Added additional comments to denester.py
PhoebeWheelerCode Nov 6, 2019
536633d
Include denesterin code documentation
nigelmegitt Nov 6, 2019
f637681
Minor tidying
nigelmegitt Nov 6, 2019
1a6cbd7
Add Denester node documentation
nigelmegitt Nov 6, 2019
287422e
Documentation changes
PhoebeWheelerCode Nov 8, 2019
a224a8a
Correcting documentation typo
PhoebeWheelerCode Nov 8, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 8 additions & 32 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,44 +1,20 @@
language: python

python:
- '2.7'
- '3.7'

branches:
except:
- gh-pages

before_install:
- echo "Checking for java"
- java -version
- openssl aes-256-cbc -K $encrypted_f18bb5b3c3d4_key -iv $encrypted_f18bb5b3c3d4_iv -in publish-key.enc -out ~/.ssh/publish-key -d
- chmod u=rw,og= ~/.ssh/publish-key
- echo "Host github.com" >> ~/.ssh/config
- echo " IdentityFile ~/.ssh/publish-key" >> ~/.ssh/config
- sudo apt-get -qq update
- sudo apt-get install graphviz
- git --version
- git remote set-url origin git@github.com:ebu/ebu-tt-live-toolkit.git
- git fetch origin -f gh-pages:gh-pages

install:
- pip install pip==9.0.1
- make
- python setup.py develop
- pip install coveralls
- pip install ghp-import
- pip install -U pip
- make init

script:
- pyxbgen --binding-root=./ebu_tt_live/bindings -m __init__ --schema-root=./ebu_tt_live/xsd/ -r -u ebutt_all.xsd
- python setup.py test
- export PR=https://api.github.com/repos/$TRAVIS_REPO_SLUG/pulls/$TRAVIS_PULL_REQUEST
- export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo `curl -s $PR | jq -r .head.ref`; fi)
- echo "TRAVIS_BRANCH=$TRAVIS_BRANCH, PR=$PR, BRANCH=$BRANCH"

after_success:
- coveralls
- make test
- echo "Compiling documentation"
- make
- sudo apt-get -qq update
- sudo apt-get install graphviz
- python setup.py build_sphinx
- if [ $BRANCH == "master" ]; then ghp-import -n -p -m "Update gh-pages." docs/build; fi

notifications:
slack:
secure: RLs+ufYg4HcZfaTl73tTKKVja8DJ9LGk1WO+7B1qa4Fgpv0c/3uXcM+6tvDXllTlqcfLHgum0vbzvE4yapo0em0g/m75t1pEfkh5Pfrcas8IKAM9jg/xycWdYoAkKLRvNOcS4amO29FlzOHFaSSjX+V8JK8Mpm+QM2eGnqIbJokaXgBf/bQxHqGSRvYCeSisEG0f79Mn/0v5OOlbvmetFfvcQVNZDtgpA1CDbvK99F/vQT3qZ04Rh/vRnlcZyH9PEp/TEEJ0yX0NhHYEDnuBKPc86Ack/YCQpT12Ej2xTWjgoUrqv1pcr+h0ltYPXnnCyETrlbeLgNIMUsGW579MThqXekBZ7byRK2DMfjyL5+UgPSmTOIzEg7Qbc1u0r1bUuEi7nsJ5Vf65QeIAcewVASOUKmda9ag0MidTR/VoAsMtf5MzbDifSa1aBHRxZJToshNJVY9V8/lJLdwsbqsG2H+8v8ZuzyRLNiaFhNQTZQWpRXMipvbTDnnVxwpeFiLAqAwEKO9KIyGijqv2yeVvi4WtYvwzM7/1y/ycu7kTDFemKKuKmW2rzzBKJ30vo79ufMQCmn0riKkpaNxBf8R3EUEr86A9yuFpo6/Qc47i3PkeD7MszDqhEAfxl1+sQssWXOdVy6ALFb7RZ6EczpPvAjYLLO0aPa8OPaZjy3aJr+8=
2 changes: 2 additions & 0 deletions docs/source/configurator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ Node type dependent options for [nodeN] : ::

type="distributor" : No options

type="denester" : No options
nigelmegitt marked this conversation as resolved.
Show resolved Hide resolved

Output carriage type dependent options for "carriage": ::

type="direct"
Expand Down
33 changes: 33 additions & 0 deletions docs/source/denesting.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Denesting of EBU-TT-Live documents
======================================

DenesterNode should be used when a EBU-TT-3 document has a div that contains
other divs, or a span contains another span. These elements are not
nigelmegitt marked this conversation as resolved.
Show resolved Hide resolved
able to be nested inside each other in EBU-TT-D documents.

When documents are Denested, any nested elements must be removed from
their parent elements, while retaining attributes they would have inherited.
To address this, the DenesterNode node processes the
document(s) with the
:py:func:`ebu_tt_live.node.denester.DenesterNode.recurse` and
:py:func:`ebu_tt_live.node.denester.DenesterNode.recurse_span`
functions. These will iterate through the file to locate the deepest
nested element, and create a new copy of it with its content and
expected inherited attributes. The end result is a file containing
these newly created divs/spans in place of the nested ones.

Once the new divs and spans are created, divs that are sequential in
the list of divs and have the same attributes are combined into a single
div. This is done by the
:py:func:`ebu_tt_live.node.denester.DenesterNode.combine_divs`
function to reduce the number of divs in the resulting file.

In the combined divs, every p element should have the same region
as its parent div, or be removed. The
nigelmegitt marked this conversation as resolved.
Show resolved Hide resolved
:py:func:`ebu_tt_live.node.denester.DenesterNode.check_p_regions`
function iterates through the divs that have an assigned region and
removes any p where its region does not match.
It then removes the region attribute from any remaining p, as it will
inhert the region of its parent div and the attribute is unnecessary.
It will also remove any now-empty divs that exist as a result of having
their p elements removed.
8 changes: 8 additions & 0 deletions docs/source/ebu_tt_live.node.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,11 @@ node Package
:members:
:undoc-members:
:show-inheritance:

:mod:`denester` Module
----------------------

.. automodule:: ebu_tt_live.node.denester
:members:
:undoc-members:
:show-inheritance:
5 changes: 5 additions & 0 deletions docs/source/inode.puml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ class BufferDelayNode {
+process_document()
}

class DenesterNode{
nigelmegitt marked this conversation as resolved.
Show resolved Hide resolved
..methods..
+process_document()
}

class RetimingDelayNode{
..methods..
+process_document()
Expand Down
1 change: 1 addition & 0 deletions docs/source/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ The components mimic the nodes and carriage mechanisms defined in the specificat
timing_resolution
segmentation
deduplication
denesting
conversion
11 changes: 11 additions & 0 deletions docs/source/scripts_and_their_functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,17 @@ attributes are duplicated.
For the default configuration of the node, see:
``ebu-run --admin.conf=ebu_tt_live/examples/config/deduplicator_fs.json``

Denester Node
-------------
This node flattens nested ``div`` and ``span`` elements such that no
``div`` ends up containing a ``div`` and no ``span`` ends up containing
a ``span``. It also removes any ``p`` elements that specify a ``region``
attribute that differs from a specified region on an ancester element.

If nested ``div`` or ``p`` elements might be present in a document, the
nigelmegitt marked this conversation as resolved.
Show resolved Hide resolved
Denester node should be used to flatten them before passing them to the
EBU-TT-D Encoder, because EBU-TT-D does not permit such nested elements.

Retiming Delay Node
-------------------
This script modifies the times within each Document and issues them without
Expand Down
24 changes: 24 additions & 0 deletions ebu_tt_live/bindings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ class style_type(StyledElementMixin, IDMixin, SizingValidationMixin, SemanticVal
}
_default_attrs = None

def check_equal(self, other):
return (self.backgroundColor == other.backgroundColor and
self.padding == other.padding and
self.unicodeBidi == other.unicodeBidi and
self.color == other.color and
self.direction == other.direction and
self.fontFamily == other.fontFamily and
self.fontStyle == other.fontStyle and
self.fontWeight == other.fontWeight and
self.linePadding == other.linePadding and
self.multiRowAlign == other.multiRowAlign and
self.textAlign == other.textAlign and
self.textDecoration == other.textDecoration and
self.fontSize == other.fontSize and
self.lineHeight == other.lineHeight and
self.wrapOption == other.wrapOption)


def __repr__(self):
return '<style ID: {id} at {addr}>'.format(
id=self.id,
Expand Down Expand Up @@ -620,6 +638,7 @@ def _semantic_before_traversal(self, dataset, element_content=None, parent_bindi
# attributes.
dataset['timing_begin_stack'] = []
dataset['timing_end_stack'] = []
dataset['div_stack'] = []
dataset['timing_syncbase'] = timedelta()
dataset['tt_element'] = self
dataset['styles_stack'] = []
Expand Down Expand Up @@ -886,6 +905,9 @@ def __copy__(self):
)
return copied_div

def merge(self, elem):
return self

def _semantic_before_traversal(self, dataset, element_content=None, parent_binding=None):
self._semantic_register_id(dataset=dataset)
self._semantic_timebase_validation(
Expand Down Expand Up @@ -922,6 +944,8 @@ def _semantic_after_subtree_copy(self, copied_instance, dataset, element_content
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)
Expand Down
17 changes: 13 additions & 4 deletions ebu_tt_live/bindings/_ebuttdt.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,12 @@ def as_timedelta(cls, instance):
:param instance:
:return:
"""
hours, minutes, seconds, milliseconds = [cls._int_or_none(x) for x in cls._groups_regex.match(instance).groups()]
return timedelta(hours=hours, minutes=minutes, seconds=seconds, milliseconds=milliseconds)
hours_str, minutes_str, seconds_str, seconds_fraction_str = [x for x in cls._groups_regex.match(instance).groups()]
milliseconds = seconds_fraction_str and cls._int_or_none('{:0<3}'.format(seconds_fraction_str)[:3]) or 0
return timedelta(hours=cls._int_or_none(hours_str),
minutes=cls._int_or_none(minutes_str),
seconds=cls._int_or_none(seconds_str),
milliseconds=milliseconds)

@classmethod
def from_timedelta(cls, instance):
Expand Down Expand Up @@ -374,8 +378,13 @@ def as_timedelta(cls, instance):
:param instance:
:return:
"""
hours, minutes, seconds, milliseconds = [cls._int_or_none(x) for x in cls._groups_regex.match(instance).groups()]
return timedelta(hours=hours, minutes=minutes, seconds=seconds, milliseconds=milliseconds)
hours_str, minutes_str, seconds_str, seconds_fraction_str = [x for x in cls._groups_regex.match(instance).groups()]
milliseconds = seconds_fraction_str and cls._int_or_none('{:0<3}'.format(seconds_fraction_str)[:3]) or 0
return timedelta(hours=cls._int_or_none(hours_str),
minutes=cls._int_or_none(minutes_str),
seconds=cls._int_or_none(seconds_str),
milliseconds=milliseconds)


@classmethod
def from_timedelta(cls, instance):
Expand Down
2 changes: 2 additions & 0 deletions ebu_tt_live/bindings/converters/ebutt3_ebuttd.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ def convert_body(self, body_in, dataset):
return new_elem

def convert_div(self, div_in, dataset):
if len(div_in.orderedContent()) == 0:
return None
new_elem = d_div_type(
*self.convert_children(div_in, dataset),
id=div_in.id,
Expand Down
72 changes: 72 additions & 0 deletions ebu_tt_live/bindings/test/test_times.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

from unittest import TestCase
from unittest import skip, SkipTest
from datetime import timedelta
from ebu_tt_live.bindings import ebuttdt
from pyxb import SimpleTypeValueError, SimpleFacetValueError

class TestTimecountTimingType(TestCase):

_test_cases = {
'1.3s': timedelta(seconds = 1, milliseconds = 300),
'1.03s': timedelta(seconds = 1, milliseconds = 30),
'1.30s': timedelta(seconds = 1, milliseconds = 300),
'1s': timedelta(seconds = 1),
'1.3m': timedelta(minutes = 1, seconds = 18),
'1.03m': timedelta(minutes = 1, seconds = 1, milliseconds = 800),
'1.30m': timedelta(minutes = 1, seconds = 18),
'1m': timedelta(minutes = 1),
'1.3h': timedelta(hours = 1, minutes = 18),
'1.03h': timedelta(hours = 1, minutes = 1, seconds = 48),
'1.30h': timedelta(hours = 1, minutes = 18),
'1h': timedelta(hours = 1),
'1.3ms': timedelta(milliseconds = 1, microseconds = 300),
'1.03ms': timedelta(milliseconds = 1, microseconds = 30),
'1.30ms': timedelta(milliseconds = 1, microseconds = 300),
'1ms': timedelta(milliseconds = 1),
}

_type_class = ebuttdt.TimecountTimingType

def test_as_timedelta(self):
for t, td in self._test_cases.items() :
test_instance = self._type_class(t)
self.assertEqual(test_instance.timedelta, td)
assert test_instance == t



class TestFullClockTimingType(TestCase):

_test_cases = {
'111:22:33': timedelta(hours = 111, minutes = 22, seconds = 33),
'111:22:33.4': timedelta(hours = 111, minutes = 22, seconds = 33, milliseconds = 400),
'111:22:33.04': timedelta(hours = 111, minutes = 22, seconds = 33, milliseconds = 40),
'111:22:33.40': timedelta(hours = 111, minutes = 22, seconds = 33, milliseconds = 400),
}

_type_class = ebuttdt.FullClockTimingType

def test_as_timedelta(self):
for t, td in self._test_cases.items() :
test_instance = self._type_class(t)
self.assertEqual(test_instance.timedelta, td)
assert test_instance == t


class TestLimitedClockTimingType(TestCase):

_test_cases = {
'11:22:33': timedelta(hours = 11, minutes = 22, seconds = 33),
'11:22:33.4': timedelta(hours = 11, minutes = 22, seconds = 33, milliseconds = 400),
'11:22:33.04': timedelta(hours = 11, minutes = 22, seconds = 33, milliseconds = 40),
'11:22:33.40': timedelta(hours = 11, minutes = 22, seconds = 33, milliseconds = 400),
}

_type_class = ebuttdt.LimitedClockTimingType

def test_as_timedelta(self):
for t, td in self._test_cases.items() :
test_instance = self._type_class(t)
self.assertEqual(test_instance.timedelta, td)
assert test_instance == t
1 change: 1 addition & 0 deletions ebu_tt_live/bindings/validation/presentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class StyledElementMixin(object):
"""
_compatible_style_type = None
_referenced_styles = None
_referenced_divs = None
_inherited_styles = None
_region_styles = None
_validated_styles = None
Expand Down
36 changes: 35 additions & 1 deletion ebu_tt_live/bindings/validation/timing.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,12 @@ def _post_calculate_begin(self, children):
earliest_child_computed_begin = min(children_computed_begin_times)
if earliest_child_computed_begin > self._computed_begin_time:
# Adjustment scenario
self._computed_begin_time = earliest_child_computed_begin
# If no parent element specified a begin time, then we have found
# a case for the "earliest specified computed begin time" as per the
# specification and we can adjust the begin time to match the
# children's begin time.
if len(self._semantic_dataset['timing_begin_stack']) == 0:
self._computed_begin_time = earliest_child_computed_begin

def _semantic_preprocess_timing(self, dataset, element_content):
"""
Expand Down Expand Up @@ -395,6 +400,35 @@ def _post_pop_end(self):

return end_timedelta

def _post_calculate_begin(self, children):
"""
The computed begin time shall be moved down to match that of the earliest child begin time in case the container
does not specify a begin time itself. NOTE: This does not modify the syncbase.

:param children:
:return:
"""

if not children:
if 'availability_time' in self._semantic_dataset:
if self.begin is None:
self._computed_begin_time = self._semantic_dataset['availability_time']
if self.end is None and self.dur is not None:
self._computed_end_time = self._computed_begin_time + self._dur_timedelta
return

children_computed_begin_times = [item.computed_begin_time for item in children]

earliest_child_computed_begin = min(children_computed_begin_times)
if 'availability_time' in self._semantic_dataset:
earliest_child_computed_begin = max(earliest_child_computed_begin, self._semantic_dataset['availability_time'])

if earliest_child_computed_begin > self._computed_begin_time:
# Adjustment scenario
self._computed_begin_time = earliest_child_computed_begin
if self.dur is not None and self.begin is None:
self._computed_end_time = self._computed_begin_time + self._dur_timedelta

def _semantic_timebase_validation(self, dataset, element_content):

super(BodyTimingValidationMixin, self)._semantic_timebase_validation(dataset, element_content)
Expand Down
14 changes: 14 additions & 0 deletions ebu_tt_live/config/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,20 @@ def __init__(self, config, local_config):
self._create_input(config)
self._create_output(config)

class Denester(ConsumerMixin, ProducerMixin, NodeBase):
required_config = Namespace()

def _create_component(self, config):
self.component = processing_node.DenesterNode(
node_id=self.config.id
)

def __init__(self, config, local_config):
super(RetimingDelay, self).__init__(config, local_config)
self._create_component(config)
self._create_input(config)
self._create_output(config)


class SimpleProducer(ProducerMixin, NodeBase):
required_config = Namespace()
Expand Down
Loading