@motionscript/browser 0.1.0-alpha.6 → 0.1.0-alpha.8
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 +12 -0
- package/NOTICE +15 -14
- package/README.md +66 -43
- package/dist/asset-bytes.d.ts +7 -4
- package/dist/asset-bytes.d.ts.map +1 -1
- package/dist/asset-bytes.js +9 -4
- package/dist/asset-bytes.js.map +1 -1
- package/dist/audio/bus-graph.d.ts.map +1 -1
- package/dist/audio/bus-graph.js +15 -6
- package/dist/audio/bus-graph.js.map +1 -1
- package/dist/audio/effect-graph.d.ts +62 -0
- package/dist/audio/effect-graph.d.ts.map +1 -0
- package/dist/audio/effect-graph.js +234 -0
- package/dist/audio/effect-graph.js.map +1 -0
- package/dist/audio/index.d.ts.map +1 -1
- package/dist/audio/index.js +2 -3
- package/dist/audio/index.js.map +1 -1
- package/dist/audio/mixer.d.ts +4 -4
- package/dist/audio/mixer.d.ts.map +1 -1
- package/dist/audio/mixer.js +18 -38
- package/dist/audio/mixer.js.map +1 -1
- package/dist/audio/player.d.ts +12 -6
- package/dist/audio/player.d.ts.map +1 -1
- package/dist/audio/player.js +49 -62
- package/dist/audio/player.js.map +1 -1
- package/dist/browser/THIRD_PARTY_LICENSES.txt +379 -27
- package/dist/browser/audio.js +1 -1
- package/dist/browser/chunks/chunk-IILVN4SK.js +2 -0
- package/dist/browser/chunks/chunk-IILVN4SK.js.map +7 -0
- package/dist/browser/chunks/chunk-KLXMRKTL.js +2 -0
- package/dist/browser/chunks/chunk-L5UFQACH.js +163 -0
- package/dist/browser/chunks/chunk-L5UFQACH.js.map +7 -0
- package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js +14 -0
- package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js.map +7 -0
- package/dist/browser/index.js +51 -1343
- package/dist/browser/index.js.map +4 -4
- package/dist/browser/manifest.json +8 -8
- package/dist/browser/ms_renderer_bg.wasm +0 -0
- package/dist/canvaskit-renderer.d.ts +8 -0
- package/dist/canvaskit-renderer.d.ts.map +1 -0
- package/dist/canvaskit-renderer.js +23 -0
- package/dist/canvaskit-renderer.js.map +1 -0
- package/dist/concat.d.ts +33 -7
- package/dist/concat.d.ts.map +1 -1
- package/dist/concat.js +117 -72
- package/dist/concat.js.map +1 -1
- package/dist/deliver.d.ts +10 -0
- package/dist/deliver.d.ts.map +1 -0
- package/dist/deliver.js +22 -0
- package/dist/deliver.js.map +1 -0
- package/dist/dynamic-nodes.d.ts.map +1 -1
- package/dist/dynamic-nodes.js +2 -3
- package/dist/dynamic-nodes.js.map +1 -1
- package/dist/engine.d.ts +37 -9
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +41 -32
- package/dist/engine.js.map +1 -1
- package/dist/export-audio.d.ts +36 -0
- package/dist/export-audio.d.ts.map +1 -0
- package/dist/export-audio.js +56 -0
- package/dist/export-audio.js.map +1 -0
- package/dist/exporter.d.ts +125 -14
- package/dist/exporter.d.ts.map +1 -1
- package/dist/exporter.js +133 -89
- package/dist/exporter.js.map +1 -1
- package/dist/formats.d.ts +36 -0
- package/dist/formats.d.ts.map +1 -0
- package/dist/formats.js +83 -0
- package/dist/formats.js.map +1 -0
- package/dist/frame-loop.d.ts +190 -0
- package/dist/frame-loop.d.ts.map +1 -0
- package/dist/frame-loop.js +304 -0
- package/dist/frame-loop.js.map +1 -0
- package/dist/getter.d.ts +4 -1
- package/dist/getter.d.ts.map +1 -1
- package/dist/getter.js +7 -6
- package/dist/getter.js.map +1 -1
- package/dist/index.d.ts +12 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -21
- package/dist/index.js.map +1 -1
- package/dist/mix-timeline-audio.d.ts +5 -5
- package/dist/mix-timeline-audio.d.ts.map +1 -1
- package/dist/mix-timeline-audio.js +10 -12
- package/dist/mix-timeline-audio.js.map +1 -1
- package/dist/parallel/export-parallel.d.ts +32 -0
- package/dist/parallel/export-parallel.d.ts.map +1 -0
- package/dist/parallel/export-parallel.js +114 -0
- package/dist/parallel/export-parallel.js.map +1 -0
- package/dist/parallel/governor.d.ts +57 -0
- package/dist/parallel/governor.d.ts.map +1 -0
- package/dist/parallel/governor.js +79 -0
- package/dist/parallel/governor.js.map +1 -0
- package/dist/parallel/lane.d.ts +45 -0
- package/dist/parallel/lane.d.ts.map +1 -0
- package/dist/parallel/lane.js +2 -0
- package/dist/parallel/lane.js.map +1 -0
- package/dist/parallel/lanes.d.ts +20 -0
- package/dist/parallel/lanes.d.ts.map +1 -0
- package/dist/parallel/lanes.js +152 -0
- package/dist/parallel/lanes.js.map +1 -0
- package/dist/parallel/plan.d.ts +23 -0
- package/dist/parallel/plan.d.ts.map +1 -0
- package/dist/parallel/plan.js +30 -0
- package/dist/parallel/plan.js.map +1 -0
- package/dist/parallel/run-lanes.d.ts +38 -0
- package/dist/parallel/run-lanes.d.ts.map +1 -0
- package/dist/parallel/run-lanes.js +164 -0
- package/dist/parallel/run-lanes.js.map +1 -0
- package/dist/parallel/session.d.ts +9 -0
- package/dist/parallel/session.d.ts.map +1 -0
- package/dist/parallel/session.js +79 -0
- package/dist/parallel/session.js.map +1 -0
- package/dist/render-context.d.ts +2 -0
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js +8 -4
- package/dist/render-context.js.map +1 -1
- package/dist/renderer.d.ts +22 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +23 -0
- package/dist/renderer.js.map +1 -0
- package/dist/screenshot.d.ts +4 -4
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +3 -3
- package/dist/screenshot.js.map +1 -1
- package/dist/still.d.ts +17 -19
- package/dist/still.d.ts.map +1 -1
- package/dist/still.js +27 -34
- package/dist/still.js.map +1 -1
- package/dist/ticker.d.ts +22 -8
- package/dist/ticker.d.ts.map +1 -1
- package/dist/ticker.js +55 -20
- package/dist/ticker.js.map +1 -1
- package/package.json +9 -10
- package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js +0 -2
- package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js.map +0 -7
- package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js +0 -2
- package/dist/browser/chunks/chunk-2MAFJWBZ.js +0 -2
- package/dist/browser/chunks/chunk-2MAFJWBZ.js.map +0 -7
- package/dist/browser/chunks/chunk-CTLBXZSU.js +0 -4119
- package/dist/browser/chunks/chunk-CTLBXZSU.js.map +0 -7
- package/dist/browser/chunks/chunk-FQ32PDIU.js +0 -2
- package/dist/browser/chunks/chunk-FQ32PDIU.js.map +0 -7
- package/dist/browser/chunks/chunk-FY3BTKY4.js +0 -2
- package/dist/browser/chunks/chunk-FY3BTKY4.js.map +0 -7
- package/dist/browser/chunks/three.module-Z4EOVUWF.js +0 -2
- package/dist/browser/chunks/three.module-Z4EOVUWF.js.map +0 -7
- /package/dist/browser/chunks/{SkeletonUtils-XYDMTGTM.js.map → chunk-KLXMRKTL.js.map} +0 -0
package/dist/exporter.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { Registry, type AssetManifest, type FrameRange, type ProjectDocument, type VisualDocument, type Size2D, type Theme } from "@motionscript/core";
|
|
2
|
-
import { type
|
|
1
|
+
import { ManifestAssetCatalog, Registry, type AssetManifest, type FrameRange, type ProjectDocument, type RenderIssue, type VisualDocument, type Size2D, type StorageAdapter, type Theme } from "@motionscript/core";
|
|
2
|
+
import { type VideoFrameSink } from "./frame-loop";
|
|
3
|
+
import { type Quality, type VideoCodec } from 'mediabunny';
|
|
4
|
+
import type { BrowserRenderContext } from "./engine";
|
|
5
|
+
import { type RendererWasmOption } from "./renderer";
|
|
6
|
+
import { type VideoFormat } from "./formats";
|
|
3
7
|
/** Reports export progress in [0, 1]; video encoding occupies most of the range, audio mixing/finalize the tail. */
|
|
4
8
|
export type ExportProgressCallback = (progress: number) => void;
|
|
5
9
|
/**
|
|
@@ -10,17 +14,19 @@ export type ExportProgressCallback = (progress: number) => void;
|
|
|
10
14
|
*/
|
|
11
15
|
export type VideoEncoderOptions = {
|
|
12
16
|
/**
|
|
13
|
-
* Which codec the video track is encoded with. Defaults to
|
|
14
|
-
* which every player, editor and upload target
|
|
17
|
+
* Which codec the video track is encoded with. Defaults to the format's own:
|
|
18
|
+
* `'avc'` (H.264) for MP4, which every player, editor and upload target
|
|
19
|
+
* accepts, and `'vp9'` for WebM.
|
|
15
20
|
*
|
|
16
21
|
* `'hevc'` and `'av1'` reconstruct sharp edges far better at the same
|
|
17
22
|
* bitrate, which is what a listing of syntax-highlighted code or a hairline
|
|
18
23
|
* stroke is made of — worth reaching for when the file is going somewhere
|
|
19
24
|
* that decodes them. Both are still 4:2:0, so they narrow the gap rather
|
|
20
25
|
* than close it. Availability depends on the platform's encoders; an
|
|
21
|
-
* unsupported choice
|
|
26
|
+
* unsupported choice, or one the container cannot hold (`'avc'` in WebM),
|
|
27
|
+
* falls back to the format's default rather than failing the export.
|
|
22
28
|
*/
|
|
23
|
-
codec?: 'avc' | 'hevc' | 'av1' | 'vp9';
|
|
29
|
+
codec?: 'avc' | 'hevc' | 'av1' | 'vp9' | 'vp8';
|
|
24
30
|
/**
|
|
25
31
|
* Which encoder implementation WebCodecs should prefer.
|
|
26
32
|
*
|
|
@@ -88,12 +94,12 @@ export type VideoEncoderOptions = {
|
|
|
88
94
|
export declare function motionGraphicsBitrate(width: number, height: number, fps: number): number;
|
|
89
95
|
/** How the mixed audio track is encoded. */
|
|
90
96
|
export type AudioEncoderOptions = {
|
|
91
|
-
/**
|
|
97
|
+
/** Bitrate in bits per second for AAC, Opus and MP3; WAV ignores it. Defaults to 192 kbps. */
|
|
92
98
|
bitrate?: number;
|
|
93
|
-
/** Mixdown sample rate in Hz. Defaults to the mixer's own (44100). */
|
|
99
|
+
/** Mixdown sample rate in Hz. Defaults to the mixer's own (44100); Opus is resampled to 48 kHz. */
|
|
94
100
|
sampleRate?: number;
|
|
95
101
|
};
|
|
96
|
-
export type ExportParams = {
|
|
102
|
+
export type ExportParams = RendererWasmOption & {
|
|
97
103
|
/** A project, or a lone still or composition as the one track of one. */
|
|
98
104
|
document: ProjectDocument | VisualDocument;
|
|
99
105
|
/** The vocabulary its clips compile against. Defaults to what the library ships. */
|
|
@@ -102,12 +108,15 @@ export type ExportParams = {
|
|
|
102
108
|
viewport?: Size2D;
|
|
103
109
|
fps?: number;
|
|
104
110
|
scale?: number;
|
|
111
|
+
/** The container, and with it the default codecs: H.264 and AAC for `'mp4'` (the default), VP9 and Opus for `'webm'`. */
|
|
112
|
+
format?: VideoFormat;
|
|
113
|
+
/** Defaults to `export.<extension>`. */
|
|
105
114
|
filename?: string;
|
|
106
115
|
manifest?: AssetManifest;
|
|
107
116
|
/** Theme for a document that carries none of its own. */
|
|
108
117
|
theme?: Theme;
|
|
109
118
|
/**
|
|
110
|
-
* Set false to produce a silent
|
|
119
|
+
* Set false to produce a silent file — no audio track is added to the
|
|
111
120
|
* container and the mix is skipped entirely. Defaults to true.
|
|
112
121
|
*/
|
|
113
122
|
includeAudio?: boolean;
|
|
@@ -123,7 +132,12 @@ export type ExportParams = {
|
|
|
123
132
|
frames?: FrameRange;
|
|
124
133
|
onProgress?: ExportProgressCallback;
|
|
125
134
|
signal?: AbortSignal;
|
|
126
|
-
|
|
135
|
+
/**
|
|
136
|
+
* What the renderer drew but not as the document says, once per kind,
|
|
137
|
+
* feature and node. A lost GPU device fails the export with a
|
|
138
|
+
* `RenderBackendError` instead.
|
|
139
|
+
*/
|
|
140
|
+
onIssue?: (issue: RenderIssue) => void;
|
|
127
141
|
/**
|
|
128
142
|
* Render each frame at this multiple of the output resolution and downsample
|
|
129
143
|
* it before encoding. **Defaults to 2**; pass 1 to turn it off.
|
|
@@ -150,20 +164,117 @@ export type ExportParams = {
|
|
|
150
164
|
*/
|
|
151
165
|
supersample?: number;
|
|
152
166
|
/**
|
|
153
|
-
* When true, the encoded
|
|
167
|
+
* When true, the encoded bytes are returned instead of triggering a
|
|
154
168
|
* browser download. Used by headless drivers (e.g. the CLI) that capture
|
|
155
169
|
* the output themselves and write it to disk; leave unset for the
|
|
156
170
|
* interactive player, which downloads via an `<a>` element.
|
|
157
171
|
*/
|
|
158
172
|
returnBytes?: boolean;
|
|
159
173
|
};
|
|
174
|
+
/**
|
|
175
|
+
* mediabunny/WebCodecs implementation of the frame sink, writing whichever
|
|
176
|
+
* container `format` names.
|
|
177
|
+
*
|
|
178
|
+
* Wraps the very `<canvas>` the renderer draws into, so `addFrame` costs nothing but
|
|
179
|
+
* a timestamp — WebCodecs pulls the pixels from the canvas itself, with no
|
|
180
|
+
* GPU→CPU readback. That is why the seam signals a frame rather than passing one.
|
|
181
|
+
*
|
|
182
|
+
* The readback claim holds only while the encoder is a hardware one, which is
|
|
183
|
+
* why {@link VideoEncoderOptions.hardwareAcceleration} defaults to letting the
|
|
184
|
+
* platform choose: a software encoder has to be handed CPU pixels, so it drags
|
|
185
|
+
* a full-frame copy off the GPU on every single frame.
|
|
186
|
+
*/
|
|
187
|
+
export declare class MediabunnyVideoSink implements VideoFrameSink<AudioBuffer> {
|
|
188
|
+
private readonly format;
|
|
189
|
+
private readonly audioOptions;
|
|
190
|
+
private readonly target;
|
|
191
|
+
private readonly output;
|
|
192
|
+
private readonly video;
|
|
193
|
+
private audio;
|
|
194
|
+
constructor(canvas: HTMLCanvasElement,
|
|
195
|
+
/** Encoded output size, which is the canvas' own unless supersampling is on. */
|
|
196
|
+
output: {
|
|
197
|
+
width: number;
|
|
198
|
+
height: number;
|
|
199
|
+
}, format: VideoFormat, encoding: ResolvedVideoEncoding, audioOptions?: AudioEncoderOptions);
|
|
200
|
+
start(info: {
|
|
201
|
+
fps: number;
|
|
202
|
+
hasAudio: boolean;
|
|
203
|
+
}): Promise<void>;
|
|
204
|
+
addFrame(timestamp: number, duration: number): Promise<void>;
|
|
205
|
+
addAudio(mixed: AudioBuffer): Promise<void>;
|
|
206
|
+
finalize(): Promise<Uint8Array>;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Every encoder setting, decided. A split export pins one of these across its
|
|
210
|
+
* segments, since `concatVideoSegments` refuses parts encoded differently. With
|
|
211
|
+
* a numeric `bitrate` (see {@link plainVideoEncoding}) it is plain data and
|
|
212
|
+
* crosses a process boundary.
|
|
213
|
+
*/
|
|
214
|
+
export interface ResolvedVideoEncoding {
|
|
215
|
+
codec: VideoCodec;
|
|
216
|
+
/** A `Quality` is left for the encoder to size, as an export always has. */
|
|
217
|
+
bitrate: number | Quality;
|
|
218
|
+
hardwareAcceleration: NonNullable<VideoEncoderOptions['hardwareAcceleration']>;
|
|
219
|
+
keyFrameInterval: number;
|
|
220
|
+
latencyMode: NonNullable<VideoEncoderOptions['latencyMode']>;
|
|
221
|
+
contentHint: string;
|
|
222
|
+
bitrateMode?: VideoEncoderOptions['bitrateMode'];
|
|
223
|
+
}
|
|
224
|
+
/** {@link VideoEncoderOptions} with its defaults applied against the encoded size and rate. */
|
|
225
|
+
export declare function resolveVideoEncoding(video: VideoEncoderOptions | undefined, format: VideoFormat, output: {
|
|
226
|
+
width: number;
|
|
227
|
+
height: number;
|
|
228
|
+
}, fps: number): Promise<ResolvedVideoEncoding>;
|
|
229
|
+
/**
|
|
230
|
+
* The encoding with a `Quality` turned into the bits per second mediabunny would
|
|
231
|
+
* give it at this size, since a class instance cannot cross a `MessagePort`.
|
|
232
|
+
*/
|
|
233
|
+
export declare function plainVideoEncoding(encoding: ResolvedVideoEncoding, output: {
|
|
234
|
+
width: number;
|
|
235
|
+
height: number;
|
|
236
|
+
}): ResolvedVideoEncoding & {
|
|
237
|
+
bitrate: number;
|
|
238
|
+
};
|
|
239
|
+
/** The encoded size, rounded up to even, and how far each frame is supersampled. */
|
|
240
|
+
export declare function exportGeometry(viewport: Size2D, scale: number, supersample: number): {
|
|
241
|
+
output: {
|
|
242
|
+
width: number;
|
|
243
|
+
height: number;
|
|
244
|
+
};
|
|
245
|
+
oversample: number;
|
|
246
|
+
renderScale: number;
|
|
247
|
+
};
|
|
248
|
+
/** A hidden canvas with a mounted render context, reusable across renders of one project. */
|
|
249
|
+
export interface ExportSurface {
|
|
250
|
+
canvas: HTMLCanvasElement;
|
|
251
|
+
renderContext: BrowserRenderContext;
|
|
252
|
+
storageAdapter: StorageAdapter;
|
|
253
|
+
assetCatalog: ManifestAssetCatalog;
|
|
254
|
+
dispose(): void;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Open the canvas, renderer, storage and render context an export draws with,
|
|
258
|
+
* resolving once the surface can draw (a frame drawn before an asynchronously
|
|
259
|
+
* started surface is ready encodes blank). The canvas is a whole multiple of the
|
|
260
|
+
* output, so the downsample is exact.
|
|
261
|
+
*/
|
|
262
|
+
export declare function openExportSurface(params: {
|
|
263
|
+
canvasSize: {
|
|
264
|
+
width: number;
|
|
265
|
+
height: number;
|
|
266
|
+
};
|
|
267
|
+
viewport: Size2D;
|
|
268
|
+
fps: number;
|
|
269
|
+
manifest: AssetManifest;
|
|
270
|
+
} & RendererWasmOption): Promise<ExportSurface>;
|
|
160
271
|
/**
|
|
161
272
|
* Renders a document frame-by-frame to an offscreen canvas and muxes the result
|
|
162
|
-
* into an MP4 via mediabunny.
|
|
273
|
+
* into an MP4 or WebM via mediabunny.
|
|
163
274
|
*
|
|
164
275
|
* The frame loop itself — discovery, state evaluation, the warm-and-re-render retry
|
|
165
276
|
* that keeps every frame accurate, audio scheduling, progress and cancellation —
|
|
166
|
-
* lives in
|
|
277
|
+
* lives in `renderTimeline` (frame-loop.ts). What this function
|
|
167
278
|
* supplies is the browser-specific half: a canvas to draw into, a WebCodecs sink,
|
|
168
279
|
* a Web Audio mixer, and the `<a download>` delivery.
|
|
169
280
|
*/
|
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":"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"}
|
package/dist/exporter.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { DEFAULT_OUTPUT, ManifestAssetCatalog, Registry, resolveProjectDocument, } from "@motionscript/core";
|
|
2
|
-
import { renderTimeline, } from "
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { WebStorageAdapter } from "./storage-adapter";
|
|
6
|
-
import { getCanvasKit } from "./getter";
|
|
2
|
+
import { renderTimeline, } from "./frame-loop";
|
|
3
|
+
import { BufferTarget, CanvasSource, canEncodeVideo, Output, } from 'mediabunny';
|
|
4
|
+
import { loadRenderer } from "./renderer";
|
|
7
5
|
import { WebAudioMixer } from "./audio/mixer";
|
|
6
|
+
import { deliver } from "./deliver";
|
|
7
|
+
import { audioTrackSource, DEFAULT_AUDIO_BITRATE, ensureAudioEncoder, formatSpec, } from "./formats";
|
|
8
8
|
/**
|
|
9
9
|
* Bits per pixel per frame the default bitrate is built from.
|
|
10
10
|
*
|
|
@@ -36,7 +36,6 @@ export function motionGraphicsBitrate(width, height, fps) {
|
|
|
36
36
|
const raw = width * height * fps * BITS_PER_PIXEL;
|
|
37
37
|
return Math.round(Math.min(MAX_VIDEO_BITRATE, Math.max(MIN_VIDEO_BITRATE, raw)));
|
|
38
38
|
}
|
|
39
|
-
const DEFAULT_AUDIO_BITRATE = 192_000;
|
|
40
39
|
/** See {@link VideoEncoderOptions.keyFrameInterval}. */
|
|
41
40
|
const DEFAULT_KEY_FRAME_INTERVAL = 1;
|
|
42
41
|
/** See {@link ExportParams.supersample}. */
|
|
@@ -53,9 +52,10 @@ const EMPTY_MANIFEST = {
|
|
|
53
52
|
// split export and a whole one cannot mix differently.
|
|
54
53
|
// ── Video sink ───────────────────────────────────────────────────────────────
|
|
55
54
|
/**
|
|
56
|
-
* mediabunny/WebCodecs implementation of the frame sink
|
|
55
|
+
* mediabunny/WebCodecs implementation of the frame sink, writing whichever
|
|
56
|
+
* container `format` names.
|
|
57
57
|
*
|
|
58
|
-
* Wraps the very `<canvas>`
|
|
58
|
+
* Wraps the very `<canvas>` the renderer draws into, so `addFrame` costs nothing but
|
|
59
59
|
* a timestamp — WebCodecs pulls the pixels from the canvas itself, with no
|
|
60
60
|
* GPU→CPU readback. That is why the seam signals a frame rather than passing one.
|
|
61
61
|
*
|
|
@@ -64,44 +64,41 @@ const EMPTY_MANIFEST = {
|
|
|
64
64
|
* platform choose: a software encoder has to be handed CPU pixels, so it drags
|
|
65
65
|
* a full-frame copy off the GPU on every single frame.
|
|
66
66
|
*/
|
|
67
|
-
class MediabunnyVideoSink {
|
|
67
|
+
export class MediabunnyVideoSink {
|
|
68
|
+
format;
|
|
68
69
|
audioOptions;
|
|
69
70
|
target = new BufferTarget();
|
|
70
71
|
output;
|
|
71
72
|
video;
|
|
72
73
|
audio = null;
|
|
73
|
-
constructor(canvas,
|
|
74
|
+
constructor(canvas,
|
|
74
75
|
/** Encoded output size, which is the canvas' own unless supersampling is on. */
|
|
75
|
-
output,
|
|
76
|
+
output, format, encoding, audioOptions = {}) {
|
|
77
|
+
this.format = format;
|
|
76
78
|
this.audioOptions = audioOptions;
|
|
77
79
|
const downsampling = output.width !== canvas.width || output.height !== canvas.height;
|
|
78
80
|
this.video = new CanvasSource(canvas, {
|
|
79
|
-
codec,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
latencyMode: video.latencyMode ?? 'quality',
|
|
87
|
-
contentHint: video.contentHint ?? 'detail',
|
|
88
|
-
...(video.bitrateMode !== undefined && { bitrateMode: video.bitrateMode }),
|
|
81
|
+
codec: encoding.codec,
|
|
82
|
+
bitrate: encoding.bitrate,
|
|
83
|
+
hardwareAcceleration: encoding.hardwareAcceleration,
|
|
84
|
+
keyFrameInterval: encoding.keyFrameInterval,
|
|
85
|
+
latencyMode: encoding.latencyMode,
|
|
86
|
+
contentHint: encoding.contentHint,
|
|
87
|
+
...(encoding.bitrateMode !== undefined && { bitrateMode: encoding.bitrateMode }),
|
|
89
88
|
// `fit` is exact here — the supersampled canvas is a whole multiple
|
|
90
89
|
// of the output — but mediabunny requires it whenever both axes are
|
|
91
90
|
// given, so it is stated rather than inferred.
|
|
92
91
|
...(downsampling && { transform: { width: output.width, height: output.height, fit: 'fill' } }),
|
|
93
92
|
});
|
|
94
|
-
this.output = new Output({ format:
|
|
93
|
+
this.output = new Output({ format: formatSpec(format).container(), target: this.target });
|
|
95
94
|
}
|
|
96
95
|
async start(info) {
|
|
97
96
|
this.output.addVideoTrack(this.video, { frameRate: info.fps });
|
|
98
97
|
// Tracks must be declared before the muxer starts, which is why the
|
|
99
98
|
// orchestrator reports this rather than leaving it to be inferred.
|
|
100
99
|
if (info.hasAudio) {
|
|
101
|
-
this.
|
|
102
|
-
|
|
103
|
-
bitrate: this.audioOptions.bitrate ?? DEFAULT_AUDIO_BITRATE,
|
|
104
|
-
});
|
|
100
|
+
await ensureAudioEncoder(this.format);
|
|
101
|
+
this.audio = audioTrackSource(this.format, this.audioOptions.bitrate ?? DEFAULT_AUDIO_BITRATE);
|
|
105
102
|
this.output.addAudioTrack(this.audio);
|
|
106
103
|
}
|
|
107
104
|
await this.output.start();
|
|
@@ -156,109 +153,156 @@ function fittedSupersample(requested, output) {
|
|
|
156
153
|
return factor;
|
|
157
154
|
}
|
|
158
155
|
/**
|
|
159
|
-
* The codec to actually encode with: the one asked for if
|
|
160
|
-
* encode it at this size,
|
|
156
|
+
* The codec to actually encode with: the one asked for if the container holds
|
|
157
|
+
* it and this platform can encode it at this size, else the format's default,
|
|
158
|
+
* else its fallback (`'avc'` for MP4, `'vp8'` for WebM).
|
|
161
159
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* encoder is worse than one that quietly produces H.264. AVC is not probed: it
|
|
166
|
-
* is the fallback, and there is nothing to fall back to.
|
|
160
|
+
* Which encoders a machine has varies by OS, browser and GPU, and an export that
|
|
161
|
+
* dies at the muxer for want of an HEVC encoder is worse than one that quietly
|
|
162
|
+
* produces H.264. The fallback is not probed: there is nothing to fall back to.
|
|
167
163
|
*/
|
|
168
|
-
async function resolveCodec(requested, size, fps) {
|
|
169
|
-
|
|
170
|
-
|
|
164
|
+
async function resolveCodec(requested, format, size, fps) {
|
|
165
|
+
const { codecs, fallback } = formatSpec(format).video;
|
|
166
|
+
let wanted = requested ?? codecs[0];
|
|
167
|
+
if (!codecs.includes(wanted)) {
|
|
168
|
+
console.warn(`[motionscript] ${format.toUpperCase()} cannot hold ${wanted}; exporting as ${CODEC_NAMES[codecs[0]]} instead.`);
|
|
169
|
+
wanted = codecs[0];
|
|
170
|
+
}
|
|
171
|
+
for (const codec of new Set([wanted, codecs[0], fallback])) {
|
|
172
|
+
if (codec === fallback || await canEncode(codec, size, fps)) {
|
|
173
|
+
if (codec !== wanted) {
|
|
174
|
+
console.warn(`[motionscript] No ${wanted} encoder available; exporting as ${CODEC_NAMES[codec]} instead.`);
|
|
175
|
+
}
|
|
176
|
+
return codec;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return fallback;
|
|
180
|
+
}
|
|
181
|
+
const CODEC_NAMES = { avc: 'H.264', hevc: 'HEVC', vp9: 'VP9', av1: 'AV1', vp8: 'VP8' };
|
|
182
|
+
async function canEncode(codec, size, fps) {
|
|
171
183
|
try {
|
|
172
|
-
|
|
184
|
+
return await canEncodeVideo(codec, {
|
|
173
185
|
width: size.width,
|
|
174
186
|
height: size.height,
|
|
175
187
|
bitrate: motionGraphicsBitrate(size.width, size.height, fps),
|
|
176
188
|
});
|
|
177
|
-
if (ok)
|
|
178
|
-
return requested;
|
|
179
189
|
}
|
|
180
190
|
catch {
|
|
181
191
|
// A probe that throws is a probe that answered "no".
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/** {@link VideoEncoderOptions} with its defaults applied against the encoded size and rate. */
|
|
196
|
+
export async function resolveVideoEncoding(video, format, output, fps) {
|
|
197
|
+
const codec = await resolveCodec(video?.codec, format, output, fps);
|
|
198
|
+
return {
|
|
199
|
+
codec,
|
|
200
|
+
bitrate: video?.bitrate ?? motionGraphicsBitrate(output.width, output.height, fps),
|
|
201
|
+
hardwareAcceleration: video?.hardwareAcceleration ?? 'no-preference',
|
|
202
|
+
keyFrameInterval: video?.keyFrameInterval ?? DEFAULT_KEY_FRAME_INTERVAL,
|
|
203
|
+
latencyMode: video?.latencyMode ?? 'quality',
|
|
204
|
+
contentHint: video?.contentHint ?? 'detail',
|
|
205
|
+
...(video?.bitrateMode !== undefined && { bitrateMode: video.bitrateMode }),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The encoding with a `Quality` turned into the bits per second mediabunny would
|
|
210
|
+
* give it at this size, since a class instance cannot cross a `MessagePort`.
|
|
211
|
+
*/
|
|
212
|
+
export function plainVideoEncoding(encoding, output) {
|
|
213
|
+
const { bitrate } = encoding;
|
|
214
|
+
if (typeof bitrate === 'number')
|
|
215
|
+
return { ...encoding, bitrate };
|
|
216
|
+
const quality = bitrate;
|
|
217
|
+
return { ...encoding, bitrate: quality._toVideoBitrate(encoding.codec, output.width, output.height) };
|
|
218
|
+
}
|
|
219
|
+
/** The encoded size, rounded up to even, and how far each frame is supersampled. */
|
|
220
|
+
export function exportGeometry(viewport, scale, supersample) {
|
|
221
|
+
// Every block-based codec works in macroblocks, so an odd dimension is
|
|
222
|
+
// padded by the encoder with a smeared strip that was never drawn.
|
|
223
|
+
const output = { width: evenUp(viewport.width * scale), height: evenUp(viewport.height * scale) };
|
|
224
|
+
const oversample = fittedSupersample(supersample, output);
|
|
225
|
+
return { output, oversample, renderScale: scale * oversample };
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Open the canvas, renderer, storage and render context an export draws with,
|
|
229
|
+
* resolving once the surface can draw (a frame drawn before an asynchronously
|
|
230
|
+
* started surface is ready encodes blank). The canvas is a whole multiple of the
|
|
231
|
+
* output, so the downsample is exact.
|
|
232
|
+
*/
|
|
233
|
+
export async function openExportSurface(params) {
|
|
234
|
+
const canvas = document.createElement('canvas');
|
|
235
|
+
canvas.width = params.canvasSize.width;
|
|
236
|
+
canvas.height = params.canvasSize.height;
|
|
237
|
+
canvas.style.display = 'none';
|
|
238
|
+
document.body.appendChild(canvas);
|
|
239
|
+
try {
|
|
240
|
+
const assetCatalog = new ManifestAssetCatalog(params.manifest);
|
|
241
|
+
const renderer = await loadRenderer(params);
|
|
242
|
+
const storageAdapter = renderer.createStorageAdapter({ catalog: assetCatalog, viewport: params.viewport, fps: params.fps });
|
|
243
|
+
const renderContext = renderer.createRenderContext(storageAdapter);
|
|
244
|
+
renderContext.mount(canvas);
|
|
245
|
+
await renderContext.whenMounted?.();
|
|
246
|
+
return {
|
|
247
|
+
canvas, renderContext, storageAdapter, assetCatalog,
|
|
248
|
+
dispose: () => {
|
|
249
|
+
renderContext.dispose();
|
|
250
|
+
canvas.remove();
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
canvas.remove();
|
|
256
|
+
throw error;
|
|
182
257
|
}
|
|
183
|
-
console.warn(`[motionscript] No ${requested} encoder available; exporting as H.264 instead.`);
|
|
184
|
-
return 'avc';
|
|
185
258
|
}
|
|
186
259
|
/**
|
|
187
260
|
* Renders a document frame-by-frame to an offscreen canvas and muxes the result
|
|
188
|
-
* into an MP4 via mediabunny.
|
|
261
|
+
* into an MP4 or WebM via mediabunny.
|
|
189
262
|
*
|
|
190
263
|
* The frame loop itself — discovery, state evaluation, the warm-and-re-render retry
|
|
191
264
|
* that keeps every frame accurate, audio scheduling, progress and cancellation —
|
|
192
|
-
* lives in
|
|
265
|
+
* lives in `renderTimeline` (frame-loop.ts). What this function
|
|
193
266
|
* supplies is the browser-specific half: a canvas to draw into, a WebCodecs sink,
|
|
194
267
|
* a Web Audio mixer, and the `<a download>` delivery.
|
|
195
268
|
*/
|
|
196
269
|
export async function exportVideo(params) {
|
|
197
270
|
const {
|
|
198
271
|
// Not `document`, which would shadow the DOM global this function uses.
|
|
199
|
-
document: source, registry = new Registry(), scale = 1,
|
|
272
|
+
document: source, registry = new Registry(), scale = 1, format = 'mp4', manifest = EMPTY_MANIFEST, theme, includeAudio = true, video, audio = {}, frames, onProgress, signal, rendererWasmUrl, supersample = DEFAULT_SUPERSAMPLE, returnBytes = false, } = params;
|
|
273
|
+
const spec = formatSpec(format);
|
|
274
|
+
if (spec.kind !== 'video')
|
|
275
|
+
throw new RangeError(`exportVideo writes video; "${format}" is audio, which exportAudio writes.`);
|
|
276
|
+
const filename = params.filename ?? `export.${spec.extension}`;
|
|
200
277
|
const project = resolveProjectDocument(source, params, DEFAULT_OUTPUT);
|
|
201
278
|
if (project.tracks.length === 0)
|
|
202
279
|
return;
|
|
203
280
|
signal?.throwIfAborted();
|
|
204
281
|
const viewport = project.viewport;
|
|
205
282
|
const fps = project.fps;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const outputSize = {
|
|
212
|
-
width: evenUp(viewport.width * scale),
|
|
213
|
-
height: evenUp(viewport.height * scale),
|
|
214
|
-
};
|
|
215
|
-
const oversample = fittedSupersample(supersample, outputSize);
|
|
216
|
-
const renderScale = scale * oversample;
|
|
217
|
-
const offscreenCanvas = document.createElement('canvas');
|
|
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;
|
|
222
|
-
offscreenCanvas.style.display = 'none';
|
|
223
|
-
document.body.appendChild(offscreenCanvas);
|
|
224
|
-
const canvasKit = await getCanvasKit(wasmUrl);
|
|
225
|
-
const assetCatalog = new ManifestAssetCatalog(manifest);
|
|
226
|
-
const storageAdapter = new WebStorageAdapter(canvasKit, assetCatalog, viewport, fps);
|
|
227
|
-
const renderContext = new WebRenderContext(canvasKit, storageAdapter);
|
|
228
|
-
renderContext.mount(offscreenCanvas);
|
|
229
|
-
const codec = await resolveCodec(video?.codec, outputSize, fps);
|
|
283
|
+
const { output, oversample, renderScale } = exportGeometry(viewport, scale, supersample);
|
|
284
|
+
const surface = await openExportSurface({
|
|
285
|
+
canvasSize: { width: output.width * oversample, height: output.height * oversample },
|
|
286
|
+
viewport, fps, manifest, rendererWasmUrl,
|
|
287
|
+
});
|
|
230
288
|
let bytes;
|
|
231
289
|
try {
|
|
290
|
+
const encoding = await resolveVideoEncoding(video, format, output, fps);
|
|
232
291
|
bytes = await renderTimeline({
|
|
233
292
|
project, registry, theme, scale: renderScale, includeAudio, frames,
|
|
234
|
-
renderContext, storageAdapter, assetCatalog,
|
|
235
|
-
sink: new MediabunnyVideoSink(
|
|
236
|
-
mixer: new WebAudioMixer(audio.sampleRate, assetCatalog),
|
|
293
|
+
renderContext: surface.renderContext, storageAdapter: surface.storageAdapter, assetCatalog: surface.assetCatalog,
|
|
294
|
+
sink: new MediabunnyVideoSink(surface.canvas, output, format, encoding, audio),
|
|
295
|
+
mixer: new WebAudioMixer(audio.sampleRate, surface.assetCatalog),
|
|
237
296
|
onProgress,
|
|
297
|
+
onIssue: params.onIssue,
|
|
238
298
|
signal,
|
|
239
299
|
});
|
|
240
300
|
}
|
|
241
301
|
finally {
|
|
242
|
-
|
|
243
|
-
document.body.removeChild(offscreenCanvas);
|
|
302
|
+
surface.dispose();
|
|
244
303
|
}
|
|
245
304
|
if (!bytes)
|
|
246
305
|
return;
|
|
247
|
-
|
|
248
|
-
// so skip the DOM-based download path entirely.
|
|
249
|
-
if (returnBytes)
|
|
250
|
-
return bytes;
|
|
251
|
-
// The sink builds this Uint8Array over a whole, plain ArrayBuffer, so the cast
|
|
252
|
-
// is sound — TS only balks because the generic parameter admits a
|
|
253
|
-
// SharedArrayBuffer, which a BlobPart may not be.
|
|
254
|
-
const blob = new Blob([bytes.buffer], { type: 'video/mp4' });
|
|
255
|
-
const url = URL.createObjectURL(blob);
|
|
256
|
-
const link = document.createElement('a');
|
|
257
|
-
link.href = url;
|
|
258
|
-
link.download = filename;
|
|
259
|
-
document.body.appendChild(link);
|
|
260
|
-
link.click();
|
|
261
|
-
document.body.removeChild(link);
|
|
262
|
-
URL.revokeObjectURL(url);
|
|
306
|
+
return deliver(bytes, { returnBytes, filename, mimeType: spec.mimeType });
|
|
263
307
|
}
|
|
264
308
|
//# sourceMappingURL=exporter.js.map
|