fix(api): 一键生成自动选素材,创建计划时从项目视频素材库自动分配ready视频素材
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 / Build Production API 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 Web Image (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 / 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 24s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 45s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 37s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m20s
Auto Approve CI PRs / Auto Approve on CI Green (pull_request) Successful in 2m57s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m21s
Auto Merge CI PRs / Auto Merge on CI Green + Approved (pull_request) Successful in 4m17s
AI Code Review / AI Code Review (pull_request) Successful in 4m23s

- 根因:create_plan/generate-from-template 未传asset_ids时,片段asset_id全为空
  自动选素材逻辑仅在generate阶段兜底且需material_mode=auto(默认manual)
- 修复:在两个创建计划入口添加自动选素材逻辑
  - 未传asset_ids但有project_id时,从项目视频素材库选全部ready视频素材
  - 按创建时间降序排列(新素材在前)
  - 提取共享函数auto_select_video_assets到_helpers.py
- 影响范围:POST /edit-plans、POST /edit-plans/generate-from-template
  传了asset_ids的场景不受影响,行为完全不变
This commit is contained in:
CI Bot
2026-07-18 08:32:42 +08:00
parent d25c7e4a75
commit cfd6bbb4b6
3 changed files with 112 additions and 9 deletions
+55
View File
@@ -46,3 +46,58 @@ def require_project_and_library(
libraries = asset_library_repository.find_by_project(project_id)
if not any(item.id == library_id for item in libraries):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Asset library not found")
def auto_select_video_assets(
*,
project_id: str,
asset_library_repo: Any,
asset_repo: Any,
logger=None,
) -> list[str]:
"""从项目视频素材库自动选取 ready 状态的视频素材。
Args:
project_id: 项目 ID
asset_library_repo: 素材库仓储
asset_repo: 素材仓储
logger: 可选的 logger 实例,用于记录警告
Returns:
选中的素材 ID 列表,无可用素材时返回空列表
"""
if not project_id:
return []
# 找到项目的视频素材库
libs = asset_library_repo.find_by_project(project_id)
video_lib = None
for lib in libs:
lib_kind = lib.kind.value if hasattr(lib.kind, "value") else lib.kind
if lib_kind == "video":
video_lib = lib
break
if not video_lib:
if logger:
logger.warning("自动选素材: 项目 %s 无视频素材库", project_id)
return []
# 从素材库中选取 ready 状态的视频素材
assets = asset_repo.find_by_library(video_lib.id)
ready_videos = [
a
for a in assets
if (a.status.value if hasattr(a.status, "value") else a.status) == "ready"
and a.mime_type
and a.mime_type.startswith("video")
]
if not ready_videos:
if logger:
logger.warning("自动选素材: 素材库 %s 无可用视频素材", video_lib.name)
return []
# 按创建时间降序(新素材在前)
ready_videos.sort(key=lambda a: a.created_at, reverse=True)
return [a.id for a in ready_videos]
+30 -6
View File
@@ -22,7 +22,12 @@ from datetime import datetime
from typing import Any, List, Optional
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import get_db_session, get_project_repository
from app.dependencies import (
get_asset_library_repository,
get_asset_repository,
get_db_session,
get_project_repository,
)
from app.schemas.generation_task import GenerationTaskResponse
from app.services import EditPlanService, EditTemplateService
from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
@@ -32,7 +37,7 @@ from sqlalchemy.orm import Session
from packages.domain.config_schemas import normalize_plan_config
from packages.domain.edit_plan import EditPlan, EditPlanStatus
from ._helpers import check_project_access
from ._helpers import auto_select_video_assets, check_project_access
logger = logging.getLogger(__name__)
@@ -365,6 +370,8 @@ def create_plan(
db: Session = Depends(get_db_session),
current_user: AuthenticatedUser = Depends(get_current_user),
project_repository: Any = Depends(get_project_repository),
asset_repository: Any = Depends(get_asset_repository),
asset_library_repository: Any = Depends(get_asset_library_repository),
) -> EditPlanResponse:
"""创建剪辑计划
@@ -415,12 +422,29 @@ def create_plan(
# 模板存在,从模板生成计划+片段
clip_configs = template_svc.list_clip_configs(body.template_id, skip=0, limit=200)
# 自动选素材:未传 asset_ids 但有 project_id 时,从项目视频素材库选 ready 的视频素材
resolved_asset_ids = list(body.asset_ids)
if not resolved_asset_ids and project_id:
auto_assets = auto_select_video_assets(
project_id=project_id,
asset_library_repo=asset_library_repository,
asset_repo=asset_repository,
logger=logger,
)
if auto_assets:
resolved_asset_ids = auto_assets
logger.info(
"自动选素材: project_id=%s count=%d",
project_id,
len(auto_assets),
)
generator = PlanGeneratorService(db)
try:
result = generator.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=body.asset_ids,
asset_ids=resolved_asset_ids,
project_id=project_id,
created_by_user_id=current_user.user.id,
name=body.name,
@@ -447,10 +471,10 @@ def create_plan(
)
# 把 asset_ids 写入 plan.config,供生成时兜底分配使用
if body.asset_ids:
if resolved_asset_ids:
current_config = plan.config or {}
if current_config.get("asset_ids") != body.asset_ids:
current_config["asset_ids"] = body.asset_ids
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(
+27 -3
View File
@@ -10,7 +10,7 @@ from __future__ import annotations
import logging
from typing import Any, List
from app.api.routes._helpers import check_project_access
from app.api.routes._helpers import auto_select_video_assets, check_project_access
from app.api.routes.edit_plans import (
GenerateFromTemplateRequest,
GenerateFromTemplateResponse,
@@ -18,7 +18,12 @@ from app.api.routes.edit_plans import (
_to_response,
)
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import get_db_session, get_project_repository
from app.dependencies import (
get_asset_library_repository,
get_asset_repository,
get_db_session,
get_project_repository,
)
from app.services import EditPlanService, PlanGeneratorService
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
@@ -161,6 +166,8 @@ def generate_from_template(
db: Session = Depends(get_db_session),
current_user: AuthenticatedUser = Depends(get_current_user),
project_repository: Any = Depends(get_project_repository),
asset_repository: Any = Depends(get_asset_repository),
asset_library_repository: Any = Depends(get_asset_library_repository),
) -> GenerateFromTemplateResponse:
"""基于模板 + 素材自动生成剪辑计划"""
from app.services import EditTemplateService
@@ -177,11 +184,28 @@ def generate_from_template(
clip_configs = template_svc.list_clip_configs(body.template_id, skip=0, limit=200)
# 自动选素材:未传 asset_ids 但有 project_id 时,从项目视频素材库选 ready 的视频素材
resolved_asset_ids = list(body.asset_ids)
if not resolved_asset_ids and body.project_id:
auto_assets = auto_select_video_assets(
project_id=body.project_id,
asset_library_repo=asset_library_repository,
asset_repo=asset_repository,
logger=logger,
)
if auto_assets:
resolved_asset_ids = auto_assets
logger.info(
"generate-from-template 自动选素材: project_id=%s count=%d",
body.project_id,
len(auto_assets),
)
generator = PlanGeneratorService(db)
result = generator.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=body.asset_ids,
asset_ids=resolved_asset_ids,
project_id=body.project_id,
created_by_user_id=current_user.user.id,
name=body.name,