@koda-sl/baker-cli 0.270.0-dev.1f1c09c80 → 0.271.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/dist/cli.js CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  ulid,
59
59
  validateCanvasDeep,
60
60
  ytDlpBlockSignal
61
- } from "./chunk-SZJWEOW2.js";
61
+ } from "./chunk-4IJX4R4J.js";
62
62
  import {
63
63
  csvOrJson,
64
64
  daysAgoIso,
@@ -27320,6 +27320,17 @@ var RecurringElement = z27.object({
27320
27320
  * slot emits a TODO for somebody to drop one by hand.
27321
27321
  */
27322
27322
  source_image: z27.string().optional(),
27323
+ /**
27324
+ * The supplied picture is ALREADY a multi-view reference sheet, so no sheet is
27325
+ * built from it.
27326
+ *
27327
+ * A cast Avatar's identity sheet is exactly that: a rendered turnaround of one
27328
+ * person, which is what `buildElementSheets` exists to produce. Fanning it into a
27329
+ * second sheet paid for an image generation to make a generation of a generation,
27330
+ * and every frame then grounded on the copy rather than on the avatar's own
27331
+ * identity — which is the drift the sheet is supposed to prevent.
27332
+ */
27333
+ source_is_sheet: z27.boolean().optional(),
27323
27334
  // Scenes the element appears in. Either a bare list of scene indices (both
27324
27335
  // edges) or per-{scene,edge} entries. Both forms are accepted and merged.
27325
27336
  scenes: z27.array(z27.number()).optional(),
@@ -27493,6 +27504,7 @@ function buildElementSlots(elements) {
27493
27504
  description: el.description,
27494
27505
  sameAs: el.same_as ?? void 0,
27495
27506
  castId: el.cast_id ?? void 0,
27507
+ sourceIsSheet: el.source_is_sheet === true,
27496
27508
  presence: presenceOf(el)
27497
27509
  });
27498
27510
  });
@@ -27536,8 +27548,12 @@ var SHEET_SUBJECT_TYPE = {
27536
27548
  product: "product",
27537
27549
  location: "location"
27538
27550
  };
27539
- function buildElementSheets(slots, nodes) {
27551
+ function buildElementSheets(slots, nodes, sheetModel) {
27540
27552
  for (const slot of slots) {
27553
+ if (slot.sourceIsSheet) {
27554
+ slot.sheetBacked = true;
27555
+ continue;
27556
+ }
27541
27557
  const subjectType = SHEET_SUBJECT_TYPE[slot.type.toLowerCase()];
27542
27558
  if (!subjectType) continue;
27543
27559
  if (slot.sameAs) continue;
@@ -27551,7 +27567,7 @@ function buildElementSheets(slots, nodes) {
27551
27567
  // The lone dropped ingest is the source; the sheet fans it into a turnaround.
27552
27568
  inputs: { references: [slot.ref] },
27553
27569
  params: {
27554
- model: ACTOR_SHEET_MODEL,
27570
+ model: sheetModel ?? ACTOR_SHEET_MODEL,
27555
27571
  // The clean-plate clause mirrors the frame prompts' CLEAN PLATE block: a sheet
27556
27572
  // that comes back with a fake camera app baked in (P2-22) poisons EVERY frame
27557
27573
  // grounded on it, so the suppression must live on the sheet too.
@@ -29616,7 +29632,7 @@ function scaffoldVideoCanvas(input, elementsInput, opts) {
29616
29632
  }
29617
29633
  });
29618
29634
  });
29619
- buildElementSheets(slots, nodes);
29635
+ buildElementSheets(slots, nodes, opts.sheetModel);
29620
29636
  const { clips, voTracks, vo_segments, talking_scenes, routes } = buildTimeline(blueprint, slots, opts, nodes);
29621
29637
  const outAspect = resolveAspect(blueprint.source?.aspect_ratio, opts.aspect, genAspectsFor(opts.videoModel));
29622
29638
  let videoRef = buildSpine(clips, opts.seamDedup ?? "head", canvasDims(outAspect.outAr), nodes);
@@ -32658,7 +32674,18 @@ function adSpecCastElements(spec, avatar) {
32658
32674
  const scenes = spec.beats.map((beat, i) => ({ beat, i })).filter(({ beat, i }) => beat.cast !== false && !(endCardWanted(spec) && i === lastIndex)).map(({ i }) => i);
32659
32675
  if (scenes.length === 0) return [];
32660
32676
  const sheet = avatar?.sheetUrl?.trim();
32661
- return [{ type: "person", label: "customer", description, scenes, ...sheet ? { source_image: sheet } : {} }];
32677
+ return [
32678
+ {
32679
+ type: "person",
32680
+ label: "customer",
32681
+ description,
32682
+ scenes,
32683
+ // An avatar's identity sheet IS the turnaround the engine would otherwise render,
32684
+ // so it is handed over as one. Left unmarked, the run paid to generate a sheet OF
32685
+ // a sheet and every frame grounded on the copy instead of the avatar.
32686
+ ...sheet ? { source_image: sheet, source_is_sheet: true } : {}
32687
+ }
32688
+ ];
32662
32689
  }
32663
32690
  function adSpecToBlueprint(spec) {
32664
32691
  const total = spec.beats.length;
@@ -32805,26 +32832,34 @@ function adBrandOverlayCss() {
32805
32832
  " viewBox is wider than it is tall rendered clipped \u2014 the first real ad drew",
32806
32833
  " `greenlea`. Constraining the height and letting the width follow keeps any",
32807
32834
  " aspect ratio whole, and the max-width stops a very wide mark spanning the frame. */",
32808
- " /* Legible over ANY picture. A drop shadow alone left a pale mark invisible",
32809
- " against a bright window or an overcast sky for the first third of an ad;",
32810
- " a dark, blurred pill behind it reads on both without boxing the mark in. */",
32811
- " .brand-mark { position: absolute; top: 90px; left: 56px; height: 64px; width: auto;",
32812
- " max-width: 420px; object-fit: contain; padding: 14px 20px; border-radius: 18px;",
32813
- " background: rgba(0,0,0,0.34); backdrop-filter: blur(6px);",
32814
- " filter: drop-shadow(0 2px 8px rgba(0,0,0,0.55)); }",
32835
+ " /* A LIGHT panel behind the mark, always. A brand mark is drawn to sit on white",
32836
+ " \u2014 this one is #1D5D47 on transparent \u2014 so a dark backing hid it on the corner",
32837
+ " and the brand-coloured closing plate erased it completely: the end card came",
32838
+ " out as an empty green frame with a single leaf floating in it. A near-white",
32839
+ " panel is the one background every mark in a brand kit is designed against. */",
32840
+ " .brand-mark { position: absolute; top: 90px; left: 56px; height: 52px; width: auto;",
32841
+ " max-width: 420px; object-fit: contain; padding: 14px 22px; border-radius: 18px;",
32842
+ " background: rgba(255,255,255,0.94);",
32843
+ " filter: drop-shadow(0 3px 10px rgba(0,0,0,0.35)); }",
32815
32844
  " .endcard { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);",
32816
32845
  " display: flex; flex-direction: column; align-items: center; gap: 56px;",
32817
32846
  " width: 100%; max-width: 1080px; padding: 0 60px; text-align: center; }",
32818
32847
  " /* Sized to FILL, not merely bounded. Given only max-* constraints an SVG",
32819
32848
  " renders at its own intrinsic size, and a small viewBox drew a closing mark",
32820
32849
  " a few dozen pixels tall in the middle of a 1080px frame. */",
32821
- " .endcard-logo { width: 62%; height: auto; max-height: 460px; object-fit: contain; }",
32850
+ " .endcard-logo { width: 62%; height: auto; max-height: 300px; object-fit: contain;",
32851
+ " padding: 46px 56px; border-radius: 40px; background: rgba(255,255,255,0.96);",
32852
+ " box-shadow: 0 10px 40px rgba(0,0,0,0.28); }",
32822
32853
  " /* Capped and wrapping, not sized to its content: measured in a real browser,",
32823
32854
  " `GET YOUR FREE QUOTE` renders 948px wide against a 1080px canvas, so the next",
32824
32855
  " slightly longer call to action runs off the frame. Two lines beat an edge. */",
32825
- " /* Declares its own family: rendered without one it inherited the document",
32826
- " default and the call to action came out in Times, beside sans-serif captions. */",
32827
- " .endcard-cta { font-family: 'BrandFont', 'Arial Black', 'Helvetica Neue', Arial, sans-serif;",
32856
+ " /* The SAME family the caption track uses, and only names the compiler maps.",
32857
+ " Declaring one matters \u2014 rendered without it the call to action inherited the",
32858
+ " document default and came out in Times beside sans-serif captions. But the",
32859
+ " first attempt led with 'BrandFont', which the overlay composition defines and",
32860
+ " the caption composition does not: HyperFrames refuses an unmapped family and",
32861
+ " the whole render died with `No deterministic font mapping for: BrandFont`. */",
32862
+ " .endcard-cta { font-family: 'Arial Black', 'Helvetica Neue', Arial, sans-serif;",
32828
32863
  " font-size: 64px; font-weight: 900; text-transform: uppercase;",
32829
32864
  " letter-spacing: 0.01em; color: #fff; padding: 28px 58px; border-radius: 56px;",
32830
32865
  " max-width: 900px; line-height: 1.15; background: rgba(0,0,0,0.82); }"
@@ -33031,6 +33066,9 @@ var scaffoldAdCommand = defineCommand104({
33031
33066
  const logoPath = spec.data.brand?.logo?.trim();
33032
33067
  const opts = {
33033
33068
  imageModel: AD_IMAGE_MODEL,
33069
+ // The sheet on the SAME model as the frames it feeds. A sheet from another
33070
+ // generator hands every frame a look its own model then reinterprets.
33071
+ sheetModel: AD_IMAGE_MODEL,
33034
33072
  videoModel: DEFAULT_VIDEO_GENERATE_MODEL,
33035
33073
  overlayCompositionPath: path24.relative(outDir, compositionDest),
33036
33074
  captionsCompositionPath: path24.relative(outDir, captionsDest),
@@ -33084,6 +33122,9 @@ var scaffoldAdCommand = defineCommand104({
33084
33122
  ...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.`] : [],
33085
33123
  ...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."] : [],
33086
33124
  ...endCardWanted(spec.data) ? ["The last beat closes on a flat brand plate with the call to action over it, instead of one more photograph."] : [],
33125
+ ...spec.data.brand?.logo && !filledLogo && !spec.data.brand.logo.startsWith(`${BRAND_DIR}/`) ? [
33126
+ `The mark at \`${spec.data.brand.logo}\` is not in \`${BRAND_DIR}/logos/\`. If you drew it for this ad, it is not the client's logo and the ad is going out wearing something you invented \u2014 put the real one in the brand folder (\`/brand-build\`) and re-run. A hand-drawn wordmark also tends to overflow its own viewBox, which renders as a clipped word.`
33127
+ ] : [],
33087
33128
  ...filledLogo || filledPalette ? [
33088
33129
  `The spec left the brand empty, so it was read from the workspace: ${[filledLogo ? `logo \`${filledLogo}\`` : null, filledPalette ? `palette ${filledPalette.slice(0, 3).join(", ")}` : null].filter(Boolean).join(" and ")}. Pass them yourself when the ad needs a different mark or colour.`
33089
33130
  ] : [],