summaryrefslogtreecommitdiff
path: root/apps/web/src/services/renderer/scene-builder.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/services/renderer/scene-builder.ts')
-rw-r--r--apps/web/src/services/renderer/scene-builder.ts130
1 files changed, 130 insertions, 0 deletions
diff --git a/apps/web/src/services/renderer/scene-builder.ts b/apps/web/src/services/renderer/scene-builder.ts
new file mode 100644
index 00000000..b36edda1
--- /dev/null
+++ b/apps/web/src/services/renderer/scene-builder.ts
@@ -0,0 +1,130 @@
+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 mainTrack = visibleTracks.find((track) => isMainTrack(track)) ?? null;
+ const orderedTracksTopToBottom = [
+ ...visibleTracks.filter((track) => !isMainTrack(track)),
+ ...(mainTrack ? [mainTrack] : []),
+ ];
+
+ 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) {
+ continue;
+ }
+
+ if (mediaAsset.type === "video") {
+ contentNodes.push(
+ new VideoNode({
+ mediaId: mediaAsset.id,
+ file: mediaAsset.file,
+ duration: element.duration,
+ timeOffset: element.startTime,
+ trimStart: element.trimStart,
+ trimEnd: element.trimEnd,
+ }),
+ );
+ }
+ if (mediaAsset.type === "image") {
+ contentNodes.push(
+ new ImageNode({
+ file: mediaAsset.file,
+ duration: element.duration,
+ timeOffset: element.startTime,
+ trimStart: element.trimStart,
+ trimEnd: element.trimEnd,
+ }),
+ );
+ }
+ }
+
+ if (element.type === "text") {
+ contentNodes.push(
+ new TextNode({
+ ...element,
+ canvasCenter: { x: canvasSize.width / 2, y: canvasSize.height / 2 },
+ textBaseline: "middle",
+ }),
+ );
+ }
+
+ if (element.type === "sticker") {
+ contentNodes.push(
+ new StickerNode({
+ iconName: element.iconName,
+ duration: element.duration,
+ timeOffset: element.startTime,
+ trimStart: element.trimStart,
+ trimEnd: element.trimEnd,
+ transform: element.transform,
+ opacity: element.opacity,
+ color: element.color,
+ }),
+ );
+ }
+ }
+ }
+
+ 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;
+}