pycaption 2.2.22__tar.gz → 2.2.23__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.2.22/pycaption.egg-info → pycaption-2.2.23}/PKG-INFO +2 -2
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/dfxp/base.py +7 -3
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/scc/__init__.py +6 -5
- {pycaption-2.2.22 → pycaption-2.2.23/pycaption.egg-info}/PKG-INFO +2 -2
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption.egg-info/requires.txt +1 -1
- {pycaption-2.2.22 → pycaption-2.2.23}/setup.py +2 -2
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/conftest.py +1 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/fixtures/dfxp.py +28 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_dfxp.py +43 -1
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_scc_conversion.py +33 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/LICENSE +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/MANIFEST.in +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/README.rst +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/__init__.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/base.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/dfxp/__init__.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/dfxp/extras.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/exceptions.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/geometry.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/microdvd.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/sami.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/scc/constants.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/scc/specialized_collections.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/scc/state_machines.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/scc/translator.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/srt.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/transcript.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/utils.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption/webvtt.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption.egg-info/SOURCES.txt +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption.egg-info/dependency_links.txt +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/pycaption.egg-info/top_level.txt +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/setup.cfg +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/__init__.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/fixtures/__init__.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/fixtures/microdvd.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/fixtures/sami.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/fixtures/scc.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/fixtures/srt.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/fixtures/translated_scc.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/fixtures/webvtt.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/mixins.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_base.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_dfxp_conversion.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_dfxp_extras.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_functions.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_geometry.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_microdvd.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_microdvd_conversion.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_sami.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_sami_conversion.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_scc.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_scc_translator.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_srt.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_srt_conversion.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_webvtt.py +0 -0
- {pycaption-2.2.22 → pycaption-2.2.23}/tests/test_webvtt_conversion.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycaption
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.23
|
|
4
4
|
Summary: Closed caption converter
|
|
5
5
|
Author: Joe Norton
|
|
6
6
|
Author-email: joey@nortoncrew.com
|
|
@@ -27,7 +27,7 @@ Provides-Extra: dev
|
|
|
27
27
|
Requires-Dist: pytest; extra == "dev"
|
|
28
28
|
Requires-Dist: pytest-lazy-fixture; extra == "dev"
|
|
29
29
|
Provides-Extra: transcript
|
|
30
|
-
Requires-Dist: nltk==3.9.
|
|
30
|
+
Requires-Dist: nltk==3.9.4; extra == "transcript"
|
|
31
31
|
Dynamic: author
|
|
32
32
|
Dynamic: author-email
|
|
33
33
|
Dynamic: classifier
|
|
@@ -147,9 +147,13 @@ class DFXPReader(BaseReader):
|
|
|
147
147
|
def _convert_div_to_caption_list(self, div):
|
|
148
148
|
return CaptionList(
|
|
149
149
|
[
|
|
150
|
-
|
|
151
|
-
for
|
|
152
|
-
|
|
150
|
+
caption
|
|
151
|
+
for caption in (
|
|
152
|
+
self._convert_p_tag_to_caption(p_tag)
|
|
153
|
+
for p_tag in div.find_all("p")
|
|
154
|
+
if p_tag.get_text().strip()
|
|
155
|
+
)
|
|
156
|
+
if caption is not None
|
|
153
157
|
],
|
|
154
158
|
div.layout_info,
|
|
155
159
|
)
|
|
@@ -584,11 +584,12 @@ class SCCWriter(BaseWriter):
|
|
|
584
584
|
code_words = len(code) / 5 + 8
|
|
585
585
|
code_time_microseconds = code_words * MICROSECONDS_PER_CODEWORD
|
|
586
586
|
code_start = start - code_time_microseconds
|
|
587
|
-
if index
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
587
|
+
if index > 0:
|
|
588
|
+
previous_code, previous_start, previous_end = codes[index - 1]
|
|
589
|
+
if code_start < previous_start:
|
|
590
|
+
code_start = previous_start
|
|
591
|
+
if previous_end + 3 * MICROSECONDS_PER_CODEWORD >= code_start:
|
|
592
|
+
codes[index - 1] = (previous_code, previous_start, None)
|
|
592
593
|
codes[index] = (code, code_start, end)
|
|
593
594
|
|
|
594
595
|
# PASS 3:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycaption
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.23
|
|
4
4
|
Summary: Closed caption converter
|
|
5
5
|
Author: Joe Norton
|
|
6
6
|
Author-email: joey@nortoncrew.com
|
|
@@ -27,7 +27,7 @@ Provides-Extra: dev
|
|
|
27
27
|
Requires-Dist: pytest; extra == "dev"
|
|
28
28
|
Requires-Dist: pytest-lazy-fixture; extra == "dev"
|
|
29
29
|
Provides-Extra: transcript
|
|
30
|
-
Requires-Dist: nltk==3.9.
|
|
30
|
+
Requires-Dist: nltk==3.9.4; extra == "transcript"
|
|
31
31
|
Dynamic: author
|
|
32
32
|
Dynamic: author-email
|
|
33
33
|
Dynamic: classifier
|
|
@@ -16,11 +16,11 @@ dependencies = [
|
|
|
16
16
|
|
|
17
17
|
dev_dependencies = ["pytest", "pytest-lazy-fixture"]
|
|
18
18
|
|
|
19
|
-
transcript_dependencies = ["nltk==3.9.
|
|
19
|
+
transcript_dependencies = ["nltk==3.9.4"]
|
|
20
20
|
|
|
21
21
|
setup(
|
|
22
22
|
name="pycaption",
|
|
23
|
-
version="2.2.
|
|
23
|
+
version="2.2.23",
|
|
24
24
|
description="Closed caption converter",
|
|
25
25
|
long_description=open(README_PATH).read(),
|
|
26
26
|
author="Joe Norton",
|
|
@@ -45,6 +45,7 @@ from tests.fixtures.dfxp import ( # noqa: F401
|
|
|
45
45
|
sample_dfxp_with_relativized_positioning,
|
|
46
46
|
sample_dfxp_with_templated_style,
|
|
47
47
|
sample_dfxp_without_region_and_style,
|
|
48
|
+
sample_dfxp_concurrent_with_empty_p,
|
|
48
49
|
)
|
|
49
50
|
from tests.fixtures.microdvd import missing_fps_sample_microdvd # noqa: F401
|
|
50
51
|
from tests.fixtures.microdvd import (
|
|
@@ -1521,3 +1521,31 @@ def sample_dfxp_default_styling_p_tags():
|
|
|
1521
1521
|
</div>
|
|
1522
1522
|
</body>
|
|
1523
1523
|
</tt>"""
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
@pytest.fixture(scope="session")
|
|
1527
|
+
def sample_dfxp_concurrent_with_empty_p():
|
|
1528
|
+
return """\
|
|
1529
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
1530
|
+
<tt xml:lang="en" xmlns="http://www.w3.org/ns/ttml"
|
|
1531
|
+
xmlns:tts="http://www.w3.org/ns/ttml#styling">
|
|
1532
|
+
<head>
|
|
1533
|
+
<styling>
|
|
1534
|
+
<style xml:id="basic" tts:color="white"/>
|
|
1535
|
+
</styling>
|
|
1536
|
+
<layout>
|
|
1537
|
+
<region xml:id="pop1" tts:origin="17.5% 84.66%" tts:extent="62.5% 5.33%"/>
|
|
1538
|
+
<region xml:id="pop2" tts:origin="50% 84.66%" tts:extent="0% 5.33%"/>
|
|
1539
|
+
</layout>
|
|
1540
|
+
</head>
|
|
1541
|
+
<body>
|
|
1542
|
+
<div xml:lang="en">
|
|
1543
|
+
<p region="pop1" style="basic" begin="01:55:05:08" end="01:55:11:14"
|
|
1544
|
+
tts:origin="17.5% 84.66%" tts:extent="62.5% 5.33%">Subtitle End
|
|
1545
|
+
</p>
|
|
1546
|
+
<p region="pop2" style="basic" begin="01:55:05:08" end="01:55:11:14"
|
|
1547
|
+
tts:origin="50% 84.66%" tts:extent="0% 5.33%">
|
|
1548
|
+
</p>
|
|
1549
|
+
</div>
|
|
1550
|
+
</body>
|
|
1551
|
+
</tt>"""
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from pycaption import CaptionReadNoCaptions, DFXPReader
|
|
3
|
+
from pycaption import CaptionReadNoCaptions, DFXPReader, SRTWriter
|
|
4
|
+
from pycaption.base import merge_concurrent_captions
|
|
4
5
|
from pycaption.exceptions import (
|
|
5
6
|
CaptionReadError,
|
|
6
7
|
CaptionReadSyntaxError,
|
|
@@ -230,3 +231,44 @@ class TestDFXPReader(ReaderTestingMixIn):
|
|
|
230
231
|
caps = caption_set.get_captions("en-US")
|
|
231
232
|
|
|
232
233
|
assert len(caps) == 1
|
|
234
|
+
|
|
235
|
+
def test_concurrent_captions_with_empty_p_no_none_in_list(
|
|
236
|
+
self, sample_dfxp_concurrent_with_empty_p
|
|
237
|
+
):
|
|
238
|
+
caption_set = DFXPReader().read(sample_dfxp_concurrent_with_empty_p)
|
|
239
|
+
captions = caption_set.get_captions("en")
|
|
240
|
+
assert all(c is not None for c in captions)
|
|
241
|
+
assert len(captions) == 1
|
|
242
|
+
|
|
243
|
+
def test_concurrent_captions_with_empty_p_merge_does_not_crash(
|
|
244
|
+
self, sample_dfxp_concurrent_with_empty_p
|
|
245
|
+
):
|
|
246
|
+
caption_set = DFXPReader().read(sample_dfxp_concurrent_with_empty_p)
|
|
247
|
+
merged = merge_concurrent_captions(caption_set)
|
|
248
|
+
captions = merged.get_captions("en")
|
|
249
|
+
assert len(captions) >= 1
|
|
250
|
+
assert all(c is not None for c in captions)
|
|
251
|
+
|
|
252
|
+
def test_concurrent_captions_with_empty_p_srt_writer_does_not_crash(
|
|
253
|
+
self, sample_dfxp_concurrent_with_empty_p
|
|
254
|
+
):
|
|
255
|
+
caption_set = DFXPReader().read(sample_dfxp_concurrent_with_empty_p)
|
|
256
|
+
output = SRTWriter().write(caption_set)
|
|
257
|
+
assert "Subtitle End" in output
|
|
258
|
+
|
|
259
|
+
def test_none_from_convert_p_tag_is_filtered(self, sample_dfxp):
|
|
260
|
+
reader = DFXPReader()
|
|
261
|
+
original = reader._convert_p_tag_to_caption
|
|
262
|
+
call_count = [0]
|
|
263
|
+
|
|
264
|
+
def patched(p_tag):
|
|
265
|
+
call_count[0] += 1
|
|
266
|
+
if call_count[0] == 2:
|
|
267
|
+
return None
|
|
268
|
+
return original(p_tag)
|
|
269
|
+
|
|
270
|
+
reader._convert_p_tag_to_caption = patched
|
|
271
|
+
caption_set = reader.read(sample_dfxp)
|
|
272
|
+
captions = caption_set.get_captions("en-US")
|
|
273
|
+
assert all(c is not None for c in captions)
|
|
274
|
+
assert len(captions) == 6
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
1
3
|
import pytest
|
|
2
4
|
|
|
3
5
|
from pycaption import (
|
|
@@ -6,6 +8,7 @@ from pycaption import (
|
|
|
6
8
|
SCCWriter,
|
|
7
9
|
SRTReader,
|
|
8
10
|
SRTWriter,
|
|
11
|
+
WebVTTReader,
|
|
9
12
|
WebVTTWriter,
|
|
10
13
|
)
|
|
11
14
|
from tests.mixins import CaptionSetTestingMixIn
|
|
@@ -65,6 +68,36 @@ class TestSCCtoDFXP:
|
|
|
65
68
|
assert dfxp == sample_dfxp_with_ampersand_character
|
|
66
69
|
|
|
67
70
|
|
|
71
|
+
class TestSCCTimestampOrdering:
|
|
72
|
+
def test_scc_captions_are_in_order_when_short_text_followed_by_long(self):
|
|
73
|
+
"""When short caption text is followed by longer caption text,
|
|
74
|
+
the SCC output timestamps should remain in chronological order.
|
|
75
|
+
"""
|
|
76
|
+
vtt_input = (
|
|
77
|
+
"WEBVTT\n\n"
|
|
78
|
+
"0\n"
|
|
79
|
+
"00:00:02.200 --> 00:00:02.359\n"
|
|
80
|
+
"you know,\n\n"
|
|
81
|
+
"1\n"
|
|
82
|
+
"00:00:02.400 --> 00:00:03.760\n"
|
|
83
|
+
"the way he kind of looked at me.\n\n"
|
|
84
|
+
"2\n"
|
|
85
|
+
"00:00:04.700 --> 00:00:05.169\n"
|
|
86
|
+
"And I said,\n\n"
|
|
87
|
+
"3\n"
|
|
88
|
+
"00:00:05.210 --> 00:00:05.520\n"
|
|
89
|
+
"oh\n"
|
|
90
|
+
)
|
|
91
|
+
captions = WebVTTReader().read(vtt_input)
|
|
92
|
+
scc_output = SCCWriter().write(captions)
|
|
93
|
+
# SCC timestamps use HH:MM:SS:FF format (FF = frames)
|
|
94
|
+
timestamps = re.findall(r"(\d+:\d+:\d+:\d+)", scc_output)
|
|
95
|
+
for i in range(1, len(timestamps)):
|
|
96
|
+
assert timestamps[i] >= timestamps[i - 1], (
|
|
97
|
+
f"Timestamps out of order: {timestamps[i - 1]} > {timestamps[i]}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
68
101
|
class TestSCCToWebVTT:
|
|
69
102
|
def test_webvtt_newlines_are_properly_rendered(
|
|
70
103
|
self,
|
|
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
|