import type { TimelineTrack } from "@/types/timeline"; import type { MediaAsset } from "@/types/assets"; import { RootNode } from "./nodes/root-node"; import { VideoNode } from "./nodes/video-node"; import { ImageNode } from "./nodes/image-node"; import { TextNode } from "./nodes/text-node"; import { StickerNode } from "./nodes/sticker-node"; import { ColorNode } from "./nodes/color-node"; import { BlurBackgroundNode } from "./nodes/blur-background-node"; import type { TBackground, TCanvasSize } from "@/types/project"; import { DEFAULT_BLUR_INTENSITY } from "@/constants/project-constants"; import { isMainTrack } from "@/lib/timeline"; export type BuildSceneParams = { canvasSize: TCanvasSize; tracks: TimelineTrack[]; mediaAssets: MediaAsset[]; duration: number; background: TBackground; }; export function buildScene(params: BuildSceneParams) { const { tracks, mediaAssets, duration, canvasSize, background } = params; const rootNode = new RootNode({ duration }); const mediaMap = new Map(mediaAssets.map((m) => [m.id, m])); const visibleTracks = tracks.filter( (track) => !("hidden" in track && track.hidden), ); const orderedTracksTopToBottom = [ ...visibleTracks.filter((track) => !isMainTrack(track)), ...visibleTracks.filter((track) => isMainTrack(track)), ]; const orderedTracksBottomToTop = orderedTracksTopToBottom.slice().reverse(); const contentNodes = []; for (const track of orderedTracksBottomToTop) { const elements = track.elements .filter((element) => !("hidden" in element && element.hidden)) .slice() .sort((a, b) => { if (a.startTime !== b.startTime) return a.startTime - b.startTime; return a.id.localeCompare(b.id); }); for (const element of elements) { if (element.type === "video" || element.type === "image") { const mediaAsset = mediaMap.get(element.mediaId); if (!mediaAsset?.file || !mediaAsset?.url) { continue; } if (mediaAsset.type === "video") { contentNodes.push( new VideoNode({ mediaId: mediaAsset.id, url: mediaAsset.url, file: mediaAsset.file, duration: element.duration, timeOffset: element.startTime, trimStart: element.trimStart, trimEnd: element.trimEnd, transform: element.transform, opacity: element.opacity, blendMode: element.blendMode, }), ); } if (mediaAsset.type === "image") { contentNodes.push( new ImageNode({ url: mediaAsset.url, duration: element.duration, timeOffset: element.startTime, trimStart: element.trimStart, trimEnd: element.trimEnd, transform: element.transform, opacity: element.opacity, blendMode: element.blendMode, }), ); } } if (element.type === "text") { contentNodes.push( new TextNode({ ...element, canvasCenter: { x: canvasSize.width / 2, y: canvasSize.height / 2 }, canvasHeight: canvasSize.height, textBaseline: "middle", }), ); } if (element.type === "sticker") { contentNodes.push( new StickerNode({ stickerId: element.stickerId, duration: element.duration, timeOffset: element.startTime, trimStart: element.trimStart, trimEnd: element.trimEnd, transform: element.transform, opacity: element.opacity, blendMode: element.blendMode, }), ); } } } if (background.type === "blur") { rootNode.add( new BlurBackgroundNode({ blurIntensity: background.blurIntensity ?? DEFAULT_BLUR_INTENSITY, contentNodes, }), ); for (const node of contentNodes) { rootNode.add(node); } } else { if (background.type === "color" && background.color !== "transparent") { rootNode.add(new ColorNode({ color: background.color })); } for (const node of contentNodes) { rootNode.add(node); } } return rootNode; }