@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.
- package/CHANGELOG.md +19 -0
- package/dist/asset-bytes.d.ts +12 -50
- package/dist/asset-bytes.d.ts.map +1 -1
- package/dist/asset-bytes.js +20 -52
- package/dist/asset-bytes.js.map +1 -1
- package/dist/audio/bus-graph.d.ts +4 -39
- package/dist/audio/bus-graph.d.ts.map +1 -1
- package/dist/audio/bus-graph.js +4 -38
- package/dist/audio/bus-graph.js.map +1 -1
- package/dist/audio/effect-graph.d.ts +4 -29
- package/dist/audio/effect-graph.d.ts.map +1 -1
- package/dist/audio/effect-graph.js +4 -31
- package/dist/audio/effect-graph.js.map +1 -1
- package/dist/audio/loop-region.d.ts +3 -21
- package/dist/audio/loop-region.d.ts.map +1 -1
- package/dist/audio/loop-region.js +3 -21
- package/dist/audio/loop-region.js.map +1 -1
- package/dist/audio/mixer.d.ts +9 -41
- package/dist/audio/mixer.d.ts.map +1 -1
- package/dist/audio/mixer.js +11 -44
- package/dist/audio/mixer.js.map +1 -1
- package/dist/audio/player.d.ts +3 -17
- package/dist/audio/player.d.ts.map +1 -1
- package/dist/audio/player.js +4 -23
- package/dist/audio/player.js.map +1 -1
- package/dist/browser/audio.js +1 -1
- package/dist/browser/chunks/chunk-NDJYMKIV.js +2 -0
- package/dist/browser/chunks/chunk-NDJYMKIV.js.map +7 -0
- package/dist/browser/index.js +19 -19
- package/dist/browser/index.js.map +4 -4
- package/dist/browser/manifest.json +1 -1
- package/dist/browser/ms_renderer_bg.wasm +0 -0
- package/dist/concat.d.ts +25 -84
- package/dist/concat.d.ts.map +1 -1
- package/dist/concat.js +19 -69
- package/dist/concat.js.map +1 -1
- package/dist/dynamic-nodes.d.ts +1 -6
- package/dist/dynamic-nodes.d.ts.map +1 -1
- package/dist/dynamic-nodes.js +3 -24
- package/dist/dynamic-nodes.js.map +1 -1
- package/dist/engine.d.ts +9 -42
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +10 -44
- package/dist/engine.js.map +1 -1
- package/dist/export-audio.d.ts +1 -6
- package/dist/export-audio.d.ts.map +1 -1
- package/dist/export-audio.js +1 -6
- package/dist/export-audio.js.map +1 -1
- package/dist/exporter.d.ts +28 -141
- package/dist/exporter.d.ts.map +1 -1
- package/dist/exporter.js +15 -90
- package/dist/exporter.js.map +1 -1
- package/dist/frame-loop.d.ts +19 -105
- package/dist/frame-loop.d.ts.map +1 -1
- package/dist/frame-loop.js +20 -82
- package/dist/frame-loop.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -15
- package/dist/index.js.map +1 -1
- package/dist/mix-timeline-audio.d.ts +7 -24
- package/dist/mix-timeline-audio.d.ts.map +1 -1
- package/dist/mix-timeline-audio.js +7 -23
- package/dist/mix-timeline-audio.js.map +1 -1
- package/dist/native-export.d.ts +29 -0
- package/dist/native-export.d.ts.map +1 -0
- package/dist/native-export.js +99 -0
- package/dist/native-export.js.map +1 -0
- package/dist/parallel/export-parallel.d.ts +2 -16
- package/dist/parallel/export-parallel.d.ts.map +1 -1
- package/dist/parallel/export-parallel.js +1 -11
- package/dist/parallel/export-parallel.js.map +1 -1
- package/dist/parallel/governor.d.ts +2 -15
- package/dist/parallel/governor.d.ts.map +1 -1
- package/dist/parallel/governor.js +1 -5
- package/dist/parallel/governor.js.map +1 -1
- package/dist/parallel/lane.d.ts +1 -5
- package/dist/parallel/lane.d.ts.map +1 -1
- package/dist/parallel/lane.js.map +1 -1
- package/dist/parallel/plan.d.ts +3 -7
- package/dist/parallel/plan.d.ts.map +1 -1
- package/dist/parallel/plan.js +1 -6
- package/dist/parallel/plan.js.map +1 -1
- package/dist/parallel/run-lanes.d.ts +4 -18
- package/dist/parallel/run-lanes.d.ts.map +1 -1
- package/dist/parallel/run-lanes.js +1 -12
- package/dist/parallel/run-lanes.js.map +1 -1
- package/dist/screenshot.d.ts +1 -12
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +1 -12
- package/dist/screenshot.js.map +1 -1
- package/dist/still.d.ts +17 -74
- package/dist/still.d.ts.map +1 -1
- package/dist/still.js +13 -50
- package/dist/still.js.map +1 -1
- package/dist/ticker.d.ts +10 -39
- package/dist/ticker.d.ts.map +1 -1
- package/dist/ticker.js +13 -45
- package/dist/ticker.js.map +1 -1
- package/package.json +4 -4
- package/dist/browser/chunks/chunk-F3AZKPHU.js +0 -2
- package/dist/browser/chunks/chunk-F3AZKPHU.js.map +0 -7
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ManifestAssetCatalog, Registry, resolveProjectDocument, DEFAULT_OUTPUT } from "@motionscript/core";
|
|
2
|
+
import { createNativeExportContext, NATIVE_RECORD } from "@motionscript/renderer";
|
|
3
|
+
import { renderTimeline } from "./frame-loop";
|
|
4
|
+
import { exportGeometry, motionGraphicsBitrate } from "./exporter";
|
|
5
|
+
import { loadRenderer } from "./renderer";
|
|
6
|
+
import { WebAudioMixer } from "./audio/mixer";
|
|
7
|
+
/** Stream bytes held before they are handed to the transport. */
|
|
8
|
+
const FLUSH_BYTES = 1 << 18;
|
|
9
|
+
const CODEC_INDEX = { avc: 0, hevc: 1, av1: 2 };
|
|
10
|
+
class NativeSink {
|
|
11
|
+
context;
|
|
12
|
+
transport;
|
|
13
|
+
output;
|
|
14
|
+
encoding;
|
|
15
|
+
constructor(context, transport, output, encoding) {
|
|
16
|
+
this.context = context;
|
|
17
|
+
this.transport = transport;
|
|
18
|
+
this.output = output;
|
|
19
|
+
this.encoding = encoding;
|
|
20
|
+
}
|
|
21
|
+
async flush(force = false) {
|
|
22
|
+
if (this.context.out.size >= (force ? 1 : FLUSH_BYTES))
|
|
23
|
+
await this.transport.write(this.context.out.take());
|
|
24
|
+
}
|
|
25
|
+
async start(info) {
|
|
26
|
+
const { width, height } = this.output;
|
|
27
|
+
const bitrate = this.encoding.bitrate ?? motionGraphicsBitrate(width, height, info.fps);
|
|
28
|
+
this.context.out.record(NATIVE_RECORD.BEGIN, [width, height, Math.round(info.fps * 1000), info.totalFrames, bitrate, this.encoding.codec, 0]);
|
|
29
|
+
await this.flush(true);
|
|
30
|
+
}
|
|
31
|
+
async addFrame() {
|
|
32
|
+
this.context.frameDone();
|
|
33
|
+
await this.flush();
|
|
34
|
+
}
|
|
35
|
+
async addAudio(mixed) {
|
|
36
|
+
const channels = mixed.numberOfChannels, frames = mixed.length;
|
|
37
|
+
const pcm = new Float32Array(frames * channels);
|
|
38
|
+
for (let c = 0; c < channels; c++) {
|
|
39
|
+
const data = mixed.getChannelData(c);
|
|
40
|
+
for (let i = 0; i < frames; i++)
|
|
41
|
+
pcm[i * channels + c] = data[i];
|
|
42
|
+
}
|
|
43
|
+
this.context.out.record(NATIVE_RECORD.AUDIO, [mixed.sampleRate, channels], new Uint8Array(pcm.buffer));
|
|
44
|
+
await this.flush(true);
|
|
45
|
+
}
|
|
46
|
+
async finalize() {
|
|
47
|
+
this.context.out.record(NATIVE_RECORD.END, []);
|
|
48
|
+
await this.flush(true);
|
|
49
|
+
return this.transport.finish();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
|
|
53
|
+
/**
|
|
54
|
+
* Experimental: {@link exportVideo} through the native sidecar. The frame
|
|
55
|
+
* loop, adapter, text and assets run here exactly as for a canvas export;
|
|
56
|
+
* what would be drawn is streamed to `transport` instead, and the sidecar
|
|
57
|
+
* draws it with the same renderer on the native GPU and encodes it. MP4
|
|
58
|
+
* only; `video.codec` picks avc (default), hevc or av1.
|
|
59
|
+
*/
|
|
60
|
+
export async function exportVideoNative(params) {
|
|
61
|
+
const { document: source, registry = new Registry(), scale = 1, manifest = EMPTY_MANIFEST, theme, includeAudio = true, video, audio = {}, frames, onProgress, signal, supersample = 2, transport, } = params;
|
|
62
|
+
const project = resolveProjectDocument(source, params, DEFAULT_OUTPUT);
|
|
63
|
+
if (project.tracks.length === 0)
|
|
64
|
+
return;
|
|
65
|
+
signal?.throwIfAborted();
|
|
66
|
+
const viewport = project.viewport;
|
|
67
|
+
const fps = project.fps;
|
|
68
|
+
const { output, oversample, renderScale } = exportGeometry(viewport, scale, supersample);
|
|
69
|
+
if (output.width % 4 !== 0)
|
|
70
|
+
throw new RangeError(`A native export's width must be a multiple of 4 (NV12 packing); ${output.width} is not.`);
|
|
71
|
+
// The WASM for text and shader validation only: nothing here draws.
|
|
72
|
+
const renderer = await loadRenderer({ rendererWasmUrl: params.rendererWasmUrl }, { gpu: false });
|
|
73
|
+
const assetCatalog = new ManifestAssetCatalog(manifest);
|
|
74
|
+
const storageAdapter = renderer.createStorageAdapter({ catalog: assetCatalog, viewport, fps });
|
|
75
|
+
const renderContext = createNativeExportContext(storageAdapter, {
|
|
76
|
+
viewport,
|
|
77
|
+
effects: transport.effects,
|
|
78
|
+
size: { width: output.width * oversample, height: output.height * oversample },
|
|
79
|
+
});
|
|
80
|
+
const codec = video?.codec === "hevc" || video?.codec === "av1" ? CODEC_INDEX[video.codec] : CODEC_INDEX.avc;
|
|
81
|
+
const bitrate = typeof video?.bitrate === "number" ? video.bitrate : undefined;
|
|
82
|
+
try {
|
|
83
|
+
return await renderTimeline({
|
|
84
|
+
project, registry, theme, scale: renderScale, includeAudio, frames,
|
|
85
|
+
renderContext: renderContext, storageAdapter, assetCatalog,
|
|
86
|
+
sink: new NativeSink(renderContext, transport, output, { codec, bitrate }),
|
|
87
|
+
mixer: new WebAudioMixer(audio.sampleRate, assetCatalog),
|
|
88
|
+
onProgress, onIssue: params.onIssue, signal,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
transport.abort?.(error);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
renderContext.dispose();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=native-export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-export.js","sourceRoot":"","sources":["../src/native-export.ts"],"names":[],"mappings":"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","sourcesContent":["import { ManifestAssetCatalog, Registry, resolveProjectDocument, DEFAULT_OUTPUT, type AssetManifest } from \"@motionscript/core\";\nimport { createNativeExportContext, NATIVE_RECORD, type NativeExportContext } from \"@motionscript/renderer\";\nimport { renderTimeline, type VideoFrameSink } from \"./frame-loop\";\nimport { exportGeometry, motionGraphicsBitrate, type ExportParams } from \"./exporter\";\nimport { loadRenderer } from \"./renderer\";\nimport { WebAudioMixer } from \"./audio/mixer\";\n\n/**\n * Where a native export's stream goes: in a desktop host, a pipe into the\n * `ms-export` sidecar (`packages/renderer/crates/export`), which renders on\n * the native GPU and encodes with NVENC (or libx264). Experimental.\n */\nexport interface NativeExportTransport {\n /** The sidecar's built-in effect names, in its index order (`ms-export info`). */\n readonly effects: readonly string[];\n /** The next bytes of the stream, in order. Resolves when the transport can take more. */\n write(bytes: Uint8Array): Promise<void>;\n /** The stream is complete; resolves with the file's bytes, or nothing when the sidecar wrote it where the host asked. */\n finish(): Promise<Uint8Array | void>;\n /** The export failed or was cancelled: stop the sidecar, keep no file. */\n abort?(reason: unknown): void;\n}\n\nexport type NativeExportParams = Omit<ExportParams, \"format\" | \"returnBytes\" | \"filename\" | \"rendererWasmUrl\"> & {\n rendererWasmUrl?: string | URL;\n transport: NativeExportTransport;\n};\n\n/** Stream bytes held before they are handed to the transport. */\nconst FLUSH_BYTES = 1 << 18;\n\nconst CODEC_INDEX = { avc: 0, hevc: 1, av1: 2 } as const;\n\nclass NativeSink implements VideoFrameSink<AudioBuffer> {\n constructor(\n private readonly context: NativeExportContext,\n private readonly transport: NativeExportTransport,\n private readonly output: { width: number; height: number },\n private readonly encoding: { codec: number; bitrate?: number },\n ) { }\n\n private async flush(force = false): Promise<void> {\n if (this.context.out.size >= (force ? 1 : FLUSH_BYTES)) await this.transport.write(this.context.out.take());\n }\n\n async start(info: { width: number; height: number; fps: number; totalFrames: number }): Promise<void> {\n const { width, height } = this.output;\n const bitrate = this.encoding.bitrate ?? motionGraphicsBitrate(width, height, info.fps);\n this.context.out.record(NATIVE_RECORD.BEGIN, [width, height, Math.round(info.fps * 1000), info.totalFrames, bitrate, this.encoding.codec, 0]);\n await this.flush(true);\n }\n\n async addFrame(): Promise<void> {\n this.context.frameDone();\n await this.flush();\n }\n\n async addAudio(mixed: AudioBuffer): Promise<void> {\n const channels = mixed.numberOfChannels, frames = mixed.length;\n const pcm = new Float32Array(frames * channels);\n for (let c = 0; c < channels; c++) {\n const data = mixed.getChannelData(c);\n for (let i = 0; i < frames; i++) pcm[i * channels + c] = data[i];\n }\n this.context.out.record(NATIVE_RECORD.AUDIO, [mixed.sampleRate, channels], new Uint8Array(pcm.buffer));\n await this.flush(true);\n }\n\n async finalize(): Promise<Uint8Array | void> {\n this.context.out.record(NATIVE_RECORD.END, []);\n await this.flush(true);\n return this.transport.finish();\n }\n}\n\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\n\n/**\n * Experimental: {@link exportVideo} through the native sidecar. The frame\n * loop, adapter, text and assets run here exactly as for a canvas export;\n * what would be drawn is streamed to `transport` instead, and the sidecar\n * draws it with the same renderer on the native GPU and encodes it. MP4\n * only; `video.codec` picks avc (default), hevc or av1.\n */\nexport async function exportVideoNative(params: NativeExportParams): Promise<Uint8Array | void> {\n const {\n document: source, registry = new Registry(), scale = 1, manifest = EMPTY_MANIFEST, theme,\n includeAudio = true, video, audio = {}, frames, onProgress, signal, supersample = 2, transport,\n } = params;\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 const { output, oversample, renderScale } = exportGeometry(viewport, scale, supersample);\n if (output.width % 4 !== 0) throw new RangeError(`A native export's width must be a multiple of 4 (NV12 packing); ${output.width} is not.`);\n\n // The WASM for text and shader validation only: nothing here draws.\n const renderer = await loadRenderer({ rendererWasmUrl: params.rendererWasmUrl }, { gpu: false });\n const assetCatalog = new ManifestAssetCatalog(manifest);\n const storageAdapter = renderer.createStorageAdapter({ catalog: assetCatalog, viewport, fps });\n const renderContext = createNativeExportContext(storageAdapter, {\n viewport,\n effects: transport.effects,\n size: { width: output.width * oversample, height: output.height * oversample },\n });\n const codec = video?.codec === \"hevc\" || video?.codec === \"av1\" ? CODEC_INDEX[video.codec] : CODEC_INDEX.avc;\n const bitrate = typeof video?.bitrate === \"number\" ? video.bitrate : undefined;\n try {\n return await renderTimeline<AudioBuffer>({\n project, registry, theme, scale: renderScale, includeAudio, frames,\n renderContext: renderContext as never, storageAdapter, assetCatalog,\n sink: new NativeSink(renderContext, transport, output, { codec, bitrate }),\n mixer: new WebAudioMixer(audio.sampleRate, assetCatalog),\n onProgress, onIssue: params.onIssue, signal,\n });\n } catch (error) {\n transport.abort?.(error);\n throw error;\n } finally {\n renderContext.dispose();\n }\n}\n"]}
|
|
@@ -2,11 +2,7 @@ import { type ConcatOptions } from "../concat";
|
|
|
2
2
|
import { type ExportParams } from "../exporter";
|
|
3
3
|
import type { ExportLane } from "./lane";
|
|
4
4
|
export interface ParallelExportParams extends Omit<ExportParams, "frames" | "returnBytes" | "filename"> {
|
|
5
|
-
/**
|
|
6
|
-
* Where the frames are drawn, most preferred first. Every run starts on the
|
|
7
|
-
* first alone and admits the next only while that raises throughput (see
|
|
8
|
-
* `LaneGovernor`), so the first should be the warmest: usually `localLane()`.
|
|
9
|
-
*/
|
|
5
|
+
/** Where the frames are drawn, most preferred first. A run starts on the first alone and admits the next only while that raises throughput (see `LaneGovernor`), so the first should be the warmest, usually `localLane()`. */
|
|
10
6
|
lanes: readonly ExportLane[];
|
|
11
7
|
/** Write the file here as it is joined; the call then resolves to an empty array. See `ConcatOptions.stream`. */
|
|
12
8
|
stream?: ConcatOptions["stream"];
|
|
@@ -17,16 +13,6 @@ export interface ParallelExportParams extends Omit<ExportParams, "frames" | "ret
|
|
|
17
13
|
/** How many lanes are rendering, each time that changes. */
|
|
18
14
|
onLanes?: (active: number) => void;
|
|
19
15
|
}
|
|
20
|
-
/**
|
|
21
|
-
* {@link exportVideo}, rendered in chunks across `lanes` and joined into one
|
|
22
|
-
* file without re-encoding.
|
|
23
|
-
*
|
|
24
|
-
* Each chunk is a frame range of the **whole** project, so layered and gapped
|
|
25
|
-
* timelines split like any other. Every chunk is encoded with one pinned
|
|
26
|
-
* configuration, the audio is mixed once over the whole timeline while the frames
|
|
27
|
-
* render (joined AAC would click at every seam), and each chunk is muxed the
|
|
28
|
-
* moment it and the ones before it are done. Progress is frames drawn across
|
|
29
|
-
* every lane, so it climbs smoothly and never goes back.
|
|
30
|
-
*/
|
|
16
|
+
/** {@link exportVideo} rendered in chunks across `lanes` and joined without re-encoding. Each chunk is a frame range of the **whole** project, so layered and gapped timelines split like any other. All chunks use one pinned encoding, the audio is mixed once over the whole timeline (joined AAC would click at every seam), and each chunk is muxed as soon as it and the ones before it are done. Progress is frames drawn across every lane, so it never goes back. */
|
|
31
17
|
export declare function exportVideoParallel(params: ParallelExportParams): Promise<Uint8Array>;
|
|
32
18
|
//# sourceMappingURL=export-parallel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export-parallel.d.ts","sourceRoot":"","sources":["../../src/parallel/export-parallel.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAA4D,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAG1G,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,QAAQ,CAAC;AAItD,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,YAAY,EAAE,QAAQ,GAAG,aAAa,GAAG,UAAU,CAAC;IACnG
|
|
1
|
+
{"version":3,"file":"export-parallel.d.ts","sourceRoot":"","sources":["../../src/parallel/export-parallel.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAA4D,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAG1G,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,QAAQ,CAAC;AAItD,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,YAAY,EAAE,QAAQ,GAAG,aAAa,GAAG,UAAU,CAAC;IACnG,+NAA+N;IAC/N,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,iHAAiH;IACjH,MAAM,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4DAA4D;IAC5D,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAOD,8cAA8c;AAC9c,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,CAAC,CA4F3F"}
|
|
@@ -9,17 +9,7 @@ const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
|
|
|
9
9
|
const DEFAULT_SUPERSAMPLE = 2;
|
|
10
10
|
/** The share of progress drawing takes; finishing the file is the rest. */
|
|
11
11
|
const DRAWN = 0.97;
|
|
12
|
-
/**
|
|
13
|
-
* {@link exportVideo}, rendered in chunks across `lanes` and joined into one
|
|
14
|
-
* file without re-encoding.
|
|
15
|
-
*
|
|
16
|
-
* Each chunk is a frame range of the **whole** project, so layered and gapped
|
|
17
|
-
* timelines split like any other. Every chunk is encoded with one pinned
|
|
18
|
-
* configuration, the audio is mixed once over the whole timeline while the frames
|
|
19
|
-
* render (joined AAC would click at every seam), and each chunk is muxed the
|
|
20
|
-
* moment it and the ones before it are done. Progress is frames drawn across
|
|
21
|
-
* every lane, so it climbs smoothly and never goes back.
|
|
22
|
-
*/
|
|
12
|
+
/** {@link exportVideo} rendered in chunks across `lanes` and joined without re-encoding. Each chunk is a frame range of the **whole** project, so layered and gapped timelines split like any other. All chunks use one pinned encoding, the audio is mixed once over the whole timeline (joined AAC would click at every seam), and each chunk is muxed as soon as it and the ones before it are done. Progress is frames drawn across every lane, so it never goes back. */
|
|
23
13
|
export async function exportVideoParallel(params) {
|
|
24
14
|
const { registry = new Registry(), scale = 1, format = "mp4", manifest = EMPTY_MANIFEST, includeAudio = true, audio = {}, signal, onProgress, supersample = DEFAULT_SUPERSAMPLE, } = params;
|
|
25
15
|
if (formatSpec(format).kind !== "video") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export-parallel.js","sourceRoot":"","sources":["../../src/parallel/export-parallel.ts"],"names":[],"mappings":"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","sourcesContent":["import { DEFAULT_OUTPUT, placeTracks, Registry, renderIssueKey, resolveProjectDocument, totalFramesOf, type AssetManifest } from \"@motionscript/core\";\nimport { createSegmentJoiner, type ConcatOptions } from \"../concat\";\nimport { exportGeometry, plainVideoEncoding, resolveVideoEncoding, type ExportParams } from \"../exporter\";\nimport { formatSpec } from \"../formats\";\nimport { mixTimelineAudio } from \"../mix-timeline-audio\";\nimport type { ExportLane, SegmentSpec } from \"./lane\";\nimport { planChunks } from \"./plan\";\nimport { runLanes } from \"./run-lanes\";\n\nexport interface ParallelExportParams extends Omit<ExportParams, \"frames\" | \"returnBytes\" | \"filename\"> {\n /**\n * Where the frames are drawn, most preferred first. Every run starts on the\n * first alone and admits the next only while that raises throughput (see\n * `LaneGovernor`), so the first should be the warmest: usually `localLane()`.\n */\n lanes: readonly ExportLane[];\n /** Write the file here as it is joined; the call then resolves to an empty array. See `ConcatOptions.stream`. */\n stream?: ConcatOptions[\"stream\"];\n /** Seconds per chunk; by default from the timeline's length and the lane count. */\n chunkSeconds?: number;\n /** Handed to every lane in its spec, for what only the host knows (see `serveExportLane`). */\n host?: unknown;\n /** How many lanes are rendering, each time that changes. */\n onLanes?: (active: number) => void;\n}\n\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\nconst DEFAULT_SUPERSAMPLE = 2;\n/** The share of progress drawing takes; finishing the file is the rest. */\nconst DRAWN = 0.97;\n\n/**\n * {@link exportVideo}, rendered in chunks across `lanes` and joined into one\n * file without re-encoding.\n *\n * Each chunk is a frame range of the **whole** project, so layered and gapped\n * timelines split like any other. Every chunk is encoded with one pinned\n * configuration, the audio is mixed once over the whole timeline while the frames\n * render (joined AAC would click at every seam), and each chunk is muxed the\n * moment it and the ones before it are done. Progress is frames drawn across\n * every lane, so it climbs smoothly and never goes back.\n */\nexport async function exportVideoParallel(params: ParallelExportParams): Promise<Uint8Array> {\n const {\n registry = new Registry(), scale = 1, format = \"mp4\", manifest = EMPTY_MANIFEST,\n includeAudio = true, audio = {}, signal, onProgress, supersample = DEFAULT_SUPERSAMPLE,\n } = params;\n if (formatSpec(format).kind !== \"video\") {\n throw new RangeError(`exportVideoParallel writes video; \"${format}\" is audio, which exportAudio writes.`);\n }\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\n if (project.tracks.length === 0) throw new Error(\"exportVideoParallel: the project has no tracks.\");\n signal?.throwIfAborted();\n const fps = project.fps!;\n const totalFrames = totalFramesOf(placeTracks(project.tracks, fps));\n\n const { output } = exportGeometry(project.viewport!, scale, supersample);\n const encoding = plainVideoEncoding(await resolveVideoEncoding(params.video, format, output, fps), output);\n const spec: SegmentSpec = {\n project, manifest, theme: params.theme, scale, supersample, format, encoding, host: params.host,\n };\n const chunks = planChunks(totalFrames, {\n fps, keyFrameInterval: encoding.keyFrameInterval, lanes: params.lanes.length, chunkSeconds: params.chunkSeconds,\n });\n\n // Started now so it is ready by the first join; failures surface there.\n const mixed = includeAudio\n ? mixTimelineAudio({\n document: project, registry, manifest, sampleRate: audio.sampleRate,\n rendererWasmUrl: params.rendererWasmUrl,\n })\n : Promise.resolve(null);\n mixed.catch(() => {});\n\n let reported = 0;\n const seenIssues = new Set<string>();\n const report = (p: number) => {\n if (p <= reported) return;\n reported = p;\n onProgress?.(p);\n };\n\n // One signal for both ways a run ends early: the caller cancelling, and the join failing.\n const run = new AbortController();\n const abortRun = () => run.abort(signal?.reason);\n if (signal?.aborted) abortRun();\n signal?.addEventListener(\"abort\", abortRun, { once: true });\n\n // Keyed by first frame: a merged range spans several planned chunks.\n const ready = new Map<number, { end: number; bytes: Uint8Array }>();\n let joinedTo = 0;\n let joining: Promise<void> = Promise.resolve();\n let joiner: ReturnType<typeof createSegmentJoiner> | null = null;\n const pump = () => {\n joining = joining.then(async () => {\n while (ready.has(joinedTo)) {\n joiner ??= createSegmentJoiner({ stream: params.stream, audio: await mixed, audioBitrate: audio.bitrate });\n const { end, bytes } = ready.get(joinedTo)!;\n ready.delete(joinedTo);\n joinedTo = end;\n await joiner.append(bytes);\n }\n });\n return joining;\n };\n\n try {\n await runLanes({\n lanes: params.lanes, spec, chunks, signal: run.signal,\n onIssue: (issue) => {\n const key = renderIssueKey(issue);\n if (seenIssues.has(key)) return;\n seenIssues.add(key);\n params.onIssue?.(issue);\n },\n onFrames: (frames) => report(DRAWN * Math.min(1, frames / totalFrames)),\n onActive: params.onLanes,\n onChunk: (chunk, bytes) => {\n ready.set(chunk.start, { end: chunk.end, bytes });\n pump().catch((error: unknown) => run.abort(error));\n },\n });\n await pump();\n if (joinedTo !== totalFrames) throw new Error(`exportVideoParallel: joined ${joinedTo} of ${totalFrames} frames.`);\n const bytes = await joiner!.finish();\n report(1);\n return bytes;\n } catch (error) {\n await joining.catch(() => {});\n await (joiner as ReturnType<typeof createSegmentJoiner> | null)?.cancel().catch(() => {});\n throw error;\n } finally {\n signal?.removeEventListener(\"abort\", abortRun);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"export-parallel.js","sourceRoot":"","sources":["../../src/parallel/export-parallel.ts"],"names":[],"mappings":"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","sourcesContent":["import { DEFAULT_OUTPUT, placeTracks, Registry, renderIssueKey, resolveProjectDocument, totalFramesOf, type AssetManifest } from \"@motionscript/core\";\nimport { createSegmentJoiner, type ConcatOptions } from \"../concat\";\nimport { exportGeometry, plainVideoEncoding, resolveVideoEncoding, type ExportParams } from \"../exporter\";\nimport { formatSpec } from \"../formats\";\nimport { mixTimelineAudio } from \"../mix-timeline-audio\";\nimport type { ExportLane, SegmentSpec } from \"./lane\";\nimport { planChunks } from \"./plan\";\nimport { runLanes } from \"./run-lanes\";\n\nexport interface ParallelExportParams extends Omit<ExportParams, \"frames\" | \"returnBytes\" | \"filename\"> {\n /** Where the frames are drawn, most preferred first. A run starts on the first alone and admits the next only while that raises throughput (see `LaneGovernor`), so the first should be the warmest, usually `localLane()`. */\n lanes: readonly ExportLane[];\n /** Write the file here as it is joined; the call then resolves to an empty array. See `ConcatOptions.stream`. */\n stream?: ConcatOptions[\"stream\"];\n /** Seconds per chunk; by default from the timeline's length and the lane count. */\n chunkSeconds?: number;\n /** Handed to every lane in its spec, for what only the host knows (see `serveExportLane`). */\n host?: unknown;\n /** How many lanes are rendering, each time that changes. */\n onLanes?: (active: number) => void;\n}\n\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\nconst DEFAULT_SUPERSAMPLE = 2;\n/** The share of progress drawing takes; finishing the file is the rest. */\nconst DRAWN = 0.97;\n\n/** {@link exportVideo} rendered in chunks across `lanes` and joined without re-encoding. Each chunk is a frame range of the **whole** project, so layered and gapped timelines split like any other. All chunks use one pinned encoding, the audio is mixed once over the whole timeline (joined AAC would click at every seam), and each chunk is muxed as soon as it and the ones before it are done. Progress is frames drawn across every lane, so it never goes back. */\nexport async function exportVideoParallel(params: ParallelExportParams): Promise<Uint8Array> {\n const {\n registry = new Registry(), scale = 1, format = \"mp4\", manifest = EMPTY_MANIFEST,\n includeAudio = true, audio = {}, signal, onProgress, supersample = DEFAULT_SUPERSAMPLE,\n } = params;\n if (formatSpec(format).kind !== \"video\") {\n throw new RangeError(`exportVideoParallel writes video; \"${format}\" is audio, which exportAudio writes.`);\n }\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\n if (project.tracks.length === 0) throw new Error(\"exportVideoParallel: the project has no tracks.\");\n signal?.throwIfAborted();\n const fps = project.fps!;\n const totalFrames = totalFramesOf(placeTracks(project.tracks, fps));\n\n const { output } = exportGeometry(project.viewport!, scale, supersample);\n const encoding = plainVideoEncoding(await resolveVideoEncoding(params.video, format, output, fps), output);\n const spec: SegmentSpec = {\n project, manifest, theme: params.theme, scale, supersample, format, encoding, host: params.host,\n };\n const chunks = planChunks(totalFrames, {\n fps, keyFrameInterval: encoding.keyFrameInterval, lanes: params.lanes.length, chunkSeconds: params.chunkSeconds,\n });\n\n // Started now so it is ready by the first join; failures surface there.\n const mixed = includeAudio\n ? mixTimelineAudio({\n document: project, registry, manifest, sampleRate: audio.sampleRate,\n rendererWasmUrl: params.rendererWasmUrl,\n })\n : Promise.resolve(null);\n mixed.catch(() => {});\n\n let reported = 0;\n const seenIssues = new Set<string>();\n const report = (p: number) => {\n if (p <= reported) return;\n reported = p;\n onProgress?.(p);\n };\n\n // One signal for both ways a run ends early: the caller cancelling, and the join failing.\n const run = new AbortController();\n const abortRun = () => run.abort(signal?.reason);\n if (signal?.aborted) abortRun();\n signal?.addEventListener(\"abort\", abortRun, { once: true });\n\n // Keyed by first frame: a merged range spans several planned chunks.\n const ready = new Map<number, { end: number; bytes: Uint8Array }>();\n let joinedTo = 0;\n let joining: Promise<void> = Promise.resolve();\n let joiner: ReturnType<typeof createSegmentJoiner> | null = null;\n const pump = () => {\n joining = joining.then(async () => {\n while (ready.has(joinedTo)) {\n joiner ??= createSegmentJoiner({ stream: params.stream, audio: await mixed, audioBitrate: audio.bitrate });\n const { end, bytes } = ready.get(joinedTo)!;\n ready.delete(joinedTo);\n joinedTo = end;\n await joiner.append(bytes);\n }\n });\n return joining;\n };\n\n try {\n await runLanes({\n lanes: params.lanes, spec, chunks, signal: run.signal,\n onIssue: (issue) => {\n const key = renderIssueKey(issue);\n if (seenIssues.has(key)) return;\n seenIssues.add(key);\n params.onIssue?.(issue);\n },\n onFrames: (frames) => report(DRAWN * Math.min(1, frames / totalFrames)),\n onActive: params.onLanes,\n onChunk: (chunk, bytes) => {\n ready.set(chunk.start, { end: chunk.end, bytes });\n pump().catch((error: unknown) => run.abort(error));\n },\n });\n await pump();\n if (joinedTo !== totalFrames) throw new Error(`exportVideoParallel: joined ${joinedTo} of ${totalFrames} frames.`);\n const bytes = await joiner!.finish();\n report(1);\n return bytes;\n } catch (error) {\n await joining.catch(() => {});\n await (joiner as ReturnType<typeof createSegmentJoiner> | null)?.cancel().catch(() => {});\n throw error;\n } finally {\n signal?.removeEventListener(\"abort\", abortRun);\n }\n}\n"]}
|
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Decides how many lanes render at once, from what the run itself measures.
|
|
3
|
-
*
|
|
4
|
-
* Lanes do not add throughput for free. Every lane's encoder, and on a WebGPU
|
|
5
|
-
* canvas its frame capture, run in the browser's one GPU process, so once a lane
|
|
6
|
-
* spends its time waiting on the encoder rather than drawing, a second lane only
|
|
7
|
-
* splits the same encoder between two sessions and the export gets slower. The
|
|
8
|
-
* governor starts with one lane, adds the next only while lanes are mostly
|
|
9
|
-
* drawing, keeps it only if total throughput rose, and then settles.
|
|
10
|
-
*/
|
|
1
|
+
/** Decides how many lanes render at once from what the run measures. Lanes do not add throughput for free: every lane's encoder (and, on a WebGPU canvas, frame capture) runs in the browser's one GPU process, so once a lane waits on the encoder rather than drawing, a second only splits the same encoder and the export gets slower. It starts with one lane, adds the next only while lanes are mostly drawing, keeps it only if total throughput rose, then settles. */
|
|
11
2
|
export interface GovernorOptions {
|
|
12
3
|
/** Lanes available, the first being the one every run starts with. */
|
|
13
4
|
lanes: number;
|
|
@@ -47,11 +38,7 @@ export declare class LaneGovernor {
|
|
|
47
38
|
get settled(): boolean;
|
|
48
39
|
/** Stop measuring once a lane dies: the run continues on at most `alive` lanes. */
|
|
49
40
|
laneLost(alive: number): void;
|
|
50
|
-
/**
|
|
51
|
-
* Record frames a lane drew. Returns the new active count when it changed.
|
|
52
|
-
* A window opens once the newest lane has drawn for `warmupMs`, so its
|
|
53
|
-
* start-up is not counted against it.
|
|
54
|
-
*/
|
|
41
|
+
/** Records frames a lane drew; returns the new active count when it changed. A window opens once the newest lane has drawn for `warmupMs`, so its start-up isn't counted against it. */
|
|
55
42
|
record(sample: FrameSample): number | null;
|
|
56
43
|
}
|
|
57
44
|
//# sourceMappingURL=governor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"governor.d.ts","sourceRoot":"","sources":["../../src/parallel/governor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"governor.d.ts","sourceRoot":"","sources":["../../src/parallel/governor.ts"],"names":[],"mappings":"AAAA,gdAAgd;AAChd,MAAM,WAAW,eAAe;IAC5B,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+GAA+G;IAC/G,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;CACd;AASD,qBAAa,YAAY;IAYT,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAXrC,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEN,QAAQ,EAAE,eAAe;IAStD,2CAA2C;IAC3C,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7B,wLAAwL;IACxL,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI;CA+B7C"}
|
|
@@ -31,11 +31,7 @@ export class LaneGovernor {
|
|
|
31
31
|
this._active = Math.max(1, Math.min(this._active, alive));
|
|
32
32
|
this._settled = true;
|
|
33
33
|
}
|
|
34
|
-
/**
|
|
35
|
-
* Record frames a lane drew. Returns the new active count when it changed.
|
|
36
|
-
* A window opens once the newest lane has drawn for `warmupMs`, so its
|
|
37
|
-
* start-up is not counted against it.
|
|
38
|
-
*/
|
|
34
|
+
/** Records frames a lane drew; returns the new active count when it changed. A window opens once the newest lane has drawn for `warmupMs`, so its start-up isn't counted against it. */
|
|
39
35
|
record(sample) {
|
|
40
36
|
if (this._settled || sample.lane >= this._active)
|
|
41
37
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"governor.js","sourceRoot":"","sources":["../../src/parallel/governor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"governor.js","sourceRoot":"","sources":["../../src/parallel/governor.ts"],"names":[],"mappings":"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","sourcesContent":["/** Decides how many lanes render at once from what the run measures. Lanes do not add throughput for free: every lane's encoder (and, on a WebGPU canvas, frame capture) runs in the browser's one GPU process, so once a lane waits on the encoder rather than drawing, a second only splits the same encoder and the export gets slower. It starts with one lane, adds the next only while lanes are mostly drawing, keeps it only if total throughput rose, then settles. */\nexport interface GovernorOptions {\n /** Lanes available, the first being the one every run starts with. */\n lanes: number;\n /** How long a configuration is measured before it is judged, in ms. */\n windowMs?: number;\n /** Frames a window needs before it is judged. */\n minFrames?: number;\n /** Above this share of time waiting on the encoder, another lane cannot help. */\n maxWaitShare?: number;\n /** A lane is kept only if it raises throughput by at least this factor. */\n minGain?: number;\n /** How long the newest lane draws before it is measured: its first frames wait on loads and a cold encoder. */\n warmupMs?: number;\n}\n\nexport interface FrameSample {\n lane: number;\n frames: number;\n busyMs: number;\n waitMs: number;\n at: number;\n}\n\ninterface Window {\n start: number | null;\n frames: number;\n busyMs: number;\n waitMs: number;\n}\n\nexport class LaneGovernor {\n private _active = 1;\n private _settled: boolean;\n private _window: Window = emptyWindow();\n private _previousFps = 0;\n private _newestFirstAt: number | null = null;\n private readonly _windowMs: number;\n private readonly _minFrames: number;\n private readonly _maxWaitShare: number;\n private readonly _minGain: number;\n private readonly _warmupMs: number;\n\n constructor(private readonly _options: GovernorOptions) {\n this._windowMs = _options.windowMs ?? 1500;\n this._minFrames = _options.minFrames ?? 24;\n this._maxWaitShare = _options.maxWaitShare ?? 0.45;\n this._minGain = _options.minGain ?? 1.1;\n this._warmupMs = _options.warmupMs ?? 300;\n this._settled = _options.lanes <= 1;\n }\n\n /** Lanes `0..active-1` may take chunks. */\n get active(): number {\n return this._active;\n }\n\n get settled(): boolean {\n return this._settled;\n }\n\n /** Stop measuring once a lane dies: the run continues on at most `alive` lanes. */\n laneLost(alive: number): void {\n this._active = Math.max(1, Math.min(this._active, alive));\n this._settled = true;\n }\n\n /** Records frames a lane drew; returns the new active count when it changed. A window opens once the newest lane has drawn for `warmupMs`, so its start-up isn't counted against it. */\n record(sample: FrameSample): number | null {\n if (this._settled || sample.lane >= this._active) return null;\n const w = this._window;\n if (w.start === null) {\n if (sample.lane !== this._active - 1) return null;\n this._newestFirstAt ??= sample.at;\n if (sample.at - this._newestFirstAt >= this._warmupMs) w.start = sample.at;\n return null;\n }\n w.frames += sample.frames;\n w.busyMs += sample.busyMs;\n w.waitMs += sample.waitMs;\n const elapsed = sample.at - w.start;\n if (elapsed < this._windowMs || w.frames < this._minFrames) return null;\n\n const fps = (w.frames * 1000) / elapsed;\n const waitShare = w.waitMs / Math.max(1, w.busyMs + w.waitMs);\n const before = this._active;\n if (this._active > 1 && fps < this._previousFps * this._minGain) {\n this._active -= 1;\n this._settled = true;\n } else if (waitShare <= this._maxWaitShare && this._active < this._options.lanes) {\n this._previousFps = fps;\n this._active += 1;\n } else {\n this._settled = true;\n }\n this._window = emptyWindow();\n this._newestFirstAt = null;\n return this._active !== before ? this._active : null;\n }\n}\n\nfunction emptyWindow(): Window {\n return { start: null, frames: 0, busyMs: 0, waitMs: 0 };\n}\n"]}
|
package/dist/parallel/lane.d.ts
CHANGED
|
@@ -3,11 +3,7 @@ import type { RendererWasmOption } from "../renderer";
|
|
|
3
3
|
import type { ResolvedVideoEncoding } from "../exporter";
|
|
4
4
|
import type { VideoFormat } from "../formats";
|
|
5
5
|
import type { ExportChunk } from "./plan";
|
|
6
|
-
/**
|
|
7
|
-
* Everything a lane needs to render chunks of one export. Plain data, so it
|
|
8
|
-
* crosses a `MessagePort` intact; the encoding is pinned here because segments
|
|
9
|
-
* encoded differently cannot be joined.
|
|
10
|
-
*/
|
|
6
|
+
/** Everything a lane needs to render chunks of one export: plain data (it crosses a `MessagePort`), with the encoding pinned since differently encoded segments cannot be joined. */
|
|
11
7
|
export interface SegmentSpec {
|
|
12
8
|
/** Its fps and viewport resolved. */
|
|
13
9
|
project: ProjectDocument;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lane.d.ts","sourceRoot":"","sources":["../../src/parallel/lane.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACvG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1C
|
|
1
|
+
{"version":3,"file":"lane.d.ts","sourceRoot":"","sources":["../../src/parallel/lane.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACvG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1C,qLAAqL;AACrL,MAAM,WAAW,WAAW;IACxB,qCAAqC;IACrC,OAAO,EAAE,eAAe,CAAC;IACzB,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,qBAAqB,CAAC;IAChC,2FAA2F;IAC3F,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,kGAAkG;AAClG,MAAM,WAAW,UAAU;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,oCAAoC;AACpC,MAAM,WAAW,WAAW;IACxB,oEAAoE;IACpE,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7G,KAAK,IAAI,IAAI,CAAC;CACjB;AAED,kFAAkF;AAClF,MAAM,WAAW,UAAU;IACvB,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACtE;AAED,4FAA4F;AAC5F,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IACvD,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACvB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lane.js","sourceRoot":"","sources":["../../src/parallel/lane.ts"],"names":[],"mappings":"","sourcesContent":["import type { AssetManifest, ProjectDocument, Registry, RenderIssue, Theme } from \"@motionscript/core\";\nimport type { RendererWasmOption } from \"../renderer\";\nimport type { ResolvedVideoEncoding } from \"../exporter\";\nimport type { VideoFormat } from \"../formats\";\nimport type { ExportChunk } from \"./plan\";\n\n
|
|
1
|
+
{"version":3,"file":"lane.js","sourceRoot":"","sources":["../../src/parallel/lane.ts"],"names":[],"mappings":"","sourcesContent":["import type { AssetManifest, ProjectDocument, Registry, RenderIssue, Theme } from \"@motionscript/core\";\nimport type { RendererWasmOption } from \"../renderer\";\nimport type { ResolvedVideoEncoding } from \"../exporter\";\nimport type { VideoFormat } from \"../formats\";\nimport type { ExportChunk } from \"./plan\";\n\n/** Everything a lane needs to render chunks of one export: plain data (it crosses a `MessagePort`), with the encoding pinned since differently encoded segments cannot be joined. */\nexport interface SegmentSpec {\n /** Its fps and viewport resolved. */\n project: ProjectDocument;\n manifest: AssetManifest;\n theme?: Theme;\n scale: number;\n supersample: number;\n format: VideoFormat;\n encoding: ResolvedVideoEncoding;\n /** Passed through untouched to a remote lane's `resolve`, for what only its host knows. */\n host?: unknown;\n}\n\n/** A chunk's frames so far, and how its time split between drawing and waiting on the encoder. */\nexport interface LaneReport {\n frames: number;\n busyMs: number;\n waitMs: number;\n /** Render issues since the lane's last report. */\n issues?: RenderIssue[];\n}\n\n/** One lane, open on one export. */\nexport interface LaneSession {\n /** Render the chunk's frames to a complete, silent segment file. */\n render(chunk: ExportChunk, onReport: (report: LaneReport) => void, signal: AbortSignal): Promise<Uint8Array>;\n close(): void;\n}\n\n/** Something that renders chunks: this window, or another realm across a port. */\nexport interface ExportLane {\n open(spec: SegmentSpec, signal: AbortSignal): Promise<LaneSession>;\n}\n\n/** What a realm renders with: its own registry, and where its renderer's WASM is served. */\nexport interface LaneEnvironment extends RendererWasmOption {\n registry?: Registry;\n}\n"]}
|
package/dist/parallel/plan.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface ExportChunk {
|
|
|
4
4
|
start: number;
|
|
5
5
|
end: number;
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
interface ChunkPlanOptions {
|
|
8
8
|
fps: number;
|
|
9
9
|
/** Seconds between key frames; every chunk but the last is a whole number of them. */
|
|
10
10
|
keyFrameInterval: number;
|
|
@@ -13,11 +13,7 @@ export interface ChunkPlanOptions {
|
|
|
13
13
|
/** Overrides the chunk length the plan would pick, in seconds. */
|
|
14
14
|
chunkSeconds?: number;
|
|
15
15
|
}
|
|
16
|
-
/**
|
|
17
|
-
* Cut the timeline into chunks in timeline order.
|
|
18
|
-
*
|
|
19
|
-
* A chunk starts on a key-frame boundary of the whole timeline, so the joined
|
|
20
|
-
* file keeps the key-frame rhythm a single export would have.
|
|
21
|
-
*/
|
|
16
|
+
/** Cuts the timeline into chunks in order, each starting on a key-frame boundary of the whole timeline so the joined file keeps the key-frame rhythm of a single export. */
|
|
22
17
|
export declare function planChunks(totalFrames: number, options: ChunkPlanOptions): ExportChunk[];
|
|
18
|
+
export {};
|
|
23
19
|
//# sourceMappingURL=plan.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/parallel/plan.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACf;AAED,
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/parallel/plan.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACf;AAED,UAAU,gBAAgB;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,sFAAsF;IACtF,gBAAgB,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAOD,4KAA4K;AAC5K,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,WAAW,EAAE,CAkBxF"}
|
package/dist/parallel/plan.js
CHANGED
|
@@ -2,12 +2,7 @@ const MIN_CHUNK_SECONDS = 2;
|
|
|
2
2
|
const MAX_CHUNK_SECONDS = 6;
|
|
3
3
|
/** Chunks per lane: enough that lanes finish close together, few enough that a chunk's setup stays small. */
|
|
4
4
|
const CHUNKS_PER_LANE = 4;
|
|
5
|
-
/**
|
|
6
|
-
* Cut the timeline into chunks in timeline order.
|
|
7
|
-
*
|
|
8
|
-
* A chunk starts on a key-frame boundary of the whole timeline, so the joined
|
|
9
|
-
* file keeps the key-frame rhythm a single export would have.
|
|
10
|
-
*/
|
|
5
|
+
/** Cuts the timeline into chunks in order, each starting on a key-frame boundary of the whole timeline so the joined file keeps the key-frame rhythm of a single export. */
|
|
11
6
|
export function planChunks(totalFrames, options) {
|
|
12
7
|
if (!Number.isInteger(totalFrames) || totalFrames <= 0)
|
|
13
8
|
return [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/parallel/plan.ts"],"names":[],"mappings":"AAiBA,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAC5B,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAC5B,6GAA6G;AAC7G,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/parallel/plan.ts"],"names":[],"mappings":"AAiBA,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAC5B,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAC5B,6GAA6G;AAC7G,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B,4KAA4K;AAC5K,MAAM,UAAU,UAAU,CAAC,WAAmB,EAAE,OAAyB;IACrE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,WAAW,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAClE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClH,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY;WAC7B,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;IAC5I,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;IAE/E,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,WAAW,EAAE,KAAK,IAAI,IAAI,EAAE,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3F,CAAC;IACD,qFAAqF;IACrF,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,EAAE,CAAC;QACpD,MAAM,CAAC,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;IAC7C,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC","sourcesContent":["/** Frames `[start, end)` of the timeline, rendered as one complete segment file. */\nexport interface ExportChunk {\n index: number;\n start: number;\n end: number;\n}\n\ninterface ChunkPlanOptions {\n fps: number;\n /** Seconds between key frames; every chunk but the last is a whole number of them. */\n keyFrameInterval: number;\n /** The most lanes that may render at once. */\n lanes: number;\n /** Overrides the chunk length the plan would pick, in seconds. */\n chunkSeconds?: number;\n}\n\nconst MIN_CHUNK_SECONDS = 2;\nconst MAX_CHUNK_SECONDS = 6;\n/** Chunks per lane: enough that lanes finish close together, few enough that a chunk's setup stays small. */\nconst CHUNKS_PER_LANE = 4;\n\n/** Cuts the timeline into chunks in order, each starting on a key-frame boundary of the whole timeline so the joined file keeps the key-frame rhythm of a single export. */\nexport function planChunks(totalFrames: number, options: ChunkPlanOptions): ExportChunk[] {\n if (!Number.isInteger(totalFrames) || totalFrames <= 0) return [];\n const unit = Math.max(1, Math.round(options.fps * (options.keyFrameInterval > 0 ? options.keyFrameInterval : 1)));\n const seconds = options.chunkSeconds\n ?? Math.min(MAX_CHUNK_SECONDS, Math.max(MIN_CHUNK_SECONDS, totalFrames / options.fps / (Math.max(1, options.lanes) * CHUNKS_PER_LANE)));\n const size = Math.max(unit, Math.round((seconds * options.fps) / unit) * unit);\n\n const chunks: ExportChunk[] = [];\n for (let start = 0; start < totalFrames; start += size) {\n chunks.push({ index: chunks.length, start, end: Math.min(totalFrames, start + size) });\n }\n // A sliver of a last chunk costs a whole chunk's setup; fold it into the one before.\n const last = chunks[chunks.length - 1];\n if (chunks.length > 1 && last.end - last.start < unit) {\n chunks.pop();\n chunks[chunks.length - 1].end = last.end;\n }\n return chunks;\n}\n"]}
|
|
@@ -2,15 +2,11 @@ import type { RenderIssue } from "@motionscript/core";
|
|
|
2
2
|
import { type GovernorOptions } from "./governor";
|
|
3
3
|
import type { ExportLane, SegmentSpec } from "./lane";
|
|
4
4
|
import type { ExportChunk } from "./plan";
|
|
5
|
-
|
|
5
|
+
interface RunLanesOptions {
|
|
6
6
|
lanes: readonly ExportLane[];
|
|
7
7
|
spec: SegmentSpec;
|
|
8
8
|
chunks: readonly ExportChunk[];
|
|
9
|
-
/**
|
|
10
|
-
* A finished range, in whatever order ranges finish. Once the governor
|
|
11
|
-
* settles, adjacent chunks still queued are merged, so a range can span
|
|
12
|
-
* several of the planned chunks; it is named by the first.
|
|
13
|
-
*/
|
|
9
|
+
/** A finished range, in whatever order ranges finish. Once the governor settles, adjacent queued chunks are merged, so a range can span several planned chunks; it is named by the first. */
|
|
14
10
|
onChunk: (chunk: ExportChunk, bytes: Uint8Array) => void;
|
|
15
11
|
/** Frames drawn so far across every lane. Drops when a failed range's frames are discarded. */
|
|
16
12
|
onFrames?: (frames: number) => void;
|
|
@@ -22,17 +18,7 @@ export interface RunLanesOptions {
|
|
|
22
18
|
now?: () => number;
|
|
23
19
|
signal?: AbortSignal;
|
|
24
20
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Render every chunk across the lanes the governor admits, pulling from one queue
|
|
27
|
-
* in timeline order, so the earliest finish first and the join rarely waits.
|
|
28
|
-
*
|
|
29
|
-
* Small chunks are what let the governor measure early and lanes share the work,
|
|
30
|
-
* but each costs a fresh encoder and scene build; once the governor settles, a
|
|
31
|
-
* lane takes its share of what is left as one merged range instead (all of it,
|
|
32
|
-
* when one lane is running). A lane opens only once admitted, so a window that
|
|
33
|
-
* is not needed never competes with the first for the GPU. A range that fails is
|
|
34
|
-
* retried once on another lane and its lane is retired; a second failure, or the
|
|
35
|
-
* caller's abort, ends the run.
|
|
36
|
-
*/
|
|
21
|
+
/** Renders every chunk across the lanes the governor admits, pulling from one queue in timeline order so the join rarely waits. Small chunks let the governor measure early, but each costs a fresh encoder and scene build, so once it settles a lane takes its share of what is left as one merged range. A lane opens only once admitted. A failed range is retried once on another lane (its lane retired); a second failure, or the caller's abort, ends the run. */
|
|
37
22
|
export declare function runLanes(options: RunLanesOptions): Promise<void>;
|
|
23
|
+
export {};
|
|
38
24
|
//# sourceMappingURL=run-lanes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-lanes.d.ts","sourceRoot":"","sources":["../../src/parallel/run-lanes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,UAAU,EAA2B,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1C,
|
|
1
|
+
{"version":3,"file":"run-lanes.d.ts","sourceRoot":"","sources":["../../src/parallel/run-lanes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,UAAU,EAA2B,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1C,UAAU,eAAe;IACrB,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,6LAA6L;IAC7L,OAAO,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACzD,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,mDAAmD;IACnD,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,uFAAuF;IACvF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACvC,QAAQ,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;IAC1C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACxB;AAUD,0cAA0c;AAC1c,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAqItE"}
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import { LaneGovernor } from "./governor";
|
|
2
2
|
/** Tries a range gets before the run fails: once on its lane, once more on another. */
|
|
3
3
|
const ATTEMPTS = 2;
|
|
4
|
-
/**
|
|
5
|
-
* Render every chunk across the lanes the governor admits, pulling from one queue
|
|
6
|
-
* in timeline order, so the earliest finish first and the join rarely waits.
|
|
7
|
-
*
|
|
8
|
-
* Small chunks are what let the governor measure early and lanes share the work,
|
|
9
|
-
* but each costs a fresh encoder and scene build; once the governor settles, a
|
|
10
|
-
* lane takes its share of what is left as one merged range instead (all of it,
|
|
11
|
-
* when one lane is running). A lane opens only once admitted, so a window that
|
|
12
|
-
* is not needed never competes with the first for the GPU. A range that fails is
|
|
13
|
-
* retried once on another lane and its lane is retired; a second failure, or the
|
|
14
|
-
* caller's abort, ends the run.
|
|
15
|
-
*/
|
|
4
|
+
/** Renders every chunk across the lanes the governor admits, pulling from one queue in timeline order so the join rarely waits. Small chunks let the governor measure early, but each costs a fresh encoder and scene build, so once it settles a lane takes its share of what is left as one merged range. A lane opens only once admitted. A failed range is retried once on another lane (its lane retired); a second failure, or the caller's abort, ends the run. */
|
|
16
5
|
export async function runLanes(options) {
|
|
17
6
|
const { lanes, spec, chunks, signal } = options;
|
|
18
7
|
const now = options.now ?? (() => performance.now());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-lanes.js","sourceRoot":"","sources":["../../src/parallel/run-lanes.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RenderIssue } from \"@motionscript/core\";\nimport { LaneGovernor, type GovernorOptions } from \"./governor\";\nimport type { ExportLane, LaneReport, LaneSession, SegmentSpec } from \"./lane\";\nimport type { ExportChunk } from \"./plan\";\n\nexport interface RunLanesOptions {\n lanes: readonly ExportLane[];\n spec: SegmentSpec;\n chunks: readonly ExportChunk[];\n /**\n * A finished range, in whatever order ranges finish. Once the governor\n * settles, adjacent chunks still queued are merged, so a range can span\n * several of the planned chunks; it is named by the first.\n */\n onChunk: (chunk: ExportChunk, bytes: Uint8Array) => void;\n /** Frames drawn so far across every lane. Drops when a failed range's frames are discarded. */\n onFrames?: (frames: number) => void;\n /** The active lane count, each time it changes. */\n onActive?: (active: number) => void;\n /** A render issue a lane reported (a range retried elsewhere may report one twice). */\n onIssue?: (issue: RenderIssue) => void;\n governor?: Omit<GovernorOptions, \"lanes\">;\n now?: () => number;\n signal?: AbortSignal;\n}\n\n/** Tries a range gets before the run fails: once on its lane, once more on another. */\nconst ATTEMPTS = 2;\n\ninterface Job {\n chunk: ExportChunk;\n attempts: number;\n}\n\n/**\n * Render every chunk across the lanes the governor admits, pulling from one queue\n * in timeline order, so the earliest finish first and the join rarely waits.\n *\n * Small chunks are what let the governor measure early and lanes share the work,\n * but each costs a fresh encoder and scene build; once the governor settles, a\n * lane takes its share of what is left as one merged range instead (all of it,\n * when one lane is running). A lane opens only once admitted, so a window that\n * is not needed never competes with the first for the GPU. A range that fails is\n * retried once on another lane and its lane is retired; a second failure, or the\n * caller's abort, ends the run.\n */\nexport async function runLanes(options: RunLanesOptions): Promise<void> {\n const { lanes, spec, chunks, signal } = options;\n const now = options.now ?? (() => performance.now());\n if (lanes.length === 0) throw new Error(\"runLanes: no lanes\");\n if (chunks.length === 0) return;\n\n const controller = new AbortController();\n const abort = () => controller.abort(signal?.reason);\n if (signal?.aborted) abort();\n signal?.addEventListener(\"abort\", abort, { once: true });\n\n const governor = new LaneGovernor({ lanes: lanes.length, ...options.governor });\n const queue: Job[] = chunks.map((chunk) => ({ chunk, attempts: 0 }));\n const inFlight = new Map<Job, number>();\n const dead = new Set<number>();\n let doneFrames = 0;\n let remaining = chunks.reduce((sum, c) => sum + (c.end - c.start), 0);\n let failure: unknown = null;\n\n let wake: () => void = () => {};\n let woken = new Promise<void>((resolve) => (wake = resolve));\n const notify = () => {\n const resolve = wake;\n woken = new Promise<void>((r) => (wake = r));\n resolve();\n };\n controller.signal.addEventListener(\"abort\", notify, { once: true });\n\n const alive = () => lanes.length - dead.size;\n const admitted = (i: number) => {\n let rank = 0;\n for (let j = 0; j < i; j++) if (!dead.has(j)) rank++;\n return rank < governor.active;\n };\n const retire = (i: number) => {\n dead.add(i);\n governor.laneLost(alive());\n options.onActive?.(governor.active);\n notify();\n };\n const framesDrawn = () => {\n let frames = doneFrames;\n for (const f of inFlight.values()) frames += f;\n return frames;\n };\n const take = (): Job => {\n const job = queue.shift()!;\n if (!governor.settled || job.attempts > 0) return job;\n const queued = queue.reduce((sum, j) => sum + (j.chunk.end - j.chunk.start), job.chunk.end - job.chunk.start);\n const share = Math.ceil(queued / (governor.active === 1 ? 1 : governor.active * 2));\n let { end } = job.chunk;\n while (queue.length > 0 && queue[0].attempts === 0 && queue[0].chunk.start === end && end - job.chunk.start < share) {\n end = queue.shift()!.chunk.end;\n }\n return { chunk: { ...job.chunk, end }, attempts: 0 };\n };\n\n const runLane = async (i: number): Promise<void> => {\n while (!admitted(i) && remaining > 0 && !controller.signal.aborted && !dead.has(i)) await woken;\n if (remaining === 0 || controller.signal.aborted || dead.has(i)) return;\n\n let opened: LaneSession;\n try {\n opened = await lanes[i].open(spec, controller.signal);\n } catch (error) {\n if (controller.signal.aborted) return;\n if (alive() <= 1) throw error;\n console.warn(`[motionscript] export lane ${i} failed to open; continuing without it.`, error);\n retire(i);\n return;\n }\n try {\n for (;;) {\n controller.signal.throwIfAborted();\n if (remaining === 0) return;\n if (!admitted(i) || queue.length === 0) {\n await woken;\n continue;\n }\n const job = take();\n const frames = job.chunk.end - job.chunk.start;\n const reported: LaneReport = { frames: 0, busyMs: 0, waitMs: 0 };\n inFlight.set(job, 0);\n try {\n const bytes = await opened.render(job.chunk, (report) => {\n const changed = governor.record({\n lane: i,\n frames: report.frames - reported.frames,\n busyMs: report.busyMs - reported.busyMs,\n waitMs: report.waitMs - reported.waitMs,\n at: now(),\n });\n for (const issue of report.issues ?? []) options.onIssue?.(issue);\n Object.assign(reported, { frames: report.frames, busyMs: report.busyMs, waitMs: report.waitMs });\n inFlight.set(job, report.frames);\n options.onFrames?.(framesDrawn());\n if (changed !== null) {\n options.onActive?.(changed);\n notify();\n }\n }, controller.signal);\n inFlight.delete(job);\n doneFrames += frames;\n remaining -= frames;\n options.onFrames?.(framesDrawn());\n options.onChunk(job.chunk, bytes);\n notify();\n } catch (error) {\n inFlight.delete(job);\n if (controller.signal.aborted) throw error;\n if (++job.attempts >= ATTEMPTS || alive() <= 1) throw error;\n console.warn(`[motionscript] export lane ${i} failed frames ${job.chunk.start} to ${job.chunk.end}; retrying on another lane.`, error);\n queue.unshift(job);\n options.onFrames?.(framesDrawn());\n retire(i);\n return;\n }\n }\n } finally {\n opened.close();\n }\n };\n\n try {\n await Promise.all(lanes.map((_, i) => runLane(i).catch((error: unknown) => {\n failure ??= error;\n controller.abort(error);\n })));\n if (failure) throw failure;\n if (remaining > 0) throw new Error(`runLanes: ${remaining} frames were never rendered.`);\n } finally {\n signal?.removeEventListener(\"abort\", abort);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"run-lanes.js","sourceRoot":"","sources":["../../src/parallel/run-lanes.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RenderIssue } from \"@motionscript/core\";\nimport { LaneGovernor, type GovernorOptions } from \"./governor\";\nimport type { ExportLane, LaneReport, LaneSession, SegmentSpec } from \"./lane\";\nimport type { ExportChunk } from \"./plan\";\n\ninterface RunLanesOptions {\n lanes: readonly ExportLane[];\n spec: SegmentSpec;\n chunks: readonly ExportChunk[];\n /** A finished range, in whatever order ranges finish. Once the governor settles, adjacent queued chunks are merged, so a range can span several planned chunks; it is named by the first. */\n onChunk: (chunk: ExportChunk, bytes: Uint8Array) => void;\n /** Frames drawn so far across every lane. Drops when a failed range's frames are discarded. */\n onFrames?: (frames: number) => void;\n /** The active lane count, each time it changes. */\n onActive?: (active: number) => void;\n /** A render issue a lane reported (a range retried elsewhere may report one twice). */\n onIssue?: (issue: RenderIssue) => void;\n governor?: Omit<GovernorOptions, \"lanes\">;\n now?: () => number;\n signal?: AbortSignal;\n}\n\n/** Tries a range gets before the run fails: once on its lane, once more on another. */\nconst ATTEMPTS = 2;\n\ninterface Job {\n chunk: ExportChunk;\n attempts: number;\n}\n\n/** Renders every chunk across the lanes the governor admits, pulling from one queue in timeline order so the join rarely waits. Small chunks let the governor measure early, but each costs a fresh encoder and scene build, so once it settles a lane takes its share of what is left as one merged range. A lane opens only once admitted. A failed range is retried once on another lane (its lane retired); a second failure, or the caller's abort, ends the run. */\nexport async function runLanes(options: RunLanesOptions): Promise<void> {\n const { lanes, spec, chunks, signal } = options;\n const now = options.now ?? (() => performance.now());\n if (lanes.length === 0) throw new Error(\"runLanes: no lanes\");\n if (chunks.length === 0) return;\n\n const controller = new AbortController();\n const abort = () => controller.abort(signal?.reason);\n if (signal?.aborted) abort();\n signal?.addEventListener(\"abort\", abort, { once: true });\n\n const governor = new LaneGovernor({ lanes: lanes.length, ...options.governor });\n const queue: Job[] = chunks.map((chunk) => ({ chunk, attempts: 0 }));\n const inFlight = new Map<Job, number>();\n const dead = new Set<number>();\n let doneFrames = 0;\n let remaining = chunks.reduce((sum, c) => sum + (c.end - c.start), 0);\n let failure: unknown = null;\n\n let wake: () => void = () => {};\n let woken = new Promise<void>((resolve) => (wake = resolve));\n const notify = () => {\n const resolve = wake;\n woken = new Promise<void>((r) => (wake = r));\n resolve();\n };\n controller.signal.addEventListener(\"abort\", notify, { once: true });\n\n const alive = () => lanes.length - dead.size;\n const admitted = (i: number) => {\n let rank = 0;\n for (let j = 0; j < i; j++) if (!dead.has(j)) rank++;\n return rank < governor.active;\n };\n const retire = (i: number) => {\n dead.add(i);\n governor.laneLost(alive());\n options.onActive?.(governor.active);\n notify();\n };\n const framesDrawn = () => {\n let frames = doneFrames;\n for (const f of inFlight.values()) frames += f;\n return frames;\n };\n const take = (): Job => {\n const job = queue.shift()!;\n if (!governor.settled || job.attempts > 0) return job;\n const queued = queue.reduce((sum, j) => sum + (j.chunk.end - j.chunk.start), job.chunk.end - job.chunk.start);\n const share = Math.ceil(queued / (governor.active === 1 ? 1 : governor.active * 2));\n let { end } = job.chunk;\n while (queue.length > 0 && queue[0].attempts === 0 && queue[0].chunk.start === end && end - job.chunk.start < share) {\n end = queue.shift()!.chunk.end;\n }\n return { chunk: { ...job.chunk, end }, attempts: 0 };\n };\n\n const runLane = async (i: number): Promise<void> => {\n while (!admitted(i) && remaining > 0 && !controller.signal.aborted && !dead.has(i)) await woken;\n if (remaining === 0 || controller.signal.aborted || dead.has(i)) return;\n\n let opened: LaneSession;\n try {\n opened = await lanes[i].open(spec, controller.signal);\n } catch (error) {\n if (controller.signal.aborted) return;\n if (alive() <= 1) throw error;\n console.warn(`[motionscript] export lane ${i} failed to open; continuing without it.`, error);\n retire(i);\n return;\n }\n try {\n for (;;) {\n controller.signal.throwIfAborted();\n if (remaining === 0) return;\n if (!admitted(i) || queue.length === 0) {\n await woken;\n continue;\n }\n const job = take();\n const frames = job.chunk.end - job.chunk.start;\n const reported: LaneReport = { frames: 0, busyMs: 0, waitMs: 0 };\n inFlight.set(job, 0);\n try {\n const bytes = await opened.render(job.chunk, (report) => {\n const changed = governor.record({\n lane: i,\n frames: report.frames - reported.frames,\n busyMs: report.busyMs - reported.busyMs,\n waitMs: report.waitMs - reported.waitMs,\n at: now(),\n });\n for (const issue of report.issues ?? []) options.onIssue?.(issue);\n Object.assign(reported, { frames: report.frames, busyMs: report.busyMs, waitMs: report.waitMs });\n inFlight.set(job, report.frames);\n options.onFrames?.(framesDrawn());\n if (changed !== null) {\n options.onActive?.(changed);\n notify();\n }\n }, controller.signal);\n inFlight.delete(job);\n doneFrames += frames;\n remaining -= frames;\n options.onFrames?.(framesDrawn());\n options.onChunk(job.chunk, bytes);\n notify();\n } catch (error) {\n inFlight.delete(job);\n if (controller.signal.aborted) throw error;\n if (++job.attempts >= ATTEMPTS || alive() <= 1) throw error;\n console.warn(`[motionscript] export lane ${i} failed frames ${job.chunk.start} to ${job.chunk.end}; retrying on another lane.`, error);\n queue.unshift(job);\n options.onFrames?.(framesDrawn());\n retire(i);\n return;\n }\n }\n } finally {\n opened.close();\n }\n };\n\n try {\n await Promise.all(lanes.map((_, i) => runLane(i).catch((error: unknown) => {\n failure ??= error;\n controller.abort(error);\n })));\n if (failure) throw failure;\n if (remaining > 0) throw new Error(`runLanes: ${remaining} frames were never rendered.`);\n } finally {\n signal?.removeEventListener(\"abort\", abort);\n }\n}\n"]}
|
package/dist/screenshot.d.ts
CHANGED
|
@@ -29,17 +29,6 @@ export type ScreenshotResult = {
|
|
|
29
29
|
/** Encoded image bytes. */
|
|
30
30
|
bytes: Uint8Array;
|
|
31
31
|
};
|
|
32
|
-
/**
|
|
33
|
-
* Renders a single frame of a document to an image.
|
|
34
|
-
*
|
|
35
|
-
* A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.
|
|
36
|
-
* It therefore takes the same path as a live preview, and as the video export at
|
|
37
|
-
* that frame — there is one renderer, not three.
|
|
38
|
-
*
|
|
39
|
-
* For anything that captures **repeatedly** — a thumbnail builder, a preview that
|
|
40
|
-
* repaints as the user edits — hold a {@link StillRenderer} instead. This function
|
|
41
|
-
* builds and tears down a WebGPU surface per call, which is the right trade for one
|
|
42
|
-
* capture and the wrong one for a hundred.
|
|
43
|
-
*/
|
|
32
|
+
/** Renders a single frame of a document to an image: a one-shot wrapper over {@link StillRenderer} (create, render, encode, dispose), so it takes the same path as a live preview and the video export at that frame. For repeated capture hold a {@link StillRenderer}: this builds and tears down a WebGPU surface per call. */
|
|
44
33
|
export declare function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult>;
|
|
45
34
|
//# sourceMappingURL=screenshot.d.ts.map
|
package/dist/screenshot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,KAAK,KAAK,EACxH,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG;IAChD,yEAAyE;IACzE,QAAQ,EAAE,eAAe,GAAG,cAAc,CAAC;IAC3C,oFAAoF;IACpF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,gEAAgE;IAChE,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,uCAAuC;IACvC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,KAAK,EAAE,UAAU,CAAC;CACrB,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,KAAK,KAAK,EACxH,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG;IAChD,yEAAyE;IACzE,QAAQ,EAAE,eAAe,GAAG,cAAc,CAAC;IAC3C,oFAAoF;IACpF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,gEAAgE;IAChE,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,uCAAuC;IACvC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,KAAK,EAAE,UAAU,CAAC;CACrB,CAAC;AAEF,kUAAkU;AAClU,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmB1F"}
|