├── .coveragerc ├── .editorconfig ├── .github └── workflows │ ├── docs_and_ui.yml │ └── test.yml ├── .gitignore ├── CHANGES.txt ├── CONTRIBUTING.md ├── LICENCE.txt ├── MANIFEST.in ├── Makefile ├── README.md ├── docs ├── Makefile ├── build │ ├── .nojekyll │ ├── img │ │ └── ebu-logo.png │ └── index.html ├── contrib │ └── plantuml.jar ├── make.bat └── source │ ├── conf.py │ ├── configurator.rst │ ├── conversion.rst │ ├── deduplication.rst │ ├── direct_carriage_mechanism.rst │ ├── dot │ └── dfs.dot │ ├── ebu_tt_live.bindings.raw.rst │ ├── ebu_tt_live.bindings.rst │ ├── ebu_tt_live.bindings.validation.rst │ ├── ebu_tt_live.carriage.rst │ ├── ebu_tt_live.clocks.rst │ ├── ebu_tt_live.config.rst │ ├── ebu_tt_live.documents.rst │ ├── ebu_tt_live.examples.rst │ ├── ebu_tt_live.node.rst │ ├── ebu_tt_live.rst │ ├── ebu_tt_live.scripts.rst │ ├── ebu_tt_live.twisted.rst │ ├── filesystem_carriage_mechanism.rst │ ├── icarriage.puml │ ├── index.rst │ ├── inode.puml │ ├── nodes_and_carriage_mechanisms.rst │ ├── nodes_and_carriages_comp.puml │ ├── overview.rst │ ├── scripts_and_their_functions.rst │ ├── segmentation.rst │ ├── timing_class_diagram.puml │ ├── timing_resolution.rst │ ├── timing_sequence_diagram.puml │ ├── user_input_producer.rst │ ├── validation.rst │ ├── validation_framework.rst │ └── websocket_carriage_mechanism.rst ├── ebu-debug.py ├── ebu_tt_live ├── __init__.py ├── adapters │ ├── __init__.py │ ├── base.py │ ├── document_data.py │ ├── node_carriage.py │ └── test │ │ ├── test_data │ │ └── testSeq_1.xml │ │ ├── test_document_data_adapters.py │ │ └── test_node_carriage.py ├── bindings │ ├── __init__.py │ ├── _ebuttdt.py │ ├── _ebuttlm.py │ ├── _ebuttm.py │ ├── _ebuttp.py │ ├── _ebutts.py │ ├── _ttm.py │ ├── _ttp.py │ ├── _tts.py │ ├── converters │ │ ├── __init__.py │ │ └── ebutt3_ebuttd.py │ ├── pyxb_utils.py │ ├── test │ │ ├── test_sizing.py │ │ └── test_times.py │ └── validation │ │ ├── __init__.py │ │ ├── base.py │ │ ├── content.py │ │ ├── presentation.py │ │ ├── timing.py │ │ └── validator.py ├── carriage │ ├── __init__.py │ ├── base.py │ ├── direct.py │ ├── filesystem.py │ ├── interface.py │ ├── test │ │ ├── test_base_carriage.py │ │ ├── test_data │ │ │ ├── manifest_testSeq.txt │ │ │ └── testSeq_1.xml │ │ └── test_filesystem.py │ └── websocket.py ├── clocks │ ├── __init__.py │ ├── base.py │ ├── local.py │ ├── media.py │ ├── test │ │ ├── test_base.py │ │ ├── test_init.py │ │ ├── test_local.py │ │ └── test_utc.py │ └── utc.py ├── config │ ├── __init__.py │ ├── adapters.py │ ├── backend.py │ ├── carriage.py │ ├── clocks.py │ ├── common.py │ └── node.py ├── documents │ ├── __init__.py │ ├── base.py │ ├── converters.py │ ├── ebutt3.py │ ├── ebutt3_segmentation.py │ ├── ebutt3_splicer.py │ ├── ebuttd.py │ └── test │ │ ├── converter_ericsson1.xml │ │ ├── data │ │ ├── document.xml │ │ └── message.xml │ │ ├── test_converters.py │ │ ├── test_deduplicationComparableElement.py │ │ ├── test_deduplicationReplaceStylesAndRegions.py │ │ ├── test_deduplication_tests.py │ │ ├── test_ebutt3document.py │ │ ├── test_ebutt3segmentation.py │ │ ├── test_ebutt3sequence.py │ │ └── test_memory_profile.py ├── errors.py ├── examples │ ├── __init__.py │ ├── config │ │ ├── buffer_delay.json │ │ ├── deduplicator_fs.json │ │ ├── producer_resequencer_consumer_fs.json │ │ ├── resequencer_deduplicator_consumer_fs.json │ │ ├── retiming_delay.json │ │ ├── retiming_double_delay.json │ │ ├── simple_consumer.json │ │ ├── simple_producer.json │ │ ├── sproducer_dist_sconsumer_ws.json │ │ ├── sproducer_ebuttd_direct.json │ │ ├── sproducer_resequencer_deduplicator_direct_ebuttd_encoder_fs.json │ │ ├── sproducer_resequencer_direct.json │ │ ├── sproducer_resequencer_direct_ebuttd_encoder_fs.json │ │ ├── sproducer_retiming_fs.json │ │ ├── sproducer_retiming_fss.json │ │ ├── sproducer_rseq_ebuttd_direct.json │ │ ├── sproducer_sconsumer_direct.json │ │ ├── sproducer_sconsumer_ws.json │ │ ├── sproducer_sconsumer_ws_flip.json │ │ ├── user_input_producer_buffer_consumer.json │ │ ├── user_input_producer_consumer.json │ │ ├── user_input_producer_dist_consumers.json │ │ ├── user_input_producer_dist_filesystem.json │ │ ├── user_input_producer_handover.json │ │ └── user_input_producer_retiming_consumer.json │ └── simple_producer.txt ├── node │ ├── __init__.py │ ├── base.py │ ├── consumer.py │ ├── deduplicator.py │ ├── delay.py │ ├── distributing.py │ ├── encoder.py │ ├── handover.py │ ├── interface.py │ ├── producer.py │ ├── switcher.py │ └── test │ │ ├── test_consumer_unit.py │ │ ├── test_delay_unit.py │ │ ├── test_distributing.py │ │ ├── test_encoder.py │ │ └── test_handover_unit.py ├── scripts │ ├── __init__.py │ ├── common.py │ ├── ebu_dummy_encoder.py │ ├── ebu_ebuttd_encoder.py │ ├── ebu_interactive_shell.py │ ├── ebu_run.py │ ├── ebu_simple_consumer.py │ ├── ebu_simple_producer.py │ ├── ebu_user_input_consumer.py │ ├── ebu_user_input_forwarder.py │ └── test │ │ └── test_scripts.py ├── strings.py ├── test │ └── test_utils.py ├── twisted │ ├── __init__.py │ ├── base.py │ ├── node.py │ ├── test │ │ ├── test_twisted_base.py │ │ ├── test_twisted_node.py │ │ └── test_twisted_websocket.py │ └── websocket.py ├── ui │ ├── assets │ │ ├── css │ │ │ ├── bootstrap.css │ │ │ ├── custom.min.css │ │ │ └── main.css │ │ └── img │ │ │ └── ebu-logo-relaunch.png │ ├── test │ │ └── index.html │ └── user_input_producer │ │ ├── index.html │ │ ├── moment.js │ │ ├── nunjucks.js │ │ ├── template │ │ ├── live_message_template.xml │ │ └── user_input_producer_template.xml │ │ └── uip.js ├── utils.py └── xsd │ ├── ebutt_all.xsd │ ├── ebutt_d.xsd │ ├── ebutt_datatypes.xsd │ ├── ebutt_live.xsd │ ├── ebutt_livemessage.xsd │ ├── ebutt_metadata.xsd │ ├── ebutt_parameters.xsd │ ├── ebutt_styling.xsd │ ├── metadata.xsd │ ├── parameter.xsd │ ├── styling.xsd │ └── xml.xsd ├── make.bat ├── publish-key.enc ├── pytest.ini ├── requirements.txt ├── setup.cfg ├── setup.py └── testing ├── SPEC-CONFORMANCE.md ├── bdd ├── conftest.py ├── features │ ├── config │ │ └── websocket_carriage_config.feature │ ├── deduplicator │ │ └── deduplicator.feature │ ├── handover │ │ ├── handover.feature │ │ └── handover_algorithm.feature │ ├── nodes │ │ └── passive_nodes_shall_not_modify_document.feature │ ├── segmentation │ │ ├── duplicate_sequence_id+nun.feature │ │ ├── segmenting_sequence.feature │ │ └── splitting_documents.feature │ ├── styles │ │ ├── ebuttd_fontsize_conversion.feature │ │ ├── ebuttd_segmenter.feature │ │ ├── fontSize_inherited.feature │ │ ├── lineHeight.feature │ │ ├── padding.feature │ │ ├── style_attribute_inherited.feature │ │ └── style_attribute_simple.feature │ ├── timing │ │ ├── bufferDelayNode.feature │ │ ├── computed_times.feature │ │ ├── computed_times_empty_doc.feature │ │ ├── elements_active_times.feature │ │ ├── elements_active_times_empty_body.feature │ │ ├── resolved_times.feature │ │ ├── resolved_times_no_body.feature │ │ └── retimingDelayNode.feature │ └── validation │ │ ├── 3350-value-types.feature │ │ ├── applied-processing.feature │ │ ├── body_element_content.feature │ │ ├── delayTimingType.feature │ │ ├── documentMetadata_elements_order.feature │ │ ├── facet.feature │ │ ├── padding_data_type.feature │ │ ├── referenceClockIdentifier_constraints.feature │ │ ├── sequence_id_num.feature │ │ ├── sequence_identical_timing_model.feature │ │ ├── smpte_constraints.feature │ │ ├── timeBase_attribute_mandatory.feature │ │ ├── timeBase_clock_clockMode_mandatory.feature │ │ ├── timeBase_timeformat_constraints.feature │ │ ├── time_regex_parsing.feature │ │ └── xml_lang_attribute.feature ├── templates │ ├── 3350_value_types.xml │ ├── applied-processing.xml │ ├── body_element_content.xml │ ├── complete_document.xml │ ├── computed_resolved_time_semantics.xml │ ├── computed_resolved_time_semantics_empty_doc.xml │ ├── deduplicator_templates │ │ ├── 1Sty1Reg4DupAtts.xml │ │ ├── 3DupSty3DupRegRefs.xml │ │ ├── 4Sty2Dup5Reg2Dup2SimilarForeignNamespacesEach.xml │ │ ├── 6Sty3Dup6Reg3DupForeignNamespace.xml │ │ ├── NoDupStyNoDupReg.xml │ │ ├── NoStylesNoRegions.xml │ │ ├── NoStylesOneRegion.xml │ │ ├── NoStylesTwoDuplicateRegions.xml │ │ ├── OneStyleNoRegions.xml │ │ ├── OneStyleOneRegion.xml │ │ ├── OneStyleOneRegionWithOneStyleAttr.xml │ │ ├── ReSequenced1_12.xml │ │ ├── ReSequenced1_13.xml │ │ ├── ThreeDuplicateStylesThreeDuplicateRegionsAllAttrsSpecified.xml │ │ └── TwoDuplicateStylesNoRegions.xml │ ├── delayNode.xml │ ├── delayTimingType.xml │ ├── documentMetadata_elements_order.xml │ ├── ebuttd_fontsize_convert.xml │ ├── elements_active_time_semantics.xml │ ├── elements_active_time_semantics_empty_body.xml │ ├── facet.xml │ ├── handover.xml │ ├── padding.xml │ ├── padding_data_type.xml │ ├── referenceClockIdentifier.xml │ ├── segmentation.xml │ ├── segmentation_short.xml │ ├── sequence_id_num.xml │ ├── sequence_identical_timing_model.xml │ ├── smpte.xml │ ├── style_attribute_inherited.xml │ ├── style_attribute_pairs.xml │ ├── timeBase_attribute_mandatory.xml │ ├── timeBase_clock_clockMode_mandatory.xml │ ├── timeBase_timeformat.xml │ ├── time_regex_parsing.xml │ ├── websocket_carriage_config.json │ └── xml_lang_attribute.xml ├── test_3350_value_types.py ├── test_applied_processing.py ├── test_body_element_content.py ├── test_bufferDelayNode.py ├── test_computed_times.py ├── test_deduplicator.py ├── test_delayTimingType.py ├── test_documentMetadata_elements_order.py ├── test_elements_active_times.py ├── test_facet.py ├── test_handover.py ├── test_padding_data_type.py ├── test_passive_node_shall_not_modify_documents.py ├── test_referenceClockIdentifier.py ├── test_resolved_times.py ├── test_retimingDelayNode.py ├── test_segmentation.py ├── test_sequence_attributes_validation.py ├── test_sequence_identical_timing_model.py ├── test_smpte.py ├── test_style.py ├── test_timeBase_attribute_mandatory.py ├── test_timeBase_clock_clockMode_mandatory.py ├── test_timeBase_timeformat.py ├── test_time_regex_parsing.py ├── test_websocket_carriage_config.py └── test_xml_lang_attribute.py ├── example-sequences ├── Ericsson 2016-09-05 │ ├── 192.168.56.99 IBC EBUTT3_434.xml │ ├── 192.168.56.99 IBC EBUTT3_435.xml │ ├── 192.168.56.99 IBC EBUTT3_436.xml │ ├── 192.168.56.99 IBC EBUTT3_437.xml │ ├── 192.168.56.99 IBC EBUTT3_438.xml │ ├── 192.168.56.99 IBC EBUTT3_439.xml │ ├── 192.168.56.99 IBC EBUTT3_440.xml │ ├── 192.168.56.99 IBC EBUTT3_441.xml │ ├── 192.168.56.99 IBC EBUTT3_442.xml │ ├── 192.168.56.99 IBC EBUTT3_443.xml │ ├── 192.168.56.99 IBC EBUTT3_444.xml │ ├── 192.168.56.99 IBC EBUTT3_445.xml │ ├── 192.168.56.99 IBC EBUTT3_446.xml │ ├── 192.168.56.99 IBC EBUTT3_447.xml │ ├── 192.168.56.99 IBC EBUTT3_448.xml │ ├── 192.168.56.99 IBC EBUTT3_449.xml │ ├── 192.168.56.99 IBC EBUTT3_450.xml │ └── manifest_192.168.56.99 IBC EBUTT3.txt └── Ericsson 2016-09-06 │ ├── 1.xml │ ├── 2.xml │ ├── 3.xml │ ├── 4.xml │ └── manifest_localhost EbuTT3 TestSeq.txt ├── test_config.py ├── test_consumer.py └── test_integration.py /.coveragerc: -------------------------------------------------------------------------------- 1 | # .coveragerc to control coverage.py 2 | [run] 3 | branch = True 4 | omit = 5 | */tests/* 6 | */test/* 7 | */bindings/raw/* 8 | */scripts/* 9 | 10 | [report] 11 | # Regexes for lines to exclude from consideration 12 | exclude_lines = 13 | # Have to re-enable the standard pragma 14 | pragma: no cover 15 | 16 | # Don't complain about missing debug-only code: 17 | def __repr__ 18 | if self\.debug 19 | 20 | # Don't complain if tests don't hit defensive assertion code: 21 | raise AssertionError 22 | raise NotImplementedError 23 | 24 | # Don't complain if non-runnable code isn't run: 25 | if 0: 26 | if __name__ == .__main__.: -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = True 2 | 3 | [Makefile] 4 | indent_style=tab 5 | indent_size=4 6 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Check the tests pass 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | name: Install and build and test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout the code 15 | uses: actions/checkout@v2 16 | - name: Setup Python 2.7 17 | uses: actions/setup-python@v2 18 | with: 19 | python-version: '2.7' 20 | cache: 'pip' 21 | - name: Install older pip 22 | run: | 23 | python -m pip install pip==20.3.4 24 | shell: bash 25 | - name: Install dependencies 26 | run: | 27 | pip install -r requirements.txt 28 | shell: bash 29 | - name: Generate bindings 30 | run: | 31 | pyxbgen --binding-root=./ebu_tt_live/bindings -m __init__ --schema-root=./ebu_tt_live/xsd/ -r -u ebutt_all.xsd 32 | shell: bash 33 | - name: Test 34 | run: | 35 | python setup.py test 36 | shell: bash 37 | - name: Install coveralls 38 | run: | 39 | pip install "coveralls<2" 40 | shell: bash 41 | - name: Coveralls 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 45 | run: | 46 | coveralls 47 | - name: Build notification 48 | if: always() 49 | uses: edge/simple-slack-notify@v1.1.2 50 | env: 51 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 52 | BRANCH: ${{ github.ref_name }} 53 | ACTOR: ${{ github.actor }} 54 | COMMIT_REF: ${{ github.sha }} 55 | with: 56 | channel: '#ci' 57 | status: ${{ job.status }} 58 | success_text: 'Build <${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}|#${env.GITHUB_RUN_NUMBER}> (${env.COMMIT_REF}) of ${env.BRANCH} by ${env.ACTOR} completed successfully' 59 | failure_text: 'Build <${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}|#${env.GITHUB_RUN_NUMBER}> (${env.COMMIT_REF}) of ${env.BRANCH} by ${env.ACTOR} failed' 60 | cancelled_text: 'Build <${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}|#${env.GITHUB_RUN_NUMBER}> (${env.COMMIT_REF}) of ${env.BRANCH} by ${env.ACTOR} was cancelled' 61 | fields: | 62 | [{ "title": "${env.GITHUB_WORKFLOW}"}] 63 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebu/ebu-tt-live-toolkit/8c67f83010d9fc0af5d3e270c70dce87674ad289/CHANGES.txt -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, EBU 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENCE.txt 2 | include CHANGES.txt 3 | 4 | recursive-include ebu_tt_live/examples *.txt 5 | recursive-include ebu_tt_live/examples *.json -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: docs uiclean uibuild template initnpm uicopy 2 | 3 | all: init ui 4 | 5 | init: 6 | pip install --upgrade -r requirements.txt 7 | pyxbgen --binding-root=./ebu_tt_live/bindings -m __init__ --schema-root=./ebu_tt_live/xsd/ -r -u ebutt_all.xsd 8 | 9 | initnpm: 10 | ifeq ("$(wildcard node_modules)","") 11 | npm install nunjucks 12 | else 13 | npm update nunjucks 14 | endif 15 | 16 | test: 17 | python setup.py test 18 | 19 | docs: 20 | python setup.py build_sphinx 21 | 22 | bindings: 23 | pyxbgen --binding-root=./ebu_tt_live/bindings -m __init__ --schema-root=./ebu_tt_live/xsd/ -r -u ebutt_all.xsd 24 | 25 | ui: uiclean uibuild 26 | 27 | uiclean: 28 | ifneq ("$(wildcard docs/build/ui/.)","") 29 | rm -R docs/build/ui 30 | endif 31 | 32 | uibuild: template uicopy 33 | 34 | template: initnpm 35 | node_modules/nunjucks/bin/precompile ebu_tt_live/ui/user_input_producer/template/user_input_producer_template.xml > ebu_tt_live/ui/user_input_producer/template/user_input_producer_template.js 36 | node_modules/nunjucks/bin/precompile ebu_tt_live/ui/user_input_producer/template/live_message_template.xml > ebu_tt_live/ui/user_input_producer/template/live_message_template.js 37 | 38 | uicopy: 39 | mkdir -p docs/build/ui 40 | cp -R ebu_tt_live/ui/user_input_producer docs/build/ui/ 41 | cp -R ebu_tt_live/ui/test docs/build/ui/ 42 | cp -R ebu_tt_live/ui/assets docs/build/ui/user_input_producer/ 43 | cp -R ebu_tt_live/ui/assets docs/build/ui/test/ 44 | 45 | -------------------------------------------------------------------------------- /docs/build/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebu/ebu-tt-live-toolkit/8c67f83010d9fc0af5d3e270c70dce87674ad289/docs/build/.nojekyll -------------------------------------------------------------------------------- /docs/build/img/ebu-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebu/ebu-tt-live-toolkit/8c67f83010d9fc0af5d3e270c70dce87674ad289/docs/build/img/ebu-logo.png -------------------------------------------------------------------------------- /docs/contrib/plantuml.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebu/ebu-tt-live-toolkit/8c67f83010d9fc0af5d3e270c70dce87674ad289/docs/contrib/plantuml.jar -------------------------------------------------------------------------------- /docs/source/direct_carriage_mechanism.rst: -------------------------------------------------------------------------------- 1 | Direct Carriage Mechanism 2 | ============================= 3 | 4 | The direct carriage mechanism copies documents in memory via a named "pipe" [#]_ from one node to another node. When configuring nodes to use the direct carriage mechanism the node emitting a document must specify the same pipe name as the node receiving that document. 5 | 6 | This carriage mechanism facilitates efficient chaining of nodes that perform sequential processing to the same content without having to use the network stack or local storage, however it requires the nodes to be hosted in the same process running on the same machine. 7 | 8 | .. rubric:: Footnotes 9 | 10 | .. [#] This is similar in concept to a UNIX pipe but completely unrelated in the implementation. -------------------------------------------------------------------------------- /docs/source/dot/dfs.dot: -------------------------------------------------------------------------------- 1 | digraph shells { 2 | size="7,8"; 3 | node [fontsize=24, shape = plaintext]; 4 | "Document level" -> L1 -> L2 -> L3 -> L4; 5 | 6 | node [fontsize=20, shape = ellipse]; 7 | { rank = same; "Document level" tt; } 8 | { rank = same; L1 body; } 9 | { rank = same; L2 div; } 10 | { rank = same; L3 p1 p2; } 11 | { rank = same; L4 span1 span2 span3; } 12 | 13 | /* 'visible' edges */ 14 | tt -> body; 15 | body -> div; 16 | div -> {p1 p2}; 17 | p1 -> {span1 span2}; 18 | p2 -> span3; 19 | 20 | /* ’invisible’ edges to adjust node placement */ 21 | edge [style=invis]; 22 | L4 -> span1; 23 | } -------------------------------------------------------------------------------- /docs/source/ebu_tt_live.bindings.raw.rst: -------------------------------------------------------------------------------- 1 | raw Package 2 | =========== 3 | 4 | :mod:`raw` Package 5 | ------------------ 6 | 7 | .. automodule:: ebu_tt_live.bindings.raw 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`_ebuttdt` Module 13 | ---------------------- 14 | 15 | .. automodule:: ebu_tt_live.bindings.raw._ebuttdt 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`_ebuttm` Module 21 | --------------------- 22 | 23 | .. automodule:: ebu_tt_live.bindings.raw._ebuttm 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`_ebuttp` Module 29 | --------------------- 30 | 31 | .. automodule:: ebu_tt_live.bindings.raw._ebuttp 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`_ebutts` Module 37 | --------------------- 38 | 39 | .. automodule:: ebu_tt_live.bindings.raw._ebutts 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | :mod:`_ttm` Module 45 | ------------------ 46 | 47 | .. automodule:: ebu_tt_live.bindings.raw._ttm 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | :mod:`_ttp` Module 53 | ------------------ 54 | 55 | .. automodule:: ebu_tt_live.bindings.raw._ttp 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | :mod:`_tts` Module 61 | ------------------ 62 | 63 | .. automodule:: ebu_tt_live.bindings.raw._tts 64 | :members: 65 | :undoc-members: 66 | :show-inheritance: 67 | 68 | -------------------------------------------------------------------------------- /docs/source/ebu_tt_live.bindings.rst: -------------------------------------------------------------------------------- 1 | bindings Package 2 | ================ 3 | 4 | :mod:`bindings` Package 5 | ----------------------- 6 | 7 | .. automodule:: ebu_tt_live.bindings 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`_ebuttdt` Module 13 | ---------------------- 14 | 15 | .. automodule:: ebu_tt_live.bindings._ebuttdt 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`_ebuttm` Module 21 | --------------------- 22 | 23 | .. automodule:: ebu_tt_live.bindings._ebuttm 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`_ebuttp` Module 29 | --------------------- 30 | 31 | .. automodule:: ebu_tt_live.bindings._ebuttp 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`_ebutts` Module 37 | --------------------- 38 | 39 | .. automodule:: ebu_tt_live.bindings._ebutts 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | :mod:`_ttm` Module 45 | ------------------ 46 | 47 | .. automodule:: ebu_tt_live.bindings._ttm 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | :mod:`_ttp` Module 53 | ------------------ 54 | 55 | .. automodule:: ebu_tt_live.bindings._ttp 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | :mod:`_tts` Module 61 | ------------------ 62 | 63 | .. automodule:: ebu_tt_live.bindings._tts 64 | :members: 65 | :undoc-members: 66 | :show-inheritance: 67 | 68 | :mod:`pyxb_utils` Module 69 | ------------------------ 70 | 71 | .. automodule:: ebu_tt_live.bindings.pyxb_utils 72 | :members: 73 | :undoc-members: 74 | :show-inheritance: 75 | 76 | Subpackages 77 | ----------- 78 | 79 | .. toctree:: 80 | 81 | ebu_tt_live.bindings.raw 82 | ebu_tt_live.bindings.validation 83 | 84 | -------------------------------------------------------------------------------- /docs/source/ebu_tt_live.bindings.validation.rst: -------------------------------------------------------------------------------- 1 | validation package 2 | ================== 3 | 4 | :mod:`validation` Package 5 | ------------------------- 6 | 7 | .. automodule:: ebu_tt_live.bindings.validation 8 | :members: 9 | :private-members: 10 | :undoc-members: 11 | :show-inheritance: 12 | 13 | 14 | :mod:`base` Module 15 | ------------------ 16 | 17 | .. automodule:: ebu_tt_live.bindings.validation.base 18 | :members: 19 | :private-members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | :mod:`validator` Module 24 | ----------------------- 25 | 26 | .. automodule:: ebu_tt_live.bindings.validation.validator 27 | :members: 28 | :private-members: 29 | :undoc-members: 30 | :show-inheritance: 31 | 32 | :mod:`timing` Module 33 | -------------------- 34 | 35 | .. automodule:: ebu_tt_live.bindings.validation.timing 36 | :members: 37 | :private-members: 38 | :undoc-members: 39 | :show-inheritance: 40 | 41 | :mod:`presentation` Module 42 | -------------------------- 43 | 44 | .. automodule:: ebu_tt_live.bindings.validation.presentation 45 | :members: 46 | :private-members: 47 | :undoc-members: 48 | :show-inheritance: 49 | 50 | :mod:`content` Module 51 | --------------------- 52 | 53 | .. automodule:: ebu_tt_live.bindings.validation.content 54 | :members: 55 | :private-members: 56 | :undoc-members: 57 | :show-inheritance: 58 | -------------------------------------------------------------------------------- /docs/source/ebu_tt_live.carriage.rst: -------------------------------------------------------------------------------- 1 | carriage Package 2 | ================ 3 | 4 | :mod:`interface` Module 5 | ----------------------- 6 | 7 | .. automodule:: ebu_tt_live.carriage.interface 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`base` Module 13 | ------------------ 14 | 15 | .. automodule:: ebu_tt_live.carriage.base 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`direct` Module 21 | -------------------- 22 | 23 | .. automodule:: ebu_tt_live.carriage.direct 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`filesystem` Module 29 | ------------------------ 30 | 31 | .. automodule:: ebu_tt_live.carriage.filesystem 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`websocket` Module 37 | ----------------------- 38 | 39 | .. automodule:: ebu_tt_live.carriage.websocket 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | -------------------------------------------------------------------------------- /docs/source/ebu_tt_live.clocks.rst: -------------------------------------------------------------------------------- 1 | clocks Package 2 | ============== 3 | 4 | :mod:`base` Module 5 | ------------------ 6 | 7 | .. automodule:: ebu_tt_live.clocks.base 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`local` Module 13 | ------------------- 14 | 15 | .. automodule:: ebu_tt_live.clocks.local 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`media` Module 21 | ------------------- 22 | 23 | .. automodule:: ebu_tt_live.clocks.media 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | -------------------------------------------------------------------------------- /docs/source/ebu_tt_live.config.rst: -------------------------------------------------------------------------------- 1 | config Package - Component configurators 2 | ======================================== 3 | 4 | :mod:`config` Package 5 | ------------------------- 6 | 7 | .. automodule:: ebu_tt_live.config 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`backend` module 13 | ------------------------- 14 | 15 | .. automodule:: ebu_tt_live.config.backend 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`node` module 21 | ------------------------- 22 | 23 | .. automodule:: ebu_tt_live.config.node 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`carriage` Package 29 | ------------------------- 30 | 31 | .. automodule:: ebu_tt_live.config.carriage 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`adapters` module 37 | ------------------------- 38 | 39 | .. automodule:: ebu_tt_live.config.adapters 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | :mod:`clocks` module 45 | ------------------------- 46 | 47 | .. automodule:: ebu_tt_live.config.clocks 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | :mod:`common` module 53 | ------------------------- 54 | 55 | .. automodule:: ebu_tt_live.config.common 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | -------------------------------------------------------------------------------- /docs/source/ebu_tt_live.documents.rst: -------------------------------------------------------------------------------- 1 | documents Package 2 | ================= 3 | 4 | :mod:`documents` Package 5 | ------------------------ 6 | 7 | .. automodule:: ebu_tt_live.documents 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`base` Module 13 | ------------------ 14 | 15 | .. automodule:: ebu_tt_live.documents.base 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`ebutt3` Module 21 | -------------------- 22 | 23 | .. automodule:: ebu_tt_live.documents.ebutt3 24 | :members: 25 | :private-members: 26 | :undoc-members: 27 | :show-inheritance: 28 | 29 | :mod:`ebutt3_segmentation` Module 30 | --------------------------------- 31 | 32 | .. automodule:: ebu_tt_live.documents.ebutt3_segmentation 33 | :members: 34 | :private-members: 35 | :undoc-members: 36 | :show-inheritance: 37 | 38 | :mod:`ebutt3_splicer` Module 39 | ---------------------------- 40 | 41 | .. automodule:: ebu_tt_live.documents.ebutt3_splicer 42 | :members: 43 | :private-members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | :mod:`bindings_converters` Module 48 | --------------------------------- 49 | 50 | .. automodule:: ebu_tt_live.bindings.converters.ebutt3_ebuttd 51 | :members: 52 | :private-members: 53 | :undoc-members: 54 | :show-inheritance: 55 | 56 | :mod:`document_converters` Module 57 | --------------------------------- 58 | 59 | .. automodule:: ebu_tt_live.documents.converters 60 | :members: 61 | :private-members: 62 | :undoc-members: 63 | :show-inheritance: 64 | 65 | 66 | :mod:`ebuttd` Module 67 | -------------------- 68 | 69 | .. automodule:: ebu_tt_live.documents.ebuttd 70 | :members: 71 | :private-members: 72 | :undoc-members: 73 | :show-inheritance: 74 | 75 | -------------------------------------------------------------------------------- /docs/source/ebu_tt_live.examples.rst: -------------------------------------------------------------------------------- 1 | examples Package 2 | ================ 3 | 4 | :mod:`examples` Package 5 | ----------------------- 6 | 7 | .. automodule:: ebu_tt_live.examples 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | -------------------------------------------------------------------------------- /docs/source/ebu_tt_live.node.rst: -------------------------------------------------------------------------------- 1 | node Package 2 | ============ 3 | 4 | :mod:`node` Package 5 | ------------------- 6 | 7 | .. automodule:: ebu_tt_live.node 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`interface` Module 13 | ----------------------- 14 | 15 | .. automodule:: ebu_tt_live.node.interface 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`base` Module 21 | ------------------ 22 | 23 | .. automodule:: ebu_tt_live.node.base 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`consumer` Module 29 | ---------------------- 30 | 31 | .. automodule:: ebu_tt_live.node.consumer 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`producer` Module 37 | ---------------------- 38 | 39 | .. automodule:: ebu_tt_live.node.producer 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | :mod:`encoder` Module 45 | --------------------- 46 | 47 | .. automodule:: ebu_tt_live.node.encoder 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | :mod:`delay` Module 53 | ------------------- 54 | 55 | .. automodule:: ebu_tt_live.node.delay 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | :mod:`distributing` Module 61 | -------------------------- 62 | 63 | .. automodule:: ebu_tt_live.node.distributing 64 | :members: 65 | :undoc-members: 66 | :show-inheritance: 67 | 68 | :mod:`handover` Module 69 | ---------------------- 70 | 71 | .. automodule:: ebu_tt_live.node.handover 72 | :members: 73 | :undoc-members: 74 | :show-inheritance: 75 | 76 | :mod:`deduplicator` Module 77 | -------------------------- 78 | 79 | .. automodule:: ebu_tt_live.node.deduplicator 80 | :members: 81 | :undoc-members: 82 | :show-inheritance: 83 | -------------------------------------------------------------------------------- /docs/source/ebu_tt_live.scripts.rst: -------------------------------------------------------------------------------- 1 | scripts Package 2 | =============== 3 | 4 | :mod:`common` Module 5 | -------------------- 6 | 7 | .. automodule:: ebu_tt_live.scripts.common 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`ebu_run` Module 13 | ------------------------------- 14 | 15 | .. automodule:: ebu_tt_live.scripts.ebu_run 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`ebu_dummy_encoder` Module 21 | ------------------------------- 22 | 23 | .. automodule:: ebu_tt_live.scripts.ebu_dummy_encoder 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`ebu_interactive_shell` Module 29 | ----------------------------------- 30 | 31 | .. automodule:: ebu_tt_live.scripts.ebu_interactive_shell 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`ebu_simple_consumer` Module 37 | --------------------------------- 38 | 39 | .. automodule:: ebu_tt_live.scripts.ebu_simple_consumer 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | :mod:`ebu_simple_producer` Module 45 | --------------------------------- 46 | 47 | .. automodule:: ebu_tt_live.scripts.ebu_simple_producer 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | 53 | :mod:`ebu_user_input_consumer` Module 54 | ------------------------------------- 55 | 56 | .. automodule:: ebu_tt_live.scripts.ebu_user_input_consumer 57 | :members: 58 | :undoc-members: 59 | :show-inheritance: 60 | 61 | -------------------------------------------------------------------------------- /docs/source/ebu_tt_live.twisted.rst: -------------------------------------------------------------------------------- 1 | twisted Package 2 | =============== 3 | 4 | :mod:`twisted` Package 5 | ---------------------- 6 | 7 | .. automodule:: ebu_tt_live.twisted 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`base` Module 13 | ------------------ 14 | 15 | .. automodule:: ebu_tt_live.twisted.base 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`node` Module 21 | ------------------ 22 | 23 | .. automodule:: ebu_tt_live.twisted.node 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`websocket` Module 29 | ----------------------- 30 | 31 | .. automodule:: ebu_tt_live.twisted.websocket 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | -------------------------------------------------------------------------------- /docs/source/filesystem_carriage_mechanism.rst: -------------------------------------------------------------------------------- 1 | Filesystem Carriage Mechanism 2 | ============================= 3 | 4 | The filesystem carriage mechanism writes produced documents to the filesystem and consumes documents from the filesystem. Along with the documents, it writes a manifest file named according to the format `manifest_.txt`. Documents are named following the format `_.xml`. 5 | 6 | Each time a document is written to the file system, a line using the following format is appended to the manifest file: 7 | 8 | `availability_time,path_to_xml_file` 9 | 10 | For example: 11 | 12 | `09:20:31.279,TestSequence1_474.xml` 13 | 14 | The format is `hh:mm:ss.fff,path` where `fff` represents milliseconds digits. 15 | 16 | The manifest file gives the availability time for each document along with the path to the corresponding document. The timeline used for the availability times is the same as the one used in the documents, indeed the carriage implementation uses the same clock (or time reference) as the node that produces the documents. The writing order and thus the reading order is from top to bottom. 17 | -------------------------------------------------------------------------------- /docs/source/icarriage.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | interface ICarriageMechanism 4 | interface IConsumerCarriage { 5 | .. properties .. 6 | +{abstract} consumer_node 7 | .. classmethods .. 8 | +provides() 9 | .. methods .. 10 | +{abstract} on_new_data() 11 | +{abstract} register_consumer_node() 12 | } 13 | interface IProducerCarriage { 14 | .. properties .. 15 | +{abstract} producer_node 16 | .. classmethods .. 17 | +expects() 18 | .. methods .. 19 | +{abstract} emit_data() 20 | +{abstract} register_producer_node() 21 | } 22 | 23 | abstract AbstractConsumerCarriage { 24 | .. properties .. 25 | +consumer_node 26 | .. methods .. 27 | +register_consumer_node() 28 | } 29 | abstract AbstractProducerCarriage { 30 | .. properties .. 31 | +producer_node 32 | .. methods .. 33 | +register_producer_node() 34 | } 35 | abstract AbstractCombinedCarriage 36 | 37 | class WebsocketConsumerCarriage { 38 | -_provides : String 39 | .. methods .. 40 | +on_new_data() 41 | } 42 | class WebsocketProducerCarriage { 43 | -_expects : String 44 | .. methods .. 45 | +emit_data() 46 | } 47 | class FilesystemConsumerImpl { 48 | -_provides : String 49 | .. methods .. 50 | +on_new_data() 51 | } 52 | class FilesystemProducerImpl { 53 | -_expects : String 54 | .. methods .. 55 | +emit_data() 56 | } 57 | class SimpleFolderExport { 58 | -_expects : String 59 | .. methods .. 60 | +emit_data() 61 | } 62 | class RotatingFolderExport{ 63 | .. methods .. 64 | +emit_data() 65 | } 66 | 67 | class DirectCarriageImpl { 68 | -_expects : Any 69 | -_provides : Any 70 | .. methods .. 71 | +emit_data() 72 | +on_new_data() 73 | } 74 | 75 | 76 | ICarriageMechanism <|-- IProducerCarriage 77 | ICarriageMechanism <|-- IConsumerCarriage 78 | 79 | IProducerCarriage <|-- AbstractProducerCarriage 80 | IConsumerCarriage <|-- AbstractConsumerCarriage 81 | AbstractProducerCarriage <|-- AbstractCombinedCarriage 82 | AbstractConsumerCarriage <|-- AbstractCombinedCarriage 83 | 84 | AbstractConsumerCarriage <|-- WebsocketConsumerCarriage 85 | AbstractProducerCarriage <|-- WebsocketProducerCarriage 86 | 87 | AbstractConsumerCarriage <|-- FilesystemConsumerImpl 88 | AbstractProducerCarriage <|-- FilesystemProducerImpl 89 | 90 | AbstractCombinedCarriage <|-- DirectCarriageImpl 91 | 92 | AbstractProducerCarriage <|-- SimpleFolderExport 93 | SimpleFolderExport <|-- RotatingFolderExport 94 | 95 | @enduml -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. EBU-TT-Live Toolkit documentation master file, created by 2 | sphinx-quickstart on Thu Jun 09 13:03:03 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to EBU-TT-Live Toolkit's documentation! 7 | =============================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: -1 13 | 14 | overview 15 | ebu_tt_live 16 | 17 | 18 | 19 | Indices and tables 20 | ================== 21 | 22 | * :ref:`genindex` 23 | * :ref:`modindex` 24 | * :ref:`search` 25 | 26 | -------------------------------------------------------------------------------- /docs/source/nodes_and_carriage_mechanisms.rst: -------------------------------------------------------------------------------- 1 | Nodes and Carriage Mechanisms 2 | ============================= 3 | 4 | The following piece should present the decisions behind the Node architecture and the carriage mechanism pluggability. 5 | 6 | .. uml:: nodes_and_carriages_comp.puml 7 | :caption: Processing node and Carriage mechanism components 8 | 9 | Processing Nodes 10 | ---------------- 11 | 12 | The processing nodes are the key components of this package and facilitate several different use-cases laid out 13 | in the Node conformance section of the specification (EBU Tech3370). A node can be a producer or a consumer or both. This requirement 14 | is fulfilled by the class structure depicted below. 15 | 16 | .. uml:: inode.puml 17 | :caption: Processing node classes 18 | 19 | Carriage mechanisms 20 | ------------------- 21 | 22 | The carriage mechanisms are divided into 2 distinct kinds: producer and consumer carriages (not to be confused with producer and consumer nodes). 23 | 24 | Producer carriages serve the purpose of sending/serialising a document once a processing node 25 | has finished processing them and push them via their domain forward. 26 | 27 | Consumers carriages on the other hand receive a (probably serialised) document from their own domain and 28 | their job is to make sure the document is picked up (possibly parsed and validated) and given 29 | to the consumer processing node that the carriage mechanism object is registered to. 30 | 31 | The class hierarchy providing the necessary steps looks the following way: 32 | 33 | .. uml:: icarriage.puml 34 | :caption: Carriage mechanism classes 35 | 36 | .. toctree:: 37 | filesystem_carriage_mechanism 38 | websocket_carriage_mechanism 39 | direct_carriage_mechanism 40 | -------------------------------------------------------------------------------- /docs/source/nodes_and_carriages_comp.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | package "Processing Nodes" { 4 | [Consumer Nodes] 5 | [Synthesizer Nodes] 6 | [Producer Nodes] 7 | [Producer Nodes] -- IProducerNode 8 | [Synthesizer Nodes] -- IProducerNode 9 | [Synthesizer Nodes] -- IConsumerNode 10 | [Consumer Nodes] -- IConsumerNode 11 | IProducerNode -- INode 12 | IConsumerNode -- INode 13 | } 14 | 15 | package "Carriage Mechanisms" { 16 | [Producer Nodes] -> IProducerCarriage : emit_data 17 | [Websocket] - IProducerCarriage 18 | [Websocket] ..> HTTP :use 19 | IConsumerCarriage - [Websocket] 20 | [Websocket] -up-> INode : process_document 21 | [Synthesizer Nodes] -> IProducerCarriage : emit_data 22 | 23 | note bottom of [Websocket] 24 | Carriage mechanisms may 25 | or may not work 26 | both directions. 27 | end note 28 | 29 | } 30 | 31 | note top of [Producer Nodes] 32 | Producers produce only 33 | end note 34 | 35 | note top of [Synthesizer Nodes] 36 | Synthesizers produce 37 | and consume as well. 38 | end note 39 | 40 | note top of [Consumer Nodes] 41 | Consumers consume only 42 | end note 43 | @enduml 44 | 45 | -------------------------------------------------------------------------------- /docs/source/overview.rst: -------------------------------------------------------------------------------- 1 | Overview of the EBU-TT live toolkit. 2 | ==================================== 3 | 4 | This page is a short introduction to using those scripts that demonstrate toolkit components. For more details and information about other scripts, follow the links below. 5 | 6 | To run the scripts, you will need to first set up the environment and build the code. Please follow the instructions in https://github.com/ebu/ebu-tt-live-toolkit/blob/master/README.md. 7 | 8 | Not all components are implemented yet - see https://github.com/ebu/ebu-tt-live-toolkit/wiki/Components for a list of all components. This page will be updated as more scripts are added. 9 | 10 | The components mimic the nodes and carriage mechanisms defined in the specification. Producer components create documents; consumer components consume them. Each script combines a carriage mechanism implementation with a processing node to create code that can operate as a node. 11 | 12 | .. toctree:: 13 | nodes_and_carriage_mechanisms 14 | scripts_and_their_functions 15 | configurator 16 | validation 17 | user_input_producer 18 | timing_resolution 19 | segmentation 20 | deduplication 21 | conversion 22 | -------------------------------------------------------------------------------- /docs/source/segmentation.rst: -------------------------------------------------------------------------------- 1 | Segmentation of EBU-TT-Live document sequences 2 | ============================================== 3 | 4 | Document sequences may contain arbitrary length of documents with irregular issue times. Some output formats or 5 | carriage mechanisms may require a regular issuing schedule of documents (i.e. every 2 seconds). Therefore the live 6 | sequence supports resegmentation of the subtitle stream into blocks required by the user. The sequence object 7 | has the :py:func:`ebu_tt_live.documents.ebutt3.EBUTT3DocumentSequence.extract_segment` function that looks up the 8 | internal timeline to find any documents that intersect the requested range and in turn calls 9 | :py:func:`ebu_tt_live.documents.ebutt3.EBUTT3Document.extract_segment` on each of them 10 | using :py:class:`ebu_tt_live.documents.ebutt3_segmentation.EBUTT3Segmenter` and merge the resulting 11 | documents into one EBUTT3Document in the end using :py:class:`ebu_tt_live.documents.ebutt3_splicer.EBUTT3Splicer` 12 | After that these documents can be converted to EBU-TT-D for instance to be embedded into DASH, 13 | which requires such a regular document issuing strategy. 14 | -------------------------------------------------------------------------------- /docs/source/timing_class_diagram.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | class tt_type { 4 | #_semantic_before_validation() 5 | #_semantic_after_validation() 6 | #_semantic_before_traversal() 7 | #_semantic_after_traversal() 8 | } 9 | class body_type { 10 | #_semantic_before_traversal() 11 | #_semantic_after_traversal() 12 | } 13 | class div_type { 14 | #_semantic_before_traversal() 15 | #_semantic_after_traversal() 16 | } 17 | class p_type { 18 | #_semantic_before_traversal() 19 | #_semantic_after_traversal() 20 | } 21 | class span_type { 22 | #_semantic_before_traversal() 23 | #_semantic_after_traversal() 24 | } 25 | 26 | class SemanticValidationMixin { 27 | #_semantic_before_traversal() 28 | #_semantic_after_traversal() 29 | } 30 | class SemanticDocumentMixin { 31 | #_semantic_before_validation() 32 | #_semantic_after_validation() 33 | +validateBinding() 34 | } 35 | class TimingValidationMixin { 36 | #_semantic_preprocess_timing() 37 | #_semantic_postprocess_timing() 38 | } 39 | class RecursiveOperation { 40 | #{abstract}_process_element() 41 | #{abstract}_process_non_element() 42 | #_before_element() 43 | #_after_element() 44 | +proceed() 45 | } 46 | class SemanticValidator { 47 | #_process_element() 48 | #_process_non_element() 49 | #_before_element() 50 | #_after_element() 51 | } 52 | 53 | RecursiveOperation <|-- SemanticValidator 54 | SemanticValidationMixin --> SemanticValidator 55 | SemanticDocumentMixin <|-- SemanticValidationMixin 56 | SemanticDocumentMixin <|-- tt_type 57 | SemanticValidationMixin <|-- body_type 58 | SemanticValidationMixin <|-- div_type 59 | SemanticValidationMixin <|-- p_type 60 | SemanticValidationMixin <|-- span_type 61 | TimingValidationMixin <|-- body_type 62 | TimingValidationMixin <|-- div_type 63 | TimingValidationMixin <|-- p_type 64 | TimingValidationMixin <|-- span_type 65 | 66 | @enduml 67 | -------------------------------------------------------------------------------- /docs/source/websocket_carriage_mechanism.rst: -------------------------------------------------------------------------------- 1 | WebSocket Carriage Mechanism 2 | ============================= 3 | 4 | The WebSocket carriage mechanism writes produced documents to WebSocket connections and consumes documents from WebSocket connections. This is conformant to the EBU-TT Live WebSocket Carriage Mechanism specification, using the URL form: :: 5 | 6 | ws://[host]:[port]:[sequenceId]/[publish | subscribe] 7 | 8 | There are two ways to make a connection between node A and node B where documents of sequence "Sequence1" flow from A to B. 9 | 10 | 1. Node A makes a /publish connection, for example: :: 11 | 12 | ws://1.2.3.4:9000/Sequence1/publish 13 | 14 | 2. Node B makes a /subscribe connection, for example : :: 15 | 16 | ws://1.2.3.4/9001/Sequence1/subscribe 17 | 18 | If the sequence identifier contains a character that is reserved for use in URLs then it must be percent encoded exactly once before inserting into the URL. For example a sequence id "abc/def" becomes "abc%2Fdef" before inserting into the URL. 19 | 20 | Error handling 21 | -------------- 22 | 23 | The expected documents are those that have the correct sequence number and flow from the emitter to the receiver. As per the specification, any unexpected data will cause the connection to be closed. These include: 24 | 25 | * Any data received by the emitter, since this reverse flow of data is unexpected. 26 | * Any document with a non-matching sequence identifier 27 | * Any data that is not a valid document 28 | -------------------------------------------------------------------------------- /ebu-debug.py: -------------------------------------------------------------------------------- 1 | from ebu_tt_live.scripts import ebu_run 2 | 3 | ebu_run.main() 4 | -------------------------------------------------------------------------------- /ebu_tt_live/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ebu_tt_live/adapters/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ['base', 'document_data', 'node_carriage'] 3 | -------------------------------------------------------------------------------- /ebu_tt_live/adapters/test/test_data/testSeq_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | It only took me six days. 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ebu_tt_live/bindings/_ebuttlm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from raw._ebuttlm import * 3 | from raw import _ebuttlm as raw 4 | from raw import _ebuttp as ebuttp 5 | from pyxb.utils.domutils import BindingDOMSupport 6 | 7 | 8 | namespace_prefix_map = { 9 | 'ebuttlm': Namespace, 10 | 'ebuttp': ebuttp.Namespace 11 | } 12 | 13 | 14 | class message_type(raw.message_type): 15 | 16 | @classmethod 17 | def __check_bds(cls, bds): 18 | if bds: 19 | return bds 20 | else: 21 | return BindingDOMSupport( 22 | namespace_prefix_map=namespace_prefix_map 23 | ) 24 | 25 | def toDOM(self, bds=None, parent=None, element_name=None): 26 | return super(message_type, self).toDOM( 27 | bds=self.__check_bds(bds), 28 | parent=parent, 29 | element_name=element_name 30 | ) 31 | 32 | def toxml(self, encoding=None, bds=None, root_only=False, element_name=None): 33 | dom = self.toDOM(self.__check_bds(bds), element_name=element_name) 34 | if root_only: 35 | dom = dom.documentElement 36 | return dom.toprettyxml( 37 | encoding=encoding, 38 | indent=' ' 39 | ) 40 | 41 | 42 | raw.message_type._SetSupersedingClass(message_type) 43 | -------------------------------------------------------------------------------- /ebu_tt_live/bindings/_ebuttm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from raw._ebuttm import * 3 | -------------------------------------------------------------------------------- /ebu_tt_live/bindings/_ebuttp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from raw._ebuttp import * 3 | -------------------------------------------------------------------------------- /ebu_tt_live/bindings/_ebutts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from raw._ebutts import * 3 | -------------------------------------------------------------------------------- /ebu_tt_live/bindings/_ttm.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from raw._ttm import * 3 | -------------------------------------------------------------------------------- /ebu_tt_live/bindings/_ttp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from raw._ttp import * 3 | -------------------------------------------------------------------------------- /ebu_tt_live/bindings/_tts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from raw._tts import * 3 | -------------------------------------------------------------------------------- /ebu_tt_live/bindings/converters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebu/ebu-tt-live-toolkit/8c67f83010d9fc0af5d3e270c70dce87674ad289/ebu_tt_live/bindings/converters/__init__.py -------------------------------------------------------------------------------- /ebu_tt_live/bindings/validation/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ['base', 'timing', 'presentation', 'validator', 'content'] 3 | -------------------------------------------------------------------------------- /ebu_tt_live/bindings/validation/validator.py: -------------------------------------------------------------------------------- 1 | 2 | from ..pyxb_utils import RecursiveOperation 3 | from .base import SemanticValidationMixin 4 | from pyxb.binding.basis import NonElementContent 5 | 6 | 7 | class SemanticValidator(RecursiveOperation): 8 | 9 | _semantic_dataset = None 10 | 11 | def __init__(self, root_element): 12 | super(SemanticValidator, self).__init__( 13 | root_element=root_element, 14 | filter=self._semantic_validation_filter, 15 | children_iterator='_validatedChildren' 16 | ) 17 | self._semantic_dataset = {} 18 | 19 | def _semantic_validation_filter(self, value, element): 20 | if isinstance(value, SemanticValidationMixin): 21 | return True 22 | else: 23 | return False 24 | 25 | def _before_element(self, value, element=None, parent_binding=None, **kwargs): 26 | value._semantic_before_traversal(dataset=self._semantic_dataset, element_content=element, parent_binding=parent_binding) 27 | pass 28 | 29 | def _process_element(self, value, element=None, parent_binding=None, **kwargs): 30 | return None 31 | 32 | def _after_element(self, value, element=None, parent_binding=None, **kwargs): 33 | value._semantic_after_traversal(dataset=self._semantic_dataset, element_content=element, parent_binding=parent_binding) 34 | 35 | def _process_non_element(self, value, non_element, parent_binding=None, **kwargs): 36 | return None 37 | 38 | def proceed(self, **kwargs): 39 | self._semantic_dataset = {} 40 | self._semantic_dataset.update(kwargs) 41 | super(SemanticValidator, self).proceed(**kwargs) 42 | return self._semantic_dataset 43 | -------------------------------------------------------------------------------- /ebu_tt_live/carriage/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .interface import IConsumerCarriage, IProducerCarriage, ICarriageMechanism 3 | from .base import AbstractProducerCarriage, AbstractConsumerCarriage, AbstractCombinedCarriage 4 | from .filesystem import FilesystemConsumerImpl, FilesystemProducerImpl, FilesystemReader 5 | from .websocket import WebsocketConsumerCarriage, WebsocketProducerCarriage 6 | 7 | 8 | __all__ = [ 9 | 'interface', 'base', 'filesystem', 'twisted' 10 | ] 11 | -------------------------------------------------------------------------------- /ebu_tt_live/carriage/direct.py: -------------------------------------------------------------------------------- 1 | from .base import AbstractCombinedCarriage 2 | from ebu_tt_live.utils import ANY 3 | 4 | 5 | class DirectCarriageImpl(AbstractCombinedCarriage): 6 | 7 | _expects = ANY 8 | _provides = ANY 9 | 10 | def on_new_data(self, data, **kwargs): 11 | self.producer_node.emit_data(data, **kwargs) 12 | 13 | def resume_producing(self): 14 | self.producer_node.resume_producing() 15 | 16 | def emit_data(self, data, **kwargs): 17 | self.consumer_node.process_document(data, **kwargs) 18 | -------------------------------------------------------------------------------- /ebu_tt_live/carriage/test/test_data/manifest_testSeq.txt: -------------------------------------------------------------------------------- 1 | 13:43:22.738331,testSeq_1.xml 2 | -------------------------------------------------------------------------------- /ebu_tt_live/carriage/test/test_data/testSeq_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | It only took me six days. 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ebu_tt_live/carriage/websocket.py: -------------------------------------------------------------------------------- 1 | 2 | from .base import AbstractProducerCarriage, AbstractConsumerCarriage 3 | import logging 4 | import six 5 | 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | class WebsocketProducerCarriage(AbstractProducerCarriage): 11 | 12 | _backend_producer = None 13 | _expects = six.text_type 14 | 15 | def register_backend_producer(self, producer): 16 | self._backend_producer = producer 17 | 18 | def resume_producing(self): 19 | # None, since this is a producer module. It will produce a new document. 20 | self.producer_node.resume_producing() 21 | 22 | def emit_data(self, data, sequence_identifier='default', delay=None, **kwargs): 23 | if self._backend_producer: 24 | self._backend_producer.emit_data(sequence_identifier, data, delay=delay) 25 | 26 | 27 | class WebsocketConsumerCarriage(AbstractConsumerCarriage): 28 | 29 | _provides = six.text_type 30 | 31 | def on_new_data(self, data, **kwargs): 32 | self.consumer_node.process_document(data, **kwargs) 33 | -------------------------------------------------------------------------------- /ebu_tt_live/clocks/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from . import base 3 | from . import local 4 | from . import utc 5 | from . import media 6 | 7 | # NOTE: Some of the code below includes handling of SMPTE time base, which was removed from version 1.0 of the specification. 8 | 9 | 10 | def get_clock(time_base, **kwargs): 11 | if time_base == 'clock': 12 | if kwargs['clock_mode'] == 'local': 13 | return local.LocalMachineClock() 14 | elif kwargs['clock_mode'] == 'utc': 15 | return utc.UTCClock() 16 | elif time_base == 'media': 17 | # TODO: Here we need the reference clock identifier 18 | return media.MediaClock() 19 | elif time_base == 'smpte': 20 | return media.SMPTEClock() 21 | else: 22 | return None 23 | 24 | 25 | def get_clock_from_document(document): 26 | # TODO: Finish with reference clock identifier 27 | return get_clock( 28 | time_base=document.time_base, 29 | clock_mode=document.clock_mode 30 | ) 31 | -------------------------------------------------------------------------------- /ebu_tt_live/clocks/local.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from .base import Clock 3 | 4 | 5 | class LocalMachineClock(Clock): 6 | 7 | _time_base = 'clock' 8 | _clock_mode = 'local' 9 | 10 | def get_real_clock_time(self): 11 | now = datetime.now().time() 12 | return timedelta(hours=now.hour, minutes=now.minute, seconds=now.second, microseconds=now.microsecond) 13 | -------------------------------------------------------------------------------- /ebu_tt_live/clocks/media.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from collections import namedtuple 3 | from ebu_tt_live.errors import TimeFormatError 4 | from ebu_tt_live.strings import ERR_TIME_WRONG_FORMAT 5 | from .local import Clock 6 | 7 | 8 | ReferenceTime = namedtuple('ReferenceTime', ['local', 'remote']) 9 | 10 | 11 | class MediaClock(Clock): 12 | """ 13 | MediaClock is a reference clock that simulates the mediastream's time by interpolating the local machine time 14 | from the last adjusted reference time. 15 | """ 16 | _reference_mapping = None 17 | _time_base = 'media' 18 | 19 | def __init__(self): 20 | self.adjust_time(timedelta()) 21 | 22 | def get_machine_time(self): 23 | now = datetime.now().time() 24 | current_time = timedelta(hours=now.hour, minutes=now.minute, seconds=now.second, microseconds=now.microsecond) 25 | return current_time 26 | 27 | def get_media_time(self, real_clock_timedelta): 28 | return self._reference_mapping.remote + (real_clock_timedelta - self._reference_mapping.local) 29 | 30 | def get_real_clock_time(self): 31 | return self._reference_mapping.remote + (self.get_machine_time() - self._reference_mapping.local) 32 | 33 | def adjust_time(self, current_time, local_time=None): 34 | """ 35 | By default the current local time is used as reference point but optionally can be adjusted to a given time 36 | :param current_time: 37 | :param local_time: 38 | :return: 39 | """ 40 | if not isinstance(current_time, timedelta) or local_time is not None and not isinstance(local_time, timedelta): 41 | raise TimeFormatError(ERR_TIME_WRONG_FORMAT) 42 | if local_time is None: 43 | local_time = self.get_machine_time() 44 | self._reference_mapping = ReferenceTime(local_time, current_time) 45 | 46 | # NOTE: Some of the code below includes handling of SMPTE time base, which was removed from version 1.0 of the specification. 47 | 48 | class SMPTEClock(MediaClock): 49 | 50 | _time_base = 'smpte' 51 | _framerate = None 52 | _framerate_multiplier = None 53 | _drop_mode = None 54 | -------------------------------------------------------------------------------- /ebu_tt_live/clocks/test/test_base.py: -------------------------------------------------------------------------------- 1 | 2 | from unittest import TestCase 3 | from ebu_tt_live.clocks.base import Clock 4 | from datetime import timedelta 5 | 6 | 7 | class TestLocalClock(TestCase): 8 | 9 | def test_fixed_time_case(self): 10 | clock = Clock() 11 | test_time = timedelta(hours=1) 12 | clock.set_fixed_time(test_time) 13 | clock.set_fixed_time_mode(True) 14 | self.assertEqual(clock.get_time(), test_time) 15 | clock.set_fixed_time_mode(False) 16 | self.assertRaises(NotImplementedError, lambda: clock.get_time()) 17 | self.assertRaises(TypeError, lambda: clock.set_fixed_time(1)) 18 | -------------------------------------------------------------------------------- /ebu_tt_live/clocks/test/test_init.py: -------------------------------------------------------------------------------- 1 | 2 | from unittest import TestCase 3 | from ebu_tt_live.clocks.utc import UTCClock 4 | from ebu_tt_live.clocks.local import LocalMachineClock 5 | from ebu_tt_live.clocks.media import MediaClock, SMPTEClock 6 | from ebu_tt_live.clocks import get_clock 7 | 8 | # NOTE: Some of the code below includes handling of SMPTE time base, which was removed from version 1.0 of the specification. 9 | 10 | class TestInit(TestCase): 11 | 12 | def test_get_clock(self): 13 | clock = get_clock(time_base='clock', clock_mode='local') 14 | self.assertIsInstance(clock, LocalMachineClock) 15 | clock = get_clock(time_base='clock', clock_mode='utc') 16 | self.assertIsInstance(clock, UTCClock) 17 | clock = get_clock(time_base='media') 18 | self.assertIsInstance(clock, MediaClock) 19 | clock = get_clock(time_base='smpte') 20 | self.assertIsInstance(clock, SMPTEClock) 21 | -------------------------------------------------------------------------------- /ebu_tt_live/clocks/test/test_local.py: -------------------------------------------------------------------------------- 1 | 2 | from unittest import TestCase 3 | from ebu_tt_live.clocks.local import LocalMachineClock 4 | from datetime import timedelta 5 | 6 | 7 | class TestLocalClock(TestCase): 8 | 9 | def test_instantiation(self): 10 | clock = LocalMachineClock() 11 | self.assertIsInstance(clock.get_time(), timedelta) 12 | -------------------------------------------------------------------------------- /ebu_tt_live/clocks/test/test_utc.py: -------------------------------------------------------------------------------- 1 | 2 | from unittest import TestCase 3 | from ebu_tt_live.clocks.utc import UTCClock 4 | from datetime import timedelta 5 | 6 | 7 | class TestUTCClock(TestCase): 8 | 9 | def test_instantiation(self): 10 | clock = UTCClock() 11 | self.assertIsInstance(clock.get_time(), timedelta) 12 | -------------------------------------------------------------------------------- /ebu_tt_live/clocks/utc.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from .base import Clock 3 | 4 | 5 | class UTCClock(Clock): 6 | 7 | _time_base = 'clock' 8 | _clock_mode = 'utc' 9 | 10 | def get_real_clock_time(self): 11 | now = datetime.utcnow().time() 12 | return timedelta(hours=now.hour, minutes=now.minute, seconds=now.second, microseconds=now.microsecond) 13 | -------------------------------------------------------------------------------- /ebu_tt_live/config/adapters.py: -------------------------------------------------------------------------------- 1 | 2 | from .common import ConfigurableComponent 3 | from ebu_tt_live.adapters import document_data, node_carriage 4 | 5 | 6 | data_adapters_by_directed_conversion = { 7 | 'xml->ebutt3': document_data.XMLtoEBUTT3Adapter, 8 | 'xml->ebuttd': document_data.XMLtoEBUTTDAdapter, 9 | 'ebutt3->xml': document_data.EBUTT3toXMLAdapter, 10 | 'ebuttd->xml': document_data.EBUTTDtoXMLAdapter 11 | } 12 | 13 | 14 | def parse_adapter_list(value): 15 | # This is working around a bug that configman leaves the lists intact 16 | parsed_value = [] 17 | if value is not None: 18 | for item in value: 19 | conv_type = item['type'] 20 | kwargs = {ckey: carg for ckey, carg in item.items() if ckey != 'type'} 21 | parsed_value.append(data_adapters_by_directed_conversion.get(conv_type)(**kwargs)) 22 | return parsed_value or None 23 | 24 | 25 | class ProducerNodeCarriageAdapter(ConfigurableComponent): 26 | 27 | @classmethod 28 | def configure_component(cls, config, local_config, producer=None, carriage=None, **kwargs): 29 | instance = cls(config=config, local_config=local_config) 30 | adapter_list = parse_adapter_list(local_config) 31 | instance.component = node_carriage.ProducerNodeCarriageAdapter( 32 | producer_carriage=carriage, 33 | producer_node=producer, 34 | data_adapters=adapter_list 35 | ) 36 | 37 | 38 | class ConsumerNodeCarriageAdapter(ConfigurableComponent): 39 | 40 | @classmethod 41 | def configure_component(cls, config, local_config, consumer=None, carriage=None, **kwargs): 42 | instance = cls(config=config, local_config=local_config) 43 | adapter_list = parse_adapter_list(local_config) 44 | instance.component = node_carriage.ConsumerNodeCarriageAdapter( 45 | consumer_carriage=carriage, 46 | consumer_node=consumer, 47 | data_adapters=adapter_list 48 | ) 49 | 50 | return instance 51 | -------------------------------------------------------------------------------- /ebu_tt_live/config/clocks.py: -------------------------------------------------------------------------------- 1 | from .common import ConfigurableComponent, Namespace 2 | from ebu_tt_live import clocks 3 | from ebu_tt_live.errors import ConfigurationError 4 | from ebu_tt_live.strings import ERR_NO_SUCH_COMPONENT 5 | 6 | 7 | class LocalMachineClock(ConfigurableComponent): 8 | 9 | def __init__(self, config, local_config): 10 | super(LocalMachineClock, self).__init__(config, local_config) 11 | self.component = clocks.local.LocalMachineClock() 12 | 13 | 14 | class UTCClock(ConfigurableComponent): 15 | 16 | def __init__(self, config, local_config): 17 | super(UTCClock, self).__init__(config, local_config) 18 | self.component = clocks.utc.UTCClock() 19 | 20 | 21 | class DummyClock(ConfigurableComponent): 22 | """ 23 | This wrapper returns None for reference clock allowing the consumer to create a reference clock from the first 24 | document received 25 | """ 26 | component = None 27 | 28 | 29 | clock_by_type = { 30 | 'utc': UTCClock, 31 | 'local': LocalMachineClock, 32 | 'auto': DummyClock 33 | } 34 | 35 | 36 | def get_clock(clock_type): 37 | try: 38 | return clock_by_type[clock_type] 39 | except KeyError: 40 | raise ConfigurationError( 41 | ERR_NO_SUCH_COMPONENT.format( 42 | type_name=clock_type 43 | ) 44 | ) 45 | -------------------------------------------------------------------------------- /ebu_tt_live/documents/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import SubtitleDocument, TimeBase, DocumentSequence 2 | from .ebutt3 import EBUTT3Document, EBUTT3DocumentSequence, EBUTTAuthorsGroupControlRequest, EBUTT3ObjectBase, \ 3 | EBUTTLiveMessage 4 | from .ebuttd import EBUTTDDocument 5 | from .converters import ebutt3_to_ebuttd, EBUTT3EBUTTDConverter 6 | 7 | __all__ = [ 8 | 'base', 'ebutt3', 'ebuttd', 'ebutt3_splicer', 'ebutt3_segmentation', 'converters' 9 | ] 10 | -------------------------------------------------------------------------------- /ebu_tt_live/documents/converters.py: -------------------------------------------------------------------------------- 1 | 2 | from ebu_tt_live.bindings.converters.ebutt3_ebuttd import EBUTT3EBUTTDConverter 3 | from ebu_tt_live.documents.ebuttd import EBUTTDDocument 4 | from subprocess import Popen, PIPE 5 | import tempfile 6 | import os 7 | import logging 8 | 9 | 10 | log = logging.getLogger(__name__) 11 | 12 | 13 | def ebutt3_to_ebuttd(ebutt3_in, media_clock): 14 | """ 15 | This function takes an EBUTT3Document instance and returns the same document as an EBUTTDDocument instance. 16 | :param ebutt3_in: 17 | :return: 18 | """ 19 | converter = EBUTT3EBUTTDConverter(media_clock=media_clock) 20 | ebuttd_bindings = converter.convert_document(ebutt3_in.binding) 21 | ebuttd_document = EBUTTDDocument.create_from_raw_binding(ebuttd_bindings) 22 | ebuttd_document.validate() 23 | return ebuttd_document 24 | -------------------------------------------------------------------------------- /ebu_tt_live/documents/test/data/document.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Some example text... 26 | 27 | And another line 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /ebu_tt_live/documents/test/data/message.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | testsender 8 | testrecipient1 9 | testrecipient2 10 | authorsGroupControlRequest 11 | 12 | This is a message for unittesting this messaging class. 13 | -------------------------------------------------------------------------------- /ebu_tt_live/documents/test/test_converters.py: -------------------------------------------------------------------------------- 1 | 2 | from unittest import TestCase 3 | from datetime import timedelta 4 | import os 5 | from ebu_tt_live.documents.converters import ebutt3_to_ebuttd 6 | from ebu_tt_live.documents.ebutt3 import EBUTT3Document 7 | from ebu_tt_live.clocks.local import LocalMachineClock 8 | from ebu_tt_live.clocks.media import MediaClock 9 | from ebu_tt_live.bindings import div_type, p_type, span_type, br_type, ebuttdt 10 | 11 | 12 | class TestEBUTT3ToEBUTTDConverter(TestCase): 13 | 14 | def setUp(self): 15 | self._media_clock = MediaClock() 16 | 17 | def _load_asset(self, file_name): 18 | dirpath = os.path.dirname(os.path.abspath(__file__)) 19 | with open(os.path.join(dirpath, file_name), 'r') as ifile: 20 | contents = ifile.read() 21 | return contents 22 | 23 | def test_simple(self): 24 | div = div_type( 25 | p_type( 26 | span_type( 27 | 'Here we are', 28 | br_type(), 29 | 'in 2 lines.' 30 | ), 31 | id='ID001', 32 | begin=ebuttdt.FullClockTimingType(timedelta(seconds=1)), 33 | end=ebuttdt.FullClockTimingType(timedelta(seconds=3)) 34 | ) 35 | ) 36 | 37 | document = EBUTT3Document( 38 | time_base='media', 39 | lang='en-GB', 40 | sequence_identifier='TestSeq1', 41 | sequence_number=1 42 | ) 43 | document.add_div(div) 44 | document.validate() 45 | 46 | ebutt3_to_ebuttd(document, self._media_clock) 47 | 48 | def test_ericsson_1(self): 49 | 50 | xml_file = self._load_asset('converter_ericsson1.xml') 51 | 52 | self._media_clock.adjust_time(timedelta(), ebuttdt.LimitedClockTimingType('12:11:50.000').timedelta) 53 | 54 | document = EBUTT3Document.create_from_xml(xml_file) 55 | cdoc = ebutt3_to_ebuttd(document, self._media_clock) 56 | -------------------------------------------------------------------------------- /ebu_tt_live/errors.py: -------------------------------------------------------------------------------- 1 | 2 | from .strings import ERR_DOCUMENT_EXTENT_MISSING 3 | 4 | 5 | class ComponentCompatError(TypeError): 6 | pass 7 | 8 | 9 | class DataCompatError(TypeError): 10 | pass 11 | 12 | 13 | class DocumentNotLoadedError(Exception): 14 | pass 15 | 16 | 17 | class TimeFormatError(Exception): 18 | pass 19 | 20 | 21 | class TimeFormatOverflowError(Exception): 22 | pass 23 | 24 | 25 | class XMLParsingFailed(Exception): 26 | pass 27 | 28 | 29 | class SemanticValidationError(Exception): 30 | pass 31 | 32 | 33 | class EndOfData(Exception): 34 | pass 35 | 36 | 37 | class IncompatibleSequenceError(Exception): 38 | pass 39 | 40 | 41 | class SequenceNumberCollisionError(IncompatibleSequenceError): 42 | pass 43 | 44 | 45 | class DocumentDiscardedError(Exception): 46 | offending_document = None 47 | 48 | 49 | class SequenceOverridden(Exception): 50 | pass 51 | 52 | 53 | class LogicError(Exception): 54 | pass 55 | 56 | 57 | class ExtentMissingError(Exception): 58 | 59 | _attribute = None 60 | 61 | def __init__(self, attribute): 62 | self._attribute = attribute 63 | 64 | def __str__(self): 65 | return ERR_DOCUMENT_EXTENT_MISSING.format(type=type(self._attribute), value=self._attribute) 66 | 67 | 68 | class StopBranchIteration(Exception): 69 | """ 70 | Let the iterator know that it can proceed to the next branch. It does not need to traverse the current one any 71 | further. 72 | """ 73 | 74 | 75 | class OutsideSegmentError(StopBranchIteration): 76 | """ 77 | This exception is meant to be raised by the copying functionality to make the iterator know that a particular 78 | subtree is not meant to be parsed. 79 | """ 80 | 81 | 82 | class DiscardElement(Exception): 83 | """ 84 | There is a possibility that an element may become superfluous or lose its value. Such a possibility can happen 85 | in segmentation when a p element gets selected because it contains 2 spans but the segment happens to be selecting 86 | an interval between them so the container ends up being empty and thus should be discarded. 87 | """ 88 | 89 | 90 | class ConfigurationError(Exception): 91 | pass 92 | 93 | 94 | class UnexpectedSequenceIdentifierError(Exception): 95 | pass 96 | 97 | 98 | class UnexpectedAuthorsGroupError(Exception): 99 | pass 100 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/__init__.py: -------------------------------------------------------------------------------- 1 | from pkg_resources import ResourceManager, get_provider 2 | 3 | 4 | def get_example_data(dataset_name): 5 | """ 6 | This is a smart package loader that locates text files inside our package 7 | :param dataset_name: 8 | :return: 9 | """ 10 | provider = get_provider('ebu_tt_live') 11 | manager = ResourceManager() 12 | 13 | source = provider.get_resource_string(manager, 'examples/'+dataset_name) 14 | 15 | return source 16 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/buffer_delay.json: -------------------------------------------------------------------------------- 1 | {"nodes": { 2 | "node1": { 3 | "id": "buffer1", 4 | "type": "buffer-delay", 5 | "input": {"carriage": { 6 | "type": "websocket", 7 | "listen": "ws://localhost:9001" 8 | }}, 9 | "output": {"carriage": { 10 | "type": "websocket", 11 | "listen": "ws://localhost:9001" 12 | }}, 13 | "delay": 5 14 | }, 15 | "backend": {"type": "twisted"} 16 | }} -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/deduplicator_fs.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "deduplicator1", 5 | "type": "deduplicator", 6 | "sequence_identifier": "DeDuplicated1", 7 | "input": { 8 | "carriage": { 9 | "type": "filesystem", 10 | "manifest_file": "export/manifest_ReSequenced2.txt", 11 | "tail": "true" 12 | } 13 | }, 14 | "output": { 15 | "carriage": { 16 | "type": "filesystem" 17 | } 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/producer_resequencer_consumer_fs.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "producer1", 5 | "type": "simple-producer", 6 | "sequence_identifier": "TestSequence1", 7 | "output": { 8 | "carriage": { 9 | "type": "filesystem" 10 | } 11 | } 12 | }, 13 | "node2": { 14 | "id": "resequencer1", 15 | "type": "resequencer", 16 | "sequence_identifier": "ReSequenced1", 17 | "segment_length": "5.0", 18 | "clock": { 19 | "type": "local" 20 | }, 21 | "input": { 22 | "carriage": { 23 | "type": "filesystem", 24 | "manifest_file": "export/manifest_TestSequence1.txt", 25 | "tail": "true" 26 | } 27 | }, 28 | "output": { 29 | "carriage": { 30 | "type": "filesystem" 31 | } 32 | } 33 | }, 34 | "node3": { 35 | "id": "consumer1", 36 | "type": "simple-consumer", 37 | "input": { 38 | "carriage": { 39 | "type": "filesystem", 40 | "manifest_file": "export/manifest_TestSequence1.txt", 41 | "tail": "true" 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/resequencer_deduplicator_consumer_fs.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "resequencer1", 5 | "type": "resequencer", 6 | "sequence_identifier": "ReSequenced1", 7 | "segment_length": "5.0", 8 | "clock": { 9 | "type": "local" 10 | }, 11 | "input": { 12 | "carriage": { 13 | "type": "filesystem", 14 | "manifest_file": "export/manifest_TestSequence1.txt", 15 | "tail": "true" 16 | } 17 | }, 18 | "output": { 19 | "carriage": { 20 | "type": "filesystem" 21 | } 22 | } 23 | }, 24 | "node2": { 25 | "id": "deduplicator1", 26 | "type": "deduplicator", 27 | "sequence_identifier": "DeDuplicated1", 28 | "input": { 29 | "carriage": { 30 | "type": "filesystem", 31 | "manifest_file": "export/manifest_ReSequenced2.txt", 32 | "tail": "true" 33 | } 34 | }, 35 | "node3": { 36 | "id": "consumer1", 37 | "type": "simple-consumer", 38 | "input": { 39 | "carriage": { 40 | "type": "filesystem", 41 | "manifest_file": "export/manifest_DeDuplicated1.txt", 42 | "tail": "true" 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/retiming_delay.json: -------------------------------------------------------------------------------- 1 | {"nodes": { 2 | "node1": { 3 | "id": "retiming1", 4 | "type": "retiming-delay", 5 | "input": {"carriage": { 6 | "type": "websocket", 7 | "listen": "ws://localhost:9001" 8 | }}, 9 | "output": {"carriage": { 10 | "type": "websocket", 11 | "listen": "ws://localhost:9001" 12 | }}, 13 | "delay": 5, 14 | "sequence_identifier": "RetimedSequence1" 15 | }, 16 | "backend": {"type": "twisted"} 17 | }} -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/retiming_double_delay.json: -------------------------------------------------------------------------------- 1 | {"nodes": 2 | { 3 | "node1": { 4 | "id": "retiming1", 5 | "type": "retiming-delay", 6 | "input": {"carriage": { 7 | "type": "websocket", 8 | "listen": "ws://localhost:9001" 9 | }}, 10 | "output": {"carriage": { 11 | "type": "direct", 12 | "id": "rt1" 13 | }}, 14 | "delay": 5, 15 | "sequence_identifier": "RetimedSequence1" 16 | }, 17 | "node2": { 18 | "id": "retiming2", 19 | "type": "retiming-delay", 20 | "input": { 21 | "carriage": { 22 | "type": "direct", 23 | "id": "rt1" 24 | } 25 | }, 26 | "output": 27 | { 28 | "carriage": { 29 | "type": "websocket", 30 | "listen": "ws://localhost:9001" 31 | } 32 | }, 33 | "delay": 2, 34 | "sequence_identifier": "RetimedSequence2" 35 | }, 36 | "backend": {"type": "twisted"} 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/simple_consumer.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "consumer1", 5 | "type": "simple-consumer", 6 | "input": { 7 | "carriage": { 8 | "type": "websocket", 9 | "uri": "ws://localhost:9000/TestSequence1/subscribe" 10 | } 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/simple_producer.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "producer1", 5 | "type": "simple-producer", 6 | "show_time": true, 7 | "sequence_identifier": "TestSequence1", 8 | "output": { 9 | "carriage": { 10 | "type": "websocket", 11 | "listen": "ws://localhost:9000" 12 | } 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/sproducer_dist_sconsumer_ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "producer1", 5 | "type": "simple-producer", 6 | "show_time": true, 7 | "sequence_identifier": "TestSequence1", 8 | "output": { 9 | "carriage": { 10 | "type": "websocket", 11 | "listen": "ws://localhost:9000" 12 | } 13 | } 14 | }, 15 | "node2": { 16 | "id": "distributor1", 17 | "type": "distributor", 18 | "input": { 19 | "carriage": { 20 | "type": "websocket", 21 | "connect": [ 22 | "ws://localhost:9000/TestSequence1/subscribe" 23 | ] 24 | } 25 | }, 26 | "output": { 27 | "carriage": { 28 | "type": "websocket", 29 | "listen": "ws://localhost:9001" 30 | } 31 | } 32 | }, 33 | "node3": { 34 | "id": "consumer1", 35 | "type": "simple-consumer", 36 | "input": { 37 | "carriage": { 38 | "type": "websocket", 39 | "connect": [ 40 | "ws://localhost:9001/TestSequence1/subscribe" 41 | ] 42 | } 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/sproducer_ebuttd_direct.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "producer1", 5 | "type": "simple-producer", 6 | "show_time": true, 7 | "sequence_identifier": "TestSequence1", 8 | "output": { 9 | "carriage": { 10 | "type": "direct", 11 | "id": "pipe1" 12 | } 13 | } 14 | }, 15 | "node2": { 16 | "id": "encoder1", 17 | "type": "ebuttd-encoder", 18 | "input": { 19 | "carriage": { 20 | "type": "direct", 21 | "id": "pipe1" 22 | } 23 | }, 24 | "output": { 25 | "carriage": { 26 | "type": "websocket", 27 | "uri": "ws://localhost:9004" 28 | } 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/sproducer_resequencer_deduplicator_direct_ebuttd_encoder_fs.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "producer1", 5 | "type": "simple-producer", 6 | "show_time": true, 7 | "sequence_identifier": "TestSequence1", 8 | "output": { 9 | "carriage": { 10 | "type": "direct", 11 | "id": "pipe1" 12 | } 13 | } 14 | }, 15 | "node2": { 16 | "id": "resequencer1", 17 | "type": "resequencer", 18 | "sequence_identifier": "ReSequenced1", 19 | "segment_length": "5.0", 20 | "input": { 21 | "carriage": { 22 | "type": "direct", 23 | "id": "pipe1" 24 | } 25 | }, 26 | "output": { 27 | "carriage": { 28 | "type": "direct", 29 | "id": "pipe2" 30 | } 31 | } 32 | }, 33 | "node3": { 34 | "id": "deduplicator1", 35 | "type": "deduplicator", 36 | "sequence_identifier": "Deduplicated1", 37 | "input": { 38 | "carriage": { 39 | "type": "direct", 40 | "id": "pipe2" 41 | } 42 | }, 43 | "output": { 44 | "carriage": { 45 | "type": "direct", 46 | "id": "pipe3" 47 | } 48 | } 49 | }, 50 | "node4": { 51 | "id": "encoder1", 52 | "type": "ebuttd-encoder", 53 | "input": { 54 | "carriage": { 55 | "type": "direct", 56 | "id": "pipe3" 57 | } 58 | }, 59 | "output": { 60 | "carriage": { 61 | "type": "filesystem", 62 | "suppress_manifest": true, 63 | "message_filename_pattern": "ebuttd_{counter}.ebuttd.xml" 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/sproducer_resequencer_direct.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "producer1", 5 | "type": "simple-producer", 6 | "show_time": true, 7 | "sequence_identifier": "TestSequence1", 8 | "output": { 9 | "carriage": { 10 | "type": "direct", 11 | "id": "pipe1" 12 | } 13 | } 14 | }, 15 | "node2": { 16 | "id": "resequencer1", 17 | "type": "resequencer", 18 | "sequence_identifier": "ReSequenced1", 19 | "segment_length": "5.0", 20 | "input": { 21 | "carriage": { 22 | "type": "direct", 23 | "id": "pipe1" 24 | } 25 | }, 26 | "output": { 27 | "carriage": { 28 | "type": "direct", 29 | "id": "pipe2" 30 | } 31 | } 32 | }, 33 | "node3": { 34 | "id": "consumer1", 35 | "type": "simple-consumer", 36 | "input": { 37 | "carriage": { 38 | "type": "direct", 39 | "id": "pipe2" 40 | } 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/sproducer_resequencer_direct_ebuttd_encoder_fs.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "producer1", 5 | "type": "simple-producer", 6 | "show_time": true, 7 | "sequence_identifier": "TestSequence1", 8 | "output": { 9 | "carriage": { 10 | "type": "direct", 11 | "id": "pipe1" 12 | } 13 | } 14 | }, 15 | "node2": { 16 | "id": "resequencer1", 17 | "type": "resequencer", 18 | "sequence_identifier": "ReSequenced1", 19 | "segment_length": "5.0", 20 | "input": { 21 | "carriage": { 22 | "type": "direct", 23 | "id": "pipe1" 24 | } 25 | }, 26 | "output": { 27 | "carriage": { 28 | "type": "direct", 29 | "id": "pipe2" 30 | } 31 | } 32 | }, 33 | "node3": { 34 | "id": "encoder1", 35 | "type": "ebuttd-encoder", 36 | "input": { 37 | "carriage": { 38 | "type": "direct", 39 | "id": "pipe2" 40 | } 41 | }, 42 | "output": { 43 | "carriage": { 44 | "type": "filesystem", 45 | "suppress_manifest": true 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/sproducer_retiming_fs.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "producer1", 5 | "type": "simple-producer", 6 | "show_time": true, 7 | "sequence_identifier": "TestSequence1", 8 | "output": { 9 | "carriage": { 10 | "type": "direct", 11 | "id": "pipe1" 12 | } 13 | } 14 | }, 15 | "node2": { 16 | "id": "retiming1", 17 | "type": "retiming-delay", 18 | "input": { 19 | "carriage": { 20 | "type": "direct", 21 | "id": "pipe1" 22 | } 23 | }, 24 | "output": { 25 | "carriage": { 26 | "type": "filesystem" 27 | } 28 | }, 29 | "delay": 3, 30 | "sequence_identifier": "RetimedSequence1" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/sproducer_retiming_fss.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "producer1", 5 | "type": "simple-producer", 6 | "show_time": true, 7 | "sequence_identifier": "TestSequence1", 8 | "output": { 9 | "carriage": { 10 | "type": "direct", 11 | "id": "pipe1" 12 | } 13 | } 14 | }, 15 | "node2": { 16 | "id": "retiming1", 17 | "type": "retiming-delay", 18 | "input": { 19 | "carriage": { 20 | "type": "direct", 21 | "id": "pipe1" 22 | } 23 | }, 24 | "output": { 25 | "carriage": { 26 | "type": "filesystem" 27 | } 28 | }, 29 | "delay": 3, 30 | "sequence_identifier": "RetimedSequence1" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/sproducer_rseq_ebuttd_direct.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "producer1", 5 | "type": "simple-producer", 6 | "show_time": true, 7 | "sequence_identifier": "TestSequence1", 8 | "output": { 9 | "carriage": { 10 | "type": "direct", 11 | "id": "pipe1" 12 | } 13 | } 14 | }, 15 | "node2": { 16 | "id": "resequencer1", 17 | "type": "resequencer", 18 | "sequence_identifier": "ReSequenced1", 19 | "segment_length": "1.0", 20 | "input": { 21 | "carriage": { 22 | "type": "direct", 23 | "id": "pipe1" 24 | } 25 | }, 26 | "output": { 27 | "carriage": { 28 | "type": "direct", 29 | "id": "pipe2" 30 | } 31 | } 32 | }, 33 | "node3": { 34 | "id": "encoder1", 35 | "type": "ebuttd-encoder", 36 | "default_namespace": "true", 37 | "input": { 38 | "carriage": { 39 | "type": "direct", 40 | "id": "pipe2" 41 | } 42 | }, 43 | "output": { 44 | "carriage": { 45 | "type": "filesystem", 46 | "folder": "demo/ebuttd_export", 47 | "rotating_buf": 0, 48 | "message_filename_pattern": "ebuttd-encode-{counter}.xml" 49 | } 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/sproducer_sconsumer_direct.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "producer1", 5 | "type": "simple-producer", 6 | "show_time": false, 7 | "sequence_identifier": "TestSequence1", 8 | "output": { 9 | "carriage": { 10 | "type": "direct", 11 | "id": "pipe1" 12 | } 13 | } 14 | }, 15 | "node2": { 16 | "id": "consumer1", 17 | "type": "simple-consumer", 18 | "verbose": false, 19 | "input": { 20 | "carriage": { 21 | "type": "direct", 22 | "id": "pipe1" 23 | } 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/sproducer_sconsumer_ws.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "producer1", 5 | "type": "simple-producer", 6 | "show_time": true, 7 | "sequence_identifier": "TestSequence1", 8 | "output": { 9 | "carriage": { 10 | "type": "websocket", 11 | "listen": "ws://localhost:9000" 12 | } 13 | } 14 | }, 15 | "node2": { 16 | "id": "consumer1", 17 | "type": "simple-consumer", 18 | "input": { 19 | "carriage": { 20 | "type": "websocket", 21 | "connect": [ 22 | "ws://localhost:9000/TestSequence1/subscribe" 23 | ] 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/sproducer_sconsumer_ws_flip.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node2": { 4 | "id": "producer1", 5 | "type": "simple-producer", 6 | "show_time": true, 7 | "sequence_identifier": "TestSequence1", 8 | "output": { 9 | "carriage": { 10 | "type": "websocket", 11 | "connect": [ 12 | "ws://localhost:9000/TestSequence1/publish" 13 | ] 14 | } 15 | } 16 | }, 17 | "node1": { 18 | "id": "consumer1", 19 | "type": "simple-consumer", 20 | "input": { 21 | "carriage": { 22 | "type": "websocket", 23 | "listen": "ws://localhost:9000" 24 | } 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/user_input_producer_buffer_consumer.json: -------------------------------------------------------------------------------- 1 | {"nodes": { 2 | "node1": { 3 | "id": "buffer1", 4 | "type": "buffer-delay", 5 | "input": {"carriage": { 6 | "type": "websocket", 7 | "listen": "ws://localhost:9001" 8 | }}, 9 | "output": {"carriage": { 10 | "type": "websocket", 11 | "listen": "ws://localhost:9001" 12 | }}, 13 | "delay": 5 14 | }, 15 | "node2": { 16 | "id": "consumer1", 17 | "type": "simple-consumer", 18 | "verbose": true, 19 | "input": {"carriage": { 20 | "type": "websocket", 21 | "connect": ["ws://127.0.0.1:9001/TestSequence1/subscribe"] 22 | }} 23 | }, 24 | "backend": {"type": "twisted"} 25 | }} -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/user_input_producer_consumer.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes" : 3 | { 4 | "node1" : 5 | { 6 | "type" : "simple-consumer", 7 | "verbose" : true, 8 | "input" : 9 | { 10 | "carriage" : 11 | { 12 | "type" : "websocket", 13 | "listen" : "ws://127.0.0.1:9001" 14 | } 15 | } 16 | } 17 | }, 18 | "backend" : 19 | { 20 | "type": "twisted" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/user_input_producer_dist_consumers.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes" : 3 | { 4 | "node1": 5 | { 6 | "id": "distributor1", 7 | "type": "distributor", 8 | "input": 9 | { 10 | "carriage": 11 | { 12 | "type": "websocket", 13 | "listen": "ws://localhost:9001" 14 | } 15 | }, 16 | "output": 17 | { 18 | "carriage": 19 | { 20 | "type": "websocket", 21 | "listen": "ws://localhost:9001" 22 | } 23 | } 24 | }, 25 | "node2" : 26 | { 27 | "id" : "consumer1", 28 | "type" : "simple-consumer", 29 | "verbose" : true, 30 | "input" : 31 | { 32 | "carriage" : 33 | { 34 | "type" : "websocket", 35 | "connect" : 36 | [ 37 | "ws://127.0.0.1:9001/TestSequence1/subscribe" 38 | ] 39 | } 40 | } 41 | }, 42 | "node3" : 43 | { 44 | "id" : "consumer2", 45 | "type" : "simple-consumer", 46 | "verbose" : true, 47 | "input" : 48 | { 49 | "carriage" : 50 | { 51 | "type" : "websocket", 52 | "connect" : 53 | [ 54 | "ws://127.0.0.1:9001/TestSequence2/subscribe" 55 | ] 56 | } 57 | } 58 | } 59 | }, 60 | "backend" : 61 | { 62 | "type": "twisted" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/user_input_producer_dist_filesystem.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes" : 3 | { 4 | "node1": 5 | { 6 | "id": "distributor1", 7 | "type": "distributor", 8 | "input": 9 | { 10 | "carriage": 11 | { 12 | "type": "websocket", 13 | "listen": "ws://localhost:9001" 14 | } 15 | }, 16 | "output": 17 | { 18 | "carriage": 19 | { 20 | "type": "filesystem", 21 | "folder": "filesystem_export" 22 | } 23 | } 24 | } 25 | }, 26 | "backend" : 27 | { 28 | "type": "twisted" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/user_input_producer_handover.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes" : 3 | { 4 | "node1": { 5 | "id": "handover1", 6 | "type": "handover", 7 | "authors_group_identifier": "TestGroup1", 8 | "sequence_identifier": "HandoverSequence1", 9 | "input": { 10 | "carriage": { 11 | "type": "websocket", 12 | "listen" : "ws://127.0.0.1:9001" 13 | } 14 | }, 15 | "output": { 16 | "carriage": { 17 | "type": "websocket", 18 | "listen" : "ws://127.0.0.1:9001" 19 | } 20 | } 21 | } 22 | }, 23 | "backend" : 24 | { 25 | "type": "twisted" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ebu_tt_live/examples/config/user_input_producer_retiming_consumer.json: -------------------------------------------------------------------------------- 1 | {"nodes": { 2 | "node1": { 3 | "id": "retiming1", 4 | "type": "retiming-delay", 5 | "input": {"carriage": { 6 | "type": "websocket", 7 | "listen": "ws://localhost:9001" 8 | }}, 9 | "output": {"carriage": { 10 | "type": "websocket", 11 | "listen": "ws://localhost:9001" 12 | }}, 13 | "delay": 5, 14 | "sequence_identifier": "RetimedSequence1" 15 | }, 16 | "node2": { 17 | "id": "consumer1", 18 | "type": "simple-consumer", 19 | "verbose": true, 20 | "input": {"carriage": { 21 | "type": "websocket", 22 | "connect": ["ws://127.0.0.1:9001/TestSequence1/subscribe"] 23 | }} 24 | }, 25 | "backend": {"type": "twisted"} 26 | }} -------------------------------------------------------------------------------- /ebu_tt_live/node/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .base import INode, IConsumerNode, IProducerNode, AbstractConsumerNode, AbstractProducerNode, AbstractCombinedNode 3 | from .producer import SimpleProducer 4 | from .consumer import SimpleConsumer, ReSequencer 5 | from .encoder import EBUTTDEncoder 6 | from .delay import BufferDelayNode, RetimingDelayNode 7 | from .distributing import DistributingNode 8 | from .handover import HandoverNode 9 | from .deduplicator import DeDuplicatorNode 10 | -------------------------------------------------------------------------------- /ebu_tt_live/node/distributing.py: -------------------------------------------------------------------------------- 1 | from .base import AbstractCombinedNode 2 | from ebu_tt_live.documents import EBUTT3Document 3 | import logging 4 | import six 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | 9 | class DistributingNode(AbstractCombinedNode): 10 | 11 | _reference_clock = None 12 | _expects = EBUTT3Document 13 | _provides = six.text_type 14 | 15 | def __init__(self, node_id, producer_carriage=None, consumer_carriage=None, **kwargs): 16 | super(DistributingNode, self).__init__( 17 | node_id=node_id, 18 | consumer_carriage=consumer_carriage, 19 | producer_carriage=producer_carriage, 20 | **kwargs 21 | ) 22 | 23 | def process_document(self, document, raw_xml=None, **kwargs): 24 | if self.is_document(document): 25 | if self.check_if_document_seen(document) is True: 26 | if raw_xml is not None: 27 | data = raw_xml 28 | else: 29 | data = document.get_xml() 30 | 31 | kwargs.update(dict( 32 | sequence_identifier=document.sequence_identifier, 33 | sequence_number=document.sequence_number, 34 | time_base=document.time_base, 35 | availability_time=document.availability_time, 36 | clock_mode=document.clock_mode 37 | )) 38 | self.producer_carriage.emit_data( 39 | data=data, 40 | **kwargs 41 | ) 42 | else: 43 | log.warning( 44 | 'Ignoring duplicate document: {}__{}'.format( 45 | document.sequence_identifier, 46 | document.sequence_number 47 | ) 48 | ) 49 | else: 50 | kwargs.update(dict( 51 | sequence_identifier=document.sequence_identifier, 52 | availability_time=document.availability_time 53 | )) 54 | self.producer_carriage.emit_data( 55 | data=document.get_xml(), 56 | **kwargs 57 | ) 58 | -------------------------------------------------------------------------------- /ebu_tt_live/node/encoder.py: -------------------------------------------------------------------------------- 1 | 2 | from datetime import timedelta 3 | from .base import AbstractCombinedNode 4 | from ebu_tt_live.clocks.media import MediaClock 5 | from ebu_tt_live.documents.converters import EBUTT3EBUTTDConverter 6 | from ebu_tt_live.documents import EBUTTDDocument, EBUTT3Document 7 | 8 | 9 | class EBUTTDEncoder(AbstractCombinedNode): 10 | 11 | _ebuttd_converter = None 12 | _default_ns = None 13 | _default_ebuttd_doc = None 14 | _expects = EBUTT3Document 15 | _provides = EBUTTDDocument 16 | 17 | def __init__(self, node_id, media_time_zero, default_ns=False, producer_carriage=None, 18 | consumer_carriage=None, **kwargs): 19 | super(EBUTTDEncoder, self).__init__( 20 | producer_carriage=producer_carriage, 21 | consumer_carriage=consumer_carriage, 22 | node_id=node_id, 23 | **kwargs 24 | ) 25 | self._default_ns = default_ns 26 | media_clock = MediaClock() 27 | media_clock.adjust_time(timedelta(), media_time_zero) 28 | self._ebuttd_converter = EBUTT3EBUTTDConverter( 29 | media_clock=media_clock 30 | ) 31 | self._default_ebuttd_doc = EBUTTDDocument(lang='en-GB') 32 | self._default_ebuttd_doc.set_implicit_ns(self._default_ns) 33 | self._default_ebuttd_doc.validate() 34 | 35 | def process_document(self, document, **kwargs): 36 | # Convert each received document into EBU-TT-D 37 | if self.is_document(document): 38 | self.limit_sequence_to_one(document) 39 | 40 | 41 | converted_doc = EBUTTDDocument.create_from_raw_binding( 42 | self._ebuttd_converter.convert_document(document.binding) 43 | ) 44 | # Specify the time_base since the FilesystemProducerImpl can't derive it otherwise. 45 | # Hard coded to 'media' because that's all that's permitted in EBU-TT-D. Alternative 46 | # would be to extract it from the EBUTTDDocument but since it's the only permitted 47 | # value that would be an unnecessary overhead... 48 | self.producer_carriage.emit_data(data=converted_doc, sequence_identifier='default', time_base='media', **kwargs) 49 | -------------------------------------------------------------------------------- /ebu_tt_live/node/switcher.py: -------------------------------------------------------------------------------- 1 | 2 | from .base import AbstractCombinedNode 3 | 4 | 5 | class SwitcherNode(AbstractCombinedNode): 6 | pass 7 | -------------------------------------------------------------------------------- /ebu_tt_live/scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebu/ebu-tt-live-toolkit/8c67f83010d9fc0af5d3e270c70dce87674ad289/ebu_tt_live/scripts/__init__.py -------------------------------------------------------------------------------- /ebu_tt_live/scripts/common.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import re 3 | 4 | from twisted.python import log as twisted_log 5 | 6 | log = logging.getLogger(__name__) 7 | log_format = '[%(levelname)s] (%(asctime)s) in %(name)s[%(lineno)d] - %(message)s' 8 | yaml_file = re.compile('^.*(\.yml|\.yaml)(\w)?$') 9 | 10 | 11 | def create_loggers(level=logging.INFO): 12 | # Pipe Twisted's loggers into python logging package 13 | log_observer = twisted_log.PythonLoggingObserver() 14 | log_observer.start() 15 | # Python logging setup 16 | # TODO: Make this configurable (https://github.com/bbc/ebu-tt-live-toolkit/issues/15) 17 | logging.basicConfig(level=level, format=log_format) 18 | -------------------------------------------------------------------------------- /ebu_tt_live/scripts/ebu_interactive_shell.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | from argparse import ArgumentParser 4 | from common import create_loggers 5 | from ebu_tt_live.documents import EBUTT3Document 6 | 7 | 8 | log = logging.getLogger('ebu_interactive_shell') 9 | parser = ArgumentParser() 10 | 11 | parser.add_argument('-i', '--input-file', dest='input_file', default=None) 12 | 13 | 14 | def main(): 15 | create_loggers() 16 | log.info('Let\'s get started') 17 | args = parser.parse_args() 18 | 19 | if args.input_file: 20 | with open(args.input_file, 'r') as ifile: 21 | document = EBUTT3Document.create_from_xml(ifile.read()) 22 | 23 | import ipdb 24 | ipdb.set_trace() 25 | -------------------------------------------------------------------------------- /ebu_tt_live/scripts/ebu_run.py: -------------------------------------------------------------------------------- 1 | """ 2 | The ``ebu-run`` script is a universal runner for any component or complex interconnected sets of components 3 | based on a compliant configuration file or set of valid options. The configuration framework uses 4 | the mozilla/configman package that picks up configuration from command line/config file. 5 | 6 | Basic usage: 7 | ------------ 8 | 9 | :: 10 | 11 | ebu-run --admin.conf=ebu_tt_live/examples/config/simple-producer.json 12 | 13 | Adjust existing config file: 14 | ---------------------------- 15 | 16 | :: 17 | 18 | ebu-run --admin.conf=ebu_tt_live/examples/config/simple-producer.json --nodes.node1.sequence_identifier=Sequence2 19 | 20 | Help: 21 | ----- 22 | 23 | :: 24 | 25 | ebu-run --admin.conf=ebu_tt_live/examples/config/simple-producer.json --help 26 | 27 | The --help flag aims to be context matching so anywhere as long as configman is used is expected and it elaborates 28 | the directly accessible config file keys using the current configuration structure as well as possible alternative 29 | values for the keys already having a value from the config file or from the command line. 30 | 31 | Since the components and their connections are not hard-coded the ``ebu-run`` does not have a single purpose 32 | limitation. The usage of a configurator facilitates the configuration mapping of a single node or an entire complex 33 | interconnected set of nodes via various carriage mechanisms, events and timings. 34 | """ 35 | 36 | from ebu_tt_live.config import create_app 37 | from .common import create_loggers 38 | 39 | 40 | def main(): 41 | create_loggers() 42 | app = create_app() 43 | app.start() 44 | 45 | 46 | if __name__ == '__main__': 47 | main() 48 | -------------------------------------------------------------------------------- /ebu_tt_live/scripts/ebu_user_input_consumer.py: -------------------------------------------------------------------------------- 1 | # NOTE: This script is no longer maintained. Use `ebu-run` instead. 2 | 3 | import logging 4 | from argparse import ArgumentParser 5 | from .common import create_loggers 6 | 7 | from ebu_tt_live.node import SimpleConsumer 8 | from ebu_tt_live.clocks.local import LocalMachineClock 9 | from ebu_tt_live.twisted import TwistedConsumer, UserInputServerProtocol, UserInputServerFactory 10 | from ebu_tt_live.carriage.websocket import WebsocketConsumerCarriage 11 | from twisted.internet import reactor 12 | 13 | 14 | log = logging.getLogger('ebu_simple_consumer') 15 | 16 | 17 | parser = ArgumentParser() 18 | 19 | parser.add_argument('-c', '--config', dest='config', metavar='CONFIG') 20 | 21 | 22 | def main(): 23 | args = parser.parse_args() 24 | create_loggers() 25 | log.info('This is a User Input Consumer example') 26 | 27 | consumer_impl = None 28 | 29 | consumer_impl = WebsocketConsumerCarriage() 30 | 31 | reference_clock = LocalMachineClock() 32 | reference_clock.clock_mode = 'local' 33 | 34 | simple_consumer = SimpleConsumer( 35 | node_id='user-input-consumer', 36 | consumer_carriage=consumer_impl, 37 | reference_clock=reference_clock 38 | ) 39 | 40 | factory = UserInputServerFactory( 41 | url='ws://127.0.0.1:9001', 42 | consumer=TwistedConsumer( 43 | custom_consumer=consumer_impl 44 | ) 45 | ) 46 | factory.protocol = UserInputServerProtocol 47 | 48 | factory.listen() 49 | 50 | reactor.run() 51 | -------------------------------------------------------------------------------- /ebu_tt_live/scripts/test/test_scripts.py: -------------------------------------------------------------------------------- 1 | 2 | from subprocess import Popen, PIPE 3 | from unittest import TestCase 4 | from ebu_tt_live.scripts import ebu_dummy_encoder 5 | 6 | 7 | class TestDummyScript(TestCase): 8 | 9 | def test_simple_run(self): 10 | process = Popen('ebu-dummy-encoder', stderr=PIPE, stdout=PIPE) 11 | process.communicate() 12 | self.assertEquals(process.returncode, 0) 13 | -------------------------------------------------------------------------------- /ebu_tt_live/twisted/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .base import IBroadcaster 3 | from websocket import BroadcastServerFactory, BroadcastServerProtocol, BroadcastClientFactory, \ 4 | BroadcastClientProtocol, UserInputServerFactory, UserInputServerProtocol, TwistedWSConsumer, TwistedWSPushProducer, \ 5 | TwistedConsumer, TwistedPullProducer 6 | from twisted.internet import task 7 | from twisted.internet import reactor 8 | -------------------------------------------------------------------------------- /ebu_tt_live/twisted/base.py: -------------------------------------------------------------------------------- 1 | 2 | from zope.interface import Interface 3 | 4 | 5 | class IBroadcaster(Interface): 6 | 7 | def broadcast(self, channel, msg): 8 | """ 9 | Broadcast message to all connected clients. 10 | :param channel: 11 | :param msg: 12 | :return: 13 | """ 14 | raise NotImplementedError() 15 | 16 | def register(self, client): 17 | """ 18 | Register new client on connection opening. 19 | :param client: 20 | :return: 21 | """ 22 | raise NotImplementedError() 23 | 24 | def unregister(self, client): 25 | """ 26 | Remove client from clients list on connection loss. 27 | :param client: 28 | :return: 29 | """ 30 | raise NotImplementedError() 31 | -------------------------------------------------------------------------------- /ebu_tt_live/twisted/node.py: -------------------------------------------------------------------------------- 1 | 2 | from twisted.internet import interfaces, reactor 3 | from zope.interface import implementer 4 | import logging 5 | 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | -------------------------------------------------------------------------------- /ebu_tt_live/twisted/test/test_twisted_base.py: -------------------------------------------------------------------------------- 1 | 2 | from twisted.trial.unittest import TestCase 3 | from twisted.internet import reactor 4 | from ebu_tt_live.twisted import base 5 | from pytest import fixture 6 | 7 | 8 | @fixture(autouse=True) 9 | def clean_reactor(): 10 | reactor.removeAll() 11 | 12 | 13 | class TestInterfaces(TestCase): 14 | 15 | def test_broadcaster(self): 16 | self.assertRaises(TypeError, base.IBroadcaster) 17 | -------------------------------------------------------------------------------- /ebu_tt_live/twisted/test/test_twisted_node.py: -------------------------------------------------------------------------------- 1 | 2 | from twisted.trial.unittest import TestCase 3 | from ebu_tt_live.twisted import TwistedWSPushProducer, TwistedWSConsumer 4 | from mock import MagicMock 5 | 6 | 7 | class TestTwistedConsumer(TestCase): 8 | 9 | def setUp(self): 10 | self._custom_consumer = MagicMock() 11 | 12 | def test_instantiate(self): 13 | instance = TwistedWSConsumer(self._custom_consumer) 14 | 15 | 16 | class TestTwistedProducer(TestCase): 17 | 18 | def setUp(self): 19 | self._custom_producer = MagicMock() 20 | self._consumer = MagicMock() 21 | 22 | def test_instantiate(self): 23 | instance = TwistedWSPushProducer(custom_producer=self._custom_producer) 24 | 25 | -------------------------------------------------------------------------------- /ebu_tt_live/ui/assets/css/custom.min.css: -------------------------------------------------------------------------------- 1 | body{padding-top:50px}body>.navbar{-webkit-transition:background-color .3s ease-in;transition:background-color .3s ease-in}@media (min-width:768px){body>.navbar-transparent{background-color:transparent}body>.navbar-transparent .navbar-nav>.open>a{background-color:transparent!important}}#home{padding-top:0}#home .navbar-brand{padding:13.5px 15px 12.5px}#home .navbar-brand>img{display:inline;margin:0 10px;height:100%}#banner{min-height:300px;border-bottom:none}.table-of-contents{margin-top:1em}.page-header h1{font-size:4em}.bs-docs-section{margin-top:6em}.bs-docs-section h1{padding-top:100px}.bs-component{position:relative}.bs-component .modal{position:relative;top:auto;right:auto;left:auto;bottom:auto;z-index:1;display:block}.bs-component .modal-dialog{width:90%}.bs-component .popover{position:relative;display:inline-block;width:220px;margin:20px}#source-button{position:absolute;top:0;right:0;z-index:100;font-weight:700}.nav-tabs{margin-bottom:15px}.progress{margin-bottom:10px}footer{margin:5em 0}footer li{float:left;margin-right:1.5em;margin-bottom:1.5em}footer p{clear:left;margin-bottom:0}.splash{padding:9em 0 2em;background-color:#141d27;background-image:url(../img/bg.jpg);background-size:cover;background-attachment:fixed;color:#fff;text-align:center}.splash .logo{width:160px}.splash h1{font-size:3em}.splash #social{margin:2em 0}.splash .alert{margin:2em 0}.section-tout{padding:4em 0 3em;border-bottom:1px solid rgba(0,0,0,.05);background-color:#eaf1f1}.section-tout .fa{margin-right:.5em}.section-tout p{margin-bottom:3em}.section-preview{padding:4em 0 4em}.section-preview .preview{margin-bottom:4em;background-color:#eaf1f1}.section-preview .preview .image{position:relative}.section-preview .preview .image:before{box-shadow:inset 0 0 0 1px rgba(0,0,0,.1);position:absolute;top:0;left:0;width:100%;height:100%;content:"";pointer-events:none}.section-preview .preview .options{padding:1em 2em 2em;border:1px solid rgba(0,0,0,.05);border-top:none;text-align:center}.section-preview .preview .options p{margin-bottom:2em}.section-preview .dropdown-menu{text-align:left}.section-preview .lead{margin-bottom:2em}@media (max-width:767px){.section-preview .image img{width:100%}}.sponsor #carbonads{max-width:240px;margin:0 auto}.sponsor .carbon-text{display:block;margin-top:1em;font-size:12px}.sponsor .carbon-poweredby{float:right;margin-top:1em;font-size:10px}@media (max-width:767px){.splash{padding-top:4em}.splash .logo{width:100px}.splash h1{font-size:2em}#banner{margin-bottom:2em;text-align:center}} -------------------------------------------------------------------------------- /ebu_tt_live/ui/assets/css/main.css: -------------------------------------------------------------------------------- 1 | 2 | #new-sequence-div { 3 | background-color: #cccccc; 4 | padding:5px; 5 | } 6 | 7 | #websocket-notifications-span, #new-sequence-notification-span { 8 | display: inline; 9 | margin: 1%; 10 | 11 | } 12 | 13 | body { 14 | padding-top: 5px; 15 | } 16 | 17 | #header { 18 | padding: 10px; 19 | flex: 1 100%; 20 | margin-top: 0; 21 | } 22 | 23 | #header h1 { 24 | margin-top: 0 25 | } 26 | 27 | #header a { 28 | margin-right: 40px; 29 | } 30 | 31 | #header .left { 32 | position: relative; 33 | display: block; 34 | float: left; 35 | } 36 | 37 | #header .right { 38 | position: relative; 39 | display: block; 40 | } 41 | 42 | .wrapper { 43 | display: flex; 44 | flex-flow: row wrap; 45 | margin-top: 0; 46 | } 47 | 48 | .left-side { 49 | flex: 2 48%; 50 | } 51 | 52 | .right-side { 53 | flex: 2 48%; 54 | } 55 | 56 | .footer { 57 | flex: 3 100%; 58 | } 59 | 60 | .control-section-div { 61 | border: 1px solid lightgrey; 62 | border-radius: 6px; 63 | padding: 3px; 64 | } 65 | 66 | .control-section-header-span { 67 | font-weight: bold; 68 | padding-right: 30px; 69 | } 70 | 71 | #submit-subtitle-button { 72 | font-weight: bold; 73 | } 74 | 75 | .time-input { 76 | width: 10em; 77 | } 78 | 79 | div {margin-top: 1%;} 80 | 81 | .result-list { 82 | display: block; 83 | padding-left: 25px; 84 | padding-right: 25px; 85 | } 86 | 87 | .result-list .doc-item-template, .result-list .message-item-template { 88 | display: none; 89 | } 90 | 91 | .result-list .result-list-item { 92 | border: 1px solid lightgrey; 93 | border-radius: 3px; 94 | background: rgb(255,255,255); /* Old browsers */ 95 | background: linear-gradient( 96 | to bottom, 97 | rgba(255,255,255,1) 0%, 98 | rgba(229,229,229,1) 100% 99 | ); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 100 | } 101 | 102 | .result-list .result-list-item.message-item { 103 | background: rgb(250,198,149); 104 | background: linear-gradient( 105 | to bottom, 106 | rgba(250,198,149,1) 0%, 107 | rgba(245,171,102,1) 47%, 108 | rgba(239,141,49,1) 100% 109 | ); 110 | } 111 | 112 | #result-view-pre { 113 | white-space: pre-wrap; 114 | display: none; 115 | } 116 | 117 | #on-air-span { 118 | color: white; 119 | font-weight: bold; 120 | } 121 | 122 | #on-air-span.selected { 123 | background: red; 124 | } -------------------------------------------------------------------------------- /ebu_tt_live/ui/assets/img/ebu-logo-relaunch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebu/ebu-tt-live-toolkit/8c67f83010d9fc0af5d3e270c70dce87674ad289/ebu_tt_live/ui/assets/img/ebu-logo-relaunch.png -------------------------------------------------------------------------------- /ebu_tt_live/ui/user_input_producer/template/live_message_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | {% if sender %} 8 | {{ sender }} 9 | {% endif %} 10 | {% if recipient %} 11 | {% for item in recipient %} 12 | {{ item }} 13 | {% endfor %} 14 | {% endif %} 15 | authorsGroupControlRequest 16 | 17 | {{ payload }} 18 | -------------------------------------------------------------------------------- /ebu_tt_live/ui/user_input_producer/template/user_input_producer_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 52 | 53 | {{body_content}} 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /ebu_tt_live/xsd/ebutt_all.xsd: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ebu_tt_live/xsd/ebutt_livemessage.xsd: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | Message header to help a recipient identify the purpose of the message 13 | and decide whether it is relevant to them and if so what type of payload it contains if any 14 | and how to process that payload 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | The message container type that can be used as a root document element 27 | to create a new message. It shall specify its header and it could specify a payload. 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ebu_tt_live/xsd/ebutt_styling.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 11 | 12 | 13 | Alignment of multiple ‘rows’ of inline areas within a containing block 14 | area. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | Padding (or inset) space on the start and end edges of each rendered 28 | line area 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set HERE=%cd% 3 | 4 | IF NOT EXIST %HERE%\env ( 5 | virtualenv env 6 | ) 7 | 8 | REM Make sure we are in an active virtualenv 9 | IF NOT DEFINED VIRTUAL_ENV ( 10 | call env\Scripts\activate 11 | ) 12 | 13 | call pip install --upgrade -r requirements.txt 14 | 15 | python %HERE%\env\Scripts\pyxbgen --binding-root=ebu_tt_live/bindings -m __init__ -r -u file:///%HERE%/ebu_tt_live/xsd/ebutt_all.xsd 16 | 17 | IF EXIST %HERE%\node_modules ( 18 | call npm update nunjucks 19 | ) ELSE ( 20 | call npm install nunjucks 21 | ) 22 | call node_modules/nunjucks/bin/precompile ebu_tt_live/ui/user_input_producer/template/user_input_producer_template.xml > ebu_tt_live/ui/user_input_producer/template/user_input_producer_template.js 23 | call node_modules/nunjucks/bin/precompile ebu_tt_live/ui/user_input_producer/template/live_message_template.xml > ebu_tt_live/ui/user_input_producer/template/live_message_template.js 24 | -------------------------------------------------------------------------------- /publish-key.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebu/ebu-tt-live-toolkit/8c67f83010d9fc0af5d3e270c70dce87674ad289/publish-key.enc -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | 2 | [pytest] 3 | twisted = 1 4 | testpaths = testing ebu_tt_live 5 | addopts = 6 | --cov=ebu_tt_live 7 | --cov-report html 8 | --cov-report xml 9 | --cov-report term-missing -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.12 2 | argh==0.26.2 3 | atomicwrites==1.4.0 4 | attrs==20.2.0 5 | autobahn==17.10.1 6 | Automat==20.2.0 7 | Babel==2.8.0 8 | backports-abc==0.5 9 | backports.functools-lru-cache==1.6.1 10 | backports.shutil-get-terminal-size==1.0.0 11 | certifi==2020.6.20 12 | cffi==1.14.2 13 | chardet==3.0.4 14 | commonmark==0.9.1 15 | configman==1.3.0 16 | configobj==5.0.6 17 | configparser==4.0.2 18 | constantly==15.1.0 19 | contextlib2==0.6.0.post1 20 | coverage==5.3 21 | cryptography==3.1 22 | decorator==4.4.2 23 | docutils==0.16 24 | -e . 25 | enum34==1.1.10 26 | funcsigs==1.0.2 27 | future==0.18.2 28 | futures==3.3.0 29 | glob2==0.7 30 | greenlet==0.4.16 31 | hyperlink==17.1.1 32 | idna==2.10 33 | imagesize==1.2.0 34 | importlib-metadata==1.7.0 35 | incremental==17.5.0 36 | ipaddress==1.0.23 37 | ipdb==0.10.2 38 | ipython==4.2.0 39 | ipython-genutils==0.2.0 40 | Jinja2==2.11.2 41 | livereload==2.6.3 42 | Mako==1.1.3 43 | MarkupSafe==1.1.1 44 | mock==3.0.5 45 | more-itertools==5.0.0 46 | nltk==3.4.5 47 | packaging==20.4 48 | parse==1.18.0 49 | parse-type==0.5.2 50 | pathlib2==2.3.5 51 | pathtools==0.1.2 52 | pexpect==4.8.0 53 | pickleshare==0.7.5 54 | pluggy==0.13.1 55 | port-for==0.3.1 56 | ptyprocess==0.6.0 57 | py==1.9.0 58 | pyasn1==0.4.8 59 | pyasn1-modules==0.2.8 60 | pycparser==2.20 61 | Pygments==2.5.2 62 | PyHamcrest==1.10.1 63 | pyparsing==2.4.7 64 | pytest==4.6.11 65 | pytest-bdd==3.2.1 66 | pytest-cov==2.10.1 67 | pytest-mock==2.0.0 68 | pytest-runner==5.2 69 | pytest-twisted==1.13.2 70 | pytz==2020.1 71 | PyXB==1.2.6 72 | PyYAML==5.3.1 73 | recommonmark==0.6.0 74 | requests==2.24.0 75 | scandir==1.10.0 76 | service-identity==18.1.0 77 | simplegeneric==0.8.1 78 | singledispatch==3.4.0.3 79 | six==1.15.0 80 | snowballstemmer==2.0.0 81 | sortedcontainers==2.2.2 82 | Sphinx==1.8.5 83 | sphinx-autobuild==0.7.1 84 | sphinx-rtd-theme==0.5.0 85 | sphinxcontrib-plantuml==0.18.1 86 | sphinxcontrib-websupport==1.1.2 87 | tornado==5.1.1 88 | total-ordering==0.1.0 89 | traitlets==4.3.3 90 | Twisted==20.3.0 91 | txaio==18.8.1 92 | typing==3.7.4.3 93 | urllib3==1.25.10 94 | watchdog==0.10.3 95 | wcwidth==0.2.5 96 | zipp==1.2.0 97 | zope.interface==5.1.0 98 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | 4 | [build_sphinx] 5 | source-dir = docs/source 6 | build-dir = docs/build 7 | all_files = 1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | try: 3 | from setuptools import setup 4 | extra = dict( 5 | include_package_data=True, 6 | # setup_requires=['pytest-runner'] 7 | ) 8 | except ImportError: 9 | from distutils.core import setup 10 | extra = {} 11 | 12 | 13 | packages=[ 14 | "ebu_tt_live", 15 | "ebu_tt_live.bindings", 16 | "ebu_tt_live.clocks", 17 | "ebu_tt_live.scripts", 18 | "ebu_tt_live.twisted", 19 | "ebu_tt_live.node", 20 | "ebu_tt_live.documents", 21 | "ebu_tt_live.examples" 22 | ] 23 | 24 | setup( 25 | name="ebu-tt-live", 26 | version="2.1.3", 27 | description="EBU-TT Part 3 library implementing Specification EBU-3370", 28 | install_requires=[ 29 | "PyXB", 30 | "ipdb>=0.10.1,<0.10.3", # This will eventually be removed from here 31 | "configobj", 32 | "pyyaml", 33 | "service_identity", 34 | "twisted", 35 | "autobahn<18", 36 | "nltk<3.5", 37 | "sortedcontainers", 38 | "configman", 39 | "six", 40 | "hyperlink<17.2.0" # This should be removed if https://github.com/python-hyper/hyperlink/issues/16 is fixed 41 | ], 42 | license="BSD3", 43 | packages=packages, 44 | package_data={ 45 | 'ebu_tt_live.examples': ['*.txt', '*.json'] 46 | }, 47 | entry_points={ 48 | 'console_scripts': [ 49 | 'ebu-dummy-encoder = ebu_tt_live.scripts.ebu_dummy_encoder:main', 50 | 'ebu-interactive-shell = ebu_tt_live.scripts.ebu_interactive_shell:main', 51 | 'ebu-simple-consumer = ebu_tt_live.scripts.ebu_simple_consumer:main', 52 | 'ebu-simple-producer = ebu_tt_live.scripts.ebu_simple_producer:main', 53 | 'ebu-user-input-consumer = ebu_tt_live.scripts.ebu_user_input_consumer:main', 54 | 'ebu-user-input-forwarder = ebu_tt_live.scripts.ebu_user_input_forwarder:main', 55 | 'ebu-ebuttd-encoder = ebu_tt_live.scripts.ebu_ebuttd_encoder:main', 56 | 'ebu-run = ebu_tt_live.scripts.ebu_run:main' 57 | ] 58 | }, 59 | **extra 60 | ) 61 | -------------------------------------------------------------------------------- /testing/bdd/features/config/websocket_carriage_config.feature: -------------------------------------------------------------------------------- 1 | 2 | Feature: Configuration of websocket carriage 3 | # These examples hold a websocket carriage mechanism configuration for a producer-consumer pair of nodes 4 | 5 | Examples: 6 | | config_file | xml_file | sequence_identifier | time_base | 7 | | websocket_carriage_config.json | sequence_id_num.xml | test | media | 8 | 9 | Scenario: Get parts of sequence 10 | Given an xml file 11 | And a configuration file 12 | And a sequence with timeBase 13 | When a free port has been found 14 | And the producer listens on the port 15 | And the consumer connects to the port with 16 | And the configuration file is loaded 17 | And producer sends document with 18 | And producer sends document with 19 | Then transmission should be successful 20 | 21 | Examples: 22 | | sequence_number_1 | sequence_number_2 | client_url_path | 23 | | 1 | 2 | TestSequence1/subscribe | 24 | -------------------------------------------------------------------------------- /testing/bdd/features/nodes/passive_nodes_shall_not_modify_document.feature: -------------------------------------------------------------------------------- 1 | @node @passive 2 | Feature: Passive nodes shall not modify documents in any way 3 | 4 | # SPEC-CONFORMANCE: R12 5 | Scenario: Distributing node does not modify documents 6 | Given an xml file 7 | And a distributing node 8 | When it processes the document 9 | Then the emitted document is identical to the received one 10 | 11 | Examples: 12 | | xml_file | 13 | | complete_document.xml | 14 | 15 | # SPEC-CONFORMANCE: R118 (see note below) R119 16 | # Note: R118 is not fully implemented because the libraries we use do not fully support XQuery functions 17 | Scenario: BufferDelay node does not modify documents 18 | Given an xml file 19 | And a buffer delay node 20 | When it delays the document 21 | Then the delayed document is identical to the received one 22 | 23 | Examples: 24 | | xml_file | 25 | | complete_document.xml | 26 | 27 | -------------------------------------------------------------------------------- /testing/bdd/features/segmentation/duplicate_sequence_id+nun.feature: -------------------------------------------------------------------------------- 1 | 2 | 3 | Feature: Discard document with already seen pair of sequence identifier and sequence number 4 | 5 | Examples: 6 | | xml_file | 7 | | sequence_id_num.xml | 8 | 9 | 10 | # SPEC-CONFORMANCE: R107 11 | Scenario: Discard document 12 | Given an xml file 13 | And a processing node 14 | When it has sequence identifier 15 | And it has sequence number 16 | And the document is generated 17 | And the document is processed 18 | And another document arrives 19 | And it has sequence identifier 20 | And it has sequence number 21 | And the document is generated 22 | Then the document is not processed 23 | 24 | Examples: 25 | | seq_id_1 | seq_n_1 | seq_id_2 | seq_n_2 | 26 | | n | 1 | n | 1 | 27 | | 1 | 1 | 1 | 1 | 28 | 29 | 30 | Scenario: Do not discard document 31 | Given an xml file 32 | And a processing node 33 | When it has sequence identifier 34 | And it has sequence number 35 | And the document is generated 36 | And the document is processed 37 | And another document arrives 38 | And it has sequence identifier 39 | And it has sequence number 40 | And the document is generated 41 | Then the document is processed 42 | 43 | Examples: 44 | | seq_id_1 | seq_n_1 | seq_id_2 | seq_n_2 | 45 | | n | 1 | n | 2 | 46 | | 0 | 1 | 1 | 1 | 47 | 48 | 49 | # SPEC-CONFORMANCE: R108 50 | Scenario: Availability time unchanged when discarding 51 | Given an xml file 52 | And a processing node 53 | When it has sequence identifier 54 | And it has sequence number 55 | And the document is generated 56 | And the document is processed 57 | And another document arrives 58 | And it has sequence identifier 59 | And it has sequence number 60 | And the document is generated 61 | And the document has availability time 62 | Then the document is not processed 63 | And the document has availability time 64 | 65 | Examples: 66 | | seq_id_1 | seq_n_1 | avail_time_1 | seq_id_2 | seq_n_2 | 67 | | n | 1 | 00:00:00.000 | n | 1 | 68 | | n | 2 | 00:00:01.000 | n | 2 | 69 | -------------------------------------------------------------------------------- /testing/bdd/features/styles/fontSize_inherited.feature: -------------------------------------------------------------------------------- 1 | @styles @document @fontSize 2 | Feature: Compute fontSize on a single EBU-TT Live element 3 | 4 | # fontSize behaves in a special way compared to the other style attributes as it 5 | # not only inherits, in the case of percentage values it also cascades on computed fontSize 6 | # values defined in the parent elements of the element in question. 7 | 8 | Examples: 9 | | xml_file | elem_id | style_attribute | 10 | | style_attribute_inherited.xml | span1 | tts:fontSize | 11 | 12 | # Elements referencing styles with different fontSize attribute values 13 | # Inheritence: region (S1) > div (S2) > p (S3) > span (S4) 14 | Scenario: Font size inheritance 15 | Given an xml file 16 | When it has a cell resolution of 17 | And it has extent of 18 | And it contains style S1 with value 19 | And it contains style S2 with value 20 | And it contains style S3 with value 21 | And it contains style S4 with value 22 | And the document is generated 23 | Then the computed in is 24 | 25 | Examples: 26 | | extent | cell_resolution | S1_value | S2_value | S3_value | S4_value | computed_value | 27 | | | 32 15 | 50% | 200% | 50% | 200% | 1c | 28 | | | 32 15 | 100% | 100% | 100% | 100% | 1c | 29 | | | 32 15 | 1c | 200% | 100% | 50% | 1c | 30 | | | 32 15 | 100% | 2c | 100% | 50% | 1c | 31 | | | 10 10 | 100% | | | 50% | .5c | 32 | | 100px 100px | 10 10 | 1c 2c | 100% 50% | 5px 20px | 200% 50% | 1c 1c | 33 | | | 10 10 | | 50% | | 400% | 2c | 34 | | | 10 10 | 1c 2c | 100% 50% | 50% 100% | 200% 100% | 1c 1c | 35 | # implicit cell resolution of 32 15: 36 | | 100px 100px | | | 10px | 200% | 50% | 1.5c | 37 | 38 | 39 | -------------------------------------------------------------------------------- /testing/bdd/features/styles/padding.feature: -------------------------------------------------------------------------------- 1 | @styles @document 2 | Feature: Compute padding on a single EBU-TT Live element 3 | 4 | # tts:padding may appear both in the tt:style and tt:region element. 5 | # The padding property shall not be inherited. To apply padding to tt:p and tt:span elements, a tt:style element shall be referenced by tt:p or tt:span. 6 | # Percentage relative to width and height of region. 7 | 8 | Examples: 9 | | xml_file | cell_resolution | style_attribute | elem_id | 10 | | padding.xml | 10 10 | tts:padding | region1 | 11 | 12 | 13 | # Inheritence: region (S1) > div (S2) > p (S3) > span (S4) 14 | # tts:extent="100% 100%" 15 | Scenario: padding 16 | Given an xml file 17 | When it has a cell resolution of 18 | And it contains style S1 with value 19 | And it contains value applied to region 20 | And the document is generated 21 | Then the computed in is 22 | 23 | Examples: 24 | | S1_value | S2_value | computed_value | 25 | | 1% | 5% | 5% | 26 | | 1c 1c | | 10% 10% 10% 10% | 27 | | | 1c .5c 1c 0.5c | 10% 5% 10% 5% | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /testing/bdd/features/timing/bufferDelayNode.feature: -------------------------------------------------------------------------------- 1 | @buffer @delay 2 | Feature: BufferDelayNode 3 | 4 | Examples: 5 | | xml_file | 6 | | delayNode.xml | 7 | 8 | # SPEC-CONFORMANCE.md R109, R112 9 | # Any delay introduced by the carriage mechanism can not lead to a test passing falsely. 10 | # For example, if the desired delay offset is 10s and the carriage mech imposes a delay of 3s, 11 | # then an actual delay in the range 7s -> 10s would lead to the test passing because the 3s of 12 | # carriage delay would be added. This would not be correct. 13 | # BufferDelay.emission_time - BufferDelay.availability_time >= delay_offset 14 | 15 | Scenario: BufferDelayNode delays emission by no less than the delay period 16 | Given an xml file 17 | And the document is generated 18 | And the buffer delay node delays it by 19 | And the document is emitted 20 | Then the delta between emission and availability time is greater or equal to 21 | 22 | Examples: 23 | | delay_offset | 24 | | 00:00:00.500 | 25 | | 00:00:01.0 | 26 | | 00:01:01.0 | 27 | | 01:01:01.0 | 28 | -------------------------------------------------------------------------------- /testing/bdd/features/timing/computed_times_empty_doc.feature: -------------------------------------------------------------------------------- 1 | @timing @resolution @document 2 | Feature: Computed times computation 3 | 4 | # This tests mostly operates on document computed begin and end times. It does not deal with active duration of 5 | # child elements. 6 | 7 | Examples: 8 | | xml_file | sequence_identifier | sequence_number | 9 | | computed_resolved_time_semantics_empty_doc.xml | testSequence1 | 1 | 10 | 11 | 12 | # A document without . This means no timings at all in the document since the is the topmost element that accepts timing attributes. 13 | # From TTML1: 14 | # "The Root Temporal Extent, i.e., the time interval over which a Document Instance is active, 15 | # has an implicit duration that is equal to ... zero if the body element is absent." 16 | Scenario: Computed times of a document with empty body 17 | Given an xml file 18 | And it has sequenceIdentifier 19 | And it has timeBase 20 | And it has sequenceNumber 21 | And the document is generated # implicitly means it is valid. 22 | And it has availability time 23 | Then it has computed begin time 24 | And it has computed end time 25 | 26 | Examples: 27 | | time_base | avail_time | computed_begin | computed_end | 28 | | clock | 00:00:00.0 | 00:00:00.0 | 00:00:00.0 | 29 | | media | 109:00:00.0 | 109:00:00.0 | 109:00:00.0 | 30 | -------------------------------------------------------------------------------- /testing/bdd/features/validation/applied-processing.feature: -------------------------------------------------------------------------------- 1 | @validation @syntax @metadata 2 | Feature: Applied processing element constrainst 3 | Attributes `process` and `generatedBy` are mandatory. 4 | 5 | # SPEC-CONFORMANCE : R31 R42 R43 6 | Scenario: Invalid applied processing attributes 7 | Given an xml file 8 | When appliedProcessing element has process attribute 9 | And appliedProcessing element has generatedBy attribute 10 | And appliedProcessing element has sourceId attribute 11 | Then document is invalid 12 | 13 | Examples: 14 | | xml_file | process | generated_by | source_id | 15 | | applied-processing.xml | | producer.py | | 16 | | applied-processing.xml | creation | | | 17 | | applied-processing.xml | | | lorem_ipsum.txt | 18 | 19 | 20 | Scenario: Valid applied process attributes 21 | Given an xml file 22 | When appliedProcessing element has process attribute 23 | And appliedProcessing element has generatedBy attribute 24 | And appliedProcessing element has sourceId attribute 25 | Then document is valid 26 | 27 | Examples: 28 | | xml_file | process | generated_by | source_id | 29 | | applied-processing.xml | creation | producer.py | lorem_ipsum.txt | 30 | | applied-processing.xml | validation | producer.py | | 31 | -------------------------------------------------------------------------------- /testing/bdd/features/validation/body_element_content.feature: -------------------------------------------------------------------------------- 1 | @validation @syntax 2 | Feature: Body Element Content testing 3 | The body element is restricted in terms of allowed child elements 4 | 5 | Scenario: Invalid body element content 6 | Given an xml file 7 | When its body has a 8 | Then document is invalid 9 | 10 | Examples: 11 | | xml_file | child_element| 12 | | body_element_content.xml | span | 13 | | body_element_content.xml | p | 14 | | body_element_content.xml | br | 15 | -------------------------------------------------------------------------------- /testing/bdd/features/validation/delayTimingType.feature: -------------------------------------------------------------------------------- 1 | @validation @xsd 2 | Feature: delayTimingType (used by attribute ebuttm:authoringDelay). 3 | delayTimingType is constrained to a signed (positive or negative) number with an optional decimal fraction, followed by a time metric being one of: "h" (hours), "m" (minutes), "s" (seconds), "ms" (milliseconds). 4 | 5 | # SPEC-CONFORMANCE: R68 6 | Scenario: Invalid delayTimingType format 7 | Given an xml file 8 | When ebuttm:authoringDelay attribute has value 9 | Then document is invalid 10 | 11 | Examples: 12 | | xml_file | authoring_delay | 13 | | delayTimingType.xml | 01:00:00 | 14 | | delayTimingType.xml | 01:00:00:25 | 15 | | delayTimingType.xml | 125a | 16 | 17 | 18 | # SPEC-CONFORMANCE: R68 19 | Scenario: Valid delayTimingType format 20 | Given an xml file 21 | When ebuttm:authoringDelay attribute has value 22 | Then document is valid 23 | 24 | Examples: 25 | | xml_file | authoring_delay | 26 | | delayTimingType.xml | -5h | 27 | | delayTimingType.xml | 1.5m | 28 | | delayTimingType.xml | 125s | 29 | | delayTimingType.xml | -5.4ms | 30 | -------------------------------------------------------------------------------- /testing/bdd/features/validation/referenceClockIdentifier_constraints.feature: -------------------------------------------------------------------------------- 1 | # Note: contains examples of SMPTE time base. SMPTE was removed from the specification in version 1.0. --> 2 | 3 | 4 | @validation 5 | Feature: ttp:referenceClockIdentifier constraints testing 6 | Scenario: Valid use of referenceClockIdentifier 7 | Given an xml file 8 | When it has timeBase 9 | And it has clock mode 10 | And it has reference clock identifier 11 | Then document is valid 12 | 13 | Examples: 14 | | xml_file | time_base | clock_mode | ref_clock_id | 15 | | referenceClockIdentifier.xml | clock | local | http://test.com | 16 | | referenceClockIdentifier.xml | clock | utc | | 17 | | referenceClockIdentifier.xml | clock | gps | | 18 | | referenceClockIdentifier.xml | media | | | 19 | | referenceClockIdentifier.xml | smpte | | ../clock/clock.clock | 20 | | referenceClockIdentifier.xml | clock | local | | 21 | | referenceClockIdentifier.xml | smpte | | | 22 | 23 | 24 | @skip 25 | Scenario: Invalid use of referenceClockIdentifier 26 | Given an xml file 27 | When it has timeBase 28 | And it has clock mode 29 | And it has reference clock identifier 30 | Then document is invalid 31 | 32 | Examples: 33 | | xml_file | time_base | clock_mode | ref_clock_id | 34 | | referenceClockIdentifier.xml | clock | utc | http://test.com | 35 | | referenceClockIdentifier.xml | clock | gps | http://test.com | 36 | | referenceClockIdentifier.xml | media | | http://test.com | 37 | -------------------------------------------------------------------------------- /testing/bdd/features/validation/sequence_id_num.feature: -------------------------------------------------------------------------------- 1 | @validation @syntax @sequence @xsd 2 | Feature: Sequence ID and Sequence Number 3 | Both are mandatory parameters and the sequence number has to be a number (no letters) 4 | 5 | # SPEC-CONFORMANCE: R6 R7 R34 R35 R36 R124 6 | Scenario: Invalid Sequence head attributes 7 | Given an xml file 8 | When it has sequence identifier 9 | And it has sequence number 10 | Then document is invalid 11 | 12 | Examples: 13 | | xml_file | seq_id | seq_n | 14 | | sequence_id_num.xml | | 5 | 15 | | sequence_id_num.xml | testSeq1 | a | 16 | | sequence_id_num.xml | testSeq1 | -5 | 17 | | sequence_id_num.xml | testSeq1 | | 18 | | sequence_id_num.xml | | | 19 | | sequence_id_num.xml | *?Empty?* | | 20 | | sequence_id_num.xml | *?Empty?* | 5 | 21 | 22 | # SPEC-CONFORMANCE: R6 R7 R34 R35 R36 23 | Scenario: Valid Sequence head attributes 24 | Given an xml file 25 | When it has sequence identifier 26 | And it has sequence number 27 | Then document is valid 28 | 29 | Examples: 30 | | xml_file | seq_id | seq_n | 31 | | sequence_id_num.xml | testSeq1 | 5 | 32 | | sequence_id_num.xml | a | 10 | 33 | | sequence_id_num.xml | testSeq1 | 999999999 | 34 | -------------------------------------------------------------------------------- /testing/bdd/features/validation/timeBase_attribute_mandatory.feature: -------------------------------------------------------------------------------- 1 | # Note: contains examples of SMPTE time base. SMPTE was removed from the specification in version 1.0. --> 2 | 3 | 4 | @validation @xsd 5 | Feature: ttp:timeBase attribute is mandatory 6 | Every document shall declare ttp:timeBase 7 | 8 | # SPEC-CONFORMANCE: R32 9 | Scenario: Invalid ttp:timeBase 10 | Given an xml file 11 | When it has ttp:timeBase attribute 12 | Then document is invalid 13 | 14 | Examples: 15 | | xml_file | time_base | 16 | | timeBase_attribute_mandatory.xml | | 17 | | timeBase_attribute_mandatory.xml | *?Empty?* | 18 | | timeBase_attribute_mandatory.xml | hello | 19 | | timeBase_attribute_mandatory.xml | wrong timebase | 20 | 21 | 22 | # SPEC-CONFORMANCE: R32 23 | Scenario: Valid ttp:timeBase 24 | Given an xml file 25 | When it has ttp:timeBase attribute 26 | Then document is valid 27 | 28 | Examples: 29 | | xml_file | time_base | 30 | | timeBase_attribute_mandatory.xml | clock | 31 | | timeBase_attribute_mandatory.xml | media | 32 | | timeBase_attribute_mandatory.xml | smpte | 33 | -------------------------------------------------------------------------------- /testing/bdd/features/validation/timeBase_clock_clockMode_mandatory.feature: -------------------------------------------------------------------------------- 1 | @validation @syntax @xsd 2 | Feature: clockMode attribute is mandatory when timeBase="clock" 3 | 4 | Examples: 5 | | xml_file | 6 | | timeBase_clock_clockMode_mandatory.xml | 7 | 8 | # SPEC-CONFORMANCE: R73b 9 | Scenario: Valid ttp:clockMode 10 | Given an xml file 11 | When it has ttp:timeBase attribute 12 | And it has ttp:clockMode attribute 13 | Then document is valid 14 | 15 | Examples: 16 | | time_base | clock_mode | 17 | | clock | local | 18 | | clock | utc | 19 | | clock | gps | 20 | 21 | 22 | # SPEC-CONFORMANCE: R73b 23 | Scenario: Invalid ttp:clockMode 24 | Given an xml file 25 | When it has ttp:timeBase attribute 26 | And it has ttp:clockMode attribute 27 | Then document is invalid 28 | 29 | Examples: 30 | | time_base | clock_mode | 31 | | clock | | 32 | | clock | *?Empty?* | 33 | | clock | other | 34 | -------------------------------------------------------------------------------- /testing/bdd/features/validation/time_regex_parsing.feature: -------------------------------------------------------------------------------- 1 | @times @validation @parse_times 2 | Feature: Regex parsing TimecountTimingType values 3 | # Regex for other time types are inderectly checked by 4 | 5 | # SPEC-CONFORMANCE: 6 | Scenario: Valid times according to timeBase 7 | Given an xml file 8 | When it has timeBase 9 | And it has body begin time 10 | Then timedelta value given when reading body.begin should be 11 | # The trusted array of timedeltas is defined in test_time_regex_parsing.py. Best way found to check 12 | # the correctness of the parsing. 13 | 14 | Examples: 15 | | xml_file | time_base | body_begin | trusted_timedeltas_index | 16 | | time_regex_parsing.xml | clock | 15h | 0 | 17 | | time_regex_parsing.xml | clock | 30m | 1 | 18 | | time_regex_parsing.xml | clock | 42s | 2 | 19 | | time_regex_parsing.xml | clock | 67ms | 3 | 20 | | time_regex_parsing.xml | clock | 42:05:60.234 | 4 | 21 | | time_regex_parsing.xml | media | 999:09:60.005 | 5 | 22 | -------------------------------------------------------------------------------- /testing/bdd/features/validation/xml_lang_attribute.feature: -------------------------------------------------------------------------------- 1 | @validation @xsd 2 | Feature: xml:lang attribute is mandatory on tt element 3 | 4 | # SPEC-CONFORMANCE: R77 5 | Scenario: Invalid xml:lang attribute 6 | Given an xml file 7 | When it has xml:lang attribute 8 | Then document is invalid 9 | 10 | Examples: 11 | | xml_file | lang | 12 | | xml_lang_attribute.xml | | 13 | 14 | 15 | # SPEC-CONFORMANCE: R77 16 | Scenario: Valid xml:lang attribute 17 | Given an xml file 18 | When it has xml:lang attribute 19 | Then document is valid 20 | 21 | Examples: 22 | | xml_file | lang | 23 | | xml_lang_attribute.xml | *?Empty?* | 24 | | xml_lang_attribute.xml | en-GB | 25 | -------------------------------------------------------------------------------- /testing/bdd/templates/3350_value_types.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | 23 | 54 | 55 | 56 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Some example text... 84 | 85 | And another line 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /testing/bdd/templates/applied-processing.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Some example text... 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /testing/bdd/templates/body_element_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{ body_content }} 10 | 11 | 12 | -------------------------------------------------------------------------------- /testing/bdd/templates/complete_document.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | Fake metadata 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | Some example text... 28 | 29 | And another line 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /testing/bdd/templates/computed_resolved_time_semantics_empty_doc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /testing/bdd/templates/deduplicator_templates/1Sty1Reg4DupAtts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Multiply your anger by about a 17 | 18 | 19 | 20 | hundred, Kate, that's how much he 21 | 22 | 23 | 24 | 25 | 26 | 27 | thinks he loves you. 28 | 29 | 30 | 31 | 32 | 33 | 34 | Multiply your anger by about a 35 | 36 | 37 | 38 | hundred, Kate, that's how much he 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /testing/bdd/templates/deduplicator_templates/3DupSty3DupRegRefs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Multiply your anger by about a 19 | 20 | hundred, Kate, that's how much he 21 | 22 | 23 | 24 | 25 | Multiply your anger by about a 26 | 27 | hundred, Kate, that's how much he 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /testing/bdd/templates/deduplicator_templates/NoStylesNoRegions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Multiply your anger by about a 10 | 11 | 12 | 13 | hundred, Kate, that's how much he 14 | 15 | 16 | 17 | thinks he loves you. 18 | 19 | 20 | 21 | Multiply your anger by about a 22 | 23 | 24 | 25 | hundred, Kate, that's how much he 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /testing/bdd/templates/deduplicator_templates/NoStylesOneRegion.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Multiply your anger by about a 13 | 14 | 15 | 16 | hundred, Kate, that's how much he 17 | 18 | 19 | 20 | thinks he loves you. 21 | 22 | 23 | 24 | Multiply your anger by about a 25 | 26 | 27 | 28 | hundred, Kate, that's how much he 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /testing/bdd/templates/deduplicator_templates/NoStylesTwoDuplicateRegions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Multiply your anger by about a 14 | 15 | 16 | 17 | hundred, Kate, that's how much he 18 | 19 | 20 | 21 | thinks he loves you. 22 | 23 | 24 | 25 | Multiply your anger by about a 26 | 27 | 28 | 29 | hundred, Kate, that's how much he 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /testing/bdd/templates/deduplicator_templates/OneStyleNoRegions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Multiply your anger by about a 13 | 14 | 15 | 16 | hundred, Kate, that's how much he 17 | 18 | 19 | 20 | thinks he loves you. 21 | 22 | 23 | 24 | Multiply your anger by about a 25 | 26 | 27 | 28 | hundred, Kate, that's how much he 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /testing/bdd/templates/deduplicator_templates/OneStyleOneRegion.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Multiply your anger by about a 16 | 17 | 18 | 19 | hundred, Kate, that's how much he 20 | 21 | 22 | 23 | thinks he loves you. 24 | 25 | 26 | 27 | Multiply your anger by about a 28 | 29 | 30 | 31 | hundred, Kate, that's how much he 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /testing/bdd/templates/deduplicator_templates/OneStyleOneRegionWithOneStyleAttr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Multiply your anger by about a 16 | 17 | 18 | 19 | hundred, Kate, that's how much he 20 | 21 | 22 | 23 | thinks he loves you. 24 | 25 | 26 | 27 | Multiply your anger by about a 28 | 29 | 30 | 31 | hundred, Kate, that's how much he 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /testing/bdd/templates/deduplicator_templates/TwoDuplicateStylesNoRegions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Multiply your anger by about a 14 | 15 | 16 | 17 | hundred, Kate, that's how much he 18 | 19 | 20 | 21 | thinks he loves you. 22 | 23 | 24 | 25 | Multiply your anger by about a 26 | 27 | 28 | 29 | hundred, Kate, that's how much he 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /testing/bdd/templates/delayNode.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 34 | 35 | 43 | 44 | 52 | 53 | 61 | Some example text... 62 | 63 | 71 | Some example text 2... 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /testing/bdd/templates/delayTimingType.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Some example text... 33 | 34 | And another line 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /testing/bdd/templates/documentMetadata_elements_order.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{document_metadata}} 7 | 8 | 9 | 10 | 11 | 12 | test 13 | 14 | 15 | 16 | 17 | Some example text... 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /testing/bdd/templates/elements_active_time_semantics_empty_body.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /testing/bdd/templates/facet.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% if document_facets %} 6 | 7 | {{document_facets}} 8 | 9 | {% else %} 10 | 11 | {% endif %} 12 | 13 | 14 | 15 | {% if body_facets %} 16 | 17 | {{body_facets}} 18 | 19 | {% endif %} 20 | 21 | {% if div_facets %} 22 | 23 | {{div_facets}} 24 | 25 | {% endif %} 26 | 27 | {% if p_facets %} 28 | 29 | {{p_facets}} 30 | 31 | {% endif %} 32 | 33 | {% if span_facets %} 34 | 35 | {{span_facets}} 36 | 37 | {% endif %} 38 | Some example text... 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /testing/bdd/templates/handover.xml: -------------------------------------------------------------------------------- 1 | 2 | 28 | 29 | 30 | 31 | Fake metadata 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Some example text... 42 | 43 | And another line 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /testing/bdd/templates/padding.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | Some example text... 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /testing/bdd/templates/padding_data_type.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% if test_padding_syntax %} 9 | 10 | {% else %} 11 | 12 | {% endif %} 13 | 14 | 15 | {% if test_padding_syntax %} 16 | 17 | {% else %} 18 | 19 | {% endif %} 20 | 21 | 22 | 23 | 24 | 25 | Some example text... 26 | 27 | And another line 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /testing/bdd/templates/referenceClockIdentifier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Some example text... 48 | 49 | And another line 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /testing/bdd/templates/segmentation.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Some example text... 32 | And another line 33 | And yet another line 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /testing/bdd/templates/segmentation_short.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Some example text... 31 | And another line 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /testing/bdd/templates/sequence_id_num.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Some example text... 38 | 39 | And another line 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /testing/bdd/templates/sequence_identical_timing_model.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Some example text... 37 | 38 | And another line 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /testing/bdd/templates/smpte.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {% if time_base == 'smpte' %} 56 | 57 | 58 | 59 | {% else %} 60 | 61 | 62 | 63 | {% endif %} 64 | Some example text... 65 | 66 | And another line 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /testing/bdd/templates/style_attribute_inherited.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Some example text... 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /testing/bdd/templates/style_attribute_pairs.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Some example text... 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /testing/bdd/templates/timeBase_attribute_mandatory.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | {% if time_base == "smpte" %} 40 | 41 | 42 | 43 | {% else %} 44 | 45 | 46 | 47 | {% endif %} 48 | Some example text... 49 | 50 | And another line 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /testing/bdd/templates/timeBase_clock_clockMode_mandatory.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Some example text... 32 | 33 | And another line 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /testing/bdd/templates/time_regex_parsing.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | Some example text... 37 | 38 | 39 | And another line 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /testing/bdd/templates/websocket_carriage_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "node1": { 4 | "id": "producer1", 5 | "type": "distributor", 6 | "output": { 7 | "carriage": { 8 | "type": "websocket", 9 | "listen": "ws://localhost:{{ephemeral_port}}" 10 | } 11 | } 12 | }, 13 | "node2": { 14 | "id": "consumer1", 15 | "type": "distributor", 16 | "input": { 17 | "carriage": { 18 | "type": "websocket", 19 | "connect": [ 20 | "ws://localhost:{{ephemeral_port}}/{{client_url_path}}" 21 | ] 22 | } 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /testing/bdd/templates/xml_lang_attribute.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Some example text... 32 | 33 | And another line 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /testing/bdd/test_3350_value_types.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import when, scenarios 2 | 3 | 4 | scenarios('features/validation/3350-value-types.feature') 5 | 6 | 7 | @when('it has tts:color attribute with value ') 8 | def when_color(color, template_dict): 9 | template_dict['color'] = color 10 | 11 | 12 | @when('it has region extent attribute with value ') 13 | def when_extent(extent, template_dict): 14 | template_dict['extent'] = extent 15 | if 'px' in extent: 16 | template_dict['default_root_extent'] = True 17 | 18 | 19 | @when('it has tts:fontSize attribute with value ') 20 | def when_font_size(font_size, template_dict): 21 | template_dict['font_size'] = font_size 22 | if 'px' in font_size: 23 | template_dict['default_root_extent'] = True 24 | 25 | 26 | @when('it has linePadding attribute with value ') 27 | def when_line_padding(line_padding, template_dict): 28 | template_dict['line_padding'] = line_padding 29 | 30 | 31 | @when('it has lineHeight attribute with value ') 32 | def when_line_height(line_height, template_dict): 33 | template_dict['line_height'] = line_height 34 | if 'px' in line_height: 35 | template_dict['default_root_extent'] = True 36 | 37 | 38 | @when('it has origin attribute with value ') 39 | def when_origin(origin, template_dict): 40 | template_dict['origin'] = origin 41 | if 'px' in origin: 42 | template_dict['default_root_extent'] = True 43 | -------------------------------------------------------------------------------- /testing/bdd/test_applied_processing.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import when, scenarios 2 | 3 | 4 | scenarios('features/validation/applied-processing.feature') 5 | 6 | @when('appliedProcessing element has process attribute ') 7 | def when_applied_processing_process_attribute(process, template_dict): 8 | template_dict["process"] = process 9 | 10 | 11 | @when('appliedProcessing element has generatedBy attribute ') 12 | def when_applied_processing_generatedBy_attribute(generated_by, template_dict): 13 | template_dict["generated_by"] = generated_by 14 | 15 | 16 | @when('appliedProcessing element has sourceId attribute ') 17 | def when_applied_processing_sourceId_attribute(source_id, template_dict): 18 | template_dict["source_id"] = source_id 19 | -------------------------------------------------------------------------------- /testing/bdd/test_body_element_content.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import when, scenarios 2 | 3 | 4 | scenarios('features/validation/body_element_content.feature') 5 | 6 | 7 | def handle_element(element): 8 | if element == 'span': 9 | return '' 10 | elif element == 'p': 11 | return '' 12 | elif element == 'br': 13 | return '' 14 | 15 | 16 | @when('its body has a ') 17 | def when_child_element(child_element, template_dict): 18 | template_dict['body_content'] = handle_element(child_element) 19 | -------------------------------------------------------------------------------- /testing/bdd/test_delayTimingType.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import when, scenarios 2 | 3 | 4 | scenarios('features/validation/delayTimingType.feature') 5 | 6 | 7 | @when('ebuttm:authoringDelay attribute has value ') 8 | def when_authoring_delay(authoring_delay, template_dict): 9 | template_dict['authoring_delay'] = authoring_delay 10 | -------------------------------------------------------------------------------- /testing/bdd/test_documentMetadata_elements_order.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import when, scenarios 2 | 3 | 4 | scenarios('features/validation/documentMetadata_elements_order.feature') 5 | 6 | 7 | def handle_document_metadata_element(element): 8 | if element == 'originalSourceServiceIdentifier': 9 | return 'Source identifier\n' 10 | elif element == 'intendedDestinationServiceIdentifier': 11 | return 'Destination identifier\n' 12 | elif element == 'documentFacet': 13 | return 'test\n' 14 | elif element == 'appliedProcessing': 15 | return '\n' 16 | elif element == 'documentIdentifier': 17 | return 'Doc ID' 18 | else: 19 | return '' 20 | 21 | 22 | @when('it has documentMetadata 1 ') 23 | def when_document_metadata_1(document_metadata_1, template_dict): 24 | template_dict['document_metadata'] = "" 25 | template_dict['document_metadata'] += handle_document_metadata_element(document_metadata_1) 26 | 27 | 28 | @when('it has documentMetadata 2 ') 29 | def when_document_metadata_2(document_metadata_2, template_dict): 30 | template_dict['document_metadata'] += handle_document_metadata_element(document_metadata_2) 31 | 32 | 33 | @when('it has documentMetadata 3 ') 34 | def when_document_metadata_3(document_metadata_3, template_dict): 35 | template_dict['document_metadata'] += handle_document_metadata_element(document_metadata_3) 36 | 37 | 38 | @when('it has documentMetadata 4 ') 39 | def when_document_metadata_4(document_metadata_4, template_dict): 40 | template_dict['document_metadata'] += handle_document_metadata_element(document_metadata_4) 41 | -------------------------------------------------------------------------------- /testing/bdd/test_padding_data_type.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import when, scenarios 2 | 3 | 4 | scenarios('features/validation/padding_data_type.feature') 5 | 6 | 7 | @when(' has a padding attribute') 8 | def when_tag_has_padding(tag, template_dict): 9 | if tag == "tt:style": 10 | template_dict["style_padding"] = 'tts:padding="1px"' 11 | elif tag == "tt:region": 12 | template_dict["region_padding"] = 'tts:padding="1px"' 13 | elif tag == "tt:p": 14 | template_dict["p_padding"] = 'tts:padding="1px"' 15 | elif tag == "tt:span": 16 | template_dict["span_padding"] = 'tts:padding="1px"' 17 | 18 | 19 | @when('it has a padding attribute') 20 | def when_padding_attribute(template_dict): 21 | template_dict["test_padding_syntax"] = True 22 | 23 | 24 | @when('the padding attribute has ') 25 | def when_padding_value1(value1, template_dict): 26 | template_dict["value1"] = value1 27 | 28 | 29 | @when('the padding attribute has ') 30 | def when_padding_value2(value2, template_dict): 31 | template_dict["value2"] = value2 32 | 33 | 34 | @when('the padding attribute has ') 35 | def when_padding_value3(value3, template_dict): 36 | template_dict["value3"] = value3 37 | 38 | 39 | @when('the padding attribute has ') 40 | def when_padding_value4(value4, template_dict): 41 | template_dict["value4"] = value4 42 | -------------------------------------------------------------------------------- /testing/bdd/test_referenceClockIdentifier.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import scenarios, when 2 | 3 | scenarios('features/validation/referenceClockIdentifier_constraints.feature') 4 | 5 | 6 | @when('it has timeBase ') 7 | def when_time_base(time_base, template_dict): 8 | template_dict['time_base'] = time_base 9 | 10 | 11 | @when('it has clock mode ') 12 | def when_clock_mode(clock_mode, template_dict): 13 | template_dict['clock_mode'] = clock_mode 14 | 15 | 16 | @when('it has reference clock identifier ') 17 | def when_ref_clock_id(ref_clock_id, template_dict): 18 | template_dict['ref_clock_id'] = ref_clock_id 19 | -------------------------------------------------------------------------------- /testing/bdd/test_sequence_attributes_validation.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import when, scenarios 2 | 3 | 4 | scenarios('features/validation/sequence_id_num.feature') 5 | 6 | 7 | @when('it has sequence identifier ') 8 | def when_sequence_id(seq_id, template_dict): 9 | template_dict['sequence_id'] = seq_id 10 | 11 | 12 | @when('it has sequence number ') 13 | def when_sequence_number(seq_n, template_dict): 14 | template_dict['sequence_num'] = seq_n 15 | -------------------------------------------------------------------------------- /testing/bdd/test_smpte.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import scenarios, when 2 | 3 | scenarios('features/validation/smpte_constraints.feature') 4 | 5 | 6 | @when('it has timeBase ') 7 | def when_time_base(time_base, template_dict): 8 | template_dict['time_base'] = time_base 9 | 10 | 11 | @when('it has frameRate ') 12 | def when_frame_rate(frame_rate, template_dict): 13 | template_dict['frame_rate'] = frame_rate 14 | 15 | 16 | @when('it has frameRateMultiplier ') 17 | def when_frame_rate_multiplier(frame_rate_multiplier, template_dict): 18 | template_dict['frame_rate_multiplier'] = frame_rate_multiplier 19 | 20 | 21 | @when('it has dropMode ') 22 | def when_drop_mode(drop_mode, template_dict): 23 | template_dict['drop_mode'] = drop_mode 24 | 25 | 26 | @when('it has markerMode ') 27 | def when_marker_mode(marker_mode, template_dict): 28 | template_dict['marker_mode'] = marker_mode 29 | -------------------------------------------------------------------------------- /testing/bdd/test_timeBase_attribute_mandatory.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import when, scenarios 2 | 3 | 4 | scenarios('features/validation/timeBase_attribute_mandatory.feature') 5 | 6 | 7 | @when('it has ttp:timeBase attribute ') 8 | def when_has_time_base(time_base, template_dict): 9 | template_dict['time_base'] = time_base 10 | -------------------------------------------------------------------------------- /testing/bdd/test_timeBase_clock_clockMode_mandatory.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import when, scenarios 2 | 3 | 4 | scenarios('features/validation/timeBase_clock_clockMode_mandatory.feature') 5 | 6 | 7 | @when('it has ttp:timeBase attribute ') 8 | def when_has_time_base(time_base, template_dict): 9 | template_dict['time_base'] = time_base 10 | 11 | 12 | @when('it has ttp:clockMode attribute ') 13 | def when_has_clock_mode(clock_mode, template_dict): 14 | template_dict['clock_mode'] = clock_mode 15 | -------------------------------------------------------------------------------- /testing/bdd/test_timeBase_timeformat.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import scenarios, when 2 | 3 | scenarios('features/validation/timeBase_timeformat_constraints.feature') 4 | 5 | 6 | @when('it has timeBase ') 7 | def when_time_base(time_base, template_dict): 8 | template_dict['time_base'] = time_base 9 | 10 | 11 | @when('it has body begin time ') 12 | def when_body_begin(body_begin, template_dict): 13 | template_dict['body_begin'] = body_begin 14 | 15 | 16 | @when('it has body end time ') 17 | def when_body_end(body_end, template_dict): 18 | template_dict['body_end'] = body_end 19 | 20 | 21 | @when('it has body duration ') 22 | def when_body_dur(body_dur, template_dict): 23 | template_dict['body_dur'] = body_dur 24 | 25 | 26 | @when('it has div begin time ') 27 | def when_div_begin(div_begin, template_dict): 28 | template_dict['div_begin'] = div_begin 29 | 30 | 31 | @when('it has div end time ') 32 | def when_div_end(div_end, template_dict): 33 | template_dict['div_end'] = div_end 34 | 35 | 36 | @when('it has p begin time ') 37 | def when_p_begin(p_begin, template_dict): 38 | template_dict['p_begin'] = p_begin 39 | 40 | 41 | @when('it has p end time ') 42 | def when_p_end(p_end, template_dict): 43 | template_dict['p_end'] = p_end 44 | 45 | 46 | @when('it has span begin time ') 47 | def when_span_begin(span_begin, template_dict): 48 | template_dict['span_begin'] = span_begin 49 | 50 | 51 | @when('it has span end time ') 52 | def when_span_end(span_end, template_dict): 53 | template_dict['span_end'] = span_end 54 | 55 | @when('it has documentStartOfProgramme ') 56 | def when_documentStartOfProgramme(start_time, template_dict): 57 | template_dict['start_of_programme'] = start_time 58 | -------------------------------------------------------------------------------- /testing/bdd/test_time_regex_parsing.py: -------------------------------------------------------------------------------- 1 | from ebu_tt_live.documents import EBUTT3Document 2 | from datetime import timedelta 3 | from pytest_bdd import scenarios, when, then 4 | 5 | scenarios('features/validation/time_regex_parsing.feature', example_converters=dict(trusted_timedeltas_index=int)) 6 | 7 | trusted_timedeltas = [ 8 | timedelta(hours=15), 9 | timedelta(minutes=30), 10 | timedelta(seconds=42), 11 | timedelta(milliseconds=67), 12 | timedelta(hours=42, minutes=5, seconds=60, milliseconds=234), 13 | timedelta(hours=999, minutes=9, seconds=60, milliseconds=5) 14 | ] 15 | 16 | 17 | @when('it has timeBase ') 18 | def when_time_base(time_base, template_dict): 19 | template_dict['time_base'] = time_base 20 | 21 | 22 | @when('it has body begin time ') 23 | def when_body_begin(body_begin, template_dict): 24 | template_dict['body_begin'] = body_begin 25 | 26 | 27 | @then('timedelta value given when reading body.begin should be ') 28 | def check_correct_parsing(template_file, template_dict, trusted_timedeltas_index): 29 | xml_file = template_file.render(template_dict) 30 | document = EBUTT3Document.create_from_xml(xml_file) 31 | assert document._ebutt3_content.body.begin.timedelta == trusted_timedeltas[trusted_timedeltas_index] 32 | -------------------------------------------------------------------------------- /testing/bdd/test_websocket_carriage_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from jinja2 import Environment, FileSystemLoader 3 | from pytest_bdd import scenarios, given, when, then 4 | from pytest import fixture 5 | import socket 6 | 7 | 8 | scenarios('features/config/websocket_carriage_config.feature') 9 | 10 | 11 | @fixture 12 | def config_dict(): 13 | return dict() 14 | 15 | 16 | @given('a configuration file ') 17 | def given_config_file(config_file): 18 | cur_dir = os.path.dirname(os.path.abspath(__file__)) 19 | j2_env = Environment(loader=FileSystemLoader(os.path.join(cur_dir, 'templates')), 20 | trim_blocks=True) 21 | return j2_env.get_template(config_file) 22 | 23 | 24 | @when('the configuration file is loaded') 25 | def when_config_loaded(given_config_file, config_dict): 26 | full_config = given_config_file.render(config_dict) 27 | 28 | 29 | @when('a free port has been found') 30 | def when_an_ephemeral_port_is_found(template_dict): 31 | sock = socket.socket() 32 | sock.bind(('', 0)) 33 | template_dict['ephemeral_port'] = sock.getsockname()[1] 34 | sock.close() 35 | 36 | 37 | @when('the producer listens on the port') 38 | def when_producer_listens_port(): 39 | pass 40 | 41 | 42 | @when('the consumer connects to the port with ') 43 | def when_consumer_connects_port(client_url_path): 44 | pass 45 | 46 | 47 | @when('producer sends document with ') 48 | def when_producer_sends_document1(sequence_number_1): 49 | pass 50 | 51 | 52 | @when('producer sends document with ') 53 | def when_producer_sends_document2(sequence_number_2): 54 | pass 55 | 56 | 57 | @then('transmission should be successful') 58 | def then_transmission_successful(): 59 | pass 60 | -------------------------------------------------------------------------------- /testing/bdd/test_xml_lang_attribute.py: -------------------------------------------------------------------------------- 1 | from pytest_bdd import when, scenarios 2 | 3 | 4 | scenarios('features/validation/xml_lang_attribute.feature') 5 | 6 | 7 | @when('it has xml:lang attribute ') 8 | def when_lang(lang, template_dict): 9 | template_dict['lang'] = lang 10 | -------------------------------------------------------------------------------- /testing/example-sequences/Ericsson 2016-09-05/manifest_192.168.56.99 IBC EBUTT3.txt: -------------------------------------------------------------------------------- 1 | 06:08:16.520,192.168.56.99 IBC EBUTT3_434.xml 2 | 06:08:16.764,192.168.56.99 IBC EBUTT3_435.xml 3 | 06:08:16.999,192.168.56.99 IBC EBUTT3_436.xml 4 | 06:08:17.263,192.168.56.99 IBC EBUTT3_437.xml 5 | 06:08:17.512,192.168.56.99 IBC EBUTT3_438.xml 6 | 06:08:17.757,192.168.56.99 IBC EBUTT3_439.xml 7 | 06:08:18.018,192.168.56.99 IBC EBUTT3_440.xml 8 | 06:08:18.271,192.168.56.99 IBC EBUTT3_441.xml 9 | 06:08:18.513,192.168.56.99 IBC EBUTT3_442.xml 10 | 06:08:18.767,192.168.56.99 IBC EBUTT3_443.xml 11 | 06:08:19.018,192.168.56.99 IBC EBUTT3_444.xml 12 | 06:08:19.266,192.168.56.99 IBC EBUTT3_445.xml 13 | 06:08:19.512,192.168.56.99 IBC EBUTT3_446.xml 14 | 06:08:19.756,192.168.56.99 IBC EBUTT3_447.xml 15 | 06:08:20.010,192.168.56.99 IBC EBUTT3_448.xml 16 | 06:08:20.267,192.168.56.99 IBC EBUTT3_449.xml 17 | 06:08:24.713,192.168.56.99 IBC EBUTT3_450.xml 18 | -------------------------------------------------------------------------------- /testing/example-sequences/Ericsson 2016-09-06/manifest_localhost EbuTT3 TestSeq.txt: -------------------------------------------------------------------------------- 1 | 12:11:53.0,1.xml 2 | 12:11:57.0,2.xml 3 | 12:11:57.5,3.xml 4 | 12:11:58.0,4.xml 5 | -------------------------------------------------------------------------------- /testing/test_consumer.py: -------------------------------------------------------------------------------- 1 | 2 | from unittest import TestCase 3 | from datetime import timedelta 4 | from ebu_tt_live.bindings._ebuttdt import LimitedClockTimingType 5 | from ebu_tt_live.documents.ebutt3 import EBUTT3Document 6 | from ebu_tt_live.node import SimpleConsumer 7 | from ebu_tt_live.carriage.interface import IConsumerCarriage 8 | from mock import MagicMock 9 | 10 | 11 | class SCDocumentProcessingTest(TestCase): 12 | 13 | def _get_timing_type(self, value): 14 | return LimitedClockTimingType(value) 15 | 16 | def _create_document(self, sequence_number, begin, end): 17 | doc = EBUTT3Document( 18 | time_base='clock', 19 | clock_mode='local', 20 | lang='en-gb', 21 | sequence_identifier='ConsumerTest', 22 | sequence_number=sequence_number 23 | ) 24 | doc.set_begin(self._get_timing_type(timedelta(seconds=begin))) 25 | doc.set_end(self._get_timing_type(timedelta(seconds=end))) 26 | doc.availability_time = timedelta() 27 | return doc 28 | 29 | def _create_simple_consumer(self): 30 | carriage = MagicMock(spec=IConsumerCarriage) 31 | carriage.provides.return_value = EBUTT3Document 32 | self.consumer = SimpleConsumer( 33 | 'consumer-testing', 34 | consumer_carriage=carriage 35 | ) 36 | 37 | def setUp(self): 38 | self._create_simple_consumer() 39 | 40 | def test_duplicate_sequence_number_insert(self): 41 | doc1 = self._create_document(1, 1, 2) 42 | doc2 = self._create_document(2, 3, 4) 43 | doc3 = self._create_document(3, 4, 5) 44 | # Here is the duplicate 45 | doc4 = self._create_document(2, 2, 3) 46 | self.consumer.process_document(doc1) 47 | self.consumer.process_document(doc2) 48 | self.consumer.process_document(doc3) 49 | # This should work regardless of duplication. Ignoring this document 50 | self.consumer.process_document(doc4) -------------------------------------------------------------------------------- /testing/test_integration.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def test_dummy(): 4 | print 'test the testing' 5 | assert 1 6 | --------------------------------------------------------------------------------