@koda-sl/baker-cli 0.97.0 → 0.98.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/README.md +12 -0
- package/canvas/tiktok-captions-composition/index.html +7 -2
- package/canvas/video-overlay-composition/index.html +18 -25
- package/dist/{chunk-RCPMJKI7.js → chunk-3JVYU72O.js} +130 -24
- package/dist/chunk-3JVYU72O.js.map +1 -0
- package/dist/cli.js +524 -203
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +7 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-RCPMJKI7.js.map +0 -1
package/README.md
CHANGED
|
@@ -2436,14 +2436,26 @@ baker canvas validate my-canvas.json
|
|
|
2436
2436
|
|
|
2437
2437
|
# 2. Run (executes nodes, writes files to ./canvas/<run_id>/)
|
|
2438
2438
|
baker canvas run my-canvas.json
|
|
2439
|
+
# Only nodes reachable from the declared output execute (orphaned nodes are skipped,
|
|
2440
|
+
# not billed). Add --keep-runs N to prune old r_* run dirs, keeping the N newest.
|
|
2439
2441
|
|
|
2440
2442
|
# 3. Inspect a finished run (per-node timing, file list, optional video thumbs)
|
|
2441
2443
|
baker canvas inspect <run_id>
|
|
2442
2444
|
|
|
2443
2445
|
# 4. Discover what's available (all node types, all models, all compositions)
|
|
2444
2446
|
baker canvas catalog | jq
|
|
2447
|
+
|
|
2448
|
+
# 5. Safely edit a node's prompt without hand-jq (UTF-8 safe — use --text-file for
|
|
2449
|
+
# multi-line / accented copy; the edit is re-validated before it is written back)
|
|
2450
|
+
baker canvas set-prompt my-canvas.json s0_start --text "Close-up, flushed and breathless"
|
|
2451
|
+
baker canvas set-prompt my-canvas.json s0_start --text-file ./hook-frame.txt
|
|
2445
2452
|
```
|
|
2446
2453
|
|
|
2454
|
+
> Scaffolded canvases use **workspace-relative** composition/asset paths and are validated
|
|
2455
|
+
> against the engine's hyperframe pre-render gate at `validate` time, so a broken overlay
|
|
2456
|
+
> (or a moved/renamed workspace) is caught before any billed clip. The scaffold also adds
|
|
2457
|
+
> `canvas/` + `.context/` to your `.gitignore` on first run.
|
|
2458
|
+
|
|
2447
2459
|
A re-run with no changes hits the cache for every node — total runtime drops to sub-second.
|
|
2448
2460
|
|
|
2449
2461
|
---
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
data-width="1080"
|
|
66
66
|
data-height="1920"
|
|
67
67
|
>
|
|
68
|
-
<video id="bg" src="background.mp4" muted></video>
|
|
68
|
+
<video id="bg" class="clip" src="background.mp4" data-start="0" data-duration="{{duration}}" muted></video>
|
|
69
69
|
<div class="caption-container" id="captions"></div>
|
|
70
70
|
</div>
|
|
71
71
|
|
|
@@ -84,8 +84,13 @@
|
|
|
84
84
|
|
|
85
85
|
groups.forEach((group, gi) => {
|
|
86
86
|
const el = document.createElement('div');
|
|
87
|
-
|
|
87
|
+
// `clip` marks this as a hyperframes-timed element (the GSAP timeline
|
|
88
|
+
// below drives its actual show/hide); without it the pre-render gate
|
|
89
|
+
// flags every group as visible for the whole composition.
|
|
90
|
+
el.className = 'caption-group clip';
|
|
88
91
|
el.id = 'cg_' + gi;
|
|
92
|
+
el.setAttribute('data-start', String(group[0].start));
|
|
93
|
+
el.setAttribute('data-duration', String((group[group.length - 1].end - group[0].start) || 0.5));
|
|
89
94
|
|
|
90
95
|
group.forEach((word, wi) => {
|
|
91
96
|
const span = document.createElement('span');
|
|
@@ -70,10 +70,8 @@
|
|
|
70
70
|
bottom, but if you hand-place, respect the bands. */
|
|
71
71
|
|
|
72
72
|
/* COLLISION-SAFE TOP BAR — wrap co-timed top items (logo + trust badge + …) in ONE
|
|
73
|
-
.top-bar so they pack side-by-side
|
|
74
|
-
|
|
75
|
-
<img class="brandmark" src="logo.svg"><span class="trust">★ 4,5/5</span>
|
|
76
|
-
</div> */
|
|
73
|
+
.top-bar (a div.ov.clip.top-bar with data-start/data-dur) so they pack side-by-side
|
|
74
|
+
with a gap and never overlap; put the brandmark img + a trust span inside it. */
|
|
77
75
|
.top-bar {
|
|
78
76
|
position: absolute; top: 70px; left: 56px; right: 56px;
|
|
79
77
|
display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
|
|
@@ -102,30 +100,25 @@
|
|
|
102
100
|
</head>
|
|
103
101
|
<body>
|
|
104
102
|
<div id="root" data-composition-id="main" data-start="0" data-duration="{{duration}}" data-width="1080" data-height="1920">
|
|
105
|
-
<video id="bg" src="background.mp4" muted></video>
|
|
103
|
+
<video id="bg" class="clip" src="background.mp4" data-start="0" data-duration="{{duration}}" muted></video>
|
|
106
104
|
<div id="overlay-root">
|
|
107
105
|
<!--OVERLAYS-->
|
|
108
106
|
|
|
109
|
-
<!--
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
the deconstruct produced. Floor: `npx hyperframes add caption-highlight`
|
|
125
|
-
(TikTok karaoke); escalate by register (caption-kinetic-slam, etc.).
|
|
126
|
-
Wiring + the {id,text,start,end} sync contract:
|
|
127
|
-
references/hyperframes/captions-and-audio.md.
|
|
128
|
-
──────────────────────────────────────────────────────────────────── -->
|
|
107
|
+
<!-- NESTED BLOCK SLOT (optional): for an ANIMATED graphic (lower-third,
|
|
108
|
+
social/review card, stat counter, chart, logo sting), pull a ready-made
|
|
109
|
+
block instead of a static image. From this dir run `npx hyperframes
|
|
110
|
+
catalog --type block` to find one, `npx hyperframes add yt-lower-third`
|
|
111
|
+
to write it into compositions/, then add a nested clip element — a div
|
|
112
|
+
with class clip plus data-composition-src pointing at the block file,
|
|
113
|
+
data-start, data-duration and data-track-index. See
|
|
114
|
+
references/hyperframes/blueprints-and-transitions.md. -->
|
|
115
|
+
|
|
116
|
+
<!-- CAPTION TRACK SLOT (optional, recommended for sound-off feeds): the feed
|
|
117
|
+
plays muted, so burn in captions from the word-level transcript the
|
|
118
|
+
deconstruct produced. Floor: `npx hyperframes add caption-highlight`
|
|
119
|
+
(TikTok karaoke); escalate by register (caption-kinetic-slam, etc.). The
|
|
120
|
+
scaffold already wires a default caption track as a separate composition;
|
|
121
|
+
see references/hyperframes/captions-and-audio.md for the sync contract. -->
|
|
129
122
|
</div>
|
|
130
123
|
</div>
|
|
131
124
|
|
|
@@ -1937,6 +1937,25 @@ function topologicalLayers(graph) {
|
|
|
1937
1937
|
}
|
|
1938
1938
|
return layers;
|
|
1939
1939
|
}
|
|
1940
|
+
function reachableNodes(graph, roots) {
|
|
1941
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
1942
|
+
const stack = [...roots];
|
|
1943
|
+
while (stack.length > 0) {
|
|
1944
|
+
const id = stack.pop();
|
|
1945
|
+
if (id === void 0 || reachable.has(id) || !graph.has(id)) continue;
|
|
1946
|
+
reachable.add(id);
|
|
1947
|
+
for (const dep of graph.get(id) ?? []) stack.push(dep);
|
|
1948
|
+
}
|
|
1949
|
+
return reachable;
|
|
1950
|
+
}
|
|
1951
|
+
function subgraph(graph, keep) {
|
|
1952
|
+
const out = /* @__PURE__ */ new Map();
|
|
1953
|
+
for (const [id, deps] of graph) {
|
|
1954
|
+
if (!keep.has(id)) continue;
|
|
1955
|
+
out.set(id, new Set([...deps].filter((d) => keep.has(d))));
|
|
1956
|
+
}
|
|
1957
|
+
return out;
|
|
1958
|
+
}
|
|
1940
1959
|
function cloneGraph(graph) {
|
|
1941
1960
|
const out = /* @__PURE__ */ new Map();
|
|
1942
1961
|
for (const [k, deps] of graph) out.set(k, new Set(deps));
|
|
@@ -2369,6 +2388,18 @@ function estimateCredits(ctx) {
|
|
|
2369
2388
|
}
|
|
2370
2389
|
return total;
|
|
2371
2390
|
}
|
|
2391
|
+
function nativeAudioReachesMix(ctx, scene) {
|
|
2392
|
+
const wanted = [
|
|
2393
|
+
`$ref:s${scene}_voextract.audio`,
|
|
2394
|
+
`$ref:s${scene}_clip.audio`,
|
|
2395
|
+
new RegExp(`\\$ref:s${scene}_r\\d+_(voextract|clip)\\.audio`)
|
|
2396
|
+
];
|
|
2397
|
+
return ctx.canvas.nodes.some((n) => {
|
|
2398
|
+
if (n.type !== "audio_timeline") return false;
|
|
2399
|
+
const blob = JSON.stringify(n.inputs ?? {});
|
|
2400
|
+
return wanted.some((w) => typeof w === "string" ? blob.includes(w) : w.test(blob));
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2372
2403
|
function talkingSceneSatisfied(ctx, entry, scene) {
|
|
2373
2404
|
const nodes = ctx.canvas.nodes;
|
|
2374
2405
|
if (typeof entry === "object" && "voice_convert_node" in entry) {
|
|
@@ -2377,7 +2408,7 @@ function talkingSceneSatisfied(ctx, entry, scene) {
|
|
|
2377
2408
|
(n) => nativeClipRe.test(n.id) && n.type === "video_generate" && n.params?.generate_audio === true
|
|
2378
2409
|
);
|
|
2379
2410
|
const converted = nodes.some((n) => n.id === entry.voice_convert_node && n.type === "audio_voice_convert");
|
|
2380
|
-
return clipNativeAudio && converted;
|
|
2411
|
+
return clipNativeAudio && (converted || nativeAudioReachesMix(ctx, scene));
|
|
2381
2412
|
}
|
|
2382
2413
|
if (typeof entry === "object") {
|
|
2383
2414
|
return nodes.some((n) => n.id === entry.lipsync_node && n.type === "video_lipsync");
|
|
@@ -2418,7 +2449,7 @@ function checkVideoInvariants(ctx) {
|
|
|
2418
2449
|
ctx.issues.push({
|
|
2419
2450
|
path: "metadata.video.talking_scenes",
|
|
2420
2451
|
code: STAGE_CODES.LIPSYNC_MISSING,
|
|
2421
|
-
message: `scene ${scene} is a single-on-camera-speaker talking head but its clip lacks native audio (generate_audio)
|
|
2452
|
+
message: `scene ${scene} is a single-on-camera-speaker talking head but its clip lacks native audio (generate_audio: true) \u2014 the lips would drift. Generate the clip with native audio, then route it to the mix via either the audio_voice_convert node (one brand voice) or directly (s${scene}_voextract \u2192 audio_mix)`
|
|
2422
2453
|
});
|
|
2423
2454
|
}
|
|
2424
2455
|
}
|
|
@@ -2596,7 +2627,7 @@ var Engine = class {
|
|
|
2596
2627
|
return { run_id: runId, output, outputs_by_node: outputs, stats, outputs_dir: writer.runDir };
|
|
2597
2628
|
}
|
|
2598
2629
|
async runLayers(canvas, outputs, runId, writer, opts, counters, nodeRuns) {
|
|
2599
|
-
const layers = topologicalLayers(buildGraph(canvas));
|
|
2630
|
+
const layers = topologicalLayers(this.pruneToOutput(canvas, buildGraph(canvas)));
|
|
2600
2631
|
for (const layer of layers) {
|
|
2601
2632
|
const tasks = layer.map(
|
|
2602
2633
|
(nodeId) => this.executeOne(canvas, nodeId, outputs, runId, writer, opts).then((r) => {
|
|
@@ -2617,6 +2648,21 @@ var Engine = class {
|
|
|
2617
2648
|
await Promise.all(tasks);
|
|
2618
2649
|
}
|
|
2619
2650
|
}
|
|
2651
|
+
/**
|
|
2652
|
+
* Dead-node elimination: when the canvas declares an `output`, execute only the
|
|
2653
|
+
* nodes that output transitively depends on. Orphaned nodes (left by an edit or
|
|
2654
|
+
* a rewire) otherwise still run, bill, and abort the run on their own failure.
|
|
2655
|
+
* No declared output ⇒ intent is ambiguous, so run everything (back-compat).
|
|
2656
|
+
*/
|
|
2657
|
+
pruneToOutput(canvas, graph) {
|
|
2658
|
+
const rootId = canvas.output?.node;
|
|
2659
|
+
if (!rootId || !graph.has(rootId)) return graph;
|
|
2660
|
+
const keep = reachableNodes(graph, [rootId]);
|
|
2661
|
+
if (keep.size === graph.size) return graph;
|
|
2662
|
+
const dropped = [...graph.keys()].filter((id) => !keep.has(id));
|
|
2663
|
+
this.log(`[prune ] skipping ${dropped.length} node(s) unreachable from output: ${dropped.join(", ")}`);
|
|
2664
|
+
return subgraph(graph, keep);
|
|
2665
|
+
}
|
|
2620
2666
|
async writeFinal(writer, output) {
|
|
2621
2667
|
if (output === void 0) return;
|
|
2622
2668
|
if (isAssetRefArray(output)) {
|
|
@@ -2984,6 +3030,27 @@ var FontRef = BaseAssetRef.extend({
|
|
|
2984
3030
|
});
|
|
2985
3031
|
var AssetRef = z4.discriminatedUnion("kind", [ImageRef, VideoRef, AudioRef, JsonRef, TextRef, FontRef]);
|
|
2986
3032
|
|
|
3033
|
+
// src/engine/nodes/remote/upload.ts
|
|
3034
|
+
async function presignAndPut(args) {
|
|
3035
|
+
const { putUrl, publicUrl } = await args.ctx.client.presignAssetUpload(args.sha256, args.mime, args.ctx.signal);
|
|
3036
|
+
const putRes = await fetch(putUrl, {
|
|
3037
|
+
method: "PUT",
|
|
3038
|
+
body: new Uint8Array(args.bytes),
|
|
3039
|
+
headers: { "Content-Type": args.mime },
|
|
3040
|
+
signal: args.ctx.signal
|
|
3041
|
+
});
|
|
3042
|
+
if (!putRes.ok) {
|
|
3043
|
+
throw new Error(`upload: presigned PUT failed ${putRes.status} ${putRes.statusText}`);
|
|
3044
|
+
}
|
|
3045
|
+
return publicUrl;
|
|
3046
|
+
}
|
|
3047
|
+
async function ensureUploaded(ref, ctx) {
|
|
3048
|
+
if (ref.url) return ref;
|
|
3049
|
+
const bytes = await ctx.assets.readBytes(ref.sha256, ref.mime);
|
|
3050
|
+
const url = await presignAndPut({ bytes, sha256: ref.sha256, mime: ref.mime, ctx });
|
|
3051
|
+
return { ...ref, url };
|
|
3052
|
+
}
|
|
3053
|
+
|
|
2987
3054
|
// src/engine/nodes/remote/delegate.ts
|
|
2988
3055
|
function delegated(spec) {
|
|
2989
3056
|
return {
|
|
@@ -3003,7 +3070,7 @@ function delegated(spec) {
|
|
|
3003
3070
|
}
|
|
3004
3071
|
async function callBackendExec(args) {
|
|
3005
3072
|
try {
|
|
3006
|
-
const serialized = serializeInputs(args.inputs);
|
|
3073
|
+
const serialized = await serializeInputs(args.inputs, args.ctx);
|
|
3007
3074
|
const idempotencyKey = args.ctx.cacheKey ? `${args.ctx.canvasRunId}:${args.ctx.cacheKey}` : void 0;
|
|
3008
3075
|
const res = await args.ctx.client.exec(
|
|
3009
3076
|
{
|
|
@@ -3020,19 +3087,19 @@ async function callBackendExec(args) {
|
|
|
3020
3087
|
throw mapClientError(args.ctx, e);
|
|
3021
3088
|
}
|
|
3022
3089
|
}
|
|
3023
|
-
function serializeInputs(inputs) {
|
|
3090
|
+
async function serializeInputs(inputs, ctx) {
|
|
3024
3091
|
if (!inputs || typeof inputs !== "object") return {};
|
|
3025
3092
|
const out = {};
|
|
3026
3093
|
for (const [k, v] of Object.entries(inputs)) {
|
|
3027
|
-
out[k] = stripLocalRefs(v);
|
|
3094
|
+
out[k] = await stripLocalRefs(v, ctx);
|
|
3028
3095
|
}
|
|
3029
3096
|
return out;
|
|
3030
3097
|
}
|
|
3031
|
-
function stripLocalRefs(value) {
|
|
3032
|
-
if (Array.isArray(value)) return value.map(stripLocalRefs);
|
|
3098
|
+
async function stripLocalRefs(value, ctx) {
|
|
3099
|
+
if (Array.isArray(value)) return Promise.all(value.map((v) => stripLocalRefs(v, ctx)));
|
|
3033
3100
|
if (value && typeof value === "object") {
|
|
3034
3101
|
if ("kind" in value && "sha256" in value && "mime" in value) {
|
|
3035
|
-
const ref = value;
|
|
3102
|
+
const ref = await ensureUploaded(value, ctx);
|
|
3036
3103
|
return {
|
|
3037
3104
|
kind: ref.kind,
|
|
3038
3105
|
url: ref.url,
|
|
@@ -3042,7 +3109,7 @@ function stripLocalRefs(value) {
|
|
|
3042
3109
|
};
|
|
3043
3110
|
}
|
|
3044
3111
|
const out = {};
|
|
3045
|
-
for (const [k, v] of Object.entries(value)) out[k] = stripLocalRefs(v);
|
|
3112
|
+
for (const [k, v] of Object.entries(value)) out[k] = await stripLocalRefs(v, ctx);
|
|
3046
3113
|
return out;
|
|
3047
3114
|
}
|
|
3048
3115
|
return value;
|
|
@@ -3555,16 +3622,7 @@ function buildYtDlpMetadata(sourceUrl, info) {
|
|
|
3555
3622
|
}
|
|
3556
3623
|
async function uploadAndIngest(args) {
|
|
3557
3624
|
const sha = sha256Hex(args.bytes);
|
|
3558
|
-
const
|
|
3559
|
-
const putRes = await fetch(putUrl, {
|
|
3560
|
-
method: "PUT",
|
|
3561
|
-
body: new Uint8Array(args.bytes),
|
|
3562
|
-
headers: { "Content-Type": args.mime },
|
|
3563
|
-
signal: args.ctx.signal
|
|
3564
|
-
});
|
|
3565
|
-
if (!putRes.ok) {
|
|
3566
|
-
throw new Error(`ingest: presigned PUT failed ${putRes.status} ${putRes.statusText}`);
|
|
3567
|
-
}
|
|
3625
|
+
const publicUrl = await presignAndPut({ bytes: args.bytes, sha256: sha, mime: args.mime, ctx: args.ctx });
|
|
3568
3626
|
const localRef = await args.ctx.assets.ingestBytes({
|
|
3569
3627
|
bytes: args.bytes,
|
|
3570
3628
|
kind: args.kind,
|
|
@@ -4412,7 +4470,15 @@ import { readFile as readFile7 } from "fs/promises";
|
|
|
4412
4470
|
import path9 from "path";
|
|
4413
4471
|
import { promisify as promisify3 } from "util";
|
|
4414
4472
|
var execFileAsync = promisify3(execFile3);
|
|
4415
|
-
var NEVER_BLOCK = [
|
|
4473
|
+
var NEVER_BLOCK = [
|
|
4474
|
+
/contrast/i,
|
|
4475
|
+
/\bwcag\b/i,
|
|
4476
|
+
/missing_local_asset/i,
|
|
4477
|
+
/font[_-]?family/i,
|
|
4478
|
+
/font[_-]?face/i,
|
|
4479
|
+
/transcript[_-]?not[_-]?inline/i,
|
|
4480
|
+
/text[_-]?occluded/i
|
|
4481
|
+
];
|
|
4416
4482
|
var UNAVAILABLE = /unknown command|command not found|not found|Did you mean|Unknown argument|ENOENT/i;
|
|
4417
4483
|
function isAdvisory(code, message) {
|
|
4418
4484
|
const hay = `${code} ${message}`;
|
|
@@ -4474,6 +4540,31 @@ function classifyCheckOutput(lintRaw, inspectRaw) {
|
|
|
4474
4540
|
function buildLintArgs(dir) {
|
|
4475
4541
|
return ["hyperframes", "lint", dir, "--json"];
|
|
4476
4542
|
}
|
|
4543
|
+
function buildLintArgsNoInstall(dir) {
|
|
4544
|
+
return ["--no-install", "hyperframes", "lint", dir, "--json"];
|
|
4545
|
+
}
|
|
4546
|
+
var VALIDATE_STRUCTURAL_CODES = /media_missing_data_start|timed_element_missing_clip_class/i;
|
|
4547
|
+
function filterStructuralFindings(findings) {
|
|
4548
|
+
return findings.filter((f) => VALIDATE_STRUCTURAL_CODES.test(f.code));
|
|
4549
|
+
}
|
|
4550
|
+
var hyperframesAvailable = null;
|
|
4551
|
+
async function isHyperframesAvailable() {
|
|
4552
|
+
if (hyperframesAvailable !== null) return hyperframesAvailable;
|
|
4553
|
+
try {
|
|
4554
|
+
await execFileAsync("hyperframes", ["--version"], { timeout: 1e4 });
|
|
4555
|
+
hyperframesAvailable = true;
|
|
4556
|
+
} catch {
|
|
4557
|
+
hyperframesAvailable = false;
|
|
4558
|
+
}
|
|
4559
|
+
return hyperframesAvailable;
|
|
4560
|
+
}
|
|
4561
|
+
async function lintStructuralIssues(dir, timeoutMs) {
|
|
4562
|
+
if (!await isHyperframesAvailable()) return [];
|
|
4563
|
+
const lintRaw = await runOne(buildLintArgsNoInstall(dir), timeoutMs);
|
|
4564
|
+
if (lintRaw === null) return [];
|
|
4565
|
+
const findings = classifyLint(parseCheckJson(lintRaw)).map((f) => ({ code: f.code, message: f.message }));
|
|
4566
|
+
return filterStructuralFindings(findings);
|
|
4567
|
+
}
|
|
4477
4568
|
function buildInspectArgs(dir, samples) {
|
|
4478
4569
|
return ["hyperframes", "inspect", dir, "--json", "--samples", String(samples)];
|
|
4479
4570
|
}
|
|
@@ -4681,7 +4772,7 @@ var hyperframeRenderNode = defineNode({
|
|
|
4681
4772
|
params: HyperframeParams,
|
|
4682
4773
|
outputs: HyperframeOutputs,
|
|
4683
4774
|
cost: () => ({ credits: 0, seconds_estimate: 30 }),
|
|
4684
|
-
validateExtra: ({ rawParams }) =>
|
|
4775
|
+
validateExtra: ({ rawParams }) => validateComposition(rawParams),
|
|
4685
4776
|
cacheKeyExtras: async ({ params }) => {
|
|
4686
4777
|
const out = {};
|
|
4687
4778
|
const composition = params.composition;
|
|
@@ -4734,6 +4825,21 @@ async function resolveCompositionDir(composition) {
|
|
|
4734
4825
|
if (s.isDirectory()) return compositionPath;
|
|
4735
4826
|
return path11.dirname(compositionPath);
|
|
4736
4827
|
}
|
|
4828
|
+
async function validateComposition(rawParams) {
|
|
4829
|
+
const issues = await validateCompositionParams(rawParams);
|
|
4830
|
+
const composition = rawParams?.composition;
|
|
4831
|
+
if (typeof composition !== "string") return issues;
|
|
4832
|
+
const dir = await resolveCompositionDir(composition).catch(() => null);
|
|
4833
|
+
if (!dir) return issues;
|
|
4834
|
+
const structural = await lintStructuralIssues(dir, 2e4);
|
|
4835
|
+
for (const s of structural) {
|
|
4836
|
+
issues.push({
|
|
4837
|
+
path: "params.composition",
|
|
4838
|
+
message: `pre-render gate would fail: [${s.code}] ${s.message}`
|
|
4839
|
+
});
|
|
4840
|
+
}
|
|
4841
|
+
return issues;
|
|
4842
|
+
}
|
|
4737
4843
|
async function validateCompositionParams(rawParams) {
|
|
4738
4844
|
const issues = [];
|
|
4739
4845
|
if (!rawParams || typeof rawParams !== "object") return issues;
|
|
@@ -5735,7 +5841,7 @@ import { z as z29 } from "zod";
|
|
|
5735
5841
|
var VIDEO_DECONSTRUCT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
5736
5842
|
var videoDeconstructNode = delegated({
|
|
5737
5843
|
id: "video_deconstruct",
|
|
5738
|
-
version: "1.
|
|
5844
|
+
version: "1.4.0",
|
|
5739
5845
|
category: "video",
|
|
5740
5846
|
summary: 'Deconstruct a video into a replication-grade blueprint: scene boundaries, the real start/end frame of every scene (extracted from the video as images), and an exhaustive JSON analysis \u2014 per-scene action detail, camera motion, generation-ready frame/motion prompts, overlay text with full typographic style, floating elements, deeply detailed cast (perceived demographics, ethnicity/skin-tone, styling, market-recasting notes), brand-identified logos (named by brand and what they signal, not by appearance, with on-screen timestamps), dialogue with voice descriptions, music spec, SFX list, plus a word-level transcript. `mode:"index"` is the cheap structure-first pass: scene boundaries + global blueprint only (one LLM call, no frames).',
|
|
5741
5847
|
when_to_use: 'Use to reverse-engineer a reference video (e.g. a competitor ad) so a new canvas can reproduce or remix it scene by scene. Agent loop: (1) optionally run `mode:"index"` to see the structure cheaply (scene count, boundaries, transcript) before planning; (2) run the full deconstruct; (3) read `analysis` and author the reproduction canvas. The blueprint maps 1:1 onto generation nodes: `analysis.scenes[i]` aligns positionally with `start_frames#i`/`end_frames#i`; per scene, `start_frame_prompt`/`end_frame_prompt` feed image_generate (overlay text is excluded from them by contract \u2014 recomposite it from `overlays`), `motion_prompt` + the two frames feed video_generate (first_frame/last_frame), `dialogue[].voice_description` casts tts/dialogue voices, `global.music.music_prompt` feeds music, `sfx[].sound_effect_prompt` feeds sound_effect, and `overlays`/`floating_elements` drive an ffmpeg/hyperframe overlay pass. Long videos (over ~8 min single-shot): run `mode:"index"` first, then several full nodes IN PARALLEL each with a `start_s`/`end_s` window (\u2264480s, snap edges to index scene boundaries), and merge by concatenating `analysis.scenes`; over-length errors include suggested windows. Inject fields into downstream prompts via `{{slot}}`. Pick `~google/gemini-pro-latest` for the densest extraction, `~google/gemini-flash-latest` for cheap/fast passes.',
|
|
@@ -6146,4 +6252,4 @@ export {
|
|
|
6146
6252
|
defaultRegistry,
|
|
6147
6253
|
createEngineFromEnv
|
|
6148
6254
|
};
|
|
6149
|
-
//# sourceMappingURL=chunk-
|
|
6255
|
+
//# sourceMappingURL=chunk-3JVYU72O.js.map
|