@koda-sl/baker-cli 0.275.0-dev.1f1c09c80 → 0.277.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-4IJX4R4J.js";
61
+ } from "./chunk-AUOSDGWY.js";
62
62
  import {
63
63
  csvOrJson,
64
64
  daysAgoIso,
@@ -3699,14 +3699,22 @@ var avatarSummarySchema = z7.object({
3699
3699
  subjectDescription: z7.string(),
3700
3700
  /** The identity sheet to wire into `--reference`. Absent until the build settles. */
3701
3701
  sheetUrl: z7.string().optional(),
3702
+ /**
3703
+ * The avatar's pinned voice.
3704
+ *
3705
+ * A casting essential, not a profile detail: an avatar is a face AND a voice, and
3706
+ * casting the face without it is what makes a presenter speak in somebody else's.
3707
+ * It lived only behind `--full`, so an ad that grounded every frame on the identity
3708
+ * sheet still had its lines read by a voice cast from a prose description.
3709
+ */
3710
+ voiceId: z7.string().optional(),
3711
+ voiceDescription: z7.string().optional(),
3702
3712
  coverUrl: z7.string().optional(),
3703
3713
  errorMessage: z7.string().optional()
3704
3714
  });
3705
3715
  var avatarDetailSchema = avatarSummarySchema.extend({
3706
3716
  profile: avatarProfileSchema,
3707
3717
  sourceUrls: z7.array(z7.string()),
3708
- voiceId: z7.string().optional(),
3709
- voiceDescription: z7.string().optional(),
3710
3718
  createdAt: z7.number()
3711
3719
  });
3712
3720
  var avatarsListRequestSchema = z7.object({
@@ -28705,7 +28713,7 @@ function buildPhrases(blueprint, canonical2, compositeScenes, presenterPresent,
28705
28713
  flush();
28706
28714
  return phrases;
28707
28715
  }
28708
- function makeVoiceFactory(blueprint, canonical2, nodes, voiceLanguage) {
28716
+ function makeVoiceFactory(blueprint, canonical2, nodes, voiceLanguage, pinnedVoiceId) {
28709
28717
  const bySpeaker = /* @__PURE__ */ new Map();
28710
28718
  const describe = (speaker) => {
28711
28719
  for (const scene of blueprint.scenes)
@@ -28721,7 +28729,9 @@ function makeVoiceFactory(blueprint, canonical2, nodes, voiceLanguage) {
28721
28729
  const description = describe(speaker);
28722
28730
  const traits = parseVoiceTraits(description);
28723
28731
  if (voiceLanguage) traits.language = voiceLanguage;
28724
- nodes.push({ id, type: "voice_select", params: { description, ...traits } });
28732
+ nodes.push(
28733
+ pinnedVoiceId ? { id, type: "voice_select", params: { description, voice_id: pinnedVoiceId, ...traits } } : { id, type: "voice_select", params: { description, ...traits } }
28734
+ );
28725
28735
  bySpeaker.set(speaker, id);
28726
28736
  return id;
28727
28737
  };
@@ -29122,7 +29132,7 @@ function buildTimeline(blueprint, slots, opts, nodes) {
29122
29132
  });
29123
29133
  }
29124
29134
  const canonical2 = collapseVoiceover(blueprint);
29125
- const ensureVoiceNode = makeVoiceFactory(blueprint, canonical2, nodes, opts.voiceLanguage);
29135
+ const ensureVoiceNode = makeVoiceFactory(blueprint, canonical2, nodes, opts.voiceLanguage, opts.voiceId);
29126
29136
  const aspect = resolveAspect(blueprint.source?.aspect_ratio, opts.aspect, genAspectsFor(opts.videoModel));
29127
29137
  const env = {
29128
29138
  blueprint,
@@ -31002,7 +31012,7 @@ function classifyReelFindings(json) {
31002
31012
  if (!r.brand_mark_visible) {
31003
31013
  findings.push({
31004
31014
  severity: "warning",
31005
- what: "No brand mark is visible in any frame. `baker canvas scaffold-ad` draws one from `brand.logo` (the repo path in src/brand/BRAND.md) \u2014 top-left throughout and large on the closing card \u2014 so a mark missing here means the spec carried no logo, or the path did not resolve and the scaffold said so."
31015
+ what: "No brand mark is visible in any frame. THIS IS NOT A RE-RENDER: the mark is drawn by the caption composition as an ordinary `<img>`, never generated as a picture \u2014 so fix the mark itself (a bad path, or an SVG whose geometry clips its own wordmark) or the `.brand-mark` / `.endcard-logo` rules in `<composition>/index.html`, then re-run. Every clip comes back from the cache and only the composition pass renders again, so it costs seconds and no generation."
31006
31016
  });
31007
31017
  }
31008
31018
  if (!r.shows_more_than_a_face) {
@@ -31429,7 +31439,7 @@ ${describeRewrites(healed.rewrites)}
31429
31439
  `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.`,
31430
31440
  ...review.map((f) => `${f.severity === "blocking" ? "MUST FIX" : "SHOULD FIX"} \u2014 ${f.what}`),
31431
31441
  ...review.some((f) => f.scene) ? [
31432
- `Fix them, then re-run: edit the \`show\` of the scene files named above \u2014 a shot with a person handling equipment is where limbs and objects go wrong, so widen it, move the hands out of frame, or drop the person from that beat. Re-running re-renders ONLY the scenes you edited; everything else comes from the cache (this run: ${result.stats?.cached_nodes ?? 0} of ${result.stats?.total_nodes ?? 0} nodes were cached).`
31442
+ `Anything about the MARK or the closing card is a composition fix, not a re-render: both are drawn as HTML by the caption composition, so editing it and re-running re-renders only that pass \u2014 the clips are cached and no generation is paid for again. Fix them, then re-run: edit the \`show\` of the scene files named above \u2014 a shot with a person handling equipment is where limbs and objects go wrong, so widen it, move the hands out of frame, or drop the person from that beat. Re-running re-renders ONLY the scenes you edited; everything else comes from the cache (this run: ${result.stats?.cached_nodes ?? 0} of ${result.stats?.total_nodes ?? 0} nodes were cached).`
31433
31443
  ] : []
31434
31444
  ] : [];
31435
31445
  if (poster) {
@@ -32775,7 +32785,8 @@ function adSpecCastElements(spec, avatar) {
32775
32785
  function motionPrompt(beat, isClosingCard, place, physics) {
32776
32786
  if (isClosingCard) return BRAND_PLATE;
32777
32787
  const shot = `${beat.show}${place}${physics}`;
32778
- return beat.on_camera ? shot : `${shot} Nobody in frame is speaking \u2014 mouths closed, no dialogue.`;
32788
+ if (beat.on_camera) return shot;
32789
+ return `${shot} Nobody in frame is speaking: mouths CLOSED and STILL throughout, no talking, no lip movement. Whoever is in shot is looking, reacting or working \u2014 never addressing the camera.`;
32779
32790
  }
32780
32791
  function staticTranscript(spec) {
32781
32792
  const out = [];
@@ -32864,6 +32875,7 @@ function adSpecToBlueprint(spec) {
32864
32875
  }
32865
32876
 
32866
32877
  // src/engine/scaffold/ad-brand-overlay.ts
32878
+ var markFallback = (name) => name.trim() ? ` onerror="this.replaceWith(Object.assign(document.createElement('span'),{className:this.className+' brand-wordmark',textContent:${JSON.stringify(name.trim())}}))"` : "";
32867
32879
  var escapeHtml2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
32868
32880
  function firstHex(palette) {
32869
32881
  const hex = (palette ?? []).find((h) => /^#?[0-9a-fA-F]{6}$/.test(h.trim()));
@@ -32883,12 +32895,14 @@ function buildAdBrandOverlay(opts) {
32883
32895
  ];
32884
32896
  if (logo) {
32885
32897
  parts.push(
32886
- `<img class="clip brand-mark" src="${escapeHtml2(logo.file)}" alt="${escapeHtml2(logo.alt)}" data-start="0" data-dur="${totalS}" />`
32898
+ `<img class="clip brand-mark" src="${escapeHtml2(logo.file)}" alt="${escapeHtml2(logo.alt)}" data-start="0" data-dur="${totalS}"${markFallback(logo.alt)} />`
32887
32899
  );
32888
32900
  }
32889
32901
  const cardBits = [];
32890
32902
  if (logo) {
32891
- cardBits.push(`<img class="endcard-logo" src="${escapeHtml2(logo.file)}" alt="${escapeHtml2(logo.alt)}" />`);
32903
+ cardBits.push(
32904
+ `<img class="endcard-logo" src="${escapeHtml2(logo.file)}" alt="${escapeHtml2(logo.alt)}"${markFallback(logo.alt)} />`
32905
+ );
32892
32906
  }
32893
32907
  if (cta?.trim()) {
32894
32908
  const style = accent ? ` style="background:${escapeHtml2(accent)}"` : "";
@@ -32959,6 +32973,11 @@ function adBrandOverlayCss() {
32959
32973
  " .endcard-logo { width: 62%; height: auto; max-height: 300px; object-fit: contain;",
32960
32974
  " padding: 46px 56px; border-radius: 40px; background: rgba(255,255,255,0.96);",
32961
32975
  " box-shadow: 0 10px 40px rgba(0,0,0,0.28); }",
32976
+ " /* The name, when the mark itself could not be drawn. */",
32977
+ " .brand-wordmark { display: inline-block; font-family: 'Arial Black', 'Helvetica Neue', Arial, sans-serif;",
32978
+ " font-weight: 900; color: #14231b; letter-spacing: -0.01em; }",
32979
+ " .brand-mark.brand-wordmark { font-size: 34px; line-height: 1; height: auto; }",
32980
+ " .endcard-logo.brand-wordmark { font-size: 96px; line-height: 1; width: auto; }",
32962
32981
  " /* Capped and wrapping, not sized to its content: measured in a real browser,",
32963
32982
  " `GET YOUR FREE QUOTE` renders 948px wide against a 1080px canvas, so the next",
32964
32983
  " slightly longer call to action runs off the frame. Two lines beat an edge. */",
@@ -33211,6 +33230,10 @@ var scaffoldAdCommand = defineCommand104({
33211
33230
  // Told, not guessed: the voice description is written in the ad's own language
33212
33231
  // and the engine's trait parser reads English only.
33213
33232
  voiceLanguage: voiceLanguageFor(spec.data),
33233
+ // The cast avatar's own voice. An avatar is a face AND a voice; grounding every
33234
+ // frame on their identity sheet while a separately cast voice reads the lines is
33235
+ // how a presenter ends up speaking in somebody else's.
33236
+ ...avatar?.voiceId ? { voiceId: avatar.voiceId } : {},
33214
33237
  ...spec.data.voiceover === false ? { staticTranscriptPath: path24.relative(outDir, transcriptPath) } : {}
33215
33238
  };
33216
33239
  const canvas = scaffoldVideoCanvas(blueprint, adSpecCastElements(spec.data, avatar), opts);
@@ -33264,6 +33287,9 @@ var scaffoldAdCommand = defineCommand104({
33264
33287
  ] : [],
33265
33288
  ...avatarError ? [avatarError] : [],
33266
33289
  ...avatar ? [
33290
+ ...avatar?.voiceId ? [] : [
33291
+ `Avatar \`${handle}\` has no pinned voice, so the ad's lines are read by a voice cast from a description \u2014 it will not sound like them. Give them one (\`baker avatars\` voice) before this ships.`
33292
+ ],
33267
33293
  `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.`
33268
33294
  ] : [],
33269
33295
  ...presenterIsInEveryShot(spec.data) ? [