import { DEFAULT_SCENE_ACCENT_COLOR, DEFAULT_SCENE_BACKGROUND_COLOR, } from "@/lib/studio-color-palettes"; import type { Layer, Scene, SceneTransition } from "@/lib/studio-types"; import { DEFAULT_SCENE_DURATION } from "@/lib/studio-types"; const SCENE_TRANSITIONS: SceneTransition[] = [ "none", "fade", "slide-left", "zoom", ]; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function parseLayer(value: unknown): Layer | null { if (!isRecord(value)) return null; if (typeof value.id !== "string" || typeof value.type !== "string") return null; if ( typeof value.x !== "number" || typeof value.y !== "number" || typeof value.width !== "number" || typeof value.height !== "number" ) { return null; } return { id: value.id, type: value.type as Layer["type"], name: typeof value.name === "string" ? value.name : undefined, visible: typeof value.visible === "boolean" ? value.visible : undefined, x: value.x, y: value.y, width: value.width, height: value.height, rotation: typeof value.rotation === "number" ? value.rotation : 0, opacity: typeof value.opacity === "number" ? value.opacity : 1, zIndex: typeof value.zIndex === "number" ? value.zIndex : 0, props: isRecord(value.props) ? value.props : {}, }; } function parseScene(value: unknown): Scene | null { if (!isRecord(value)) return null; if (typeof value.id !== "string" || typeof value.name !== "string") return null; if (!Array.isArray(value.layers)) return null; const layers = value.layers .map(parseLayer) .filter((layer): layer is Layer => layer !== null); const transitionType = typeof value.transitionType === "string" && SCENE_TRANSITIONS.includes(value.transitionType as SceneTransition) ? (value.transitionType as SceneTransition) : "none"; return { id: value.id, name: value.name, duration: typeof value.duration === "number" ? value.duration : DEFAULT_SCENE_DURATION, layers, transitionType, }; } export function parseVideoScenes(value: unknown): Scene[] { if (!Array.isArray(value)) return []; return value.map(parseScene).filter((scene): scene is Scene => scene !== null); } export function isVideoSceneDataEmpty(sceneData: Record): boolean { const scenes = parseVideoScenes(sceneData.scenes); return scenes.length === 0; } /** Omit generated thumbnails — they are re-created in the editor */ export function scenesForPersistence(scenes: Scene[]): Scene[] { return scenes.map((scene) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars -- strip before save const { thumbnailUrl, ...rest } = scene; return rest; }); } export interface VideoPersistedSceneData { scenes: Scene[]; activeSceneId: string; currentTime?: number; pxPerSecond?: number; audioFileName?: string | null; audioSrc?: string | null; audioVolume?: number; sceneBackgroundColor?: string; sceneAccentColor?: string; } export function buildVideoSceneDataPayload( input: VideoPersistedSceneData ): Record { return { scenes: scenesForPersistence(input.scenes), activeSceneId: input.activeSceneId, currentTime: input.currentTime, pxPerSecond: input.pxPerSecond, audioFileName: input.audioFileName, audioSrc: input.audioSrc, audioVolume: input.audioVolume, sceneBackgroundColor: input.sceneBackgroundColor, sceneAccentColor: input.sceneAccentColor, }; } export function parseVideoSceneData( sceneData: Record ): VideoPersistedSceneData | null { const scenes = parseVideoScenes(sceneData.scenes); if (scenes.length === 0) return null; const activeSceneId = typeof sceneData.activeSceneId === "string" && scenes.some((scene) => scene.id === sceneData.activeSceneId) ? sceneData.activeSceneId : scenes[0].id; return { scenes, activeSceneId, currentTime: typeof sceneData.currentTime === "number" ? sceneData.currentTime : 0, pxPerSecond: typeof sceneData.pxPerSecond === "number" ? sceneData.pxPerSecond : undefined, audioFileName: typeof sceneData.audioFileName === "string" ? sceneData.audioFileName : null, audioSrc: typeof sceneData.audioSrc === "string" ? sceneData.audioSrc : null, audioVolume: typeof sceneData.audioVolume === "number" ? sceneData.audioVolume : 100, sceneBackgroundColor: typeof sceneData.sceneBackgroundColor === "string" ? sceneData.sceneBackgroundColor : DEFAULT_SCENE_BACKGROUND_COLOR, sceneAccentColor: typeof sceneData.sceneAccentColor === "string" ? sceneData.sceneAccentColor : DEFAULT_SCENE_ACCENT_COLOR, }; }