@koda-sl/baker-cli 0.437.0-dev.ee2f8e252 → 0.438.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -42
- package/canvas/tiktok-captions-composition/index.html +6 -5
- package/canvas/tiktok-captions-composition/meta.json +0 -5
- package/dist/{chunk-FB3GTXQG.js → chunk-6PP3MBGZ.js} +791 -1067
- package/dist/chunk-6PP3MBGZ.js.map +1 -0
- package/dist/cli.js +5929 -7057
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +0 -10
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-FB3GTXQG.js.map +0 -1
|
@@ -24,9 +24,9 @@ import {
|
|
|
24
24
|
shouldEscalate
|
|
25
25
|
} from "./chunk-F7LXMR73.js";
|
|
26
26
|
|
|
27
|
-
//
|
|
27
|
+
// ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js
|
|
28
28
|
var require_safe_stable_stringify = __commonJS({
|
|
29
|
-
"
|
|
29
|
+
"../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js"(exports, module) {
|
|
30
30
|
"use strict";
|
|
31
31
|
var { hasOwnProperty } = Object.prototype;
|
|
32
32
|
var stringify = configure2();
|
|
@@ -1408,7 +1408,7 @@ function resolveAdaptFormats(params) {
|
|
|
1408
1408
|
return params.formats ?? [];
|
|
1409
1409
|
}
|
|
1410
1410
|
|
|
1411
|
-
//
|
|
1411
|
+
// ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
|
|
1412
1412
|
var import__ = __toESM(require_safe_stable_stringify(), 1);
|
|
1413
1413
|
var configure = import__.default.configure;
|
|
1414
1414
|
var wrapper_default = import__.default;
|
|
@@ -4398,18 +4398,7 @@ var CanvasMetadata = z4.object({
|
|
|
4398
4398
|
// `scaffold-video`) so the editable checklist travels inside the canvas
|
|
4399
4399
|
// instead of only printing to stdout. Ignored by the engine.
|
|
4400
4400
|
todo: z4.unknown().optional(),
|
|
4401
|
-
video: VideoMeta
|
|
4402
|
-
// A static ad of a series (`seriesMemberAd`): the lead ad its picture is an edit of, the
|
|
4403
|
-
// lead's version, and the lead's words as that version drew them, which this ad's replace.
|
|
4404
|
-
// Ignored by the engine; the brief projection reads it.
|
|
4405
|
-
series: z4.object({
|
|
4406
|
-
lead: z4.string().min(1),
|
|
4407
|
-
run_id: z4.string().min(1),
|
|
4408
|
-
copy: z4.array(z4.object({ text: z4.string(), role: z4.string().optional() }).strict()),
|
|
4409
|
-
design: z4.string().optional(),
|
|
4410
|
-
// A translation of the lead: the language (BCP-47) its words are in.
|
|
4411
|
-
language: z4.string().min(2).optional()
|
|
4412
|
-
}).strict().optional()
|
|
4401
|
+
video: VideoMeta
|
|
4413
4402
|
}).strict().optional();
|
|
4414
4403
|
var CanvasSchema = z4.object({
|
|
4415
4404
|
schema: z4.literal("baker-canvas/1"),
|
|
@@ -4640,7 +4629,7 @@ function dfsCycle(u, color, stack, reverseAdj) {
|
|
|
4640
4629
|
|
|
4641
4630
|
// src/engine/engine/validator.ts
|
|
4642
4631
|
import path5 from "path";
|
|
4643
|
-
import { z as
|
|
4632
|
+
import { z as z8 } from "zod";
|
|
4644
4633
|
|
|
4645
4634
|
// src/engine/lib/url.ts
|
|
4646
4635
|
function looksLikeHttpUrl(value) {
|
|
@@ -5408,26 +5397,27 @@ function audienceRefs(value) {
|
|
|
5408
5397
|
return id ? [{ id, name: text(ref?.name) }] : [];
|
|
5409
5398
|
});
|
|
5410
5399
|
}
|
|
5400
|
+
var BUYERS = /purchas|buyer|customer|client|compra|cliente|achat|acheteur|kunde|käufer/i;
|
|
5401
|
+
var LOOKALIKE_NAME = /lookalike|\blal\b|similar|parecid/i;
|
|
5411
5402
|
var COLD = { temperature: "cold", knows: [] };
|
|
5412
|
-
function tierOf(audience, ref
|
|
5403
|
+
function tierOf(audience, ref) {
|
|
5413
5404
|
const subtype = text(audience?.subtype).toUpperCase();
|
|
5414
5405
|
const name = text(audience?.name) || ref.name;
|
|
5415
|
-
|
|
5416
|
-
if (subtype === "LOOKALIKE" || !subtype && read?.lookalike) {
|
|
5406
|
+
if (subtype === "LOOKALIKE" || !subtype && LOOKALIKE_NAME.test(name)) {
|
|
5417
5407
|
return { ...COLD, who: "a lookalike: strangers who resemble the seed" };
|
|
5418
5408
|
}
|
|
5419
|
-
if (
|
|
5409
|
+
if (BUYERS.test(name)) return { temperature: "hot", knows: ["brand", "offer", "product"], who: "buyers" };
|
|
5420
5410
|
if (subtype === "WEBSITE") return { temperature: "warm", knows: ["brand", "offer"], who: "site visitors" };
|
|
5421
5411
|
if (subtype === "CUSTOM") {
|
|
5422
5412
|
return { temperature: "hot", knows: ["brand", "offer", "product"], who: "an uploaded customer list" };
|
|
5423
5413
|
}
|
|
5424
5414
|
return { temperature: "warm", knows: ["brand"], who: "people who engaged with the brand" };
|
|
5425
5415
|
}
|
|
5426
|
-
function classifyAudience(adSet, audiences, campaign
|
|
5416
|
+
function classifyAudience(adSet, audiences, campaign) {
|
|
5427
5417
|
const targeting = asRecord(adSet.targeting) ?? {};
|
|
5428
5418
|
const byId = new Map(audiences.map((audience) => [text(audience.id), audience]));
|
|
5429
5419
|
const includedRefs = audienceRefs(targeting.custom_audiences);
|
|
5430
|
-
const included = includedRefs.map((ref) => tierOf(byId.get(ref.id), ref
|
|
5420
|
+
const included = includedRefs.map((ref) => tierOf(byId.get(ref.id), ref));
|
|
5431
5421
|
const reasons = [
|
|
5432
5422
|
includedRefs.length === 0 ? "No custom audience: Meta finds new people from age, place and interests, so every viewer is a stranger." : `Reaches ${includedRefs.map((ref, i) => `"${ref.name || ref.id}" (${included[i]?.who})`).join(", ")}.`
|
|
5433
5423
|
];
|
|
@@ -5444,7 +5434,7 @@ function classifyAudience(adSet, audiences, campaign, readName) {
|
|
|
5444
5434
|
}
|
|
5445
5435
|
const excludedRefs = audienceRefs(targeting.excluded_custom_audiences);
|
|
5446
5436
|
if (excludedRefs.length > 0) reasons.push(`Keeps out ${quoteNames(excludedRefs)}.`);
|
|
5447
|
-
if (excludedRefs.some((ref) => tierOf(byId.get(ref.id), ref
|
|
5437
|
+
if (excludedRefs.some((ref) => tierOf(byId.get(ref.id), ref).temperature === "hot")) {
|
|
5448
5438
|
reached = belowHot(reached);
|
|
5449
5439
|
reasons.push("Buyers are excluded, so nobody reached has used the product.");
|
|
5450
5440
|
}
|
|
@@ -5548,7 +5538,7 @@ function viewerFromKnowledge(input) {
|
|
|
5548
5538
|
}
|
|
5549
5539
|
|
|
5550
5540
|
// src/engine/scaffold/static-ad-spec.ts
|
|
5551
|
-
import { z as
|
|
5541
|
+
import { z as z7 } from "zod";
|
|
5552
5542
|
|
|
5553
5543
|
// src/engine/scaffold/lib/elements.ts
|
|
5554
5544
|
var ACTOR_SHEET_MODEL = REFERENCE_SHEET_MODEL;
|
|
@@ -5636,72 +5626,7 @@ function staticElementTodo(el, t, label, brief) {
|
|
|
5636
5626
|
}
|
|
5637
5627
|
|
|
5638
5628
|
// src/engine/scaffold/native-surface.ts
|
|
5639
|
-
import { z as z6 } from "zod";
|
|
5640
|
-
|
|
5641
|
-
// src/engine/nodes/local/lib/jev.ts
|
|
5642
5629
|
import { z as z5 } from "zod";
|
|
5643
|
-
var JEV_MODEL = "typesafe/jev-1.13";
|
|
5644
|
-
var ENDPOINT = "https://openrouter.ai/api/v1/systemone";
|
|
5645
|
-
var TIMEOUT_MS = 2500;
|
|
5646
|
-
var Reply = z5.object({
|
|
5647
|
-
answers: z5.record(z5.string(), z5.object({ noul: z5.number() })),
|
|
5648
|
-
usage: z5.object({ cost: z5.number() }).optional()
|
|
5649
|
-
});
|
|
5650
|
-
var Answer = z5.union([
|
|
5651
|
-
z5.object({ choice: z5.string(), probabilities: z5.record(z5.string(), z5.number()), confidence: z5.number() }),
|
|
5652
|
-
z5.object({ noul: z5.number() })
|
|
5653
|
-
]);
|
|
5654
|
-
var AnswersReply = z5.object({
|
|
5655
|
-
answers: z5.record(z5.string(), z5.unknown()),
|
|
5656
|
-
usage: z5.object({ cost: z5.number() }).optional()
|
|
5657
|
-
});
|
|
5658
|
-
function readAnswers(body, ids) {
|
|
5659
|
-
const parsed = AnswersReply.safeParse(body);
|
|
5660
|
-
if (!parsed.success) return null;
|
|
5661
|
-
const answers = [];
|
|
5662
|
-
for (const id of ids) {
|
|
5663
|
-
const answer = Answer.safeParse(parsed.data.answers[id]);
|
|
5664
|
-
if (!answer.success) return null;
|
|
5665
|
-
answers.push(
|
|
5666
|
-
"choice" in answer.data ? { choice: answer.data.choice, probabilities: answer.data.probabilities, confidence: answer.data.confidence } : { noul: answer.data.noul }
|
|
5667
|
-
);
|
|
5668
|
-
}
|
|
5669
|
-
return { answers, costUsd: parsed.data.usage?.cost ?? 0 };
|
|
5670
|
-
}
|
|
5671
|
-
async function askJevQuestions(questions, onSpend) {
|
|
5672
|
-
const key = openRouterKey();
|
|
5673
|
-
if (questions.length === 0) return [];
|
|
5674
|
-
if (!key) return null;
|
|
5675
|
-
const ids = questions.map((_, i) => `q${i}`);
|
|
5676
|
-
try {
|
|
5677
|
-
const response = await fetch(ENDPOINT, {
|
|
5678
|
-
method: "POST",
|
|
5679
|
-
headers: { authorization: `Bearer ${key}`, "content-type": "application/json" },
|
|
5680
|
-
body: JSON.stringify({
|
|
5681
|
-
model: JEV_MODEL,
|
|
5682
|
-
state: {},
|
|
5683
|
-
questions: Object.fromEntries(questions.map((q, i) => [ids[i], q]))
|
|
5684
|
-
}),
|
|
5685
|
-
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
5686
|
-
});
|
|
5687
|
-
if (!response.ok) return null;
|
|
5688
|
-
const read = readAnswers(await response.json(), ids);
|
|
5689
|
-
if (!read) return null;
|
|
5690
|
-
onSpend?.({ model: JEV_MODEL, inputTokens: 0, outputTokens: 0, costUsd: read.costUsd });
|
|
5691
|
-
return read.answers;
|
|
5692
|
-
} catch {
|
|
5693
|
-
return null;
|
|
5694
|
-
}
|
|
5695
|
-
}
|
|
5696
|
-
async function askJev(questions, onSpend) {
|
|
5697
|
-
const answers = await askJevQuestions(
|
|
5698
|
-
questions.map((q) => ({ type: "noul", instructions: q.instructions })),
|
|
5699
|
-
onSpend
|
|
5700
|
-
);
|
|
5701
|
-
if (!answers) return null;
|
|
5702
|
-
const nouls = answers.flatMap((a) => "noul" in a ? [a.noul] : []);
|
|
5703
|
-
return nouls.length === answers.length ? nouls : null;
|
|
5704
|
-
}
|
|
5705
5630
|
|
|
5706
5631
|
// src/engine/scaffold/review-platforms.ts
|
|
5707
5632
|
var REVIEW_PLATFORMS = [
|
|
@@ -5716,40 +5641,24 @@ var REVIEW_PLATFORMS = [
|
|
|
5716
5641
|
{ name: "Google", domain: "google.com", pattern: /\bgoogle\b/i, reviewOnly: false },
|
|
5717
5642
|
{ name: "the App Store", domain: "apple.com", pattern: /\bapp ?store\b/i, reviewOnly: false }
|
|
5718
5643
|
];
|
|
5719
|
-
var
|
|
5644
|
+
var RATING_WORDS = /★|\b(?:reviews?|reseñas?|rese[ñn]as?|ratings?|rated|valoraci[oó]n(?:es)?|opiniones|estrellas|stars?|avis|bewertung(?:en)?|verificad[ao]s?|verified|recensioni|avalia[cç][oõ]es)\b|\d[.,]\d\s*(?:\/\s*5|★)/i;
|
|
5720
5645
|
function namedPlatform(text2) {
|
|
5721
5646
|
return REVIEW_PLATFORMS.find((p) => p.pattern.test(text2)) ?? null;
|
|
5722
5647
|
}
|
|
5723
5648
|
function claimedPlatform(text2) {
|
|
5724
|
-
|
|
5725
|
-
return platform && (platform.reviewOnly || RATING_SIGN.test(text2)) ? platform : null;
|
|
5726
|
-
}
|
|
5727
|
-
var CITES_PLATFORM = 0.5;
|
|
5728
|
-
var CITATION_QUESTION = "Does `line` cite `platform` as the source of a rating or of customers' reviews (stars, a score, reviews or opinions on it), in whatever language it is written? Only naming the platform (download it on, find us on, search on, sign in with) is not citing it for reviews.";
|
|
5729
|
-
function jevCitationJudge(onSpend) {
|
|
5730
|
-
return (items) => askJev(
|
|
5731
|
-
items.map((i) => ({ instructions: { line: i.line, platform: i.platform, question: CITATION_QUESTION } })),
|
|
5732
|
-
onSpend
|
|
5733
|
-
);
|
|
5734
|
-
}
|
|
5735
|
-
async function citedPlatforms(lines, judge = jevCitationJudge()) {
|
|
5736
|
-
const cited = /* @__PURE__ */ new Map();
|
|
5737
|
-
const asked = [];
|
|
5738
|
-
for (const line of new Set(lines)) {
|
|
5739
|
-
const shown = claimedPlatform(line);
|
|
5740
|
-
const platform = namedPlatform(line);
|
|
5741
|
-
if (shown) cited.set(line, shown);
|
|
5742
|
-
else if (platform) asked.push({ line, platform });
|
|
5743
|
-
}
|
|
5744
|
-
if (asked.length === 0) return cited;
|
|
5745
|
-
const judged = await judge(asked.map((a) => ({ line: a.line, platform: a.platform.name }))).catch(() => null);
|
|
5746
|
-
asked.forEach((a, k) => {
|
|
5747
|
-
if ((judged?.[k] ?? 0) >= CITES_PLATFORM) cited.set(a.line, a.platform);
|
|
5748
|
-
});
|
|
5749
|
-
return cited;
|
|
5649
|
+
return REVIEW_PLATFORMS.find((p) => p.pattern.test(text2) && (p.reviewOnly || RATING_WORDS.test(text2))) ?? null;
|
|
5750
5650
|
}
|
|
5751
|
-
function
|
|
5752
|
-
|
|
5651
|
+
function ratingFigures(text2) {
|
|
5652
|
+
const out = /* @__PURE__ */ new Set();
|
|
5653
|
+
for (const m of text2.matchAll(/(\d[.,]\d)\s*(?:\/\s*5|★|\s?(?:stars?|estrellas|étoiles|sterne))/gi))
|
|
5654
|
+
if (m[1]) out.add(m[1]);
|
|
5655
|
+
for (const m of text2.matchAll(/★\s*(\d[.,]\d)/g)) if (m[1]) out.add(m[1]);
|
|
5656
|
+
for (const m of text2.matchAll(
|
|
5657
|
+
/\+?\s?(\d{1,3}(?:[.,]\d{3})+|\d{2,})\s+(?:reviews|reseñas|opiniones|valoraciones|avis|bewertungen|avaliações)/gi
|
|
5658
|
+
)) {
|
|
5659
|
+
if (m[1]) out.add(m[1]);
|
|
5660
|
+
}
|
|
5661
|
+
return [...out];
|
|
5753
5662
|
}
|
|
5754
5663
|
function figureForms(figure) {
|
|
5755
5664
|
const forms = [figure, figure.replace(/,/g, "."), figure.replace(/\./g, ",")];
|
|
@@ -5758,128 +5667,128 @@ function figureForms(figure) {
|
|
|
5758
5667
|
}
|
|
5759
5668
|
|
|
5760
5669
|
// src/engine/scaffold/native-surface.ts
|
|
5761
|
-
var Hex =
|
|
5762
|
-
var PhotoId =
|
|
5763
|
-
var Author =
|
|
5764
|
-
name:
|
|
5670
|
+
var Hex = z5.string().regex(/^#[0-9a-fA-F]{6}$/, "a colour is a 6-digit hex like #123551").transform((h) => h.toLowerCase());
|
|
5671
|
+
var PhotoId = z5.string().regex(/^PHOTO_\d+$/, "a photo id is PHOTO_1, PHOTO_2\u2026");
|
|
5672
|
+
var Author = z5.object({
|
|
5673
|
+
name: z5.string().trim().min(1),
|
|
5765
5674
|
/** Their handle with the @, as the platform prints it: a real one of ours, never an invented one. */
|
|
5766
|
-
handle:
|
|
5767
|
-
avatar:
|
|
5768
|
-
verified:
|
|
5675
|
+
handle: z5.string().trim().min(2).optional(),
|
|
5676
|
+
avatar: z5.union([z5.literal("logo"), z5.literal("initials"), PhotoId]).default("initials"),
|
|
5677
|
+
verified: z5.boolean().optional(),
|
|
5769
5678
|
/** A line under the name: a review site's "3 reviews · ES", a chat's "online". */
|
|
5770
|
-
subtitle:
|
|
5679
|
+
subtitle: z5.string().trim().min(1).optional()
|
|
5771
5680
|
}).strict();
|
|
5772
|
-
var Photo =
|
|
5681
|
+
var Photo = z5.object({
|
|
5773
5682
|
id: PhotoId,
|
|
5774
5683
|
/** What the photo shows, in plain words: who, doing what, where. */
|
|
5775
|
-
what:
|
|
5684
|
+
what: z5.string().trim().min(1),
|
|
5776
5685
|
/** Its shape on the screen. */
|
|
5777
|
-
ratio:
|
|
5778
|
-
look:
|
|
5686
|
+
ratio: z5.enum(["1:1", "4:3", "3:4", "16:9", "4:5"]).default("4:3"),
|
|
5687
|
+
look: z5.enum(["photo", "illustration"]).default("photo"),
|
|
5779
5688
|
/**
|
|
5780
5689
|
* A picture of ours (a path from the creative's folder, or a URL): used as it is, nothing
|
|
5781
5690
|
* rendered. There is no `avatar`: nothing grounds a rendered photo on an avatar's sheet, so a
|
|
5782
5691
|
* photo naming one would still render a made-up face (the strict schema refuses the key).
|
|
5783
5692
|
*/
|
|
5784
|
-
image:
|
|
5693
|
+
image: z5.string().trim().min(1).optional()
|
|
5785
5694
|
}).strict();
|
|
5786
|
-
var XPost =
|
|
5695
|
+
var XPost = z5.object({
|
|
5787
5696
|
author: Author,
|
|
5788
|
-
text:
|
|
5697
|
+
text: z5.string().trim().min(1),
|
|
5789
5698
|
photo: PhotoId.optional(),
|
|
5790
5699
|
/** The handle this post answers, printed above it as X does ("Replying to @alexbuilds"). */
|
|
5791
|
-
reply_to:
|
|
5700
|
+
reply_to: z5.string().trim().min(2).optional(),
|
|
5792
5701
|
/** How long ago, as X prints it: 2h, 14m, Sep 3. */
|
|
5793
|
-
time:
|
|
5794
|
-
stats:
|
|
5795
|
-
replies:
|
|
5796
|
-
reposts:
|
|
5797
|
-
likes:
|
|
5798
|
-
views:
|
|
5702
|
+
time: z5.string().trim().min(1).optional(),
|
|
5703
|
+
stats: z5.object({
|
|
5704
|
+
replies: z5.string().optional(),
|
|
5705
|
+
reposts: z5.string().optional(),
|
|
5706
|
+
likes: z5.string().optional(),
|
|
5707
|
+
views: z5.string().optional()
|
|
5799
5708
|
}).strict().optional()
|
|
5800
5709
|
}).strict();
|
|
5801
|
-
var ChatCard =
|
|
5710
|
+
var ChatCard = z5.object({
|
|
5802
5711
|
photo: PhotoId.optional(),
|
|
5803
|
-
title:
|
|
5804
|
-
subtitle:
|
|
5712
|
+
title: z5.string().trim().min(1),
|
|
5713
|
+
subtitle: z5.string().trim().min(1).optional(),
|
|
5805
5714
|
/** The card's button, as the chat prints it ("Pagar $899 MXN"). */
|
|
5806
|
-
button:
|
|
5715
|
+
button: z5.string().trim().min(1).optional()
|
|
5807
5716
|
}).strict();
|
|
5808
|
-
var ChatMessage =
|
|
5717
|
+
var ChatMessage = z5.object({
|
|
5809
5718
|
/** `them` is the other side of the chat (the contact in the header), `me` the phone's owner. */
|
|
5810
|
-
from:
|
|
5811
|
-
text:
|
|
5812
|
-
time:
|
|
5719
|
+
from: z5.enum(["them", "me"]),
|
|
5720
|
+
text: z5.string().trim().min(1).optional(),
|
|
5721
|
+
time: z5.string().trim().min(1).optional(),
|
|
5813
5722
|
card: ChatCard.optional()
|
|
5814
5723
|
}).strict().refine((m) => m.text || m.card, "a message has text or a card");
|
|
5815
|
-
var Review =
|
|
5724
|
+
var Review = z5.object({
|
|
5816
5725
|
author: Author,
|
|
5817
|
-
stars:
|
|
5818
|
-
title:
|
|
5819
|
-
text:
|
|
5820
|
-
date:
|
|
5726
|
+
stars: z5.number().int().min(1).max(5).default(5),
|
|
5727
|
+
title: z5.string().trim().min(1).optional(),
|
|
5728
|
+
text: z5.string().trim().min(1),
|
|
5729
|
+
date: z5.string().trim().min(1).optional()
|
|
5821
5730
|
}).strict();
|
|
5822
|
-
var NotesItem =
|
|
5823
|
-
var SlackMessage =
|
|
5731
|
+
var NotesItem = z5.object({ text: z5.string().trim().min(1), checked: z5.boolean().default(false) }).strict();
|
|
5732
|
+
var SlackMessage = z5.object({
|
|
5824
5733
|
author: Author,
|
|
5825
|
-
text:
|
|
5826
|
-
time:
|
|
5827
|
-
reactions:
|
|
5734
|
+
text: z5.string().trim().min(1),
|
|
5735
|
+
time: z5.string().trim().min(1).optional(),
|
|
5736
|
+
reactions: z5.array(z5.object({ emoji: z5.string().trim().min(1), count: z5.number().int().min(1) }).strict()).default([]),
|
|
5828
5737
|
/** "3 replies" under the message. */
|
|
5829
|
-
replies:
|
|
5738
|
+
replies: z5.string().trim().min(1).optional()
|
|
5830
5739
|
}).strict();
|
|
5831
|
-
var Frame =
|
|
5832
|
-
headline:
|
|
5833
|
-
caption:
|
|
5834
|
-
logo:
|
|
5740
|
+
var Frame = z5.object({
|
|
5741
|
+
headline: z5.string().trim().min(1).optional(),
|
|
5742
|
+
caption: z5.string().trim().min(1).optional(),
|
|
5743
|
+
logo: z5.boolean().default(false),
|
|
5835
5744
|
/** Where the frame's logo goes: under the screen, or over it as a badge (a review wall's anchor). */
|
|
5836
|
-
logo_position:
|
|
5745
|
+
logo_position: z5.enum(["under", "over"]).default("under"),
|
|
5837
5746
|
background: Hex.optional()
|
|
5838
5747
|
}).strict().default({ logo: false, logo_position: "under" });
|
|
5839
|
-
var Base = { photos:
|
|
5840
|
-
var NativeSurfaceSchema =
|
|
5841
|
-
|
|
5842
|
-
surface:
|
|
5843
|
-
theme:
|
|
5748
|
+
var Base = { photos: z5.array(Photo).default([]), frame: Frame };
|
|
5749
|
+
var NativeSurfaceSchema = z5.discriminatedUnion("surface", [
|
|
5750
|
+
z5.object({
|
|
5751
|
+
surface: z5.literal("x_post"),
|
|
5752
|
+
theme: z5.enum(["light", "dark"]).default("light"),
|
|
5844
5753
|
/** One post, or a thread: each after the first a reply under it, its line joining them. */
|
|
5845
|
-
posts:
|
|
5754
|
+
posts: z5.array(XPost).min(1).max(4),
|
|
5846
5755
|
...Base
|
|
5847
5756
|
}).strict(),
|
|
5848
|
-
|
|
5849
|
-
surface:
|
|
5757
|
+
z5.object({
|
|
5758
|
+
surface: z5.enum(["whatsapp", "imessage"]),
|
|
5850
5759
|
/** Who the chat is with: the header's name and picture. */
|
|
5851
5760
|
contact: Author,
|
|
5852
|
-
messages:
|
|
5761
|
+
messages: z5.array(ChatMessage).min(1).max(8),
|
|
5853
5762
|
/** The day pill above the messages ("Hoy", "Today"). */
|
|
5854
|
-
day:
|
|
5763
|
+
day: z5.string().trim().min(1).optional(),
|
|
5855
5764
|
/** Drawn inside a phone; else the screen alone. Default true. */
|
|
5856
|
-
device:
|
|
5765
|
+
device: z5.boolean().default(true),
|
|
5857
5766
|
...Base
|
|
5858
5767
|
}).strict(),
|
|
5859
|
-
|
|
5860
|
-
surface:
|
|
5768
|
+
z5.object({
|
|
5769
|
+
surface: z5.enum(["trustpilot", "google_reviews"]),
|
|
5861
5770
|
/** The rating the platform shows for the company: a real one, in `src/content`. */
|
|
5862
|
-
summary:
|
|
5771
|
+
summary: z5.object({ score: z5.string().trim().min(1), count: z5.string().trim().min(1), label: z5.string().optional() }).strict().optional(),
|
|
5863
5772
|
/**
|
|
5864
5773
|
* Every real review the wall shows, the strongest first: the first two are read as the ad's
|
|
5865
5774
|
* copy, whole in the safe area; the rest are the wall they lie on, as large and as sharp, across
|
|
5866
5775
|
* the whole frame and cut by its edges. A wall wins on how many there are (8 to 16), each one a
|
|
5867
5776
|
* real review in the company's own words.
|
|
5868
5777
|
*/
|
|
5869
|
-
reviews:
|
|
5778
|
+
reviews: z5.array(Review).min(1).max(16),
|
|
5870
5779
|
...Base
|
|
5871
5780
|
}).strict(),
|
|
5872
|
-
|
|
5873
|
-
surface:
|
|
5874
|
-
title:
|
|
5875
|
-
items:
|
|
5876
|
-
body:
|
|
5781
|
+
z5.object({
|
|
5782
|
+
surface: z5.literal("notes"),
|
|
5783
|
+
title: z5.string().trim().min(1),
|
|
5784
|
+
items: z5.array(NotesItem).default([]),
|
|
5785
|
+
body: z5.string().trim().min(1).optional(),
|
|
5877
5786
|
...Base
|
|
5878
5787
|
}).strict(),
|
|
5879
|
-
|
|
5880
|
-
surface:
|
|
5881
|
-
channel:
|
|
5882
|
-
messages:
|
|
5788
|
+
z5.object({
|
|
5789
|
+
surface: z5.literal("slack"),
|
|
5790
|
+
channel: z5.string().trim().min(1),
|
|
5791
|
+
messages: z5.array(SlackMessage).min(1).max(6),
|
|
5883
5792
|
...Base
|
|
5884
5793
|
}).strict()
|
|
5885
5794
|
]);
|
|
@@ -5941,8 +5850,8 @@ function authorsOf(surface) {
|
|
|
5941
5850
|
return [];
|
|
5942
5851
|
}
|
|
5943
5852
|
}
|
|
5944
|
-
function nativeChrome(surface, language
|
|
5945
|
-
const t = chromeWords(language
|
|
5853
|
+
function nativeChrome(surface, language) {
|
|
5854
|
+
const t = chromeWords(language);
|
|
5946
5855
|
const handleParts = (h) => h ? [h, h.replace(/^@/, ""), ...h.replace(/^@/, "").split(/[_.]/)] : [];
|
|
5947
5856
|
const authors = authorsOf(surface).flatMap((a) => [a.name, ...handleParts(a.handle), a.subtitle, initials(a.name)]);
|
|
5948
5857
|
const own = (() => {
|
|
@@ -6022,25 +5931,7 @@ function nativeGateProblems(surface, records, brandName) {
|
|
|
6022
5931
|
}
|
|
6023
5932
|
return problems;
|
|
6024
5933
|
}
|
|
6025
|
-
var
|
|
6026
|
-
today: "the day label over today's messages in a chat",
|
|
6027
|
-
online: "the status under a chat contact's name",
|
|
6028
|
-
business: "the label under a WhatsApp business contact's name",
|
|
6029
|
-
message: "the placeholder in a chat's message bar",
|
|
6030
|
-
verified: "the badge on a verified review",
|
|
6031
|
-
excellent: "the word a review site prints beside a 4.5-5 star score",
|
|
6032
|
-
reviewsWord: "the plural noun after the review count (1,200 reviews)",
|
|
6033
|
-
notes: "the title of the phone's notes app",
|
|
6034
|
-
experience: "the label before the date a reviewer had the experience",
|
|
6035
|
-
replying: "the words before the handle a post on X replies to",
|
|
6036
|
-
post: "the title over a thread on X",
|
|
6037
|
-
replyPrompt: "the placeholder of the reply bar under a thread on X",
|
|
6038
|
-
messagePrompt: "the placeholder of Slack's message bar, before the channel name"
|
|
6039
|
-
};
|
|
6040
|
-
var InterfaceWordsSchema = z6.object(
|
|
6041
|
-
Object.fromEntries(Object.keys(WORD_MEANINGS).map((k) => [k, z6.string().trim().min(1)]))
|
|
6042
|
-
);
|
|
6043
|
-
var CHECKED = {
|
|
5934
|
+
var WORDS = {
|
|
6044
5935
|
es: {
|
|
6045
5936
|
today: "Hoy",
|
|
6046
5937
|
online: "en l\xEDnea",
|
|
@@ -6087,29 +5978,9 @@ var CHECKED = {
|
|
|
6087
5978
|
messagePrompt: "Message"
|
|
6088
5979
|
}
|
|
6089
5980
|
};
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
return
|
|
6093
|
-
}
|
|
6094
|
-
var INTERFACE_WORDS_PROMPT_VERSION = 1;
|
|
6095
|
-
function interfaceWordsPrompt(language) {
|
|
6096
|
-
const english = CHECKED.en;
|
|
6097
|
-
return [
|
|
6098
|
-
`These are the words WhatsApp, iMessage, X, Slack, Trustpilot, Google reviews and the phone's notes app print around what people write, in English. Give each as those apps print it in their interface for people whose language is ${language}: the apps' own wording, short, in that language's script and capitalisation.`,
|
|
6099
|
-
"",
|
|
6100
|
-
...Object.entries(WORD_MEANINGS).map(
|
|
6101
|
-
([key, meaning]) => `- "${key}": ${JSON.stringify(english[key])} (${meaning})`
|
|
6102
|
-
),
|
|
6103
|
-
"",
|
|
6104
|
-
`Reply with only this JSON, every key above with its words: {${Object.keys(WORD_MEANINGS).map((k) => `"${k}": "\u2026"`).join(", ")}}`
|
|
6105
|
-
].join("\n");
|
|
6106
|
-
}
|
|
6107
|
-
function readInterfaceWords(answer) {
|
|
6108
|
-
const parsed = InterfaceWordsSchema.safeParse(answer);
|
|
6109
|
-
return parsed.success ? parsed.data : null;
|
|
6110
|
-
}
|
|
6111
|
-
function chromeWords(language, words) {
|
|
6112
|
-
return CHECKED[primary(language)] ?? words ?? CHECKED.en;
|
|
5981
|
+
function chromeWords(language) {
|
|
5982
|
+
const lang = (language ?? "en").slice(0, 2).toLowerCase();
|
|
5983
|
+
return WORDS[lang] ?? WORDS.en;
|
|
6113
5984
|
}
|
|
6114
5985
|
function inked(assets, src, groundIsDark) {
|
|
6115
5986
|
return src && !groundIsDark && assets.lightLogos?.includes(src) ? " inked" : "";
|
|
@@ -6650,7 +6521,7 @@ function frameGround(surface, brand, size2) {
|
|
|
6650
6521
|
function nativeSurfaceHtml(surface, assets, opts) {
|
|
6651
6522
|
const w = opts.width / 2;
|
|
6652
6523
|
const h = opts.height / 2;
|
|
6653
|
-
const t = chromeWords(opts.language
|
|
6524
|
+
const t = chromeWords(opts.language);
|
|
6654
6525
|
const bg = backdrop(surface, opts.brand);
|
|
6655
6526
|
const ink = isDark(bg) ? "#ffffff" : opts.brand.ink ?? "#111111";
|
|
6656
6527
|
const font = assets.font ? `@font-face{font-family:BrandFont;src:url(${assets.font});font-display:block}` : "";
|
|
@@ -6758,7 +6629,7 @@ function layoutBox(safe, w, h) {
|
|
|
6758
6629
|
}
|
|
6759
6630
|
|
|
6760
6631
|
// src/engine/scaffold/static-copy.ts
|
|
6761
|
-
import { z as
|
|
6632
|
+
import { z as z6 } from "zod";
|
|
6762
6633
|
var COPY_DIRECTIVE_HEADER = "TEXT \u2014 RENDER THE COPY EXACTLY:";
|
|
6763
6634
|
var normalize = (s) => typeof s === "string" ? s.trim().toLowerCase() : "";
|
|
6764
6635
|
function textEntries(blueprint) {
|
|
@@ -6800,7 +6671,7 @@ function conceptHasLogo(blueprint) {
|
|
|
6800
6671
|
(l) => normalize(l?.function_in_image) === "advertiser_brand"
|
|
6801
6672
|
);
|
|
6802
6673
|
}
|
|
6803
|
-
var WinningMechanismSchema =
|
|
6674
|
+
var WinningMechanismSchema = z6.object({ kind: z6.string(), device: z6.string(), why_it_works: z6.string().optional() }).loose();
|
|
6804
6675
|
function winningMechanisms(blueprint) {
|
|
6805
6676
|
const raw = blueprint?.winning_mechanisms;
|
|
6806
6677
|
if (!Array.isArray(raw)) return [];
|
|
@@ -6836,58 +6707,58 @@ ${directive}${tail2}`;
|
|
|
6836
6707
|
// src/engine/scaffold/static-ad-spec.ts
|
|
6837
6708
|
var AD_SPEC_SCHEMA = "baker-static-ad/3";
|
|
6838
6709
|
var AD_SPEC_SCHEMAS = ["baker-static-ad/2", AD_SPEC_SCHEMA];
|
|
6839
|
-
var Hex2 =
|
|
6840
|
-
var AdCopyLine =
|
|
6841
|
-
text:
|
|
6842
|
-
lettering:
|
|
6710
|
+
var Hex2 = z7.string().regex(/^#[0-9a-fA-F]{6}$/, "a colour is a 6-digit hex like #123551").transform((h) => h.toLowerCase());
|
|
6711
|
+
var AdCopyLine = z7.object({
|
|
6712
|
+
text: z7.string().trim().min(1),
|
|
6713
|
+
lettering: z7.enum(["in_scene", "designed"]),
|
|
6843
6714
|
/** headline | body | cta | caption | fine_print | label. */
|
|
6844
|
-
role:
|
|
6715
|
+
role: z7.string().optional(),
|
|
6845
6716
|
/** In-scene lines: the object it is written on (a whiteboard, a phone's notes app, a sticky note). */
|
|
6846
|
-
on:
|
|
6717
|
+
on: z7.string().optional(),
|
|
6847
6718
|
/** How it looks: an in-scene line's own lettering (chunky navy marker), a designed line's size or treatment. */
|
|
6848
|
-
look:
|
|
6719
|
+
look: z7.string().optional(),
|
|
6849
6720
|
/** Where it sits, by what is around it ("top of the board", "under the headline"). */
|
|
6850
|
-
position:
|
|
6721
|
+
position: z7.string().optional()
|
|
6851
6722
|
}).strict();
|
|
6852
6723
|
var AD_REFERENCE_ROLES = ["identity", "product", "prop"];
|
|
6853
|
-
var AdReference =
|
|
6724
|
+
var AdReference = z7.object({
|
|
6854
6725
|
/** A role-based label, never a brand's: PERSON_1, ANIMAL_1, PRODUCT_1, BADGE_1, PROP_1. */
|
|
6855
|
-
id:
|
|
6726
|
+
id: z7.string().regex(
|
|
6856
6727
|
/^(PERSON|ANIMAL|PRODUCT|BADGE|PROP)_\d+$/,
|
|
6857
6728
|
"a reference id is PERSON_1, ANIMAL_1, PRODUCT_1, BADGE_1 or PROP_1"
|
|
6858
6729
|
),
|
|
6859
|
-
role:
|
|
6730
|
+
role: z7.enum(AD_REFERENCE_ROLES),
|
|
6860
6731
|
/** The canvas ingest node that holds its picture (bookkeeping, never sent to a model). */
|
|
6861
|
-
slot:
|
|
6732
|
+
slot: z7.string().optional(),
|
|
6862
6733
|
/** How it appears in this ad. */
|
|
6863
|
-
what:
|
|
6734
|
+
what: z7.string().trim().min(1),
|
|
6864
6735
|
/** What must stay exactly as in the picture, when more than the default for its role. */
|
|
6865
|
-
keep:
|
|
6736
|
+
keep: z7.string().optional(),
|
|
6866
6737
|
/** The company avatar cast in this role (`baker avatars list`): a licensed face, never an invented one. */
|
|
6867
|
-
avatar:
|
|
6738
|
+
avatar: z7.string().optional(),
|
|
6868
6739
|
/**
|
|
6869
6740
|
* A picture of ours that shows it (a path from the creative's folder, or a URL): every run
|
|
6870
6741
|
* wires it into the renders (`ref_<id>`), so a picture is named here, never hand-wired.
|
|
6871
6742
|
*/
|
|
6872
|
-
image:
|
|
6743
|
+
image: z7.string().trim().min(1).optional()
|
|
6873
6744
|
}).strict();
|
|
6874
|
-
var Market =
|
|
6745
|
+
var Market = z7.object({
|
|
6875
6746
|
/** ISO country code (ES, MX), or "global" for a version that runs in several. */
|
|
6876
|
-
country:
|
|
6877
|
-
language:
|
|
6878
|
-
currency:
|
|
6747
|
+
country: z7.string().trim().min(2),
|
|
6748
|
+
language: z7.string().trim().min(2).optional(),
|
|
6749
|
+
currency: z7.string().trim().min(1).optional(),
|
|
6879
6750
|
/** How the copy addresses the viewer: tú, usted, vos, você, du, Sie, vous… */
|
|
6880
|
-
address:
|
|
6751
|
+
address: z7.string().trim().min(1).optional()
|
|
6881
6752
|
}).strict();
|
|
6882
|
-
var Viewer =
|
|
6753
|
+
var Viewer = z7.object({
|
|
6883
6754
|
/** Who will see the ad, in the words a marketer would use. */
|
|
6884
|
-
who:
|
|
6755
|
+
who: z7.string().trim().min(1),
|
|
6885
6756
|
market: Market.optional(),
|
|
6886
6757
|
/** What they already know about the brand and the offer; a list (as _definition.md keeps it) reads as one line. */
|
|
6887
|
-
knows:
|
|
6888
|
-
temperature:
|
|
6758
|
+
knows: z7.preprocess((v) => Array.isArray(v) ? v.map(String).join("; ") || void 0 : v, z7.string().optional()),
|
|
6759
|
+
temperature: z7.enum(["cold", "warm", "hot"]).optional(),
|
|
6889
6760
|
/** Where they meet it (feed, Stories, Reels). */
|
|
6890
|
-
placement:
|
|
6761
|
+
placement: z7.string().optional()
|
|
6891
6762
|
}).strict();
|
|
6892
6763
|
var withoutFormatName = (v) => {
|
|
6893
6764
|
if (!v || typeof v !== "object" || Array.isArray(v) || typeof v.concept !== "string")
|
|
@@ -6895,104 +6766,104 @@ var withoutFormatName = (v) => {
|
|
|
6895
6766
|
const { concept: _format, ...scene } = v;
|
|
6896
6767
|
return scene;
|
|
6897
6768
|
};
|
|
6898
|
-
var SceneFields =
|
|
6899
|
-
description:
|
|
6769
|
+
var SceneFields = z7.object({
|
|
6770
|
+
description: z7.string().trim().min(1),
|
|
6900
6771
|
/** A photograph, a designed graphic, or an illustration; plain synonyms ("photography") read as them. */
|
|
6901
|
-
look:
|
|
6772
|
+
look: z7.preprocess(
|
|
6902
6773
|
(v) => typeof v !== "string" ? v : /^photo/i.test(v.trim()) ? "photo" : /^illustrat/i.test(v.trim()) ? "illustration" : /^(design|graphic)/i.test(v.trim()) ? "designed" : v,
|
|
6903
|
-
|
|
6774
|
+
z7.enum(["photo", "designed", "illustration"])
|
|
6904
6775
|
),
|
|
6905
6776
|
/** How a photo was taken, in plain words (handheld at standing height, window light, faint glare). */
|
|
6906
|
-
capture:
|
|
6907
|
-
mood:
|
|
6777
|
+
capture: z7.string().optional(),
|
|
6778
|
+
mood: z7.string().optional(),
|
|
6908
6779
|
/** How the frame is arranged, by what fills each part of it. */
|
|
6909
|
-
layout:
|
|
6780
|
+
layout: z7.string().optional()
|
|
6910
6781
|
}).strict();
|
|
6911
|
-
var Scene =
|
|
6912
|
-
var Brand =
|
|
6913
|
-
name:
|
|
6782
|
+
var Scene = z7.preprocess(withoutFormatName, SceneFields);
|
|
6783
|
+
var Brand = z7.object({
|
|
6784
|
+
name: z7.string().optional(),
|
|
6914
6785
|
/** The words the brand's logo prints (its wordmark), so a check reads them as the logo, never as copy. */
|
|
6915
|
-
lockup:
|
|
6786
|
+
lockup: z7.array(z7.string().trim().min(1)).optional(),
|
|
6916
6787
|
/** The brand's face in words, when no specimen of it is wired ("a heavy geometric sans"). */
|
|
6917
|
-
typeface:
|
|
6788
|
+
typeface: z7.string().optional(),
|
|
6918
6789
|
ground: Hex2.optional(),
|
|
6919
6790
|
ink: Hex2.optional(),
|
|
6920
6791
|
accent: Hex2.optional(),
|
|
6921
6792
|
cta: Hex2.optional(),
|
|
6922
|
-
palette:
|
|
6793
|
+
palette: z7.array(Hex2).default([])
|
|
6923
6794
|
}).strict();
|
|
6924
|
-
var ColorUse =
|
|
6925
|
-
var Mechanism =
|
|
6926
|
-
var ThirdPartyBrand =
|
|
6927
|
-
name:
|
|
6795
|
+
var ColorUse = z7.object({ object: z7.string().trim().min(1), name: z7.string().trim().min(1), hex: Hex2.optional() }).strict();
|
|
6796
|
+
var Mechanism = z7.object({ device: z7.string().trim().min(1), why_it_works: z7.string().optional() }).strict();
|
|
6797
|
+
var ThirdPartyBrand = z7.object({
|
|
6798
|
+
name: z7.string().trim().min(1),
|
|
6928
6799
|
/** Where it shows in this ad, by what carries it ("the car the loan is for"). */
|
|
6929
|
-
what:
|
|
6800
|
+
what: z7.string().optional(),
|
|
6930
6801
|
/** The reference (BADGE_1) that holds a picture of its logo, when one is wired. */
|
|
6931
|
-
logo:
|
|
6802
|
+
logo: z7.string().regex(/^(PERSON|ANIMAL|PRODUCT|BADGE|PROP)_\d+$/, "a logo is a reference id like BADGE_1").optional(),
|
|
6932
6803
|
/**
|
|
6933
6804
|
* Why its logo is on our ad: `partner` (the offer is about it), `proof` (the review platform a
|
|
6934
6805
|
* rating comes from), `category_signal` (how a stranger knows in a second what this is: the
|
|
6935
6806
|
* Instagram and TikTok marks on a social media tool's ad).
|
|
6936
6807
|
*/
|
|
6937
|
-
role:
|
|
6808
|
+
role: z7.enum(["partner", "proof", "category_signal"]).optional()
|
|
6938
6809
|
}).strict();
|
|
6939
|
-
var LoadBearing =
|
|
6810
|
+
var LoadBearing = z7.object({
|
|
6940
6811
|
/** The element, in a few words ("the free gift", "a peer's face", "the notes app"). */
|
|
6941
|
-
element:
|
|
6812
|
+
element: z7.string().trim().min(1),
|
|
6942
6813
|
/** What it takes to carry it ("something the viewer wants for free"). */
|
|
6943
|
-
needs:
|
|
6814
|
+
needs: z7.string().optional(),
|
|
6944
6815
|
/** Whether we have it as it is, can adapt it, or have nothing for it. */
|
|
6945
|
-
have:
|
|
6816
|
+
have: z7.enum(["have", "adapt", "missing"]),
|
|
6946
6817
|
/** What of ours carries it in this ad. */
|
|
6947
|
-
ours:
|
|
6818
|
+
ours: z7.string().optional()
|
|
6948
6819
|
}).strict();
|
|
6949
|
-
var Concept =
|
|
6950
|
-
reference_ad_id:
|
|
6951
|
-
why_it_wins:
|
|
6952
|
-
disguise:
|
|
6953
|
-
voice:
|
|
6954
|
-
trigger:
|
|
6955
|
-
attention:
|
|
6956
|
-
logic:
|
|
6820
|
+
var Concept = z7.object({
|
|
6821
|
+
reference_ad_id: z7.string().optional(),
|
|
6822
|
+
why_it_wins: z7.object({
|
|
6823
|
+
disguise: z7.string().optional(),
|
|
6824
|
+
voice: z7.string().optional(),
|
|
6825
|
+
trigger: z7.string().optional(),
|
|
6826
|
+
attention: z7.string().optional(),
|
|
6827
|
+
logic: z7.string().optional()
|
|
6957
6828
|
}).strict().default({}),
|
|
6958
|
-
load_bearing:
|
|
6959
|
-
verdict:
|
|
6829
|
+
load_bearing: z7.array(LoadBearing).default([]),
|
|
6830
|
+
verdict: z7.enum(["use", "adapt", "reject"]),
|
|
6960
6831
|
/** How the idea transfers to us, in a sentence. Bookkeeping: never compiled into a render. */
|
|
6961
|
-
transfer_note:
|
|
6832
|
+
transfer_note: z7.string().optional()
|
|
6962
6833
|
}).strict();
|
|
6963
|
-
var AdSpecSchema =
|
|
6964
|
-
schema:
|
|
6834
|
+
var AdSpecSchema = z7.object({
|
|
6835
|
+
schema: z7.enum(AD_SPEC_SCHEMAS),
|
|
6965
6836
|
viewer: Viewer.optional(),
|
|
6966
6837
|
/** The one thing the ad tells the viewer. Intent, never printed. */
|
|
6967
|
-
message:
|
|
6968
|
-
copy:
|
|
6838
|
+
message: z7.string().optional(),
|
|
6839
|
+
copy: z7.array(AdCopyLine).default([]),
|
|
6969
6840
|
scene: Scene,
|
|
6970
6841
|
brand: Brand.optional(),
|
|
6971
|
-
colors:
|
|
6972
|
-
references:
|
|
6842
|
+
colors: z7.array(ColorUse).default([]),
|
|
6843
|
+
references: z7.array(AdReference).default([]),
|
|
6973
6844
|
/** The ad shows the brand's logo (the real file, wired on the canvas). */
|
|
6974
|
-
logo:
|
|
6975
|
-
logo_position:
|
|
6845
|
+
logo: z7.boolean().default(false),
|
|
6846
|
+
logo_position: z7.string().optional(),
|
|
6976
6847
|
/** Other brands the offer is about, whose logos the ad may show. */
|
|
6977
|
-
third_party_brands:
|
|
6848
|
+
third_party_brands: z7.array(ThirdPartyBrand).default([]),
|
|
6978
6849
|
/**
|
|
6979
6850
|
* Words the scene prints by itself, never copy: a phone's status bar and app chrome, a code
|
|
6980
6851
|
* editor's line numbers, a product's own label, a platform's badge. The render may draw them
|
|
6981
6852
|
* and the review never calls them extra words.
|
|
6982
6853
|
*/
|
|
6983
|
-
scene_text:
|
|
6854
|
+
scene_text: z7.preprocess(
|
|
6984
6855
|
(v) => Array.isArray(v) ? v.map((w) => w && typeof w === "object" && "text" in w ? w.text : w) : v,
|
|
6985
|
-
|
|
6856
|
+
z7.array(z7.string().trim().min(1)).default([])
|
|
6986
6857
|
),
|
|
6987
6858
|
/** The formats the ad is made for; the canvas delivers them. */
|
|
6988
|
-
formats:
|
|
6859
|
+
formats: z7.array(z7.string()).optional(),
|
|
6989
6860
|
/** An optional note on direction for this concept. */
|
|
6990
|
-
direction:
|
|
6861
|
+
direction: z7.string().optional(),
|
|
6991
6862
|
/** The device the ad is built on, and why it works on a stranger. Judged, never drawn. */
|
|
6992
|
-
mechanism:
|
|
6863
|
+
mechanism: z7.preprocess((v) => {
|
|
6993
6864
|
const list = v && typeof v === "object" && !Array.isArray(v) ? [v] : v;
|
|
6994
6865
|
return Array.isArray(list) ? list.map((m) => typeof m === "string" ? { device: m } : m) : list;
|
|
6995
|
-
},
|
|
6866
|
+
}, z7.array(Mechanism).default([])),
|
|
6996
6867
|
/** Why the reference won and what this ad keeps of it (schema /3). */
|
|
6997
6868
|
concept: Concept.optional(),
|
|
6998
6869
|
/**
|
|
@@ -7000,9 +6871,9 @@ var AdSpecSchema = z8.object({
|
|
|
7000
6871
|
* chat) carries the logo as the sender or avatar and no big button; `standard` the logo large
|
|
7001
6872
|
* and a solid button. Unset: standard.
|
|
7002
6873
|
*/
|
|
7003
|
-
brand_presence:
|
|
6874
|
+
brand_presence: z7.enum(["native", "standard"]).optional(),
|
|
7004
6875
|
/** What kind of product a stranger takes it for in one second, never its format ("a trade course", "a payroll app"). */
|
|
7005
|
-
recognition:
|
|
6876
|
+
recognition: z7.string().optional(),
|
|
7006
6877
|
/**
|
|
7007
6878
|
* The ad is a screen people know (a post on X, a WhatsApp chat, a wall of Trustpilot reviews, a
|
|
7008
6879
|
* note, a Slack message), drawn exactly from HTML at every size (`native-surface.ts`); the image
|
|
@@ -7010,9 +6881,9 @@ var AdSpecSchema = z8.object({
|
|
|
7010
6881
|
*/
|
|
7011
6882
|
native: NativeSurfaceSchema.optional(),
|
|
7012
6883
|
// Bookkeeping: read by the agent and the judge's context, never compiled into a render.
|
|
7013
|
-
source:
|
|
7014
|
-
reference:
|
|
7015
|
-
notes:
|
|
6884
|
+
source: z7.unknown().optional(),
|
|
6885
|
+
reference: z7.unknown().optional(),
|
|
6886
|
+
notes: z7.unknown().optional()
|
|
7016
6887
|
}).strict();
|
|
7017
6888
|
function referenceNodeId(reference) {
|
|
7018
6889
|
return `ref_${reference.id.toLowerCase()}`;
|
|
@@ -7089,7 +6960,7 @@ var FIELD_SHAPES = {
|
|
|
7089
6960
|
};
|
|
7090
6961
|
var SPEC_SHAPES_HINT = `prompt.json shapes: ${["copy", "colors", "viewer"].map((f) => FIELD_SHAPES[f]).join("; ")}.`;
|
|
7091
6962
|
function adSpecErrorText(e) {
|
|
7092
|
-
if (!(e instanceof
|
|
6963
|
+
if (!(e instanceof z7.ZodError)) return e instanceof Error ? e.message : String(e);
|
|
7093
6964
|
const items = /* @__PURE__ */ new Map();
|
|
7094
6965
|
for (const issue of e.issues) {
|
|
7095
6966
|
const [field2, index, key] = issue.path;
|
|
@@ -7365,7 +7236,7 @@ function adSpecFromBlueprint(raw, opts = {}) {
|
|
|
7365
7236
|
};
|
|
7366
7237
|
}
|
|
7367
7238
|
function adSpecJsonSchema() {
|
|
7368
|
-
return
|
|
7239
|
+
return z7.toJSONSchema(AdSpecSchema, { io: "input", unrepresentable: "any" });
|
|
7369
7240
|
}
|
|
7370
7241
|
|
|
7371
7242
|
// src/engine/scaffold/static-brief.ts
|
|
@@ -7593,7 +7464,7 @@ function pictureInstruction(p, ctx) {
|
|
|
7593
7464
|
const keep = p.spec?.keep ? ` Keep ${plain(p.spec.keep, ctx.clean)}.` : "";
|
|
7594
7465
|
switch (p.role) {
|
|
7595
7466
|
case "base":
|
|
7596
|
-
return ctx.job === "fix" ? "the current version of this ad, to edit." :
|
|
7467
|
+
return ctx.job === "fix" ? "the current version of this ad, to edit." : "the finished ad, to re-lay for this shape.";
|
|
7597
7468
|
case "identity":
|
|
7598
7469
|
return identityInstruction(p, what, keep, ctx.flowing);
|
|
7599
7470
|
// The user saw one label come back two ways (E4 gallery, BENCH.md § "E1 v2"): the label is the product's.
|
|
@@ -7972,10 +7843,6 @@ function deliverableSection(c) {
|
|
|
7972
7843
|
const change = sentence(c.opts.instruction?.trim() || "correct what is wrong");
|
|
7973
7844
|
return c.flowing ? `Image 1 is the current version of this ad. ${capitalize(change)} Keep everything else in Image 1 exactly the same, preserving its picture, layout, colours, lettering and every word.` : `Edit: Image 1 is the current version of this ad. ${capitalize(change)} Keep everything else exactly as it is in Image 1: the picture, the layout, the colours, the lettering and every word.`;
|
|
7974
7845
|
}
|
|
7975
|
-
if (c.job === "series") {
|
|
7976
|
-
const change = sentence(c.opts.instruction?.trim() || "set the new words");
|
|
7977
|
-
return c.flowing ? `Image 1 is a finished ad of this series. Make the next ad of the same series${forBrand}: ${change} Keep everything else in Image 1 exactly the same, preserving its picture, layout, background, shapes, colours, lettering style and logo, each word in the place and style of the one it replaces.` : `Edit: Image 1 is a finished ad of this series. Make the next ad of the same series${forBrand}. ${capitalize(change)} Keep everything else exactly as it is in Image 1: the picture, the layout, the background, the shapes, the colours, the lettering style and the logo, each new line in the place, size and style of the line it replaces.`;
|
|
7978
|
-
}
|
|
7979
7846
|
if (c.job === "relay") {
|
|
7980
7847
|
return c.flowing ? `Image 1 is a finished ad. Recompose this same ad as a ${noun}${forBrand}: the same picture, colours, lettering and every word, rearranged for the new shape, with more of the scene revealed at the edges.` : `Deliverable: the same ad as Image 1, re-laid as a finished ${noun}${forBrand}. Keep the same picture, colours, lettering and every word, rearranged for the new shape; show more of the scene rather than cropping it.`;
|
|
7981
7848
|
}
|
|
@@ -8646,7 +8513,6 @@ var STATIC_ADAPT_ROLE = "static_adapt";
|
|
|
8646
8513
|
var STATIC_SAFE_FIT_ROLE = "static_safe_fit";
|
|
8647
8514
|
var STATIC_PLACE_ROLE = "static_place";
|
|
8648
8515
|
var LOGO_COMPOSITE_ROLE = "logo_composite";
|
|
8649
|
-
var SERIES_LEAD_ROLE = "series_lead";
|
|
8650
8516
|
var STATIC_FIX_ROLE = "static_fix";
|
|
8651
8517
|
var LOGO_COMPOSITE_DEFAULT = false;
|
|
8652
8518
|
var ADAPT_IMAGE_SIZE = "2K";
|
|
@@ -8666,19 +8532,6 @@ function rolesById(canvas) {
|
|
|
8666
8532
|
}
|
|
8667
8533
|
return out;
|
|
8668
8534
|
}
|
|
8669
|
-
function relaidPictureRole(canvas) {
|
|
8670
|
-
const roles = rolesById(canvas);
|
|
8671
|
-
const final = nodesOf(canvas).find((n) => n.id === "final" && n.type === "collect");
|
|
8672
|
-
const labels = final?.params?.labels;
|
|
8673
|
-
const images = final?.inputs?.images;
|
|
8674
|
-
if (!Array.isArray(labels) || !Array.isArray(images)) return {};
|
|
8675
|
-
const at2 = images.findIndex((ref) => {
|
|
8676
|
-
const from = typeof ref === "string" ? /^\$ref:([^.]+)\./.exec(ref)?.[1] : void 0;
|
|
8677
|
-
return from !== void 0 && roles[from] === STATIC_MASTER_ROLE;
|
|
8678
|
-
});
|
|
8679
|
-
const format = labels[at2];
|
|
8680
|
-
return typeof format === "string" ? { [`last_${formatSlug(format)}`]: STATIC_MASTER_ROLE } : {};
|
|
8681
|
-
}
|
|
8682
8535
|
function formatSlug(format) {
|
|
8683
8536
|
return format.replace(":", "x").replace(".", "_");
|
|
8684
8537
|
}
|
|
@@ -10214,12 +10067,12 @@ function staticInputKind(ctx, value) {
|
|
|
10214
10067
|
if (!value.startsWith(REF_PREFIX)) return "value";
|
|
10215
10068
|
const target = refTarget(ctx, value);
|
|
10216
10069
|
if (!target) return "unknown";
|
|
10217
|
-
const outputSchema = unwrap(shapeOf(target.def.outputs)?.[target.expr.output] ??
|
|
10218
|
-
if (outputSchema instanceof
|
|
10070
|
+
const outputSchema = unwrap(shapeOf(target.def.outputs)?.[target.expr.output] ?? z8.unknown());
|
|
10071
|
+
if (outputSchema instanceof z8.ZodArray && target.expr.index === void 0) return "list";
|
|
10219
10072
|
const kind = refOutputKind(target);
|
|
10220
10073
|
if (kind && ASSET_KINDS.has(kind)) return kind;
|
|
10221
|
-
const plain2 = outputSchema instanceof
|
|
10222
|
-
return plain2 || outputSchema instanceof
|
|
10074
|
+
const plain2 = outputSchema instanceof z8.ZodString || outputSchema instanceof z8.ZodNumber;
|
|
10075
|
+
return plain2 || outputSchema instanceof z8.ZodBoolean ? "value" : "unknown";
|
|
10223
10076
|
}
|
|
10224
10077
|
function staticInputSource(ctx, value) {
|
|
10225
10078
|
if (typeof value !== "string" || !value.startsWith(REF_PREFIX)) return void 0;
|
|
@@ -10386,7 +10239,7 @@ function listShapeKeys(schema) {
|
|
|
10386
10239
|
}
|
|
10387
10240
|
function shapeOf(schema) {
|
|
10388
10241
|
const obj = unwrap(schema);
|
|
10389
|
-
return obj instanceof
|
|
10242
|
+
return obj instanceof z8.ZodObject ? obj.shape : void 0;
|
|
10390
10243
|
}
|
|
10391
10244
|
function unwrap(schema) {
|
|
10392
10245
|
let cur = schema;
|
|
@@ -10928,7 +10781,7 @@ import { mkdtemp, readFile as readFile5, rm, stat as stat2 } from "fs/promises";
|
|
|
10928
10781
|
import { tmpdir } from "os";
|
|
10929
10782
|
import path8 from "path";
|
|
10930
10783
|
import { promisify } from "util";
|
|
10931
|
-
import { z as
|
|
10784
|
+
import { z as z9 } from "zod";
|
|
10932
10785
|
|
|
10933
10786
|
// ../proxy/src/fetch.ts
|
|
10934
10787
|
import { lookup as dnsLookup } from "dns";
|
|
@@ -11683,7 +11536,7 @@ function mapClientError(ctx, e) {
|
|
|
11683
11536
|
|
|
11684
11537
|
// src/engine/nodes/ingest.ts
|
|
11685
11538
|
var execFile = promisify(execFileCb);
|
|
11686
|
-
var ExpectEnum =
|
|
11539
|
+
var ExpectEnum = z9.enum(["image", "video", "audio", "text", "json", "font"]);
|
|
11687
11540
|
function coercePathUrlToUrl(input) {
|
|
11688
11541
|
if (!input || typeof input !== "object") return input;
|
|
11689
11542
|
const obj = input;
|
|
@@ -11693,23 +11546,23 @@ function coercePathUrlToUrl(input) {
|
|
|
11693
11546
|
}
|
|
11694
11547
|
return input;
|
|
11695
11548
|
}
|
|
11696
|
-
var IngestParams =
|
|
11549
|
+
var IngestParams = z9.preprocess(
|
|
11697
11550
|
coercePathUrlToUrl,
|
|
11698
|
-
|
|
11699
|
-
|
|
11700
|
-
source:
|
|
11701
|
-
url:
|
|
11551
|
+
z9.discriminatedUnion("source", [
|
|
11552
|
+
z9.object({
|
|
11553
|
+
source: z9.literal("url"),
|
|
11554
|
+
url: z9.string().url(),
|
|
11702
11555
|
expect: ExpectEnum
|
|
11703
11556
|
}).strict(),
|
|
11704
|
-
|
|
11705
|
-
source:
|
|
11706
|
-
path:
|
|
11557
|
+
z9.object({
|
|
11558
|
+
source: z9.literal("path"),
|
|
11559
|
+
path: z9.string().min(1),
|
|
11707
11560
|
expect: ExpectEnum
|
|
11708
11561
|
}).strict()
|
|
11709
11562
|
])
|
|
11710
11563
|
);
|
|
11711
|
-
var IngestInputs =
|
|
11712
|
-
var IngestOutputs =
|
|
11564
|
+
var IngestInputs = z9.object({}).strict();
|
|
11565
|
+
var IngestOutputs = z9.object({ asset: AssetRef }).strict();
|
|
11713
11566
|
var RAW_TEXT_EXTENSIONS = [".txt", ".md"];
|
|
11714
11567
|
function resolveStrategy(params) {
|
|
11715
11568
|
if (params.source === "path") return "local_file";
|
|
@@ -12293,7 +12146,7 @@ ingestNode.cacheKeyExtras = async ({ params }) => {
|
|
|
12293
12146
|
};
|
|
12294
12147
|
|
|
12295
12148
|
// src/engine/nodes/local/audioTimeline.ts
|
|
12296
|
-
import { z as
|
|
12149
|
+
import { z as z10 } from "zod";
|
|
12297
12150
|
|
|
12298
12151
|
// src/engine/nodes/local/lib/cli-runner.ts
|
|
12299
12152
|
import { execFile as execFileCb2, spawn as spawn2 } from "child_process";
|
|
@@ -12512,50 +12365,50 @@ async function getToolVersion(bin) {
|
|
|
12512
12365
|
var FFMPEG_BIN = "ffmpeg";
|
|
12513
12366
|
var FORMAT_EXT = { mp3: "mp3", wav: "wav", m4a: "m4a" };
|
|
12514
12367
|
var FORMAT_CODEC = { mp3: "libmp3lame", wav: "pcm_s16le", m4a: "aac" };
|
|
12515
|
-
var Track =
|
|
12368
|
+
var Track = z10.object({
|
|
12516
12369
|
/** Input slot name (must match a wired `inputs.<slot>` audio ref). */
|
|
12517
|
-
slot:
|
|
12370
|
+
slot: z10.string().min(1),
|
|
12518
12371
|
/** When this track starts on the timeline, seconds from 0. */
|
|
12519
|
-
start_s:
|
|
12372
|
+
start_s: z10.number().min(0),
|
|
12520
12373
|
/**
|
|
12521
12374
|
* Optional hard cap on this track's length, seconds. The clip is trimmed BEFORE
|
|
12522
12375
|
* placement, so a source that runs long (an over-long voice extract, a converted
|
|
12523
12376
|
* track that came back oversized) cannot bleed into the next track's window.
|
|
12524
12377
|
*/
|
|
12525
|
-
duration_s:
|
|
12378
|
+
duration_s: z10.number().positive().optional(),
|
|
12526
12379
|
/**
|
|
12527
12380
|
* Level adjustment in dB — negative attenuates. A MUSIC BED under speech
|
|
12528
12381
|
* belongs at −20 dB (see the `describe` text: this number is a settled
|
|
12529
12382
|
* decision, not a per-cut judgement).
|
|
12530
12383
|
*/
|
|
12531
|
-
gain_db:
|
|
12384
|
+
gain_db: z10.number().optional().describe(
|
|
12532
12385
|
"Level adjustment in dB (negative attenuates, 0 = untouched). A music bed under speech sits at \u221220 dB and is sidechain-ducked as well; \u221212 dB crowded the voice and \u221213 dB was rejected by the client as too loud. If a silent tail (an end card, a logo plate) sounds empty, do NOT raise the bed \u2014 wire the same bed as a SECOND track windowed over the tail with its own higher gain. Measuring loudness across a window that includes the silent tail is what makes a correct bed look quiet."
|
|
12533
12386
|
)
|
|
12534
12387
|
}).strict();
|
|
12535
|
-
var DuckSpec =
|
|
12536
|
-
track:
|
|
12537
|
-
against:
|
|
12538
|
-
threshold:
|
|
12539
|
-
ratio:
|
|
12388
|
+
var DuckSpec = z10.object({
|
|
12389
|
+
track: z10.string().min(1).describe("Slot to duck \u2014 the music bed."),
|
|
12390
|
+
against: z10.array(z10.string().min(1)).min(1).describe("Key slots whose signal ducks it \u2014 the voice tracks."),
|
|
12391
|
+
threshold: z10.number().min(0).max(1).optional().describe("Linear 0\u20131, not dB. Lower ducks more readily. Default 0.03."),
|
|
12392
|
+
ratio: z10.number().min(1).max(20).optional().describe("How hard it ducks. Default 8; 12 for a firm hold."),
|
|
12540
12393
|
// MILLISECONDS is spelled out because a canvas shipped `attack: 0.05,
|
|
12541
12394
|
// release: 0.45` — read as seconds — which is a 0.45 ms release: the bed
|
|
12542
12395
|
// snapped back to full level between syllables and the client heard the
|
|
12543
12396
|
// music as "too loud" at a level that measured correct.
|
|
12544
|
-
attack:
|
|
12545
|
-
release:
|
|
12397
|
+
attack: z10.number().min(0.01).optional().describe("MILLISECONDS (not seconds). Typical 5\u201350; default 5. A value below 0.5 is a unit mistake."),
|
|
12398
|
+
release: z10.number().min(0.01).optional().describe(
|
|
12546
12399
|
"MILLISECONDS (not seconds). Typical 200\u2013800; default 300. Anything under ~10 makes the bed pump back up between words and is a unit mistake."
|
|
12547
12400
|
)
|
|
12548
12401
|
}).strict();
|
|
12549
|
-
var AudioTimelineParams =
|
|
12550
|
-
tracks:
|
|
12402
|
+
var AudioTimelineParams = z10.object({
|
|
12403
|
+
tracks: z10.array(Track).min(1),
|
|
12551
12404
|
/** Final track length in ms — pads short / trims long. Defaults to the natural mix length. */
|
|
12552
|
-
total_ms:
|
|
12553
|
-
output_format:
|
|
12405
|
+
total_ms: z10.number().int().positive().optional(),
|
|
12406
|
+
output_format: z10.enum(["mp3", "wav", "m4a"]).optional(),
|
|
12554
12407
|
/** Optional sidechain duck of one track under others (e.g. music under voice). */
|
|
12555
12408
|
duck: DuckSpec.optional()
|
|
12556
12409
|
}).strict();
|
|
12557
|
-
var AudioTimelineInputs =
|
|
12558
|
-
var AudioTimelineOutputs =
|
|
12410
|
+
var AudioTimelineInputs = z10.record(z10.string(), z10.unknown());
|
|
12411
|
+
var AudioTimelineOutputs = z10.object({ audio: z10.custom() }).strict();
|
|
12559
12412
|
function applyDuck(params, labelFor, filterChains) {
|
|
12560
12413
|
const duck = params.duck;
|
|
12561
12414
|
if (!duck) return;
|
|
@@ -12669,28 +12522,28 @@ var audioTimelineNode = defineNode({
|
|
|
12669
12522
|
});
|
|
12670
12523
|
|
|
12671
12524
|
// src/engine/nodes/local/avMux.ts
|
|
12672
|
-
import { z as
|
|
12525
|
+
import { z as z11 } from "zod";
|
|
12673
12526
|
var FFMPEG_BIN2 = "ffmpeg";
|
|
12674
|
-
var Loudnorm =
|
|
12675
|
-
i:
|
|
12676
|
-
tp:
|
|
12677
|
-
lra:
|
|
12527
|
+
var Loudnorm = z11.object({
|
|
12528
|
+
i: z11.number().default(-14).describe("Integrated loudness target, LUFS. -14 is the social/streaming target."),
|
|
12529
|
+
tp: z11.number().default(-1.5).describe("True-peak ceiling, dBTP."),
|
|
12530
|
+
lra: z11.number().default(11).describe("Loudness range target, LU.")
|
|
12678
12531
|
}).strict();
|
|
12679
|
-
var AvMuxParams =
|
|
12680
|
-
loudnorm:
|
|
12532
|
+
var AvMuxParams = z11.object({
|
|
12533
|
+
loudnorm: z11.union([z11.literal(false), Loudnorm]).default({ i: -14, tp: -1.5, lra: 11 }).describe(
|
|
12681
12534
|
'Loudness normalization of the soundtrack (EBU R128). The raw mix is a quiet track that reads as "no sound" in casual players, so it is on by default at -14 LUFS / -1.5 dBTP. `false` keeps the mix level.'
|
|
12682
12535
|
),
|
|
12683
|
-
audio_bitrate:
|
|
12684
|
-
sample_rate:
|
|
12685
|
-
length:
|
|
12536
|
+
audio_bitrate: z11.string().regex(/^\d+k$/, 'a bitrate like "192k"').default("192k").describe("AAC bitrate of the delivered soundtrack."),
|
|
12537
|
+
sample_rate: z11.number().int().positive().default(48e3).describe("Delivered sample rate, Hz."),
|
|
12538
|
+
length: z11.union([z11.literal("shortest"), z11.object({ total_s: z11.number().positive() }).strict()]).default("shortest").describe(
|
|
12686
12539
|
"`shortest` ends the file with the shorter stream, so a picture and mix pinned to the same timeline come out exact (a longer one is silently cut: `canvas validate` checks both against the timeline). `{ total_s }` cuts both streams at that second; they must each run at least that long."
|
|
12687
12540
|
)
|
|
12688
12541
|
}).strict();
|
|
12689
|
-
var AvMuxInputs =
|
|
12690
|
-
video:
|
|
12691
|
-
audio:
|
|
12542
|
+
var AvMuxInputs = z11.object({
|
|
12543
|
+
video: z11.custom().describe("The finished picture (its own audio, if any, is dropped)."),
|
|
12544
|
+
audio: z11.custom().describe("The mixed soundtrack, usually an `audio_timeline`.")
|
|
12692
12545
|
}).strict();
|
|
12693
|
-
var AvMuxOutputs =
|
|
12546
|
+
var AvMuxOutputs = z11.object({ video: z11.custom() }).strict();
|
|
12694
12547
|
function audioFilter(params) {
|
|
12695
12548
|
const stereo = "aformat=channel_layouts=stereo";
|
|
12696
12549
|
if (params.loudnorm === false) return stereo;
|
|
@@ -12758,7 +12611,7 @@ var avMuxNode = defineNode({
|
|
|
12758
12611
|
});
|
|
12759
12612
|
|
|
12760
12613
|
// src/engine/nodes/local/collect.ts
|
|
12761
|
-
import { z as
|
|
12614
|
+
import { z as z12 } from "zod";
|
|
12762
12615
|
var collectNode = defineNode({
|
|
12763
12616
|
id: "collect",
|
|
12764
12617
|
version: "1.0.0",
|
|
@@ -12767,9 +12620,9 @@ var collectNode = defineNode({
|
|
|
12767
12620
|
passthroughRefs: true,
|
|
12768
12621
|
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.",
|
|
12769
12622
|
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.",
|
|
12770
|
-
inputs:
|
|
12771
|
-
params:
|
|
12772
|
-
outputs:
|
|
12623
|
+
inputs: z12.object({ images: z12.array(ImageRef).min(1) }).strict(),
|
|
12624
|
+
params: z12.object({ labels: z12.array(z12.string().min(1)).min(1).optional() }).strict(),
|
|
12625
|
+
outputs: z12.object({ images: z12.array(ImageRef).min(1) }).strict(),
|
|
12773
12626
|
outputKinds: { images: "image" },
|
|
12774
12627
|
cost: () => ({ credits: 0, seconds_estimate: 0 }),
|
|
12775
12628
|
// Arity is only knowable at validate time when `images` is a literal array;
|
|
@@ -12795,18 +12648,18 @@ var collectNode = defineNode({
|
|
|
12795
12648
|
});
|
|
12796
12649
|
|
|
12797
12650
|
// src/engine/nodes/local/ffmpeg.ts
|
|
12798
|
-
import { z as
|
|
12651
|
+
import { z as z13 } from "zod";
|
|
12799
12652
|
var FFMPEG_BIN3 = "ffmpeg";
|
|
12800
|
-
var OutputDecl =
|
|
12801
|
-
kind:
|
|
12802
|
-
ext:
|
|
12653
|
+
var OutputDecl = z13.object({
|
|
12654
|
+
kind: z13.enum(["image", "video", "audio"]),
|
|
12655
|
+
ext: z13.string().min(1).max(8)
|
|
12803
12656
|
}).strict();
|
|
12804
|
-
var FfmpegParams =
|
|
12805
|
-
args:
|
|
12806
|
-
outputs:
|
|
12657
|
+
var FfmpegParams = z13.object({
|
|
12658
|
+
args: z13.array(z13.string()).min(1),
|
|
12659
|
+
outputs: z13.record(z13.string(), OutputDecl).default({})
|
|
12807
12660
|
}).strict();
|
|
12808
|
-
var FfmpegInputs =
|
|
12809
|
-
var FfmpegOutputs =
|
|
12661
|
+
var FfmpegInputs = z13.record(z13.string(), z13.unknown());
|
|
12662
|
+
var FfmpegOutputs = z13.record(z13.string(), z13.custom());
|
|
12810
12663
|
var ffmpegNode = defineNode({
|
|
12811
12664
|
id: "ffmpeg",
|
|
12812
12665
|
version: "2.0.0",
|
|
@@ -12837,7 +12690,7 @@ import { mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile4 } from "fs/prom
|
|
|
12837
12690
|
import { createRequire } from "module";
|
|
12838
12691
|
import { tmpdir as tmpdir3 } from "os";
|
|
12839
12692
|
import path11 from "path";
|
|
12840
|
-
import { z as
|
|
12693
|
+
import { z as z14 } from "zod";
|
|
12841
12694
|
|
|
12842
12695
|
// src/engine/nodes/local/lib/assets.ts
|
|
12843
12696
|
import { copyFile as copyFile3, readFile as readFile7 } from "fs/promises";
|
|
@@ -12867,15 +12720,15 @@ var DEFAULT_SPECIMEN = [
|
|
|
12867
12720
|
"abcdefghijklmnopqrstuvwxyz",
|
|
12868
12721
|
`0123456789 !?&@#$%().,:;'"-`
|
|
12869
12722
|
].join("\n");
|
|
12870
|
-
var FontSpecimenParams =
|
|
12871
|
-
text:
|
|
12872
|
-
font_size:
|
|
12873
|
-
padding:
|
|
12874
|
-
line_height:
|
|
12875
|
-
max_width:
|
|
12723
|
+
var FontSpecimenParams = z14.object({
|
|
12724
|
+
text: z14.string().min(1).max(2e3).optional().default(DEFAULT_SPECIMEN),
|
|
12725
|
+
font_size: z14.number().int().min(8).max(512).optional().default(72),
|
|
12726
|
+
padding: z14.number().int().min(0).max(512).optional().default(64),
|
|
12727
|
+
line_height: z14.number().min(0.8).max(3).optional().default(1.35),
|
|
12728
|
+
max_width: z14.number().int().min(256).max(4096).optional()
|
|
12876
12729
|
}).strict();
|
|
12877
|
-
var FontSpecimenInputs =
|
|
12878
|
-
var FontSpecimenOutputs =
|
|
12730
|
+
var FontSpecimenInputs = z14.object({ font: FontRef }).loose();
|
|
12731
|
+
var FontSpecimenOutputs = z14.object({ image: ImageRef }).strict();
|
|
12879
12732
|
var DEVICE_SCALE_FACTOR = 2;
|
|
12880
12733
|
var PAGE_TIMEOUT_MS = 3e4;
|
|
12881
12734
|
function escapeHtml(text2) {
|
|
@@ -13015,7 +12868,7 @@ import { mkdtemp as mkdtemp5, readFile as readFile12, rm as rm5, stat as stat6,
|
|
|
13015
12868
|
import { cpus, tmpdir as tmpdir5 } from "os";
|
|
13016
12869
|
import path18 from "path";
|
|
13017
12870
|
import { promisify as promisify4 } from "util";
|
|
13018
|
-
import { z as
|
|
12871
|
+
import { z as z16 } from "zod";
|
|
13019
12872
|
|
|
13020
12873
|
// src/engine/engine/composition-hash.ts
|
|
13021
12874
|
import { readdir as readdir2, readFile as readFile8, stat as stat4 } from "fs/promises";
|
|
@@ -13053,62 +12906,62 @@ async function collectFiles(root, current) {
|
|
|
13053
12906
|
// src/engine/engine/composition-meta.ts
|
|
13054
12907
|
import { readFile as readFile9 } from "fs/promises";
|
|
13055
12908
|
import path13 from "path";
|
|
13056
|
-
import { z as
|
|
13057
|
-
var InputKind =
|
|
13058
|
-
var InputSpec =
|
|
12909
|
+
import { z as z15 } from "zod";
|
|
12910
|
+
var InputKind = z15.enum(["video", "image", "audio", "json", "font"]);
|
|
12911
|
+
var InputSpec = z15.object({
|
|
13059
12912
|
kind: InputKind,
|
|
13060
|
-
required:
|
|
12913
|
+
required: z15.boolean().optional().default(false),
|
|
13061
12914
|
// Filename the composition's HTML references (e.g. `input.mp4`, `logo.png`).
|
|
13062
12915
|
// Defaults to `<key><ext>` derived from the kind.
|
|
13063
|
-
staged_as:
|
|
13064
|
-
description:
|
|
12916
|
+
staged_as: z15.string().min(1).optional(),
|
|
12917
|
+
description: z15.string().optional()
|
|
13065
12918
|
}).strict();
|
|
13066
12919
|
var ParamSpecBase = {
|
|
13067
|
-
required:
|
|
13068
|
-
description:
|
|
12920
|
+
required: z15.boolean().optional().default(false),
|
|
12921
|
+
description: z15.string().optional()
|
|
13069
12922
|
};
|
|
13070
|
-
var StringParam =
|
|
12923
|
+
var StringParam = z15.object({
|
|
13071
12924
|
...ParamSpecBase,
|
|
13072
|
-
kind:
|
|
13073
|
-
default:
|
|
13074
|
-
enum:
|
|
12925
|
+
kind: z15.literal("string"),
|
|
12926
|
+
default: z15.string().optional(),
|
|
12927
|
+
enum: z15.array(z15.string()).optional()
|
|
13075
12928
|
}).strict();
|
|
13076
|
-
var IntegerParam =
|
|
12929
|
+
var IntegerParam = z15.object({
|
|
13077
12930
|
...ParamSpecBase,
|
|
13078
|
-
kind:
|
|
13079
|
-
default:
|
|
13080
|
-
min:
|
|
13081
|
-
max:
|
|
12931
|
+
kind: z15.literal("integer"),
|
|
12932
|
+
default: z15.number().int().optional(),
|
|
12933
|
+
min: z15.number().int().optional(),
|
|
12934
|
+
max: z15.number().int().optional()
|
|
13082
12935
|
}).strict();
|
|
13083
|
-
var NumberParam =
|
|
12936
|
+
var NumberParam = z15.object({
|
|
13084
12937
|
...ParamSpecBase,
|
|
13085
|
-
kind:
|
|
13086
|
-
default:
|
|
13087
|
-
min:
|
|
13088
|
-
max:
|
|
12938
|
+
kind: z15.literal("number"),
|
|
12939
|
+
default: z15.number().optional(),
|
|
12940
|
+
min: z15.number().optional(),
|
|
12941
|
+
max: z15.number().optional()
|
|
13089
12942
|
}).strict();
|
|
13090
|
-
var BooleanParam =
|
|
12943
|
+
var BooleanParam = z15.object({
|
|
13091
12944
|
...ParamSpecBase,
|
|
13092
|
-
kind:
|
|
13093
|
-
default:
|
|
12945
|
+
kind: z15.literal("boolean"),
|
|
12946
|
+
default: z15.boolean().optional()
|
|
13094
12947
|
}).strict();
|
|
13095
|
-
var ColorParam =
|
|
12948
|
+
var ColorParam = z15.object({
|
|
13096
12949
|
...ParamSpecBase,
|
|
13097
|
-
kind:
|
|
13098
|
-
default:
|
|
12950
|
+
kind: z15.literal("color"),
|
|
12951
|
+
default: z15.string().optional()
|
|
13099
12952
|
}).strict();
|
|
13100
|
-
var ImageParam =
|
|
12953
|
+
var ImageParam = z15.object({
|
|
13101
12954
|
...ParamSpecBase,
|
|
13102
|
-
kind:
|
|
13103
|
-
default:
|
|
12955
|
+
kind: z15.literal("image"),
|
|
12956
|
+
default: z15.string().optional()
|
|
13104
12957
|
}).strict();
|
|
13105
|
-
var JsonParam =
|
|
12958
|
+
var JsonParam = z15.object({
|
|
13106
12959
|
...ParamSpecBase,
|
|
13107
|
-
kind:
|
|
13108
|
-
schema:
|
|
13109
|
-
default:
|
|
12960
|
+
kind: z15.literal("json"),
|
|
12961
|
+
schema: z15.unknown().optional(),
|
|
12962
|
+
default: z15.unknown().optional()
|
|
13110
12963
|
}).strict();
|
|
13111
|
-
var ParamSpec =
|
|
12964
|
+
var ParamSpec = z15.discriminatedUnion("kind", [
|
|
13112
12965
|
StringParam,
|
|
13113
12966
|
IntegerParam,
|
|
13114
12967
|
NumberParam,
|
|
@@ -13117,21 +12970,21 @@ var ParamSpec = z16.discriminatedUnion("kind", [
|
|
|
13117
12970
|
ImageParam,
|
|
13118
12971
|
JsonParam
|
|
13119
12972
|
]);
|
|
13120
|
-
var CompositionMetaSchema =
|
|
13121
|
-
id:
|
|
13122
|
-
title:
|
|
13123
|
-
description:
|
|
13124
|
-
width:
|
|
13125
|
-
height:
|
|
13126
|
-
fps:
|
|
13127
|
-
default_duration:
|
|
13128
|
-
inputs:
|
|
13129
|
-
params:
|
|
12973
|
+
var CompositionMetaSchema = z15.object({
|
|
12974
|
+
id: z15.string().min(1),
|
|
12975
|
+
title: z15.string().min(1),
|
|
12976
|
+
description: z15.string().optional(),
|
|
12977
|
+
width: z15.number().int().positive(),
|
|
12978
|
+
height: z15.number().int().positive(),
|
|
12979
|
+
fps: z15.number().int().positive().default(30),
|
|
12980
|
+
default_duration: z15.number().positive().default(10),
|
|
12981
|
+
inputs: z15.record(z15.string(), InputSpec).default({}),
|
|
12982
|
+
params: z15.record(z15.string(), ParamSpec).default({}),
|
|
13130
12983
|
/**
|
|
13131
12984
|
* The page reads its own staged files (a picture's pixels), so a snapshot opens it with
|
|
13132
12985
|
* file access. Off by default: Chromium grants it for every file:// path, not just these.
|
|
13133
12986
|
*/
|
|
13134
|
-
reads_staged_files:
|
|
12987
|
+
reads_staged_files: z15.boolean().optional()
|
|
13135
12988
|
}).strict();
|
|
13136
12989
|
async function loadCompositionMeta(compositionDir) {
|
|
13137
12990
|
const metaPath = path13.join(compositionDir, "meta.json");
|
|
@@ -13159,39 +13012,39 @@ function buildParamsSchema(meta) {
|
|
|
13159
13012
|
for (const [name, spec] of Object.entries(meta.params)) {
|
|
13160
13013
|
shape[name] = buildParamFieldSchema(name, spec);
|
|
13161
13014
|
}
|
|
13162
|
-
return
|
|
13015
|
+
return z15.object(shape).strict();
|
|
13163
13016
|
}
|
|
13164
13017
|
function buildParamFieldSchema(name, spec) {
|
|
13165
13018
|
switch (spec.kind) {
|
|
13166
13019
|
case "string": {
|
|
13167
|
-
const s = spec.enum && spec.enum.length > 0 ?
|
|
13020
|
+
const s = spec.enum && spec.enum.length > 0 ? z15.enum(spec.enum) : z15.string();
|
|
13168
13021
|
return finalize(s, spec.default, spec.required);
|
|
13169
13022
|
}
|
|
13170
13023
|
case "integer": {
|
|
13171
|
-
let s =
|
|
13024
|
+
let s = z15.number().int();
|
|
13172
13025
|
if (spec.min !== void 0) s = s.min(spec.min);
|
|
13173
13026
|
if (spec.max !== void 0) s = s.max(spec.max);
|
|
13174
13027
|
return finalize(s, spec.default, spec.required);
|
|
13175
13028
|
}
|
|
13176
13029
|
case "number": {
|
|
13177
|
-
let s =
|
|
13030
|
+
let s = z15.number();
|
|
13178
13031
|
if (spec.min !== void 0) s = s.min(spec.min);
|
|
13179
13032
|
if (spec.max !== void 0) s = s.max(spec.max);
|
|
13180
13033
|
return finalize(s, spec.default, spec.required);
|
|
13181
13034
|
}
|
|
13182
13035
|
case "boolean":
|
|
13183
|
-
return finalize(
|
|
13036
|
+
return finalize(z15.boolean(), spec.default, spec.required);
|
|
13184
13037
|
case "color": {
|
|
13185
|
-
const s =
|
|
13038
|
+
const s = z15.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
|
|
13186
13039
|
message: `param "${name}": must be a 3/6/8-digit hex color (e.g. "#ff0066")`
|
|
13187
13040
|
});
|
|
13188
13041
|
return finalize(s, spec.default, spec.required);
|
|
13189
13042
|
}
|
|
13190
13043
|
case "image":
|
|
13191
|
-
return finalize(
|
|
13044
|
+
return finalize(z15.union([z15.string().min(1), z15.record(z15.string(), z15.unknown())]), spec.default, spec.required);
|
|
13192
13045
|
case "json":
|
|
13193
13046
|
return finalize(
|
|
13194
|
-
|
|
13047
|
+
z15.unknown().refine((v) => v !== void 0, { message: `param "${name}" is required` }),
|
|
13195
13048
|
spec.default,
|
|
13196
13049
|
spec.required
|
|
13197
13050
|
);
|
|
@@ -13787,17 +13640,17 @@ ${stderr.slice(0, 1500)}`;
|
|
|
13787
13640
|
|
|
13788
13641
|
// src/engine/nodes/local/hyperframe.ts
|
|
13789
13642
|
var execFileAsync2 = promisify4(execFile4);
|
|
13790
|
-
var HyperframeParams =
|
|
13791
|
-
composition:
|
|
13643
|
+
var HyperframeParams = z16.object({
|
|
13644
|
+
composition: z16.string().min(1),
|
|
13792
13645
|
// Output container. mp4 (default) for delivery; webm/mov render WITH
|
|
13793
13646
|
// transparency (alpha) when the composition background is transparent —
|
|
13794
13647
|
// use for motion-graphic overlays dropped into Premiere/AE/Nuke.
|
|
13795
|
-
format:
|
|
13796
|
-
timeout_ms:
|
|
13797
|
-
}).catchall(
|
|
13798
|
-
var HyperframeInputs =
|
|
13799
|
-
var HyperframeOutputs =
|
|
13800
|
-
video:
|
|
13648
|
+
format: z16.enum(["mp4", "webm", "mov"]).optional().default("mp4"),
|
|
13649
|
+
timeout_ms: z16.number().int().positive().optional().default(10 * 60 * 1e3)
|
|
13650
|
+
}).catchall(z16.unknown());
|
|
13651
|
+
var HyperframeInputs = z16.record(z16.string(), z16.custom()).optional().default({});
|
|
13652
|
+
var HyperframeOutputs = z16.object({
|
|
13653
|
+
video: z16.custom()
|
|
13801
13654
|
}).strict();
|
|
13802
13655
|
var NODE_OWNED_PARAM_KEYS = /* @__PURE__ */ new Set(["composition", "format", "timeout_ms"]);
|
|
13803
13656
|
var MIME_BY_FORMAT = {
|
|
@@ -14074,23 +13927,23 @@ import { createRequire as createRequire3 } from "module";
|
|
|
14074
13927
|
import { tmpdir as tmpdir6 } from "os";
|
|
14075
13928
|
import path19 from "path";
|
|
14076
13929
|
import { promisify as promisify5 } from "util";
|
|
14077
|
-
import { z as
|
|
13930
|
+
import { z as z17 } from "zod";
|
|
14078
13931
|
var _execFileAsync = promisify5(execFile5);
|
|
14079
13932
|
var require_3 = createRequire3(import.meta.url);
|
|
14080
|
-
var WaitForSpec =
|
|
14081
|
-
|
|
14082
|
-
|
|
14083
|
-
|
|
14084
|
-
|
|
13933
|
+
var WaitForSpec = z17.discriminatedUnion("kind", [
|
|
13934
|
+
z17.object({ kind: z17.literal("auto") }),
|
|
13935
|
+
z17.object({ kind: z17.literal("selector"), value: z17.string().min(1) }),
|
|
13936
|
+
z17.object({ kind: z17.literal("function"), value: z17.string().min(1) }),
|
|
13937
|
+
z17.object({ kind: z17.literal("timeout"), ms: z17.number().int().min(0).max(6e4) })
|
|
14085
13938
|
]);
|
|
14086
|
-
var HyperframeSnapshotParams =
|
|
14087
|
-
composition:
|
|
13939
|
+
var HyperframeSnapshotParams = z17.object({
|
|
13940
|
+
composition: z17.string().min(1),
|
|
14088
13941
|
wait_for: WaitForSpec.optional().default({ kind: "auto" }),
|
|
14089
|
-
timeout_ms:
|
|
14090
|
-
}).catchall(
|
|
14091
|
-
var HyperframeSnapshotInputs =
|
|
14092
|
-
var HyperframeSnapshotOutputs =
|
|
14093
|
-
image:
|
|
13942
|
+
timeout_ms: z17.number().int().positive().optional().default(6e4)
|
|
13943
|
+
}).catchall(z17.unknown());
|
|
13944
|
+
var HyperframeSnapshotInputs = z17.record(z17.string(), z17.custom()).optional().default({});
|
|
13945
|
+
var HyperframeSnapshotOutputs = z17.object({
|
|
13946
|
+
image: z17.custom()
|
|
14094
13947
|
}).strict();
|
|
14095
13948
|
var NODE_OWNED_PARAM_KEYS2 = /* @__PURE__ */ new Set(["composition", "wait_for", "timeout_ms"]);
|
|
14096
13949
|
var DEVICE_SCALE_FACTOR2 = 2;
|
|
@@ -14268,18 +14121,18 @@ async function waitForReady(page, waitFor, timeoutMs) {
|
|
|
14268
14121
|
// src/engine/nodes/local/imagemagick.ts
|
|
14269
14122
|
import { execFile as execFile6 } from "child_process";
|
|
14270
14123
|
import { promisify as promisify6 } from "util";
|
|
14271
|
-
import { z as
|
|
14124
|
+
import { z as z18 } from "zod";
|
|
14272
14125
|
var execFileAsync3 = promisify6(execFile6);
|
|
14273
|
-
var OutputDecl2 =
|
|
14274
|
-
kind:
|
|
14275
|
-
ext:
|
|
14126
|
+
var OutputDecl2 = z18.object({
|
|
14127
|
+
kind: z18.enum(["image", "video", "audio"]),
|
|
14128
|
+
ext: z18.string().min(1).max(8)
|
|
14276
14129
|
}).strict();
|
|
14277
|
-
var ImageMagickParams =
|
|
14278
|
-
args:
|
|
14279
|
-
outputs:
|
|
14130
|
+
var ImageMagickParams = z18.object({
|
|
14131
|
+
args: z18.array(z18.string()).min(1),
|
|
14132
|
+
outputs: z18.record(z18.string(), OutputDecl2).default({})
|
|
14280
14133
|
}).strict();
|
|
14281
|
-
var ImageMagickInputs =
|
|
14282
|
-
var ImageMagickOutputs =
|
|
14134
|
+
var ImageMagickInputs = z18.record(z18.string(), z18.unknown());
|
|
14135
|
+
var ImageMagickOutputs = z18.record(z18.string(), z18.custom());
|
|
14283
14136
|
var resolvedBin;
|
|
14284
14137
|
async function resolveBin() {
|
|
14285
14138
|
if (resolvedBin) return resolvedBin;
|
|
@@ -14322,7 +14175,7 @@ var imagemagickNode = defineNode({
|
|
|
14322
14175
|
|
|
14323
14176
|
// src/engine/nodes/local/staticNative.ts
|
|
14324
14177
|
import { createRequire as createRequire4 } from "module";
|
|
14325
|
-
import { z as
|
|
14178
|
+
import { z as z22 } from "zod";
|
|
14326
14179
|
|
|
14327
14180
|
// src/engine/nodes/local/lib/native-assets.ts
|
|
14328
14181
|
import sharp from "sharp";
|
|
@@ -14439,7 +14292,7 @@ function leadingMark(opaque, box) {
|
|
|
14439
14292
|
import { createHash as createHash2 } from "crypto";
|
|
14440
14293
|
import { readFile as readFile15 } from "fs/promises";
|
|
14441
14294
|
import sharp5 from "sharp";
|
|
14442
|
-
import { z as
|
|
14295
|
+
import { z as z21 } from "zod";
|
|
14443
14296
|
|
|
14444
14297
|
// src/engine/landing-library/visualHash.ts
|
|
14445
14298
|
import sharp2 from "sharp";
|
|
@@ -14845,7 +14698,7 @@ import { mkdtemp as mkdtemp7, readdir as readdir3, readFile as readFile14, rm as
|
|
|
14845
14698
|
import { tmpdir as tmpdir7 } from "os";
|
|
14846
14699
|
import path20 from "path";
|
|
14847
14700
|
import { promisify as promisify7 } from "util";
|
|
14848
|
-
import { z as
|
|
14701
|
+
import { z as z19 } from "zod";
|
|
14849
14702
|
var RENDER_REVIEW_MODEL = "~google/gemini-pro-latest";
|
|
14850
14703
|
var RENDER_REVIEW_FRAMES = 12;
|
|
14851
14704
|
var RENDER_REVIEW_WIDTH = 1024;
|
|
@@ -14862,27 +14715,6 @@ function usageFrom(model, body) {
|
|
|
14862
14715
|
};
|
|
14863
14716
|
}
|
|
14864
14717
|
var WATCH_MAX_BYTES = 60 * 1024 * 1024;
|
|
14865
|
-
var STATIC_REVIEW_ITEMS = [
|
|
14866
|
-
"text",
|
|
14867
|
-
"logo",
|
|
14868
|
-
"layout",
|
|
14869
|
-
"safe_zone",
|
|
14870
|
-
"picture",
|
|
14871
|
-
"subject",
|
|
14872
|
-
"person",
|
|
14873
|
-
"people",
|
|
14874
|
-
"one_idea",
|
|
14875
|
-
"thumbnail",
|
|
14876
|
-
"concept",
|
|
14877
|
-
"viewer",
|
|
14878
|
-
"proof",
|
|
14879
|
-
"sizes",
|
|
14880
|
-
"native",
|
|
14881
|
-
"other"
|
|
14882
|
-
];
|
|
14883
|
-
var FINDING_ELEMENTS = ["copy", "button", "logo", "scene_text", "picture", "other"];
|
|
14884
|
-
var FINDING_DEFECTS = ["cut", "missing", "unreadable", "other"];
|
|
14885
|
-
var FRAME_EDGES = ["top", "bottom", "left", "right"];
|
|
14886
14718
|
function sceneAt(windows, atS) {
|
|
14887
14719
|
return windows.find((w) => atS >= w.startS && atS < w.endS)?.file;
|
|
14888
14720
|
}
|
|
@@ -14908,24 +14740,24 @@ function buildFrameGrabArgs(video, atS, out) {
|
|
|
14908
14740
|
"-y"
|
|
14909
14741
|
];
|
|
14910
14742
|
}
|
|
14911
|
-
var FrameReview =
|
|
14743
|
+
var FrameReview = z19.object({
|
|
14912
14744
|
/** Something in shot that could not happen, or is being done wrong. */
|
|
14913
|
-
impossible:
|
|
14745
|
+
impossible: z19.array(z19.string()).default([]),
|
|
14914
14746
|
/** Objects present in frame that belong to no part of the scene. */
|
|
14915
|
-
strays:
|
|
14747
|
+
strays: z19.array(z19.string()).default([])
|
|
14916
14748
|
});
|
|
14917
|
-
var ReelReview =
|
|
14749
|
+
var ReelReview = z19.object({
|
|
14918
14750
|
/** Whether the people the script treats as one person stay one person. */
|
|
14919
|
-
cast_is_consistent:
|
|
14920
|
-
cast_note:
|
|
14751
|
+
cast_is_consistent: z19.boolean().default(true),
|
|
14752
|
+
cast_note: z19.string().default(""),
|
|
14921
14753
|
/** Whether the advertiser's mark is visible anywhere. */
|
|
14922
|
-
brand_mark_visible:
|
|
14754
|
+
brand_mark_visible: z19.boolean().default(false),
|
|
14923
14755
|
/** Whether the last frames close the ad rather than trailing off. */
|
|
14924
|
-
ends_on_a_close:
|
|
14925
|
-
ending_note:
|
|
14756
|
+
ends_on_a_close: z19.boolean().default(false),
|
|
14757
|
+
ending_note: z19.string().default(""),
|
|
14926
14758
|
/** Whether the ad ever cuts away from the presenter to show anything. */
|
|
14927
|
-
shows_more_than_a_face:
|
|
14928
|
-
shows_note:
|
|
14759
|
+
shows_more_than_a_face: z19.boolean().default(true),
|
|
14760
|
+
shows_note: z19.string().default("")
|
|
14929
14761
|
});
|
|
14930
14762
|
function buildFrameReviewPrompt(opts) {
|
|
14931
14763
|
const subject = opts.subject ? ` The advertiser is ${opts.subject}.` : "";
|
|
@@ -15018,20 +14850,20 @@ function buildWatchReviewPrompt(opts) {
|
|
|
15018
14850
|
4. DEFECTS. List anything else wrong, each with the exact timestamp where it happens \u2014 a moment maps to the shot that produced it, and a defect without one is a complaint rather than an instruction.
|
|
15019
14851
|
Answer JSON only: {"spoken_variety_matches":true|false,"variety_note":"...","voice_matches_mouth":true|false,"mouth_note":"...","cuts_are_clean":true|false,"cuts_note":"...","defects":[{"at":"MM:SS.s","kind":"cut|lipsync|audio|visual","what":"..."}]}`;
|
|
15020
14852
|
}
|
|
15021
|
-
var WatchReview =
|
|
15022
|
-
spoken_variety_matches:
|
|
15023
|
-
variety_note:
|
|
15024
|
-
voice_matches_mouth:
|
|
15025
|
-
mouth_note:
|
|
15026
|
-
cuts_are_clean:
|
|
15027
|
-
cuts_note:
|
|
14853
|
+
var WatchReview = z19.object({
|
|
14854
|
+
spoken_variety_matches: z19.boolean(),
|
|
14855
|
+
variety_note: z19.string().default(""),
|
|
14856
|
+
voice_matches_mouth: z19.boolean(),
|
|
14857
|
+
mouth_note: z19.string().default(""),
|
|
14858
|
+
cuts_are_clean: z19.boolean(),
|
|
14859
|
+
cuts_note: z19.string().default("")
|
|
15028
14860
|
});
|
|
15029
|
-
var WatchDefects =
|
|
15030
|
-
defects:
|
|
15031
|
-
|
|
15032
|
-
at:
|
|
15033
|
-
kind:
|
|
15034
|
-
what:
|
|
14861
|
+
var WatchDefects = z19.object({
|
|
14862
|
+
defects: z19.array(
|
|
14863
|
+
z19.object({
|
|
14864
|
+
at: z19.string().default(""),
|
|
14865
|
+
kind: z19.string().default(""),
|
|
14866
|
+
what: z19.string().default("")
|
|
15035
14867
|
})
|
|
15036
14868
|
).default([])
|
|
15037
14869
|
});
|
|
@@ -15056,10 +14888,10 @@ function classifyWatchDefects(json, windows) {
|
|
|
15056
14888
|
};
|
|
15057
14889
|
});
|
|
15058
14890
|
}
|
|
15059
|
-
var FidelityReview =
|
|
15060
|
-
delivers_the_ask:
|
|
15061
|
-
missing:
|
|
15062
|
-
contradicted:
|
|
14891
|
+
var FidelityReview = z19.object({
|
|
14892
|
+
delivers_the_ask: z19.boolean(),
|
|
14893
|
+
missing: z19.array(z19.string()).default([]),
|
|
14894
|
+
contradicted: z19.array(z19.string()).default([])
|
|
15063
14895
|
});
|
|
15064
14896
|
function buildFidelityPrompt(brief) {
|
|
15065
14897
|
return `Someone asked for an advertisement in these words:
|
|
@@ -15226,7 +15058,7 @@ async function videoDuration(video) {
|
|
|
15226
15058
|
return null;
|
|
15227
15059
|
}
|
|
15228
15060
|
}
|
|
15229
|
-
async function askGateway(model, parts, timeoutMs, spent, reasoning
|
|
15061
|
+
async function askGateway(model, parts, timeoutMs, spent, reasoning) {
|
|
15230
15062
|
const key = openRouterKey();
|
|
15231
15063
|
if (!key) return null;
|
|
15232
15064
|
try {
|
|
@@ -15237,8 +15069,7 @@ async function askGateway(model, parts, timeoutMs, spent, reasoning, extra = {})
|
|
|
15237
15069
|
model,
|
|
15238
15070
|
messages: [{ role: "user", content: parts }],
|
|
15239
15071
|
usage: { include: true },
|
|
15240
|
-
...reasoning ? { reasoning } : {}
|
|
15241
|
-
...extra
|
|
15072
|
+
...reasoning ? { reasoning } : {}
|
|
15242
15073
|
}),
|
|
15243
15074
|
signal: AbortSignal.timeout(timeoutMs)
|
|
15244
15075
|
});
|
|
@@ -15302,16 +15133,9 @@ async function askAboutImages(model, images, prompt, onSpend, timeoutMs = 6e4, r
|
|
|
15302
15133
|
reasoning
|
|
15303
15134
|
);
|
|
15304
15135
|
}
|
|
15305
|
-
async function askAboutText(model, prompt, onSpend
|
|
15136
|
+
async function askAboutText(model, prompt, onSpend) {
|
|
15306
15137
|
if (!hasOpenRouterKey()) return null;
|
|
15307
|
-
return await askGateway(
|
|
15308
|
-
model,
|
|
15309
|
-
[{ type: "text", text: prompt }],
|
|
15310
|
-
opts.timeoutMs ?? 6e4,
|
|
15311
|
-
(usage) => onSpend?.(usage),
|
|
15312
|
-
void 0,
|
|
15313
|
-
opts.request
|
|
15314
|
-
);
|
|
15138
|
+
return await askGateway(model, [{ type: "text", text: prompt }], 6e4, (usage) => onSpend?.(usage));
|
|
15315
15139
|
}
|
|
15316
15140
|
async function reviewRenderedVideo(opts) {
|
|
15317
15141
|
if (!hasOpenRouterKey()) return null;
|
|
@@ -15430,14 +15254,14 @@ function unreviewedNotes(state) {
|
|
|
15430
15254
|
|
|
15431
15255
|
// src/engine/nodes/local/lib/text-boxes.ts
|
|
15432
15256
|
import sharp4 from "sharp";
|
|
15433
|
-
import { z as
|
|
15257
|
+
import { z as z20 } from "zod";
|
|
15434
15258
|
var GRID = 1e3;
|
|
15435
|
-
var ReaderItem =
|
|
15436
|
-
text:
|
|
15437
|
-
kind:
|
|
15438
|
-
box_2d:
|
|
15259
|
+
var ReaderItem = z20.object({
|
|
15260
|
+
text: z20.string(),
|
|
15261
|
+
kind: z20.enum(["text", "logo"]),
|
|
15262
|
+
box_2d: z20.array(z20.number()).length(4)
|
|
15439
15263
|
});
|
|
15440
|
-
var ReaderAnswer =
|
|
15264
|
+
var ReaderAnswer = z20.object({ items: z20.array(ReaderItem) });
|
|
15441
15265
|
var fraction = (value) => Math.min(1, Math.max(0, value / GRID));
|
|
15442
15266
|
function textBoxesFromReader(answer) {
|
|
15443
15267
|
const parsed = ReaderAnswer.safeParse(answer);
|
|
@@ -15555,39 +15379,32 @@ function judgeWinner(judge) {
|
|
|
15555
15379
|
};
|
|
15556
15380
|
return [...judge].sort(byGate)[0] ?? null;
|
|
15557
15381
|
}
|
|
15558
|
-
var StaticFinding =
|
|
15559
|
-
severity:
|
|
15560
|
-
format:
|
|
15561
|
-
what:
|
|
15562
|
-
item: z22.enum(STATIC_REVIEW_ITEMS).catch("other"),
|
|
15563
|
-
element: z22.enum(FINDING_ELEMENTS).catch("other"),
|
|
15564
|
-
defect: z22.enum(FINDING_DEFECTS).catch("other"),
|
|
15565
|
-
edges: z22.array(z22.enum(FRAME_EDGES)).catch([])
|
|
15382
|
+
var StaticFinding = z21.object({
|
|
15383
|
+
severity: z21.enum(["blocking", "warning"]),
|
|
15384
|
+
format: z21.string().optional(),
|
|
15385
|
+
what: z21.string().trim().min(1)
|
|
15566
15386
|
});
|
|
15567
|
-
var
|
|
15387
|
+
var ABSENCE_NEVER_A_FINDING = /\b(?:missing|no|lacks?|without)\s+(?:an?\s+|any\s+|clear\s+|visible\s+)?(?:call to action|cta)\b|\b(?:does not|doesn't) (?:show|tell) the viewer what to do/i;
|
|
15388
|
+
var ALWAYS_BLOCKING = /^(?:SUBJECT|PERSON):/i;
|
|
15389
|
+
var ABOUT_OWN_COPY = /^COPY:|["“«]|\b(?:headline|subhead\w*|button|cta|tagline|copy)\b/i;
|
|
15390
|
+
var BLURRED_FIGURES = /\b(?:blur\w*|obscur\w*|unreadable|illegible|smudg\w*)\b(?=[^.]*\b(?:figures?|numbers?|digits?|amounts?|prices?|savings?|values?)\b)|\b(?:figures?|numbers?|digits?|amounts?|prices?|savings?|values?)\b(?=[^.]*\b(?:blur\w*|obscur\w*|unreadable|illegible|smudg\w*)\b)/i;
|
|
15568
15391
|
function classifyStaticFindings(json) {
|
|
15569
15392
|
const findings = json?.findings;
|
|
15570
15393
|
if (!Array.isArray(findings)) return [];
|
|
15571
15394
|
return findings.flatMap((raw) => {
|
|
15572
15395
|
const parsed = StaticFinding.safeParse(raw);
|
|
15573
15396
|
if (!parsed.success) return [];
|
|
15574
|
-
const { severity, format, what
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
defect: fields.defect,
|
|
15579
|
-
edges: [...new Set(fields.edges)]
|
|
15580
|
-
};
|
|
15581
|
-
if (kind.element === "button" && kind.defect === "missing") return [];
|
|
15582
|
-
const graded = ALWAYS_BLOCKING.has(kind.item) ? "blocking" : kind.element === "scene_text" && kind.defect === "unreadable" ? "warning" : severity;
|
|
15583
|
-
return [{ severity: graded, what: format?.trim() ? `${format.trim()}: ${what}` : what, kind }];
|
|
15397
|
+
const { severity, format, what } = parsed.data;
|
|
15398
|
+
if (ABSENCE_NEVER_A_FINDING.test(what)) return [];
|
|
15399
|
+
const graded = ALWAYS_BLOCKING.test(what) ? "blocking" : BLURRED_FIGURES.test(what) && !ABOUT_OWN_COPY.test(what) ? "warning" : severity;
|
|
15400
|
+
return [{ severity: graded, what: format?.trim() ? `${format.trim()}: ${what}` : what }];
|
|
15584
15401
|
});
|
|
15585
15402
|
}
|
|
15586
|
-
var CopyRead =
|
|
15587
|
-
format:
|
|
15588
|
-
string:
|
|
15403
|
+
var CopyRead = z21.object({
|
|
15404
|
+
format: z21.string().trim().min(1),
|
|
15405
|
+
string: z21.string(),
|
|
15589
15406
|
/** What the model read where the string should be; null when it found nothing like it. */
|
|
15590
|
-
seen:
|
|
15407
|
+
seen: z21.string().nullable()
|
|
15591
15408
|
});
|
|
15592
15409
|
function comparableCopy(text2) {
|
|
15593
15410
|
return text2.replace(/[‘’‚′]/g, "'").replace(/[“”„″]/g, '"').replace(/[‐-―−]/g, "-").replace(/…/g, "...").replace(/[«»]/g, '"').replace(/\s+/g, " ").trim().replace(/^"([^"]*)"$/, "$1").trim().toLowerCase();
|
|
@@ -15758,12 +15575,12 @@ function measuredFindings(input) {
|
|
|
15758
15575
|
if (!input.adapted || loadBearing(l)) return [blocking(said(l))];
|
|
15759
15576
|
return l.similarity >= MEANING_MATCH ? [] : [{ severity: "warning", what: `${format}: ${said(l)}` }];
|
|
15760
15577
|
}),
|
|
15761
|
-
...extraWordFindings(format, extra, input.boxes, [...copyBoxes]
|
|
15578
|
+
...extraWordFindings(format, extra, input.boxes, [...copyBoxes]),
|
|
15762
15579
|
...twice.map(
|
|
15763
15580
|
(t) => ({ severity: "warning", what: `${format}: "${t}" is on the ad twice; keep it once.` })
|
|
15764
15581
|
),
|
|
15765
15582
|
...misspelt.map((m) => blocking(`the brand's name reads "${m.read}", not "${m.name}".`)),
|
|
15766
|
-
...unbadgedClaims(input.lines, input.boxes
|
|
15583
|
+
...unbadgedClaims(input.lines, input.boxes).map(
|
|
15767
15584
|
(c) => blocking(
|
|
15768
15585
|
`"${c.line}" names ${c.platform}, but ${c.platform}'s logo is not on the ad: show its real badge, or drop the claim.`
|
|
15769
15586
|
)
|
|
@@ -15847,9 +15664,9 @@ function misspeltNames(texts, names) {
|
|
|
15847
15664
|
}
|
|
15848
15665
|
return out;
|
|
15849
15666
|
}
|
|
15850
|
-
function unbadgedClaims(lines, boxes
|
|
15667
|
+
function unbadgedClaims(lines, boxes) {
|
|
15851
15668
|
return lines.flatMap((line) => {
|
|
15852
|
-
const platform =
|
|
15669
|
+
const platform = claimedPlatform(line.text);
|
|
15853
15670
|
if (!platform) return [];
|
|
15854
15671
|
const naming = boxes.filter((b) => b.kind === "text" && namedPlatform(b.text) === platform);
|
|
15855
15672
|
const mentions = new RegExp(platform.pattern.source, `${platform.pattern.flags}g`);
|
|
@@ -15965,11 +15782,10 @@ function contextItems(opts) {
|
|
|
15965
15782
|
opts.thumbnails ? "7. THUMBNAIL: at thumbnail size the lead line (the largest words) cannot be read. Judge only whether those words can be read, nothing else. That is blocking: most people only ever see the ad that small." : "7. THUMBNAIL: the lead line (the largest words) would be unreadable on a phone feed. That is blocking."
|
|
15966
15783
|
],
|
|
15967
15784
|
sizes: opts.formats.length > 1 ? [
|
|
15968
|
-
|
|
15785
|
+
`12. SIZES: one size loses what the others are built on: the letter sheet, the email or chat window, the post card, the comic's panels and their order, the product the ad is about, or a size that shows something the others do not (blocking; start the finding with "SIZES:" and report it on the size that differs). Rearranged for its shape is fine.`
|
|
15969
15786
|
] : []
|
|
15970
15787
|
};
|
|
15971
15788
|
}
|
|
15972
|
-
var FINDING_FIELDS = `For each finding also give: "item", the item above it answers, in lower case (TEXT \u2192 "text", ONE IDEA \u2192 "one_idea", the 9:16 safe-zone item \u2192 "safe_zone"); "element", what on the ad it is about ("copy" the ad's own words, "button", "logo", "scene_text" words or figures printed on something in the picture that are not the copy, "picture" anything else drawn, else "other"); "defect" ("cut" when the frame's edge cuts it, "missing" when it is not there, "unreadable" when it is too blurred or small to read, else "other"); "edges", the frame edges that cut it, [] unless "cut". Write "what" without a label.`;
|
|
15973
15789
|
function buildStaticReviewPrompt(opts) {
|
|
15974
15790
|
const brand = opts.brand ? ` for ${opts.brand}` : "";
|
|
15975
15791
|
const order = opts.formats.join(", ");
|
|
@@ -15988,9 +15804,9 @@ function buildStaticReviewPrompt(opts) {
|
|
|
15988
15804
|
...storiesItem(opts),
|
|
15989
15805
|
// Tier 0, Oct 2026: the real defects were where a size was extended; 8 of 8 hand claims checked were wrong.
|
|
15990
15806
|
"5. PICTURE: a ghost, a smear, a seam, a blurred strip, a duplicated or mirrored object, or garbled pseudo-text in focus where a viewer reads it, typically where the picture was extended to fill a size (blocking). Text out of focus in the background (a screen, a shelf of books) is the scene; an ordinary object a viewer would not stop at on a phone is not malformed.",
|
|
15991
|
-
|
|
15807
|
+
`5a. SUBJECT: the ad's subject (its pet, person or product) shown more than once as visibly different ones where the ad means one, or a second, different subject beside it that leaves a viewer asking whose story it is (blocking; start the finding with "SUBJECT:").`,
|
|
15992
15808
|
...opts.uncastPeople && opts.uncastPeople.length > 0 ? [
|
|
15993
|
-
`5c. PERSON: the copy names ${opts.uncastPeople.join(" and ")}, real, and this ad was given no photo of them: a face shown as them or beside their name is a stranger wearing that name (blocking).`
|
|
15809
|
+
`5c. PERSON: the copy names ${opts.uncastPeople.join(" and ")}, real, and this ad was given no photo of them: a face shown as them or beside their name is a stranger wearing that name (blocking; start the finding with "PERSON:").`
|
|
15994
15810
|
] : [],
|
|
15995
15811
|
"5b. PEOPLE: a person's hands, face or body drawn so wrong a viewer would notice at a glance on a phone (warning). Never a detail you have to zoom in to see.",
|
|
15996
15812
|
"6. ONE IDEA: two or more elements compete equally for the eye, so no single promise leads (warning).",
|
|
@@ -16004,8 +15820,7 @@ function buildStaticReviewPrompt(opts) {
|
|
|
16004
15820
|
] : [],
|
|
16005
15821
|
"Blocking = it cannot ship as is (a broken logo, clipped content, a ghost or smear in the picture). Warning = it can ship but should improve.",
|
|
16006
15822
|
"Never report an element as missing because ads usually have one: judge only what this concept holds. An empty findings list is the right answer for a clean ad. Keep each item under twenty words.",
|
|
16007
|
-
|
|
16008
|
-
`Reply with only this JSON: {"findings":[{"severity":"blocking|warning","format":"4:5","item":"${STATIC_REVIEW_ITEMS.join("|")}","element":"${FINDING_ELEMENTS.join("|")}","defect":"${FINDING_DEFECTS.join("|")}","edges":["${FRAME_EDGES.join("|")}"],"what":"..."}]${copyShape}}`
|
|
15823
|
+
`Reply with only this JSON: {"findings":[{"severity":"blocking|warning","format":"4:5","what":"..."}]${copyShape}}`
|
|
16009
15824
|
].join("\n");
|
|
16010
15825
|
}
|
|
16011
15826
|
var SCENE_PRINT_SCALE = 0.45;
|
|
@@ -16019,10 +15834,10 @@ function mostOfALine(chunk, copy) {
|
|
|
16019
15834
|
return words.length > 0 && words.filter((w) => said.has(w)).length / words.length >= LINE_REPEAT_SHARE;
|
|
16020
15835
|
});
|
|
16021
15836
|
}
|
|
16022
|
-
var
|
|
15837
|
+
var CLAIM = /[%€$£]|\b(?:free|gratis|gratuit[oa]?s?|off|sale|oferta|descuento|rebajas?|promo)\b/i;
|
|
16023
15838
|
var PRICE = /[€$£]\s?\d|\d\s?[€$£]/;
|
|
16024
15839
|
var COPY_SIZE_SHARE = 0.8;
|
|
16025
|
-
function extraWordFindings(format, extra, boxes, copyBoxes
|
|
15840
|
+
function extraWordFindings(format, extra, boxes, copyBoxes) {
|
|
16026
15841
|
if (extra.length === 0) return [];
|
|
16027
15842
|
const heights = copyBoxes.map((b) => b.y1 - b.y0).filter((h) => h > 0);
|
|
16028
15843
|
const smallest = heights.length > 0 ? Math.min(...heights) : 0;
|
|
@@ -16035,9 +15850,7 @@ function extraWordFindings(format, extra, boxes, copyBoxes, offers) {
|
|
|
16035
15850
|
const usual = sorted[Math.floor(sorted.length / 2)] ?? 0;
|
|
16036
15851
|
const underCopy = extra.filter((chunk) => height(chunk) < usual * COPY_SIZE_SHARE);
|
|
16037
15852
|
const scenery = (chunk) => height(chunk) < smallest * SCENE_PRINT_SCALE || underCopy.length >= SCENE_BLOCK_LINES && underCopy.includes(chunk);
|
|
16038
|
-
const
|
|
16039
|
-
const claims = (chunk) => SIGN_CLAIM.test(chunk) || offered.has(normalizeCopy(chunk));
|
|
16040
|
-
const printed = (chunk) => usual > 0 && underCopy.includes(chunk) && (!claims(chunk) || scenery(chunk));
|
|
15853
|
+
const printed = (chunk) => usual > 0 && underCopy.includes(chunk) && (!CLAIM.test(chunk) || scenery(chunk));
|
|
16041
15854
|
const quote = (chunks) => chunks.map((e) => `"${e}"`).join(", ");
|
|
16042
15855
|
const asCopy = extra.filter((chunk) => !printed(chunk));
|
|
16043
15856
|
const prices = extra.filter((chunk) => printed(chunk) && PRICE.test(chunk));
|
|
@@ -16102,29 +15915,41 @@ async function samePictureSizes(current, previous) {
|
|
|
16102
15915
|
return same;
|
|
16103
15916
|
}
|
|
16104
15917
|
var AT_EDGE = 0.025;
|
|
15918
|
+
var CLIPPED = /\b(?:clipped|cut off|cut by|cropped|runs? off)\b|\btouch(?:es|ing)?\s+(?:the\s+)?(?:(?:top|bottom|left|right|frame)\b|edge)/i;
|
|
15919
|
+
var ABOUT_COPY = /\b(?:headline|text|copy|line|words?|title|subhead(?:line)?|tagline|price|letters?)\b|["“«]/i;
|
|
15920
|
+
var EDGE_WORD = [
|
|
15921
|
+
["top", /\btop\b/i],
|
|
15922
|
+
["bottom", /\bbottom\b/i],
|
|
15923
|
+
["left", /\bleft\b/i],
|
|
15924
|
+
["right", /\bright\b/i]
|
|
15925
|
+
];
|
|
16105
15926
|
var clearanceTo = (b, edge) => edge === "top" ? b.y0 : edge === "bottom" ? 1 - b.y1 : edge === "left" ? b.x0 : 1 - b.x1;
|
|
16106
|
-
function measuredBoxes(element, boxes, buttons) {
|
|
16107
|
-
if (element === "logo") return boxes.filter((b) => b.kind === "logo");
|
|
16108
|
-
if (element === "button") return buttons.flatMap((l) => boxesOfLine(l, boxes));
|
|
16109
|
-
return element === "copy" ? boxes.filter((b) => b.kind === "text") : [];
|
|
16110
|
-
}
|
|
16111
|
-
var cutByFrame = (f) => f.kind?.defect === "cut";
|
|
16112
15927
|
function corroborateEdgeClaims(findings, boxesByFormat, lines) {
|
|
16113
15928
|
const buttons = lines.filter((l) => /^(cta|button|call_to_action)$/.test(roleKey2(l.role)));
|
|
16114
15929
|
return findings.map((f) => {
|
|
16115
|
-
if (f.severity !== "blocking" || !
|
|
15930
|
+
if (f.severity !== "blocking" || !CLIPPED.test(f.what)) return f;
|
|
16116
15931
|
const format = f.what.match(/^([\d.]+:[\d.]+):/)?.[1];
|
|
16117
15932
|
const boxes = format ? boxesByFormat[format] : void 0;
|
|
16118
15933
|
if (!boxes) return f;
|
|
16119
|
-
const
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
const
|
|
16123
|
-
const
|
|
16124
|
-
const atEdge = measured.some((b) => named.some((edge) => clearanceTo(b, edge) - pad * (b.y1 - b.y0) <= AT_EDGE));
|
|
15934
|
+
const element = claimedBoxes(f.what, boxes, buttons);
|
|
15935
|
+
if (element.length === 0) return f;
|
|
15936
|
+
const named = claimedEdges(f.what);
|
|
15937
|
+
const pad = BUTTON.test(f.what) && !LOGO_CLAIM.test(f.what) ? BUTTON_PAD : 0;
|
|
15938
|
+
const atEdge = element.some((b) => named.some((edge) => clearanceTo(b, edge) - pad * (b.y1 - b.y0) <= AT_EDGE));
|
|
16125
15939
|
return atEdge ? f : { ...f, severity: "warning" };
|
|
16126
15940
|
});
|
|
16127
15941
|
}
|
|
15942
|
+
function claimedEdges(what) {
|
|
15943
|
+
const edges = EDGE_WORD.filter(([, word]) => word.test(what)).map(([edge]) => edge);
|
|
15944
|
+
return edges.length > 0 ? edges : ["top", "bottom", "left", "right"];
|
|
15945
|
+
}
|
|
15946
|
+
var LOGO_CLAIM = /\b(?:logos?|wordmark|mark)\b/i;
|
|
15947
|
+
var BUTTON = /\b(button|cta|call[- ]to[- ]action)\b/i;
|
|
15948
|
+
function claimedBoxes(what, boxes, buttons) {
|
|
15949
|
+
if (LOGO_CLAIM.test(what)) return boxes.filter((b) => b.kind === "logo");
|
|
15950
|
+
if (BUTTON.test(what)) return buttons.flatMap((l) => boxesOfLine(l, boxes));
|
|
15951
|
+
return ABOUT_COPY.test(what) ? boxes.filter((b) => b.kind === "text") : [];
|
|
15952
|
+
}
|
|
16128
15953
|
async function bytesOf(source) {
|
|
16129
15954
|
if (!/^https?:\/\//i.test(source)) return await readFile15(source);
|
|
16130
15955
|
const res = await fetch(source);
|
|
@@ -16172,25 +15997,12 @@ function fragmentOf(chunk, own, opts = {}) {
|
|
|
16172
15997
|
return shared / words.length >= share;
|
|
16173
15998
|
});
|
|
16174
15999
|
}
|
|
16175
|
-
var OFFER_MADE = 0.35;
|
|
16176
|
-
var OFFER_QUESTION = "Does `words` make an offer of its own: something free, a discount, a sale, a promotion, a deal or a price, in whatever language it is written? A sign, a label, a name or a screen's words are not an offer.";
|
|
16177
|
-
var offerQuestion = (words) => ({ instructions: { words, question: OFFER_QUESTION } });
|
|
16178
|
-
async function offersAmong(chunks, judge) {
|
|
16179
|
-
const worded = chunks.filter((c) => new RegExp("\\p{L}", "u").test(c));
|
|
16180
|
-
if (worded.length === 0) return /* @__PURE__ */ new Set();
|
|
16181
|
-
const judged = await judge(worded);
|
|
16182
|
-
return new Set(worded.filter((_, i) => (judged?.[i] ?? 0) >= OFFER_MADE));
|
|
16183
|
-
}
|
|
16184
16000
|
async function checkStaticImage(image, opts) {
|
|
16185
16001
|
const reading = await (opts.read ?? ((bytes) => readTextBoxes(bytes, { onSpend: opts.onSpend })))(image);
|
|
16186
16002
|
if (reading.status === "not_checked") return reading;
|
|
16187
16003
|
const palette = opts.palette ?? [];
|
|
16188
16004
|
const inks = palette.length > 0 ? await designedInks(image, opts.lines, reading.boxes) : [];
|
|
16189
16005
|
const boxes = opts.toFrame ? opts.toFrame(reading.boxes) : reading.boxes;
|
|
16190
|
-
const offers = await offersAmong(
|
|
16191
|
-
textMismatch(opts.lines, boxes, { allowed: opts.allowed }).extra,
|
|
16192
|
-
opts.judgeOffers ?? ((chunks) => askJev(chunks.map(offerQuestion), opts.onSpend))
|
|
16193
|
-
);
|
|
16194
16006
|
const findings = measuredFindings({
|
|
16195
16007
|
format: opts.format,
|
|
16196
16008
|
boxes,
|
|
@@ -16202,12 +16014,7 @@ async function checkStaticImage(image, opts) {
|
|
|
16202
16014
|
...opts.native ? { native: true } : {},
|
|
16203
16015
|
...opts.placements ? { placements: opts.placements } : {},
|
|
16204
16016
|
...opts.band ? { band: opts.band } : {},
|
|
16205
|
-
offBrand: offBrandInks(inks, palette)
|
|
16206
|
-
offers,
|
|
16207
|
-
cited: opts.cited ?? await citedPlatforms(
|
|
16208
|
-
opts.lines.map((l) => l.text),
|
|
16209
|
-
opts.judgeCitations ?? jevCitationJudge(opts.onSpend)
|
|
16210
|
-
)
|
|
16017
|
+
offBrand: offBrandInks(inks, palette)
|
|
16211
16018
|
});
|
|
16212
16019
|
return { status: "checked", findings, boxes };
|
|
16213
16020
|
}
|
|
@@ -16271,7 +16078,7 @@ function everySizeChecks(opts) {
|
|
|
16271
16078
|
};
|
|
16272
16079
|
}
|
|
16273
16080
|
async function confirmBlocking(findings, stills, formats, ask2) {
|
|
16274
|
-
const claims = findings.filter((f) => f.severity === "blocking" && !
|
|
16081
|
+
const claims = findings.filter((f) => f.severity === "blocking" && !CLIPPED.test(f.what));
|
|
16275
16082
|
if (claims.length === 0 || stills.length === 0) return [...findings];
|
|
16276
16083
|
const prompt = [
|
|
16277
16084
|
`These are the finished sizes of one ad, in order: ${formats.map((f, i) => `Image ${i + 1} is the ${f}`).join("; ")}.`,
|
|
@@ -16300,17 +16107,12 @@ async function reviewRenderedStatic(opts) {
|
|
|
16300
16107
|
return askAboutStills(shown, prompt, opts.onSpend);
|
|
16301
16108
|
};
|
|
16302
16109
|
const asking = ask2(true);
|
|
16303
|
-
const cited = await citedPlatforms(
|
|
16304
|
-
lines.map((l) => l.text),
|
|
16305
|
-
jevCitationJudge(opts.onSpend)
|
|
16306
|
-
);
|
|
16307
16110
|
const checks = await Promise.all(
|
|
16308
16111
|
originals.map(
|
|
16309
16112
|
(bytes, i) => checkStaticImage(bytes, {
|
|
16310
16113
|
...scrolledOff(every, opts.scrolled?.[formats[i] ?? ""] ?? []),
|
|
16311
16114
|
format: formats[i] ?? "",
|
|
16312
|
-
adapted: opts.masterFormat !== void 0 && formats[i] !== opts.masterFormat
|
|
16313
|
-
cited
|
|
16115
|
+
adapted: opts.masterFormat !== void 0 && formats[i] !== opts.masterFormat
|
|
16314
16116
|
})
|
|
16315
16117
|
)
|
|
16316
16118
|
);
|
|
@@ -16351,28 +16153,26 @@ var staticNativeNode = defineNode({
|
|
|
16351
16153
|
location: "local",
|
|
16352
16154
|
summary: "Draw a static ad that is a screen people know (a post on X, a WhatsApp or iMessage chat, a wall of Trustpilot or Google reviews, a note, a Slack message) exactly, from HTML, at one delivered size: the `native` block of the ad spec (prompt.json) is laid out in the platform's own layout, type and colours, with the brand's real logo and the photos given, fitted inside the size's safe area. Free and deterministic.",
|
|
16353
16155
|
when_to_use: "Scaffolded by `baker canvas scaffold-static-ad` for an ad spec with a `native` block, one node per size (`native_<size>`, role `static_native`). Edit prompt.json's `native` and run again: nothing is billed but the photos whose description changed.",
|
|
16354
|
-
inputs:
|
|
16156
|
+
inputs: z22.object({
|
|
16355
16157
|
spec: JsonRef,
|
|
16356
16158
|
/** The brand's logo files (every variant the brand has): the one that reads on the screen is used. */
|
|
16357
|
-
logos:
|
|
16159
|
+
logos: z22.array(ImageRef).optional(),
|
|
16358
16160
|
/** The brand's square mark, for an avatar; else cut from the logo's lockup. */
|
|
16359
16161
|
mark: ImageRef.optional(),
|
|
16360
16162
|
/** The brand's face, for the frame's headline. */
|
|
16361
16163
|
font: FontRef.optional(),
|
|
16362
16164
|
/** The photos the screen shows, in `params.photo_ids` order. */
|
|
16363
|
-
photos:
|
|
16165
|
+
photos: z22.array(ImageRef).optional()
|
|
16364
16166
|
}).strict(),
|
|
16365
|
-
params:
|
|
16366
|
-
format:
|
|
16367
|
-
placements:
|
|
16368
|
-
photo_ids:
|
|
16369
|
-
/** The interface's own words written for the viewer's language, when no checked ones exist (`needsInterfaceWords`). */
|
|
16370
|
-
interface_words: z23.record(z23.string(), z23.string()).optional()
|
|
16167
|
+
params: z22.object({
|
|
16168
|
+
format: z22.string().min(1),
|
|
16169
|
+
placements: z22.array(z22.string().min(1)).optional(),
|
|
16170
|
+
photo_ids: z22.array(z22.string().regex(/^PHOTO_\d+$/)).default([])
|
|
16371
16171
|
}).strict(),
|
|
16372
|
-
outputs:
|
|
16172
|
+
outputs: z22.object({
|
|
16373
16173
|
image: ImageRef,
|
|
16374
16174
|
// A chat's earliest lines this size scrolled up under its header to keep the rest readable.
|
|
16375
|
-
scrolled:
|
|
16175
|
+
scrolled: z22.array(z22.string()).optional()
|
|
16376
16176
|
}).strict(),
|
|
16377
16177
|
outputKinds: { image: "image" },
|
|
16378
16178
|
cost: () => ({ credits: 0, seconds_estimate: 4 }),
|
|
@@ -16431,7 +16231,6 @@ async function nativePage(spec, files, params) {
|
|
|
16431
16231
|
const native = spec.native;
|
|
16432
16232
|
if (!native) throw new Error("static_native: prompt.json has no `native` block to draw");
|
|
16433
16233
|
const size2 = deliveredSize(params.format);
|
|
16434
|
-
const words = readInterfaceWords(params.interface_words);
|
|
16435
16234
|
const assets = await pageAssets(
|
|
16436
16235
|
files,
|
|
16437
16236
|
params.photo_ids,
|
|
@@ -16447,8 +16246,7 @@ async function nativePage(spec, files, params) {
|
|
|
16447
16246
|
...spec.brand?.ink ? { ink: spec.brand.ink } : {},
|
|
16448
16247
|
...spec.brand?.accent ? { accent: spec.brand.accent } : {}
|
|
16449
16248
|
},
|
|
16450
|
-
...spec.viewer?.market?.language ? { language: spec.viewer.market.language } : {}
|
|
16451
|
-
...words ? { words } : {}
|
|
16249
|
+
...spec.viewer?.market?.language ? { language: spec.viewer.market.language } : {}
|
|
16452
16250
|
});
|
|
16453
16251
|
}
|
|
16454
16252
|
async function screenshot(html, size2, log) {
|
|
@@ -16472,13 +16270,13 @@ async function screenshot(html, size2, log) {
|
|
|
16472
16270
|
}
|
|
16473
16271
|
|
|
16474
16272
|
// src/engine/nodes/local/staticPlace.ts
|
|
16475
|
-
import { z as
|
|
16273
|
+
import { z as z25 } from "zod";
|
|
16476
16274
|
|
|
16477
16275
|
// src/engine/nodes/remote/image.ts
|
|
16478
|
-
import { z as
|
|
16276
|
+
import { z as z24 } from "zod";
|
|
16479
16277
|
|
|
16480
16278
|
// src/engine/nodes/remote/registry-params.ts
|
|
16481
|
-
import { z as
|
|
16279
|
+
import { z as z23 } from "zod";
|
|
16482
16280
|
function registryParamShape(kind, overrides = {}) {
|
|
16483
16281
|
const specs = Object.values(MODEL_REGISTRY[kind]);
|
|
16484
16282
|
const byParam = /* @__PURE__ */ new Map();
|
|
@@ -16499,7 +16297,7 @@ function unionOf(schemas) {
|
|
|
16499
16297
|
const kinds = [...new Set(schemas.map((schema) => schema.kind))];
|
|
16500
16298
|
if (kinds.length > 1) {
|
|
16501
16299
|
const [first, second, ...rest] = kinds.map((k) => unionOf(schemas.filter((s) => s.kind === k)));
|
|
16502
|
-
return
|
|
16300
|
+
return z23.union([first, second, ...rest]);
|
|
16503
16301
|
}
|
|
16504
16302
|
switch (kinds[0]) {
|
|
16505
16303
|
case "string":
|
|
@@ -16507,32 +16305,32 @@ function unionOf(schemas) {
|
|
|
16507
16305
|
case "number":
|
|
16508
16306
|
return numberUnion(schemas);
|
|
16509
16307
|
case "boolean":
|
|
16510
|
-
return
|
|
16308
|
+
return z23.boolean();
|
|
16511
16309
|
default:
|
|
16512
|
-
return
|
|
16310
|
+
return z23.unknown();
|
|
16513
16311
|
}
|
|
16514
16312
|
}
|
|
16515
16313
|
function stringUnion(schemas) {
|
|
16516
|
-
if (schemas.some((schema) => !schema.enum)) return
|
|
16314
|
+
if (schemas.some((schema) => !schema.enum)) return z23.string();
|
|
16517
16315
|
const values = [...new Set(schemas.flatMap((schema) => schema.enum ?? []))];
|
|
16518
|
-
return
|
|
16316
|
+
return z23.enum(values);
|
|
16519
16317
|
}
|
|
16520
16318
|
function numberUnion(schemas) {
|
|
16521
|
-
if (schemas.some((schema) => !schema.enum)) return
|
|
16319
|
+
if (schemas.some((schema) => !schema.enum)) return z23.number();
|
|
16522
16320
|
const values = [...new Set(schemas.flatMap((schema) => schema.enum ?? []))].sort((a, b) => a - b);
|
|
16523
|
-
return
|
|
16321
|
+
return z23.literal(values);
|
|
16524
16322
|
}
|
|
16525
16323
|
|
|
16526
16324
|
// src/engine/nodes/remote/image.ts
|
|
16527
|
-
var imageModelEnum =
|
|
16528
|
-
var ImageGenerateParams =
|
|
16325
|
+
var imageModelEnum = z24.enum(IMAGE_GENERATE_MODELS);
|
|
16326
|
+
var ImageGenerateParams = z24.object({
|
|
16529
16327
|
model: imageModelEnum,
|
|
16530
16328
|
...registryParamShape("image_generate", {
|
|
16531
|
-
prompt:
|
|
16329
|
+
prompt: z24.string().min(1),
|
|
16532
16330
|
// Recraft v4 vector controls, sent as the provider's own `controls` block. The
|
|
16533
16331
|
// registry types them as JSON; this is their shape.
|
|
16534
|
-
rgb_colors:
|
|
16535
|
-
background_rgb_color:
|
|
16332
|
+
rgb_colors: z24.array(z24.array(z24.number().int().min(0).max(255))),
|
|
16333
|
+
background_rgb_color: z24.array(z24.number().int().min(0).max(255))
|
|
16536
16334
|
})
|
|
16537
16335
|
}).strict();
|
|
16538
16336
|
var PRO_QUALITY = "max";
|
|
@@ -16550,9 +16348,9 @@ var imageGenerateNode = delegated({
|
|
|
16550
16348
|
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-image-2` for photorealistic and in-image text (NOTE: it has no `image_size`, and no 4:5 / 5:4 \u2014 route portrait ad formats through Gemini or `image_aspect_adapt`); Gemini variants for fast iteration, extreme ratios, 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. `n` (only on models that declare it, e.g. `openai/gpt-image-2.5-sunburst`) returns several candidates of the same brief from one call, billed per image like separate calls; wire the whole `images` array into `image_select` to keep the best.",
|
|
16551
16349
|
// `reference` is one image or an ordered array of images. The backend forwards
|
|
16552
16350
|
// each as a separate `image_url` to the provider (OpenRouter accepts many).
|
|
16553
|
-
inputs:
|
|
16351
|
+
inputs: z24.object({ reference: z24.union([ImageRef, z24.array(ImageRef).min(1)]).optional() }).loose(),
|
|
16554
16352
|
params: ImageGenerateParams,
|
|
16555
|
-
outputs:
|
|
16353
|
+
outputs: z24.object({ images: z24.array(ImageRef).min(1) }).strict(),
|
|
16556
16354
|
outputKinds: { images: "image" },
|
|
16557
16355
|
// Per image: `n` asks one call for several, and the provider bills each (measured: n=3
|
|
16558
16356
|
// cost exactly 3x n=1, prompt tokens included). Usage-billed, so this is only the pre-run
|
|
@@ -16682,9 +16480,6 @@ var MIN_EXTEND_SCALE = 0.6;
|
|
|
16682
16480
|
var GREY_LEVEL = 128;
|
|
16683
16481
|
var GREY = { r: GREY_LEVEL, g: GREY_LEVEL, b: GREY_LEVEL };
|
|
16684
16482
|
var FEATHER = 0.012;
|
|
16685
|
-
var KEEP_PAD = 0.3;
|
|
16686
|
-
var KEEP_FEATHER = 0.03;
|
|
16687
|
-
var MOVED = 120;
|
|
16688
16483
|
function ratioOf2(ratio) {
|
|
16689
16484
|
const [w = 1, h = 1] = ratio.split(":").map(Number);
|
|
16690
16485
|
return w / h;
|
|
@@ -16747,16 +16542,14 @@ function clearBusyMargins(rgba, frame, keep) {
|
|
|
16747
16542
|
}
|
|
16748
16543
|
return shown;
|
|
16749
16544
|
}
|
|
16750
|
-
async function mergeExtended(extended, placed, ratio
|
|
16545
|
+
async function mergeExtended(extended, placed, ratio) {
|
|
16751
16546
|
const ad = await sharp7(placed).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
16752
16547
|
const { width, height } = ad.info;
|
|
16753
16548
|
const bounds = placedBounds(ad.data, width, height);
|
|
16754
16549
|
if (!bounds) return sharp7(placed).removeAlpha().png().toBuffer();
|
|
16755
16550
|
const canvas = renderCanvas({ width, height }, ratio);
|
|
16756
16551
|
const paint = await sharp7(extended).removeAlpha().resize(canvas.width, canvas.height, { fit: "fill" }).extract({ left: canvas.left, top: canvas.top, width, height }).raw().toBuffer();
|
|
16757
|
-
const
|
|
16758
|
-
const boxes = keep.length > 0 && keptInPlace(keep, ad.data, paint, width, height) ? keptWeights(keep, width, height) : null;
|
|
16759
|
-
const weight = boxes ? (x, y) => Math.min(edge(x, y), boxes[y * width + x] ?? 0) : edge;
|
|
16552
|
+
const weight = edgeWeight(bounds, width, height, Math.max(1, Math.round(FEATHER * width)));
|
|
16760
16553
|
const out = Buffer.alloc(width * height * 3);
|
|
16761
16554
|
for (let y = 0; y < height; y++) {
|
|
16762
16555
|
for (let x = 0; x < width; x++) {
|
|
@@ -16795,86 +16588,37 @@ function edgeWeight(b, width, height, feather) {
|
|
|
16795
16588
|
ramp(b.y1 - y, b.y1 === height - 1)
|
|
16796
16589
|
);
|
|
16797
16590
|
}
|
|
16798
|
-
function keptWeights(keep, width, height) {
|
|
16799
|
-
const out = new Float32Array(width * height);
|
|
16800
|
-
const feather = Math.max(1, KEEP_FEATHER * width);
|
|
16801
|
-
for (const box of keep) {
|
|
16802
|
-
const pad = KEEP_PAD * (box.y1 - box.y0) * height;
|
|
16803
|
-
const x0 = box.x0 * width - pad;
|
|
16804
|
-
const x1 = box.x1 * width + pad;
|
|
16805
|
-
const y0 = box.y0 * height - pad;
|
|
16806
|
-
const y1 = box.y1 * height + pad;
|
|
16807
|
-
const ys = Math.max(0, Math.floor(y0 - feather));
|
|
16808
|
-
const ye = Math.min(height - 1, Math.ceil(y1 + feather));
|
|
16809
|
-
const xs = Math.max(0, Math.floor(x0 - feather));
|
|
16810
|
-
const xe = Math.min(width - 1, Math.ceil(x1 + feather));
|
|
16811
|
-
for (let y = ys; y <= ye; y++) {
|
|
16812
|
-
const dy = Math.max(0, y0 - y, y - y1);
|
|
16813
|
-
for (let x = xs; x <= xe; x++) {
|
|
16814
|
-
const dx = Math.max(0, x0 - x, x - x1);
|
|
16815
|
-
const t = Math.max(0, 1 - Math.hypot(dx, dy) / feather);
|
|
16816
|
-
const w = t * t * (3 - 2 * t);
|
|
16817
|
-
const i = y * width + x;
|
|
16818
|
-
if (w > (out[i] ?? 0)) out[i] = w;
|
|
16819
|
-
}
|
|
16820
|
-
}
|
|
16821
|
-
}
|
|
16822
|
-
return out;
|
|
16823
|
-
}
|
|
16824
|
-
function keptInPlace(keep, ad, paint, width, height) {
|
|
16825
|
-
const diffs = keep.map((box) => boxDiff(box, ad, paint, width, height)).filter((d) => d !== null).sort((a, b) => a - b);
|
|
16826
|
-
return (diffs[Math.floor(diffs.length / 2)] ?? Number.POSITIVE_INFINITY) <= MOVED;
|
|
16827
|
-
}
|
|
16828
|
-
function boxDiff(box, ad, paint, width, height) {
|
|
16829
|
-
let sum = 0;
|
|
16830
|
-
let n = 0;
|
|
16831
|
-
const xs = Math.floor(box.x0 * width);
|
|
16832
|
-
const xe = Math.min(width - 1, Math.ceil(box.x1 * width));
|
|
16833
|
-
for (let y = Math.floor(box.y0 * height); y <= Math.min(height - 1, Math.ceil(box.y1 * height)); y++) {
|
|
16834
|
-
for (let x = xs; x <= xe; x++) {
|
|
16835
|
-
const i = y * width + x;
|
|
16836
|
-
if ((ad[i * 4 + 3] ?? 0) === 0) continue;
|
|
16837
|
-
for (let c = 0; c < 3; c++) sum += Math.abs((ad[i * 4 + c] ?? 0) - (paint[i * 3 + c] ?? 0));
|
|
16838
|
-
n++;
|
|
16839
|
-
}
|
|
16840
|
-
}
|
|
16841
|
-
return n > 0 ? sum / n : null;
|
|
16842
|
-
}
|
|
16843
16591
|
|
|
16844
16592
|
// src/engine/nodes/local/staticPlace.ts
|
|
16845
|
-
var Usage =
|
|
16846
|
-
var Fit =
|
|
16847
|
-
var
|
|
16848
|
-
var
|
|
16849
|
-
var RenderRatio = z26.string().regex(/^\d+(?:\.\d+)?:\d+(?:\.\d+)?$/);
|
|
16850
|
-
var Copy = z26.array(z26.object({ text: z26.string().min(1), role: z26.string().optional() }).strict());
|
|
16593
|
+
var Usage = z25.object({ model: z25.string(), inputTokens: z25.number(), outputTokens: z25.number(), costUsd: z25.number() }).strict();
|
|
16594
|
+
var Fit = z25.object({ scale: z25.number(), x: z25.number(), y: z25.number() }).strict();
|
|
16595
|
+
var RenderRatio = z25.string().regex(/^\d+(?:\.\d+)?:\d+(?:\.\d+)?$/);
|
|
16596
|
+
var Copy = z25.array(z25.object({ text: z25.string().min(1), role: z25.string().optional() }).strict());
|
|
16851
16597
|
var staticPlaceNode = defineNode({
|
|
16852
16598
|
id: "static_place",
|
|
16853
|
-
version: "1.
|
|
16599
|
+
version: "1.0.0",
|
|
16854
16600
|
category: "image",
|
|
16855
16601
|
location: "local",
|
|
16856
|
-
summary: "Place a finished static ad's words and logo where no interface covers them and nothing runs off the frame, for an image model to paint the margins: reads where every line and the logo sit, shrinks the ad (to 60% at most) and sets it inside the size's safe area. `placed` keeps the margins clear (for `static_extend_merge`); `shown` is the same inside the model's render shape with grey margins, to paint
|
|
16602
|
+
summary: "Place a finished static ad's words and logo where no interface covers them and nothing runs off the frame, for an image model to paint the margins: reads where every line and the logo sit, shrinks the ad (to 60% at most) and sets it inside the size's safe area. `placed` keeps the margins clear (for `static_extend_merge`); `shown` is the same inside the model's render shape with grey margins, to paint. `fit` is null, and both are the ad unchanged, when nothing sits outside.",
|
|
16857
16603
|
when_to_use: "Added by a static ad's autofix round (place-and-extend) before an `image_generate` that paints the margins and a `static_extend_merge`; never scaffolded. One box-reader call (about $0.005).",
|
|
16858
|
-
inputs:
|
|
16859
|
-
params:
|
|
16860
|
-
format:
|
|
16861
|
-
placements:
|
|
16604
|
+
inputs: z25.object({ image: ImageRef }).strict(),
|
|
16605
|
+
params: z25.object({
|
|
16606
|
+
format: z25.string().min(1),
|
|
16607
|
+
placements: z25.array(z25.string().min(1)).optional(),
|
|
16862
16608
|
render_ratio: RenderRatio,
|
|
16863
16609
|
copy: Copy.optional()
|
|
16864
16610
|
}).strict(),
|
|
16865
|
-
outputs:
|
|
16611
|
+
outputs: z25.object({
|
|
16866
16612
|
placed: ImageRef,
|
|
16867
16613
|
shown: ImageRef,
|
|
16868
16614
|
fit: Fit.nullable(),
|
|
16869
|
-
|
|
16870
|
-
|
|
16871
|
-
reason: z26.enum(["not_read", "inside_band", "too_far"]).optional(),
|
|
16872
|
-
usage: z26.array(Usage).optional()
|
|
16615
|
+
reason: z25.enum(["not_read", "inside_band", "too_far"]).optional(),
|
|
16616
|
+
usage: z25.array(Usage).optional()
|
|
16873
16617
|
}).strict(),
|
|
16874
16618
|
outputKinds: { placed: "image", shown: "image" },
|
|
16875
16619
|
cost: () => ({ credits: 0, seconds_estimate: 5 }),
|
|
16876
16620
|
async execute({ inputs, params, ctx }) {
|
|
16877
|
-
const unchanged = { placed: inputs.image, shown: inputs.image, fit: null
|
|
16621
|
+
const unchanged = { placed: inputs.image, shown: inputs.image, fit: null };
|
|
16878
16622
|
const source = inputs.image.path ?? inputs.image.url;
|
|
16879
16623
|
if (!source) return { ...unchanged, reason: "not_read" };
|
|
16880
16624
|
const bytes = await bytesOf(source);
|
|
@@ -16910,30 +16654,20 @@ var staticPlaceNode = defineNode({
|
|
|
16910
16654
|
placed: placedRef,
|
|
16911
16655
|
shown: shownRef,
|
|
16912
16656
|
fit,
|
|
16913
|
-
keep: keptBoxes([...reading.boxes, ...boxes.filter((b) => !reading.boxes.includes(b))], fit),
|
|
16914
16657
|
...spend
|
|
16915
16658
|
};
|
|
16916
16659
|
}
|
|
16917
16660
|
});
|
|
16918
|
-
function keptBoxes(boxes, fit) {
|
|
16919
|
-
const at2 = (offset2, v) => Math.min(1, Math.max(0, offset2 + fit.scale * v));
|
|
16920
|
-
return boxes.map((b) => ({ x0: at2(fit.x, b.x0), y0: at2(fit.y, b.y0), x1: at2(fit.x, b.x1), y1: at2(fit.y, b.y1) }));
|
|
16921
|
-
}
|
|
16922
16661
|
var staticExtendMergeNode = defineNode({
|
|
16923
16662
|
id: "static_extend_merge",
|
|
16924
|
-
version: "1.
|
|
16663
|
+
version: "1.1.0",
|
|
16925
16664
|
category: "image",
|
|
16926
16665
|
location: "local",
|
|
16927
|
-
summary: "Have the image model paint a placed static ad's margins (`static_place`'s `shown`, with `paint`, an `image_generate`'s params) and lay the ad
|
|
16666
|
+
summary: "Have the image model paint a placed static ad's margins (`static_place`'s `shown`, with `paint`, an `image_generate`'s params) and lay the ad (`placed`) back over the painting: the ad's own pixels where it sits, the painting only in the margins, blended at their seam. Same pixel size as the ad. An ad that was never placed (`shown` is `placed`) passes on as it is, and nothing is painted or billed. A painting already made can be wired as `image` instead of `paint`.",
|
|
16928
16667
|
when_to_use: "Added by a static ad's autofix round after a `static_place`; never scaffolded. One picture at the painter's price, only for a size that was placed.",
|
|
16929
|
-
inputs:
|
|
16930
|
-
|
|
16931
|
-
|
|
16932
|
-
image: ImageRef.optional(),
|
|
16933
|
-
keep: z26.array(Span).optional()
|
|
16934
|
-
}).strict(),
|
|
16935
|
-
params: z26.object({ render_ratio: RenderRatio, paint: z26.record(z26.string(), z26.unknown()).optional() }).strict(),
|
|
16936
|
-
outputs: z26.object({ image: ImageRef }).strict(),
|
|
16668
|
+
inputs: z25.object({ placed: ImageRef, shown: ImageRef.optional(), image: ImageRef.optional() }).strict(),
|
|
16669
|
+
params: z25.object({ render_ratio: RenderRatio, paint: z25.record(z25.string(), z25.unknown()).optional() }).strict(),
|
|
16670
|
+
outputs: z25.object({ image: ImageRef }).strict(),
|
|
16937
16671
|
outputKinds: { image: "image" },
|
|
16938
16672
|
cost: ({ params }) => params.paint ? imageGenerateNode.cost?.({ params: params.paint }) ?? NO_COST : NO_COST,
|
|
16939
16673
|
async execute({ inputs, params, ctx }) {
|
|
@@ -16941,7 +16675,7 @@ var staticExtendMergeNode = defineNode({
|
|
|
16941
16675
|
const painted = painting?.path ?? painting?.url;
|
|
16942
16676
|
const placed = inputs.placed.path ?? inputs.placed.url;
|
|
16943
16677
|
if (!painted || !placed) return { image: inputs.placed };
|
|
16944
|
-
const merged = await mergeExtended(await bytesOf(painted), await bytesOf(placed), params.render_ratio
|
|
16678
|
+
const merged = await mergeExtended(await bytesOf(painted), await bytesOf(placed), params.render_ratio);
|
|
16945
16679
|
const image = await ctx.assets.ingestBytes({ bytes: merged, kind: "image", mime: "image/png" });
|
|
16946
16680
|
return { image };
|
|
16947
16681
|
}
|
|
@@ -16965,7 +16699,7 @@ async function paintMargins(inputs, paint, ctx) {
|
|
|
16965
16699
|
|
|
16966
16700
|
// src/engine/nodes/local/staticPrefilter.ts
|
|
16967
16701
|
import { readFile as readFile16 } from "fs/promises";
|
|
16968
|
-
import { z as
|
|
16702
|
+
import { z as z26 } from "zod";
|
|
16969
16703
|
|
|
16970
16704
|
// src/engine/nodes/local/lib/variant-prefilter.ts
|
|
16971
16705
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -17183,8 +16917,8 @@ The candidates are Image 1 to Image ${jpegs.length}, in the order shown. Reply w
|
|
|
17183
16917
|
}
|
|
17184
16918
|
|
|
17185
16919
|
// src/engine/nodes/local/staticPrefilter.ts
|
|
17186
|
-
var Dropped =
|
|
17187
|
-
var Usage2 =
|
|
16920
|
+
var Dropped = z26.object({ index: z26.number().int().min(0), reasons: z26.array(z26.string()) }).strict();
|
|
16921
|
+
var Usage2 = z26.object({ model: z26.string(), inputTokens: z26.number(), outputTokens: z26.number(), costUsd: z26.number() }).strict();
|
|
17188
16922
|
async function readSpec(ref) {
|
|
17189
16923
|
if (!ref?.path) return null;
|
|
17190
16924
|
try {
|
|
@@ -17203,42 +16937,42 @@ var staticPrefilterNode = defineNode({
|
|
|
17203
16937
|
passthroughRefs: true,
|
|
17204
16938
|
summary: "Rank a static ad's variants before the judge by the deterministic checks (blocking defects, copy read back, the brand's logo found, the button's words, words inside the safe area) and pass on only the best-ranked, so the judge only breaks an exact tie. Outputs them in an order seeded by the pictures (never the render order), `order` (which variant each one is) and `dropped` with each set-aside variant's reasons.",
|
|
17205
16939
|
when_to_use: "Scaffolded by `baker canvas scaffold-static-ad` between the variants and the judge; wire the variants' whole `images` list and the ad spec (`$ref:prompt.asset`). Never leaves the judge empty: when every variant fails, the one with the fewest defects passes as `flagged`, and the review names what is wrong. A variant the reader could not read passes unchecked.",
|
|
17206
|
-
inputs:
|
|
16940
|
+
inputs: z26.object({ images: z26.array(ImageRef).min(1), spec: JsonRef.optional() }).strict(),
|
|
17207
16941
|
// `master_shape` and `placements` are the ones the canvas's sizes were planned with, so the
|
|
17208
16942
|
// band the variants are checked against is the band every size is cut to keep.
|
|
17209
|
-
params:
|
|
17210
|
-
formats:
|
|
17211
|
-
master_shape:
|
|
17212
|
-
placements:
|
|
16943
|
+
params: z26.object({
|
|
16944
|
+
formats: z26.array(z26.string().min(1)).min(1),
|
|
16945
|
+
master_shape: z26.enum(MASTER_SHAPES).optional(),
|
|
16946
|
+
placements: z26.array(z26.string().min(1)).optional(),
|
|
17213
16947
|
// The judge, asked only on a real tie: its model, and the criteria compiled from prompt.json.
|
|
17214
|
-
judge_model:
|
|
17215
|
-
judge_prompt:
|
|
16948
|
+
judge_model: z26.string().min(1).optional(),
|
|
16949
|
+
judge_prompt: z26.string().min(1).optional(),
|
|
17216
16950
|
// What the checks read of prompt.json, projected on every run (`staticChecksOf`); without it, the `spec` input.
|
|
17217
|
-
checks:
|
|
17218
|
-
kind:
|
|
17219
|
-
lines:
|
|
17220
|
-
|
|
16951
|
+
checks: z26.object({
|
|
16952
|
+
kind: z26.literal("static_checks"),
|
|
16953
|
+
lines: z26.array(
|
|
16954
|
+
z26.object({ text: z26.string(), lettering: z26.string(), role: z26.string().optional() }).passthrough()
|
|
17221
16955
|
),
|
|
17222
|
-
buttons:
|
|
17223
|
-
brand:
|
|
17224
|
-
logo:
|
|
16956
|
+
buttons: z26.array(z26.string()),
|
|
16957
|
+
brand: z26.string().optional(),
|
|
16958
|
+
logo: z26.boolean()
|
|
17225
16959
|
}).optional()
|
|
17226
16960
|
}).strict(),
|
|
17227
|
-
outputs:
|
|
17228
|
-
images:
|
|
16961
|
+
outputs: z26.object({
|
|
16962
|
+
images: z26.array(ImageRef).min(1),
|
|
17229
16963
|
// Which variant (its place among the inputs) each of `images` is, so the judge's "Image k" maps back.
|
|
17230
|
-
order:
|
|
16964
|
+
order: z26.array(z26.number().int().min(0)).optional(),
|
|
17231
16965
|
// The picture every size is made from: the checks' first, or on a tie the judge's. Cut to the
|
|
17232
16966
|
// master's frame where every word stays whole when its centre would cut one, else as rendered.
|
|
17233
16967
|
image: ImageRef.optional(),
|
|
17234
16968
|
// Which variant it is (its place among the inputs).
|
|
17235
|
-
chosen:
|
|
16969
|
+
chosen: z26.number().int().min(0).optional(),
|
|
17236
16970
|
// The judge's ranking lines, when a tie was put to it.
|
|
17237
|
-
reasoning:
|
|
17238
|
-
dropped:
|
|
16971
|
+
reasoning: z26.string().optional(),
|
|
16972
|
+
dropped: z26.array(Dropped),
|
|
17239
16973
|
flagged: Dropped.optional(),
|
|
17240
16974
|
// What the reader's calls cost, as the gateway reported it (the run record carries it home).
|
|
17241
|
-
usage:
|
|
16975
|
+
usage: z26.array(Usage2).optional()
|
|
17242
16976
|
}).strict(),
|
|
17243
16977
|
outputKinds: { images: "image", image: "image" },
|
|
17244
16978
|
cost: () => ({ credits: 0, seconds_estimate: 1 }),
|
|
@@ -17284,10 +17018,10 @@ async function framed(picture, frame, size2, ctx) {
|
|
|
17284
17018
|
}
|
|
17285
17019
|
|
|
17286
17020
|
// src/engine/nodes/local/staticSafeFit.ts
|
|
17287
|
-
import { z as
|
|
17288
|
-
var Usage3 =
|
|
17289
|
-
var Fit2 =
|
|
17290
|
-
var Copy2 =
|
|
17021
|
+
import { z as z27 } from "zod";
|
|
17022
|
+
var Usage3 = z27.object({ model: z27.string(), inputTokens: z27.number(), outputTokens: z27.number(), costUsd: z27.number() }).strict();
|
|
17023
|
+
var Fit2 = z27.object({ scale: z27.number(), x: z27.number(), y: z27.number() }).strict();
|
|
17024
|
+
var Copy2 = z27.array(z27.object({ text: z27.string().min(1), role: z27.string().optional() }).strict());
|
|
17291
17025
|
var staticSafeFitNode = defineNode({
|
|
17292
17026
|
id: "static_safe_fit",
|
|
17293
17027
|
version: "1.0.0",
|
|
@@ -17295,19 +17029,19 @@ var staticSafeFitNode = defineNode({
|
|
|
17295
17029
|
location: "local",
|
|
17296
17030
|
summary: "Bring a finished static ad's words and logo inside the band its placement leaves clear (the Stories header, the Reels footer) without rendering again: reads where every line and the logo sit, then shrinks the picture a little (to 85% at most) and shifts it, carrying its plain edge into the uncovered margin. Outputs the same pixel size; `fit` is the scale and position used, or null with `reason` when the picture is passed on unchanged.",
|
|
17297
17031
|
when_to_use: "Scaffolded by `baker canvas scaffold-static-ad` after every 9:16 size (`safe_<size>`, role `static_safe_fit`). Free, besides one box-reader call (about $0.005, carried on the run record's review spend). A layout too tall or wide for the band at 85%, or whose edge is busy (cards, leaves, a scene that would smear), is passed on unchanged: the review still names what Reels or Stories covers, and the layout itself has to change (shorter copy, fewer lines).",
|
|
17298
|
-
inputs:
|
|
17032
|
+
inputs: z27.object({ image: ImageRef }).strict(),
|
|
17299
17033
|
// `placements` are the ones the canvas's sizes were planned with (the ad set's), so the band
|
|
17300
17034
|
// the fit clears is the one the review holds the size to.
|
|
17301
|
-
params:
|
|
17302
|
-
outputs:
|
|
17035
|
+
params: z27.object({ format: z27.string().min(1), placements: z27.array(z27.string().min(1)).optional(), copy: Copy2.optional() }).strict(),
|
|
17036
|
+
outputs: z27.object({
|
|
17303
17037
|
image: ImageRef,
|
|
17304
17038
|
fit: Fit2.nullable(),
|
|
17305
17039
|
// Why the picture passed unchanged: the reader could not read it, nothing was outside the
|
|
17306
17040
|
// band, clearing it needed more than the smallest shrink that still looks native, or the
|
|
17307
17041
|
// edge it would carry outward is not plain ground.
|
|
17308
|
-
reason:
|
|
17042
|
+
reason: z27.enum(["not_read", "inside_band", "too_far", "busy_edge"]).optional(),
|
|
17309
17043
|
// What the reader's call cost, as the gateway reported it (the run record carries it home).
|
|
17310
|
-
usage:
|
|
17044
|
+
usage: z27.array(Usage3).optional()
|
|
17311
17045
|
}).strict(),
|
|
17312
17046
|
outputKinds: { image: "image" },
|
|
17313
17047
|
cost: () => ({ credits: 0, seconds_estimate: 5 }),
|
|
@@ -17343,60 +17077,60 @@ var staticSafeFitNode = defineNode({
|
|
|
17343
17077
|
});
|
|
17344
17078
|
|
|
17345
17079
|
// src/engine/nodes/local/text.ts
|
|
17346
|
-
import { z as
|
|
17080
|
+
import { z as z28 } from "zod";
|
|
17347
17081
|
var textNode = defineNode({
|
|
17348
17082
|
id: "text",
|
|
17349
17083
|
version: "1.0.0",
|
|
17350
17084
|
category: "data",
|
|
17351
17085
|
location: "local",
|
|
17352
17086
|
summary: "A literal text value. Use for prompts, descriptions, copy.",
|
|
17353
|
-
inputs:
|
|
17354
|
-
params:
|
|
17355
|
-
outputs:
|
|
17087
|
+
inputs: z28.object({}).strict(),
|
|
17088
|
+
params: z28.object({ value: z28.string() }).strict(),
|
|
17089
|
+
outputs: z28.object({ text: z28.string() }).strict(),
|
|
17356
17090
|
cost: () => ({ credits: 0, seconds_estimate: 0 }),
|
|
17357
17091
|
execute: ({ params }) => Promise.resolve({ text: params.value })
|
|
17358
17092
|
});
|
|
17359
17093
|
|
|
17360
17094
|
// src/engine/nodes/local/videoConcat.ts
|
|
17361
|
-
import { z as
|
|
17095
|
+
import { z as z29 } from "zod";
|
|
17362
17096
|
var FFMPEG_BIN4 = "ffmpeg";
|
|
17363
|
-
var evenPixels = (axis) =>
|
|
17364
|
-
var Transition =
|
|
17365
|
-
type:
|
|
17366
|
-
duration_s:
|
|
17097
|
+
var evenPixels = (axis) => z29.number().int().positive().refine((n) => n % 2 === 0, `${axis} must be even: libx264 yuv420p rejects odd sizes`);
|
|
17098
|
+
var Transition = z29.object({
|
|
17099
|
+
type: z29.string().regex(/^[a-z]+$/, "an ffmpeg xfade transition name, e.g. fade, dissolve, zoomin, slideleft").describe("ffmpeg xfade transition name: fade, dissolve, zoomin, wipeleft, slideleft, \u2026"),
|
|
17100
|
+
duration_s: z29.number().positive().describe("Overlap length in seconds (0.4 is the scaffold default).")
|
|
17367
17101
|
}).strict();
|
|
17368
|
-
var ConcatClip =
|
|
17369
|
-
duration_s:
|
|
17102
|
+
var ConcatClip = z29.object({
|
|
17103
|
+
duration_s: z29.number().positive().describe(
|
|
17370
17104
|
"Seconds this clip occupies on the timeline. A clip with a `transition` must carry that much EXTRA footage past it (the overlap eats it), so the joined length is the sum of the clip durations."
|
|
17371
17105
|
),
|
|
17372
|
-
length:
|
|
17106
|
+
length: z29.enum(["source", "trim", "still"]).default("source").describe(
|
|
17373
17107
|
"How the clip gets its length. `source`: play the input as rendered (it must already be duration_s long, plus the transition tail). `trim`: cut duration_s (+ tail) out of a longer input, starting at `offset_s`. `still`: the input is an IMAGE held on screen for duration_s (+ tail)."
|
|
17374
17108
|
),
|
|
17375
|
-
offset_s:
|
|
17376
|
-
fit:
|
|
17109
|
+
offset_s: z29.number().min(0).optional().describe("`trim` only: where the window starts in the input, seconds."),
|
|
17110
|
+
fit: z29.enum(["fill", "pad"]).default("fill").describe(
|
|
17377
17111
|
"`fill` scales to cover the frame and center-crops (footage). `pad` fits inside and letterboxes in black (a real screenshot or designed graphic, whose edges must never be cropped)."
|
|
17378
17112
|
),
|
|
17379
17113
|
transition: Transition.optional().describe(
|
|
17380
17114
|
"The transition OUT of this clip into the next one. Absent = a hard cut."
|
|
17381
17115
|
),
|
|
17382
|
-
continues_frame:
|
|
17116
|
+
continues_frame: z29.boolean().optional().describe(
|
|
17383
17117
|
"True when this clip's first frame repeats the previous clip's last frame (a long shot split in two). `seam_dedup` drops the duplicate without shortening the timeline."
|
|
17384
17118
|
)
|
|
17385
17119
|
}).strict().refine((c) => c.offset_s === void 0 || c.length === "trim", {
|
|
17386
17120
|
message: "offset_s only applies to a `trim` clip",
|
|
17387
17121
|
path: ["offset_s"]
|
|
17388
17122
|
});
|
|
17389
|
-
var VideoConcatParams =
|
|
17123
|
+
var VideoConcatParams = z29.object({
|
|
17390
17124
|
width: evenPixels("width").describe("Output width in pixels (even). Every clip is normalized onto it."),
|
|
17391
17125
|
height: evenPixels("height").describe("Output height in pixels (even)."),
|
|
17392
|
-
fps:
|
|
17393
|
-
seam_dedup:
|
|
17126
|
+
fps: z29.number().int().min(1).max(120).default(30).describe("Output frame rate every clip is resampled to."),
|
|
17127
|
+
seam_dedup: z29.enum(["head", "tail", "off"]).default("head").describe(
|
|
17394
17128
|
"Which copy of a repeated splice frame survives on a `continues_frame` clip: `head` drops the continuing clip's first frame, `tail` drops the previous clip's last frame, `off` keeps both."
|
|
17395
17129
|
),
|
|
17396
|
-
clips:
|
|
17130
|
+
clips: z29.array(ConcatClip).min(1).describe("The clips in timeline order; clip i is wired as `inputs.c<i>`.")
|
|
17397
17131
|
}).strict();
|
|
17398
|
-
var VideoConcatInputs =
|
|
17399
|
-
var VideoConcatOutputs =
|
|
17132
|
+
var VideoConcatInputs = z29.record(z29.string(), z29.custom());
|
|
17133
|
+
var VideoConcatOutputs = z29.object({ video: z29.custom() }).strict();
|
|
17400
17134
|
function inputLengthS(clip2) {
|
|
17401
17135
|
return clip2.duration_s + (clip2.transition?.duration_s ?? 0);
|
|
17402
17136
|
}
|
|
@@ -17539,14 +17273,14 @@ var videoConcatNode = defineNode({
|
|
|
17539
17273
|
});
|
|
17540
17274
|
|
|
17541
17275
|
// src/engine/nodes/remote/audioVoiceConvert.ts
|
|
17542
|
-
import { z as
|
|
17543
|
-
var AudioVoiceConvertParams =
|
|
17544
|
-
model:
|
|
17276
|
+
import { z as z30 } from "zod";
|
|
17277
|
+
var AudioVoiceConvertParams = z30.object({
|
|
17278
|
+
model: z30.literal("elevenlabs/eleven_multilingual_sts_v2"),
|
|
17545
17279
|
/** Target voice id. Splice an upstream `voice_select` via `"{{voice_ref}}"`. */
|
|
17546
|
-
voice:
|
|
17547
|
-
output_format:
|
|
17280
|
+
voice: z30.string().min(1),
|
|
17281
|
+
output_format: z30.string().optional(),
|
|
17548
17282
|
/** Strip the source clip's background noise before re-voicing. */
|
|
17549
|
-
remove_background_noise:
|
|
17283
|
+
remove_background_noise: z30.boolean().optional()
|
|
17550
17284
|
}).strict();
|
|
17551
17285
|
var audioVoiceConvertNode = delegated({
|
|
17552
17286
|
id: "audio_voice_convert",
|
|
@@ -17554,12 +17288,12 @@ var audioVoiceConvertNode = delegated({
|
|
|
17554
17288
|
category: "audio",
|
|
17555
17289
|
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.",
|
|
17556
17290
|
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}}"`.',
|
|
17557
|
-
inputs:
|
|
17291
|
+
inputs: z30.object({
|
|
17558
17292
|
audio: AudioRef,
|
|
17559
17293
|
voice_ref: TextRef.optional()
|
|
17560
17294
|
}).strict(),
|
|
17561
17295
|
params: AudioVoiceConvertParams,
|
|
17562
|
-
outputs:
|
|
17296
|
+
outputs: z30.object({ audio: AudioRef }).strict(),
|
|
17563
17297
|
outputKinds: { audio: "audio" },
|
|
17564
17298
|
// Priced by the shared contract per minute of source audio. The quote cannot see
|
|
17565
17299
|
// the clip's length, so it assumes one minute; the charge uses the real length.
|
|
@@ -17570,33 +17304,33 @@ var audioVoiceConvertNode = delegated({
|
|
|
17570
17304
|
});
|
|
17571
17305
|
|
|
17572
17306
|
// src/engine/nodes/remote/dialogue.ts
|
|
17573
|
-
import { z as
|
|
17574
|
-
var DialogueInput =
|
|
17575
|
-
text:
|
|
17576
|
-
voice_id:
|
|
17307
|
+
import { z as z31 } from "zod";
|
|
17308
|
+
var DialogueInput = z31.object({
|
|
17309
|
+
text: z31.string().min(1),
|
|
17310
|
+
voice_id: z31.string().min(1)
|
|
17577
17311
|
});
|
|
17578
17312
|
var DIALOGUE_MODELS = ["elevenlabs/eleven_v3"];
|
|
17579
|
-
var DialogueParams =
|
|
17580
|
-
model:
|
|
17313
|
+
var DialogueParams = z31.object({
|
|
17314
|
+
model: z31.enum(DIALOGUE_MODELS),
|
|
17581
17315
|
/**
|
|
17582
17316
|
* Ordered list of lines, each tagged with the voice that should speak it.
|
|
17583
17317
|
* Up to 10 unique voice_ids; total text across all lines should stay under
|
|
17584
17318
|
* ~2000 characters for best quality (ElevenLabs guidance).
|
|
17585
17319
|
*/
|
|
17586
|
-
inputs:
|
|
17587
|
-
language_code:
|
|
17320
|
+
inputs: z31.array(DialogueInput).min(1).max(50),
|
|
17321
|
+
language_code: z31.string().optional(),
|
|
17588
17322
|
/** ElevenLabs voice/model settings passthrough (e.g. `{ stability: 0.5 }`). */
|
|
17589
|
-
settings:
|
|
17590
|
-
seed:
|
|
17591
|
-
apply_text_normalization:
|
|
17323
|
+
settings: z31.record(z31.string(), z31.unknown()).optional(),
|
|
17324
|
+
seed: z31.number().int().min(0).max(4294967295).optional(),
|
|
17325
|
+
apply_text_normalization: z31.enum(["auto", "on", "off"]).optional(),
|
|
17592
17326
|
/**
|
|
17593
17327
|
* When true, hits `/v1/text-to-dialogue/with-timestamps` and emits a
|
|
17594
17328
|
* separate `timestamps` output — character-level alignment plus
|
|
17595
17329
|
* per-voice segment markers usable for captions, lipsync, or
|
|
17596
17330
|
* beat-matched cuts in ad creatives.
|
|
17597
17331
|
*/
|
|
17598
|
-
with_timestamps:
|
|
17599
|
-
output_format:
|
|
17332
|
+
with_timestamps: z31.boolean().optional(),
|
|
17333
|
+
output_format: z31.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
|
|
17600
17334
|
}).strict().refine((p) => p.inputs.reduce((sum, line) => sum + line.text.length, 0) <= ELEVENLABS_MAX_TEXT_CHARS, {
|
|
17601
17335
|
message: `total dialogue text exceeds ${ELEVENLABS_MAX_TEXT_CHARS} characters`,
|
|
17602
17336
|
path: ["inputs"]
|
|
@@ -17607,36 +17341,36 @@ var dialogueNode = delegated({
|
|
|
17607
17341
|
category: "audio",
|
|
17608
17342
|
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.",
|
|
17609
17343
|
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.",
|
|
17610
|
-
inputs:
|
|
17344
|
+
inputs: z31.object({}).loose(),
|
|
17611
17345
|
params: DialogueParams,
|
|
17612
|
-
outputs:
|
|
17346
|
+
outputs: z31.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
|
|
17613
17347
|
outputKinds: { audio: "audio", timestamps: "json" },
|
|
17614
17348
|
// Priced by the shared contract, the same per-character rate the backend charges.
|
|
17615
17349
|
cost: ({ params }) => ({ credits: estimateNodeCredits({ nodeType: "dialogue", params }) ?? 1, seconds_estimate: 8 })
|
|
17616
17350
|
});
|
|
17617
17351
|
|
|
17618
17352
|
// src/engine/nodes/remote/imageAspectAdapt.ts
|
|
17619
|
-
import { z as
|
|
17353
|
+
import { z as z32 } from "zod";
|
|
17620
17354
|
var ASPECT_ADAPT_MODELS = [
|
|
17621
17355
|
"google/gemini-3-pro-image",
|
|
17622
17356
|
"google/gemini-3.1-flash-image",
|
|
17623
17357
|
"google/gemini-3-pro-image-preview",
|
|
17624
17358
|
"google/gemini-3.1-flash-image-preview"
|
|
17625
17359
|
];
|
|
17626
|
-
var ImageAspectAdaptParams =
|
|
17627
|
-
model:
|
|
17628
|
-
formats:
|
|
17629
|
-
platform:
|
|
17630
|
-
guidance:
|
|
17360
|
+
var ImageAspectAdaptParams = z32.object({
|
|
17361
|
+
model: z32.enum(ASPECT_ADAPT_MODELS),
|
|
17362
|
+
formats: z32.array(z32.enum(ASPECT_ADAPT_FORMATS)).min(1).max(6).refine((formats) => new Set(formats).size === formats.length, { message: "formats must be unique" }).optional(),
|
|
17363
|
+
platform: z32.enum(AD_FORMAT_PLATFORMS).optional(),
|
|
17364
|
+
guidance: z32.string().min(1).optional(),
|
|
17631
17365
|
// The exact lines the ad carries: the reframe keeps each, spelled as given, once.
|
|
17632
|
-
copy:
|
|
17366
|
+
copy: z32.array(z32.string().min(1)).min(1).max(20).optional(),
|
|
17633
17367
|
// Per target format, the fractions of each edge the platform's interface covers: words,
|
|
17634
17368
|
// logo and button stay inside. The static scaffold sets both from prompt.json and `staticSafeArea`.
|
|
17635
|
-
safe_areas:
|
|
17636
|
-
|
|
17637
|
-
|
|
17369
|
+
safe_areas: z32.record(
|
|
17370
|
+
z32.string(),
|
|
17371
|
+
z32.object({ top: z32.number(), bottom: z32.number(), left: z32.number(), right: z32.number() }).strict()
|
|
17638
17372
|
).optional(),
|
|
17639
|
-
image_size:
|
|
17373
|
+
image_size: z32.enum(OPENROUTER_IMAGE_SIZES).optional()
|
|
17640
17374
|
}).strict().refine((p) => p.formats === void 0 !== (p.platform === void 0), {
|
|
17641
17375
|
message: "provide exactly one of `formats` (explicit ratios) or `platform` (a placement preset)",
|
|
17642
17376
|
path: ["formats"]
|
|
@@ -17647,9 +17381,9 @@ var imageAspectAdaptNode = delegated({
|
|
|
17647
17381
|
category: "image",
|
|
17648
17382
|
summary: "Adapt ONE creative into multiple aspect ratios (Meta: 9:16 stories, 1:1 feed, 4:5, 16:9\u2026) in a single step. Give it either an explicit `formats` list OR a `platform` preset (meta_feed, meta_feed_stories, meta_all, linkedin, google_demandgen, tiktok, youtube) that expands to that platform's canonical set. 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 the resolved format list.",
|
|
17649
17383
|
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 set EITHER `formats` (exact ratios) OR `platform` (e.g. `meta_feed_stories`). Cost is estimated per format; formats matching the source ratio are free pass-throughs. Off-grid platform targets (e.g. Google 1.91:1) are not rendered here \u2014 crop them from the nearest ratio with `baker images crop`. Pick `google/gemini-3.1-flash-image` (Nano Banana 2) while iterating, `google/gemini-3-pro-image` (Nano Banana Pro) for final-quality adaptation.",
|
|
17650
|
-
inputs:
|
|
17384
|
+
inputs: z32.object({ source: ImageRef }).loose(),
|
|
17651
17385
|
params: ImageAspectAdaptParams,
|
|
17652
|
-
outputs:
|
|
17386
|
+
outputs: z32.object({ images: z32.array(ImageRef).min(1) }).strict(),
|
|
17653
17387
|
outputKinds: { images: "image" },
|
|
17654
17388
|
cost: ({ params }) => {
|
|
17655
17389
|
const p = params;
|
|
@@ -17662,18 +17396,18 @@ var imageAspectAdaptNode = delegated({
|
|
|
17662
17396
|
});
|
|
17663
17397
|
|
|
17664
17398
|
// src/engine/nodes/remote/imageBackgroundRemove.ts
|
|
17665
|
-
import { z as
|
|
17666
|
-
var ImageBackgroundRemoveParams =
|
|
17399
|
+
import { z as z33 } from "zod";
|
|
17400
|
+
var ImageBackgroundRemoveParams = z33.object({
|
|
17667
17401
|
// `fal/birefnet-v2` is the legacy id stored canvases carry. It stays the default for one
|
|
17668
17402
|
// release: every backend accepts it, while `replicate/birefnet` needs the backend that ships with
|
|
17669
17403
|
// this CLI, and the two deploy independently.
|
|
17670
|
-
model:
|
|
17671
|
-
operating_resolution:
|
|
17404
|
+
model: z33.enum(modelIdsAccepted("replicate/birefnet")).optional().default("fal/birefnet-v2"),
|
|
17405
|
+
operating_resolution: z33.enum(["1024x1024", "2048x2048", "2304x2304"]).optional(),
|
|
17672
17406
|
// DEPRECATED, accepted so stored canvases still parse, and ignored: the model this
|
|
17673
17407
|
// node runs (`men1scus/birefnet`) takes only an image and a resolution, has no
|
|
17674
17408
|
// variant and returns no mask. `mask_only: true` still returns the cutout.
|
|
17675
|
-
model_variant:
|
|
17676
|
-
mask_only:
|
|
17409
|
+
model_variant: z33.enum(["General Use (Light)", "General Use (Heavy)", "Matting", "Portrait", "DIS", "HRSOD", "COD"]).optional().meta({ deprecated: true, description: "Ignored: the model has no variants." }),
|
|
17410
|
+
mask_only: z33.boolean().optional().meta({ deprecated: true, description: "Ignored: the model returns the cutout only, never a mask." })
|
|
17677
17411
|
}).strict();
|
|
17678
17412
|
var imageBackgroundRemoveNode = delegated({
|
|
17679
17413
|
id: "image_background_remove",
|
|
@@ -17681,11 +17415,11 @@ var imageBackgroundRemoveNode = delegated({
|
|
|
17681
17415
|
category: "image",
|
|
17682
17416
|
summary: "Remove the background from an image and return a transparent PNG. Powered by BiRefNet (`men1scus/birefnet` on Replicate).",
|
|
17683
17417
|
when_to_use: "Use to extract subjects from photos for use as overlays in hyperframe compositions, product shots, or compositing pipelines. It returns the alpha-cut image only; there is no mask output and no model variant to pick.",
|
|
17684
|
-
inputs:
|
|
17418
|
+
inputs: z33.object({
|
|
17685
17419
|
image: ImageRef
|
|
17686
17420
|
}).strict(),
|
|
17687
17421
|
params: ImageBackgroundRemoveParams,
|
|
17688
|
-
outputs:
|
|
17422
|
+
outputs: z33.object({ image: ImageRef }).strict(),
|
|
17689
17423
|
outputKinds: { image: "image" },
|
|
17690
17424
|
cost: ({ params }) => ({
|
|
17691
17425
|
credits: estimateNodeCredits({ nodeType: "image_background_remove", params }) ?? 1,
|
|
@@ -17694,7 +17428,7 @@ var imageBackgroundRemoveNode = delegated({
|
|
|
17694
17428
|
});
|
|
17695
17429
|
|
|
17696
17430
|
// src/engine/nodes/remote/imageDescribe.ts
|
|
17697
|
-
import { z as
|
|
17431
|
+
import { z as z34 } from "zod";
|
|
17698
17432
|
var IMAGE_DESCRIBE_MODELS = ["~google/gemini-pro-latest", "~google/gemini-flash-latest"];
|
|
17699
17433
|
var imageDescribeNode = delegated({
|
|
17700
17434
|
id: "image_describe",
|
|
@@ -17702,27 +17436,27 @@ var imageDescribeNode = delegated({
|
|
|
17702
17436
|
category: "vision",
|
|
17703
17437
|
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.",
|
|
17704
17438
|
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.',
|
|
17705
|
-
inputs:
|
|
17706
|
-
params:
|
|
17707
|
-
model:
|
|
17708
|
-
focus:
|
|
17709
|
-
context:
|
|
17710
|
-
temperature:
|
|
17711
|
-
max_tokens:
|
|
17439
|
+
inputs: z34.object({ image: ImageRef }).loose(),
|
|
17440
|
+
params: z34.object({
|
|
17441
|
+
model: z34.enum(IMAGE_DESCRIBE_MODELS),
|
|
17442
|
+
focus: z34.string().optional(),
|
|
17443
|
+
context: z34.string().optional(),
|
|
17444
|
+
temperature: z34.number().min(0).max(2).optional(),
|
|
17445
|
+
max_tokens: z34.number().int().positive().optional()
|
|
17712
17446
|
}).strict(),
|
|
17713
|
-
outputs:
|
|
17447
|
+
outputs: z34.object({ description: JsonRef }).strict(),
|
|
17714
17448
|
outputKinds: { description: "json" },
|
|
17715
17449
|
cost: () => ({ credits: 2, seconds_estimate: 10 })
|
|
17716
17450
|
});
|
|
17717
17451
|
|
|
17718
17452
|
// src/engine/nodes/remote/imageLayerize.ts
|
|
17719
|
-
import { z as
|
|
17720
|
-
var ImageLayerizeParams =
|
|
17453
|
+
import { z as z35 } from "zod";
|
|
17454
|
+
var ImageLayerizeParams = z35.object({
|
|
17721
17455
|
/**
|
|
17722
17456
|
* What to separate. Read as extraction instructions, not as a generation
|
|
17723
17457
|
* prompt — "keep the bottle and its shadow as one layer", not "make it blue".
|
|
17724
17458
|
*/
|
|
17725
|
-
instructions:
|
|
17459
|
+
instructions: z35.string().max(600).optional()
|
|
17726
17460
|
}).strict();
|
|
17727
17461
|
var imageLayerizeNode = delegated({
|
|
17728
17462
|
id: "image_layerize",
|
|
@@ -17730,12 +17464,12 @@ var imageLayerizeNode = delegated({
|
|
|
17730
17464
|
category: "image",
|
|
17731
17465
|
summary: "Split an image into editable layers: a transparent PNG per element, plus any copy baked into the picture recovered as real text with its typography. Powered by Seedream 5.0 Pro Layerize, with Ideogram lifting the text off first.",
|
|
17732
17466
|
when_to_use: "Use when you need ONE element of a picture on its own (a product with no background, a logo lockup), or when a shot is right but its copy is wrong and regenerating would change the picture too. The manifest gives you each layer's box and, for text, its exact wording, font, size and colour \u2014 enough to re-set a headline in the original's type. For a plain subject cutout with no text involved, `image_background_remove` is cheaper and one call.",
|
|
17733
|
-
inputs:
|
|
17467
|
+
inputs: z35.object({
|
|
17734
17468
|
image: ImageRef
|
|
17735
17469
|
}).strict(),
|
|
17736
17470
|
params: ImageLayerizeParams,
|
|
17737
|
-
outputs:
|
|
17738
|
-
layers:
|
|
17471
|
+
outputs: z35.object({
|
|
17472
|
+
layers: z35.array(ImageRef),
|
|
17739
17473
|
base: ImageRef,
|
|
17740
17474
|
manifest: JsonRef
|
|
17741
17475
|
}).strict(),
|
|
@@ -17746,24 +17480,24 @@ var imageLayerizeNode = delegated({
|
|
|
17746
17480
|
});
|
|
17747
17481
|
|
|
17748
17482
|
// src/engine/nodes/remote/imageReferenceSheet.ts
|
|
17749
|
-
import { z as
|
|
17483
|
+
import { z as z36 } from "zod";
|
|
17750
17484
|
var REFERENCE_SHEET_MODELS = [
|
|
17751
17485
|
"google/gemini-3-pro-image",
|
|
17752
17486
|
"google/gemini-3.1-flash-image",
|
|
17753
17487
|
"google/gemini-3-pro-image-preview",
|
|
17754
17488
|
"google/gemini-3.1-flash-image-preview"
|
|
17755
17489
|
];
|
|
17756
|
-
var ImageReferenceSheetParams =
|
|
17757
|
-
model:
|
|
17758
|
-
subject_description:
|
|
17490
|
+
var ImageReferenceSheetParams = z36.object({
|
|
17491
|
+
model: z36.enum(REFERENCE_SHEET_MODELS),
|
|
17492
|
+
subject_description: z36.string().min(1),
|
|
17759
17493
|
// `location` = a set/room shown from several camera ANGLES (not a rotated subject),
|
|
17760
17494
|
// so a multi-scene shoot keeps one consistent set.
|
|
17761
|
-
subject_type:
|
|
17762
|
-
views:
|
|
17763
|
-
style:
|
|
17764
|
-
prompt_override:
|
|
17765
|
-
aspect_ratio:
|
|
17766
|
-
image_size:
|
|
17495
|
+
subject_type: z36.enum(["character", "person", "product", "location"]),
|
|
17496
|
+
views: z36.array(z36.string().min(1)).min(2).max(8).optional(),
|
|
17497
|
+
style: z36.string().optional(),
|
|
17498
|
+
prompt_override: z36.string().min(1).optional(),
|
|
17499
|
+
aspect_ratio: z36.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(),
|
|
17500
|
+
image_size: z36.enum(OPENROUTER_IMAGE_SIZES).optional()
|
|
17767
17501
|
}).strict();
|
|
17768
17502
|
var imageReferenceSheetNode = delegated({
|
|
17769
17503
|
id: "image_reference_sheet",
|
|
@@ -17771,9 +17505,9 @@ var imageReferenceSheetNode = delegated({
|
|
|
17771
17505
|
category: "image",
|
|
17772
17506
|
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).",
|
|
17773
17507
|
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` for final 6-view sheets at 2K+, `google/gemini-3.1-flash-image` while iterating.",
|
|
17774
|
-
inputs:
|
|
17508
|
+
inputs: z36.object({ references: z36.array(ImageRef).min(1).max(6) }).loose(),
|
|
17775
17509
|
params: ImageReferenceSheetParams,
|
|
17776
|
-
outputs:
|
|
17510
|
+
outputs: z36.object({ sheet: ImageRef }).strict(),
|
|
17777
17511
|
outputKinds: { sheet: "image" },
|
|
17778
17512
|
cost: ({ params }) => ({
|
|
17779
17513
|
credits: imagePictureCredits(String(params?.model)),
|
|
@@ -17782,10 +17516,10 @@ var imageReferenceSheetNode = delegated({
|
|
|
17782
17516
|
});
|
|
17783
17517
|
|
|
17784
17518
|
// src/engine/nodes/remote/imageSearch.ts
|
|
17785
|
-
import { z as
|
|
17786
|
-
var ImageSearchParams =
|
|
17787
|
-
prompt:
|
|
17788
|
-
count:
|
|
17519
|
+
import { z as z37 } from "zod";
|
|
17520
|
+
var ImageSearchParams = z37.object({
|
|
17521
|
+
prompt: z37.string().min(1),
|
|
17522
|
+
count: z37.number().int().min(1).max(20).default(5)
|
|
17789
17523
|
}).strict();
|
|
17790
17524
|
var imageSearchNode = delegated({
|
|
17791
17525
|
id: "image_search",
|
|
@@ -17793,15 +17527,15 @@ var imageSearchNode = delegated({
|
|
|
17793
17527
|
category: "image",
|
|
17794
17528
|
summary: "Agentic image search across Google Images, free stock photography (Pexels and Pixabay), and Pinterest. An LLM agent picks the search tools and queries, selects the best matches, and the results are downloaded into canvas assets.",
|
|
17795
17529
|
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.",
|
|
17796
|
-
inputs:
|
|
17530
|
+
inputs: z37.object({}).loose(),
|
|
17797
17531
|
params: ImageSearchParams,
|
|
17798
|
-
outputs:
|
|
17532
|
+
outputs: z37.object({ images: z37.array(ImageRef).min(1) }).strict(),
|
|
17799
17533
|
outputKinds: { images: "image" },
|
|
17800
17534
|
cost: ({ params }) => ({ credits: Math.ceil(2 + params.count / 2), seconds_estimate: 30 })
|
|
17801
17535
|
});
|
|
17802
17536
|
|
|
17803
17537
|
// src/engine/nodes/remote/imageSelect.ts
|
|
17804
|
-
import { z as
|
|
17538
|
+
import { z as z38 } from "zod";
|
|
17805
17539
|
var IMAGE_SELECT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
17806
17540
|
var imageSelectNode = delegated({
|
|
17807
17541
|
id: "image_select",
|
|
@@ -17809,16 +17543,16 @@ var imageSelectNode = delegated({
|
|
|
17809
17543
|
category: "vision",
|
|
17810
17544
|
summary: "Pick the best `count` images out of the 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. A single candidate passes through unjudged, free and without reasoning.",
|
|
17811
17545
|
when_to_use: "Use after an image_generate run with `n` > 1 (wire its whole `images` array), several image_generate variants, or any pool of 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.",
|
|
17812
|
-
inputs:
|
|
17813
|
-
params:
|
|
17814
|
-
model:
|
|
17815
|
-
prompt:
|
|
17816
|
-
count:
|
|
17817
|
-
temperature:
|
|
17818
|
-
max_tokens:
|
|
17546
|
+
inputs: z38.object({ images: z38.array(ImageRef).min(1) }).loose(),
|
|
17547
|
+
params: z38.object({
|
|
17548
|
+
model: z38.enum(IMAGE_SELECT_MODELS),
|
|
17549
|
+
prompt: z38.string().min(1),
|
|
17550
|
+
count: z38.number().int().min(1).default(1),
|
|
17551
|
+
temperature: z38.number().min(0).max(2).optional(),
|
|
17552
|
+
max_tokens: z38.number().int().positive().optional()
|
|
17819
17553
|
}).strict(),
|
|
17820
17554
|
// No `reasoning` when a lone candidate passed through: there was no judgement to explain.
|
|
17821
|
-
outputs:
|
|
17555
|
+
outputs: z38.object({ images: z38.array(ImageRef).min(1), reasoning: TextRef.optional() }).strict(),
|
|
17822
17556
|
outputKinds: { images: "image", reasoning: "text" },
|
|
17823
17557
|
cost: () => ({ credits: 1, seconds_estimate: 5 }),
|
|
17824
17558
|
// Arity is only knowable at validate time when `images` is a literal array
|
|
@@ -17843,26 +17577,26 @@ var imageSelectNode = delegated({
|
|
|
17843
17577
|
});
|
|
17844
17578
|
|
|
17845
17579
|
// src/engine/nodes/remote/music.ts
|
|
17846
|
-
import { z as
|
|
17580
|
+
import { z as z39 } from "zod";
|
|
17847
17581
|
var MUSIC_MODELS = ["elevenlabs/music-v1", "elevenlabs/video-background-music-v1"];
|
|
17848
|
-
var MusicParams =
|
|
17849
|
-
model:
|
|
17582
|
+
var MusicParams = z39.object({
|
|
17583
|
+
model: z39.enum(MUSIC_MODELS),
|
|
17850
17584
|
/** Free-form prompt. Used by `elevenlabs/music-v1` (compose-detailed). */
|
|
17851
|
-
prompt:
|
|
17585
|
+
prompt: z39.string().optional(),
|
|
17852
17586
|
/**
|
|
17853
17587
|
* Structured composition plan (intro / hook / verse / outro sections with
|
|
17854
17588
|
* per-section styles + durations). Mutually exclusive with `prompt`.
|
|
17855
17589
|
*/
|
|
17856
|
-
composition_plan:
|
|
17590
|
+
composition_plan: z39.record(z39.string(), z39.unknown()).optional(),
|
|
17857
17591
|
/** Target length when using `prompt`. 3000–454545ms (capped by the $10 per-node cost limit). */
|
|
17858
|
-
music_length_ms:
|
|
17859
|
-
seed:
|
|
17592
|
+
music_length_ms: z39.number().int().min(3e3).max(ELEVENLABS_MAX_MUSIC_LENGTH_MS).optional(),
|
|
17593
|
+
seed: z39.number().int().optional(),
|
|
17860
17594
|
/** Prompt mode only — forces an instrumental (no vocals) track. */
|
|
17861
|
-
force_instrumental:
|
|
17595
|
+
force_instrumental: z39.boolean().optional(),
|
|
17862
17596
|
/** composition_plan only — honor exact section durations. */
|
|
17863
|
-
respect_sections_durations:
|
|
17597
|
+
respect_sections_durations: z39.boolean().optional(),
|
|
17864
17598
|
/** Emit word-level timestamps alongside the audio. */
|
|
17865
|
-
with_timestamps:
|
|
17599
|
+
with_timestamps: z39.boolean().optional(),
|
|
17866
17600
|
/**
|
|
17867
17601
|
* video-to-music only — short description of the desired score
|
|
17868
17602
|
* ("upbeat synth, fast cuts, 80s") used to bias the model.
|
|
@@ -17874,14 +17608,14 @@ var MusicParams = z40.object({
|
|
|
17874
17608
|
* confident") came back as a film score and the client's verdict was "la
|
|
17875
17609
|
* musica que has elegido es muy dramática".
|
|
17876
17610
|
*/
|
|
17877
|
-
description:
|
|
17611
|
+
description: z39.string().max(1e3).optional().describe(
|
|
17878
17612
|
"The score you want, in sound (instruments, tempo, texture) \u2014 not in feelings. Under speech, name ONE steady character and forbid movement: no build, no drop, no climax, no key change, nothing that 'follows the arc'. This model WATCHES the cut, so it will score the footage's emotion unless told not to \u2014 asking it to track a speaker's emotional arc is how a testimonial bed comes back dramatic. A bed is furniture; the voice carries the story."
|
|
17879
17613
|
),
|
|
17880
17614
|
/** video-to-music only — up to 10 style tags. */
|
|
17881
|
-
tags:
|
|
17615
|
+
tags: z39.array(z39.string()).max(10).optional().describe(
|
|
17882
17616
|
"Style tags. Under speech, prefer tags that hold it still (instrumental, understated, steady, no drums) over tags that move it (cinematic, epic, building, resolving, trailer)."
|
|
17883
17617
|
),
|
|
17884
|
-
output_format:
|
|
17618
|
+
output_format: z39.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
|
|
17885
17619
|
}).strict();
|
|
17886
17620
|
var musicNode = delegated({
|
|
17887
17621
|
id: "music",
|
|
@@ -17892,9 +17626,9 @@ var musicNode = delegated({
|
|
|
17892
17626
|
category: "audio",
|
|
17893
17627
|
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`.",
|
|
17894
17628
|
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.",
|
|
17895
|
-
inputs:
|
|
17629
|
+
inputs: z39.object({ video: VideoRef.optional() }).loose(),
|
|
17896
17630
|
params: MusicParams,
|
|
17897
|
-
outputs:
|
|
17631
|
+
outputs: z39.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
|
|
17898
17632
|
outputKinds: { audio: "audio", timestamps: "json" },
|
|
17899
17633
|
// Priced by the shared contract: per second of audio, 30s when no length is pinned.
|
|
17900
17634
|
cost: ({ params }) => {
|
|
@@ -17926,25 +17660,25 @@ var musicNode = delegated({
|
|
|
17926
17660
|
});
|
|
17927
17661
|
|
|
17928
17662
|
// src/engine/nodes/remote/soundEffect.ts
|
|
17929
|
-
import { z as
|
|
17663
|
+
import { z as z40 } from "zod";
|
|
17930
17664
|
var SOUND_EFFECT_MODELS = ["elevenlabs/eleven_text_to_sound_v2"];
|
|
17931
|
-
var SoundEffectParams =
|
|
17932
|
-
model:
|
|
17665
|
+
var SoundEffectParams = z40.object({
|
|
17666
|
+
model: z40.enum(SOUND_EFFECT_MODELS),
|
|
17933
17667
|
/** Prompt describing the SFX ("metal door slam", "soft UI tap", "ocean waves"). */
|
|
17934
|
-
text:
|
|
17668
|
+
text: z40.string().min(1),
|
|
17935
17669
|
/**
|
|
17936
17670
|
* Target length in seconds. 0.5–30. Leave unset to let the model pick the
|
|
17937
17671
|
* natural length for the described effect.
|
|
17938
17672
|
*/
|
|
17939
|
-
duration_seconds:
|
|
17673
|
+
duration_seconds: z40.number().min(0.5).max(30).optional(),
|
|
17940
17674
|
/**
|
|
17941
17675
|
* 0–1. Higher = stick closer to the prompt at the cost of variety; lower
|
|
17942
17676
|
* = let the model interpret more freely. Defaults to 0.3 on the provider.
|
|
17943
17677
|
*/
|
|
17944
|
-
prompt_influence:
|
|
17678
|
+
prompt_influence: z40.number().min(0).max(1).optional(),
|
|
17945
17679
|
/** Only valid on `eleven_text_to_sound_v2` — produce a seamless loop. */
|
|
17946
|
-
loop:
|
|
17947
|
-
output_format:
|
|
17680
|
+
loop: z40.boolean().optional(),
|
|
17681
|
+
output_format: z40.enum(ELEVENLABS_OUTPUT_FORMATS).optional()
|
|
17948
17682
|
}).strict();
|
|
17949
17683
|
var soundEffectNode = delegated({
|
|
17950
17684
|
id: "sound_effect",
|
|
@@ -17952,9 +17686,9 @@ var soundEffectNode = delegated({
|
|
|
17952
17686
|
category: "audio",
|
|
17953
17687
|
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.",
|
|
17954
17688
|
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.",
|
|
17955
|
-
inputs:
|
|
17689
|
+
inputs: z40.object({}).loose(),
|
|
17956
17690
|
params: SoundEffectParams,
|
|
17957
|
-
outputs:
|
|
17691
|
+
outputs: z40.object({ audio: AudioRef }).strict(),
|
|
17958
17692
|
outputKinds: { audio: "audio" },
|
|
17959
17693
|
// Priced by the shared contract: per second of audio, 5s when the model picks the length.
|
|
17960
17694
|
cost: ({ params }) => {
|
|
@@ -17964,7 +17698,7 @@ var soundEffectNode = delegated({
|
|
|
17964
17698
|
});
|
|
17965
17699
|
|
|
17966
17700
|
// src/engine/nodes/remote/textGenerate.ts
|
|
17967
|
-
import { z as
|
|
17701
|
+
import { z as z41 } from "zod";
|
|
17968
17702
|
var TEXT_GENERATE_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
17969
17703
|
var textGenerateNode = delegated({
|
|
17970
17704
|
id: "text_generate",
|
|
@@ -17972,58 +17706,58 @@ var textGenerateNode = delegated({
|
|
|
17972
17706
|
category: "language",
|
|
17973
17707
|
summary: "Single-turn LLM text generation via OpenRouter. Returns a text response.",
|
|
17974
17708
|
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.',
|
|
17975
|
-
inputs:
|
|
17976
|
-
params:
|
|
17977
|
-
model:
|
|
17978
|
-
prompt:
|
|
17979
|
-
system:
|
|
17980
|
-
response_format:
|
|
17981
|
-
web_search:
|
|
17982
|
-
temperature:
|
|
17983
|
-
max_tokens:
|
|
17709
|
+
inputs: z41.object({}).loose(),
|
|
17710
|
+
params: z41.object({
|
|
17711
|
+
model: z41.enum(TEXT_GENERATE_MODELS),
|
|
17712
|
+
prompt: z41.string().min(1),
|
|
17713
|
+
system: z41.string().optional(),
|
|
17714
|
+
response_format: z41.enum(["text", "json_object"]).optional(),
|
|
17715
|
+
web_search: z41.boolean().optional(),
|
|
17716
|
+
temperature: z41.number().min(0).max(2).optional(),
|
|
17717
|
+
max_tokens: z41.number().int().positive().optional()
|
|
17984
17718
|
}).strict(),
|
|
17985
|
-
outputs:
|
|
17719
|
+
outputs: z41.object({ text: TextRef }).strict(),
|
|
17986
17720
|
outputKinds: { text: "text" },
|
|
17987
17721
|
cost: () => ({ credits: 1, seconds_estimate: 3 })
|
|
17988
17722
|
});
|
|
17989
17723
|
|
|
17990
17724
|
// src/engine/nodes/remote/tts.ts
|
|
17991
|
-
import { z as
|
|
17725
|
+
import { z as z42 } from "zod";
|
|
17992
17726
|
var TTS_MODELS = ["elevenlabs/eleven_v3"];
|
|
17993
|
-
var TtsVoiceSettings =
|
|
17994
|
-
stability:
|
|
17995
|
-
similarity_boost:
|
|
17996
|
-
style:
|
|
17997
|
-
use_speaker_boost:
|
|
17998
|
-
speed:
|
|
17727
|
+
var TtsVoiceSettings = z42.object({
|
|
17728
|
+
stability: z42.number().min(0).max(1).optional(),
|
|
17729
|
+
similarity_boost: z42.number().min(0).max(1).optional(),
|
|
17730
|
+
style: z42.number().min(0).max(1).optional(),
|
|
17731
|
+
use_speaker_boost: z42.boolean().optional(),
|
|
17732
|
+
speed: z42.number().min(0.25).max(4).optional()
|
|
17999
17733
|
}).strict();
|
|
18000
|
-
var TtsPronunciationLocator =
|
|
18001
|
-
pronunciation_dictionary_id:
|
|
18002
|
-
version_id:
|
|
17734
|
+
var TtsPronunciationLocator = z42.object({
|
|
17735
|
+
pronunciation_dictionary_id: z42.string().min(1),
|
|
17736
|
+
version_id: z42.string().nullable().optional()
|
|
18003
17737
|
}).strict();
|
|
18004
|
-
var TtsParams =
|
|
18005
|
-
model:
|
|
18006
|
-
text:
|
|
18007
|
-
voice:
|
|
17738
|
+
var TtsParams = z42.object({
|
|
17739
|
+
model: z42.enum(TTS_MODELS),
|
|
17740
|
+
text: z42.string().min(1).max(ELEVENLABS_MAX_TEXT_CHARS),
|
|
17741
|
+
voice: z42.string().min(1),
|
|
18008
17742
|
/** Provider output_format (mp3 family only — assets are stored as audio/mpeg). */
|
|
18009
|
-
output_format:
|
|
18010
|
-
seed:
|
|
17743
|
+
output_format: z42.enum(ELEVENLABS_OUTPUT_FORMATS).optional(),
|
|
17744
|
+
seed: z42.number().int().min(0).max(4294967295).optional(),
|
|
18011
17745
|
// Top-level shortcuts; structured form is `voice_settings`.
|
|
18012
|
-
stability:
|
|
18013
|
-
similarity_boost:
|
|
17746
|
+
stability: z42.number().min(0).max(1).optional(),
|
|
17747
|
+
similarity_boost: z42.number().min(0).max(1).optional(),
|
|
18014
17748
|
voice_settings: TtsVoiceSettings.optional(),
|
|
18015
17749
|
/** ISO 639-1 language code. eleven_v3 supports language hints. */
|
|
18016
|
-
language_code:
|
|
18017
|
-
pronunciation_dictionary_locators:
|
|
18018
|
-
apply_text_normalization:
|
|
17750
|
+
language_code: z42.string().optional(),
|
|
17751
|
+
pronunciation_dictionary_locators: z42.array(TtsPronunciationLocator).max(3).optional(),
|
|
17752
|
+
apply_text_normalization: z42.enum(["auto", "on", "off"]).optional(),
|
|
18019
17753
|
/** Currently Japanese-only. Adds latency. */
|
|
18020
|
-
apply_language_text_normalization:
|
|
17754
|
+
apply_language_text_normalization: z42.boolean().optional(),
|
|
18021
17755
|
/**
|
|
18022
17756
|
* When true, hits `/v1/text-to-speech/{voice_id}/with-timestamps` and
|
|
18023
17757
|
* adds a `timestamps` output (character-level alignment) for caption
|
|
18024
17758
|
* rendering, lipsync, and beat-matched cuts.
|
|
18025
17759
|
*/
|
|
18026
|
-
with_timestamps:
|
|
17760
|
+
with_timestamps: z42.boolean().optional()
|
|
18027
17761
|
}).strict();
|
|
18028
17762
|
var ttsNode = delegated({
|
|
18029
17763
|
id: "tts",
|
|
@@ -18031,9 +17765,9 @@ var ttsNode = delegated({
|
|
|
18031
17765
|
category: "audio",
|
|
18032
17766
|
summary: "Single-voice text-to-speech via ElevenLabs Eleven v3. Optional character-level timestamps for caption rendering and beat-matched cuts.",
|
|
18033
17767
|
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).",
|
|
18034
|
-
inputs:
|
|
17768
|
+
inputs: z42.object({}).loose(),
|
|
18035
17769
|
params: TtsParams,
|
|
18036
|
-
outputs:
|
|
17770
|
+
outputs: z42.object({ audio: AudioRef, timestamps: JsonRef.optional() }).strict(),
|
|
18037
17771
|
outputKinds: { audio: "audio", timestamps: "json" },
|
|
18038
17772
|
// Priced by the shared contract, the same per-character rate the backend charges.
|
|
18039
17773
|
cost: ({ params }) => ({
|
|
@@ -18043,11 +17777,11 @@ var ttsNode = delegated({
|
|
|
18043
17777
|
});
|
|
18044
17778
|
|
|
18045
17779
|
// src/engine/nodes/remote/video.ts
|
|
18046
|
-
import { z as
|
|
18047
|
-
var videoModelEnum =
|
|
18048
|
-
var VideoGenerateParams =
|
|
17780
|
+
import { z as z43 } from "zod";
|
|
17781
|
+
var videoModelEnum = z43.enum(VIDEO_GENERATE_MODELS);
|
|
17782
|
+
var VideoGenerateParams = z43.object({
|
|
18049
17783
|
model: videoModelEnum,
|
|
18050
|
-
...registryParamShape("video_generate", { prompt:
|
|
17784
|
+
...registryParamShape("video_generate", { prompt: z43.string().min(1) })
|
|
18051
17785
|
}).strict();
|
|
18052
17786
|
var videoGenerateNode = delegated({
|
|
18053
17787
|
id: "video_generate",
|
|
@@ -18055,7 +17789,7 @@ var videoGenerateNode = delegated({
|
|
|
18055
17789
|
category: "video",
|
|
18056
17790
|
summary: "Generate video for ad creatives. Curated roster: `google/gemini-omni-1.1-flash` (DEFAULT \u2014 native audio + reference images in one call, 360p-4k, 3-10s), `bytedance/seedance-2.0` (identity/product workhorse, NO photoreal human faces), `google/veo-3.1` (photoreal cine ceiling + the human-face model), `google/veo-3.1-fast` (cheap Veo iteration), `kwaivgi/kling-v3.0-pro` (motion-transfer/dynamic). Async with polling.",
|
|
18057
17791
|
when_to_use: "`google/gemini-omni-1.1-flash` is the DEFAULT: it renders picture and synchronized audio in one call, and is the only model here that takes reference images alongside a prompt. SET `duration` (3-10s) to the length the beat actually needs \u2014 it bills PER SECOND, so leaving it out renders the 10s default and charges for ten seconds of a beat that may need three. SET `resolution` too: `360p` bills a THIRD of `720p` ($0.034 vs $0.101 per second) and is the right tier while you are still choosing between takes \u2014 render the drafts at 360p, then re-render the keeper at `720p` (default), `1080p` ($0.152/s) or `4k` ($0.304/s). It accepts only `16:9` or `9:16`, and takes NO `seed`, `negative_prompt` or `generate_audio` param \u2014 sending one fails validation, and it always renders audio. Keep the default for ordinary beats; move off it only when a beat needs a length outside 3-10s or a 1:1 frame. Use `bytedance/seedance-2.0` for identity/product output WITHOUT a photoreal human face on camera. Any clip whose frame shows a photoreal human face \u2014 a UGC creator, presenter, or testimonial speaker, REAL OR AI-GENERATED \u2014 must use `google/veo-3.1`: ByteDance's filter rejects photoreal human faces on Seedance (content_policy_blocked/E005), and a synthetic AI face does NOT clear it. Use `google/veo-3.1-fast` while iterating to keep cost low; `kwaivgi/kling-v3.0-pro` for motion-transfer/hyper-dynamic beats. `baker canvas scaffold-video` auto-routes a photoreal cast to Veo. A Seedance clip that is refused for a face at runtime is NOT moved to another model: one guided only by reference images is re-rendered on the same model through a second route that accepts faces (about twice the price), and one that starts from a frame comes back as content_policy_blocked \u2014 re-run that clip on `google/veo-3.1`. Each model gates its own durations, resolutions and aspect ratios in the registry: `baker studio models --kind video --model <id>` shows them.",
|
|
18058
|
-
inputs:
|
|
17792
|
+
inputs: z43.object({
|
|
18059
17793
|
first_frame: ImageRef.optional(),
|
|
18060
17794
|
last_frame: ImageRef.optional(),
|
|
18061
17795
|
reference: ImageRef.optional(),
|
|
@@ -18065,10 +17799,10 @@ var videoGenerateNode = delegated({
|
|
|
18065
17799
|
* exclusive with `first_frame` — OpenRouter treats a request carrying both
|
|
18066
17800
|
* as image-to-video and ignores these, so wire one or the other.
|
|
18067
17801
|
*/
|
|
18068
|
-
references:
|
|
17802
|
+
references: z43.array(ImageRef).optional()
|
|
18069
17803
|
}).loose(),
|
|
18070
17804
|
params: VideoGenerateParams,
|
|
18071
|
-
outputs:
|
|
17805
|
+
outputs: z43.object({ video: VideoRef }).strict(),
|
|
18072
17806
|
outputKinds: { video: "video" },
|
|
18073
17807
|
// Priced from the shared contract, so `validate`'s quote and the charge the
|
|
18074
17808
|
// backend applies come from ONE table. A flat number here under-quoted a
|
|
@@ -18083,15 +17817,15 @@ var videoGenerateNode = delegated({
|
|
|
18083
17817
|
});
|
|
18084
17818
|
|
|
18085
17819
|
// src/engine/nodes/remote/videoBackgroundRemove.ts
|
|
18086
|
-
import { z as
|
|
18087
|
-
var VideoBackgroundRemoveParams =
|
|
17820
|
+
import { z as z44 } from "zod";
|
|
17821
|
+
var VideoBackgroundRemoveParams = z44.object({
|
|
18088
17822
|
// `fal/veed-video-background-removal` is the legacy id stored canvases carry. It stays the default for one
|
|
18089
17823
|
// release: every backend accepts it, while `replicate/birefnet-video` needs the backend that ships with
|
|
18090
17824
|
// this CLI, and the two deploy independently.
|
|
18091
|
-
model:
|
|
17825
|
+
model: z44.enum(modelIdsAccepted("replicate/birefnet-video")).optional().default("fal/veed-video-background-removal"),
|
|
18092
17826
|
// Refines the matte's soft edges against background bleed (the model's `refine_fg`).
|
|
18093
|
-
edge_refinement:
|
|
18094
|
-
output_codec:
|
|
17827
|
+
edge_refinement: z44.boolean().optional().default(true),
|
|
17828
|
+
output_codec: z44.enum(["vp9", "h264"]).optional().default("vp9")
|
|
18095
17829
|
}).strict();
|
|
18096
17830
|
var videoBackgroundRemoveNode = delegated({
|
|
18097
17831
|
id: "video_background_remove",
|
|
@@ -18099,11 +17833,11 @@ var videoBackgroundRemoveNode = delegated({
|
|
|
18099
17833
|
category: "video",
|
|
18100
17834
|
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 BiRefNet Video (`sprited/birefnet-video` on Replicate).",
|
|
18101
17835
|
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.",
|
|
18102
|
-
inputs:
|
|
17836
|
+
inputs: z44.object({
|
|
18103
17837
|
video: VideoRef
|
|
18104
17838
|
}).strict(),
|
|
18105
17839
|
params: VideoBackgroundRemoveParams,
|
|
18106
|
-
outputs:
|
|
17840
|
+
outputs: z44.object({ video: VideoRef }).strict(),
|
|
18107
17841
|
outputKinds: { video: "video" },
|
|
18108
17842
|
// Priced by the shared contract per 30 frames; the quote assumes the same 30s of
|
|
18109
17843
|
// footage the charge falls back to when the clip carries no duration.
|
|
@@ -18114,7 +17848,7 @@ var videoBackgroundRemoveNode = delegated({
|
|
|
18114
17848
|
});
|
|
18115
17849
|
|
|
18116
17850
|
// src/engine/nodes/remote/videoDeconstruct.ts
|
|
18117
|
-
import { z as
|
|
17851
|
+
import { z as z45 } from "zod";
|
|
18118
17852
|
var VIDEO_DECONSTRUCT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
18119
17853
|
var videoDeconstructNode = delegated({
|
|
18120
17854
|
id: "video_deconstruct",
|
|
@@ -18122,34 +17856,34 @@ var videoDeconstructNode = delegated({
|
|
|
18122
17856
|
category: "video",
|
|
18123
17857
|
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).',
|
|
18124
17858
|
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.',
|
|
18125
|
-
inputs:
|
|
18126
|
-
params:
|
|
18127
|
-
model:
|
|
18128
|
-
mode:
|
|
18129
|
-
language:
|
|
18130
|
-
max_scenes:
|
|
18131
|
-
focus:
|
|
18132
|
-
start_s:
|
|
18133
|
-
end_s:
|
|
17859
|
+
inputs: z45.object({ video: VideoRef }).loose(),
|
|
17860
|
+
params: z45.object({
|
|
17861
|
+
model: z45.enum(VIDEO_DECONSTRUCT_MODELS),
|
|
17862
|
+
mode: z45.enum(["full", "index"]).optional(),
|
|
17863
|
+
language: z45.string().min(2).max(8).optional(),
|
|
17864
|
+
max_scenes: z45.number().int().min(1).max(60).optional(),
|
|
17865
|
+
focus: z45.string().optional(),
|
|
17866
|
+
start_s: z45.number().min(0).optional(),
|
|
17867
|
+
end_s: z45.number().positive().optional(),
|
|
18134
17868
|
// Real visual shot-cut timestamps (absolute seconds), detected locally with
|
|
18135
17869
|
// ffmpeg before the deconstruct. The backend SNAPS its LLM scene boundaries
|
|
18136
17870
|
// onto these and SPLITS any scene that spans one, so a scene's frames never
|
|
18137
17871
|
// straddle a hard cut. `scaffold-video` populates this; omit for LLM-only cuts.
|
|
18138
|
-
shot_cuts:
|
|
17872
|
+
shot_cuts: z45.array(z45.number().min(0)).max(200).optional(),
|
|
18139
17873
|
// The video model's per-clip ceiling (seconds). A shot longer than this is
|
|
18140
17874
|
// split into seamless continuation sub-scenes (shared splice frame), so long
|
|
18141
17875
|
// shots reproduce in full instead of being truncated. `scaffold-video` sets
|
|
18142
17876
|
// the Seedance ceiling (15); omit to disable length splitting.
|
|
18143
|
-
max_clip_s:
|
|
17877
|
+
max_clip_s: z45.number().positive().max(60).optional(),
|
|
18144
17878
|
// Transcript provider for the blueprint's dialogue/transcript. Default
|
|
18145
17879
|
// Groq Whisper; "deepgram" routes to Nova-3 so words carry punctuation.
|
|
18146
|
-
transcriber:
|
|
17880
|
+
transcriber: z45.enum(["groq", "deepgram"]).optional()
|
|
18147
17881
|
}).strict(),
|
|
18148
|
-
outputs:
|
|
17882
|
+
outputs: z45.object({
|
|
18149
17883
|
analysis: JsonRef,
|
|
18150
17884
|
// Absent in mode:"index" (structure only, no Mux frame extraction).
|
|
18151
|
-
start_frames:
|
|
18152
|
-
end_frames:
|
|
17885
|
+
start_frames: z45.array(ImageRef).min(1).optional(),
|
|
17886
|
+
end_frames: z45.array(ImageRef).min(1).optional(),
|
|
18153
17887
|
transcript: JsonRef
|
|
18154
17888
|
}).strict(),
|
|
18155
17889
|
outputKinds: { analysis: "json", start_frames: "image", end_frames: "image", transcript: "json" },
|
|
@@ -18157,21 +17891,21 @@ var videoDeconstructNode = delegated({
|
|
|
18157
17891
|
});
|
|
18158
17892
|
|
|
18159
17893
|
// src/engine/nodes/remote/videoLipsync.ts
|
|
18160
|
-
import { z as
|
|
18161
|
-
var VideoLipsyncParams =
|
|
18162
|
-
model:
|
|
17894
|
+
import { z as z46 } from "zod";
|
|
17895
|
+
var VideoLipsyncParams = z46.object({
|
|
17896
|
+
model: z46.enum(modelIdsAccepted("replicate/sync-lipsync-2"))
|
|
18163
17897
|
}).strict();
|
|
18164
17898
|
var videoLipsyncNode = delegated({
|
|
18165
17899
|
id: "video_lipsync",
|
|
18166
17900
|
version: "1.0.0",
|
|
18167
17901
|
category: "video",
|
|
18168
17902
|
summary: "Lip-sync a video to an audio track (`replicate/sync-lipsync-2`: Sync Labs lipsync-2 on Replicate; the legacy id `fal/veed-lipsync` still runs). Billed at $0.40 per minute of the longer input. Runs as a background job.",
|
|
18169
|
-
inputs:
|
|
17903
|
+
inputs: z46.object({
|
|
18170
17904
|
video: VideoRef,
|
|
18171
17905
|
audio: AudioRef
|
|
18172
17906
|
}).strict(),
|
|
18173
17907
|
params: VideoLipsyncParams,
|
|
18174
|
-
outputs:
|
|
17908
|
+
outputs: z46.object({ video: VideoRef }).strict(),
|
|
18175
17909
|
outputKinds: { video: "video" },
|
|
18176
17910
|
// Priced by the shared contract per minute of output; the quote cannot see the
|
|
18177
17911
|
// clips' lengths, so it assumes the same one minute the charge falls back to.
|
|
@@ -18185,7 +17919,7 @@ var videoLipsyncNode = delegated({
|
|
|
18185
17919
|
import { mkdtemp as mkdtemp8, readFile as readFile17, rm as rm8 } from "fs/promises";
|
|
18186
17920
|
import { tmpdir as tmpdir8 } from "os";
|
|
18187
17921
|
import path21 from "path";
|
|
18188
|
-
import { z as
|
|
17922
|
+
import { z as z47 } from "zod";
|
|
18189
17923
|
|
|
18190
17924
|
// src/engine/nodes/local/lib/ffmpeg.ts
|
|
18191
17925
|
import { execFile as execFile8 } from "child_process";
|
|
@@ -18423,30 +18157,30 @@ function timeScriptWords(script, transcript, language) {
|
|
|
18423
18157
|
}
|
|
18424
18158
|
|
|
18425
18159
|
// src/engine/nodes/remote/videoTranscribe.ts
|
|
18426
|
-
var VideoTranscribeParams =
|
|
18427
|
-
language:
|
|
18160
|
+
var VideoTranscribeParams = z47.object({
|
|
18161
|
+
language: z47.string().min(2).max(8).optional(),
|
|
18428
18162
|
// Provider choice is explicit (no env-based silent branching). Default Groq
|
|
18429
18163
|
// Whisper; "deepgram" routes to Deepgram Nova-3, which additionally emits a
|
|
18430
18164
|
// `rich` JSON output with punctuated words + paragraph/sentence grouping.
|
|
18431
|
-
transcriber:
|
|
18432
|
-
keyterms:
|
|
18165
|
+
transcriber: z47.enum(["groq", "deepgram"]).optional(),
|
|
18166
|
+
keyterms: z47.array(z47.string().min(2).max(60)).max(30).optional().describe(
|
|
18433
18167
|
"Names the recogniser must not mangle \u2014 a product, a person, a phrase the script coins. The COMPANY'S OWN NAME is always applied server-side, so you never list it: this is for the rest. Every term is biased into the request AND its spelling enforced on the way out, because a caption prints a mishearing at full size over the ad (a real cut shipped with the brand name spelled wrong). Never patch a misspelling inside a caption composition \u2014 that fix dies with that one creative."
|
|
18434
18168
|
),
|
|
18435
|
-
script:
|
|
18169
|
+
script: z47.string().min(1).optional().describe(
|
|
18436
18170
|
"The words the track was asked to say, when there is a script: the transcript's words are then the script's, spelled as written, at the times the recogniser heard them. A misheard word stops being a typo on screen, and a word the recogniser added (a filler) is never printed. Aligned locally, never sent."
|
|
18437
18171
|
)
|
|
18438
18172
|
}).strict();
|
|
18439
|
-
var VideoTranscribeInputs =
|
|
18173
|
+
var VideoTranscribeInputs = z47.object({
|
|
18440
18174
|
// A video (audio auto-extracted locally) OR a bare audio track. The key stays
|
|
18441
18175
|
// `video` for back-compat; the backend already accepts audio-kind refs on it —
|
|
18442
18176
|
// the local extraction path has been shipping one for every video input.
|
|
18443
|
-
video:
|
|
18177
|
+
video: z47.union([VideoRef, AudioRef])
|
|
18444
18178
|
}).strict();
|
|
18445
|
-
var VideoTranscribeOutputs =
|
|
18446
|
-
transcript:
|
|
18179
|
+
var VideoTranscribeOutputs = z47.object({
|
|
18180
|
+
transcript: z47.custom(),
|
|
18447
18181
|
// Only emitted by the Deepgram path: full punctuated words + paragraph /
|
|
18448
18182
|
// sentence grouping with speaker indices. Absent for the default Groq path.
|
|
18449
|
-
rich:
|
|
18183
|
+
rich: z47.custom().optional()
|
|
18450
18184
|
}).strict();
|
|
18451
18185
|
var AUDIO_EXTRACT_TIMEOUT_MS = 6e4;
|
|
18452
18186
|
var videoTranscribeNode = defineNode({
|
|
@@ -18559,7 +18293,7 @@ async function tryExtractAudio(inputs, ctx) {
|
|
|
18559
18293
|
}
|
|
18560
18294
|
|
|
18561
18295
|
// src/engine/nodes/remote/voiceSelect.ts
|
|
18562
|
-
import { z as
|
|
18296
|
+
import { z as z48 } from "zod";
|
|
18563
18297
|
var voiceSelectNode = delegated({
|
|
18564
18298
|
id: "voice_select",
|
|
18565
18299
|
// 1.1.0: a pinned voice used to come back as an inline `{ kind, text }` with no sha256
|
|
@@ -18577,27 +18311,27 @@ var voiceSelectNode = delegated({
|
|
|
18577
18311
|
category: "audio",
|
|
18578
18312
|
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.',
|
|
18579
18313
|
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. `language` is strict: when no voice on the account speaks it the node fails before anything is voiced (add a voice in that language, or pass its `voice_id`). A `gender` nobody in that language has is dropped and `baker canvas run` prints a `[warn]` naming the voice cast instead.',
|
|
18580
|
-
inputs:
|
|
18581
|
-
params:
|
|
18582
|
-
description:
|
|
18583
|
-
gender:
|
|
18584
|
-
age:
|
|
18585
|
-
accent:
|
|
18586
|
-
language:
|
|
18314
|
+
inputs: z48.object({}).loose(),
|
|
18315
|
+
params: z48.object({
|
|
18316
|
+
description: z48.string().min(1),
|
|
18317
|
+
gender: z48.string().optional(),
|
|
18318
|
+
age: z48.string().optional(),
|
|
18319
|
+
accent: z48.string().optional(),
|
|
18320
|
+
language: z48.string().optional(),
|
|
18587
18321
|
/**
|
|
18588
18322
|
* A voice already chosen — an Avatar's pinned one. Given, nothing is cast: the
|
|
18589
18323
|
* ranking is skipped and this id is emitted as-is.
|
|
18590
18324
|
*/
|
|
18591
|
-
voice_id:
|
|
18592
|
-
limit:
|
|
18325
|
+
voice_id: z48.string().min(1).max(120).optional(),
|
|
18326
|
+
limit: z48.number().int().min(1).max(20).optional()
|
|
18593
18327
|
}).strict(),
|
|
18594
|
-
outputs:
|
|
18328
|
+
outputs: z48.object({ voice_id: TextRef, candidates: JsonRef }).strict(),
|
|
18595
18329
|
outputKinds: { voice_id: "text", candidates: "json" },
|
|
18596
18330
|
cost: () => ({ credits: 0, seconds_estimate: 5 })
|
|
18597
18331
|
});
|
|
18598
18332
|
|
|
18599
18333
|
// src/engine/schema/catalog.ts
|
|
18600
|
-
import { z as
|
|
18334
|
+
import { z as z49 } from "zod";
|
|
18601
18335
|
function generateCatalog(registry, opts = {}) {
|
|
18602
18336
|
const entries2 = registry.all().map((def) => {
|
|
18603
18337
|
const cost = catalogCost(def);
|
|
@@ -18608,9 +18342,9 @@ function generateCatalog(registry, opts = {}) {
|
|
|
18608
18342
|
summary: def.summary,
|
|
18609
18343
|
when_to_use: def.when_to_use,
|
|
18610
18344
|
location: def.location,
|
|
18611
|
-
inputs:
|
|
18612
|
-
params:
|
|
18613
|
-
outputs:
|
|
18345
|
+
inputs: z49.toJSONSchema(def.inputs, { unrepresentable: "any" }),
|
|
18346
|
+
params: z49.toJSONSchema(def.params, { unrepresentable: "any" }),
|
|
18347
|
+
outputs: z49.toJSONSchema(def.outputs, { unrepresentable: "any" }),
|
|
18614
18348
|
cost_estimate_credits: cost?.credits,
|
|
18615
18349
|
runtime_estimate_seconds: cost?.seconds_estimate
|
|
18616
18350
|
};
|
|
@@ -18819,33 +18553,6 @@ function createEngineFromEnv(opts = {}) {
|
|
|
18819
18553
|
}
|
|
18820
18554
|
|
|
18821
18555
|
export {
|
|
18822
|
-
JEV_MODEL,
|
|
18823
|
-
askJevQuestions,
|
|
18824
|
-
askJev,
|
|
18825
|
-
looksLikeHttpUrl,
|
|
18826
|
-
MAX_REMOTE_IMAGE_BYTES,
|
|
18827
|
-
fetchExternalBytes,
|
|
18828
|
-
isVimeoAuthFailure,
|
|
18829
|
-
ytDlpBlockSignal,
|
|
18830
|
-
YtDlpError,
|
|
18831
|
-
runYtDlp,
|
|
18832
|
-
probeYtDlp,
|
|
18833
|
-
isAudioOnly,
|
|
18834
|
-
durationSeconds,
|
|
18835
|
-
canonicalUrl,
|
|
18836
|
-
RunAbortedError,
|
|
18837
|
-
LayerExecutionError,
|
|
18838
|
-
describeFailureReason,
|
|
18839
|
-
runFailureCode,
|
|
18840
|
-
sha256Hex,
|
|
18841
|
-
MAX_ASSET_BYTES,
|
|
18842
|
-
isPersistedAssetRef,
|
|
18843
|
-
collectAssetRefLikes,
|
|
18844
|
-
HttpClient,
|
|
18845
|
-
coercePathUrlToUrl,
|
|
18846
|
-
canvasAssetMime,
|
|
18847
|
-
inferMimeFromPath,
|
|
18848
|
-
toModelSafeImage,
|
|
18849
18556
|
asRecord,
|
|
18850
18557
|
shapeListRows,
|
|
18851
18558
|
VIEWER_TEMPERATURE_VALUES,
|
|
@@ -18856,16 +18563,13 @@ export {
|
|
|
18856
18563
|
classifyAudience,
|
|
18857
18564
|
AWARENESS_VALUES,
|
|
18858
18565
|
viewerFromKnowledge,
|
|
18859
|
-
|
|
18860
|
-
videoPathFromOutput,
|
|
18861
|
-
RENDER_REVIEW_UNAVAILABLE,
|
|
18862
|
-
askAboutImages,
|
|
18863
|
-
askAboutText,
|
|
18864
|
-
reviewRenderedVideo,
|
|
18865
|
-
reviewContextBeside,
|
|
18866
|
-
sceneWindowsBeside,
|
|
18566
|
+
HttpClient,
|
|
18867
18567
|
BackendClient,
|
|
18868
18568
|
requireCredentialsFromEnv,
|
|
18569
|
+
RunAbortedError,
|
|
18570
|
+
LayerExecutionError,
|
|
18571
|
+
describeFailureReason,
|
|
18572
|
+
runFailureCode,
|
|
18869
18573
|
AD_PLATFORMS,
|
|
18870
18574
|
deliverySet,
|
|
18871
18575
|
AD_FORMAT_PLATFORMS,
|
|
@@ -18920,9 +18624,14 @@ export {
|
|
|
18920
18624
|
BlueprintFloatingElementSchema,
|
|
18921
18625
|
resolveConcurrency,
|
|
18922
18626
|
ulid,
|
|
18627
|
+
isPersistedAssetRef,
|
|
18628
|
+
collectAssetRefLikes,
|
|
18629
|
+
sha256Hex,
|
|
18630
|
+
MAX_ASSET_BYTES,
|
|
18923
18631
|
REF_PREFIX,
|
|
18924
18632
|
parseRefExpr,
|
|
18925
18633
|
nodeDependencies,
|
|
18634
|
+
looksLikeHttpUrl,
|
|
18926
18635
|
SEEDANCE_PROFILE,
|
|
18927
18636
|
clipProfileFor,
|
|
18928
18637
|
clipParamRecipe,
|
|
@@ -18936,9 +18645,8 @@ export {
|
|
|
18936
18645
|
assignElementLabels,
|
|
18937
18646
|
referenceRole,
|
|
18938
18647
|
elementTodo,
|
|
18939
|
-
|
|
18940
|
-
|
|
18941
|
-
citedBy,
|
|
18648
|
+
claimedPlatform,
|
|
18649
|
+
ratingFigures,
|
|
18942
18650
|
figureForms,
|
|
18943
18651
|
nativeSurfaceName,
|
|
18944
18652
|
nativeCopy,
|
|
@@ -18946,10 +18654,6 @@ export {
|
|
|
18946
18654
|
nativePhotosShown,
|
|
18947
18655
|
avatarPhotoIds,
|
|
18948
18656
|
nativeGateProblems,
|
|
18949
|
-
needsInterfaceWords,
|
|
18950
|
-
INTERFACE_WORDS_PROMPT_VERSION,
|
|
18951
|
-
interfaceWordsPrompt,
|
|
18952
|
-
readInterfaceWords,
|
|
18953
18657
|
COPY_DIRECTIVE_HEADER,
|
|
18954
18658
|
copyStrings,
|
|
18955
18659
|
inSceneText,
|
|
@@ -18993,12 +18697,10 @@ export {
|
|
|
18993
18697
|
STATIC_SAFE_FIT_ROLE,
|
|
18994
18698
|
STATIC_PLACE_ROLE,
|
|
18995
18699
|
LOGO_COMPOSITE_ROLE,
|
|
18996
|
-
SERIES_LEAD_ROLE,
|
|
18997
18700
|
STATIC_FIX_ROLE,
|
|
18998
18701
|
LOGO_COMPOSITE_DEFAULT,
|
|
18999
18702
|
nodesWithRole,
|
|
19000
18703
|
rolesById,
|
|
19001
|
-
relaidPictureRole,
|
|
19002
18704
|
formatSlug,
|
|
19003
18705
|
staticMasterFormat,
|
|
19004
18706
|
staticRenderNodes,
|
|
@@ -19011,8 +18713,30 @@ export {
|
|
|
19011
18713
|
wordCount2 as wordCount,
|
|
19012
18714
|
estSpeechS,
|
|
19013
18715
|
estSpeechWindowS,
|
|
18716
|
+
MAX_REMOTE_IMAGE_BYTES,
|
|
18717
|
+
fetchExternalBytes,
|
|
18718
|
+
isVimeoAuthFailure,
|
|
18719
|
+
ytDlpBlockSignal,
|
|
18720
|
+
YtDlpError,
|
|
18721
|
+
runYtDlp,
|
|
18722
|
+
probeYtDlp,
|
|
18723
|
+
isAudioOnly,
|
|
18724
|
+
durationSeconds,
|
|
18725
|
+
canonicalUrl,
|
|
18726
|
+
coercePathUrlToUrl,
|
|
18727
|
+
canvasAssetMime,
|
|
18728
|
+
inferMimeFromPath,
|
|
18729
|
+
toModelSafeImage,
|
|
19014
18730
|
loadCompositionMeta,
|
|
19015
18731
|
perceptualHash,
|
|
18732
|
+
RENDER_REVIEW_MODEL,
|
|
18733
|
+
videoPathFromOutput,
|
|
18734
|
+
RENDER_REVIEW_UNAVAILABLE,
|
|
18735
|
+
askAboutImages,
|
|
18736
|
+
askAboutText,
|
|
18737
|
+
reviewRenderedVideo,
|
|
18738
|
+
reviewContextBeside,
|
|
18739
|
+
sceneWindowsBeside,
|
|
19016
18740
|
parseJudgeScores,
|
|
19017
18741
|
judgeWinner,
|
|
19018
18742
|
staticVerdict,
|
|
@@ -19034,4 +18758,4 @@ export {
|
|
|
19034
18758
|
defaultRegistry,
|
|
19035
18759
|
createEngineFromEnv
|
|
19036
18760
|
};
|
|
19037
|
-
//# sourceMappingURL=chunk-
|
|
18761
|
+
//# sourceMappingURL=chunk-6PP3MBGZ.js.map
|