Files
video-gen/video-gen-api/app/services/video_upscale/config_service.py
T
2026-07-16 15:02:03 +08:00

176 lines
7.2 KiB
Python

from __future__ import annotations
import json
from typing import Any
from fastapi import HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.enums.video_upscale import (
ALL_PROCESSOR_KEYS,
VIDEO_UPSCALE_CONFIG_DESCRIPTION,
VIDEO_UPSCALE_CONFIG_KEY,
VIDEO_UPSCALE_CONFIG_VERSION,
VIDEO_UPSCALE_RESOLUTION_RANK,
VIDEO_UPSCALE_RESOLUTIONS,
VideoUpscaleProcessorKey,
normalize_video_upscale_resolution,
)
from app.models.system_config import SystemConfig
from app.schemas.video_upscale import VideoUpscaleConfigData
from app.utils.id_gen import generate_id
def default_video_upscale_config() -> dict[str, Any]:
return {
"enabled": False,
"version": VIDEO_UPSCALE_CONFIG_VERSION,
"delete_source_after_success": True,
"rules": [],
}
def _dump(data: dict[str, Any]) -> str:
return json.dumps(data, ensure_ascii=False, separators=(",", ":"), default=str)
def _load(value: str | None) -> dict[str, Any]:
if not value:
return default_video_upscale_config()
try:
data = json.loads(value)
except Exception as exc:
raise HTTPException(status_code=500, detail=f"视频超分配置 JSON 损坏: {exc}") from exc
if not isinstance(data, dict):
raise HTTPException(status_code=500, detail="视频超分配置必须是 JSON 对象")
return data
def _simplify_stored_config(data: dict[str, Any]) -> dict[str, Any]:
"""兼容开发阶段已保存的旧页面配置,忽略 processors、比例和手工像素等多余字段。"""
rules: list[dict[str, Any]] = []
for raw_rule in data.get("rules") or []:
if not isinstance(raw_rule, dict):
continue
rules.append(
{
"target_resolution": raw_rule.get("target_resolution"),
"provider_generation_resolution": raw_rule.get("provider_generation_resolution"),
"processor_key": raw_rule.get("processor_key"),
"enabled": bool(raw_rule.get("enabled", True)),
}
)
return {
"enabled": bool(data.get("enabled", False)),
"version": max(1, int(data.get("version") or VIDEO_UPSCALE_CONFIG_VERSION)),
"delete_source_after_success": bool(data.get("delete_source_after_success", True)),
"rules": rules,
}
async def _get_record(db: AsyncSession) -> SystemConfig | None:
result = await db.execute(select(SystemConfig).where(SystemConfig.key == VIDEO_UPSCALE_CONFIG_KEY).limit(1))
return result.scalar_one_or_none()
def validate_video_upscale_config(data: dict[str, Any] | VideoUpscaleConfigData) -> dict[str, Any]:
raw = data.model_dump() if isinstance(data, VideoUpscaleConfigData) else _simplify_stored_config(dict(data))
try:
model = VideoUpscaleConfigData.model_validate(raw)
except Exception as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
normalized = model.model_dump()
seen: set[str] = set()
for rule in normalized.get("rules") or []:
target = normalize_video_upscale_resolution(rule.get("target_resolution"))
provider_resolution = normalize_video_upscale_resolution(rule.get("provider_generation_resolution"))
processor_key = str(rule.get("processor_key") or "").strip()
if target not in VIDEO_UPSCALE_RESOLUTIONS:
raise HTTPException(
status_code=400,
detail=f"不支持的客户目标分辨率: {target},仅支持 {'、'.join(VIDEO_UPSCALE_RESOLUTIONS)}",
)
if provider_resolution not in VIDEO_UPSCALE_RESOLUTIONS:
raise HTTPException(
status_code=400,
detail=f"不支持的实际生成分辨率: {provider_resolution},仅支持 {'、'.join(VIDEO_UPSCALE_RESOLUTIONS)}",
)
if processor_key not in ALL_PROCESSOR_KEYS:
raise HTTPException(status_code=400, detail=f"未注册的超分处理器: {processor_key}")
rule["target_resolution"] = target
rule["provider_generation_resolution"] = provider_resolution
rule["processor_key"] = processor_key
if VIDEO_UPSCALE_RESOLUTION_RANK[provider_resolution] > VIDEO_UPSCALE_RESOLUTION_RANK[target]:
raise HTTPException(
status_code=400,
detail=f"规则 {target} 的实际生成分辨率 {provider_resolution} 不能高于客户目标分辨率",
)
if processor_key == VideoUpscaleProcessorKey.VOLC_LARGE_MODEL_V1.value:
if target not in {"720p", "1080p", "2K"}:
raise HTTPException(status_code=400, detail="火山画质增强大模型目标分辨率仅支持 720p、1080p、2K")
if VIDEO_UPSCALE_RESOLUTION_RANK[provider_resolution] > VIDEO_UPSCALE_RESOLUTION_RANK["1080p"]:
raise HTTPException(status_code=400, detail="火山画质增强大模型输入视频最高支持 1080p")
if processor_key in {
VideoUpscaleProcessorKey.VOLC_STANDARD_V1.value,
VideoUpscaleProcessorKey.VOLC_PROFESSIONAL_V1.value,
} and VIDEO_UPSCALE_RESOLUTION_RANK[provider_resolution] > VIDEO_UPSCALE_RESOLUTION_RANK["2K"]:
raise HTTPException(status_code=400, detail="火山标准版/专业版输入视频最高支持 2K")
if rule.get("enabled"):
if target in seen:
raise HTTPException(status_code=400, detail=f"客户目标分辨率存在重复启用规则: {target}")
seen.add(target)
return normalized
async def get_video_upscale_config(db: AsyncSession) -> dict[str, Any]:
record = await _get_record(db)
data = default_video_upscale_config() if record is None else validate_video_upscale_config(_load(record.value))
return {
"id": record.id if record else None,
"key": VIDEO_UPSCALE_CONFIG_KEY,
"description": record.description if record else VIDEO_UPSCALE_CONFIG_DESCRIPTION,
"data": data,
"created_at": record.created_at if record else None,
"updated_at": record.updated_at if record else None,
}
async def get_runtime_video_upscale_config(db: AsyncSession) -> dict[str, Any]:
record = await _get_record(db)
if record is None:
return default_video_upscale_config()
return validate_video_upscale_config(_load(record.value))
async def save_video_upscale_config(db: AsyncSession, data: dict[str, Any] | VideoUpscaleConfigData) -> dict[str, Any]:
normalized = validate_video_upscale_config(data)
record = await _get_record(db)
old_version = 0
if record:
try:
old_version = int((_load(record.value).get("version") or 0))
except Exception:
old_version = 0
normalized["version"] = max(old_version + 1, VIDEO_UPSCALE_CONFIG_VERSION)
if record is None:
record = SystemConfig(
id=generate_id(),
key=VIDEO_UPSCALE_CONFIG_KEY,
value=_dump(normalized),
description=VIDEO_UPSCALE_CONFIG_DESCRIPTION,
)
db.add(record)
else:
record.value = _dump(normalized)
record.description = VIDEO_UPSCALE_CONFIG_DESCRIPTION
await db.flush()
return await get_video_upscale_config(db)