chore: squash merge feature/smart-edit-plan into develop (resolve conflicts)
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Waiting to run
Tests / test (pull_request) Waiting to run
Tests / lint (pull_request) Waiting to run
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
Deploy / Deploy Staging (push) Has been cancelled
Deploy / Build Production Runtime Images (push) Has been cancelled
Deploy / Deploy Production (push) Has been cancelled
Deploy / Production Browser E2E (push) Has been cancelled

- Resolve CI/CD and deploy workflow conflicts with develop version
- Resolve database and dependencies conflicts with develop version
- Keep new edit plan generator and API functionality from feature branch
This commit is contained in:
CI Test
2026-06-26 21:21:39 +08:00
parent 5dbde7e176
commit f5bc1b8947
4 changed files with 515 additions and 2 deletions
+106 -2
View File
@@ -9,7 +9,17 @@ from app.dependencies import (
get_project_repository,
get_workspace_member_repository,
)
from app.schemas.edit_plan import CreateEditPlanRequest, EditPlanClipResponse, EditPlanResponse, EditTemplateResponse
from app.schemas.edit_plan import (
AutoGenerateEditPlanRequest,
CreateEditPlanRequest,
EditPlanClipResponse,
EditPlanResponse,
EditTemplateResponse,
)
from apps.worker.worker_app.tasks.edit_plan_generator import (
EditingMode,
SmartEditPlanGenerator,
)
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
@@ -81,6 +91,7 @@ def _to_plan_response(
title_id=plan.title_id,
status=plan.status,
summary=plan.summary,
editing_mode=plan.editing_mode,
clips=[
EditPlanClipResponse(
id=clip.id,
@@ -90,6 +101,7 @@ def _to_plan_response(
start_time=float(clip.start_time or 0),
duration=float(clip.duration or 0),
reason=clip.reason,
layer=clip.layer,
)
for clip in clips
],
@@ -98,7 +110,7 @@ def _to_plan_response(
)
@router.get("/templates", response_model=list[EditTemplateResponse])
@router.get("/templates/", response_model=list[EditTemplateResponse])
def list_edit_templates(
project_id: str,
workspace_id: str,
@@ -178,6 +190,98 @@ def create_edit_plan(
return _to_plan_response(plan, clips, {asset.id: asset.name for asset in selected})
@router.post("/auto-generate", response_model=EditPlanResponse)
def auto_generate_edit_plan(
project_id: str,
request: AutoGenerateEditPlanRequest,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
project_repository=Depends(get_project_repository),
asset_repository=Depends(get_asset_repository),
workspace_member_repository: WorkspaceMemberRepository = Depends(get_workspace_member_repository),
session: Session = Depends(get_db_session),
) -> EditPlanResponse:
"""
智能生成剪辑计划
根据素材的分类结果和质量评分,自动编排剪辑计划。
支持多种剪辑模式:
- one-take: 按分类分组,组内按质量排序,顺序拼接
- pip: 第一个高质量素材为主画面,其余为画中画
- voiceover: person 类素材为主播口播,其余穿插为 B-roll
- voice_pip: 结合 voiceover 和 pip,第一个高质量 person 素材为主画面
"""
_ensure_project(
project_id, request.workspace_id, authenticated_user, project_repository, workspace_member_repository
)
# 获取素材库中的所有素材
assets = asset_repository.list_by_library(request.asset_library_id)
if not assets:
raise HTTPException(status_code=422, detail="素材库中暂无素材")
# 使用智能生成器
generator = SmartEditPlanGenerator(project_id, assets)
try:
plan_result = generator.generate_plan(
editing_mode=request.editing_mode,
target_duration=request.target_duration
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
if not plan_result.clips:
raise HTTPException(status_code=422, detail="无符合条件的视频素材")
# 获取模板
template = (
session.query(EditTemplateModel).filter(EditTemplateModel.id == request.template_id).first()
if request.template_id
else None
)
if template is None:
template = _default_template(session, request.workspace_id, project_id, authenticated_user.user.id)
# 创建剪辑计划
plan = EditPlanModel(
id=uuid4().hex,
workspace_id=request.workspace_id,
project_id=project_id,
template_id=template.id,
asset_library_id=request.asset_library_id,
title_id=request.title_id or "",
status="draft",
editing_mode=request.editing_mode,
summary=plan_result.summary,
created_by_user_id=authenticated_user.user.id,
updated_at=datetime.now(timezone.utc),
)
session.add(plan)
# 创建剪辑片段
clips: list[EditPlanClipModel] = []
asset_name_map = {asset.id: asset.name for asset in assets}
for clip_plan in plan_result.clips:
clip = EditPlanClipModel(
id=uuid4().hex,
edit_plan_id=plan.id,
asset_id=clip_plan.asset_id,
sequence=clip_plan.sequence,
start_time=clip_plan.start_time,
duration=clip_plan.duration,
reason=clip_plan.reason,
layer=clip_plan.layer,
)
session.add(clip)
clips.append(clip)
session.commit()
return _to_plan_response(plan, clips, asset_name_map)
@router.get("/{plan_id}", response_model=EditPlanResponse)
def get_edit_plan(
project_id: str,