@motionscript/browser 0.1.0 → 0.1.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/asset-bytes.d.ts +12 -50
  3. package/dist/asset-bytes.d.ts.map +1 -1
  4. package/dist/asset-bytes.js +20 -52
  5. package/dist/asset-bytes.js.map +1 -1
  6. package/dist/audio/bus-graph.d.ts +4 -39
  7. package/dist/audio/bus-graph.d.ts.map +1 -1
  8. package/dist/audio/bus-graph.js +4 -38
  9. package/dist/audio/bus-graph.js.map +1 -1
  10. package/dist/audio/effect-graph.d.ts +4 -29
  11. package/dist/audio/effect-graph.d.ts.map +1 -1
  12. package/dist/audio/effect-graph.js +4 -31
  13. package/dist/audio/effect-graph.js.map +1 -1
  14. package/dist/audio/loop-region.d.ts +3 -21
  15. package/dist/audio/loop-region.d.ts.map +1 -1
  16. package/dist/audio/loop-region.js +3 -21
  17. package/dist/audio/loop-region.js.map +1 -1
  18. package/dist/audio/mixer.d.ts +9 -41
  19. package/dist/audio/mixer.d.ts.map +1 -1
  20. package/dist/audio/mixer.js +11 -44
  21. package/dist/audio/mixer.js.map +1 -1
  22. package/dist/audio/player.d.ts +3 -17
  23. package/dist/audio/player.d.ts.map +1 -1
  24. package/dist/audio/player.js +4 -23
  25. package/dist/audio/player.js.map +1 -1
  26. package/dist/browser/audio.js +1 -1
  27. package/dist/browser/chunks/chunk-NDJYMKIV.js +2 -0
  28. package/dist/browser/chunks/chunk-NDJYMKIV.js.map +7 -0
  29. package/dist/browser/index.js +19 -19
  30. package/dist/browser/index.js.map +4 -4
  31. package/dist/browser/manifest.json +1 -1
  32. package/dist/browser/ms_renderer_bg.wasm +0 -0
  33. package/dist/concat.d.ts +25 -84
  34. package/dist/concat.d.ts.map +1 -1
  35. package/dist/concat.js +19 -69
  36. package/dist/concat.js.map +1 -1
  37. package/dist/dynamic-nodes.d.ts +1 -6
  38. package/dist/dynamic-nodes.d.ts.map +1 -1
  39. package/dist/dynamic-nodes.js +3 -24
  40. package/dist/dynamic-nodes.js.map +1 -1
  41. package/dist/engine.d.ts +9 -42
  42. package/dist/engine.d.ts.map +1 -1
  43. package/dist/engine.js +10 -44
  44. package/dist/engine.js.map +1 -1
  45. package/dist/export-audio.d.ts +1 -6
  46. package/dist/export-audio.d.ts.map +1 -1
  47. package/dist/export-audio.js +1 -6
  48. package/dist/export-audio.js.map +1 -1
  49. package/dist/exporter.d.ts +28 -141
  50. package/dist/exporter.d.ts.map +1 -1
  51. package/dist/exporter.js +15 -90
  52. package/dist/exporter.js.map +1 -1
  53. package/dist/frame-loop.d.ts +19 -105
  54. package/dist/frame-loop.d.ts.map +1 -1
  55. package/dist/frame-loop.js +20 -82
  56. package/dist/frame-loop.js.map +1 -1
  57. package/dist/index.d.ts +1 -0
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +7 -15
  60. package/dist/index.js.map +1 -1
  61. package/dist/mix-timeline-audio.d.ts +7 -24
  62. package/dist/mix-timeline-audio.d.ts.map +1 -1
  63. package/dist/mix-timeline-audio.js +7 -23
  64. package/dist/mix-timeline-audio.js.map +1 -1
  65. package/dist/native-export.d.ts +29 -0
  66. package/dist/native-export.d.ts.map +1 -0
  67. package/dist/native-export.js +99 -0
  68. package/dist/native-export.js.map +1 -0
  69. package/dist/parallel/export-parallel.d.ts +2 -16
  70. package/dist/parallel/export-parallel.d.ts.map +1 -1
  71. package/dist/parallel/export-parallel.js +1 -11
  72. package/dist/parallel/export-parallel.js.map +1 -1
  73. package/dist/parallel/governor.d.ts +2 -15
  74. package/dist/parallel/governor.d.ts.map +1 -1
  75. package/dist/parallel/governor.js +1 -5
  76. package/dist/parallel/governor.js.map +1 -1
  77. package/dist/parallel/lane.d.ts +1 -5
  78. package/dist/parallel/lane.d.ts.map +1 -1
  79. package/dist/parallel/lane.js.map +1 -1
  80. package/dist/parallel/plan.d.ts +3 -7
  81. package/dist/parallel/plan.d.ts.map +1 -1
  82. package/dist/parallel/plan.js +1 -6
  83. package/dist/parallel/plan.js.map +1 -1
  84. package/dist/parallel/run-lanes.d.ts +4 -18
  85. package/dist/parallel/run-lanes.d.ts.map +1 -1
  86. package/dist/parallel/run-lanes.js +1 -12
  87. package/dist/parallel/run-lanes.js.map +1 -1
  88. package/dist/screenshot.d.ts +1 -12
  89. package/dist/screenshot.d.ts.map +1 -1
  90. package/dist/screenshot.js +1 -12
  91. package/dist/screenshot.js.map +1 -1
  92. package/dist/still.d.ts +17 -74
  93. package/dist/still.d.ts.map +1 -1
  94. package/dist/still.js +13 -50
  95. package/dist/still.js.map +1 -1
  96. package/dist/ticker.d.ts +10 -39
  97. package/dist/ticker.d.ts.map +1 -1
  98. package/dist/ticker.js +13 -45
  99. package/dist/ticker.js.map +1 -1
  100. package/package.json +4 -4
  101. package/dist/browser/chunks/chunk-F3AZKPHU.js +0 -2
  102. package/dist/browser/chunks/chunk-F3AZKPHU.js.map +0 -7
package/dist/exporter.js CHANGED
@@ -5,33 +5,13 @@ import { loadRenderer } from "./renderer";
5
5
  import { WebAudioMixer } from "./audio/mixer";
6
6
  import { deliver } from "./deliver";
7
7
  import { audioTrackSource, DEFAULT_AUDIO_BITRATE, ensureAudioEncoder, formatSpec, } from "./formats";
8
- /**
9
- * Bits per pixel per frame the default bitrate is built from.
10
- *
11
- * Rendered motion graphics are the hard case for a codec tuned on camera
12
- * footage: hard glyph edges, large flat fields, and gradients with no sensor
13
- * noise to hide the quantisation in. They compress *worse* than live action at
14
- * the same perceived quality, not better, so the figure here is well above what
15
- * a streaming preset would use — this is a master being rendered once, not a
16
- * bitstream being pushed down a pipe.
17
- */
8
+ /** Bits per pixel per frame for the default bitrate. Motion graphics (hard glyph edges, flat fields, noise-free gradients) compress worse than camera footage, so this is well above a streaming preset. */
18
9
  const BITS_PER_PIXEL = 0.2;
19
10
  /** Floor, so a tiny viewport still gets enough bits to be legible. */
20
11
  const MIN_VIDEO_BITRATE = 2_000_000;
21
- /**
22
- * Ceiling. Past this the returns are gone and the levels start excluding
23
- * hardware decoders — 4K60 lands here, and AVC level 5.1 (which every consumer
24
- * device implements) tops out at 240 Mbps, well clear of it.
25
- */
12
+ /** Ceiling: past it the returns are gone and AVC levels start excluding hardware decoders (4K60 lands here; level 5.1 allows 240 Mbps). */
26
13
  const MAX_VIDEO_BITRATE = 100_000_000;
27
- /**
28
- * The default video bitrate for an export of this size and frame rate.
29
- *
30
- * Scaling with **both** axes is the point. mediabunny's `Quality` levels scale
31
- * with resolution only, so doubling the frame rate halved the bits available per
32
- * frame while leaving the number unchanged — which is exactly the configuration
33
- * that produced clean stills and a mushy video from the very same renderer.
34
- */
14
+ /** Default video bitrate scaled by **both** axes: mediabunny's `Quality` levels scale with resolution only, so doubling the frame rate halved the bits per frame (clean stills, mushy video). */
35
15
  export function motionGraphicsBitrate(width, height, fps) {
36
16
  const raw = width * height * fps * BITS_PER_PIXEL;
37
17
  return Math.round(Math.min(MAX_VIDEO_BITRATE, Math.max(MIN_VIDEO_BITRATE, raw)));
@@ -46,23 +26,10 @@ const EMPTY_MANIFEST = {
46
26
  audio: {},
47
27
  font: {},
48
28
  };
49
- // ── Audio mixing ─────────────────────────────────────────────────────────────
50
- //
51
- // The mix itself lives in ./audio/mixer, shared with `mixTimelineAudio` so a
52
- // split export and a whole one cannot mix differently.
53
- // ── Video sink ───────────────────────────────────────────────────────────────
54
29
  /**
55
- * mediabunny/WebCodecs implementation of the frame sink, writing whichever
56
- * container `format` names.
57
- *
58
- * Wraps the very `<canvas>` the renderer draws into, so `addFrame` costs nothing but
59
- * a timestamp — WebCodecs pulls the pixels from the canvas itself, with no
60
- * GPU→CPU readback. That is why the seam signals a frame rather than passing one.
61
- *
62
- * The readback claim holds only while the encoder is a hardware one, which is
63
- * why {@link VideoEncoderOptions.hardwareAcceleration} defaults to letting the
64
- * platform choose: a software encoder has to be handed CPU pixels, so it drags
65
- * a full-frame copy off the GPU on every single frame.
30
+ * mediabunny/WebCodecs frame sink wrapping the very canvas the renderer draws into: `addFrame` costs only a timestamp
31
+ * because WebCodecs pulls pixels from the canvas with no GPU->CPU readback (hence the seam signals a frame rather
32
+ * than passing one). Holds only for a hardware encoder; a software one forces a full-frame copy every frame.
66
33
  */
67
34
  export class MediabunnyVideoSink {
68
35
  format;
@@ -85,17 +52,14 @@ export class MediabunnyVideoSink {
85
52
  latencyMode: encoding.latencyMode,
86
53
  contentHint: encoding.contentHint,
87
54
  ...(encoding.bitrateMode !== undefined && { bitrateMode: encoding.bitrateMode }),
88
- // `fit` is exact here — the supersampled canvas is a whole multiple
89
- // of the output — but mediabunny requires it whenever both axes are
90
- // given, so it is stated rather than inferred.
55
+ // `fit` is exact (the canvas is a whole multiple of the output) but mediabunny requires it when both axes are given.
91
56
  ...(downsampling && { transform: { width: output.width, height: output.height, fit: 'fill' } }),
92
57
  });
93
58
  this.output = new Output({ format: formatSpec(format).container(), target: this.target });
94
59
  }
95
60
  async start(info) {
96
61
  this.output.addVideoTrack(this.video, { frameRate: info.fps });
97
- // Tracks must be declared before the muxer starts, which is why the
98
- // orchestrator reports this rather than leaving it to be inferred.
62
+ // Tracks must be declared before the muxer starts.
99
63
  if (info.hasAudio) {
100
64
  await ensureAudioEncoder(this.format);
101
65
  this.audio = audioTrackSource(this.format, this.audioOptions.bitrate ?? DEFAULT_AUDIO_BITRATE);
@@ -121,30 +85,13 @@ export class MediabunnyVideoSink {
121
85
  return new Uint8Array(buffer);
122
86
  }
123
87
  }
124
- // ── Main export ──────────────────────────────────────────────────────────────
125
- /** Round up to the nearest even integer — see the call site. */
126
88
  function evenUp(n) {
127
89
  const rounded = Math.round(n);
128
90
  return rounded % 2 === 0 ? rounded : rounded + 1;
129
91
  }
130
- /**
131
- * Longest axis the render surface is allowed to reach.
132
- *
133
- * Supersampling multiplies the surface, not the output: a 4K export at
134
- * `supersample: 2` is drawing 8K frames, and the next step up asks for a
135
- * 15360-wide WebGL surface that most drivers will not give and none will give
136
- * cheaply. 8192 is the smallest `MAX_TEXTURE_SIZE` in wide circulation, so a
137
- * surface inside it is one every machine that can run the export at all can
138
- * also allocate.
139
- */
92
+ /** Longest axis the render surface may reach: a 4K export at `supersample: 2` already draws 8K frames, and 8192 is WebGPU's default `maxTextureDimension2D`. */
140
93
  const MAX_RENDER_AXIS = 8192;
141
- /**
142
- * How far the frame can actually be supersampled at this output size.
143
- *
144
- * Reduced rather than refused: an export that quietly renders at 1× is a
145
- * slightly softer file, and one that throws because the default was too
146
- * ambitious for a 4K project is no file at all.
147
- */
94
+ /** How far the frame can be supersampled at this output size; reduced rather than refused, since a slightly softer file beats no file. */
148
95
  function fittedSupersample(requested, output) {
149
96
  const longest = Math.max(output.width, output.height);
150
97
  let factor = Math.max(1, Math.round(requested));
@@ -153,13 +100,8 @@ function fittedSupersample(requested, output) {
153
100
  return factor;
154
101
  }
155
102
  /**
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).
159
- *
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.
103
+ * The codec to encode with: the one asked for if the container holds it and this platform can encode it at this
104
+ * size, else the format's default, else its fallback (`'avc'` MP4, `'vp8'` WebM). The fallback is not probed.
163
105
  */
164
106
  async function resolveCodec(requested, format, size, fps) {
165
107
  const { codecs, fallback } = formatSpec(format).video;
@@ -205,10 +147,7 @@ export async function resolveVideoEncoding(video, format, output, fps) {
205
147
  ...(video?.bitrateMode !== undefined && { bitrateMode: video.bitrateMode }),
206
148
  };
207
149
  }
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
- */
150
+ /** Turns a `Quality` into bits per second at this size, since a class instance cannot cross a `MessagePort`. */
212
151
  export function plainVideoEncoding(encoding, output) {
213
152
  const { bitrate } = encoding;
214
153
  if (typeof bitrate === 'number')
@@ -224,12 +163,7 @@ export function exportGeometry(viewport, scale, supersample) {
224
163
  const oversample = fittedSupersample(supersample, output);
225
164
  return { output, oversample, renderScale: scale * oversample };
226
165
  }
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
- */
166
+ /** Opens the canvas, renderer, storage and render context an export draws with, resolving once the surface can draw (a frame drawn earlier encodes blank). The canvas is a whole multiple of the output, so the downsample is exact. */
233
167
  export async function openExportSurface(params) {
234
168
  const canvas = document.createElement('canvas');
235
169
  canvas.width = params.canvasSize.width;
@@ -256,16 +190,7 @@ export async function openExportSurface(params) {
256
190
  throw error;
257
191
  }
258
192
  }
259
- /**
260
- * Renders a document frame-by-frame to an offscreen canvas and muxes the result
261
- * into an MP4 or WebM via mediabunny.
262
- *
263
- * The frame loop itself — discovery, state evaluation, the warm-and-re-render retry
264
- * that keeps every frame accurate, audio scheduling, progress and cancellation —
265
- * lives in `renderTimeline` (frame-loop.ts). What this function
266
- * supplies is the browser-specific half: a canvas to draw into, a WebCodecs sink,
267
- * a Web Audio mixer, and the `<a download>` delivery.
268
- */
193
+ /** Renders a document frame by frame to an offscreen canvas and muxes it into MP4/WebM via mediabunny. The frame loop lives in `renderTimeline` (frame-loop.ts); this supplies the browser half: canvas, WebCodecs sink, Web Audio mixer, download. */
269
194
  export async function exportVideo(params) {
270
195
  const {
271
196
  // Not `document`, which would shadow the DOM global this function uses.
@@ -1 +1 @@
1
- {"version":3,"file":"exporter.js","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DEFAULT_OUTPUT, ManifestAssetCatalog, Registry, resolveProjectDocument, type AssetManifest,\n type FrameRange, type ProjectDocument, type RenderIssue, type VisualDocument, type Size2D, type StorageAdapter, type Theme,\n} from \"@motionscript/core\";\nimport {\n renderTimeline,\n type VideoFrameSink,\n} from \"./frame-loop\";\nimport {\n AudioBufferSource,\n BufferTarget,\n CanvasSource,\n canEncodeVideo,\n Output,\n type Quality,\n type VideoCodec,\n} from 'mediabunny';\nimport type { BrowserRenderContext } from \"./engine\";\nimport { loadRenderer, type RendererWasmOption } from \"./renderer\";\nimport { WebAudioMixer } from \"./audio/mixer\";\nimport { deliver } from \"./deliver\";\nimport {\n audioTrackSource, DEFAULT_AUDIO_BITRATE, ensureAudioEncoder, formatSpec, type VideoFormat,\n} from \"./formats\";\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 the format's own:\n * `'avc'` (H.264) for MP4, which every player, editor and upload target\n * accepts, and `'vp9'` for WebM.\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, or one the container cannot hold (`'avc'` in WebM),\n * falls back to the format's default rather than failing the export.\n */\n codec?: 'avc' | 'hevc' | 'av1' | 'vp9' | 'vp8';\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 /** Bitrate in bits per second for AAC, Opus and MP3; WAV ignores it. Defaults to 192 kbps. */\n bitrate?: number;\n /** Mixdown sample rate in Hz. Defaults to the mixer's own (44100); Opus is resampled to 48 kHz. */\n sampleRate?: number;\n};\n\n/** See {@link VideoEncoderOptions.keyFrameInterval}. */\nconst DEFAULT_KEY_FRAME_INTERVAL = 1;\n/** See {@link ExportParams.supersample}. */\nconst DEFAULT_SUPERSAMPLE = 2;\n\nexport type ExportParams = RendererWasmOption & {\n /** A project, or a lone still or composition as the one track of one. */\n document: ProjectDocument | VisualDocument;\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\n registry?: Registry;\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\n viewport?: Size2D;\n fps?: number;\n scale?: number;\n /** The container, and with it the default codecs: H.264 and AAC for `'mp4'` (the default), VP9 and Opus for `'webm'`. */\n format?: VideoFormat;\n /** Defaults to `export.<extension>`. */\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 file — no audio 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 /**\n * Encode only these frames (`end` excluded), timestamped from 0 and silent:\n * one segment of a split export, for `concatVideoSegments` to join. Mix\n * the audio once with `mixTimelineAudio` instead.\n */\n frames?: FrameRange;\n onProgress?: ExportProgressCallback;\n signal?: AbortSignal;\n /**\n * What the renderer drew but not as the document says, once per kind,\n * feature and node. A lost GPU device fails the export with a\n * `RenderBackendError` instead.\n */\n onIssue?: (issue: RenderIssue) => void;\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 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, writing whichever\n * container `format` names.\n *\n * Wraps the very `<canvas>` the renderer 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 */\nexport class 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 /** Encoded output size, which is the canvas' own unless supersampling is on. */\n output: { width: number; height: number },\n private readonly format: VideoFormat,\n encoding: ResolvedVideoEncoding,\n private readonly audioOptions: AudioEncoderOptions = {},\n ) {\n const downsampling = output.width !== canvas.width || output.height !== canvas.height;\n this.video = new CanvasSource(canvas, {\n codec: encoding.codec,\n bitrate: encoding.bitrate,\n hardwareAcceleration: encoding.hardwareAcceleration,\n keyFrameInterval: encoding.keyFrameInterval,\n latencyMode: encoding.latencyMode,\n contentHint: encoding.contentHint,\n ...(encoding.bitrateMode !== undefined && { bitrateMode: encoding.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: formatSpec(format).container(), 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 await ensureAudioEncoder(this.format);\n this.audio = audioTrackSource(this.format, this.audioOptions.bitrate ?? DEFAULT_AUDIO_BITRATE);\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 the container holds\n * it and this platform can encode it at this size, else the format's default,\n * else its fallback (`'avc'` for MP4, `'vp8'` for WebM).\n *\n * Which encoders a machine has varies by OS, browser and GPU, and an export that\n * dies at the muxer for want of an HEVC encoder is worse than one that quietly\n * produces H.264. The fallback is not probed: there is nothing to fall back to.\n */\nasync function resolveCodec(\n requested: VideoCodec | undefined,\n format: VideoFormat,\n size: { width: number; height: number },\n fps: number,\n): Promise<VideoCodec> {\n const { codecs, fallback } = formatSpec(format).video!;\n let wanted = requested ?? codecs[0];\n if (!codecs.includes(wanted)) {\n console.warn(`[motionscript] ${format.toUpperCase()} cannot hold ${wanted}; exporting as ${CODEC_NAMES[codecs[0]]} instead.`);\n wanted = codecs[0];\n }\n for (const codec of new Set([wanted, codecs[0], fallback])) {\n if (codec === fallback || await canEncode(codec, size, fps)) {\n if (codec !== wanted) {\n console.warn(`[motionscript] No ${wanted} encoder available; exporting as ${CODEC_NAMES[codec]} instead.`);\n }\n return codec;\n }\n }\n return fallback;\n}\n\nconst CODEC_NAMES: Record<VideoCodec, string> = { avc: 'H.264', hevc: 'HEVC', vp9: 'VP9', av1: 'AV1', vp8: 'VP8' };\n\nasync function canEncode(codec: VideoCodec, size: { width: number; height: number }, fps: number): Promise<boolean> {\n try {\n return await canEncodeVideo(codec, {\n width: size.width,\n height: size.height,\n bitrate: motionGraphicsBitrate(size.width, size.height, fps),\n });\n } catch {\n // A probe that throws is a probe that answered \"no\".\n return false;\n }\n}\n\n/**\n * Every encoder setting, decided. A split export pins one of these across its\n * segments, since `concatVideoSegments` refuses parts encoded differently. With\n * a numeric `bitrate` (see {@link plainVideoEncoding}) it is plain data and\n * crosses a process boundary.\n */\nexport interface ResolvedVideoEncoding {\n codec: VideoCodec;\n /** A `Quality` is left for the encoder to size, as an export always has. */\n bitrate: number | Quality;\n hardwareAcceleration: NonNullable<VideoEncoderOptions['hardwareAcceleration']>;\n keyFrameInterval: number;\n latencyMode: NonNullable<VideoEncoderOptions['latencyMode']>;\n contentHint: string;\n bitrateMode?: VideoEncoderOptions['bitrateMode'];\n}\n\n/** {@link VideoEncoderOptions} with its defaults applied against the encoded size and rate. */\nexport async function resolveVideoEncoding(\n video: VideoEncoderOptions | undefined,\n format: VideoFormat,\n output: { width: number; height: number },\n fps: number,\n): Promise<ResolvedVideoEncoding> {\n const codec = await resolveCodec(video?.codec, format, output, fps);\n return {\n codec,\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 };\n}\n\n/**\n * The encoding with a `Quality` turned into the bits per second mediabunny would\n * give it at this size, since a class instance cannot cross a `MessagePort`.\n */\nexport function plainVideoEncoding(\n encoding: ResolvedVideoEncoding,\n output: { width: number; height: number },\n): ResolvedVideoEncoding & { bitrate: number } {\n const { bitrate } = encoding;\n if (typeof bitrate === 'number') return { ...encoding, bitrate };\n const quality = bitrate as unknown as { _toVideoBitrate(c: VideoCodec, w: number, h: number): number };\n return { ...encoding, bitrate: quality._toVideoBitrate(encoding.codec, output.width, output.height) };\n}\n\n/** The encoded size, rounded up to even, and how far each frame is supersampled. */\nexport function exportGeometry(viewport: Size2D, scale: number, supersample: number) {\n // Every block-based codec works in macroblocks, so an odd dimension is\n // padded by the encoder with a smeared strip that was never drawn.\n const output = { width: evenUp(viewport.width * scale), height: evenUp(viewport.height * scale) };\n const oversample = fittedSupersample(supersample, output);\n return { output, oversample, renderScale: scale * oversample };\n}\n\n/** A hidden canvas with a mounted render context, reusable across renders of one project. */\nexport interface ExportSurface {\n canvas: HTMLCanvasElement;\n renderContext: BrowserRenderContext;\n storageAdapter: StorageAdapter;\n assetCatalog: ManifestAssetCatalog;\n dispose(): void;\n}\n\n/**\n * Open the canvas, renderer, storage and render context an export draws with,\n * resolving once the surface can draw (a frame drawn before an asynchronously\n * started surface is ready encodes blank). The canvas is a whole multiple of the\n * output, so the downsample is exact.\n */\nexport async function openExportSurface(params: {\n canvasSize: { width: number; height: number };\n viewport: Size2D;\n fps: number;\n manifest: AssetManifest;\n} & RendererWasmOption): Promise<ExportSurface> {\n const canvas = document.createElement('canvas');\n canvas.width = params.canvasSize.width;\n canvas.height = params.canvasSize.height;\n canvas.style.display = 'none';\n document.body.appendChild(canvas);\n try {\n const assetCatalog = new ManifestAssetCatalog(params.manifest);\n const renderer = await loadRenderer(params);\n const storageAdapter = renderer.createStorageAdapter({ catalog: assetCatalog, viewport: params.viewport, fps: params.fps });\n const renderContext = renderer.createRenderContext(storageAdapter);\n renderContext.mount(canvas);\n await renderContext.whenMounted?.();\n return {\n canvas, renderContext, storageAdapter, assetCatalog,\n dispose: () => {\n renderContext.dispose();\n canvas.remove();\n },\n };\n } catch (error) {\n canvas.remove();\n throw error;\n }\n}\n\n/**\n * Renders a document frame-by-frame to an offscreen canvas and muxes the result\n * into an MP4 or WebM 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 `renderTimeline` (frame-loop.ts). 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 format = 'mp4',\n manifest = EMPTY_MANIFEST,\n theme,\n includeAudio = true,\n video,\n audio = {},\n frames,\n onProgress,\n signal,\n rendererWasmUrl,\n supersample = DEFAULT_SUPERSAMPLE,\n returnBytes = false,\n } = params;\n\n const spec = formatSpec(format);\n if (spec.kind !== 'video') throw new RangeError(`exportVideo writes video; \"${format}\" is audio, which exportAudio writes.`);\n const filename = params.filename ?? `export.${spec.extension}`;\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 const { output, oversample, renderScale } = exportGeometry(viewport, scale, supersample);\n const surface = await openExportSurface({\n canvasSize: { width: output.width * oversample, height: output.height * oversample },\n viewport, fps, manifest, rendererWasmUrl,\n });\n\n let bytes: Uint8Array | undefined;\n try {\n const encoding = await resolveVideoEncoding(video, format, output, fps);\n bytes = await renderTimeline<AudioBuffer>({\n project, registry, theme, scale: renderScale, includeAudio, frames,\n renderContext: surface.renderContext, storageAdapter: surface.storageAdapter, assetCatalog: surface.assetCatalog,\n sink: new MediabunnyVideoSink(surface.canvas, output, format, encoding, audio),\n mixer: new WebAudioMixer(audio.sampleRate, surface.assetCatalog),\n onProgress,\n onIssue: params.onIssue,\n signal,\n }) as Uint8Array | undefined;\n } finally {\n surface.dispose();\n }\n\n if (!bytes) return;\n return deliver(bytes, { returnBytes, filename, mimeType: spec.mimeType });\n}\n"]}
1
+ {"version":3,"file":"exporter.js","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DEFAULT_OUTPUT, ManifestAssetCatalog, Registry, resolveProjectDocument, type AssetManifest,\n type FrameRange, type ProjectDocument, type RenderIssue, type VisualDocument, type Size2D, type StorageAdapter, type Theme,\n} from \"@motionscript/core\";\nimport {\n renderTimeline,\n type VideoFrameSink,\n} from \"./frame-loop\";\nimport {\n AudioBufferSource,\n BufferTarget,\n CanvasSource,\n canEncodeVideo,\n Output,\n type Quality,\n type VideoCodec,\n} from 'mediabunny';\nimport type { BrowserRenderContext } from \"./engine\";\nimport { loadRenderer, type RendererWasmOption } from \"./renderer\";\nimport { WebAudioMixer } from \"./audio/mixer\";\nimport { deliver } from \"./deliver\";\nimport {\n audioTrackSource, DEFAULT_AUDIO_BITRATE, ensureAudioEncoder, formatSpec, type VideoFormat,\n} from \"./formats\";\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/** How the video track is encoded; fields pass through to mediabunny's `VideoEncodingConfig`, defaulted here. */\nexport type VideoEncoderOptions = {\n /**\n * Defaults to the format's own: `'avc'` for MP4, `'vp9'` for WebM. `'hevc'`/`'av1'` keep hairline strokes and\n * code listings sharper at the same bitrate (still 4:2:0). An unsupported choice, or one the container cannot\n * hold, falls back to the default.\n */\n codec?: 'avc' | 'hevc' | 'av1' | 'vp9' | 'vp8';\n /**\n * Defaults to `'no-preference'` (the platform's hardware encoder): several times faster, and the only setup where\n * this sink's zero-readback design holds, since a software encoder forces a GPU->CPU copy of every frame.\n * `'prefer-software'` when byte-identical output across machines matters more than speed.\n */\n hardwareAcceleration?: 'no-preference' | 'prefer-hardware' | 'prefer-software';\n /** Bits per second or a mediabunny `Quality`. Defaults to {@link motionGraphicsBitrate}, not `QUALITY_VERY_HIGH`, which is frame-rate blind (ringing on glyph edges at 60 fps). */\n bitrate?: number | Quality;\n /** Defaults to mediabunny's `'variable'`. `'constant'` is the reproducible-output choice. */\n bitrateMode?: 'constant' | 'variable';\n /** Seconds between key frames (default 1). Shorter than mediabunny's 2 because inter frames carry a frame's error, so flat synthetic backgrounds shimmer less; it also halves seek granularity. */\n keyFrameInterval?: number;\n /** Defaults to `'quality'`, which also guarantees no frame is dropped: the only correct setting for a file being written. */\n latencyMode?: 'quality' | 'realtime';\n /** A [content hint](https://w3c.github.io/mst-content-hint/#video-content-hints). Defaults to `'detail'`: bias towards sharp edges and fine texture over smoothing motion. Ignored where unimplemented. */\n contentHint?: string;\n};\n\n/** Bits per pixel per frame for the default bitrate. Motion graphics (hard glyph edges, flat fields, noise-free gradients) compress worse than camera footage, so this is well above a streaming preset. */\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/** Ceiling: past it the returns are gone and AVC levels start excluding hardware decoders (4K60 lands here; level 5.1 allows 240 Mbps). */\nconst MAX_VIDEO_BITRATE = 100_000_000;\n\n/** Default video bitrate scaled by **both** axes: mediabunny's `Quality` levels scale with resolution only, so doubling the frame rate halved the bits per frame (clean stills, mushy video). */\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 /** Bitrate in bits per second for AAC, Opus and MP3; WAV ignores it. Defaults to 192 kbps. */\n bitrate?: number;\n /** Mixdown sample rate in Hz. Defaults to the mixer's own (44100); Opus is resampled to 48 kHz. */\n sampleRate?: number;\n};\n\n/** See {@link VideoEncoderOptions.keyFrameInterval}. */\nconst DEFAULT_KEY_FRAME_INTERVAL = 1;\n/** See {@link ExportParams.supersample}. */\nconst DEFAULT_SUPERSAMPLE = 2;\n\nexport type ExportParams = RendererWasmOption & {\n /** A project, or a lone still or composition as the one track of one. */\n document: ProjectDocument | VisualDocument;\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\n registry?: Registry;\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\n viewport?: Size2D;\n fps?: number;\n scale?: number;\n /** The container, and with it the default codecs: H.264 and AAC for `'mp4'` (the default), VP9 and Opus for `'webm'`. */\n format?: VideoFormat;\n /** Defaults to `export.<extension>`. */\n filename?: string;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n /** Set false to produce a silent file: no audio track, mix skipped. Defaults to true. */\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 /**\n * Encode only these frames (`end` excluded), timestamped from 0 and silent:\n * one segment of a split export, for `concatVideoSegments` to join. Mix\n * the audio once with `mixTimelineAudio` instead.\n */\n frames?: FrameRange;\n onProgress?: ExportProgressCallback;\n signal?: AbortSignal;\n /**\n * What the renderer drew but not as the document says, once per kind,\n * feature and node. A lost GPU device fails the export with a\n * `RenderBackendError` instead.\n */\n onIssue?: (issue: RenderIssue) => void;\n /**\n * Render each frame at this multiple of the output resolution and downsample before encoding (default 2; 1 turns\n * it off). Every browser-encodable codec is 4:2:0, so chroma fringing on saturated edges is a property of the\n * format; downsampling first makes each chroma sample average four real pixels (2 measured 2.7 dB closer to a\n * reference than a 1x encode). Costs `supersample²` render work; `scale: 2` is cheaper and better when the\n * resolution is wanted in the file. Clamped to {@link MAX_RENDER_AXIS}.\n */\n supersample?: number;\n /** Return the encoded bytes instead of triggering a browser download (for headless drivers that write to disk). */\n returnBytes?: boolean;\n}\n\nconst EMPTY_MANIFEST: AssetManifest = {\n image: {},\n video: {},\n audio: {},\n font: {},\n};\n\n/**\n * mediabunny/WebCodecs frame sink wrapping the very canvas the renderer draws into: `addFrame` costs only a timestamp\n * because WebCodecs pulls pixels from the canvas with no GPU->CPU readback (hence the seam signals a frame rather\n * than passing one). Holds only for a hardware encoder; a software one forces a full-frame copy every frame.\n */\nexport class 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 /** Encoded output size, which is the canvas' own unless supersampling is on. */\n output: { width: number; height: number },\n private readonly format: VideoFormat,\n encoding: ResolvedVideoEncoding,\n private readonly audioOptions: AudioEncoderOptions = {},\n ) {\n const downsampling = output.width !== canvas.width || output.height !== canvas.height;\n this.video = new CanvasSource(canvas, {\n codec: encoding.codec,\n bitrate: encoding.bitrate,\n hardwareAcceleration: encoding.hardwareAcceleration,\n keyFrameInterval: encoding.keyFrameInterval,\n latencyMode: encoding.latencyMode,\n contentHint: encoding.contentHint,\n ...(encoding.bitrateMode !== undefined && { bitrateMode: encoding.bitrateMode }),\n // `fit` is exact (the canvas is a whole multiple of the output) but mediabunny requires it when both axes are given.\n ...(downsampling && { transform: { width: output.width, height: output.height, fit: 'fill' as const } }),\n });\n this.output = new Output({ format: formatSpec(format).container(), 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.\n if (info.hasAudio) {\n await ensureAudioEncoder(this.format);\n this.audio = audioTrackSource(this.format, this.audioOptions.bitrate ?? DEFAULT_AUDIO_BITRATE);\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\nfunction evenUp(n: number): number {\n const rounded = Math.round(n);\n return rounded % 2 === 0 ? rounded : rounded + 1;\n}\n\n/** Longest axis the render surface may reach: a 4K export at `supersample: 2` already draws 8K frames, and 8192 is WebGPU's default `maxTextureDimension2D`. */\nconst MAX_RENDER_AXIS = 8192;\n\n/** How far the frame can be supersampled at this output size; reduced rather than refused, since a slightly softer file beats no file. */\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 encode with: the one asked for if the container holds it and this platform can encode it at this\n * size, else the format's default, else its fallback (`'avc'` MP4, `'vp8'` WebM). The fallback is not probed.\n */\nasync function resolveCodec(\n requested: VideoCodec | undefined,\n format: VideoFormat,\n size: { width: number; height: number },\n fps: number,\n): Promise<VideoCodec> {\n const { codecs, fallback } = formatSpec(format).video!;\n let wanted = requested ?? codecs[0];\n if (!codecs.includes(wanted)) {\n console.warn(`[motionscript] ${format.toUpperCase()} cannot hold ${wanted}; exporting as ${CODEC_NAMES[codecs[0]]} instead.`);\n wanted = codecs[0];\n }\n for (const codec of new Set([wanted, codecs[0], fallback])) {\n if (codec === fallback || await canEncode(codec, size, fps)) {\n if (codec !== wanted) {\n console.warn(`[motionscript] No ${wanted} encoder available; exporting as ${CODEC_NAMES[codec]} instead.`);\n }\n return codec;\n }\n }\n return fallback;\n}\n\nconst CODEC_NAMES: Record<VideoCodec, string> = { avc: 'H.264', hevc: 'HEVC', vp9: 'VP9', av1: 'AV1', vp8: 'VP8' };\n\nasync function canEncode(codec: VideoCodec, size: { width: number; height: number }, fps: number): Promise<boolean> {\n try {\n return await canEncodeVideo(codec, {\n width: size.width,\n height: size.height,\n bitrate: motionGraphicsBitrate(size.width, size.height, fps),\n });\n } catch {\n // A probe that throws is a probe that answered \"no\".\n return false;\n }\n}\n\n/** Every encoder setting, decided; a split export pins one across segments since `concatVideoSegments` refuses parts encoded differently. Plain data with a numeric `bitrate` (see {@link plainVideoEncoding}). */\nexport interface ResolvedVideoEncoding {\n codec: VideoCodec;\n /** A `Quality` is left for the encoder to size, as an export always has. */\n bitrate: number | Quality;\n hardwareAcceleration: NonNullable<VideoEncoderOptions['hardwareAcceleration']>;\n keyFrameInterval: number;\n latencyMode: NonNullable<VideoEncoderOptions['latencyMode']>;\n contentHint: string;\n bitrateMode?: VideoEncoderOptions['bitrateMode'];\n}\n\n/** {@link VideoEncoderOptions} with its defaults applied against the encoded size and rate. */\nexport async function resolveVideoEncoding(\n video: VideoEncoderOptions | undefined,\n format: VideoFormat,\n output: { width: number; height: number },\n fps: number,\n): Promise<ResolvedVideoEncoding> {\n const codec = await resolveCodec(video?.codec, format, output, fps);\n return {\n codec,\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 };\n}\n\n/** Turns a `Quality` into bits per second at this size, since a class instance cannot cross a `MessagePort`. */\nexport function plainVideoEncoding(\n encoding: ResolvedVideoEncoding,\n output: { width: number; height: number },\n): ResolvedVideoEncoding & { bitrate: number } {\n const { bitrate } = encoding;\n if (typeof bitrate === 'number') return { ...encoding, bitrate };\n const quality = bitrate as unknown as { _toVideoBitrate(c: VideoCodec, w: number, h: number): number };\n return { ...encoding, bitrate: quality._toVideoBitrate(encoding.codec, output.width, output.height) };\n}\n\n/** The encoded size, rounded up to even, and how far each frame is supersampled. */\nexport function exportGeometry(viewport: Size2D, scale: number, supersample: number) {\n // Every block-based codec works in macroblocks, so an odd dimension is\n // padded by the encoder with a smeared strip that was never drawn.\n const output = { width: evenUp(viewport.width * scale), height: evenUp(viewport.height * scale) };\n const oversample = fittedSupersample(supersample, output);\n return { output, oversample, renderScale: scale * oversample };\n}\n\n/** A hidden canvas with a mounted render context, reusable across renders of one project. */\ninterface ExportSurface {\n canvas: HTMLCanvasElement;\n renderContext: BrowserRenderContext;\n storageAdapter: StorageAdapter;\n assetCatalog: ManifestAssetCatalog;\n dispose(): void;\n}\n\n/** Opens the canvas, renderer, storage and render context an export draws with, resolving once the surface can draw (a frame drawn earlier encodes blank). The canvas is a whole multiple of the output, so the downsample is exact. */\nexport async function openExportSurface(params: {\n canvasSize: { width: number; height: number };\n viewport: Size2D;\n fps: number;\n manifest: AssetManifest;\n} & RendererWasmOption): Promise<ExportSurface> {\n const canvas = document.createElement('canvas');\n canvas.width = params.canvasSize.width;\n canvas.height = params.canvasSize.height;\n canvas.style.display = 'none';\n document.body.appendChild(canvas);\n try {\n const assetCatalog = new ManifestAssetCatalog(params.manifest);\n const renderer = await loadRenderer(params);\n const storageAdapter = renderer.createStorageAdapter({ catalog: assetCatalog, viewport: params.viewport, fps: params.fps });\n const renderContext = renderer.createRenderContext(storageAdapter);\n renderContext.mount(canvas);\n await renderContext.whenMounted?.();\n return {\n canvas, renderContext, storageAdapter, assetCatalog,\n dispose: () => {\n renderContext.dispose();\n canvas.remove();\n },\n };\n } catch (error) {\n canvas.remove();\n throw error;\n }\n}\n\n/** Renders a document frame by frame to an offscreen canvas and muxes it into MP4/WebM via mediabunny. The frame loop lives in `renderTimeline` (frame-loop.ts); this supplies the browser half: canvas, WebCodecs sink, Web Audio mixer, download. */\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 format = 'mp4',\n manifest = EMPTY_MANIFEST,\n theme,\n includeAudio = true,\n video,\n audio = {},\n frames,\n onProgress,\n signal,\n rendererWasmUrl,\n supersample = DEFAULT_SUPERSAMPLE,\n returnBytes = false,\n } = params;\n\n const spec = formatSpec(format);\n if (spec.kind !== 'video') throw new RangeError(`exportVideo writes video; \"${format}\" is audio, which exportAudio writes.`);\n const filename = params.filename ?? `export.${spec.extension}`;\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 const { output, oversample, renderScale } = exportGeometry(viewport, scale, supersample);\n const surface = await openExportSurface({\n canvasSize: { width: output.width * oversample, height: output.height * oversample },\n viewport, fps, manifest, rendererWasmUrl,\n });\n\n let bytes: Uint8Array | undefined;\n try {\n const encoding = await resolveVideoEncoding(video, format, output, fps);\n bytes = await renderTimeline<AudioBuffer>({\n project, registry, theme, scale: renderScale, includeAudio, frames,\n renderContext: surface.renderContext, storageAdapter: surface.storageAdapter, assetCatalog: surface.assetCatalog,\n sink: new MediabunnyVideoSink(surface.canvas, output, format, encoding, audio),\n mixer: new WebAudioMixer(audio.sampleRate, surface.assetCatalog),\n onProgress,\n onIssue: params.onIssue,\n signal,\n }) as Uint8Array | undefined;\n } finally {\n surface.dispose();\n }\n\n if (!bytes) return;\n return deliver(bytes, { returnBytes, filename, mimeType: spec.mimeType });\n}\n"]}
@@ -1,42 +1,15 @@
1
1
  import { AssetCatalog, AudioSink, type AudioRequest, type BuildError, type CanvasRenderContext2D, type AssetTimelineResult, type FrameRange, type ProjectDocument, type Registry, type RenderIssue, type FrameSpec, type StorageAdapter, type Theme } from "@motionscript/core";
2
- /**
3
- * Rendering a timeline is portable; *encoding* it is not.
4
- *
5
- * Everything about driving the frames — discovery, state evaluation, asset
6
- * windows, the bounded warm-and-re-render retry that makes a frame accurate,
7
- * audio scheduling, progress, cancellation — is the same work whatever the
8
- * output format. Only the codec differs, so it is injected as a `VideoFrameSink`
9
- * (exporter.ts: mediabunny over WebCodecs).
10
- */
11
- /** Image formats a still can be encoded to. */
2
+ /** Rendering a timeline is portable; encoding is not, so the codec is injected as a `VideoFrameSink` (exporter.ts: mediabunny over WebCodecs). */
12
3
  export type ScreenshotFormat = "png" | "jpeg";
13
4
  export type { FrameSpec };
14
- /** One scheduled audio request, with the absolute time its track starts at. */
15
5
  export interface ScheduledAudioRequest {
16
6
  request: AudioRequest;
17
- /**
18
- * Seconds to add to the request's own times, which are clip-relative: its
19
- * track's start. Getting it wrong produces an export that looks right and
20
- * drifts out of sync, which no pixel comparison catches.
21
- */
7
+ /** Seconds added to the request's clip-relative times (its track's start). A wrong value exports in apparent sync and drifts, which no pixel comparison catches. */
22
8
  globalOffset: number;
23
9
  }
24
- /**
25
- * Where finished frames go.
26
- *
27
- * `TAudio` ties the sink to its {@link AudioMixer}: what a mixer produces is
28
- * platform-specific (a Web Audio `AudioBuffer`, interleaved PCM, a temp file) and
29
- * only its own sink consumes it, so this module relays it without naming it.
30
- */
10
+ /** Where finished frames go. `TAudio` ties the sink to its {@link AudioMixer}: the mixed form is platform-specific and only its own sink consumes it. */
31
11
  export interface VideoFrameSink<TAudio = unknown> {
32
- /**
33
- * Called once before the first frame, with the resolved output geometry.
34
- *
35
- * `hasAudio` is here rather than left for the sink to infer because a muxer
36
- * generally has to declare its tracks before writing any, and whether the
37
- * timeline schedules any audio is only known after discovery — which happens
38
- * inside the orchestrator, after the sink was constructed.
39
- */
12
+ /** Called once before the first frame. `hasAudio` is passed because a muxer declares its tracks before writing, and whether any audio is scheduled is only known after discovery, which runs after the sink is built. */
40
13
  start(info: {
41
14
  width: number;
42
15
  height: number;
@@ -45,14 +18,9 @@ export interface VideoFrameSink<TAudio = unknown> {
45
18
  hasAudio: boolean;
46
19
  }): Promise<void>;
47
20
  /**
48
- * One frame has been drawn and the surface flushed. Times are in seconds.
49
- *
50
- * **Deliberately does not receive pixels.** A browser sink wraps the canvas
51
- * the renderer already drew into and lets WebCodecs pull from it directly, with no
52
- * readback; handing a buffer here instead would force a full GPU→CPU
53
- * `readPixels` on every frame of every export. A sink that *does* need bytes
54
- * (ffmpeg over stdin) reads them itself, because it holds the render target it
55
- * was constructed with. Do not "simplify" this signature.
21
+ * One frame has been drawn and the surface flushed. **Deliberately does not receive pixels**: a browser sink lets
22
+ * WebCodecs pull from the canvas, while a buffer here would force a GPU->CPU `readPixels` on every frame. A sink
23
+ * that needs bytes (ffmpeg over stdin) reads them from its own render target. Do not "simplify" this signature.
56
24
  */
57
25
  addFrame(timestamp: number, duration: number): Promise<void>;
58
26
  /** Mixed audio for the whole timeline, if any was produced. */
@@ -64,11 +32,7 @@ export interface VideoFrameSink<TAudio = unknown> {
64
32
  export interface AudioMixer<TAudio = unknown> {
65
33
  mix(requests: readonly ScheduledAudioRequest[], totalDuration: number): Promise<TAudio | null>;
66
34
  }
67
- /**
68
- * Audio is mixed offline at the end of a run (or irrelevant, for a still), so the
69
- * `AssetManager` just needs an `AudioSink`-shaped sink that does nothing during
70
- * the per-frame pass.
71
- */
35
+ /** Audio is mixed offline at the end, so the per-frame pass needs an `AudioSink` that does nothing. */
72
36
  export declare class NoopAudioSink extends AudioSink {
73
37
  /** Nothing here records a request, so `AssetManager` can skip the whole sync. */
74
38
  get schedulesAudio(): boolean;
@@ -80,58 +44,35 @@ export declare class NoopAudioSink extends AudioSink {
80
44
  play(_time: number, _speed: number, _reverse: boolean): void;
81
45
  stop(): void;
82
46
  }
83
- /** The platform pieces both entry points need. */
84
- /**
85
- * What the frame loop draws with. Any backend's context and adapter: the loop
86
- * uses only core's base members, so another renderer exports through it too.
87
- */
47
+ /** What the frame loop draws with: any backend's context and adapter (the loop uses only core's base members). */
88
48
  interface RenderHarness {
89
49
  renderContext: CanvasRenderContext2D & {
90
50
  pixelRatio: number;
91
51
  };
92
- /** The same adapter the context was built with. */
93
52
  storageAdapter: StorageAdapter;
94
53
  assetCatalog: AssetCatalog;
95
54
  }
96
- /** Fields shared by the video and still entry points. */
97
55
  interface CommonParams extends RenderHarness {
98
56
  /** Its fps and viewport resolved — see `Session`. */
99
57
  project: ProjectDocument;
100
- /** The vocabulary its clips compile against. */
101
58
  registry: Registry;
102
59
  /** Theme for a project that carries none of its own. */
103
60
  theme?: Theme;
104
- /**
105
- * What the backend drew but not as the document says (a feature it lacks,
106
- * a shader that does not compile, a GPU error), once per kind, feature and
107
- * node. A lost device or a crashed backend is not reported here: the render
108
- * throws a {@link RenderBackendError} instead.
109
- */
61
+ /** What the backend drew but not as the document says, once per kind, feature and node. A lost device or crashed backend throws a {@link RenderBackendError} instead. */
110
62
  onIssue?: (issue: RenderIssue) => void;
111
63
  }
112
64
  /**
113
- * Collect every audio request with the absolute offset it plays at. See
114
- * {@link ScheduledAudioRequest.globalOffset}.
115
- *
116
- * Exported because a **split** export has to mix its audio somewhere other than
117
- * alongside its frames: the video is rendered in pieces by separate workers, and
118
- * concatenating separately-encoded AAC would put the encoder's priming delay at
119
- * every join. The coordinator mixes once over the whole timeline instead — and
120
- * must schedule it exactly as this does. A second implementation that drifted by
121
- * a track offset would produce a file that plays perfectly and is out of sync.
65
+ * Collects every audio request with the absolute offset it plays at ({@link ScheduledAudioRequest.globalOffset}).
66
+ * Exported because a **split** export mixes once over the whole timeline in the coordinator (concatenated AAC would
67
+ * put the encoder's priming delay at every join) and must schedule exactly as this does: a drifted copy would give
68
+ * a file that plays perfectly and is out of sync.
122
69
  */
123
70
  export declare function collectAudio(precomp: AssetTimelineResult): ScheduledAudioRequest[];
124
71
  export interface RenderTimelineParams<TAudio> extends CommonParams {
125
72
  scale?: number;
126
73
  sink: VideoFrameSink<TAudio>;
127
74
  mixer?: AudioMixer<TAudio>;
128
- /**
129
- * Set false to render a silent file: no mix, and no audio track declared on
130
- * the container at all.
131
- *
132
- * Distinct from muting. The audio is still discovered — the pass that finds
133
- * it is the one that finds the frames' assets — it simply is not encoded.
134
- */
75
+ /** Set false for a silent file: no mix and no audio track. Not muting: audio is still discovered (the same pass finds the frames' assets), just not encoded. */
135
76
  includeAudio?: boolean;
136
77
  /**
137
78
  * Render only these frames, timestamped from 0, with no audio: one segment of a
@@ -141,50 +82,23 @@ export interface RenderTimelineParams<TAudio> extends CommonParams {
141
82
  onProgress?: (progress: number) => void;
142
83
  signal?: AbortSignal;
143
84
  }
144
- /**
145
- * Drive every frame of `project` (or of `frames`) into `sink`, then mix audio and finalize.
146
- *
147
- * The caller owns the render context and its surface — this neither creates nor
148
- * disposes them, because how a surface comes to exist is exactly the thing that
149
- * differs between backends.
150
- */
85
+ /** Drives every frame of `project` (or `frames`) into `sink`, then mixes audio and finalizes. The caller owns the render context and surface; this neither creates nor disposes them. */
151
86
  export declare function renderTimeline<TAudio>(params: RenderTimelineParams<TAudio>): Promise<Uint8Array | void>;
152
87
  export interface DrawFrameParams extends CommonParams {
153
88
  scale?: number;
154
89
  frame: FrameSpec;
155
90
  }
156
91
  export interface DrawnFrame {
157
- /** The frame actually drawn, after clamping the request into range. */
158
92
  frame: number;
159
93
  totalFrames: number;
160
94
  }
161
- /**
162
- * Every track covering {@link frame} failed to build. Drawn anyway, it would be
163
- * a blank frame — the consequence, with the cause hidden — so a still and an
164
- * export both refuse it.
165
- */
95
+ /** Every track covering {@link frame} failed to build; drawn anyway it would be a blank frame with the cause hidden, so a still and an export both refuse it. */
166
96
  export declare class NothingToDrawError extends Error {
167
97
  readonly frame: number;
168
- /** One per track covering the frame. */
169
98
  readonly failures: readonly BuildError[];
170
99
  readonly name = "NothingToDrawError";
171
- constructor(frame: number,
172
- /** One per track covering the frame. */
173
- failures: readonly BuildError[]);
100
+ constructor(frame: number, failures: readonly BuildError[]);
174
101
  }
175
- /**
176
- * Draw one frame of `project` into the caller's render context and leave it on
177
- * the surface.
178
- *
179
- * Same discovery/evaluator/warm-retry path as {@link renderTimeline}, so a still
180
- * is the same render the video would produce at that frame — and only the tracks
181
- * on that frame are discovered, since a track's span is known without it.
182
- *
183
- * The drawing is shared while what happens to the frame varies: a still encodes
184
- * it, a live preview leaves it on a visible canvas. The context is the
185
- * caller's throughout — this neither creates, resizes nor disposes it — so a host
186
- * holding one surface can call this repeatedly and keep both the surface and the
187
- * storage adapter's decode cache warm.
188
- */
102
+ /** Draws one frame into the caller's render context and leaves it on the surface, via the same path as {@link renderTimeline} so a still equals the video's frame. Only tracks on that frame are discovered. The context stays the caller's (never created, resized or disposed), so a host can call this repeatedly and keep the decode cache warm. */
189
103
  export declare function drawFrameAt(params: DrawFrameParams): Promise<DrawnFrame>;
190
104
  //# sourceMappingURL=frame-loop.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"frame-loop.d.ts","sourceRoot":"","sources":["../src/frame-loop.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"frame-loop.d.ts","sourceRoot":"","sources":["../src/frame-loop.ts"],"names":[],"mappings":"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"}