From cfd6bbb4b605fd8f4dcf37f0387d8cd8f7f32c95 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 18 Jul 2026 08:32:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(api):=20=E4=B8=80=E9=94=AE=E7=94=9F?= =?UTF-8?q?=E6=88=90=E8=87=AA=E5=8A=A8=E9=80=89=E7=B4=A0=E6=9D=90=EF=BC=8C?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E8=AE=A1=E5=88=92=E6=97=B6=E4=BB=8E=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E8=A7=86=E9=A2=91=E7=B4=A0=E6=9D=90=E5=BA=93=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=88=86=E9=85=8Dready=E8=A7=86=E9=A2=91=E7=B4=A0?= =?UTF-8?q?=E6=9D=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 根因: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的场景不受影响,行为完全不变 --- apps/api/app/api/routes/_helpers.py | 55 +++++++++++++++++++ apps/api/app/api/routes/edit_plans.py | 36 ++++++++++-- .../api/app/api/routes/edit_plans_timeline.py | 30 +++++++++- 3 files changed, 112 insertions(+), 9 deletions(-) mode change 100644 => 100755 apps/api/app/api/routes/_helpers.py diff --git a/apps/api/app/api/routes/_helpers.py b/apps/api/app/api/routes/_helpers.py old mode 100644 new mode 100755 index dbdec25a6..caf4eefa8 --- a/apps/api/app/api/routes/_helpers.py +++ b/apps/api/app/api/routes/_helpers.py @@ -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] diff --git a/apps/api/app/api/routes/edit_plans.py b/apps/api/app/api/routes/edit_plans.py index ec347a32a..d624b7e75 100755 --- a/apps/api/app/api/routes/edit_plans.py +++ b/apps/api/app/api/routes/edit_plans.py @@ -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( diff --git a/apps/api/app/api/routes/edit_plans_timeline.py b/apps/api/app/api/routes/edit_plans_timeline.py index afc108f1c..4b99c00b6 100755 --- a/apps/api/app/api/routes/edit_plans_timeline.py +++ b/apps/api/app/api/routes/edit_plans_timeline.py @@ -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, -- 2.54.0