export type VideoHdResolution = "1080P" | "2K" | "4K"; export type VideoHdInterpolationMode = "none" | "high-quality"; export type VideoHdResolutionConfig = { width: number; height: number; baseCost: number; interpolationExtraCost: number; }; export type VideoCanvasVideoHdConfig = { enabled: boolean; model: string; setupCost: number; frameRates: number[]; slowMultipliers: number[]; resolutions: Record; }; export const defaultVideoCanvasVideoHdConfig: VideoCanvasVideoHdConfig = { enabled: true, model: "Topazlabs", setupCost: 6, frameRates: [24, 30, 60, 90, 120], slowMultipliers: [1, 2, 3, 5], resolutions: { "1080P": { width: 1920, height: 1080, baseCost: 24, interpolationExtraCost: 12 }, "2K": { width: 2560, height: 1440, baseCost: 42, interpolationExtraCost: 24 }, "4K": { width: 3840, height: 2160, baseCost: 90, interpolationExtraCost: 48 }, }, }; function positiveInteger(value: unknown, fallback: number) { const parsed = Math.round(Number(value)); return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; } function nonnegativeNumber(value: unknown, fallback: number) { const parsed = Number(value); return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback; } function numberList(value: unknown, fallback: number[]) { if (!Array.isArray(value)) return fallback; const normalized = Array.from( new Set(value.map((item) => positiveInteger(item, 0)).filter((item) => item > 0)), ).sort((a, b) => a - b); return normalized.length > 0 ? normalized : fallback; } export function normalizeVideoCanvasVideoHdConfig(value: unknown): VideoCanvasVideoHdConfig { const candidate = value && typeof value === "object" ? value as Partial : {}; const candidateResolutions = candidate.resolutions && typeof candidate.resolutions === "object" ? candidate.resolutions as Partial>> : {}; const resolutions = Object.fromEntries( (Object.keys(defaultVideoCanvasVideoHdConfig.resolutions) as VideoHdResolution[]).map((key) => { const fallback = defaultVideoCanvasVideoHdConfig.resolutions[key]; const item = candidateResolutions[key] || {}; return [key, { width: positiveInteger(item.width, fallback.width), height: positiveInteger(item.height, fallback.height), baseCost: nonnegativeNumber(item.baseCost, fallback.baseCost), interpolationExtraCost: nonnegativeNumber( item.interpolationExtraCost, fallback.interpolationExtraCost, ), }]; }), ) as Record; return { enabled: typeof candidate.enabled === "boolean" ? candidate.enabled : true, model: String(candidate.model || defaultVideoCanvasVideoHdConfig.model).trim() || "Topazlabs", setupCost: nonnegativeNumber(candidate.setupCost, defaultVideoCanvasVideoHdConfig.setupCost), frameRates: numberList(candidate.frameRates, defaultVideoCanvasVideoHdConfig.frameRates), slowMultipliers: numberList( candidate.slowMultipliers, defaultVideoCanvasVideoHdConfig.slowMultipliers, ), resolutions, }; } export function getVideoHdCost( config: VideoCanvasVideoHdConfig, resolution: VideoHdResolution, interpolationMode: VideoHdInterpolationMode, slowMultiplier: number, ) { const definition = config.resolutions[resolution]; const singleDurationCost = definition.baseCost + (interpolationMode === "high-quality" ? definition.interpolationExtraCost : 0); const multiplier = Math.max(1, positiveInteger(slowMultiplier, 1)); return Math.round(config.setupCost + (singleDurationCost - config.setupCost) * multiplier); } export function formatVideoHdFrameRate(frameRate: number) { if (frameRate === 24 || frameRate === 30) return `标准(${frameRate}fps)`; if (frameRate === 60) return "流畅(60fps)"; if (frameRate === 90) return "超流畅(90fps)"; if (frameRate === 120) return "极致流畅(120fps)"; return `${frameRate}fps`; }