@motionscript/browser 0.1.0-alpha.3 → 0.1.0-alpha.6
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 +108 -0
- package/LICENSE +201 -201
- package/NOTICE +48 -0
- package/README.md +118 -69
- package/dist/asset-bytes.js.map +1 -1
- package/dist/audio/bus-graph.d.ts +2 -2
- package/dist/audio/bus-graph.d.ts.map +1 -1
- package/dist/audio/bus-graph.js +6 -6
- package/dist/audio/bus-graph.js.map +1 -1
- package/dist/audio/filter-graph.d.ts +5 -5
- package/dist/audio/filter-graph.d.ts.map +1 -1
- package/dist/audio/filter-graph.js +15 -15
- package/dist/audio/filter-graph.js.map +1 -1
- 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.map +1 -1
- package/dist/audio/mixer.js +26 -10
- package/dist/audio/mixer.js.map +1 -1
- package/dist/audio/player.d.ts +4 -4
- package/dist/audio/player.d.ts.map +1 -1
- package/dist/audio/player.js +15 -14
- package/dist/audio/player.js.map +1 -1
- package/dist/browser/THIRD_PARTY_LICENSES.txt +432 -0
- package/dist/browser/audio.js +1 -1
- package/dist/browser/chunks/chunk-2MAFJWBZ.js +2 -0
- package/dist/browser/chunks/chunk-2MAFJWBZ.js.map +7 -0
- package/dist/browser/index.js +130 -276
- package/dist/browser/index.js.map +4 -4
- package/dist/browser/manifest.json +19 -2
- package/dist/concat.js.map +1 -1
- package/dist/decode/image-levels.js.map +1 -1
- package/dist/decode/levels.js.map +1 -1
- package/dist/decode/video-levels.js.map +1 -1
- package/dist/dynamic-nodes.d.ts.map +1 -1
- package/dist/dynamic-nodes.js +6 -12
- package/dist/dynamic-nodes.js.map +1 -1
- package/dist/engine.d.ts +6 -6
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1 -0
- package/dist/engine.js.map +1 -1
- package/dist/exporter.d.ts +8 -2
- package/dist/exporter.d.ts.map +1 -1
- package/dist/exporter.js +8 -7
- package/dist/exporter.js.map +1 -1
- package/dist/getter.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/measure-context.js.map +1 -1
- package/dist/mix-timeline-audio.d.ts +2 -2
- package/dist/mix-timeline-audio.d.ts.map +1 -1
- package/dist/mix-timeline-audio.js +1 -2
- package/dist/mix-timeline-audio.js.map +1 -1
- package/dist/render-context.js.map +1 -1
- package/dist/screenshot.d.ts +3 -3
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +1 -2
- package/dist/screenshot.js.map +1 -1
- package/dist/still.d.ts +6 -4
- package/dist/still.d.ts.map +1 -1
- package/dist/still.js +12 -9
- package/dist/still.js.map +1 -1
- package/dist/storage-adapter.d.ts.map +1 -1
- package/dist/storage-adapter.js +5 -4
- package/dist/storage-adapter.js.map +1 -1
- package/dist/three/renderer.js.map +1 -1
- package/dist/ticker.d.ts +22 -4
- package/dist/ticker.d.ts.map +1 -1
- package/dist/ticker.js +65 -28
- package/dist/ticker.js.map +1 -1
- package/package.json +7 -6
- package/dist/browser/chunks/chunk-M7XD73ED.js +0 -2
- package/dist/browser/chunks/chunk-M7XD73ED.js.map +0 -7
- 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/exporter.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Registry, type AssetManifest, type ProjectDocument, type
|
|
1
|
+
import { Registry, type AssetManifest, type FrameRange, type ProjectDocument, type VisualDocument, type Size2D, type Theme } from "@motionscript/core";
|
|
2
2
|
import { type Quality } from 'mediabunny';
|
|
3
3
|
/** Reports export progress in [0, 1]; video encoding occupies most of the range, audio mixing/finalize the tail. */
|
|
4
4
|
export type ExportProgressCallback = (progress: number) => void;
|
|
@@ -95,7 +95,7 @@ export type AudioEncoderOptions = {
|
|
|
95
95
|
};
|
|
96
96
|
export type ExportParams = {
|
|
97
97
|
/** A project, or a lone still or composition as the one track of one. */
|
|
98
|
-
document: ProjectDocument |
|
|
98
|
+
document: ProjectDocument | VisualDocument;
|
|
99
99
|
/** The vocabulary its clips compile against. Defaults to what the library ships. */
|
|
100
100
|
registry?: Registry;
|
|
101
101
|
/** Win over the document's own; 1920×1080 at 60 fps when neither says. */
|
|
@@ -115,6 +115,12 @@ export type ExportParams = {
|
|
|
115
115
|
video?: VideoEncoderOptions;
|
|
116
116
|
/** Audio encoder settings; ignored when `includeAudio` is false. */
|
|
117
117
|
audio?: AudioEncoderOptions;
|
|
118
|
+
/**
|
|
119
|
+
* Encode only these frames (`end` excluded), timestamped from 0 and silent:
|
|
120
|
+
* one segment of a split export, for `concatVideoSegments` to join. Mix
|
|
121
|
+
* the audio once with `mixTimelineAudio` instead.
|
|
122
|
+
*/
|
|
123
|
+
frames?: FrameRange;
|
|
118
124
|
onProgress?: ExportProgressCallback;
|
|
119
125
|
signal?: AbortSignal;
|
|
120
126
|
wasmUrl?: string;
|
package/dist/exporter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exporter.d.ts","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"exporter.d.ts","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"AAAA,OAAO,EACmC,QAAQ,EAA0B,KAAK,aAAa,EAC1F,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,KAAK,KAAK,EACtF,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAOH,KAAK,OAAO,EAEf,MAAM,YAAY,CAAC;AAMpB,oHAAoH;AACpH,MAAM,MAAM,sBAAsB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAC9B;;;;;;;;;;OAUG;IACH,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;IACvC;;;;;;;;;;;;;;;OAeG;IACH,oBAAoB,CAAC,EAAE,eAAe,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;IAC/E;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,6FAA6F;IAC7F,WAAW,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACtC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACrC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAsBF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGxF;AAED,4CAA4C;AAC5C,MAAM,MAAM,mBAAmB,GAAG;IAC9B,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAQF,MAAM,MAAM,YAAY,GAAG;IACvB,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,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gFAAgF;IAChF,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,oEAAoE;IACpE,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,UAAU,CAAC,EAAE,sBAAsB,CAAC;IACpC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB,CAAA;AAgKD;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAuFlF"}
|
package/dist/exporter.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { ManifestAssetCatalog, Registry, resolveProjectDocument, } from "@motionscript/core";
|
|
1
|
+
import { DEFAULT_OUTPUT, ManifestAssetCatalog, Registry, resolveProjectDocument, } from "@motionscript/core";
|
|
2
2
|
import { renderTimeline, } from "@motionscript/skia-render/export";
|
|
3
3
|
import { AudioBufferSource, BufferTarget, CanvasSource, canEncodeVideo, Mp4OutputFormat, Output, } from 'mediabunny';
|
|
4
4
|
import { WebRenderContext } from "./render-context";
|
|
5
5
|
import { WebStorageAdapter } from "./storage-adapter";
|
|
6
6
|
import { getCanvasKit } from "./getter";
|
|
7
7
|
import { WebAudioMixer } from "./audio/mixer";
|
|
8
|
-
import { DEFAULT_OUTPUT } from "./output";
|
|
9
8
|
/**
|
|
10
9
|
* Bits per pixel per frame the default bitrate is built from.
|
|
11
10
|
*
|
|
@@ -181,7 +180,7 @@ async function resolveCodec(requested, size, fps) {
|
|
|
181
180
|
catch {
|
|
182
181
|
// A probe that throws is a probe that answered "no".
|
|
183
182
|
}
|
|
184
|
-
console.warn(`[
|
|
183
|
+
console.warn(`[motionscript] No ${requested} encoder available; exporting as H.264 instead.`);
|
|
185
184
|
return 'avc';
|
|
186
185
|
}
|
|
187
186
|
/**
|
|
@@ -197,7 +196,7 @@ async function resolveCodec(requested, size, fps) {
|
|
|
197
196
|
export async function exportVideo(params) {
|
|
198
197
|
const {
|
|
199
198
|
// Not `document`, which would shadow the DOM global this function uses.
|
|
200
|
-
document: source, registry = new Registry(), scale = 1, filename = 'export.mp4', manifest = EMPTY_MANIFEST, theme, includeAudio = true, video, audio = {}, onProgress, signal, wasmUrl, supersample = DEFAULT_SUPERSAMPLE, returnBytes = false, } = params;
|
|
199
|
+
document: source, registry = new Registry(), scale = 1, filename = 'export.mp4', manifest = EMPTY_MANIFEST, theme, includeAudio = true, video, audio = {}, frames, onProgress, signal, wasmUrl, supersample = DEFAULT_SUPERSAMPLE, returnBytes = false, } = params;
|
|
201
200
|
const project = resolveProjectDocument(source, params, DEFAULT_OUTPUT);
|
|
202
201
|
if (project.tracks.length === 0)
|
|
203
202
|
return;
|
|
@@ -216,8 +215,10 @@ export async function exportVideo(params) {
|
|
|
216
215
|
const oversample = fittedSupersample(supersample, outputSize);
|
|
217
216
|
const renderScale = scale * oversample;
|
|
218
217
|
const offscreenCanvas = document.createElement('canvas');
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
// A whole multiple of the output, so the downsample is exact: the picture is drawn at
|
|
219
|
+
// `renderScale` from the corner, as it is at 1×, and any odd-size padding is a strip.
|
|
220
|
+
offscreenCanvas.width = outputSize.width * oversample;
|
|
221
|
+
offscreenCanvas.height = outputSize.height * oversample;
|
|
221
222
|
offscreenCanvas.style.display = 'none';
|
|
222
223
|
document.body.appendChild(offscreenCanvas);
|
|
223
224
|
const canvasKit = await getCanvasKit(wasmUrl);
|
|
@@ -229,7 +230,7 @@ export async function exportVideo(params) {
|
|
|
229
230
|
let bytes;
|
|
230
231
|
try {
|
|
231
232
|
bytes = await renderTimeline({
|
|
232
|
-
project, registry, theme, scale: renderScale, includeAudio,
|
|
233
|
+
project, registry, theme, scale: renderScale, includeAudio, frames,
|
|
233
234
|
renderContext, storageAdapter, assetCatalog,
|
|
234
235
|
sink: new MediabunnyVideoSink(offscreenCanvas, fps, outputSize, video, audio, codec),
|
|
235
236
|
mixer: new WebAudioMixer(audio.sampleRate, assetCatalog),
|
package/dist/exporter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exporter.js","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ManifestAssetCatalog, Registry, resolveProjectDocument, type AssetManifest,\n type ProjectDocument, type SceneDocument, type Size2D, type Theme,\n} from \"@motionscript/core\";\nimport {\n renderTimeline,\n type VideoFrameSink,\n} from \"@motionscript/skia-render/export\";\nimport {\n AudioBufferSource,\n BufferTarget,\n CanvasSource,\n canEncodeVideo,\n Mp4OutputFormat,\n Output,\n type Quality,\n type VideoCodec,\n} from 'mediabunny';\nimport { WebRenderContext } from \"./render-context\";\nimport { WebStorageAdapter } from \"./storage-adapter\";\nimport { getCanvasKit } from \"./getter\";\nimport { WebAudioMixer } from \"./audio/mixer\";\nimport { DEFAULT_OUTPUT } from \"./output\";\n\n/** Reports export progress in [0, 1]; video encoding occupies most of the range, audio mixing/finalize the tail. */\nexport type ExportProgressCallback = (progress: number) => void;\n\n/**\n * How the video track is encoded.\n *\n * Every field is a straight pass-through to mediabunny's `VideoEncodingConfig`,\n * defaulted here rather than there so an export has one obvious place to look.\n */\nexport type VideoEncoderOptions = {\n /**\n * Which codec the video track is encoded with. Defaults to `'avc'` (H.264),\n * which every player, editor and upload target accepts.\n *\n * `'hevc'` and `'av1'` reconstruct sharp edges far better at the same\n * bitrate, which is what a listing of syntax-highlighted code or a hairline\n * stroke is made of — worth reaching for when the file is going somewhere\n * that decodes them. Both are still 4:2:0, so they narrow the gap rather\n * than close it. Availability depends on the platform's encoders; an\n * unsupported choice falls back to `'avc'` rather than failing the export.\n */\n codec?: 'avc' | 'hevc' | 'av1' | 'vp9';\n /**\n * Which encoder implementation WebCodecs should prefer.\n *\n * Defaults to `'no-preference'`, which lets each platform use its own\n * hardware encoder (VideoToolbox on macOS, Media Foundation/NVENC on\n * Windows, VA-API on Linux). That is typically several times faster than the\n * software path *and* it is the only configuration in which this sink's\n * zero-readback design actually holds: a software encoder needs the pixels\n * on the CPU, so it forces a full GPU→CPU copy of every frame that a\n * hardware encoder reads directly off the canvas.\n *\n * Pass `'prefer-software'` when byte-identical output across machines\n * matters more than speed — different hardware encoders tune quality and\n * colour differently at the same nominal bitrate, so the same settings do\n * not produce the same file.\n */\n hardwareAcceleration?: 'no-preference' | 'prefer-hardware' | 'prefer-software';\n /**\n * Target bitrate in bits per second, or one of mediabunny's subjective\n * `Quality` levels.\n *\n * Defaults to {@link motionGraphicsBitrate}, which is deliberately *not*\n * mediabunny's `QUALITY_VERY_HIGH`: that level is frame-rate blind, so a\n * 60 fps export got the same ~12 Mbps at 1080p as a 30 fps one and paid for\n * it with ringing along every glyph edge and banding across every gradient.\n * See that function for the reasoning.\n */\n bitrate?: number | Quality;\n /** Defaults to mediabunny's `'variable'`. `'constant'` is the reproducible-output choice. */\n bitrateMode?: 'constant' | 'variable';\n /**\n * Seconds between key frames. Defaults to 1.\n *\n * Shorter than mediabunny's 2, because a frame's error is carried by every\n * inter frame that references it: on synthetic content, where a flat\n * background makes a shimmer obvious, a keyframe twice as often visibly\n * resets the drift. It also halves the seek granularity, which matters for a\n * clip an editor is going to scrub.\n */\n keyFrameInterval?: number;\n /**\n * WebCodecs performance/quality trade-off. Defaults to `'quality'`, which\n * also guarantees no frame is dropped — the only correct setting for a file\n * being written rather than streamed.\n */\n latencyMode?: 'quality' | 'realtime';\n /**\n * A hint about what the frames contain, per\n * [mst-content-hint](https://w3c.github.io/mst-content-hint/#video-content-hints).\n *\n * Defaults to `'detail'`: encoders that honour it bias towards preserving\n * sharp edges and fine texture over smoothing motion, which is what motion\n * graphics are. Ignored where it is not implemented.\n */\n contentHint?: string;\n};\n\n/**\n * Bits per pixel per frame the default bitrate is built from.\n *\n * Rendered motion graphics are the hard case for a codec tuned on camera\n * footage: hard glyph edges, large flat fields, and gradients with no sensor\n * noise to hide the quantisation in. They compress *worse* than live action at\n * the same perceived quality, not better, so the figure here is well above what\n * a streaming preset would use — this is a master being rendered once, not a\n * bitstream being pushed down a pipe.\n */\nconst BITS_PER_PIXEL = 0.2;\n/** Floor, so a tiny viewport still gets enough bits to be legible. */\nconst MIN_VIDEO_BITRATE = 2_000_000;\n/**\n * Ceiling. Past this the returns are gone and the levels start excluding\n * hardware decoders — 4K60 lands here, and AVC level 5.1 (which every consumer\n * device implements) tops out at 240 Mbps, well clear of it.\n */\nconst MAX_VIDEO_BITRATE = 100_000_000;\n\n/**\n * The default video bitrate for an export of this size and frame rate.\n *\n * Scaling with **both** axes is the point. mediabunny's `Quality` levels scale\n * with resolution only, so doubling the frame rate halved the bits available per\n * frame while leaving the number unchanged — which is exactly the configuration\n * that produced clean stills and a mushy video from the very same renderer.\n */\nexport function motionGraphicsBitrate(width: number, height: number, fps: number): number {\n const raw = width * height * fps * BITS_PER_PIXEL;\n return Math.round(Math.min(MAX_VIDEO_BITRATE, Math.max(MIN_VIDEO_BITRATE, raw)));\n}\n\n/** How the mixed audio track is encoded. */\nexport type AudioEncoderOptions = {\n /** AAC bitrate in bits per second. Defaults to 192 kbps. */\n bitrate?: number;\n /** Mixdown sample rate in Hz. Defaults to the mixer's own (44100). */\n sampleRate?: number;\n};\n\nconst DEFAULT_AUDIO_BITRATE = 192_000;\n/** See {@link VideoEncoderOptions.keyFrameInterval}. */\nconst DEFAULT_KEY_FRAME_INTERVAL = 1;\n/** See {@link ExportParams.supersample}. */\nconst DEFAULT_SUPERSAMPLE = 2;\n\nexport type ExportParams = {\n /** A project, or a lone still or composition as the one track of one. */\n document: ProjectDocument | SceneDocument;\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 filename?: string;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n /**\n * Set false to produce a silent MP4 — no AAC track is added to the\n * container and the mix is skipped entirely. Defaults to true.\n */\n includeAudio?: boolean;\n /** Video encoder settings; see {@link VideoEncoderOptions} for the defaults. */\n video?: VideoEncoderOptions;\n /** Audio encoder settings; ignored when `includeAudio` is false. */\n audio?: AudioEncoderOptions;\n onProgress?: ExportProgressCallback;\n signal?: AbortSignal;\n wasmUrl?: string;\n /**\n * Render each frame at this multiple of the output resolution and downsample\n * it before encoding. **Defaults to 2**; pass 1 to turn it off.\n *\n * The one lever that improves an export at a *fixed* output size. Every\n * codec a browser can encode is 4:2:0 — colour is stored at half resolution\n * on both axes, and no bitrate buys that back — so the colour fringing along\n * a saturated glyph or a hard edge is a property of the format, not of the\n * settings. Handing the encoder an already-downsampled frame does not undo\n * the subsampling, but it does mean every chroma sample averages four real\n * pixels instead of one, which is what removes most of the crawl on fine\n * detail and much of the banding on gradients.\n *\n * Measured against a correctly-sampled reference frame of a syntax-highlighted\n * listing, `2` lands 2.7 dB closer than a 1× encode — and 1.3 dB closer than\n * an *unencoded* 1× still, which is why it is the default despite costing\n * `supersample²` times the render work per frame plus a rescale. Pass 1 when\n * turnaround matters more than the file, and note that exporting at\n * `scale: 2` is cheaper than supersampling *and* better whenever the extra\n * resolution is wanted in the file itself.\n *\n * Clamped so the render surface stays inside {@link MAX_RENDER_AXIS}, since a\n * supersampled 4K export is already drawing 8K frames.\n */\n supersample?: number;\n /**\n * When true, the encoded MP4 bytes are returned instead of triggering a\n * browser download. Used by headless drivers (e.g. the CLI) that capture\n * the output themselves and write it to disk; leave unset for the\n * interactive player, which downloads via an `<a>` element.\n */\n returnBytes?: boolean;\n}\n\nconst EMPTY_MANIFEST: AssetManifest = {\n image: {},\n video: {},\n audio: {},\n font: {},\n};\n\n// ── Audio mixing ─────────────────────────────────────────────────────────────\n//\n// The mix itself lives in ./audio/mixer, shared with `mixTimelineAudio` so a\n// split export and a whole one cannot mix differently.\n\n// ── Video sink ───────────────────────────────────────────────────────────────\n\n/**\n * mediabunny/WebCodecs implementation of the frame sink.\n *\n * Wraps the very `<canvas>` CanvasKit draws into, so `addFrame` costs nothing but\n * a timestamp — WebCodecs pulls the pixels from the canvas itself, with no\n * GPU→CPU readback. That is why the seam signals a frame rather than passing one.\n *\n * The readback claim holds only while the encoder is a hardware one, which is\n * why {@link VideoEncoderOptions.hardwareAcceleration} defaults to letting the\n * platform choose: a software encoder has to be handed CPU pixels, so it drags\n * a full-frame copy off the GPU on every single frame.\n */\nclass MediabunnyVideoSink implements VideoFrameSink<AudioBuffer> {\n private readonly target = new BufferTarget();\n private readonly output: Output;\n private readonly video: CanvasSource;\n private audio: AudioBufferSource | null = null;\n\n constructor(\n canvas: HTMLCanvasElement,\n fps: number,\n /** Encoded output size, which is the canvas' own unless supersampling is on. */\n output: { width: number; height: number },\n video: VideoEncoderOptions = {},\n private readonly audioOptions: AudioEncoderOptions = {},\n codec: VideoCodec = 'avc',\n ) {\n const downsampling = output.width !== canvas.width || output.height !== canvas.height;\n this.video = new CanvasSource(canvas, {\n codec,\n // Resolved against what is actually being encoded — the *output*\n // size and the rate the frame loop will feed it — rather than\n // against a subjective level that never sees the frame rate.\n bitrate: video.bitrate ?? motionGraphicsBitrate(output.width, output.height, fps),\n hardwareAcceleration: video.hardwareAcceleration ?? 'no-preference',\n keyFrameInterval: video.keyFrameInterval ?? DEFAULT_KEY_FRAME_INTERVAL,\n latencyMode: video.latencyMode ?? 'quality',\n contentHint: video.contentHint ?? 'detail',\n ...(video.bitrateMode !== undefined && { bitrateMode: video.bitrateMode }),\n // `fit` is exact here — the supersampled canvas is a whole multiple\n // of the output — but mediabunny requires it whenever both axes are\n // given, so it is stated rather than inferred.\n ...(downsampling && { transform: { width: output.width, height: output.height, fit: 'fill' as const } }),\n });\n this.output = new Output({ format: new Mp4OutputFormat(), target: this.target });\n }\n\n async start(info: { fps: number; hasAudio: boolean }): Promise<void> {\n this.output.addVideoTrack(this.video, { frameRate: info.fps });\n // Tracks must be declared before the muxer starts, which is why the\n // orchestrator reports this rather than leaving it to be inferred.\n if (info.hasAudio) {\n this.audio = new AudioBufferSource({\n codec: 'aac',\n bitrate: this.audioOptions.bitrate ?? DEFAULT_AUDIO_BITRATE,\n });\n this.output.addAudioTrack(this.audio);\n }\n await this.output.start();\n }\n\n async addFrame(timestamp: number, duration: number): Promise<void> {\n await this.video.add(timestamp, duration);\n }\n\n async addAudio(mixed: AudioBuffer): Promise<void> {\n if (!this.audio) return;\n await this.audio.add(mixed);\n }\n\n async finalize(): Promise<Uint8Array> {\n this.video.close();\n this.audio?.close();\n await this.output.finalize();\n const buffer = this.target.buffer;\n if (!buffer) throw new Error('Export produced no data');\n return new Uint8Array(buffer);\n }\n}\n\n// ── Main export ──────────────────────────────────────────────────────────────\n\n/** Round up to the nearest even integer — see the call site. */\nfunction evenUp(n: number): number {\n const rounded = Math.round(n);\n return rounded % 2 === 0 ? rounded : rounded + 1;\n}\n\n/**\n * Longest axis the render surface is allowed to reach.\n *\n * Supersampling multiplies the surface, not the output: a 4K export at\n * `supersample: 2` is drawing 8K frames, and the next step up asks for a\n * 15360-wide WebGL surface that most drivers will not give and none will give\n * cheaply. 8192 is the smallest `MAX_TEXTURE_SIZE` in wide circulation, so a\n * surface inside it is one every machine that can run the export at all can\n * also allocate.\n */\nconst MAX_RENDER_AXIS = 8192;\n\n/**\n * How far the frame can actually be supersampled at this output size.\n *\n * Reduced rather than refused: an export that quietly renders at 1× is a\n * slightly softer file, and one that throws because the default was too\n * ambitious for a 4K project is no file at all.\n */\nfunction fittedSupersample(requested: number, output: { width: number; height: number }): number {\n const longest = Math.max(output.width, output.height);\n let factor = Math.max(1, Math.round(requested));\n while (factor > 1 && longest * factor > MAX_RENDER_AXIS) factor--;\n return factor;\n}\n\n/**\n * The codec to actually encode with: the one asked for if this platform can\n * encode it at this size, `'avc'` otherwise.\n *\n * A better codec is the single largest quality win available on synthetic\n * content — but which ones a machine has encoders for varies by OS, browser and\n * GPU, and an export that dies at the muxer because the machine has no HEVC\n * encoder is worse than one that quietly produces H.264. AVC is not probed: it\n * is the fallback, and there is nothing to fall back to.\n */\nasync function resolveCodec(\n requested: VideoCodec | undefined,\n size: { width: number; height: number },\n fps: number,\n): Promise<VideoCodec> {\n if (!requested || requested === 'avc') return 'avc';\n try {\n const ok = await canEncodeVideo(requested, {\n width: size.width,\n height: size.height,\n bitrate: motionGraphicsBitrate(size.width, size.height, fps),\n });\n if (ok) return requested;\n } catch {\n // A probe that throws is a probe that answered \"no\".\n }\n console.warn(`[motion-script] No ${requested} encoder available; exporting as H.264 instead.`);\n return 'avc';\n}\n\n/**\n * Renders a document frame-by-frame to an offscreen canvas and muxes the result\n * into an MP4 via mediabunny.\n *\n * The frame loop itself — discovery, state evaluation, the warm-and-re-render retry\n * that keeps every frame accurate, audio scheduling, progress and cancellation —\n * lives in `@motionscript/skia-render`'s `renderTimeline`. What this function\n * supplies is the browser-specific half: a canvas to draw into, a WebCodecs sink,\n * a Web Audio mixer, and the `<a download>` delivery.\n */\nexport async function exportVideo(params: ExportParams): Promise<Uint8Array | void> {\n const {\n // Not `document`, which would shadow the DOM global this function uses.\n document: source,\n registry = new Registry(),\n scale = 1,\n filename = 'export.mp4',\n manifest = EMPTY_MANIFEST,\n theme,\n includeAudio = true,\n video,\n audio = {},\n onProgress,\n signal,\n wasmUrl,\n supersample = DEFAULT_SUPERSAMPLE,\n returnBytes = false,\n } = params;\n\n const project = resolveProjectDocument(source, params, DEFAULT_OUTPUT);\n if (project.tracks.length === 0) return;\n signal?.throwIfAborted();\n const viewport = project.viewport!;\n const fps = project.fps!;\n\n // Every block-based codec works in macroblocks, so an odd dimension is\n // padded by the encoder and the padding is *not* what was drawn — it shows up\n // as a smeared or discoloured strip down one edge. Rounding up to an even\n // size instead costs at most half a pixel of centring and keeps the whole\n // frame real.\n const outputSize = {\n width: evenUp(viewport.width * scale),\n height: evenUp(viewport.height * scale),\n };\n const oversample = fittedSupersample(supersample, outputSize);\n const renderScale = scale * oversample;\n\n const offscreenCanvas = document.createElement('canvas');\n offscreenCanvas.width = evenUp(viewport.width * renderScale);\n offscreenCanvas.height = evenUp(viewport.height * renderScale);\n offscreenCanvas.style.display = 'none';\n document.body.appendChild(offscreenCanvas);\n\n const canvasKit = await getCanvasKit(wasmUrl);\n const assetCatalog = new ManifestAssetCatalog(manifest);\n const storageAdapter = new WebStorageAdapter(canvasKit, assetCatalog, viewport, fps);\n const renderContext = new WebRenderContext(canvasKit, storageAdapter);\n renderContext.mount(offscreenCanvas);\n\n const codec = await resolveCodec(video?.codec, outputSize, fps);\n\n let bytes: Uint8Array | undefined;\n try {\n bytes = await renderTimeline<AudioBuffer>({\n project, registry, theme, scale: renderScale, includeAudio,\n renderContext, storageAdapter, assetCatalog,\n sink: new MediabunnyVideoSink(offscreenCanvas, fps, outputSize, video, audio, codec),\n mixer: new WebAudioMixer(audio.sampleRate, assetCatalog),\n onProgress,\n signal,\n }) as Uint8Array | undefined;\n } finally {\n renderContext.dispose();\n document.body.removeChild(offscreenCanvas);\n }\n\n if (!bytes) return;\n\n // Headless callers capture the bytes and write them to disk themselves,\n // so skip the DOM-based download path entirely.\n if (returnBytes) return bytes;\n\n // The sink builds this Uint8Array over a whole, plain ArrayBuffer, so the cast\n // is sound — TS only balks because the generic parameter admits a\n // SharedArrayBuffer, which a BlobPart may not be.\n const blob = new Blob([bytes.buffer as ArrayBuffer], { type: 'video/mp4' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"exporter.js","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"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","sourcesContent":["import {\r\n DEFAULT_OUTPUT, ManifestAssetCatalog, Registry, resolveProjectDocument, type AssetManifest,\r\n type FrameRange, type ProjectDocument, type VisualDocument, type Size2D, type Theme,\r\n} from \"@motionscript/core\";\r\nimport {\r\n renderTimeline,\r\n type VideoFrameSink,\r\n} from \"@motionscript/skia-render/export\";\r\nimport {\r\n AudioBufferSource,\r\n BufferTarget,\r\n CanvasSource,\r\n canEncodeVideo,\r\n Mp4OutputFormat,\r\n Output,\r\n type Quality,\r\n type VideoCodec,\r\n} from 'mediabunny';\r\nimport { WebRenderContext } from \"./render-context\";\r\nimport { WebStorageAdapter } from \"./storage-adapter\";\r\nimport { getCanvasKit } from \"./getter\";\r\nimport { WebAudioMixer } from \"./audio/mixer\";\r\n\r\n/** Reports export progress in [0, 1]; video encoding occupies most of the range, audio mixing/finalize the tail. */\r\nexport type ExportProgressCallback = (progress: number) => void;\r\n\r\n/**\r\n * How the video track is encoded.\r\n *\r\n * Every field is a straight pass-through to mediabunny's `VideoEncodingConfig`,\r\n * defaulted here rather than there so an export has one obvious place to look.\r\n */\r\nexport type VideoEncoderOptions = {\r\n /**\r\n * Which codec the video track is encoded with. Defaults to `'avc'` (H.264),\r\n * which every player, editor and upload target accepts.\r\n *\r\n * `'hevc'` and `'av1'` reconstruct sharp edges far better at the same\r\n * bitrate, which is what a listing of syntax-highlighted code or a hairline\r\n * stroke is made of — worth reaching for when the file is going somewhere\r\n * that decodes them. Both are still 4:2:0, so they narrow the gap rather\r\n * than close it. Availability depends on the platform's encoders; an\r\n * unsupported choice falls back to `'avc'` rather than failing the export.\r\n */\r\n codec?: 'avc' | 'hevc' | 'av1' | 'vp9';\r\n /**\r\n * Which encoder implementation WebCodecs should prefer.\r\n *\r\n * Defaults to `'no-preference'`, which lets each platform use its own\r\n * hardware encoder (VideoToolbox on macOS, Media Foundation/NVENC on\r\n * Windows, VA-API on Linux). That is typically several times faster than the\r\n * software path *and* it is the only configuration in which this sink's\r\n * zero-readback design actually holds: a software encoder needs the pixels\r\n * on the CPU, so it forces a full GPU→CPU copy of every frame that a\r\n * hardware encoder reads directly off the canvas.\r\n *\r\n * Pass `'prefer-software'` when byte-identical output across machines\r\n * matters more than speed — different hardware encoders tune quality and\r\n * colour differently at the same nominal bitrate, so the same settings do\r\n * not produce the same file.\r\n */\r\n hardwareAcceleration?: 'no-preference' | 'prefer-hardware' | 'prefer-software';\r\n /**\r\n * Target bitrate in bits per second, or one of mediabunny's subjective\r\n * `Quality` levels.\r\n *\r\n * Defaults to {@link motionGraphicsBitrate}, which is deliberately *not*\r\n * mediabunny's `QUALITY_VERY_HIGH`: that level is frame-rate blind, so a\r\n * 60 fps export got the same ~12 Mbps at 1080p as a 30 fps one and paid for\r\n * it with ringing along every glyph edge and banding across every gradient.\r\n * See that function for the reasoning.\r\n */\r\n bitrate?: number | Quality;\r\n /** Defaults to mediabunny's `'variable'`. `'constant'` is the reproducible-output choice. */\r\n bitrateMode?: 'constant' | 'variable';\r\n /**\r\n * Seconds between key frames. Defaults to 1.\r\n *\r\n * Shorter than mediabunny's 2, because a frame's error is carried by every\r\n * inter frame that references it: on synthetic content, where a flat\r\n * background makes a shimmer obvious, a keyframe twice as often visibly\r\n * resets the drift. It also halves the seek granularity, which matters for a\r\n * clip an editor is going to scrub.\r\n */\r\n keyFrameInterval?: number;\r\n /**\r\n * WebCodecs performance/quality trade-off. Defaults to `'quality'`, which\r\n * also guarantees no frame is dropped — the only correct setting for a file\r\n * being written rather than streamed.\r\n */\r\n latencyMode?: 'quality' | 'realtime';\r\n /**\r\n * A hint about what the frames contain, per\r\n * [mst-content-hint](https://w3c.github.io/mst-content-hint/#video-content-hints).\r\n *\r\n * Defaults to `'detail'`: encoders that honour it bias towards preserving\r\n * sharp edges and fine texture over smoothing motion, which is what motion\r\n * graphics are. Ignored where it is not implemented.\r\n */\r\n contentHint?: string;\r\n};\r\n\r\n/**\r\n * Bits per pixel per frame the default bitrate is built from.\r\n *\r\n * Rendered motion graphics are the hard case for a codec tuned on camera\r\n * footage: hard glyph edges, large flat fields, and gradients with no sensor\r\n * noise to hide the quantisation in. They compress *worse* than live action at\r\n * the same perceived quality, not better, so the figure here is well above what\r\n * a streaming preset would use — this is a master being rendered once, not a\r\n * bitstream being pushed down a pipe.\r\n */\r\nconst BITS_PER_PIXEL = 0.2;\r\n/** Floor, so a tiny viewport still gets enough bits to be legible. */\r\nconst MIN_VIDEO_BITRATE = 2_000_000;\r\n/**\r\n * Ceiling. Past this the returns are gone and the levels start excluding\r\n * hardware decoders — 4K60 lands here, and AVC level 5.1 (which every consumer\r\n * device implements) tops out at 240 Mbps, well clear of it.\r\n */\r\nconst MAX_VIDEO_BITRATE = 100_000_000;\r\n\r\n/**\r\n * The default video bitrate for an export of this size and frame rate.\r\n *\r\n * Scaling with **both** axes is the point. mediabunny's `Quality` levels scale\r\n * with resolution only, so doubling the frame rate halved the bits available per\r\n * frame while leaving the number unchanged — which is exactly the configuration\r\n * that produced clean stills and a mushy video from the very same renderer.\r\n */\r\nexport function motionGraphicsBitrate(width: number, height: number, fps: number): number {\r\n const raw = width * height * fps * BITS_PER_PIXEL;\r\n return Math.round(Math.min(MAX_VIDEO_BITRATE, Math.max(MIN_VIDEO_BITRATE, raw)));\r\n}\r\n\r\n/** How the mixed audio track is encoded. */\r\nexport type AudioEncoderOptions = {\r\n /** AAC bitrate in bits per second. Defaults to 192 kbps. */\r\n bitrate?: number;\r\n /** Mixdown sample rate in Hz. Defaults to the mixer's own (44100). */\r\n sampleRate?: number;\r\n};\r\n\r\nconst DEFAULT_AUDIO_BITRATE = 192_000;\r\n/** See {@link VideoEncoderOptions.keyFrameInterval}. */\r\nconst DEFAULT_KEY_FRAME_INTERVAL = 1;\r\n/** See {@link ExportParams.supersample}. */\r\nconst DEFAULT_SUPERSAMPLE = 2;\r\n\r\nexport type ExportParams = {\r\n /** A project, or a lone still or composition as the one track of one. */\r\n document: ProjectDocument | VisualDocument;\r\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\r\n registry?: Registry;\r\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\r\n viewport?: Size2D;\r\n fps?: number;\r\n scale?: number;\r\n filename?: string;\r\n manifest?: AssetManifest;\r\n /** Theme for a document that carries none of its own. */\r\n theme?: Theme;\r\n /**\r\n * Set false to produce a silent MP4 — no AAC track is added to the\r\n * container and the mix is skipped entirely. Defaults to true.\r\n */\r\n includeAudio?: boolean;\r\n /** Video encoder settings; see {@link VideoEncoderOptions} for the defaults. */\r\n video?: VideoEncoderOptions;\r\n /** Audio encoder settings; ignored when `includeAudio` is false. */\r\n audio?: AudioEncoderOptions;\r\n /**\r\n * Encode only these frames (`end` excluded), timestamped from 0 and silent:\r\n * one segment of a split export, for `concatVideoSegments` to join. Mix\r\n * the audio once with `mixTimelineAudio` instead.\r\n */\r\n frames?: FrameRange;\r\n onProgress?: ExportProgressCallback;\r\n signal?: AbortSignal;\r\n wasmUrl?: string;\r\n /**\r\n * Render each frame at this multiple of the output resolution and downsample\r\n * it before encoding. **Defaults to 2**; pass 1 to turn it off.\r\n *\r\n * The one lever that improves an export at a *fixed* output size. Every\r\n * codec a browser can encode is 4:2:0 — colour is stored at half resolution\r\n * on both axes, and no bitrate buys that back — so the colour fringing along\r\n * a saturated glyph or a hard edge is a property of the format, not of the\r\n * settings. Handing the encoder an already-downsampled frame does not undo\r\n * the subsampling, but it does mean every chroma sample averages four real\r\n * pixels instead of one, which is what removes most of the crawl on fine\r\n * detail and much of the banding on gradients.\r\n *\r\n * Measured against a correctly-sampled reference frame of a syntax-highlighted\r\n * listing, `2` lands 2.7 dB closer than a 1× encode — and 1.3 dB closer than\r\n * an *unencoded* 1× still, which is why it is the default despite costing\r\n * `supersample²` times the render work per frame plus a rescale. Pass 1 when\r\n * turnaround matters more than the file, and note that exporting at\r\n * `scale: 2` is cheaper than supersampling *and* better whenever the extra\r\n * resolution is wanted in the file itself.\r\n *\r\n * Clamped so the render surface stays inside {@link MAX_RENDER_AXIS}, since a\r\n * supersampled 4K export is already drawing 8K frames.\r\n */\r\n supersample?: number;\r\n /**\r\n * When true, the encoded MP4 bytes are returned instead of triggering a\r\n * browser download. Used by headless drivers (e.g. the CLI) that capture\r\n * the output themselves and write it to disk; leave unset for the\r\n * interactive player, which downloads via an `<a>` element.\r\n */\r\n returnBytes?: boolean;\r\n}\r\n\r\nconst EMPTY_MANIFEST: AssetManifest = {\r\n image: {},\r\n video: {},\r\n audio: {},\r\n font: {},\r\n};\r\n\r\n// ── Audio mixing ─────────────────────────────────────────────────────────────\r\n//\r\n// The mix itself lives in ./audio/mixer, shared with `mixTimelineAudio` so a\r\n// split export and a whole one cannot mix differently.\r\n\r\n// ── Video sink ───────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * mediabunny/WebCodecs implementation of the frame sink.\r\n *\r\n * Wraps the very `<canvas>` CanvasKit draws into, so `addFrame` costs nothing but\r\n * a timestamp — WebCodecs pulls the pixels from the canvas itself, with no\r\n * GPU→CPU readback. That is why the seam signals a frame rather than passing one.\r\n *\r\n * The readback claim holds only while the encoder is a hardware one, which is\r\n * why {@link VideoEncoderOptions.hardwareAcceleration} defaults to letting the\r\n * platform choose: a software encoder has to be handed CPU pixels, so it drags\r\n * a full-frame copy off the GPU on every single frame.\r\n */\r\nclass MediabunnyVideoSink implements VideoFrameSink<AudioBuffer> {\r\n private readonly target = new BufferTarget();\r\n private readonly output: Output;\r\n private readonly video: CanvasSource;\r\n private audio: AudioBufferSource | null = null;\r\n\r\n constructor(\r\n canvas: HTMLCanvasElement,\r\n fps: number,\r\n /** Encoded output size, which is the canvas' own unless supersampling is on. */\r\n output: { width: number; height: number },\r\n video: VideoEncoderOptions = {},\r\n private readonly audioOptions: AudioEncoderOptions = {},\r\n codec: VideoCodec = 'avc',\r\n ) {\r\n const downsampling = output.width !== canvas.width || output.height !== canvas.height;\r\n this.video = new CanvasSource(canvas, {\r\n codec,\r\n // Resolved against what is actually being encoded — the *output*\r\n // size and the rate the frame loop will feed it — rather than\r\n // against a subjective level that never sees the frame rate.\r\n bitrate: video.bitrate ?? motionGraphicsBitrate(output.width, output.height, fps),\r\n hardwareAcceleration: video.hardwareAcceleration ?? 'no-preference',\r\n keyFrameInterval: video.keyFrameInterval ?? DEFAULT_KEY_FRAME_INTERVAL,\r\n latencyMode: video.latencyMode ?? 'quality',\r\n contentHint: video.contentHint ?? 'detail',\r\n ...(video.bitrateMode !== undefined && { bitrateMode: video.bitrateMode }),\r\n // `fit` is exact here — the supersampled canvas is a whole multiple\r\n // of the output — but mediabunny requires it whenever both axes are\r\n // given, so it is stated rather than inferred.\r\n ...(downsampling && { transform: { width: output.width, height: output.height, fit: 'fill' as const } }),\r\n });\r\n this.output = new Output({ format: new Mp4OutputFormat(), target: this.target });\r\n }\r\n\r\n async start(info: { fps: number; hasAudio: boolean }): Promise<void> {\r\n this.output.addVideoTrack(this.video, { frameRate: info.fps });\r\n // Tracks must be declared before the muxer starts, which is why the\r\n // orchestrator reports this rather than leaving it to be inferred.\r\n if (info.hasAudio) {\r\n this.audio = new AudioBufferSource({\r\n codec: 'aac',\r\n bitrate: this.audioOptions.bitrate ?? DEFAULT_AUDIO_BITRATE,\r\n });\r\n this.output.addAudioTrack(this.audio);\r\n }\r\n await this.output.start();\r\n }\r\n\r\n async addFrame(timestamp: number, duration: number): Promise<void> {\r\n await this.video.add(timestamp, duration);\r\n }\r\n\r\n async addAudio(mixed: AudioBuffer): Promise<void> {\r\n if (!this.audio) return;\r\n await this.audio.add(mixed);\r\n }\r\n\r\n async finalize(): Promise<Uint8Array> {\r\n this.video.close();\r\n this.audio?.close();\r\n await this.output.finalize();\r\n const buffer = this.target.buffer;\r\n if (!buffer) throw new Error('Export produced no data');\r\n return new Uint8Array(buffer);\r\n }\r\n}\r\n\r\n// ── Main export ──────────────────────────────────────────────────────────────\r\n\r\n/** Round up to the nearest even integer — see the call site. */\r\nfunction evenUp(n: number): number {\r\n const rounded = Math.round(n);\r\n return rounded % 2 === 0 ? rounded : rounded + 1;\r\n}\r\n\r\n/**\r\n * Longest axis the render surface is allowed to reach.\r\n *\r\n * Supersampling multiplies the surface, not the output: a 4K export at\r\n * `supersample: 2` is drawing 8K frames, and the next step up asks for a\r\n * 15360-wide WebGL surface that most drivers will not give and none will give\r\n * cheaply. 8192 is the smallest `MAX_TEXTURE_SIZE` in wide circulation, so a\r\n * surface inside it is one every machine that can run the export at all can\r\n * also allocate.\r\n */\r\nconst MAX_RENDER_AXIS = 8192;\r\n\r\n/**\r\n * How far the frame can actually be supersampled at this output size.\r\n *\r\n * Reduced rather than refused: an export that quietly renders at 1× is a\r\n * slightly softer file, and one that throws because the default was too\r\n * ambitious for a 4K project is no file at all.\r\n */\r\nfunction fittedSupersample(requested: number, output: { width: number; height: number }): number {\r\n const longest = Math.max(output.width, output.height);\r\n let factor = Math.max(1, Math.round(requested));\r\n while (factor > 1 && longest * factor > MAX_RENDER_AXIS) factor--;\r\n return factor;\r\n}\r\n\r\n/**\r\n * The codec to actually encode with: the one asked for if this platform can\r\n * encode it at this size, `'avc'` otherwise.\r\n *\r\n * A better codec is the single largest quality win available on synthetic\r\n * content — but which ones a machine has encoders for varies by OS, browser and\r\n * GPU, and an export that dies at the muxer because the machine has no HEVC\r\n * encoder is worse than one that quietly produces H.264. AVC is not probed: it\r\n * is the fallback, and there is nothing to fall back to.\r\n */\r\nasync function resolveCodec(\r\n requested: VideoCodec | undefined,\r\n size: { width: number; height: number },\r\n fps: number,\r\n): Promise<VideoCodec> {\r\n if (!requested || requested === 'avc') return 'avc';\r\n try {\r\n const ok = await canEncodeVideo(requested, {\r\n width: size.width,\r\n height: size.height,\r\n bitrate: motionGraphicsBitrate(size.width, size.height, fps),\r\n });\r\n if (ok) return requested;\r\n } catch {\r\n // A probe that throws is a probe that answered \"no\".\r\n }\r\n console.warn(`[motionscript] No ${requested} encoder available; exporting as H.264 instead.`);\r\n return 'avc';\r\n}\r\n\r\n/**\r\n * Renders a document frame-by-frame to an offscreen canvas and muxes the result\r\n * into an MP4 via mediabunny.\r\n *\r\n * The frame loop itself — discovery, state evaluation, the warm-and-re-render retry\r\n * that keeps every frame accurate, audio scheduling, progress and cancellation —\r\n * lives in `@motionscript/skia-render`'s `renderTimeline`. What this function\r\n * supplies is the browser-specific half: a canvas to draw into, a WebCodecs sink,\r\n * a Web Audio mixer, and the `<a download>` delivery.\r\n */\r\nexport async function exportVideo(params: ExportParams): Promise<Uint8Array | void> {\r\n const {\r\n // Not `document`, which would shadow the DOM global this function uses.\r\n document: source,\r\n registry = new Registry(),\r\n scale = 1,\r\n filename = 'export.mp4',\r\n manifest = EMPTY_MANIFEST,\r\n theme,\r\n includeAudio = true,\r\n video,\r\n audio = {},\r\n frames,\r\n onProgress,\r\n signal,\r\n wasmUrl,\r\n supersample = DEFAULT_SUPERSAMPLE,\r\n returnBytes = false,\r\n } = params;\r\n\r\n const project = resolveProjectDocument(source, params, DEFAULT_OUTPUT);\r\n if (project.tracks.length === 0) return;\r\n signal?.throwIfAborted();\r\n const viewport = project.viewport!;\r\n const fps = project.fps!;\r\n\r\n // Every block-based codec works in macroblocks, so an odd dimension is\r\n // padded by the encoder and the padding is *not* what was drawn — it shows up\r\n // as a smeared or discoloured strip down one edge. Rounding up to an even\r\n // size instead costs at most half a pixel of centring and keeps the whole\r\n // frame real.\r\n const outputSize = {\r\n width: evenUp(viewport.width * scale),\r\n height: evenUp(viewport.height * scale),\r\n };\r\n const oversample = fittedSupersample(supersample, outputSize);\r\n const renderScale = scale * oversample;\r\n\r\n const offscreenCanvas = document.createElement('canvas');\r\n // A whole multiple of the output, so the downsample is exact: the picture is drawn at\r\n // `renderScale` from the corner, as it is at 1×, and any odd-size padding is a strip.\r\n offscreenCanvas.width = outputSize.width * oversample;\r\n offscreenCanvas.height = outputSize.height * oversample;\r\n offscreenCanvas.style.display = 'none';\r\n document.body.appendChild(offscreenCanvas);\r\n\r\n const canvasKit = await getCanvasKit(wasmUrl);\r\n const assetCatalog = new ManifestAssetCatalog(manifest);\r\n const storageAdapter = new WebStorageAdapter(canvasKit, assetCatalog, viewport, fps);\r\n const renderContext = new WebRenderContext(canvasKit, storageAdapter);\r\n renderContext.mount(offscreenCanvas);\r\n\r\n const codec = await resolveCodec(video?.codec, outputSize, fps);\r\n\r\n let bytes: Uint8Array | undefined;\r\n try {\r\n bytes = await renderTimeline<AudioBuffer>({\r\n project, registry, theme, scale: renderScale, includeAudio, frames,\r\n renderContext, storageAdapter, assetCatalog,\r\n sink: new MediabunnyVideoSink(offscreenCanvas, fps, outputSize, video, audio, codec),\r\n mixer: new WebAudioMixer(audio.sampleRate, assetCatalog),\r\n onProgress,\r\n signal,\r\n }) as Uint8Array | undefined;\r\n } finally {\r\n renderContext.dispose();\r\n document.body.removeChild(offscreenCanvas);\r\n }\r\n\r\n if (!bytes) return;\r\n\r\n // Headless callers capture the bytes and write them to disk themselves,\r\n // so skip the DOM-based download path entirely.\r\n if (returnBytes) return bytes;\r\n\r\n // The sink builds this Uint8Array over a whole, plain ArrayBuffer, so the cast\r\n // is sound — TS only balks because the generic parameter admits a\r\n // SharedArrayBuffer, which a BlobPart may not be.\r\n const blob = new Blob([bytes.buffer as ArrayBuffer], { type: 'video/mp4' });\r\n const url = URL.createObjectURL(blob);\r\n const link = document.createElement('a');\r\n link.href = url;\r\n link.download = filename;\r\n document.body.appendChild(link);\r\n link.click();\r\n document.body.removeChild(link);\r\n URL.revokeObjectURL(url);\r\n}\r\n"]}
|
package/dist/getter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getter.js","sourceRoot":"","sources":["../src/getter.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,OAAO,aAAiC,MAAM,yBAAyB,CAAC;AAExE,IAAI,SAAS,GAAqB,IAAI,CAAC;AACvC,IAAI,WAAW,GAA8B,IAAI,CAAC;AAElD,4EAA4E;AAC5E,6EAA6E;AAC7E,+EAA+E;AAC/E,0DAA0D;AAC1D,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,SAAS,cAAc;IACnB,OAAO,IAAI,GAAG,CAAC,wCAAwC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;AACnF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAgB;IAC/C,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAChC,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IACpC,MAAM,GAAG,GAAG,OAAO,IAAI,cAAc,EAAE,CAAC;IAExC,WAAW,GAAG,aAAa,CAAC;QACxB,UAAU,EAAE,GAAG,EAAE,CAAC,GAAG;KACxB,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;QACX,SAAS,GAAG,EAAE,CAAC;QACf,OAAO,EAAE,CAAC;IACd,CAAC,CAAC,CAAC;IACH,OAAO,WAAW,CAAC;AACvB,CAAC","sourcesContent":["// @motionscript/core/src/engine.ts\nimport CanvasKitInit, { type CanvasKit } from \"@motionscript/canvaskit\";\n\nlet canvasKit: CanvasKit | null = null;\nlet initPromise: Promise<CanvasKit> | null = null;\n\n// Default to the wasm shipped by @motionscript/canvaskit so consumers don't\n// have to configure anything. This MUST be the custom build that matches the\n// glue imported above — the stock canvaskit-wasm binary lacks imports our glue\n// expects (e.g. `pd`) and fails to link with a LinkError.\n//\n// Resolved **lazily**, not at module scope. `import.meta.url` is empty in a\n// non-ESM output — an IIFE bundle, which is what the headless page loads — and\n// `new URL(\"…\", \"\")` throws on import, before any caller has had the chance to\n// pass the explicit URL that would have made the default irrelevant.\nfunction defaultWasmUrl(): string {\n return new URL(\"@motionscript/canvaskit/canvaskit.wasm\", import.meta.url).href;\n}\n\n/**\n * Loads and caches the CanvasKit wasm module (memoized — concurrent callers\n * share the same init promise, and once resolved every call returns the same\n * instance). `wasmUrl` defaults to the wasm shipped by `@motionscript/canvaskit`\n * (resolved by the consumer's bundler), so consumers don't have to configure\n * anything; pass a URL to override where the binary is loaded from.\n */\nexport async function getCanvasKit(wasmUrl?: string): Promise<CanvasKit> {\n if (canvasKit) return canvasKit;\n if (initPromise) return initPromise;\n const url = wasmUrl ?? defaultWasmUrl();\n\n initPromise = CanvasKitInit({\n locateFile: () => url,\n }).then((ck) => {\n canvasKit = ck;\n return ck;\n });\n return initPromise;\n}"]}
|
|
1
|
+
{"version":3,"file":"getter.js","sourceRoot":"","sources":["../src/getter.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,OAAO,aAAiC,MAAM,yBAAyB,CAAC;AAExE,IAAI,SAAS,GAAqB,IAAI,CAAC;AACvC,IAAI,WAAW,GAA8B,IAAI,CAAC;AAElD,4EAA4E;AAC5E,6EAA6E;AAC7E,+EAA+E;AAC/E,0DAA0D;AAC1D,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,SAAS,cAAc;IACnB,OAAO,IAAI,GAAG,CAAC,wCAAwC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;AACnF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAgB;IAC/C,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAChC,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IACpC,MAAM,GAAG,GAAG,OAAO,IAAI,cAAc,EAAE,CAAC;IAExC,WAAW,GAAG,aAAa,CAAC;QACxB,UAAU,EAAE,GAAG,EAAE,CAAC,GAAG;KACxB,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;QACX,SAAS,GAAG,EAAE,CAAC;QACf,OAAO,EAAE,CAAC;IACd,CAAC,CAAC,CAAC;IACH,OAAO,WAAW,CAAC;AACvB,CAAC","sourcesContent":["// @motionscript/core/src/engine.ts\r\nimport CanvasKitInit, { type CanvasKit } from \"@motionscript/canvaskit\";\r\n\r\nlet canvasKit: CanvasKit | null = null;\r\nlet initPromise: Promise<CanvasKit> | null = null;\r\n\r\n// Default to the wasm shipped by @motionscript/canvaskit so consumers don't\r\n// have to configure anything. This MUST be the custom build that matches the\r\n// glue imported above — the stock canvaskit-wasm binary lacks imports our glue\r\n// expects (e.g. `pd`) and fails to link with a LinkError.\r\n//\r\n// Resolved **lazily**, not at module scope. `import.meta.url` is empty in a\r\n// non-ESM output — an IIFE bundle, which is what the headless page loads — and\r\n// `new URL(\"…\", \"\")` throws on import, before any caller has had the chance to\r\n// pass the explicit URL that would have made the default irrelevant.\r\nfunction defaultWasmUrl(): string {\r\n return new URL(\"@motionscript/canvaskit/canvaskit.wasm\", import.meta.url).href;\r\n}\r\n\r\n/**\r\n * Loads and caches the CanvasKit wasm module (memoized — concurrent callers\r\n * share the same init promise, and once resolved every call returns the same\r\n * instance). `wasmUrl` defaults to the wasm shipped by `@motionscript/canvaskit`\r\n * (resolved by the consumer's bundler), so consumers don't have to configure\r\n * anything; pass a URL to override where the binary is loaded from.\r\n */\r\nexport async function getCanvasKit(wasmUrl?: string): Promise<CanvasKit> {\r\n if (canvasKit) return canvasKit;\r\n if (initPromise) return initPromise;\r\n const url = wasmUrl ?? defaultWasmUrl();\r\n\r\n initPromise = CanvasKitInit({\r\n locateFile: () => url,\r\n }).then((ck) => {\r\n canvasKit = ck;\r\n return ck;\r\n });\r\n return initPromise;\r\n}"]}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,6EAA6E;AAE7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,8EAA8E;AAC9E,oEAAoE;AACpE,OAAO,EAAE,kBAAkB,EAAwB,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,+EAA+E;AAC/E,8EAA8E;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,EACH,WAAW,GAKd,MAAM,YAAY,CAAC;AACpB,+EAA+E;AAC/E,iFAAiF;AACjF,+CAA+C;AAC/C,OAAO,EACH,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,YAAY,EACZ,iBAAiB,GAEpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAuF,MAAM,cAAc,CAAC;AACrI,uFAAuF;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,8EAA8E;AAC9E,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAsB,MAAM,UAAU,CAAC;AACxF,iFAAiF;AACjF,8EAA8E;AAC9E,oDAAoD;AACpD,OAAO,EAAE,gBAAgB,EAA+B,MAAM,sBAAsB,CAAC;AACrF,mFAAmF;AACnF,qFAAqF;AACrF,yCAAyC;AACzC,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAQ7D,OAAO,EAAE,YAAY,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,2EAA2E;AAC3E,+EAA+E;AAC/E,SAAS;AACT,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAwB,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAG/C,gFAAgF;AAEhF,iEAAiE;AACjE,+EAA+E;AAC/E,gFAAgF;AAChF,8BAA8B;AAC9B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["// @motionscript/browser — CanvasKit/Skia-based renderer for @motionscript/core,\n// running in the browser (canvas mount, video export, audio playback/clock).\n\nexport { WebRenderContext } from \"./render-context\";\nexport { WebStorageAdapter } from \"./storage-adapter\";\n// The seam letting a host answer for an asset's bytes before the adapter goes\n// to the network — see `asset-bytes.ts` for why it is module-level.\nexport { setAssetByteSource, type AssetByteSource } from \"./asset-bytes\";\nexport { getCanvasKit } from \"./getter\";\n// Re-exported from @motionscript/skia-render, whose canonical home these are —\n// kept on this barrel so the specifier consumers already use keeps resolving.\nexport { EffectRegistry } from \"@motionscript/skia-render\";\nexport type { EffectHandler, EffectGeometry, EffectResources, EffectTarget, RenderEffect } from \"@motionscript/skia-render\";\nexport {\n exportVideo,\n type ExportParams,\n type ExportProgressCallback,\n type VideoEncoderOptions,\n type AudioEncoderOptions,\n} from \"./exporter\";\n// mediabunny's resolution-aware quality levels, re-exported so a host can pick\n// one for `ExportParams.video.bitrate` without taking a direct dependency on the\n// encoder library this package happens to use.\nexport {\n QUALITY_VERY_LOW,\n QUALITY_LOW,\n QUALITY_MEDIUM,\n QUALITY_HIGH,\n QUALITY_VERY_HIGH,\n type Quality,\n} from \"mediabunny\";\nexport { exportScreenshot, type ScreenshotParams, type ScreenshotResult, type ScreenshotFormat, type FrameSpec } from \"./screenshot\";\n// Thrown by a still and an export alike for a frame whose every track failed to build.\nexport { NothingToDrawError } from \"@motionscript/skia-render\";\n// Joins separately-encoded MP4s end to end without re-encoding. What makes it\n// worth rendering an export in parallel at all: the parts still have to become\n// one file, and re-encoding to join them gives back everything the parallelism\n// won. See `concat.ts` for the two things a caller has to guarantee.\nexport { concatVideoSegments, ConcatMismatchError, type ConcatOptions } from \"./concat\";\n// Mixes a timeline's audio without rendering a frame — the other half of a split\n// export, whose video arrives in separately-encoded pieces that AAC cannot be\n// concatenated across. See `mix-timeline-audio.ts`.\nexport { mixTimelineAudio, type MixTimelineAudioParams } from \"./mix-timeline-audio\";\n// Single frames as a first-class capability: one long-lived renderer that repaints\n// into a canvas the host owns, for thumbnails and still previews. `exportScreenshot`\n// above is the one-shot wrapper over it.\nexport { StillRenderer, createStillRenderer } from \"./still\";\nexport type {\n StillRendererOptions,\n RenderStillOptions,\n EncodeStillOptions,\n StillSource,\n FrameRef,\n} from \"./still\";\nexport { WebAudioSink as WebAudioPlayer } from \"./audio/player\";\n// Mixdown. Also on the `@motionscript/browser/audio` subpath, which is the\n// import to prefer — it reaches it without loading CanvasKit, since none of it\n// draws.\nexport { mixAudio, encodeWav, type MixAudioOptions } from \"./audio/mixer\";\nexport { WebMeasureContext } from \"./measure-context\";\nexport { WebTicker } from \"./ticker\";\n\nexport { createBrowserEngine } from \"./engine\";\nexport type { BrowserEngineOptions, BrowserEncodeOptions } from \"./engine\";\n\n// ─── 3D ──────────────────────────────────────────────────────────────────────\n\n// The reconciler, handlers and lazy `three` boundary all live in\n// @motionscript/skia-render now; this package supplies only the WebGL renderer\n// that rasterizes what they build. Re-exported here so the specifiers consumers\n// already use keep resolving.\nexport { Canvas3DBackend, canvas3DBackend, loadCanvas3D, disposeCanvas3DBackend } from \"@motionscript/skia-render\";\nexport type { Canvas3DAssets, RenderedCanvas3D } from \"@motionscript/skia-render\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,6EAA6E;AAE7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,8EAA8E;AAC9E,oEAAoE;AACpE,OAAO,EAAE,kBAAkB,EAAwB,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,+EAA+E;AAC/E,8EAA8E;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,EACH,WAAW,GAKd,MAAM,YAAY,CAAC;AACpB,+EAA+E;AAC/E,iFAAiF;AACjF,+CAA+C;AAC/C,OAAO,EACH,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,YAAY,EACZ,iBAAiB,GAEpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAuF,MAAM,cAAc,CAAC;AACrI,uFAAuF;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,8EAA8E;AAC9E,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAsB,MAAM,UAAU,CAAC;AACxF,iFAAiF;AACjF,8EAA8E;AAC9E,oDAAoD;AACpD,OAAO,EAAE,gBAAgB,EAA+B,MAAM,sBAAsB,CAAC;AACrF,mFAAmF;AACnF,qFAAqF;AACrF,yCAAyC;AACzC,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAQ7D,OAAO,EAAE,YAAY,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,2EAA2E;AAC3E,+EAA+E;AAC/E,SAAS;AACT,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAwB,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAG/C,gFAAgF;AAEhF,iEAAiE;AACjE,+EAA+E;AAC/E,gFAAgF;AAChF,8BAA8B;AAC9B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["// @motionscript/browser — CanvasKit/Skia-based renderer for @motionscript/core,\r\n// running in the browser (canvas mount, video export, audio playback/clock).\r\n\r\nexport { WebRenderContext } from \"./render-context\";\r\nexport { WebStorageAdapter } from \"./storage-adapter\";\r\n// The seam letting a host answer for an asset's bytes before the adapter goes\r\n// to the network — see `asset-bytes.ts` for why it is module-level.\r\nexport { setAssetByteSource, type AssetByteSource } from \"./asset-bytes\";\r\nexport { getCanvasKit } from \"./getter\";\r\n// Re-exported from @motionscript/skia-render, whose canonical home these are —\r\n// kept on this barrel so the specifier consumers already use keeps resolving.\r\nexport { EffectRegistry } from \"@motionscript/skia-render\";\r\nexport type { EffectHandler, EffectGeometry, EffectResources, EffectTarget, RenderEffect } from \"@motionscript/skia-render\";\r\nexport {\r\n exportVideo,\r\n type ExportParams,\r\n type ExportProgressCallback,\r\n type VideoEncoderOptions,\r\n type AudioEncoderOptions,\r\n} from \"./exporter\";\r\n// mediabunny's resolution-aware quality levels, re-exported so a host can pick\r\n// one for `ExportParams.video.bitrate` without taking a direct dependency on the\r\n// encoder library this package happens to use.\r\nexport {\r\n QUALITY_VERY_LOW,\r\n QUALITY_LOW,\r\n QUALITY_MEDIUM,\r\n QUALITY_HIGH,\r\n QUALITY_VERY_HIGH,\r\n type Quality,\r\n} from \"mediabunny\";\r\nexport { exportScreenshot, type ScreenshotParams, type ScreenshotResult, type ScreenshotFormat, type FrameSpec } from \"./screenshot\";\r\n// Thrown by a still and an export alike for a frame whose every track failed to build.\r\nexport { NothingToDrawError } from \"@motionscript/skia-render\";\r\n// Joins separately-encoded MP4s end to end without re-encoding. What makes it\r\n// worth rendering an export in parallel at all: the parts still have to become\r\n// one file, and re-encoding to join them gives back everything the parallelism\r\n// won. See `concat.ts` for the two things a caller has to guarantee.\r\nexport { concatVideoSegments, ConcatMismatchError, type ConcatOptions } from \"./concat\";\r\n// Mixes a timeline's audio without rendering a frame — the other half of a split\r\n// export, whose video arrives in separately-encoded pieces that AAC cannot be\r\n// concatenated across. See `mix-timeline-audio.ts`.\r\nexport { mixTimelineAudio, type MixTimelineAudioParams } from \"./mix-timeline-audio\";\r\n// Single frames as a first-class capability: one long-lived renderer that repaints\r\n// into a canvas the host owns, for thumbnails and still previews. `exportScreenshot`\r\n// above is the one-shot wrapper over it.\r\nexport { StillRenderer, createStillRenderer } from \"./still\";\r\nexport type {\r\n StillRendererOptions,\r\n RenderStillOptions,\r\n EncodeStillOptions,\r\n StillSource,\r\n FrameRef,\r\n} from \"./still\";\r\nexport { WebAudioSink as WebAudioPlayer } from \"./audio/player\";\r\n// Mixdown. Also on the `@motionscript/browser/audio` subpath, which is the\r\n// import to prefer — it reaches it without loading CanvasKit, since none of it\r\n// draws.\r\nexport { mixAudio, encodeWav, type MixAudioOptions } from \"./audio/mixer\";\r\nexport { WebMeasureContext } from \"./measure-context\";\r\nexport { WebTicker } from \"./ticker\";\r\n\r\nexport { createBrowserEngine } from \"./engine\";\r\nexport type { BrowserEngineOptions, BrowserEncodeOptions } from \"./engine\";\r\n\r\n// ─── 3D ──────────────────────────────────────────────────────────────────────\r\n\r\n// The reconciler, handlers and lazy `three` boundary all live in\r\n// @motionscript/skia-render now; this package supplies only the WebGL renderer\r\n// that rasterizes what they build. Re-exported here so the specifiers consumers\r\n// already use keep resolving.\r\nexport { Canvas3DBackend, canvas3DBackend, loadCanvas3D, disposeCanvas3DBackend } from \"@motionscript/skia-render\";\r\nexport type { Canvas3DAssets, RenderedCanvas3D } from \"@motionscript/skia-render\";\r\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"measure-context.js","sourceRoot":"","sources":["../src/measure-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAE/E;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAkB,SAAQ,kBAAkB;CAAI","sourcesContent":["import { SkiaMeasureContext } from \"@motionscript/skia-render/measure-context\";\n\n/**\n * Browser alias for {@link SkiaMeasureContext}.\n *\n * A real subclass rather than a re-export, so `instanceof WebMeasureContext` and the\n * name in a stack trace both still mean something, and so a future\n * browser-specific measurement path has an obvious home. Text measurement itself\n * is pure Skia paragraph layout and needs no platform code at all.\n */\nexport class WebMeasureContext extends SkiaMeasureContext { }\n"]}
|
|
1
|
+
{"version":3,"file":"measure-context.js","sourceRoot":"","sources":["../src/measure-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAE/E;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAkB,SAAQ,kBAAkB;CAAI","sourcesContent":["import { SkiaMeasureContext } from \"@motionscript/skia-render/measure-context\";\r\n\r\n/**\r\n * Browser alias for {@link SkiaMeasureContext}.\r\n *\r\n * A real subclass rather than a re-export, so `instanceof WebMeasureContext` and the\r\n * name in a stack trace both still mean something, and so a future\r\n * browser-specific measurement path has an obvious home. Text measurement itself\r\n * is pure Skia paragraph layout and needs no platform code at all.\r\n */\r\nexport class WebMeasureContext extends SkiaMeasureContext { }\r\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Registry, type AssetManifest, type ProjectDocument, type
|
|
1
|
+
import { Registry, type AssetManifest, type ProjectDocument, type VisualDocument, type Size2D } from "@motionscript/core";
|
|
2
2
|
export interface MixTimelineAudioParams {
|
|
3
3
|
/** A project, or a lone still or composition as the one track of one. */
|
|
4
|
-
document: ProjectDocument |
|
|
4
|
+
document: ProjectDocument | VisualDocument;
|
|
5
5
|
/** The vocabulary its clips compile against. Defaults to what the library ships. */
|
|
6
6
|
registry?: Registry;
|
|
7
7
|
/** Win over the document's own; 1920×1080 at 60 fps when neither says. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mix-timeline-audio.d.ts","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"mix-timeline-audio.d.ts","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,QAAQ,EAER,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,MAAM,EACd,MAAM,oBAAoB,CAAC;AAQ5B,MAAM,WAAW,sBAAsB;IACnC,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,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAID;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,gBAAgB,CAClC,MAAM,EAAE,sBAAsB,GAC/B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA4B7B"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { ManifestAssetCatalog, AssetTimeline, Registry, resolveProjectDocument, } from "@motionscript/core";
|
|
1
|
+
import { DEFAULT_OUTPUT, ManifestAssetCatalog, AssetTimeline, Registry, resolveProjectDocument, } from "@motionscript/core";
|
|
2
2
|
import { collectAudio } from "@motionscript/skia-render/export";
|
|
3
3
|
import { mixAudio } from "./audio/mixer";
|
|
4
4
|
import { getCanvasKit } from "./getter";
|
|
5
5
|
import { WebMeasureContext } from "./measure-context";
|
|
6
|
-
import { DEFAULT_OUTPUT } from "./output";
|
|
7
6
|
import { WebStorageAdapter } from "./storage-adapter";
|
|
8
7
|
const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
|
|
9
8
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mix-timeline-audio.js","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,oBAAoB,EACpB,aAAa,EACb,QAAQ,EACR,sBAAsB,GAKzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAEhE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"mix-timeline-audio.js","sourceRoot":"","sources":["../src/mix-timeline-audio.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,QAAQ,EACR,sBAAsB,GAKzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAEhE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAgBtD,MAAM,cAAc,GAAkB,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAClC,MAA8B;IAE9B,MAAM,EAAE,QAAQ,GAAG,IAAI,QAAQ,EAAE,EAAE,QAAQ,GAAG,cAAc,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAC7F,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAChF,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAS,CAAC;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAI,CAAC;IAEzB,oFAAoF;IACpF,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,IAAI,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,iBAAiB,CAAC,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;IACzE,MAAM,QAAQ,GAAG,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAEhD,qFAAqF;IACrF,MAAM,SAAS,GAAG,IAAI,aAAa,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACxH,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC;QAEhC,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAExC,OAAO,MAAM,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,aAAa,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;IACrF,CAAC;YAAS,CAAC;QACP,SAAS,CAAC,OAAO,EAAE,CAAC;QACpB,0EAA0E;QAC1E,uCAAuC;QACvC,OAAO,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;AACL,CAAC","sourcesContent":["import {\r\n DEFAULT_OUTPUT,\r\n ManifestAssetCatalog,\r\n AssetTimeline,\r\n Registry,\r\n resolveProjectDocument,\r\n type AssetManifest,\r\n type ProjectDocument,\r\n type VisualDocument,\r\n type Size2D,\r\n} from \"@motionscript/core\";\r\nimport { collectAudio } from \"@motionscript/skia-render/export\";\r\n\r\nimport { mixAudio } from \"./audio/mixer\";\r\nimport { getCanvasKit } from \"./getter\";\r\nimport { WebMeasureContext } from \"./measure-context\";\r\nimport { WebStorageAdapter } from \"./storage-adapter\";\r\n\r\nexport interface MixTimelineAudioParams {\r\n /** A project, or a lone still or composition as the one track of one. */\r\n document: ProjectDocument | VisualDocument;\r\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\r\n registry?: Registry;\r\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\r\n viewport?: Size2D;\r\n fps?: number;\r\n manifest?: AssetManifest;\r\n /** Mixdown rate. Defaults to the mixer's own (44100). */\r\n sampleRate?: number;\r\n wasmUrl?: string;\r\n}\r\n\r\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\r\n\r\n/**\r\n * Mix a timeline's audio without rendering a single frame.\r\n *\r\n * `exportVideo` normally does this as part of the render, and for a\r\n * single-threaded export that is exactly right. A **split** export cannot: its\r\n * video is rendered in pieces by separate workers, and concatenating\r\n * separately-encoded AAC puts the encoder's priming delay at every join — a click\r\n * at each boundary and a track that drifts further out of sync with every one. So\r\n * the coordinator mixes the whole timeline once, here, and the joined file gets a\r\n * single continuous audio track.\r\n *\r\n * Returns `null` when the timeline schedules no audio at all, which is the common\r\n * case and not a failure — the caller should then mux video only rather than\r\n * declare an empty track.\r\n *\r\n * ## Why this discovers\r\n *\r\n * Audio requests are an *output of discovery*: each node declares its clips as it\r\n * enters the tree, and the pass that finds what a clip's frames draw is the one\r\n * that puts its nodes in the tree, so there is no way to know what plays when\r\n * without it.\r\n *\r\n * Nothing is drawn, so this takes a {@link WebMeasureContext} rather than a render\r\n * context: discovery lays each clip out, which needs text measured, not\r\n * rasterized. No canvas, no surface, no GPU.\r\n */\r\nexport async function mixTimelineAudio(\r\n params: MixTimelineAudioParams,\r\n): Promise<AudioBuffer | null> {\r\n const { registry = new Registry(), manifest = EMPTY_MANIFEST, sampleRate, wasmUrl } = params;\r\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\r\n if (project.tracks.length === 0) return null;\r\n const viewport = project.viewport!;\r\n const fps = project.fps!;\r\n\r\n // CanvasKit only for its font manager — `WebMeasureContext` shapes text through it.\r\n const canvasKit = await getCanvasKit(wasmUrl);\r\n const catalog = new ManifestAssetCatalog(manifest);\r\n const storage = new WebStorageAdapter(canvasKit, catalog, viewport, fps);\r\n const measurer = new WebMeasureContext(storage);\r\n\r\n // Audio is declared from each node's arrival state, so no asset windows are refined.\r\n const precomper = new AssetTimeline(project, { registry, viewport, fps, assets: catalog, measurer }, { assets: false });\r\n try {\r\n const precomp = precomper.run();\r\n\r\n const scheduled = collectAudio(precomp);\r\n if (scheduled.length === 0) return null;\r\n\r\n return await mixAudio(scheduled, precomp.totalDuration, { sampleRate, catalog });\r\n } finally {\r\n precomper.dispose();\r\n // The adapter holds decode sessions and cached images; without this a mix\r\n // leaks them for the life of the page.\r\n storage.dispose();\r\n }\r\n}\r\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAE7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,gBAAiB,SAAQ,iBAAiB;IACnD,YAAY,SAAoB,EAAE,cAAiC;QAC/D,KAAK,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;QACjC,kBAAkB,EAAE,CAAC;IACzB,CAAC;IAED,sGAAsG;IACtG,KAAK,CAAC,MAAyB;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;QAC9D,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACpE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED;;;OAGG;IACM,OAAO;QACZ,IAAI,CAAC,MAAM,EAAE,CAAC;IAClB,CAAC;IAED;;;;OAIG;IACM,UAAU,CAAC,OAAe,WAAW,EAAE,OAAgB;QAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACvC,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAC;QAChC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;QAE3C,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,4EAA4E;QAC5E,uCAAuC;QACvC,GAAG,CAAC,YAAY,CAAC,IAAI,SAAS,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACpF,wEAAwE;QACxE,uEAAuE;QACvE,2DAA2D;QAC3D,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;CACJ","sourcesContent":["import type { CanvasKit } from \"@motionscript/canvaskit\";\nimport { SkiaRenderContext } from \"@motionscript/skia-render/render-context\";\nimport type { WebStorageAdapter } from \"./storage-adapter\";\nimport { installWebCanvas3D } from \"./three/renderer\";\n\n/**\n * Browser bindings for {@link SkiaRenderContext}.\n *\n * Everything about *how a frame is drawn* — shapes, fills, strokes, text, effects,\n * clips, masks, the camera, the 3D composite — lives in\n * `@motionscript/skia-render` and is shared with every other backend. What is\n * genuinely browser-specific is only what is below: the WebGL renderer 3D draws\n * with (installed on construction, so every path that draws has it), creating a\n * WebGL-backed Skia surface over a `<canvas>`, and encoding a snapshot (this\n * CanvasKit build ships no wasm image encoders, so the encode has to come from\n * the host).\n */\nexport class WebRenderContext extends SkiaRenderContext {\n constructor(canvasKit: CanvasKit, storageAdapter: WebStorageAdapter) {\n super(canvasKit, storageAdapter);\n installWebCanvas3D();\n }\n\n /** Attaches a WebGL CanvasKit surface to `canvas`, remounting if already mounted (HMR/StrictMode). */\n mount(canvas: HTMLCanvasElement): void {\n const surface = this.canvasKit.MakeWebGLCanvasSurface(canvas);\n if (!surface) throw new Error(\"Failed to create CanvasKit surface\");\n this.attach(surface);\n }\n\n /**\n * Core declares `unmount()`; it is `detach()` on the portable base, since\n * \"unmount\" only means anything where there is a DOM element to unmount from.\n */\n override unmount(): void {\n this.detach();\n }\n\n /**\n * Snapshots the current surface and encodes it as an image data URL through a\n * 2D canvas. Defaults to PNG; pass `mime`/`quality` (e.g. `\"image/jpeg\", 0.9`)\n * for other formats.\n */\n override screenshot(mime: string = \"image/png\", quality?: number): string | undefined {\n const snapshot = this.snapshotPixels();\n if (!snapshot) return undefined;\n const { pixels, width, height } = snapshot;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return undefined;\n // `pixels` is already unpremultiplied RGBA8888 copied out of the wasm heap,\n // which is exactly ImageData's layout.\n ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels), width, height), 0, 0);\n // A data: URL rather than a blob: URL — both work as an <img> src and a\n // data URL needs no revoke. `quality` is honored only by lossy formats\n // (e.g. image/jpeg); the browser ignores it for image/png.\n return canvas.toDataURL(mime, quality);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"render-context.js","sourceRoot":"","sources":["../src/render-context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAE7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,gBAAiB,SAAQ,iBAAiB;IACnD,YAAY,SAAoB,EAAE,cAAiC;QAC/D,KAAK,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;QACjC,kBAAkB,EAAE,CAAC;IACzB,CAAC;IAED,sGAAsG;IACtG,KAAK,CAAC,MAAyB;QAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;QAC9D,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACpE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED;;;OAGG;IACM,OAAO;QACZ,IAAI,CAAC,MAAM,EAAE,CAAC;IAClB,CAAC;IAED;;;;OAIG;IACM,UAAU,CAAC,OAAe,WAAW,EAAE,OAAgB;QAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACvC,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAC;QAChC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;QAE3C,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,4EAA4E;QAC5E,uCAAuC;QACvC,GAAG,CAAC,YAAY,CAAC,IAAI,SAAS,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACpF,wEAAwE;QACxE,uEAAuE;QACvE,2DAA2D;QAC3D,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;CACJ","sourcesContent":["import type { CanvasKit } from \"@motionscript/canvaskit\";\r\nimport { SkiaRenderContext } from \"@motionscript/skia-render/render-context\";\r\nimport type { WebStorageAdapter } from \"./storage-adapter\";\r\nimport { installWebCanvas3D } from \"./three/renderer\";\r\n\r\n/**\r\n * Browser bindings for {@link SkiaRenderContext}.\r\n *\r\n * Everything about *how a frame is drawn* — shapes, fills, strokes, text, effects,\r\n * clips, masks, the camera, the 3D composite — lives in\r\n * `@motionscript/skia-render` and is shared with every other backend. What is\r\n * genuinely browser-specific is only what is below: the WebGL renderer 3D draws\r\n * with (installed on construction, so every path that draws has it), creating a\r\n * WebGL-backed Skia surface over a `<canvas>`, and encoding a snapshot (this\r\n * CanvasKit build ships no wasm image encoders, so the encode has to come from\r\n * the host).\r\n */\r\nexport class WebRenderContext extends SkiaRenderContext {\r\n constructor(canvasKit: CanvasKit, storageAdapter: WebStorageAdapter) {\r\n super(canvasKit, storageAdapter);\r\n installWebCanvas3D();\r\n }\r\n\r\n /** Attaches a WebGL CanvasKit surface to `canvas`, remounting if already mounted (HMR/StrictMode). */\r\n mount(canvas: HTMLCanvasElement): void {\r\n const surface = this.canvasKit.MakeWebGLCanvasSurface(canvas);\r\n if (!surface) throw new Error(\"Failed to create CanvasKit surface\");\r\n this.attach(surface);\r\n }\r\n\r\n /**\r\n * Core declares `unmount()`; it is `detach()` on the portable base, since\r\n * \"unmount\" only means anything where there is a DOM element to unmount from.\r\n */\r\n override unmount(): void {\r\n this.detach();\r\n }\r\n\r\n /**\r\n * Snapshots the current surface and encodes it as an image data URL through a\r\n * 2D canvas. Defaults to PNG; pass `mime`/`quality` (e.g. `\"image/jpeg\", 0.9`)\r\n * for other formats.\r\n */\r\n override screenshot(mime: string = \"image/png\", quality?: number): string | undefined {\r\n const snapshot = this.snapshotPixels();\r\n if (!snapshot) return undefined;\r\n const { pixels, width, height } = snapshot;\r\n\r\n const canvas = document.createElement(\"canvas\");\r\n canvas.width = width;\r\n canvas.height = height;\r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) return undefined;\r\n // `pixels` is already unpremultiplied RGBA8888 copied out of the wasm heap,\r\n // which is exactly ImageData's layout.\r\n ctx.putImageData(new ImageData(new Uint8ClampedArray(pixels), width, height), 0, 0);\r\n // A data: URL rather than a blob: URL — both work as an <img> src and a\r\n // data URL needs no revoke. `quality` is honored only by lossy formats\r\n // (e.g. image/jpeg); the browser ignores it for image/png.\r\n return canvas.toDataURL(mime, quality);\r\n }\r\n}\r\n"]}
|
package/dist/screenshot.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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 "@motionscript/skia-render/export";
|
|
3
3
|
import { type FrameRef } from "./still";
|
|
4
4
|
export type { ScreenshotFormat, FrameSpec };
|
|
5
5
|
export type ScreenshotParams = {
|
|
6
6
|
/** A project, or a lone still or composition as the one track of one. */
|
|
7
|
-
document: ProjectDocument |
|
|
7
|
+
document: ProjectDocument | VisualDocument;
|
|
8
8
|
/** The vocabulary its clips compile against. Defaults to what the library ships. */
|
|
9
9
|
registry?: Registry;
|
|
10
10
|
/** Win over the document's own; 1920×1080 at 60 fps when neither says. */
|
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,kCAAkC,CAAC;AACzE,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GAAG;IAC3B,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,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,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.
|
package/dist/screenshot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,sBAAsB,
|
|
1
|
+
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,cAAc,EAAE,sBAAsB,GAEzC,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAiC7D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAwB;IAC3D,MAAM,EACF,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EACzC,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,OAAO,EAAE,QAAQ;KAC1F,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 {\r\n DEFAULT_OUTPUT, resolveProjectDocument,\r\n type AssetManifest, type FrameSpec, type ProjectDocument, type Registry, type VisualDocument, type Size2D, type Theme,\r\n} from \"@motionscript/core\";\r\nimport type { ScreenshotFormat } from \"@motionscript/skia-render/export\";\r\nimport { createStillRenderer, type FrameRef } from \"./still\";\r\n\r\nexport type { ScreenshotFormat, FrameSpec };\r\n\r\nexport type ScreenshotParams = {\r\n /** A project, or a lone still or composition as the one track of one. */\r\n document: ProjectDocument | VisualDocument;\r\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\r\n registry?: Registry;\r\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\r\n viewport?: Size2D;\r\n fps?: number;\r\n scale?: number;\r\n manifest?: AssetManifest;\r\n /** Theme for a document that carries none of its own. */\r\n theme?: Theme;\r\n wasmUrl?: string;\r\n /** Which frame to capture. Defaults to the timeline's first. */\r\n frame?: FrameRef;\r\n /** Encoding format (default \"png\"). */\r\n format?: ScreenshotFormat;\r\n /** JPEG quality in [0,1] (ignored for png; default 0.92). */\r\n quality?: number;\r\n};\r\n\r\nexport type ScreenshotResult = {\r\n /** The project frame that was captured, after clamping. */\r\n frame: number;\r\n totalFrames: number;\r\n /** Encoded image bytes. */\r\n bytes: Uint8Array;\r\n};\r\n\r\n/**\r\n * Renders a single frame of a document to an image.\r\n *\r\n * A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.\r\n * It therefore takes the same path as a live preview, and as the video export at\r\n * that frame — there is one renderer, not three.\r\n *\r\n * For anything that captures **repeatedly** — a thumbnail builder, a preview that\r\n * repaints as the user edits — hold a {@link StillRenderer} instead. This function\r\n * builds and tears down a WebGL surface per call, which is the right trade for one\r\n * capture and the wrong one for a hundred.\r\n */\r\nexport async function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult> {\r\n const {\r\n registry, scale, manifest, theme, wasmUrl,\r\n frame, format = \"png\", quality = 0.92,\r\n } = params;\r\n\r\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\r\n if (project.tracks.length === 0) throw new Error(\"The project has no tracks to screenshot.\");\r\n\r\n const renderer = await createStillRenderer({\r\n viewport: project.viewport, fps: project.fps, scale, manifest, theme, wasmUrl, registry,\r\n });\r\n\r\n try {\r\n const drawn = await renderer.render(project, { frame });\r\n return { ...drawn, bytes: await renderer.toBytes({ format, quality }) };\r\n } finally {\r\n renderer.dispose();\r\n }\r\n}\r\n"]}
|
package/dist/still.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Registry, type AssetManifest, type ProjectDocument, type
|
|
2
|
-
import { type DrawnFrame, type
|
|
1
|
+
import { Registry, type AssetManifest, type FrameSpec, type ProjectDocument, type VisualDocument, type Size2D, type Theme } from "@motionscript/core";
|
|
2
|
+
import { type DrawnFrame, type ScreenshotFormat } from "@motionscript/skia-render/export";
|
|
3
3
|
import { getCanvasKit } from "./getter";
|
|
4
4
|
/** 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 |
|
|
5
|
+
export type StillSource = ProjectDocument | VisualDocument;
|
|
6
6
|
/**
|
|
7
7
|
* Which frame to draw, in whatever form is most convenient — a global frame
|
|
8
8
|
* index, the timeline's first or last frame, or a `FrameSpec`.
|
|
@@ -55,7 +55,7 @@ export interface EncodeStillOptions {
|
|
|
55
55
|
quality?: number;
|
|
56
56
|
}
|
|
57
57
|
/** Normalize the many ways a caller can name a frame into the engine's own form. */
|
|
58
|
-
export declare function
|
|
58
|
+
export declare function stillFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec;
|
|
59
59
|
/**
|
|
60
60
|
* A long-lived single-frame renderer.
|
|
61
61
|
*
|
|
@@ -108,6 +108,8 @@ export declare class StillRenderer {
|
|
|
108
108
|
private theme?;
|
|
109
109
|
private readonly registry;
|
|
110
110
|
private disposed;
|
|
111
|
+
/** False until a frame has been drawn onto the current surface; a remount clears it. */
|
|
112
|
+
private drawn;
|
|
111
113
|
/** @internal Use {@link createStillRenderer}, which loads CanvasKit first. */
|
|
112
114
|
constructor(canvasKit: Awaited<ReturnType<typeof getCanvasKit>>, options: StillRendererOptions);
|
|
113
115
|
get viewport(): Size2D;
|
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,13 @@
|
|
|
1
|
-
import { ManifestAssetCatalog, Registry, resolveProjectDocument, } from "@motionscript/core";
|
|
1
|
+
import { DEFAULT_OUTPUT, ManifestAssetCatalog, frameSpecAtTime, Registry, resolveProjectDocument, } from "@motionscript/core";
|
|
2
2
|
import { drawFrameAt, } from "@motionscript/skia-render/export";
|
|
3
3
|
import { WebRenderContext } from "./render-context";
|
|
4
4
|
import { WebStorageAdapter } from "./storage-adapter";
|
|
5
5
|
import { getCanvasKit } from "./getter";
|
|
6
|
-
import { DEFAULT_OUTPUT } from "./output";
|
|
7
6
|
const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
|
|
8
7
|
/** 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
|
-
}
|
|
8
|
+
export function stillFrameSpec(options, fps) {
|
|
9
|
+
if (options?.time !== undefined)
|
|
10
|
+
return frameSpecAtTime(options.time, fps);
|
|
13
11
|
const frame = options?.frame;
|
|
14
12
|
if (frame === undefined || frame === "first")
|
|
15
13
|
return { kind: "first" };
|
|
@@ -74,6 +72,8 @@ export class StillRenderer {
|
|
|
74
72
|
theme;
|
|
75
73
|
registry;
|
|
76
74
|
disposed = false;
|
|
75
|
+
/** False until a frame has been drawn onto the current surface; a remount clears it. */
|
|
76
|
+
drawn = false;
|
|
77
77
|
/** @internal Use {@link createStillRenderer}, which loads CanvasKit first. */
|
|
78
78
|
constructor(canvasKit, options) {
|
|
79
79
|
this.canvasKit = canvasKit;
|
|
@@ -109,7 +109,7 @@ export class StillRenderer {
|
|
|
109
109
|
if (scale !== this.surfaceScale)
|
|
110
110
|
this.remount(scale);
|
|
111
111
|
const project = resolveProjectDocument(source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue });
|
|
112
|
-
|
|
112
|
+
const frame = await drawFrameAt({
|
|
113
113
|
project,
|
|
114
114
|
registry: this.registry,
|
|
115
115
|
theme: this.theme,
|
|
@@ -117,8 +117,10 @@ export class StillRenderer {
|
|
|
117
117
|
renderContext: this.renderContext,
|
|
118
118
|
storageAdapter: this.storageAdapter,
|
|
119
119
|
assetCatalog: this.catalog,
|
|
120
|
-
frame:
|
|
120
|
+
frame: stillFrameSpec(options, project.fps),
|
|
121
121
|
});
|
|
122
|
+
this.drawn = true;
|
|
123
|
+
return frame;
|
|
122
124
|
}
|
|
123
125
|
/** Theme for a document that carries none of its own. */
|
|
124
126
|
setTheme(theme) { this.theme = theme; }
|
|
@@ -186,7 +188,7 @@ export class StillRenderer {
|
|
|
186
188
|
*/
|
|
187
189
|
snapshotToCanvas() {
|
|
188
190
|
this.assertLive();
|
|
189
|
-
const snapshot = this.renderContext.snapshotPixels();
|
|
191
|
+
const snapshot = this.drawn ? this.renderContext.snapshotPixels() : undefined;
|
|
190
192
|
if (!snapshot)
|
|
191
193
|
throw new Error("Nothing has been rendered yet.");
|
|
192
194
|
const canvas = document.createElement("canvas");
|
|
@@ -206,6 +208,7 @@ export class StillRenderer {
|
|
|
206
208
|
/** Resize the backing store and build a fresh Skia surface over it. */
|
|
207
209
|
remount(scale) {
|
|
208
210
|
this.surfaceScale = scale;
|
|
211
|
+
this.drawn = false;
|
|
209
212
|
this.sizeCanvas();
|
|
210
213
|
this.renderContext.mount(this.canvas);
|
|
211
214
|
}
|