import copy import unittest from subtitles import convert, timestamp class SubtitleTests(unittest.TestCase): def test_minute_long_segment_becomes_short_cues(self): text = ( "Okay, all right, brilliant. Oh dude, we've got it this time. " "You know what? I can feel it. Okay, we found the longer self " "tappers that are for these little SSD cooling fans. " ) * 4 tokens = text.split() words = [ {"word": word, "start": 971.68 + i * 0.4, "end": 971.68 + i * 0.4 + 0.35} for i, word in enumerate(tokens) ] result = convert( { "text": text, "words": words, "segments": [{"text": text, "start": 971.68, "end": 1037.42}], } ) cues = result.strip().split("\n\n") self.assertGreater(len(cues), 10) recovered = [] for index, cue in enumerate(cues, 1): number, timing, *body = cue.splitlines() self.assertEqual(int(number), index) self.assertLessEqual(len(body), 2) self.assertTrue(all(len(line) <= 42 for line in body)) def seconds(value): h, m, s = value.replace(",", ".").split(":") return int(h) * 3600 + int(m) * 60 + float(s) start, end = map(seconds, timing.split(" --> ")) self.assertLessEqual(end - start, 6.001) recovered.extend(" ".join(body).split()) self.assertEqual(recovered, tokens) self.assertIn("00:16:11,680", result) def test_pause_sentence_and_speaker_boundaries(self): result = convert( { "words": [ {"word": "First sentence.", "start": 0, "end": 1.2, "speaker": 0}, {"word": "Next", "start": 1.3, "end": 1.6, "speaker": 0}, {"word": "pause", "start": 3, "end": 3.4, "speaker": 0}, {"word": "speaker", "start": 3.5, "end": 4, "speaker": 1}, ] } ) self.assertEqual(len(result.strip().split("\n\n")), 4) def test_missing_and_invalid_timestamps(self): for response in ( {"text": "speech"}, {"text": "speech", "words": []}, {"words": [{"word": "bad", "start": 2, "end": 1}]}, {"words": [{"word": "bad", "start": float("nan"), "end": 1}]}, {"words": [{"word": "bad", "start": -1, "end": 1}]}, {"words": [{"word": "bad", "start": 0, "end": float("inf")}]}, {"words": [{"word": "bad", "start": True, "end": 1}]}, {"error": {"message": "failed"}}, ): with self.assertRaises(ValueError): convert(response) def test_regressing_timestamps_preserve_text_and_response(self): response = {"words": [ {"word": "Glasfaser", "start": 10, "end": 10.4}, {"word": "mit", "start": 9.8, "end": 10.2}, {"word": "10", "start": 9.7, "end": 9.9}, {"word": "Gbit/s", "start": 10.5, "end": 11}, ]} original = copy.deepcopy(response) self.assertEqual( convert(response), "1\n00:00:10,000 --> 00:00:11,000\nGlasfaser mit 10 Gbit/s\n\n", ) self.assertEqual(response, original) def test_regression_across_cues_and_zero_duration(self): self.assertEqual(convert({"words": [ {"word": "First", "start": 10, "end": 11, "speaker": 0}, {"word": "Second", "start": 9, "end": 9.5, "speaker": 1}, {"word": "Third", "start": 12, "end": 13, "speaker": 0}, ]}), ( "1\n00:00:10,000 --> 00:00:11,000\nFirst\n\n" "2\n00:00:10,000 --> 00:00:10,001\nSecond\n\n" "3\n00:00:12,000 --> 00:00:13,000\nThird\n\n" )) def test_blank_words_do_not_shift_timing(self): self.assertEqual(convert({"words": [ {"word": " ", "start": 20, "end": 21}, {"word": "Speech", "start": 10, "end": 11}, ]}), "1\n00:00:10,000 --> 00:00:11,000\nSpeech\n\n") def test_regression_does_not_hide_invalid_duration(self): with self.assertRaises(ValueError): convert({"words": [ {"word": "valid", "start": 10, "end": 11}, {"word": "invalid", "start": 9, "end": 8}, ]}) def test_empty_and_rounding(self): self.assertEqual(convert({"words": [], "text": ""}), "") self.assertEqual(timestamp(59.9996), "00:01:00,000") self.assertEqual(timestamp(3601.234), "01:00:01,234") if __name__ == "__main__": unittest.main()