@motionscript/browser 0.1.0-alpha.0 → 0.1.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +187 -0
- package/NOTICE +49 -0
- package/README.md +96 -24
- package/dist/asset-bytes.d.ts +14 -5
- package/dist/asset-bytes.d.ts.map +1 -1
- package/dist/asset-bytes.js +23 -5
- package/dist/asset-bytes.js.map +1 -1
- package/dist/audio/bus-graph.d.ts +12 -3
- package/dist/audio/bus-graph.d.ts.map +1 -1
- package/dist/audio/bus-graph.js +35 -31
- package/dist/audio/bus-graph.js.map +1 -1
- package/dist/audio/effect-graph.d.ts +62 -0
- package/dist/audio/effect-graph.d.ts.map +1 -0
- package/dist/audio/effect-graph.js +234 -0
- package/dist/audio/effect-graph.js.map +1 -0
- package/dist/audio/index.d.ts.map +1 -1
- package/dist/audio/index.js +2 -3
- package/dist/audio/index.js.map +1 -1
- package/dist/audio/loop-region.d.ts +31 -0
- package/dist/audio/loop-region.d.ts.map +1 -0
- package/dist/audio/loop-region.js +43 -0
- package/dist/audio/loop-region.js.map +1 -0
- package/dist/audio/mixer.d.ts +4 -4
- package/dist/audio/mixer.d.ts.map +1 -1
- package/dist/audio/mixer.js +43 -44
- package/dist/audio/mixer.js.map +1 -1
- package/dist/audio/player.d.ts +15 -9
- package/dist/audio/player.d.ts.map +1 -1
- package/dist/audio/player.js +60 -69
- package/dist/audio/player.js.map +1 -1
- package/dist/browser/THIRD_PARTY_LICENSES.txt +784 -0
- package/dist/browser/audio.js +1 -1
- package/dist/browser/chunks/chunk-F3AZKPHU.js +2 -0
- package/dist/browser/chunks/chunk-F3AZKPHU.js.map +7 -0
- package/dist/browser/chunks/chunk-KLXMRKTL.js +2 -0
- package/dist/browser/chunks/chunk-L5UFQACH.js +163 -0
- package/dist/browser/chunks/chunk-L5UFQACH.js.map +7 -0
- package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js +14 -0
- package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js.map +7 -0
- package/dist/browser/index.js +52 -1490
- package/dist/browser/index.js.map +4 -4
- package/dist/browser/manifest.json +21 -4
- package/dist/browser/ms_renderer_bg.wasm +0 -0
- package/dist/concat.d.ts +33 -7
- package/dist/concat.d.ts.map +1 -1
- package/dist/concat.js +117 -72
- package/dist/concat.js.map +1 -1
- package/dist/deliver.d.ts +10 -0
- package/dist/deliver.d.ts.map +1 -0
- package/dist/deliver.js +22 -0
- package/dist/deliver.js.map +1 -0
- package/dist/dynamic-nodes.d.ts.map +1 -1
- package/dist/dynamic-nodes.js +9 -12
- package/dist/dynamic-nodes.js.map +1 -1
- package/dist/engine.d.ts +43 -15
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +58 -36
- package/dist/engine.js.map +1 -1
- package/dist/export-audio.d.ts +36 -0
- package/dist/export-audio.d.ts.map +1 -0
- package/dist/export-audio.js +56 -0
- package/dist/export-audio.js.map +1 -0
- package/dist/exporter.d.ts +134 -15
- package/dist/exporter.d.ts.map +1 -1
- package/dist/exporter.js +135 -90
- package/dist/exporter.js.map +1 -1
- package/dist/formats.d.ts +36 -0
- package/dist/formats.d.ts.map +1 -0
- package/dist/formats.js +83 -0
- package/dist/formats.js.map +1 -0
- package/dist/frame-loop.d.ts +190 -0
- package/dist/frame-loop.d.ts.map +1 -0
- package/dist/frame-loop.js +304 -0
- package/dist/frame-loop.js.map +1 -0
- package/dist/index.d.ts +12 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -47
- package/dist/index.js.map +1 -1
- package/dist/mix-timeline-audio.d.ts +7 -7
- package/dist/mix-timeline-audio.d.ts.map +1 -1
- package/dist/mix-timeline-audio.js +11 -14
- package/dist/mix-timeline-audio.js.map +1 -1
- package/dist/parallel/export-parallel.d.ts +32 -0
- package/dist/parallel/export-parallel.d.ts.map +1 -0
- package/dist/parallel/export-parallel.js +114 -0
- package/dist/parallel/export-parallel.js.map +1 -0
- package/dist/parallel/governor.d.ts +57 -0
- package/dist/parallel/governor.d.ts.map +1 -0
- package/dist/parallel/governor.js +79 -0
- package/dist/parallel/governor.js.map +1 -0
- package/dist/parallel/lane.d.ts +45 -0
- package/dist/parallel/lane.d.ts.map +1 -0
- package/dist/parallel/lane.js +2 -0
- package/dist/parallel/lane.js.map +1 -0
- package/dist/parallel/lanes.d.ts +20 -0
- package/dist/parallel/lanes.d.ts.map +1 -0
- package/dist/parallel/lanes.js +152 -0
- package/dist/parallel/lanes.js.map +1 -0
- package/dist/parallel/plan.d.ts +23 -0
- package/dist/parallel/plan.d.ts.map +1 -0
- package/dist/parallel/plan.js +30 -0
- package/dist/parallel/plan.js.map +1 -0
- package/dist/parallel/run-lanes.d.ts +38 -0
- package/dist/parallel/run-lanes.d.ts.map +1 -0
- package/dist/parallel/run-lanes.js +164 -0
- package/dist/parallel/run-lanes.js.map +1 -0
- package/dist/parallel/session.d.ts +9 -0
- package/dist/parallel/session.d.ts.map +1 -0
- package/dist/parallel/session.js +79 -0
- package/dist/parallel/session.js.map +1 -0
- package/dist/renderer.d.ts +22 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +23 -0
- package/dist/renderer.js.map +1 -0
- package/dist/screenshot.d.ts +6 -6
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +4 -5
- package/dist/screenshot.js.map +1 -1
- package/dist/still.d.ts +32 -24
- package/dist/still.d.ts.map +1 -1
- package/dist/still.js +54 -54
- package/dist/still.js.map +1 -1
- package/dist/ticker.d.ts +41 -9
- package/dist/ticker.d.ts.map +1 -1
- package/dist/ticker.js +113 -41
- package/dist/ticker.js.map +1 -1
- package/package.json +11 -10
- package/dist/audio/filter-graph.d.ts +0 -58
- package/dist/audio/filter-graph.d.ts.map +0 -1
- package/dist/audio/filter-graph.js +0 -235
- package/dist/audio/filter-graph.js.map +0 -1
- package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js +0 -2
- package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js.map +0 -7
- package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js +0 -2
- package/dist/browser/chunks/chunk-CTLBXZSU.js +0 -4119
- package/dist/browser/chunks/chunk-CTLBXZSU.js.map +0 -7
- package/dist/browser/chunks/chunk-FQ32PDIU.js +0 -2
- package/dist/browser/chunks/chunk-FQ32PDIU.js.map +0 -7
- package/dist/browser/chunks/chunk-FY3BTKY4.js +0 -2
- package/dist/browser/chunks/chunk-FY3BTKY4.js.map +0 -7
- package/dist/browser/chunks/chunk-NSFLRI4P.js +0 -2
- package/dist/browser/chunks/chunk-NSFLRI4P.js.map +0 -7
- package/dist/browser/chunks/three.module-Z4EOVUWF.js +0 -2
- package/dist/browser/chunks/three.module-Z4EOVUWF.js.map +0 -7
- package/dist/decode/image-levels.d.ts +0 -137
- package/dist/decode/image-levels.d.ts.map +0 -1
- package/dist/decode/image-levels.js +0 -477
- package/dist/decode/image-levels.js.map +0 -1
- package/dist/decode/levels.d.ts +0 -37
- package/dist/decode/levels.d.ts.map +0 -1
- package/dist/decode/levels.js +0 -68
- package/dist/decode/levels.js.map +0 -1
- package/dist/decode/video-levels.d.ts +0 -37
- package/dist/decode/video-levels.d.ts.map +0 -1
- package/dist/decode/video-levels.js +0 -95
- package/dist/decode/video-levels.js.map +0 -1
- package/dist/getter.d.ts +0 -10
- package/dist/getter.d.ts.map +0 -1
- package/dist/getter.js +0 -38
- package/dist/getter.js.map +0 -1
- package/dist/measure-context.d.ts +0 -12
- package/dist/measure-context.d.ts.map +0 -1
- package/dist/measure-context.js +0 -12
- package/dist/measure-context.js.map +0 -1
- package/dist/output.d.ts +0 -7
- package/dist/output.d.ts.map +0 -1
- package/dist/output.js +0 -6
- package/dist/output.js.map +0 -1
- package/dist/render-context.d.ts +0 -30
- package/dist/render-context.d.ts.map +0 -1
- package/dist/render-context.js +0 -55
- package/dist/render-context.js.map +0 -1
- package/dist/storage-adapter.d.ts +0 -367
- package/dist/storage-adapter.d.ts.map +0 -1
- package/dist/storage-adapter.js +0 -1278
- package/dist/storage-adapter.js.map +0 -1
- package/dist/three/renderer.d.ts +0 -48
- package/dist/three/renderer.d.ts.map +0 -1
- package/dist/three/renderer.js +0 -177
- package/dist/three/renderer.js.map +0 -1
- /package/dist/browser/chunks/{SkeletonUtils-XYDMTGTM.js.map → chunk-KLXMRKTL.js.map} +0 -0
package/dist/exporter.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { Registry, type AssetManifest, type ProjectDocument, type
|
|
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,24 +94,29 @@ 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
|
-
document: ProjectDocument |
|
|
104
|
+
document: ProjectDocument | VisualDocument;
|
|
99
105
|
/** The vocabulary its clips compile against. Defaults to what the library ships. */
|
|
100
106
|
registry?: Registry;
|
|
101
107
|
/** Win over the document's own; 1920×1080 at 60 fps when neither says. */
|
|
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;
|
|
116
|
+
/** Theme for a document that carries none of its own. */
|
|
117
|
+
theme?: Theme;
|
|
107
118
|
/**
|
|
108
|
-
* Set false to produce a silent
|
|
119
|
+
* Set false to produce a silent file — no audio track is added to the
|
|
109
120
|
* container and the mix is skipped entirely. Defaults to true.
|
|
110
121
|
*/
|
|
111
122
|
includeAudio?: boolean;
|
|
@@ -113,9 +124,20 @@ export type ExportParams = {
|
|
|
113
124
|
video?: VideoEncoderOptions;
|
|
114
125
|
/** Audio encoder settings; ignored when `includeAudio` is false. */
|
|
115
126
|
audio?: AudioEncoderOptions;
|
|
127
|
+
/**
|
|
128
|
+
* Encode only these frames (`end` excluded), timestamped from 0 and silent:
|
|
129
|
+
* one segment of a split export, for `concatVideoSegments` to join. Mix
|
|
130
|
+
* the audio once with `mixTimelineAudio` instead.
|
|
131
|
+
*/
|
|
132
|
+
frames?: FrameRange;
|
|
116
133
|
onProgress?: ExportProgressCallback;
|
|
117
134
|
signal?: AbortSignal;
|
|
118
|
-
|
|
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;
|
|
119
141
|
/**
|
|
120
142
|
* Render each frame at this multiple of the output resolution and downsample
|
|
121
143
|
* it before encoding. **Defaults to 2**; pass 1 to turn it off.
|
|
@@ -142,20 +164,117 @@ export type ExportParams = {
|
|
|
142
164
|
*/
|
|
143
165
|
supersample?: number;
|
|
144
166
|
/**
|
|
145
|
-
* When true, the encoded
|
|
167
|
+
* When true, the encoded bytes are returned instead of triggering a
|
|
146
168
|
* browser download. Used by headless drivers (e.g. the CLI) that capture
|
|
147
169
|
* the output themselves and write it to disk; leave unset for the
|
|
148
170
|
* interactive player, which downloads via an `<a>` element.
|
|
149
171
|
*/
|
|
150
172
|
returnBytes?: boolean;
|
|
151
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>;
|
|
152
271
|
/**
|
|
153
272
|
* Renders a document frame-by-frame to an offscreen canvas and muxes the result
|
|
154
|
-
* into an MP4 via mediabunny.
|
|
273
|
+
* into an MP4 or WebM via mediabunny.
|
|
155
274
|
*
|
|
156
275
|
* The frame loop itself — discovery, state evaluation, the warm-and-re-render retry
|
|
157
276
|
* that keeps every frame accurate, audio scheduling, progress and cancellation —
|
|
158
|
-
* lives in
|
|
277
|
+
* lives in `renderTimeline` (frame-loop.ts). What this function
|
|
159
278
|
* supplies is the browser-specific half: a canvas to draw into, a WebCodecs sink,
|
|
160
279
|
* a Web Audio mixer, and the `<a download>` delivery.
|
|
161
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,11 +1,10 @@
|
|
|
1
|
-
import { 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";
|
|
1
|
+
import { DEFAULT_OUTPUT, ManifestAssetCatalog, Registry, resolveProjectDocument, } from "@motionscript/core";
|
|
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";
|
|
8
|
-
import {
|
|
6
|
+
import { deliver } from "./deliver";
|
|
7
|
+
import { audioTrackSource, DEFAULT_AUDIO_BITRATE, ensureAudioEncoder, formatSpec, } from "./formats";
|
|
9
8
|
/**
|
|
10
9
|
* Bits per pixel per frame the default bitrate is built from.
|
|
11
10
|
*
|
|
@@ -37,7 +36,6 @@ export function motionGraphicsBitrate(width, height, fps) {
|
|
|
37
36
|
const raw = width * height * fps * BITS_PER_PIXEL;
|
|
38
37
|
return Math.round(Math.min(MAX_VIDEO_BITRATE, Math.max(MIN_VIDEO_BITRATE, raw)));
|
|
39
38
|
}
|
|
40
|
-
const DEFAULT_AUDIO_BITRATE = 192_000;
|
|
41
39
|
/** See {@link VideoEncoderOptions.keyFrameInterval}. */
|
|
42
40
|
const DEFAULT_KEY_FRAME_INTERVAL = 1;
|
|
43
41
|
/** See {@link ExportParams.supersample}. */
|
|
@@ -54,9 +52,10 @@ const EMPTY_MANIFEST = {
|
|
|
54
52
|
// split export and a whole one cannot mix differently.
|
|
55
53
|
// ── Video sink ───────────────────────────────────────────────────────────────
|
|
56
54
|
/**
|
|
57
|
-
* mediabunny/WebCodecs implementation of the frame sink
|
|
55
|
+
* mediabunny/WebCodecs implementation of the frame sink, writing whichever
|
|
56
|
+
* container `format` names.
|
|
58
57
|
*
|
|
59
|
-
* Wraps the very `<canvas>`
|
|
58
|
+
* Wraps the very `<canvas>` the renderer draws into, so `addFrame` costs nothing but
|
|
60
59
|
* a timestamp — WebCodecs pulls the pixels from the canvas itself, with no
|
|
61
60
|
* GPU→CPU readback. That is why the seam signals a frame rather than passing one.
|
|
62
61
|
*
|
|
@@ -65,44 +64,41 @@ const EMPTY_MANIFEST = {
|
|
|
65
64
|
* platform choose: a software encoder has to be handed CPU pixels, so it drags
|
|
66
65
|
* a full-frame copy off the GPU on every single frame.
|
|
67
66
|
*/
|
|
68
|
-
class MediabunnyVideoSink {
|
|
67
|
+
export class MediabunnyVideoSink {
|
|
68
|
+
format;
|
|
69
69
|
audioOptions;
|
|
70
70
|
target = new BufferTarget();
|
|
71
71
|
output;
|
|
72
72
|
video;
|
|
73
73
|
audio = null;
|
|
74
|
-
constructor(canvas,
|
|
74
|
+
constructor(canvas,
|
|
75
75
|
/** Encoded output size, which is the canvas' own unless supersampling is on. */
|
|
76
|
-
output,
|
|
76
|
+
output, format, encoding, audioOptions = {}) {
|
|
77
|
+
this.format = format;
|
|
77
78
|
this.audioOptions = audioOptions;
|
|
78
79
|
const downsampling = output.width !== canvas.width || output.height !== canvas.height;
|
|
79
80
|
this.video = new CanvasSource(canvas, {
|
|
80
|
-
codec,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
latencyMode: video.latencyMode ?? 'quality',
|
|
88
|
-
contentHint: video.contentHint ?? 'detail',
|
|
89
|
-
...(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 }),
|
|
90
88
|
// `fit` is exact here — the supersampled canvas is a whole multiple
|
|
91
89
|
// of the output — but mediabunny requires it whenever both axes are
|
|
92
90
|
// given, so it is stated rather than inferred.
|
|
93
91
|
...(downsampling && { transform: { width: output.width, height: output.height, fit: 'fill' } }),
|
|
94
92
|
});
|
|
95
|
-
this.output = new Output({ format:
|
|
93
|
+
this.output = new Output({ format: formatSpec(format).container(), target: this.target });
|
|
96
94
|
}
|
|
97
95
|
async start(info) {
|
|
98
96
|
this.output.addVideoTrack(this.video, { frameRate: info.fps });
|
|
99
97
|
// Tracks must be declared before the muxer starts, which is why the
|
|
100
98
|
// orchestrator reports this rather than leaving it to be inferred.
|
|
101
99
|
if (info.hasAudio) {
|
|
102
|
-
this.
|
|
103
|
-
|
|
104
|
-
bitrate: this.audioOptions.bitrate ?? DEFAULT_AUDIO_BITRATE,
|
|
105
|
-
});
|
|
100
|
+
await ensureAudioEncoder(this.format);
|
|
101
|
+
this.audio = audioTrackSource(this.format, this.audioOptions.bitrate ?? DEFAULT_AUDIO_BITRATE);
|
|
106
102
|
this.output.addAudioTrack(this.audio);
|
|
107
103
|
}
|
|
108
104
|
await this.output.start();
|
|
@@ -157,107 +153,156 @@ function fittedSupersample(requested, output) {
|
|
|
157
153
|
return factor;
|
|
158
154
|
}
|
|
159
155
|
/**
|
|
160
|
-
* The codec to actually encode with: the one asked for if
|
|
161
|
-
* 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).
|
|
162
159
|
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* encoder is worse than one that quietly produces H.264. AVC is not probed: it
|
|
167
|
-
* 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.
|
|
168
163
|
*/
|
|
169
|
-
async function resolveCodec(requested, size, fps) {
|
|
170
|
-
|
|
171
|
-
|
|
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) {
|
|
172
183
|
try {
|
|
173
|
-
|
|
184
|
+
return await canEncodeVideo(codec, {
|
|
174
185
|
width: size.width,
|
|
175
186
|
height: size.height,
|
|
176
187
|
bitrate: motionGraphicsBitrate(size.width, size.height, fps),
|
|
177
188
|
});
|
|
178
|
-
if (ok)
|
|
179
|
-
return requested;
|
|
180
189
|
}
|
|
181
190
|
catch {
|
|
182
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;
|
|
183
257
|
}
|
|
184
|
-
console.warn(`[motion-script] No ${requested} encoder available; exporting as H.264 instead.`);
|
|
185
|
-
return 'avc';
|
|
186
258
|
}
|
|
187
259
|
/**
|
|
188
260
|
* Renders a document frame-by-frame to an offscreen canvas and muxes the result
|
|
189
|
-
* into an MP4 via mediabunny.
|
|
261
|
+
* into an MP4 or WebM via mediabunny.
|
|
190
262
|
*
|
|
191
263
|
* The frame loop itself — discovery, state evaluation, the warm-and-re-render retry
|
|
192
264
|
* that keeps every frame accurate, audio scheduling, progress and cancellation —
|
|
193
|
-
* lives in
|
|
265
|
+
* lives in `renderTimeline` (frame-loop.ts). What this function
|
|
194
266
|
* supplies is the browser-specific half: a canvas to draw into, a WebCodecs sink,
|
|
195
267
|
* a Web Audio mixer, and the `<a download>` delivery.
|
|
196
268
|
*/
|
|
197
269
|
export async function exportVideo(params) {
|
|
198
270
|
const {
|
|
199
271
|
// Not `document`, which would shadow the DOM global this function uses.
|
|
200
|
-
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}`;
|
|
201
277
|
const project = resolveProjectDocument(source, params, DEFAULT_OUTPUT);
|
|
202
278
|
if (project.tracks.length === 0)
|
|
203
279
|
return;
|
|
204
280
|
signal?.throwIfAborted();
|
|
205
281
|
const viewport = project.viewport;
|
|
206
282
|
const fps = project.fps;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const outputSize = {
|
|
213
|
-
width: evenUp(viewport.width * scale),
|
|
214
|
-
height: evenUp(viewport.height * scale),
|
|
215
|
-
};
|
|
216
|
-
const oversample = fittedSupersample(supersample, outputSize);
|
|
217
|
-
const renderScale = scale * oversample;
|
|
218
|
-
const offscreenCanvas = document.createElement('canvas');
|
|
219
|
-
offscreenCanvas.width = evenUp(viewport.width * renderScale);
|
|
220
|
-
offscreenCanvas.height = evenUp(viewport.height * renderScale);
|
|
221
|
-
offscreenCanvas.style.display = 'none';
|
|
222
|
-
document.body.appendChild(offscreenCanvas);
|
|
223
|
-
const canvasKit = await getCanvasKit(wasmUrl);
|
|
224
|
-
const assetCatalog = new ManifestAssetCatalog(manifest);
|
|
225
|
-
const storageAdapter = new WebStorageAdapter(canvasKit, assetCatalog, viewport, fps);
|
|
226
|
-
const renderContext = new WebRenderContext(canvasKit, storageAdapter);
|
|
227
|
-
renderContext.mount(offscreenCanvas);
|
|
228
|
-
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
|
+
});
|
|
229
288
|
let bytes;
|
|
230
289
|
try {
|
|
290
|
+
const encoding = await resolveVideoEncoding(video, format, output, fps);
|
|
231
291
|
bytes = await renderTimeline({
|
|
232
|
-
project, registry, scale: renderScale, includeAudio,
|
|
233
|
-
renderContext, storageAdapter, assetCatalog,
|
|
234
|
-
sink: new MediabunnyVideoSink(
|
|
235
|
-
mixer: new WebAudioMixer(audio.sampleRate, assetCatalog),
|
|
292
|
+
project, registry, theme, scale: renderScale, includeAudio, frames,
|
|
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),
|
|
236
296
|
onProgress,
|
|
297
|
+
onIssue: params.onIssue,
|
|
237
298
|
signal,
|
|
238
299
|
});
|
|
239
300
|
}
|
|
240
301
|
finally {
|
|
241
|
-
|
|
242
|
-
document.body.removeChild(offscreenCanvas);
|
|
302
|
+
surface.dispose();
|
|
243
303
|
}
|
|
244
304
|
if (!bytes)
|
|
245
305
|
return;
|
|
246
|
-
|
|
247
|
-
// so skip the DOM-based download path entirely.
|
|
248
|
-
if (returnBytes)
|
|
249
|
-
return bytes;
|
|
250
|
-
// The sink builds this Uint8Array over a whole, plain ArrayBuffer, so the cast
|
|
251
|
-
// is sound — TS only balks because the generic parameter admits a
|
|
252
|
-
// SharedArrayBuffer, which a BlobPart may not be.
|
|
253
|
-
const blob = new Blob([bytes.buffer], { type: 'video/mp4' });
|
|
254
|
-
const url = URL.createObjectURL(blob);
|
|
255
|
-
const link = document.createElement('a');
|
|
256
|
-
link.href = url;
|
|
257
|
-
link.download = filename;
|
|
258
|
-
document.body.appendChild(link);
|
|
259
|
-
link.click();
|
|
260
|
-
document.body.removeChild(link);
|
|
261
|
-
URL.revokeObjectURL(url);
|
|
306
|
+
return deliver(bytes, { returnBytes, filename, mimeType: spec.mimeType });
|
|
262
307
|
}
|
|
263
308
|
//# sourceMappingURL=exporter.js.map
|