NarratoAI/app/services/test_subtitle_translator_unittest.py
viccy 9f28fcfa98 feat: 添加LLM驱动的字幕翻译功能
新增配置项以配置字幕翻译批次大小和最大并发数
补充中、英文国际化文案支持翻译相关界面
实现核心字幕翻译服务,支持批量处理与并发执行
添加WebUI界面用于触发和监控字幕翻译任务
新增完整单元测试覆盖翻译功能全流程
2026-06-11 10:09:58 +08:00

158 lines
5.3 KiB
Python

import json
import tempfile
import unittest
from pathlib import Path
from unittest import mock
from app.services import subtitle_translator as translator
SAMPLE_SRT = """1
00:00:01,000 --> 00:00:03,000
Hello, everyone.
2
00:00:04,000 --> 00:00:06,000
We are going to Beijing.
"""
BATCH_SAMPLE_SRT = """1
00:00:01,000 --> 00:00:02,000
Line one.
2
00:00:02,000 --> 00:00:03,000
Line two.
3
00:00:03,000 --> 00:00:04,000
Line three.
4
00:00:04,000 --> 00:00:05,000
Line four.
5
00:00:05,000 --> 00:00:06,000
Line five.
"""
class SubtitleTranslatorTests(unittest.TestCase):
def test_translate_srt_content_preserves_timecodes_and_rebuilds_text(self):
llm_output = {
"items": [
{"id": 1, "text": "大家好。"},
{"id": 2, "text": "我们要去北京。"},
]
}
with (
mock.patch("app.services.subtitle_translator._ensure_llm_providers_registered"),
mock.patch(
"app.services.subtitle_translator._run_async_safely",
return_value=json.dumps(llm_output, ensure_ascii=False),
) as run_llm,
):
translated = translator.translate_srt_content(
SAMPLE_SRT,
target_language="中文",
provider="openai",
api_key="sk-test",
base_url="https://llm.example/v1",
)
self.assertIn("00:00:01,000 --> 00:00:03,000", translated)
self.assertIn("大家好。", translated)
self.assertIn("我们要去北京。", translated)
self.assertNotIn("Hello, everyone.", translated)
call_kwargs = run_llm.call_args.kwargs
self.assertEqual("openai", call_kwargs["provider"])
self.assertEqual("sk-test", call_kwargs["api_key"])
self.assertEqual("https://llm.example/v1", call_kwargs["api_base"])
self.assertEqual("json", call_kwargs["response_format"])
self.assertIn("专业字幕翻译员", call_kwargs["system_prompt"])
self.assertIn("翻译为中文", call_kwargs["prompt"])
def test_translate_srt_content_rejects_missing_items(self):
llm_output = {"items": [{"id": 1, "text": "大家好。"}]}
with (
mock.patch("app.services.subtitle_translator._ensure_llm_providers_registered"),
mock.patch(
"app.services.subtitle_translator._run_async_safely",
return_value=json.dumps(llm_output, ensure_ascii=False),
),
):
with self.assertRaises(translator.SubtitleTranslationError):
translator.translate_srt_content(SAMPLE_SRT, provider="openai")
def test_translate_subtitle_file_writes_translated_srt(self):
llm_output = {
"items": [
{"id": 1, "text": "大家好。"},
{"id": 2, "text": "我们要去北京。"},
]
}
with tempfile.TemporaryDirectory() as tmp_dir:
input_file = Path(tmp_dir) / "input.srt"
output_file = Path(tmp_dir) / "output.srt"
input_file.write_text(SAMPLE_SRT, encoding="utf-8")
with (
mock.patch("app.services.subtitle_translator._ensure_llm_providers_registered"),
mock.patch(
"app.services.subtitle_translator._run_async_safely",
return_value=json.dumps(llm_output, ensure_ascii=False),
),
):
result_path = translator.translate_subtitle_file(
str(input_file),
str(output_file),
target_language="中文",
provider="openai",
)
self.assertEqual(str(output_file), result_path)
self.assertIn("大家好。", output_file.read_text(encoding="utf-8"))
def test_translate_srt_content_batches_requests_and_reports_progress(self):
progress_events = []
def fake_run_llm(*args, **kwargs):
payload_text = kwargs["prompt"].rsplit("待翻译字幕条目:", 1)[1].strip()
payload = json.loads(payload_text)
translated = {key: f"译文{key}" for key in payload}
return json.dumps(translated, ensure_ascii=False)
with (
mock.patch("app.services.subtitle_translator._ensure_llm_providers_registered"),
mock.patch(
"app.services.subtitle_translator._run_async_safely",
side_effect=fake_run_llm,
) as run_llm,
):
translated = translator.translate_srt_content(
BATCH_SAMPLE_SRT,
target_language="中文",
provider="openai",
batch_size=2,
max_workers=1,
progress_callback=lambda completed, total, message: progress_events.append(
(completed, total, message)
),
)
self.assertEqual(3, run_llm.call_count)
self.assertIn("译文1", translated)
self.assertIn("译文5", translated)
self.assertEqual((0, 5), progress_events[0][:2])
self.assertEqual((5, 5), progress_events[-1][:2])
self.assertTrue(any("完成批次" in event[2] for event in progress_events))
if __name__ == "__main__":
unittest.main()