create-aura3d 1.1.2 → 1.3.1
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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
- package/templates/{cartoon-channel → animation-channel}/README.md +11 -11
- package/templates/{cartoon-studio → animation-channel}/index.html +1 -1
- package/templates/{cartoon-channel → animation-channel}/package.json +2 -2
- package/templates/{cartoon-channel → animation-channel}/src/episode.ts +22 -22
- package/templates/{cartoon-channel → animation-channel}/src/experimental/README.md +5 -5
- package/templates/{cartoon-channel → animation-channel}/src/experimental/concept-episode-2-5d.css +3 -3
- package/templates/{cartoon-channel → animation-channel}/src/experimental/concept-episode-2-5d.ts +7 -7
- package/templates/{cartoon-channel → animation-channel}/src/experimental/image-puppet-episode.css +4 -4
- package/templates/{cartoon-channel → animation-channel}/src/experimental/image-puppet-episode.ts +6 -6
- package/templates/{cartoon-channel → animation-channel}/src/experimental/puppet-episode-2d.css +3 -3
- package/templates/{cartoon-channel → animation-channel}/src/experimental/puppet-episode-2d.ts +7 -7
- package/templates/{cartoon-channel → animation-channel}/src/main.ts +32 -32
- package/templates/{cartoon-channel → animation-channel}/src/render-plan.ts +13 -13
- package/templates/{cartoon-channel → animation-channel}/src/sample-episode-visual.css +34 -34
- package/templates/{cartoon-channel → animation-channel}/src/sample-episode-visual.ts +20 -20
- package/templates/{cartoon-channel → animation-channel}/tests/concept-2-5d-animation.spec.ts +3 -3
- package/templates/{cartoon-channel → animation-channel}/tests/concept-2-5d.spec.ts +6 -6
- package/templates/{cartoon-channel → animation-channel}/tests/image-puppet-animation.spec.ts +3 -3
- package/templates/{cartoon-channel → animation-channel}/tests/image-puppet.spec.ts +5 -5
- package/templates/{cartoon-channel → animation-channel}/tests/puppet-2d-animation.spec.ts +3 -3
- package/templates/{cartoon-channel → animation-channel}/tests/puppet-2d.spec.ts +4 -4
- package/templates/{prompt-cartoon-channel → animation-channel}/tests/route-health.spec.ts +1 -1
- package/templates/{cartoon-channel → animation-channel}/tests/sample-episode-visual.spec.ts +5 -5
- package/templates/{cartoon-channel → animation-channel}/tests/storyboard-playback.spec.ts +7 -7
- package/templates/animation-studio/AGENTS.md +71 -0
- package/templates/{cartoon-studio → animation-studio}/ASSET-LICENSES.md +7 -7
- package/templates/{cartoon-studio → animation-studio}/README.md +6 -6
- package/templates/animation-studio/aura.assets.json +669 -0
- package/templates/animation-studio/dist/episodes/scene/episode-3d.webm +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/action.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/dialogue.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/final.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/first.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/mouth-closed.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/frames/mouth-open.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/render-live-summary.json +10284 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-1.png +0 -0
- package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-2.png +0 -0
- package/templates/animation-studio/dist/generated/second-scene.document.json +1210 -0
- package/templates/animation-studio/dist/generated/studio-edited.document.json +1196 -0
- package/templates/animation-studio/dist/scene/inshader-accept.document.json +1 -0
- package/templates/animation-studio/dist/scene/inshader-test.document.json +1 -0
- package/templates/animation-studio/dist/scene/integ.document.json +1 -0
- package/templates/animation-studio/dist/scene/working.document.json +723 -0
- package/templates/animation-studio/dist/scene/working.history.json +1 -0
- package/templates/{cartoon-studio → animation-studio}/live-route.html +1 -1
- package/templates/{cartoon-studio → animation-studio}/package-lock.json +2 -2
- package/templates/animation-studio/package.json +32 -0
- package/templates/animation-studio/public/aura-assets/cast-a.catalog.glb +0 -0
- package/templates/animation-studio/public/aura-assets/cast-b.catalog.glb +0 -0
- package/templates/animation-studio/public/aura-assets/luma.catalog.glb +0 -0
- package/templates/animation-studio/public/aura-assets/luma2.catalog.glb +0 -0
- package/templates/animation-studio/public/aura-assets/miko.catalog.glb +0 -0
- package/templates/{cartoon-studio → animation-studio}/public/aura-assets/moonGarden.gltf +1 -1
- package/templates/animation-studio/public/aura-assets/mushroom.catalog.glb +0 -0
- package/templates/animation-studio/public/aura-assets/rusty.catalog.glb +0 -0
- package/templates/animation-studio/public/cast-library/cast-library.json +71 -0
- package/templates/animation-studio/public/clip-library/gesture.json +173903 -0
- package/templates/animation-studio/public/clip-library/idle.json +23035 -0
- package/templates/animation-studio/public/clip-library/manifest.json +154 -0
- package/templates/animation-studio/public/clip-library/nod.json +173903 -0
- package/templates/animation-studio/public/clip-library/point.json +69879 -0
- package/templates/animation-studio/public/clip-library/react.json +66084 -0
- package/templates/animation-studio/public/clip-library/run.json +9648 -0
- package/templates/animation-studio/public/clip-library/talk.json +69879 -0
- package/templates/animation-studio/public/clip-library/walk.json +8283 -0
- package/templates/animation-studio/public/hifi-cast/BrainStem.glb +0 -0
- package/templates/animation-studio/public/hifi-cast/CesiumMan.glb +0 -0
- package/templates/animation-studio/public/hifi-cast/RiggedFigure.glb +0 -0
- package/templates/animation-studio/public/hifi-cast/kaykit/Barbarian.glb +0 -0
- package/templates/animation-studio/public/hifi-cast/kaykit/Knight.glb +0 -0
- package/templates/animation-studio/public/hifi-cast/kaykit/Mage.glb +0 -0
- package/templates/animation-studio/public/hifi-cast/kaykit/Rogue.glb +0 -0
- package/templates/animation-studio/public/hifi-cast/kaykit/Rogue_Hooded.glb +0 -0
- package/templates/animation-studio/public/hifi-cast/khronos-report.json +295 -0
- package/templates/animation-studio/public/hifi-cast/quaternius-report.json +449 -0
- package/templates/animation-studio/public/hifi-cast/rpm/cesiumman.glb +0 -0
- package/templates/animation-studio/public/hifi-cast/rpm/fox.glb +0 -0
- package/templates/animation-studio/public/hifi-cast/rpm/riggedfigure.glb +0 -0
- package/templates/animation-studio/public/hifi-cast/rpm-report.json +301 -0
- package/templates/animation-studio/scripts/anim.ts +103 -0
- package/templates/animation-studio/scripts/animation-scene.ts +457 -0
- package/templates/animation-studio/scripts/asset-motion-probe.ts +450 -0
- package/templates/animation-studio/scripts/asset-render-probe.ts +152 -0
- package/templates/animation-studio/scripts/author-office-scene.ts +65 -0
- package/templates/animation-studio/scripts/build-cast-library.ts +97 -0
- package/templates/animation-studio/scripts/build-characters.ts +1543 -0
- package/templates/animation-studio/scripts/build-clip-library.ts +972 -0
- package/templates/animation-studio/scripts/clip-decision-cli.ts +51 -0
- package/templates/animation-studio/scripts/determinism-check.ts +48 -0
- package/templates/animation-studio/scripts/generate-scene.ts +95 -0
- package/templates/animation-studio/scripts/hifi-cast-validate.ts +572 -0
- package/templates/animation-studio/scripts/motion-evidence-cli.ts +150 -0
- package/templates/animation-studio/scripts/motion-evidence.ts +362 -0
- package/templates/animation-studio/scripts/preview-server.ts +52 -0
- package/templates/animation-studio/scripts/regen-manifest.ts +156 -0
- package/templates/animation-studio/scripts/render-core.ts +360 -0
- package/templates/animation-studio/scripts/render-live.ts +813 -0
- package/templates/animation-studio/scripts/render-parallel.ts +70 -0
- package/templates/animation-studio/scripts/render-server.ts +119 -0
- package/templates/animation-studio/scripts/resolve-asset.ts +669 -0
- package/templates/animation-studio/scripts/skeleton-overlay-cli.ts +82 -0
- package/templates/animation-studio/scripts/skeleton-overlay.ts +293 -0
- package/templates/animation-studio/scripts/studio-tools-demo.ts +45 -0
- package/templates/animation-studio/scripts/texture-bake.ts +297 -0
- package/templates/{cartoon-studio → animation-studio}/scripts/validate-characters.ts +0 -0
- package/templates/animation-studio/scripts/verify-extracted-overlay-cli.ts +264 -0
- package/templates/animation-studio/src/animation-episode-validator.ts +97 -0
- package/templates/animation-studio/src/animation-performance.ts +556 -0
- package/templates/animation-studio/src/aura-assets.ts +71 -0
- package/templates/animation-studio/src/character.ts +40 -0
- package/templates/animation-studio/src/director/compile-episode-document.ts +45 -0
- package/templates/animation-studio/src/director/director-heuristics.ts +697 -0
- package/templates/animation-studio/src/director/prompt-to-scene.ts +369 -0
- package/templates/animation-studio/src/empty-document.ts +77 -0
- package/templates/animation-studio/src/episode-document.ts +442 -0
- package/templates/animation-studio/src/examples/moon-garden.example.ts +183 -0
- package/templates/animation-studio/src/fidelity.ts +129 -0
- package/templates/animation-studio/src/graph.ts +14 -0
- package/templates/animation-studio/src/procedural-hdri.ts +243 -0
- package/templates/animation-studio/src/profile.ts +97 -0
- package/templates/animation-studio/src/render-live-route.ts +42 -0
- package/templates/animation-studio/src/render-modes.ts +137 -0
- package/templates/animation-studio/src/scene-player.ts +867 -0
- package/templates/animation-studio/src/set-templates.ts +361 -0
- package/templates/animation-studio/src/studio/episode-document-store.ts +72 -0
- package/templates/animation-studio/src/studio/scene-tool-registry.ts +86 -0
- package/templates/animation-studio/src/studio/scene-tools.ts +169 -0
- package/templates/animation-studio/tests/reports/animation-studio/hifi-cast-validation.json +938 -0
- package/templates/animation-studio/tsconfig.json +26 -0
- package/templates/{cartoon-studio → animation-studio}/vite.config.ts +6 -1
- package/templates/character-controller/README.md +33 -0
- package/templates/character-controller/index.html +19 -0
- package/templates/character-controller/package.json +23 -0
- package/templates/character-controller/playwright.config.ts +14 -0
- package/templates/character-controller/src/controller.ts +44 -0
- package/templates/character-controller/src/main.ts +62 -0
- package/templates/character-controller/tests/route-health.spec.ts +26 -0
- package/templates/character-controller/tests/screenshot.spec.ts +9 -0
- package/templates/character-controller/tsconfig.json +15 -0
- package/templates/cinematic-scene/package.json +1 -1
- package/templates/episode-builder/README.md +4 -4
- package/templates/episode-builder/package.json +1 -1
- package/templates/episode-builder/src/aura-assets.ts +1 -1
- package/templates/episode-builder/src/builder.ts +4 -4
- package/templates/episode-builder/src/episode.ts +21 -21
- package/templates/episode-builder/src/main.ts +20 -20
- package/templates/episode-builder/src/render-plan.ts +13 -13
- package/templates/episode-builder/tests/storyboard-playback.spec.ts +7 -7
- package/templates/fighting-game/package.json +1 -1
- package/templates/mini-game/package.json +1 -1
- package/templates/product-viewer/package.json +1 -1
- package/templates/{prompt-cartoon-channel → prompt-animation-channel}/README.md +5 -5
- package/templates/{cartoon-channel → prompt-animation-channel}/index.html +1 -1
- package/templates/{prompt-cartoon-channel → prompt-animation-channel}/package.json +2 -2
- package/templates/{prompt-cartoon-channel → prompt-animation-channel}/src/aura-assets.ts +1 -1
- package/templates/{prompt-cartoon-channel → prompt-animation-channel}/src/episode.ts +22 -22
- package/templates/{prompt-cartoon-channel → prompt-animation-channel}/src/main.ts +28 -28
- package/templates/{prompt-cartoon-channel → prompt-animation-channel}/src/render-plan.ts +13 -13
- package/templates/{cartoon-channel → prompt-animation-channel}/tests/route-health.spec.ts +1 -1
- package/templates/{prompt-cartoon-channel → prompt-animation-channel}/tests/storyboard-playback.spec.ts +8 -8
- package/templates/cartoon-studio/aura.assets.json +0 -400
- package/templates/cartoon-studio/package.json +0 -33
- package/templates/cartoon-studio/public/aura-assets/luma.authored.glb +0 -0
- package/templates/cartoon-studio/public/aura-assets/miko.authored.glb +0 -0
- package/templates/cartoon-studio/scripts/build-characters.ts +0 -738
- package/templates/cartoon-studio/scripts/build-dialogue-audio.ts +0 -262
- package/templates/cartoon-studio/scripts/episode.ts +0 -323
- package/templates/cartoon-studio/scripts/render-live.ts +0 -873
- package/templates/cartoon-studio/src/aura-assets.ts +0 -64
- package/templates/cartoon-studio/src/characters.ts +0 -65
- package/templates/cartoon-studio/src/episode-renderer.ts +0 -605
- package/templates/cartoon-studio/src/episode.ts +0 -306
- package/templates/cartoon-studio/src/main.ts +0 -400
- package/templates/cartoon-studio/src/render-live-route.ts +0 -913
- package/templates/cartoon-studio/src/render-plan.ts +0 -567
- package/templates/cartoon-studio/src/review.ts +0 -99
- package/templates/cartoon-studio/src/studio.ts +0 -101
- package/templates/cartoon-studio/test-results/.last-run.json +0 -4
- package/templates/cartoon-studio/tests/episode-render.spec.ts +0 -44
- package/templates/cartoon-studio/tests/motion-quality.spec.ts +0 -45
- package/templates/cartoon-studio/tests/route-health.spec.ts +0 -7
- package/templates/cartoon-studio/tests/storyboard-playback.spec.ts +0 -99
- package/templates/cartoon-studio/tests/visual-review.spec.ts +0 -21
- package/templates/cartoon-studio/tsconfig.json +0 -14
- package/templates/prompt-cartoon-channel/index.html +0 -13
- package/templates/prompt-cartoon-channel/src/sets.ts +0 -14
- /package/templates/{cartoon-channel → animation-channel}/aura.assets.json +0 -0
- /package/templates/{cartoon-channel → animation-channel}/playwright.config.ts +0 -0
- /package/templates/{cartoon-channel → animation-channel}/public/aura-assets/luma.047f5e5f.glb +0 -0
- /package/templates/{cartoon-channel → animation-channel}/public/aura-assets/luma.thumb.svg +0 -0
- /package/templates/{cartoon-channel → animation-channel}/public/aura-assets/miko.047f5e5f.glb +0 -0
- /package/templates/{cartoon-channel → animation-channel}/public/aura-assets/miko.thumb.svg +0 -0
- /package/templates/{cartoon-channel → animation-channel}/public/aura-assets/moon-garden-feature-frame.png +0 -0
- /package/templates/{cartoon-channel → animation-channel}/src/aura-assets.ts +0 -0
- /package/templates/{cartoon-channel → animation-channel}/src/characters.ts +0 -0
- /package/templates/{cartoon-channel → animation-channel}/src/contract.ts +0 -0
- /package/templates/{cartoon-channel → animation-channel}/src/sets.ts +0 -0
- /package/templates/{cartoon-channel → animation-channel}/test-results/.last-run.json +0 -0
- /package/templates/{cartoon-channel → animation-channel}/tests/reports/prompt-animation/cartoon-sample-episode.png +0 -0
- /package/templates/{cartoon-channel → animation-channel}/tsconfig.json +0 -0
- /package/templates/{cartoon-studio → animation-studio}/playwright.config.ts +0 -0
- /package/templates/{cartoon-studio → animation-studio}/public/aura-assets/luma.thumb.svg +0 -0
- /package/templates/{cartoon-studio → animation-studio}/public/aura-assets/miko.thumb.svg +0 -0
- /package/templates/{cartoon-studio → animation-studio}/src/contract.ts +0 -0
- /package/templates/{prompt-cartoon-channel → prompt-animation-channel}/playwright.config.ts +0 -0
- /package/templates/{prompt-cartoon-channel → prompt-animation-channel}/src/characters.ts +0 -0
- /package/templates/{prompt-cartoon-channel → prompt-animation-channel}/src/contract.ts +0 -0
- /package/templates/{cartoon-studio → prompt-animation-channel}/src/sets.ts +0 -0
- /package/templates/{prompt-cartoon-channel → prompt-animation-channel}/tsconfig.json +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render-parallel.ts — render a full episode FASTER by splitting the timeline across N
|
|
3
|
+
* render-live processes (each uses its own core / browser page), then concatenating the
|
|
4
|
+
* partial videos (#8). Process-level parallelism so the CPU toon pass is parallel too.
|
|
5
|
+
*
|
|
6
|
+
* AURA_DOCUMENT=<doc.json> AURA_PARALLEL=4 \
|
|
7
|
+
* pnpm exec tsx --tsconfig tsconfig.base.json packages/create-aura3d/templates/animation-studio/scripts/render-parallel.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { createRequire } from "node:module";
|
|
13
|
+
import { dirname, resolve } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const REPO = resolve(__dirname, "..", "..", "..", "..", "..");
|
|
18
|
+
const TEMPLATE = resolve(__dirname, "..");
|
|
19
|
+
|
|
20
|
+
const DOC = process.env.AURA_DOCUMENT ? resolve(process.env.AURA_DOCUMENT) : resolve(TEMPLATE, "dist/scene/working.document.json");
|
|
21
|
+
const N = Math.max(1, Math.min(8, Number(process.env.AURA_PARALLEL ?? "4")));
|
|
22
|
+
const FINAL_OUT = resolve(TEMPLATE, process.env.AURA_OUTPUT_DIR ?? "dist/episodes/scene");
|
|
23
|
+
|
|
24
|
+
function ffmpegBin(): string {
|
|
25
|
+
try {
|
|
26
|
+
const req = createRequire(import.meta.url);
|
|
27
|
+
const inst = req("@ffmpeg-installer/ffmpeg") as { path?: string };
|
|
28
|
+
if (inst.path && existsSync(inst.path)) return inst.path;
|
|
29
|
+
} catch { /* PATH */ }
|
|
30
|
+
return "ffmpeg";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function renderSlice(index: number, start: number, end: number): Promise<number> {
|
|
34
|
+
const outDir = `dist/episodes/parallel/slice-${index}`;
|
|
35
|
+
return new Promise((res) => {
|
|
36
|
+
const child = spawn(
|
|
37
|
+
"pnpm",
|
|
38
|
+
["exec", "tsx", "--tsconfig", "tsconfig.base.json", "packages/create-aura3d/templates/animation-studio/scripts/render-live.ts"],
|
|
39
|
+
{ cwd: REPO, stdio: "ignore", env: { ...process.env, AURA_DOCUMENT: DOC, AURA_OUTPUT_DIR: outDir, AURA_PREVIEW_RANGE: `${start}-${end}` } }
|
|
40
|
+
);
|
|
41
|
+
child.on("exit", (code) => res(code ?? 1));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function main(): Promise<void> {
|
|
46
|
+
if (!existsSync(DOC)) { console.error(`document not found: ${DOC}`); process.exitCode = 1; return; }
|
|
47
|
+
const duration = (JSON.parse(readFileSync(DOC, "utf8")) as { duration: number }).duration;
|
|
48
|
+
const sliceDur = duration / N;
|
|
49
|
+
const ranges = Array.from({ length: N }, (_, i) => ({ i, start: +(i * sliceDur).toFixed(3), end: +((i + 1) * sliceDur).toFixed(3) }));
|
|
50
|
+
|
|
51
|
+
console.log(`rendering ${duration}s across ${N} parallel processes ...`);
|
|
52
|
+
const t0 = Date.now();
|
|
53
|
+
const codes = await Promise.all(ranges.map((r) => renderSlice(r.i, r.start, r.end)));
|
|
54
|
+
if (codes.some((c) => c !== 0)) { console.error(`a slice failed: ${codes.join(",")}`); process.exitCode = 1; return; }
|
|
55
|
+
console.log(`all slices rendered in ${((Date.now() - t0) / 1000).toFixed(1)}s; concatenating ...`);
|
|
56
|
+
|
|
57
|
+
// Concatenate the partial webms in order (ffmpeg concat demuxer).
|
|
58
|
+
mkdirSync(FINAL_OUT, { recursive: true });
|
|
59
|
+
const listPath = resolve(FINAL_OUT, "concat.txt");
|
|
60
|
+
writeFileSync(
|
|
61
|
+
listPath,
|
|
62
|
+
ranges.map((r) => `file '${resolve(TEMPLATE, `dist/episodes/parallel/slice-${r.i}/episode-3d.webm`)}'`).join("\n") + "\n"
|
|
63
|
+
);
|
|
64
|
+
const cat = spawnSync(ffmpegBin(), ["-y", "-f", "concat", "-safe", "0", "-i", listPath, "-c", "copy", resolve(FINAL_OUT, "episode-3d.webm")], { stdio: "ignore" });
|
|
65
|
+
rmSync(listPath, { force: true });
|
|
66
|
+
if (cat.status !== 0) { console.error("concat failed"); process.exitCode = 1; return; }
|
|
67
|
+
console.log(`done → ${resolve(FINAL_OUT, "episode-3d.webm")} (total ${((Date.now() - t0) / 1000).toFixed(1)}s)`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
void main();
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render-server.ts — WARM persistent render server (#7). It pays the expensive startup
|
|
3
|
+
* costs ONCE — Vite dev server + Chromium/swiftshader launch — and then keeps them alive,
|
|
4
|
+
* serving many render requests over HTTP. Each request opens a fresh page in the SAME warm
|
|
5
|
+
* browser context (so GLB fetches are HTTP-cache hits) and renders via the shared
|
|
6
|
+
* render-core pipeline. This turns the studio iteration loop from "~25s cold every edit"
|
|
7
|
+
* into "page-load + capture only".
|
|
8
|
+
*
|
|
9
|
+
* Start: pnpm exec tsx --tsconfig tsconfig.base.json packages/create-aura3d/templates/animation-studio/scripts/render-server.ts
|
|
10
|
+
* (prints PORT=<n>; honours AURA_RENDER_PORT to pin it)
|
|
11
|
+
* Render: curl -s localhost:<port>/render -d '{"document":"dist/scene/working.document.json","range":"0-6","lowFi":true,"outputDir":"dist/episodes/preview"}'
|
|
12
|
+
* Health: curl localhost:<port>/health Stop: curl localhost:<port>/shutdown
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createServer as createHttpServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
import { dirname, resolve } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { chromium, type Browser } from "@playwright/test";
|
|
20
|
+
import type { ViteDevServer } from "vite";
|
|
21
|
+
import type { EpisodeDocument } from "../src/episode-document.js";
|
|
22
|
+
import { profileFor, renderRange, startWarmVite } from "./render-core.js";
|
|
23
|
+
|
|
24
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const TEMPLATE_ROOT = resolve(__dirname, "..");
|
|
26
|
+
const MONOREPO_ROOT = resolve(TEMPLATE_ROOT, "../../../..");
|
|
27
|
+
const PORT = Number(process.env.AURA_RENDER_PORT ?? "0");
|
|
28
|
+
|
|
29
|
+
interface RenderRequest {
|
|
30
|
+
readonly document: string; // path to a document JSON (relative to template root or absolute)
|
|
31
|
+
readonly range?: string; // "start-end" seconds; default whole episode
|
|
32
|
+
readonly lowFi?: boolean;
|
|
33
|
+
readonly outputDir?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function readBody(req: IncomingMessage): Promise<string> {
|
|
37
|
+
const chunks: Buffer[] = [];
|
|
38
|
+
for await (const chunk of req) chunks.push(chunk as Buffer);
|
|
39
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function main(): Promise<void> {
|
|
43
|
+
console.log("warm render server: launching Vite + Chromium (one-time startup) ...");
|
|
44
|
+
const vite: ViteDevServer = await startWarmVite(TEMPLATE_ROOT, MONOREPO_ROOT);
|
|
45
|
+
const address = vite.httpServer?.address();
|
|
46
|
+
if (!address || typeof address === "string") throw new Error("Vite did not expose a numeric port.");
|
|
47
|
+
const routeUrl = `http://127.0.0.1:${address.port}/live-route.html`;
|
|
48
|
+
const browser: Browser = await chromium.launch({
|
|
49
|
+
args: ["--use-gl=angle", "--use-angle=swiftshader", "--enable-unsafe-swdecoder", "--ignore-gpu-blocklist"]
|
|
50
|
+
});
|
|
51
|
+
const context = await browser.newContext(); // shared context → warm HTTP cache for GLBs
|
|
52
|
+
|
|
53
|
+
let busy = false;
|
|
54
|
+
async function handleRender(body: RenderRequest): Promise<unknown> {
|
|
55
|
+
const docPath = resolve(TEMPLATE_ROOT, body.document);
|
|
56
|
+
const document = JSON.parse(readFileSync(docPath, "utf8")) as EpisodeDocument;
|
|
57
|
+
const profile = profileFor(Boolean(body.lowFi));
|
|
58
|
+
const [a, b] = (body.range ?? `0-${document.duration}`).split("-").map(Number);
|
|
59
|
+
const outDir = resolve(TEMPLATE_ROOT, body.outputDir ?? "dist/episodes/warm-preview");
|
|
60
|
+
|
|
61
|
+
const page = await context.newPage();
|
|
62
|
+
try {
|
|
63
|
+
await page.addInitScript(() => { (window as unknown as { __AURA_LIVE_ROUTE_HEADLESS__: boolean }).__AURA_LIVE_ROUTE_HEADLESS__ = true; });
|
|
64
|
+
await page.addInitScript((d) => { (window as unknown as { __AURA_EPISODE_DOCUMENT__: unknown }).__AURA_EPISODE_DOCUMENT__ = d; }, document as unknown);
|
|
65
|
+
await page.setViewportSize({ width: profile.width + 40, height: profile.height + 40 });
|
|
66
|
+
await page.goto(routeUrl, { waitUntil: "load", timeout: 60_000 });
|
|
67
|
+
await page.waitForFunction(() => {
|
|
68
|
+
const w = window as unknown as { __AURA_LIVE_ROUTE_READY__?: unknown; __AURA_LIVE_ROUTE_ERROR__?: string };
|
|
69
|
+
if (w.__AURA_LIVE_ROUTE_ERROR__) throw new Error(`route error: ${w.__AURA_LIVE_ROUTE_ERROR__}`);
|
|
70
|
+
return Boolean(w.__AURA_LIVE_ROUTE_READY__);
|
|
71
|
+
}, { timeout: 60_000 });
|
|
72
|
+
const t0 = Date.now();
|
|
73
|
+
const result = await renderRange(page, { document, firstSec: a ?? 0, lastSec: b ?? document.duration, profile, outDir });
|
|
74
|
+
return { ...result, ms: Date.now() - t0, document: document.id };
|
|
75
|
+
} finally {
|
|
76
|
+
await page.close();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const http = createHttpServer((req: IncomingMessage, res: ServerResponse) => {
|
|
81
|
+
void (async () => {
|
|
82
|
+
const url = req.url ?? "/";
|
|
83
|
+
if (url === "/health") { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ ok: true, routeUrl })); return; }
|
|
84
|
+
if (url === "/shutdown") {
|
|
85
|
+
res.writeHead(200); res.end("shutting down");
|
|
86
|
+
await context.close(); await browser.close(); await vite.close();
|
|
87
|
+
http.close(() => process.exit(0));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (url === "/render" && req.method === "POST") {
|
|
91
|
+
if (busy) { res.writeHead(429, { "content-type": "application/json" }); res.end(JSON.stringify({ error: "render in progress" })); return; }
|
|
92
|
+
busy = true;
|
|
93
|
+
try {
|
|
94
|
+
const body = JSON.parse(await readBody(req)) as RenderRequest;
|
|
95
|
+
const out = await handleRender(body);
|
|
96
|
+
res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify(out));
|
|
97
|
+
} catch (err) {
|
|
98
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
99
|
+
res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
|
|
100
|
+
} finally { busy = false; }
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
res.writeHead(404); res.end("not found");
|
|
104
|
+
})();
|
|
105
|
+
});
|
|
106
|
+
http.listen(PORT, "127.0.0.1", () => {
|
|
107
|
+
const a = http.address();
|
|
108
|
+
const port = a && typeof a !== "string" ? a.port : PORT;
|
|
109
|
+
console.log(`PORT=${port}`);
|
|
110
|
+
console.log(`warm render server ready on http://127.0.0.1:${port} (route ${routeUrl})`);
|
|
111
|
+
console.log(` POST /render {"document":"dist/scene/working.document.json","range":"0-6","lowFi":true}`);
|
|
112
|
+
console.log(` GET /health GET /shutdown`);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
main().catch((error: unknown) => {
|
|
117
|
+
console.error("render-server failed:", error instanceof Error ? error.stack ?? error.message : error);
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
});
|