@koda-sl/baker-cli 0.306.6 → 0.306.7

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 CHANGED
@@ -3736,6 +3736,8 @@ baker studio animate "she keeps walking, camera stays with her, then she stops a
3736
3736
 
3737
3737
  **A rendered clip is not usable anywhere until it is kept** — `keep` is what ingests it into the video library. Unkept takes are never ingested, which is what makes a rejected batch cheap.
3738
3738
 
3739
+ **One take of a presenter is an asset; an ad is not.** From the second clip in a conversation that carries a scripted line, `animate` warns that an ad is being assembled by hand and names `baker canvas scaffold-ad` instead. Separate takes have no cut between them, no continuous read, no captions and no end card, so three takes of a presenter are three clips and never one ad. It is a hint and never a refusal — splitting one long read into takes you then cut together is a legitimate shape, and building on a previous take (`--from-clip` / `--from-video`) is not flagged at all.
3740
+
3739
3741
  ### `baker studio get` / `list` / `keep` / `skills`
3740
3742
 
3741
3743
  ```bash
package/dist/cli.js CHANGED
@@ -58456,6 +58456,43 @@ import { defineCommand as defineCommand209 } from "citty";
58456
58456
  // src/commands/studio/animate.ts
58457
58457
  import { defineCommand as defineCommand201 } from "citty";
58458
58458
 
58459
+ // src/commands/studio/spokenPace.ts
58460
+ var WORDS_PER_SECOND3 = SPOKEN_WORDS_PER_SECOND;
58461
+ var spokenWords = spokenWordsIn;
58462
+ function wordsThatFit(seconds) {
58463
+ return Math.floor(seconds * WORDS_PER_SECOND3);
58464
+ }
58465
+ function spokenPaceHint(prompt, seconds, model) {
58466
+ const words2 = spokenWords(prompt);
58467
+ if (words2 === 0) return null;
58468
+ const split = model ? splitScriptFor(words2, model) : null;
58469
+ if (split) {
58470
+ return `The line is ${words2} words \u2014 about ${Math.ceil(words2 / WORDS_PER_SECOND3)}s of speech \u2014 and ${model} renders at most ${split.maxSeconds}s (~${Math.floor(split.maxSeconds * WORDS_PER_SECOND3)} words). It will not refuse and it will not cut: it will RUSH, swallowing syllables. Split the script into ${split.clips} takes of roughly ${split.wordsPerClip} words each, rendered separately and cut together \u2014 break it where the thought breaks, not mid-sentence. Or move to a model that renders longer (bytedance/seedance-2.5 goes to 30s).`;
58471
+ }
58472
+ if (!seconds) return null;
58473
+ const fits = wordsThatFit(seconds);
58474
+ if (words2 <= fits) return null;
58475
+ const rate = (words2 / seconds).toFixed(1);
58476
+ return `The line is ${words2} words and this clip is ${seconds}s \u2014 that is ${rate} words a second against a natural ${WORDS_PER_SECOND3}. The model will not run over, it will RUSH: syllables swallowed, endings clipped, and the take unusable however good the picture. Cut the line to about ${fits} words, or give it the length it needs (${Math.ceil(words2 / WORDS_PER_SECOND3)}s). Do not re-render the same line at the same length and hope.`;
58477
+ }
58478
+
58479
+ // src/commands/studio/adByHand.ts
58480
+ function priorScriptedTakes(batches) {
58481
+ return batches.filter(
58482
+ (batch) => batch.kind === "video" && spokenWords(batch.prompt) > 0 && batch.variants.some((variant) => variant.status !== "failed")
58483
+ ).length;
58484
+ }
58485
+ function ordinal(n) {
58486
+ const suffix = n % 100 >= 11 && n % 100 <= 13 ? "th" : ["th", "st", "nd", "rd"][n % 10] ?? "th";
58487
+ return `${n}${suffix}`;
58488
+ }
58489
+ function adByHandHint(input) {
58490
+ if (!input.thisTakeIsScripted || input.buildsOnAClip) return null;
58491
+ if (input.priorScriptedTakes < 1) return null;
58492
+ const which = ordinal(input.priorScriptedTakes + 1);
58493
+ return `This is the ${which} clip with a scripted line in this conversation. Separate takes stay separate \u2014 nothing cuts them together, the audio stops and restarts at every join, and there are no captions and no closing card \u2014 so ${input.priorScriptedTakes + 1} takes of a presenter are ${input.priorScriptedTakes + 1} clips and never one ad. If these are the beats of one ad, stop rendering them one at a time: write them as beats (one line the voice says plus one shot on screen while it is said) and \`baker canvas scaffold-ad\` emits the whole ad \u2014 one canvas, one render, continuous read, synced captions and a brand end card. If they really are consecutive parts of ONE long read you will cut together yourself, carry on.`;
58494
+ }
58495
+
58459
58496
  // src/commands/studio/batch.ts
58460
58497
  function projectBatch(generation, full) {
58461
58498
  const compact3 = {
@@ -58670,26 +58707,6 @@ function uploadFailure(path48) {
58670
58707
  };
58671
58708
  }
58672
58709
 
58673
- // src/commands/studio/spokenPace.ts
58674
- var WORDS_PER_SECOND3 = SPOKEN_WORDS_PER_SECOND;
58675
- var spokenWords = spokenWordsIn;
58676
- function wordsThatFit(seconds) {
58677
- return Math.floor(seconds * WORDS_PER_SECOND3);
58678
- }
58679
- function spokenPaceHint(prompt, seconds, model) {
58680
- const words2 = spokenWords(prompt);
58681
- if (words2 === 0) return null;
58682
- const split = model ? splitScriptFor(words2, model) : null;
58683
- if (split) {
58684
- return `The line is ${words2} words \u2014 about ${Math.ceil(words2 / WORDS_PER_SECOND3)}s of speech \u2014 and ${model} renders at most ${split.maxSeconds}s (~${Math.floor(split.maxSeconds * WORDS_PER_SECOND3)} words). It will not refuse and it will not cut: it will RUSH, swallowing syllables. Split the script into ${split.clips} takes of roughly ${split.wordsPerClip} words each, rendered separately and cut together \u2014 break it where the thought breaks, not mid-sentence. Or move to a model that renders longer (bytedance/seedance-2.5 goes to 30s).`;
58685
- }
58686
- if (!seconds) return null;
58687
- const fits = wordsThatFit(seconds);
58688
- if (words2 <= fits) return null;
58689
- const rate = (words2 / seconds).toFixed(1);
58690
- return `The line is ${words2} words and this clip is ${seconds}s \u2014 that is ${rate} words a second against a natural ${WORDS_PER_SECOND3}. The model will not run over, it will RUSH: syllables swallowed, endings clipped, and the take unusable however good the picture. Cut the line to about ${fits} words, or give it the length it needs (${Math.ceil(words2 / WORDS_PER_SECOND3)}s). Do not re-render the same line at the same length and hope.`;
58691
- }
58692
-
58693
58710
  // src/commands/studio/animate.ts
58694
58711
  var MODEL_LIST = VIDEO_MODEL_IDS;
58695
58712
  var DEFAULT_MAX_WAIT_MS = 6e5;
@@ -58931,6 +58948,25 @@ function costHintsFor(body) {
58931
58948
  }
58932
58949
  return hints2;
58933
58950
  }
58951
+ var PRIOR_TAKE_WINDOW = 20;
58952
+ async function adByHandHintFor(body) {
58953
+ const thisTakeIsScripted = spokenWords(body.prompt) > 0;
58954
+ const buildsOnAClip = (body.clipRefs?.length ?? 0) + (body.videoIds?.length ?? 0) > 0;
58955
+ if (!thisTakeIsScripted || buildsOnAClip) return null;
58956
+ try {
58957
+ const { generations } = await apiGet("/api/studio/generations", {
58958
+ kind: "video",
58959
+ limit: String(PRIOR_TAKE_WINDOW)
58960
+ });
58961
+ return adByHandHint({
58962
+ priorScriptedTakes: priorScriptedTakes(generations),
58963
+ thisTakeIsScripted,
58964
+ buildsOnAClip
58965
+ });
58966
+ } catch {
58967
+ return null;
58968
+ }
58969
+ }
58934
58970
  var animateCommand = defineCommand201({
58935
58971
  meta: {
58936
58972
  name: "animate",
@@ -59020,7 +59056,8 @@ var animateCommand = defineCommand201({
59020
59056
  }
59021
59057
  const { imageIds } = args.image ? await resolveLibraryImageIds(args.image, clipImageLimitFor(args)) : { imageIds: [] };
59022
59058
  const body = buildAnimateBody(args, prompt, imageIds);
59023
- const costHints = costHintsFor(body);
59059
+ const assembling = await adByHandHintFor(body);
59060
+ const preflightHints = [...assembling ? [assembling] : [], ...costHintsFor(body)];
59024
59061
  const start = await apiPost("/api/studio/animate", body);
59025
59062
  const maxWaitMs = resolveMaxWaitMs(args.wait, args["max-wait"], DEFAULT_MAX_WAIT_MS);
59026
59063
  if (maxWaitMs === 0) {
@@ -59029,7 +59066,7 @@ var animateCommand = defineCommand201({
59029
59066
  data: start,
59030
59067
  hints: [
59031
59068
  `Rendering ${start.count} take(s). Read them with \`baker studio get ${start.generationId}\`, then keep the one you want.`,
59032
- ...costHints
59069
+ ...preflightHints
59033
59070
  ]
59034
59071
  });
59035
59072
  return;
@@ -59039,14 +59076,14 @@ var animateCommand = defineCommand201({
59039
59076
  writeJson({
59040
59077
  ok: true,
59041
59078
  data: { ...start, status: "running" },
59042
- hints: [waitingHint(outcome, start.generationId, maxWaitMs), ...costHints]
59079
+ hints: [waitingHint(outcome, start.generationId, maxWaitMs), ...preflightHints]
59043
59080
  });
59044
59081
  return;
59045
59082
  }
59046
59083
  writeJson({
59047
59084
  ok: true,
59048
59085
  data: projectBatch(outcome.generation, args.full === true),
59049
- hints: [...batchHints(outcome.generation), ...costHints]
59086
+ hints: [...batchHints(outcome.generation), ...preflightHints]
59050
59087
  });
59051
59088
  } catch (error) {
59052
59089
  failStudioCommand(error, { withFix: true });