146 lines
5.1 KiB
Python
146 lines
5.1 KiB
Python
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()
|