export type VideoDurationCostMap = Record; export type VideoImageGenConfigLike = { creditCostPerVideo?: number | null; videoDurationOptions?: number[] | null; creditCostPerVideoByDuration?: Record | null; }; function normalizePositiveInteger(value: unknown) { const normalized = Number(value); if (!Number.isFinite(normalized)) return null; const rounded = Math.round(normalized); if (rounded < 1) return null; return rounded; } export function normalizeVideoDurationOptions(value: unknown): number[] { if (!Array.isArray(value)) return []; return Array.from( new Set( value .map((item) => normalizePositiveInteger(item)) .filter((item): item is number => item !== null) ) ).sort((left, right) => left - right); } export function normalizeVideoDurationCostMap( value: unknown, durationOptions?: readonly number[] | null ): VideoDurationCostMap | undefined { if (!value || typeof value !== "object") return undefined; const allowedDurations = Array.isArray(durationOptions) ? new Set(durationOptions.map((item) => String(item))) : null; const entries = Object.entries(value as Record) .map(([durationKey, costValue]) => { const normalizedDuration = normalizePositiveInteger(durationKey); const normalizedCost = Number(costValue); if (normalizedDuration === null || !Number.isFinite(normalizedCost)) { return null; } const durationText = String(normalizedDuration); if (allowedDurations && !allowedDurations.has(durationText)) { return null; } return [durationText, Math.max(0, Math.round(normalizedCost))] as const; }) .filter((entry): entry is readonly [string, number] => Boolean(entry)); if (entries.length === 0) return undefined; return Object.fromEntries(entries); } export function getConfiguredVideoDurationOptions( config: VideoImageGenConfigLike | null | undefined ): number[] { return normalizeVideoDurationOptions(config?.videoDurationOptions); } export function buildVideoDurationCostMap(params: { durationOptions: readonly number[]; costMap?: unknown; fallbackCost?: number | null | undefined; }): VideoDurationCostMap | undefined { const fallbackCost = Number.isFinite(Number(params.fallbackCost)) ? Math.max(0, Math.round(Number(params.fallbackCost))) : 0; const normalizedCostMap = normalizeVideoDurationCostMap(params.costMap, params.durationOptions) || {}; if (params.durationOptions.length === 0 && Object.keys(normalizedCostMap).length === 0) { return undefined; } return Object.fromEntries( params.durationOptions.map((duration) => { const key = String(duration); return [key, normalizedCostMap[key] ?? fallbackCost]; }) ); } export function getVideoCreditCostFromConfig( config: VideoImageGenConfigLike | null | undefined, duration?: number | null ): number { const normalizedDuration = normalizePositiveInteger(duration); const durationOptions = getConfiguredVideoDurationOptions(config); const durationCostMap = normalizeVideoDurationCostMap( config?.creditCostPerVideoByDuration, durationOptions ); if (normalizedDuration !== null && durationCostMap) { const durationKey = String(normalizedDuration); if (Object.prototype.hasOwnProperty.call(durationCostMap, durationKey)) { return durationCostMap[durationKey] || 0; } } const fallbackCost = Number(config?.creditCostPerVideo ?? 0); return Number.isFinite(fallbackCost) ? Math.max(0, Math.round(fallbackCost)) : 0; }