onda-engine 0.1.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/LICENSE +106 -0
- package/LICENSE-APACHE +202 -0
- package/README.md +84 -0
- package/dist/chunk-NCNYMPIQ.js +12763 -0
- package/dist/chunk-NCNYMPIQ.js.map +1 -0
- package/dist/cinema.d.ts +580 -0
- package/dist/cinema.js +1687 -0
- package/dist/cinema.js.map +1 -0
- package/dist/components-manifest.d.ts +2 -0
- package/dist/components-manifest.js +3 -0
- package/dist/components-manifest.js.map +1 -0
- package/dist/components.d.ts +3480 -0
- package/dist/components.js +11486 -0
- package/dist/components.js.map +1 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest-7N3yu9tB.d.ts +131 -0
- package/dist/player.d.ts +177 -0
- package/dist/player.js +1749 -0
- package/dist/player.js.map +1 -0
- package/dist/react.d.ts +2141 -0
- package/dist/react.js +2052 -0
- package/dist/react.js.map +1 -0
- package/dist/render.d.ts +42 -0
- package/dist/render.js +113 -0
- package/dist/render.js.map +1 -0
- package/dist/wasm/pkg/onda_wasm.js +598 -0
- package/dist/wasm/pkg/onda_wasm_bg.wasm +0 -0
- package/dist/wasm-audio/pkg/onda_wasm_audio.js +417 -0
- package/dist/wasm-audio/pkg/onda_wasm_audio_bg.wasm +0 -0
- package/dist/wasm-vello/index.js +32 -0
- package/dist/wasm-vello/pkg/onda_wasm_vello.js +1325 -0
- package/dist/wasm-vello/pkg/onda_wasm_vello_bg.wasm +0 -0
- package/package.json +112 -0
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
type Backend = 'auto' | 'vello' | 'cpu';
|
|
4
|
+
type Encoder = 'auto' | 'videotoolbox' | 'nvenc' | 'qsv' | 'libx264';
|
|
5
|
+
interface RenderProgress {
|
|
6
|
+
renderedFrames: number;
|
|
7
|
+
totalFrames: number;
|
|
8
|
+
}
|
|
9
|
+
interface RenderToFileOptions {
|
|
10
|
+
/** Output path — `.mp4` (or `.gif`). */
|
|
11
|
+
output: string;
|
|
12
|
+
/** Rendering backend. Default `'auto'` (Vello/GPU if available, else CPU). */
|
|
13
|
+
backend?: Backend;
|
|
14
|
+
/** H.264 encoder for mp4. Default `'auto'` (probes for hardware, else libx264). */
|
|
15
|
+
encoder?: Encoder;
|
|
16
|
+
/** Called once per rendered frame. */
|
|
17
|
+
onProgress?: (progress: RenderProgress) => void;
|
|
18
|
+
/** Path to the `onda` binary. Default: `$ONDA_BIN`, else `onda` on PATH. */
|
|
19
|
+
ondaBin?: string;
|
|
20
|
+
/** Spatial supersampling factor (1–4, default 1 = off): the CLI renders each
|
|
21
|
+
* frame at N× resolution then box-downscales to native, area-averaging away the
|
|
22
|
+
* minification aliasing detailed images shimmer with under motion. N=2 is the
|
|
23
|
+
* sweet spot (matches the image decoder's 2× headroom). Costs ~N² render time. */
|
|
24
|
+
superSample?: number;
|
|
25
|
+
}
|
|
26
|
+
interface RenderStillOptions {
|
|
27
|
+
/** Output path — `.png`. */
|
|
28
|
+
output: string;
|
|
29
|
+
/** Which frame to render. Default `0`. */
|
|
30
|
+
frame?: number;
|
|
31
|
+
backend?: Backend;
|
|
32
|
+
ondaBin?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Render a composition to a video file. Generates every frame's scene graph
|
|
36
|
+
* in-process, then hands it to the `onda` CLI to rasterize + encode.
|
|
37
|
+
*/
|
|
38
|
+
declare function renderToFile(composition: ReactElement, options: RenderToFileOptions): Promise<void>;
|
|
39
|
+
/** Render a single frame to a PNG (e.g. a poster frame or a vision check). */
|
|
40
|
+
declare function renderStillToFile(composition: ReactElement, options: RenderStillOptions): Promise<void>;
|
|
41
|
+
|
|
42
|
+
export { type Backend, type Encoder, type RenderProgress, type RenderStillOptions, type RenderToFileOptions, renderStillToFile, renderToFile };
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { mkdtemp, writeFile, rm } from 'fs/promises';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { runEngineWarmers, motionBlurConfig, renderFramesJSON, renderFrame, registeredFonts } from 'onda-engine/react';
|
|
6
|
+
|
|
7
|
+
// ../render/src/index.ts
|
|
8
|
+
var resolveBin = (override) => override ?? process.env.ONDA_BIN ?? "onda";
|
|
9
|
+
async function fontArgs(dir) {
|
|
10
|
+
const fonts = registeredFonts();
|
|
11
|
+
const args = [];
|
|
12
|
+
for (let i = 0; i < fonts.length; i++) {
|
|
13
|
+
const fontPath = join(dir, `font-${i}.ttf`);
|
|
14
|
+
await writeFile(fontPath, fonts[i]);
|
|
15
|
+
args.push("--font", fontPath);
|
|
16
|
+
}
|
|
17
|
+
return args;
|
|
18
|
+
}
|
|
19
|
+
async function renderToFile(composition, options) {
|
|
20
|
+
const { output, backend = "auto", encoder = "auto", onProgress, ondaBin, superSample } = options;
|
|
21
|
+
await runEngineWarmers();
|
|
22
|
+
const motionBlur = motionBlurConfig(composition);
|
|
23
|
+
const framesJson = renderFramesJSON(composition);
|
|
24
|
+
const dir = await mkdtemp(join(tmpdir(), "onda-render-"));
|
|
25
|
+
const framesPath = join(dir, "frames.json");
|
|
26
|
+
try {
|
|
27
|
+
await writeFile(framesPath, framesJson);
|
|
28
|
+
await runOnda(
|
|
29
|
+
resolveBin(ondaBin),
|
|
30
|
+
[
|
|
31
|
+
"export-frames",
|
|
32
|
+
framesPath,
|
|
33
|
+
output,
|
|
34
|
+
"--backend",
|
|
35
|
+
backend,
|
|
36
|
+
"--encoder",
|
|
37
|
+
encoder,
|
|
38
|
+
"--progress",
|
|
39
|
+
...motionBlur ? ["--motion-blur", String(motionBlur.samples)] : [],
|
|
40
|
+
...superSample && superSample > 1 ? ["--supersample", String(Math.min(4, Math.round(superSample)))] : [],
|
|
41
|
+
...await fontArgs(dir)
|
|
42
|
+
],
|
|
43
|
+
onProgress
|
|
44
|
+
);
|
|
45
|
+
} finally {
|
|
46
|
+
await rm(dir, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function renderStillToFile(composition, options) {
|
|
50
|
+
const { output, frame = 0, backend = "auto", ondaBin } = options;
|
|
51
|
+
await runEngineWarmers();
|
|
52
|
+
const sceneJson = JSON.stringify(renderFrame(composition, frame));
|
|
53
|
+
const dir = await mkdtemp(join(tmpdir(), "onda-still-"));
|
|
54
|
+
const scenePath = join(dir, "scene.json");
|
|
55
|
+
try {
|
|
56
|
+
await writeFile(scenePath, sceneJson);
|
|
57
|
+
await runOnda(resolveBin(ondaBin), [
|
|
58
|
+
"render",
|
|
59
|
+
scenePath,
|
|
60
|
+
output,
|
|
61
|
+
"--backend",
|
|
62
|
+
backend,
|
|
63
|
+
...await fontArgs(dir)
|
|
64
|
+
]);
|
|
65
|
+
} finally {
|
|
66
|
+
await rm(dir, { recursive: true, force: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function runOnda(bin, args, onProgress) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const child = spawn(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
72
|
+
let stderr = "";
|
|
73
|
+
let buffer = "";
|
|
74
|
+
child.stdout?.on("data", (chunk) => {
|
|
75
|
+
buffer += chunk.toString();
|
|
76
|
+
let nl = buffer.indexOf("\n");
|
|
77
|
+
while (nl !== -1) {
|
|
78
|
+
const line = buffer.slice(0, nl);
|
|
79
|
+
buffer = buffer.slice(nl + 1);
|
|
80
|
+
const match = line.match(/^\[onda-progress\](\{.*\})/);
|
|
81
|
+
if (match?.[1] && onProgress) {
|
|
82
|
+
try {
|
|
83
|
+
const p = JSON.parse(match[1]);
|
|
84
|
+
onProgress({ renderedFrames: p.frame, totalFrames: p.total });
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
nl = buffer.indexOf("\n");
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
child.stderr?.on("data", (chunk) => {
|
|
92
|
+
stderr += chunk.toString();
|
|
93
|
+
});
|
|
94
|
+
child.on(
|
|
95
|
+
"error",
|
|
96
|
+
(err) => reject(
|
|
97
|
+
new Error(`failed to launch onda ('${bin}' \u2014 installed and on PATH?): ${err.message}`)
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
child.on("close", (code) => {
|
|
101
|
+
if (code === 0) resolve();
|
|
102
|
+
else reject(new Error(`onda exited with code ${code}${stderr ? `: ${stderr.trim()}` : ""}`));
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//! `@onda-engine/render` — render `@onda-engine/react` compositions to a file via the ONDA
|
|
107
|
+
//! engine. The no-Chromium equivalent of Remotion's `renderMedia`: the scene
|
|
108
|
+
//! graph is generated in-process (`renderFramesJSON`) and handed to the `onda`
|
|
109
|
+
//! CLI, which rasterizes on the GPU (Vello) and encodes with ffmpeg.
|
|
110
|
+
|
|
111
|
+
export { renderStillToFile, renderToFile };
|
|
112
|
+
//# sourceMappingURL=render.js.map
|
|
113
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../render/src/index.ts"],"names":[],"mappings":"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","file":"render.js","sourcesContent":["//! `@onda-engine/render` — render `@onda-engine/react` compositions to a file via the ONDA\n//! engine. The no-Chromium equivalent of Remotion's `renderMedia`: the scene\n//! graph is generated in-process (`renderFramesJSON`) and handed to the `onda`\n//! CLI, which rasterizes on the GPU (Vello) and encodes with ffmpeg.\n\nimport { spawn } from 'node:child_process'\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n motionBlurConfig,\n registeredFonts,\n renderFrame,\n renderFramesJSON,\n runEngineWarmers,\n} from '@onda-engine/react'\nimport type { ReactElement } from 'react'\n\nexport type Backend = 'auto' | 'vello' | 'cpu'\nexport type Encoder = 'auto' | 'videotoolbox' | 'nvenc' | 'qsv' | 'libx264'\n\nexport interface RenderProgress {\n renderedFrames: number\n totalFrames: number\n}\n\nexport interface RenderToFileOptions {\n /** Output path — `.mp4` (or `.gif`). */\n output: string\n /** Rendering backend. Default `'auto'` (Vello/GPU if available, else CPU). */\n backend?: Backend\n /** H.264 encoder for mp4. Default `'auto'` (probes for hardware, else libx264). */\n encoder?: Encoder\n /** Called once per rendered frame. */\n onProgress?: (progress: RenderProgress) => void\n /** Path to the `onda` binary. Default: `$ONDA_BIN`, else `onda` on PATH. */\n ondaBin?: string\n /** Spatial supersampling factor (1–4, default 1 = off): the CLI renders each\n * frame at N× resolution then box-downscales to native, area-averaging away the\n * minification aliasing detailed images shimmer with under motion. N=2 is the\n * sweet spot (matches the image decoder's 2× headroom). Costs ~N² render time. */\n superSample?: number\n}\n\nexport interface RenderStillOptions {\n /** Output path — `.png`. */\n output: string\n /** Which frame to render. Default `0`. */\n frame?: number\n backend?: Backend\n ondaBin?: string\n}\n\nconst resolveBin = (override?: string): string => override ?? process.env.ONDA_BIN ?? 'onda'\n\n/** Materialize any fonts the composition declared via `loadFont` into `dir` and\n * return the `--font <path>` CLI args, so the renderer draws with the SAME bytes\n * the author-time measurement used (single-source — no manual `--font`). Empty\n * when no custom font was registered. Call AFTER rendering, so fonts declared\n * during the render are included. */\nasync function fontArgs(dir: string): Promise<string[]> {\n const fonts = registeredFonts()\n const args: string[] = []\n for (let i = 0; i < fonts.length; i++) {\n const fontPath = join(dir, `font-${i}.ttf`)\n await writeFile(fontPath, fonts[i] as Uint8Array)\n args.push('--font', fontPath)\n }\n return args\n}\n\n/**\n * Render a composition to a video file. Generates every frame's scene graph\n * in-process, then hands it to the `onda` CLI to rasterize + encode.\n */\nexport async function renderToFile(\n composition: ReactElement,\n options: RenderToFileOptions,\n): Promise<void> {\n const { output, backend = 'auto', encoder = 'auto', onProgress, ondaBin, superSample } = options\n // Warm async engine assets (e.g. wasm text measurement) before the sync render,\n // so components bake real values into the frames instead of estimates.\n await runEngineWarmers()\n // `renderFramesJSON` already expands each frame to its motion-blur sub-frames; the\n // CLI averages each group of K back into one frame, so pass the matching K.\n const motionBlur = motionBlurConfig(composition)\n const framesJson = renderFramesJSON(composition)\n const dir = await mkdtemp(join(tmpdir(), 'onda-render-'))\n const framesPath = join(dir, 'frames.json')\n try {\n await writeFile(framesPath, framesJson)\n await runOnda(\n resolveBin(ondaBin),\n [\n 'export-frames',\n framesPath,\n output,\n '--backend',\n backend,\n '--encoder',\n encoder,\n '--progress',\n ...(motionBlur ? ['--motion-blur', String(motionBlur.samples)] : []),\n ...(superSample && superSample > 1\n ? ['--supersample', String(Math.min(4, Math.round(superSample)))]\n : []),\n ...(await fontArgs(dir)),\n ],\n onProgress,\n )\n } finally {\n await rm(dir, { recursive: true, force: true })\n }\n}\n\n/** Render a single frame to a PNG (e.g. a poster frame or a vision check). */\nexport async function renderStillToFile(\n composition: ReactElement,\n options: RenderStillOptions,\n): Promise<void> {\n const { output, frame = 0, backend = 'auto', ondaBin } = options\n await runEngineWarmers()\n const sceneJson = JSON.stringify(renderFrame(composition, frame))\n const dir = await mkdtemp(join(tmpdir(), 'onda-still-'))\n const scenePath = join(dir, 'scene.json')\n try {\n await writeFile(scenePath, sceneJson)\n await runOnda(resolveBin(ondaBin), [\n 'render',\n scenePath,\n output,\n '--backend',\n backend,\n ...(await fontArgs(dir)),\n ])\n } finally {\n await rm(dir, { recursive: true, force: true })\n }\n}\n\n/** Spawn the `onda` CLI, stream `[onda-progress]` lines to `onProgress`, and\n * resolve on a clean exit (reject with stderr otherwise). */\nfunction runOnda(\n bin: string,\n args: string[],\n onProgress?: (progress: RenderProgress) => void,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn(bin, args, { stdio: ['ignore', 'pipe', 'pipe'] })\n let stderr = ''\n let buffer = ''\n child.stdout?.on('data', (chunk: Buffer) => {\n buffer += chunk.toString()\n let nl = buffer.indexOf('\\n')\n while (nl !== -1) {\n const line = buffer.slice(0, nl)\n buffer = buffer.slice(nl + 1)\n const match = line.match(/^\\[onda-progress\\](\\{.*\\})/)\n if (match?.[1] && onProgress) {\n try {\n const p = JSON.parse(match[1]) as { frame: number; total: number }\n onProgress({ renderedFrames: p.frame, totalFrames: p.total })\n } catch {\n // ignore a malformed progress line\n }\n }\n nl = buffer.indexOf('\\n')\n }\n })\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString()\n })\n child.on('error', (err) =>\n reject(\n new Error(`failed to launch onda ('${bin}' — installed and on PATH?): ${err.message}`),\n ),\n )\n child.on('close', (code) => {\n if (code === 0) resolve()\n else reject(new Error(`onda exited with code ${code}${stderr ? `: ${stderr.trim()}` : ''}`))\n })\n })\n}\n"]}
|