pycaption 2.3.2__tar.gz → 2.3.3__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.2/pycaption.egg-info → pycaption-2.3.3}/PKG-INFO +1 -1
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/base.py +27 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/dfxp/reader.py +3 -4
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/microdvd.py +2 -3
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/sami/reader.py +2 -3
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/reader.py +3 -4
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/srt.py +3 -3
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/webvtt/reader.py +2 -17
- {pycaption-2.3.2 → pycaption-2.3.3/pycaption.egg-info}/PKG-INFO +1 -1
- {pycaption-2.3.2 → pycaption-2.3.3}/setup.py +1 -1
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/mixins.py +12 -2
- {pycaption-2.3.2 → pycaption-2.3.3}/LICENSE +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/MANIFEST.in +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/README.rst +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/__init__.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/dfxp/__init__.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/dfxp/constants.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/dfxp/extras.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/dfxp/writer.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/exceptions.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/geometry.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/sami/__init__.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/sami/constants.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/sami/parser.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/sami/writer.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/__init__.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/constants.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/specialized_collections.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/state_machines.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/translator.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/writer.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/transcript.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/utils.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/webvtt/__init__.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/webvtt/constants.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/webvtt/writer.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption.egg-info/SOURCES.txt +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption.egg-info/dependency_links.txt +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption.egg-info/requires.txt +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/pycaption.egg-info/top_level.txt +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/setup.cfg +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/__init__.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/conftest.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/__init__.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/dfxp.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/microdvd.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/sami.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/scc.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/srt.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/translated_scc.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/webvtt.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_base.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_dfxp.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_dfxp_conversion.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_dfxp_extras.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_functions.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_geometry.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_microdvd.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_microdvd_conversion.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_sami.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_sami_conversion.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_scc.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_scc_conversion.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_scc_translator.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_scc_writer.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_srt.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_srt_conversion.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_webvtt.py +0 -0
- {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_webvtt_conversion.py +0 -0
|
@@ -64,6 +64,33 @@ class BaseReader:
|
|
|
64
64
|
def __init__(self, *args, **kwargs):
|
|
65
65
|
pass
|
|
66
66
|
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _decode_content(content):
|
|
69
|
+
"""Decode bytes to str (UTF-8 with BOM handling).
|
|
70
|
+
|
|
71
|
+
:param content: str or bytes input.
|
|
72
|
+
:returns: decoded str with BOM stripped.
|
|
73
|
+
:raises InvalidInputError: if content is not str/bytes, is empty
|
|
74
|
+
bytes, or is not valid UTF-8.
|
|
75
|
+
"""
|
|
76
|
+
if isinstance(content, bytes):
|
|
77
|
+
if not content:
|
|
78
|
+
raise InvalidInputError("The content is empty.")
|
|
79
|
+
try:
|
|
80
|
+
content = content.decode("utf-8-sig")
|
|
81
|
+
except UnicodeDecodeError as e:
|
|
82
|
+
raise InvalidInputError(
|
|
83
|
+
f"Content is not valid UTF-8: {e}"
|
|
84
|
+
) from e
|
|
85
|
+
elif isinstance(content, str):
|
|
86
|
+
if content.startswith(""):
|
|
87
|
+
content = content[1:]
|
|
88
|
+
else:
|
|
89
|
+
raise InvalidInputError(
|
|
90
|
+
"The content must be a unicode string or UTF-8 bytes."
|
|
91
|
+
)
|
|
92
|
+
return content
|
|
93
|
+
|
|
67
94
|
def detect(self, content):
|
|
68
95
|
"""Return True if content appears to be in this reader's format.
|
|
69
96
|
|
|
@@ -21,7 +21,6 @@ from ..exceptions import (
|
|
|
21
21
|
CaptionReadNoCaptions,
|
|
22
22
|
CaptionReadSyntaxError,
|
|
23
23
|
CaptionReadTimingError,
|
|
24
|
-
InvalidInputError,
|
|
25
24
|
)
|
|
26
25
|
from ..geometry import (
|
|
27
26
|
Alignment,
|
|
@@ -108,9 +107,10 @@ class DFXPReader(BaseReader):
|
|
|
108
107
|
def detect(self, content):
|
|
109
108
|
"""Return True if content looks like a DFXP/TTML document.
|
|
110
109
|
|
|
111
|
-
:type content: str
|
|
110
|
+
:type content: str or bytes
|
|
112
111
|
:rtype: bool
|
|
113
112
|
"""
|
|
113
|
+
content = self._decode_content(content)
|
|
114
114
|
lowered = content.lower()
|
|
115
115
|
return bool(re.search(r"<tt[\s>]", lowered)) and "</tt>" in lowered
|
|
116
116
|
|
|
@@ -122,8 +122,7 @@ class DFXPReader(BaseReader):
|
|
|
122
122
|
:raises InvalidInputError: if content is not a string
|
|
123
123
|
:raises CaptionReadNoCaptions: if no captions are found
|
|
124
124
|
"""
|
|
125
|
-
|
|
126
|
-
raise InvalidInputError("The content is not a unicode string.")
|
|
125
|
+
content = self._decode_content(content)
|
|
127
126
|
|
|
128
127
|
dfxp_document = LayoutAwareDFXPParser(
|
|
129
128
|
content, read_invalid_positioning=self.read_invalid_positioning
|
|
@@ -20,7 +20,6 @@ from .exceptions import (
|
|
|
20
20
|
CaptionReadNoCaptions,
|
|
21
21
|
CaptionReadSyntaxError,
|
|
22
22
|
CaptionReadTimingError,
|
|
23
|
-
InvalidInputError,
|
|
24
23
|
)
|
|
25
24
|
from .geometry import HorizontalAlignmentEnum
|
|
26
25
|
|
|
@@ -30,6 +29,7 @@ class MicroDVDReader(BaseReader):
|
|
|
30
29
|
|
|
31
30
|
def detect(self, content):
|
|
32
31
|
"""Return True if content starts with MicroDVD frame markers."""
|
|
32
|
+
content = self._decode_content(content)
|
|
33
33
|
return re.match(r"{\d+}{\d+}", content) is not None
|
|
34
34
|
|
|
35
35
|
def read(self, content, lang=DEFAULT_LANGUAGE_CODE):
|
|
@@ -39,8 +39,7 @@ class MicroDVDReader(BaseReader):
|
|
|
39
39
|
:param lang: Language code to assign.
|
|
40
40
|
:rtype: CaptionSet
|
|
41
41
|
"""
|
|
42
|
-
|
|
43
|
-
raise InvalidInputError("The content is not a unicode string.")
|
|
42
|
+
content = self._decode_content(content)
|
|
44
43
|
|
|
45
44
|
lines = content.splitlines()
|
|
46
45
|
captions = CaptionList()
|
|
@@ -10,7 +10,6 @@ from ..base import BaseReader, Caption, CaptionList, CaptionNode, CaptionSet
|
|
|
10
10
|
from ..exceptions import (
|
|
11
11
|
CaptionReadNoCaptions,
|
|
12
12
|
CaptionReadTimingError,
|
|
13
|
-
InvalidInputError,
|
|
14
13
|
)
|
|
15
14
|
from ..geometry import Alignment, HorizontalAlignmentEnum, Layout, Padding, Size
|
|
16
15
|
from .parser import SAMIParser
|
|
@@ -32,6 +31,7 @@ class SAMIReader(BaseReader):
|
|
|
32
31
|
|
|
33
32
|
def detect(self, content):
|
|
34
33
|
"""Return True if content looks like a SAMI document."""
|
|
34
|
+
content = self._decode_content(content)
|
|
35
35
|
return "<sami" in content.lower()
|
|
36
36
|
|
|
37
37
|
def read(self, content):
|
|
@@ -42,8 +42,7 @@ class SAMIReader(BaseReader):
|
|
|
42
42
|
:raises InvalidInputError: if content is not a string
|
|
43
43
|
:raises CaptionReadNoCaptions: if no captions are found
|
|
44
44
|
"""
|
|
45
|
-
|
|
46
|
-
raise InvalidInputError("The content is not a unicode string.")
|
|
45
|
+
content = self._decode_content(content)
|
|
47
46
|
|
|
48
47
|
content, doc_styles, doc_langs = SAMIParser().feed(content)
|
|
49
48
|
sami_soup = BeautifulSoup(content, features="lxml")
|
|
@@ -87,7 +87,6 @@ from pycaption.exceptions import (
|
|
|
87
87
|
CaptionLineLengthError,
|
|
88
88
|
CaptionReadNoCaptions,
|
|
89
89
|
CaptionReadTimingError,
|
|
90
|
-
InvalidInputError,
|
|
91
90
|
)
|
|
92
91
|
|
|
93
92
|
from .constants import (
|
|
@@ -148,10 +147,11 @@ class SCCReader(BaseReader):
|
|
|
148
147
|
def detect(self, content):
|
|
149
148
|
"""Checks whether the given content is a proper SCC file
|
|
150
149
|
|
|
151
|
-
:type content: str
|
|
150
|
+
:type content: str or bytes
|
|
152
151
|
|
|
153
152
|
:rtype: bool
|
|
154
153
|
"""
|
|
154
|
+
content = self._decode_content(content)
|
|
155
155
|
lines = content.splitlines()
|
|
156
156
|
if lines[0] == HEADER:
|
|
157
157
|
return True
|
|
@@ -177,8 +177,7 @@ class SCCReader(BaseReader):
|
|
|
177
177
|
|
|
178
178
|
:rtype: CaptionSet
|
|
179
179
|
"""
|
|
180
|
-
|
|
181
|
-
raise InvalidInputError("The content is not a unicode string.")
|
|
180
|
+
content = self._decode_content(content)
|
|
182
181
|
|
|
183
182
|
self.simulate_roll_up = simulate_roll_up
|
|
184
183
|
self.time_translator.offset = offset * 1000000
|
|
@@ -6,7 +6,7 @@ from .base import (
|
|
|
6
6
|
BaseReader, BaseWriter, Caption, CaptionList, CaptionNode, CaptionSet,
|
|
7
7
|
merge_caption_list,
|
|
8
8
|
)
|
|
9
|
-
from .exceptions import CaptionReadNoCaptions
|
|
9
|
+
from .exceptions import CaptionReadNoCaptions
|
|
10
10
|
from .geometry import HorizontalAlignmentEnum
|
|
11
11
|
|
|
12
12
|
|
|
@@ -19,6 +19,7 @@ class SRTReader(BaseReader):
|
|
|
19
19
|
Checks that the first line is a sequence number and the second
|
|
20
20
|
contains an arrow ('-->').
|
|
21
21
|
"""
|
|
22
|
+
content = self._decode_content(content)
|
|
22
23
|
lines = content.splitlines()
|
|
23
24
|
if lines[0].isdigit() and "-->" in lines[1]:
|
|
24
25
|
return True
|
|
@@ -34,8 +35,7 @@ class SRTReader(BaseReader):
|
|
|
34
35
|
:raises InvalidInputError: if content is not a string.
|
|
35
36
|
:raises CaptionReadNoCaptions: if no captions are found.
|
|
36
37
|
"""
|
|
37
|
-
|
|
38
|
-
raise InvalidInputError("The content is not a unicode string.")
|
|
38
|
+
content = self._decode_content(content)
|
|
39
39
|
|
|
40
40
|
lines = content.splitlines()
|
|
41
41
|
start_line = 0
|
|
@@ -15,7 +15,6 @@ from ..exceptions import (
|
|
|
15
15
|
CaptionReadNoCaptions,
|
|
16
16
|
CaptionReadSyntaxError,
|
|
17
17
|
CaptionReadWarning,
|
|
18
|
-
InvalidInputError,
|
|
19
18
|
)
|
|
20
19
|
from ..geometry import (
|
|
21
20
|
Alignment,
|
|
@@ -126,8 +125,7 @@ class WebVTTReader(BaseReader):
|
|
|
126
125
|
optionally followed by a space or tab and header metadata.
|
|
127
126
|
Handles BOM-prefixed content.
|
|
128
127
|
"""
|
|
129
|
-
|
|
130
|
-
content = content[1:]
|
|
128
|
+
content = self._decode_content(content)
|
|
131
129
|
first_line = content.splitlines()[0] if content.strip() else ""
|
|
132
130
|
return (
|
|
133
131
|
first_line == "WEBVTT"
|
|
@@ -147,20 +145,7 @@ class WebVTTReader(BaseReader):
|
|
|
147
145
|
:raises InvalidInputError: If content is not a string.
|
|
148
146
|
:raises CaptionReadNoCaptions: If no cues are found.
|
|
149
147
|
"""
|
|
150
|
-
|
|
151
|
-
if not content:
|
|
152
|
-
raise InvalidInputError("The content is not a unicode string.")
|
|
153
|
-
try:
|
|
154
|
-
content = content.decode("utf-8")
|
|
155
|
-
except UnicodeDecodeError as e:
|
|
156
|
-
raise InvalidInputError(
|
|
157
|
-
f"WebVTT content is not valid UTF-8: {e}"
|
|
158
|
-
) from e
|
|
159
|
-
elif not isinstance(content, str):
|
|
160
|
-
raise InvalidInputError("The content is not a unicode string.")
|
|
161
|
-
|
|
162
|
-
if content.startswith(""):
|
|
163
|
-
content = content[1:]
|
|
148
|
+
content = self._decode_content(content)
|
|
164
149
|
|
|
165
150
|
# str.splitlines() handles CR, LF, CRLF (W3C WebVTT §3 RULE-FMT-005)
|
|
166
151
|
lines = content.splitlines()
|
|
@@ -17,10 +17,20 @@ class ReaderTestingMixIn:
|
|
|
17
17
|
def assert_negative_answer_for_detection(self, different_sample):
|
|
18
18
|
assert self.reader.detect(different_sample) is False
|
|
19
19
|
|
|
20
|
-
def
|
|
20
|
+
def test_reader_rejects_non_string_non_bytes_input(self):
|
|
21
|
+
with pytest.raises(InvalidInputError) as exc_info:
|
|
22
|
+
self.reader.read(123)
|
|
23
|
+
assert "must be a unicode string or UTF-8 bytes" in exc_info.value.args[0]
|
|
24
|
+
|
|
25
|
+
def test_reader_rejects_empty_bytes(self):
|
|
21
26
|
with pytest.raises(InvalidInputError) as exc_info:
|
|
22
27
|
self.reader.read(b"")
|
|
23
|
-
assert exc_info.value.args[0] == "The content is
|
|
28
|
+
assert exc_info.value.args[0] == "The content is empty."
|
|
29
|
+
|
|
30
|
+
def test_reader_rejects_invalid_utf8_bytes(self):
|
|
31
|
+
with pytest.raises(InvalidInputError) as exc_info:
|
|
32
|
+
self.reader.read(b"\xff\xfe invalid utf8")
|
|
33
|
+
assert "not valid UTF-8" in exc_info.value.args[0]
|
|
24
34
|
|
|
25
35
|
|
|
26
36
|
class WebVTTTestingMixIn:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|