fix(#463): 直通模式调速失效 - final_duration未考虑调速导致减速场景被截断 #625

Merged
auto-approve-bot merged 2 commits from fix/463-pass-through-speed into develop 2026-07-20 11:41:09 +08:00
2 changed files with 89 additions and 2 deletions
@@ -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
+85
View File
@@ -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,
)
@@ -1925,6 +1928,88 @@ 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)]
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。