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.ts46
1 files changed, 42 insertions, 4 deletions
diff --git a/apps/web/src/services/renderer/scene-builder.ts b/apps/web/src/services/renderer/scene-builder.ts
index 431b8269..9df154ef 100644
--- a/apps/web/src/services/renderer/scene-builder.ts
+++ b/apps/web/src/services/renderer/scene-builder.ts
@@ -1,4 +1,4 @@
-import type { SceneTracks, TimelineTrack } from "@/timeline";
+import type { SceneTracks, TimelineElement, TimelineTrack } from "@/timeline";
import type { MediaAsset } from "@/media/types";
import { RootNode } from "./nodes/root-node";
import { VideoNode } from "./nodes/video-node";
@@ -17,6 +17,7 @@ import {
readBlendModeFromParams,
readOpacityFromParams,
} from "@/rendering";
+import { buildTransitionFromParams } from "@/rendering/transition";
const PREVIEW_MAX_IMAGE_SIZE = 2048;
@@ -29,6 +30,32 @@ function getVisibleSortedElements({ track }: { track: TimelineTrack }) {
return a.id.localeCompare(b.id);
});
}
+function getTailHoldDuration({
+ elements,
+ elementIndex,
+}: {
+ elements: TimelineElement[];
+ elementIndex: number;
+}): number {
+ const element = elements[elementIndex];
+ const nextElement = elements[elementIndex + 1];
+ if (
+ !element ||
+ !nextElement ||
+ (element.type !== "video" && element.type !== "image") ||
+ (nextElement.type !== "video" && nextElement.type !== "image")
+ ) {
+ return 0;
+ }
+ const gap = nextElement.startTime - (element.startTime + element.duration);
+ if (gap < 0) {
+ return 0;
+ }
+ const transitionDuration = buildTransitionFromParams({
+ params: nextElement.params,
+ }).inDuration;
+ return transitionDuration > 0 ? gap + transitionDuration : 0;
+}
function buildTrackNodes({
tracks,
@@ -46,7 +73,11 @@ function buildTrackNodes({
for (const track of tracks) {
const elements = getVisibleSortedElements({ track });
- for (const element of elements) {
+ for (const [elementIndex, element] of elements.entries()) {
+ const tailHoldDuration = getTailHoldDuration({
+ elements,
+ elementIndex,
+ });
if (element.type === "effect") {
nodes.push(
new EffectLayerNode({
@@ -71,7 +102,8 @@ function buildTrackNodes({
mediaId: mediaAsset.id,
url: mediaAsset.url,
file: mediaAsset.file,
- duration: element.duration,
+ duration: element.duration + tailHoldDuration,
+ contentDuration: element.duration,
timeOffset: element.startTime,
trimStart: element.trimStart,
trimEnd: element.trimEnd,
@@ -80,6 +112,7 @@ function buildTrackNodes({
animations: element.animations,
opacity: readOpacityFromParams({ params: element.params }),
blendMode: readBlendModeFromParams({ params: element.params }),
+ transition: buildTransitionFromParams({ params: element.params }),
effects: element.effects ?? [],
masks: element.masks ?? [],
}),
@@ -89,7 +122,8 @@ function buildTrackNodes({
nodes.push(
new ImageNode({
url: mediaAsset.url,
- duration: element.duration,
+ duration: element.duration + tailHoldDuration,
+ contentDuration: element.duration,
timeOffset: element.startTime,
trimStart: element.trimStart,
trimEnd: element.trimEnd,
@@ -97,6 +131,7 @@ function buildTrackNodes({
animations: element.animations,
opacity: readOpacityFromParams({ params: element.params }),
blendMode: readBlendModeFromParams({ params: element.params }),
+ transition: buildTransitionFromParams({ params: element.params }),
effects: element.effects ?? [],
masks: element.masks ?? [],
...(isPreview && {
@@ -114,6 +149,7 @@ function buildTrackNodes({
transform: buildTransformFromParams({ params: element.params }),
opacity: readOpacityFromParams({ params: element.params }),
blendMode: readBlendModeFromParams({ params: element.params }),
+ transition: buildTransitionFromParams({ params: element.params }),
canvasCenter: { x: canvasSize.width / 2, y: canvasSize.height / 2 },
canvasHeight: canvasSize.height,
textBaseline: "middle",
@@ -136,6 +172,7 @@ function buildTrackNodes({
animations: element.animations,
opacity: readOpacityFromParams({ params: element.params }),
blendMode: readBlendModeFromParams({ params: element.params }),
+ transition: buildTransitionFromParams({ params: element.params }),
effects: element.effects ?? [],
}),
);
@@ -154,6 +191,7 @@ function buildTrackNodes({
animations: element.animations,
opacity: readOpacityFromParams({ params: element.params }),
blendMode: readBlendModeFromParams({ params: element.params }),
+ transition: buildTransitionFromParams({ params: element.params }),
effects: element.effects ?? [],
masks: element.masks ?? [],
}),