@motionscript/browser 0.1.0-alpha.6 → 0.1.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/NOTICE +15 -14
- package/README.md +66 -43
- package/dist/asset-bytes.d.ts +7 -4
- package/dist/asset-bytes.d.ts.map +1 -1
- package/dist/asset-bytes.js +9 -4
- package/dist/asset-bytes.js.map +1 -1
- package/dist/audio/bus-graph.d.ts.map +1 -1
- package/dist/audio/bus-graph.js +15 -6
- package/dist/audio/bus-graph.js.map +1 -1
- package/dist/audio/effect-graph.d.ts +62 -0
- package/dist/audio/effect-graph.d.ts.map +1 -0
- package/dist/audio/effect-graph.js +234 -0
- package/dist/audio/effect-graph.js.map +1 -0
- package/dist/audio/index.d.ts.map +1 -1
- package/dist/audio/index.js +2 -3
- package/dist/audio/index.js.map +1 -1
- package/dist/audio/mixer.d.ts +4 -4
- package/dist/audio/mixer.d.ts.map +1 -1
- package/dist/audio/mixer.js +18 -38
- package/dist/audio/mixer.js.map +1 -1
- package/dist/audio/player.d.ts +12 -6
- package/dist/audio/player.d.ts.map +1 -1
- package/dist/audio/player.js +49 -62
- package/dist/audio/player.js.map +1 -1
- package/dist/browser/THIRD_PARTY_LICENSES.txt +379 -27
- package/dist/browser/audio.js +1 -1
- package/dist/browser/chunks/chunk-IILVN4SK.js +2 -0
- package/dist/browser/chunks/chunk-IILVN4SK.js.map +7 -0
- package/dist/browser/chunks/chunk-KLXMRKTL.js +2 -0
- package/dist/browser/chunks/chunk-L5UFQACH.js +163 -0
- package/dist/browser/chunks/chunk-L5UFQACH.js.map +7 -0
- package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js +14 -0
- package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js.map +7 -0
- package/dist/browser/index.js +51 -1343
- package/dist/browser/index.js.map +4 -4
- package/dist/browser/manifest.json +8 -8
- package/dist/browser/ms_renderer_bg.wasm +0 -0
- package/dist/canvaskit-renderer.d.ts +8 -0
- package/dist/canvaskit-renderer.d.ts.map +1 -0
- package/dist/canvaskit-renderer.js +23 -0
- package/dist/canvaskit-renderer.js.map +1 -0
- package/dist/concat.d.ts +33 -7
- package/dist/concat.d.ts.map +1 -1
- package/dist/concat.js +117 -72
- package/dist/concat.js.map +1 -1
- package/dist/deliver.d.ts +10 -0
- package/dist/deliver.d.ts.map +1 -0
- package/dist/deliver.js +22 -0
- package/dist/deliver.js.map +1 -0
- package/dist/dynamic-nodes.d.ts.map +1 -1
- package/dist/dynamic-nodes.js +2 -3
- package/dist/dynamic-nodes.js.map +1 -1
- package/dist/engine.d.ts +37 -9
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +41 -32
- package/dist/engine.js.map +1 -1
- package/dist/export-audio.d.ts +36 -0
- package/dist/export-audio.d.ts.map +1 -0
- package/dist/export-audio.js +56 -0
- package/dist/export-audio.js.map +1 -0
- package/dist/exporter.d.ts +125 -14
- package/dist/exporter.d.ts.map +1 -1
- package/dist/exporter.js +133 -89
- package/dist/exporter.js.map +1 -1
- package/dist/formats.d.ts +36 -0
- package/dist/formats.d.ts.map +1 -0
- package/dist/formats.js +83 -0
- package/dist/formats.js.map +1 -0
- package/dist/frame-loop.d.ts +190 -0
- package/dist/frame-loop.d.ts.map +1 -0
- package/dist/frame-loop.js +304 -0
- package/dist/frame-loop.js.map +1 -0
- package/dist/getter.d.ts +4 -1
- package/dist/getter.d.ts.map +1 -1
- package/dist/getter.js +7 -6
- package/dist/getter.js.map +1 -1
- package/dist/index.d.ts +12 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -21
- package/dist/index.js.map +1 -1
- package/dist/mix-timeline-audio.d.ts +5 -5
- package/dist/mix-timeline-audio.d.ts.map +1 -1
- package/dist/mix-timeline-audio.js +10 -12
- package/dist/mix-timeline-audio.js.map +1 -1
- package/dist/parallel/export-parallel.d.ts +32 -0
- package/dist/parallel/export-parallel.d.ts.map +1 -0
- package/dist/parallel/export-parallel.js +114 -0
- package/dist/parallel/export-parallel.js.map +1 -0
- package/dist/parallel/governor.d.ts +57 -0
- package/dist/parallel/governor.d.ts.map +1 -0
- package/dist/parallel/governor.js +79 -0
- package/dist/parallel/governor.js.map +1 -0
- package/dist/parallel/lane.d.ts +45 -0
- package/dist/parallel/lane.d.ts.map +1 -0
- package/dist/parallel/lane.js +2 -0
- package/dist/parallel/lane.js.map +1 -0
- package/dist/parallel/lanes.d.ts +20 -0
- package/dist/parallel/lanes.d.ts.map +1 -0
- package/dist/parallel/lanes.js +152 -0
- package/dist/parallel/lanes.js.map +1 -0
- package/dist/parallel/plan.d.ts +23 -0
- package/dist/parallel/plan.d.ts.map +1 -0
- package/dist/parallel/plan.js +30 -0
- package/dist/parallel/plan.js.map +1 -0
- package/dist/parallel/run-lanes.d.ts +38 -0
- package/dist/parallel/run-lanes.d.ts.map +1 -0
- package/dist/parallel/run-lanes.js +164 -0
- package/dist/parallel/run-lanes.js.map +1 -0
- package/dist/parallel/session.d.ts +9 -0
- package/dist/parallel/session.d.ts.map +1 -0
- package/dist/parallel/session.js +79 -0
- package/dist/parallel/session.js.map +1 -0
- package/dist/render-context.d.ts +2 -0
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js +8 -4
- package/dist/render-context.js.map +1 -1
- package/dist/renderer.d.ts +22 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +23 -0
- package/dist/renderer.js.map +1 -0
- package/dist/screenshot.d.ts +4 -4
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +3 -3
- package/dist/screenshot.js.map +1 -1
- package/dist/still.d.ts +17 -19
- package/dist/still.d.ts.map +1 -1
- package/dist/still.js +27 -34
- package/dist/still.js.map +1 -1
- package/dist/ticker.d.ts +22 -8
- package/dist/ticker.d.ts.map +1 -1
- package/dist/ticker.js +55 -20
- package/dist/ticker.js.map +1 -1
- package/package.json +9 -10
- package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js +0 -2
- package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js.map +0 -7
- package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js +0 -2
- package/dist/browser/chunks/chunk-2MAFJWBZ.js +0 -2
- package/dist/browser/chunks/chunk-2MAFJWBZ.js.map +0 -7
- package/dist/browser/chunks/chunk-CTLBXZSU.js +0 -4119
- package/dist/browser/chunks/chunk-CTLBXZSU.js.map +0 -7
- package/dist/browser/chunks/chunk-FQ32PDIU.js +0 -2
- package/dist/browser/chunks/chunk-FQ32PDIU.js.map +0 -7
- package/dist/browser/chunks/chunk-FY3BTKY4.js +0 -2
- package/dist/browser/chunks/chunk-FY3BTKY4.js.map +0 -7
- package/dist/browser/chunks/three.module-Z4EOVUWF.js +0 -2
- package/dist/browser/chunks/three.module-Z4EOVUWF.js.map +0 -7
- /package/dist/browser/chunks/{SkeletonUtils-XYDMTGTM.js.map → chunk-KLXMRKTL.js.map} +0 -0
package/dist/exporter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exporter.js","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"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","sourcesContent":["import {\r\n DEFAULT_OUTPUT, ManifestAssetCatalog, Registry, resolveProjectDocument, type AssetManifest,\r\n type FrameRange, type ProjectDocument, type VisualDocument, type Size2D, type Theme,\r\n} from \"@motionscript/core\";\r\nimport {\r\n renderTimeline,\r\n type VideoFrameSink,\r\n} from \"@motionscript/skia-render/export\";\r\nimport {\r\n AudioBufferSource,\r\n BufferTarget,\r\n CanvasSource,\r\n canEncodeVideo,\r\n Mp4OutputFormat,\r\n Output,\r\n type Quality,\r\n type VideoCodec,\r\n} from 'mediabunny';\r\nimport { WebRenderContext } from \"./render-context\";\r\nimport { WebStorageAdapter } from \"./storage-adapter\";\r\nimport { getCanvasKit } from \"./getter\";\r\nimport { WebAudioMixer } from \"./audio/mixer\";\r\n\r\n/** Reports export progress in [0, 1]; video encoding occupies most of the range, audio mixing/finalize the tail. */\r\nexport type ExportProgressCallback = (progress: number) => void;\r\n\r\n/**\r\n * How the video track is encoded.\r\n *\r\n * Every field is a straight pass-through to mediabunny's `VideoEncodingConfig`,\r\n * defaulted here rather than there so an export has one obvious place to look.\r\n */\r\nexport type VideoEncoderOptions = {\r\n /**\r\n * Which codec the video track is encoded with. Defaults to `'avc'` (H.264),\r\n * which every player, editor and upload target accepts.\r\n *\r\n * `'hevc'` and `'av1'` reconstruct sharp edges far better at the same\r\n * bitrate, which is what a listing of syntax-highlighted code or a hairline\r\n * stroke is made of — worth reaching for when the file is going somewhere\r\n * that decodes them. Both are still 4:2:0, so they narrow the gap rather\r\n * than close it. Availability depends on the platform's encoders; an\r\n * unsupported choice falls back to `'avc'` rather than failing the export.\r\n */\r\n codec?: 'avc' | 'hevc' | 'av1' | 'vp9';\r\n /**\r\n * Which encoder implementation WebCodecs should prefer.\r\n *\r\n * Defaults to `'no-preference'`, which lets each platform use its own\r\n * hardware encoder (VideoToolbox on macOS, Media Foundation/NVENC on\r\n * Windows, VA-API on Linux). That is typically several times faster than the\r\n * software path *and* it is the only configuration in which this sink's\r\n * zero-readback design actually holds: a software encoder needs the pixels\r\n * on the CPU, so it forces a full GPU→CPU copy of every frame that a\r\n * hardware encoder reads directly off the canvas.\r\n *\r\n * Pass `'prefer-software'` when byte-identical output across machines\r\n * matters more than speed — different hardware encoders tune quality and\r\n * colour differently at the same nominal bitrate, so the same settings do\r\n * not produce the same file.\r\n */\r\n hardwareAcceleration?: 'no-preference' | 'prefer-hardware' | 'prefer-software';\r\n /**\r\n * Target bitrate in bits per second, or one of mediabunny's subjective\r\n * `Quality` levels.\r\n *\r\n * Defaults to {@link motionGraphicsBitrate}, which is deliberately *not*\r\n * mediabunny's `QUALITY_VERY_HIGH`: that level is frame-rate blind, so a\r\n * 60 fps export got the same ~12 Mbps at 1080p as a 30 fps one and paid for\r\n * it with ringing along every glyph edge and banding across every gradient.\r\n * See that function for the reasoning.\r\n */\r\n bitrate?: number | Quality;\r\n /** Defaults to mediabunny's `'variable'`. `'constant'` is the reproducible-output choice. */\r\n bitrateMode?: 'constant' | 'variable';\r\n /**\r\n * Seconds between key frames. Defaults to 1.\r\n *\r\n * Shorter than mediabunny's 2, because a frame's error is carried by every\r\n * inter frame that references it: on synthetic content, where a flat\r\n * background makes a shimmer obvious, a keyframe twice as often visibly\r\n * resets the drift. It also halves the seek granularity, which matters for a\r\n * clip an editor is going to scrub.\r\n */\r\n keyFrameInterval?: number;\r\n /**\r\n * WebCodecs performance/quality trade-off. Defaults to `'quality'`, which\r\n * also guarantees no frame is dropped — the only correct setting for a file\r\n * being written rather than streamed.\r\n */\r\n latencyMode?: 'quality' | 'realtime';\r\n /**\r\n * A hint about what the frames contain, per\r\n * [mst-content-hint](https://w3c.github.io/mst-content-hint/#video-content-hints).\r\n *\r\n * Defaults to `'detail'`: encoders that honour it bias towards preserving\r\n * sharp edges and fine texture over smoothing motion, which is what motion\r\n * graphics are. Ignored where it is not implemented.\r\n */\r\n contentHint?: string;\r\n};\r\n\r\n/**\r\n * Bits per pixel per frame the default bitrate is built from.\r\n *\r\n * Rendered motion graphics are the hard case for a codec tuned on camera\r\n * footage: hard glyph edges, large flat fields, and gradients with no sensor\r\n * noise to hide the quantisation in. They compress *worse* than live action at\r\n * the same perceived quality, not better, so the figure here is well above what\r\n * a streaming preset would use — this is a master being rendered once, not a\r\n * bitstream being pushed down a pipe.\r\n */\r\nconst BITS_PER_PIXEL = 0.2;\r\n/** Floor, so a tiny viewport still gets enough bits to be legible. */\r\nconst MIN_VIDEO_BITRATE = 2_000_000;\r\n/**\r\n * Ceiling. Past this the returns are gone and the levels start excluding\r\n * hardware decoders — 4K60 lands here, and AVC level 5.1 (which every consumer\r\n * device implements) tops out at 240 Mbps, well clear of it.\r\n */\r\nconst MAX_VIDEO_BITRATE = 100_000_000;\r\n\r\n/**\r\n * The default video bitrate for an export of this size and frame rate.\r\n *\r\n * Scaling with **both** axes is the point. mediabunny's `Quality` levels scale\r\n * with resolution only, so doubling the frame rate halved the bits available per\r\n * frame while leaving the number unchanged — which is exactly the configuration\r\n * that produced clean stills and a mushy video from the very same renderer.\r\n */\r\nexport function motionGraphicsBitrate(width: number, height: number, fps: number): number {\r\n const raw = width * height * fps * BITS_PER_PIXEL;\r\n return Math.round(Math.min(MAX_VIDEO_BITRATE, Math.max(MIN_VIDEO_BITRATE, raw)));\r\n}\r\n\r\n/** How the mixed audio track is encoded. */\r\nexport type AudioEncoderOptions = {\r\n /** AAC bitrate in bits per second. Defaults to 192 kbps. */\r\n bitrate?: number;\r\n /** Mixdown sample rate in Hz. Defaults to the mixer's own (44100). */\r\n sampleRate?: number;\r\n};\r\n\r\nconst DEFAULT_AUDIO_BITRATE = 192_000;\r\n/** See {@link VideoEncoderOptions.keyFrameInterval}. */\r\nconst DEFAULT_KEY_FRAME_INTERVAL = 1;\r\n/** See {@link ExportParams.supersample}. */\r\nconst DEFAULT_SUPERSAMPLE = 2;\r\n\r\nexport type ExportParams = {\r\n /** A project, or a lone still or composition as the one track of one. */\r\n document: ProjectDocument | VisualDocument;\r\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\r\n registry?: Registry;\r\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\r\n viewport?: Size2D;\r\n fps?: number;\r\n scale?: number;\r\n filename?: string;\r\n manifest?: AssetManifest;\r\n /** Theme for a document that carries none of its own. */\r\n theme?: Theme;\r\n /**\r\n * Set false to produce a silent MP4 — no AAC track is added to the\r\n * container and the mix is skipped entirely. Defaults to true.\r\n */\r\n includeAudio?: boolean;\r\n /** Video encoder settings; see {@link VideoEncoderOptions} for the defaults. */\r\n video?: VideoEncoderOptions;\r\n /** Audio encoder settings; ignored when `includeAudio` is false. */\r\n audio?: AudioEncoderOptions;\r\n /**\r\n * Encode only these frames (`end` excluded), timestamped from 0 and silent:\r\n * one segment of a split export, for `concatVideoSegments` to join. Mix\r\n * the audio once with `mixTimelineAudio` instead.\r\n */\r\n frames?: FrameRange;\r\n onProgress?: ExportProgressCallback;\r\n signal?: AbortSignal;\r\n wasmUrl?: string;\r\n /**\r\n * Render each frame at this multiple of the output resolution and downsample\r\n * it before encoding. **Defaults to 2**; pass 1 to turn it off.\r\n *\r\n * The one lever that improves an export at a *fixed* output size. Every\r\n * codec a browser can encode is 4:2:0 — colour is stored at half resolution\r\n * on both axes, and no bitrate buys that back — so the colour fringing along\r\n * a saturated glyph or a hard edge is a property of the format, not of the\r\n * settings. Handing the encoder an already-downsampled frame does not undo\r\n * the subsampling, but it does mean every chroma sample averages four real\r\n * pixels instead of one, which is what removes most of the crawl on fine\r\n * detail and much of the banding on gradients.\r\n *\r\n * Measured against a correctly-sampled reference frame of a syntax-highlighted\r\n * listing, `2` lands 2.7 dB closer than a 1× encode — and 1.3 dB closer than\r\n * an *unencoded* 1× still, which is why it is the default despite costing\r\n * `supersample²` times the render work per frame plus a rescale. Pass 1 when\r\n * turnaround matters more than the file, and note that exporting at\r\n * `scale: 2` is cheaper than supersampling *and* better whenever the extra\r\n * resolution is wanted in the file itself.\r\n *\r\n * Clamped so the render surface stays inside {@link MAX_RENDER_AXIS}, since a\r\n * supersampled 4K export is already drawing 8K frames.\r\n */\r\n supersample?: number;\r\n /**\r\n * When true, the encoded MP4 bytes are returned instead of triggering a\r\n * browser download. Used by headless drivers (e.g. the CLI) that capture\r\n * the output themselves and write it to disk; leave unset for the\r\n * interactive player, which downloads via an `<a>` element.\r\n */\r\n returnBytes?: boolean;\r\n}\r\n\r\nconst EMPTY_MANIFEST: AssetManifest = {\r\n image: {},\r\n video: {},\r\n audio: {},\r\n font: {},\r\n};\r\n\r\n// ── Audio mixing ─────────────────────────────────────────────────────────────\r\n//\r\n// The mix itself lives in ./audio/mixer, shared with `mixTimelineAudio` so a\r\n// split export and a whole one cannot mix differently.\r\n\r\n// ── Video sink ───────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * mediabunny/WebCodecs implementation of the frame sink.\r\n *\r\n * Wraps the very `<canvas>` CanvasKit draws into, so `addFrame` costs nothing but\r\n * a timestamp — WebCodecs pulls the pixels from the canvas itself, with no\r\n * GPU→CPU readback. That is why the seam signals a frame rather than passing one.\r\n *\r\n * The readback claim holds only while the encoder is a hardware one, which is\r\n * why {@link VideoEncoderOptions.hardwareAcceleration} defaults to letting the\r\n * platform choose: a software encoder has to be handed CPU pixels, so it drags\r\n * a full-frame copy off the GPU on every single frame.\r\n */\r\nclass MediabunnyVideoSink implements VideoFrameSink<AudioBuffer> {\r\n private readonly target = new BufferTarget();\r\n private readonly output: Output;\r\n private readonly video: CanvasSource;\r\n private audio: AudioBufferSource | null = null;\r\n\r\n constructor(\r\n canvas: HTMLCanvasElement,\r\n fps: number,\r\n /** Encoded output size, which is the canvas' own unless supersampling is on. */\r\n output: { width: number; height: number },\r\n video: VideoEncoderOptions = {},\r\n private readonly audioOptions: AudioEncoderOptions = {},\r\n codec: VideoCodec = 'avc',\r\n ) {\r\n const downsampling = output.width !== canvas.width || output.height !== canvas.height;\r\n this.video = new CanvasSource(canvas, {\r\n codec,\r\n // Resolved against what is actually being encoded — the *output*\r\n // size and the rate the frame loop will feed it — rather than\r\n // against a subjective level that never sees the frame rate.\r\n bitrate: video.bitrate ?? motionGraphicsBitrate(output.width, output.height, fps),\r\n hardwareAcceleration: video.hardwareAcceleration ?? 'no-preference',\r\n keyFrameInterval: video.keyFrameInterval ?? DEFAULT_KEY_FRAME_INTERVAL,\r\n latencyMode: video.latencyMode ?? 'quality',\r\n contentHint: video.contentHint ?? 'detail',\r\n ...(video.bitrateMode !== undefined && { bitrateMode: video.bitrateMode }),\r\n // `fit` is exact here — the supersampled canvas is a whole multiple\r\n // of the output — but mediabunny requires it whenever both axes are\r\n // given, so it is stated rather than inferred.\r\n ...(downsampling && { transform: { width: output.width, height: output.height, fit: 'fill' as const } }),\r\n });\r\n this.output = new Output({ format: new Mp4OutputFormat(), target: this.target });\r\n }\r\n\r\n async start(info: { fps: number; hasAudio: boolean }): Promise<void> {\r\n this.output.addVideoTrack(this.video, { frameRate: info.fps });\r\n // Tracks must be declared before the muxer starts, which is why the\r\n // orchestrator reports this rather than leaving it to be inferred.\r\n if (info.hasAudio) {\r\n this.audio = new AudioBufferSource({\r\n codec: 'aac',\r\n bitrate: this.audioOptions.bitrate ?? DEFAULT_AUDIO_BITRATE,\r\n });\r\n this.output.addAudioTrack(this.audio);\r\n }\r\n await this.output.start();\r\n }\r\n\r\n async addFrame(timestamp: number, duration: number): Promise<void> {\r\n await this.video.add(timestamp, duration);\r\n }\r\n\r\n async addAudio(mixed: AudioBuffer): Promise<void> {\r\n if (!this.audio) return;\r\n await this.audio.add(mixed);\r\n }\r\n\r\n async finalize(): Promise<Uint8Array> {\r\n this.video.close();\r\n this.audio?.close();\r\n await this.output.finalize();\r\n const buffer = this.target.buffer;\r\n if (!buffer) throw new Error('Export produced no data');\r\n return new Uint8Array(buffer);\r\n }\r\n}\r\n\r\n// ── Main export ──────────────────────────────────────────────────────────────\r\n\r\n/** Round up to the nearest even integer — see the call site. */\r\nfunction evenUp(n: number): number {\r\n const rounded = Math.round(n);\r\n return rounded % 2 === 0 ? rounded : rounded + 1;\r\n}\r\n\r\n/**\r\n * Longest axis the render surface is allowed to reach.\r\n *\r\n * Supersampling multiplies the surface, not the output: a 4K export at\r\n * `supersample: 2` is drawing 8K frames, and the next step up asks for a\r\n * 15360-wide WebGL surface that most drivers will not give and none will give\r\n * cheaply. 8192 is the smallest `MAX_TEXTURE_SIZE` in wide circulation, so a\r\n * surface inside it is one every machine that can run the export at all can\r\n * also allocate.\r\n */\r\nconst MAX_RENDER_AXIS = 8192;\r\n\r\n/**\r\n * How far the frame can actually be supersampled at this output size.\r\n *\r\n * Reduced rather than refused: an export that quietly renders at 1× is a\r\n * slightly softer file, and one that throws because the default was too\r\n * ambitious for a 4K project is no file at all.\r\n */\r\nfunction fittedSupersample(requested: number, output: { width: number; height: number }): number {\r\n const longest = Math.max(output.width, output.height);\r\n let factor = Math.max(1, Math.round(requested));\r\n while (factor > 1 && longest * factor > MAX_RENDER_AXIS) factor--;\r\n return factor;\r\n}\r\n\r\n/**\r\n * The codec to actually encode with: the one asked for if this platform can\r\n * encode it at this size, `'avc'` otherwise.\r\n *\r\n * A better codec is the single largest quality win available on synthetic\r\n * content — but which ones a machine has encoders for varies by OS, browser and\r\n * GPU, and an export that dies at the muxer because the machine has no HEVC\r\n * encoder is worse than one that quietly produces H.264. AVC is not probed: it\r\n * is the fallback, and there is nothing to fall back to.\r\n */\r\nasync function resolveCodec(\r\n requested: VideoCodec | undefined,\r\n size: { width: number; height: number },\r\n fps: number,\r\n): Promise<VideoCodec> {\r\n if (!requested || requested === 'avc') return 'avc';\r\n try {\r\n const ok = await canEncodeVideo(requested, {\r\n width: size.width,\r\n height: size.height,\r\n bitrate: motionGraphicsBitrate(size.width, size.height, fps),\r\n });\r\n if (ok) return requested;\r\n } catch {\r\n // A probe that throws is a probe that answered \"no\".\r\n }\r\n console.warn(`[motionscript] No ${requested} encoder available; exporting as H.264 instead.`);\r\n return 'avc';\r\n}\r\n\r\n/**\r\n * Renders a document frame-by-frame to an offscreen canvas and muxes the result\r\n * into an MP4 via mediabunny.\r\n *\r\n * The frame loop itself — discovery, state evaluation, the warm-and-re-render retry\r\n * that keeps every frame accurate, audio scheduling, progress and cancellation —\r\n * lives in `@motionscript/skia-render`'s `renderTimeline`. What this function\r\n * supplies is the browser-specific half: a canvas to draw into, a WebCodecs sink,\r\n * a Web Audio mixer, and the `<a download>` delivery.\r\n */\r\nexport async function exportVideo(params: ExportParams): Promise<Uint8Array | void> {\r\n const {\r\n // Not `document`, which would shadow the DOM global this function uses.\r\n document: source,\r\n registry = new Registry(),\r\n scale = 1,\r\n filename = 'export.mp4',\r\n manifest = EMPTY_MANIFEST,\r\n theme,\r\n includeAudio = true,\r\n video,\r\n audio = {},\r\n frames,\r\n onProgress,\r\n signal,\r\n wasmUrl,\r\n supersample = DEFAULT_SUPERSAMPLE,\r\n returnBytes = false,\r\n } = params;\r\n\r\n const project = resolveProjectDocument(source, params, DEFAULT_OUTPUT);\r\n if (project.tracks.length === 0) return;\r\n signal?.throwIfAborted();\r\n const viewport = project.viewport!;\r\n const fps = project.fps!;\r\n\r\n // Every block-based codec works in macroblocks, so an odd dimension is\r\n // padded by the encoder and the padding is *not* what was drawn — it shows up\r\n // as a smeared or discoloured strip down one edge. Rounding up to an even\r\n // size instead costs at most half a pixel of centring and keeps the whole\r\n // frame real.\r\n const outputSize = {\r\n width: evenUp(viewport.width * scale),\r\n height: evenUp(viewport.height * scale),\r\n };\r\n const oversample = fittedSupersample(supersample, outputSize);\r\n const renderScale = scale * oversample;\r\n\r\n const offscreenCanvas = document.createElement('canvas');\r\n // A whole multiple of the output, so the downsample is exact: the picture is drawn at\r\n // `renderScale` from the corner, as it is at 1×, and any odd-size padding is a strip.\r\n offscreenCanvas.width = outputSize.width * oversample;\r\n offscreenCanvas.height = outputSize.height * oversample;\r\n offscreenCanvas.style.display = 'none';\r\n document.body.appendChild(offscreenCanvas);\r\n\r\n const canvasKit = await getCanvasKit(wasmUrl);\r\n const assetCatalog = new ManifestAssetCatalog(manifest);\r\n const storageAdapter = new WebStorageAdapter(canvasKit, assetCatalog, viewport, fps);\r\n const renderContext = new WebRenderContext(canvasKit, storageAdapter);\r\n renderContext.mount(offscreenCanvas);\r\n\r\n const codec = await resolveCodec(video?.codec, outputSize, fps);\r\n\r\n let bytes: Uint8Array | undefined;\r\n try {\r\n bytes = await renderTimeline<AudioBuffer>({\r\n project, registry, theme, scale: renderScale, includeAudio, frames,\r\n renderContext, storageAdapter, assetCatalog,\r\n sink: new MediabunnyVideoSink(offscreenCanvas, fps, outputSize, video, audio, codec),\r\n mixer: new WebAudioMixer(audio.sampleRate, assetCatalog),\r\n onProgress,\r\n signal,\r\n }) as Uint8Array | undefined;\r\n } finally {\r\n renderContext.dispose();\r\n document.body.removeChild(offscreenCanvas);\r\n }\r\n\r\n if (!bytes) return;\r\n\r\n // Headless callers capture the bytes and write them to disk themselves,\r\n // so skip the DOM-based download path entirely.\r\n if (returnBytes) return bytes;\r\n\r\n // The sink builds this Uint8Array over a whole, plain ArrayBuffer, so the cast\r\n // is sound — TS only balks because the generic parameter admits a\r\n // SharedArrayBuffer, which a BlobPart may not be.\r\n const blob = new Blob([bytes.buffer as ArrayBuffer], { type: 'video/mp4' });\r\n const url = URL.createObjectURL(blob);\r\n const link = document.createElement('a');\r\n link.href = url;\r\n link.download = filename;\r\n document.body.appendChild(link);\r\n link.click();\r\n document.body.removeChild(link);\r\n URL.revokeObjectURL(url);\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"exporter.js","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"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","sourcesContent":["import {\r\n DEFAULT_OUTPUT, ManifestAssetCatalog, Registry, resolveProjectDocument, type AssetManifest,\r\n type FrameRange, type ProjectDocument, type RenderIssue, type VisualDocument, type Size2D, type StorageAdapter, type Theme,\r\n} from \"@motionscript/core\";\r\nimport {\r\n renderTimeline,\r\n type VideoFrameSink,\r\n} from \"./frame-loop\";\r\nimport {\r\n AudioBufferSource,\r\n BufferTarget,\r\n CanvasSource,\r\n canEncodeVideo,\r\n Output,\r\n type Quality,\r\n type VideoCodec,\r\n} from 'mediabunny';\r\nimport type { BrowserRenderContext } from \"./engine\";\r\nimport { loadRenderer, type RendererWasmOption } from \"./renderer\";\r\nimport { WebAudioMixer } from \"./audio/mixer\";\r\nimport { deliver } from \"./deliver\";\r\nimport {\r\n audioTrackSource, DEFAULT_AUDIO_BITRATE, ensureAudioEncoder, formatSpec, type VideoFormat,\r\n} from \"./formats\";\r\n\r\n/** Reports export progress in [0, 1]; video encoding occupies most of the range, audio mixing/finalize the tail. */\r\nexport type ExportProgressCallback = (progress: number) => void;\r\n\r\n/**\r\n * How the video track is encoded.\r\n *\r\n * Every field is a straight pass-through to mediabunny's `VideoEncodingConfig`,\r\n * defaulted here rather than there so an export has one obvious place to look.\r\n */\r\nexport type VideoEncoderOptions = {\r\n /**\r\n * Which codec the video track is encoded with. Defaults to the format's own:\r\n * `'avc'` (H.264) for MP4, which every player, editor and upload target\r\n * accepts, and `'vp9'` for WebM.\r\n *\r\n * `'hevc'` and `'av1'` reconstruct sharp edges far better at the same\r\n * bitrate, which is what a listing of syntax-highlighted code or a hairline\r\n * stroke is made of — worth reaching for when the file is going somewhere\r\n * that decodes them. Both are still 4:2:0, so they narrow the gap rather\r\n * than close it. Availability depends on the platform's encoders; an\r\n * unsupported choice, or one the container cannot hold (`'avc'` in WebM),\r\n * falls back to the format's default rather than failing the export.\r\n */\r\n codec?: 'avc' | 'hevc' | 'av1' | 'vp9' | 'vp8';\r\n /**\r\n * Which encoder implementation WebCodecs should prefer.\r\n *\r\n * Defaults to `'no-preference'`, which lets each platform use its own\r\n * hardware encoder (VideoToolbox on macOS, Media Foundation/NVENC on\r\n * Windows, VA-API on Linux). That is typically several times faster than the\r\n * software path *and* it is the only configuration in which this sink's\r\n * zero-readback design actually holds: a software encoder needs the pixels\r\n * on the CPU, so it forces a full GPU→CPU copy of every frame that a\r\n * hardware encoder reads directly off the canvas.\r\n *\r\n * Pass `'prefer-software'` when byte-identical output across machines\r\n * matters more than speed — different hardware encoders tune quality and\r\n * colour differently at the same nominal bitrate, so the same settings do\r\n * not produce the same file.\r\n */\r\n hardwareAcceleration?: 'no-preference' | 'prefer-hardware' | 'prefer-software';\r\n /**\r\n * Target bitrate in bits per second, or one of mediabunny's subjective\r\n * `Quality` levels.\r\n *\r\n * Defaults to {@link motionGraphicsBitrate}, which is deliberately *not*\r\n * mediabunny's `QUALITY_VERY_HIGH`: that level is frame-rate blind, so a\r\n * 60 fps export got the same ~12 Mbps at 1080p as a 30 fps one and paid for\r\n * it with ringing along every glyph edge and banding across every gradient.\r\n * See that function for the reasoning.\r\n */\r\n bitrate?: number | Quality;\r\n /** Defaults to mediabunny's `'variable'`. `'constant'` is the reproducible-output choice. */\r\n bitrateMode?: 'constant' | 'variable';\r\n /**\r\n * Seconds between key frames. Defaults to 1.\r\n *\r\n * Shorter than mediabunny's 2, because a frame's error is carried by every\r\n * inter frame that references it: on synthetic content, where a flat\r\n * background makes a shimmer obvious, a keyframe twice as often visibly\r\n * resets the drift. It also halves the seek granularity, which matters for a\r\n * clip an editor is going to scrub.\r\n */\r\n keyFrameInterval?: number;\r\n /**\r\n * WebCodecs performance/quality trade-off. Defaults to `'quality'`, which\r\n * also guarantees no frame is dropped — the only correct setting for a file\r\n * being written rather than streamed.\r\n */\r\n latencyMode?: 'quality' | 'realtime';\r\n /**\r\n * A hint about what the frames contain, per\r\n * [mst-content-hint](https://w3c.github.io/mst-content-hint/#video-content-hints).\r\n *\r\n * Defaults to `'detail'`: encoders that honour it bias towards preserving\r\n * sharp edges and fine texture over smoothing motion, which is what motion\r\n * graphics are. Ignored where it is not implemented.\r\n */\r\n contentHint?: string;\r\n};\r\n\r\n/**\r\n * Bits per pixel per frame the default bitrate is built from.\r\n *\r\n * Rendered motion graphics are the hard case for a codec tuned on camera\r\n * footage: hard glyph edges, large flat fields, and gradients with no sensor\r\n * noise to hide the quantisation in. They compress *worse* than live action at\r\n * the same perceived quality, not better, so the figure here is well above what\r\n * a streaming preset would use — this is a master being rendered once, not a\r\n * bitstream being pushed down a pipe.\r\n */\r\nconst BITS_PER_PIXEL = 0.2;\r\n/** Floor, so a tiny viewport still gets enough bits to be legible. */\r\nconst MIN_VIDEO_BITRATE = 2_000_000;\r\n/**\r\n * Ceiling. Past this the returns are gone and the levels start excluding\r\n * hardware decoders — 4K60 lands here, and AVC level 5.1 (which every consumer\r\n * device implements) tops out at 240 Mbps, well clear of it.\r\n */\r\nconst MAX_VIDEO_BITRATE = 100_000_000;\r\n\r\n/**\r\n * The default video bitrate for an export of this size and frame rate.\r\n *\r\n * Scaling with **both** axes is the point. mediabunny's `Quality` levels scale\r\n * with resolution only, so doubling the frame rate halved the bits available per\r\n * frame while leaving the number unchanged — which is exactly the configuration\r\n * that produced clean stills and a mushy video from the very same renderer.\r\n */\r\nexport function motionGraphicsBitrate(width: number, height: number, fps: number): number {\r\n const raw = width * height * fps * BITS_PER_PIXEL;\r\n return Math.round(Math.min(MAX_VIDEO_BITRATE, Math.max(MIN_VIDEO_BITRATE, raw)));\r\n}\r\n\r\n/** How the mixed audio track is encoded. */\r\nexport type AudioEncoderOptions = {\r\n /** Bitrate in bits per second for AAC, Opus and MP3; WAV ignores it. Defaults to 192 kbps. */\r\n bitrate?: number;\r\n /** Mixdown sample rate in Hz. Defaults to the mixer's own (44100); Opus is resampled to 48 kHz. */\r\n sampleRate?: number;\r\n};\r\n\r\n/** See {@link VideoEncoderOptions.keyFrameInterval}. */\r\nconst DEFAULT_KEY_FRAME_INTERVAL = 1;\r\n/** See {@link ExportParams.supersample}. */\r\nconst DEFAULT_SUPERSAMPLE = 2;\r\n\r\nexport type ExportParams = RendererWasmOption & {\r\n /** A project, or a lone still or composition as the one track of one. */\r\n document: ProjectDocument | VisualDocument;\r\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\r\n registry?: Registry;\r\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\r\n viewport?: Size2D;\r\n fps?: number;\r\n scale?: number;\r\n /** The container, and with it the default codecs: H.264 and AAC for `'mp4'` (the default), VP9 and Opus for `'webm'`. */\r\n format?: VideoFormat;\r\n /** Defaults to `export.<extension>`. */\r\n filename?: string;\r\n manifest?: AssetManifest;\r\n /** Theme for a document that carries none of its own. */\r\n theme?: Theme;\r\n /**\r\n * Set false to produce a silent file — no audio track is added to the\r\n * container and the mix is skipped entirely. Defaults to true.\r\n */\r\n includeAudio?: boolean;\r\n /** Video encoder settings; see {@link VideoEncoderOptions} for the defaults. */\r\n video?: VideoEncoderOptions;\r\n /** Audio encoder settings; ignored when `includeAudio` is false. */\r\n audio?: AudioEncoderOptions;\r\n /**\r\n * Encode only these frames (`end` excluded), timestamped from 0 and silent:\r\n * one segment of a split export, for `concatVideoSegments` to join. Mix\r\n * the audio once with `mixTimelineAudio` instead.\r\n */\r\n frames?: FrameRange;\r\n onProgress?: ExportProgressCallback;\r\n signal?: AbortSignal;\r\n /**\r\n * What the renderer drew but not as the document says, once per kind,\r\n * feature and node. A lost GPU device fails the export with a\r\n * `RenderBackendError` instead.\r\n */\r\n onIssue?: (issue: RenderIssue) => void;\r\n /**\r\n * Render each frame at this multiple of the output resolution and downsample\r\n * it before encoding. **Defaults to 2**; pass 1 to turn it off.\r\n *\r\n * The one lever that improves an export at a *fixed* output size. Every\r\n * codec a browser can encode is 4:2:0 — colour is stored at half resolution\r\n * on both axes, and no bitrate buys that back — so the colour fringing along\r\n * a saturated glyph or a hard edge is a property of the format, not of the\r\n * settings. Handing the encoder an already-downsampled frame does not undo\r\n * the subsampling, but it does mean every chroma sample averages four real\r\n * pixels instead of one, which is what removes most of the crawl on fine\r\n * detail and much of the banding on gradients.\r\n *\r\n * Measured against a correctly-sampled reference frame of a syntax-highlighted\r\n * listing, `2` lands 2.7 dB closer than a 1× encode — and 1.3 dB closer than\r\n * an *unencoded* 1× still, which is why it is the default despite costing\r\n * `supersample²` times the render work per frame plus a rescale. Pass 1 when\r\n * turnaround matters more than the file, and note that exporting at\r\n * `scale: 2` is cheaper than supersampling *and* better whenever the extra\r\n * resolution is wanted in the file itself.\r\n *\r\n * Clamped so the render surface stays inside {@link MAX_RENDER_AXIS}, since a\r\n * supersampled 4K export is already drawing 8K frames.\r\n */\r\n supersample?: number;\r\n /**\r\n * When true, the encoded bytes are returned instead of triggering a\r\n * browser download. Used by headless drivers (e.g. the CLI) that capture\r\n * the output themselves and write it to disk; leave unset for the\r\n * interactive player, which downloads via an `<a>` element.\r\n */\r\n returnBytes?: boolean;\r\n}\r\n\r\nconst EMPTY_MANIFEST: AssetManifest = {\r\n image: {},\r\n video: {},\r\n audio: {},\r\n font: {},\r\n};\r\n\r\n// ── Audio mixing ─────────────────────────────────────────────────────────────\r\n//\r\n// The mix itself lives in ./audio/mixer, shared with `mixTimelineAudio` so a\r\n// split export and a whole one cannot mix differently.\r\n\r\n// ── Video sink ───────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * mediabunny/WebCodecs implementation of the frame sink, writing whichever\r\n * container `format` names.\r\n *\r\n * Wraps the very `<canvas>` the renderer draws into, so `addFrame` costs nothing but\r\n * a timestamp — WebCodecs pulls the pixels from the canvas itself, with no\r\n * GPU→CPU readback. That is why the seam signals a frame rather than passing one.\r\n *\r\n * The readback claim holds only while the encoder is a hardware one, which is\r\n * why {@link VideoEncoderOptions.hardwareAcceleration} defaults to letting the\r\n * platform choose: a software encoder has to be handed CPU pixels, so it drags\r\n * a full-frame copy off the GPU on every single frame.\r\n */\r\nexport class MediabunnyVideoSink implements VideoFrameSink<AudioBuffer> {\r\n private readonly target = new BufferTarget();\r\n private readonly output: Output;\r\n private readonly video: CanvasSource;\r\n private audio: AudioBufferSource | null = null;\r\n\r\n constructor(\r\n canvas: HTMLCanvasElement,\r\n /** Encoded output size, which is the canvas' own unless supersampling is on. */\r\n output: { width: number; height: number },\r\n private readonly format: VideoFormat,\r\n encoding: ResolvedVideoEncoding,\r\n private readonly audioOptions: AudioEncoderOptions = {},\r\n ) {\r\n const downsampling = output.width !== canvas.width || output.height !== canvas.height;\r\n this.video = new CanvasSource(canvas, {\r\n codec: encoding.codec,\r\n bitrate: encoding.bitrate,\r\n hardwareAcceleration: encoding.hardwareAcceleration,\r\n keyFrameInterval: encoding.keyFrameInterval,\r\n latencyMode: encoding.latencyMode,\r\n contentHint: encoding.contentHint,\r\n ...(encoding.bitrateMode !== undefined && { bitrateMode: encoding.bitrateMode }),\r\n // `fit` is exact here — the supersampled canvas is a whole multiple\r\n // of the output — but mediabunny requires it whenever both axes are\r\n // given, so it is stated rather than inferred.\r\n ...(downsampling && { transform: { width: output.width, height: output.height, fit: 'fill' as const } }),\r\n });\r\n this.output = new Output({ format: formatSpec(format).container(), target: this.target });\r\n }\r\n\r\n async start(info: { fps: number; hasAudio: boolean }): Promise<void> {\r\n this.output.addVideoTrack(this.video, { frameRate: info.fps });\r\n // Tracks must be declared before the muxer starts, which is why the\r\n // orchestrator reports this rather than leaving it to be inferred.\r\n if (info.hasAudio) {\r\n await ensureAudioEncoder(this.format);\r\n this.audio = audioTrackSource(this.format, this.audioOptions.bitrate ?? DEFAULT_AUDIO_BITRATE);\r\n this.output.addAudioTrack(this.audio);\r\n }\r\n await this.output.start();\r\n }\r\n\r\n async addFrame(timestamp: number, duration: number): Promise<void> {\r\n await this.video.add(timestamp, duration);\r\n }\r\n\r\n async addAudio(mixed: AudioBuffer): Promise<void> {\r\n if (!this.audio) return;\r\n await this.audio.add(mixed);\r\n }\r\n\r\n async finalize(): Promise<Uint8Array> {\r\n this.video.close();\r\n this.audio?.close();\r\n await this.output.finalize();\r\n const buffer = this.target.buffer;\r\n if (!buffer) throw new Error('Export produced no data');\r\n return new Uint8Array(buffer);\r\n }\r\n}\r\n\r\n// ── Main export ──────────────────────────────────────────────────────────────\r\n\r\n/** Round up to the nearest even integer — see the call site. */\r\nfunction evenUp(n: number): number {\r\n const rounded = Math.round(n);\r\n return rounded % 2 === 0 ? rounded : rounded + 1;\r\n}\r\n\r\n/**\r\n * Longest axis the render surface is allowed to reach.\r\n *\r\n * Supersampling multiplies the surface, not the output: a 4K export at\r\n * `supersample: 2` is drawing 8K frames, and the next step up asks for a\r\n * 15360-wide WebGL surface that most drivers will not give and none will give\r\n * cheaply. 8192 is the smallest `MAX_TEXTURE_SIZE` in wide circulation, so a\r\n * surface inside it is one every machine that can run the export at all can\r\n * also allocate.\r\n */\r\nconst MAX_RENDER_AXIS = 8192;\r\n\r\n/**\r\n * How far the frame can actually be supersampled at this output size.\r\n *\r\n * Reduced rather than refused: an export that quietly renders at 1× is a\r\n * slightly softer file, and one that throws because the default was too\r\n * ambitious for a 4K project is no file at all.\r\n */\r\nfunction fittedSupersample(requested: number, output: { width: number; height: number }): number {\r\n const longest = Math.max(output.width, output.height);\r\n let factor = Math.max(1, Math.round(requested));\r\n while (factor > 1 && longest * factor > MAX_RENDER_AXIS) factor--;\r\n return factor;\r\n}\r\n\r\n/**\r\n * The codec to actually encode with: the one asked for if the container holds\r\n * it and this platform can encode it at this size, else the format's default,\r\n * else its fallback (`'avc'` for MP4, `'vp8'` for WebM).\r\n *\r\n * Which encoders a machine has varies by OS, browser and GPU, and an export that\r\n * dies at the muxer for want of an HEVC encoder is worse than one that quietly\r\n * produces H.264. The fallback is not probed: there is nothing to fall back to.\r\n */\r\nasync function resolveCodec(\r\n requested: VideoCodec | undefined,\r\n format: VideoFormat,\r\n size: { width: number; height: number },\r\n fps: number,\r\n): Promise<VideoCodec> {\r\n const { codecs, fallback } = formatSpec(format).video!;\r\n let wanted = requested ?? codecs[0];\r\n if (!codecs.includes(wanted)) {\r\n console.warn(`[motionscript] ${format.toUpperCase()} cannot hold ${wanted}; exporting as ${CODEC_NAMES[codecs[0]]} instead.`);\r\n wanted = codecs[0];\r\n }\r\n for (const codec of new Set([wanted, codecs[0], fallback])) {\r\n if (codec === fallback || await canEncode(codec, size, fps)) {\r\n if (codec !== wanted) {\r\n console.warn(`[motionscript] No ${wanted} encoder available; exporting as ${CODEC_NAMES[codec]} instead.`);\r\n }\r\n return codec;\r\n }\r\n }\r\n return fallback;\r\n}\r\n\r\nconst CODEC_NAMES: Record<VideoCodec, string> = { avc: 'H.264', hevc: 'HEVC', vp9: 'VP9', av1: 'AV1', vp8: 'VP8' };\r\n\r\nasync function canEncode(codec: VideoCodec, size: { width: number; height: number }, fps: number): Promise<boolean> {\r\n try {\r\n return await canEncodeVideo(codec, {\r\n width: size.width,\r\n height: size.height,\r\n bitrate: motionGraphicsBitrate(size.width, size.height, fps),\r\n });\r\n } catch {\r\n // A probe that throws is a probe that answered \"no\".\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Every encoder setting, decided. A split export pins one of these across its\r\n * segments, since `concatVideoSegments` refuses parts encoded differently. With\r\n * a numeric `bitrate` (see {@link plainVideoEncoding}) it is plain data and\r\n * crosses a process boundary.\r\n */\r\nexport interface ResolvedVideoEncoding {\r\n codec: VideoCodec;\r\n /** A `Quality` is left for the encoder to size, as an export always has. */\r\n bitrate: number | Quality;\r\n hardwareAcceleration: NonNullable<VideoEncoderOptions['hardwareAcceleration']>;\r\n keyFrameInterval: number;\r\n latencyMode: NonNullable<VideoEncoderOptions['latencyMode']>;\r\n contentHint: string;\r\n bitrateMode?: VideoEncoderOptions['bitrateMode'];\r\n}\r\n\r\n/** {@link VideoEncoderOptions} with its defaults applied against the encoded size and rate. */\r\nexport async function resolveVideoEncoding(\r\n video: VideoEncoderOptions | undefined,\r\n format: VideoFormat,\r\n output: { width: number; height: number },\r\n fps: number,\r\n): Promise<ResolvedVideoEncoding> {\r\n const codec = await resolveCodec(video?.codec, format, output, fps);\r\n return {\r\n codec,\r\n bitrate: video?.bitrate ?? motionGraphicsBitrate(output.width, output.height, fps),\r\n hardwareAcceleration: video?.hardwareAcceleration ?? 'no-preference',\r\n keyFrameInterval: video?.keyFrameInterval ?? DEFAULT_KEY_FRAME_INTERVAL,\r\n latencyMode: video?.latencyMode ?? 'quality',\r\n contentHint: video?.contentHint ?? 'detail',\r\n ...(video?.bitrateMode !== undefined && { bitrateMode: video.bitrateMode }),\r\n };\r\n}\r\n\r\n/**\r\n * The encoding with a `Quality` turned into the bits per second mediabunny would\r\n * give it at this size, since a class instance cannot cross a `MessagePort`.\r\n */\r\nexport function plainVideoEncoding(\r\n encoding: ResolvedVideoEncoding,\r\n output: { width: number; height: number },\r\n): ResolvedVideoEncoding & { bitrate: number } {\r\n const { bitrate } = encoding;\r\n if (typeof bitrate === 'number') return { ...encoding, bitrate };\r\n const quality = bitrate as unknown as { _toVideoBitrate(c: VideoCodec, w: number, h: number): number };\r\n return { ...encoding, bitrate: quality._toVideoBitrate(encoding.codec, output.width, output.height) };\r\n}\r\n\r\n/** The encoded size, rounded up to even, and how far each frame is supersampled. */\r\nexport function exportGeometry(viewport: Size2D, scale: number, supersample: number) {\r\n // Every block-based codec works in macroblocks, so an odd dimension is\r\n // padded by the encoder with a smeared strip that was never drawn.\r\n const output = { width: evenUp(viewport.width * scale), height: evenUp(viewport.height * scale) };\r\n const oversample = fittedSupersample(supersample, output);\r\n return { output, oversample, renderScale: scale * oversample };\r\n}\r\n\r\n/** A hidden canvas with a mounted render context, reusable across renders of one project. */\r\nexport interface ExportSurface {\r\n canvas: HTMLCanvasElement;\r\n renderContext: BrowserRenderContext;\r\n storageAdapter: StorageAdapter;\r\n assetCatalog: ManifestAssetCatalog;\r\n dispose(): void;\r\n}\r\n\r\n/**\r\n * Open the canvas, renderer, storage and render context an export draws with,\r\n * resolving once the surface can draw (a frame drawn before an asynchronously\r\n * started surface is ready encodes blank). The canvas is a whole multiple of the\r\n * output, so the downsample is exact.\r\n */\r\nexport async function openExportSurface(params: {\r\n canvasSize: { width: number; height: number };\r\n viewport: Size2D;\r\n fps: number;\r\n manifest: AssetManifest;\r\n} & RendererWasmOption): Promise<ExportSurface> {\r\n const canvas = document.createElement('canvas');\r\n canvas.width = params.canvasSize.width;\r\n canvas.height = params.canvasSize.height;\r\n canvas.style.display = 'none';\r\n document.body.appendChild(canvas);\r\n try {\r\n const assetCatalog = new ManifestAssetCatalog(params.manifest);\r\n const renderer = await loadRenderer(params);\r\n const storageAdapter = renderer.createStorageAdapter({ catalog: assetCatalog, viewport: params.viewport, fps: params.fps });\r\n const renderContext = renderer.createRenderContext(storageAdapter);\r\n renderContext.mount(canvas);\r\n await renderContext.whenMounted?.();\r\n return {\r\n canvas, renderContext, storageAdapter, assetCatalog,\r\n dispose: () => {\r\n renderContext.dispose();\r\n canvas.remove();\r\n },\r\n };\r\n } catch (error) {\r\n canvas.remove();\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * Renders a document frame-by-frame to an offscreen canvas and muxes the result\r\n * into an MP4 or WebM via mediabunny.\r\n *\r\n * The frame loop itself — discovery, state evaluation, the warm-and-re-render retry\r\n * that keeps every frame accurate, audio scheduling, progress and cancellation —\r\n * lives in `renderTimeline` (frame-loop.ts). What this function\r\n * supplies is the browser-specific half: a canvas to draw into, a WebCodecs sink,\r\n * a Web Audio mixer, and the `<a download>` delivery.\r\n */\r\nexport async function exportVideo(params: ExportParams): Promise<Uint8Array | void> {\r\n const {\r\n // Not `document`, which would shadow the DOM global this function uses.\r\n document: source,\r\n registry = new Registry(),\r\n scale = 1,\r\n format = 'mp4',\r\n manifest = EMPTY_MANIFEST,\r\n theme,\r\n includeAudio = true,\r\n video,\r\n audio = {},\r\n frames,\r\n onProgress,\r\n signal,\r\n rendererWasmUrl,\r\n supersample = DEFAULT_SUPERSAMPLE,\r\n returnBytes = false,\r\n } = params;\r\n\r\n const spec = formatSpec(format);\r\n if (spec.kind !== 'video') throw new RangeError(`exportVideo writes video; \"${format}\" is audio, which exportAudio writes.`);\r\n const filename = params.filename ?? `export.${spec.extension}`;\r\n const project = resolveProjectDocument(source, params, DEFAULT_OUTPUT);\r\n if (project.tracks.length === 0) return;\r\n signal?.throwIfAborted();\r\n const viewport = project.viewport!;\r\n const fps = project.fps!;\r\n\r\n const { output, oversample, renderScale } = exportGeometry(viewport, scale, supersample);\r\n const surface = await openExportSurface({\r\n canvasSize: { width: output.width * oversample, height: output.height * oversample },\r\n viewport, fps, manifest, rendererWasmUrl,\r\n });\r\n\r\n let bytes: Uint8Array | undefined;\r\n try {\r\n const encoding = await resolveVideoEncoding(video, format, output, fps);\r\n bytes = await renderTimeline<AudioBuffer>({\r\n project, registry, theme, scale: renderScale, includeAudio, frames,\r\n renderContext: surface.renderContext, storageAdapter: surface.storageAdapter, assetCatalog: surface.assetCatalog,\r\n sink: new MediabunnyVideoSink(surface.canvas, output, format, encoding, audio),\r\n mixer: new WebAudioMixer(audio.sampleRate, surface.assetCatalog),\r\n onProgress,\r\n onIssue: params.onIssue,\r\n signal,\r\n }) as Uint8Array | undefined;\r\n } finally {\r\n surface.dispose();\r\n }\r\n\r\n if (!bytes) return;\r\n return deliver(bytes, { returnBytes, filename, mimeType: spec.mimeType });\r\n}\r\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AudioBufferSource, type AudioCodec, type OutputFormat, type VideoCodec } from "mediabunny";
|
|
2
|
+
/** A file with a picture: rendered frame by frame, with the mixed audio as a second track. */
|
|
3
|
+
export type VideoFormat = "mp4" | "webm";
|
|
4
|
+
/** A file of the timeline's mixed audio alone; nothing is drawn. */
|
|
5
|
+
export type AudioFormat = "wav" | "mp3" | "m4a";
|
|
6
|
+
export type ExportFormat = VideoFormat | AudioFormat;
|
|
7
|
+
/** What a host needs to name, save and play an exported file. */
|
|
8
|
+
export interface ExportFormatInfo {
|
|
9
|
+
kind: "video" | "audio";
|
|
10
|
+
/** Without the dot. */
|
|
11
|
+
extension: string;
|
|
12
|
+
mimeType: string;
|
|
13
|
+
}
|
|
14
|
+
interface FormatSpec extends ExportFormatInfo {
|
|
15
|
+
container: () => OutputFormat;
|
|
16
|
+
audioCodec: AudioCodec;
|
|
17
|
+
/** Video formats only: what the container takes, the default first, and the codec used unprobed when none of those encode. */
|
|
18
|
+
video?: {
|
|
19
|
+
codecs: readonly VideoCodec[];
|
|
20
|
+
fallback: VideoCodec;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare const EXPORT_FORMATS: Readonly<Record<ExportFormat, ExportFormatInfo>>;
|
|
24
|
+
export declare function formatSpec(format: ExportFormat): FormatSpec;
|
|
25
|
+
export declare function isAudioFormat(format: ExportFormat): format is AudioFormat;
|
|
26
|
+
export declare const DEFAULT_AUDIO_BITRATE = 192000;
|
|
27
|
+
/** The mixed-audio track for `format`. Ensure its encoder first with {@link ensureAudioEncoder}. */
|
|
28
|
+
export declare function audioTrackSource(format: ExportFormat, bitrate?: number): AudioBufferSource;
|
|
29
|
+
/**
|
|
30
|
+
* Throws before a frame is rendered when this browser cannot encode `format`'s
|
|
31
|
+
* audio. No browser ships an MP3 encoder, so LAME is loaded on first use (a
|
|
32
|
+
* WASM worker from a `blob:` URL, which a host's CSP must allow).
|
|
33
|
+
*/
|
|
34
|
+
export declare function ensureAudioEncoder(format: ExportFormat): Promise<void>;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=formats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formats.d.ts","sourceRoot":"","sources":["../src/formats.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,iBAAiB,EAMjB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,UAAU,EAClB,MAAM,YAAY,CAAC;AAEpB,8FAA8F;AAC9F,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,CAAC;AACzC,oEAAoE;AACpE,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAChD,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,WAAW,CAAC;AAErD,iEAAiE;AACjE,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IACxB,uBAAuB;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,UAAW,SAAQ,gBAAgB;IACzC,SAAS,EAAE,MAAM,YAAY,CAAC;IAC9B,UAAU,EAAE,UAAU,CAAC;IACvB,8HAA8H;IAC9H,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;QAAC,QAAQ,EAAE,UAAU,CAAA;KAAE,CAAC;CACnE;AAgCD,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAW,CAAC;AAExF,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,CAI3D;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,IAAI,WAAW,CAEzE;AAED,eAAO,MAAM,qBAAqB,SAAU,CAAC;AAG7C,oGAAoG;AACpG,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,SAAwB,GAAG,iBAAiB,CAQzG;AAID;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB5E"}
|
package/dist/formats.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { AudioBufferSource, Mp3OutputFormat, Mp4OutputFormat, WavOutputFormat, WebMOutputFormat, canEncodeAudio, } from "mediabunny";
|
|
2
|
+
const FORMATS = {
|
|
3
|
+
mp4: {
|
|
4
|
+
kind: "video", extension: "mp4", mimeType: "video/mp4",
|
|
5
|
+
container: () => new Mp4OutputFormat(),
|
|
6
|
+
audioCodec: "aac",
|
|
7
|
+
video: { codecs: ["avc", "hevc", "av1", "vp9"], fallback: "avc" },
|
|
8
|
+
},
|
|
9
|
+
webm: {
|
|
10
|
+
kind: "video", extension: "webm", mimeType: "video/webm",
|
|
11
|
+
container: () => new WebMOutputFormat(),
|
|
12
|
+
audioCodec: "opus",
|
|
13
|
+
video: { codecs: ["vp9", "av1", "vp8"], fallback: "vp8" },
|
|
14
|
+
},
|
|
15
|
+
wav: {
|
|
16
|
+
kind: "audio", extension: "wav", mimeType: "audio/wav",
|
|
17
|
+
container: () => new WavOutputFormat(),
|
|
18
|
+
audioCodec: "pcm-s16",
|
|
19
|
+
},
|
|
20
|
+
mp3: {
|
|
21
|
+
kind: "audio", extension: "mp3", mimeType: "audio/mpeg",
|
|
22
|
+
container: () => new Mp3OutputFormat(),
|
|
23
|
+
audioCodec: "mp3",
|
|
24
|
+
},
|
|
25
|
+
m4a: {
|
|
26
|
+
kind: "audio", extension: "m4a", mimeType: "audio/mp4",
|
|
27
|
+
container: () => new Mp4OutputFormat(),
|
|
28
|
+
audioCodec: "aac",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export const EXPORT_FORMATS = FORMATS;
|
|
32
|
+
export function formatSpec(format) {
|
|
33
|
+
const spec = FORMATS[format];
|
|
34
|
+
if (!spec)
|
|
35
|
+
throw new RangeError(`"${String(format)}" is not an export format; expected one of ${Object.keys(FORMATS).join(", ")}.`);
|
|
36
|
+
return spec;
|
|
37
|
+
}
|
|
38
|
+
export function isAudioFormat(format) {
|
|
39
|
+
return formatSpec(format).kind === "audio";
|
|
40
|
+
}
|
|
41
|
+
export const DEFAULT_AUDIO_BITRATE = 192_000;
|
|
42
|
+
const OPUS_SAMPLE_RATE = 48_000;
|
|
43
|
+
/** The mixed-audio track for `format`. Ensure its encoder first with {@link ensureAudioEncoder}. */
|
|
44
|
+
export function audioTrackSource(format, bitrate = DEFAULT_AUDIO_BITRATE) {
|
|
45
|
+
const codec = formatSpec(format).audioCodec;
|
|
46
|
+
return new AudioBufferSource({
|
|
47
|
+
codec,
|
|
48
|
+
bitrate,
|
|
49
|
+
// Opus encodes at 48 kHz only, and the mix runs at 44.1 kHz unless asked otherwise.
|
|
50
|
+
...(codec === "opus" && { transform: { sampleRate: OPUS_SAMPLE_RATE } }),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
let mp3Registered = null;
|
|
54
|
+
/**
|
|
55
|
+
* Throws before a frame is rendered when this browser cannot encode `format`'s
|
|
56
|
+
* audio. No browser ships an MP3 encoder, so LAME is loaded on first use (a
|
|
57
|
+
* WASM worker from a `blob:` URL, which a host's CSP must allow).
|
|
58
|
+
*/
|
|
59
|
+
export async function ensureAudioEncoder(format) {
|
|
60
|
+
const codec = formatSpec(format).audioCodec;
|
|
61
|
+
if (codec === "mp3") {
|
|
62
|
+
mp3Registered ??= (async () => {
|
|
63
|
+
if (await canEncodeAudio("mp3"))
|
|
64
|
+
return;
|
|
65
|
+
const { registerMp3Encoder } = await import("@mediabunny/mp3-encoder");
|
|
66
|
+
registerMp3Encoder();
|
|
67
|
+
})();
|
|
68
|
+
try {
|
|
69
|
+
await mp3Registered;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
mp3Registered = null;
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (codec.startsWith("pcm-"))
|
|
78
|
+
return;
|
|
79
|
+
if (!(await canEncodeAudio(codec))) {
|
|
80
|
+
throw new Error(`This browser has no ${codec.toUpperCase()} encoder, so it cannot export ${format.toUpperCase()} audio.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=formats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formats.js","sourceRoot":"","sources":["../src/formats.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AudioBufferSource,\n Mp3OutputFormat,\n Mp4OutputFormat,\n WavOutputFormat,\n WebMOutputFormat,\n canEncodeAudio,\n type AudioCodec,\n type OutputFormat,\n type VideoCodec,\n} from \"mediabunny\";\n\n/** A file with a picture: rendered frame by frame, with the mixed audio as a second track. */\nexport type VideoFormat = \"mp4\" | \"webm\";\n/** A file of the timeline's mixed audio alone; nothing is drawn. */\nexport type AudioFormat = \"wav\" | \"mp3\" | \"m4a\";\nexport type ExportFormat = VideoFormat | AudioFormat;\n\n/** What a host needs to name, save and play an exported file. */\nexport interface ExportFormatInfo {\n kind: \"video\" | \"audio\";\n /** Without the dot. */\n extension: string;\n mimeType: string;\n}\n\ninterface FormatSpec extends ExportFormatInfo {\n container: () => OutputFormat;\n audioCodec: AudioCodec;\n /** Video formats only: what the container takes, the default first, and the codec used unprobed when none of those encode. */\n video?: { codecs: readonly VideoCodec[]; fallback: VideoCodec };\n}\n\nconst FORMATS: Record<ExportFormat, FormatSpec> = {\n mp4: {\n kind: \"video\", extension: \"mp4\", mimeType: \"video/mp4\",\n container: () => new Mp4OutputFormat(),\n audioCodec: \"aac\",\n video: { codecs: [\"avc\", \"hevc\", \"av1\", \"vp9\"], fallback: \"avc\" },\n },\n webm: {\n kind: \"video\", extension: \"webm\", mimeType: \"video/webm\",\n container: () => new WebMOutputFormat(),\n audioCodec: \"opus\",\n video: { codecs: [\"vp9\", \"av1\", \"vp8\"], fallback: \"vp8\" },\n },\n wav: {\n kind: \"audio\", extension: \"wav\", mimeType: \"audio/wav\",\n container: () => new WavOutputFormat(),\n audioCodec: \"pcm-s16\",\n },\n mp3: {\n kind: \"audio\", extension: \"mp3\", mimeType: \"audio/mpeg\",\n container: () => new Mp3OutputFormat(),\n audioCodec: \"mp3\",\n },\n m4a: {\n kind: \"audio\", extension: \"m4a\", mimeType: \"audio/mp4\",\n container: () => new Mp4OutputFormat(),\n audioCodec: \"aac\",\n },\n};\n\nexport const EXPORT_FORMATS: Readonly<Record<ExportFormat, ExportFormatInfo>> = FORMATS;\n\nexport function formatSpec(format: ExportFormat): FormatSpec {\n const spec = FORMATS[format];\n if (!spec) throw new RangeError(`\"${String(format)}\" is not an export format; expected one of ${Object.keys(FORMATS).join(\", \")}.`);\n return spec;\n}\n\nexport function isAudioFormat(format: ExportFormat): format is AudioFormat {\n return formatSpec(format).kind === \"audio\";\n}\n\nexport const DEFAULT_AUDIO_BITRATE = 192_000;\nconst OPUS_SAMPLE_RATE = 48_000;\n\n/** The mixed-audio track for `format`. Ensure its encoder first with {@link ensureAudioEncoder}. */\nexport function audioTrackSource(format: ExportFormat, bitrate = DEFAULT_AUDIO_BITRATE): AudioBufferSource {\n const codec = formatSpec(format).audioCodec;\n return new AudioBufferSource({\n codec,\n bitrate,\n // Opus encodes at 48 kHz only, and the mix runs at 44.1 kHz unless asked otherwise.\n ...(codec === \"opus\" && { transform: { sampleRate: OPUS_SAMPLE_RATE } }),\n });\n}\n\nlet mp3Registered: Promise<void> | null = null;\n\n/**\n * Throws before a frame is rendered when this browser cannot encode `format`'s\n * audio. No browser ships an MP3 encoder, so LAME is loaded on first use (a\n * WASM worker from a `blob:` URL, which a host's CSP must allow).\n */\nexport async function ensureAudioEncoder(format: ExportFormat): Promise<void> {\n const codec = formatSpec(format).audioCodec;\n if (codec === \"mp3\") {\n mp3Registered ??= (async () => {\n if (await canEncodeAudio(\"mp3\")) return;\n const { registerMp3Encoder } = await import(\"@mediabunny/mp3-encoder\");\n registerMp3Encoder();\n })();\n try {\n await mp3Registered;\n } catch (error) {\n mp3Registered = null;\n throw error;\n }\n return;\n }\n if (codec.startsWith(\"pcm-\")) return;\n if (!(await canEncodeAudio(codec))) {\n throw new Error(`This browser has no ${codec.toUpperCase()} encoder, so it cannot export ${format.toUpperCase()} audio.`);\n }\n}\n"]}
|
|
@@ -0,0 +1,190 @@
|
|
|
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. */
|
|
12
|
+
export type ScreenshotFormat = "png" | "jpeg";
|
|
13
|
+
export type { FrameSpec };
|
|
14
|
+
/** One scheduled audio request, with the absolute time its track starts at. */
|
|
15
|
+
export interface ScheduledAudioRequest {
|
|
16
|
+
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
|
+
*/
|
|
22
|
+
globalOffset: number;
|
|
23
|
+
}
|
|
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
|
+
*/
|
|
31
|
+
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
|
+
*/
|
|
40
|
+
start(info: {
|
|
41
|
+
width: number;
|
|
42
|
+
height: number;
|
|
43
|
+
fps: number;
|
|
44
|
+
totalFrames: number;
|
|
45
|
+
hasAudio: boolean;
|
|
46
|
+
}): Promise<void>;
|
|
47
|
+
/**
|
|
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.
|
|
56
|
+
*/
|
|
57
|
+
addFrame(timestamp: number, duration: number): Promise<void>;
|
|
58
|
+
/** Mixed audio for the whole timeline, if any was produced. */
|
|
59
|
+
addAudio?(mixed: TAudio): Promise<void>;
|
|
60
|
+
/** Finish the container. Returns encoded bytes, or nothing if the sink delivered them itself. */
|
|
61
|
+
finalize(): Promise<Uint8Array | void>;
|
|
62
|
+
}
|
|
63
|
+
/** Mixes every scheduled request into one timeline-length buffer. */
|
|
64
|
+
export interface AudioMixer<TAudio = unknown> {
|
|
65
|
+
mix(requests: readonly ScheduledAudioRequest[], totalDuration: number): Promise<TAudio | null>;
|
|
66
|
+
}
|
|
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
|
+
*/
|
|
72
|
+
export declare class NoopAudioSink extends AudioSink {
|
|
73
|
+
/** Nothing here records a request, so `AssetManager` can skip the whole sync. */
|
|
74
|
+
get schedulesAudio(): boolean;
|
|
75
|
+
has(_src: string): boolean;
|
|
76
|
+
append(_src: string, _data: ArrayBuffer): Promise<void>;
|
|
77
|
+
retain(_keep: ReadonlySet<string>): void;
|
|
78
|
+
schedule(_requests: readonly AudioRequest[]): void;
|
|
79
|
+
syncTo(_clipTime: number): void;
|
|
80
|
+
play(_time: number, _speed: number, _reverse: boolean): void;
|
|
81
|
+
stop(): void;
|
|
82
|
+
}
|
|
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
|
+
*/
|
|
88
|
+
interface RenderHarness {
|
|
89
|
+
renderContext: CanvasRenderContext2D & {
|
|
90
|
+
pixelRatio: number;
|
|
91
|
+
};
|
|
92
|
+
/** The same adapter the context was built with. */
|
|
93
|
+
storageAdapter: StorageAdapter;
|
|
94
|
+
assetCatalog: AssetCatalog;
|
|
95
|
+
}
|
|
96
|
+
/** Fields shared by the video and still entry points. */
|
|
97
|
+
interface CommonParams extends RenderHarness {
|
|
98
|
+
/** Its fps and viewport resolved — see `Session`. */
|
|
99
|
+
project: ProjectDocument;
|
|
100
|
+
/** The vocabulary its clips compile against. */
|
|
101
|
+
registry: Registry;
|
|
102
|
+
/** Theme for a project that carries none of its own. */
|
|
103
|
+
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
|
+
*/
|
|
110
|
+
onIssue?: (issue: RenderIssue) => void;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
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.
|
|
122
|
+
*/
|
|
123
|
+
export declare function collectAudio(precomp: AssetTimelineResult): ScheduledAudioRequest[];
|
|
124
|
+
export interface RenderTimelineParams<TAudio> extends CommonParams {
|
|
125
|
+
scale?: number;
|
|
126
|
+
sink: VideoFrameSink<TAudio>;
|
|
127
|
+
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
|
+
*/
|
|
135
|
+
includeAudio?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Render only these frames, timestamped from 0, with no audio: one segment of a
|
|
138
|
+
* split export. Only the tracks they cover are discovered.
|
|
139
|
+
*/
|
|
140
|
+
frames?: FrameRange;
|
|
141
|
+
onProgress?: (progress: number) => void;
|
|
142
|
+
signal?: AbortSignal;
|
|
143
|
+
}
|
|
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
|
+
*/
|
|
151
|
+
export declare function renderTimeline<TAudio>(params: RenderTimelineParams<TAudio>): Promise<Uint8Array | void>;
|
|
152
|
+
export interface DrawFrameParams extends CommonParams {
|
|
153
|
+
scale?: number;
|
|
154
|
+
frame: FrameSpec;
|
|
155
|
+
}
|
|
156
|
+
export interface DrawnFrame {
|
|
157
|
+
/** The frame actually drawn, after clamping the request into range. */
|
|
158
|
+
frame: number;
|
|
159
|
+
totalFrames: number;
|
|
160
|
+
}
|
|
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
|
+
*/
|
|
166
|
+
export declare class NothingToDrawError extends Error {
|
|
167
|
+
readonly frame: number;
|
|
168
|
+
/** One per track covering the frame. */
|
|
169
|
+
readonly failures: readonly BuildError[];
|
|
170
|
+
readonly name = "NothingToDrawError";
|
|
171
|
+
constructor(frame: number,
|
|
172
|
+
/** One per track covering the frame. */
|
|
173
|
+
failures: readonly BuildError[]);
|
|
174
|
+
}
|
|
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
|
+
*/
|
|
189
|
+
export declare function drawFrameAt(params: DrawFrameParams): Promise<DrawnFrame>;
|
|
190
|
+
//# sourceMappingURL=frame-loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-loop.d.ts","sourceRoot":"","sources":["../src/frame-loop.ts"],"names":[],"mappings":"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"}
|