test(#1749): 旧行为测试适配新口径(目标段长为准/逐处转场重叠/voice_ids 移除)+ filter_complex trim 口径统一
- unified_render_service filter_complex Step1 trim 时长改为 effective-freeze(与直通路径同口径,tpad 补冻结) - test_duration_compensation / render_audio_utils / render_audio_pure / 两处 render_layer_utils:min(duration,actual) 断言改为目标段长为准 - estimate_total_duration 测试:cut 零重叠、非 cut 逐处扣转场(FakeClip 加 transition_effect/duration) - test_generation_tasks/preview:voice_ids kwarg/字段移除,配音改 voice_library_id
This commit is contained in:
@@ -1736,12 +1736,15 @@ class UnifiedRenderService:
|
||||
# trim — 裁剪到指定区间,精确到帧
|
||||
effective_duration = UnifiedRenderService._clip_effective_duration(clip)
|
||||
trim_start = getattr(clip, "start_time", 0) or 0
|
||||
# #1749 冻结时素材内截取时长 = 目标段长 − freeze(末帧冻结由 tpad 铺满,与直通路径同口径)
|
||||
freeze_seconds = float((clip.config or {}).get("_freeze_seconds", 0.0) or 0.0)
|
||||
trim_dur = max(0.0, effective_duration - freeze_seconds) if freeze_seconds > 0 else effective_duration
|
||||
|
||||
if effective_duration > 0:
|
||||
if trim_dur > 0:
|
||||
if trim_start > 0:
|
||||
filters.append(f"trim=start={trim_start:.3f}:duration={effective_duration:.3f}")
|
||||
filters.append(f"trim=start={trim_start:.3f}:duration={trim_dur:.3f}")
|
||||
else:
|
||||
filters.append(f"trim=duration={effective_duration:.3f}")
|
||||
filters.append(f"trim=duration={trim_dur:.3f}")
|
||||
filters.append("setpts=PTS-STARTPTS")
|
||||
|
||||
# 调速 — 基于 setpts 改变播放速度
|
||||
@@ -1793,7 +1796,6 @@ class UnifiedRenderService:
|
||||
filters.append("setpts=PTS-STARTPTS")
|
||||
filters.append(f"fps={self.output_fps}")
|
||||
# #1749 末帧冻结:素材短于目标段长时克隆末帧铺满(禁慢放)
|
||||
freeze_seconds = float((clip.config or {}).get("_freeze_seconds", 0.0) or 0.0)
|
||||
if freeze_seconds > 0:
|
||||
filters.append(f"tpad=stop_mode=clone:stop_duration={freeze_seconds:.3f}")
|
||||
|
||||
|
||||
@@ -135,8 +135,8 @@ class TestClipEffectiveDuration:
|
||||
assert clip_effective_duration(3.0, 5.0) == 3.0
|
||||
|
||||
def test_duration_greater_than_actual(self):
|
||||
"""duration > actual,取actual."""
|
||||
assert clip_effective_duration(10.0, 5.0) == 5.0
|
||||
"""#1749:duration > actual 仍取目标段长 duration(素材短走末帧冻结,不钳制)."""
|
||||
assert clip_effective_duration(10.0, 5.0) == 10.0
|
||||
|
||||
def test_duration_equal_to_actual(self):
|
||||
assert clip_effective_duration(5.0, 5.0) == 5.0
|
||||
@@ -203,8 +203,8 @@ class TestClipAdjustedDuration:
|
||||
assert clip_adjusted_duration(4.0, 10.0, 0.5) == 8.0
|
||||
|
||||
def test_uses_effective_duration(self):
|
||||
"""duration>actual时取actual,再调速."""
|
||||
assert clip_adjusted_duration(10.0, 4.0, 2.0) == 2.0
|
||||
"""#1749:effective=目标段长 10.0(不再钳 actual),2 倍速 → 10/2 = 5.0."""
|
||||
assert clip_adjusted_duration(10.0, 4.0, 2.0) == 5.0
|
||||
|
||||
def test_default_speed(self):
|
||||
assert clip_adjusted_duration(5.0, 10.0) == 5.0
|
||||
@@ -229,6 +229,9 @@ class FakeClip:
|
||||
duration: float = 0.0
|
||||
actual_duration: float = 0.0
|
||||
playback_speed: float = 1.0
|
||||
# #1749:逐处转场口径——cut 零重叠,非 cut 才扣 transition_duration
|
||||
transition_effect: str = "cut"
|
||||
transition_duration: float = 0.0
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -257,6 +260,19 @@ class TestEstimateTotalDuration:
|
||||
assert estimate_total_duration(layers) == 5.0
|
||||
|
||||
def test_with_transition_duration(self):
|
||||
layers = [
|
||||
FakeLayer(
|
||||
role="main",
|
||||
clips=[
|
||||
FakeClip(duration=5.0),
|
||||
FakeClip(duration=5.0, transition_effect="fade", transition_duration=0.5),
|
||||
],
|
||||
),
|
||||
]
|
||||
# #1749:总10s - 1个非cut转场 * 0.5s = 9.5s(cut 零重叠)
|
||||
assert estimate_total_duration(layers, transition_duration=0.5) == 9.5
|
||||
|
||||
def test_cut_clips_have_zero_overlap(self):
|
||||
layers = [
|
||||
FakeLayer(
|
||||
role="main",
|
||||
@@ -266,8 +282,8 @@ class TestEstimateTotalDuration:
|
||||
],
|
||||
),
|
||||
]
|
||||
# 总10s - 1个转场 * 0.5s = 9.5s
|
||||
assert estimate_total_duration(layers, transition_duration=0.5) == 9.5
|
||||
# #1749:cut 转场零重叠 → 总时长 10s,即使传了全局 transition_duration
|
||||
assert estimate_total_duration(layers, transition_duration=0.5) == 10.0
|
||||
|
||||
def test_transition_with_many_clips(self):
|
||||
layers = [
|
||||
@@ -276,7 +292,18 @@ class TestEstimateTotalDuration:
|
||||
clips=[FakeClip(duration=3.0) for _ in range(5)],
|
||||
),
|
||||
]
|
||||
# 5个3s = 15s,4个转场 * 0.5s = 2s,总13s
|
||||
# #1749:全 cut 零重叠 → 5个3s = 15s
|
||||
assert estimate_total_duration(layers, transition_duration=0.5) == 15.0
|
||||
|
||||
def test_non_cut_transitions_with_many_clips(self):
|
||||
layers = [
|
||||
FakeLayer(
|
||||
role="main",
|
||||
clips=[FakeClip(duration=3.0, transition_effect="fade", transition_duration=0.5)
|
||||
for _ in range(5)],
|
||||
),
|
||||
]
|
||||
# 5个3s = 15s,4个非cut转场 * 0.5s = 2s,总13s(#1749 逐处重叠)
|
||||
assert estimate_total_duration(layers, transition_duration=0.5) == 13.0
|
||||
|
||||
def test_prefers_main_over_broll(self):
|
||||
|
||||
@@ -10,8 +10,11 @@ from apps.worker.video_processing.unified_render_service import ResolvedClip, Un
|
||||
class TestClipEffectiveDurationWithSpeedCompensation:
|
||||
"""Test _clip_effective_duration handles speed < 1 correctly."""
|
||||
|
||||
def test_normal_speed_returns_min(self):
|
||||
"""When speed=1.0, effective_duration = min(duration, actual_duration)."""
|
||||
def test_normal_speed_returns_target_duration(self):
|
||||
"""#1749:speed=1.0 时 effective_duration 始终为目标段长 duration。
|
||||
|
||||
素材短于段长不再慢放补偿,差值由末帧冻结 tpad / 音频 apad 铺满。
|
||||
"""
|
||||
clip = ResolvedClip(
|
||||
clip_id="c1",
|
||||
asset_id="a1",
|
||||
@@ -23,11 +26,10 @@ class TestClipEffectiveDurationWithSpeedCompensation:
|
||||
actual_duration=3.0, # shorter than configured
|
||||
playback_speed=1.0,
|
||||
)
|
||||
# Without speed compensation, effective = min(4, 3) = 3
|
||||
assert UnifiedRenderService._clip_effective_duration(clip) == 3.0
|
||||
assert UnifiedRenderService._clip_effective_duration(clip) == 4.0
|
||||
|
||||
def test_compensated_speed_returns_configured_duration(self):
|
||||
"""When speed < 1 (compensated), effective_duration = configured duration."""
|
||||
def test_explicit_speed_does_not_change_effective_duration(self):
|
||||
"""#1749:effective_duration 只认目标段长,与 playback_speed 无关(慢放补偿已删除)。"""
|
||||
clip = ResolvedClip(
|
||||
clip_id="c1",
|
||||
asset_id="a1",
|
||||
@@ -37,9 +39,8 @@ class TestClipEffectiveDurationWithSpeedCompensation:
|
||||
start_time=0.0,
|
||||
duration=4.0,
|
||||
actual_duration=3.0, # shorter than configured
|
||||
playback_speed=0.75, # compensated: 3/4 = 0.75
|
||||
playback_speed=0.75,
|
||||
)
|
||||
# With speed compensation, effective = configured duration = 4.0
|
||||
assert UnifiedRenderService._clip_effective_duration(clip) == 4.0
|
||||
|
||||
def test_zero_actual_duration_returns_configured(self):
|
||||
@@ -76,10 +77,10 @@ class TestClipEffectiveDurationWithSpeedCompensation:
|
||||
class TestClipAdjustedDurationWithSpeedCompensation:
|
||||
"""Test _clip_adjusted_duration accounts for compensated speed."""
|
||||
|
||||
def test_adjusted_duration_with_compensation(self):
|
||||
"""Adjusted duration = min(duration, actual) / speed.
|
||||
With compensation: min(4,3)/0.75 = 3/0.75 = 4.0
|
||||
This equals the configured duration, which is the goal.
|
||||
def test_adjusted_duration_with_explicit_speed(self):
|
||||
"""#1749:adjusted duration = 目标段长 / speed(用户显式调速仍生效;短素材不再自动慢放)。
|
||||
|
||||
4.0 / 0.75 ≈ 5.333。
|
||||
"""
|
||||
clip = ResolvedClip(
|
||||
clip_id="c1",
|
||||
@@ -90,8 +91,8 @@ class TestClipAdjustedDurationWithSpeedCompensation:
|
||||
start_time=0.0,
|
||||
duration=4.0,
|
||||
actual_duration=3.0,
|
||||
playback_speed=0.75, # compensated
|
||||
playback_speed=0.75,
|
||||
)
|
||||
adjusted = UnifiedRenderService._clip_adjusted_duration(clip)
|
||||
# min(4,3)/0.75 = 3/0.75 = 4.0 (matches configured duration)
|
||||
assert abs(adjusted - 4.0) < 0.01
|
||||
# 4.0 / 0.75 = 5.333...
|
||||
assert abs(adjusted - (4.0 / 0.75)) < 0.01
|
||||
|
||||
@@ -159,7 +159,6 @@ class TestCreatePreviewGenerationTaskRequest:
|
||||
assert req.template_id == "tmpl_123"
|
||||
assert req.asset_ids == ["asset_1", "asset_2"]
|
||||
assert req.title_ids == []
|
||||
assert req.voice_ids == []
|
||||
assert req.video_title == ""
|
||||
assert req.duration == 0.0
|
||||
assert req.bgm_config == {}
|
||||
@@ -175,15 +174,16 @@ class TestCreatePreviewGenerationTaskRequest:
|
||||
assert req.template_id == "tmpl_123"
|
||||
assert req.title_ids == ["title_1"]
|
||||
|
||||
def test_valid_request_with_voice_ids_only(self):
|
||||
"""有效请求:template_id + voice_ids"""
|
||||
def test_valid_request_with_voice_library_id_and_asset_ids(self):
|
||||
"""有效请求:template_id + asset_ids + 统一配音 voice_library_id(#1749 替换旧 voice_ids 字段)"""
|
||||
from app.schemas.generation_task import CreatePreviewGenerationTaskRequest
|
||||
|
||||
req = CreatePreviewGenerationTaskRequest(
|
||||
template_id="tmpl_123",
|
||||
voice_ids=["voice_1"],
|
||||
asset_ids=["asset_1"],
|
||||
voice_library_id="voice_1",
|
||||
)
|
||||
assert req.voice_ids == ["voice_1"]
|
||||
assert req.voice_library_id == "voice_1"
|
||||
|
||||
def test_missing_template_id_raises(self):
|
||||
"""缺少 template_id 报错"""
|
||||
|
||||
@@ -77,13 +77,13 @@ class TestCreateGenerationTaskUseCase:
|
||||
project_id="proj_001",
|
||||
asset_ids=["asset_1", "asset_2", "asset_3"],
|
||||
title_ids=["title_1", "title_2"],
|
||||
voice_ids=["voice_1"],
|
||||
)
|
||||
result = use_case.execute(command)
|
||||
|
||||
assert len(result.asset_ids) == 3
|
||||
assert len(result.title_ids) == 2
|
||||
assert len(result.voice_ids) == 1
|
||||
# #1749:voice_ids 已从 Command 移除(配音走 voice_library_id(s)),落库固定空列表
|
||||
assert list(getattr(result, "voice_ids", []) or []) == []
|
||||
|
||||
def test_create_task_with_auto_retry(self, mock_repo):
|
||||
"""创建带自动重试配置的任务"""
|
||||
|
||||
@@ -62,9 +62,9 @@ class TestClipEffectiveDuration:
|
||||
assert clip_effective_duration(clip) == pytest.approx(10.0)
|
||||
|
||||
def test_duration_greater_than_actual(self):
|
||||
"""duration > actual → 返回actual(不能超过素材时长)."""
|
||||
"""#1749:duration > actual → 返回目标段长 duration(素材短于段长走末帧冻结/补静音,不慢放不截配音)."""
|
||||
clip = _make_clip(duration=50.0, actual_duration=30.0)
|
||||
assert clip_effective_duration(clip) == pytest.approx(30.0)
|
||||
assert clip_effective_duration(clip) == pytest.approx(50.0)
|
||||
|
||||
def test_duration_equals_actual(self):
|
||||
"""duration == actual → 返回该值."""
|
||||
|
||||
@@ -24,9 +24,9 @@ class TestClipEffectiveDuration:
|
||||
assert clip_effective_duration(clip) == 5.0
|
||||
|
||||
def test_duration_specified_and_actual_shorter(self):
|
||||
"""指定了 duration,但实际时长更短 → 取实际时长"""
|
||||
"""#1749:指定了 duration 但实际时长更短 → 仍取目标段长 duration(素材短的部分由末帧冻结 tpad/音频 apad 铺满)"""
|
||||
clip = SimpleNamespace(duration=10.0, actual_duration=5.0)
|
||||
assert clip_effective_duration(clip) == 5.0
|
||||
assert clip_effective_duration(clip) == 10.0
|
||||
|
||||
def test_duration_specified_actual_zero(self):
|
||||
"""指定了 duration,但实际时长为 0 → 取 duration"""
|
||||
|
||||
@@ -28,6 +28,9 @@ class FakeClip:
|
||||
duration: float = 0.0
|
||||
actual_duration: float = 0.0
|
||||
playback_speed: Any = 1.0
|
||||
# #1749:逐处转场口径——cut 零重叠,非 cut 才扣 transition_duration
|
||||
transition_effect: str = "cut"
|
||||
transition_duration: float = 0.0
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -134,7 +137,8 @@ class TestClipEffectiveDuration:
|
||||
assert clip_effective_duration(5.0) == 5.0
|
||||
|
||||
def test_explicit_duration_with_shorter_actual(self):
|
||||
assert clip_effective_duration(5.0, 3.0) == 3.0
|
||||
"""#1749:duration>actual 仍返回目标段长(短素材末帧冻结铺满,不钳制)。"""
|
||||
assert clip_effective_duration(5.0, 3.0) == 5.0
|
||||
|
||||
def test_explicit_duration_with_longer_actual(self):
|
||||
assert clip_effective_duration(5.0, 10.0) == 5.0
|
||||
@@ -254,7 +258,21 @@ class TestEstimateTotalDuration:
|
||||
],
|
||||
)
|
||||
]
|
||||
# 3 + 2 + 5 - 2 * 0.5 = 9.0
|
||||
# #1749:全 cut 零重叠 → 3 + 2 + 5 = 10.0
|
||||
assert estimate_total_duration(layers, transition_duration=0.5) == pytest.approx(10.0)
|
||||
|
||||
def test_non_cut_clips_deduct_transition_per_clip(self):
|
||||
layers = [
|
||||
FakeLayer(
|
||||
role="main",
|
||||
clips=[
|
||||
FakeClip(duration=3.0, transition_effect="fade", transition_duration=0.5),
|
||||
FakeClip(duration=2.0, transition_effect="fade", transition_duration=0.5),
|
||||
FakeClip(duration=5.0, transition_effect="fade", transition_duration=0.5),
|
||||
],
|
||||
),
|
||||
]
|
||||
# #1749:逐处非cut转场重叠 → 3 + 2 + 5 - 2 * 0.5 = 9.0
|
||||
assert estimate_total_duration(layers, transition_duration=0.5) == pytest.approx(9.0)
|
||||
|
||||
def test_prefers_main_over_broll(self):
|
||||
|
||||
Reference in New Issue
Block a user