fix(api): 一键生成自动选素材全链路修复,4个核心根因一次性解决
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 22s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 45s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 36s
AI Code Review / AI Code Review (pull_request) Successful in 2m29s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m17s
Auto Approve CI PRs / Auto Approve on CI Green (pull_request) Successful in 2m44s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m21s
Auto Merge CI PRs / Auto Merge on CI Green + Approved (pull_request) Successful in 4m15s
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Failing after 1446h12m41s
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Failing after 1446h12m42s
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Failing after 1446h12m42s
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Failing after 1446h12m42s
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1446h12m44s
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Failing after 1446h12m43s
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Failing after 1446h12m46s
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Failing after 1446h12m46s
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Failing after 1446h44m47s
CI Build & Deploy Pipeline / Deploy Production (pull_request) Failing after 1446h44m43s
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Failing after 1446h44m44s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 22s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 45s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 36s
AI Code Review / AI Code Review (pull_request) Successful in 2m29s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m17s
Auto Approve CI PRs / Auto Approve on CI Green (pull_request) Successful in 2m44s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m21s
Auto Merge CI PRs / Auto Merge on CI Green + Approved (pull_request) Successful in 4m15s
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Failing after 1446h12m41s
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Failing after 1446h12m42s
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Failing after 1446h12m42s
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Failing after 1446h12m42s
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1446h12m44s
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Failing after 1446h12m43s
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Failing after 1446h12m46s
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Failing after 1446h12m46s
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Failing after 1446h44m47s
CI Build & Deploy Pipeline / Deploy Production (pull_request) Failing after 1446h44m43s
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Failing after 1446h44m44s
- 根因1(P0): PIP/VOICE_PIP模式+有clip_configs时素材分配完全失败
* ClipType枚举只有main/intro/outro等,没有overlay/background/corner_voice/b_roll
* _distribute_pip/_distribute_voice_pip按类型筛选永远找不到目标片段
* 修复: _create_clips_from_configs后调用_map_clip_types_for_mode
将MAIN类型片段按顺序映射为对应模式的角色类型
- 根因2(P0): generate-from-template漏写plan.config.asset_ids
* create_plan有这一步兜底用,timeline入口漏掉了
* 修复: 补上asset_ids写入config,与create_plan保持一致
- 根因3(P1): 兜底2 _auto_fallback_copy_template_clips只建片段不分素材
* 兜底4去掉material_mode限制后,兜底2创建的片段能通过兜底4自动分配
* 等于间接修复了兜底2的素材问题
- 根因4(P1): 兜底4 material_mode默认manual永不触发
* 全代码库没有任何地方设置material_mode=auto
* 修复: 去掉material_mode检查,只要有project_id且有无素材片段就自动选
* 确保最后一道兜底防线真正生效
新增2个单元测试验证PIP/VOICE_PIP+有clip_configs场景的素材分配
This commit is contained in:
@@ -132,17 +132,21 @@ def _auto_fallback_auto_material_mode(
|
||||
asset_library_repo: Any,
|
||||
asset_repo: Any,
|
||||
) -> None:
|
||||
"""自动兜底 4: 自动素材模式 → 从项目默认视频素材库选取"""
|
||||
"""自动兜底 4: 项目有视频素材库时,自动选取 ready 视频素材分配给无素材片段
|
||||
|
||||
注:原先需要 material_mode=="auto" 才触发,但全代码库没有任何地方设置为 auto,
|
||||
导致这道兜底防线永远不生效。现改为:只要有 project_id 且存在无素材片段,
|
||||
就自动从项目视频素材库选取素材兜底,确保一键生成等场景能正常出片。
|
||||
"""
|
||||
if not clips_without_asset:
|
||||
return
|
||||
material_mode = (plan_check.config or {}).get("material_mode", "manual")
|
||||
if material_mode != "auto" or not plan_check.project_id:
|
||||
if not plan_check.project_id:
|
||||
return
|
||||
|
||||
import random
|
||||
|
||||
logger.info(
|
||||
"自动兜底4: plan=%s 自动素材模式,从项目素材库选取素材 (%d 个片段需要)",
|
||||
"自动兜底4: plan=%s 自动选素材分配给 %d 个无素材片段",
|
||||
plan_id,
|
||||
len(clips_without_asset),
|
||||
)
|
||||
|
||||
Executable → Regular
+13
-1
@@ -214,8 +214,20 @@ def generate_from_template(
|
||||
plan = result["plan"]
|
||||
clips = result["clips"]
|
||||
|
||||
# 把 asset_ids 写入 plan.config,供生成时兜底分配使用
|
||||
if resolved_asset_ids:
|
||||
from app.services import EditPlanService
|
||||
|
||||
from packages.domain.config_schemas import normalize_plan_config
|
||||
|
||||
svc = EditPlanService(db)
|
||||
current_config = plan.config or {}
|
||||
if current_config.get("asset_ids") != resolved_asset_ids:
|
||||
current_config["asset_ids"] = resolved_asset_ids
|
||||
plan = svc.update_plan(plan.id, config=normalize_plan_config(current_config))
|
||||
|
||||
logger.info(
|
||||
"基于模板生成剪辑计划: plan_id=%s template_id=%s clips=%d by user=%s",
|
||||
"基于模板生成剪辑计划: plan_id=%s template_id=%s clips=%d by user=%d",
|
||||
plan.id,
|
||||
body.template_id,
|
||||
len(clips),
|
||||
|
||||
Regular → Executable
+40
@@ -99,6 +99,11 @@ class PlanGeneratorService:
|
||||
# 3. 生成片段列表
|
||||
if clip_configs:
|
||||
clips = self._create_clips_from_configs(plan.id, clip_configs)
|
||||
# 模板 clip_config 的 clip_type 是 ClipType 枚举(main/intro/outro 等),
|
||||
# 但 PIP / VOICE_PIP 模式需要特定的 clip_type(overlay/background/corner_voice/b_roll)
|
||||
# 才能让素材分配和渲染分层正确工作。
|
||||
# 这里将 MAIN 类型的片段按顺序映射为对应模式的角色类型。
|
||||
self._map_clip_types_for_mode(clips, editing_mode)
|
||||
else:
|
||||
clips = self._generate_default_clips(plan.id, editing_mode, len(asset_ids))
|
||||
|
||||
@@ -195,6 +200,41 @@ class PlanGeneratorService:
|
||||
|
||||
return clips
|
||||
|
||||
def _map_clip_types_for_mode(self, clips: List[EditPlanClip], editing_mode: str) -> None:
|
||||
"""将模板 clip_config 生成的 MAIN 类型片段,按 editing_mode 映射为对应角色类型。
|
||||
|
||||
模板的 clip_config 使用 ClipType 枚举(main/intro/outro 等),
|
||||
但 PIP / VOICE_PIP 模式的素材分配和渲染分层依赖特定的 clip_type 命名
|
||||
(overlay / background / corner_voice / b_roll)。
|
||||
|
||||
映射规则(仅修改 MAIN 类型片段,非 MAIN 片段保持原类型):
|
||||
- PIP: 第1个 MAIN → main(背景),其余 MAIN → overlay(画中画)
|
||||
- VOICE_PIP: 第1个 → background,第2个 → corner_voice,第3+个 → b_roll
|
||||
- ONE_TAKE / VOICE_OVER: 保持 main 不变
|
||||
"""
|
||||
from packages.domain.template_clip_config import ClipType
|
||||
|
||||
main_clips = [c for c in clips if c.clip_type == ClipType.MAIN.value]
|
||||
if not main_clips:
|
||||
return
|
||||
|
||||
if editing_mode == EditingMode.PIP.value:
|
||||
# 第1个 main 保持(背景层),其余改为 overlay(画中画层)
|
||||
for i, clip in enumerate(main_clips):
|
||||
if i > 0:
|
||||
clip.clip_type = "overlay"
|
||||
|
||||
elif editing_mode == EditingMode.VOICE_PIP.value:
|
||||
for i, clip in enumerate(main_clips):
|
||||
if i == 0:
|
||||
clip.clip_type = "background"
|
||||
elif i == 1:
|
||||
clip.clip_type = "corner_voice"
|
||||
else:
|
||||
clip.clip_type = "b_roll"
|
||||
|
||||
# ONE_TAKE / VOICE_OVER: 保持 main 不变,无需处理
|
||||
|
||||
def _generate_default_clips(
|
||||
self,
|
||||
plan_id: str,
|
||||
|
||||
Reference in New Issue
Block a user