From 94219f9068e23c7a454de5e4cd1f3c84ece1059b Mon Sep 17 00:00:00 2001 From: eeshsaxena Date: Sun, 16 Aug 2026 10:31:25 +0530 Subject: [PATCH] Do not crash on empty WebVTT input or empty SRT cue text Two subtitle readers raised on trivial input the parsers otherwise tolerate: - vtt.to_model on an empty document iterated straight to the None terminator in the START state and called None.startswith, raising AttributeError. - srt.to_model hit UnboundLocalError on a cue whose text body is empty (a time line immediately followed by a blank line), because subtitle_text was only initialized when a non-empty text line was seen. Guard the None line in the WebVTT START state, and initialize subtitle_text when the paragraph is created. --- src/main/python/ttconv/srt/reader.py | 1 + src/main/python/ttconv/vtt/reader.py | 2 ++ src/test/python/test_srt_reader.py | 13 +++++++++++++ src/test/python/test_vtt_reader.py | 5 +++++ 4 files changed, 21 insertions(+) diff --git a/src/main/python/ttconv/srt/reader.py b/src/main/python/ttconv/srt/reader.py index 5b1b89eb..ed2e4309 100644 --- a/src/main/python/ttconv/srt/reader.py +++ b/src/main/python/ttconv/srt/reader.py @@ -270,6 +270,7 @@ def to_model(data_file: typing.IO, _config: SRTReaderConfiguration = None, progr return None current_p = model.P(doc) + subtitle_text = "" current_p.set_begin( int(m.group('begin_h')) * 3600 + diff --git a/src/main/python/ttconv/vtt/reader.py b/src/main/python/ttconv/vtt/reader.py index 7682632e..4ff7985d 100644 --- a/src/main/python/ttconv/vtt/reader.py +++ b/src/main/python/ttconv/vtt/reader.py @@ -547,6 +547,8 @@ class _State(Enum): for line_index, line in enumerate(_none_terminated(lines)): if state is _State.START: + if line is None: + break if not line.startswith("WEBVTT"): LOGGER.warning("The first line of the file does not start with WEBVTT") state = _State.LOOKING diff --git a/src/test/python/test_srt_reader.py b/src/test/python/test_srt_reader.py index e1c3f9df..97fb5b9f 100644 --- a/src/test/python/test_srt_reader.py +++ b/src/test/python/test_srt_reader.py @@ -546,6 +546,19 @@ def test_alignment_multiple_tags_uses_first(self): self.assertNotIn("{\\an", text_content) self.assertIn("Multiple tags here", text_content) + def test_empty_cue_text(self): + # A cue whose text body is empty (a time line immediately followed by a + # blank line) used to raise UnboundLocalError. + f = io.StringIO( + "1\n" + "00:00:01,000 --> 00:00:04,000\n" + "\n" + "2\n" + "00:00:05,000 --> 00:00:08,000\n" + "Second\n" + ) + self.assertIsNotNone(to_model(f)) + if __name__ == '__main__': unittest.main() diff --git a/src/test/python/test_vtt_reader.py b/src/test/python/test_vtt_reader.py index 76e57805..769a3900 100644 --- a/src/test/python/test_vtt_reader.py +++ b/src/test/python/test_vtt_reader.py @@ -438,5 +438,10 @@ def test_default_positioning(self): self.assertEqual(o.x.value, 100*1/40) self.assertEqual(e.width.value, 100 - 2*100*1/40) + def test_empty_input(self): + # An empty document used to raise AttributeError on the None sentinel. + self.assertIsNotNone(to_model(io.StringIO(""))) + self.assertIsNotNone(to_model(io.StringIO(" \n"))) + if __name__ == '__main__': unittest.main()