@koda-sl/baker-cli 0.281.1-dev.1f1c09c80 → 0.281.3-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 +56 -11
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -32778,14 +32778,19 @@ var AdSpec = z30.object({
|
|
|
32778
32778
|
message: "give `avatar` (a cast avatar's handle) or `description` \u2014 a cast with neither grounds on nothing"
|
|
32779
32779
|
}).optional(),
|
|
32780
32780
|
/**
|
|
32781
|
-
*
|
|
32782
|
-
* music when asked, and silence is a legitimate choice.
|
|
32781
|
+
* The bed, described in words — or `false` for an ad that is meant to be dry.
|
|
32783
32782
|
*
|
|
32784
|
-
*
|
|
32785
|
-
*
|
|
32786
|
-
*
|
|
32783
|
+
* Omitted lays a soft, neutral bed. It used to leave the ad silent, on the reasoning
|
|
32784
|
+
* that silence is a legitimate choice; it is, but only as a choice. In practice nothing
|
|
32785
|
+
* ever set this, and finished ads shipped with a voice over nothing — which reads as
|
|
32786
|
+
* unfinished, not as restraint. A bed is the ordinary case, so it is the default, and
|
|
32787
|
+
* an ad that wants silence says `false` and means it.
|
|
32788
|
+
*
|
|
32789
|
+
* The bed never plays over the first beat: the opening line is the scroll-stopper and
|
|
32790
|
+
* has to read clean. That is the engine's rule, not this one, and it is why the prompt
|
|
32791
|
+
* is all this needs to carry.
|
|
32787
32792
|
*/
|
|
32788
|
-
music: z30.string().min(1).optional(),
|
|
32793
|
+
music: z30.union([z30.string().min(1), z30.literal(false)]).optional(),
|
|
32789
32794
|
/**
|
|
32790
32795
|
* How the ad closes.
|
|
32791
32796
|
*
|
|
@@ -32981,6 +32986,11 @@ function staticTranscript(spec) {
|
|
|
32981
32986
|
}
|
|
32982
32987
|
return out;
|
|
32983
32988
|
}
|
|
32989
|
+
function musicBed(spec) {
|
|
32990
|
+
if (spec.music === false) return null;
|
|
32991
|
+
if (typeof spec.music === "string") return spec.music;
|
|
32992
|
+
return "A soft, warm instrumental bed that sits well under a spoken voiceover: gentle acoustic guitar or soft piano with light pads, steady and unobtrusive, no vocals, no lyrics, no drop, no build to a climax, nothing that pulls attention off the voice.";
|
|
32993
|
+
}
|
|
32984
32994
|
function beatToCamera(spec, beat, isClosingCard) {
|
|
32985
32995
|
if (isClosingCard) return false;
|
|
32986
32996
|
if (beat.on_camera !== void 0) return beat.on_camera;
|
|
@@ -33081,7 +33091,7 @@ function adSpecToBlueprint(spec) {
|
|
|
33081
33091
|
}
|
|
33082
33092
|
} : {},
|
|
33083
33093
|
...spec.brand?.palette?.length ? { style: { palette: spec.brand.palette.map((hex) => ({ hex })) } } : {},
|
|
33084
|
-
...spec
|
|
33094
|
+
...musicBed(spec) ? { music: { music_prompt: musicBed(spec) } } : {}
|
|
33085
33095
|
},
|
|
33086
33096
|
scenes
|
|
33087
33097
|
};
|
|
@@ -33243,7 +33253,7 @@ function pickBrandMark(files) {
|
|
|
33243
33253
|
const score = (f) => {
|
|
33244
33254
|
const name = f.toLowerCase();
|
|
33245
33255
|
let s = 0;
|
|
33246
|
-
if (
|
|
33256
|
+
if (/\.(png|webp)$/.test(name)) s += 1;
|
|
33247
33257
|
if (/\b(logo|wordmark|lockup)\b/.test(name)) s += 2;
|
|
33248
33258
|
if (/\b(mono|white|black|inverse|icon|favicon|mark-only|isotipo)\b/.test(name)) s -= 3;
|
|
33249
33259
|
return s;
|
|
@@ -33268,6 +33278,17 @@ async function readBrandFromWorkspace(root = ".") {
|
|
|
33268
33278
|
function svgIsLiveText(svg) {
|
|
33269
33279
|
return /<text[\s>]/i.test(svg);
|
|
33270
33280
|
}
|
|
33281
|
+
async function markDirectoryIsReal(dir) {
|
|
33282
|
+
const files = await readdir8(dir).catch(() => null);
|
|
33283
|
+
if (!files) return false;
|
|
33284
|
+
const marks = files.filter((f) => /\.(svg|png|webp)$/i.test(f));
|
|
33285
|
+
if (marks.length === 0) return false;
|
|
33286
|
+
for (const f of marks.filter((f2) => /\.svg$/i.test(f2))) {
|
|
33287
|
+
const svg = await readFile15(path22.join(dir, f), "utf-8").catch(() => null);
|
|
33288
|
+
if (svg === null || svgIsLiveText(svg)) return false;
|
|
33289
|
+
}
|
|
33290
|
+
return true;
|
|
33291
|
+
}
|
|
33271
33292
|
|
|
33272
33293
|
// src/commands/canvas/composition-path.ts
|
|
33273
33294
|
import { existsSync as existsSync4 } from "fs";
|
|
@@ -33449,20 +33470,44 @@ var scaffoldAdCommand = defineCommand105({
|
|
|
33449
33470
|
return;
|
|
33450
33471
|
}
|
|
33451
33472
|
if (resolvedLogo?.toLowerCase().endsWith(".svg")) {
|
|
33452
|
-
const svg = await readFile16(resolvedLogo, "utf-8").catch(() =>
|
|
33453
|
-
if (svg
|
|
33473
|
+
const svg = await readFile16(resolvedLogo, "utf-8").catch(() => null);
|
|
33474
|
+
if (svg === null) {
|
|
33475
|
+
writeJson({
|
|
33476
|
+
ok: false,
|
|
33477
|
+
error: {
|
|
33478
|
+
code: "VALIDATION_ERROR",
|
|
33479
|
+
message: `The brand mark at \`${resolvedLogo}\` could not be read, so there is no way to tell whether it is a real logo or a typed stand-in. An ad is not shipped on a mark nobody could open.`,
|
|
33480
|
+
fix: `Check the file is there and readable, or put the client's logo in \`${BRAND_DIR}/logos/\` \u2014 a PNG at 1000px or wider, or an outlined SVG.`
|
|
33481
|
+
}
|
|
33482
|
+
});
|
|
33483
|
+
process.exit(1);
|
|
33484
|
+
return;
|
|
33485
|
+
}
|
|
33486
|
+
if (svgIsLiveText(svg)) {
|
|
33454
33487
|
writeJson({
|
|
33455
33488
|
ok: false,
|
|
33456
33489
|
error: {
|
|
33457
33490
|
code: "VALIDATION_ERROR",
|
|
33458
33491
|
message: `The mark at \`${resolvedLogo}\` draws its wordmark with live \`<text>\`, not outlines. That is a typed stand-in, not a logo: it renders in whatever font the machine happens to have, and the one that shipped this way came out reading "greenle\xE1" in every frame.`,
|
|
33459
|
-
fix: `Ask the client for their real logo file and put it in \`${BRAND_DIR}/logos/\` \u2014 an outlined SVG
|
|
33492
|
+
fix: `Ask the client for their real logo file and put it in \`${BRAND_DIR}/logos/\` \u2014 the one their designer delivered, as an outlined SVG or a PNG they gave you. Do not redraw it, and do not export this file to PNG: rasterising it only bakes the invented letters into pixels, where nothing downstream can tell they are wrong.`
|
|
33460
33493
|
}
|
|
33461
33494
|
});
|
|
33462
33495
|
process.exit(1);
|
|
33463
33496
|
return;
|
|
33464
33497
|
}
|
|
33465
33498
|
}
|
|
33499
|
+
if (resolvedLogo && !await markDirectoryIsReal(path24.dirname(resolvedLogo))) {
|
|
33500
|
+
writeJson({
|
|
33501
|
+
ok: false,
|
|
33502
|
+
error: {
|
|
33503
|
+
code: "VALIDATION_ERROR",
|
|
33504
|
+
message: `There is no real logo in \`${BRAND_DIR}/logos/\` \u2014 what is there is a typed stand-in, whose wordmark is drawn as live text in a font the renderer does not have. An ad built on it wears letters the machine invented, and converting it to a PNG only bakes them in.`,
|
|
33505
|
+
fix: `Ask the client for their actual logo file and put it in \`${BRAND_DIR}/logos/\` \u2014 the one their designer delivered, as an outlined SVG or a PNG they gave you. Do not redraw it and do not export the stand-in to another format.`
|
|
33506
|
+
}
|
|
33507
|
+
});
|
|
33508
|
+
process.exit(1);
|
|
33509
|
+
return;
|
|
33510
|
+
}
|
|
33466
33511
|
const blueprint = adSpecToBlueprint(spec.data);
|
|
33467
33512
|
const slug = args.slug ?? path24.basename(specPath).replace(/\.[^.]+$/, "");
|
|
33468
33513
|
const outPath = args.out ?? (args.slug ? path24.join("src/creatives", slug, `${slug}.canvas.json`) : path24.join(path24.dirname(specPath), `${slug}.canvas.json`));
|