@koda-sl/baker-cli 0.122.1-dev.57a9836c5 → 0.123.0-dev.4a85b9f30
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 +29 -13
- package/dist/{chunk-T6HBTZOO.js → chunk-43KBQLP5.js} +144 -35
- package/dist/chunk-43KBQLP5.js.map +1 -0
- package/dist/cli.js +548 -183
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +33 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-T6HBTZOO.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
IMAGE_GENERATE_MODELS,
|
|
6
6
|
LayerExecutionError,
|
|
7
7
|
MODEL_REGISTRY,
|
|
8
|
+
REF_PREFIX,
|
|
8
9
|
SEEDANCE_DURATIONS,
|
|
9
10
|
ValidationError,
|
|
10
11
|
collectAssetRefLikes,
|
|
@@ -14,13 +15,14 @@ import {
|
|
|
14
15
|
elementMentionKeywords,
|
|
15
16
|
generateCatalog,
|
|
16
17
|
isPersistedAssetRef,
|
|
18
|
+
parseRefExpr,
|
|
17
19
|
requireCredentialsFromEnv,
|
|
18
20
|
resolveConcurrency,
|
|
19
21
|
sha256Hex,
|
|
20
22
|
toModelSafeImage,
|
|
21
23
|
ulid,
|
|
22
24
|
validateCanvasDeep
|
|
23
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-43KBQLP5.js";
|
|
24
26
|
import {
|
|
25
27
|
csvOrJson,
|
|
26
28
|
daysAgoIso,
|
|
@@ -4671,11 +4673,11 @@ function rawTextEntries(value) {
|
|
|
4671
4673
|
const values = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
|
|
4672
4674
|
return values.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
4673
4675
|
}
|
|
4674
|
-
function rawFileEntries(
|
|
4675
|
-
if (typeof
|
|
4676
|
+
function rawFileEntries(path15) {
|
|
4677
|
+
if (typeof path15 !== "string" || path15.length === 0) {
|
|
4676
4678
|
return [];
|
|
4677
4679
|
}
|
|
4678
|
-
return readFileSync2(
|
|
4680
|
+
return readFileSync2(path15, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
4679
4681
|
}
|
|
4680
4682
|
function keywordEntries(args) {
|
|
4681
4683
|
const defaultMatch = typeof args["match-type"] === "string" ? args["match-type"].toUpperCase() : void 0;
|
|
@@ -4698,19 +4700,19 @@ function keywordEntries(args) {
|
|
|
4698
4700
|
}
|
|
4699
4701
|
return entries;
|
|
4700
4702
|
}
|
|
4701
|
-
function loadJsonFileArg(
|
|
4702
|
-
if (typeof
|
|
4703
|
+
function loadJsonFileArg(path15) {
|
|
4704
|
+
if (typeof path15 !== "string" || path15.length === 0) {
|
|
4703
4705
|
return {};
|
|
4704
4706
|
}
|
|
4705
4707
|
try {
|
|
4706
|
-
const parsed = JSON.parse(readFileSync2(
|
|
4708
|
+
const parsed = JSON.parse(readFileSync2(path15, "utf8"));
|
|
4707
4709
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4708
|
-
failWriteValidation(`${
|
|
4710
|
+
failWriteValidation(`${path15} must contain a JSON object`);
|
|
4709
4711
|
}
|
|
4710
4712
|
return parsed;
|
|
4711
4713
|
} catch (err) {
|
|
4712
4714
|
if (err instanceof SyntaxError) {
|
|
4713
|
-
failWriteValidation(`${
|
|
4715
|
+
failWriteValidation(`${path15} is not valid JSON: ${err.message}`);
|
|
4714
4716
|
}
|
|
4715
4717
|
throw err;
|
|
4716
4718
|
}
|
|
@@ -4821,10 +4823,10 @@ async function stageUpdate(kind, customerId, target, payload) {
|
|
|
4821
4823
|
async function stageTarget(kind, customerId, target) {
|
|
4822
4824
|
await stageGoogleOp({ kind, customerId, target });
|
|
4823
4825
|
}
|
|
4824
|
-
async function draftAction(
|
|
4826
|
+
async function draftAction(path15, body) {
|
|
4825
4827
|
try {
|
|
4826
4828
|
const chatId = requireChatId();
|
|
4827
|
-
const response = await apiPost(
|
|
4829
|
+
const response = await apiPost(path15, { chatId, ...body });
|
|
4828
4830
|
writeJsonEnvelope(response);
|
|
4829
4831
|
} catch (err) {
|
|
4830
4832
|
handleGoogleError(err);
|
|
@@ -8579,19 +8581,19 @@ function failWriteValidation2(message) {
|
|
|
8579
8581
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
8580
8582
|
process.exit(1);
|
|
8581
8583
|
}
|
|
8582
|
-
function loadJsonFileArg2(
|
|
8583
|
-
if (typeof
|
|
8584
|
+
function loadJsonFileArg2(path15) {
|
|
8585
|
+
if (typeof path15 !== "string" || path15.length === 0) {
|
|
8584
8586
|
return {};
|
|
8585
8587
|
}
|
|
8586
8588
|
try {
|
|
8587
|
-
const parsed = JSON.parse(readFileSync6(
|
|
8589
|
+
const parsed = JSON.parse(readFileSync6(path15, "utf8"));
|
|
8588
8590
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8589
|
-
failWriteValidation2(`${
|
|
8591
|
+
failWriteValidation2(`${path15} must contain a JSON object`);
|
|
8590
8592
|
}
|
|
8591
8593
|
return parsed;
|
|
8592
8594
|
} catch (err) {
|
|
8593
8595
|
if (err instanceof SyntaxError) {
|
|
8594
|
-
failWriteValidation2(`${
|
|
8596
|
+
failWriteValidation2(`${path15} is not valid JSON: ${err.message}`);
|
|
8595
8597
|
}
|
|
8596
8598
|
throw err;
|
|
8597
8599
|
}
|
|
@@ -8676,15 +8678,15 @@ function parseLocaleFlag(value) {
|
|
|
8676
8678
|
}
|
|
8677
8679
|
return { language: match[1], country: match[2].toUpperCase() };
|
|
8678
8680
|
}
|
|
8679
|
-
function loadTargetingFileArg(
|
|
8680
|
-
if (typeof
|
|
8681
|
+
function loadTargetingFileArg(path15) {
|
|
8682
|
+
if (typeof path15 !== "string" || path15.length === 0) {
|
|
8681
8683
|
return void 0;
|
|
8682
8684
|
}
|
|
8683
|
-
const parsed = loadJsonFileArg2(
|
|
8685
|
+
const parsed = loadJsonFileArg2(path15);
|
|
8684
8686
|
const criteria = parsed.targetingCriteria ?? parsed;
|
|
8685
8687
|
if (!criteria.include) {
|
|
8686
8688
|
failWriteValidation2(
|
|
8687
|
-
`${
|
|
8689
|
+
`${path15} must contain targeting criteria with an "include" block (see baker schema ads.linkedin.campaigns.create)`
|
|
8688
8690
|
);
|
|
8689
8691
|
}
|
|
8690
8692
|
return criteria;
|
|
@@ -8719,14 +8721,14 @@ function parseCsvLine(line) {
|
|
|
8719
8721
|
cells.push(current);
|
|
8720
8722
|
return cells.map((cell) => cell.trim());
|
|
8721
8723
|
}
|
|
8722
|
-
function parseListFileArg(
|
|
8723
|
-
if (typeof
|
|
8724
|
+
function parseListFileArg(path15, maxRows) {
|
|
8725
|
+
if (typeof path15 !== "string" || path15.length === 0) {
|
|
8724
8726
|
return void 0;
|
|
8725
8727
|
}
|
|
8726
|
-
const raw = readFileSync6(
|
|
8728
|
+
const raw = readFileSync6(path15, "utf8");
|
|
8727
8729
|
const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
8728
8730
|
if (lines.length < 2) {
|
|
8729
|
-
failWriteValidation2(`${
|
|
8731
|
+
failWriteValidation2(`${path15} needs a header row and at least one data row`);
|
|
8730
8732
|
}
|
|
8731
8733
|
const columns = parseCsvLine(lines[0]).map((column) => column.trim());
|
|
8732
8734
|
const rows = [];
|
|
@@ -8745,7 +8747,7 @@ function parseListFileArg(path14, maxRows) {
|
|
|
8745
8747
|
}
|
|
8746
8748
|
}
|
|
8747
8749
|
if (rows.length > maxRows) {
|
|
8748
|
-
failWriteValidation2(`${
|
|
8750
|
+
failWriteValidation2(`${path15} has ${rows.length} rows \u2014 the inline limit is ${maxRows}. Split the list.`);
|
|
8749
8751
|
}
|
|
8750
8752
|
return { columns, rows };
|
|
8751
8753
|
}
|
|
@@ -10828,11 +10830,11 @@ var updateStatusSchema = z9.enum(UPDATE_STATUSES);
|
|
|
10828
10830
|
function currencyMinimums2(currencyCode) {
|
|
10829
10831
|
return CURRENCY_MINIMUMS2[currencyCode] ?? DEFAULT_CURRENCY_MINIMUM2;
|
|
10830
10832
|
}
|
|
10831
|
-
function validateDailyBudgetFloor(money, ctx,
|
|
10833
|
+
function validateDailyBudgetFloor(money, ctx, path15) {
|
|
10832
10834
|
if (money?.currencyCode) {
|
|
10833
10835
|
const min = currencyMinimums2(money.currencyCode).dailyBudgetMin;
|
|
10834
10836
|
if (Number(money.amount) < min) {
|
|
10835
|
-
ctx.addIssue({ code: "custom", path:
|
|
10837
|
+
ctx.addIssue({ code: "custom", path: path15, message: `below the ${min} ${money.currencyCode} daily minimum` });
|
|
10836
10838
|
}
|
|
10837
10839
|
}
|
|
10838
10840
|
}
|
|
@@ -11320,19 +11322,19 @@ function failWriteValidation3(message) {
|
|
|
11320
11322
|
writeJsonEnvelope({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
11321
11323
|
process.exit(1);
|
|
11322
11324
|
}
|
|
11323
|
-
function loadJsonFileArg3(
|
|
11324
|
-
if (typeof
|
|
11325
|
+
function loadJsonFileArg3(path15) {
|
|
11326
|
+
if (typeof path15 !== "string" || path15.length === 0) {
|
|
11325
11327
|
return {};
|
|
11326
11328
|
}
|
|
11327
11329
|
try {
|
|
11328
|
-
const parsed = JSON.parse(readFileSync8(
|
|
11330
|
+
const parsed = JSON.parse(readFileSync8(path15, "utf8"));
|
|
11329
11331
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11330
|
-
failWriteValidation3(`${
|
|
11332
|
+
failWriteValidation3(`${path15} must contain a JSON object`);
|
|
11331
11333
|
}
|
|
11332
11334
|
return parsed;
|
|
11333
11335
|
} catch (err) {
|
|
11334
11336
|
if (err instanceof SyntaxError) {
|
|
11335
|
-
failWriteValidation3(`${
|
|
11337
|
+
failWriteValidation3(`${path15} is not valid JSON: ${err.message}`);
|
|
11336
11338
|
}
|
|
11337
11339
|
throw err;
|
|
11338
11340
|
}
|
|
@@ -14394,8 +14396,8 @@ async function probeDuration(filePath) {
|
|
|
14394
14396
|
}
|
|
14395
14397
|
|
|
14396
14398
|
// src/commands/canvas/run.ts
|
|
14397
|
-
import { readFile as
|
|
14398
|
-
import
|
|
14399
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
14400
|
+
import path6 from "path";
|
|
14399
14401
|
import { defineCommand as defineCommand88 } from "citty";
|
|
14400
14402
|
|
|
14401
14403
|
// src/commands/canvas/placeholders.ts
|
|
@@ -14658,6 +14660,7 @@ var RunRecordPoster = class {
|
|
|
14658
14660
|
latest = null;
|
|
14659
14661
|
inflight = null;
|
|
14660
14662
|
warned = false;
|
|
14663
|
+
keepaliveTimer = null;
|
|
14661
14664
|
constructor(post) {
|
|
14662
14665
|
this.post = post;
|
|
14663
14666
|
}
|
|
@@ -14666,12 +14669,36 @@ var RunRecordPoster = class {
|
|
|
14666
14669
|
this.latest = payload;
|
|
14667
14670
|
if (!this.inflight) this.inflight = this.pump();
|
|
14668
14671
|
}
|
|
14672
|
+
/**
|
|
14673
|
+
* Re-post the latest snapshot on an interval even with no new node events, so
|
|
14674
|
+
* the backend's `canvasRuns.updatedAt` heartbeat stays fresh during a long
|
|
14675
|
+
* single-clip poll (a video_generate clip can run minutes with no
|
|
14676
|
+
* intervening node events). When this process dies the keepalive stops → the
|
|
14677
|
+
* run's `updatedAt` goes stale → the backend reconciliation sweep force-fails
|
|
14678
|
+
* it as interrupted and surfaces the clips that finished. `produce` returns
|
|
14679
|
+
* null before the plan lands (nothing worth posting yet).
|
|
14680
|
+
*/
|
|
14681
|
+
startKeepalive(produce, intervalMs = 6e4) {
|
|
14682
|
+
if (this.keepaliveTimer) return;
|
|
14683
|
+
this.keepaliveTimer = setInterval(() => {
|
|
14684
|
+
const snapshot = produce();
|
|
14685
|
+
if (snapshot) this.enqueue(snapshot);
|
|
14686
|
+
}, intervalMs);
|
|
14687
|
+
this.keepaliveTimer.unref?.();
|
|
14688
|
+
}
|
|
14689
|
+
stopKeepalive() {
|
|
14690
|
+
if (this.keepaliveTimer) {
|
|
14691
|
+
clearInterval(this.keepaliveTimer);
|
|
14692
|
+
this.keepaliveTimer = null;
|
|
14693
|
+
}
|
|
14694
|
+
}
|
|
14669
14695
|
/**
|
|
14670
14696
|
* Post the terminal record (awaited, errors surfaced to the caller). Any
|
|
14671
14697
|
* queued progress snapshot is superseded — the terminal record is the full
|
|
14672
14698
|
* state — but an in-flight POST is awaited first so it can't land after.
|
|
14673
14699
|
*/
|
|
14674
14700
|
async flush(terminal) {
|
|
14701
|
+
this.stopKeepalive();
|
|
14675
14702
|
this.latest = null;
|
|
14676
14703
|
if (this.inflight) await this.inflight;
|
|
14677
14704
|
await this.post(terminal);
|
|
@@ -14721,6 +14748,45 @@ async function pruneOldRuns(outputsDir, keep, currentRunId, log) {
|
|
|
14721
14748
|
log(`[prune ] removed ${toPrune.length} old run dir(s), kept the ${keep} newest`);
|
|
14722
14749
|
}
|
|
14723
14750
|
|
|
14751
|
+
// src/commands/canvas/run-resume.ts
|
|
14752
|
+
import { mkdir, readFile as readFile2, rm as rm2, writeFile } from "fs/promises";
|
|
14753
|
+
import path5 from "path";
|
|
14754
|
+
function markerPath(outputsDir, canvasPath) {
|
|
14755
|
+
const key = sha256Hex(Buffer.from(path5.resolve(canvasPath))).slice(0, 32);
|
|
14756
|
+
return path5.join(outputsDir, ".inflight", `${key}.json`);
|
|
14757
|
+
}
|
|
14758
|
+
async function resolveRunId(opts) {
|
|
14759
|
+
if (opts.explicitRunId) return { runId: opts.explicitRunId, resumed: false };
|
|
14760
|
+
if (!opts.fresh) {
|
|
14761
|
+
const existing = await readMarkerRunId(opts.outputsDir, opts.canvasPath);
|
|
14762
|
+
if (existing) return { runId: existing, resumed: true };
|
|
14763
|
+
}
|
|
14764
|
+
return { runId: `r_${ulid()}`, resumed: false };
|
|
14765
|
+
}
|
|
14766
|
+
async function readMarkerRunId(outputsDir, canvasPath) {
|
|
14767
|
+
try {
|
|
14768
|
+
const raw = await readFile2(markerPath(outputsDir, canvasPath), "utf8");
|
|
14769
|
+
const parsed = JSON.parse(raw);
|
|
14770
|
+
return typeof parsed.runId === "string" && parsed.runId.length > 0 ? parsed.runId : null;
|
|
14771
|
+
} catch {
|
|
14772
|
+
return null;
|
|
14773
|
+
}
|
|
14774
|
+
}
|
|
14775
|
+
async function markRunInFlight(outputsDir, canvasPath, runId) {
|
|
14776
|
+
try {
|
|
14777
|
+
const file = markerPath(outputsDir, canvasPath);
|
|
14778
|
+
await mkdir(path5.dirname(file), { recursive: true });
|
|
14779
|
+
await writeFile(file, JSON.stringify({ runId, canvasPath: path5.resolve(canvasPath), startedAt: Date.now() }));
|
|
14780
|
+
} catch {
|
|
14781
|
+
}
|
|
14782
|
+
}
|
|
14783
|
+
async function clearRunMarker(outputsDir, canvasPath) {
|
|
14784
|
+
try {
|
|
14785
|
+
await rm2(markerPath(outputsDir, canvasPath), { force: true });
|
|
14786
|
+
} catch {
|
|
14787
|
+
}
|
|
14788
|
+
}
|
|
14789
|
+
|
|
14724
14790
|
// src/commands/canvas/run.ts
|
|
14725
14791
|
var runCommand = defineCommand88({
|
|
14726
14792
|
meta: { name: "run", description: "Validate and execute a canvas JSON file." },
|
|
@@ -14728,8 +14794,17 @@ var runCommand = defineCommand88({
|
|
|
14728
14794
|
file: { type: "positional", required: true, description: "Path to canvas JSON" },
|
|
14729
14795
|
"cache-dir": { type: "string", description: "Cache root (default ./canvas/.cache)" },
|
|
14730
14796
|
"outputs-dir": { type: "string", description: "Per-run outputs root (default ./canvas)" },
|
|
14731
|
-
"run-id": { type: "string", description: "Override run id" },
|
|
14797
|
+
"run-id": { type: "string", description: "Override run id (also resumes that run, re-attaching its in-flight jobs)" },
|
|
14798
|
+
fresh: {
|
|
14799
|
+
type: "boolean",
|
|
14800
|
+
default: false,
|
|
14801
|
+
description: "Ignore any interrupted-run marker and start a new run id instead of resuming"
|
|
14802
|
+
},
|
|
14732
14803
|
"cache-policy": { type: "string", description: "read_write | bypass | read_only" },
|
|
14804
|
+
regenerate: {
|
|
14805
|
+
type: "string",
|
|
14806
|
+
description: "Comma-separated node ids to force fresh THIS run (e.g. --regenerate gen_4x5,gen_9x16), bypassing the content cache for just those nodes + everything downstream. For a persistent re-render, bump a node's `regenerate` field in the canvas JSON instead."
|
|
14807
|
+
},
|
|
14733
14808
|
concurrency: {
|
|
14734
14809
|
type: "string",
|
|
14735
14810
|
description: "Max nodes per layer in flight at once (default 5; env BAKER_CANVAS_CONCURRENCY)"
|
|
@@ -14756,8 +14831,8 @@ var runCommand = defineCommand88({
|
|
|
14756
14831
|
}
|
|
14757
14832
|
},
|
|
14758
14833
|
async run({ args }) {
|
|
14759
|
-
const filePath =
|
|
14760
|
-
const raw = await
|
|
14834
|
+
const filePath = path6.resolve(String(args.file));
|
|
14835
|
+
const raw = await readFile3(filePath, "utf8");
|
|
14761
14836
|
let parsed;
|
|
14762
14837
|
try {
|
|
14763
14838
|
parsed = JSON.parse(raw);
|
|
@@ -14767,7 +14842,7 @@ var runCommand = defineCommand88({
|
|
|
14767
14842
|
`);
|
|
14768
14843
|
process.exit(2);
|
|
14769
14844
|
}
|
|
14770
|
-
parsed = resolveRelativeCanvasPaths(parsed,
|
|
14845
|
+
parsed = resolveRelativeCanvasPaths(parsed, path6.dirname(filePath));
|
|
14771
14846
|
const pending = unsuppliedPlaceholderAssets(parsed);
|
|
14772
14847
|
if (pending.length > 0) {
|
|
14773
14848
|
process.stderr.write(
|
|
@@ -14787,6 +14862,34 @@ var runCommand = defineCommand88({
|
|
|
14787
14862
|
);
|
|
14788
14863
|
process.exit(2);
|
|
14789
14864
|
}
|
|
14865
|
+
let regenerate;
|
|
14866
|
+
if (args.regenerate !== void 0) {
|
|
14867
|
+
const requested = String(args.regenerate).split(",").map((id) => id.trim()).filter((id) => id.length > 0);
|
|
14868
|
+
const known = new Set(canvasNodeIds(parsed));
|
|
14869
|
+
const unknown = requested.filter((id) => !known.has(id));
|
|
14870
|
+
if (unknown.length > 0) {
|
|
14871
|
+
process.stderr.write(
|
|
14872
|
+
`${JSON.stringify(
|
|
14873
|
+
{
|
|
14874
|
+
ok: false,
|
|
14875
|
+
error: {
|
|
14876
|
+
code: "unknown_regenerate_node",
|
|
14877
|
+
message: `--regenerate names node id(s) not in this canvas: ${unknown.join(", ")}. Known ids: ${[...known].join(", ")}`
|
|
14878
|
+
}
|
|
14879
|
+
},
|
|
14880
|
+
null,
|
|
14881
|
+
2
|
|
14882
|
+
)}
|
|
14883
|
+
`
|
|
14884
|
+
);
|
|
14885
|
+
process.exit(2);
|
|
14886
|
+
}
|
|
14887
|
+
if (requested.length > 0) {
|
|
14888
|
+
regenerate = new Set(requested);
|
|
14889
|
+
process.stdout.write(`[regenerate] forcing fresh this run: ${[...regenerate].join(", ")} (+ downstream)
|
|
14890
|
+
`);
|
|
14891
|
+
}
|
|
14892
|
+
}
|
|
14790
14893
|
const remoteCache = args["remote-cache"] !== void 0 ? String(args["remote-cache"]) !== "off" : void 0;
|
|
14791
14894
|
const engine = createEngineFromEnv({
|
|
14792
14895
|
cacheDir: args["cache-dir"] ? String(args["cache-dir"]) : void 0,
|
|
@@ -14795,16 +14898,30 @@ var runCommand = defineCommand88({
|
|
|
14795
14898
|
`),
|
|
14796
14899
|
remoteCache
|
|
14797
14900
|
});
|
|
14798
|
-
const
|
|
14901
|
+
const outputsDir = args["outputs-dir"] ? path6.resolve(String(args["outputs-dir"])) : path6.resolve("canvas");
|
|
14902
|
+
const { runId, resumed } = await resolveRunId({
|
|
14903
|
+
explicitRunId: args["run-id"] ? String(args["run-id"]) : void 0,
|
|
14904
|
+
fresh: args.fresh === true,
|
|
14905
|
+
outputsDir,
|
|
14906
|
+
canvasPath: filePath
|
|
14907
|
+
});
|
|
14908
|
+
if (resumed) {
|
|
14909
|
+
process.stdout.write(`[resume] continuing interrupted run ${runId} \u2014 in-flight jobs re-attach, cached nodes skip
|
|
14910
|
+
`);
|
|
14911
|
+
}
|
|
14912
|
+
await markRunInFlight(outputsDir, filePath, runId);
|
|
14799
14913
|
const recordMeta = {
|
|
14800
14914
|
creativeSlug: creativeSlugFromCanvasPath(filePath) ?? void 0,
|
|
14801
|
-
canvasPath:
|
|
14915
|
+
canvasPath: path6.relative(process.cwd(), filePath) || void 0,
|
|
14802
14916
|
canvasSha: sha256Hex(Buffer.from(raw)),
|
|
14803
14917
|
chatId: getEnv().BAKER_CHAT_ID || void 0
|
|
14804
14918
|
};
|
|
14805
14919
|
const record = args.record === false ? null : buildRecorder();
|
|
14806
14920
|
const progress = record ? new RunProgressTracker(runId, recordMeta) : null;
|
|
14807
14921
|
const poster = record ? new RunRecordPoster(record) : null;
|
|
14922
|
+
if (progress && poster) {
|
|
14923
|
+
poster.startKeepalive(() => progress.hasPlan() ? progress.snapshot() : null);
|
|
14924
|
+
}
|
|
14808
14925
|
try {
|
|
14809
14926
|
const policy = args["cache-policy"] ?? "read_write";
|
|
14810
14927
|
const result = await engine.run(parsed, {
|
|
@@ -14815,15 +14932,16 @@ var runCommand = defineCommand88({
|
|
|
14815
14932
|
(args.concurrency ?? args.parallel) !== void 0 ? String(args.concurrency ?? args.parallel) : void 0,
|
|
14816
14933
|
process.env.BAKER_CANVAS_CONCURRENCY
|
|
14817
14934
|
),
|
|
14935
|
+
regenerate,
|
|
14818
14936
|
onProgress: progress && poster ? (event) => {
|
|
14819
14937
|
progress.apply(event);
|
|
14820
14938
|
if (progress.hasPlan()) poster.enqueue(progress.snapshot());
|
|
14821
14939
|
} : void 0
|
|
14822
14940
|
});
|
|
14941
|
+
await clearRunMarker(outputsDir, filePath);
|
|
14823
14942
|
if (poster) await poster.flush(buildRunRecord(result, recordMeta, progress?.planInfo()));
|
|
14824
14943
|
const keepRuns = args["keep-runs"] !== void 0 ? Number(args["keep-runs"]) : void 0;
|
|
14825
14944
|
if (keepRuns !== void 0 && Number.isFinite(keepRuns)) {
|
|
14826
|
-
const outputsDir = args["outputs-dir"] ? path5.resolve(String(args["outputs-dir"])) : path5.resolve("canvas");
|
|
14827
14945
|
await pruneOldRuns(outputsDir, keepRuns, result.run_id, (line) => process.stdout.write(`${line}
|
|
14828
14946
|
`));
|
|
14829
14947
|
}
|
|
@@ -14842,6 +14960,7 @@ var runCommand = defineCommand88({
|
|
|
14842
14960
|
`
|
|
14843
14961
|
);
|
|
14844
14962
|
} catch (e) {
|
|
14963
|
+
await clearRunMarker(outputsDir, filePath);
|
|
14845
14964
|
if (e instanceof ValidationError) {
|
|
14846
14965
|
process.stderr.write(
|
|
14847
14966
|
`${JSON.stringify({ ok: false, error: { code: "validation", issues: e.issues } }, null, 2)}
|
|
@@ -14867,6 +14986,11 @@ var runCommand = defineCommand88({
|
|
|
14867
14986
|
}
|
|
14868
14987
|
}
|
|
14869
14988
|
});
|
|
14989
|
+
function canvasNodeIds(parsed) {
|
|
14990
|
+
const nodes = parsed?.nodes;
|
|
14991
|
+
if (!Array.isArray(nodes)) return [];
|
|
14992
|
+
return nodes.map((node) => node?.id).filter((id) => typeof id === "string");
|
|
14993
|
+
}
|
|
14870
14994
|
function buildRecorder() {
|
|
14871
14995
|
return async (payload) => {
|
|
14872
14996
|
try {
|
|
@@ -14882,8 +15006,8 @@ function buildRecorder() {
|
|
|
14882
15006
|
}
|
|
14883
15007
|
|
|
14884
15008
|
// src/commands/canvas/scaffold-static-ad.ts
|
|
14885
|
-
import { access, mkdir, readFile as
|
|
14886
|
-
import
|
|
15009
|
+
import { access, mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
15010
|
+
import path9 from "path";
|
|
14887
15011
|
import { defineCommand as defineCommand89 } from "citty";
|
|
14888
15012
|
|
|
14889
15013
|
// src/engine/scaffold/staticAd.ts
|
|
@@ -15070,7 +15194,7 @@ function staticAdReport(input, elementsInput, opts) {
|
|
|
15070
15194
|
}
|
|
15071
15195
|
|
|
15072
15196
|
// src/commands/canvas/creative-definition.ts
|
|
15073
|
-
import
|
|
15197
|
+
import path7 from "path";
|
|
15074
15198
|
var PLATFORM_VALUES = ["meta", "google", "linkedin", "tiktok", "youtube", "x", "other"];
|
|
15075
15199
|
var FORMAT_VALUES = ["1:1", "4:5", "9:16", "16:9", "1.91:1"];
|
|
15076
15200
|
function titleFromSlug(slug) {
|
|
@@ -15116,16 +15240,16 @@ function buildCreativeDefinition(input) {
|
|
|
15116
15240
|
}
|
|
15117
15241
|
|
|
15118
15242
|
// src/commands/canvas/scaffold-static-ad-paths.ts
|
|
15119
|
-
import
|
|
15243
|
+
import path8 from "path";
|
|
15120
15244
|
function resolveScaffoldStaticAdPaths(rawFile, out, cwd = process.cwd(), slug) {
|
|
15121
15245
|
const file = rawFile.trim();
|
|
15122
15246
|
const imageIsUrl = /^https?:\/\//i.test(file);
|
|
15123
|
-
const imageSource = imageIsUrl ? file :
|
|
15124
|
-
const outPath = out ?
|
|
15125
|
-
const blueprintPath =
|
|
15126
|
-
const creativeDir = slug ?
|
|
15127
|
-
const definitionPath = creativeDir ?
|
|
15128
|
-
const referencesDir = creativeDir ?
|
|
15247
|
+
const imageSource = imageIsUrl ? file : path8.resolve(cwd, file);
|
|
15248
|
+
const outPath = out ? path8.resolve(cwd, out) : slug ? path8.join(cwd, "src", "creatives", slug, `${slug}.canvas.json`) : imageIsUrl ? path8.join(cwd, "static-ad.canvas.json") : path8.join(path8.dirname(imageSource), "static-ad.canvas.json");
|
|
15249
|
+
const blueprintPath = path8.join(path8.dirname(outPath), "prompt.json");
|
|
15250
|
+
const creativeDir = slug ? path8.dirname(outPath) : null;
|
|
15251
|
+
const definitionPath = creativeDir ? path8.join(creativeDir, "_definition.md") : null;
|
|
15252
|
+
const referencesDir = creativeDir ? path8.join(creativeDir, "references") : null;
|
|
15129
15253
|
return { imageIsUrl, imageSource, outPath, blueprintPath, creativeDir, definitionPath, referencesDir };
|
|
15130
15254
|
}
|
|
15131
15255
|
var SCAFFOLD_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
@@ -15134,6 +15258,79 @@ function isValidScaffoldSlug(slug) {
|
|
|
15134
15258
|
return slug.length <= SCAFFOLD_SLUG_MAX_LENGTH && SCAFFOLD_SLUG_PATTERN.test(slug);
|
|
15135
15259
|
}
|
|
15136
15260
|
|
|
15261
|
+
// src/commands/canvas/definition-graph.ts
|
|
15262
|
+
var MAX_NODES = 300;
|
|
15263
|
+
function walkStrings(value, cb) {
|
|
15264
|
+
if (typeof value === "string") {
|
|
15265
|
+
cb(value);
|
|
15266
|
+
return;
|
|
15267
|
+
}
|
|
15268
|
+
if (Array.isArray(value)) {
|
|
15269
|
+
for (const v of value) walkStrings(v, cb);
|
|
15270
|
+
return;
|
|
15271
|
+
}
|
|
15272
|
+
if (value && typeof value === "object") {
|
|
15273
|
+
for (const v of Object.values(value)) walkStrings(v, cb);
|
|
15274
|
+
}
|
|
15275
|
+
}
|
|
15276
|
+
function canvasToDefinitionGraph(canvas) {
|
|
15277
|
+
const rawNodes = canvas?.nodes;
|
|
15278
|
+
if (!Array.isArray(rawNodes)) return null;
|
|
15279
|
+
const parsed = [];
|
|
15280
|
+
for (const raw of rawNodes) {
|
|
15281
|
+
const id = raw?.id;
|
|
15282
|
+
const type = raw?.type;
|
|
15283
|
+
if (typeof id !== "string" || typeof type !== "string") continue;
|
|
15284
|
+
parsed.push({ id, type, inputs: raw.inputs, params: raw.params });
|
|
15285
|
+
if (parsed.length >= MAX_NODES) break;
|
|
15286
|
+
}
|
|
15287
|
+
if (parsed.length === 0) return null;
|
|
15288
|
+
const ids = new Set(parsed.map((n) => n.id));
|
|
15289
|
+
const nodes = parsed.map(({ id, type, inputs, params }) => {
|
|
15290
|
+
const deps = /* @__PURE__ */ new Set();
|
|
15291
|
+
const collect = (s) => {
|
|
15292
|
+
if (!s.startsWith(REF_PREFIX)) return;
|
|
15293
|
+
const expr = parseRefExpr(s);
|
|
15294
|
+
if (expr && expr.nodeId !== id && ids.has(expr.nodeId)) deps.add(expr.nodeId);
|
|
15295
|
+
};
|
|
15296
|
+
walkStrings(inputs, collect);
|
|
15297
|
+
walkStrings(params, collect);
|
|
15298
|
+
return deps.size > 0 ? { id, type, deps: [...deps] } : { id, type };
|
|
15299
|
+
});
|
|
15300
|
+
const rawOutput = canvas?.output;
|
|
15301
|
+
const outNode = rawOutput?.node;
|
|
15302
|
+
const outSlot = rawOutput?.output;
|
|
15303
|
+
const output = typeof outNode === "string" && typeof outSlot === "string" ? { node: outNode, output: outSlot } : void 0;
|
|
15304
|
+
return { nodes, output };
|
|
15305
|
+
}
|
|
15306
|
+
|
|
15307
|
+
// src/commands/canvas/sync-definition.ts
|
|
15308
|
+
async function syncCreativeDefinitionBestEffort(input) {
|
|
15309
|
+
const chatId = process.env.BAKER_CHAT_ID;
|
|
15310
|
+
if (!chatId) return;
|
|
15311
|
+
const graph = canvasToDefinitionGraph(input.canvas);
|
|
15312
|
+
if (!graph || graph.nodes.length === 0) return;
|
|
15313
|
+
try {
|
|
15314
|
+
const creds = requireCredentialsFromEnv();
|
|
15315
|
+
const client = new BackendClient({ baseUrl: creds.url, apiKey: creds.apiKey });
|
|
15316
|
+
await client.syncCreativeDefinition({
|
|
15317
|
+
slug: input.slug,
|
|
15318
|
+
title: input.title,
|
|
15319
|
+
platform: input.platform,
|
|
15320
|
+
formats: input.formats,
|
|
15321
|
+
sourceReferenceUrl: input.sourceReferenceUrl,
|
|
15322
|
+
graph,
|
|
15323
|
+
chatId
|
|
15324
|
+
});
|
|
15325
|
+
process.stdout.write(`[definition] synced workflow graph (${graph.nodes.length} nodes) \u2014 view it in the dashboard
|
|
15326
|
+
`);
|
|
15327
|
+
} catch (e) {
|
|
15328
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
15329
|
+
process.stderr.write(`[warn] workflow graph not synced (${msg})
|
|
15330
|
+
`);
|
|
15331
|
+
}
|
|
15332
|
+
}
|
|
15333
|
+
|
|
15137
15334
|
// src/commands/canvas/scaffold-static-ad.ts
|
|
15138
15335
|
async function fileExists(target) {
|
|
15139
15336
|
try {
|
|
@@ -15150,19 +15347,19 @@ var MODEL_SAFE_EXT_BY_MIME = {
|
|
|
15150
15347
|
"image/webp": ".webp"
|
|
15151
15348
|
};
|
|
15152
15349
|
async function copySourceIntoReferences(source, isUrl, referencesDir) {
|
|
15153
|
-
await
|
|
15350
|
+
await mkdir2(referencesDir, { recursive: true });
|
|
15154
15351
|
let bytes;
|
|
15155
15352
|
if (isUrl) {
|
|
15156
15353
|
const res = await fetch(source);
|
|
15157
15354
|
if (!res.ok) throw new Error(`failed to download source image (${res.status})`);
|
|
15158
15355
|
bytes = Buffer.from(await res.arrayBuffer());
|
|
15159
15356
|
} else {
|
|
15160
|
-
bytes = await
|
|
15357
|
+
bytes = await readFile4(source);
|
|
15161
15358
|
}
|
|
15162
15359
|
const safe = await toModelSafeImage(bytes);
|
|
15163
15360
|
const relPath = referenceRelativePath("image", MODEL_SAFE_EXT_BY_MIME[safe.mime] ?? ".png");
|
|
15164
|
-
const dest =
|
|
15165
|
-
await
|
|
15361
|
+
const dest = path9.join(referencesDir, path9.basename(relPath));
|
|
15362
|
+
await writeFile2(dest, safe.bytes);
|
|
15166
15363
|
return relPath;
|
|
15167
15364
|
}
|
|
15168
15365
|
function resolveModel(kind, preferred) {
|
|
@@ -15207,7 +15404,7 @@ DROP background extras, decorative props, generic scenery, and anything small or
|
|
|
15207
15404
|
For each kept element return: { "type": one of logo|product|person|animal|badge, "label": a short UPPER_SNAKE_CASE name (e.g. LOGO, PRODUCT, HERO_DOG, TRUSTPILOT), "description": a concrete reusable description to source/shoot the real asset (include the exact expression for a living subject, and its castable attributes \u2014 breed/species for an animal, apparent age band, apparent origin/ethnicity, and wardrobe/setting for a person \u2014 so it can be recast to fit OUR audience/market), "expression": the facial expression for a living subject or null, "reason": why it is identity-critical, "locator": the blueprint entry this element came from as { "collection": one of "subjects" | "people" | "brands_logos", "index": its 0-based position in that array } (people -> people; logos/badges -> brands_logos; products/animals/objects -> subjects). Output ONLY the JSON object.`;
|
|
15208
15405
|
async function loadAssetText(ref, label) {
|
|
15209
15406
|
const r = ref;
|
|
15210
|
-
if (typeof r?.path === "string") return
|
|
15407
|
+
if (typeof r?.path === "string") return readFile4(r.path, "utf8");
|
|
15211
15408
|
if (typeof r?.url === "string") {
|
|
15212
15409
|
const res = await fetch(r.url);
|
|
15213
15410
|
if (!res.ok) throw new Error(`failed to fetch ${label} (${res.status})`);
|
|
@@ -15356,7 +15553,7 @@ var scaffoldStaticAdCommand = defineCommand89({
|
|
|
15356
15553
|
process.cwd(),
|
|
15357
15554
|
slug
|
|
15358
15555
|
);
|
|
15359
|
-
await
|
|
15556
|
+
await mkdir2(path9.dirname(outPath), { recursive: true });
|
|
15360
15557
|
const { describeModel, selectModel, layoutModel, genModel } = resolveModels(args);
|
|
15361
15558
|
const describeCanvas = buildDescribeCanvas(
|
|
15362
15559
|
imageSource,
|
|
@@ -15371,7 +15568,7 @@ var scaffoldStaticAdCommand = defineCommand89({
|
|
|
15371
15568
|
if (layout && annotated && typeof annotated === "object") {
|
|
15372
15569
|
annotated.layout = layout;
|
|
15373
15570
|
}
|
|
15374
|
-
await
|
|
15571
|
+
await writeFile2(blueprintPath, `${JSON.stringify(annotated, null, 2)}
|
|
15375
15572
|
`, "utf8");
|
|
15376
15573
|
let canvasImagePath = imageSource;
|
|
15377
15574
|
let canvasImageIsUrl = imageIsUrl;
|
|
@@ -15407,10 +15604,10 @@ var scaffoldStaticAdCommand = defineCommand89({
|
|
|
15407
15604
|
);
|
|
15408
15605
|
process.exit(2);
|
|
15409
15606
|
}
|
|
15410
|
-
await
|
|
15607
|
+
await writeFile2(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
15411
15608
|
`, "utf8");
|
|
15412
15609
|
if (definitionPath && !await fileExists(definitionPath)) {
|
|
15413
|
-
await
|
|
15610
|
+
await writeFile2(
|
|
15414
15611
|
definitionPath,
|
|
15415
15612
|
buildCreativeDefinition({
|
|
15416
15613
|
title: args.title ? String(args.title) : titleFromSlug(slug ?? ""),
|
|
@@ -15426,6 +15623,16 @@ var scaffoldStaticAdCommand = defineCommand89({
|
|
|
15426
15623
|
"utf8"
|
|
15427
15624
|
);
|
|
15428
15625
|
}
|
|
15626
|
+
if (slug) {
|
|
15627
|
+
await syncCreativeDefinitionBestEffort({
|
|
15628
|
+
slug,
|
|
15629
|
+
title: args.title ? String(args.title) : titleFromSlug(slug),
|
|
15630
|
+
platform: resolvePlatform(args.platform ? String(args.platform) : void 0),
|
|
15631
|
+
formats: resolveFormats(args.aspect ? String(args.aspect) : report.aspect_ratio),
|
|
15632
|
+
sourceReferenceUrl: imageIsUrl ? imageSource : void 0,
|
|
15633
|
+
canvas
|
|
15634
|
+
});
|
|
15635
|
+
}
|
|
15429
15636
|
process.stdout.write(
|
|
15430
15637
|
`${JSON.stringify(
|
|
15431
15638
|
{
|
|
@@ -15444,10 +15651,10 @@ var scaffoldStaticAdCommand = defineCommand89({
|
|
|
15444
15651
|
run_estimated_credits: validation.estimatedCredits
|
|
15445
15652
|
},
|
|
15446
15653
|
checklist: {
|
|
15447
|
-
edit_prompt: `Edit ${
|
|
15654
|
+
edit_prompt: `Edit ${path9.basename(blueprintPath)} \u2014 it is the blueprint generated from your image; rewrite it into the ad you want (palette, copy, claims, subjects). It feeds the generator directly.`,
|
|
15448
15655
|
assets_to_supply: report.elements,
|
|
15449
15656
|
font_slot: report.includes_font ? "Drop a brand font at the [TODO] brandfont path, or delete the brandfont + type_ref nodes to skip it." : "skipped (--skip-font)",
|
|
15450
|
-
note: "
|
|
15657
|
+
note: "Populate as you go: for each [TODO] ingest slot, source its real asset and wire it into the slot right away \u2014 one at a time, not all sourced first then reconciled at the end. When every slot is filled, `baker canvas validate` then `baker canvas run`. Running generates a billed image \u2014 it is not free."
|
|
15451
15658
|
}
|
|
15452
15659
|
},
|
|
15453
15660
|
null,
|
|
@@ -15459,13 +15666,14 @@ var scaffoldStaticAdCommand = defineCommand89({
|
|
|
15459
15666
|
});
|
|
15460
15667
|
|
|
15461
15668
|
// src/commands/canvas/scaffold-video.ts
|
|
15462
|
-
import { cp, mkdir as
|
|
15463
|
-
import
|
|
15669
|
+
import { access as access2, cp, mkdir as mkdir3, readFile as readFile7, writeFile as writeFile3 } from "fs/promises";
|
|
15670
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
15671
|
+
import path12 from "path";
|
|
15464
15672
|
import { defineCommand as defineCommand90 } from "citty";
|
|
15465
15673
|
|
|
15466
15674
|
// src/engine/nodes/local/lib/sceneDetect.ts
|
|
15467
15675
|
import { execFile as execFile2 } from "child_process";
|
|
15468
|
-
import { mkdtemp, readdir as readdir2, readFile as
|
|
15676
|
+
import { mkdtemp, readdir as readdir2, readFile as readFile5, rm as rm3 } from "fs/promises";
|
|
15469
15677
|
import { tmpdir } from "os";
|
|
15470
15678
|
import { join as join2 } from "path";
|
|
15471
15679
|
import { promisify as promisify2 } from "util";
|
|
@@ -15541,9 +15749,9 @@ async function runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs)
|
|
|
15541
15749
|
);
|
|
15542
15750
|
const csvName = (await readdir2(outDir)).find((f) => f.toLowerCase().endsWith(".csv"));
|
|
15543
15751
|
if (!csvName) return [];
|
|
15544
|
-
return parsePySceneDetectCsvCuts(await
|
|
15752
|
+
return parsePySceneDetectCsvCuts(await readFile5(join2(outDir, csvName), "utf-8"));
|
|
15545
15753
|
} finally {
|
|
15546
|
-
await
|
|
15754
|
+
await rm3(outDir, { recursive: true, force: true });
|
|
15547
15755
|
}
|
|
15548
15756
|
}
|
|
15549
15757
|
async function detectSceneCutsPySceneDetect(filePath, opts = {}) {
|
|
@@ -16168,6 +16376,13 @@ function slimBlueprintForSelection(blueprintInput) {
|
|
|
16168
16376
|
}
|
|
16169
16377
|
return out;
|
|
16170
16378
|
}
|
|
16379
|
+
function slimBlueprintForFrameStyle(blueprintInput) {
|
|
16380
|
+
if (!blueprintInput || typeof blueprintInput !== "object" || Array.isArray(blueprintInput)) return blueprintInput;
|
|
16381
|
+
const bp = blueprintInput;
|
|
16382
|
+
const out = {};
|
|
16383
|
+
for (const k of ["version", "source", "global", "reference_elements"]) if (k in bp) out[k] = bp[k];
|
|
16384
|
+
return out;
|
|
16385
|
+
}
|
|
16171
16386
|
function roleForType2(type) {
|
|
16172
16387
|
switch (type.toLowerCase()) {
|
|
16173
16388
|
case "logo":
|
|
@@ -16425,9 +16640,10 @@ function buildFrameRef(edge, url, framePrompt, present, ctx, nodes) {
|
|
|
16425
16640
|
id: genId,
|
|
16426
16641
|
type: "image_generate",
|
|
16427
16642
|
// `params.prompt` is this frame's authoritative, edit-per-frame description.
|
|
16428
|
-
// `target_blueprint` is the shared ad spec (cast identity, palette, brand,
|
|
16429
|
-
// the frame must stay consistent with
|
|
16430
|
-
|
|
16643
|
+
// `target_blueprint` is the SLIM shared ad spec (global cast identity, palette, brand,
|
|
16644
|
+
// type — no per-scene content) the frame must stay consistent with; editing one frame
|
|
16645
|
+
// never touches another, and no image inlines the whole film to render one frame.
|
|
16646
|
+
inputs: { target_blueprint: "$ref:prompt_style.asset", ...reference.length > 0 ? { reference } : {} },
|
|
16431
16647
|
params: genParams
|
|
16432
16648
|
});
|
|
16433
16649
|
return `$ref:${genId}.images#0`;
|
|
@@ -16949,18 +17165,24 @@ function emitFlashHold(i, scene, slots, ctx, lengths, out, outAr, nodes, clips)
|
|
|
16949
17165
|
});
|
|
16950
17166
|
clips.push({ ref: `$ref:s${i}_clip.video`, scene_s: lengths.dur, out });
|
|
16951
17167
|
}
|
|
16952
|
-
function emitScreenScene(i, scene, lengths, out, outAr, nodes, clips) {
|
|
16953
|
-
const
|
|
16954
|
-
const
|
|
16955
|
-
|
|
16956
|
-
|
|
16957
|
-
|
|
16958
|
-
|
|
16959
|
-
|
|
16960
|
-
|
|
16961
|
-
|
|
16962
|
-
|
|
16963
|
-
|
|
17168
|
+
function emitScreenScene(i, scene, lengths, out, outAr, surfaceIngests, nodes, clips) {
|
|
17169
|
+
const regions = (scene.composition?.regions ?? []).filter((r) => Boolean(r) && typeof r === "object");
|
|
17170
|
+
const surfaceId = regions.find((r) => r.surface_id)?.surface_id;
|
|
17171
|
+
let refId = surfaceId ? surfaceIngests.get(surfaceId) : void 0;
|
|
17172
|
+
if (!refId) {
|
|
17173
|
+
const label = commentSafe((scene.summary || scene.start_frame_prompt || "the app screen").slice(0, 120));
|
|
17174
|
+
refId = `s${i}_screen_ref`;
|
|
17175
|
+
nodes.push({
|
|
17176
|
+
id: refId,
|
|
17177
|
+
type: "ingest",
|
|
17178
|
+
params: {
|
|
17179
|
+
source: "path",
|
|
17180
|
+
path: `[TODO: supply the REAL screen for "${label}" \u2014 NEVER AI-generate a UI. Capture a clean, text-free screenshot with \`baker images screenshot https://<brand-domain>/<path>\` (image-library skill); spoken/overlay text rides the overlay layer, not the screenshot]`,
|
|
17181
|
+
expect: "image"
|
|
17182
|
+
}
|
|
17183
|
+
});
|
|
17184
|
+
if (surfaceId) surfaceIngests.set(surfaceId, refId);
|
|
17185
|
+
}
|
|
16964
17186
|
nodes.push({
|
|
16965
17187
|
id: `s${i}_clip`,
|
|
16966
17188
|
type: "ffmpeg",
|
|
@@ -17207,6 +17429,7 @@ function makePresenterPresent(slots, canonical, opts = {}) {
|
|
|
17207
17429
|
var PAUSE_GAP_S = 0.6;
|
|
17208
17430
|
var SEEDANCE_SAFE_MAX_S = SEEDANCE_DURATIONS.find((d) => d >= 10) ?? 10;
|
|
17209
17431
|
var PHRASE_MAX_S = SEEDANCE_SAFE_MAX_S;
|
|
17432
|
+
var SEEDANCE_MAX_WORDS_PER_TAKE = 16;
|
|
17210
17433
|
var JOIN_DEDUP_MAX_WORDS = 4;
|
|
17211
17434
|
function joinKey(word) {
|
|
17212
17435
|
return word.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
|
|
@@ -17310,7 +17533,11 @@ function buildPhrases(blueprint, canonical, compositeScenes, presenterPresent, p
|
|
|
17310
17533
|
const breakRun = !cur || cur.speaker !== ln.speaker || ln.start - cur.end > PAUSE_GAP_S || // Cap by SCENE COVERAGE span, not line end — a presenter run whose sliced scenes span
|
|
17311
17534
|
// more than one Seedance clip splits into the next take here (at this scene's
|
|
17312
17535
|
// boundary, never mid-scene), so no segment ever reads past the generated clip.
|
|
17313
|
-
Math.max(cur.coverEnd, lineCover) - Math.min(cur.clipStart, lineClipStart) > PHRASE_MAX_S
|
|
17536
|
+
Math.max(cur.coverEnd, lineCover) - Math.min(cur.clipStart, lineClipStart) > PHRASE_MAX_S || // Cap by SPOKEN WORDS too: a dense read that fits the time ceiling can still cram a
|
|
17537
|
+
// monologue Seedance can't lip-sync cleanly. Break before this line pushes the run
|
|
17538
|
+
// past the word cap (a fresh run always accepts its own first line, so one long line
|
|
17539
|
+
// is never blocked — it just can't be sub-split without a mid-word cut).
|
|
17540
|
+
cur.words + wordCount(ln.text) > SEEDANCE_MAX_WORDS_PER_TAKE;
|
|
17314
17541
|
if (breakRun || !cur) {
|
|
17315
17542
|
flush();
|
|
17316
17543
|
cur = {
|
|
@@ -17321,6 +17548,7 @@ function buildPhrases(blueprint, canonical, compositeScenes, presenterPresent, p
|
|
|
17321
17548
|
coverEnd: lineCover,
|
|
17322
17549
|
clipStart: lineClipStart,
|
|
17323
17550
|
texts: [ln.text],
|
|
17551
|
+
words: wordCount(ln.text),
|
|
17324
17552
|
shown: /* @__PURE__ */ new Set()
|
|
17325
17553
|
};
|
|
17326
17554
|
} else {
|
|
@@ -17328,6 +17556,7 @@ function buildPhrases(blueprint, canonical, compositeScenes, presenterPresent, p
|
|
|
17328
17556
|
cur.end = Math.max(cur.end, ln.end);
|
|
17329
17557
|
cur.coverEnd = Math.max(cur.coverEnd, lineCover);
|
|
17330
17558
|
cur.clipStart = Math.min(cur.clipStart, lineClipStart);
|
|
17559
|
+
cur.words += wordCount(ln.text);
|
|
17331
17560
|
}
|
|
17332
17561
|
if (ln.shown) cur.shown.add(ln.sceneIndex);
|
|
17333
17562
|
}
|
|
@@ -17435,19 +17664,12 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
17435
17664
|
inputs: { clip: clipRef },
|
|
17436
17665
|
params: { args: audioExtractArgs(extractLen, speechOffset), outputs: { audio: { kind: "audio", ext: "mp3" } } }
|
|
17437
17666
|
});
|
|
17438
|
-
const convId =
|
|
17439
|
-
|
|
17440
|
-
|
|
17441
|
-
|
|
17442
|
-
inputs: { audio: `$ref:s${anchor}_voextract.audio`, voice_ref: `$ref:${voiceNode}.voice_id` },
|
|
17443
|
-
params: { model: FIXED_VOICE_CONVERT_MODEL, voice: "{{voice_ref}}" }
|
|
17444
|
-
});
|
|
17445
|
-
out.voTracks.push({
|
|
17446
|
-
slot: convId,
|
|
17447
|
-
ref: `$ref:${convId}.audio`,
|
|
17667
|
+
const convId = `${voiceNode}_conv`;
|
|
17668
|
+
out.nativeSegments.push({
|
|
17669
|
+
voiceNode,
|
|
17670
|
+
ref: `$ref:s${anchor}_voextract.audio`,
|
|
17448
17671
|
start_s: phrase.start_s,
|
|
17449
|
-
end_s: phrase.
|
|
17450
|
-
kind: "vo"
|
|
17672
|
+
end_s: phrase.start_s + extractLen
|
|
17451
17673
|
});
|
|
17452
17674
|
out.voSegments.push({
|
|
17453
17675
|
slot: convId,
|
|
@@ -17463,18 +17685,32 @@ function emitPhraseClip(phrase, voiceNode, env, nodes, out) {
|
|
|
17463
17685
|
est_speech_s: Math.round(estSpeechWindowS(phrase.text, phrase.start_s, phrase.end_s) * 100) / 100,
|
|
17464
17686
|
speech_words: wordCount(phrase.text)
|
|
17465
17687
|
});
|
|
17466
|
-
|
|
17467
|
-
|
|
17468
|
-
|
|
17469
|
-
|
|
17470
|
-
|
|
17688
|
+
registerMergedPhraseSlices(phrase, clipRef, clipStart, genDur, env, out);
|
|
17689
|
+
}
|
|
17690
|
+
function registerMergedPhraseSlices(phrase, clipRef, clipStart, genDur, env, out) {
|
|
17691
|
+
const shown = [...phrase.shownScenes].sort((a, b) => a - b);
|
|
17692
|
+
let r = 0;
|
|
17693
|
+
while (r < shown.length) {
|
|
17694
|
+
const first = shown[r];
|
|
17695
|
+
let last = first;
|
|
17696
|
+
while (r + 1 < shown.length && shown[r + 1] === last + 1) last = shown[++r];
|
|
17697
|
+
r++;
|
|
17698
|
+
const firstSc = env.blueprint.scenes[first];
|
|
17699
|
+
if (!firstSc) continue;
|
|
17700
|
+
const firstStart = firstSc.start_s ?? clipStart;
|
|
17701
|
+
const rawOffset = firstStart - clipStart;
|
|
17702
|
+
const runEnd = env.blueprint.scenes[last]?.end_s ?? firstStart + sceneDurationS(firstSc);
|
|
17703
|
+
out.sceneSlice.set(first, {
|
|
17471
17704
|
clipRef,
|
|
17472
|
-
// Snap a sub-frame offset (line-start vs scene-start drift) to 0 so a
|
|
17473
|
-
//
|
|
17705
|
+
// Snap a sub-frame offset (line-start vs scene-start drift) to 0 so a run that tiles
|
|
17706
|
+
// the clip hits the whole-clip fast path instead of a needless re-encode + tiny shift.
|
|
17474
17707
|
offset: rawOffset < 0.05 ? 0 : rawOffset,
|
|
17475
|
-
len:
|
|
17708
|
+
len: Math.max(0.5, runEnd - firstStart),
|
|
17476
17709
|
clipDur: genDur
|
|
17477
17710
|
});
|
|
17711
|
+
for (let s = first + 1; s <= last; s++) {
|
|
17712
|
+
out.sceneSlice.set(s, { clipRef, offset: 0, len: 0, clipDur: genDur, skip: true });
|
|
17713
|
+
}
|
|
17478
17714
|
}
|
|
17479
17715
|
}
|
|
17480
17716
|
function emitPhraseTts(phrase, voiceNode, idx, used, nodes, out, languageCode) {
|
|
@@ -17617,7 +17853,7 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
17617
17853
|
return void 0;
|
|
17618
17854
|
}
|
|
17619
17855
|
if (!env.reuse && sceneIsFullScreenUi(scene, present)) {
|
|
17620
|
-
emitScreenScene(i, scene, lengths, lengths.out, env.outAr, nodes, out.clips);
|
|
17856
|
+
emitScreenScene(i, scene, lengths, lengths.out, env.outAr, env.surfaceIngests, nodes, out.clips);
|
|
17621
17857
|
return void 0;
|
|
17622
17858
|
}
|
|
17623
17859
|
const isCta = scene.narrative_role?.trim() === "cta" || isLast;
|
|
@@ -17680,6 +17916,22 @@ function emitBrollScene(scene, i, isLast, env, nodes, out, prevEndFrame) {
|
|
|
17680
17916
|
out.clips.push(clip);
|
|
17681
17917
|
return last;
|
|
17682
17918
|
}
|
|
17919
|
+
function emitPresenterSliceClip(i, slice, env, nodes, out) {
|
|
17920
|
+
if (slice.skip) return;
|
|
17921
|
+
const normDims = env.genAr !== env.outAr ? canvasDims(env.outAr) : void 0;
|
|
17922
|
+
const whole = slice.offset === 0 && Math.abs(slice.len - slice.clipDur) <= 0.05 && !normDims;
|
|
17923
|
+
if (whole) {
|
|
17924
|
+
out.clips.push({ ref: slice.clipRef, scene_s: slice.len, out: null });
|
|
17925
|
+
return;
|
|
17926
|
+
}
|
|
17927
|
+
nodes.push({
|
|
17928
|
+
id: `s${i}_seg`,
|
|
17929
|
+
type: "ffmpeg",
|
|
17930
|
+
inputs: { clip: slice.clipRef },
|
|
17931
|
+
params: { args: trimArgs(slice.len, slice.offset, normDims), outputs: { video: { kind: "video", ext: "mp4" } } }
|
|
17932
|
+
});
|
|
17933
|
+
out.clips.push({ ref: `$ref:s${i}_seg.video`, scene_s: slice.len, out: null });
|
|
17934
|
+
}
|
|
17683
17935
|
function buildTimeline(blueprint, slots, opts, nodes) {
|
|
17684
17936
|
const reuse = opts.frames === "reuse";
|
|
17685
17937
|
const uiRouted = uiRoutedSceneSet(blueprint);
|
|
@@ -17752,22 +18004,7 @@ function buildTimeline(blueprint, slots, opts, nodes) {
|
|
|
17752
18004
|
}
|
|
17753
18005
|
const slice = out.sceneSlice.get(i);
|
|
17754
18006
|
if (slice) {
|
|
17755
|
-
|
|
17756
|
-
const whole = slice.offset === 0 && Math.abs(slice.len - slice.clipDur) <= 0.05 && !normDims;
|
|
17757
|
-
if (whole) {
|
|
17758
|
-
out.clips.push({ ref: slice.clipRef, scene_s: slice.len, out: null });
|
|
17759
|
-
} else {
|
|
17760
|
-
nodes.push({
|
|
17761
|
-
id: `s${i}_seg`,
|
|
17762
|
-
type: "ffmpeg",
|
|
17763
|
-
inputs: { clip: slice.clipRef },
|
|
17764
|
-
params: {
|
|
17765
|
-
args: trimArgs(slice.len, slice.offset, normDims),
|
|
17766
|
-
outputs: { video: { kind: "video", ext: "mp4" } }
|
|
17767
|
-
}
|
|
17768
|
-
});
|
|
17769
|
-
out.clips.push({ ref: `$ref:s${i}_seg.video`, scene_s: slice.len, out: null });
|
|
17770
|
-
}
|
|
18007
|
+
emitPresenterSliceClip(i, slice, env, nodes, out);
|
|
17771
18008
|
prevEndFrame = void 0;
|
|
17772
18009
|
return;
|
|
17773
18010
|
}
|
|
@@ -18108,16 +18345,24 @@ function buildSpine(clips, nodes) {
|
|
|
18108
18345
|
});
|
|
18109
18346
|
return "$ref:spine.video";
|
|
18110
18347
|
}
|
|
18111
|
-
function
|
|
18112
|
-
const blueprint = VideoBlueprint.parse(input);
|
|
18113
|
-
injectHookPhysicality(blueprint);
|
|
18114
|
-
const elements = RecurringElements.parse(elementsInput);
|
|
18115
|
-
const nodes = [];
|
|
18348
|
+
function emitBlueprintIngests(opts, nodes) {
|
|
18116
18349
|
nodes.push({
|
|
18117
18350
|
id: "prompt",
|
|
18118
18351
|
type: "ingest",
|
|
18119
18352
|
params: { source: "path", path: opts.blueprintPath ?? "./prompt.json", expect: "json" }
|
|
18120
18353
|
});
|
|
18354
|
+
nodes.push({
|
|
18355
|
+
id: "prompt_style",
|
|
18356
|
+
type: "ingest",
|
|
18357
|
+
params: { source: "path", path: opts.blueprintStylePath ?? "./prompt.style.json", expect: "json" }
|
|
18358
|
+
});
|
|
18359
|
+
}
|
|
18360
|
+
function scaffoldVideoCanvas(input, elementsInput, opts) {
|
|
18361
|
+
const blueprint = VideoBlueprint.parse(input);
|
|
18362
|
+
injectHookPhysicality(blueprint);
|
|
18363
|
+
const elements = RecurringElements.parse(elementsInput);
|
|
18364
|
+
const nodes = [];
|
|
18365
|
+
emitBlueprintIngests(opts, nodes);
|
|
18121
18366
|
const slots = buildElementSlots(elements);
|
|
18122
18367
|
extendPresenceByPromptMentions(slots, blueprint);
|
|
18123
18368
|
slots.forEach((slot, i) => {
|
|
@@ -18568,23 +18813,23 @@ function videoReport(input, elementsInput) {
|
|
|
18568
18813
|
|
|
18569
18814
|
// src/commands/canvas/composition-path.ts
|
|
18570
18815
|
import { existsSync as existsSync3 } from "fs";
|
|
18571
|
-
import
|
|
18816
|
+
import path10 from "path";
|
|
18572
18817
|
function resolveShippedCanvasDir(name, startDir, exists = existsSync3, maxDepth = 8) {
|
|
18573
|
-
const rel =
|
|
18818
|
+
const rel = path10.join("canvas", name);
|
|
18574
18819
|
let dir = startDir;
|
|
18575
18820
|
for (let i = 0; i < maxDepth; i++) {
|
|
18576
|
-
const candidate =
|
|
18577
|
-
if (exists(
|
|
18578
|
-
const parent =
|
|
18821
|
+
const candidate = path10.join(dir, rel);
|
|
18822
|
+
if (exists(path10.join(candidate, "meta.json"))) return candidate;
|
|
18823
|
+
const parent = path10.dirname(dir);
|
|
18579
18824
|
if (parent === dir) break;
|
|
18580
18825
|
dir = parent;
|
|
18581
18826
|
}
|
|
18582
|
-
return
|
|
18827
|
+
return path10.resolve(startDir, "../../../", rel);
|
|
18583
18828
|
}
|
|
18584
18829
|
|
|
18585
18830
|
// src/commands/canvas/gitignore.ts
|
|
18586
|
-
import { appendFile, readFile as
|
|
18587
|
-
import
|
|
18831
|
+
import { appendFile, readFile as readFile6 } from "fs/promises";
|
|
18832
|
+
import path11 from "path";
|
|
18588
18833
|
function missingGitignoreEntries(existing, entries) {
|
|
18589
18834
|
const present = new Set(
|
|
18590
18835
|
existing.split("\n").map((l) => l.trim().replace(/\/+$/, "")).filter((l) => l.length > 0 && !l.startsWith("#"))
|
|
@@ -18592,10 +18837,10 @@ function missingGitignoreEntries(existing, entries) {
|
|
|
18592
18837
|
return entries.filter((e) => !present.has(e.trim().replace(/\/+$/, "")));
|
|
18593
18838
|
}
|
|
18594
18839
|
async function ensureGitignore(dir, entries) {
|
|
18595
|
-
const file =
|
|
18840
|
+
const file = path11.join(dir, ".gitignore");
|
|
18596
18841
|
let existing;
|
|
18597
18842
|
try {
|
|
18598
|
-
existing = await
|
|
18843
|
+
existing = await readFile6(file, "utf8");
|
|
18599
18844
|
} catch {
|
|
18600
18845
|
return;
|
|
18601
18846
|
}
|
|
@@ -18634,7 +18879,7 @@ ONE PERSON, MULTIPLE LOOKS: if a single individual plays MULTIPLE personas or wa
|
|
|
18634
18879
|
For each kept element return: { "type": one of person|animal|product|logo|badge|location, "label": a short UPPER_SNAKE_CASE name (e.g. HERO, CREATOR_SKEPTIC, INSURANCE_CARD, LOGO), "description": a concrete reusable description to source/shoot the real asset \u2014 for a person/animal give a NEUTRAL castable role (e.g. "hero pet-owner, woman in her 30s" or "a small beagle"), NOT the original individual's literal face/identity: we RECAST with a FRESH person/animal, so never tell the agent to reuse the original. "expression": a living subject's typical expression or null, "cast_id": the global.cast id if it maps to one else null, "same_as": the label of another element this is the SAME individual as (different wardrobe/persona) else null, "scenes": the 0-based indices of ONLY the scenes where the element is ACTUALLY VISIBLE ON SCREEN \u2014 judged from that scene's start_frame_prompt / end_frame_prompt subjects and its action_detail, NOT from who is merely speaking. A narrator heard over b-roll is NOT present in that b-roll scene; a dog-running cutaway does NOT contain the couch creator just because she talks across it. Do NOT pad the list \u2014 an element wrongly listed in a scene makes the reproduction render the wrong subject there (e.g. the creator appearing in a pure-dog b-roll). When in doubt, leave a scene OUT. Output ONLY the JSON object.`;
|
|
18635
18880
|
async function loadAssetText2(ref, label) {
|
|
18636
18881
|
const r = ref;
|
|
18637
|
-
if (typeof r?.path === "string") return
|
|
18882
|
+
if (typeof r?.path === "string") return readFile7(r.path, "utf8");
|
|
18638
18883
|
if (typeof r?.url === "string") {
|
|
18639
18884
|
const res = await fetch(r.url);
|
|
18640
18885
|
if (!res.ok) throw new Error(`failed to fetch ${label} (${res.status})`);
|
|
@@ -18653,7 +18898,7 @@ async function loadTranscriptBestEffort(ref) {
|
|
|
18653
18898
|
async function stageCaptions(outDir, transcript) {
|
|
18654
18899
|
const text = transcript?.trim();
|
|
18655
18900
|
if (!text || text === "[]") return {};
|
|
18656
|
-
const compositionPath =
|
|
18901
|
+
const compositionPath = path12.join(outDir, "tiktok-captions-composition");
|
|
18657
18902
|
await cp(SHIPPED_CAPTIONS_DIR, compositionPath, { recursive: true });
|
|
18658
18903
|
return { compositionPath };
|
|
18659
18904
|
}
|
|
@@ -18671,12 +18916,12 @@ function patchCompositionHtml(html, dims) {
|
|
|
18671
18916
|
return html.replace(/(<meta\s+name="viewport"\s+content="width=)\d+(,\s*height=)\d+(")/i, `$1${dims.w}$2${dims.h}$3`).replace(/(width:\s*)\d+(px;\s*height:\s*)\d+(px;)/i, `$1${dims.w}$2${dims.h}$3`).replace(/(data-width=")\d+(")/i, `$1${dims.w}$2`).replace(/(data-height=")\d+(")/i, `$1${dims.h}$2`);
|
|
18672
18917
|
}
|
|
18673
18918
|
async function stampCompositionDims(compositionDir, dims) {
|
|
18674
|
-
const metaPath =
|
|
18675
|
-
const rawMeta = await
|
|
18676
|
-
await
|
|
18677
|
-
const htmlPath =
|
|
18678
|
-
const rawHtml = await
|
|
18679
|
-
await
|
|
18919
|
+
const metaPath = path12.join(compositionDir, "meta.json");
|
|
18920
|
+
const rawMeta = await readFile7(metaPath, "utf8");
|
|
18921
|
+
await writeFile3(metaPath, patchCompositionMeta(rawMeta, dims), "utf8");
|
|
18922
|
+
const htmlPath = path12.join(compositionDir, "index.html");
|
|
18923
|
+
const rawHtml = await readFile7(htmlPath, "utf8");
|
|
18924
|
+
await writeFile3(htmlPath, patchCompositionHtml(rawHtml, dims), "utf8");
|
|
18680
18925
|
}
|
|
18681
18926
|
function parseElements2(raw) {
|
|
18682
18927
|
const parsed = JSON.parse(raw);
|
|
@@ -18716,6 +18961,56 @@ function fail2(code, message) {
|
|
|
18716
18961
|
`);
|
|
18717
18962
|
process.exit(2);
|
|
18718
18963
|
}
|
|
18964
|
+
var VIDEO_EXT_BY_MIME = {
|
|
18965
|
+
"video/mp4": ".mp4",
|
|
18966
|
+
"video/quicktime": ".mov",
|
|
18967
|
+
"video/webm": ".webm",
|
|
18968
|
+
"video/x-matroska": ".mkv"
|
|
18969
|
+
};
|
|
18970
|
+
function referenceVideoExt(url, contentType) {
|
|
18971
|
+
const fromPath = path12.extname(new URL(url).pathname).toLowerCase();
|
|
18972
|
+
if (fromPath && fromPath.length <= 5) return fromPath;
|
|
18973
|
+
const mime = (contentType ?? "").split(";")[0]?.trim().toLowerCase();
|
|
18974
|
+
return mime && VIDEO_EXT_BY_MIME[mime] || ".mp4";
|
|
18975
|
+
}
|
|
18976
|
+
async function fileExists2(target) {
|
|
18977
|
+
return access2(target).then(
|
|
18978
|
+
() => true,
|
|
18979
|
+
() => false
|
|
18980
|
+
);
|
|
18981
|
+
}
|
|
18982
|
+
function videoSourceReference(blueprint, fileArg2) {
|
|
18983
|
+
const bp = blueprint ?? {};
|
|
18984
|
+
const durable = typeof bp.source?.url === "string" ? bp.source.url : void 0;
|
|
18985
|
+
const original = /^https?:\/\//i.test(fileArg2) ? fileArg2 : void 0;
|
|
18986
|
+
const brand = bp.global?.branding?.brand_name;
|
|
18987
|
+
return { url: durable ?? original, advertiser: typeof brand === "string" && brand.trim() ? brand.trim() : void 0 };
|
|
18988
|
+
}
|
|
18989
|
+
function videoDefinitionDescription(blueprint) {
|
|
18990
|
+
const g = (blueprint ?? {}).global ?? {};
|
|
18991
|
+
const notes = g.reproduction_notes;
|
|
18992
|
+
if (typeof notes === "string" && notes.trim()) return notes.trim();
|
|
18993
|
+
const product = g.branding?.product;
|
|
18994
|
+
return typeof product === "string" && product.trim() ? product.trim() : void 0;
|
|
18995
|
+
}
|
|
18996
|
+
async function materializeReferenceVideo(fileArg2) {
|
|
18997
|
+
if (!/^https?:\/\//i.test(fileArg2)) return path12.resolve(fileArg2);
|
|
18998
|
+
let res;
|
|
18999
|
+
try {
|
|
19000
|
+
res = await fetch(fileArg2);
|
|
19001
|
+
} catch (e) {
|
|
19002
|
+
throw new Error(`failed to download reference video: ${e instanceof Error ? e.message : String(e)}`);
|
|
19003
|
+
}
|
|
19004
|
+
if (!res.ok) throw new Error(`failed to download reference video (${res.status} ${res.statusText})`);
|
|
19005
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
19006
|
+
if (bytes.length === 0) throw new Error("reference video download was empty");
|
|
19007
|
+
const dest = path12.join(
|
|
19008
|
+
tmpdir2(),
|
|
19009
|
+
`baker-ref-${sha256Hex(bytes).slice(0, 16)}${referenceVideoExt(fileArg2, res.headers.get("content-type"))}`
|
|
19010
|
+
);
|
|
19011
|
+
await writeFile3(dest, bytes);
|
|
19012
|
+
return dest;
|
|
19013
|
+
}
|
|
18719
19014
|
function resolveModels2(args) {
|
|
18720
19015
|
const pick = (flag, kind, fallback) => args[flag] ? String(args[flag]) : resolveModel2(kind, fallback);
|
|
18721
19016
|
return {
|
|
@@ -18812,7 +19107,11 @@ var scaffoldVideoCommand = defineCommand90({
|
|
|
18812
19107
|
description: "Turn a reference video into a runnable reproduction canvas in one command. Runs billed passes \u2014 video_deconstruct (the full scene-by-scene blueprint + transcript, baked to prompt.json as the editable 'prompt') and an AI selection of the video's RECURRING identity elements (person/animal/product/logo) \u2014 then scaffolds a pipeline where every scene boundary is a static-ad-grade frame (the blueprint as target_blueprint, a reference legend, the real frame as anchor) and each recurring element gets ONE shared [TODO] ingest slot wired into every frame it appears in. The clips feed Seedance an ultra-detailed motion brief (action, camera, dialogue, transcript). Edit prompt.json, drop the real source images, then `baker canvas run`."
|
|
18813
19108
|
},
|
|
18814
19109
|
args: {
|
|
18815
|
-
file: {
|
|
19110
|
+
file: {
|
|
19111
|
+
type: "positional",
|
|
19112
|
+
required: true,
|
|
19113
|
+
description: "Reference video \u2014 a local path OR an http(s) URL (e.g. a winning-ads link). A URL is downloaded for you; pass --slug or --out with it."
|
|
19114
|
+
},
|
|
18816
19115
|
out: { type: "string", description: "Output canvas path (default <video-dir>/<name>.video.canvas.json)" },
|
|
18817
19116
|
slug: {
|
|
18818
19117
|
type: "string",
|
|
@@ -18830,6 +19129,14 @@ var scaffoldVideoCommand = defineCommand90({
|
|
|
18830
19129
|
},
|
|
18831
19130
|
language: { type: "string", description: "Transcript/dialogue language hint (e.g. fr, en)" },
|
|
18832
19131
|
focus: { type: "string", description: "Known provenance/emphasis to ground the deconstruct" },
|
|
19132
|
+
advertiser: {
|
|
19133
|
+
type: "string",
|
|
19134
|
+
description: "Source advertiser recorded in _definition.md (default: the brand the deconstruct identified)"
|
|
19135
|
+
},
|
|
19136
|
+
platform: {
|
|
19137
|
+
type: "string",
|
|
19138
|
+
description: "Ad platform for _definition.md (meta|google|linkedin|tiktok|youtube|x|other; default meta)"
|
|
19139
|
+
},
|
|
18833
19140
|
"deconstruct-model": { type: "string", description: "Override the video_deconstruct model id" },
|
|
18834
19141
|
"select-model": { type: "string", description: "Override the text_generate model id for element selection" },
|
|
18835
19142
|
"image-model": { type: "string", description: "Override the image_generate model id for frames" },
|
|
@@ -18844,8 +19151,7 @@ var scaffoldVideoCommand = defineCommand90({
|
|
|
18844
19151
|
}
|
|
18845
19152
|
},
|
|
18846
19153
|
async run({ args }) {
|
|
18847
|
-
const
|
|
18848
|
-
const base = path11.basename(videoPath, path11.extname(videoPath));
|
|
19154
|
+
const fileArg2 = String(args.file);
|
|
18849
19155
|
const slug = args.slug ? String(args.slug) : void 0;
|
|
18850
19156
|
if (slug && !isValidScaffoldSlug(slug)) {
|
|
18851
19157
|
process.stderr.write(
|
|
@@ -18854,9 +19160,24 @@ var scaffoldVideoCommand = defineCommand90({
|
|
|
18854
19160
|
);
|
|
18855
19161
|
process.exit(2);
|
|
18856
19162
|
}
|
|
18857
|
-
const
|
|
18858
|
-
|
|
18859
|
-
|
|
19163
|
+
const isUrl = /^https?:\/\//i.test(fileArg2);
|
|
19164
|
+
if (isUrl && !slug && !args.out) {
|
|
19165
|
+
return fail2(
|
|
19166
|
+
"missing_output_target",
|
|
19167
|
+
"When the reference is a URL, pass --slug (writes src/creatives/<slug>/) or --out <path> so the scaffolded canvas has a home in the repo."
|
|
19168
|
+
);
|
|
19169
|
+
}
|
|
19170
|
+
let videoPath;
|
|
19171
|
+
try {
|
|
19172
|
+
videoPath = await materializeReferenceVideo(fileArg2);
|
|
19173
|
+
} catch (e) {
|
|
19174
|
+
return fail2("download", e instanceof Error ? e.message : String(e));
|
|
19175
|
+
}
|
|
19176
|
+
const base = path12.basename(videoPath, path12.extname(videoPath));
|
|
19177
|
+
const outPath = args.out ? path12.resolve(String(args.out)) : slug ? path12.join(process.cwd(), "src", "creatives", slug, `${slug}.canvas.json`) : path12.join(path12.dirname(videoPath), `${base}.video.canvas.json`);
|
|
19178
|
+
const outDir = path12.dirname(outPath);
|
|
19179
|
+
const blueprintPath = path12.join(outDir, "prompt.json");
|
|
19180
|
+
const blueprintStylePath = path12.join(outDir, "prompt.style.json");
|
|
18860
19181
|
const frames = args.frames === "reuse" ? "reuse" : "generate";
|
|
18861
19182
|
const maxScenes = args["max-scenes"] ? Number(args["max-scenes"]) : void 0;
|
|
18862
19183
|
if (Number.isFinite(maxScenes)) {
|
|
@@ -18875,10 +19196,16 @@ var scaffoldVideoCommand = defineCommand90({
|
|
|
18875
19196
|
shotCuts
|
|
18876
19197
|
});
|
|
18877
19198
|
const { blueprint, elements, transcript, creditsSpent } = await runAnalysisPasses(deconstructCanvas, selectModel);
|
|
18878
|
-
await
|
|
19199
|
+
await mkdir3(outDir, { recursive: true });
|
|
18879
19200
|
const annotated = annotateBlueprintWithElements(blueprint, elements);
|
|
18880
|
-
await
|
|
19201
|
+
await writeFile3(blueprintPath, `${JSON.stringify(annotated, null, 2)}
|
|
18881
19202
|
`, "utf8");
|
|
19203
|
+
await writeFile3(
|
|
19204
|
+
blueprintStylePath,
|
|
19205
|
+
`${JSON.stringify(slimBlueprintForFrameStyle(annotated), null, 2)}
|
|
19206
|
+
`,
|
|
19207
|
+
"utf8"
|
|
19208
|
+
);
|
|
18882
19209
|
let aspect;
|
|
18883
19210
|
try {
|
|
18884
19211
|
aspect = resolveAspect(
|
|
@@ -18896,12 +19223,12 @@ var scaffoldVideoCommand = defineCommand90({
|
|
|
18896
19223
|
`
|
|
18897
19224
|
);
|
|
18898
19225
|
}
|
|
18899
|
-
const compositionDest =
|
|
19226
|
+
const compositionDest = path12.join(outDir, "video-overlay-composition");
|
|
18900
19227
|
await cp(SHIPPED_COMPOSITION_DIR, compositionDest, { recursive: true });
|
|
18901
19228
|
await stampCompositionDims(compositionDest, outDims);
|
|
18902
|
-
const indexPath =
|
|
19229
|
+
const indexPath = path12.join(compositionDest, "index.html");
|
|
18903
19230
|
const overlayHtml = buildOverlayHtml(blueprint, { captionsActive: Boolean(transcript) });
|
|
18904
|
-
const indexHtml = await
|
|
19231
|
+
const indexHtml = await readFile7(indexPath, "utf8");
|
|
18905
19232
|
const injected = indexHtml.replace("<!--OVERLAYS-->", () => overlayHtml);
|
|
18906
19233
|
if (injected === indexHtml && overlayHtml.trim()) {
|
|
18907
19234
|
fail2(
|
|
@@ -18909,15 +19236,16 @@ var scaffoldVideoCommand = defineCommand90({
|
|
|
18909
19236
|
`video-overlay-composition/index.html is missing the <!--OVERLAYS--> marker \u2014 cannot inject the overlay layer`
|
|
18910
19237
|
);
|
|
18911
19238
|
}
|
|
18912
|
-
await
|
|
19239
|
+
await writeFile3(indexPath, injected, "utf8");
|
|
18913
19240
|
const captions = await stageCaptions(outDir, transcript);
|
|
18914
19241
|
if (captions.compositionPath) await stampCompositionDims(captions.compositionPath, outDims);
|
|
18915
19242
|
const opts = {
|
|
18916
19243
|
imageModel,
|
|
18917
19244
|
videoModel,
|
|
18918
|
-
overlayCompositionPath:
|
|
18919
|
-
captionsCompositionPath: captions.compositionPath ?
|
|
18920
|
-
blueprintPath:
|
|
19245
|
+
overlayCompositionPath: path12.relative(outDir, compositionDest),
|
|
19246
|
+
captionsCompositionPath: captions.compositionPath ? path12.relative(outDir, captions.compositionPath) : void 0,
|
|
19247
|
+
blueprintPath: path12.relative(outDir, blueprintPath),
|
|
19248
|
+
blueprintStylePath: path12.relative(outDir, blueprintStylePath),
|
|
18921
19249
|
frames,
|
|
18922
19250
|
ambient: Boolean(args.ambient),
|
|
18923
19251
|
...args.aspect ? { aspect: String(args.aspect) } : {},
|
|
@@ -18938,7 +19266,7 @@ var scaffoldVideoCommand = defineCommand90({
|
|
|
18938
19266
|
todo.blocking_validation_issues = validation.issues;
|
|
18939
19267
|
meta.todo = todo;
|
|
18940
19268
|
}
|
|
18941
|
-
await
|
|
19269
|
+
await writeFile3(outPath, `${JSON.stringify(canvas, null, 2)}
|
|
18942
19270
|
`, "utf8");
|
|
18943
19271
|
if (!validation.ok) {
|
|
18944
19272
|
process.stderr.write(
|
|
@@ -18958,12 +19286,42 @@ var scaffoldVideoCommand = defineCommand90({
|
|
|
18958
19286
|
process.exit(2);
|
|
18959
19287
|
}
|
|
18960
19288
|
await ensureGitignore(process.cwd(), ["canvas/", ".context/"]);
|
|
19289
|
+
const sourceRef = videoSourceReference(blueprint, fileArg2);
|
|
19290
|
+
if (slug) {
|
|
19291
|
+
const definitionPath = path12.join(outDir, "_definition.md");
|
|
19292
|
+
if (!await fileExists2(definitionPath)) {
|
|
19293
|
+
await writeFile3(
|
|
19294
|
+
definitionPath,
|
|
19295
|
+
buildCreativeDefinition({
|
|
19296
|
+
title: titleFromSlug(slug),
|
|
19297
|
+
kind: "video",
|
|
19298
|
+
platform: resolvePlatform(args.platform ? String(args.platform) : void 0),
|
|
19299
|
+
formats: resolveFormats(aspect.outAr),
|
|
19300
|
+
sourceReferenceUrl: sourceRef.url,
|
|
19301
|
+
sourceAdvertiser: args.advertiser ? String(args.advertiser) : sourceRef.advertiser,
|
|
19302
|
+
sourceKind: "video",
|
|
19303
|
+
description: videoDefinitionDescription(blueprint)
|
|
19304
|
+
}),
|
|
19305
|
+
"utf8"
|
|
19306
|
+
);
|
|
19307
|
+
}
|
|
19308
|
+
}
|
|
19309
|
+
if (slug) {
|
|
19310
|
+
await syncCreativeDefinitionBestEffort({
|
|
19311
|
+
slug,
|
|
19312
|
+
title: titleFromSlug(slug),
|
|
19313
|
+
formats: [aspect.outAr],
|
|
19314
|
+
canvas,
|
|
19315
|
+
sourceReferenceUrl: sourceRef.url
|
|
19316
|
+
});
|
|
19317
|
+
}
|
|
18961
19318
|
process.stdout.write(
|
|
18962
19319
|
`${JSON.stringify(
|
|
18963
19320
|
{
|
|
18964
19321
|
ok: true,
|
|
18965
19322
|
canvas_path: outPath,
|
|
18966
19323
|
prompt_path: blueprintPath,
|
|
19324
|
+
source_reference: sourceRef.url,
|
|
18967
19325
|
composition_dir: compositionDest,
|
|
18968
19326
|
output: canvas.output,
|
|
18969
19327
|
frames_mode: frames,
|
|
@@ -18975,7 +19333,7 @@ var scaffoldVideoCommand = defineCommand90({
|
|
|
18975
19333
|
run_estimated_credits: validation.estimatedCredits
|
|
18976
19334
|
},
|
|
18977
19335
|
checklist: {
|
|
18978
|
-
edit_prompt: `Edit ${
|
|
19336
|
+
edit_prompt: `Edit ${path12.basename(blueprintPath)} \u2014 the blueprint deconstructed from your video; rewrite it into the ad you want (cast, palette, copy, claims). Every scene frame reads it via target_blueprint.`,
|
|
18979
19337
|
recurring_elements_to_supply: report.elements,
|
|
18980
19338
|
voices_to_confirm: report.dialogue.map((d) => ({
|
|
18981
19339
|
scene: d.scene,
|
|
@@ -18989,6 +19347,13 @@ var scaffoldVideoCommand = defineCommand90({
|
|
|
18989
19347
|
scenes_clamped_to_15s: report.clamped_scenes,
|
|
18990
19348
|
oversize_scenes: report.oversize_scenes,
|
|
18991
19349
|
overstuffed_scenes: report.overstuffed_scenes,
|
|
19350
|
+
// A photoreal on-camera person/animal on Seedance can trip ByteDance's
|
|
19351
|
+
// real-person-likeness filter (422 content_policy_blocked, NON-retryable — no
|
|
19352
|
+
// prompt reframe clears it). Surface the escape BEFORE the billed run so a
|
|
19353
|
+
// face-heavy ad isn't discovered broken mid-render.
|
|
19354
|
+
...report.elements.some((e) => e.type === "person" || e.type === "animal") && /seedance/i.test(videoModel) ? {
|
|
19355
|
+
content_policy_risk: "This ad has a photoreal on-camera cast generating on Seedance. ByteDance's real-person-likeness filter can reject a photoreal AI face with a NON-retryable 422 (content_policy_blocked) \u2014 no prompt change clears it. If clips fail that way, regenerate on Veo (re-run with `--video-model google/veo-3.1-fast`) or make the frame less photoreal."
|
|
19356
|
+
} : {},
|
|
18992
19357
|
note: "Drop ONE real source image at each el_* [TODO] (reused across every frame that element appears in), confirm each voice_select casting, then `baker canvas validate` and `baker canvas run`. Running generates many billed image/video/audio assets \u2014 it is not free."
|
|
18993
19358
|
}
|
|
18994
19359
|
},
|
|
@@ -19001,8 +19366,8 @@ var scaffoldVideoCommand = defineCommand90({
|
|
|
19001
19366
|
});
|
|
19002
19367
|
|
|
19003
19368
|
// src/commands/canvas/set-prompt.ts
|
|
19004
|
-
import { readFile as
|
|
19005
|
-
import
|
|
19369
|
+
import { readFile as readFile8, writeFile as writeFile4 } from "fs/promises";
|
|
19370
|
+
import path13 from "path";
|
|
19006
19371
|
import { defineCommand as defineCommand91 } from "citty";
|
|
19007
19372
|
function setNodePrompt(canvas, nodeId, text) {
|
|
19008
19373
|
const nodes = canvas?.nodes;
|
|
@@ -19030,17 +19395,17 @@ var setPromptCommand = defineCommand91({
|
|
|
19030
19395
|
"text-file": { type: "string", description: "Read the new prompt from a UTF-8 file (preserves accents/newlines)" }
|
|
19031
19396
|
},
|
|
19032
19397
|
async run({ args }) {
|
|
19033
|
-
const filePath =
|
|
19398
|
+
const filePath = path13.resolve(String(args.file));
|
|
19034
19399
|
let canvas;
|
|
19035
19400
|
try {
|
|
19036
|
-
canvas = JSON.parse(await
|
|
19401
|
+
canvas = JSON.parse(await readFile8(filePath, "utf8"));
|
|
19037
19402
|
} catch (e) {
|
|
19038
19403
|
process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "parse", message: String(e) } }, null, 2)}
|
|
19039
19404
|
`);
|
|
19040
19405
|
process.exit(2);
|
|
19041
19406
|
}
|
|
19042
19407
|
let text;
|
|
19043
|
-
if (args["text-file"]) text = await
|
|
19408
|
+
if (args["text-file"]) text = await readFile8(path13.resolve(String(args["text-file"])), "utf8");
|
|
19044
19409
|
else if (args.text !== void 0) text = String(args.text);
|
|
19045
19410
|
else {
|
|
19046
19411
|
process.stderr.write(
|
|
@@ -19061,14 +19426,14 @@ var setPromptCommand = defineCommand91({
|
|
|
19061
19426
|
process.exit(2);
|
|
19062
19427
|
return;
|
|
19063
19428
|
}
|
|
19064
|
-
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated,
|
|
19429
|
+
const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(updated, path13.dirname(filePath)), defaultRegistry());
|
|
19065
19430
|
if (!validation.ok) {
|
|
19066
19431
|
process.stderr.write(`${JSON.stringify({ ok: false, error: { code: "validation", issues: validation.issues } }, null, 2)}
|
|
19067
19432
|
`);
|
|
19068
19433
|
process.exit(2);
|
|
19069
19434
|
return;
|
|
19070
19435
|
}
|
|
19071
|
-
await
|
|
19436
|
+
await writeFile4(filePath, `${JSON.stringify(updated, null, 2)}
|
|
19072
19437
|
`, "utf8");
|
|
19073
19438
|
process.stdout.write(`${JSON.stringify({ ok: true, node: String(args.node), bytes: text.length }, null, 2)}
|
|
19074
19439
|
`);
|
|
@@ -19076,8 +19441,8 @@ var setPromptCommand = defineCommand91({
|
|
|
19076
19441
|
});
|
|
19077
19442
|
|
|
19078
19443
|
// src/commands/canvas/validate.ts
|
|
19079
|
-
import { readFile as
|
|
19080
|
-
import
|
|
19444
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
19445
|
+
import path14 from "path";
|
|
19081
19446
|
import { defineCommand as defineCommand92 } from "citty";
|
|
19082
19447
|
var validateCommand = defineCommand92({
|
|
19083
19448
|
meta: {
|
|
@@ -19086,8 +19451,8 @@ var validateCommand = defineCommand92({
|
|
|
19086
19451
|
},
|
|
19087
19452
|
args: { file: { type: "positional", required: true, description: "Path to canvas JSON" } },
|
|
19088
19453
|
async run({ args }) {
|
|
19089
|
-
const filePath =
|
|
19090
|
-
const raw = await
|
|
19454
|
+
const filePath = path14.resolve(String(args.file));
|
|
19455
|
+
const raw = await readFile9(filePath, "utf8");
|
|
19091
19456
|
let parsed;
|
|
19092
19457
|
try {
|
|
19093
19458
|
parsed = JSON.parse(raw);
|
|
@@ -19097,7 +19462,7 @@ var validateCommand = defineCommand92({
|
|
|
19097
19462
|
`);
|
|
19098
19463
|
process.exit(2);
|
|
19099
19464
|
}
|
|
19100
|
-
parsed = resolveRelativeCanvasPaths(parsed,
|
|
19465
|
+
parsed = resolveRelativeCanvasPaths(parsed, path14.dirname(filePath));
|
|
19101
19466
|
const result = await validateCanvasDeep(parsed, defaultRegistry());
|
|
19102
19467
|
if (!result.ok) {
|
|
19103
19468
|
process.stderr.write(`${JSON.stringify({ ok: false, issues: result.issues }, null, 2)}
|
|
@@ -19156,7 +19521,7 @@ import { defineCommand as defineCommand95 } from "citty";
|
|
|
19156
19521
|
import { defineCommand as defineCommand94 } from "citty";
|
|
19157
19522
|
|
|
19158
19523
|
// src/commands/images/api.ts
|
|
19159
|
-
import { readFile as
|
|
19524
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
19160
19525
|
import { extname } from "path";
|
|
19161
19526
|
var imageProcessingTimeoutMs = 18e4;
|
|
19162
19527
|
var imageReadyPollIntervalMs = 2e3;
|
|
@@ -19170,7 +19535,7 @@ var mimeMap = {
|
|
|
19170
19535
|
".avif": "image/avif"
|
|
19171
19536
|
};
|
|
19172
19537
|
var defaultImageApiDeps = {
|
|
19173
|
-
readFile:
|
|
19538
|
+
readFile: readFile10,
|
|
19174
19539
|
post: apiPost,
|
|
19175
19540
|
get: apiGet,
|
|
19176
19541
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms))
|
|
@@ -20200,7 +20565,7 @@ function cropSprite(input, region) {
|
|
|
20200
20565
|
|
|
20201
20566
|
// src/lib/image/io.ts
|
|
20202
20567
|
import { randomBytes } from "crypto";
|
|
20203
|
-
import { glob as fsGlob, readFile as
|
|
20568
|
+
import { glob as fsGlob, readFile as readFile11, rename, stat as stat2, writeFile as writeFile5 } from "fs/promises";
|
|
20204
20569
|
import { dirname as dirname2, extname as extname2, join as join3, resolve as resolve4 } from "path";
|
|
20205
20570
|
var REMOTE_RE = /^https?:\/\//i;
|
|
20206
20571
|
var GLOB_RE = /[*?[\]{}]/;
|
|
@@ -20236,11 +20601,11 @@ async function readImageBuffer(pathOrUrl) {
|
|
|
20236
20601
|
}
|
|
20237
20602
|
return Buffer.from(await response.arrayBuffer());
|
|
20238
20603
|
}
|
|
20239
|
-
return
|
|
20604
|
+
return readFile11(pathOrUrl);
|
|
20240
20605
|
}
|
|
20241
|
-
async function isDirectory(
|
|
20606
|
+
async function isDirectory(path15) {
|
|
20242
20607
|
try {
|
|
20243
|
-
const s = await stat2(
|
|
20608
|
+
const s = await stat2(path15);
|
|
20244
20609
|
return s.isDirectory();
|
|
20245
20610
|
} catch {
|
|
20246
20611
|
return false;
|
|
@@ -20259,7 +20624,7 @@ async function atomicWrite(targetPath, data) {
|
|
|
20259
20624
|
const absolute = resolve4(targetPath);
|
|
20260
20625
|
const dir = dirname2(absolute);
|
|
20261
20626
|
const tmp = join3(dir, `.baker-image-${randomBytes(8).toString("hex")}.tmp`);
|
|
20262
|
-
await
|
|
20627
|
+
await writeFile5(tmp, data);
|
|
20263
20628
|
await rename(tmp, absolute);
|
|
20264
20629
|
}
|
|
20265
20630
|
|
|
@@ -20602,7 +20967,7 @@ var findCommand = defineCommand108({
|
|
|
20602
20967
|
});
|
|
20603
20968
|
|
|
20604
20969
|
// src/commands/images/generate.ts
|
|
20605
|
-
import { readFile as
|
|
20970
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
20606
20971
|
import { defineCommand as defineCommand109 } from "citty";
|
|
20607
20972
|
import sharp2 from "sharp";
|
|
20608
20973
|
var GENERATE_TIMEOUT_MS = 18e4;
|
|
@@ -20692,7 +21057,7 @@ async function resolveReferences(spec) {
|
|
|
20692
21057
|
}
|
|
20693
21058
|
let raw;
|
|
20694
21059
|
try {
|
|
20695
|
-
raw = await
|
|
21060
|
+
raw = await readFile12(entry);
|
|
20696
21061
|
} catch {
|
|
20697
21062
|
throw new ApiError("VALIDATION_ERROR", `Reference file not found: ${entry}`);
|
|
20698
21063
|
}
|
|
@@ -24719,7 +25084,7 @@ var searchCommand3 = defineCommand154({
|
|
|
24719
25084
|
var tagsCommand4 = makeTagsCommand("videos", "video", "/api/videos/tags");
|
|
24720
25085
|
|
|
24721
25086
|
// src/commands/videos/upload.ts
|
|
24722
|
-
import { readFile as
|
|
25087
|
+
import { readFile as readFile13, stat as stat3 } from "fs/promises";
|
|
24723
25088
|
import { extname as extname3 } from "path";
|
|
24724
25089
|
import { defineCommand as defineCommand155 } from "citty";
|
|
24725
25090
|
var MIME_MAP = {
|
|
@@ -24784,7 +25149,7 @@ var uploadCommand2 = defineCommand155({
|
|
|
24784
25149
|
return;
|
|
24785
25150
|
}
|
|
24786
25151
|
const { uploadUrl, videoId } = await apiPost("/api/videos/upload", {});
|
|
24787
|
-
const fileBuffer = await
|
|
25152
|
+
const fileBuffer = await readFile13(filePath);
|
|
24788
25153
|
const uploadResponse = await fetch(uploadUrl, {
|
|
24789
25154
|
method: "PUT",
|
|
24790
25155
|
headers: { "Content-Type": contentType },
|