@vibeo/renderer 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.
Files changed (56) hide show
  1. package/dist/browser.d.ts +15 -0
  2. package/dist/browser.d.ts.map +1 -0
  3. package/dist/browser.js +40 -0
  4. package/dist/browser.js.map +1 -0
  5. package/dist/bundler.d.ts +10 -0
  6. package/dist/bundler.d.ts.map +1 -0
  7. package/dist/bundler.js +135 -0
  8. package/dist/bundler.js.map +1 -0
  9. package/dist/capture-frame.d.ts +12 -0
  10. package/dist/capture-frame.d.ts.map +1 -0
  11. package/dist/capture-frame.js +21 -0
  12. package/dist/capture-frame.js.map +1 -0
  13. package/dist/frame-range.d.ts +15 -0
  14. package/dist/frame-range.d.ts.map +1 -0
  15. package/dist/frame-range.js +55 -0
  16. package/dist/frame-range.js.map +1 -0
  17. package/dist/index.d.ts +11 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +18 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/parallel-render.d.ts +22 -0
  22. package/dist/parallel-render.d.ts.map +1 -0
  23. package/dist/parallel-render.js +69 -0
  24. package/dist/parallel-render.js.map +1 -0
  25. package/dist/render-composition.d.ts +19 -0
  26. package/dist/render-composition.d.ts.map +1 -0
  27. package/dist/render-composition.js +104 -0
  28. package/dist/render-composition.js.map +1 -0
  29. package/dist/seek-to-frame.d.ts +15 -0
  30. package/dist/seek-to-frame.d.ts.map +1 -0
  31. package/dist/seek-to-frame.js +58 -0
  32. package/dist/seek-to-frame.js.map +1 -0
  33. package/dist/stitch-audio.d.ts +7 -0
  34. package/dist/stitch-audio.d.ts.map +1 -0
  35. package/dist/stitch-audio.js +53 -0
  36. package/dist/stitch-audio.js.map +1 -0
  37. package/dist/stitch-frames.d.ts +11 -0
  38. package/dist/stitch-frames.d.ts.map +1 -0
  39. package/dist/stitch-frames.js +77 -0
  40. package/dist/stitch-frames.js.map +1 -0
  41. package/dist/types.d.ts +74 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +2 -0
  44. package/dist/types.js.map +1 -0
  45. package/package.json +33 -0
  46. package/src/browser.ts +48 -0
  47. package/src/bundler.ts +146 -0
  48. package/src/capture-frame.ts +39 -0
  49. package/src/frame-range.ts +81 -0
  50. package/src/index.ts +36 -0
  51. package/src/parallel-render.ts +134 -0
  52. package/src/render-composition.ts +144 -0
  53. package/src/seek-to-frame.ts +89 -0
  54. package/src/stitch-audio.ts +79 -0
  55. package/src/stitch-frames.ts +95 -0
  56. package/src/types.ts +80 -0
@@ -0,0 +1,15 @@
1
+ import type { Browser, Page } from "playwright";
2
+ /**
3
+ * Launch a headless Chromium browser via Playwright.
4
+ * Reuses the existing browser if already launched.
5
+ */
6
+ export declare function launchBrowser(): Promise<Browser>;
7
+ /**
8
+ * Close the browser and release resources.
9
+ */
10
+ export declare function closeBrowser(): Promise<void>;
11
+ /**
12
+ * Create a new browser page with a viewport matching the composition dimensions.
13
+ */
14
+ export declare function createPage(browser: Browser, width: number, height: number): Promise<Page>;
15
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIhD;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CActD;AAED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAKlD;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAMf"}
@@ -0,0 +1,40 @@
1
+ let _browser = null;
2
+ /**
3
+ * Launch a headless Chromium browser via Playwright.
4
+ * Reuses the existing browser if already launched.
5
+ */
6
+ export async function launchBrowser() {
7
+ if (_browser && _browser.isConnected()) {
8
+ return _browser;
9
+ }
10
+ const { chromium } = await import("playwright");
11
+ _browser = await chromium.launch({
12
+ headless: true,
13
+ args: [
14
+ "--disable-web-security",
15
+ "--allow-file-access-from-files",
16
+ "--autoplay-policy=no-user-gesture-required",
17
+ ],
18
+ });
19
+ return _browser;
20
+ }
21
+ /**
22
+ * Close the browser and release resources.
23
+ */
24
+ export async function closeBrowser() {
25
+ if (_browser) {
26
+ await _browser.close();
27
+ _browser = null;
28
+ }
29
+ }
30
+ /**
31
+ * Create a new browser page with a viewport matching the composition dimensions.
32
+ */
33
+ export async function createPage(browser, width, height) {
34
+ const page = await browser.newPage({
35
+ viewport: { width, height },
36
+ deviceScaleFactor: 1,
37
+ });
38
+ return page;
39
+ }
40
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAEA,IAAI,QAAQ,GAAmB,IAAI,CAAC;AAEpC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,QAAQ,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC;QACvC,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;IAChD,QAAQ,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC;QAC/B,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE;YACJ,wBAAwB;YACxB,gCAAgC;YAChC,4CAA4C;SAC7C;KACF,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;QACvB,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,OAAgB,EAChB,KAAa,EACb,MAAc;IAEd,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC;QACjC,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;QAC3B,iBAAiB,EAAE,CAAC;KACrB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { BundleResult } from "./types.js";
2
+ /**
3
+ * Bundle the user's React entry point using Bun.build,
4
+ * then serve it via a local HTTP server for the headless browser to load.
5
+ *
6
+ * Generates a thin bootstrap entry that imports the user's Root export
7
+ * and mounts it with ReactDOM.createRoot.
8
+ */
9
+ export declare function bundle(entryPoint: string): Promise<BundleResult>;
10
+ //# sourceMappingURL=bundler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundler.d.ts","sourceRoot":"","sources":["../src/bundler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;GAMG;AACH,wBAAsB,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAqItE"}
@@ -0,0 +1,135 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { join, dirname } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ /**
5
+ * Bundle the user's React entry point using Bun.build,
6
+ * then serve it via a local HTTP server for the headless browser to load.
7
+ *
8
+ * Generates a thin bootstrap entry that imports the user's Root export
9
+ * and mounts it with ReactDOM.createRoot.
10
+ */
11
+ export async function bundle(entryPoint) {
12
+ const outDir = await mkdtemp(join(tmpdir(), "vibeo-bundle-"));
13
+ // Create a bootstrap entry next to the user's file so Bun.build
14
+ // resolves node_modules from the project root, not from /tmp.
15
+ const entryDir = dirname(entryPoint);
16
+ const bootstrapPath = join(entryDir, "__vibeo_entry.tsx");
17
+ await Bun.write(bootstrapPath, `import React from "react";
18
+ import { createRoot } from "react-dom/client";
19
+ import { Root } from ${JSON.stringify(entryPoint)};
20
+ import { Player } from "@vibeo/player";
21
+ import { VibeoRoot, useCompositionContext } from "@vibeo/core";
22
+
23
+ /**
24
+ * PreviewShell: renders <Root> to register compositions,
25
+ * then picks the first one and renders it inside a <Player>.
26
+ */
27
+ function CompositionPlayer() {
28
+ const { compositions } = useCompositionContext();
29
+
30
+ // Derive comp directly during render — no useEffect delay
31
+ const hash = window.location.hash.slice(1);
32
+ let comp: any = null;
33
+ if (hash && compositions.has(hash)) {
34
+ comp = compositions.get(hash);
35
+ } else if (compositions.size > 0) {
36
+ // Get first value from map
37
+ for (const v of compositions.values()) { comp = v; break; }
38
+ }
39
+
40
+ if (!comp) {
41
+ return React.createElement("div", {
42
+ style: { color: "#888", fontFamily: "sans-serif", padding: 40 }
43
+ }, "Loading composition...");
44
+ }
45
+
46
+ return React.createElement(Player, {
47
+ component: comp.component,
48
+ durationInFrames: comp.durationInFrames,
49
+ compositionWidth: comp.width,
50
+ compositionHeight: comp.height,
51
+ fps: comp.fps,
52
+ controls: true,
53
+ loop: true,
54
+ autoPlay: true,
55
+ style: { width: "100vw", height: "100vh" },
56
+ inputProps: comp.defaultProps,
57
+ });
58
+ }
59
+
60
+ function PreviewApp() {
61
+ return React.createElement(VibeoRoot, null,
62
+ React.createElement(Root),
63
+ React.createElement(CompositionPlayer),
64
+ );
65
+ }
66
+
67
+ const container = document.getElementById("root")!;
68
+ const root = createRoot(container);
69
+ root.render(React.createElement(PreviewApp));
70
+
71
+ window.vibeo_ready = true;
72
+ `);
73
+ try {
74
+ const result = await Bun.build({
75
+ entrypoints: [bootstrapPath],
76
+ outdir: outDir,
77
+ target: "browser",
78
+ format: "esm",
79
+ minify: false,
80
+ splitting: false,
81
+ define: {
82
+ "process.env.NODE_ENV": JSON.stringify("production"),
83
+ },
84
+ });
85
+ if (!result.success) {
86
+ const messages = result.logs.map((l) => l.message).join("\n");
87
+ throw new Error(`Bundle failed:\n${messages}`);
88
+ }
89
+ // Write a minimal HTML shell that loads the bundle
90
+ const bundleName = result.outputs[0]?.path.split("/").pop() ?? "__vibeo_entry.js";
91
+ const html = `<!DOCTYPE html>
92
+ <html>
93
+ <head>
94
+ <meta charset="utf-8" />
95
+ <style>
96
+ * { margin: 0; padding: 0; box-sizing: border-box; }
97
+ html, body, #root { width: 100%; height: 100%; overflow: hidden; }
98
+ </style>
99
+ </head>
100
+ <body>
101
+ <div id="root"></div>
102
+ <script type="module" src="/${bundleName}"></script>
103
+ </body>
104
+ </html>`;
105
+ await Bun.write(join(outDir, "index.html"), html);
106
+ // Start a local HTTP server to serve the bundle
107
+ const server = Bun.serve({
108
+ port: 0, // auto-assign
109
+ async fetch(req) {
110
+ const url = new URL(req.url);
111
+ const filePath = join(outDir, url.pathname === "/" ? "index.html" : url.pathname);
112
+ const file = Bun.file(filePath);
113
+ if (await file.exists()) {
114
+ return new Response(file);
115
+ }
116
+ return new Response("Not found", { status: 404 });
117
+ },
118
+ });
119
+ const serverPort = server.port ?? 0;
120
+ const cleanup = async () => {
121
+ server.stop(true);
122
+ await rm(outDir, { recursive: true, force: true });
123
+ };
124
+ return {
125
+ outDir,
126
+ url: `http://localhost:${serverPort}`,
127
+ cleanup,
128
+ };
129
+ }
130
+ finally {
131
+ // Always clean up the bootstrap file we dropped next to the user's entry
132
+ await rm(bootstrapPath, { force: true });
133
+ }
134
+ }
135
+ //# sourceMappingURL=bundler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundler.js","sourceRoot":"","sources":["../src/bundler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Page } from "playwright";
2
+ import type { ImageFormat } from "./types.js";
3
+ /**
4
+ * Capture a screenshot of the current page state and write it to disk.
5
+ *
6
+ * @returns The file path of the saved frame.
7
+ */
8
+ export declare function captureFrame(page: Page, outputDir: string, frameNumber: number, options: {
9
+ imageFormat: ImageFormat;
10
+ quality?: number;
11
+ }): Promise<string>;
12
+ //# sourceMappingURL=capture-frame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-frame.d.ts","sourceRoot":"","sources":["../src/capture-frame.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;IACP,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GACA,OAAO,CAAC,MAAM,CAAC,CAqBjB"}
@@ -0,0 +1,21 @@
1
+ import { join } from "node:path";
2
+ /**
3
+ * Capture a screenshot of the current page state and write it to disk.
4
+ *
5
+ * @returns The file path of the saved frame.
6
+ */
7
+ export async function captureFrame(page, outputDir, frameNumber, options) {
8
+ const ext = options.imageFormat === "jpeg" ? "jpg" : "png";
9
+ const paddedFrame = String(frameNumber).padStart(6, "0");
10
+ const filePath = join(outputDir, `frame-${paddedFrame}.${ext}`);
11
+ const screenshotOptions = {
12
+ type: options.imageFormat,
13
+ path: filePath,
14
+ };
15
+ if (options.imageFormat === "jpeg" && options.quality !== undefined) {
16
+ screenshotOptions.quality = options.quality;
17
+ }
18
+ await page.screenshot(screenshotOptions);
19
+ return filePath;
20
+ }
21
+ //# sourceMappingURL=capture-frame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-frame.js","sourceRoot":"","sources":["../src/capture-frame.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAU,EACV,SAAiB,EACjB,WAAmB,EACnB,OAGC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;IAC3D,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,SAAS,WAAW,IAAI,GAAG,EAAE,CAAC,CAAC;IAEhE,MAAM,iBAAiB,GAInB;QACF,IAAI,EAAE,OAAO,CAAC,WAAW;QACzB,IAAI,EAAE,QAAQ;KACf,CAAC;IAEF,IAAI,OAAO,CAAC,WAAW,KAAK,MAAM,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACpE,iBAAiB,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAC9C,CAAC;IAED,MAAM,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAEzC,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { FrameRange } from "./types.js";
2
+ /**
3
+ * Parse a frame range string like "10-50" or "25" into a [start, end] tuple.
4
+ * Returns null if the input is null or empty.
5
+ */
6
+ export declare function parseFrameRange(input: string | null, durationInFrames: number): FrameRange | null;
7
+ /**
8
+ * Get the real frame range to render, accounting for optional user-specified range.
9
+ */
10
+ export declare function getRealFrameRange(durationInFrames: number, frameRange: FrameRange | null): FrameRange;
11
+ /**
12
+ * Validate that a frame range is within bounds and well-formed.
13
+ */
14
+ export declare function validateFrameRange(range: FrameRange, durationInFrames: number): void;
15
+ //# sourceMappingURL=frame-range.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-range.d.ts","sourceRoot":"","sources":["../src/frame-range.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,gBAAgB,EAAE,MAAM,GACvB,UAAU,GAAG,IAAI,CA2BnB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,gBAAgB,EAAE,MAAM,EACxB,UAAU,EAAE,UAAU,GAAG,IAAI,GAC5B,UAAU,CAKZ;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,UAAU,EACjB,gBAAgB,EAAE,MAAM,GACvB,IAAI,CAuBN"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Parse a frame range string like "10-50" or "25" into a [start, end] tuple.
3
+ * Returns null if the input is null or empty.
4
+ */
5
+ export function parseFrameRange(input, durationInFrames) {
6
+ if (input === null || input.trim() === "") {
7
+ return null;
8
+ }
9
+ const trimmed = input.trim();
10
+ if (trimmed.includes("-")) {
11
+ const [startStr, endStr] = trimmed.split("-");
12
+ const start = parseInt(startStr, 10);
13
+ const end = parseInt(endStr, 10);
14
+ if (isNaN(start) || isNaN(end)) {
15
+ throw new Error(`Invalid frame range: "${input}". Expected format: "start-end"`);
16
+ }
17
+ validateFrameRange([start, end], durationInFrames);
18
+ return [start, end];
19
+ }
20
+ const singleFrame = parseInt(trimmed, 10);
21
+ if (isNaN(singleFrame)) {
22
+ throw new Error(`Invalid frame range: "${input}". Expected a number or "start-end"`);
23
+ }
24
+ validateFrameRange([singleFrame, singleFrame], durationInFrames);
25
+ return [singleFrame, singleFrame];
26
+ }
27
+ /**
28
+ * Get the real frame range to render, accounting for optional user-specified range.
29
+ */
30
+ export function getRealFrameRange(durationInFrames, frameRange) {
31
+ if (frameRange === null) {
32
+ return [0, durationInFrames - 1];
33
+ }
34
+ return frameRange;
35
+ }
36
+ /**
37
+ * Validate that a frame range is within bounds and well-formed.
38
+ */
39
+ export function validateFrameRange(range, durationInFrames) {
40
+ const [start, end] = range;
41
+ if (!Number.isInteger(start) || !Number.isInteger(end)) {
42
+ throw new Error(`Frame range values must be integers. Got [${start}, ${end}]`);
43
+ }
44
+ if (start < 0) {
45
+ throw new Error(`Frame range start must be >= 0. Got ${start}`);
46
+ }
47
+ if (end < start) {
48
+ throw new Error(`Frame range end (${end}) must be >= start (${start})`);
49
+ }
50
+ if (end >= durationInFrames) {
51
+ throw new Error(`Frame range end (${end}) exceeds duration (${durationInFrames} frames). ` +
52
+ `Max allowed end frame is ${durationInFrames - 1}`);
53
+ }
54
+ }
55
+ //# sourceMappingURL=frame-range.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-range.js","sourceRoot":"","sources":["../src/frame-range.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAoB,EACpB,gBAAwB;IAExB,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAE7B,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAS,EAAE,EAAE,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAO,EAAE,EAAE,CAAC,CAAC;QAElC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,yBAAyB,KAAK,iCAAiC,CAAC,CAAC;QACnF,CAAC;QAED,kBAAkB,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,gBAAgB,CAAC,CAAC;QACnD,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACtB,CAAC;IAED,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC1C,IAAI,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,yBAAyB,KAAK,qCAAqC,CAAC,CAAC;IACvF,CAAC;IAED,kBAAkB,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACjE,OAAO,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,gBAAwB,EACxB,UAA6B;IAE7B,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACxB,OAAO,CAAC,CAAC,EAAE,gBAAgB,GAAG,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAiB,EACjB,gBAAwB;IAExB,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;IAE3B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,6CAA6C,KAAK,KAAK,GAAG,GAAG,CAAC,CAAC;IACjF,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,uCAAuC,KAAK,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,IAAI,GAAG,GAAG,KAAK,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CACb,oBAAoB,GAAG,uBAAuB,KAAK,GAAG,CACvD,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,IAAI,gBAAgB,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb,oBAAoB,GAAG,uBAAuB,gBAAgB,YAAY;YACxE,4BAA4B,gBAAgB,GAAG,CAAC,EAAE,CACrD,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ export type { RenderConfig, RenderProgress, Codec, ImageFormat, FrameRange, StitchOptions, AudioMuxOptions, BundleResult, } from "./types.js";
2
+ export { launchBrowser, closeBrowser, createPage } from "./browser.js";
3
+ export { bundle } from "./bundler.js";
4
+ export { seekToFrame, loadBundle } from "./seek-to-frame.js";
5
+ export { captureFrame } from "./capture-frame.js";
6
+ export { parseFrameRange, getRealFrameRange, validateFrameRange } from "./frame-range.js";
7
+ export { stitchFrames, getContainerExt } from "./stitch-frames.js";
8
+ export { stitchAudio } from "./stitch-audio.js";
9
+ export { parallelRender } from "./parallel-render.js";
10
+ export { renderComposition } from "./render-composition.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,YAAY,EACZ,cAAc,EACd,KAAK,EACL,WAAW,EACX,UAAU,EACV,aAAa,EACb,eAAe,EACf,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAGvE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAGtC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAG7D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG1F,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ // Browser lifecycle
2
+ export { launchBrowser, closeBrowser, createPage } from "./browser.js";
3
+ // Bundler
4
+ export { bundle } from "./bundler.js";
5
+ // Frame navigation
6
+ export { seekToFrame, loadBundle } from "./seek-to-frame.js";
7
+ // Frame capture
8
+ export { captureFrame } from "./capture-frame.js";
9
+ // Frame range utilities
10
+ export { parseFrameRange, getRealFrameRange, validateFrameRange } from "./frame-range.js";
11
+ // FFmpeg stitching
12
+ export { stitchFrames, getContainerExt } from "./stitch-frames.js";
13
+ export { stitchAudio } from "./stitch-audio.js";
14
+ // Parallel rendering
15
+ export { parallelRender } from "./parallel-render.js";
16
+ // Full render orchestration
17
+ export { renderComposition } from "./render-composition.js";
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,oBAAoB;AACpB,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEvE,UAAU;AACV,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,mBAAmB;AACnB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE7D,gBAAgB;AAChB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,wBAAwB;AACxB,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE1F,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,qBAAqB;AACrB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,4BAA4B;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { Browser } from "playwright";
2
+ import type { FrameRange, ImageFormat, RenderProgress } from "./types.js";
3
+ interface ParallelRenderOptions {
4
+ browser: Browser;
5
+ bundleUrl: string;
6
+ compositionId: string;
7
+ frameRange: FrameRange;
8
+ outputDir: string;
9
+ width: number;
10
+ height: number;
11
+ concurrency: number;
12
+ imageFormat: ImageFormat;
13
+ quality?: number;
14
+ onProgress?: (progress: RenderProgress) => void;
15
+ }
16
+ /**
17
+ * Distribute frame rendering across multiple browser tabs in parallel.
18
+ * Opens N pages, each rendering a chunk of the frame range.
19
+ */
20
+ export declare function parallelRender(options: ParallelRenderOptions): Promise<void>;
21
+ export {};
22
+ //# sourceMappingURL=parallel-render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parallel-render.d.ts","sourceRoot":"","sources":["../src/parallel-render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAK1E,UAAU,qBAAqB;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;CACjD;AAoDD;;;GAGG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAyDf"}
@@ -0,0 +1,69 @@
1
+ import { createPage } from "./browser.js";
2
+ import { seekToFrame, loadBundle } from "./seek-to-frame.js";
3
+ import { captureFrame } from "./capture-frame.js";
4
+ /**
5
+ * Split a frame range into N roughly-equal chunks.
6
+ */
7
+ function splitFrameRange(range, chunks) {
8
+ const [start, end] = range;
9
+ const totalFrames = end - start + 1;
10
+ const framesPerChunk = Math.ceil(totalFrames / chunks);
11
+ const ranges = [];
12
+ for (let i = 0; i < chunks; i++) {
13
+ const chunkStart = start + i * framesPerChunk;
14
+ const chunkEnd = Math.min(chunkStart + framesPerChunk - 1, end);
15
+ if (chunkStart > end)
16
+ break;
17
+ ranges.push([chunkStart, chunkEnd]);
18
+ }
19
+ return ranges;
20
+ }
21
+ /**
22
+ * Render a chunk of frames using a single browser page.
23
+ */
24
+ async function renderChunk(browser, bundleUrl, compositionId, frameRange, outputDir, width, height, imageFormat, quality, onFrameRendered) {
25
+ const page = await createPage(browser, width, height);
26
+ try {
27
+ await loadBundle(page, bundleUrl);
28
+ const [start, end] = frameRange;
29
+ for (let frame = start; frame <= end; frame++) {
30
+ await seekToFrame(page, frame, compositionId);
31
+ await captureFrame(page, outputDir, frame, { imageFormat, quality });
32
+ onFrameRendered();
33
+ }
34
+ }
35
+ finally {
36
+ await page.close();
37
+ }
38
+ }
39
+ /**
40
+ * Distribute frame rendering across multiple browser tabs in parallel.
41
+ * Opens N pages, each rendering a chunk of the frame range.
42
+ */
43
+ export async function parallelRender(options) {
44
+ const { browser, bundleUrl, compositionId, frameRange, outputDir, width, height, concurrency, imageFormat, quality, onProgress, } = options;
45
+ const [start, end] = frameRange;
46
+ const totalFrames = end - start + 1;
47
+ const effectiveConcurrency = Math.min(concurrency, totalFrames);
48
+ const chunks = splitFrameRange(frameRange, effectiveConcurrency);
49
+ let framesRendered = 0;
50
+ const startTime = Date.now();
51
+ const onFrameRendered = () => {
52
+ framesRendered++;
53
+ if (onProgress) {
54
+ const elapsed = Date.now() - startTime;
55
+ const msPerFrame = elapsed / framesRendered;
56
+ const remaining = totalFrames - framesRendered;
57
+ const etaMs = remaining > 0 ? msPerFrame * remaining : 0;
58
+ onProgress({
59
+ framesRendered,
60
+ totalFrames,
61
+ percent: framesRendered / totalFrames,
62
+ etaMs: framesRendered > 0 ? etaMs : null,
63
+ });
64
+ }
65
+ };
66
+ // Render all chunks in parallel
67
+ await Promise.all(chunks.map((chunk) => renderChunk(browser, bundleUrl, compositionId, chunk, outputDir, width, height, imageFormat, quality, onFrameRendered)));
68
+ }
69
+ //# sourceMappingURL=parallel-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parallel-render.js","sourceRoot":"","sources":["../src/parallel-render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { RenderConfig } from "./types.js";
2
+ interface CompositionInfo {
3
+ width: number;
4
+ height: number;
5
+ fps: number;
6
+ durationInFrames: number;
7
+ }
8
+ /**
9
+ * Orchestrate a full composition render:
10
+ * 1. Bundle the entry point
11
+ * 2. Launch headless browser
12
+ * 3. Render frames in parallel across browser tabs
13
+ * 4. Stitch frames into video with FFmpeg
14
+ * 5. Mux audio if present
15
+ * 6. Cleanup temp files
16
+ */
17
+ export declare function renderComposition(config: RenderConfig, compositionInfo: CompositionInfo): Promise<string>;
18
+ export {};
19
+ //# sourceMappingURL=render-composition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-composition.d.ts","sourceRoot":"","sources":["../src/render-composition.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,YAAY,CAAC;AAQ/D,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,YAAY,EACpB,eAAe,EAAE,eAAe,GAC/B,OAAO,CAAC,MAAM,CAAC,CAgHjB"}
@@ -0,0 +1,104 @@
1
+ import { mkdtemp, rm, mkdir } from "node:fs/promises";
2
+ import { join, dirname } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ import { availableParallelism } from "node:os";
5
+ import { bundle } from "./bundler.js";
6
+ import { launchBrowser, closeBrowser } from "./browser.js";
7
+ import { getRealFrameRange } from "./frame-range.js";
8
+ import { parallelRender } from "./parallel-render.js";
9
+ import { stitchFrames } from "./stitch-frames.js";
10
+ import { stitchAudio } from "./stitch-audio.js";
11
+ /**
12
+ * Orchestrate a full composition render:
13
+ * 1. Bundle the entry point
14
+ * 2. Launch headless browser
15
+ * 3. Render frames in parallel across browser tabs
16
+ * 4. Stitch frames into video with FFmpeg
17
+ * 5. Mux audio if present
18
+ * 6. Cleanup temp files
19
+ */
20
+ export async function renderComposition(config, compositionInfo) {
21
+ const { entry, compositionId, outputPath, codec, imageFormat, quality, frameRange: userFrameRange, concurrency, pixelFormat, onProgress, } = config;
22
+ const fps = config.fps ?? compositionInfo.fps;
23
+ const { width, height, durationInFrames } = compositionInfo;
24
+ const frameRange = getRealFrameRange(durationInFrames, userFrameRange);
25
+ const totalFrames = frameRange[1] - frameRange[0] + 1;
26
+ // Create temp directories
27
+ const framesDir = await mkdtemp(join(tmpdir(), "vibeo-frames-"));
28
+ // Ensure output directory exists
29
+ await mkdir(dirname(outputPath), { recursive: true });
30
+ let bundleResult = null;
31
+ try {
32
+ // Step 1: Bundle
33
+ const reportStage = (stage) => {
34
+ if (onProgress) {
35
+ onProgress({
36
+ framesRendered: 0,
37
+ totalFrames,
38
+ percent: 0,
39
+ etaMs: null,
40
+ });
41
+ }
42
+ };
43
+ reportStage("bundling");
44
+ bundleResult = await bundle(entry);
45
+ // Step 2: Launch browser
46
+ reportStage("launching browser");
47
+ const browser = await launchBrowser();
48
+ // Step 3: Render frames in parallel
49
+ const effectiveConcurrency = concurrency > 0 ? concurrency : Math.max(1, Math.floor(availableParallelism() / 2));
50
+ await parallelRender({
51
+ browser,
52
+ bundleUrl: bundleResult.url,
53
+ compositionId,
54
+ frameRange,
55
+ outputDir: framesDir,
56
+ width,
57
+ height,
58
+ concurrency: effectiveConcurrency,
59
+ imageFormat,
60
+ quality,
61
+ onProgress,
62
+ });
63
+ // Step 4: Stitch frames into video
64
+ const videoPath = join(framesDir, `stitched.${codec === "vp9" ? "webm" : codec === "prores" ? "mov" : "mp4"}`);
65
+ await stitchFrames({
66
+ framesDir,
67
+ outputPath: videoPath,
68
+ fps,
69
+ codec,
70
+ imageFormat,
71
+ pixelFormat,
72
+ quality,
73
+ width,
74
+ height,
75
+ });
76
+ // Step 5: Mux audio (if audio assets exist)
77
+ // For now, just copy the video to the output path if no audio
78
+ // Audio assets would be collected from the composition at render time
79
+ const finalPath = await stitchAudio({
80
+ videoPath,
81
+ audioPaths: [], // Audio collection will be implemented per-composition
82
+ outputPath,
83
+ });
84
+ // Report completion
85
+ if (onProgress) {
86
+ onProgress({
87
+ framesRendered: totalFrames,
88
+ totalFrames,
89
+ percent: 1,
90
+ etaMs: 0,
91
+ });
92
+ }
93
+ return finalPath;
94
+ }
95
+ finally {
96
+ // Step 6: Cleanup
97
+ await closeBrowser();
98
+ if (bundleResult) {
99
+ await bundleResult.cleanup();
100
+ }
101
+ await rm(framesDir, { recursive: true, force: true }).catch(() => { });
102
+ }
103
+ }
104
+ //# sourceMappingURL=render-composition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-composition.js","sourceRoot":"","sources":["../src/render-composition.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Page } from "playwright";
2
+ /**
3
+ * Navigate the headless browser page to render a specific frame.
4
+ *
5
+ * 1. Call window.vibeo_setFrame(frame, compositionId) to update the React tree.
6
+ * 2. Wait for React render to complete (poll for window.vibeo_ready flag).
7
+ * 3. Wait for all fonts to load.
8
+ * 4. Wait for any pending delayRender handles to resolve.
9
+ */
10
+ export declare function seekToFrame(page: Page, frame: number, compositionId: string): Promise<void>;
11
+ /**
12
+ * Load the bundled app in the browser page.
13
+ */
14
+ export declare function loadBundle(page: Page, url: string): Promise<void>;
15
+ //# sourceMappingURL=seek-to-frame.d.ts.map