@vibeframe/cli 0.113.3 → 0.113.5
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.
|
@@ -15,6 +15,7 @@ export interface BuildPlanBeat {
|
|
|
15
15
|
assets: {
|
|
16
16
|
narration: AssetPlan | null;
|
|
17
17
|
backdrop: AssetPlan | null;
|
|
18
|
+
keyframe: AssetPlan | null;
|
|
18
19
|
video: AssetPlan | null;
|
|
19
20
|
music: AssetPlan | null;
|
|
20
21
|
};
|
|
@@ -93,6 +94,7 @@ export interface CreateBuildPlanOptions {
|
|
|
93
94
|
skipNarration?: boolean;
|
|
94
95
|
skipBackdrop?: boolean;
|
|
95
96
|
skipVideo?: boolean;
|
|
97
|
+
skipKeyframe?: boolean;
|
|
96
98
|
skipMusic?: boolean;
|
|
97
99
|
ttsProvider?: string;
|
|
98
100
|
voice?: string;
|
|
@@ -81,6 +81,23 @@ export type SceneBuildProgressEvent = {
|
|
|
81
81
|
type: "backdrop-skipped";
|
|
82
82
|
beatId: string;
|
|
83
83
|
reason: string;
|
|
84
|
+
} | {
|
|
85
|
+
type: "keyframe-cached";
|
|
86
|
+
beatId: string;
|
|
87
|
+
path: string;
|
|
88
|
+
} | {
|
|
89
|
+
type: "keyframe-generated";
|
|
90
|
+
beatId: string;
|
|
91
|
+
path: string;
|
|
92
|
+
provider: string;
|
|
93
|
+
} | {
|
|
94
|
+
type: "keyframe-failed";
|
|
95
|
+
beatId: string;
|
|
96
|
+
error: string;
|
|
97
|
+
} | {
|
|
98
|
+
type: "keyframe-skipped";
|
|
99
|
+
beatId: string;
|
|
100
|
+
reason: string;
|
|
84
101
|
} | {
|
|
85
102
|
type: "video-cached";
|
|
86
103
|
beatId: string;
|
|
@@ -233,6 +250,8 @@ export interface SceneBuildOptions {
|
|
|
233
250
|
musicProvider?: BuildMusicProvider;
|
|
234
251
|
/** Skip AI video generation even when beats declare video cues. */
|
|
235
252
|
skipVideo?: boolean;
|
|
253
|
+
/** Skip keyframe-still generation even when beats declare keyframe cues. */
|
|
254
|
+
skipKeyframe?: boolean;
|
|
236
255
|
/** Skip music generation even when beats declare music cues. */
|
|
237
256
|
skipMusic?: boolean;
|
|
238
257
|
/** OpenAI image quality — see `vibe generate image --quality`. */
|
package/dist/index.js
CHANGED
|
@@ -60690,6 +60690,14 @@ async function createBuildPlan(opts) {
|
|
|
60690
60690
|
duration: beat.duration,
|
|
60691
60691
|
keyframe: keyframePrompt
|
|
60692
60692
|
}) : null;
|
|
60693
|
+
const keyframeCache = keyframePrompt ? keyframeCacheDescriptor({
|
|
60694
|
+
beatId: beat.id,
|
|
60695
|
+
cue: keyframePrompt,
|
|
60696
|
+
provider: resolved.image.resolved,
|
|
60697
|
+
quality: imageQuality,
|
|
60698
|
+
size: imageSize2,
|
|
60699
|
+
ratio: imageRatio
|
|
60700
|
+
}) : null;
|
|
60693
60701
|
const musicCache = musicPrompt && !musicReference ? musicCacheDescriptor({
|
|
60694
60702
|
beatId: beat.id,
|
|
60695
60703
|
cue: musicPrompt,
|
|
@@ -60725,6 +60733,19 @@ async function createBuildPlan(opts) {
|
|
|
60725
60733
|
cost: backdropCostUsd(imageQuality),
|
|
60726
60734
|
active: includeAssets
|
|
60727
60735
|
}) : null;
|
|
60736
|
+
const keyframe = keyframePrompt && !opts.skipKeyframe ? assetPlan({
|
|
60737
|
+
kind: "keyframe",
|
|
60738
|
+
beatId: beat.id,
|
|
60739
|
+
cue: keyframePrompt,
|
|
60740
|
+
provider: resolved.image.resolved,
|
|
60741
|
+
path: `assets/keyframe-${beat.id}.png`,
|
|
60742
|
+
cache: keyframeCache,
|
|
60743
|
+
reference: null,
|
|
60744
|
+
projectDir,
|
|
60745
|
+
force: opts.force,
|
|
60746
|
+
cost: backdropCostUsd(imageQuality),
|
|
60747
|
+
active: includeAssets
|
|
60748
|
+
}) : null;
|
|
60728
60749
|
const video = videoCue && !opts.skipVideo ? assetPlan({
|
|
60729
60750
|
kind: "video",
|
|
60730
60751
|
beatId: beat.id,
|
|
@@ -60757,7 +60778,7 @@ async function createBuildPlan(opts) {
|
|
|
60757
60778
|
}) : null;
|
|
60758
60779
|
const compositionPath = `compositions/scene-${beat.id}.html`;
|
|
60759
60780
|
const compositionExists = existsSync47(join23(projectDir, compositionPath));
|
|
60760
|
-
for (const asset of [narration, backdrop, video, music]) {
|
|
60781
|
+
for (const asset of [narration, backdrop, keyframe, video, music]) {
|
|
60761
60782
|
if (!asset) continue;
|
|
60762
60783
|
if (asset.referenceError) {
|
|
60763
60784
|
missing.add("assets");
|
|
@@ -60782,13 +60803,9 @@ async function createBuildPlan(opts) {
|
|
|
60782
60803
|
if (narration?.willGenerate) noteProviderNeed(resolved.narration, "Narration");
|
|
60783
60804
|
if (backdrop?.willGenerate && isSupportedBuildImageProvider(resolved.image.resolved))
|
|
60784
60805
|
noteProviderNeed(resolved.image, "Backdrop generation");
|
|
60806
|
+
if (keyframe?.willGenerate && isSupportedBuildImageProvider(resolved.image.resolved))
|
|
60807
|
+
noteProviderNeed(resolved.image, "Keyframe generation");
|
|
60785
60808
|
if (video?.willGenerate) noteProviderNeed(resolved.video, "Video generation");
|
|
60786
|
-
if (keyframePrompt && video?.willGenerate) {
|
|
60787
|
-
estimatedCostUsd += backdropCostUsd(imageQuality);
|
|
60788
|
-
providers.add(resolved.image.resolved);
|
|
60789
|
-
if (isSupportedBuildImageProvider(resolved.image.resolved))
|
|
60790
|
-
noteProviderNeed(resolved.image, "Keyframe generation");
|
|
60791
|
-
}
|
|
60792
60809
|
if (music?.willGenerate) noteProviderNeed(resolved.music, "Music generation");
|
|
60793
60810
|
if (!compositionExists) missing.add("compositions");
|
|
60794
60811
|
if (includeCompose && !compositionExists && mode !== "agent") {
|
|
@@ -60801,7 +60818,7 @@ async function createBuildPlan(opts) {
|
|
|
60801
60818
|
heading: beat.heading,
|
|
60802
60819
|
durationSec: beat.duration ?? null,
|
|
60803
60820
|
cues: cue,
|
|
60804
|
-
assets: { narration, backdrop, video, music },
|
|
60821
|
+
assets: { narration, backdrop, keyframe, video, music },
|
|
60805
60822
|
composition: {
|
|
60806
60823
|
path: compositionPath,
|
|
60807
60824
|
exists: compositionExists
|
|
@@ -61017,6 +61034,7 @@ function providerResolutionsForPlan(resolved, beats, opts, includeAssets) {
|
|
|
61017
61034
|
return [
|
|
61018
61035
|
includeAssets && !opts.skipNarration && needsProvider("narration") ? resolved.narration : null,
|
|
61019
61036
|
includeAssets && !opts.skipBackdrop && needsProvider("backdrop") ? resolved.image : null,
|
|
61037
|
+
includeAssets && !opts.skipKeyframe && needsProvider("keyframe") ? resolved.image : null,
|
|
61020
61038
|
includeAssets && !opts.skipVideo && needsProvider("video") ? resolved.video : null,
|
|
61021
61039
|
includeAssets && !opts.skipMusic && needsProvider("music") ? resolved.music : null,
|
|
61022
61040
|
resolved.composer ?? null
|
|
@@ -62072,10 +62090,17 @@ async function executeSceneBuild(opts) {
|
|
|
62072
62090
|
const onProgress = opts.onProgress ?? (() => {
|
|
62073
62091
|
});
|
|
62074
62092
|
const mode = resolveSceneBuildMode(opts);
|
|
62075
|
-
|
|
62093
|
+
let selectedStage = opts.stage ?? (opts.skipRender ? "sync" : "all");
|
|
62094
|
+
const skipVideoStopsRender = (opts.skipVideo ?? false) && (selectedStage === "all" || selectedStage === "render");
|
|
62095
|
+
if (skipVideoStopsRender) selectedStage = "sync";
|
|
62076
62096
|
const stageReports = createEmptyStageReports();
|
|
62077
62097
|
const warnings = [];
|
|
62078
62098
|
const retryWith = [];
|
|
62099
|
+
if (skipVideoStopsRender) {
|
|
62100
|
+
warnings.push(
|
|
62101
|
+
"--skip-video: skipped render (no clips to capture). Review assets/keyframe-*.png, then build without --skip-video to animate them."
|
|
62102
|
+
);
|
|
62103
|
+
}
|
|
62079
62104
|
let sceneRepair = skippedSceneRepairSummary();
|
|
62080
62105
|
const storyboardPath2 = join25(projectDir, "STORYBOARD.md");
|
|
62081
62106
|
const buildPlan = await createBuildPlan({
|
|
@@ -62086,6 +62111,7 @@ async function executeSceneBuild(opts) {
|
|
|
62086
62111
|
skipNarration: opts.skipNarration,
|
|
62087
62112
|
skipBackdrop: opts.skipBackdrop,
|
|
62088
62113
|
skipVideo: opts.skipVideo,
|
|
62114
|
+
skipKeyframe: opts.skipKeyframe,
|
|
62089
62115
|
skipMusic: opts.skipMusic,
|
|
62090
62116
|
ttsProvider: opts.ttsProvider,
|
|
62091
62117
|
voice: opts.voice,
|
|
@@ -62234,7 +62260,7 @@ async function executeSceneBuild(opts) {
|
|
|
62234
62260
|
if (shouldRunStage(selectedStage, "assets")) {
|
|
62235
62261
|
onProgress({ type: "phase-start", phase: "primitives" });
|
|
62236
62262
|
const characterPaths = /* @__PURE__ */ new Map();
|
|
62237
|
-
if (!(opts.skipVideo ?? false)) {
|
|
62263
|
+
if (!((opts.skipVideo ?? false) && (opts.skipKeyframe ?? false))) {
|
|
62238
62264
|
const referenced = /* @__PURE__ */ new Set();
|
|
62239
62265
|
for (const beat of activeBeats) {
|
|
62240
62266
|
for (const name of beatCharacterNames(beat.cues)) referenced.add(name);
|
|
@@ -62271,6 +62297,7 @@ async function executeSceneBuild(opts) {
|
|
|
62271
62297
|
skipNarration: opts.skipNarration ?? false,
|
|
62272
62298
|
skipBackdrop: opts.skipBackdrop ?? false,
|
|
62273
62299
|
skipVideo: opts.skipVideo ?? false,
|
|
62300
|
+
skipKeyframe: opts.skipKeyframe ?? false,
|
|
62274
62301
|
skipMusic: opts.skipMusic ?? false,
|
|
62275
62302
|
force: opts.force ?? false,
|
|
62276
62303
|
onProgress,
|
|
@@ -62647,10 +62674,11 @@ async function executeSceneBuild(opts) {
|
|
|
62647
62674
|
});
|
|
62648
62675
|
}
|
|
62649
62676
|
async function buildBeatPrimitives(beat, ctx) {
|
|
62677
|
+
const keyframe = ctx.skipKeyframe ? await skipped("keyframe", beat.id, "--skip-keyframe", ctx) : await dispatchKeyframe(beat, ctx);
|
|
62650
62678
|
const [narration, backdrop, video, music] = await Promise.all([
|
|
62651
62679
|
ctx.skipNarration ? skipped("narration", beat.id, "--skip-narration", ctx) : dispatchNarration(beat, ctx),
|
|
62652
62680
|
ctx.skipBackdrop ? skipped("backdrop", beat.id, "--skip-backdrop", ctx) : dispatchBackdrop(beat, ctx),
|
|
62653
|
-
ctx.skipVideo ? skipped("video", beat.id, "--skip-video", ctx) : dispatchVideo(beat, ctx),
|
|
62681
|
+
ctx.skipVideo ? skipped("video", beat.id, "--skip-video", ctx) : dispatchVideo(beat, ctx, keyframe),
|
|
62654
62682
|
ctx.skipMusic ? skipped("music", beat.id, "--skip-music", ctx) : dispatchMusic(beat, ctx)
|
|
62655
62683
|
]);
|
|
62656
62684
|
return {
|
|
@@ -62694,8 +62722,8 @@ async function buildBeatPrimitives(beat, ctx) {
|
|
|
62694
62722
|
videoMetadataPath: video.metadataPath,
|
|
62695
62723
|
videoFreshness: video.freshness,
|
|
62696
62724
|
videoSourcePath: video.sourcePath,
|
|
62697
|
-
keyframeStatus:
|
|
62698
|
-
keyframePath:
|
|
62725
|
+
keyframeStatus: keyframe.status,
|
|
62726
|
+
keyframePath: keyframe.path,
|
|
62699
62727
|
musicStatus: music.status,
|
|
62700
62728
|
musicPath: music.path,
|
|
62701
62729
|
musicJobId: music.job?.id,
|
|
@@ -63266,7 +63294,21 @@ function imageProviderKeyInfo(provider) {
|
|
|
63266
63294
|
if (provider === "grok") return { configKey: "xai", envVar: "XAI_API_KEY" };
|
|
63267
63295
|
return { configKey: "openai", envVar: "OPENAI_API_KEY" };
|
|
63268
63296
|
}
|
|
63269
|
-
async function
|
|
63297
|
+
async function dispatchKeyframe(beat, ctx) {
|
|
63298
|
+
const keyframeCue = stringOrUndefined4(beat.cues?.keyframe);
|
|
63299
|
+
if (!keyframeCue) return { status: "no-cue" };
|
|
63300
|
+
const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
|
|
63301
|
+
const kf = await ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel);
|
|
63302
|
+
if (kf.status === "failed") {
|
|
63303
|
+
ctx.onProgress({ type: "keyframe-failed", beatId: beat.id, error: kf.error });
|
|
63304
|
+
return { status: "failed", error: kf.error };
|
|
63305
|
+
}
|
|
63306
|
+
ctx.onProgress(
|
|
63307
|
+
kf.status === "cached" ? { type: "keyframe-cached", beatId: beat.id, path: kf.rel } : { type: "keyframe-generated", beatId: beat.id, path: kf.rel, provider: ctx.imageProvider }
|
|
63308
|
+
);
|
|
63309
|
+
return { status: kf.status, path: kf.rel, provider: ctx.imageProvider };
|
|
63310
|
+
}
|
|
63311
|
+
async function dispatchVideo(beat, ctx, keyframe) {
|
|
63270
63312
|
const reference = assetReferenceForBeat(ctx.projectDir, "video", beat);
|
|
63271
63313
|
if (reference) return referencePrimitiveOutcome("video", beat, ctx, reference);
|
|
63272
63314
|
const keyframeCue = stringOrUndefined4(beat.cues?.keyframe);
|
|
@@ -63336,20 +63378,14 @@ async function dispatchVideo(beat, ctx) {
|
|
|
63336
63378
|
freshness: "fresh"
|
|
63337
63379
|
};
|
|
63338
63380
|
}
|
|
63339
|
-
|
|
63340
|
-
|
|
63341
|
-
|
|
63342
|
-
|
|
63343
|
-
|
|
63344
|
-
const kf = await ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel);
|
|
63345
|
-
if (kf.status === "failed") {
|
|
63346
|
-
ctx.onProgress({ type: "video-failed", beatId: beat.id, error: kf.error });
|
|
63347
|
-
return { status: "failed", error: kf.error };
|
|
63348
|
-
}
|
|
63349
|
-
keyframeStatus = kf.status;
|
|
63350
|
-
keyframeRel = kf.rel;
|
|
63351
|
-
keyframeImageAbs = join25(ctx.projectDir, kf.rel);
|
|
63381
|
+
const keyframeImageRel = keyframeCue && (keyframe.status === "generated" || keyframe.status === "cached") ? keyframe.path : void 0;
|
|
63382
|
+
if (keyframeCue && !keyframeImageRel) {
|
|
63383
|
+
const reason = keyframe.status === "failed" ? `keyframe unavailable: ${keyframe.error ?? "generation failed"}` : "keyframe not generated (build without --skip-keyframe first)";
|
|
63384
|
+
ctx.onProgress({ type: "video-skipped", beatId: beat.id, reason });
|
|
63385
|
+
return { status: "skipped" };
|
|
63352
63386
|
}
|
|
63387
|
+
const keyframeImageAbs = keyframeImageRel ? join25(ctx.projectDir, keyframeImageRel) : void 0;
|
|
63388
|
+
loadSceneBuildEnv(ctx.projectDir);
|
|
63353
63389
|
const result = await executeVideoGenerate({
|
|
63354
63390
|
prompt: prompt3,
|
|
63355
63391
|
provider: ctx.videoProvider,
|
|
@@ -63395,9 +63431,7 @@ async function dispatchVideo(beat, ctx) {
|
|
|
63395
63431
|
cachePath: cache.path,
|
|
63396
63432
|
cacheKey: cache.key,
|
|
63397
63433
|
metadataPath,
|
|
63398
|
-
freshness: "fresh"
|
|
63399
|
-
keyframeStatus,
|
|
63400
|
-
keyframePath: keyframeRel
|
|
63434
|
+
freshness: "fresh"
|
|
63401
63435
|
};
|
|
63402
63436
|
}
|
|
63403
63437
|
const job = await createAndWriteJobRecord({
|
|
@@ -63429,9 +63463,7 @@ async function dispatchVideo(beat, ctx) {
|
|
|
63429
63463
|
provider: job.provider,
|
|
63430
63464
|
cachePath: cache.path,
|
|
63431
63465
|
cacheKey: cache.key,
|
|
63432
|
-
metadataPath
|
|
63433
|
-
keyframeStatus,
|
|
63434
|
-
keyframePath: keyframeRel
|
|
63466
|
+
metadataPath
|
|
63435
63467
|
};
|
|
63436
63468
|
}
|
|
63437
63469
|
async function dispatchMusic(beat, ctx) {
|
|
@@ -74819,7 +74851,7 @@ init_source();
|
|
|
74819
74851
|
// package.json
|
|
74820
74852
|
var package_default = {
|
|
74821
74853
|
name: "@vibeframe/cli",
|
|
74822
|
-
version: "0.113.
|
|
74854
|
+
version: "0.113.5",
|
|
74823
74855
|
description: "VibeFrame CLI - agentic video workflow runtime",
|
|
74824
74856
|
private: false,
|
|
74825
74857
|
license: "MIT",
|
|
@@ -85105,7 +85137,10 @@ var VALID_STAGES2 = ["assets", "compose", "sync", "render", "all"];
|
|
|
85105
85137
|
var VALID_IMAGE_PROVIDERS2 = ["openai", "gemini", "grok"];
|
|
85106
85138
|
var VALID_VIDEO_PROVIDERS2 = ["seedance", "grok", "kling", "runway", "veo"];
|
|
85107
85139
|
var VALID_MUSIC_PROVIDERS2 = ["elevenlabs", "replicate"];
|
|
85108
|
-
var buildCommand = new Command("build").description("Build a VibeFrame video project from STORYBOARD.md").argument("[project-dir]", "Video project directory", ".").option("--stage <stage>", `Build stage: ${VALID_STAGES2.join("|")}`, "all").option("--beat <id>", "Restrict asset/compose work to one beat id").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--max-cost <usd>", "Fail before provider spend when estimated cost exceeds this USD cap").option("--skip-narration", "Don't dispatch TTS even when beats declare narration cues").option("--skip-backdrop", "Don't dispatch image-gen even when beats declare backdrop cues").option("--skip-video", "Don't dispatch video generation even when beats declare video cues").option(
|
|
85140
|
+
var buildCommand = new Command("build").description("Build a VibeFrame video project from STORYBOARD.md").argument("[project-dir]", "Video project directory", ".").option("--stage <stage>", `Build stage: ${VALID_STAGES2.join("|")}`, "all").option("--beat <id>", "Restrict asset/compose work to one beat id").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--max-cost <usd>", "Fail before provider spend when estimated cost exceeds this USD cap").option("--skip-narration", "Don't dispatch TTS even when beats declare narration cues").option("--skip-backdrop", "Don't dispatch image-gen even when beats declare backdrop cues").option("--skip-video", "Don't dispatch video generation even when beats declare video cues").option(
|
|
85141
|
+
"--skip-keyframe",
|
|
85142
|
+
"Don't generate keyframe stills (review keyframes first with --skip-video, then build)"
|
|
85143
|
+
).option("--skip-music", "Don't dispatch music generation even when beats declare music cues").option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|openai|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", `Image provider: ${VALID_IMAGE_PROVIDERS2.join("|")}`).option("--video-provider <name>", `Video provider: ${VALID_VIDEO_PROVIDERS2.join("|")}`).option("--music-provider <name>", `Music provider: ${VALID_MUSIC_PROVIDERS2.join("|")}`).option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").addHelpText(
|
|
85109
85144
|
"after",
|
|
85110
85145
|
`
|
|
85111
85146
|
Examples:
|
|
@@ -85164,6 +85199,7 @@ Advanced equivalent: \`vibe scene build\`.`
|
|
|
85164
85199
|
skipNarration: options.skipNarration ?? false,
|
|
85165
85200
|
skipBackdrop: options.skipBackdrop ?? false,
|
|
85166
85201
|
skipVideo: options.skipVideo ?? false,
|
|
85202
|
+
skipKeyframe: options.skipKeyframe ?? false,
|
|
85167
85203
|
skipMusic: options.skipMusic ?? false,
|
|
85168
85204
|
skipRender: options.skipRender ?? false,
|
|
85169
85205
|
ttsProvider: options.tts,
|
|
@@ -85184,6 +85220,7 @@ Advanced equivalent: \`vibe scene build\`.`
|
|
|
85184
85220
|
skipNarration: options.skipNarration,
|
|
85185
85221
|
skipBackdrop: options.skipBackdrop,
|
|
85186
85222
|
skipVideo: options.skipVideo,
|
|
85223
|
+
skipKeyframe: options.skipKeyframe,
|
|
85187
85224
|
skipMusic: options.skipMusic,
|
|
85188
85225
|
ttsProvider: options.tts,
|
|
85189
85226
|
voice: options.voice,
|
|
@@ -85274,6 +85311,7 @@ Advanced equivalent: \`vibe scene build\`.`
|
|
|
85274
85311
|
skipNarration: options.skipNarration,
|
|
85275
85312
|
skipBackdrop: options.skipBackdrop,
|
|
85276
85313
|
skipVideo: options.skipVideo,
|
|
85314
|
+
skipKeyframe: options.skipKeyframe,
|
|
85277
85315
|
skipMusic: options.skipMusic,
|
|
85278
85316
|
skipRender: options.skipRender,
|
|
85279
85317
|
ttsProvider: options.tts,
|
|
@@ -33679,6 +33679,14 @@ async function createBuildPlan(opts) {
|
|
|
33679
33679
|
duration: beat.duration,
|
|
33680
33680
|
keyframe: keyframePrompt
|
|
33681
33681
|
}) : null;
|
|
33682
|
+
const keyframeCache = keyframePrompt ? keyframeCacheDescriptor({
|
|
33683
|
+
beatId: beat.id,
|
|
33684
|
+
cue: keyframePrompt,
|
|
33685
|
+
provider: resolved.image.resolved,
|
|
33686
|
+
quality: imageQuality,
|
|
33687
|
+
size: imageSize2,
|
|
33688
|
+
ratio: imageRatio
|
|
33689
|
+
}) : null;
|
|
33682
33690
|
const musicCache = musicPrompt && !musicReference ? musicCacheDescriptor({
|
|
33683
33691
|
beatId: beat.id,
|
|
33684
33692
|
cue: musicPrompt,
|
|
@@ -33714,6 +33722,19 @@ async function createBuildPlan(opts) {
|
|
|
33714
33722
|
cost: backdropCostUsd(imageQuality),
|
|
33715
33723
|
active: includeAssets
|
|
33716
33724
|
}) : null;
|
|
33725
|
+
const keyframe = keyframePrompt && !opts.skipKeyframe ? assetPlan({
|
|
33726
|
+
kind: "keyframe",
|
|
33727
|
+
beatId: beat.id,
|
|
33728
|
+
cue: keyframePrompt,
|
|
33729
|
+
provider: resolved.image.resolved,
|
|
33730
|
+
path: `assets/keyframe-${beat.id}.png`,
|
|
33731
|
+
cache: keyframeCache,
|
|
33732
|
+
reference: null,
|
|
33733
|
+
projectDir,
|
|
33734
|
+
force: opts.force,
|
|
33735
|
+
cost: backdropCostUsd(imageQuality),
|
|
33736
|
+
active: includeAssets
|
|
33737
|
+
}) : null;
|
|
33717
33738
|
const video = videoCue && !opts.skipVideo ? assetPlan({
|
|
33718
33739
|
kind: "video",
|
|
33719
33740
|
beatId: beat.id,
|
|
@@ -33746,7 +33767,7 @@ async function createBuildPlan(opts) {
|
|
|
33746
33767
|
}) : null;
|
|
33747
33768
|
const compositionPath = `compositions/scene-${beat.id}.html`;
|
|
33748
33769
|
const compositionExists = existsSync17(join15(projectDir, compositionPath));
|
|
33749
|
-
for (const asset of [narration, backdrop, video, music]) {
|
|
33770
|
+
for (const asset of [narration, backdrop, keyframe, video, music]) {
|
|
33750
33771
|
if (!asset) continue;
|
|
33751
33772
|
if (asset.referenceError) {
|
|
33752
33773
|
missing.add("assets");
|
|
@@ -33771,13 +33792,9 @@ async function createBuildPlan(opts) {
|
|
|
33771
33792
|
if (narration?.willGenerate) noteProviderNeed(resolved.narration, "Narration");
|
|
33772
33793
|
if (backdrop?.willGenerate && isSupportedBuildImageProvider(resolved.image.resolved))
|
|
33773
33794
|
noteProviderNeed(resolved.image, "Backdrop generation");
|
|
33795
|
+
if (keyframe?.willGenerate && isSupportedBuildImageProvider(resolved.image.resolved))
|
|
33796
|
+
noteProviderNeed(resolved.image, "Keyframe generation");
|
|
33774
33797
|
if (video?.willGenerate) noteProviderNeed(resolved.video, "Video generation");
|
|
33775
|
-
if (keyframePrompt && video?.willGenerate) {
|
|
33776
|
-
estimatedCostUsd += backdropCostUsd(imageQuality);
|
|
33777
|
-
providers.add(resolved.image.resolved);
|
|
33778
|
-
if (isSupportedBuildImageProvider(resolved.image.resolved))
|
|
33779
|
-
noteProviderNeed(resolved.image, "Keyframe generation");
|
|
33780
|
-
}
|
|
33781
33798
|
if (music?.willGenerate) noteProviderNeed(resolved.music, "Music generation");
|
|
33782
33799
|
if (!compositionExists) missing.add("compositions");
|
|
33783
33800
|
if (includeCompose && !compositionExists && mode !== "agent") {
|
|
@@ -33790,7 +33807,7 @@ async function createBuildPlan(opts) {
|
|
|
33790
33807
|
heading: beat.heading,
|
|
33791
33808
|
durationSec: beat.duration ?? null,
|
|
33792
33809
|
cues: cue,
|
|
33793
|
-
assets: { narration, backdrop, video, music },
|
|
33810
|
+
assets: { narration, backdrop, keyframe, video, music },
|
|
33794
33811
|
composition: {
|
|
33795
33812
|
path: compositionPath,
|
|
33796
33813
|
exists: compositionExists
|
|
@@ -34006,6 +34023,7 @@ function providerResolutionsForPlan(resolved, beats, opts, includeAssets) {
|
|
|
34006
34023
|
return [
|
|
34007
34024
|
includeAssets && !opts.skipNarration && needsProvider("narration") ? resolved.narration : null,
|
|
34008
34025
|
includeAssets && !opts.skipBackdrop && needsProvider("backdrop") ? resolved.image : null,
|
|
34026
|
+
includeAssets && !opts.skipKeyframe && needsProvider("keyframe") ? resolved.image : null,
|
|
34009
34027
|
includeAssets && !opts.skipVideo && needsProvider("video") ? resolved.video : null,
|
|
34010
34028
|
includeAssets && !opts.skipMusic && needsProvider("music") ? resolved.music : null,
|
|
34011
34029
|
resolved.composer ?? null
|
|
@@ -34566,10 +34584,17 @@ async function executeSceneBuild(opts) {
|
|
|
34566
34584
|
const onProgress = opts.onProgress ?? (() => {
|
|
34567
34585
|
});
|
|
34568
34586
|
const mode = resolveSceneBuildMode(opts);
|
|
34569
|
-
|
|
34587
|
+
let selectedStage = opts.stage ?? (opts.skipRender ? "sync" : "all");
|
|
34588
|
+
const skipVideoStopsRender = (opts.skipVideo ?? false) && (selectedStage === "all" || selectedStage === "render");
|
|
34589
|
+
if (skipVideoStopsRender) selectedStage = "sync";
|
|
34570
34590
|
const stageReports = createEmptyStageReports();
|
|
34571
34591
|
const warnings = [];
|
|
34572
34592
|
const retryWith = [];
|
|
34593
|
+
if (skipVideoStopsRender) {
|
|
34594
|
+
warnings.push(
|
|
34595
|
+
"--skip-video: skipped render (no clips to capture). Review assets/keyframe-*.png, then build without --skip-video to animate them."
|
|
34596
|
+
);
|
|
34597
|
+
}
|
|
34573
34598
|
let sceneRepair = skippedSceneRepairSummary();
|
|
34574
34599
|
const storyboardPath = join16(projectDir, "STORYBOARD.md");
|
|
34575
34600
|
const buildPlan = await createBuildPlan({
|
|
@@ -34580,6 +34605,7 @@ async function executeSceneBuild(opts) {
|
|
|
34580
34605
|
skipNarration: opts.skipNarration,
|
|
34581
34606
|
skipBackdrop: opts.skipBackdrop,
|
|
34582
34607
|
skipVideo: opts.skipVideo,
|
|
34608
|
+
skipKeyframe: opts.skipKeyframe,
|
|
34583
34609
|
skipMusic: opts.skipMusic,
|
|
34584
34610
|
ttsProvider: opts.ttsProvider,
|
|
34585
34611
|
voice: opts.voice,
|
|
@@ -34728,7 +34754,7 @@ async function executeSceneBuild(opts) {
|
|
|
34728
34754
|
if (shouldRunStage(selectedStage, "assets")) {
|
|
34729
34755
|
onProgress({ type: "phase-start", phase: "primitives" });
|
|
34730
34756
|
const characterPaths = /* @__PURE__ */ new Map();
|
|
34731
|
-
if (!(opts.skipVideo ?? false)) {
|
|
34757
|
+
if (!((opts.skipVideo ?? false) && (opts.skipKeyframe ?? false))) {
|
|
34732
34758
|
const referenced = /* @__PURE__ */ new Set();
|
|
34733
34759
|
for (const beat of activeBeats) {
|
|
34734
34760
|
for (const name of beatCharacterNames(beat.cues)) referenced.add(name);
|
|
@@ -34765,6 +34791,7 @@ async function executeSceneBuild(opts) {
|
|
|
34765
34791
|
skipNarration: opts.skipNarration ?? false,
|
|
34766
34792
|
skipBackdrop: opts.skipBackdrop ?? false,
|
|
34767
34793
|
skipVideo: opts.skipVideo ?? false,
|
|
34794
|
+
skipKeyframe: opts.skipKeyframe ?? false,
|
|
34768
34795
|
skipMusic: opts.skipMusic ?? false,
|
|
34769
34796
|
force: opts.force ?? false,
|
|
34770
34797
|
onProgress,
|
|
@@ -35141,10 +35168,11 @@ async function executeSceneBuild(opts) {
|
|
|
35141
35168
|
});
|
|
35142
35169
|
}
|
|
35143
35170
|
async function buildBeatPrimitives(beat, ctx) {
|
|
35171
|
+
const keyframe = ctx.skipKeyframe ? await skipped("keyframe", beat.id, "--skip-keyframe", ctx) : await dispatchKeyframe(beat, ctx);
|
|
35144
35172
|
const [narration, backdrop, video, music] = await Promise.all([
|
|
35145
35173
|
ctx.skipNarration ? skipped("narration", beat.id, "--skip-narration", ctx) : dispatchNarration(beat, ctx),
|
|
35146
35174
|
ctx.skipBackdrop ? skipped("backdrop", beat.id, "--skip-backdrop", ctx) : dispatchBackdrop(beat, ctx),
|
|
35147
|
-
ctx.skipVideo ? skipped("video", beat.id, "--skip-video", ctx) : dispatchVideo(beat, ctx),
|
|
35175
|
+
ctx.skipVideo ? skipped("video", beat.id, "--skip-video", ctx) : dispatchVideo(beat, ctx, keyframe),
|
|
35148
35176
|
ctx.skipMusic ? skipped("music", beat.id, "--skip-music", ctx) : dispatchMusic(beat, ctx)
|
|
35149
35177
|
]);
|
|
35150
35178
|
return {
|
|
@@ -35188,8 +35216,8 @@ async function buildBeatPrimitives(beat, ctx) {
|
|
|
35188
35216
|
videoMetadataPath: video.metadataPath,
|
|
35189
35217
|
videoFreshness: video.freshness,
|
|
35190
35218
|
videoSourcePath: video.sourcePath,
|
|
35191
|
-
keyframeStatus:
|
|
35192
|
-
keyframePath:
|
|
35219
|
+
keyframeStatus: keyframe.status,
|
|
35220
|
+
keyframePath: keyframe.path,
|
|
35193
35221
|
musicStatus: music.status,
|
|
35194
35222
|
musicPath: music.path,
|
|
35195
35223
|
musicJobId: music.job?.id,
|
|
@@ -35760,7 +35788,21 @@ function imageProviderKeyInfo(provider) {
|
|
|
35760
35788
|
if (provider === "grok") return { configKey: "xai", envVar: "XAI_API_KEY" };
|
|
35761
35789
|
return { configKey: "openai", envVar: "OPENAI_API_KEY" };
|
|
35762
35790
|
}
|
|
35763
|
-
async function
|
|
35791
|
+
async function dispatchKeyframe(beat, ctx) {
|
|
35792
|
+
const keyframeCue = stringOrUndefined4(beat.cues?.keyframe);
|
|
35793
|
+
if (!keyframeCue) return { status: "no-cue" };
|
|
35794
|
+
const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
|
|
35795
|
+
const kf = await ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel);
|
|
35796
|
+
if (kf.status === "failed") {
|
|
35797
|
+
ctx.onProgress({ type: "keyframe-failed", beatId: beat.id, error: kf.error });
|
|
35798
|
+
return { status: "failed", error: kf.error };
|
|
35799
|
+
}
|
|
35800
|
+
ctx.onProgress(
|
|
35801
|
+
kf.status === "cached" ? { type: "keyframe-cached", beatId: beat.id, path: kf.rel } : { type: "keyframe-generated", beatId: beat.id, path: kf.rel, provider: ctx.imageProvider }
|
|
35802
|
+
);
|
|
35803
|
+
return { status: kf.status, path: kf.rel, provider: ctx.imageProvider };
|
|
35804
|
+
}
|
|
35805
|
+
async function dispatchVideo(beat, ctx, keyframe) {
|
|
35764
35806
|
const reference = assetReferenceForBeat(ctx.projectDir, "video", beat);
|
|
35765
35807
|
if (reference) return referencePrimitiveOutcome("video", beat, ctx, reference);
|
|
35766
35808
|
const keyframeCue = stringOrUndefined4(beat.cues?.keyframe);
|
|
@@ -35830,20 +35872,14 @@ async function dispatchVideo(beat, ctx) {
|
|
|
35830
35872
|
freshness: "fresh"
|
|
35831
35873
|
};
|
|
35832
35874
|
}
|
|
35833
|
-
|
|
35834
|
-
|
|
35835
|
-
|
|
35836
|
-
|
|
35837
|
-
|
|
35838
|
-
const kf = await ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel);
|
|
35839
|
-
if (kf.status === "failed") {
|
|
35840
|
-
ctx.onProgress({ type: "video-failed", beatId: beat.id, error: kf.error });
|
|
35841
|
-
return { status: "failed", error: kf.error };
|
|
35842
|
-
}
|
|
35843
|
-
keyframeStatus = kf.status;
|
|
35844
|
-
keyframeRel = kf.rel;
|
|
35845
|
-
keyframeImageAbs = join16(ctx.projectDir, kf.rel);
|
|
35875
|
+
const keyframeImageRel = keyframeCue && (keyframe.status === "generated" || keyframe.status === "cached") ? keyframe.path : void 0;
|
|
35876
|
+
if (keyframeCue && !keyframeImageRel) {
|
|
35877
|
+
const reason = keyframe.status === "failed" ? `keyframe unavailable: ${keyframe.error ?? "generation failed"}` : "keyframe not generated (build without --skip-keyframe first)";
|
|
35878
|
+
ctx.onProgress({ type: "video-skipped", beatId: beat.id, reason });
|
|
35879
|
+
return { status: "skipped" };
|
|
35846
35880
|
}
|
|
35881
|
+
const keyframeImageAbs = keyframeImageRel ? join16(ctx.projectDir, keyframeImageRel) : void 0;
|
|
35882
|
+
loadSceneBuildEnv(ctx.projectDir);
|
|
35847
35883
|
const result = await executeVideoGenerate({
|
|
35848
35884
|
prompt: prompt3,
|
|
35849
35885
|
provider: ctx.videoProvider,
|
|
@@ -35889,9 +35925,7 @@ async function dispatchVideo(beat, ctx) {
|
|
|
35889
35925
|
cachePath: cache.path,
|
|
35890
35926
|
cacheKey: cache.key,
|
|
35891
35927
|
metadataPath,
|
|
35892
|
-
freshness: "fresh"
|
|
35893
|
-
keyframeStatus,
|
|
35894
|
-
keyframePath: keyframeRel
|
|
35928
|
+
freshness: "fresh"
|
|
35895
35929
|
};
|
|
35896
35930
|
}
|
|
35897
35931
|
const job = await createAndWriteJobRecord({
|
|
@@ -35923,9 +35957,7 @@ async function dispatchVideo(beat, ctx) {
|
|
|
35923
35957
|
provider: job.provider,
|
|
35924
35958
|
cachePath: cache.path,
|
|
35925
35959
|
cacheKey: cache.key,
|
|
35926
|
-
metadataPath
|
|
35927
|
-
keyframeStatus,
|
|
35928
|
-
keyframePath: keyframeRel
|
|
35960
|
+
metadataPath
|
|
35929
35961
|
};
|
|
35930
35962
|
}
|
|
35931
35963
|
async function dispatchMusic(beat, ctx) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibeframe/cli",
|
|
3
|
-
"version": "0.113.
|
|
3
|
+
"version": "0.113.5",
|
|
4
4
|
"description": "VibeFrame CLI - agentic video workflow runtime",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"tsx": "^4.21.0",
|
|
84
84
|
"typescript": "^5.3.3",
|
|
85
85
|
"vitest": "^1.2.2",
|
|
86
|
-
"@vibeframe/
|
|
87
|
-
"@vibeframe/
|
|
86
|
+
"@vibeframe/ai-providers": "0.113.5",
|
|
87
|
+
"@vibeframe/core": "0.113.5"
|
|
88
88
|
},
|
|
89
89
|
"engines": {
|
|
90
90
|
"node": ">=20"
|