fix: 硬切场景改用concat filter替代xfade,提升多素材场景性能与稳定性
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 1m19s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1516h27m30s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1516h27m32s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1516h27m32s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1516h27m30s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1516h27m34s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1516h27m34s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1516h27m34s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1516h27m34s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1516h59m11s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1516h59m15s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1516h59m15s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 1m19s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1516h27m30s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1516h27m32s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1516h27m32s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1516h27m30s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1516h27m34s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1516h27m34s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1516h27m34s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1516h27m34s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1516h59m11s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1516h59m15s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1516h59m15s
- 全硬切(no transition)场景下使用concat filter,性能远优于xfade - 有转场效果时继续走xfade路径,保持兼容 - 修复硬切默认走xfade(fade fallback)的设计问题 - 新增单元测试覆盖concat和xfade两种路径 - 87个unified_render测试全绿,全套2147测试零回归
This commit is contained in:
@@ -1363,23 +1363,39 @@ class UnifiedRenderService:
|
||||
# 单 clip 层,直接使用预处理标签
|
||||
layer_output_labels[layer.role] = layer_labels[0]
|
||||
else:
|
||||
# 多 clip 层,用 TransitionEngine 构建转场链
|
||||
out_label = f"{layer.role}_merged"
|
||||
# 计算该层使用的转场时长(取首个非零值,否则用默认)
|
||||
layer_dur = 0.0
|
||||
for d in layer_transition_durations:
|
||||
if d > 0:
|
||||
layer_dur = d
|
||||
break
|
||||
xfade_filter, _ = self._transition_engine.build_xfade_chain(
|
||||
clip_durations=layer_durations,
|
||||
clip_video_labels=layer_labels,
|
||||
transitions=layer_transitions,
|
||||
transition_duration=layer_dur if layer_dur > 0 else None,
|
||||
output_label=out_label,
|
||||
# 判断是否全部为硬切:是则用 concat filter,否则用 xfade 转场链
|
||||
all_cut = all(
|
||||
t is None or t == "" or str(t).lower() == "cut"
|
||||
for t in layer_transitions[1:] # 第一个 clip 的转场忽略
|
||||
)
|
||||
if xfade_filter:
|
||||
filter_parts.append(xfade_filter)
|
||||
if all_cut:
|
||||
# 全硬切:用 concat filter,性能远优于 xfade
|
||||
concat_inputs = "".join(f"[{label}]" for label in layer_labels)
|
||||
filter_parts.append(
|
||||
f"{concat_inputs}concat=n={len(layer_labels)}:v=1:a=0[{out_label}]"
|
||||
)
|
||||
logger.info(
|
||||
"[unified-render] layer=%s clips=%d using concat (all hard-cut)",
|
||||
layer.role,
|
||||
len(layer_labels),
|
||||
)
|
||||
else:
|
||||
# 有转场效果:用 TransitionEngine 构建 xfade 链
|
||||
layer_dur = 0.0
|
||||
for d in layer_transition_durations:
|
||||
if d > 0:
|
||||
layer_dur = d
|
||||
break
|
||||
xfade_filter, _ = self._transition_engine.build_xfade_chain(
|
||||
clip_durations=layer_durations,
|
||||
clip_video_labels=layer_labels,
|
||||
transitions=layer_transitions,
|
||||
transition_duration=layer_dur if layer_dur > 0 else None,
|
||||
output_label=out_label,
|
||||
)
|
||||
if xfade_filter:
|
||||
filter_parts.append(xfade_filter)
|
||||
layer_output_labels[layer.role] = out_label
|
||||
|
||||
# Step 3: 合成各层
|
||||
|
||||
@@ -45,6 +45,7 @@ class FakeClip:
|
||||
start_time: float = 0.0
|
||||
duration: float = 0.0
|
||||
transition_effect: str = "cut"
|
||||
transition_duration: float = 0.0
|
||||
status: str = "ready"
|
||||
config: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@@ -65,6 +66,7 @@ def _make_clip(
|
||||
asset_id: str = "",
|
||||
duration: float = 0.0,
|
||||
transition_effect: str = "cut",
|
||||
transition_duration: float = 0.0,
|
||||
config: dict[str, Any] | None = None,
|
||||
) -> FakeClip:
|
||||
return FakeClip(
|
||||
@@ -74,6 +76,7 @@ def _make_clip(
|
||||
asset_id=asset_id or f"asset_{clip_id}.mp4",
|
||||
duration=duration,
|
||||
transition_effect=transition_effect,
|
||||
transition_duration=transition_duration,
|
||||
config=config or {},
|
||||
)
|
||||
|
||||
@@ -332,7 +335,7 @@ class TestBuildFilterComplex:
|
||||
assert "[final_video]" in fc
|
||||
|
||||
def test_single_layer_multi_clips(self):
|
||||
"""多个 main clips → xfade 串联。"""
|
||||
"""多个 main clips(默认硬切)→ concat 串联。"""
|
||||
clips = [
|
||||
_make_clip("c1", "main", order=0, duration=3.0),
|
||||
_make_clip("c2", "main", order=1, duration=3.0),
|
||||
@@ -343,6 +346,35 @@ class TestBuildFilterComplex:
|
||||
}
|
||||
svc = _make_service(clips, asset_paths)
|
||||
|
||||
with _patch_path_exists(), patch("video_processing.unified_render_service.probe_duration", return_value=5.0):
|
||||
resolved = svc._resolve_clips()
|
||||
layers = svc._group_clips_into_layers(resolved)
|
||||
fc, input_args = svc._build_filter_complex(layers)
|
||||
|
||||
assert input_args.count("-i") == 2
|
||||
# 全硬切场景走 concat filter(性能远优于 xfade)
|
||||
assert "concat=n=2:v=1:a=0" in fc
|
||||
assert "[final_video]" in fc
|
||||
|
||||
def test_single_layer_multi_clips_with_transition(self):
|
||||
"""多个 main clips 带转场效果 → xfade 串联。"""
|
||||
clips = [
|
||||
_make_clip("c1", "main", order=0, duration=3.0),
|
||||
_make_clip(
|
||||
"c2",
|
||||
"main",
|
||||
order=1,
|
||||
duration=3.0,
|
||||
transition_effect="fade",
|
||||
transition_duration=0.5,
|
||||
),
|
||||
]
|
||||
asset_paths = {
|
||||
"asset_c1.mp4": Path("/tmp/asset_c1.mp4"),
|
||||
"asset_c2.mp4": Path("/tmp/asset_c2.mp4"),
|
||||
}
|
||||
svc = _make_service(clips, asset_paths)
|
||||
|
||||
with _patch_path_exists(), patch("video_processing.unified_render_service.probe_duration", return_value=5.0):
|
||||
resolved = svc._resolve_clips()
|
||||
layers = svc._group_clips_into_layers(resolved)
|
||||
@@ -380,7 +412,7 @@ class TestBuildFilterComplex:
|
||||
"""
|
||||
clips = [
|
||||
_make_clip("c1", "main", order=0, duration=3.0),
|
||||
_make_clip("c2", "main", order=1, duration=5.0),
|
||||
_make_clip("c2", "main", order=1, duration=5.0, transition_effect="fade", transition_duration=0.5),
|
||||
]
|
||||
asset_paths = {
|
||||
"asset_c1.mp4": Path("/tmp/asset_c1.mp4"),
|
||||
|
||||
Reference in New Issue
Block a user