b86a9c2af2
CI/CD Pipeline / Deploy Staging (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Failing after 75h41m6s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 75h41m16s
223 lines
8.0 KiB
Python
223 lines
8.0 KiB
Python
"""剪辑计划 / 模板 config JSON 结构定义.
|
||
|
||
定义 EditPlan.config 和 EditTemplate.config 中 cover / title / subtitle / bgm
|
||
四个子结构的 Pydantic 模型,供 API 层做入参校验和默认值填充。
|
||
|
||
所有字段均有合理默认值,前端可只传需要修改的字段。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from enum import Enum
|
||
from typing import List, Optional
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
# ── 枚举类型 ──────────────────────────────────────────────────────────────────
|
||
|
||
|
||
class CoverType(str, Enum):
|
||
"""封面来源类型"""
|
||
|
||
AI_FRAME = "ai_frame" # AI 智能选帧
|
||
MANUAL = "manual" # 手动选择帧
|
||
UPLOAD = "upload" # 用户上传
|
||
AI_REGENERATE = "ai_regenerate" # AI 重新生成
|
||
|
||
|
||
class TextPosition(str, Enum):
|
||
"""文字位置"""
|
||
|
||
TOP = "top"
|
||
CENTER = "center"
|
||
BOTTOM = "bottom"
|
||
|
||
|
||
class TextAnimation(str, Enum):
|
||
"""文字动画效果"""
|
||
|
||
NONE = "none"
|
||
FADE_IN = "fade_in"
|
||
SLIDE_UP = "slide_up"
|
||
SLIDE_DOWN = "slide_down"
|
||
SCALE = "scale"
|
||
|
||
|
||
class BGMSource(str, Enum):
|
||
"""BGM 来源"""
|
||
|
||
LIBRARY = "library" # 素材库
|
||
UPLOAD = "upload" # 用户上传
|
||
AI_RECOMMEND = "ai_recommend" # AI 推荐
|
||
|
||
|
||
# ── 子结构模型 ────────────────────────────────────────────────────────────────
|
||
|
||
|
||
class StrokeConfig(BaseModel):
|
||
"""文字描边配置"""
|
||
|
||
enabled: bool = Field(default=False, description="是否启用描边")
|
||
color: str = Field(default="#000000", description="描边颜色 (HEX)")
|
||
width: int = Field(default=1, ge=1, le=10, description="描边宽度")
|
||
|
||
|
||
class ShadowConfig(BaseModel):
|
||
"""文字阴影配置"""
|
||
|
||
enabled: bool = Field(default=False, description="是否启用阴影")
|
||
blur: int = Field(default=4, ge=0, le=20, description="模糊半径")
|
||
offset_x: int = Field(default=2, description="X 偏移")
|
||
offset_y: int = Field(default=2, description="Y 偏移")
|
||
|
||
|
||
class CoverConfig(BaseModel):
|
||
"""封面配置
|
||
|
||
type 说明:
|
||
- ai_frame: AI 从视频中智能选取最佳帧
|
||
- manual: 用户手动指定 frame_time 处的帧
|
||
- upload: 用户上传图片,image_url 为上传后的地址
|
||
- ai_regenerate: AI 重新生成封面图
|
||
"""
|
||
|
||
type: CoverType = Field(default=CoverType.AI_FRAME, description="封面来源类型")
|
||
image_url: str = Field(default="", description="封面图片 URL")
|
||
frame_time: Optional[float] = Field(default=None, ge=0.0, description="截取帧时间点 (秒)")
|
||
|
||
|
||
class TitleConfig(BaseModel):
|
||
"""标题配置"""
|
||
|
||
enabled: bool = Field(default=True, description="是否显示标题")
|
||
ai_auto: bool = Field(default=True, description="是否由 AI 自动生成标题文案")
|
||
text: str = Field(default="", description="标题文案 (ai_auto=false 时由用户填写)")
|
||
position: TextPosition = Field(default=TextPosition.TOP, description="标题位置")
|
||
font: str = Field(default="思源黑体", description="字体名称")
|
||
color: str = Field(default="#ffffff", description="文字颜色 (HEX)")
|
||
size: int = Field(default=48, ge=12, le=120, description="字号")
|
||
bold: bool = Field(default=True, description="是否加粗")
|
||
italic: bool = Field(default=False, description="是否斜体")
|
||
stroke: StrokeConfig = Field(default_factory=StrokeConfig, description="描边配置")
|
||
shadow: ShadowConfig = Field(default_factory=ShadowConfig, description="阴影配置")
|
||
|
||
|
||
class SubtitleConfig(BaseModel):
|
||
"""字幕配置"""
|
||
|
||
enabled: bool = Field(default=True, description="是否显示字幕")
|
||
position: TextPosition = Field(default=TextPosition.BOTTOM, description="字幕位置")
|
||
font: str = Field(default="思源黑体", description="字体名称")
|
||
color: str = Field(default="#ffffff", description="文字颜色 (HEX)")
|
||
size: int = Field(default=24, ge=12, le=60, description="字号")
|
||
animation: TextAnimation = Field(default=TextAnimation.FADE_IN, description="入场动画")
|
||
|
||
|
||
class BGMConfig(BaseModel):
|
||
"""BGM 配置"""
|
||
|
||
source: BGMSource = Field(default=BGMSource.LIBRARY, description="BGM 来源")
|
||
asset_id: str = Field(default="", description="BGM 素材 ID")
|
||
volume: float = Field(default=0.3, ge=0.0, le=1.0, description="音量 (0.0 ~ 1.0)")
|
||
|
||
|
||
# ── 完整 config 模型 ─────────────────────────────────────────────────────────
|
||
|
||
|
||
class EditPlanConfigSchema(BaseModel):
|
||
"""EditPlan.config 完整结构
|
||
|
||
用于 API 层校验和默认值填充。所有子结构均可选,
|
||
未传入时使用各自默认值。
|
||
"""
|
||
|
||
cover: CoverConfig = Field(default_factory=CoverConfig, description="封面配置")
|
||
title: TitleConfig = Field(default_factory=TitleConfig, description="标题配置")
|
||
subtitle: SubtitleConfig = Field(default_factory=SubtitleConfig, description="字幕配置")
|
||
bgm: BGMConfig = Field(default_factory=BGMConfig, description="BGM 配置")
|
||
|
||
|
||
class EditTemplateConfigSchema(BaseModel):
|
||
"""EditTemplate.config 完整结构
|
||
|
||
模板级别的默认配置,创建计划时可作为初始值继承。
|
||
"""
|
||
|
||
cover: CoverConfig = Field(default_factory=CoverConfig, description="封面默认配置")
|
||
title: TitleConfig = Field(default_factory=TitleConfig, description="标题默认配置")
|
||
subtitle: SubtitleConfig = Field(default_factory=SubtitleConfig, description="字幕默认配置")
|
||
bgm: BGMConfig = Field(default_factory=BGMConfig, description="BGM 默认配置")
|
||
|
||
|
||
# ── 默认值常量 ────────────────────────────────────────────────────────────────
|
||
|
||
DEFAULT_EDIT_PLAN_CONFIG: dict = {
|
||
"cover": {
|
||
"type": "ai_frame",
|
||
"image_url": "",
|
||
"frame_time": None,
|
||
},
|
||
"title": {
|
||
"enabled": True,
|
||
"ai_auto": True,
|
||
"text": "",
|
||
"position": "top",
|
||
"font": "思源黑体",
|
||
"color": "#ffffff",
|
||
"size": 48,
|
||
"bold": True,
|
||
"italic": False,
|
||
"stroke": {"enabled": False, "color": "#000000", "width": 1},
|
||
"shadow": {"enabled": False, "blur": 4, "offset_x": 2, "offset_y": 2},
|
||
},
|
||
"subtitle": {
|
||
"enabled": True,
|
||
"position": "bottom",
|
||
"font": "思源黑体",
|
||
"color": "#ffffff",
|
||
"size": 24,
|
||
"animation": "fade_in",
|
||
},
|
||
"bgm": {
|
||
"source": "library",
|
||
"asset_id": "",
|
||
"volume": 0.3,
|
||
},
|
||
}
|
||
|
||
DEFAULT_EDIT_TEMPLATE_CONFIG: dict = DEFAULT_EDIT_PLAN_CONFIG.copy()
|
||
|
||
|
||
# ── 工具函数 ──────────────────────────────────────────────────────────────────
|
||
|
||
|
||
def normalize_plan_config(raw: dict | None) -> dict:
|
||
"""将原始 config dict 标准化,填充缺失字段为默认值。
|
||
|
||
用于创建/更新计划时确保 config 结构完整。
|
||
"""
|
||
if raw is None:
|
||
return DEFAULT_EDIT_PLAN_CONFIG.copy()
|
||
|
||
import copy
|
||
|
||
base = copy.deepcopy(DEFAULT_EDIT_PLAN_CONFIG)
|
||
|
||
for section_key in ("cover", "title", "subtitle", "bgm"):
|
||
if section_key in raw and isinstance(raw[section_key], dict):
|
||
if section_key not in base:
|
||
base[section_key] = {}
|
||
base[section_key].update(raw[section_key])
|
||
|
||
# 保留非标准字段(如 generation_task_id)
|
||
for key, value in raw.items():
|
||
if key not in ("cover", "title", "subtitle", "bgm"):
|
||
base[key] = value
|
||
|
||
return base
|
||
|
||
|
||
def normalize_template_config(raw: dict | None) -> dict:
|
||
"""将模板原始 config dict 标准化。逻辑同 normalize_plan_config。"""
|
||
return normalize_plan_config(raw)
|