Files
xiaoxia-saas/packages/domain/config_schemas.py
T
DevOps Bot 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
chore: 统一代码格式 - black + prettier 全量格式化
2026-07-06 09:13:03 +08:00

223 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""剪辑计划 / 模板 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)