@koda-sl/baker-cli 0.123.0-dev.31b784126 → 0.123.0-dev.70bf43ce4
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 +16 -87
- package/dist/{chunk-Q3K5TXC6.js → chunk-VSVGPYJK.js} +355 -505
- package/dist/chunk-VSVGPYJK.js.map +1 -0
- package/dist/cli.js +1312 -2342
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +0 -39
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-Q3K5TXC6.js.map +0 -1
|
@@ -780,9 +780,7 @@ function failedJobError(error) {
|
|
|
780
780
|
retryable: error.retryable ?? false
|
|
781
781
|
});
|
|
782
782
|
}
|
|
783
|
-
|
|
784
|
-
return attempt < 15 ? 1e3 : 3e3;
|
|
785
|
-
}
|
|
783
|
+
var JOB_POLL_INTERVAL_MS = 3e3;
|
|
786
784
|
var JOB_POLL_MAX_MS = 20 * 60 * 1e3;
|
|
787
785
|
var BackendClient = class {
|
|
788
786
|
http;
|
|
@@ -799,7 +797,7 @@ var BackendClient = class {
|
|
|
799
797
|
async pollJob(jobId, signal) {
|
|
800
798
|
const deadline = Date.now() + JOB_POLL_MAX_MS;
|
|
801
799
|
const path16 = `/api/canvas/jobs/${encodeURIComponent(jobId)}`;
|
|
802
|
-
|
|
800
|
+
while (true) {
|
|
803
801
|
if (signal?.aborted) {
|
|
804
802
|
throw new BackendHttpError({ kind: "network", cause: signal.reason ?? new Error("aborted") });
|
|
805
803
|
}
|
|
@@ -809,7 +807,7 @@ var BackendClient = class {
|
|
|
809
807
|
if (Date.now() > deadline) {
|
|
810
808
|
throw new BackendHttpError({ kind: "timeout", message: `job ${jobId} did not finish in time` });
|
|
811
809
|
}
|
|
812
|
-
await sleep(
|
|
810
|
+
await sleep(JOB_POLL_INTERVAL_MS);
|
|
813
811
|
}
|
|
814
812
|
}
|
|
815
813
|
presignAssetUpload(sha256, mime, signal) {
|
|
@@ -840,14 +838,6 @@ var BackendClient = class {
|
|
|
840
838
|
async recordRun(payload, signal) {
|
|
841
839
|
await this.http.postJson("/api/canvas/runs", payload, signal);
|
|
842
840
|
}
|
|
843
|
-
/**
|
|
844
|
-
* Chat-scoped blueprint sync — POST /api/creatives/definition. Lets the
|
|
845
|
-
* dashboard draw a scaffolded creative's workflow graph BEFORE the first run.
|
|
846
|
-
* Additive on the backend (never archives siblings, never sets definitionPath).
|
|
847
|
-
*/
|
|
848
|
-
async syncCreativeDefinition(payload, signal) {
|
|
849
|
-
await this.http.postJson("/api/creatives/definition", payload, signal);
|
|
850
|
-
}
|
|
851
841
|
getArtifact(kind, name, version, signal) {
|
|
852
842
|
const path16 = version ? `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}` : `/api/canvas/artifacts/${encodeURIComponent(kind)}/${encodeURIComponent(name)}`;
|
|
853
843
|
return this.http.getJson(path16, signal);
|
|
@@ -996,10 +986,10 @@ var ELEVENLABS_OUTPUT_FORMATS = [
|
|
|
996
986
|
var ELEVENLABS_MAX_TEXT_CHARS = 45454;
|
|
997
987
|
var ELEVENLABS_MAX_MUSIC_LENGTH_MS = 454545;
|
|
998
988
|
var OPENROUTER_IMAGE_MIMES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
|
999
|
-
var
|
|
1000
|
-
var
|
|
989
|
+
var FAL_IMAGE_MIMES = ["image/png", "image/jpeg", "image/webp"];
|
|
990
|
+
var FAL_VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"];
|
|
1001
991
|
var DECONSTRUCT_VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"];
|
|
1002
|
-
var
|
|
992
|
+
var FAL_AUDIO_MIMES = ["audio/wav", "audio/mpeg", "audio/mp3"];
|
|
1003
993
|
var IMAGE_GENERATE_MODELS = [
|
|
1004
994
|
"openai/gpt-5.4-image-2",
|
|
1005
995
|
"google/gemini-3.5-flash",
|
|
@@ -1217,23 +1207,20 @@ var MODEL_REGISTRY = {
|
|
|
1217
1207
|
},
|
|
1218
1208
|
video_generate: {
|
|
1219
1209
|
"bytedance/seedance-2.0": {
|
|
1220
|
-
// Routed via
|
|
1221
|
-
//
|
|
1222
|
-
//
|
|
1223
|
-
// presenter face or routing real faces to Veo, not the provider choice.
|
|
1210
|
+
// Routed via fal.ai (not OpenRouter) because OpenRouter's Seedance
|
|
1211
|
+
// passthrough rejects photorealistic human reference frames via
|
|
1212
|
+
// ByteDance's "real person" safety filter.
|
|
1224
1213
|
label: "ByteDance Seedance 2.0",
|
|
1225
1214
|
inputs: [],
|
|
1226
|
-
optional_inputs: [{ kind: "image", mimes:
|
|
1215
|
+
optional_inputs: [{ kind: "image", mimes: FAL_IMAGE_MIMES }],
|
|
1227
1216
|
required: ["prompt"],
|
|
1228
1217
|
params: {
|
|
1229
|
-
|
|
1230
|
-
// it here so an over-length prompt fails validate (free) not the billed call.
|
|
1231
|
-
prompt: { kind: "string", maxLength: 4e3 },
|
|
1218
|
+
prompt: { kind: "string" },
|
|
1232
1219
|
aspect_ratio: {
|
|
1233
1220
|
kind: "string",
|
|
1234
1221
|
enum: ["1:1", "3:4", "9:16", "4:3", "16:9", "21:9", "9:21"]
|
|
1235
1222
|
},
|
|
1236
|
-
resolution: { kind: "string", enum: ["480p", "720p", "1080p"
|
|
1223
|
+
resolution: { kind: "string", enum: ["480p", "720p", "1080p"] },
|
|
1237
1224
|
duration: { kind: "number", enum: SEEDANCE_DURATIONS },
|
|
1238
1225
|
seed: { kind: "number" },
|
|
1239
1226
|
generate_audio: { kind: "boolean" }
|
|
@@ -1254,10 +1241,7 @@ var MODEL_REGISTRY = {
|
|
|
1254
1241
|
duration: { kind: "number", enum: [4, 6, 8] },
|
|
1255
1242
|
seed: { kind: "number" },
|
|
1256
1243
|
generate_audio: { kind: "boolean" },
|
|
1257
|
-
|
|
1258
|
-
// `allow_all` is text-to-video only. Allow both so an image-conditioned
|
|
1259
|
-
// Veo clip (the real-face fallback) validates.
|
|
1260
|
-
person_generation: { kind: "string", enum: ["allow_all", "allow_adult"] },
|
|
1244
|
+
person_generation: { kind: "string", enum: ["allow_all"] },
|
|
1261
1245
|
enhance_prompt: { kind: "boolean" },
|
|
1262
1246
|
conditioning_scale: { kind: "number" }
|
|
1263
1247
|
}
|
|
@@ -1308,8 +1292,8 @@ var MODEL_REGISTRY = {
|
|
|
1308
1292
|
"fal/veed-lipsync": {
|
|
1309
1293
|
label: "VEED Lipsync (fal.ai)",
|
|
1310
1294
|
inputs: [
|
|
1311
|
-
{ kind: "video", mimes:
|
|
1312
|
-
{ kind: "audio", mimes:
|
|
1295
|
+
{ kind: "video", mimes: FAL_VIDEO_MIMES },
|
|
1296
|
+
{ kind: "audio", mimes: FAL_AUDIO_MIMES }
|
|
1313
1297
|
],
|
|
1314
1298
|
required: [],
|
|
1315
1299
|
params: {}
|
|
@@ -1345,7 +1329,7 @@ var MODEL_REGISTRY = {
|
|
|
1345
1329
|
// TARGET voice, preserving timing/prosody. Used to normalize a talking-head
|
|
1346
1330
|
// clip's native (generator-chosen) voice into ONE consistent brand voice.
|
|
1347
1331
|
label: "ElevenLabs Voice Changer (multilingual STS v2)",
|
|
1348
|
-
inputs: [{ kind: "audio", mimes:
|
|
1332
|
+
inputs: [{ kind: "audio", mimes: FAL_AUDIO_MIMES }],
|
|
1349
1333
|
required: ["voice"],
|
|
1350
1334
|
params: {
|
|
1351
1335
|
voice: { kind: "string" },
|
|
@@ -1372,7 +1356,7 @@ var MODEL_REGISTRY = {
|
|
|
1372
1356
|
},
|
|
1373
1357
|
"elevenlabs/video-background-music-v1": {
|
|
1374
1358
|
label: "ElevenLabs Video Background Music v1",
|
|
1375
|
-
inputs: [{ kind: "video", mimes:
|
|
1359
|
+
inputs: [{ kind: "video", mimes: FAL_VIDEO_MIMES }],
|
|
1376
1360
|
required: [],
|
|
1377
1361
|
params: {
|
|
1378
1362
|
description: { kind: "string" },
|
|
@@ -1788,14 +1772,7 @@ var NodeDecl = z.object({
|
|
|
1788
1772
|
version: z.string().min(1).optional(),
|
|
1789
1773
|
inputs: z.record(z.string(), z.unknown()).optional(),
|
|
1790
1774
|
params: z.record(z.string(), z.unknown()).optional(),
|
|
1791
|
-
when: z.unknown().optional()
|
|
1792
|
-
// Regenerate knob. The engine is content-addressed: identical params + inputs
|
|
1793
|
-
// return the cached render, so re-running an unchanged node NEVER re-bills or
|
|
1794
|
-
// produces a new result. Bump this token (any string/number — a `2`, a `"v3"`,
|
|
1795
|
-
// a note) and re-run to force THIS node to render fresh; because its new output
|
|
1796
|
-
// changes downstream input hashes, everything depending on it regenerates too.
|
|
1797
|
-
// This is the declarative "change a value, re-run, get a new render" affordance.
|
|
1798
|
-
regenerate: z.union([z.string(), z.number()]).optional()
|
|
1775
|
+
when: z.unknown().optional()
|
|
1799
1776
|
}).strict();
|
|
1800
1777
|
var OutputRef = z.object({
|
|
1801
1778
|
node: z.string(),
|
|
@@ -3078,7 +3055,6 @@ var Engine = class {
|
|
|
3078
3055
|
const counters = { cachedNodes: 0, totalCredits: 0 };
|
|
3079
3056
|
const nodeRuns = [];
|
|
3080
3057
|
const graph = this.pruneToOutput(canvas, buildGraph(canvas));
|
|
3081
|
-
const needsBytes = computeNeedsLocalBytes(canvas, graph, this.registry);
|
|
3082
3058
|
this.emitProgress(opts, {
|
|
3083
3059
|
kind: "plan",
|
|
3084
3060
|
nodes: [...graph.entries()].map(([id, deps]) => {
|
|
@@ -3086,7 +3062,7 @@ var Engine = class {
|
|
|
3086
3062
|
return { node_id: id, node_type: node?.type ?? "unknown", deps: [...deps], params: node?.params };
|
|
3087
3063
|
})
|
|
3088
3064
|
});
|
|
3089
|
-
await this.runLayers(canvas, graph, outputs, runId, writer, opts, counters, nodeRuns
|
|
3065
|
+
await this.runLayers(canvas, graph, outputs, runId, writer, opts, counters, nodeRuns);
|
|
3090
3066
|
const output = pickFinalOutput(canvas, outputs);
|
|
3091
3067
|
const stats = {
|
|
3092
3068
|
total_nodes: canvas.nodes.length,
|
|
@@ -3111,13 +3087,13 @@ var Engine = class {
|
|
|
3111
3087
|
this.log(`outputs in: ${writer.runDir}`);
|
|
3112
3088
|
return { run_id: runId, output, outputs_by_node: outputs, stats, outputs_dir: writer.runDir, node_runs: nodeRuns };
|
|
3113
3089
|
}
|
|
3114
|
-
async runLayers(canvas, graph, outputs, runId, writer, opts, counters, nodeRuns
|
|
3090
|
+
async runLayers(canvas, graph, outputs, runId, writer, opts, counters, nodeRuns) {
|
|
3115
3091
|
const layers = topologicalLayers(graph);
|
|
3116
3092
|
const limit = resolveConcurrency(opts.concurrency);
|
|
3117
3093
|
for (const layer of layers) {
|
|
3118
3094
|
const settled = await mapWithConcurrency(layer, limit, (nodeId) => {
|
|
3119
3095
|
this.emitProgress(opts, { kind: "node_start", node_id: nodeId });
|
|
3120
|
-
return this.executeOne(canvas, nodeId, outputs, runId, writer, opts
|
|
3096
|
+
return this.executeOne(canvas, nodeId, outputs, runId, writer, opts).then((r) => {
|
|
3121
3097
|
if (r.cached) counters.cachedNodes++;
|
|
3122
3098
|
counters.totalCredits += r.credits;
|
|
3123
3099
|
const node = canvas.nodes.find((n) => n.id === nodeId);
|
|
@@ -3184,13 +3160,12 @@ var Engine = class {
|
|
|
3184
3160
|
}
|
|
3185
3161
|
await writer.writeManifest("_final", output);
|
|
3186
3162
|
}
|
|
3187
|
-
async executeOne(canvas, nodeId, outputs, runId, writer, opts
|
|
3163
|
+
async executeOne(canvas, nodeId, outputs, runId, writer, opts) {
|
|
3188
3164
|
const node = canvas.nodes.find((n) => n.id === nodeId);
|
|
3189
3165
|
if (!node) throw new Error(`executor: missing node ${nodeId}`);
|
|
3190
3166
|
const def = this.registry.get(node.type);
|
|
3191
3167
|
if (!def) throw new Error(`executor: missing registry entry for type ${node.type}`);
|
|
3192
|
-
const
|
|
3193
|
-
const prepared = await prepareForExecution(node, outputs, def, canvas.cache_salt, regenerateToken, this.assets);
|
|
3168
|
+
const prepared = await prepareForExecution(node, outputs, def, canvas.cache_salt, this.assets);
|
|
3194
3169
|
const policy = opts.cache_policy ?? "read_write";
|
|
3195
3170
|
if (policy !== "bypass") {
|
|
3196
3171
|
const cacheT0 = Date.now();
|
|
@@ -3208,14 +3183,12 @@ var Engine = class {
|
|
|
3208
3183
|
nodeId: node.id,
|
|
3209
3184
|
nodeType: node.type,
|
|
3210
3185
|
cacheKey: prepared.cacheKey,
|
|
3211
|
-
downloadOutputs,
|
|
3212
3186
|
client: this.client,
|
|
3213
3187
|
assets: this.assets,
|
|
3214
3188
|
log: this.log,
|
|
3215
3189
|
signal: opts.signal
|
|
3216
3190
|
};
|
|
3217
|
-
const
|
|
3218
|
-
const { parsedInputs, parsedParams } = parseNodeArgs(def, preparedForExec, node.id, node.type);
|
|
3191
|
+
const { parsedInputs, parsedParams } = parseNodeArgs(def, prepared, node.id, node.type);
|
|
3219
3192
|
const result = await invokeExecute(def, parsedInputs, parsedParams, ctx, node.id, node.type);
|
|
3220
3193
|
const elapsed = Date.now() - t0;
|
|
3221
3194
|
const credits = def.cost ? def.cost({ params: parsedParams }).credits : 0;
|
|
@@ -3257,42 +3230,8 @@ var Engine = class {
|
|
|
3257
3230
|
}
|
|
3258
3231
|
}
|
|
3259
3232
|
}
|
|
3260
|
-
/**
|
|
3261
|
-
* Download any URL-only asset ref reachable in a local node's inputs so the
|
|
3262
|
-
* bytes are on disk before the local runner stages them. Returns a copy —
|
|
3263
|
-
* refs are replaced, never mutated in place, so the producer's cached output
|
|
3264
|
-
* (shared object) keeps its URL-only shape.
|
|
3265
|
-
*/
|
|
3266
|
-
async materializeLocalInputs(inputs) {
|
|
3267
|
-
const fix = async (value) => {
|
|
3268
|
-
if (Array.isArray(value)) return Promise.all(value.map(fix));
|
|
3269
|
-
if (value && typeof value === "object") {
|
|
3270
|
-
const v = value;
|
|
3271
|
-
if (typeof v.kind === "string" && typeof v.url === "string" && typeof v.sha256 === "string" && typeof v.mime === "string" && typeof v.path !== "string") {
|
|
3272
|
-
this.log(`[warn ] materializing URL-only input on demand (${v.kind}/${v.mime}) \u2014 missed graph edge`);
|
|
3273
|
-
return this.assets.ingestRemote({
|
|
3274
|
-
kind: v.kind,
|
|
3275
|
-
url: v.url,
|
|
3276
|
-
sha256: v.sha256,
|
|
3277
|
-
mime: v.mime,
|
|
3278
|
-
metadata: v.metadata
|
|
3279
|
-
});
|
|
3280
|
-
}
|
|
3281
|
-
const out = {};
|
|
3282
|
-
for (const [k, val] of Object.entries(v)) out[k] = await fix(val);
|
|
3283
|
-
return out;
|
|
3284
|
-
}
|
|
3285
|
-
return value;
|
|
3286
|
-
};
|
|
3287
|
-
return await fix(inputs);
|
|
3288
|
-
}
|
|
3289
3233
|
};
|
|
3290
|
-
function
|
|
3291
|
-
if (forced?.has(node.id)) return `run:${runId}`;
|
|
3292
|
-
if (node.regenerate !== void 0) return `node:${String(node.regenerate)}`;
|
|
3293
|
-
return void 0;
|
|
3294
|
-
}
|
|
3295
|
-
async function prepareForExecution(node, outputs, def, cacheSalt, regenerateToken, assets) {
|
|
3234
|
+
async function prepareForExecution(node, outputs, def, cacheSalt, assets) {
|
|
3296
3235
|
const resolvedInputs = resolveRefs(node.inputs ?? {}, { outputs }) ?? {};
|
|
3297
3236
|
const resolvedParams = resolveRefs(node.params ?? {}, { outputs }) ?? {};
|
|
3298
3237
|
const slotValues = await hydrateTextSlots(resolvedInputs, assets, node.id, node.type);
|
|
@@ -3305,9 +3244,6 @@ async function prepareForExecution(node, outputs, def, cacheSalt, regenerateToke
|
|
|
3305
3244
|
throw new NodeExecutionError(node.id, node.type, { kind: "local", cause: e });
|
|
3306
3245
|
}
|
|
3307
3246
|
}
|
|
3308
|
-
if (regenerateToken !== void 0) {
|
|
3309
|
-
extras = { ...extras ?? {}, __regenerate__: regenerateToken };
|
|
3310
|
-
}
|
|
3311
3247
|
const cacheKey = computeCacheKey({
|
|
3312
3248
|
node_id: node.type,
|
|
3313
3249
|
node_version: def.version,
|
|
@@ -3336,9 +3272,6 @@ async function invokeExecute(def, parsedInputs, parsedParams, ctx, nodeId, nodeT
|
|
|
3336
3272
|
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e });
|
|
3337
3273
|
}
|
|
3338
3274
|
}
|
|
3339
|
-
function needsLocalMaterialization(def) {
|
|
3340
|
-
return def.location === "local" && !def.passthroughRefs;
|
|
3341
|
-
}
|
|
3342
3275
|
function pickFinalOutput(canvas, outputs) {
|
|
3343
3276
|
if (canvas.output) {
|
|
3344
3277
|
const node = outputs[canvas.output.node];
|
|
@@ -3349,16 +3282,6 @@ function pickFinalOutput(canvas, outputs) {
|
|
|
3349
3282
|
const lastOut = outputs[last.id];
|
|
3350
3283
|
return lastOut ? Object.values(lastOut)[0] : void 0;
|
|
3351
3284
|
}
|
|
3352
|
-
function computeNeedsLocalBytes(canvas, graph, registry) {
|
|
3353
|
-
const typeById = new Map(canvas.nodes.map((n) => [n.id, n.type]));
|
|
3354
|
-
const needs = /* @__PURE__ */ new Set();
|
|
3355
|
-
for (const [consumerId, deps] of graph) {
|
|
3356
|
-
const def = registry.get(typeById.get(consumerId) ?? "");
|
|
3357
|
-
if (def?.location !== "local" || def.passthroughRefs) continue;
|
|
3358
|
-
for (const dep of deps) needs.add(dep);
|
|
3359
|
-
}
|
|
3360
|
-
return needs;
|
|
3361
|
-
}
|
|
3362
3285
|
function buildGraph(canvas) {
|
|
3363
3286
|
const graph = /* @__PURE__ */ new Map();
|
|
3364
3287
|
for (const n of canvas.nodes) graph.set(n.id, /* @__PURE__ */ new Set());
|
|
@@ -3489,16 +3412,7 @@ async function hydrateSlotValue(value, assets, nodeId, nodeType) {
|
|
|
3489
3412
|
try {
|
|
3490
3413
|
bytes = await assets.readBytes(value.sha256, value.mime);
|
|
3491
3414
|
} catch (e) {
|
|
3492
|
-
|
|
3493
|
-
try {
|
|
3494
|
-
await assets.ingestRemote({ kind: value.kind, url: value.url, sha256: value.sha256, mime: value.mime });
|
|
3495
|
-
bytes = await assets.readBytes(value.sha256, value.mime);
|
|
3496
|
-
} catch (e2) {
|
|
3497
|
-
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e2 });
|
|
3498
|
-
}
|
|
3499
|
-
} else {
|
|
3500
|
-
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e });
|
|
3501
|
-
}
|
|
3415
|
+
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e });
|
|
3502
3416
|
}
|
|
3503
3417
|
if (bytes.length > MAX_INLINE_TEXT_BYTES) {
|
|
3504
3418
|
throw new NodeExecutionError(nodeId, nodeType, {
|
|
@@ -3633,9 +3547,7 @@ async function callBackendExec(args) {
|
|
|
3633
3547
|
nodeVersion: args.nodeVersion,
|
|
3634
3548
|
params: args.params,
|
|
3635
3549
|
inputs: serialized,
|
|
3636
|
-
idempotency_key: idempotencyKey
|
|
3637
|
-
canvas_run_id: args.ctx.canvasRunId,
|
|
3638
|
-
node_id: args.ctx.nodeId
|
|
3550
|
+
idempotency_key: idempotencyKey
|
|
3639
3551
|
},
|
|
3640
3552
|
args.ctx.signal
|
|
3641
3553
|
);
|
|
@@ -3687,9 +3599,6 @@ async function ingestValue(value, ctx, declaredKind) {
|
|
|
3687
3599
|
}
|
|
3688
3600
|
if (isRawAsset(value)) {
|
|
3689
3601
|
const kind = value.kind ?? declaredKind ?? "json";
|
|
3690
|
-
if (ctx.downloadOutputs === false) {
|
|
3691
|
-
return buildRef({ kind, sha: value.sha256, mime: value.mime, url: value.url, metadata: value.metadata });
|
|
3692
|
-
}
|
|
3693
3602
|
return ctx.assets.ingestRemote({
|
|
3694
3603
|
kind,
|
|
3695
3604
|
url: value.url,
|
|
@@ -3927,15 +3836,7 @@ var EXT_TO_MIME = {
|
|
|
3927
3836
|
jpeg: "image/jpeg",
|
|
3928
3837
|
webp: "image/webp",
|
|
3929
3838
|
gif: "image/gif",
|
|
3930
|
-
// Non-model-safe rasters `toModelSafeImage` transcodes to PNG at ingest — they
|
|
3931
|
-
// must resolve to an image mime here or the kind-check rejects the local file
|
|
3932
|
-
// before normalization ever runs.
|
|
3933
3839
|
avif: "image/avif",
|
|
3934
|
-
heic: "image/heic",
|
|
3935
|
-
heif: "image/heif",
|
|
3936
|
-
tif: "image/tiff",
|
|
3937
|
-
tiff: "image/tiff",
|
|
3938
|
-
bmp: "image/bmp",
|
|
3939
3840
|
mp4: "video/mp4",
|
|
3940
3841
|
webm: "video/webm",
|
|
3941
3842
|
mov: "video/quicktime",
|
|
@@ -4005,26 +3906,15 @@ async function toModelSafeImage(bytes) {
|
|
|
4005
3906
|
throw new Error(`bytes are not a decodable image (${e.message})`);
|
|
4006
3907
|
}
|
|
4007
3908
|
}
|
|
4008
|
-
function hasAscii(buf, offset, sig) {
|
|
4009
|
-
return buf.length >= offset + sig.length && buf.toString("ascii", offset, offset + sig.length) === sig;
|
|
4010
|
-
}
|
|
4011
|
-
var HEIC_BRANDS = /* @__PURE__ */ new Set(["heic", "heix", "heim", "heis", "hevc", "hevx", "mif1", "msf1", "heif"]);
|
|
4012
|
-
function sniffIsoBmff(buf) {
|
|
4013
|
-
if (!hasAscii(buf, 4, "ftyp")) return null;
|
|
4014
|
-
const brand = buf.subarray(8, 12).toString("ascii");
|
|
4015
|
-
if (brand === "avif" || brand === "avis") return "image/avif";
|
|
4016
|
-
if (HEIC_BRANDS.has(brand)) return "image/heic";
|
|
4017
|
-
return null;
|
|
4018
|
-
}
|
|
4019
3909
|
function sniffImageMime(buf) {
|
|
4020
3910
|
if (buf.length < 4) return null;
|
|
4021
|
-
if (buf[0] === 137 &&
|
|
3911
|
+
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
|
|
4022
3912
|
if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "image/jpeg";
|
|
4023
|
-
if (
|
|
4024
|
-
if (
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
return
|
|
3913
|
+
if (buf[0] === 71 && buf[1] === 73 && buf[2] === 70) return "image/gif";
|
|
3914
|
+
if (buf.length >= 12 && buf[0] === 82 && buf[1] === 73 && buf[2] === 70 && buf[3] === 70 && buf[8] === 87 && buf[9] === 69 && buf[10] === 66 && buf[11] === 80) {
|
|
3915
|
+
return "image/webp";
|
|
3916
|
+
}
|
|
3917
|
+
return null;
|
|
4028
3918
|
}
|
|
4029
3919
|
function findBoxPayload(buf, start, end, type) {
|
|
4030
3920
|
let offset = start;
|
|
@@ -4685,56 +4575,19 @@ var audioTimelineNode = defineNode({
|
|
|
4685
4575
|
}
|
|
4686
4576
|
});
|
|
4687
4577
|
|
|
4688
|
-
// src/engine/nodes/local/collect.ts
|
|
4689
|
-
import { z as z7 } from "zod";
|
|
4690
|
-
var collectNode = defineNode({
|
|
4691
|
-
id: "collect",
|
|
4692
|
-
version: "1.0.0",
|
|
4693
|
-
category: "data",
|
|
4694
|
-
location: "local",
|
|
4695
|
-
passthroughRefs: true,
|
|
4696
|
-
summary: "Gather images from multiple upstream nodes into one ordered array \u2014 the standard terminal for multi-variant canvases whose final output is several images.",
|
|
4697
|
-
when_to_use: "Point the canvas `output` at this node when several independent branches (e.g. one image_generate per scene/variant) must ALL be finals. Wire `inputs.images` as an array of refs like `$ref:gen_billboard_03.images#0` \u2014 each final inherits its producer node id as its label (or set `params.labels` to override), so variants stay identifiable in the dashboard and selection.",
|
|
4698
|
-
inputs: z7.object({ images: z7.array(ImageRef).min(1) }).strict(),
|
|
4699
|
-
params: z7.object({ labels: z7.array(z7.string().min(1)).min(1).optional() }).strict(),
|
|
4700
|
-
outputs: z7.object({ images: z7.array(ImageRef).min(1) }).strict(),
|
|
4701
|
-
outputKinds: { images: "image" },
|
|
4702
|
-
cost: () => ({ credits: 0, seconds_estimate: 0 }),
|
|
4703
|
-
// Arity is only knowable at validate time when `images` is a literal array;
|
|
4704
|
-
// a single `$ref:` string to an upstream array output defers to runtime.
|
|
4705
|
-
validateExtra: ({ rawParams, rawInputs }) => {
|
|
4706
|
-
const labels = rawParams?.labels;
|
|
4707
|
-
if (!Array.isArray(labels)) return [];
|
|
4708
|
-
if (new Set(labels).size !== labels.length) {
|
|
4709
|
-
return [{ path: "params.labels", message: "labels must be unique \u2014 each names one output variant" }];
|
|
4710
|
-
}
|
|
4711
|
-
const images = rawInputs?.images;
|
|
4712
|
-
if (Array.isArray(images) && labels.length !== images.length) {
|
|
4713
|
-
return [
|
|
4714
|
-
{
|
|
4715
|
-
path: "params.labels",
|
|
4716
|
-
message: `labels has ${labels.length} entries but ${images.length} images are wired \u2014 provide one label per image`
|
|
4717
|
-
}
|
|
4718
|
-
];
|
|
4719
|
-
}
|
|
4720
|
-
return [];
|
|
4721
|
-
},
|
|
4722
|
-
execute: ({ inputs }) => Promise.resolve({ images: inputs.images })
|
|
4723
|
-
});
|
|
4724
|
-
|
|
4725
4578
|
// src/engine/nodes/local/ffmpeg.ts
|
|
4726
|
-
import { z as
|
|
4579
|
+
import { z as z7 } from "zod";
|
|
4727
4580
|
var FFMPEG_BIN2 = "ffmpeg";
|
|
4728
|
-
var OutputDecl =
|
|
4729
|
-
kind:
|
|
4730
|
-
ext:
|
|
4581
|
+
var OutputDecl = z7.object({
|
|
4582
|
+
kind: z7.enum(["image", "video", "audio"]),
|
|
4583
|
+
ext: z7.string().min(1).max(8)
|
|
4731
4584
|
}).strict();
|
|
4732
|
-
var FfmpegParams =
|
|
4733
|
-
args:
|
|
4734
|
-
outputs:
|
|
4585
|
+
var FfmpegParams = z7.object({
|
|
4586
|
+
args: z7.array(z7.string()).min(1),
|
|
4587
|
+
outputs: z7.record(z7.string(), OutputDecl).default({})
|
|
4735
4588
|
}).strict();
|
|
4736
|
-
var FfmpegInputs =
|
|
4737
|
-
var FfmpegOutputs =
|
|
4589
|
+
var FfmpegInputs = z7.record(z7.string(), z7.unknown());
|
|
4590
|
+
var FfmpegOutputs = z7.record(z7.string(), z7.custom());
|
|
4738
4591
|
var ffmpegNode = defineNode({
|
|
4739
4592
|
id: "ffmpeg",
|
|
4740
4593
|
version: "2.0.0",
|
|
@@ -4765,7 +4618,7 @@ import { mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile3 } from "fs/prom
|
|
|
4765
4618
|
import { createRequire } from "module";
|
|
4766
4619
|
import { tmpdir as tmpdir3 } from "os";
|
|
4767
4620
|
import path6 from "path";
|
|
4768
|
-
import { z as
|
|
4621
|
+
import { z as z8 } from "zod";
|
|
4769
4622
|
|
|
4770
4623
|
// src/engine/nodes/local/lib/assets.ts
|
|
4771
4624
|
import { copyFile as copyFile3, readFile as readFile4 } from "fs/promises";
|
|
@@ -4801,15 +4654,15 @@ var DEFAULT_SPECIMEN = [
|
|
|
4801
4654
|
"abcdefghijklmnopqrstuvwxyz",
|
|
4802
4655
|
`0123456789 !?&@#$%().,:;'"-`
|
|
4803
4656
|
].join("\n");
|
|
4804
|
-
var FontSpecimenParams =
|
|
4805
|
-
text:
|
|
4806
|
-
font_size:
|
|
4807
|
-
padding:
|
|
4808
|
-
line_height:
|
|
4809
|
-
max_width:
|
|
4657
|
+
var FontSpecimenParams = z8.object({
|
|
4658
|
+
text: z8.string().min(1).max(2e3).optional().default(DEFAULT_SPECIMEN),
|
|
4659
|
+
font_size: z8.number().int().min(8).max(512).optional().default(72),
|
|
4660
|
+
padding: z8.number().int().min(0).max(512).optional().default(64),
|
|
4661
|
+
line_height: z8.number().min(0.8).max(3).optional().default(1.35),
|
|
4662
|
+
max_width: z8.number().int().min(256).max(4096).optional()
|
|
4810
4663
|
}).strict();
|
|
4811
|
-
var FontSpecimenInputs =
|
|
4812
|
-
var FontSpecimenOutputs =
|
|
4664
|
+
var FontSpecimenInputs = z8.object({ font: FontRef }).loose();
|
|
4665
|
+
var FontSpecimenOutputs = z8.object({ image: ImageRef }).strict();
|
|
4813
4666
|
var DEVICE_SCALE_FACTOR = 2;
|
|
4814
4667
|
var PAGE_TIMEOUT_MS = 3e4;
|
|
4815
4668
|
function escapeHtml(text) {
|
|
@@ -4950,7 +4803,7 @@ import { createRequire as createRequire2 } from "module";
|
|
|
4950
4803
|
import { cpus, tmpdir as tmpdir4 } from "os";
|
|
4951
4804
|
import path11 from "path";
|
|
4952
4805
|
import { promisify as promisify4 } from "util";
|
|
4953
|
-
import { z as
|
|
4806
|
+
import { z as z10 } from "zod";
|
|
4954
4807
|
|
|
4955
4808
|
// src/engine/engine/composition-hash.ts
|
|
4956
4809
|
import { readdir as readdir2, readFile as readFile5, stat as stat4 } from "fs/promises";
|
|
@@ -4988,62 +4841,62 @@ async function collectFiles(root, current) {
|
|
|
4988
4841
|
// src/engine/engine/composition-meta.ts
|
|
4989
4842
|
import { readFile as readFile6 } from "fs/promises";
|
|
4990
4843
|
import path8 from "path";
|
|
4991
|
-
import { z as
|
|
4992
|
-
var InputKind =
|
|
4993
|
-
var InputSpec =
|
|
4844
|
+
import { z as z9 } from "zod";
|
|
4845
|
+
var InputKind = z9.enum(["video", "image", "audio", "json"]);
|
|
4846
|
+
var InputSpec = z9.object({
|
|
4994
4847
|
kind: InputKind,
|
|
4995
|
-
required:
|
|
4848
|
+
required: z9.boolean().optional().default(false),
|
|
4996
4849
|
// Filename the composition's HTML references (e.g. `input.mp4`, `logo.png`).
|
|
4997
4850
|
// Defaults to `<key><ext>` derived from the kind.
|
|
4998
|
-
staged_as:
|
|
4999
|
-
description:
|
|
4851
|
+
staged_as: z9.string().min(1).optional(),
|
|
4852
|
+
description: z9.string().optional()
|
|
5000
4853
|
}).strict();
|
|
5001
4854
|
var ParamSpecBase = {
|
|
5002
|
-
required:
|
|
5003
|
-
description:
|
|
4855
|
+
required: z9.boolean().optional().default(false),
|
|
4856
|
+
description: z9.string().optional()
|
|
5004
4857
|
};
|
|
5005
|
-
var StringParam =
|
|
4858
|
+
var StringParam = z9.object({
|
|
5006
4859
|
...ParamSpecBase,
|
|
5007
|
-
kind:
|
|
5008
|
-
default:
|
|
5009
|
-
enum:
|
|
4860
|
+
kind: z9.literal("string"),
|
|
4861
|
+
default: z9.string().optional(),
|
|
4862
|
+
enum: z9.array(z9.string()).optional()
|
|
5010
4863
|
}).strict();
|
|
5011
|
-
var IntegerParam =
|
|
4864
|
+
var IntegerParam = z9.object({
|
|
5012
4865
|
...ParamSpecBase,
|
|
5013
|
-
kind:
|
|
5014
|
-
default:
|
|
5015
|
-
min:
|
|
5016
|
-
max:
|
|
4866
|
+
kind: z9.literal("integer"),
|
|
4867
|
+
default: z9.number().int().optional(),
|
|
4868
|
+
min: z9.number().int().optional(),
|
|
4869
|
+
max: z9.number().int().optional()
|
|
5017
4870
|
}).strict();
|
|
5018
|
-
var NumberParam =
|
|
4871
|
+
var NumberParam = z9.object({
|
|
5019
4872
|
...ParamSpecBase,
|
|
5020
|
-
kind:
|
|
5021
|
-
default:
|
|
5022
|
-
min:
|
|
5023
|
-
max:
|
|
4873
|
+
kind: z9.literal("number"),
|
|
4874
|
+
default: z9.number().optional(),
|
|
4875
|
+
min: z9.number().optional(),
|
|
4876
|
+
max: z9.number().optional()
|
|
5024
4877
|
}).strict();
|
|
5025
|
-
var BooleanParam =
|
|
4878
|
+
var BooleanParam = z9.object({
|
|
5026
4879
|
...ParamSpecBase,
|
|
5027
|
-
kind:
|
|
5028
|
-
default:
|
|
4880
|
+
kind: z9.literal("boolean"),
|
|
4881
|
+
default: z9.boolean().optional()
|
|
5029
4882
|
}).strict();
|
|
5030
|
-
var ColorParam =
|
|
4883
|
+
var ColorParam = z9.object({
|
|
5031
4884
|
...ParamSpecBase,
|
|
5032
|
-
kind:
|
|
5033
|
-
default:
|
|
4885
|
+
kind: z9.literal("color"),
|
|
4886
|
+
default: z9.string().optional()
|
|
5034
4887
|
}).strict();
|
|
5035
|
-
var ImageParam =
|
|
4888
|
+
var ImageParam = z9.object({
|
|
5036
4889
|
...ParamSpecBase,
|
|
5037
|
-
kind:
|
|
5038
|
-
default:
|
|
4890
|
+
kind: z9.literal("image"),
|
|
4891
|
+
default: z9.string().optional()
|
|
5039
4892
|
}).strict();
|
|
5040
|
-
var JsonParam =
|
|
4893
|
+
var JsonParam = z9.object({
|
|
5041
4894
|
...ParamSpecBase,
|
|
5042
|
-
kind:
|
|
5043
|
-
schema:
|
|
5044
|
-
default:
|
|
4895
|
+
kind: z9.literal("json"),
|
|
4896
|
+
schema: z9.unknown().optional(),
|
|
4897
|
+
default: z9.unknown().optional()
|
|
5045
4898
|
}).strict();
|
|
5046
|
-
var ParamSpec =
|
|
4899
|
+
var ParamSpec = z9.discriminatedUnion("kind", [
|
|
5047
4900
|
StringParam,
|
|
5048
4901
|
IntegerParam,
|
|
5049
4902
|
NumberParam,
|
|
@@ -5052,16 +4905,16 @@ var ParamSpec = z10.discriminatedUnion("kind", [
|
|
|
5052
4905
|
ImageParam,
|
|
5053
4906
|
JsonParam
|
|
5054
4907
|
]);
|
|
5055
|
-
var CompositionMetaSchema =
|
|
5056
|
-
id:
|
|
5057
|
-
title:
|
|
5058
|
-
description:
|
|
5059
|
-
width:
|
|
5060
|
-
height:
|
|
5061
|
-
fps:
|
|
5062
|
-
default_duration:
|
|
5063
|
-
inputs:
|
|
5064
|
-
params:
|
|
4908
|
+
var CompositionMetaSchema = z9.object({
|
|
4909
|
+
id: z9.string().min(1),
|
|
4910
|
+
title: z9.string().min(1),
|
|
4911
|
+
description: z9.string().optional(),
|
|
4912
|
+
width: z9.number().int().positive(),
|
|
4913
|
+
height: z9.number().int().positive(),
|
|
4914
|
+
fps: z9.number().int().positive().default(30),
|
|
4915
|
+
default_duration: z9.number().positive().default(10),
|
|
4916
|
+
inputs: z9.record(z9.string(), InputSpec).default({}),
|
|
4917
|
+
params: z9.record(z9.string(), ParamSpec).default({})
|
|
5065
4918
|
}).strict();
|
|
5066
4919
|
async function loadCompositionMeta(compositionDir) {
|
|
5067
4920
|
const metaPath = path8.join(compositionDir, "meta.json");
|
|
@@ -5089,39 +4942,39 @@ function buildParamsSchema(meta) {
|
|
|
5089
4942
|
for (const [name, spec] of Object.entries(meta.params)) {
|
|
5090
4943
|
shape[name] = buildParamFieldSchema(name, spec);
|
|
5091
4944
|
}
|
|
5092
|
-
return
|
|
4945
|
+
return z9.object(shape).strict();
|
|
5093
4946
|
}
|
|
5094
4947
|
function buildParamFieldSchema(name, spec) {
|
|
5095
4948
|
switch (spec.kind) {
|
|
5096
4949
|
case "string": {
|
|
5097
|
-
const s = spec.enum && spec.enum.length > 0 ?
|
|
4950
|
+
const s = spec.enum && spec.enum.length > 0 ? z9.enum(spec.enum) : z9.string();
|
|
5098
4951
|
return finalize(s, spec.default, spec.required);
|
|
5099
4952
|
}
|
|
5100
4953
|
case "integer": {
|
|
5101
|
-
let s =
|
|
4954
|
+
let s = z9.number().int();
|
|
5102
4955
|
if (spec.min !== void 0) s = s.min(spec.min);
|
|
5103
4956
|
if (spec.max !== void 0) s = s.max(spec.max);
|
|
5104
4957
|
return finalize(s, spec.default, spec.required);
|
|
5105
4958
|
}
|
|
5106
4959
|
case "number": {
|
|
5107
|
-
let s =
|
|
4960
|
+
let s = z9.number();
|
|
5108
4961
|
if (spec.min !== void 0) s = s.min(spec.min);
|
|
5109
4962
|
if (spec.max !== void 0) s = s.max(spec.max);
|
|
5110
4963
|
return finalize(s, spec.default, spec.required);
|
|
5111
4964
|
}
|
|
5112
4965
|
case "boolean":
|
|
5113
|
-
return finalize(
|
|
4966
|
+
return finalize(z9.boolean(), spec.default, spec.required);
|
|
5114
4967
|
case "color": {
|
|
5115
|
-
const s =
|
|
4968
|
+
const s = z9.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
|
|
5116
4969
|
message: `param "${name}": must be a 3/6/8-digit hex color (e.g. "#ff0066")`
|
|
5117
4970
|
});
|
|
5118
4971
|
return finalize(s, spec.default, spec.required);
|
|
5119
4972
|
}
|
|
5120
4973
|
case "image":
|
|
5121
|
-
return finalize(
|
|
4974
|
+
return finalize(z9.union([z9.string().min(1), z9.record(z9.string(), z9.unknown())]), spec.default, spec.required);
|
|
5122
4975
|
case "json":
|
|
5123
4976
|
return finalize(
|
|
5124
|
-
|
|
4977
|
+
z9.unknown().refine((v) => v !== void 0, { message: `param "${name}" is required` }),
|
|
5125
4978
|
spec.default,
|
|
5126
4979
|
spec.required
|
|
5127
4980
|
);
|
|
@@ -5421,17 +5274,17 @@ function literalize(value) {
|
|
|
5421
5274
|
// src/engine/nodes/local/hyperframe.ts
|
|
5422
5275
|
var execFileAsync2 = promisify4(execFile4);
|
|
5423
5276
|
var require_2 = createRequire2(import.meta.url);
|
|
5424
|
-
var HyperframeParams =
|
|
5425
|
-
composition:
|
|
5277
|
+
var HyperframeParams = z10.object({
|
|
5278
|
+
composition: z10.string().min(1),
|
|
5426
5279
|
// Output container. mp4 (default) for delivery; webm/mov render WITH
|
|
5427
5280
|
// transparency (alpha) when the composition background is transparent —
|
|
5428
5281
|
// use for motion-graphic overlays dropped into Premiere/AE/Nuke.
|
|
5429
|
-
format:
|
|
5430
|
-
timeout_ms:
|
|
5431
|
-
}).catchall(
|
|
5432
|
-
var HyperframeInputs =
|
|
5433
|
-
var HyperframeOutputs =
|
|
5434
|
-
video:
|
|
5282
|
+
format: z10.enum(["mp4", "webm", "mov"]).optional().default("mp4"),
|
|
5283
|
+
timeout_ms: z10.number().int().positive().optional().default(10 * 60 * 1e3)
|
|
5284
|
+
}).catchall(z10.unknown());
|
|
5285
|
+
var HyperframeInputs = z10.record(z10.string(), z10.custom()).optional().default({});
|
|
5286
|
+
var HyperframeOutputs = z10.object({
|
|
5287
|
+
video: z10.custom()
|
|
5435
5288
|
}).strict();
|
|
5436
5289
|
var NODE_OWNED_PARAM_KEYS = /* @__PURE__ */ new Set(["composition", "format", "timeout_ms"]);
|
|
5437
5290
|
var MIME_BY_FORMAT = {
|
|
@@ -5720,23 +5573,23 @@ import { createRequire as createRequire3 } from "module";
|
|
|
5720
5573
|
import { tmpdir as tmpdir5 } from "os";
|
|
5721
5574
|
import path12 from "path";
|
|
5722
5575
|
import { promisify as promisify5 } from "util";
|
|
5723
|
-
import { z as
|
|
5576
|
+
import { z as z11 } from "zod";
|
|
5724
5577
|
var _execFileAsync = promisify5(execFile5);
|
|
5725
5578
|
var require_3 = createRequire3(import.meta.url);
|
|
5726
|
-
var WaitForSpec =
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5579
|
+
var WaitForSpec = z11.discriminatedUnion("kind", [
|
|
5580
|
+
z11.object({ kind: z11.literal("auto") }),
|
|
5581
|
+
z11.object({ kind: z11.literal("selector"), value: z11.string().min(1) }),
|
|
5582
|
+
z11.object({ kind: z11.literal("function"), value: z11.string().min(1) }),
|
|
5583
|
+
z11.object({ kind: z11.literal("timeout"), ms: z11.number().int().min(0).max(6e4) })
|
|
5731
5584
|
]);
|
|
5732
|
-
var HyperframeSnapshotParams =
|
|
5733
|
-
composition:
|
|
5585
|
+
var HyperframeSnapshotParams = z11.object({
|
|
5586
|
+
composition: z11.string().min(1),
|
|
5734
5587
|
wait_for: WaitForSpec.optional().default({ kind: "auto" }),
|
|
5735
|
-
timeout_ms:
|
|
5736
|
-
}).catchall(
|
|
5737
|
-
var HyperframeSnapshotInputs =
|
|
5738
|
-
var HyperframeSnapshotOutputs =
|
|
5739
|
-
image:
|
|
5588
|
+
timeout_ms: z11.number().int().positive().optional().default(6e4)
|
|
5589
|
+
}).catchall(z11.unknown());
|
|
5590
|
+
var HyperframeSnapshotInputs = z11.record(z11.string(), z11.custom()).optional().default({});
|
|
5591
|
+
var HyperframeSnapshotOutputs = z11.object({
|
|
5592
|
+
image: z11.custom()
|
|
5740
5593
|
}).strict();
|
|
5741
5594
|
var NODE_OWNED_PARAM_KEYS2 = /* @__PURE__ */ new Set(["composition", "wait_for", "timeout_ms"]);
|
|
5742
5595
|
var DEVICE_SCALE_FACTOR2 = 2;
|
|
@@ -5928,18 +5781,18 @@ async function waitForReady(page, waitFor, timeoutMs) {
|
|
|
5928
5781
|
// src/engine/nodes/local/imagemagick.ts
|
|
5929
5782
|
import { execFile as execFile6 } from "child_process";
|
|
5930
5783
|
import { promisify as promisify6 } from "util";
|
|
5931
|
-
import { z as
|
|
5784
|
+
import { z as z12 } from "zod";
|
|
5932
5785
|
var execFileAsync3 = promisify6(execFile6);
|
|
5933
|
-
var OutputDecl2 =
|
|
5934
|
-
kind:
|
|
5935
|
-
ext:
|
|
5786
|
+
var OutputDecl2 = z12.object({
|
|
5787
|
+
kind: z12.enum(["image", "video", "audio"]),
|
|
5788
|
+
ext: z12.string().min(1).max(8)
|
|
5936
5789
|
}).strict();
|
|
5937
|
-
var ImageMagickParams =
|
|
5938
|
-
args:
|
|
5939
|
-
outputs:
|
|
5790
|
+
var ImageMagickParams = z12.object({
|
|
5791
|
+
args: z12.array(z12.string()).min(1),
|
|
5792
|
+
outputs: z12.record(z12.string(), OutputDecl2).default({})
|
|
5940
5793
|
}).strict();
|
|
5941
|
-
var ImageMagickInputs =
|
|
5942
|
-
var ImageMagickOutputs =
|
|
5794
|
+
var ImageMagickInputs = z12.record(z12.string(), z12.unknown());
|
|
5795
|
+
var ImageMagickOutputs = z12.record(z12.string(), z12.custom());
|
|
5943
5796
|
var resolvedBin;
|
|
5944
5797
|
async function resolveBin() {
|
|
5945
5798
|
if (resolvedBin) return resolvedBin;
|
|
@@ -5981,29 +5834,29 @@ var imagemagickNode = defineNode({
|
|
|
5981
5834
|
});
|
|
5982
5835
|
|
|
5983
5836
|
// src/engine/nodes/local/text.ts
|
|
5984
|
-
import { z as
|
|
5837
|
+
import { z as z13 } from "zod";
|
|
5985
5838
|
var textNode = defineNode({
|
|
5986
5839
|
id: "text",
|
|
5987
5840
|
version: "1.0.0",
|
|
5988
5841
|
category: "data",
|
|
5989
5842
|
location: "local",
|
|
5990
5843
|
summary: "A literal text value. Use for prompts, descriptions, copy.",
|
|
5991
|
-
inputs:
|
|
5992
|
-
params:
|
|
5993
|
-
outputs:
|
|
5844
|
+
inputs: z13.object({}).strict(),
|
|
5845
|
+
params: z13.object({ value: z13.string() }).strict(),
|
|
5846
|
+
outputs: z13.object({ text: z13.string() }).strict(),
|
|
5994
5847
|
cost: () => ({ credits: 0, seconds_estimate: 0 }),
|
|
5995
5848
|
execute: ({ params }) => Promise.resolve({ text: params.value })
|
|
5996
5849
|
});
|
|
5997
5850
|
|
|
5998
5851
|
// src/engine/nodes/remote/audioVoiceConvert.ts
|
|
5999
|
-
import { z as
|
|
6000
|
-
var AudioVoiceConvertParams =
|
|
6001
|
-
model:
|
|
5852
|
+
import { z as z14 } from "zod";
|
|
5853
|
+
var AudioVoiceConvertParams = z14.object({
|
|
5854
|
+
model: z14.literal("elevenlabs/eleven_multilingual_sts_v2"),
|
|
6002
5855
|
/** Target voice id. Splice an upstream `voice_select` via `"{{voice_ref}}"`. */
|
|
6003
|
-
voice:
|
|
6004
|
-
output_format:
|
|
5856
|
+
voice: z14.string().min(1),
|
|
5857
|
+
output_format: z14.string().optional(),
|
|
6005
5858
|
/** Strip the source clip's background noise before re-voicing. */
|
|
6006
|
-
remove_background_noise:
|
|
5859
|
+
remove_background_noise: z14.boolean().optional()
|
|
6007
5860
|
}).strict();
|
|
6008
5861
|
var audioVoiceConvertNode = delegated({
|
|
6009
5862
|
id: "audio_voice_convert",
|
|
@@ -6011,44 +5864,44 @@ var audioVoiceConvertNode = delegated({
|
|
|
6011
5864
|
category: "audio",
|
|
6012
5865
|
summary: "Voice Changer / speech-to-speech via ElevenLabs (eleven_multilingual_sts_v2). Re-voices an existing audio clip in a TARGET voice while preserving timing/prosody.",
|
|
6013
5866
|
when_to_use: 'Use to normalize a generator-chosen voice (e.g. a Seedance talking-head clip\'s native audio) into ONE consistent brand voice across every scene \u2014 the cadence is preserved so any lip-sync stays valid. Wire `inputs.voice_ref: $ref:<voice_select>.voice_id` and set `params.voice: "{{voice_ref}}"`.',
|
|
6014
|
-
inputs:
|
|
5867
|
+
inputs: z14.object({
|
|
6015
5868
|
audio: AudioRef,
|
|
6016
5869
|
voice_ref: TextRef.optional()
|
|
6017
5870
|
}).strict(),
|
|
6018
5871
|
params: AudioVoiceConvertParams,
|
|
6019
|
-
outputs:
|
|
5872
|
+
outputs: z14.object({ audio: AudioRef }).strict(),
|
|
6020
5873
|
outputKinds: { audio: "audio" },
|
|
6021
5874
|
cost: () => ({ credits: 1, seconds_estimate: 20 })
|
|
6022
5875
|
});
|
|
6023
5876
|
|
|
6024
5877
|
// src/engine/nodes/remote/dialogue.ts
|
|
6025
|
-
import { z as
|
|
6026
|
-
var DialogueInput =
|
|
6027
|
-
text:
|
|
6028
|
-
voice_id:
|
|
5878
|
+
import { z as z15 } from "zod";
|
|
5879
|
+
var DialogueInput = z15.object({
|
|
5880
|
+
text: z15.string().min(1),
|
|
5881
|
+
voice_id: z15.string().min(1)
|
|
6029
5882
|
});
|
|
6030
5883
|
var DIALOGUE_MODELS = ["elevenlabs/eleven_v3"];
|
|
6031
|
-
var DialogueParams =
|
|
6032
|
-
model:
|
|
5884
|
+
var DialogueParams = z15.object({
|
|
5885
|
+
model: z15.enum(DIALOGUE_MODELS),
|
|
6033
5886
|
/**
|
|
6034
5887
|
* Ordered list of lines, each tagged with the voice that should speak it.
|
|
6035
5888
|
* Up to 10 unique voice_ids; total text across all lines should stay under
|
|
6036
5889
|
* ~2000 characters for best quality (ElevenLabs guidance).
|
|
6037
5890
|
*/
|
|
6038
|
-
inputs:
|
|
6039
|
-
language_code:
|
|
5891
|
+
inputs: z15.array(DialogueInput).min(1).max(50),
|
|
5892
|
+
language_code: z15.string().optional(),
|
|
6040
5893
|
/** ElevenLabs voice/model settings passthrough (e.g. `{ stability: 0.5 }`). */
|
|
6041
|
-
settings:
|
|
6042
|
-
seed:
|
|
6043
|
-
apply_text_normalization:
|
|
5894
|
+
settings: z15.record(z15.string(), z15.unknown()).optional(),
|
|
5895
|
+
seed: z15.number().int().min(0).max(4294967295).optional(),
|
|
5896
|
+
apply_text_normalization: z15.enum(["auto", "on", "off"]).optional(),
|
|
6044
5897
|
/**
|
|
6045
5898
|
* When true, hits `/v1/text-to-dialogue/with-timestamps` and emits a
|
|
6046
5899
|
* separate `timestamps` output — character-level alignment plus
|
|
6047
5900
|
* per-voice segment markers usable for captions, lipsync, or
|
|
6048
5901
|
* beat-matched cuts in ad creatives.
|
|
6049
5902
|
*/
|
|
6050
|
-
with_timestamps:
|
|
6051
|
-
output_format:
|
|
5903
|
+
with_timestamps: z15.boolean().optional(),
|
|
5904
|
+
output_format: z15.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
|
|
6052
5905
|
}).strict().refine((p) => p.inputs.reduce((sum, line) => sum + line.text.length, 0) <= ELEVENLABS_MAX_TEXT_CHARS, {
|
|
6053
5906
|
message: `total dialogue text exceeds ${ELEVENLABS_MAX_TEXT_CHARS} characters`,
|
|
6054
5907
|
path: ["inputs"]
|
|
@@ -6059,9 +5912,9 @@ var dialogueNode = delegated({
|
|
|
6059
5912
|
category: "audio",
|
|
6060
5913
|
summary: "Multi-voice dialogue / VO with ElevenLabs Eleven v3. Each line is tagged with a `voice_id`, so you can render two-character scripts (e.g. ad VO + customer testimonial reaction) in a single call. Setting `with_timestamps: true` adds character-level alignment for caption rendering and lipsync-friendly cuts.",
|
|
6061
5914
|
when_to_use: "Use for any ad creative or website video VO that needs more than narration \u2014 interviews, two-actor scripts, character ads, testimonial reads. For single-voice flat reads the existing `tts` node is cheaper and simpler; reach for `dialogue` when you need multiple speakers in one stitched track or word-level timing for downstream lipsync / captions.",
|
|
6062
|
-
inputs:
|
|
5915
|
+
inputs: z15.object({}).loose(),
|
|
6063
5916
|
params: DialogueParams,
|
|
6064
|
-
outputs:
|
|
5917
|
+
outputs: z15.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
|
|
6065
5918
|
outputKinds: { audio: "audio", timestamps: "json" },
|
|
6066
5919
|
cost: ({ params }) => {
|
|
6067
5920
|
const chars = params.inputs.reduce((sum, line) => sum + line.text.length, 0);
|
|
@@ -6070,7 +5923,7 @@ var dialogueNode = delegated({
|
|
|
6070
5923
|
});
|
|
6071
5924
|
|
|
6072
5925
|
// src/engine/nodes/remote/image.ts
|
|
6073
|
-
import { z as
|
|
5926
|
+
import { z as z16 } from "zod";
|
|
6074
5927
|
var IMAGE_GENERATE_MODELS2 = [
|
|
6075
5928
|
"openai/gpt-5.4-image-2",
|
|
6076
5929
|
"google/gemini-3.5-flash",
|
|
@@ -6078,16 +5931,16 @@ var IMAGE_GENERATE_MODELS2 = [
|
|
|
6078
5931
|
"google/gemini-3-pro-image-preview",
|
|
6079
5932
|
"recraft/recraft-v4.1-pro-vector"
|
|
6080
5933
|
];
|
|
6081
|
-
var ImageGenerateParams =
|
|
6082
|
-
model:
|
|
6083
|
-
prompt:
|
|
6084
|
-
aspect_ratio:
|
|
6085
|
-
image_size:
|
|
5934
|
+
var ImageGenerateParams = z16.object({
|
|
5935
|
+
model: z16.enum(IMAGE_GENERATE_MODELS2),
|
|
5936
|
+
prompt: z16.string().min(1),
|
|
5937
|
+
aspect_ratio: z16.enum(["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "4:5", "5:4", "21:9", "1:4", "4:1", "1:8", "8:1"]).optional(),
|
|
5938
|
+
image_size: z16.enum(["0.5K", "1K", "2K", "4K"]).optional(),
|
|
6086
5939
|
// Recraft v4 vector controls — forwarded into `image_config`. Registry
|
|
6087
5940
|
// rejects them on non-Recraft models.
|
|
6088
|
-
strength:
|
|
6089
|
-
rgb_colors:
|
|
6090
|
-
background_rgb_color:
|
|
5941
|
+
strength: z16.number().min(0).max(1).optional(),
|
|
5942
|
+
rgb_colors: z16.array(z16.array(z16.number().int().min(0).max(255))).optional(),
|
|
5943
|
+
background_rgb_color: z16.array(z16.number().int().min(0).max(255)).optional()
|
|
6091
5944
|
}).strict();
|
|
6092
5945
|
var imageGenerateNode = delegated({
|
|
6093
5946
|
id: "image_generate",
|
|
@@ -6097,22 +5950,22 @@ var imageGenerateNode = delegated({
|
|
|
6097
5950
|
when_to_use: "Use for hero shots, product photography, illustrations, and vector logos. `recraft/recraft-v4.1-pro-vector` for crisp vector / logo work; `openai/gpt-5.4-image-2` for photorealistic; Gemini variants for fast iteration and editing via the `reference` input. `reference` accepts ONE image or an ARRAY of images \u2014 wire several to combine references in a single generation (e.g. a subject sheet + a font specimen + the original ad). Every reference is forwarded to the model in array order.",
|
|
6098
5951
|
// `reference` is one image or an ordered array of images. The backend forwards
|
|
6099
5952
|
// each as a separate `image_url` to the provider (OpenRouter accepts many).
|
|
6100
|
-
inputs:
|
|
5953
|
+
inputs: z16.object({ reference: z16.union([ImageRef, z16.array(ImageRef).min(1)]).optional() }).loose(),
|
|
6101
5954
|
params: ImageGenerateParams,
|
|
6102
|
-
outputs:
|
|
5955
|
+
outputs: z16.object({ images: z16.array(ImageRef).min(1) }).strict(),
|
|
6103
5956
|
outputKinds: { images: "image" },
|
|
6104
5957
|
cost: () => ({ credits: 5, seconds_estimate: 10 })
|
|
6105
5958
|
});
|
|
6106
5959
|
|
|
6107
5960
|
// src/engine/nodes/remote/imageAspectAdapt.ts
|
|
6108
|
-
import { z as
|
|
5961
|
+
import { z as z17 } from "zod";
|
|
6109
5962
|
var ASPECT_ADAPT_MODELS = ["google/gemini-3-pro-image-preview", "google/gemini-3.1-flash-image-preview"];
|
|
6110
5963
|
var ASPECT_ADAPT_FORMATS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
|
|
6111
|
-
var ImageAspectAdaptParams =
|
|
6112
|
-
model:
|
|
6113
|
-
formats:
|
|
6114
|
-
guidance:
|
|
6115
|
-
image_size:
|
|
5964
|
+
var ImageAspectAdaptParams = z17.object({
|
|
5965
|
+
model: z17.enum(ASPECT_ADAPT_MODELS),
|
|
5966
|
+
formats: z17.array(z17.enum(ASPECT_ADAPT_FORMATS)).min(1).max(6).refine((formats) => new Set(formats).size === formats.length, { message: "formats must be unique" }),
|
|
5967
|
+
guidance: z17.string().min(1).optional(),
|
|
5968
|
+
image_size: z17.enum(["0.5K", "1K", "2K", "4K"]).optional()
|
|
6116
5969
|
}).strict();
|
|
6117
5970
|
var imageAspectAdaptNode = delegated({
|
|
6118
5971
|
id: "image_aspect_adapt",
|
|
@@ -6120,9 +5973,9 @@ var imageAspectAdaptNode = delegated({
|
|
|
6120
5973
|
category: "image",
|
|
6121
5974
|
summary: "Adapt ONE creative into multiple aspect ratios (Meta: 9:16 stories, 1:1 feed, 4:5, 16:9\u2026) in a single step. AI recomposes the layout per format \u2014 identical subject, text, logos, colors, and style; the scene is extended/restructured, never stretched or cropped. Formats that already match the source ratio pass through unchanged at zero cost. Outputs are ordered exactly as `formats`.",
|
|
6122
5975
|
when_to_use: "Use after a hero creative exists (image_generate, ingest, image_search) to fan it out to every placement format \u2014 wire the creative into `source` and list the target ratios in `formats`. Cost is estimated per format; formats matching the source ratio are free pass-throughs. Pick `google/gemini-3.1-flash-image-preview` (Nano Banana flash) while iterating, `google/gemini-3-pro-image-preview` (Nano Banana Pro) for final-quality adaptation.",
|
|
6123
|
-
inputs:
|
|
5976
|
+
inputs: z17.object({ source: ImageRef }).loose(),
|
|
6124
5977
|
params: ImageAspectAdaptParams,
|
|
6125
|
-
outputs:
|
|
5978
|
+
outputs: z17.object({ images: z17.array(ImageRef).min(1) }).strict(),
|
|
6126
5979
|
outputKinds: { images: "image" },
|
|
6127
5980
|
cost: ({ params }) => {
|
|
6128
5981
|
const p = params;
|
|
@@ -6135,12 +5988,12 @@ var imageAspectAdaptNode = delegated({
|
|
|
6135
5988
|
});
|
|
6136
5989
|
|
|
6137
5990
|
// src/engine/nodes/remote/imageBackgroundRemove.ts
|
|
6138
|
-
import { z as
|
|
6139
|
-
var ImageBackgroundRemoveParams =
|
|
6140
|
-
model:
|
|
6141
|
-
model_variant:
|
|
6142
|
-
operating_resolution:
|
|
6143
|
-
mask_only:
|
|
5991
|
+
import { z as z18 } from "zod";
|
|
5992
|
+
var ImageBackgroundRemoveParams = z18.object({
|
|
5993
|
+
model: z18.literal("fal/birefnet-v2").optional().default("fal/birefnet-v2"),
|
|
5994
|
+
model_variant: z18.enum(["General Use (Light)", "General Use (Heavy)", "Matting", "Portrait", "DIS", "HRSOD", "COD"]).optional().default("General Use (Light)"),
|
|
5995
|
+
operating_resolution: z18.enum(["1024x1024", "2048x2048", "2304x2304"]).optional(),
|
|
5996
|
+
mask_only: z18.boolean().optional().default(false)
|
|
6144
5997
|
}).strict();
|
|
6145
5998
|
var imageBackgroundRemoveNode = delegated({
|
|
6146
5999
|
id: "image_background_remove",
|
|
@@ -6148,11 +6001,11 @@ var imageBackgroundRemoveNode = delegated({
|
|
|
6148
6001
|
category: "image",
|
|
6149
6002
|
summary: "Remove the background from an image and return a transparent PNG (or the segmentation mask). Powered by fal.ai `fal-ai/birefnet/v2`.",
|
|
6150
6003
|
when_to_use: "Use to extract subjects from photos for use as overlays in hyperframe compositions, product shots, or compositing pipelines. Set `mask_only:true` to return the binary mask instead of the alpha-cut image.",
|
|
6151
|
-
inputs:
|
|
6004
|
+
inputs: z18.object({
|
|
6152
6005
|
image: ImageRef
|
|
6153
6006
|
}).strict(),
|
|
6154
6007
|
params: ImageBackgroundRemoveParams,
|
|
6155
|
-
outputs:
|
|
6008
|
+
outputs: z18.object({
|
|
6156
6009
|
image: ImageRef,
|
|
6157
6010
|
mask: ImageRef.optional()
|
|
6158
6011
|
}).strict(),
|
|
@@ -6161,7 +6014,7 @@ var imageBackgroundRemoveNode = delegated({
|
|
|
6161
6014
|
});
|
|
6162
6015
|
|
|
6163
6016
|
// src/engine/nodes/remote/imageDescribe.ts
|
|
6164
|
-
import { z as
|
|
6017
|
+
import { z as z19 } from "zod";
|
|
6165
6018
|
var IMAGE_DESCRIBE_MODELS = ["~google/gemini-pro-latest", "~google/gemini-flash-latest"];
|
|
6166
6019
|
var imageDescribeNode = delegated({
|
|
6167
6020
|
id: "image_describe",
|
|
@@ -6169,33 +6022,33 @@ var imageDescribeNode = delegated({
|
|
|
6169
6022
|
category: "vision",
|
|
6170
6023
|
summary: "Reverse-engineer an image into an exhaustive, replication-grade JSON description: who the advertiser is and what they sell (source_context), composition, non-person subjects with expression/treatment, deeply detailed people, brand-identified logos (named by brand, not appearance), camera optics, lighting, color palette WITH per-color brand-ownership (brand vs borrowed-functional) and purpose, materials, visible text, ad signals (proof badges/CTA/price), the persuasion engine (ad_intent), style, post-processing.",
|
|
6171
6024
|
when_to_use: 'Use to turn a reference image into a structured blueprint you can inject into downstream prompts via `{{slot}}` \u2014 e.g. restyle a competitor ad onto your own product, lock a look across a series, or feed exact palette/lighting into image_generate. Purpose-built for market adaptation: logos are identified by brand ("Trustpilot", never "green star"), people and animals carry expression/emotion/intent detail, and each color is tagged brand vs borrowed-functional so a recolor can keep the reds/yellows that do a job. The extraction prompt is baked in; use `focus` to emphasise aspects and `context` to pass known provenance (advertiser, category, market) so source_context and color ownership are grounded. Pick `~google/gemini-pro-latest` for the densest extraction (recommended for ad / market-adaptation passes), `~google/gemini-flash-latest` for cheap/fast passes. The output is rich \u2014 raise `max_tokens` (e.g. 8000+) for dense ads so the JSON isn\'t truncated.',
|
|
6172
|
-
inputs:
|
|
6173
|
-
params:
|
|
6174
|
-
model:
|
|
6175
|
-
focus:
|
|
6176
|
-
context:
|
|
6177
|
-
temperature:
|
|
6178
|
-
max_tokens:
|
|
6025
|
+
inputs: z19.object({ image: ImageRef }).loose(),
|
|
6026
|
+
params: z19.object({
|
|
6027
|
+
model: z19.enum(IMAGE_DESCRIBE_MODELS),
|
|
6028
|
+
focus: z19.string().optional(),
|
|
6029
|
+
context: z19.string().optional(),
|
|
6030
|
+
temperature: z19.number().min(0).max(2).optional(),
|
|
6031
|
+
max_tokens: z19.number().int().positive().optional()
|
|
6179
6032
|
}).strict(),
|
|
6180
|
-
outputs:
|
|
6033
|
+
outputs: z19.object({ description: JsonRef }).strict(),
|
|
6181
6034
|
outputKinds: { description: "json" },
|
|
6182
6035
|
cost: () => ({ credits: 2, seconds_estimate: 10 })
|
|
6183
6036
|
});
|
|
6184
6037
|
|
|
6185
6038
|
// src/engine/nodes/remote/imageReferenceSheet.ts
|
|
6186
|
-
import { z as
|
|
6039
|
+
import { z as z20 } from "zod";
|
|
6187
6040
|
var REFERENCE_SHEET_MODELS = ["google/gemini-3-pro-image-preview", "google/gemini-3.1-flash-image-preview"];
|
|
6188
|
-
var ImageReferenceSheetParams =
|
|
6189
|
-
model:
|
|
6190
|
-
subject_description:
|
|
6041
|
+
var ImageReferenceSheetParams = z20.object({
|
|
6042
|
+
model: z20.enum(REFERENCE_SHEET_MODELS),
|
|
6043
|
+
subject_description: z20.string().min(1),
|
|
6191
6044
|
// `location` = a set/room shown from several camera ANGLES (not a rotated subject),
|
|
6192
6045
|
// so a multi-scene shoot keeps one consistent set.
|
|
6193
|
-
subject_type:
|
|
6194
|
-
views:
|
|
6195
|
-
style:
|
|
6196
|
-
prompt_override:
|
|
6197
|
-
aspect_ratio:
|
|
6198
|
-
image_size:
|
|
6046
|
+
subject_type: z20.enum(["character", "person", "product", "location"]),
|
|
6047
|
+
views: z20.array(z20.string().min(1)).min(2).max(8).optional(),
|
|
6048
|
+
style: z20.string().optional(),
|
|
6049
|
+
prompt_override: z20.string().min(1).optional(),
|
|
6050
|
+
aspect_ratio: z20.enum(["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "4:5", "5:4", "21:9", "1:4", "4:1", "1:8", "8:1"]).optional(),
|
|
6051
|
+
image_size: z20.enum(["0.5K", "1K", "2K", "4K"]).optional()
|
|
6199
6052
|
}).strict();
|
|
6200
6053
|
var imageReferenceSheetNode = delegated({
|
|
6201
6054
|
id: "image_reference_sheet",
|
|
@@ -6203,9 +6056,9 @@ var imageReferenceSheetNode = delegated({
|
|
|
6203
6056
|
category: "image",
|
|
6204
6057
|
summary: "Fuse 1\u20136 images of a single subject (person, character, product, or location/set) into ONE multi-view reference sheet \u2014 a labeled grid in consistent style and lighting: a turnaround (FRONT / SIDE / BACK\u2026) for a person/character/product, or several camera angles of the same room (WIDE / REVERSE / DETAIL\u2026) for a location. Curated models: Gemini 3 Pro Image (best fusion + labels), Gemini 3.1 Flash Image (cheap iteration).",
|
|
6205
6058
|
when_to_use: "Use before image_generate / video_generate when a subject must stay consistent across many creatives \u2014 wire the `sheet` output into their `reference` input instead of re-describing the subject per prompt. `subject_description` should be the exact wording you reuse downstream. Pick `google/gemini-3-pro-image-preview` for final 6-view sheets at 2K+, `google/gemini-3.1-flash-image-preview` while iterating.",
|
|
6206
|
-
inputs:
|
|
6059
|
+
inputs: z20.object({ references: z20.array(ImageRef).min(1).max(6) }).loose(),
|
|
6207
6060
|
params: ImageReferenceSheetParams,
|
|
6208
|
-
outputs:
|
|
6061
|
+
outputs: z20.object({ sheet: ImageRef }).strict(),
|
|
6209
6062
|
outputKinds: { sheet: "image" },
|
|
6210
6063
|
cost: ({ params }) => ({
|
|
6211
6064
|
credits: params?.model === "google/gemini-3-pro-image-preview" ? 20 : 5,
|
|
@@ -6214,10 +6067,10 @@ var imageReferenceSheetNode = delegated({
|
|
|
6214
6067
|
});
|
|
6215
6068
|
|
|
6216
6069
|
// src/engine/nodes/remote/imageSearch.ts
|
|
6217
|
-
import { z as
|
|
6218
|
-
var ImageSearchParams =
|
|
6219
|
-
prompt:
|
|
6220
|
-
count:
|
|
6070
|
+
import { z as z21 } from "zod";
|
|
6071
|
+
var ImageSearchParams = z21.object({
|
|
6072
|
+
prompt: z21.string().min(1),
|
|
6073
|
+
count: z21.number().int().min(1).max(20).default(5)
|
|
6221
6074
|
}).strict();
|
|
6222
6075
|
var imageSearchNode = delegated({
|
|
6223
6076
|
id: "image_search",
|
|
@@ -6225,15 +6078,15 @@ var imageSearchNode = delegated({
|
|
|
6225
6078
|
category: "image",
|
|
6226
6079
|
summary: "Agentic image search across Google Images, stock photography (Freepik), and Pinterest. An LLM agent picks the search tools and queries, selects the best matches, and the results are downloaded into canvas assets.",
|
|
6227
6080
|
when_to_use: "Use to gather real-world reference or inspiration images for a prompt (e.g. several photos of an australian shepherd) so a later step or the user can pick the best one. Not for creating new imagery \u2014 use image_generate for that.",
|
|
6228
|
-
inputs:
|
|
6081
|
+
inputs: z21.object({}).loose(),
|
|
6229
6082
|
params: ImageSearchParams,
|
|
6230
|
-
outputs:
|
|
6083
|
+
outputs: z21.object({ images: z21.array(ImageRef).min(1) }).strict(),
|
|
6231
6084
|
outputKinds: { images: "image" },
|
|
6232
6085
|
cost: ({ params }) => ({ credits: Math.ceil(2 + params.count / 2), seconds_estimate: 30 })
|
|
6233
6086
|
});
|
|
6234
6087
|
|
|
6235
6088
|
// src/engine/nodes/remote/imageSelect.ts
|
|
6236
|
-
import { z as
|
|
6089
|
+
import { z as z22 } from "zod";
|
|
6237
6090
|
var IMAGE_SELECT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
6238
6091
|
var imageSelectNode = delegated({
|
|
6239
6092
|
id: "image_select",
|
|
@@ -6241,15 +6094,15 @@ var imageSelectNode = delegated({
|
|
|
6241
6094
|
category: "vision",
|
|
6242
6095
|
summary: "Pick the best `count` images out of 2+ candidates with a vision LLM, judged against a prompt. Outputs a passthrough subset of the input refs (no new pixels) plus the model's comparative reasoning.",
|
|
6243
6096
|
when_to_use: "Use after fanning out several image_generate variants (or any pool of 2+ images) to keep only the strongest before expensive downstream steps \u2014 video generation, reference sheets, final delivery. `count` fixes the output size, so `images#0`\u2026`images#count-1` are always safe to wire. Pick `~google/gemini-flash-latest` for cheap/fast picks and `~google/gemini-pro-latest` for harder aesthetic judgement.",
|
|
6244
|
-
inputs:
|
|
6245
|
-
params:
|
|
6246
|
-
model:
|
|
6247
|
-
prompt:
|
|
6248
|
-
count:
|
|
6249
|
-
temperature:
|
|
6250
|
-
max_tokens:
|
|
6097
|
+
inputs: z22.object({ images: z22.array(ImageRef).min(2) }).loose(),
|
|
6098
|
+
params: z22.object({
|
|
6099
|
+
model: z22.enum(IMAGE_SELECT_MODELS),
|
|
6100
|
+
prompt: z22.string().min(1),
|
|
6101
|
+
count: z22.number().int().min(1).default(1),
|
|
6102
|
+
temperature: z22.number().min(0).max(2).optional(),
|
|
6103
|
+
max_tokens: z22.number().int().positive().optional()
|
|
6251
6104
|
}).strict(),
|
|
6252
|
-
outputs:
|
|
6105
|
+
outputs: z22.object({ images: z22.array(ImageRef).min(1), reasoning: TextRef }).strict(),
|
|
6253
6106
|
outputKinds: { images: "image", reasoning: "text" },
|
|
6254
6107
|
cost: () => ({ credits: 1, seconds_estimate: 5 }),
|
|
6255
6108
|
// Arity is only knowable at validate time when `images` is a literal array
|
|
@@ -6274,34 +6127,34 @@ var imageSelectNode = delegated({
|
|
|
6274
6127
|
});
|
|
6275
6128
|
|
|
6276
6129
|
// src/engine/nodes/remote/music.ts
|
|
6277
|
-
import { z as
|
|
6130
|
+
import { z as z23 } from "zod";
|
|
6278
6131
|
var MUSIC_MODELS = ["elevenlabs/music-v1", "elevenlabs/video-background-music-v1"];
|
|
6279
|
-
var MusicParams =
|
|
6280
|
-
model:
|
|
6132
|
+
var MusicParams = z23.object({
|
|
6133
|
+
model: z23.enum(MUSIC_MODELS),
|
|
6281
6134
|
/** Free-form prompt. Used by `elevenlabs/music-v1` (compose-detailed). */
|
|
6282
|
-
prompt:
|
|
6135
|
+
prompt: z23.string().optional(),
|
|
6283
6136
|
/**
|
|
6284
6137
|
* Structured composition plan (intro / hook / verse / outro sections with
|
|
6285
6138
|
* per-section styles + durations). Mutually exclusive with `prompt`.
|
|
6286
6139
|
*/
|
|
6287
|
-
composition_plan:
|
|
6140
|
+
composition_plan: z23.record(z23.string(), z23.unknown()).optional(),
|
|
6288
6141
|
/** Target length when using `prompt`. 3000–454545ms (capped by the $10 per-node cost limit). */
|
|
6289
|
-
music_length_ms:
|
|
6290
|
-
seed:
|
|
6142
|
+
music_length_ms: z23.number().int().min(3e3).max(ELEVENLABS_MAX_MUSIC_LENGTH_MS).optional(),
|
|
6143
|
+
seed: z23.number().int().optional(),
|
|
6291
6144
|
/** Prompt mode only — forces an instrumental (no vocals) track. */
|
|
6292
|
-
force_instrumental:
|
|
6145
|
+
force_instrumental: z23.boolean().optional(),
|
|
6293
6146
|
/** composition_plan only — honor exact section durations. */
|
|
6294
|
-
respect_sections_durations:
|
|
6147
|
+
respect_sections_durations: z23.boolean().optional(),
|
|
6295
6148
|
/** Emit word-level timestamps alongside the audio. */
|
|
6296
|
-
with_timestamps:
|
|
6149
|
+
with_timestamps: z23.boolean().optional(),
|
|
6297
6150
|
/**
|
|
6298
6151
|
* video-to-music only — short description of the desired score
|
|
6299
6152
|
* ("upbeat synth, fast cuts, 80s") used to bias the model.
|
|
6300
6153
|
*/
|
|
6301
|
-
description:
|
|
6154
|
+
description: z23.string().max(1e3).optional(),
|
|
6302
6155
|
/** video-to-music only — up to 10 style tags. */
|
|
6303
|
-
tags:
|
|
6304
|
-
output_format:
|
|
6156
|
+
tags: z23.array(z23.string()).max(10).optional(),
|
|
6157
|
+
output_format: z23.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
|
|
6305
6158
|
}).strict();
|
|
6306
6159
|
var musicNode = delegated({
|
|
6307
6160
|
id: "music",
|
|
@@ -6309,9 +6162,9 @@ var musicNode = delegated({
|
|
|
6309
6162
|
category: "audio",
|
|
6310
6163
|
summary: "Generate music for ad creatives and website video content. `elevenlabs/music-v1` composes from a text prompt or structured composition plan; `elevenlabs/video-background-music-v1` scores an existing video clip provided via `inputs.video`.",
|
|
6311
6164
|
when_to_use: "Use to produce background music or a full score for video ads, hero-section reels, or any motion content. Prefer the video-to-music model when you already have a cut and want music timed to it; use compose-detailed when you have only a brief or want section-level control (intro / hook / outro). Pair the resulting audio with `video_generate` or `video_lipsync` at compose time.",
|
|
6312
|
-
inputs:
|
|
6165
|
+
inputs: z23.object({ video: VideoRef.optional() }).loose(),
|
|
6313
6166
|
params: MusicParams,
|
|
6314
|
-
outputs:
|
|
6167
|
+
outputs: z23.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
|
|
6315
6168
|
outputKinds: { audio: "audio", timestamps: "json" },
|
|
6316
6169
|
cost: ({ params }) => {
|
|
6317
6170
|
const seconds = params.music_length_ms ? Math.ceil(params.music_length_ms / 1e3) : 30;
|
|
@@ -6342,25 +6195,25 @@ var musicNode = delegated({
|
|
|
6342
6195
|
});
|
|
6343
6196
|
|
|
6344
6197
|
// src/engine/nodes/remote/soundEffect.ts
|
|
6345
|
-
import { z as
|
|
6198
|
+
import { z as z24 } from "zod";
|
|
6346
6199
|
var SOUND_EFFECT_MODELS = ["elevenlabs/eleven_text_to_sound_v2"];
|
|
6347
|
-
var SoundEffectParams =
|
|
6348
|
-
model:
|
|
6200
|
+
var SoundEffectParams = z24.object({
|
|
6201
|
+
model: z24.enum(SOUND_EFFECT_MODELS),
|
|
6349
6202
|
/** Prompt describing the SFX ("metal door slam", "soft UI tap", "ocean waves"). */
|
|
6350
|
-
text:
|
|
6203
|
+
text: z24.string().min(1),
|
|
6351
6204
|
/**
|
|
6352
6205
|
* Target length in seconds. 0.5–30. Leave unset to let the model pick the
|
|
6353
6206
|
* natural length for the described effect.
|
|
6354
6207
|
*/
|
|
6355
|
-
duration_seconds:
|
|
6208
|
+
duration_seconds: z24.number().min(0.5).max(30).optional(),
|
|
6356
6209
|
/**
|
|
6357
6210
|
* 0–1. Higher = stick closer to the prompt at the cost of variety; lower
|
|
6358
6211
|
* = let the model interpret more freely. Defaults to 0.3 on the provider.
|
|
6359
6212
|
*/
|
|
6360
|
-
prompt_influence:
|
|
6213
|
+
prompt_influence: z24.number().min(0).max(1).optional(),
|
|
6361
6214
|
/** Only valid on `eleven_text_to_sound_v2` — produce a seamless loop. */
|
|
6362
|
-
loop:
|
|
6363
|
-
output_format:
|
|
6215
|
+
loop: z24.boolean().optional(),
|
|
6216
|
+
output_format: z24.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
|
|
6364
6217
|
}).strict();
|
|
6365
6218
|
var soundEffectNode = delegated({
|
|
6366
6219
|
id: "sound_effect",
|
|
@@ -6368,9 +6221,9 @@ var soundEffectNode = delegated({
|
|
|
6368
6221
|
category: "audio",
|
|
6369
6222
|
summary: "Generate short sound effects from a text prompt via ElevenLabs Text-to-Sound. Use for whooshes, impacts, UI clicks, ambient beds, or signature stingers in ad creatives and product videos.",
|
|
6370
6223
|
when_to_use: "Reach for this when you need a punch-in SFX layered against `video_generate` or `hyperframe_render` output \u2014 e.g. a logo whoosh on a hero shot, a click on a CTA cut, a swelling ambient bed under VO. Set `loop: true` for atmospheric beds that need to tile under longer footage; leave `duration_seconds` unset and the model picks a natural length.",
|
|
6371
|
-
inputs:
|
|
6224
|
+
inputs: z24.object({}).loose(),
|
|
6372
6225
|
params: SoundEffectParams,
|
|
6373
|
-
outputs:
|
|
6226
|
+
outputs: z24.object({ audio: AudioRef }).strict(),
|
|
6374
6227
|
outputKinds: { audio: "audio" },
|
|
6375
6228
|
cost: ({ params }) => {
|
|
6376
6229
|
const seconds = params.duration_seconds ?? 5;
|
|
@@ -6379,7 +6232,7 @@ var soundEffectNode = delegated({
|
|
|
6379
6232
|
});
|
|
6380
6233
|
|
|
6381
6234
|
// src/engine/nodes/remote/textGenerate.ts
|
|
6382
|
-
import { z as
|
|
6235
|
+
import { z as z25 } from "zod";
|
|
6383
6236
|
var TEXT_GENERATE_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
6384
6237
|
var textGenerateNode = delegated({
|
|
6385
6238
|
id: "text_generate",
|
|
@@ -6387,58 +6240,58 @@ var textGenerateNode = delegated({
|
|
|
6387
6240
|
category: "language",
|
|
6388
6241
|
summary: "Single-turn LLM text generation via OpenRouter. Returns a text response.",
|
|
6389
6242
|
when_to_use: 'Use for any short text generation step in a canvas \u2014 ad copy, hooks, headlines, JSON outputs for downstream nodes. Pick `~google/gemini-flash-latest` for cheap/fast work and `~google/gemini-pro-latest` for harder reasoning. When the output must be JSON for a downstream `{{slot}}` (e.g. the ad-blueprint transform), set `response_format: "json_object"` so the model returns clean JSON with no markdown fences or prose. Set `web_search: true` to let the model search the live web first (OpenRouter `:online`) \u2014 useful when the transform must adapt copy to the target brand\'s real facts (current pricing, the trust signals it actually has) rather than guess.',
|
|
6390
|
-
inputs:
|
|
6391
|
-
params:
|
|
6392
|
-
model:
|
|
6393
|
-
prompt:
|
|
6394
|
-
system:
|
|
6395
|
-
response_format:
|
|
6396
|
-
web_search:
|
|
6397
|
-
temperature:
|
|
6398
|
-
max_tokens:
|
|
6243
|
+
inputs: z25.object({}).loose(),
|
|
6244
|
+
params: z25.object({
|
|
6245
|
+
model: z25.enum(TEXT_GENERATE_MODELS),
|
|
6246
|
+
prompt: z25.string().min(1),
|
|
6247
|
+
system: z25.string().optional(),
|
|
6248
|
+
response_format: z25.enum(["text", "json_object"]).optional(),
|
|
6249
|
+
web_search: z25.boolean().optional(),
|
|
6250
|
+
temperature: z25.number().min(0).max(2).optional(),
|
|
6251
|
+
max_tokens: z25.number().int().positive().optional()
|
|
6399
6252
|
}).strict(),
|
|
6400
|
-
outputs:
|
|
6253
|
+
outputs: z25.object({ text: TextRef }).strict(),
|
|
6401
6254
|
outputKinds: { text: "text" },
|
|
6402
6255
|
cost: () => ({ credits: 1, seconds_estimate: 3 })
|
|
6403
6256
|
});
|
|
6404
6257
|
|
|
6405
6258
|
// src/engine/nodes/remote/tts.ts
|
|
6406
|
-
import { z as
|
|
6259
|
+
import { z as z26 } from "zod";
|
|
6407
6260
|
var TTS_MODELS = ["elevenlabs/eleven_v3"];
|
|
6408
|
-
var TtsVoiceSettings =
|
|
6409
|
-
stability:
|
|
6410
|
-
similarity_boost:
|
|
6411
|
-
style:
|
|
6412
|
-
use_speaker_boost:
|
|
6413
|
-
speed:
|
|
6261
|
+
var TtsVoiceSettings = z26.object({
|
|
6262
|
+
stability: z26.number().min(0).max(1).optional(),
|
|
6263
|
+
similarity_boost: z26.number().min(0).max(1).optional(),
|
|
6264
|
+
style: z26.number().min(0).max(1).optional(),
|
|
6265
|
+
use_speaker_boost: z26.boolean().optional(),
|
|
6266
|
+
speed: z26.number().min(0.25).max(4).optional()
|
|
6414
6267
|
}).strict();
|
|
6415
|
-
var TtsPronunciationLocator =
|
|
6416
|
-
pronunciation_dictionary_id:
|
|
6417
|
-
version_id:
|
|
6268
|
+
var TtsPronunciationLocator = z26.object({
|
|
6269
|
+
pronunciation_dictionary_id: z26.string().min(1),
|
|
6270
|
+
version_id: z26.string().nullable().optional()
|
|
6418
6271
|
}).strict();
|
|
6419
|
-
var TtsParams =
|
|
6420
|
-
model:
|
|
6421
|
-
text:
|
|
6422
|
-
voice:
|
|
6272
|
+
var TtsParams = z26.object({
|
|
6273
|
+
model: z26.enum(TTS_MODELS),
|
|
6274
|
+
text: z26.string().min(1).max(ELEVENLABS_MAX_TEXT_CHARS),
|
|
6275
|
+
voice: z26.string().min(1),
|
|
6423
6276
|
/** Provider output_format (mp3 family only — assets are stored as audio/mpeg). */
|
|
6424
|
-
output_format:
|
|
6425
|
-
seed:
|
|
6277
|
+
output_format: z26.enum(ELEVENLABS_OUTPUT_FORMATS).optional(),
|
|
6278
|
+
seed: z26.number().int().min(0).max(4294967295).optional(),
|
|
6426
6279
|
// Top-level shortcuts; structured form is `voice_settings`.
|
|
6427
|
-
stability:
|
|
6428
|
-
similarity_boost:
|
|
6280
|
+
stability: z26.number().min(0).max(1).optional(),
|
|
6281
|
+
similarity_boost: z26.number().min(0).max(1).optional(),
|
|
6429
6282
|
voice_settings: TtsVoiceSettings.optional(),
|
|
6430
6283
|
/** ISO 639-1 language code. eleven_v3 supports language hints. */
|
|
6431
|
-
language_code:
|
|
6432
|
-
pronunciation_dictionary_locators:
|
|
6433
|
-
apply_text_normalization:
|
|
6284
|
+
language_code: z26.string().optional(),
|
|
6285
|
+
pronunciation_dictionary_locators: z26.array(TtsPronunciationLocator).max(3).optional(),
|
|
6286
|
+
apply_text_normalization: z26.enum(["auto", "on", "off"]).optional(),
|
|
6434
6287
|
/** Currently Japanese-only. Adds latency. */
|
|
6435
|
-
apply_language_text_normalization:
|
|
6288
|
+
apply_language_text_normalization: z26.boolean().optional(),
|
|
6436
6289
|
/**
|
|
6437
6290
|
* When true, hits `/v1/text-to-speech/{voice_id}/with-timestamps` and
|
|
6438
6291
|
* adds a `timestamps` output (character-level alignment) for caption
|
|
6439
6292
|
* rendering, lipsync, and beat-matched cuts.
|
|
6440
6293
|
*/
|
|
6441
|
-
with_timestamps:
|
|
6294
|
+
with_timestamps: z26.boolean().optional()
|
|
6442
6295
|
}).strict();
|
|
6443
6296
|
var ttsNode = delegated({
|
|
6444
6297
|
id: "tts",
|
|
@@ -6446,9 +6299,9 @@ var ttsNode = delegated({
|
|
|
6446
6299
|
category: "audio",
|
|
6447
6300
|
summary: "Single-voice text-to-speech via ElevenLabs Eleven v3. Optional character-level timestamps for caption rendering and beat-matched cuts.",
|
|
6448
6301
|
when_to_use: "Use for single-speaker VO \u2014 ad reads, hero-section narration, product walkthroughs. Reach for `dialogue` when you need multiple voices in one stitched track. Set `with_timestamps: true` when downstream needs character-level alignment (captions, lipsync).",
|
|
6449
|
-
inputs:
|
|
6302
|
+
inputs: z26.object({}).loose(),
|
|
6450
6303
|
params: TtsParams,
|
|
6451
|
-
outputs:
|
|
6304
|
+
outputs: z26.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
|
|
6452
6305
|
outputKinds: { audio: "audio", timestamps: "json" },
|
|
6453
6306
|
cost: ({ params }) => ({
|
|
6454
6307
|
credits: Math.max(1, Math.ceil(params.text.length * 15e-4)),
|
|
@@ -6457,23 +6310,23 @@ var ttsNode = delegated({
|
|
|
6457
6310
|
});
|
|
6458
6311
|
|
|
6459
6312
|
// src/engine/nodes/remote/video.ts
|
|
6460
|
-
import { z as
|
|
6313
|
+
import { z as z27 } from "zod";
|
|
6461
6314
|
var VIDEO_GENERATE_MODELS = ["bytedance/seedance-2.0", "google/veo-3.1-fast"];
|
|
6462
|
-
var VideoGenerateParams =
|
|
6463
|
-
model:
|
|
6464
|
-
prompt:
|
|
6465
|
-
duration:
|
|
6466
|
-
resolution:
|
|
6315
|
+
var VideoGenerateParams = z27.object({
|
|
6316
|
+
model: z27.enum(VIDEO_GENERATE_MODELS),
|
|
6317
|
+
prompt: z27.string().min(1),
|
|
6318
|
+
duration: z27.number().int().positive().optional(),
|
|
6319
|
+
resolution: z27.string().optional(),
|
|
6467
6320
|
// Union of ratios accepted by at least one curated model (registry gates
|
|
6468
6321
|
// per-model). 3:2/2:3 are deliberately absent: no registered model takes them.
|
|
6469
|
-
aspect_ratio:
|
|
6470
|
-
generate_audio:
|
|
6471
|
-
seed:
|
|
6322
|
+
aspect_ratio: z27.enum(["16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "9:21"]).optional(),
|
|
6323
|
+
generate_audio: z27.boolean().optional(),
|
|
6324
|
+
seed: z27.number().int().nonnegative().optional(),
|
|
6472
6325
|
// Veo-only passthroughs (routed via `provider.options.google-vertex.parameters`).
|
|
6473
|
-
negative_prompt:
|
|
6474
|
-
person_generation:
|
|
6475
|
-
enhance_prompt:
|
|
6476
|
-
conditioning_scale:
|
|
6326
|
+
negative_prompt: z27.string().optional(),
|
|
6327
|
+
person_generation: z27.string().optional(),
|
|
6328
|
+
enhance_prompt: z27.boolean().optional(),
|
|
6329
|
+
conditioning_scale: z27.number().optional()
|
|
6477
6330
|
}).strict();
|
|
6478
6331
|
var videoGenerateNode = delegated({
|
|
6479
6332
|
id: "video_generate",
|
|
@@ -6481,23 +6334,23 @@ var videoGenerateNode = delegated({
|
|
|
6481
6334
|
category: "video",
|
|
6482
6335
|
summary: "Generate video for ad creatives. Two curated models: `bytedance/seedance-2.0` (production quality, photorealistic humans via fal.ai) and `google/veo-3.1-fast` (cheap/fast for iteration and tests). Async with polling.",
|
|
6483
6336
|
when_to_use: "Use `bytedance/seedance-2.0` for final ad output (photoreal subjects, image-to-video with first/last frames). Use `google/veo-3.1-fast` while iterating to keep cost low. Each model has different supported durations, resolutions, and aspect ratios \u2014 see the README per-model section.",
|
|
6484
|
-
inputs:
|
|
6337
|
+
inputs: z27.object({
|
|
6485
6338
|
first_frame: ImageRef.optional(),
|
|
6486
6339
|
last_frame: ImageRef.optional(),
|
|
6487
6340
|
reference: ImageRef.optional()
|
|
6488
6341
|
}).loose(),
|
|
6489
6342
|
params: VideoGenerateParams,
|
|
6490
|
-
outputs:
|
|
6343
|
+
outputs: z27.object({ video: VideoRef }).strict(),
|
|
6491
6344
|
outputKinds: { video: "video" },
|
|
6492
6345
|
cost: () => ({ credits: 50, seconds_estimate: 120 })
|
|
6493
6346
|
});
|
|
6494
6347
|
|
|
6495
6348
|
// src/engine/nodes/remote/videoBackgroundRemove.ts
|
|
6496
|
-
import { z as
|
|
6497
|
-
var VideoBackgroundRemoveParams =
|
|
6498
|
-
model:
|
|
6499
|
-
edge_refinement:
|
|
6500
|
-
output_codec:
|
|
6349
|
+
import { z as z28 } from "zod";
|
|
6350
|
+
var VideoBackgroundRemoveParams = z28.object({
|
|
6351
|
+
model: z28.literal("fal/veed-video-background-removal").optional().default("fal/veed-video-background-removal"),
|
|
6352
|
+
edge_refinement: z28.boolean().optional().default(true),
|
|
6353
|
+
output_codec: z28.enum(["vp9", "h264"]).optional().default("vp9")
|
|
6501
6354
|
}).strict();
|
|
6502
6355
|
var videoBackgroundRemoveNode = delegated({
|
|
6503
6356
|
id: "video_background_remove",
|
|
@@ -6505,18 +6358,18 @@ var videoBackgroundRemoveNode = delegated({
|
|
|
6505
6358
|
category: "video",
|
|
6506
6359
|
summary: "Remove the background from a video and return a transparent VP9-with-alpha WebM (or H264 RGB+alpha pair). Drops directly into a hyperframe composition as `<video src='...'>` for chroma-keyed picture-in-picture overlays. Powered by fal.ai `veed/video-background-removal/fast`.",
|
|
6507
6360
|
when_to_use: "Use when you need a talking-head or subject to float over a custom background in a hyperframe composition. Pair with hyperframe_render(composition: screencast-with-talker) for screencast-with-narrator videos. Output is `video/webm` with alpha \u2014 feed straight into `<video src>` in a composition.",
|
|
6508
|
-
inputs:
|
|
6361
|
+
inputs: z28.object({
|
|
6509
6362
|
video: VideoRef
|
|
6510
6363
|
}).strict(),
|
|
6511
6364
|
params: VideoBackgroundRemoveParams,
|
|
6512
|
-
outputs:
|
|
6365
|
+
outputs: z28.object({ video: VideoRef }).strict(),
|
|
6513
6366
|
outputKinds: { video: "video" },
|
|
6514
6367
|
// $0.012 per 30 frames (edge refinement on) — assume ~30fps; refine via fal dashboard.
|
|
6515
6368
|
cost: () => ({ credits: 50, seconds_estimate: 60 })
|
|
6516
6369
|
});
|
|
6517
6370
|
|
|
6518
6371
|
// src/engine/nodes/remote/videoDeconstruct.ts
|
|
6519
|
-
import { z as
|
|
6372
|
+
import { z as z29 } from "zod";
|
|
6520
6373
|
var VIDEO_DECONSTRUCT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
6521
6374
|
var videoDeconstructNode = delegated({
|
|
6522
6375
|
id: "video_deconstruct",
|
|
@@ -6524,34 +6377,34 @@ var videoDeconstructNode = delegated({
|
|
|
6524
6377
|
category: "video",
|
|
6525
6378
|
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).',
|
|
6526
6379
|
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.',
|
|
6527
|
-
inputs:
|
|
6528
|
-
params:
|
|
6529
|
-
model:
|
|
6530
|
-
mode:
|
|
6531
|
-
language:
|
|
6532
|
-
max_scenes:
|
|
6533
|
-
focus:
|
|
6534
|
-
start_s:
|
|
6535
|
-
end_s:
|
|
6380
|
+
inputs: z29.object({ video: VideoRef }).loose(),
|
|
6381
|
+
params: z29.object({
|
|
6382
|
+
model: z29.enum(VIDEO_DECONSTRUCT_MODELS),
|
|
6383
|
+
mode: z29.enum(["full", "index"]).optional(),
|
|
6384
|
+
language: z29.string().min(2).max(8).optional(),
|
|
6385
|
+
max_scenes: z29.number().int().min(1).max(60).optional(),
|
|
6386
|
+
focus: z29.string().optional(),
|
|
6387
|
+
start_s: z29.number().min(0).optional(),
|
|
6388
|
+
end_s: z29.number().positive().optional(),
|
|
6536
6389
|
// Real visual shot-cut timestamps (absolute seconds), detected locally with
|
|
6537
6390
|
// ffmpeg before the deconstruct. The backend SNAPS its LLM scene boundaries
|
|
6538
6391
|
// onto these and SPLITS any scene that spans one, so a scene's frames never
|
|
6539
6392
|
// straddle a hard cut. `scaffold-video` populates this; omit for LLM-only cuts.
|
|
6540
|
-
shot_cuts:
|
|
6393
|
+
shot_cuts: z29.array(z29.number().min(0)).max(200).optional(),
|
|
6541
6394
|
// The video model's per-clip ceiling (seconds). A shot longer than this is
|
|
6542
6395
|
// split into seamless continuation sub-scenes (shared splice frame), so long
|
|
6543
6396
|
// shots reproduce in full instead of being truncated. `scaffold-video` sets
|
|
6544
6397
|
// the Seedance ceiling (15); omit to disable length splitting.
|
|
6545
|
-
max_clip_s:
|
|
6398
|
+
max_clip_s: z29.number().positive().max(60).optional(),
|
|
6546
6399
|
// Transcript provider for the blueprint's dialogue/transcript. Default
|
|
6547
6400
|
// Groq Whisper; "deepgram" routes to Nova-3 so words carry punctuation.
|
|
6548
|
-
transcriber:
|
|
6401
|
+
transcriber: z29.enum(["groq", "deepgram"]).optional()
|
|
6549
6402
|
}).strict(),
|
|
6550
|
-
outputs:
|
|
6403
|
+
outputs: z29.object({
|
|
6551
6404
|
analysis: JsonRef,
|
|
6552
6405
|
// Absent in mode:"index" (structure only, no Mux frame extraction).
|
|
6553
|
-
start_frames:
|
|
6554
|
-
end_frames:
|
|
6406
|
+
start_frames: z29.array(ImageRef).min(1).optional(),
|
|
6407
|
+
end_frames: z29.array(ImageRef).min(1).optional(),
|
|
6555
6408
|
transcript: JsonRef
|
|
6556
6409
|
}).strict(),
|
|
6557
6410
|
outputKinds: { analysis: "json", start_frames: "image", end_frames: "image", transcript: "json" },
|
|
@@ -6559,22 +6412,22 @@ var videoDeconstructNode = delegated({
|
|
|
6559
6412
|
});
|
|
6560
6413
|
|
|
6561
6414
|
// src/engine/nodes/remote/videoLipsync.ts
|
|
6562
|
-
import { z as
|
|
6563
|
-
var FalLipsyncParams =
|
|
6564
|
-
model:
|
|
6415
|
+
import { z as z30 } from "zod";
|
|
6416
|
+
var FalLipsyncParams = z30.object({
|
|
6417
|
+
model: z30.literal("fal/veed-lipsync")
|
|
6565
6418
|
}).strict();
|
|
6566
|
-
var VideoLipsyncParams =
|
|
6419
|
+
var VideoLipsyncParams = z30.discriminatedUnion("model", [FalLipsyncParams]);
|
|
6567
6420
|
var videoLipsyncNode = delegated({
|
|
6568
6421
|
id: "video_lipsync",
|
|
6569
6422
|
version: "1.0.0",
|
|
6570
6423
|
category: "video",
|
|
6571
6424
|
summary: "Lip-sync a video to an audio track. Currently backed by VEED via fal.ai (`fal/veed-lipsync`). $0.40/min of output.",
|
|
6572
|
-
inputs:
|
|
6425
|
+
inputs: z30.object({
|
|
6573
6426
|
video: VideoRef,
|
|
6574
6427
|
audio: AudioRef
|
|
6575
6428
|
}).strict(),
|
|
6576
6429
|
params: VideoLipsyncParams,
|
|
6577
|
-
outputs:
|
|
6430
|
+
outputs: z30.object({ video: VideoRef }).strict(),
|
|
6578
6431
|
outputKinds: { video: "video" },
|
|
6579
6432
|
cost: () => ({ credits: 20, seconds_estimate: 120 })
|
|
6580
6433
|
});
|
|
@@ -6583,7 +6436,7 @@ var videoLipsyncNode = delegated({
|
|
|
6583
6436
|
import { mkdtemp as mkdtemp6, readFile as readFile10, rm as rm6 } from "fs/promises";
|
|
6584
6437
|
import { tmpdir as tmpdir6 } from "os";
|
|
6585
6438
|
import path13 from "path";
|
|
6586
|
-
import { z as
|
|
6439
|
+
import { z as z31 } from "zod";
|
|
6587
6440
|
|
|
6588
6441
|
// src/engine/nodes/local/lib/ffmpeg.ts
|
|
6589
6442
|
import { execFile as execFile7 } from "child_process";
|
|
@@ -6662,21 +6515,21 @@ ${detail.slice(-4e3)}`);
|
|
|
6662
6515
|
}
|
|
6663
6516
|
|
|
6664
6517
|
// src/engine/nodes/remote/videoTranscribe.ts
|
|
6665
|
-
var VideoTranscribeParams =
|
|
6666
|
-
language:
|
|
6518
|
+
var VideoTranscribeParams = z31.object({
|
|
6519
|
+
language: z31.string().min(2).max(8).optional(),
|
|
6667
6520
|
// Provider choice is explicit (no env-based silent branching). Default Groq
|
|
6668
6521
|
// Whisper; "deepgram" routes to Deepgram Nova-3, which additionally emits a
|
|
6669
6522
|
// `rich` JSON output with punctuated words + paragraph/sentence grouping.
|
|
6670
|
-
transcriber:
|
|
6523
|
+
transcriber: z31.enum(["groq", "deepgram"]).optional()
|
|
6671
6524
|
}).strict();
|
|
6672
|
-
var VideoTranscribeInputs =
|
|
6525
|
+
var VideoTranscribeInputs = z31.object({
|
|
6673
6526
|
video: VideoRef
|
|
6674
6527
|
}).strict();
|
|
6675
|
-
var VideoTranscribeOutputs =
|
|
6676
|
-
transcript:
|
|
6528
|
+
var VideoTranscribeOutputs = z31.object({
|
|
6529
|
+
transcript: z31.custom(),
|
|
6677
6530
|
// Only emitted by the Deepgram path: full punctuated words + paragraph /
|
|
6678
6531
|
// sentence grouping with speaker indices. Absent for the default Groq path.
|
|
6679
|
-
rich:
|
|
6532
|
+
rich: z31.custom().optional()
|
|
6680
6533
|
}).strict();
|
|
6681
6534
|
var AUDIO_EXTRACT_TIMEOUT_MS = 6e4;
|
|
6682
6535
|
var videoTranscribeNode = defineNode({
|
|
@@ -6761,29 +6614,29 @@ async function tryExtractAudio(inputs, ctx) {
|
|
|
6761
6614
|
}
|
|
6762
6615
|
|
|
6763
6616
|
// src/engine/nodes/remote/voiceSelect.ts
|
|
6764
|
-
import { z as
|
|
6617
|
+
import { z as z32 } from "zod";
|
|
6765
6618
|
var voiceSelectNode = delegated({
|
|
6766
6619
|
id: "voice_select",
|
|
6767
6620
|
version: "1.0.0",
|
|
6768
6621
|
category: "audio",
|
|
6769
6622
|
summary: 'Cast an ElevenLabs voice from a natural-language description (e.g. "warm, authoritative female narrator, American accent"). Lists the account\'s voices and ranks them against the brief, emitting the best `voice_id` as a bare-string text asset plus a ranked `candidates` JSON.',
|
|
6770
6623
|
when_to_use: 'Use to turn a voice description (e.g. from a `video_deconstruct` blueprint\'s `voice_description`) into a usable ElevenLabs voice id, then feed it into a `tts` node by wiring `inputs.voice_ref: $ref:<this>.voice_id` and setting `params.voice: "{{voice_ref}}"` \u2014 the engine splices the id in at run time. Review `candidates` (json) to pick a different voice. Optional `gender`/`age`/`accent`/`language` hints sharpen the ranking.',
|
|
6771
|
-
inputs:
|
|
6772
|
-
params:
|
|
6773
|
-
description:
|
|
6774
|
-
gender:
|
|
6775
|
-
age:
|
|
6776
|
-
accent:
|
|
6777
|
-
language:
|
|
6778
|
-
limit:
|
|
6624
|
+
inputs: z32.object({}).loose(),
|
|
6625
|
+
params: z32.object({
|
|
6626
|
+
description: z32.string().min(1),
|
|
6627
|
+
gender: z32.string().optional(),
|
|
6628
|
+
age: z32.string().optional(),
|
|
6629
|
+
accent: z32.string().optional(),
|
|
6630
|
+
language: z32.string().optional(),
|
|
6631
|
+
limit: z32.number().int().min(1).max(20).optional()
|
|
6779
6632
|
}).strict(),
|
|
6780
|
-
outputs:
|
|
6633
|
+
outputs: z32.object({ voice_id: TextRef, candidates: JsonRef }).strict(),
|
|
6781
6634
|
outputKinds: { voice_id: "text", candidates: "json" },
|
|
6782
6635
|
cost: () => ({ credits: 0, seconds_estimate: 5 })
|
|
6783
6636
|
});
|
|
6784
6637
|
|
|
6785
6638
|
// src/engine/schema/catalog.ts
|
|
6786
|
-
import { z as
|
|
6639
|
+
import { z as z33 } from "zod";
|
|
6787
6640
|
function generateCatalog(registry, opts = {}) {
|
|
6788
6641
|
const entries = registry.all().map((def) => {
|
|
6789
6642
|
const cost = def.cost ? safeCost(def) : void 0;
|
|
@@ -6794,9 +6647,9 @@ function generateCatalog(registry, opts = {}) {
|
|
|
6794
6647
|
summary: def.summary,
|
|
6795
6648
|
when_to_use: def.when_to_use,
|
|
6796
6649
|
location: def.location,
|
|
6797
|
-
inputs:
|
|
6798
|
-
params:
|
|
6799
|
-
outputs:
|
|
6650
|
+
inputs: z33.toJSONSchema(def.inputs, { unrepresentable: "any" }),
|
|
6651
|
+
params: z33.toJSONSchema(def.params, { unrepresentable: "any" }),
|
|
6652
|
+
outputs: z33.toJSONSchema(def.outputs, { unrepresentable: "any" }),
|
|
6800
6653
|
cost_estimate_credits: cost?.credits,
|
|
6801
6654
|
runtime_estimate_seconds: cost?.seconds_estimate
|
|
6802
6655
|
};
|
|
@@ -6873,8 +6726,7 @@ var LOCAL_NODES = [
|
|
|
6873
6726
|
imagemagickNode,
|
|
6874
6727
|
videoTranscribeNode,
|
|
6875
6728
|
fontSpecimenNode,
|
|
6876
|
-
audioTimelineNode
|
|
6877
|
-
collectNode
|
|
6729
|
+
audioTimelineNode
|
|
6878
6730
|
];
|
|
6879
6731
|
var REMOTE_NODES = [
|
|
6880
6732
|
textGenerateNode,
|
|
@@ -6940,8 +6792,6 @@ export {
|
|
|
6940
6792
|
ulid,
|
|
6941
6793
|
isPersistedAssetRef,
|
|
6942
6794
|
collectAssetRefLikes,
|
|
6943
|
-
REF_PREFIX,
|
|
6944
|
-
parseRefExpr,
|
|
6945
6795
|
sha256Hex,
|
|
6946
6796
|
elementMentionKeywords,
|
|
6947
6797
|
toModelSafeImage,
|
|
@@ -6955,4 +6805,4 @@ export {
|
|
|
6955
6805
|
defaultRegistry,
|
|
6956
6806
|
createEngineFromEnv
|
|
6957
6807
|
};
|
|
6958
|
-
//# sourceMappingURL=chunk-
|
|
6808
|
+
//# sourceMappingURL=chunk-VSVGPYJK.js.map
|