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.
Files changed (69) hide show
  1. {pycaption-2.3.2/pycaption.egg-info → pycaption-2.3.3}/PKG-INFO +1 -1
  2. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/base.py +27 -0
  3. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/dfxp/reader.py +3 -4
  4. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/microdvd.py +2 -3
  5. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/sami/reader.py +2 -3
  6. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/reader.py +3 -4
  7. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/srt.py +3 -3
  8. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/webvtt/reader.py +2 -17
  9. {pycaption-2.3.2 → pycaption-2.3.3/pycaption.egg-info}/PKG-INFO +1 -1
  10. {pycaption-2.3.2 → pycaption-2.3.3}/setup.py +1 -1
  11. {pycaption-2.3.2 → pycaption-2.3.3}/tests/mixins.py +12 -2
  12. {pycaption-2.3.2 → pycaption-2.3.3}/LICENSE +0 -0
  13. {pycaption-2.3.2 → pycaption-2.3.3}/MANIFEST.in +0 -0
  14. {pycaption-2.3.2 → pycaption-2.3.3}/README.rst +0 -0
  15. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/__init__.py +0 -0
  16. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/dfxp/__init__.py +0 -0
  17. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/dfxp/constants.py +0 -0
  18. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/dfxp/extras.py +0 -0
  19. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/dfxp/writer.py +0 -0
  20. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/exceptions.py +0 -0
  21. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/geometry.py +0 -0
  22. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/sami/__init__.py +0 -0
  23. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/sami/constants.py +0 -0
  24. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/sami/parser.py +0 -0
  25. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/sami/writer.py +0 -0
  26. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/__init__.py +0 -0
  27. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/constants.py +0 -0
  28. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/specialized_collections.py +0 -0
  29. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/state_machines.py +0 -0
  30. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/translator.py +0 -0
  31. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/scc/writer.py +0 -0
  32. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/transcript.py +0 -0
  33. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/utils.py +0 -0
  34. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/webvtt/__init__.py +0 -0
  35. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/webvtt/constants.py +0 -0
  36. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption/webvtt/writer.py +0 -0
  37. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption.egg-info/SOURCES.txt +0 -0
  38. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption.egg-info/dependency_links.txt +0 -0
  39. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption.egg-info/requires.txt +0 -0
  40. {pycaption-2.3.2 → pycaption-2.3.3}/pycaption.egg-info/top_level.txt +0 -0
  41. {pycaption-2.3.2 → pycaption-2.3.3}/setup.cfg +0 -0
  42. {pycaption-2.3.2 → pycaption-2.3.3}/tests/__init__.py +0 -0
  43. {pycaption-2.3.2 → pycaption-2.3.3}/tests/conftest.py +0 -0
  44. {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/__init__.py +0 -0
  45. {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/dfxp.py +0 -0
  46. {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/microdvd.py +0 -0
  47. {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/sami.py +0 -0
  48. {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/scc.py +0 -0
  49. {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/srt.py +0 -0
  50. {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/translated_scc.py +0 -0
  51. {pycaption-2.3.2 → pycaption-2.3.3}/tests/fixtures/webvtt.py +0 -0
  52. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_base.py +0 -0
  53. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_dfxp.py +0 -0
  54. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_dfxp_conversion.py +0 -0
  55. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_dfxp_extras.py +0 -0
  56. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_functions.py +0 -0
  57. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_geometry.py +0 -0
  58. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_microdvd.py +0 -0
  59. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_microdvd_conversion.py +0 -0
  60. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_sami.py +0 -0
  61. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_sami_conversion.py +0 -0
  62. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_scc.py +0 -0
  63. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_scc_conversion.py +0 -0
  64. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_scc_translator.py +0 -0
  65. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_scc_writer.py +0 -0
  66. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_srt.py +0 -0
  67. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_srt_conversion.py +0 -0
  68. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_webvtt.py +0 -0
  69. {pycaption-2.3.2 → pycaption-2.3.3}/tests/test_webvtt_conversion.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycaption
3
- Version: 2.3.2
3
+ Version: 2.3.3
4
4
  Summary: Closed caption converter
5
5
  Author: Joe Norton
6
6
  Author-email: joey@nortoncrew.com
@@ -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
- if not isinstance(content, str):
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
- if not isinstance(content, str):
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
- if not isinstance(content, str):
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
- if not isinstance(content, str):
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, InvalidInputError
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
- if not isinstance(content, str):
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
- if content.startswith(""):
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
- if isinstance(content, bytes):
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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycaption
3
- Version: 2.3.2
3
+ Version: 2.3.3
4
4
  Summary: Closed caption converter
5
5
  Author: Joe Norton
6
6
  Author-email: joey@nortoncrew.com
@@ -20,7 +20,7 @@ transcript_dependencies = ["nltk==3.10.0"]
20
20
 
21
21
  setup(
22
22
  name="pycaption",
23
- version="2.3.2",
23
+ version="2.3.3",
24
24
  description="Closed caption converter",
25
25
  long_description=open(README_PATH).read(),
26
26
  author="Joe Norton",
@@ -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 test_reader_only_supports_unicode_input(self):
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 not a unicode string."
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