pycaption 2.2.21__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.
Files changed (57) hide show
  1. {pycaption-2.2.21/pycaption.egg-info → pycaption-2.2.23}/PKG-INFO +2 -2
  2. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/dfxp/base.py +7 -3
  3. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/scc/__init__.py +17 -7
  4. {pycaption-2.2.21 → pycaption-2.2.23/pycaption.egg-info}/PKG-INFO +2 -2
  5. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption.egg-info/requires.txt +1 -1
  6. {pycaption-2.2.21 → pycaption-2.2.23}/setup.py +2 -2
  7. {pycaption-2.2.21 → pycaption-2.2.23}/tests/conftest.py +2 -0
  8. {pycaption-2.2.21 → pycaption-2.2.23}/tests/fixtures/dfxp.py +29 -1
  9. {pycaption-2.2.21 → pycaption-2.2.23}/tests/fixtures/scc.py +16 -0
  10. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_dfxp.py +43 -1
  11. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_scc.py +18 -0
  12. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_scc_conversion.py +33 -0
  13. {pycaption-2.2.21 → pycaption-2.2.23}/LICENSE +0 -0
  14. {pycaption-2.2.21 → pycaption-2.2.23}/MANIFEST.in +0 -0
  15. {pycaption-2.2.21 → pycaption-2.2.23}/README.rst +0 -0
  16. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/__init__.py +0 -0
  17. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/base.py +0 -0
  18. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/dfxp/__init__.py +0 -0
  19. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/dfxp/extras.py +0 -0
  20. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/exceptions.py +0 -0
  21. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/geometry.py +0 -0
  22. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/microdvd.py +0 -0
  23. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/sami.py +0 -0
  24. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/scc/constants.py +0 -0
  25. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/scc/specialized_collections.py +0 -0
  26. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/scc/state_machines.py +0 -0
  27. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/scc/translator.py +0 -0
  28. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/srt.py +0 -0
  29. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/transcript.py +0 -0
  30. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/utils.py +0 -0
  31. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption/webvtt.py +0 -0
  32. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption.egg-info/SOURCES.txt +0 -0
  33. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption.egg-info/dependency_links.txt +0 -0
  34. {pycaption-2.2.21 → pycaption-2.2.23}/pycaption.egg-info/top_level.txt +0 -0
  35. {pycaption-2.2.21 → pycaption-2.2.23}/setup.cfg +0 -0
  36. {pycaption-2.2.21 → pycaption-2.2.23}/tests/__init__.py +0 -0
  37. {pycaption-2.2.21 → pycaption-2.2.23}/tests/fixtures/__init__.py +0 -0
  38. {pycaption-2.2.21 → pycaption-2.2.23}/tests/fixtures/microdvd.py +0 -0
  39. {pycaption-2.2.21 → pycaption-2.2.23}/tests/fixtures/sami.py +0 -0
  40. {pycaption-2.2.21 → pycaption-2.2.23}/tests/fixtures/srt.py +0 -0
  41. {pycaption-2.2.21 → pycaption-2.2.23}/tests/fixtures/translated_scc.py +0 -0
  42. {pycaption-2.2.21 → pycaption-2.2.23}/tests/fixtures/webvtt.py +0 -0
  43. {pycaption-2.2.21 → pycaption-2.2.23}/tests/mixins.py +0 -0
  44. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_base.py +0 -0
  45. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_dfxp_conversion.py +0 -0
  46. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_dfxp_extras.py +0 -0
  47. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_functions.py +0 -0
  48. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_geometry.py +0 -0
  49. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_microdvd.py +0 -0
  50. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_microdvd_conversion.py +0 -0
  51. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_sami.py +0 -0
  52. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_sami_conversion.py +0 -0
  53. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_scc_translator.py +0 -0
  54. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_srt.py +0 -0
  55. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_srt_conversion.py +0 -0
  56. {pycaption-2.2.21 → pycaption-2.2.23}/tests/test_webvtt.py +0 -0
  57. {pycaption-2.2.21 → 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.21
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.1; extra == "transcript"
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
- self._convert_p_tag_to_caption(p_tag)
151
- for p_tag in div.find_all("p")
152
- if p_tag.get_text().strip()
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
  )
@@ -484,8 +484,17 @@ class SCCReader(BaseReader):
484
484
 
485
485
  # 942c - Erase Displayed Memory - Clear the current screen of any
486
486
  # displayed captions or text.
487
- elif word == "942c" and self.pop_ons_queue:
488
- self._pop_on(end=self.time_translator.get_time())
487
+ elif word == "942c":
488
+ edm_time = self.time_translator.get_time()
489
+ if self.pop_ons_queue:
490
+ self._pop_on(end=edm_time)
491
+ if self.buffer_dict.active_key in ("paint", "roll") \
492
+ and not self.buffer.is_empty():
493
+ self.caption_stash.create_and_store(
494
+ self.buffer, self.time, edm_time)
495
+ self.buffer = self.node_creator_factory.new_creator()
496
+ self.node_creator_factory.position_tracker.reset_for_new_caption()
497
+ self.time = edm_time
489
498
 
490
499
  # If command is not one of the aforementioned, add it to buffer
491
500
  else:
@@ -575,11 +584,12 @@ class SCCWriter(BaseWriter):
575
584
  code_words = len(code) / 5 + 8
576
585
  code_time_microseconds = code_words * MICROSECONDS_PER_CODEWORD
577
586
  code_start = start - code_time_microseconds
578
- if index == 0:
579
- continue
580
- previous_code, previous_start, previous_end = codes[index - 1]
581
- if previous_end + 3 * MICROSECONDS_PER_CODEWORD >= code_start:
582
- codes[index - 1] = (previous_code, previous_start, None)
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)
583
593
  codes[index] = (code, code_start, end)
584
594
 
585
595
  # PASS 3:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycaption
3
- Version: 2.2.21
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.1; extra == "transcript"
30
+ Requires-Dist: nltk==3.9.4; extra == "transcript"
31
31
  Dynamic: author
32
32
  Dynamic: author-email
33
33
  Dynamic: classifier
@@ -7,4 +7,4 @@ pytest
7
7
  pytest-lazy-fixture
8
8
 
9
9
  [transcript]
10
- nltk==3.9.1
10
+ nltk==3.9.4
@@ -16,11 +16,11 @@ dependencies = [
16
16
 
17
17
  dev_dependencies = ["pytest", "pytest-lazy-fixture"]
18
18
 
19
- transcript_dependencies = ["nltk==3.9.1"]
19
+ transcript_dependencies = ["nltk==3.9.4"]
20
20
 
21
21
  setup(
22
22
  name="pycaption",
23
- version="2.2.21",
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 (
@@ -123,6 +124,7 @@ from tests.fixtures.scc import ( # noqa: F401
123
124
  sample_scc_with_spaces_at_eol_pop,
124
125
  sample_scc_with_spaces_at_eol_roll,
125
126
  sample_scc_with_unknown_commands,
127
+ sample_scc_paint_on_edm,
126
128
  scc_that_generates_webvtt_with_proper_newlines,
127
129
  )
128
130
  from tests.fixtures.srt import (
@@ -963,7 +963,7 @@ def sample_dfxp_from_scc_output():
963
963
  <p begin="00:00:20.086" end="00:00:22.088" region="r9" style="default">
964
964
  2323
965
965
  </p>
966
- <p begin="00:00:22.088" end="00:00:26.088" region="r10" style="default">
966
+ <p begin="00:00:22.088" end="00:00:36.169" region="r10" style="default">
967
967
  4545<br/>
968
968
  6767<br/>
969
969
  8989
@@ -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>"""
@@ -643,3 +643,19 @@ Scenarist_SCC V1.0
643
643
  00:00:01:24 9429 91d0 c180 c220 91e0 c1c2 2020 2080 92c2 c1c2 2080
644
644
 
645
645
  """
646
+
647
+
648
+ @pytest.fixture(scope="session")
649
+ def sample_scc_paint_on_edm():
650
+ return """\
651
+ Scenarist_SCC V1.0
652
+
653
+ 00:00:00;00 9429 4920 f468 e96e 6b80
654
+
655
+ 00:00:01;00 942c
656
+
657
+ 00:00:02;00 9429 c8e5 ecec ef80
658
+
659
+ 00:00:04;00 942c
660
+
661
+ """
@@ -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
@@ -1000,3 +1000,21 @@ class TestTimingCorrectingCaptionList:
1000
1000
  num_captions = len(caption_set.get_captions("en-US"))
1001
1001
 
1002
1002
  assert num_captions == 2
1003
+
1004
+ def test_paint_on_edm_erases_displayed(self, sample_scc_paint_on_edm):
1005
+ caption_set = SCCReader().read(sample_scc_paint_on_edm)
1006
+ captions = caption_set.get_captions("en-US")
1007
+
1008
+ assert len(captions) == 2
1009
+
1010
+ texts = [
1011
+ node.content
1012
+ for caption in captions
1013
+ for node in caption.nodes
1014
+ if node.type_ == CaptionNode.TEXT
1015
+ ]
1016
+ assert texts == ["I think", "Hello"]
1017
+
1018
+ assert captions[0].end > captions[0].start
1019
+ assert captions[1].end > captions[1].start
1020
+ assert captions[0].end <= captions[1].start
@@ -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