mirror of
https://github.com/linyqh/NarratoAI.git
synced 2026-07-19 12:37:54 +00:00
新增配置项以配置字幕翻译批次大小和最大并发数 补充中、英文国际化文案支持翻译相关界面 实现核心字幕翻译服务,支持批量处理与并发执行 添加WebUI界面用于触发和监控字幕翻译任务 新增完整单元测试覆盖翻译功能全流程
158 lines
5.3 KiB
Python
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()
|