9652e9e892
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
210 lines
6.6 KiB
Python
Executable File
210 lines
6.6 KiB
Python
Executable File
"""封面管理路由.
|
|
|
|
端点:
|
|
- GET /cover 封面配置
|
|
- PUT /cover 更新封面
|
|
- POST /cover/extract 抽帧生成封面
|
|
- POST /cover/smart 智能选帧
|
|
- POST /generate-cover AI 生成封面
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from app.auth import AuthenticatedUser, get_current_user
|
|
from app.services.edit_plan_service import EditPlanService
|
|
from app.services.edit_template_service import EditTemplateService
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
from packages.domain.config_schemas import normalize_plan_config
|
|
|
|
from .dependencies import get_draft_plan_id, get_editor_services
|
|
from .schemas import (
|
|
CoverConfigResponse,
|
|
CoverExtractRequest,
|
|
CoverGenerateResponse,
|
|
CoverSmartRequest,
|
|
CoverUpdateRequest,
|
|
GenerateCoverRequest,
|
|
GenerateCoverResponse,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(tags=["Template Editor"])
|
|
|
|
|
|
@router.get("/cover", response_model=CoverConfigResponse)
|
|
def get_editor_cover(
|
|
template_id: str,
|
|
plan_id: str = Depends(get_draft_plan_id),
|
|
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
|
|
_: AuthenticatedUser = Depends(get_current_user),
|
|
) -> CoverConfigResponse:
|
|
"""获取草稿封面配置"""
|
|
_, plan_svc = services
|
|
plan = plan_svc.get_plan_or_raise(plan_id)
|
|
config = plan.config or {}
|
|
cover_config = config.get("cover", {})
|
|
|
|
return CoverConfigResponse(
|
|
type=cover_config.get("cover_type", "auto"),
|
|
image_url=cover_config.get("cover_image_url", ""),
|
|
frame_time=cover_config.get("frame_time", 0.0),
|
|
)
|
|
|
|
|
|
@router.put("/cover", response_model=CoverConfigResponse)
|
|
def update_editor_cover(
|
|
template_id: str,
|
|
body: CoverUpdateRequest,
|
|
plan_id: str = Depends(get_draft_plan_id),
|
|
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
|
|
_: AuthenticatedUser = Depends(get_current_user),
|
|
) -> CoverConfigResponse:
|
|
"""更新草稿封面配置"""
|
|
_, plan_svc = services
|
|
plan = plan_svc.get_plan_or_raise(plan_id)
|
|
|
|
config = dict(plan.config) if plan.config else {}
|
|
current_cover = dict(config.get("cover", {}))
|
|
update_data = body.model_dump(exclude_none=True)
|
|
current_cover.update(update_data)
|
|
|
|
config["cover"] = current_cover
|
|
normalized = normalize_plan_config(config)
|
|
plan_svc.update_plan_config(plan_id, {"cover": normalized["cover"]})
|
|
|
|
return CoverConfigResponse(
|
|
type=current_cover.get("cover_type", "auto"),
|
|
image_url=current_cover.get("cover_image_url", ""),
|
|
frame_time=current_cover.get("frame_time", 0.0),
|
|
)
|
|
|
|
|
|
@router.post("/cover/extract", response_model=CoverGenerateResponse)
|
|
def extract_editor_cover(
|
|
template_id: str,
|
|
body: CoverExtractRequest,
|
|
plan_id: str = Depends(get_draft_plan_id),
|
|
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
|
|
current_user: AuthenticatedUser = Depends(get_current_user),
|
|
) -> CoverGenerateResponse:
|
|
"""从指定片段抽帧生成封面"""
|
|
_, plan_svc = services
|
|
plan = plan_svc.get_plan_or_raise(plan_id)
|
|
|
|
clip = plan_svc.get_clip(body.clip_id)
|
|
if not clip or clip.plan_id != plan_id:
|
|
raise HTTPException(status_code=400, detail="片段不存在或不属于当前草稿")
|
|
|
|
cover_url = f"cover/extract/{plan_id}_{body.clip_id}_{body.frame_time}.jpg"
|
|
|
|
config = dict(plan.config) if plan.config else {}
|
|
cover_config = dict(config.get("cover", {}))
|
|
cover_config.update(
|
|
{
|
|
"cover_type": "extract",
|
|
"cover_image_url": cover_url,
|
|
"clip_id": body.clip_id,
|
|
"frame_time": body.frame_time,
|
|
}
|
|
)
|
|
config["cover"] = cover_config
|
|
normalized = normalize_plan_config(config)
|
|
plan_svc.update_plan_config(plan_id, {"cover": normalized["cover"]})
|
|
|
|
logger.info(
|
|
"模板编辑器封面抽帧: template_id=%s plan_id=%s clip_id=%s by user=%s",
|
|
template_id,
|
|
plan_id,
|
|
body.clip_id,
|
|
current_user.user.id,
|
|
)
|
|
|
|
return CoverGenerateResponse(
|
|
type="extract",
|
|
image_url=cover_url,
|
|
frame_time=body.frame_time,
|
|
)
|
|
|
|
|
|
@router.post("/cover/smart", response_model=CoverGenerateResponse)
|
|
def smart_editor_cover(
|
|
template_id: str,
|
|
body: CoverSmartRequest,
|
|
plan_id: str = Depends(get_draft_plan_id),
|
|
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
|
|
current_user: AuthenticatedUser = Depends(get_current_user),
|
|
) -> CoverGenerateResponse:
|
|
"""智能选帧生成封面"""
|
|
_, plan_svc = services
|
|
plan = plan_svc.get_plan_or_raise(plan_id)
|
|
|
|
cover_url = f"cover/smart/{plan_id}_smart.jpg"
|
|
strategy = getattr(body, "strategy", "auto")
|
|
|
|
config = dict(plan.config) if plan.config else {}
|
|
cover_config = dict(config.get("cover", {}))
|
|
cover_config.update(
|
|
{
|
|
"cover_type": "smart",
|
|
"cover_image_url": cover_url,
|
|
"strategy": strategy,
|
|
}
|
|
)
|
|
config["cover"] = cover_config
|
|
normalized = normalize_plan_config(config)
|
|
plan_svc.update_plan_config(plan_id, {"cover": normalized["cover"]})
|
|
|
|
logger.info(
|
|
"模板编辑器智能封面: template_id=%s plan_id=%s strategy=%s by user=%s",
|
|
template_id,
|
|
plan_id,
|
|
strategy,
|
|
current_user.user.id,
|
|
)
|
|
|
|
return CoverGenerateResponse(
|
|
type="smart",
|
|
image_url=cover_url,
|
|
frame_time=None,
|
|
)
|
|
|
|
|
|
@router.post("/generate-cover", response_model=GenerateCoverResponse)
|
|
def editor_generate_cover(
|
|
template_id: str,
|
|
body: GenerateCoverRequest,
|
|
plan_id: str = Depends(get_draft_plan_id),
|
|
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
|
|
current_user: AuthenticatedUser = Depends(get_current_user),
|
|
) -> GenerateCoverResponse:
|
|
"""AI 生成封面"""
|
|
_, plan_svc = services
|
|
plan = plan_svc.get_plan_or_raise(plan_id)
|
|
|
|
from packages.shared.ai_service import run_generate_cover
|
|
|
|
cover_data = run_generate_cover(
|
|
plan_id=plan_id,
|
|
asset_ids=body.asset_ids,
|
|
cover_type=body.cover_type,
|
|
frame_time=body.frame_time,
|
|
)
|
|
|
|
current_config = dict(plan.config) if plan.config else {}
|
|
current_config["cover"] = cover_data
|
|
normalized = normalize_plan_config(current_config)
|
|
plan_svc.update_plan_config(plan_id, {"cover": normalized["cover"]})
|
|
|
|
logger.info(
|
|
"模板编辑器封面生成: template_id=%s plan_id=%s type=%s by user=%s",
|
|
template_id,
|
|
plan_id,
|
|
body.cover_type,
|
|
current_user.user.id,
|
|
)
|
|
|
|
return GenerateCoverResponse(plan_id=plan_id, cover=cover_data)
|