pycaption 2.2.22__tar.gz → 2.2.24__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.22/pycaption.egg-info → pycaption-2.2.24}/PKG-INFO +2 -2
  2. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/dfxp/base.py +7 -3
  3. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/scc/__init__.py +15 -6
  4. {pycaption-2.2.22 → pycaption-2.2.24/pycaption.egg-info}/PKG-INFO +2 -2
  5. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption.egg-info/requires.txt +1 -1
  6. {pycaption-2.2.22 → pycaption-2.2.24}/setup.py +2 -2
  7. {pycaption-2.2.22 → pycaption-2.2.24}/tests/conftest.py +2 -0
  8. {pycaption-2.2.22 → pycaption-2.2.24}/tests/fixtures/dfxp.py +28 -0
  9. {pycaption-2.2.22 → pycaption-2.2.24}/tests/fixtures/scc.py +10 -0
  10. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_dfxp.py +43 -1
  11. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_scc.py +18 -0
  12. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_scc_conversion.py +33 -0
  13. {pycaption-2.2.22 → pycaption-2.2.24}/LICENSE +0 -0
  14. {pycaption-2.2.22 → pycaption-2.2.24}/MANIFEST.in +0 -0
  15. {pycaption-2.2.22 → pycaption-2.2.24}/README.rst +0 -0
  16. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/__init__.py +0 -0
  17. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/base.py +0 -0
  18. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/dfxp/__init__.py +0 -0
  19. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/dfxp/extras.py +0 -0
  20. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/exceptions.py +0 -0
  21. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/geometry.py +0 -0
  22. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/microdvd.py +0 -0
  23. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/sami.py +0 -0
  24. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/scc/constants.py +0 -0
  25. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/scc/specialized_collections.py +0 -0
  26. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/scc/state_machines.py +0 -0
  27. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/scc/translator.py +0 -0
  28. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/srt.py +0 -0
  29. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/transcript.py +0 -0
  30. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/utils.py +0 -0
  31. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption/webvtt.py +0 -0
  32. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption.egg-info/SOURCES.txt +0 -0
  33. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption.egg-info/dependency_links.txt +0 -0
  34. {pycaption-2.2.22 → pycaption-2.2.24}/pycaption.egg-info/top_level.txt +0 -0
  35. {pycaption-2.2.22 → pycaption-2.2.24}/setup.cfg +0 -0
  36. {pycaption-2.2.22 → pycaption-2.2.24}/tests/__init__.py +0 -0
  37. {pycaption-2.2.22 → pycaption-2.2.24}/tests/fixtures/__init__.py +0 -0
  38. {pycaption-2.2.22 → pycaption-2.2.24}/tests/fixtures/microdvd.py +0 -0
  39. {pycaption-2.2.22 → pycaption-2.2.24}/tests/fixtures/sami.py +0 -0
  40. {pycaption-2.2.22 → pycaption-2.2.24}/tests/fixtures/srt.py +0 -0
  41. {pycaption-2.2.22 → pycaption-2.2.24}/tests/fixtures/translated_scc.py +0 -0
  42. {pycaption-2.2.22 → pycaption-2.2.24}/tests/fixtures/webvtt.py +0 -0
  43. {pycaption-2.2.22 → pycaption-2.2.24}/tests/mixins.py +0 -0
  44. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_base.py +0 -0
  45. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_dfxp_conversion.py +0 -0
  46. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_dfxp_extras.py +0 -0
  47. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_functions.py +0 -0
  48. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_geometry.py +0 -0
  49. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_microdvd.py +0 -0
  50. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_microdvd_conversion.py +0 -0
  51. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_sami.py +0 -0
  52. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_sami_conversion.py +0 -0
  53. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_scc_translator.py +0 -0
  54. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_srt.py +0 -0
  55. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_srt_conversion.py +0 -0
  56. {pycaption-2.2.22 → pycaption-2.2.24}/tests/test_webvtt.py +0 -0
  57. {pycaption-2.2.22 → pycaption-2.2.24}/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.22
3
+ Version: 2.2.24
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
  )
@@ -332,7 +332,15 @@ class SCCReader(BaseReader):
332
332
  for idx, word in enumerate(word_list):
333
333
  word = word.strip()
334
334
  if len(word) == 4:
335
- next_command = word_list[idx + 1] if idx + 1 < len(word_list) else None
335
+ # Look ahead for the next command, skipping the duplicate
336
+ # that SCC uses for error-correction (same word repeated).
337
+ next_idx = idx + 1
338
+ if (next_idx < len(word_list)
339
+ and word_list[next_idx].strip() == word):
340
+ next_idx += 1
341
+ next_command = (
342
+ word_list[next_idx] if next_idx < len(word_list) else None
343
+ )
336
344
  self._translate_word(word=word, next_command=next_command)
337
345
 
338
346
  def _translate_word(self, word, next_command=None):
@@ -584,11 +592,12 @@ class SCCWriter(BaseWriter):
584
592
  code_words = len(code) / 5 + 8
585
593
  code_time_microseconds = code_words * MICROSECONDS_PER_CODEWORD
586
594
  code_start = start - code_time_microseconds
587
- if index == 0:
588
- continue
589
- previous_code, previous_start, previous_end = codes[index - 1]
590
- if previous_end + 3 * MICROSECONDS_PER_CODEWORD >= code_start:
591
- codes[index - 1] = (previous_code, previous_start, None)
595
+ if index > 0:
596
+ previous_code, previous_start, previous_end = codes[index - 1]
597
+ if code_start < previous_start:
598
+ code_start = previous_start
599
+ if previous_end + 3 * MICROSECONDS_PER_CODEWORD >= code_start:
600
+ codes[index - 1] = (previous_code, previous_start, None)
592
601
  codes[index] = (code, code_start, end)
593
602
 
594
603
  # PASS 3:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycaption
3
- Version: 2.2.22
3
+ Version: 2.2.24
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.22",
23
+ version="2.2.24",
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 (
@@ -90,6 +91,7 @@ from tests.fixtures.scc import ( # noqa: F401
90
91
  sample_no_positioning_at_all_scc,
91
92
  sample_scc_created_dfxp_with_wrongly_closing_spans,
92
93
  sample_scc_duplicate_special_characters,
94
+ sample_scc_doubled_mid_row_before_punctuation,
93
95
  sample_scc_duplicate_tab_offset,
94
96
  sample_scc_empty,
95
97
  sample_scc_eoc_first_command,
@@ -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>"""
@@ -659,3 +659,13 @@ Scenarist_SCC V1.0
659
659
  00:00:04;00 942c
660
660
 
661
661
  """
662
+
663
+
664
+ @pytest.fixture(scope="session")
665
+ def sample_scc_doubled_mid_row_before_punctuation():
666
+ return """\
667
+ Scenarist_SCC V1.0
668
+
669
+ 00:26:48;29\t9420 9420 94d0 94d0 97a1 97a1 3e3e 2057 e5a7 ecec 2062 e520 6261 e36b 206e e5f8 f420 f7e5 e56b 20f7 e9f4 6880 9470 9470 616e eff4 68e5 f220 e570 e973 ef64 e520 efe6 91ae 91ae 4361 6e61 6461 2046 e9ec e573 9120 9120 ae80 942c 942c 8080 8080 942f 942f
670
+
671
+ """
@@ -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
@@ -392,6 +392,24 @@ class TestSCCReader(ReaderTestingMixIn):
392
392
  ]
393
393
  assert expected_lines == actual_lines
394
394
 
395
+ def test_doubled_mid_row_before_punctuation_no_extra_space(
396
+ self,
397
+ sample_scc_doubled_mid_row_before_punctuation,
398
+ ):
399
+ caption_set = SCCReader().read(
400
+ sample_scc_doubled_mid_row_before_punctuation
401
+ )
402
+ captions = caption_set.get_captions("en-US")
403
+ text_nodes = [
404
+ node.content
405
+ for cap_ in captions
406
+ for node in cap_.nodes
407
+ if node.type_ == CaptionNode.TEXT
408
+ ]
409
+ full_text = "".join(text_nodes)
410
+ assert " ." not in full_text
411
+ assert full_text.endswith("Files.")
412
+
395
413
  def test_removing_spaces_at_end_of_lines(
396
414
  self,
397
415
  sample_scc_with_spaces_at_eol_pop,
@@ -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