@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 +14 -8
- package/dist/Video.js +23 -31
- package/package.json +4 -3
package/dist/Video.d.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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` →
|
|
12
|
-
* framerate and dimensions from the scene's own frame metadata.
|
|
13
|
-
*
|
|
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
|
|
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
|
|
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 |
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
//
|
|
63
|
-
|
|
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
|
-
}))
|
|
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.
|
|
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/
|
|
38
|
-
"effect-motion": "^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"
|