@koda-sl/baker-cli 0.281.4-dev.1f1c09c80 → 0.281.6-dev.1f1c09c80
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 +74 -33
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -32791,6 +32791,24 @@ var AdSpec = z30.object({
|
|
|
32791
32791
|
* is all this needs to carry.
|
|
32792
32792
|
*/
|
|
32793
32793
|
music: z30.union([z30.string().min(1), z30.literal(false)]).optional(),
|
|
32794
|
+
/**
|
|
32795
|
+
* The place the ad keeps coming back to, held still across every shot.
|
|
32796
|
+
*
|
|
32797
|
+
* The person has an identity sheet; nothing held the SETTING. Each shot's start frame is
|
|
32798
|
+
* generated on its own, so three beats in "her living room" came back as three different
|
|
32799
|
+
* living rooms. The engine already builds `location` reference sheets — its own comment
|
|
32800
|
+
* calls them the strongest cross-scene consistency lock it has — and the ad path simply
|
|
32801
|
+
* never emitted one.
|
|
32802
|
+
*
|
|
32803
|
+
* `image` is a real picture of the place, and it is what makes this work at all: a sheet
|
|
32804
|
+
* fuses source material into a turnaround. Fed nothing, it is not a reference sheet, it
|
|
32805
|
+
* is `image_generate` with extra steps — the model registry says exactly that. Generate
|
|
32806
|
+
* the plate once with gpt-image-2 and pass its URL here.
|
|
32807
|
+
*/
|
|
32808
|
+
set: z30.object({
|
|
32809
|
+
description: z30.string().min(1).max(400),
|
|
32810
|
+
image: z30.string().min(1).optional()
|
|
32811
|
+
}).optional(),
|
|
32794
32812
|
/**
|
|
32795
32813
|
* How the ad closes.
|
|
32796
32814
|
*
|
|
@@ -32938,10 +32956,13 @@ function endCardCta(spec) {
|
|
|
32938
32956
|
return declared.toLowerCase().replace(/[.!?]$/, "") === closing.replace(/[.!?]$/, "") ? null : declared;
|
|
32939
32957
|
}
|
|
32940
32958
|
var AD_CAST_ID = "customer";
|
|
32941
|
-
|
|
32942
|
-
|
|
32959
|
+
var AD_SET_ID = "set";
|
|
32960
|
+
function castedAvatarOnRoster(ad, roster) {
|
|
32961
|
+
if (ad.cast?.avatar) return null;
|
|
32962
|
+
const haystack = [ad.cast?.description ?? "", ...(ad.beats ?? []).map((b) => b.show ?? "")].join(" ");
|
|
32963
|
+
if (!haystack.trim()) return null;
|
|
32943
32964
|
const words2 = new Set(
|
|
32944
|
-
|
|
32965
|
+
haystack.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean)
|
|
32945
32966
|
);
|
|
32946
32967
|
for (const person of roster) {
|
|
32947
32968
|
for (const label of [person.name, person.handle]) {
|
|
@@ -32953,27 +32974,39 @@ function castedAvatarOnRoster(cast, roster) {
|
|
|
32953
32974
|
return null;
|
|
32954
32975
|
}
|
|
32955
32976
|
function adSpecCastElements(spec, avatar) {
|
|
32956
|
-
const description = (avatar?.subjectDescription ?? spec.cast?.description)?.trim();
|
|
32957
|
-
if (!description) return [];
|
|
32958
32977
|
const lastIndex = spec.beats.length - 1;
|
|
32978
|
+
const speakingScenes = spec.beats.map((_, i) => i).filter((i) => !(endCardWanted(spec) && i === lastIndex));
|
|
32979
|
+
const place = spec.set?.description?.trim();
|
|
32980
|
+
const elements = [];
|
|
32981
|
+
if (place && speakingScenes.length > 0) {
|
|
32982
|
+
elements.push({
|
|
32983
|
+
type: "location",
|
|
32984
|
+
label: AD_SET_ID,
|
|
32985
|
+
cast_id: AD_SET_ID,
|
|
32986
|
+
description: place,
|
|
32987
|
+
scenes: speakingScenes,
|
|
32988
|
+
...spec.set?.image?.trim() ? { source_image: spec.set.image.trim() } : {}
|
|
32989
|
+
});
|
|
32990
|
+
}
|
|
32991
|
+
const description = (avatar?.subjectDescription ?? spec.cast?.description)?.trim();
|
|
32992
|
+
if (!description) return elements;
|
|
32959
32993
|
const scenes = spec.beats.map((beat, i) => ({ beat, i })).filter(({ beat, i }) => beat.cast !== false && !(endCardWanted(spec) && i === lastIndex)).map(({ i }) => i);
|
|
32960
|
-
if (scenes.length === 0) return
|
|
32994
|
+
if (scenes.length === 0) return elements;
|
|
32961
32995
|
const sheet = avatar?.sheetUrl?.trim();
|
|
32962
|
-
|
|
32963
|
-
|
|
32964
|
-
|
|
32965
|
-
|
|
32966
|
-
|
|
32967
|
-
|
|
32968
|
-
|
|
32969
|
-
|
|
32970
|
-
|
|
32971
|
-
|
|
32972
|
-
|
|
32973
|
-
|
|
32974
|
-
|
|
32975
|
-
|
|
32976
|
-
];
|
|
32996
|
+
elements.push({
|
|
32997
|
+
type: "person",
|
|
32998
|
+
label: AD_CAST_ID,
|
|
32999
|
+
// Same id the dialogue names as speaker: that pairing is what lets the engine see
|
|
33000
|
+
// the presenter as present in a scene and route it to native audio.
|
|
33001
|
+
cast_id: AD_CAST_ID,
|
|
33002
|
+
description,
|
|
33003
|
+
scenes,
|
|
33004
|
+
// An avatar's identity sheet IS the turnaround the engine would otherwise render,
|
|
33005
|
+
// so it is handed over as one. Left unmarked, the run paid to generate a sheet OF
|
|
33006
|
+
// a sheet and every frame grounded on the copy instead of the avatar.
|
|
33007
|
+
...sheet ? { source_image: sheet, source_is_sheet: true } : {}
|
|
33008
|
+
});
|
|
33009
|
+
return elements;
|
|
32977
33010
|
}
|
|
32978
33011
|
function motionPrompt(beat, toCamera, isClosingCard, place, physics) {
|
|
32979
33012
|
if (isClosingCard) return BRAND_PLATE;
|
|
@@ -33412,25 +33445,30 @@ var scaffoldAdCommand = defineCommand105({
|
|
|
33412
33445
|
process.exit(1);
|
|
33413
33446
|
return;
|
|
33414
33447
|
}
|
|
33415
|
-
|
|
33448
|
+
let autoCast = null;
|
|
33449
|
+
if (!flagAvatar && !spec.data.cast?.avatar) {
|
|
33416
33450
|
const roster = await readAvatars(
|
|
33417
33451
|
"/api/avatars",
|
|
33418
33452
|
{ status: "ready" }
|
|
33419
33453
|
).catch(() => null);
|
|
33420
|
-
const named = roster ? castedAvatarOnRoster(spec.data
|
|
33454
|
+
const named = roster ? castedAvatarOnRoster(spec.data, roster.avatars) : null;
|
|
33421
33455
|
if (named) {
|
|
33422
|
-
|
|
33423
|
-
|
|
33424
|
-
error: {
|
|
33425
|
-
code: "VALIDATION_ERROR",
|
|
33426
|
-
message: `This ad describes its cast in words, but \`${named}\` is already one of this company's avatars \u2014 and the description names them. Described, they are re-invented every shot: the face drifts, their own voice is never used, and they cannot be shown speaking.`,
|
|
33427
|
-
fix: `Cast them instead: put \`"cast": { "avatar": "${named}" }\` in the spec, or pass \`--avatar ${named}\`. Keep a written description only for someone the company does not have.`
|
|
33428
|
-
}
|
|
33429
|
-
});
|
|
33430
|
-
process.exit(1);
|
|
33431
|
-
return;
|
|
33456
|
+
spec.data.cast = { ...spec.data.cast, avatar: named };
|
|
33457
|
+
autoCast = named;
|
|
33432
33458
|
}
|
|
33433
33459
|
}
|
|
33460
|
+
if (spec.data.set?.description && !spec.data.set.image?.trim()) {
|
|
33461
|
+
writeJson({
|
|
33462
|
+
ok: false,
|
|
33463
|
+
error: {
|
|
33464
|
+
code: "VALIDATION_ERROR",
|
|
33465
|
+
message: "This ad names a recurring place but gives no picture of it. A location sheet is fused from a real image; with nothing to fuse there is no sheet, and every shot invents the room again.",
|
|
33466
|
+
fix: `Make the plate once, then pass it: \`baker images generate "${spec.data.set.description}" --model openai/gpt-image-2\`, then put its url in the spec as \`"set": { "description": "\u2026", "image": "<url>" }\`. Drop \`set\` entirely if the ad does not keep returning to one place.`
|
|
33467
|
+
}
|
|
33468
|
+
});
|
|
33469
|
+
process.exit(1);
|
|
33470
|
+
return;
|
|
33471
|
+
}
|
|
33434
33472
|
const handle = flagAvatar || spec.data.cast?.avatar?.trim();
|
|
33435
33473
|
let avatar = null;
|
|
33436
33474
|
let avatarError = null;
|
|
@@ -33625,6 +33663,9 @@ var scaffoldAdCommand = defineCommand105({
|
|
|
33625
33663
|
data: { canvas: outPath, beats: spec.data.beats.length, slug },
|
|
33626
33664
|
hints: [
|
|
33627
33665
|
`Wrote ${spec.data.beats.length} beats to ${outPath}. Run it with \`baker canvas run ${outPath}\`.`,
|
|
33666
|
+
...autoCast ? [
|
|
33667
|
+
`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.'
|
|
33668
|
+
] : [],
|
|
33628
33669
|
`These beats run about ${blueprint.estimated_duration_s}s. If the ask was longer, add beats \u2014 the length is the sum of the lines, so a 25s ad needs roughly 25s of script and cannot be stretched by holding shots.`,
|
|
33629
33670
|
...logoPath && !staged ? [`Could not read the logo at \`${logoPath}\` \u2014 the ad has no mark on screen. Give the path as it appears in src/brand/BRAND.md, relative to the workspace root.`] : [],
|
|
33630
33671
|
...staged ? ["The brand mark is drawn by the composition \u2014 top-left throughout, and large on the closing card. It is never generated as a picture, so it cannot come back garbled."] : [],
|