fix: 模板生成计划时配置字段漏传
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (pull_request) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 23s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 56s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 34s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 59s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 37s
Auto Merge CI PRs / Auto Merge on CI Green + Approved (pull_request) Successful in 1m10s
Auto Approve CI PRs / Auto Approve on CI Green (pull_request) Successful in 1m13s
AI Code Review / AI Code Review (pull_request) Successful in 2m10s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m20s

- export/watermark配置:从template.config.export继承到plan.config.export
- filter配置:同步继承
- 片段级speed_ratio:从clip_config.config映射到clip.playback_speed(兼容playback_speed字段名,优先)
- clip_config.config整体传递到clip.config,保留所有自定义字段
- subtitle/ASR配置已在原有继承列表中,确认正常传递

关联 #406
This commit is contained in:
CI Bot
2026-07-18 13:15:41 +08:00
parent 9cf48d493d
commit 1c36741bb4
2 changed files with 192 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)
+184
View File
@@ -682,3 +682,187 @@ 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"