"""统一渲染管线 — 预览与确认生成使用相同品质参数。 验证点: 1. UnifiedRenderService 不再有 is_preview 参数 2. 所有渲染统一使用 medium preset + CRF 23 3. RenderAdapter 统一执行校验和缩略图生成 4. generation.py 并行下载逻辑(保留) """ from __future__ import annotations import os import tempfile from pathlib import Path from unittest.mock import MagicMock, patch import pytest # ── 1. UnifiedRenderService 无 is_preview 参数 ── class TestUnifiedRenderServiceNoPreviewParam: """UnifiedRenderService 构造函数不再接受 is_preview 参数。""" def test_constructor_has_no_is_preview(self): import inspect from video_processing.unified_render_service import UnifiedRenderService sig = inspect.signature(UnifiedRenderService.__init__) param_names = list(sig.parameters.keys()) assert ( "is_preview" not in param_names ), f"is_preview should be removed from UnifiedRenderService.__init__, found params: {param_names}" def test_no_is_preview_attribute(self): from video_processing.unified_render_service import UnifiedRenderService svc = UnifiedRenderService( plan=MagicMock(id="test"), clips=[], asset_path_map={}, work_dir=Path(tempfile.mkdtemp()), ) assert not hasattr( svc, "is_preview" ), "UnifiedRenderService should not have is_preview attribute after unification" # ── 2. FFmpeg 参数统一为 medium + CRF 23 ── class TestUnifiedFFmpegPreset: """所有渲染统一使用 medium preset + CRF 23。""" def _make_clip(self): from video_processing.unified_render_service import ResolvedClip return ResolvedClip( clip_id="c1", asset_id="a1", local_path=Path("/tmp/fake.mp4"), clip_type="main", order=0, start_time=0, duration=10.0, playback_speed=1.0, transition_effect="cut", transition_duration=0.0, config={}, ) @patch("video_processing.unified_render_service.run_ffmpeg") def test_execute_ffmpeg_uses_medium_crf23(self, mock_run): from video_processing.unified_render_service import ( RenderLayer, UnifiedRenderService, ) plan = MagicMock() plan.id = "test_plan" plan.config = {} clip = self._make_clip() svc = UnifiedRenderService( plan=plan, clips=[clip], asset_path_map={"a1": Path("/tmp/fake.mp4")}, work_dir=Path(tempfile.mkdtemp()), output_width=1920, output_height=1080, ) layers = [RenderLayer(role="main", clips=[clip])] filter_complex, input_args = svc._build_filter_complex(layers) output_path = Path(tempfile.mkdtemp()) / "out.mp4" svc._execute_ffmpeg(filter_complex, input_args, output_path) mock_run.assert_called_once() cmd = mock_run.call_args[0][0] # Check preset is medium (no conditional) preset_idx = cmd.index("-preset") assert cmd[preset_idx + 1] == "medium", f"Expected medium, got {cmd[preset_idx + 1]}" # Check crf is 23 (no conditional) crf_idx = cmd.index("-crf") assert cmd[crf_idx + 1] == "23", f"Expected crf 23, got {cmd[crf_idx + 1]}" # ── 3. RenderAdapter 统一执行校验和缩略图 ── class TestRenderAdapterUnifiedPostProcess: """RenderAdapter 不再跳过校验和缩略图。""" def test_render_adapter_no_is_preview_param(self): import inspect from video_processing.render_adapter import RenderAdapter # Check render_from_memory signature sig = inspect.signature(RenderAdapter.render_from_memory) param_names = list(sig.parameters.keys()) assert ( "is_preview" not in param_names ), f"is_preview should be removed from render_from_memory, found params: {param_names}" def test_no_preview_skip_validation_in_source(self): """渲染适配器源码中不再包含预览跳过校验的逻辑。""" with open("apps/worker/video_processing/render_adapter.py") as f: source = f.read() assert "预览模式:跳过输出校验" not in source, "Should not skip validation in any mode" assert "if not is_preview:" not in source, "Thumbnail should always be generated" # ── 4. generation.py 不再有 is_preview 覆盖逻辑 ── class TestWorkerGenerationNoPreviewOverride: """Worker generation.py 不再覆盖预览分辨率为 480p。""" def test_no_480p_override(self): with open("apps/worker/worker_app/tasks/generation.py") as f: source = f.read() assert 'resolution = "854x480"' not in source, "Should not override resolution to 480p in preview mode" assert 'bitrate = "1M"' not in source, "Should not override bitrate to 1M in preview mode" # ── 5. generation_preview.py 不再有 PREVIEW_RESOLUTION ── class TestPreviewNoLowQualityConstants: """预览 API 不再定义低质量常量。""" def test_no_preview_resolution_constant(self): with open("apps/api/app/api/routes/generation_preview.py") as f: source = f.read() assert "PREVIEW_RESOLUTION" not in source, "PREVIEW_RESOLUTION constant should be removed" assert "_calc_preview_resolution" not in source, "_calc_preview_resolution function should be removed"