diff options
| author | cashmere <cashmere@autistici.org> | 2026-08-16 06:16:56 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-08-16 06:16:56 +0200 |
| commit | f1043734438c8518a302f698d9ae6583a75f0c14 (patch) | |
| tree | d3e636a96e6349c1ad17afa7b28c9ffe5c882e37 /apps/web/src/services/renderer/__tests__ | |
| parent | 37d22b1e390bd6cea04bf8e15069281ee125cf10 (diff) | |
| download | opencut-f1043734438c8518a302f698d9ae6583a75f0c14.tar.gz | |
feat: add transitions, track mixing, and voiceover controls
Diffstat (limited to 'apps/web/src/services/renderer/__tests__')
| -rw-r--r-- | apps/web/src/services/renderer/__tests__/scene-builder-transition.test.ts | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/apps/web/src/services/renderer/__tests__/scene-builder-transition.test.ts b/apps/web/src/services/renderer/__tests__/scene-builder-transition.test.ts new file mode 100644 index 00000000..dd644f2a --- /dev/null +++ b/apps/web/src/services/renderer/__tests__/scene-builder-transition.test.ts @@ -0,0 +1,99 @@ +import { describe, expect, test } from "bun:test"; +import { buildScene } from "@/services/renderer/scene-builder"; +import { ImageNode } from "@/services/renderer/nodes/image-node"; +import type { ImageElement, SceneTracks } from "@/timeline"; +import type { MediaAsset } from "@/media/types"; +import { + mediaTime, + type MediaTime, + TICKS_PER_SECOND, + ZERO_MEDIA_TIME, +} from "@/wasm"; + +function imageElement({ + id, + mediaId, + startTime, + transitionDuration = 0, +}: { + id: string; + mediaId: string; + startTime: MediaTime; + transitionDuration?: number; +}): ImageElement { + return { + id, + name: id, + type: "image", + mediaId, + startTime, + duration: mediaTime({ ticks: 2 * TICKS_PER_SECOND }), + trimStart: ZERO_MEDIA_TIME, + trimEnd: ZERO_MEDIA_TIME, + params: { + "transform.positionX": 0, + "transform.positionY": 0, + "transform.scaleX": 1, + "transform.scaleY": 1, + "transform.rotate": 0, + opacity: 1, + blendMode: "normal", + "transition.kind": "fade", + "transition.inDuration": transitionDuration, + "transition.outDuration": 0, + }, + }; +} + +function imageAsset(id: string): MediaAsset { + return { + id, + name: id, + type: "image", + file: new File([], `${id}.png`, { type: "image/png" }), + url: `blob:${id}`, + }; +} + +describe("scene transition boundaries", () => { + test("holds the outgoing slide through the incoming transition", () => { + const first = imageElement({ + id: "first", + mediaId: "media-first", + startTime: ZERO_MEDIA_TIME, + }); + const second = imageElement({ + id: "second", + mediaId: "media-second", + startTime: mediaTime({ ticks: 3 * TICKS_PER_SECOND }), + transitionDuration: 1, + }); + const tracks: SceneTracks = { + overlay: [], + main: { + id: "main", + name: "Main Track", + type: "video", + elements: [first, second], + muted: false, + hidden: false, + }, + audio: [], + }; + const scene = buildScene({ + canvasSize: { width: 1920, height: 1080 }, + tracks, + mediaAssets: [imageAsset("media-first"), imageAsset("media-second")], + duration: 5 * TICKS_PER_SECOND, + background: { type: "color", color: "transparent" }, + }); + const imageNodes = scene.children.filter( + (node): node is ImageNode => node instanceof ImageNode, + ); + + expect(imageNodes).toHaveLength(2); + expect(imageNodes[0].params.contentDuration).toBe(2 * TICKS_PER_SECOND); + expect(imageNodes[0].params.duration).toBe(4 * TICKS_PER_SECOND); + expect(imageNodes[1].params.transition?.inDuration).toBe(TICKS_PER_SECOND); + }); +}); |
