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)