diff --git a/src/main/python/ttconv/imsc/reader.py b/src/main/python/ttconv/imsc/reader.py
index 6258c049..12b5f499 100644
--- a/src/main/python/ttconv/imsc/reader.py
+++ b/src/main/python/ttconv/imsc/reader.py
@@ -25,8 +25,10 @@
'''IMSC reader'''
+import io
import logging
import typing
+import xml.etree.ElementTree as et
import ttconv.imsc.elements as imsc_elements
import ttconv.model as model
@@ -34,10 +36,11 @@
LOGGER = logging.getLogger(__name__)
-def to_model(xml_tree, progress_callback=lambda _: None) -> typing.Optional[model.ContentDocument]:
+def to_model(data_file: typing.BinaryIO, _config=None, progress_callback=lambda _: None) -> typing.Optional[model.ContentDocument]:
'''Convers an IMSC document to the data model'''
- xml_element = xml_tree.getroot()
+ text_stream = io.TextIOWrapper(data_file, encoding='utf-8')
+ xml_element = et.parse(text_stream).getroot()
if not imsc_elements.TTElement.is_instance(xml_element):
LOGGER.fatal("A tt element is not the root element")
diff --git a/src/main/python/ttconv/imsc/writer.py b/src/main/python/ttconv/imsc/writer.py
index c22284af..092bf26f 100644
--- a/src/main/python/ttconv/imsc/writer.py
+++ b/src/main/python/ttconv/imsc/writer.py
@@ -43,13 +43,14 @@
def from_model(
model_doc: model.ContentDocument,
+ output: typing.BinaryIO,
config: typing.Optional[imsc_config.IMSCWriterConfiguration] = None,
progress_callback: typing.Callable[[numbers.Real], typing.NoReturn] = lambda _: None
):
'''Converts the data model to an IMSC document. The writer regularly the `progress_callback` function, if provided,
with a real between 0 and 1, indicating the relative progress of the process.
'''
-
+
et.register_namespace("", xml_ns.TTML)
et.register_namespace("ttp", xml_ns.TTP)
et.register_namespace("tts", xml_ns.TTS)
@@ -78,7 +79,7 @@ def from_model(
else:
time_format = TimeExpressionSyntaxEnum.clock_time
- return et.ElementTree(
+ et.ElementTree(
imsc_elements.TTElement.from_model(
model_doc,
config.fps,
@@ -86,4 +87,4 @@ def from_model(
progress_callback,
config.profile_signaling
)
- )
+ ).write(output, encoding="utf-8")
diff --git a/src/main/python/ttconv/scc/reader.py b/src/main/python/ttconv/scc/reader.py
index fa4fe855..32c96d5e 100644
--- a/src/main/python/ttconv/scc/reader.py
+++ b/src/main/python/ttconv/scc/reader.py
@@ -28,6 +28,7 @@
from __future__ import annotations
import logging
+import typing
from typing import Optional
from ttconv.model import ContentDocument, Body, Div, CellResolutionType, ActiveAreaType
@@ -45,9 +46,11 @@
# SCC reader
#
-def to_model(scc_content: str, config: Optional[SccReaderConfiguration] = None, progress_callback=lambda _: None):
+def to_model(data_file: typing.BinaryIO, config: Optional[SccReaderConfiguration] = None, progress_callback=lambda _: None):
"""Converts a SCC document to the data model"""
+ scc_content = data_file.read().decode("utf-8")
+
document = ContentDocument()
# Safe area must be a 32x15 grid, that represents 80% of the root area
@@ -104,8 +107,19 @@ def to_model(scc_content: str, config: Optional[SccReaderConfiguration] = None,
return document
-def to_disassembly(scc_content: str, show_channels = False) -> str:
- """Dumps an SCC document into the disassembly format"""
+def to_disassembly(scc_content: str | bytes, show_channels = False) -> str:
+ """Converts SCC content into a human-readable disassembly string.
+
+ Args:
+ scc_content: SCC file content as a string or UTF-8 encoded bytes.
+ show_channels: If True, include the channel number for each data word.
+
+ Returns:
+ A string with one disassembled line per SCC line, terminated by newlines.
+ """
+ if isinstance(scc_content, bytes):
+ scc_content = scc_content.decode("utf-8")
+
disassembly = ""
for line in scc_content.splitlines():
LOGGER.debug(line)
diff --git a/src/main/python/ttconv/scc/writer.py b/src/main/python/ttconv/scc/writer.py
index 55400f0e..c55ea618 100644
--- a/src/main/python/ttconv/scc/writer.py
+++ b/src/main/python/ttconv/scc/writer.py
@@ -30,6 +30,7 @@
import logging
from fractions import Fraction
import re
+import typing
from typing import List, Optional, Sequence
import ttconv.model as model
@@ -236,7 +237,7 @@ def _octet2hex(octet):
#
# scc writer
#
-def from_model(doc: model.ContentDocument, config: Optional[SccWriterConfiguration] = None, progress_callback=lambda _: None) -> str:
+def from_model(doc: model.ContentDocument, output: typing.BinaryIO, config: Optional[SccWriterConfiguration] = None, progress_callback=lambda _: None):
"""Converts the data model to an SCC document"""
# split progress between ISD construction and SCC writing
@@ -420,4 +421,4 @@ def _isd_progress(progress: float):
if start_offset + chunk.get_begin() < 0:
raise RuntimeError("The SCC stream would start earlier than the specified start timecode")
- return "Scenarist_SCC V1.0\n\n" + "\n\n".join(map(lambda e: e.to_string(config.frame_rate.fps, config.frame_rate.df, start_offset), chunks))
+ output.write(b"Scenarist_SCC V1.0\n\n" + b"\n\n".join(map(lambda e: e.to_string(config.frame_rate.fps, config.frame_rate.df, start_offset).encode("utf-8"), chunks)))
diff --git a/src/main/python/ttconv/srt/reader.py b/src/main/python/ttconv/srt/reader.py
index 5b1b89eb..e398142b 100644
--- a/src/main/python/ttconv/srt/reader.py
+++ b/src/main/python/ttconv/srt/reader.py
@@ -28,6 +28,7 @@
from __future__ import annotations
import typing
+import io
import re
import logging
from enum import Enum
@@ -208,7 +209,7 @@ class _State(Enum):
_DEFAULT_LINE_HEIGHT = styles.LengthType(125, styles.LengthType.Units.pct)
_DEFAULT_SAFE_AREA_PCT = 10
-def to_model(data_file: typing.IO, _config: SRTReaderConfiguration = None, progress_callback=lambda _: None):
+def to_model(data_file: typing.BinaryIO, _config: SRTReaderConfiguration = None, progress_callback=lambda _: None):
"""Converts an SRT document to the data model"""
extended_tags = _config.extended_tags if isinstance(_config, SRTReaderConfiguration) else False
@@ -235,7 +236,7 @@ def to_model(data_file: typing.IO, _config: SRTReaderConfiguration = None, progr
body.push_child(div)
- lines : str = data_file.readlines()
+ lines : str = io.TextIOWrapper(data_file, encoding='utf-8').readlines()
state = _State.COUNTER
current_p = None
diff --git a/src/main/python/ttconv/srt/writer.py b/src/main/python/ttconv/srt/writer.py
index fd43ac17..2395f8ae 100644
--- a/src/main/python/ttconv/srt/writer.py
+++ b/src/main/python/ttconv/srt/writer.py
@@ -26,6 +26,7 @@
"""SRT writer"""
import logging
+import typing
from fractions import Fraction
from typing import List, Optional
@@ -186,8 +187,8 @@ def __str__(self) -> str:
#
-def from_model(doc: model.ContentDocument, config: Optional[SRTWriterConfiguration] = None, progress_callback=lambda _: None) -> str:
- """Converts the data model to a SRT document"""
+def from_model(doc: model.ContentDocument, output: typing.BinaryIO, config: Optional[SRTWriterConfiguration] = None, progress_callback=lambda _: None):
+ """Converts the data model to a SRT document, writing the result to `output`."""
srt = SrtContext(config if config is not None else SRTWriterConfiguration())
@@ -217,4 +218,4 @@ def _isd_progress(progress: float):
srt.finish()
- return str(srt)
+ output.write(str(srt).encode("utf-8"))
diff --git a/src/main/python/ttconv/stl/reader.py b/src/main/python/ttconv/stl/reader.py
index 8715e34a..163c387a 100644
--- a/src/main/python/ttconv/stl/reader.py
+++ b/src/main/python/ttconv/stl/reader.py
@@ -40,7 +40,7 @@
# STL reader
#
-def to_model(data_file: typing.IO, config: typing.Optional[STLReaderConfiguration] = None, progress_callback=lambda _: None):
+def to_model(data_file: typing.BinaryIO, config: typing.Optional[STLReaderConfiguration] = None, progress_callback=lambda _: None):
"""Converts an STL document to the data model"""
m = DataFile(
diff --git a/src/main/python/ttconv/tt.py b/src/main/python/ttconv/tt.py
index 074f2b2c..d027b7cd 100755
--- a/src/main/python/ttconv/tt.py
+++ b/src/main/python/ttconv/tt.py
@@ -30,10 +30,8 @@
import os
import sys
import typing
-import xml.etree.ElementTree as et
from argparse import ArgumentParser
from enum import Enum
-from pathlib import Path
from ttconv.filters.document_filter import DocumentFilter
import ttconv.imsc.reader as imsc_reader
@@ -300,28 +298,25 @@ def convert(args):
writer_type = FileTypes.get_file_type(args.otype, output_file_extension)
if reader_type is FileTypes.TTML:
- #
- # Parse the xml input file into an ElementTree
#
- tree = et.parse(inputfile)
-
- #
- # Pass the parsed xml to the reader
+ # Open the file and pass it to the reader
#
- model = imsc_reader.to_model(tree, progress_callback_read)
+ reader_config = read_config_from_json(IMSCWriterConfiguration, json_config_data)
- elif reader_type is FileTypes.SCC:
- file_as_str = Path(inputfile).read_text()
+ with open(inputfile, "rb") as f:
+ model = imsc_reader.to_model(f, reader_config, progress_callback_read)
+ elif reader_type is FileTypes.SCC:
#
# Read the config
#
reader_config = read_config_from_json(SccReaderConfiguration, json_config_data)
#
- # Pass the parsed xml to the reader
+ # Open the file and pass it to the reader
#
- model = scc_reader.to_model(file_as_str, reader_config, progress_callback_read)
+ with open(inputfile, "rb") as f:
+ model = scc_reader.to_model(f, reader_config, progress_callback_read)
elif reader_type is FileTypes.STL:
#
@@ -344,7 +339,7 @@ def convert(args):
#
# Open the file and pass it to the reader
#
- with open(inputfile, "r", encoding="utf-8") as f:
+ with open(inputfile, "rb") as f:
model = srt_reader.to_model(f, reader_config, progress_callback_read)
elif reader_type is FileTypes.VTT:
@@ -352,7 +347,7 @@ def convert(args):
#
# Open the file and pass it to the reader
#
- with open(inputfile, "r", encoding="utf-8") as f:
+ with open(inputfile, "rb") as f:
model = vtt_reader.to_model(f, None, progress_callback_read)
else:
@@ -401,12 +396,11 @@ def convert(args):
#
# Construct and configure the writer
#
- tree_from_model = imsc_writer.from_model(model, writer_config, progress_callback_write)
-
#
# Write out the converted file
#
- tree_from_model.write(outputfile, encoding="utf-8")
+ with open(outputfile, "wb") as f:
+ imsc_writer.from_model(model, f, writer_config, progress_callback_write)
elif writer_type is FileTypes.SRT:
#
@@ -417,13 +411,8 @@ def convert(args):
#
# Construct and configure the writer
#
- srt_document = srt_writer.from_model(model, writer_config, progress_callback_write)
-
- #
- # Write out the converted file
- #
- with open(outputfile, "w", encoding="utf-8") as srt_file:
- srt_file.write(srt_document)
+ with open(outputfile, "wb") as srt_file:
+ srt_writer.from_model(model, srt_file, writer_config, progress_callback_write)
elif writer_type is FileTypes.VTT:
#
@@ -434,13 +423,11 @@ def convert(args):
#
# Construct and configure the writer
#
- vtt_document = vtt_writer.from_model(model, writer_config, progress_callback_write)
-
#
# Write out the converted file
#
- with open(outputfile, "w", encoding="utf-8") as vtt_file:
- vtt_file.write(vtt_document)
+ with open(outputfile, "wb") as vtt_file:
+ vtt_writer.from_model(model, vtt_file, writer_config, progress_callback_write)
elif writer_type is FileTypes.SCC:
#
@@ -451,13 +438,11 @@ def convert(args):
#
# Construct and configure the writer
#
- scc_document = scc_writer.from_model(model, writer_config, progress_callback_write)
-
#
# Write out the converted file
#
- with open(outputfile, "w", encoding="utf-8") as scc_file:
- scc_file.write(scc_document)
+ with open(outputfile, "wb") as scc_file:
+ scc_writer.from_model(model, scc_file, writer_config, progress_callback_write)
else:
diff --git a/src/main/python/ttconv/vtt/reader.py b/src/main/python/ttconv/vtt/reader.py
index 7682632e..a16ec845 100644
--- a/src/main/python/ttconv/vtt/reader.py
+++ b/src/main/python/ttconv/vtt/reader.py
@@ -28,6 +28,7 @@
from __future__ import annotations
from dataclasses import dataclass
+import io
import typing
import re
import logging
@@ -518,7 +519,7 @@ def vtt_timestamp_to_secs(vtt_ts: str):
return None
-def to_model(data_file: typing.IO, _config = None, progress_callback=lambda _: None):
+def to_model(data_file: typing.BinaryIO, _config = None, progress_callback=lambda _: None):
"""Converts a WebVTT document to the data model"""
class _State(Enum):
@@ -539,7 +540,7 @@ class _State(Enum):
div = model.Div(doc)
body.push_child(div)
- lines : str = data_file.readlines()
+ lines : str = io.TextIOWrapper(data_file, encoding='utf-8').readlines()
state = _State.START
current_p = None
diff --git a/src/main/python/ttconv/vtt/writer.py b/src/main/python/ttconv/vtt/writer.py
index 2a079e61..76f290c7 100644
--- a/src/main/python/ttconv/vtt/writer.py
+++ b/src/main/python/ttconv/vtt/writer.py
@@ -26,6 +26,7 @@
"""WebVTT writer"""
import logging
+import typing
from fractions import Fraction
from typing import Dict, List, Optional
@@ -269,7 +270,7 @@ def __str__(self) -> str:
#
-def from_model(doc: model.ContentDocument, config = None, progress_callback=lambda _: None) -> str:
+def from_model(doc: model.ContentDocument, output: typing.BinaryIO, config=None, progress_callback=lambda _: None):
"""Converts the data model to a VTT document"""
# split progress between ISD construction and VTT writing
@@ -293,4 +294,4 @@ def _isd_progress(progress: float):
vtt.finish()
- return str(vtt)
+ output.write(str(vtt).encode("utf-8"))
diff --git a/src/test/python/test_imsc11text_filter.py b/src/test/python/test_imsc11text_filter.py
index d960fc93..fbf0a27a 100644
--- a/src/test/python/test_imsc11text_filter.py
+++ b/src/test/python/test_imsc11text_filter.py
@@ -35,8 +35,6 @@
import unittest
import io
from fractions import Fraction
-import xml.etree.ElementTree as et
-
import ttconv.imsc.reader as imsc_reader
import ttconv.model as model
import ttconv.style_properties as styles
@@ -98,7 +96,7 @@ def test_valid_document_passes(self):
self.assertIn(IMSC_11_TEXT_PROFILE_DESIGNATOR, doc.get_content_profiles())
def test_valid_vtt_document_passes(self):
- f = io.StringIO("WEBVTT\n\n00:00:00.000 --> 00:00:05.000\nHello world\n")
+ f = io.BytesIO(b"WEBVTT\n\n00:00:00.000 --> 00:00:05.000\nHello world\n")
doc = to_model(f)
filt = IMSC11TextFilter()
filt.process(doc)
@@ -610,8 +608,8 @@ def test_imsc_1_test_suite(self):
if ext == ".ttml":
with self.subTest(name):
logging.getLogger().info("*****dummy*****") # dummy log
- tree = et.parse(os.path.join(root, filename))
- model = imsc_reader.to_model(tree)
+ with open(os.path.join(root, filename), 'rb') as f:
+ model = imsc_reader.to_model(f)
self.assertIsNotNone(model)
filt = IMSC11TextFilter()
filt.process(model)
@@ -624,8 +622,8 @@ def test_imsc_1_1_test_suite(self):
if ext == ".ttml":
with self.subTest(name):
logging.getLogger().info("*****dummy*****") # dummy log
- tree = et.parse(os.path.join(root, filename))
- model = imsc_reader.to_model(tree)
+ with open(os.path.join(root, filename), 'rb') as f:
+ model = imsc_reader.to_model(f)
self.assertIsNotNone(model)
filt = IMSC11TextFilter()
filt.process(model)
@@ -636,8 +634,8 @@ def test_imsc_1_3_test_suite(self):
(name, ext) = os.path.splitext(filename)
if ext == ".ttml":
with self.subTest(name):
- tree = et.parse(os.path.join(root, filename))
- model = imsc_reader.to_model(tree)
+ with open(os.path.join(root, filename), 'rb') as f:
+ model = imsc_reader.to_model(f)
self.assertIsNotNone(model)
filt = IMSC11TextFilter()
with self.assertRaises(ValueError):
diff --git a/src/test/python/test_imsc_reader.py b/src/test/python/test_imsc_reader.py
index d131faa5..3b05df7f 100644
--- a/src/test/python/test_imsc_reader.py
+++ b/src/test/python/test_imsc_reader.py
@@ -27,6 +27,7 @@
# pylint: disable=R0201,C0115,C0116
+import io
import unittest
import xml.etree.ElementTree as et
import os
@@ -44,7 +45,7 @@ class IMSCReaderTest(unittest.TestCase):
def test_reader_tt_element_not_root_element(self):
- xml_str = """
+ xml_str = b"""
24f = 1.001s
p = list(list(doc.get_body())[0])[3] @@ -235,7 +235,7 @@ def test_frame_rate(self): self.assertEqual(p.get_end(), Fraction(4394201, 1000)) def test_ooo_set_element(self): - xml_str = """ + xml_str = b""" @@ -246,11 +246,9 @@ def test_ooo_set_element(self):