@effect-motion/export 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Video.d.ts CHANGED
@@ -1,16 +1,21 @@
1
- import { type ThorvgException } from "@effect-motion/thorvg";
1
+ import type { ThreeException } from "@effect-motion/three";
2
2
  import * as Effect from "effect/Effect";
3
3
  import type * as Scope from "effect/Scope";
4
4
  import type { ChildProcessSpawner } from "effect/unstable/process";
5
- import { Scene } from "effect-motion";
5
+ import type { EffectMotionError } from "effect-motion";
6
+ import { type Resource, Scene } from "effect-motion";
6
7
  import * as Ffmpeg from "./Ffmpeg.js";
7
8
  type SceneRunner = typeof Scene.tick extends Effect.Effect<unknown, unknown, infer RR> ? RR : never;
8
9
  type SceneInternalR = SceneRunner | Scope.Scope;
9
10
  /**
10
11
  * The one-call export path: a scene becomes a video file. Composes the whole
11
- * pipeline — `Scene.stream` → ThorVG PNG renderer → ffmpeg — reading the
12
- * framerate and dimensions from the scene's own frame metadata. The ThorVG
13
- * engine is acquired internally (Node SW layer), so callers wire nothing.
12
+ * pipeline — `Scene.stream` → three GPU renderer (Dawn) → PNG → ffmpeg —
13
+ * reading the framerate and dimensions from the scene's own frame metadata.
14
+ * The renderer is acquired internally, so callers wire nothing.
15
+ *
16
+ * ponytail: frames rasterize serially (sync → render → readback per frame);
17
+ * readback being async allows pipelining ahead of the encoder if it ever
18
+ * becomes the bottleneck.
14
19
  */
15
20
  /**
16
21
  * Playback settings honored by the export path — a subset of the runner's
@@ -39,7 +44,7 @@ export interface VideoOptions {
39
44
  /** Extra ffmpeg arguments, appended before the output path. */
40
45
  readonly extraArgs?: ReadonlyArray<string>;
41
46
  /**
42
- * Supersampling factor: frames are rasterized at scene dimensions × dpr
47
+ * Supersampling factor: frames are rendered at scene dimensions × dpr
43
48
  * (the video's pixel size) while the scene keeps its authored logical
44
49
  * coordinates and framing. Defaults to 1.
45
50
  */
@@ -52,7 +57,8 @@ export interface VideoOptions {
52
57
  *
53
58
  * Fails with {@link Ffmpeg.EncodeError} on odd output dimensions (invalid for
54
59
  * `yuv420p`) — before any ffmpeg process is spawned — or on an ffmpeg failure;
55
- * a ThorVG render failure surfaces as `ThorvgException`.
60
+ * a GPU render failure surfaces as `ThreeException`; a failed resource
61
+ * (font layout, image decode) as `EffectMotionError`.
56
62
  */
57
- export declare const render: <E = never>(scene: Scene.Scene<E, SceneInternalR>, outPath: string, options?: VideoOptions) => Effect.Effect<void, E | Ffmpeg.EncodeError | ThorvgException, ChildProcessSpawner.ChildProcessSpawner>;
63
+ export declare const render: <E = never, LoaderR = never>(scene: Scene.Scene<E, SceneInternalR | LoaderR>, outPath: string, options?: VideoOptions) => Effect.Effect<void, E | Ffmpeg.EncodeError | ThreeException | EffectMotionError, ChildProcessSpawner.ChildProcessSpawner | Resource.ExtractLoaders<LoaderR>>;
58
64
  export {};
package/dist/Video.js CHANGED
@@ -1,33 +1,16 @@
1
- import { Font, Session } from "@effect-motion/thorvg";
2
- import { EngineNode } from "@effect-motion/thorvg/node";
1
+ import * as NodeRenderer from "@effect-motion/renderer/node";
3
2
  import * as Effect from "effect/Effect";
4
- import * as Layer from "effect/Layer";
5
3
  import * as Sink from "effect/Sink";
6
4
  import * as Stream from "effect/Stream";
7
- import { Fonts, Renderer, Scene } from "effect-motion";
8
- import * as PngExporter from "effect-motion/PngExporter";
5
+ import { Scene } from "effect-motion";
9
6
  import * as Ffmpeg from "./Ffmpeg.js";
10
- // The ThorVG engine (global font registry) plus a render session (the canvas
11
- // Renderer.render draws each frame onto). The session canvas is resized per
12
- // frame, so the seed size only has to be valid — the scene's own comp
13
- // dimensions (the canvas is resized to each frame's size regardless).
14
- const fontedLayer = (scene) => {
15
- const fonts = {
16
- "sans-serif": Font.DEFAULT_FONT_URL,
17
- ...Fonts.urlMap(scene),
18
- };
19
- return Layer.provideMerge(Session.layer({
20
- width: scene.width,
21
- height: scene.height,
22
- fonts,
23
- }), EngineNode.layer("sw", fonts));
24
- };
25
7
  /**
26
8
  * Render a scene to a video file at `outPath`.
27
9
  *
28
10
  * Fails with {@link Ffmpeg.EncodeError} on odd output dimensions (invalid for
29
11
  * `yuv420p`) — before any ffmpeg process is spawned — or on an ffmpeg failure;
30
- * a ThorVG render failure surfaces as `ThorvgException`.
12
+ * a GPU render failure surfaces as `ThreeException`; a failed resource
13
+ * (font layout, image decode) as `EffectMotionError`.
31
14
  */
32
15
  export const render = (scene, outPath, options = {}) => Effect.scoped(Effect.gen(function* () {
33
16
  let frames = Scene.stream(scene, options.settings ?? {});
@@ -55,19 +38,28 @@ export const render = (scene, outPath, options = {}) => Effect.scoped(Effect.gen
55
38
  cause: meta,
56
39
  });
57
40
  }
41
+ // one renderer for the whole export, sized from the first frame's
42
+ // metadata, released with this scope
43
+ const renderer = yield* NodeRenderer.make({
44
+ width: meta.width,
45
+ height: meta.height,
46
+ pixelRatio: dpr,
47
+ });
58
48
  const allFrames = Stream.concat(Stream.make(meta), rest);
59
- // each frame is rasterized to a framebuffer by the ThorVG renderer
60
- // then PNG-encoded. Rasterization is serial: Renderer.render mutates
61
- // one shared RenderSession canvas per frame (resize/clear/draw/read),
62
- // so concurrent renders would clobber each other's pixels.
63
- const pngStream = Stream.mapEffect(allFrames, (frame) => Renderer.render(frame, { dpr }).pipe(Effect.flatMap(PngExporter.toBuffer)));
49
+ // serial by construction: syncFrame mutates one retained scene, so
50
+ // concurrent renders would clobber each other's state
51
+ const pngStream = Stream.mapEffect(allFrames, (frame) => NodeRenderer.renderToPng(renderer, frame));
52
+ // writing a file implies its directory: create the output's parent
53
+ // so render programs carry no mkdir boilerplate (Node-only module,
54
+ // no new requirement)
55
+ yield* Effect.promise(async () => {
56
+ const { mkdir } = await import("node:fs/promises");
57
+ const { dirname } = await import("node:path");
58
+ await mkdir(dirname(outPath), { recursive: true });
59
+ });
64
60
  yield* Ffmpeg.encode(pngStream, outPath, {
65
61
  frameRate: meta.frameRate,
66
62
  binary: options.binary,
67
63
  extraArgs: options.extraArgs,
68
64
  });
69
- })).pipe(
70
- // the scene's declared url fonts, merged over the default sans, so text
71
- // in a declared family renders (design D3); path-only entries skipped.
72
- // Fonts go to both the engine (global registry) and the render session.
73
- Effect.provide(fontedLayer(scene)));
65
+ }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-motion/export",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Node-only export tools for effect-motion: render scenes to PNG frames (ThorVG) and encode to video (ffmpeg)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,8 +34,9 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "ffmpeg-static": "^5.3.0",
37
- "@effect-motion/thorvg": "^0.2.0",
38
- "effect-motion": "^0.4.0"
37
+ "@effect-motion/renderer": "^0.5.0",
38
+ "@effect-motion/three": "^0.5.0",
39
+ "effect-motion": "^0.5.0"
39
40
  },
40
41
  "peerDependencies": {
41
42
  "effect": ">=4.0.0-beta.98"