fix: 模板生成计划时配置字段漏传 - export水印 + 调速speed_ratio + clip config透传 #501

Merged
auto-approve-bot merged 2 commits from fix/template-config-propagation into develop 2026-07-18 15:36:34 +08:00
2 changed files with 212 additions and 2 deletions
@@ -151,8 +151,8 @@ class PlanGeneratorService:
plan_config: dict[str, Any] = {
"editing_mode": editing_mode,
}
# 继承模板的 cover/title/subtitle/bgm 配置
for key in ("cover", "title", "subtitle", "bgm"):
# 继承模板的 cover/title/subtitle/bgm/export/filter 配置
for key in ("cover", "title", "subtitle", "bgm", "export", "filter"):
if key in template_config:
plan_config[key] = template_config[key]
@@ -187,6 +187,10 @@ class PlanGeneratorService:
cfg.transition_effect.value if hasattr(cfg.transition_effect, "value") else cfg.transition_effect
)
# 从 clip config 中解析 playback_speed(兼容 speed_ratio 字段名)
clip_cfg = cfg.config or {}
playback_speed = clip_cfg.get("playback_speed", clip_cfg.get("speed_ratio", 1.0)) or 1.0
clip = EditPlanClip.create(
plan_id=plan_id,
clip_type=clip_type,
@@ -195,6 +199,8 @@ class PlanGeneratorService:
text_content=getattr(cfg, "text_template", "") or "",
duration=duration,
transition_effect=transition or "cut",
playback_speed=playback_speed,
config=clip_cfg,
)
clips.append(clip)
+204
View File
@@ -682,3 +682,207 @@ class TestTotalDuration:
# 3 个 clips × 5 秒 = 15 秒
assert result["plan"].total_duration == 15.0
# ---------------------------------------------------------------------------
# 测试:模板配置传递(export / filter / 片段级 speed_ratio
# ---------------------------------------------------------------------------
class TestTemplateConfigPropagation:
"""模板配置到剪辑计划的传递验证"""
def test_export_config_propagated(self):
"""模板 export 配置(watermark/resolution)应传递到 plan.config.export"""
svc, _, _ = _make_generator()
template = _make_template(
editing_mode=EditingMode.ONE_TAKE.value,
config={
"export": {
"resolution": "1080x1920",
"fps": 30,
"watermark_enabled": True,
"watermark_text": "小虾剪辑",
},
},
)
clip_configs = _make_clip_configs(
specs=[
{"clip_type": ClipType.MAIN, "order": 0, "min_duration": 3.0, "max_duration": 5.0},
]
)
result = svc.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=["a1"],
)
export_cfg = result["plan"].config.get("export", {})
assert export_cfg.get("watermark_enabled") is True
assert export_cfg.get("watermark_text") == "小虾剪辑"
assert export_cfg.get("resolution") == "1080x1920"
assert export_cfg.get("fps") == 30
def test_filter_config_propagated(self):
"""模板 filter 配置应传递到 plan.config.filter"""
svc, _, _ = _make_generator()
template = _make_template(
editing_mode=EditingMode.ONE_TAKE.value,
config={
"filter": {
"enabled": True,
"preset_id": "filter_vivid",
"intensity": 80,
},
},
)
clip_configs = _make_clip_configs(
specs=[
{"clip_type": ClipType.MAIN, "order": 0, "min_duration": 3.0, "max_duration": 5.0},
]
)
result = svc.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=["a1"],
)
filter_cfg = result["plan"].config.get("filter", {})
assert filter_cfg.get("enabled") is True
assert filter_cfg.get("preset_id") == "filter_vivid"
assert filter_cfg.get("intensity") == 80
def test_subtitle_asr_config_propagated(self):
"""模板 subtitle ASR 配置应传递到 plan.config.subtitle"""
svc, _, _ = _make_generator()
template = _make_template(
editing_mode=EditingMode.ONE_TAKE.value,
config={
"subtitle": {
"enabled": True,
"auto_generated": True,
"position": "bottom",
"source": "asr",
},
},
)
clip_configs = _make_clip_configs(
specs=[
{"clip_type": ClipType.MAIN, "order": 0, "min_duration": 3.0, "max_duration": 5.0},
]
)
result = svc.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=["a1"],
)
subtitle_cfg = result["plan"].config.get("subtitle", {})
assert subtitle_cfg.get("enabled") is True
assert subtitle_cfg.get("auto_generated") is True
assert subtitle_cfg.get("position") == "bottom"
def test_speed_ratio_mapped_to_playback_speed(self):
"""clip_config.config.speed_ratio 应映射到 clip.playback_speed"""
svc, _, _ = _make_generator()
template = _make_template(editing_mode=EditingMode.ONE_TAKE.value)
clip_configs = _make_clip_configs(
specs=[
{
"clip_type": ClipType.MAIN,
"order": 0,
"min_duration": 3.0,
"max_duration": 5.0,
"config": {"speed_ratio": 1.2, "name": "开场"},
},
{
"clip_type": ClipType.MAIN,
"order": 1,
"min_duration": 3.0,
"max_duration": 5.0,
"config": {"speed_ratio": 0.8, "name": "结尾"},
},
]
)
result = svc.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=["a1", "a2"],
)
clips = result["clips"]
assert clips[0].playback_speed == 1.2
assert clips[1].playback_speed == 0.8
def test_playback_speed_takes_priority_over_speed_ratio(self):
"""clip_config.config.playback_speed 优先于 speed_ratio"""
svc, _, _ = _make_generator()
template = _make_template(editing_mode=EditingMode.ONE_TAKE.value)
clip_configs = _make_clip_configs(
specs=[
{
"clip_type": ClipType.MAIN,
"order": 0,
"min_duration": 3.0,
"max_duration": 5.0,
"config": {"speed_ratio": 1.2, "playback_speed": 1.5},
},
]
)
result = svc.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=["a1"],
)
assert result["clips"][0].playback_speed == 1.5
def test_default_playback_speed_is_1_0(self):
"""无 speed_ratio / playback_speed 时,默认 1.0"""
svc, _, _ = _make_generator()
template = _make_template(editing_mode=EditingMode.ONE_TAKE.value)
clip_configs = _make_clip_configs(
specs=[
{"clip_type": ClipType.MAIN, "order": 0, "min_duration": 3.0, "max_duration": 5.0},
]
)
result = svc.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=["a1"],
)
assert result["clips"][0].playback_speed == 1.0
def test_clip_config_dict_propagated(self):
"""clip_config.config 整体应传递到 clip.config"""
svc, _, _ = _make_generator()
template = _make_template(editing_mode=EditingMode.ONE_TAKE.value)
clip_configs = _make_clip_configs(
specs=[
{
"clip_type": ClipType.MAIN,
"order": 0,
"min_duration": 3.0,
"max_duration": 5.0,
"config": {"speed_ratio": 1.2, "name": "开场", "custom_field": "value"},
},
]
)
result = svc.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=["a1"],
)
clip_cfg = result["clips"][0].config
assert clip_cfg.get("speed_ratio") == 1.2
assert clip_cfg.get("name") == "开场"
assert clip_cfg.get("custom_field") == "value"