Compare commits

...

1 Commits

Author SHA1 Message Date
CI Bot 7e46afc64c fix(#633): 修复一键生成转场时长不生效 - _VirtualClip缺transition_duration字段
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 50s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1m2s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 43s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 2m32s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 28s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 47s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m5s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 44s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 49s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 5m32s
AI Code Review / AI Code Review (pull_request) Successful in 9m45s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 12m43s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m9s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 2m43s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m16s
根因:_VirtualClip 数据类缺少 transition_duration 字段,导致模板配置的自定义转场时长
在一键生成路径中被静默丢弃,永远使用引擎默认值 0.5s。

修复内容:
1. _VirtualClip 添加 transition_duration 字段(默认0.0,表示用全局默认值)
2. _apply_template_clip_effects 增加 transition_duration 从模板 config 映射
3. UnifiedRenderService 多段裁剪分支 ResolvedClip 构建补上 transition_duration
   (顺便补上同分支遗漏的 playback_speed)
4. 补充 3 个单测用例:正常映射/cut转场忽略/无效值安全跳过
2026-07-22 19:28:34 +08:00
3 changed files with 64 additions and 1 deletions
@@ -1304,6 +1304,8 @@ class UnifiedRenderService:
start_time=seg_start,
duration=seg_duration,
transition_effect=clip.transition_effect or "cut",
transition_duration=getattr(clip, "transition_duration", 0.0) or 0.0,
playback_speed=getattr(clip, "playback_speed", 1.0) or 1.0,
config={**clip_config, "_segment_id": seg.segment_id},
actual_duration=actual_duration,
trim_config=seg.trim,
+12 -1
View File
@@ -171,6 +171,7 @@ class _VirtualClip:
start_time: float = 0.0
duration: float = 0.0
transition_effect: str = "cut"
transition_duration: float = 0.0 # 0 表示使用全局默认值
playback_speed: float = 1.0
status: str = "ready"
config: dict[str, Any] = field(default_factory=dict)
@@ -279,7 +280,7 @@ def _apply_template_clip_effects(
cfg_idx = min(i, len(main_configs) - 1)
template_cfg = main_configs[cfg_idx]
# 1. 转场效果
# 1. 转场效果 + 时长
transition = (
template_cfg.transition_effect.value
if hasattr(template_cfg.transition_effect, "value")
@@ -287,6 +288,16 @@ def _apply_template_clip_effects(
)
if transition and transition != "cut":
clip.transition_effect = transition
# 同步转场时长(模板 clip_config 里的 transition_duration
tpl_cfg = template_cfg.config or {}
tpl_duration = tpl_cfg.get("transition_duration")
if tpl_duration:
try:
dur_val = float(tpl_duration)
if dur_val > 0:
clip.transition_duration = dur_val
except (ValueError, TypeError):
pass
# 2. clip 级效果配置(滤镜、调速等)
template_clip_config = template_cfg.config or {}
+50
View File
@@ -309,6 +309,8 @@ class TestTemplateClipEffectMapping:
asset_id: str = f"asset_{idx}"
duration: float = 5.0
transition_effect: str = "cut"
transition_duration: float = 0.0
playback_speed: float = 1.0
config: dict = field(default_factory=dict)
return FakeClip(config=config or {})
@@ -396,6 +398,54 @@ class TestTemplateClipEffectMapping:
# 模板是 cut 时,保留原有值(避免无意义覆盖)
assert clips[0].transition_effect == "fade"
def test_transition_duration_mapped(self):
"""转场时长(transition_duration)从模板 config 正确映射到 clip."""
from worker_app.tasks.generation import _apply_template_clip_effects
clips = [self._make_virtual_clip(i) for i in range(3)]
clip_configs = [
self._make_template_clip_config("main", transition="fade", config={"transition_duration": 0.8}),
self._make_template_clip_config("main", transition="dissolve", config={"transition_duration": 1.2}),
]
_apply_template_clip_effects(clips, clip_configs, "one_take")
# 前两个按顺序映射,第三个复用最后一个
assert clips[0].transition_effect == "fade"
assert clips[0].transition_duration == 0.8
assert clips[1].transition_effect == "dissolve"
assert clips[1].transition_duration == 1.2
assert clips[2].transition_effect == "dissolve"
assert clips[2].transition_duration == 1.2
def test_transition_duration_ignored_for_cut(self):
"""模板转场为 cut 时,transition_duration 不生效(保持默认0."""
from worker_app.tasks.generation import _apply_template_clip_effects
clips = [self._make_virtual_clip(0)]
clip_configs = [
self._make_template_clip_config("main", transition="cut", config={"transition_duration": 0.5}),
]
_apply_template_clip_effects(clips, clip_configs, "one_take")
# cut 转场不映射,transition_duration 也不应用
assert clips[0].transition_duration == 0.0
def test_transition_duration_invalid_value_skipped(self):
"""transition_duration 为无效值时安全跳过."""
from worker_app.tasks.generation import _apply_template_clip_effects
clips = [self._make_virtual_clip(0)]
clip_configs = [
self._make_template_clip_config("main", transition="fade", config={"transition_duration": "abc"}),
]
_apply_template_clip_effects(clips, clip_configs, "one_take")
assert clips[0].transition_effect == "fade"
assert clips[0].transition_duration == 0.0 # 无效值保持默认
def test_intro_outro_extracted(self):
"""intro/outro 类型 clip_config 正确提取为 plan 级 intro_outro 配置."""
from worker_app.tasks.generation import _extract_intro_outro_from_clip_configs