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.
Files changed (73) hide show
  1. {pycaption-2.3.10/pycaption.egg-info → pycaption-2.3.12}/PKG-INFO +1 -1
  2. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/__init__.py +1 -0
  3. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/base.py +3 -2
  4. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/dfxp/constants.py +1 -0
  5. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/dfxp/extras.py +2 -1
  6. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/dfxp/reader.py +6 -6
  7. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/dfxp/writer.py +5 -3
  8. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/geometry.py +3 -0
  9. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/microdvd.py +1 -0
  10. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/sami/constants.py +1 -0
  11. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/sami/reader.py +1 -0
  12. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/sami/writer.py +1 -0
  13. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/scc/__init__.py +1 -0
  14. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/scc/constants.py +5 -0
  15. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/scc/reader.py +21 -2
  16. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/scc/specialized_collections.py +429 -58
  17. pycaption-2.3.12/pycaption/scc/state_machines.py +413 -0
  18. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/scc/writer.py +37 -13
  19. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/srt.py +20 -4
  20. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/transcript.py +7 -1
  21. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/webvtt/__init__.py +1 -0
  22. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/webvtt/constants.py +1 -0
  23. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/webvtt/reader.py +1 -1
  24. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/webvtt/writer.py +3 -1
  25. {pycaption-2.3.10 → pycaption-2.3.12/pycaption.egg-info}/PKG-INFO +1 -1
  26. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption.egg-info/SOURCES.txt +0 -1
  27. pycaption-2.3.12/setup.cfg +4 -0
  28. {pycaption-2.3.10 → pycaption-2.3.12}/setup.py +3 -2
  29. {pycaption-2.3.10 → pycaption-2.3.12}/tests/conftest.py +41 -7
  30. {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/scc.py +548 -7
  31. {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/webvtt.py +1 -1
  32. {pycaption-2.3.10 → pycaption-2.3.12}/tests/mixins.py +1 -0
  33. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_base.py +72 -0
  34. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_bytes_input.py +0 -2
  35. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_dfxp.py +1 -0
  36. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_dfxp_conversion.py +4 -3
  37. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_double_encoding.py +1 -3
  38. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_sami.py +2 -8
  39. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_sami_conversion.py +4 -7
  40. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_scc.py +718 -40
  41. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_scc_conversion.py +86 -24
  42. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_scc_translator.py +1 -3
  43. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_scc_writer.py +107 -49
  44. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_srt.py +66 -1
  45. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_webvtt.py +12 -32
  46. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_webvtt_conversion.py +21 -61
  47. pycaption-2.3.10/pycaption/scc/state_machines.py +0 -207
  48. pycaption-2.3.10/setup.cfg +0 -14
  49. {pycaption-2.3.10 → pycaption-2.3.12}/LICENSE +0 -0
  50. {pycaption-2.3.10 → pycaption-2.3.12}/MANIFEST.in +0 -0
  51. {pycaption-2.3.10 → pycaption-2.3.12}/README.rst +0 -0
  52. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/dfxp/__init__.py +0 -0
  53. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/exceptions.py +0 -0
  54. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/sami/__init__.py +0 -0
  55. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/sami/parser.py +0 -0
  56. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/scc/translator.py +0 -0
  57. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption/utils.py +0 -0
  58. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption.egg-info/dependency_links.txt +0 -0
  59. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption.egg-info/requires.txt +0 -0
  60. {pycaption-2.3.10 → pycaption-2.3.12}/pycaption.egg-info/top_level.txt +0 -0
  61. {pycaption-2.3.10 → pycaption-2.3.12}/tests/__init__.py +0 -0
  62. {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/__init__.py +0 -0
  63. {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/dfxp.py +0 -0
  64. {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/microdvd.py +0 -0
  65. {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/sami.py +0 -0
  66. {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/srt.py +0 -0
  67. {pycaption-2.3.10 → pycaption-2.3.12}/tests/fixtures/translated_scc.py +0 -0
  68. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_dfxp_extras.py +0 -0
  69. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_functions.py +0 -0
  70. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_geometry.py +0 -0
  71. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_microdvd.py +0 -0
  72. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_microdvd_conversion.py +0 -0
  73. {pycaption-2.3.10 → pycaption-2.3.12}/tests/test_srt_conversion.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycaption
3
- Version: 2.3.10
3
+ Version: 2.3.12
4
4
  Summary: Closed caption converter
5
5
  Author: Joe Norton
6
6
  Author-email: joey@nortoncrew.com
@@ -21,6 +21,7 @@ from .srt import SRTReader, SRTWriter
21
21
  from .transcript import TranscriptWriter
22
22
  from .webvtt import WebVTTReader, WebVTTWriter
23
23
 
24
+
24
25
  __all__ = [
25
26
  "CaptionConverter",
26
27
  "DFXPReader",
@@ -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
@@ -8,6 +8,7 @@ import re
8
8
 
9
9
  from ..geometry import Alignment, HorizontalAlignmentEnum, Layout, VerticalAlignmentEnum
10
10
 
11
+
11
12
  DFXP_BASE_MARKUP = """
12
13
  <tt xmlns="http://www.w3.org/ns/ttml"
13
14
  xmlns:tts="http://www.w3.org/ns/ttml#styling">
@@ -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, styles=style_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
 
@@ -5,6 +5,7 @@ A special line {0}{0}fps_value sets the framerate (default 25 fps).
5
5
  """
6
6
 
7
7
  import re
8
+
8
9
  from copy import deepcopy
9
10
 
10
11
  from .base import (
@@ -10,6 +10,7 @@ from cssutils import log
10
10
 
11
11
  from ..geometry import HorizontalAlignmentEnum
12
12
 
13
+
13
14
  log.setLevel(FATAL)
14
15
 
15
16
  SAMI_BASE_MARKUP = """
@@ -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
 
@@ -13,6 +13,7 @@ from ..base import BaseWriter, CaptionNode
13
13
  from ..geometry import HorizontalAlignmentEnum
14
14
  from .constants import HORIZONTAL_ALIGNMENT_MAP, SAMI_BASE_MARKUP
15
15
 
16
+
16
17
  _NON_CSS_KEYS = frozenset(
17
18
  {
18
19
  "classes",
@@ -3,4 +3,5 @@
3
3
  from .reader import SCCReader
4
4
  from .writer import SCC_TOKENS_PER_CAPTION_MAX, SCCWriter
5
5
 
6
+
6
7
  __all__ = ["SCCReader", "SCCWriter", "SCC_TOKENS_PER_CAPTION_MAX"]
@@ -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()} - {line} - Length {len(line)}"
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
- self.last_command = ""
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: