@motionscript/browser 0.1.0-alpha.0 → 0.1.0-alpha.10
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/CHANGELOG.md +187 -0
- package/NOTICE +49 -0
- package/README.md +96 -24
- package/dist/asset-bytes.d.ts +14 -5
- package/dist/asset-bytes.d.ts.map +1 -1
- package/dist/asset-bytes.js +23 -5
- package/dist/asset-bytes.js.map +1 -1
- package/dist/audio/bus-graph.d.ts +12 -3
- package/dist/audio/bus-graph.d.ts.map +1 -1
- package/dist/audio/bus-graph.js +35 -31
- package/dist/audio/bus-graph.js.map +1 -1
- package/dist/audio/effect-graph.d.ts +62 -0
- package/dist/audio/effect-graph.d.ts.map +1 -0
- package/dist/audio/effect-graph.js +234 -0
- package/dist/audio/effect-graph.js.map +1 -0
- package/dist/audio/index.d.ts.map +1 -1
- package/dist/audio/index.js +2 -3
- package/dist/audio/index.js.map +1 -1
- package/dist/audio/loop-region.d.ts +31 -0
- package/dist/audio/loop-region.d.ts.map +1 -0
- package/dist/audio/loop-region.js +43 -0
- package/dist/audio/loop-region.js.map +1 -0
- package/dist/audio/mixer.d.ts +4 -4
- package/dist/audio/mixer.d.ts.map +1 -1
- package/dist/audio/mixer.js +43 -44
- package/dist/audio/mixer.js.map +1 -1
- package/dist/audio/player.d.ts +15 -9
- package/dist/audio/player.d.ts.map +1 -1
- package/dist/audio/player.js +60 -69
- package/dist/audio/player.js.map +1 -1
- package/dist/browser/THIRD_PARTY_LICENSES.txt +784 -0
- package/dist/browser/audio.js +1 -1
- package/dist/browser/chunks/chunk-F3AZKPHU.js +2 -0
- package/dist/browser/chunks/chunk-F3AZKPHU.js.map +7 -0
- package/dist/browser/chunks/chunk-KLXMRKTL.js +2 -0
- package/dist/browser/chunks/chunk-L5UFQACH.js +163 -0
- package/dist/browser/chunks/chunk-L5UFQACH.js.map +7 -0
- package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js +14 -0
- package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js.map +7 -0
- package/dist/browser/index.js +52 -1490
- package/dist/browser/index.js.map +4 -4
- package/dist/browser/manifest.json +21 -4
- package/dist/browser/ms_renderer_bg.wasm +0 -0
- package/dist/concat.d.ts +33 -7
- package/dist/concat.d.ts.map +1 -1
- package/dist/concat.js +117 -72
- package/dist/concat.js.map +1 -1
- package/dist/deliver.d.ts +10 -0
- package/dist/deliver.d.ts.map +1 -0
- package/dist/deliver.js +22 -0
- package/dist/deliver.js.map +1 -0
- package/dist/dynamic-nodes.d.ts.map +1 -1
- package/dist/dynamic-nodes.js +9 -12
- package/dist/dynamic-nodes.js.map +1 -1
- package/dist/engine.d.ts +43 -15
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +58 -36
- package/dist/engine.js.map +1 -1
- package/dist/export-audio.d.ts +36 -0
- package/dist/export-audio.d.ts.map +1 -0
- package/dist/export-audio.js +56 -0
- package/dist/export-audio.js.map +1 -0
- package/dist/exporter.d.ts +134 -15
- package/dist/exporter.d.ts.map +1 -1
- package/dist/exporter.js +135 -90
- package/dist/exporter.js.map +1 -1
- package/dist/formats.d.ts +36 -0
- package/dist/formats.d.ts.map +1 -0
- package/dist/formats.js +83 -0
- package/dist/formats.js.map +1 -0
- package/dist/frame-loop.d.ts +190 -0
- package/dist/frame-loop.d.ts.map +1 -0
- package/dist/frame-loop.js +304 -0
- package/dist/frame-loop.js.map +1 -0
- package/dist/index.d.ts +12 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -47
- package/dist/index.js.map +1 -1
- package/dist/mix-timeline-audio.d.ts +7 -7
- package/dist/mix-timeline-audio.d.ts.map +1 -1
- package/dist/mix-timeline-audio.js +11 -14
- package/dist/mix-timeline-audio.js.map +1 -1
- package/dist/parallel/export-parallel.d.ts +32 -0
- package/dist/parallel/export-parallel.d.ts.map +1 -0
- package/dist/parallel/export-parallel.js +114 -0
- package/dist/parallel/export-parallel.js.map +1 -0
- package/dist/parallel/governor.d.ts +57 -0
- package/dist/parallel/governor.d.ts.map +1 -0
- package/dist/parallel/governor.js +79 -0
- package/dist/parallel/governor.js.map +1 -0
- package/dist/parallel/lane.d.ts +45 -0
- package/dist/parallel/lane.d.ts.map +1 -0
- package/dist/parallel/lane.js +2 -0
- package/dist/parallel/lane.js.map +1 -0
- package/dist/parallel/lanes.d.ts +20 -0
- package/dist/parallel/lanes.d.ts.map +1 -0
- package/dist/parallel/lanes.js +152 -0
- package/dist/parallel/lanes.js.map +1 -0
- package/dist/parallel/plan.d.ts +23 -0
- package/dist/parallel/plan.d.ts.map +1 -0
- package/dist/parallel/plan.js +30 -0
- package/dist/parallel/plan.js.map +1 -0
- package/dist/parallel/run-lanes.d.ts +38 -0
- package/dist/parallel/run-lanes.d.ts.map +1 -0
- package/dist/parallel/run-lanes.js +164 -0
- package/dist/parallel/run-lanes.js.map +1 -0
- package/dist/parallel/session.d.ts +9 -0
- package/dist/parallel/session.d.ts.map +1 -0
- package/dist/parallel/session.js +79 -0
- package/dist/parallel/session.js.map +1 -0
- package/dist/renderer.d.ts +22 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +23 -0
- package/dist/renderer.js.map +1 -0
- package/dist/screenshot.d.ts +6 -6
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +4 -5
- package/dist/screenshot.js.map +1 -1
- package/dist/still.d.ts +32 -24
- package/dist/still.d.ts.map +1 -1
- package/dist/still.js +54 -54
- package/dist/still.js.map +1 -1
- package/dist/ticker.d.ts +41 -9
- package/dist/ticker.d.ts.map +1 -1
- package/dist/ticker.js +113 -41
- package/dist/ticker.js.map +1 -1
- package/package.json +11 -10
- package/dist/audio/filter-graph.d.ts +0 -58
- package/dist/audio/filter-graph.d.ts.map +0 -1
- package/dist/audio/filter-graph.js +0 -235
- package/dist/audio/filter-graph.js.map +0 -1
- package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js +0 -2
- package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js.map +0 -7
- package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js +0 -2
- package/dist/browser/chunks/chunk-CTLBXZSU.js +0 -4119
- package/dist/browser/chunks/chunk-CTLBXZSU.js.map +0 -7
- package/dist/browser/chunks/chunk-FQ32PDIU.js +0 -2
- package/dist/browser/chunks/chunk-FQ32PDIU.js.map +0 -7
- package/dist/browser/chunks/chunk-FY3BTKY4.js +0 -2
- package/dist/browser/chunks/chunk-FY3BTKY4.js.map +0 -7
- package/dist/browser/chunks/chunk-NSFLRI4P.js +0 -2
- package/dist/browser/chunks/chunk-NSFLRI4P.js.map +0 -7
- package/dist/browser/chunks/three.module-Z4EOVUWF.js +0 -2
- package/dist/browser/chunks/three.module-Z4EOVUWF.js.map +0 -7
- package/dist/decode/image-levels.d.ts +0 -137
- package/dist/decode/image-levels.d.ts.map +0 -1
- package/dist/decode/image-levels.js +0 -477
- package/dist/decode/image-levels.js.map +0 -1
- package/dist/decode/levels.d.ts +0 -37
- package/dist/decode/levels.d.ts.map +0 -1
- package/dist/decode/levels.js +0 -68
- package/dist/decode/levels.js.map +0 -1
- package/dist/decode/video-levels.d.ts +0 -37
- package/dist/decode/video-levels.d.ts.map +0 -1
- package/dist/decode/video-levels.js +0 -95
- package/dist/decode/video-levels.js.map +0 -1
- package/dist/getter.d.ts +0 -10
- package/dist/getter.d.ts.map +0 -1
- package/dist/getter.js +0 -38
- package/dist/getter.js.map +0 -1
- package/dist/measure-context.d.ts +0 -12
- package/dist/measure-context.d.ts.map +0 -1
- package/dist/measure-context.js +0 -12
- package/dist/measure-context.js.map +0 -1
- package/dist/output.d.ts +0 -7
- package/dist/output.d.ts.map +0 -1
- package/dist/output.js +0 -6
- package/dist/output.js.map +0 -1
- package/dist/render-context.d.ts +0 -30
- package/dist/render-context.d.ts.map +0 -1
- package/dist/render-context.js +0 -55
- package/dist/render-context.js.map +0 -1
- package/dist/storage-adapter.d.ts +0 -367
- package/dist/storage-adapter.d.ts.map +0 -1
- package/dist/storage-adapter.js +0 -1278
- package/dist/storage-adapter.js.map +0 -1
- package/dist/three/renderer.d.ts +0 -48
- package/dist/three/renderer.d.ts.map +0 -1
- package/dist/three/renderer.js +0 -177
- package/dist/three/renderer.js.map +0 -1
- /package/dist/browser/chunks/{SkeletonUtils-XYDMTGTM.js.map → chunk-KLXMRKTL.js.map} +0 -0
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createWgpuRenderer, isWgpuSupported } from "@motionscript/renderer";
|
|
2
|
+
import { loadAssetBytes } from "./asset-bytes";
|
|
3
|
+
/**
|
|
4
|
+
* This browser cannot run the WebGPU renderer: no `navigator.gpu`, or no
|
|
5
|
+
* adapter behind it. Every entry that draws (an engine, a still, an export,
|
|
6
|
+
* a timeline's audio mix) rejects with it before drawing anything.
|
|
7
|
+
*/
|
|
8
|
+
export class WebGpuUnavailableError extends Error {
|
|
9
|
+
constructor() {
|
|
10
|
+
super("Motion Script draws with WebGPU, and this browser has no WebGPU adapter.");
|
|
11
|
+
this.name = "WebGpuUnavailableError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The renderer every entry draws with, or {@link WebGpuUnavailableError}.
|
|
16
|
+
* `gpu: false` is for an entry that only measures text, which needs no device.
|
|
17
|
+
*/
|
|
18
|
+
export async function loadRenderer(options = {}, { gpu = true } = {}) {
|
|
19
|
+
if (gpu && !(await isWgpuSupported()))
|
|
20
|
+
throw new WebGpuUnavailableError();
|
|
21
|
+
return createWgpuRenderer({ wasmUrl: options.rendererWasmUrl, loadBytes: loadAssetBytes });
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAqB,MAAM,wBAAwB,CAAC;AAChG,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAQ/C;;;;GAIG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC7C;QACI,KAAK,CAAC,0EAA0E,CAAC,CAAC;QAClF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACzC,CAAC;CACJ;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,UAA8B,EAAE,EAAE,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,EAAE;IACpF,IAAI,GAAG,IAAI,CAAC,CAAC,MAAM,eAAe,EAAE,CAAC;QAAE,MAAM,IAAI,sBAAsB,EAAE,CAAC;IAC1E,OAAO,kBAAkB,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC;AAC/F,CAAC","sourcesContent":["import { createWgpuRenderer, isWgpuSupported, type WgpuRenderer } from \"@motionscript/renderer\";\nimport { loadAssetBytes } from \"./asset-bytes\";\n\n/** Where the renderer's WebAssembly is served, for every entry that draws. */\nexport interface RendererWasmOption {\n /** URL of `ms_renderer_bg.wasm`; defaults to beside `@motionscript/renderer`'s module, which a bundler resolves. */\n rendererWasmUrl?: string | URL;\n}\n\n/**\n * This browser cannot run the WebGPU renderer: no `navigator.gpu`, or no\n * adapter behind it. Every entry that draws (an engine, a still, an export,\n * a timeline's audio mix) rejects with it before drawing anything.\n */\nexport class WebGpuUnavailableError extends Error {\n constructor() {\n super(\"Motion Script draws with WebGPU, and this browser has no WebGPU adapter.\");\n this.name = \"WebGpuUnavailableError\";\n }\n}\n\n/**\n * The renderer every entry draws with, or {@link WebGpuUnavailableError}.\n * `gpu: false` is for an entry that only measures text, which needs no device.\n */\nexport async function loadRenderer(options: RendererWasmOption = {}, { gpu = true } = {}): Promise<WgpuRenderer> {\n if (gpu && !(await isWgpuSupported())) throw new WebGpuUnavailableError();\n return createWgpuRenderer({ wasmUrl: options.rendererWasmUrl, loadBytes: loadAssetBytes });\n}\n"]}
|
package/dist/screenshot.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { type AssetManifest, type ProjectDocument, type Registry, type
|
|
2
|
-
import type {
|
|
1
|
+
import { type AssetManifest, type FrameSpec, type ProjectDocument, type Registry, type VisualDocument, type Size2D, type Theme } from "@motionscript/core";
|
|
2
|
+
import type { ScreenshotFormat } from "./frame-loop";
|
|
3
|
+
import type { RendererWasmOption } from "./renderer";
|
|
3
4
|
import { type FrameRef } from "./still";
|
|
4
5
|
export type { ScreenshotFormat, FrameSpec };
|
|
5
|
-
export type ScreenshotParams = {
|
|
6
|
+
export type ScreenshotParams = RendererWasmOption & {
|
|
6
7
|
/** A project, or a lone still or composition as the one track of one. */
|
|
7
|
-
document: ProjectDocument |
|
|
8
|
+
document: ProjectDocument | VisualDocument;
|
|
8
9
|
/** The vocabulary its clips compile against. Defaults to what the library ships. */
|
|
9
10
|
registry?: Registry;
|
|
10
11
|
/** Win over the document's own; 1920×1080 at 60 fps when neither says. */
|
|
@@ -14,7 +15,6 @@ export type ScreenshotParams = {
|
|
|
14
15
|
manifest?: AssetManifest;
|
|
15
16
|
/** Theme for a document that carries none of its own. */
|
|
16
17
|
theme?: Theme;
|
|
17
|
-
wasmUrl?: string;
|
|
18
18
|
/** Which frame to capture. Defaults to the timeline's first. */
|
|
19
19
|
frame?: FrameRef;
|
|
20
20
|
/** Encoding format (default "png"). */
|
|
@@ -38,7 +38,7 @@ export type ScreenshotResult = {
|
|
|
38
38
|
*
|
|
39
39
|
* For anything that captures **repeatedly** — a thumbnail builder, a preview that
|
|
40
40
|
* repaints as the user edits — hold a {@link StillRenderer} instead. This function
|
|
41
|
-
* builds and tears down a
|
|
41
|
+
* builds and tears down a WebGPU surface per call, which is the right trade for one
|
|
42
42
|
* capture and the wrong one for a hundred.
|
|
43
43
|
*/
|
|
44
44
|
export declare function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult>;
|
package/dist/screenshot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,KAAK,KAAK,EACxH,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG;IAChD,yEAAyE;IACzE,QAAQ,EAAE,eAAe,GAAG,cAAc,CAAC;IAC3C,oFAAoF;IACpF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,gEAAgE;IAChE,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,uCAAuC;IACvC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,KAAK,EAAE,UAAU,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmB1F"}
|
package/dist/screenshot.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { resolveProjectDocument, } from "@motionscript/core";
|
|
2
|
-
import { DEFAULT_OUTPUT } from "./output";
|
|
1
|
+
import { DEFAULT_OUTPUT, resolveProjectDocument, } from "@motionscript/core";
|
|
3
2
|
import { createStillRenderer } from "./still";
|
|
4
3
|
/**
|
|
5
4
|
* Renders a single frame of a document to an image.
|
|
@@ -10,16 +9,16 @@ import { createStillRenderer } from "./still";
|
|
|
10
9
|
*
|
|
11
10
|
* For anything that captures **repeatedly** — a thumbnail builder, a preview that
|
|
12
11
|
* repaints as the user edits — hold a {@link StillRenderer} instead. This function
|
|
13
|
-
* builds and tears down a
|
|
12
|
+
* builds and tears down a WebGPU surface per call, which is the right trade for one
|
|
14
13
|
* capture and the wrong one for a hundred.
|
|
15
14
|
*/
|
|
16
15
|
export async function exportScreenshot(params) {
|
|
17
|
-
const { registry, scale, manifest, theme,
|
|
16
|
+
const { registry, scale, manifest, theme, rendererWasmUrl, frame, format = "png", quality = 0.92, } = params;
|
|
18
17
|
const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);
|
|
19
18
|
if (project.tracks.length === 0)
|
|
20
19
|
throw new Error("The project has no tracks to screenshot.");
|
|
21
20
|
const renderer = await createStillRenderer({
|
|
22
|
-
viewport: project.viewport, fps: project.fps, scale, manifest, theme,
|
|
21
|
+
viewport: project.viewport, fps: project.fps, scale, manifest, theme, rendererWasmUrl, registry,
|
|
23
22
|
});
|
|
24
23
|
try {
|
|
25
24
|
const drawn = await renderer.render(project, { frame });
|
package/dist/screenshot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,
|
|
1
|
+
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,cAAc,EAAE,sBAAsB,GAEzC,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAgC7D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAwB;IAC3D,MAAM,EACF,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EACjD,KAAK,EAAE,MAAM,GAAG,KAAK,EAAE,OAAO,GAAG,IAAI,GACxC,GAAG,MAAM,CAAC;IAEX,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAChF,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAE7F,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC;QACvC,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ;KAClG,CAAC,CAAC;IAEH,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACxD,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAC5E,CAAC;YAAS,CAAC;QACP,QAAQ,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;AACL,CAAC","sourcesContent":["import {\n DEFAULT_OUTPUT, resolveProjectDocument,\n type AssetManifest, type FrameSpec, type ProjectDocument, type Registry, type VisualDocument, type Size2D, type Theme,\n} from \"@motionscript/core\";\nimport type { ScreenshotFormat } from \"./frame-loop\";\nimport type { RendererWasmOption } from \"./renderer\";\nimport { createStillRenderer, type FrameRef } from \"./still\";\n\nexport type { ScreenshotFormat, FrameSpec };\n\nexport type ScreenshotParams = RendererWasmOption & {\n /** A project, or a lone still or composition as the one track of one. */\n document: ProjectDocument | VisualDocument;\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\n registry?: Registry;\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\n viewport?: Size2D;\n fps?: number;\n scale?: number;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n /** Which frame to capture. Defaults to the timeline's first. */\n frame?: FrameRef;\n /** Encoding format (default \"png\"). */\n format?: ScreenshotFormat;\n /** JPEG quality in [0,1] (ignored for png; default 0.92). */\n quality?: number;\n};\n\nexport type ScreenshotResult = {\n /** The project frame that was captured, after clamping. */\n frame: number;\n totalFrames: number;\n /** Encoded image bytes. */\n bytes: Uint8Array;\n};\n\n/**\n * Renders a single frame of a document to an image.\n *\n * A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.\n * It therefore takes the same path as a live preview, and as the video export at\n * that frame — there is one renderer, not three.\n *\n * For anything that captures **repeatedly** — a thumbnail builder, a preview that\n * repaints as the user edits — hold a {@link StillRenderer} instead. This function\n * builds and tears down a WebGPU surface per call, which is the right trade for one\n * capture and the wrong one for a hundred.\n */\nexport async function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult> {\n const {\n registry, scale, manifest, theme, rendererWasmUrl,\n frame, format = \"png\", quality = 0.92,\n } = params;\n\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\n if (project.tracks.length === 0) throw new Error(\"The project has no tracks to screenshot.\");\n\n const renderer = await createStillRenderer({\n viewport: project.viewport, fps: project.fps, scale, manifest, theme, rendererWasmUrl, registry,\n });\n\n try {\n const drawn = await renderer.render(project, { frame });\n return { ...drawn, bytes: await renderer.toBytes({ format, quality }) };\n } finally {\n renderer.dispose();\n }\n}\n"]}
|
package/dist/still.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { Registry, type AssetManifest, type ProjectDocument, type
|
|
2
|
-
import { type DrawnFrame, type
|
|
3
|
-
import {
|
|
1
|
+
import { Registry, type AssetManifest, type FrameSpec, type RenderIssue, type ProjectDocument, type VisualDocument, type Size2D, type Theme } from "@motionscript/core";
|
|
2
|
+
import { type DrawnFrame, type ScreenshotFormat } from "./frame-loop";
|
|
3
|
+
import type { BrowserRenderer } from "./engine";
|
|
4
|
+
import { type RendererWasmOption } from "./renderer";
|
|
4
5
|
/** Anything a still can be rendered from: a project, or a lone still or composition as the one track of one. */
|
|
5
|
-
export type StillSource = ProjectDocument |
|
|
6
|
+
export type StillSource = ProjectDocument | VisualDocument;
|
|
6
7
|
/**
|
|
7
8
|
* Which frame to draw, in whatever form is most convenient — a global frame
|
|
8
9
|
* index, the timeline's first or last frame, or a `FrameSpec`.
|
|
9
10
|
*/
|
|
10
11
|
export type FrameRef = FrameSpec | number | "first" | "last";
|
|
11
|
-
export interface StillRendererOptions {
|
|
12
|
+
export interface StillRendererOptions extends RendererWasmOption {
|
|
12
13
|
/**
|
|
13
14
|
* The canvas to draw into. Omit and the renderer creates its own hidden one
|
|
14
15
|
* and removes it on {@link StillRenderer.dispose} — which is what a one-shot
|
|
@@ -24,7 +25,6 @@ export interface StillRendererOptions {
|
|
|
24
25
|
manifest?: AssetManifest;
|
|
25
26
|
/** Theme for a document that carries none of its own. */
|
|
26
27
|
theme?: Theme;
|
|
27
|
-
wasmUrl?: string;
|
|
28
28
|
/**
|
|
29
29
|
* The vocabulary a document is compiled against.
|
|
30
30
|
*
|
|
@@ -41,11 +41,14 @@ export interface RenderStillOptions {
|
|
|
41
41
|
/** Seconds into the timeline, converted with the document's fps. Wins over `frame`. */
|
|
42
42
|
time?: number;
|
|
43
43
|
/**
|
|
44
|
-
* Draw at this pixel ratio instead of the renderer's. **Resizes the
|
|
45
|
-
* rebuilds the
|
|
46
|
-
*
|
|
44
|
+
* Draw this call at this pixel ratio instead of the renderer's. **Resizes the
|
|
45
|
+
* canvas and rebuilds the surface**, and the next call without it rebuilds
|
|
46
|
+
* them back, so keep it off the per-edit path — render the preview at its
|
|
47
|
+
* normal scale and pass this only when capturing at a higher one.
|
|
47
48
|
*/
|
|
48
49
|
scale?: number;
|
|
50
|
+
/** What the renderer drew but not as the document says; a lost GPU device throws `RenderBackendError` instead. */
|
|
51
|
+
onIssue?: (issue: RenderIssue) => void;
|
|
49
52
|
}
|
|
50
53
|
export interface EncodeStillOptions {
|
|
51
54
|
/** Defaults to `"png"`. */
|
|
@@ -54,12 +57,12 @@ export interface EncodeStillOptions {
|
|
|
54
57
|
quality?: number;
|
|
55
58
|
}
|
|
56
59
|
/** Normalize the many ways a caller can name a frame into the engine's own form. */
|
|
57
|
-
export declare function
|
|
60
|
+
export declare function stillFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec;
|
|
58
61
|
/**
|
|
59
62
|
* A long-lived single-frame renderer.
|
|
60
63
|
*
|
|
61
64
|
* Where `exportScreenshot` is one frame from a standing start, this keeps the
|
|
62
|
-
* expensive things — the
|
|
65
|
+
* expensive things — the renderer module, its GPU surface, and the
|
|
63
66
|
* storage adapter's decoded-asset cache — alive across calls, so re-rendering
|
|
64
67
|
* after an edit costs discovering one frame and a draw rather than a new GL
|
|
65
68
|
* context. That is
|
|
@@ -84,24 +87,33 @@ export declare function toFrameSpec(options: RenderStillOptions | undefined, fps
|
|
|
84
87
|
* different themes still each draw correctly; but anything else that calls
|
|
85
88
|
* `setTheme` between a render and a read of the surface will have been
|
|
86
89
|
* overwritten by the next render.
|
|
90
|
+
*
|
|
91
|
+
* **One surface, one caller at a time.** A render and the encode of its result
|
|
92
|
+
* are two calls, and the next render draws over the surface between them, so
|
|
93
|
+
* callers that share a renderer without knowing of each other must be queued —
|
|
94
|
+
* as `session.image()` queues its calls.
|
|
87
95
|
*/
|
|
88
96
|
export declare class StillRenderer {
|
|
89
97
|
readonly canvas: HTMLCanvasElement;
|
|
90
98
|
private renderContext;
|
|
91
99
|
private storageAdapter;
|
|
92
100
|
private catalog;
|
|
93
|
-
private readonly
|
|
101
|
+
private readonly backend;
|
|
94
102
|
/** True when we made the canvas and must clean it up. */
|
|
95
103
|
private readonly ownsCanvas;
|
|
96
104
|
private viewportValue;
|
|
97
105
|
private readonly fpsValue;
|
|
98
|
-
private scaleValue;
|
|
106
|
+
private readonly scaleValue;
|
|
107
|
+
/** What the canvas is sized at now, which a call's own `scale` changes. */
|
|
108
|
+
private surfaceScale;
|
|
99
109
|
private manifest;
|
|
100
110
|
private theme?;
|
|
101
111
|
private readonly registry;
|
|
102
112
|
private disposed;
|
|
103
|
-
/**
|
|
104
|
-
|
|
113
|
+
/** False until a frame has been drawn onto the current surface; a remount clears it. */
|
|
114
|
+
private drawn;
|
|
115
|
+
/** @internal Use {@link createStillRenderer}, which loads the renderer first. */
|
|
116
|
+
constructor(backend: BrowserRenderer, options: StillRendererOptions);
|
|
105
117
|
get viewport(): Size2D;
|
|
106
118
|
get fps(): number;
|
|
107
119
|
get scale(): number;
|
|
@@ -132,20 +144,16 @@ export declare class StillRenderer {
|
|
|
132
144
|
toDataURL(options?: EncodeStillOptions): string;
|
|
133
145
|
dispose(): void;
|
|
134
146
|
/**
|
|
135
|
-
* Read the surface back into a 2D canvas, which is what actually encodes:
|
|
136
|
-
*
|
|
137
|
-
*
|
|
147
|
+
* Read the surface back into a 2D canvas, which is what actually encodes: the
|
|
148
|
+
* renderers ship no image encoders, so the browser's own codecs are the only
|
|
149
|
+
* ones available.
|
|
138
150
|
*/
|
|
139
151
|
private snapshotToCanvas;
|
|
140
152
|
private sizeCanvas;
|
|
141
|
-
/** Resize the backing store and
|
|
153
|
+
/** Resize the backing store and mount a fresh surface over it. */
|
|
142
154
|
private remount;
|
|
143
155
|
private assertLive;
|
|
144
156
|
}
|
|
145
|
-
/**
|
|
146
|
-
* Create a {@link StillRenderer}, loading CanvasKit if it isn't already.
|
|
147
|
-
*
|
|
148
|
-
* `getCanvasKit` memoizes, so a second renderer in the same page is cheap.
|
|
149
|
-
*/
|
|
157
|
+
/** Create a {@link StillRenderer}; rejects with `WebGpuUnavailableError` without WebGPU. */
|
|
150
158
|
export declare function createStillRenderer(options?: StillRendererOptions): Promise<StillRenderer>;
|
|
151
159
|
//# sourceMappingURL=still.d.ts.map
|
package/dist/still.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"still.d.ts","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"still.d.ts","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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"}
|
package/dist/still.js
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import { ManifestAssetCatalog, Registry, resolveProjectDocument,
|
|
2
|
-
import { drawFrameAt, } from "
|
|
3
|
-
import {
|
|
4
|
-
import { WebStorageAdapter } from "./storage-adapter";
|
|
5
|
-
import { getCanvasKit } from "./getter";
|
|
6
|
-
import { DEFAULT_OUTPUT } from "./output";
|
|
1
|
+
import { DEFAULT_OUTPUT, ManifestAssetCatalog, frameSpecAtTime, Registry, resolveProjectDocument, } from "@motionscript/core";
|
|
2
|
+
import { drawFrameAt, } from "./frame-loop";
|
|
3
|
+
import { loadRenderer } from "./renderer";
|
|
7
4
|
const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
|
|
8
5
|
/** Normalize the many ways a caller can name a frame into the engine's own form. */
|
|
9
|
-
export function
|
|
10
|
-
if (options?.time !== undefined)
|
|
11
|
-
return
|
|
12
|
-
}
|
|
6
|
+
export function stillFrameSpec(options, fps) {
|
|
7
|
+
if (options?.time !== undefined)
|
|
8
|
+
return frameSpecAtTime(options.time, fps);
|
|
13
9
|
const frame = options?.frame;
|
|
14
10
|
if (frame === undefined || frame === "first")
|
|
15
11
|
return { kind: "first" };
|
|
@@ -26,7 +22,7 @@ function mimeOf(format) {
|
|
|
26
22
|
* A long-lived single-frame renderer.
|
|
27
23
|
*
|
|
28
24
|
* Where `exportScreenshot` is one frame from a standing start, this keeps the
|
|
29
|
-
* expensive things — the
|
|
25
|
+
* expensive things — the renderer module, its GPU surface, and the
|
|
30
26
|
* storage adapter's decoded-asset cache — alive across calls, so re-rendering
|
|
31
27
|
* after an edit costs discovering one frame and a draw rather than a new GL
|
|
32
28
|
* context. That is
|
|
@@ -51,28 +47,38 @@ function mimeOf(format) {
|
|
|
51
47
|
* different themes still each draw correctly; but anything else that calls
|
|
52
48
|
* `setTheme` between a render and a read of the surface will have been
|
|
53
49
|
* overwritten by the next render.
|
|
50
|
+
*
|
|
51
|
+
* **One surface, one caller at a time.** A render and the encode of its result
|
|
52
|
+
* are two calls, and the next render draws over the surface between them, so
|
|
53
|
+
* callers that share a renderer without knowing of each other must be queued —
|
|
54
|
+
* as `session.image()` queues its calls.
|
|
54
55
|
*/
|
|
55
56
|
export class StillRenderer {
|
|
56
57
|
canvas;
|
|
57
58
|
renderContext;
|
|
58
59
|
storageAdapter;
|
|
59
60
|
catalog;
|
|
60
|
-
|
|
61
|
+
backend;
|
|
61
62
|
/** True when we made the canvas and must clean it up. */
|
|
62
63
|
ownsCanvas;
|
|
63
64
|
viewportValue;
|
|
64
65
|
fpsValue;
|
|
65
66
|
scaleValue;
|
|
67
|
+
/** What the canvas is sized at now, which a call's own `scale` changes. */
|
|
68
|
+
surfaceScale;
|
|
66
69
|
manifest;
|
|
67
70
|
theme;
|
|
68
71
|
registry;
|
|
69
72
|
disposed = false;
|
|
70
|
-
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
/** False until a frame has been drawn onto the current surface; a remount clears it. */
|
|
74
|
+
drawn = false;
|
|
75
|
+
/** @internal Use {@link createStillRenderer}, which loads the renderer first. */
|
|
76
|
+
constructor(backend, options) {
|
|
77
|
+
this.backend = backend;
|
|
73
78
|
this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;
|
|
74
79
|
this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;
|
|
75
80
|
this.scaleValue = options.scale ?? 1;
|
|
81
|
+
this.surfaceScale = this.scaleValue;
|
|
76
82
|
this.manifest = options.manifest ?? EMPTY_MANIFEST;
|
|
77
83
|
this.theme = options.theme;
|
|
78
84
|
this.registry = options.registry ?? new Registry();
|
|
@@ -84,8 +90,10 @@ export class StillRenderer {
|
|
|
84
90
|
}
|
|
85
91
|
this.sizeCanvas();
|
|
86
92
|
this.catalog = new ManifestAssetCatalog(this.manifest);
|
|
87
|
-
this.storageAdapter =
|
|
88
|
-
|
|
93
|
+
this.storageAdapter = this.backend.createStorageAdapter({
|
|
94
|
+
catalog: this.catalog, viewport: this.viewportValue, fps: this.fpsValue,
|
|
95
|
+
});
|
|
96
|
+
this.renderContext = this.backend.createRenderContext(this.storageAdapter);
|
|
89
97
|
this.renderContext.mount(this.canvas);
|
|
90
98
|
}
|
|
91
99
|
get viewport() { return this.viewportValue; }
|
|
@@ -98,24 +106,23 @@ export class StillRenderer {
|
|
|
98
106
|
async render(source, options) {
|
|
99
107
|
this.assertLive();
|
|
100
108
|
const scale = options?.scale ?? this.scaleValue;
|
|
101
|
-
if (scale !== this.
|
|
102
|
-
this.
|
|
103
|
-
|
|
104
|
-
}
|
|
109
|
+
if (scale !== this.surfaceScale)
|
|
110
|
+
this.remount(scale);
|
|
111
|
+
await this.renderContext.whenMounted?.();
|
|
105
112
|
const project = resolveProjectDocument(source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue });
|
|
106
|
-
|
|
107
|
-
// registry, so a second renderer (or the player) may have replaced it
|
|
108
|
-
// since. Cheap — a map swap.
|
|
109
|
-
setTheme(project.theme ?? this.theme);
|
|
110
|
-
return drawFrameAt({
|
|
113
|
+
const frame = await drawFrameAt({
|
|
111
114
|
project,
|
|
112
115
|
registry: this.registry,
|
|
116
|
+
theme: this.theme,
|
|
113
117
|
scale,
|
|
114
118
|
renderContext: this.renderContext,
|
|
115
119
|
storageAdapter: this.storageAdapter,
|
|
116
120
|
assetCatalog: this.catalog,
|
|
117
|
-
frame:
|
|
121
|
+
frame: stillFrameSpec(options, project.fps),
|
|
122
|
+
onIssue: options?.onIssue,
|
|
118
123
|
});
|
|
124
|
+
this.drawn = true;
|
|
125
|
+
return frame;
|
|
119
126
|
}
|
|
120
127
|
/** Theme for a document that carries none of its own. */
|
|
121
128
|
setTheme(theme) { this.theme = theme; }
|
|
@@ -144,9 +151,11 @@ export class StillRenderer {
|
|
|
144
151
|
// Disposing the context disposes the adapter it holds, so this releases
|
|
145
152
|
// the old surface, its handlers and every decode in one call.
|
|
146
153
|
this.renderContext.dispose();
|
|
147
|
-
this.storageAdapter =
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
this.storageAdapter = this.backend.createStorageAdapter({
|
|
155
|
+
catalog: this.catalog, viewport, fps: this.fpsValue,
|
|
156
|
+
});
|
|
157
|
+
this.renderContext = this.backend.createRenderContext(this.storageAdapter);
|
|
158
|
+
this.remount(this.scaleValue);
|
|
150
159
|
}
|
|
151
160
|
/** Encode the frame currently on the surface. */
|
|
152
161
|
async toBytes(options) {
|
|
@@ -177,31 +186,27 @@ export class StillRenderer {
|
|
|
177
186
|
this.canvas.remove();
|
|
178
187
|
}
|
|
179
188
|
/**
|
|
180
|
-
* Read the surface back into a 2D canvas, which is what actually encodes:
|
|
181
|
-
*
|
|
182
|
-
*
|
|
189
|
+
* Read the surface back into a 2D canvas, which is what actually encodes: the
|
|
190
|
+
* renderers ship no image encoders, so the browser's own codecs are the only
|
|
191
|
+
* ones available.
|
|
183
192
|
*/
|
|
184
193
|
snapshotToCanvas() {
|
|
185
194
|
this.assertLive();
|
|
186
|
-
|
|
187
|
-
if (!snapshot)
|
|
195
|
+
if (!this.drawn)
|
|
188
196
|
throw new Error("Nothing has been rendered yet.");
|
|
189
|
-
const canvas =
|
|
190
|
-
canvas
|
|
191
|
-
|
|
192
|
-
const ctx = canvas.getContext("2d");
|
|
193
|
-
if (!ctx)
|
|
194
|
-
throw new Error("Still encode: could not get a 2d context.");
|
|
195
|
-
// `pixels` is unpremultiplied RGBA8888, which is exactly ImageData's layout.
|
|
196
|
-
ctx.putImageData(new ImageData(new Uint8ClampedArray(snapshot.pixels), snapshot.width, snapshot.height), 0, 0);
|
|
197
|
+
const canvas = this.renderContext.snapshotCanvas?.();
|
|
198
|
+
if (!canvas)
|
|
199
|
+
throw new Error("Still encode: could not read the surface into a 2d canvas.");
|
|
197
200
|
return canvas;
|
|
198
201
|
}
|
|
199
202
|
sizeCanvas() {
|
|
200
|
-
this.canvas.width = this.viewportValue.width * this.
|
|
201
|
-
this.canvas.height = this.viewportValue.height * this.
|
|
203
|
+
this.canvas.width = this.viewportValue.width * this.surfaceScale;
|
|
204
|
+
this.canvas.height = this.viewportValue.height * this.surfaceScale;
|
|
202
205
|
}
|
|
203
|
-
/** Resize the backing store and
|
|
204
|
-
remount() {
|
|
206
|
+
/** Resize the backing store and mount a fresh surface over it. */
|
|
207
|
+
remount(scale) {
|
|
208
|
+
this.surfaceScale = scale;
|
|
209
|
+
this.drawn = false;
|
|
205
210
|
this.sizeCanvas();
|
|
206
211
|
this.renderContext.mount(this.canvas);
|
|
207
212
|
}
|
|
@@ -210,13 +215,8 @@ export class StillRenderer {
|
|
|
210
215
|
throw new Error("This StillRenderer has been disposed.");
|
|
211
216
|
}
|
|
212
217
|
}
|
|
213
|
-
/**
|
|
214
|
-
* Create a {@link StillRenderer}, loading CanvasKit if it isn't already.
|
|
215
|
-
*
|
|
216
|
-
* `getCanvasKit` memoizes, so a second renderer in the same page is cheap.
|
|
217
|
-
*/
|
|
218
|
+
/** Create a {@link StillRenderer}; rejects with `WebGpuUnavailableError` without WebGPU. */
|
|
218
219
|
export async function createStillRenderer(options = {}) {
|
|
219
|
-
|
|
220
|
-
return new StillRenderer(canvasKit, options);
|
|
220
|
+
return new StillRenderer(await loadRenderer(options), options);
|
|
221
221
|
}
|
|
222
222
|
//# sourceMappingURL=still.js.map
|
package/dist/still.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"still.js","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AssetCatalog, ManifestAssetCatalog,\n Registry,\n resolveProjectDocument,\n setTheme,\n type AssetManifest,\n type ProjectDocument,\n type SceneDocument,\n type Size2D,\n type Theme,\n} from \"@motionscript/core\";\nimport {\n drawFrameAt,\n type DrawnFrame,\n type FrameSpec,\n type ScreenshotFormat,\n} from \"@motionscript/skia-render/export\";\nimport { WebRenderContext } from \"./render-context\";\nimport { WebStorageAdapter } from \"./storage-adapter\";\nimport { getCanvasKit } from \"./getter\";\nimport { DEFAULT_OUTPUT } from \"./output\";\n\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\n\n/** Anything a still can be rendered from: a project, or a lone still or composition as the one track of one. */\nexport type StillSource = ProjectDocument | SceneDocument;\n\n/**\n * Which frame to draw, in whatever form is most convenient — a global frame\n * index, the timeline's first or last frame, or a `FrameSpec`.\n */\nexport type FrameRef = FrameSpec | number | \"first\" | \"last\";\n\nexport interface StillRendererOptions {\n /**\n * The canvas to draw into. Omit and the renderer creates its own hidden one\n * and removes it on {@link StillRenderer.dispose} — which is what a one-shot\n * capture wants, while a live preview passes the canvas it already shows.\n */\n canvas?: HTMLCanvasElement;\n /** Output resolution in pixels, whatever the document asks for. Defaults to 1920×1080. */\n viewport?: Size2D;\n /** Frame rate for a document that sets none. Defaults to 60. */\n fps?: number;\n /** Device pixel ratio the surface is sized at. Defaults to 1. */\n scale?: number;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n wasmUrl?: string;\n /**\n * The vocabulary a document is compiled against.\n *\n * Defaults to a fresh one carrying only what the library ships — a renderer\n * owns its registry the way an engine does, so a host with custom node types\n * passes the same one it gave its engine rather than relying on whatever a\n * module happened to register.\n */\n registry?: Registry;\n}\n\nexport interface RenderStillOptions {\n /** Which frame to draw. Defaults to the timeline's first. */\n frame?: FrameRef;\n /** Seconds into the timeline, converted with the document's fps. Wins over `frame`. */\n time?: number;\n /**\n * Draw at this pixel ratio instead of the renderer's. **Resizes the canvas and\n * rebuilds the Skia surface**, so keep it off the per-edit path — render the\n * preview at its normal scale and pass this only when capturing at a higher one.\n */\n scale?: number;\n}\n\nexport interface EncodeStillOptions {\n /** Defaults to `\"png\"`. */\n format?: ScreenshotFormat;\n /** JPEG quality in [0,1]; ignored for png. Defaults to 0.92. */\n quality?: number;\n}\n\n/** Normalize the many ways a caller can name a frame into the engine's own form. */\nexport function toFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec {\n if (options?.time !== undefined) {\n return { kind: \"frame\", frame: Math.round(options.time * fps) };\n }\n const frame = options?.frame;\n if (frame === undefined || frame === \"first\") return { kind: \"first\" };\n if (frame === \"last\") return { kind: \"last\" };\n if (typeof frame === \"number\") return { kind: \"frame\", frame };\n return frame;\n}\n\nfunction mimeOf(format: ScreenshotFormat): string {\n return format === \"jpeg\" ? \"image/jpeg\" : \"image/png\";\n}\n\n/**\n * A long-lived single-frame renderer.\n *\n * Where `exportScreenshot` is one frame from a standing start, this keeps the\n * expensive things — the CanvasKit module, the WebGL Skia surface, and the\n * storage adapter's decoded-asset cache — alive across calls, so re-rendering\n * after an edit costs discovering one frame and a draw rather than a new GL\n * context. That is\n * the difference between a one-off capture and a builder that repaints while the\n * user types.\n *\n * It is also the *same* draw: {@link render} goes through `drawFrameAt`, which is\n * the pass the video exporter runs per frame. A preview and its exported image\n * cannot drift apart, because there is only one path.\n *\n * ```ts\n * const renderer = await createStillRenderer({ canvas, viewport, manifest, theme });\n * await renderer.render({ kind: \"still\", nodes: [\n * { id: \"bg\", type: \"rect\", parentId: null, layerOrder: 0, props: { width: 1920, height: 1080, fill: \"bg\" } },\n * ] });\n * const blob = await renderer.toBlob({ format: \"png\" });\n * renderer.dispose();\n * ```\n *\n * **The theme is process-global in core.** {@link render} re-applies the\n * document's (or this renderer's) before every draw, so two renderers with\n * different themes still each draw correctly; but anything else that calls\n * `setTheme` between a render and a read of the surface will have been\n * overwritten by the next render.\n */\nexport class StillRenderer {\n readonly canvas: HTMLCanvasElement;\n\n private renderContext: WebRenderContext;\n private storageAdapter: WebStorageAdapter;\n private catalog: AssetCatalog;\n\n private readonly canvasKit: Awaited<ReturnType<typeof getCanvasKit>>;\n /** True when we made the canvas and must clean it up. */\n private readonly ownsCanvas: boolean;\n\n private viewportValue: Size2D;\n private readonly fpsValue: number;\n private scaleValue: number;\n private manifest: AssetManifest;\n private theme?: Theme;\n private readonly registry: Registry;\n private disposed = false;\n\n /** @internal Use {@link createStillRenderer}, which loads CanvasKit first. */\n constructor(\n canvasKit: Awaited<ReturnType<typeof getCanvasKit>>,\n options: StillRendererOptions,\n ) {\n this.canvasKit = canvasKit;\n this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;\n this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;\n this.scaleValue = options.scale ?? 1;\n this.manifest = options.manifest ?? EMPTY_MANIFEST;\n this.theme = options.theme;\n this.registry = options.registry ?? new Registry();\n\n this.ownsCanvas = options.canvas === undefined;\n this.canvas = options.canvas ?? document.createElement(\"canvas\");\n if (this.ownsCanvas) {\n this.canvas.style.display = \"none\";\n document.body.appendChild(this.canvas);\n }\n this.sizeCanvas();\n\n this.catalog = new ManifestAssetCatalog(this.manifest);\n this.storageAdapter = new WebStorageAdapter(\n canvasKit, this.catalog, this.viewportValue, this.fpsValue,\n );\n this.renderContext = new WebRenderContext(canvasKit, this.storageAdapter);\n this.renderContext.mount(this.canvas);\n }\n\n get viewport(): Size2D { return this.viewportValue; }\n get fps(): number { return this.fpsValue; }\n get scale(): number { return this.scaleValue; }\n\n /**\n * Draw one frame of `source`, at this renderer's viewport — the surface is\n * the output, so it wins over any size the document asks for.\n */\n async render(source: StillSource, options?: RenderStillOptions): Promise<DrawnFrame> {\n this.assertLive();\n const scale = options?.scale ?? this.scaleValue;\n if (scale !== this.scaleValue) {\n this.scaleValue = scale;\n this.remount();\n }\n\n const project = resolveProjectDocument(\n source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue },\n );\n // Re-applied per render, not once at construction: it is a global\n // registry, so a second renderer (or the player) may have replaced it\n // since. Cheap — a map swap.\n setTheme(project.theme ?? this.theme);\n\n return drawFrameAt({\n project,\n registry: this.registry,\n scale,\n renderContext: this.renderContext,\n storageAdapter: this.storageAdapter,\n assetCatalog: this.catalog,\n frame: toFrameSpec(options, project.fps!),\n });\n }\n\n /** Theme for a document that carries none of its own. */\n setTheme(theme?: Theme): void { this.theme = theme; }\n\n /** Swap the asset manifest, keeping every decode already cached. */\n setManifest(manifest: AssetManifest): void {\n this.assertLive();\n this.manifest = manifest;\n this.catalog = new ManifestAssetCatalog(manifest);\n this.storageAdapter.setCatalog(this.catalog);\n }\n\n /**\n * Change the output resolution.\n *\n * A full reset, unlike {@link setManifest}: the viewport clamps the resolution\n * images are decoded *at*, so cached decodes made for the old one would be\n * soft at the new one, and the render context's handlers captured the adapter\n * when they were built — so both are rebuilt. Call this when the output format\n * changes, not per edit.\n */\n setViewport(viewport: Size2D): void {\n this.assertLive();\n if (viewport.width === this.viewportValue.width && viewport.height === this.viewportValue.height) {\n return;\n }\n this.viewportValue = viewport;\n // Disposing the context disposes the adapter it holds, so this releases\n // the old surface, its handlers and every decode in one call.\n this.renderContext.dispose();\n this.storageAdapter = new WebStorageAdapter(\n this.canvasKit, this.catalog, viewport, this.fpsValue,\n );\n this.renderContext = new WebRenderContext(this.canvasKit, this.storageAdapter);\n this.remount();\n }\n\n /** Encode the frame currently on the surface. */\n async toBytes(options?: EncodeStillOptions): Promise<Uint8Array> {\n const blob = await this.toBlob(options);\n return new Uint8Array(await blob.arrayBuffer());\n }\n\n /** Encode the frame currently on the surface. */\n toBlob(options?: EncodeStillOptions): Promise<Blob> {\n const canvas = this.snapshotToCanvas();\n const { format = \"png\", quality = 0.92 } = options ?? {};\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) => blob\n ? resolve(blob)\n : reject(new Error(\"Still encode produced no data.\")),\n mimeOf(format),\n quality,\n );\n });\n }\n\n /** Encode the frame currently on the surface. */\n toDataURL(options?: EncodeStillOptions): string {\n const { format = \"png\", quality = 0.92 } = options ?? {};\n return this.snapshotToCanvas().toDataURL(mimeOf(format), quality);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.renderContext.dispose();\n if (this.ownsCanvas) this.canvas.remove();\n }\n\n /**\n * Read the surface back into a 2D canvas, which is what actually encodes: this\n * CanvasKit build ships no wasm image encoders, so the browser's own codecs are\n * the only ones available.\n */\n private snapshotToCanvas(): HTMLCanvasElement {\n this.assertLive();\n const snapshot = this.renderContext.snapshotPixels();\n if (!snapshot) throw new Error(\"Nothing has been rendered yet.\");\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = snapshot.width;\n canvas.height = snapshot.height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) throw new Error(\"Still encode: could not get a 2d context.\");\n // `pixels` is unpremultiplied RGBA8888, which is exactly ImageData's layout.\n ctx.putImageData(\n new ImageData(new Uint8ClampedArray(snapshot.pixels), snapshot.width, snapshot.height),\n 0, 0,\n );\n return canvas;\n }\n\n private sizeCanvas(): void {\n this.canvas.width = this.viewportValue.width * this.scaleValue;\n this.canvas.height = this.viewportValue.height * this.scaleValue;\n }\n\n /** Resize the backing store and build a fresh Skia surface over it. */\n private remount(): void {\n this.sizeCanvas();\n this.renderContext.mount(this.canvas);\n }\n\n private assertLive(): void {\n if (this.disposed) throw new Error(\"This StillRenderer has been disposed.\");\n }\n}\n\n/**\n * Create a {@link StillRenderer}, loading CanvasKit if it isn't already.\n *\n * `getCanvasKit` memoizes, so a second renderer in the same page is cheap.\n */\nexport async function createStillRenderer(\n options: StillRendererOptions = {},\n): Promise<StillRenderer> {\n const canvasKit = await getCanvasKit(options.wasmUrl);\n return new StillRenderer(canvasKit, options);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"still.js","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AssetCatalog, DEFAULT_OUTPUT, ManifestAssetCatalog,\n frameSpecAtTime,\n Registry,\n resolveProjectDocument,\n type AssetManifest,\n type FrameSpec,\n type RenderIssue,\n type ProjectDocument,\n type VisualDocument,\n type Size2D,\n type StorageAdapter,\n type Theme,\n} from \"@motionscript/core\";\nimport {\n drawFrameAt,\n type DrawnFrame,\n type ScreenshotFormat,\n} from \"./frame-loop\";\nimport type { BrowserRenderContext, BrowserRenderer } from \"./engine\";\nimport { loadRenderer, type RendererWasmOption } from \"./renderer\";\n\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\n\n/** Anything a still can be rendered from: a project, or a lone still or composition as the one track of one. */\nexport type StillSource = ProjectDocument | VisualDocument;\n\n/**\n * Which frame to draw, in whatever form is most convenient — a global frame\n * index, the timeline's first or last frame, or a `FrameSpec`.\n */\nexport type FrameRef = FrameSpec | number | \"first\" | \"last\";\n\nexport interface StillRendererOptions extends RendererWasmOption {\n /**\n * The canvas to draw into. Omit and the renderer creates its own hidden one\n * and removes it on {@link StillRenderer.dispose} — which is what a one-shot\n * capture wants, while a live preview passes the canvas it already shows.\n */\n canvas?: HTMLCanvasElement;\n /** Output resolution in pixels, whatever the document asks for. Defaults to 1920×1080. */\n viewport?: Size2D;\n /** Frame rate for a document that sets none. Defaults to 60. */\n fps?: number;\n /** Device pixel ratio the surface is sized at. Defaults to 1. */\n scale?: number;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n /**\n * The vocabulary a document is compiled against.\n *\n * Defaults to a fresh one carrying only what the library ships — a renderer\n * owns its registry the way an engine does, so a host with custom node types\n * passes the same one it gave its engine rather than relying on whatever a\n * module happened to register.\n */\n registry?: Registry;\n}\n\nexport interface RenderStillOptions {\n /** Which frame to draw. Defaults to the timeline's first. */\n frame?: FrameRef;\n /** Seconds into the timeline, converted with the document's fps. Wins over `frame`. */\n time?: number;\n /**\n * Draw this call at this pixel ratio instead of the renderer's. **Resizes the\n * canvas and rebuilds the surface**, and the next call without it rebuilds\n * them back, so keep it off the per-edit path — render the preview at its\n * normal scale and pass this only when capturing at a higher one.\n */\n scale?: number;\n /** What the renderer drew but not as the document says; a lost GPU device throws `RenderBackendError` instead. */\n onIssue?: (issue: RenderIssue) => void;\n}\n\nexport interface EncodeStillOptions {\n /** Defaults to `\"png\"`. */\n format?: ScreenshotFormat;\n /** JPEG quality in [0,1]; ignored for png. Defaults to 0.92. */\n quality?: number;\n}\n\n/** Normalize the many ways a caller can name a frame into the engine's own form. */\nexport function stillFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec {\n if (options?.time !== undefined) return frameSpecAtTime(options.time, fps);\n const frame = options?.frame;\n if (frame === undefined || frame === \"first\") return { kind: \"first\" };\n if (frame === \"last\") return { kind: \"last\" };\n if (typeof frame === \"number\") return { kind: \"frame\", frame };\n return frame;\n}\n\nfunction mimeOf(format: ScreenshotFormat): string {\n return format === \"jpeg\" ? \"image/jpeg\" : \"image/png\";\n}\n\n/**\n * A long-lived single-frame renderer.\n *\n * Where `exportScreenshot` is one frame from a standing start, this keeps the\n * expensive things — the renderer module, its GPU surface, and the\n * storage adapter's decoded-asset cache — alive across calls, so re-rendering\n * after an edit costs discovering one frame and a draw rather than a new GL\n * context. That is\n * the difference between a one-off capture and a builder that repaints while the\n * user types.\n *\n * It is also the *same* draw: {@link render} goes through `drawFrameAt`, which is\n * the pass the video exporter runs per frame. A preview and its exported image\n * cannot drift apart, because there is only one path.\n *\n * ```ts\n * const renderer = await createStillRenderer({ canvas, viewport, manifest, theme });\n * await renderer.render({ kind: \"still\", nodes: [\n * { id: \"bg\", type: \"rect\", parentId: null, layerOrder: 0, props: { width: 1920, height: 1080, fill: \"bg\" } },\n * ] });\n * const blob = await renderer.toBlob({ format: \"png\" });\n * renderer.dispose();\n * ```\n *\n * **The theme is process-global in core.** {@link render} re-applies the\n * document's (or this renderer's) before every draw, so two renderers with\n * different themes still each draw correctly; but anything else that calls\n * `setTheme` between a render and a read of the surface will have been\n * overwritten by the next render.\n *\n * **One surface, one caller at a time.** A render and the encode of its result\n * are two calls, and the next render draws over the surface between them, so\n * callers that share a renderer without knowing of each other must be queued —\n * as `session.image()` queues its calls.\n */\nexport class StillRenderer {\n readonly canvas: HTMLCanvasElement;\n\n private renderContext: BrowserRenderContext;\n private storageAdapter: StorageAdapter;\n private catalog: AssetCatalog;\n\n private readonly backend: BrowserRenderer;\n /** True when we made the canvas and must clean it up. */\n private readonly ownsCanvas: boolean;\n\n private viewportValue: Size2D;\n private readonly fpsValue: number;\n private readonly scaleValue: number;\n /** What the canvas is sized at now, which a call's own `scale` changes. */\n private surfaceScale: number;\n private manifest: AssetManifest;\n private theme?: Theme;\n private readonly registry: Registry;\n private disposed = false;\n /** False until a frame has been drawn onto the current surface; a remount clears it. */\n private drawn = false;\n\n /** @internal Use {@link createStillRenderer}, which loads the renderer first. */\n constructor(\n backend: BrowserRenderer,\n options: StillRendererOptions,\n ) {\n this.backend = backend;\n this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;\n this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;\n this.scaleValue = options.scale ?? 1;\n this.surfaceScale = this.scaleValue;\n this.manifest = options.manifest ?? EMPTY_MANIFEST;\n this.theme = options.theme;\n this.registry = options.registry ?? new Registry();\n\n this.ownsCanvas = options.canvas === undefined;\n this.canvas = options.canvas ?? document.createElement(\"canvas\");\n if (this.ownsCanvas) {\n this.canvas.style.display = \"none\";\n document.body.appendChild(this.canvas);\n }\n this.sizeCanvas();\n\n this.catalog = new ManifestAssetCatalog(this.manifest);\n this.storageAdapter = this.backend.createStorageAdapter({\n catalog: this.catalog, viewport: this.viewportValue, fps: this.fpsValue,\n });\n this.renderContext = this.backend.createRenderContext(this.storageAdapter);\n this.renderContext.mount(this.canvas);\n }\n\n get viewport(): Size2D { return this.viewportValue; }\n get fps(): number { return this.fpsValue; }\n get scale(): number { return this.scaleValue; }\n\n /**\n * Draw one frame of `source`, at this renderer's viewport — the surface is\n * the output, so it wins over any size the document asks for.\n */\n async render(source: StillSource, options?: RenderStillOptions): Promise<DrawnFrame> {\n this.assertLive();\n const scale = options?.scale ?? this.scaleValue;\n if (scale !== this.surfaceScale) this.remount(scale);\n await this.renderContext.whenMounted?.();\n\n const project = resolveProjectDocument(\n source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue },\n );\n\n const frame = await drawFrameAt({\n project,\n registry: this.registry,\n theme: this.theme,\n scale,\n renderContext: this.renderContext,\n storageAdapter: this.storageAdapter,\n assetCatalog: this.catalog,\n frame: stillFrameSpec(options, project.fps!),\n onIssue: options?.onIssue,\n });\n this.drawn = true;\n return frame;\n }\n\n /** Theme for a document that carries none of its own. */\n setTheme(theme?: Theme): void { this.theme = theme; }\n\n /** Swap the asset manifest, keeping every decode already cached. */\n setManifest(manifest: AssetManifest): void {\n this.assertLive();\n this.manifest = manifest;\n this.catalog = new ManifestAssetCatalog(manifest);\n this.storageAdapter.setCatalog(this.catalog);\n }\n\n /**\n * Change the output resolution.\n *\n * A full reset, unlike {@link setManifest}: the viewport clamps the resolution\n * images are decoded *at*, so cached decodes made for the old one would be\n * soft at the new one, and the render context's handlers captured the adapter\n * when they were built — so both are rebuilt. Call this when the output format\n * changes, not per edit.\n */\n setViewport(viewport: Size2D): void {\n this.assertLive();\n if (viewport.width === this.viewportValue.width && viewport.height === this.viewportValue.height) {\n return;\n }\n this.viewportValue = viewport;\n // Disposing the context disposes the adapter it holds, so this releases\n // the old surface, its handlers and every decode in one call.\n this.renderContext.dispose();\n this.storageAdapter = this.backend.createStorageAdapter({\n catalog: this.catalog, viewport, fps: this.fpsValue,\n });\n this.renderContext = this.backend.createRenderContext(this.storageAdapter);\n this.remount(this.scaleValue);\n }\n\n /** Encode the frame currently on the surface. */\n async toBytes(options?: EncodeStillOptions): Promise<Uint8Array> {\n const blob = await this.toBlob(options);\n return new Uint8Array(await blob.arrayBuffer());\n }\n\n /** Encode the frame currently on the surface. */\n toBlob(options?: EncodeStillOptions): Promise<Blob> {\n const canvas = this.snapshotToCanvas();\n const { format = \"png\", quality = 0.92 } = options ?? {};\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) => blob\n ? resolve(blob)\n : reject(new Error(\"Still encode produced no data.\")),\n mimeOf(format),\n quality,\n );\n });\n }\n\n /** Encode the frame currently on the surface. */\n toDataURL(options?: EncodeStillOptions): string {\n const { format = \"png\", quality = 0.92 } = options ?? {};\n return this.snapshotToCanvas().toDataURL(mimeOf(format), quality);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.renderContext.dispose();\n if (this.ownsCanvas) this.canvas.remove();\n }\n\n /**\n * Read the surface back into a 2D canvas, which is what actually encodes: the\n * renderers ship no image encoders, so the browser's own codecs are the only\n * ones available.\n */\n private snapshotToCanvas(): HTMLCanvasElement {\n this.assertLive();\n if (!this.drawn) throw new Error(\"Nothing has been rendered yet.\");\n const canvas = this.renderContext.snapshotCanvas?.();\n if (!canvas) throw new Error(\"Still encode: could not read the surface into a 2d canvas.\");\n return canvas;\n }\n\n private sizeCanvas(): void {\n this.canvas.width = this.viewportValue.width * this.surfaceScale;\n this.canvas.height = this.viewportValue.height * this.surfaceScale;\n }\n\n /** Resize the backing store and mount a fresh surface over it. */\n private remount(scale: number): void {\n this.surfaceScale = scale;\n this.drawn = false;\n this.sizeCanvas();\n this.renderContext.mount(this.canvas);\n }\n\n private assertLive(): void {\n if (this.disposed) throw new Error(\"This StillRenderer has been disposed.\");\n }\n}\n\n/** Create a {@link StillRenderer}; rejects with `WebGpuUnavailableError` without WebGPU. */\nexport async function createStillRenderer(\n options: StillRendererOptions = {},\n): Promise<StillRenderer> {\n return new StillRenderer(await loadRenderer(options), options);\n}\n"]}
|