diff --git a/apps/worker/video_processing/unified_render_service.py b/apps/worker/video_processing/unified_render_service.py index c97e6f55d..c17e965ae 100755 --- a/apps/worker/video_processing/unified_render_service.py +++ b/apps/worker/video_processing/unified_render_service.py @@ -47,13 +47,12 @@ from video_processing.reverse_engine import ReverseConfig, ReverseEngine from video_processing.speed_engine import SpeedEngine from video_processing.sticker_engine import StickerEngine from video_processing.subtitle_generator import generate_ass_from_timeline - -from packages.domain.ass_subtitle_builder import build_ass_content from video_processing.transition_engine import TransitionEngine from video_processing.trim_engine import TrimConfig, TrimEngine, extract_trim_from_clip_config from video_processing.tts_engine import TtsEngine from video_processing.watermark_engine import WatermarkConfig, WatermarkEngine +from packages.domain.ass_subtitle_builder import build_ass_content from packages.domain.render_layer_utils import LAYER_Z_INDEX as _IMPORTED_LAYER_Z_INDEX from packages.domain.render_layer_utils import clip_adjusted_duration as _clip_adjusted_duration_pure from packages.domain.render_layer_utils import clip_effective_duration as _clip_effective_duration_pure @@ -133,7 +132,6 @@ _PIP_SCALE = 0.25 # PiP 占主画面的比例 # ── 统一渲染引擎 ───────────────────────────────────────────────────────────── - def _overlay_title_on_ass( ass_path: Path, *, diff --git a/tests/unit/test_overlay_title_on_ass.py b/tests/unit/test_overlay_title_on_ass.py index 9a67e85f6..f085fe4c8 100644 --- a/tests/unit/test_overlay_title_on_ass.py +++ b/tests/unit/test_overlay_title_on_ass.py @@ -7,10 +7,11 @@ 4. ASR 失败但有标题时,降级生成标题 ASS """ -import pytest from pathlib import Path from unittest.mock import MagicMock, patch +import pytest + from apps.worker.video_processing.unified_render_service import _overlay_title_on_ass @@ -122,7 +123,7 @@ class TestMaybeGenerateAssWithTitle: def _make_service(self, tmp_path, plan_config, asr_service=None): """创建简化的 UnifiedRenderService 实例用于测试""" from apps.worker.video_processing.unified_render_service import UnifiedRenderService - + service = object.__new__(UnifiedRenderService) service.plan = MagicMock() service.plan.id = "test_plan_001" @@ -138,14 +139,21 @@ class TestMaybeGenerateAssWithTitle: def test_asr_path_with_title_overlays_title(self, tmp_path): """ASR 路径 + 有标题 → 标题叠加到 ASS 文件""" plan_config = { - "title": {"text": "测试标题", "enabled": True, "position": "top", "size": 48, "font": "思源黑体", "color": "#ffffff"}, + "title": { + "text": "测试标题", + "enabled": True, + "position": "top", + "size": 48, + "font": "思源黑体", + "color": "#ffffff", + }, "subtitle": {"enabled": True, "auto_generated": True}, } - + # Mock ASR service mock_asr = MagicMock() service = self._make_service(tmp_path, plan_config, asr_service=mock_asr) - + # Mock _generate_asr_subtitles to return a timeline with segments mock_timeline = MagicMock() mock_segment = MagicMock() @@ -154,10 +162,10 @@ class TestMaybeGenerateAssWithTitle: mock_segment.text = "ASR识别的文字" mock_timeline.segments = [mock_segment] mock_timeline.segment_count = 1 - - with patch.object(service, '_generate_asr_subtitles', return_value=mock_timeline): + + with patch.object(service, "_generate_asr_subtitles", return_value=mock_timeline): result = service._maybe_generate_ass(video_duration=10.0) - + assert result is not None, "应生成 ASS 文件" content = result.read_text(encoding="utf-8") assert "测试标题" in content, "标题应出现在 ASS 文件中" @@ -169,17 +177,17 @@ class TestMaybeGenerateAssWithTitle: "title": {"text": "仅标题", "enabled": True, "position": "top", "size": 48}, "subtitle": {"enabled": True, "auto_generated": True}, } - + mock_asr = MagicMock() service = self._make_service(tmp_path, plan_config, asr_service=mock_asr) - + # Mock ASR returns empty timeline mock_timeline = MagicMock() mock_timeline.segments = [] - - with patch.object(service, '_generate_asr_subtitles', return_value=mock_timeline): + + with patch.object(service, "_generate_asr_subtitles", return_value=mock_timeline): result = service._maybe_generate_ass(video_duration=10.0) - + assert result is not None, "有标题时应生成 ASS 文件" content = result.read_text(encoding="utf-8") assert "仅标题" in content, "标题应出现在 ASS 文件中" @@ -190,14 +198,14 @@ class TestMaybeGenerateAssWithTitle: "title": {"text": "降级标题", "enabled": True, "position": "top", "size": 48}, "subtitle": {"enabled": True, "auto_generated": True}, } - + mock_asr = MagicMock() service = self._make_service(tmp_path, plan_config, asr_service=mock_asr) - + # Mock ASR raises exception - with patch.object(service, '_generate_asr_subtitles', side_effect=RuntimeError("ASR error")): + with patch.object(service, "_generate_asr_subtitles", side_effect=RuntimeError("ASR error")): result = service._maybe_generate_ass(video_duration=10.0) - + assert result is not None, "ASR 失败但有标题时应生成 ASS 文件" content = result.read_text(encoding="utf-8") assert "降级标题" in content, "标题应出现在降级 ASS 文件中"