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
196 lines
6.9 KiB
Python
Executable File
196 lines
6.9 KiB
Python
Executable File
"""转场 & 滤镜路由.
|
|
|
|
端点:
|
|
- GET /transition-presets 转场预设列表
|
|
- PUT /clips/{clip_id}/transition 单片段转场
|
|
- POST /transitions/batch 批量转场
|
|
- GET /filter-presets 滤镜预设列表
|
|
- GET /filter 滤镜配置
|
|
- PUT /filter 更新滤镜
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
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 (
|
|
BatchTransitionRequest,
|
|
BatchTransitionResponse,
|
|
ClipTransitionResponse,
|
|
FilterConfigResponse,
|
|
FilterPresetListResponse,
|
|
FilterUpdateRequest,
|
|
TransitionPresetListResponse,
|
|
TransitionUpdateRequest,
|
|
)
|
|
|
|
router = APIRouter(tags=["Template Editor"])
|
|
|
|
|
|
# ── 转场 ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
@router.get("/transition-presets", response_model=TransitionPresetListResponse)
|
|
def list_editor_transition_presets(
|
|
_: AuthenticatedUser = Depends(get_current_user),
|
|
) -> TransitionPresetListResponse:
|
|
"""获取转场预设列表"""
|
|
from packages.domain.transition_presets import TRANSITION_PRESETS
|
|
|
|
items = [
|
|
{
|
|
"id": p["id"],
|
|
"name": p["name"],
|
|
"category": p.get("category", "通用"),
|
|
"duration": p.get("default_duration", 0.5),
|
|
"description": p.get("description", ""),
|
|
}
|
|
for p in TRANSITION_PRESETS
|
|
]
|
|
return TransitionPresetListResponse(items=items, total=len(items))
|
|
|
|
|
|
@router.put("/clips/{clip_id}/transition", response_model=ClipTransitionResponse)
|
|
def update_editor_clip_transition(
|
|
template_id: str,
|
|
clip_id: str,
|
|
body: TransitionUpdateRequest,
|
|
plan_id: str = Depends(get_draft_plan_id),
|
|
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
|
|
_: AuthenticatedUser = Depends(get_current_user),
|
|
) -> ClipTransitionResponse:
|
|
"""设置单个片段的转场效果"""
|
|
_, plan_svc = services
|
|
try:
|
|
clip = plan_svc.update_clip(
|
|
clip_id,
|
|
transition_effect=body.effect,
|
|
transition_duration=body.duration,
|
|
)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
|
|
return ClipTransitionResponse(
|
|
clip_id=clip.id,
|
|
effect=clip.transition_effect.value
|
|
if hasattr(clip.transition_effect, "value")
|
|
else clip.transition_effect,
|
|
duration=clip.transition_duration or 0.5,
|
|
)
|
|
|
|
|
|
@router.post("/transitions/batch", response_model=BatchTransitionResponse)
|
|
def batch_update_editor_transitions(
|
|
template_id: str,
|
|
body: BatchTransitionRequest,
|
|
plan_id: str = Depends(get_draft_plan_id),
|
|
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
|
|
_: AuthenticatedUser = Depends(get_current_user),
|
|
) -> BatchTransitionResponse:
|
|
"""批量设置所有片段的转场效果"""
|
|
_, plan_svc = services
|
|
clips = plan_svc.list_clips(plan_id, limit=500)
|
|
updated = 0
|
|
for clip in clips:
|
|
if clip.order > 0: # 第一个片段不加转场
|
|
try:
|
|
plan_svc.update_clip(
|
|
clip.id,
|
|
transition_effect=body.effect,
|
|
transition_duration=body.duration,
|
|
)
|
|
updated += 1
|
|
except ValueError:
|
|
pass
|
|
|
|
return BatchTransitionResponse(
|
|
updated_count=updated,
|
|
plan_id=plan_id,
|
|
)
|
|
|
|
|
|
# ── 滤镜 ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
@router.get("/filter-presets", response_model=FilterPresetListResponse)
|
|
def list_editor_filter_presets(
|
|
_: AuthenticatedUser = Depends(get_current_user),
|
|
) -> FilterPresetListResponse:
|
|
"""获取滤镜预设列表"""
|
|
from packages.domain.filter_presets import FILTER_PRESETS
|
|
|
|
items = [
|
|
{
|
|
"id": p["id"],
|
|
"name": p["name"],
|
|
"category": p.get("category", "通用"),
|
|
"thumbnail": p.get("thumbnail", ""),
|
|
"description": p.get("description", ""),
|
|
}
|
|
for p in FILTER_PRESETS
|
|
]
|
|
return FilterPresetListResponse(items=items, total=len(items))
|
|
|
|
|
|
@router.get("/filter", response_model=FilterConfigResponse)
|
|
def get_editor_filter(
|
|
template_id: str,
|
|
plan_id: str = Depends(get_draft_plan_id),
|
|
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
|
|
_: AuthenticatedUser = Depends(get_current_user),
|
|
) -> FilterConfigResponse:
|
|
"""获取草稿的全局滤镜配置"""
|
|
_, plan_svc = services
|
|
plan = plan_svc.get_plan_or_raise(plan_id)
|
|
config = plan.config or {}
|
|
filter_config = config.get("filter", {})
|
|
|
|
return FilterConfigResponse(
|
|
plan_id=plan.id,
|
|
enabled=filter_config.get("enabled", False),
|
|
preset_id=filter_config.get("preset_id", ""),
|
|
intensity=filter_config.get("intensity", 1.0),
|
|
brightness=filter_config.get("brightness", 0.0),
|
|
contrast=filter_config.get("contrast", 1.0),
|
|
saturation=filter_config.get("saturation", 1.0),
|
|
warmth=filter_config.get("warmth", 0.0),
|
|
)
|
|
|
|
|
|
@router.put("/filter", response_model=FilterConfigResponse)
|
|
def update_editor_filter(
|
|
template_id: str,
|
|
body: FilterUpdateRequest,
|
|
plan_id: str = Depends(get_draft_plan_id),
|
|
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
|
|
_: AuthenticatedUser = Depends(get_current_user),
|
|
) -> FilterConfigResponse:
|
|
"""更新草稿的全局滤镜配置"""
|
|
_, plan_svc = services
|
|
plan = plan_svc.get_plan_or_raise(plan_id)
|
|
|
|
config = dict(plan.config) if plan.config else {}
|
|
current_filter = dict(config.get("filter", {}))
|
|
update_data = body.model_dump(exclude_none=True)
|
|
current_filter.update(update_data)
|
|
|
|
config["filter"] = current_filter
|
|
updated_plan = plan_svc.update_plan_config(plan_id, normalize_plan_config(config))
|
|
|
|
return FilterConfigResponse(
|
|
plan_id=updated_plan.id,
|
|
enabled=current_filter.get("enabled", False),
|
|
preset_id=current_filter.get("preset_id", ""),
|
|
intensity=current_filter.get("intensity", 1.0),
|
|
brightness=current_filter.get("brightness", 0.0),
|
|
contrast=current_filter.get("contrast", 1.0),
|
|
saturation=current_filter.get("saturation", 1.0),
|
|
warmth=current_filter.get("warmth", 0.0),
|
|
)
|