From 2680df478d7b6f8ba0810747e80ed4ea4ad44c07 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Mon, 20 Jul 2026 10:03:53 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(#463):=20=E7=9B=B4=E9=80=9A=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E8=B0=83=E9=80=9F=E5=A4=B1=E6=95=88=20-=20final=5Fdur?= =?UTF-8?q?ation=E6=9C=AA=E8=80=83=E8=99=91=E8=B0=83=E9=80=9F=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E5=87=8F=E9=80=9F=E5=9C=BA=E6=99=AF=E8=A2=AB=E6=88=AA?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:_render_pass_through 中 final_duration 用原始 effective_duration 计算, 没有考虑调速(setpts/atempo)导致的实际输出时长变化。 减速场景(speed<1)下视频被 -t 错误截断,加速场景因截断值大于实际 时长而表现正常但逻辑错误。 修复:改用 _clip_adjusted_duration(clip) 计算调速后的实际时长作为 final_duration 的基准,与 video_duration 取较小值。 新增3个单元测试覆盖:加速时长、减速时长、video_duration截断。 --- .../unified_render_service.py | 6 +- tests/unit/test_unified_render_service.py | 86 +++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/apps/worker/video_processing/unified_render_service.py b/apps/worker/video_processing/unified_render_service.py index 8e9d780b8..fbba0890f 100755 --- a/apps/worker/video_processing/unified_render_service.py +++ b/apps/worker/video_processing/unified_render_service.py @@ -1050,8 +1050,10 @@ class UnifiedRenderService: vf_str = ",".join(filters) - # 最终输出时长:取 clip 有效时长和 video_duration 的较小值 - final_duration = effective_duration + # 最终输出时长:取 clip 调速后有效时长和 video_duration 的较小值 + # 注意:必须用调速后的时长,否则减速场景(speed<1)会被 -t 截断 + adjusted_duration = UnifiedRenderService._clip_adjusted_duration(clip) + final_duration = adjusted_duration if video_duration > 0 and (final_duration <= 0 or final_duration > video_duration): final_duration = video_duration diff --git a/tests/unit/test_unified_render_service.py b/tests/unit/test_unified_render_service.py index 837ff2d0f..23244cc54 100755 --- a/tests/unit/test_unified_render_service.py +++ b/tests/unit/test_unified_render_service.py @@ -47,6 +47,7 @@ class FakeClip: transition_effect: str = "cut" transition_duration: float = 0.0 status: str = "ready" + playback_speed: float = 1.0 config: dict[str, Any] = field(default_factory=dict) @@ -68,6 +69,7 @@ def _make_clip( transition_effect: str = "cut", transition_duration: float = 0.0, config: dict[str, Any] | None = None, + playback_speed: float = 1.0, ) -> FakeClip: return FakeClip( id=clip_id, @@ -78,6 +80,7 @@ def _make_clip( transition_effect=transition_effect, transition_duration=transition_duration, config=config or {}, + playback_speed=playback_speed, ) @@ -1926,6 +1929,89 @@ class TestConcatNormalizeVideoResolution: assert "crop=" in vf_value, "background 层应有 crop 滤镜" + def test_pass_through_speed_up_correct_duration(self): + """直通模式加速(speed=2x):视频+音频均调速,-t 时长为原始的 1/2。""" + clips = [_make_clip("c1", "main", order=0, duration=10.0, playback_speed=2.0)] + asset_paths = {"asset_c1.mp4": Path("/tmp/asset_c1.mp4")} + svc = _make_service(clips, asset_paths) + + with ( + _patch_path_exists(), + patch("video_processing.unified_render_service.probe_duration", return_value=10.0), + patch("video_processing.unified_render_service.run_ffmpeg") as mock_run, + ): + resolved = svc._resolve_clips() + layers = svc._group_clips_into_layers(resolved) + svc._render_pass_through(layers, Path("/tmp/out.mp4")) + + assert mock_run.called + cmd = mock_run.call_args[0][0] + cmd_str = " ".join(cmd) + + # 视频调速:setpts=PTS/2.0 + assert "setpts=PTS/2.0" in cmd_str, "加速场景应有 setpts=PTS/speed 滤镜" + + # 音频调速:atempo + assert "atempo" in cmd_str, "加速场景应有 atempo 音频调速滤镜" + + # 输出时长应为原始 / speed = 10 / 2 = 5 秒 + t_idx = cmd.index("-t") + t_value = float(cmd[t_idx + 1]) + assert abs(t_value - 5.0) < 0.01, f"加速后 -t 时长应为 5.0s,实际 {t_value}s" + + def test_pass_through_slow_down_correct_duration(self): + """直通模式减速(speed=0.5x):视频+音频均调速,-t 时长为原始的 2 倍(不被截断)。""" + clips = [_make_clip("c1", "main", order=0, duration=10.0, playback_speed=0.5)] + asset_paths = {"asset_c1.mp4": Path("/tmp/asset_c1.mp4")} + svc = _make_service(clips, asset_paths) + + with ( + _patch_path_exists(), + patch("video_processing.unified_render_service.probe_duration", return_value=10.0), + patch("video_processing.unified_render_service.run_ffmpeg") as mock_run, + ): + resolved = svc._resolve_clips() + layers = svc._group_clips_into_layers(resolved) + svc._render_pass_through(layers, Path("/tmp/out.mp4")) + + assert mock_run.called + cmd = mock_run.call_args[0][0] + cmd_str = " ".join(cmd) + + # 视频调速:setpts=PTS/0.5 + assert "setpts=PTS/0.5" in cmd_str, "减速场景应有 setpts=PTS/speed 滤镜" + + # 音频调速:atempo + assert "atempo" in cmd_str, "减速场景应有 atempo 音频调速滤镜" + + # 输出时长应为原始 / speed = 10 / 0.5 = 20 秒(减速后视频变长,不应被截断) + t_idx = cmd.index("-t") + t_value = float(cmd[t_idx + 1]) + assert abs(t_value - 20.0) < 0.01, f"减速后 -t 时长应为 20.0s,实际 {t_value}s" + + def test_pass_through_speed_with_video_duration_cap(self): + """直通模式调速 + video_duration 截断:取调速后时长与 video_duration 的较小值。""" + clips = [_make_clip("c1", "main", order=0, duration=10.0, playback_speed=2.0)] + asset_paths = {"asset_c1.mp4": Path("/tmp/asset_c1.mp4")} + svc = _make_service(clips, asset_paths) + + with ( + _patch_path_exists(), + patch("video_processing.unified_render_service.probe_duration", return_value=10.0), + patch("video_processing.unified_render_service.run_ffmpeg") as mock_run, + ): + resolved = svc._resolve_clips() + layers = svc._group_clips_into_layers(resolved) + # video_duration=3.0 < 调速后时长 5.0,应取 3.0 + svc._render_pass_through(layers, Path("/tmp/out.mp4"), video_duration=3.0) + + assert mock_run.called + cmd = mock_run.call_args[0][0] + t_idx = cmd.index("-t") + t_value = float(cmd[t_idx + 1]) + assert abs(t_value - 3.0) < 0.01, f"video_duration 更小时应取 video_duration,实际 {t_value}s" + + class TestConcatNormalizeVideoFps: """视频帧率归一化:fps 滤镜统一到目标 fps。 -- 2.54.0 From b45c24cfa833fe1116e5121547e43cdfeec7c9ac Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 20 Jul 2026 11:31:04 +0800 Subject: [PATCH 2/2] style: black format test_unified_render_service.py --- tests/unit/test_unified_render_service.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/test_unified_render_service.py b/tests/unit/test_unified_render_service.py index 23244cc54..f2bba9f49 100755 --- a/tests/unit/test_unified_render_service.py +++ b/tests/unit/test_unified_render_service.py @@ -1928,7 +1928,6 @@ class TestConcatNormalizeVideoResolution: vf_value = cmd[vf_idx + 1] assert "crop=" in vf_value, "background 层应有 crop 滤镜" - def test_pass_through_speed_up_correct_duration(self): """直通模式加速(speed=2x):视频+音频均调速,-t 时长为原始的 1/2。""" clips = [_make_clip("c1", "main", order=0, duration=10.0, playback_speed=2.0)] -- 2.54.0