@koda-sl/baker-cli 0.270.2-dev.f69bf4bac → 0.270.4-dev.12dd573e5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
elementMentionKeywords,
|
|
31
31
|
estimateVideoCredits,
|
|
32
32
|
fetchExternalBytes,
|
|
33
|
+
frameRealismDirection,
|
|
33
34
|
generateCatalog,
|
|
34
35
|
imageProfileFor,
|
|
35
36
|
isAudioOnly,
|
|
@@ -58,7 +59,7 @@ import {
|
|
|
58
59
|
ulid,
|
|
59
60
|
validateCanvasDeep,
|
|
60
61
|
ytDlpBlockSignal
|
|
61
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-Y2GYULGB.js";
|
|
62
63
|
import {
|
|
63
64
|
csvOrJson,
|
|
64
65
|
daysAgoIso,
|
|
@@ -32788,6 +32789,26 @@ function classifyWatchDefects(json, windows) {
|
|
|
32788
32789
|
};
|
|
32789
32790
|
});
|
|
32790
32791
|
}
|
|
32792
|
+
var FidelityReview = z29.object({
|
|
32793
|
+
delivers_the_ask: z29.boolean(),
|
|
32794
|
+
missing: z29.array(z29.string()).default([]),
|
|
32795
|
+
contradicted: z29.array(z29.string()).default([])
|
|
32796
|
+
});
|
|
32797
|
+
function buildFidelityPrompt(brief) {
|
|
32798
|
+
return `Someone asked for an advertisement in these words:
|
|
32799
|
+
|
|
32800
|
+
"""${brief.trim()}"""
|
|
32801
|
+
|
|
32802
|
+
Watch and LISTEN to the video, then answer only this: does it deliver what they asked for? Judge the request as it was written \u2014 not what would make a good ad, and not what you would have made. Two lists: what they asked for that is NOT in the video, and what the video does that CONTRADICTS what they asked for. Quote their own words in each entry so the answer can be checked. Empty lists when it delivers.
|
|
32803
|
+
Answer JSON only: {"delivers_the_ask":true|false,"missing":["..."],"contradicted":["..."]}`;
|
|
32804
|
+
}
|
|
32805
|
+
function classifyFidelityFindings(json) {
|
|
32806
|
+
const parsed = FidelityReview.safeParse(json);
|
|
32807
|
+
if (!parsed.success) return [];
|
|
32808
|
+
const r = parsed.data;
|
|
32809
|
+
if (r.delivers_the_ask) return [];
|
|
32810
|
+
return [...r.missing, ...r.contradicted].map((entry) => entry.trim()).filter(Boolean).map((what) => ({ severity: "blocking", what: `Not what was asked for: ${what}.` }));
|
|
32811
|
+
}
|
|
32791
32812
|
function classifyWatchFindings(json) {
|
|
32792
32813
|
const parsed = WatchReview.safeParse(json);
|
|
32793
32814
|
if (!parsed.success) return [];
|
|
@@ -33004,11 +33025,13 @@ async function reviewRenderedVideo(opts) {
|
|
|
33004
33025
|
const unread = perFrame.filter((f) => f.json === null).length;
|
|
33005
33026
|
if (unread === perFrame.length && reel === null) return null;
|
|
33006
33027
|
const watch = await askVideo(opts.video, buildWatchReviewPrompt({ market: opts.market, script: opts.script }));
|
|
33028
|
+
const fidelity = opts.brief?.trim() ? await askVideo(opts.video, buildFidelityPrompt(opts.brief)) : null;
|
|
33007
33029
|
const findings = [
|
|
33008
33030
|
...classifyFrameFindings(perFrame, opts.windows ?? []),
|
|
33009
33031
|
...classifyReelFindings(reel),
|
|
33010
33032
|
...classifyWatchFindings(watch),
|
|
33011
33033
|
...classifyWatchDefects(watch, opts.windows ?? []),
|
|
33034
|
+
...classifyFidelityFindings(fidelity),
|
|
33012
33035
|
// Measured, not judged: a model looking at frames cannot hear the mix.
|
|
33013
33036
|
...await measureAudio(opts.video)
|
|
33014
33037
|
];
|
|
@@ -33047,6 +33070,14 @@ var runCommand = defineCommand102({
|
|
|
33047
33070
|
meta: { name: "run", description: "Validate and execute a canvas JSON file." },
|
|
33048
33071
|
args: {
|
|
33049
33072
|
file: { type: "positional", required: true, description: "Path to canvas JSON" },
|
|
33073
|
+
until: {
|
|
33074
|
+
type: "string",
|
|
33075
|
+
description: "Stop before the expensive half and leave the run resumable. `frames` settles the pictures and stops on the doorstep of the clips, so the look can be checked for the price of a few images instead of a whole ad. Continue with `baker canvas run <file> --run-id <the same id>` \u2014 everything already done is cached and is not paid for twice."
|
|
33076
|
+
},
|
|
33077
|
+
brief: {
|
|
33078
|
+
type: "string",
|
|
33079
|
+
description: "What the person asked for, IN THEIR OWN WORDS \u2014 paste their message, do not summarise it. The review compares the finished video against this and reports what they asked for that is missing or contradicted. Without it the review can only say whether the video is well made, not whether it is the one they wanted: every other check grades the render against the spec, and a spec that misread the request agrees with itself."
|
|
33080
|
+
},
|
|
33050
33081
|
"cache-dir": { type: "string", description: "Cache root (default ./canvas/.cache)" },
|
|
33051
33082
|
"outputs-dir": { type: "string", description: "Per-run outputs root (default ./canvas)" },
|
|
33052
33083
|
"run-id": { type: "string", description: "Override run id (also resumes that run, re-attaching its in-flight jobs)" },
|
|
@@ -33096,6 +33127,8 @@ var runCommand = defineCommand102({
|
|
|
33096
33127
|
outputsDir: args["outputs-dir"] ? String(args["outputs-dir"]) : void 0,
|
|
33097
33128
|
runId: args["run-id"] ? String(args["run-id"]) : void 0,
|
|
33098
33129
|
fresh: args.fresh === true,
|
|
33130
|
+
brief: args.brief ? String(args.brief) : void 0,
|
|
33131
|
+
until: args.until ? String(args.until) : void 0,
|
|
33099
33132
|
cachePolicy: args["cache-policy"] ? String(args["cache-policy"]) : void 0,
|
|
33100
33133
|
regenerate: args.regenerate !== void 0 ? String(args.regenerate) : void 0,
|
|
33101
33134
|
// --concurrency wins; --parallel is the discoverable alias for the same bound.
|
|
@@ -33118,6 +33151,10 @@ function resolveMaxCredits(...candidates) {
|
|
|
33118
33151
|
}
|
|
33119
33152
|
return void 0;
|
|
33120
33153
|
}
|
|
33154
|
+
function stopBeforeKinds(until) {
|
|
33155
|
+
if (until?.trim() === "frames") return /* @__PURE__ */ new Set(["video_generate"]);
|
|
33156
|
+
return null;
|
|
33157
|
+
}
|
|
33121
33158
|
async function executeCanvasRun(opts) {
|
|
33122
33159
|
const filePath = path16.resolve(opts.file);
|
|
33123
33160
|
const raw = await readFile12(filePath, "utf8");
|
|
@@ -33306,6 +33343,8 @@ ${describeRewrites(healed.rewrites)}
|
|
|
33306
33343
|
if (progress && poster) {
|
|
33307
33344
|
poster.startKeepalive(() => progress.hasPlan() ? progress.snapshot() : null);
|
|
33308
33345
|
}
|
|
33346
|
+
const frameUrls = [];
|
|
33347
|
+
const isFrameNode = (id) => parsed.nodes?.find((n) => n.id === id)?.type === "image_generate";
|
|
33309
33348
|
try {
|
|
33310
33349
|
const policy = opts.cachePolicy ?? "read_write";
|
|
33311
33350
|
const result = await engine.run(parsed, {
|
|
@@ -33313,12 +33352,24 @@ ${describeRewrites(healed.rewrites)}
|
|
|
33313
33352
|
signal: abort.signal,
|
|
33314
33353
|
cache_policy: policy,
|
|
33315
33354
|
max_credits: opts.maxCredits,
|
|
33355
|
+
...stopBeforeKinds(opts.until) ? { stop_before_kinds: stopBeforeKinds(opts.until) } : {},
|
|
33316
33356
|
concurrency: resolveConcurrency(opts.concurrency, process.env.BAKER_CANVAS_CONCURRENCY),
|
|
33317
33357
|
regenerate,
|
|
33318
|
-
|
|
33319
|
-
|
|
33320
|
-
|
|
33321
|
-
|
|
33358
|
+
// Always set, because the frame collector has to run whether or not this run is
|
|
33359
|
+
// reporting progress to a backend — without it a checkpoint stops with nothing to
|
|
33360
|
+
// show, which is the only thing a checkpoint is for.
|
|
33361
|
+
onProgress: (event) => {
|
|
33362
|
+
if (event.kind === "node_settled" && isFrameNode(event.run.node_id)) {
|
|
33363
|
+
for (const out of Object.values(event.outputs ?? {})) {
|
|
33364
|
+
const url = out?.url;
|
|
33365
|
+
if (url) frameUrls.push(url);
|
|
33366
|
+
}
|
|
33367
|
+
}
|
|
33368
|
+
if (progress && poster) {
|
|
33369
|
+
progress.apply(event);
|
|
33370
|
+
if (progress.hasPlan()) poster.enqueue(progress.snapshot());
|
|
33371
|
+
}
|
|
33372
|
+
}
|
|
33322
33373
|
});
|
|
33323
33374
|
await clearRunMarker(outputsDir, filePath);
|
|
33324
33375
|
const keepRuns = opts.keepRuns;
|
|
@@ -33327,7 +33378,11 @@ ${describeRewrites(healed.rewrites)}
|
|
|
33327
33378
|
`));
|
|
33328
33379
|
}
|
|
33329
33380
|
const reviewable = videoPathFromOutput(result.output);
|
|
33330
|
-
const review = reviewable ? await reviewRenderedVideo({
|
|
33381
|
+
const review = reviewable ? await reviewRenderedVideo({
|
|
33382
|
+
video: reviewable,
|
|
33383
|
+
windows: await sceneWindowsBeside(filePath),
|
|
33384
|
+
brief: opts.brief?.trim() || void 0
|
|
33385
|
+
}) : null;
|
|
33331
33386
|
const hints2 = reviewable ? review === null ? [RENDER_REVIEW_UNAVAILABLE] : review.length === 0 ? ["Reviewed the finished video frame by frame \u2014 nothing to report."] : [
|
|
33332
33387
|
`Reviewed the finished video and found ${review.length} thing${review.length === 1 ? "" : "s"} to fix. Show the user what is wrong before you show them the ad.`,
|
|
33333
33388
|
...review.map((f) => `${f.severity === "blocking" ? "MUST FIX" : "SHOULD FIX"} \u2014 ${f.what}`),
|
|
@@ -33368,6 +33423,27 @@ ${describeRewrites(healed.rewrites)}
|
|
|
33368
33423
|
`);
|
|
33369
33424
|
process.exit(abortedBy === "SIGTERM" ? 143 : 130);
|
|
33370
33425
|
}
|
|
33426
|
+
if (e instanceof RunAbortedError && e.reason === "checkpoint") {
|
|
33427
|
+
await clearRunMarker(outputsDir, filePath);
|
|
33428
|
+
const frames = frameUrls;
|
|
33429
|
+
process.stdout.write(
|
|
33430
|
+
`${JSON.stringify(
|
|
33431
|
+
{
|
|
33432
|
+
ok: true,
|
|
33433
|
+
data: { stopped: "frames", runId, frames },
|
|
33434
|
+
hints: [
|
|
33435
|
+
`${e.message}`,
|
|
33436
|
+
frames.length > 0 ? `Show these ${frames.length} frames and ask whether to carry on, BEFORE the clips are paid for. They are the ad's look, at a fraction of its price.` : "No frame outputs were captured \u2014 carry on rather than blocking on nothing.",
|
|
33437
|
+
`Carry on with: baker canvas run ${filePath} --run-id ${runId}`
|
|
33438
|
+
]
|
|
33439
|
+
},
|
|
33440
|
+
null,
|
|
33441
|
+
2
|
|
33442
|
+
)}
|
|
33443
|
+
`
|
|
33444
|
+
);
|
|
33445
|
+
process.exit(0);
|
|
33446
|
+
}
|
|
33371
33447
|
if (e instanceof RunAbortedError && e.reason === "cost_cap") {
|
|
33372
33448
|
await clearRunMarker(outputsDir, filePath);
|
|
33373
33449
|
if (poster) await poster.flush(failedPayload(e.message));
|
|
@@ -34836,12 +34912,17 @@ function voiceoverMode(spec, nativeSpeech) {
|
|
|
34836
34912
|
if (spec.voiceover === false) return "none";
|
|
34837
34913
|
return nativeSpeech ? "on_camera" : "voiceover";
|
|
34838
34914
|
}
|
|
34915
|
+
function castRole(spec) {
|
|
34916
|
+
if (!spec.cast) return void 0;
|
|
34917
|
+
const described = spec.cast.description?.trim();
|
|
34918
|
+
return described ? `${described} \u2014 a customer telling their own story, not a worker` : "a customer telling their own story, not a worker";
|
|
34919
|
+
}
|
|
34839
34920
|
function adSpecToBlueprint(spec, avatarAccent) {
|
|
34840
34921
|
const total = spec.beats.length;
|
|
34841
34922
|
const market = marketFor(spec);
|
|
34842
34923
|
const place = market ? ` Set in ${market}: the architecture, streets and styling are ${market}'s.` : "";
|
|
34843
34924
|
const currency = market ? CURRENCY_BY_MARKET[market.toLowerCase()] : void 0;
|
|
34844
|
-
const physics =
|
|
34925
|
+
const physics = frameRealismDirection({ currency, role: castRole(spec) });
|
|
34845
34926
|
const closesOnCard = endCardWanted(spec);
|
|
34846
34927
|
const nativeSpeech = wantsNativeSpeech(spec, closesOnCard);
|
|
34847
34928
|
let clock = 0;
|
|
@@ -35464,7 +35545,11 @@ var scaffoldAdCommand = defineCommand106({
|
|
|
35464
35545
|
ok: true,
|
|
35465
35546
|
data: { canvas: outPath, beats: spec.data.beats.length, slug },
|
|
35466
35547
|
hints: [
|
|
35467
|
-
`
|
|
35548
|
+
// `--until frames` on anything carrying a face or a mark, because that is where
|
|
35549
|
+
// getting it wrong is expensive: the pictures cost a fraction of the clips, and
|
|
35550
|
+
// every defect anyone has caught in a finished ad was visible in them. The run
|
|
35551
|
+
// stops, the frames are shown, and continuing pays for none of them twice.
|
|
35552
|
+
handle || staged ? `Wrote ${spec.data.beats.length} beats to ${outPath}. Run it with \`baker canvas run ${outPath} --until frames --brief "<what they asked for, their words>"\`, SHOW the frames it returns and ask whether to carry on \u2014 then continue with the \`--run-id\` it gives you. This ad carries ${handle ? "a person's face" : "the client's mark"}, and a wrong look costs a whole ad to discover after the clips.` : `Wrote ${spec.data.beats.length} beats to ${outPath}. Run it with \`baker canvas run ${outPath} --brief "<what they asked for, their words>"\`.`,
|
|
35468
35553
|
...autoCast ? [
|
|
35469
35554
|
`Cast \`${autoCast}\` \u2014 the spec described this person in words, and they are one of this company's avatars. Every shot now grounds on their identity sheet and they speak in their own pinned voice. Write \`"cast": { "avatar": "` + autoCast + '" }` yourself next time; a written description is for someone the company does not have.'
|
|
35470
35555
|
] : [],
|