export const VIDEO_DURATION_AUTO = -1; export const VIDEO_DURATION_MIN = 4; export const VIDEO_DURATION_MAX = 15; export const VIDEO_DURATION_MAX_SEEDANCE_25 = 30; export const VIDEO_DURATION_DEFAULT = 5; export const VIDEO_DURATION_OPTIONS = Array.from( { length: VIDEO_DURATION_MAX - VIDEO_DURATION_MIN + 1 }, (_, index) => VIDEO_DURATION_MIN + index ); export const VIDEO_RATIO_DEFAULT = "9:16"; export const VIDEO_RATIOS = ["9:16", "16:9", "1:1", "4:3", "3:4", "21:9", "adaptive"] as const; export const VIDEO_RESOLUTION_DEFAULT = "720p"; export const VIDEO_RESOLUTIONS = ["720p", "1080p", "480p"] as const; export const VIDEO_FAST_RESOLUTIONS = ["720p", "480p"] as const; export const SEEDANCE_20_MODEL = "doubao-seedance-2-0-260128"; export const SEEDANCE_25_MODEL = "doubao-seedance-2-5-260628"; export const SEEDANCE_MODELS = [ { id: SEEDANCE_20_MODEL, label: "Seedance 2.0" }, { id: SEEDANCE_25_MODEL, label: "Seedance 2.5" } ] as const; export type SeedanceModel = (typeof SEEDANCE_MODELS)[number]["id"]; type VideoDurationOptions = { allowAuto?: boolean; model?: string; }; export function clampVideoDuration( value: unknown, fallback = VIDEO_DURATION_DEFAULT, options: VideoDurationOptions = {} ) { const duration = Number(value); if (!Number.isFinite(duration)) return fallback; const rounded = Math.round(duration); if (options.allowAuto !== false && rounded === VIDEO_DURATION_AUTO) return VIDEO_DURATION_AUTO; return Math.min(seedanceMaximumOutputDuration(options.model), Math.max(VIDEO_DURATION_MIN, rounded)); } export function normalizeVideoDuration(value: unknown, model?: string) { if (value === undefined || value === null || value === "") return undefined; return clampVideoDuration(value, VIDEO_DURATION_DEFAULT, { model }); } export function seedanceMaximumOutputDuration(model?: string) { return isSeedance25Model(model) ? VIDEO_DURATION_MAX_SEEDANCE_25 : VIDEO_DURATION_MAX; } export function seedanceDurationOptions(model?: string) { const maximum = seedanceMaximumOutputDuration(model); return Array.from({ length: maximum - VIDEO_DURATION_MIN + 1 }, (_, index) => VIDEO_DURATION_MIN + index); } export function normalizeSeedanceModel(value: unknown, fallback: SeedanceModel = SEEDANCE_20_MODEL): SeedanceModel { const normalized = String(value || "").trim(); return isSupportedSeedanceModel(normalized) ? normalized : fallback; } export function isSupportedSeedanceModel(value: unknown): value is SeedanceModel { const normalized = String(value || "").trim(); return SEEDANCE_MODELS.some((model) => model.id === normalized); } export function isSeedance25Model(model?: string) { return String(model || "").trim() === SEEDANCE_25_MODEL; } export function normalizeVideoRatio(value: unknown, fallback = VIDEO_RATIO_DEFAULT) { const ratio = String(value || "").trim(); if (isSupportedVideoRatio(ratio)) return ratio; return isSupportedVideoRatio(fallback) ? fallback : VIDEO_RATIO_DEFAULT; } export function normalizeVideoResolution( value: unknown, model?: string, fallback = VIDEO_RESOLUTION_DEFAULT ) { const resolutions = supportedVideoResolutions(model); const resolution = String(value || "").trim(); if (isSupportedVideoResolution(resolution, model)) return resolution; return (resolutions as readonly string[]).includes(fallback) ? fallback : VIDEO_RESOLUTION_DEFAULT; } export function supportedVideoResolutions(model?: string) { return isSeedanceFastModel(model) ? VIDEO_FAST_RESOLUTIONS : VIDEO_RESOLUTIONS; } export function isSeedanceFastModel(model?: string) { return String(model || "").toLowerCase().replace(/\./g, "-").includes("seedance-2-0-fast"); } function isSupportedVideoRatio(value: string): value is (typeof VIDEO_RATIOS)[number] { return VIDEO_RATIOS.includes(value as (typeof VIDEO_RATIOS)[number]); } function isSupportedVideoResolution(value: string, model?: string) { return (supportedVideoResolutions(model) as readonly string[]).includes(value); }