@koda-sl/baker-cli 0.265.2-dev.1f1c09c80 → 0.266.0-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/README.md +5 -3
- package/dist/cli.js +58 -6
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3641,9 +3641,11 @@ because a video model garbles a wordmark every time:
|
|
|
3641
3641
|
becomes a flat brand colour plate (rendered by ffmpeg, not generated), with the mark and
|
|
3642
3642
|
the call to action over it. `end_card: false` keeps the footage; `end_card: { cta }` sets
|
|
3643
3643
|
the words on the button, which otherwise default to the last line of the script.
|
|
3644
|
-
- **`cast`** —
|
|
3645
|
-
|
|
3646
|
-
|
|
3644
|
+
- **`cast.avatar`** — the handle of a cast Avatar (`baker avatars list`). The ad grounds
|
|
3645
|
+
every beat they appear in on that avatar's identity sheet and copies its subject
|
|
3646
|
+
description verbatim, so the ad shows the same face as the rest of the company's work.
|
|
3647
|
+
This is the whole journey: `baker avatars create` → `cast.avatar` in the spec → render.
|
|
3648
|
+
- **`cast.description`** — a person described in words, when there is no avatar.
|
|
3647
3649
|
|
|
3648
3650
|
Each of these also removes a generation: the closing beat costs no image and no clip.
|
|
3649
3651
|
|
package/dist/cli.js
CHANGED
|
@@ -27314,6 +27314,12 @@ var RecurringElement = z27.object({
|
|
|
27314
27314
|
// pink shirt and believer in a white shirt). Each look gets its own reference
|
|
27315
27315
|
// slot, but the face/identity must stay identical across them.
|
|
27316
27316
|
same_as: z27.string().nullable().optional(),
|
|
27317
|
+
/**
|
|
27318
|
+
* A picture of this element that already exists — a cast Avatar's identity sheet,
|
|
27319
|
+
* a product photo, a real location shot. Given, the slot ingests it; absent, the
|
|
27320
|
+
* slot emits a TODO for somebody to drop one by hand.
|
|
27321
|
+
*/
|
|
27322
|
+
source_image: z27.string().optional(),
|
|
27317
27323
|
// Scenes the element appears in. Either a bare list of scene indices (both
|
|
27318
27324
|
// edges) or per-{scene,edge} entries. Both forms are accepted and merged.
|
|
27319
27325
|
scenes: z27.array(z27.number()).optional(),
|
|
@@ -29596,10 +29602,16 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
|
29596
29602
|
const slots = buildElementSlots(elements);
|
|
29597
29603
|
extendPresenceByPromptMentions(slots, blueprint);
|
|
29598
29604
|
slots.forEach((slot, i) => {
|
|
29605
|
+
const element = elements[i];
|
|
29606
|
+
const supplied = typeof element?.source_image === "string" ? element.source_image.trim() : "";
|
|
29599
29607
|
nodes.push({
|
|
29600
29608
|
id: slot.id,
|
|
29601
29609
|
type: "ingest",
|
|
29602
|
-
params: {
|
|
29610
|
+
params: {
|
|
29611
|
+
source: "path",
|
|
29612
|
+
path: supplied || todoPath(element, slot.label),
|
|
29613
|
+
expect: "image"
|
|
29614
|
+
}
|
|
29603
29615
|
});
|
|
29604
29616
|
});
|
|
29605
29617
|
buildElementSheets(slots, nodes);
|
|
@@ -32483,8 +32495,20 @@ var AdSpec = z30.object({
|
|
|
32483
32495
|
* collage rather than a story.
|
|
32484
32496
|
*/
|
|
32485
32497
|
cast: z30.object({
|
|
32498
|
+
/**
|
|
32499
|
+
* A cast Avatar's handle — `baker avatars list` shows who there is.
|
|
32500
|
+
*
|
|
32501
|
+
* The right way to put a person in an ad. The avatar already has a settled
|
|
32502
|
+
* identity and a rendered identity sheet, so the ad grounds on the same face the
|
|
32503
|
+
* rest of the company's work uses, and its subject description is copied verbatim
|
|
32504
|
+
* rather than re-written per spec — re-phrasing it per render is the other reason
|
|
32505
|
+
* a face drifts across a set.
|
|
32506
|
+
*/
|
|
32507
|
+
avatar: z30.string().min(1).optional(),
|
|
32486
32508
|
/** Who they are, plainly: age, build, hair, wardrobe, the register of the ad. */
|
|
32487
|
-
description: z30.string().min(1)
|
|
32509
|
+
description: z30.string().min(1).optional()
|
|
32510
|
+
}).refine((c) => Boolean(c.avatar || c.description), {
|
|
32511
|
+
message: "give `avatar` (a cast avatar's handle) or `description` \u2014 a cast with neither grounds on nothing"
|
|
32488
32512
|
}).optional(),
|
|
32489
32513
|
/**
|
|
32490
32514
|
* A bed, described in words. Omitted leaves the ad dry — the engine only wires
|
|
@@ -32565,13 +32589,14 @@ function endCardCta(spec) {
|
|
|
32565
32589
|
const declared = spec.end_card === false ? void 0 : spec.end_card?.cta;
|
|
32566
32590
|
return declared?.trim() || (spec.beats[spec.beats.length - 1]?.say ?? "").trim() || null;
|
|
32567
32591
|
}
|
|
32568
|
-
function adSpecCastElements(spec) {
|
|
32569
|
-
const description = spec.cast?.description?.trim();
|
|
32592
|
+
function adSpecCastElements(spec, avatar) {
|
|
32593
|
+
const description = (avatar?.subjectDescription ?? spec.cast?.description)?.trim();
|
|
32570
32594
|
if (!description) return [];
|
|
32571
32595
|
const lastIndex = spec.beats.length - 1;
|
|
32572
32596
|
const scenes = spec.beats.map((beat, i) => ({ beat, i })).filter(({ beat, i }) => beat.cast !== false && !(endCardWanted(spec) && i === lastIndex)).map(({ i }) => i);
|
|
32573
32597
|
if (scenes.length === 0) return [];
|
|
32574
|
-
|
|
32598
|
+
const sheet = avatar?.sheetUrl?.trim();
|
|
32599
|
+
return [{ type: "person", label: "customer", description, scenes, ...sheet ? { source_image: sheet } : {} }];
|
|
32575
32600
|
}
|
|
32576
32601
|
function adSpecToBlueprint(spec) {
|
|
32577
32602
|
const total = spec.beats.length;
|
|
@@ -32824,6 +32849,26 @@ var scaffoldAdCommand = defineCommand104({
|
|
|
32824
32849
|
process.exit(1);
|
|
32825
32850
|
return;
|
|
32826
32851
|
}
|
|
32852
|
+
const handle = spec.data.cast?.avatar?.trim();
|
|
32853
|
+
let avatar = null;
|
|
32854
|
+
let avatarError = null;
|
|
32855
|
+
if (handle) {
|
|
32856
|
+
try {
|
|
32857
|
+
avatar = await readAvatars(
|
|
32858
|
+
"/api/avatars/get",
|
|
32859
|
+
{ handle }
|
|
32860
|
+
);
|
|
32861
|
+
if (avatar?.status && avatar.status !== "ready") {
|
|
32862
|
+
avatarError = `Avatar \`${handle}\` is ${avatar.status}, not ready \u2014 only a ready avatar can be cast. Check \`baker avatars get ${handle}\`.`;
|
|
32863
|
+
avatar = null;
|
|
32864
|
+
} else if (!avatar?.sheetUrl) {
|
|
32865
|
+
avatarError = `Avatar \`${handle}\` has no identity sheet yet, so the ad cannot ground on their face.`;
|
|
32866
|
+
avatar = null;
|
|
32867
|
+
}
|
|
32868
|
+
} catch (e) {
|
|
32869
|
+
avatarError = `Could not read avatar \`${handle}\`: ${e instanceof Error ? e.message : String(e)}. \`baker avatars list\` shows who there is.`;
|
|
32870
|
+
}
|
|
32871
|
+
}
|
|
32827
32872
|
const blueprint = adSpecToBlueprint(spec.data);
|
|
32828
32873
|
const slug = args.slug ?? path23.basename(specPath).replace(/\.[^.]+$/, "");
|
|
32829
32874
|
const outPath = args.out ?? (args.slug ? path23.join("src/creatives", slug, `${slug}.canvas.json`) : path23.join(path23.dirname(specPath), `${slug}.canvas.json`));
|
|
@@ -32853,7 +32898,7 @@ var scaffoldAdCommand = defineCommand104({
|
|
|
32853
32898
|
// 8 is the composition's own maximum and clears every beat measured here.
|
|
32854
32899
|
captionWordsPerGroup: 8
|
|
32855
32900
|
};
|
|
32856
|
-
const canvas = scaffoldVideoCanvas(blueprint, adSpecCastElements(spec.data), opts);
|
|
32901
|
+
const canvas = scaffoldVideoCanvas(blueprint, adSpecCastElements(spec.data, avatar), opts);
|
|
32857
32902
|
const renderNode3 = canvas.nodes.find((n) => n.type === "hyperframe_render");
|
|
32858
32903
|
const renderedDir = renderNode3 ? path23.join(outDir, String(renderNode3.params?.composition ?? "")) : null;
|
|
32859
32904
|
let staged = null;
|
|
@@ -32890,6 +32935,13 @@ var scaffoldAdCommand = defineCommand104({
|
|
|
32890
32935
|
...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.`] : [],
|
|
32891
32936
|
...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."] : [],
|
|
32892
32937
|
...endCardWanted(spec.data) ? ["The last beat closes on a flat brand plate with the call to action over it, instead of one more photograph."] : [],
|
|
32938
|
+
...avatarError ? [avatarError] : [],
|
|
32939
|
+
...avatar ? [
|
|
32940
|
+
`Every beat the customer appears in is grounded on \`${handle}\`'s identity sheet, and their subject description was copied into the frames verbatim \u2014 so it is the same face throughout and the same face as the rest of this company's work.`
|
|
32941
|
+
] : [],
|
|
32942
|
+
...spec.data.cast && !avatar ? [
|
|
32943
|
+
"This cast has a description but no avatar, so the canvas asks you to drop a real source photo before it can run. `baker avatars list` \u2014 casting one and passing `cast.avatar` fills that slot for you and keeps the face consistent across every piece of work."
|
|
32944
|
+
] : [],
|
|
32893
32945
|
"The caption cards are your `say` lines verbatim \u2014 read them back before running; that is the copy the viewer sees.",
|
|
32894
32946
|
"Every clip renders with nobody speaking unless a beat set `on_camera`. If a beat needs a presenter talking, set it there rather than describing it in `show`.",
|
|
32895
32947
|
...spec.data.brand?.palette?.length && spec.data.brand?.logo ? [] : [
|