diff --git a/apps/worker/video_processing/unified_render_service.py b/apps/worker/video_processing/unified_render_service.py index d29d5a314..58b6af808 100755 --- a/apps/worker/video_processing/unified_render_service.py +++ b/apps/worker/video_processing/unified_render_service.py @@ -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, diff --git a/apps/worker/worker_app/tasks/generation.py b/apps/worker/worker_app/tasks/generation.py old mode 100644 new mode 100755 index 9f096779d..0612dfa5b --- a/apps/worker/worker_app/tasks/generation.py +++ b/apps/worker/worker_app/tasks/generation.py @@ -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 {} diff --git a/tests/unit/test_oneclick_gen_p0_fixes.py b/tests/unit/test_oneclick_gen_p0_fixes.py index 283f0250d..73cf4f1fe 100755 --- a/tests/unit/test_oneclick_gen_p0_fixes.py +++ b/tests/unit/test_oneclick_gen_p0_fixes.py @@ -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