import json import tempfile import unittest from pathlib import Path from scripts.convert_whispercpp_json import ( WhisperCppConversionError, convert_entries, convert_whispercpp_json, format_timestamp, ) def whispercpp_entry( text: str, offset_from: int, offset_to: int, timestamp_from: str = "00:00:00,000", timestamp_to: str = "00:00:01,000", ) -> dict: return { "timestamps": {"from": timestamp_from, "to": timestamp_to}, "offsets": {"from": offset_from, "to": offset_to}, "text": text, } class ConvertWhisperCppJsonTests(unittest.TestCase): def test_valid_whispercpp_json_writes_compact_json_and_text(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) input_path = root / "whispercpp.json" json_output = root / "converted.json" text_output = root / "converted.txt" input_path.write_text( json.dumps( { "transcription": [ whispercpp_entry(" Hello ", 0, 12340), whispercpp_entry("world", 12340, 13000), ] }, ensure_ascii=False, ), encoding="utf-8", ) summary = convert_whispercpp_json(input_path, json_output, text_output) converted = json.loads(json_output.read_text(encoding="utf-8")) self.assertEqual(converted["text"], "Hello world") self.assertEqual( converted["segments"], [ {"id": 0, "start": 0.0, "end": 12.34, "text": "Hello"}, {"id": 1, "start": 12.34, "end": 13.0, "text": "world"}, ], ) self.assertEqual( text_output.read_text(encoding="utf-8"), "[00:00:00.000 - 00:00:12.340] Hello\n" "[00:00:12.340 - 00:00:13.000] world\n", ) self.assertEqual(summary["original_entry_count"], 2) self.assertEqual(summary["retained_segment_count"], 2) self.assertEqual(summary["total_character_count"], len("Hello world")) self.assertEqual(summary["detected_total_duration"], 13.0) def test_empty_segments_are_skipped_and_ids_are_renumbered(self) -> None: converted = convert_entries( [ whispercpp_entry("first", 0, 1000), whispercpp_entry(" ", 1000, 2000), whispercpp_entry("", 2000, 3000), whispercpp_entry("second", 3000, 4000), ] ) self.assertEqual(converted["text"], "first second") self.assertEqual( converted["segments"], [ {"id": 0, "start": 0.0, "end": 1.0, "text": "first"}, {"id": 1, "start": 3.0, "end": 4.0, "text": "second"}, ], ) def test_missing_transcription_list_is_rejected(self) -> None: with tempfile.TemporaryDirectory() as directory: input_path = Path(directory) / "missing.json" input_path.write_text(json.dumps({"segments": []}), encoding="utf-8") with self.assertRaisesRegex( WhisperCppConversionError, "Input JSON must contain a 'transcription' list.", ): convert_whispercpp_json(input_path) def test_malformed_offsets_are_rejected(self) -> None: with self.assertRaisesRegex( WhisperCppConversionError, r"transcription\[0\]\.offsets\.from must be a number.", ): convert_entries([whispercpp_entry("bad", "0", 1000)]) with self.assertRaisesRegex( WhisperCppConversionError, r"offsets\.to must be greater than or equal to offsets\.from", ): convert_entries([whispercpp_entry("bad", 2000, 1000)]) def test_utf8_text_is_preserved(self) -> None: converted = convert_entries( [ whispercpp_entry(" Grüße für Marleen – HÜSKER ", 0, 1000), ] ) self.assertEqual(converted["text"], "Grüße für Marleen – HÜSKER") self.assertEqual( converted["segments"][0]["text"], "Grüße für Marleen – HÜSKER", ) def test_ordering_is_preserved(self) -> None: converted = convert_entries( [ whispercpp_entry("third spoken first", 3000, 4000), whispercpp_entry("first spoken second", 0, 1000), whispercpp_entry("second spoken third", 1000, 2000), ] ) self.assertEqual( [segment["text"] for segment in converted["segments"]], ["third spoken first", "first spoken second", "second spoken third"], ) def test_format_timestamp_uses_hours_minutes_seconds_milliseconds(self) -> None: self.assertEqual(format_timestamp(123.45), "00:02:03.450") self.assertEqual(format_timestamp(3723.004), "01:02:03.004") if __name__ == "__main__": unittest.main()