pycaption 2.3.10__tar.gz → 2.3.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pycaption-2.3.10/pycaption.egg-info → pycaption-2.3.12}/PKG-INFO +1 -1
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/__init__.py +1 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/base.py +3 -2
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/dfxp/constants.py +1 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/dfxp/extras.py +2 -1
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/dfxp/reader.py +6 -6
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/dfxp/writer.py +5 -3
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/geometry.py +3 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/microdvd.py +1 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/sami/constants.py +1 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/sami/reader.py +1 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/sami/writer.py +1 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/scc/__init__.py +1 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/scc/constants.py +5 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/scc/reader.py +21 -2
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/scc/specialized_collections.py +429 -58
- pycaption-2.3.12/pycaption/scc/state_machines.py +413 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/scc/writer.py +37 -13
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/srt.py +20 -4
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/transcript.py +7 -1
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/webvtt/__init__.py +1 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/webvtt/constants.py +1 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/webvtt/reader.py +1 -1
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/webvtt/writer.py +3 -1
- {pycaption-2.3.10 → pycaption-2.3.12/pycaption.egg-info}/PKG-INFO +1 -1
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption.egg-info/SOURCES.txt +0 -1
- pycaption-2.3.12/setup.cfg +4 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/setup.py +3 -2
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/conftest.py +41 -7
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/scc.py +548 -7
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/webvtt.py +1 -1
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/mixins.py +1 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_base.py +72 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_bytes_input.py +0 -2
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_dfxp.py +1 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_dfxp_conversion.py +4 -3
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_double_encoding.py +1 -3
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_sami.py +2 -8
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_sami_conversion.py +4 -7
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_scc.py +718 -40
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_scc_conversion.py +86 -24
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_scc_translator.py +1 -3
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_scc_writer.py +107 -49
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_srt.py +66 -1
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_webvtt.py +12 -32
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_webvtt_conversion.py +21 -61
- pycaption-2.3.10/pycaption/scc/state_machines.py +0 -207
- pycaption-2.3.10/setup.cfg +0 -14
- {pycaption-2.3.10 → pycaption-2.3.12}/LICENSE +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/MANIFEST.in +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/README.rst +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/dfxp/__init__.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/exceptions.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/sami/__init__.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/sami/parser.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/scc/translator.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/utils.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption.egg-info/dependency_links.txt +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption.egg-info/requires.txt +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/pycaption.egg-info/top_level.txt +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/__init__.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/__init__.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/dfxp.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/microdvd.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/sami.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/srt.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/translated_scc.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_dfxp_extras.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_functions.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_geometry.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_microdvd.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_microdvd_conversion.py +0 -0
- {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_srt_conversion.py +0 -0
|
@@ -7,11 +7,13 @@ CaptionConverter orchestrator and base classes for readers/writers.
|
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
9
|
import os
|
|
10
|
+
|
|
10
11
|
from datetime import timedelta
|
|
11
12
|
from numbers import Number
|
|
12
13
|
|
|
13
14
|
from .exceptions import CaptionReadError, CaptionReadTimingError, InvalidInputError
|
|
14
15
|
|
|
16
|
+
|
|
15
17
|
logger = logging.getLogger(__name__)
|
|
16
18
|
|
|
17
19
|
# `und` a special identifier for an undetermined language according to ISO 639-2
|
|
@@ -40,8 +42,7 @@ class CaptionConverter:
|
|
|
40
42
|
"""
|
|
41
43
|
if not hasattr(caption_reader, "read"):
|
|
42
44
|
raise InvalidInputError(
|
|
43
|
-
"The caption_reader must be a BaseReader instance "
|
|
44
|
-
"with a read() method."
|
|
45
|
+
"The caption_reader must be a BaseReader instance with a read() method."
|
|
45
46
|
)
|
|
46
47
|
self.captions = caption_reader.read(content)
|
|
47
48
|
return self
|
|
@@ -13,6 +13,7 @@ from ..base import BaseWriter, CaptionNode, merge_concurrent_captions
|
|
|
13
13
|
from .constants import DFXP_DEFAULT_REGION
|
|
14
14
|
from .writer import DFXPWriter
|
|
15
15
|
|
|
16
|
+
|
|
16
17
|
LEGACY_DFXP_BASE_MARKUP = """
|
|
17
18
|
<tt xmlns="http://www.w3.org/ns/ttml"
|
|
18
19
|
xmlns:tts="http://www.w3.org/ns/ttml#styling">
|
|
@@ -255,7 +256,7 @@ class LegacyDFXPWriter(BaseWriter):
|
|
|
255
256
|
|
|
256
257
|
return line
|
|
257
258
|
|
|
258
|
-
def _recreate_style(self, content, dfxp):
|
|
259
|
+
def _recreate_style(self, content, dfxp): # noqa: C901
|
|
259
260
|
"""Convert an internal style dict to DFXP/TTS attributes.
|
|
260
261
|
|
|
261
262
|
:type content: dict
|
|
@@ -47,6 +47,7 @@ from .constants import (
|
|
|
47
47
|
VERTICAL_ALIGNMENT_TO_DFXP,
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
+
|
|
50
51
|
_LEADING_WHITESPACE_RE = re.compile("^(?:[\n\r]+\\s*)?(.+)")
|
|
51
52
|
|
|
52
53
|
_DFXP_WRITING_MODE_MAP = {
|
|
@@ -147,7 +148,8 @@ class DFXPReader(BaseReader):
|
|
|
147
148
|
style_dict[id_] = self._convert_style(style)
|
|
148
149
|
|
|
149
150
|
caption_set = CaptionSet(
|
|
150
|
-
caption_dict,
|
|
151
|
+
caption_dict,
|
|
152
|
+
styles=style_dict,
|
|
151
153
|
visual_alignment_default=HorizontalAlignmentEnum.START,
|
|
152
154
|
)
|
|
153
155
|
|
|
@@ -179,8 +181,7 @@ class DFXPReader(BaseReader):
|
|
|
179
181
|
tickrate = float(tt_attrs["ttp:tickrate"])
|
|
180
182
|
except ValueError:
|
|
181
183
|
raise CaptionReadSyntaxError(
|
|
182
|
-
f"ttp:tickRate must be a number, "
|
|
183
|
-
f"got '{tt_attrs['ttp:tickrate']}'"
|
|
184
|
+
f"ttp:tickRate must be a number, got '{tt_attrs['ttp:tickrate']}'"
|
|
184
185
|
)
|
|
185
186
|
if tickrate <= 0:
|
|
186
187
|
raise CaptionReadSyntaxError(
|
|
@@ -203,8 +204,7 @@ class DFXPReader(BaseReader):
|
|
|
203
204
|
framerate_int = int(framerate_str)
|
|
204
205
|
except ValueError:
|
|
205
206
|
raise CaptionReadSyntaxError(
|
|
206
|
-
f"ttp:frameRate must be a positive integer, "
|
|
207
|
-
f"got '{framerate_str}'"
|
|
207
|
+
f"ttp:frameRate must be a positive integer, got '{framerate_str}'"
|
|
208
208
|
)
|
|
209
209
|
self.tickrate = float(framerate_int * sub_framerate)
|
|
210
210
|
|
|
@@ -396,7 +396,7 @@ class DFXPReader(BaseReader):
|
|
|
396
396
|
self._convert_tag_to_node(a)
|
|
397
397
|
|
|
398
398
|
@staticmethod
|
|
399
|
-
def _convert_style(tag):
|
|
399
|
+
def _convert_style(tag): # noqa: C901
|
|
400
400
|
"""Convert DFXP/TTS style attributes on a tag to an internal style dict.
|
|
401
401
|
|
|
402
402
|
Maps tts:fontStyle, tts:fontWeight, tts:textDecoration, tts:textAlign,
|
|
@@ -5,6 +5,7 @@ including style elements, region-based positioning, and writing direction.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import re
|
|
8
|
+
|
|
8
9
|
from copy import deepcopy
|
|
9
10
|
from xml.sax.saxutils import escape
|
|
10
11
|
|
|
@@ -26,6 +27,7 @@ from .constants import (
|
|
|
26
27
|
_create_external_alignment,
|
|
27
28
|
)
|
|
28
29
|
|
|
30
|
+
|
|
29
31
|
_WRITING_DIRECTION_TO_DFXP = {
|
|
30
32
|
WritingDirectionEnum.VERTICAL_RL: "tbrl",
|
|
31
33
|
WritingDirectionEnum.VERTICAL_LR: "tblr",
|
|
@@ -213,7 +215,7 @@ class DFXPWriter(BaseWriter):
|
|
|
213
215
|
|
|
214
216
|
return p
|
|
215
217
|
|
|
216
|
-
def _recreate_text(self, caption, dfxp, caption_set=None, lang=None):
|
|
218
|
+
def _recreate_text(self, caption, dfxp, caption_set=None, lang=None): # noqa: C901
|
|
217
219
|
"""Serialize all nodes of a caption into DFXP inline markup.
|
|
218
220
|
|
|
219
221
|
Handles text nodes, line breaks (<br/>), and style spans. Nodes are
|
|
@@ -756,7 +758,7 @@ def _text_shadow_to_outline(value):
|
|
|
756
758
|
return thickness
|
|
757
759
|
|
|
758
760
|
|
|
759
|
-
def _recreate_style(content, dfxp):
|
|
761
|
+
def _recreate_style(content, dfxp): # noqa: C901
|
|
760
762
|
"""Convert an internal style dict to DFXP/TTS style attributes.
|
|
761
763
|
|
|
762
764
|
Maps pycaption's internal keys (class, italics, bold, underline, color,
|
|
@@ -807,7 +809,7 @@ def _recreate_style(content, dfxp):
|
|
|
807
809
|
return dfxp_style
|
|
808
810
|
|
|
809
811
|
|
|
810
|
-
def _convert_layout_to_attributes(layout, fallback_alignment=None):
|
|
812
|
+
def _convert_layout_to_attributes(layout, fallback_alignment=None): # noqa: C901
|
|
811
813
|
"""Convert a Layout object to a dict of DFXP region attributes.
|
|
812
814
|
|
|
813
815
|
Maps origin, extent, padding, alignment, and writing_direction to their
|
|
@@ -7,12 +7,15 @@ CONVENTIONS:
|
|
|
7
7
|
responsible for the recalculation should return a new object with the
|
|
8
8
|
necessary modifications.
|
|
9
9
|
"""
|
|
10
|
+
|
|
10
11
|
import re
|
|
12
|
+
|
|
11
13
|
from enum import Enum
|
|
12
14
|
from functools import total_ordering
|
|
13
15
|
|
|
14
16
|
from .exceptions import CaptionReadSyntaxError, RelativizationError
|
|
15
17
|
|
|
18
|
+
|
|
16
19
|
_UNIT_MISMATCH_MSG = "The sizes should have the same measure units."
|
|
17
20
|
|
|
18
21
|
|
|
@@ -11,6 +11,7 @@ from ..exceptions import CaptionReadNoCaptions, CaptionReadTimingError
|
|
|
11
11
|
from ..geometry import Alignment, HorizontalAlignmentEnum, Layout, Padding, Size
|
|
12
12
|
from .parser import SAMIParser
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
_TAG_TO_STYLE = {"i": "italics", "b": "bold", "u": "underline"}
|
|
15
16
|
|
|
16
17
|
|
|
@@ -7,8 +7,10 @@ background color codes, and derived inverse lookup tables.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import re as _re
|
|
10
|
+
|
|
10
11
|
from itertools import product
|
|
11
12
|
|
|
13
|
+
|
|
12
14
|
COMMANDS = {
|
|
13
15
|
"9420": "",
|
|
14
16
|
"9429": "",
|
|
@@ -1317,6 +1319,9 @@ SPECIAL_OR_EXTENDED_CHAR_TO_CODE = {
|
|
|
1317
1319
|
SPECIAL_OR_EXTENDED_CHAR_TO_CODE.update(
|
|
1318
1320
|
{character: code for code, character in SPECIAL_CHARS.items()}
|
|
1319
1321
|
)
|
|
1322
|
+
# A non-breaking space is what CEA-608's transparent space is: a blank column
|
|
1323
|
+
# that is not a break opportunity. Unmapped, it fell back to the pound sign.
|
|
1324
|
+
SPECIAL_OR_EXTENDED_CHAR_TO_CODE["\xa0"] = "91b9"
|
|
1320
1325
|
|
|
1321
1326
|
# Time to transmit a single codeword = 1 second / 29.97
|
|
1322
1327
|
MICROSECONDS_PER_CODEWORD = 1000.0 * 1000.0 / (30.0 * 1000.0 / 1001.0)
|
|
@@ -78,6 +78,7 @@ http://www.theneitherworld.com/mcpoodle/SCC_TOOLS/DOCS/SCC_FORMAT.HTML
|
|
|
78
78
|
"""
|
|
79
79
|
|
|
80
80
|
import re
|
|
81
|
+
|
|
81
82
|
from collections import deque
|
|
82
83
|
from copy import deepcopy
|
|
83
84
|
|
|
@@ -108,6 +109,7 @@ from .specialized_collections import (
|
|
|
108
109
|
)
|
|
109
110
|
from .state_machines import DefaultProvidingPositionTracker
|
|
110
111
|
|
|
112
|
+
|
|
111
113
|
_TIMECODE_RE = re.compile(r"\d{2}:\d{2}:\d{2}[:;](\d{1,2})")
|
|
112
114
|
|
|
113
115
|
|
|
@@ -242,7 +244,7 @@ class SCCReader(BaseReader):
|
|
|
242
244
|
def _validate_line_lengths(self):
|
|
243
245
|
"""Raise CaptionLineLengthError if any line exceeds 32 characters."""
|
|
244
246
|
violations = [
|
|
245
|
-
f"around {cap.format_start()} - {
|
|
247
|
+
f"around {cap.format_start()} - {self._describe_line_length(line)}"
|
|
246
248
|
for cap in (c.to_real_caption() for c in self.caption_stash._collection)
|
|
247
249
|
for line in cap.get_text().split("\n")
|
|
248
250
|
if len(line) > 32
|
|
@@ -253,6 +255,19 @@ class SCCReader(BaseReader):
|
|
|
253
255
|
"Lines longer than 32:\n" + "\n".join(violations)
|
|
254
256
|
)
|
|
255
257
|
|
|
258
|
+
@staticmethod
|
|
259
|
+
def _describe_line_length(line):
|
|
260
|
+
"""Describe a line too long for the row, separating the text from the
|
|
261
|
+
indentation that places it, which can be what pushes it off the row.
|
|
262
|
+
"""
|
|
263
|
+
text = line.lstrip("\xa0")
|
|
264
|
+
indent = len(line) - len(text)
|
|
265
|
+
if not indent:
|
|
266
|
+
return f"{line} - Length {len(line)}"
|
|
267
|
+
return (
|
|
268
|
+
f"{text} - Length {len(text)} plus a {indent}-column indent = {len(line)}"
|
|
269
|
+
)
|
|
270
|
+
|
|
256
271
|
@staticmethod
|
|
257
272
|
def _fix_last_captions_without_ending(caption_list):
|
|
258
273
|
"""Set end = start + 4s for trailing captions that were never ended."""
|
|
@@ -364,7 +379,11 @@ class SCCReader(BaseReader):
|
|
|
364
379
|
if self._is_doubled_type(word) and word == self.last_command:
|
|
365
380
|
if word in CUE_STARTING_COMMAND:
|
|
366
381
|
self.double_starter = True
|
|
367
|
-
|
|
382
|
+
# A doubled PAC stays on record so that a Tab Offset following it is
|
|
383
|
+
# still recognised as belonging to a PAC and applied; clearing it
|
|
384
|
+
# would leave the offset looking like a stray duplicate and drop the
|
|
385
|
+
# indentation it carries.
|
|
386
|
+
self.last_command = word if self._is_pac_command(word) else ""
|
|
368
387
|
return True
|
|
369
388
|
|
|
370
389
|
if self._is_pac_command(word) and word in self.last_command:
|