@vibeframe/cli 0.111.1 → 0.111.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/_shared/ffmpeg-gate.d.ts +3 -0
- package/dist/commands/_shared/root-sync.d.ts +2 -0
- package/dist/commands/_shared/root-sync.test.d.ts +1 -0
- package/dist/commands/_shared/scene-render.d.ts +7 -0
- package/dist/index.js +84 -8
- package/dist/tools/manifest/index.js +83 -7
- package/package.json +3 -3
|
@@ -49,6 +49,8 @@ export declare function resolveSyncedBeatDuration(opts: {
|
|
|
49
49
|
beatDuration?: number;
|
|
50
50
|
narrationPath?: string;
|
|
51
51
|
sceneDurationSec?: number;
|
|
52
|
+
/** Injectable for tests — defaults to the ffprobe-backed audio probe. */
|
|
53
|
+
probeAudioDuration?: (absPath: string) => Promise<number>;
|
|
52
54
|
}): Promise<number>;
|
|
53
55
|
/**
|
|
54
56
|
* Final narration-synced duration per beat for a project on disk. Reads
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -67,6 +67,13 @@ export interface SceneRenderResult {
|
|
|
67
67
|
/** Non-fatal warning from the audio mux pass — caller may surface to the user. */
|
|
68
68
|
audioMuxWarning?: string;
|
|
69
69
|
reportPath?: string;
|
|
70
|
+
/**
|
|
71
|
+
* True when the pre-render drift guard found narration-synced durations
|
|
72
|
+
* out of step with the composed timeline (e.g. sync stage skipped or run
|
|
73
|
+
* with stale durations) and re-synced root + scene durations first.
|
|
74
|
+
*/
|
|
75
|
+
autoSyncApplied?: boolean;
|
|
76
|
+
autoSyncWarning?: string;
|
|
70
77
|
code?: string;
|
|
71
78
|
error?: string;
|
|
72
79
|
retryWith?: string[];
|
package/dist/index.js
CHANGED
|
@@ -29414,14 +29414,15 @@ async function expectedRootSync(opts) {
|
|
|
29414
29414
|
return { block, totalDurationSec: Number(cursor.toFixed(2)), audioRefs };
|
|
29415
29415
|
}
|
|
29416
29416
|
async function resolveSyncedBeatDuration(opts) {
|
|
29417
|
-
if (opts.sceneDurationSec !== void 0) return Number(opts.sceneDurationSec.toFixed(2));
|
|
29418
29417
|
const storyboardMin = opts.beatDuration ?? 3;
|
|
29419
|
-
|
|
29418
|
+
const reported = opts.sceneDurationSec;
|
|
29419
|
+
if (!opts.narrationPath) return Number((reported ?? storyboardMin).toFixed(2));
|
|
29420
|
+
const probe = opts.probeAudioDuration ?? getAudioDuration;
|
|
29420
29421
|
try {
|
|
29421
|
-
const audioDuration = await
|
|
29422
|
-
return Number(Math.max(storyboardMin, audioDuration + 0.5).toFixed(2));
|
|
29422
|
+
const audioDuration = await probe(join5(opts.projectDir, opts.narrationPath));
|
|
29423
|
+
return Number(Math.max(storyboardMin, reported ?? 0, audioDuration + 0.5).toFixed(2));
|
|
29423
29424
|
} catch {
|
|
29424
|
-
return Number(storyboardMin.toFixed(2));
|
|
29425
|
+
return Number(Math.max(storyboardMin, reported ?? 0).toFixed(2));
|
|
29425
29426
|
}
|
|
29426
29427
|
}
|
|
29427
29428
|
async function resolveProjectBeatDurations(projectDir) {
|
|
@@ -60169,6 +60170,22 @@ var init_build_plan = __esm({
|
|
|
60169
60170
|
}
|
|
60170
60171
|
});
|
|
60171
60172
|
|
|
60173
|
+
// src/commands/_shared/ffmpeg-gate.ts
|
|
60174
|
+
function ffmpegToolsAvailable() {
|
|
60175
|
+
if (testOverride !== null) return testOverride;
|
|
60176
|
+
if (cached === null) cached = commandExists("ffmpeg") && commandExists("ffprobe");
|
|
60177
|
+
return cached;
|
|
60178
|
+
}
|
|
60179
|
+
var testOverride, cached;
|
|
60180
|
+
var init_ffmpeg_gate = __esm({
|
|
60181
|
+
"src/commands/_shared/ffmpeg-gate.ts"() {
|
|
60182
|
+
"use strict";
|
|
60183
|
+
init_exec_safe();
|
|
60184
|
+
testOverride = null;
|
|
60185
|
+
cached = null;
|
|
60186
|
+
}
|
|
60187
|
+
});
|
|
60188
|
+
|
|
60172
60189
|
// src/utils/concurrency.ts
|
|
60173
60190
|
async function mapWithConcurrency(items, limit, fn) {
|
|
60174
60191
|
if (!Number.isInteger(limit) || limit <= 0) {
|
|
@@ -60527,6 +60544,33 @@ async function executeSceneRender(opts = {}) {
|
|
|
60527
60544
|
error: `Project directory not found: ${projectDir}`
|
|
60528
60545
|
};
|
|
60529
60546
|
}
|
|
60547
|
+
if (!ffmpegToolsAvailable()) {
|
|
60548
|
+
return {
|
|
60549
|
+
success: false,
|
|
60550
|
+
kind: "render",
|
|
60551
|
+
beat: opts.beatId ?? null,
|
|
60552
|
+
code: "FFMPEG_MISSING",
|
|
60553
|
+
error: "FFmpeg (ffmpeg/ffprobe) was not found on PATH; rendering needs it for audio mux and duration checks. Install it (macOS: `brew install ffmpeg`) and retry."
|
|
60554
|
+
};
|
|
60555
|
+
}
|
|
60556
|
+
let autoSyncApplied = false;
|
|
60557
|
+
let autoSyncWarning;
|
|
60558
|
+
if (!opts.beatId && existsSync48(join24(projectDir, "STORYBOARD.md"))) {
|
|
60559
|
+
try {
|
|
60560
|
+
const rootPlan = await createProjectRootSyncPlan({ projectDir });
|
|
60561
|
+
const subPlans = await createSubCompDurationSyncPlans({
|
|
60562
|
+
projectDir,
|
|
60563
|
+
beats: await loadProjectRootSyncBeats(projectDir)
|
|
60564
|
+
});
|
|
60565
|
+
if (rootPlan.changed || subPlans.some((plan) => plan.changed)) {
|
|
60566
|
+
await executeSceneRepair({ projectDir });
|
|
60567
|
+
autoSyncApplied = true;
|
|
60568
|
+
autoSyncWarning = "Narration-synced durations drifted from the composed timeline; root and scene durations were re-synced before rendering (same pass as `vibe build --stage sync`).";
|
|
60569
|
+
}
|
|
60570
|
+
} catch (error) {
|
|
60571
|
+
autoSyncWarning = `Narration drift check skipped: ${error instanceof Error ? error.message : String(error)}`;
|
|
60572
|
+
}
|
|
60573
|
+
}
|
|
60530
60574
|
let root = opts.root ?? projectConfig.config.composition.entry;
|
|
60531
60575
|
if (opts.beatId) {
|
|
60532
60576
|
const prepared = await prepareBeatRenderRoot({
|
|
@@ -60643,7 +60687,9 @@ async function executeSceneRender(opts = {}) {
|
|
|
60643
60687
|
format: config2.format,
|
|
60644
60688
|
audioCount,
|
|
60645
60689
|
audioMuxApplied,
|
|
60646
|
-
audioMuxWarning
|
|
60690
|
+
audioMuxWarning,
|
|
60691
|
+
...autoSyncApplied ? { autoSyncApplied } : {},
|
|
60692
|
+
...autoSyncWarning ? { autoSyncWarning } : {}
|
|
60647
60693
|
};
|
|
60648
60694
|
if (opts.revealInFinder) {
|
|
60649
60695
|
try {
|
|
@@ -60903,6 +60949,10 @@ var init_scene_render = __esm({
|
|
|
60903
60949
|
"src/commands/_shared/scene-render.ts"() {
|
|
60904
60950
|
"use strict";
|
|
60905
60951
|
init_chrome();
|
|
60952
|
+
init_ffmpeg_gate();
|
|
60953
|
+
init_root_sync();
|
|
60954
|
+
init_sub_comp_duration_sync();
|
|
60955
|
+
init_scene_repair();
|
|
60906
60956
|
init_scene_lint();
|
|
60907
60957
|
init_scene_audio_scan();
|
|
60908
60958
|
init_scene_audio_mux();
|
|
@@ -60972,6 +61022,28 @@ async function executeSceneBuild(opts) {
|
|
|
60972
61022
|
providerResolution: buildPlan.providerResolution,
|
|
60973
61023
|
...result
|
|
60974
61024
|
});
|
|
61025
|
+
if (shouldRunStage(selectedStage, "assets") || shouldRunStage(selectedStage, "sync")) {
|
|
61026
|
+
if (!ffmpegToolsAvailable()) {
|
|
61027
|
+
return finishBuildResult({
|
|
61028
|
+
success: false,
|
|
61029
|
+
phase: "failed",
|
|
61030
|
+
mode,
|
|
61031
|
+
selectedStage,
|
|
61032
|
+
code: "FFMPEG_MISSING",
|
|
61033
|
+
error: "ffprobe (part of FFmpeg) was not found on PATH. Narration-synced durations cannot be computed without it.",
|
|
61034
|
+
message: "ffprobe not found on PATH.",
|
|
61035
|
+
suggestion: "Install FFmpeg (macOS: `brew install ffmpeg`), then re-run the build.",
|
|
61036
|
+
recoverable: true,
|
|
61037
|
+
beats: [],
|
|
61038
|
+
stageReports,
|
|
61039
|
+
warnings,
|
|
61040
|
+
retryWith,
|
|
61041
|
+
status: "failed",
|
|
61042
|
+
currentStage: "assets",
|
|
61043
|
+
totalLatencyMs: Date.now() - startedAt
|
|
61044
|
+
});
|
|
61045
|
+
}
|
|
61046
|
+
}
|
|
60975
61047
|
if (!buildPlan.validation.ok) {
|
|
60976
61048
|
let invalidBeats = [];
|
|
60977
61049
|
if (existsSync49(storyboardPath2)) {
|
|
@@ -62643,6 +62715,7 @@ var init_scene_build = __esm({
|
|
|
62643
62715
|
import_dotenv4 = __toESM(require_main(), 1);
|
|
62644
62716
|
init_dist2();
|
|
62645
62717
|
init_audio();
|
|
62718
|
+
init_ffmpeg_gate();
|
|
62646
62719
|
init_concurrency();
|
|
62647
62720
|
init_compose_scenes_skills();
|
|
62648
62721
|
init_compose_prompts();
|
|
@@ -67963,10 +68036,13 @@ function mapRenderResultToToolResult(result, projectDir) {
|
|
|
67963
68036
|
format: result.format,
|
|
67964
68037
|
audioCount: result.audioCount,
|
|
67965
68038
|
audioMuxApplied: result.audioMuxApplied,
|
|
67966
|
-
audioMuxWarning: result.audioMuxWarning
|
|
68039
|
+
audioMuxWarning: result.audioMuxWarning,
|
|
68040
|
+
...result.autoSyncApplied ? { autoSyncApplied: true } : {},
|
|
68041
|
+
...result.autoSyncWarning ? { autoSyncWarning: result.autoSyncWarning } : {}
|
|
67967
68042
|
},
|
|
67968
68043
|
humanLines: [
|
|
67969
68044
|
`\u2705 Render complete: ${result.absoluteOutputPath ?? result.outputPath}`,
|
|
68045
|
+
...result.autoSyncApplied ? [` auto-sync: narration-synced durations were re-applied before render`] : [],
|
|
67970
68046
|
` duration: ${((result.durationMs ?? 0) / 1e3).toFixed(1)}s`,
|
|
67971
68047
|
` frames: ${result.framesRendered ?? "?"}${result.totalFrames ? ` / ${result.totalFrames}` : ""}`,
|
|
67972
68048
|
` config: ${result.fps}fps \xB7 ${result.quality} \xB7 ${result.format}`,
|
|
@@ -73213,7 +73289,7 @@ init_source();
|
|
|
73213
73289
|
// package.json
|
|
73214
73290
|
var package_default = {
|
|
73215
73291
|
name: "@vibeframe/cli",
|
|
73216
|
-
version: "0.111.
|
|
73292
|
+
version: "0.111.3",
|
|
73217
73293
|
description: "VibeFrame CLI - agentic video workflow runtime",
|
|
73218
73294
|
private: false,
|
|
73219
73295
|
license: "MIT",
|
|
@@ -30123,14 +30123,15 @@ async function expectedRootSync(opts) {
|
|
|
30123
30123
|
return { block, totalDurationSec: Number(cursor.toFixed(2)), audioRefs };
|
|
30124
30124
|
}
|
|
30125
30125
|
async function resolveSyncedBeatDuration(opts) {
|
|
30126
|
-
if (opts.sceneDurationSec !== void 0) return Number(opts.sceneDurationSec.toFixed(2));
|
|
30127
30126
|
const storyboardMin = opts.beatDuration ?? 3;
|
|
30128
|
-
|
|
30127
|
+
const reported = opts.sceneDurationSec;
|
|
30128
|
+
if (!opts.narrationPath) return Number((reported ?? storyboardMin).toFixed(2));
|
|
30129
|
+
const probe = opts.probeAudioDuration ?? getAudioDuration;
|
|
30129
30130
|
try {
|
|
30130
|
-
const audioDuration = await
|
|
30131
|
-
return Number(Math.max(storyboardMin, audioDuration + 0.5).toFixed(2));
|
|
30131
|
+
const audioDuration = await probe(join7(opts.projectDir, opts.narrationPath));
|
|
30132
|
+
return Number(Math.max(storyboardMin, reported ?? 0, audioDuration + 0.5).toFixed(2));
|
|
30132
30133
|
} catch {
|
|
30133
|
-
return Number(storyboardMin.toFixed(2));
|
|
30134
|
+
return Number(Math.max(storyboardMin, reported ?? 0).toFixed(2));
|
|
30134
30135
|
}
|
|
30135
30136
|
}
|
|
30136
30137
|
async function resolveProjectBeatDurations(projectDir) {
|
|
@@ -31508,6 +31509,22 @@ var init_chrome = __esm({
|
|
|
31508
31509
|
}
|
|
31509
31510
|
});
|
|
31510
31511
|
|
|
31512
|
+
// src/commands/_shared/ffmpeg-gate.ts
|
|
31513
|
+
function ffmpegToolsAvailable() {
|
|
31514
|
+
if (testOverride !== null) return testOverride;
|
|
31515
|
+
if (cached === null) cached = commandExists("ffmpeg") && commandExists("ffprobe");
|
|
31516
|
+
return cached;
|
|
31517
|
+
}
|
|
31518
|
+
var testOverride, cached;
|
|
31519
|
+
var init_ffmpeg_gate = __esm({
|
|
31520
|
+
"src/commands/_shared/ffmpeg-gate.ts"() {
|
|
31521
|
+
"use strict";
|
|
31522
|
+
init_exec_safe();
|
|
31523
|
+
testOverride = null;
|
|
31524
|
+
cached = null;
|
|
31525
|
+
}
|
|
31526
|
+
});
|
|
31527
|
+
|
|
31511
31528
|
// src/commands/_shared/scene-audio-scan.ts
|
|
31512
31529
|
import { readFile as readFile15 } from "node:fs/promises";
|
|
31513
31530
|
import { resolve as resolve18 } from "node:path";
|
|
@@ -31833,6 +31850,33 @@ async function executeSceneRender(opts = {}) {
|
|
|
31833
31850
|
error: `Project directory not found: ${projectDir}`
|
|
31834
31851
|
};
|
|
31835
31852
|
}
|
|
31853
|
+
if (!ffmpegToolsAvailable()) {
|
|
31854
|
+
return {
|
|
31855
|
+
success: false,
|
|
31856
|
+
kind: "render",
|
|
31857
|
+
beat: opts.beatId ?? null,
|
|
31858
|
+
code: "FFMPEG_MISSING",
|
|
31859
|
+
error: "FFmpeg (ffmpeg/ffprobe) was not found on PATH; rendering needs it for audio mux and duration checks. Install it (macOS: `brew install ffmpeg`) and retry."
|
|
31860
|
+
};
|
|
31861
|
+
}
|
|
31862
|
+
let autoSyncApplied = false;
|
|
31863
|
+
let autoSyncWarning;
|
|
31864
|
+
if (!opts.beatId && existsSync15(join14(projectDir, "STORYBOARD.md"))) {
|
|
31865
|
+
try {
|
|
31866
|
+
const rootPlan = await createProjectRootSyncPlan({ projectDir });
|
|
31867
|
+
const subPlans = await createSubCompDurationSyncPlans({
|
|
31868
|
+
projectDir,
|
|
31869
|
+
beats: await loadProjectRootSyncBeats(projectDir)
|
|
31870
|
+
});
|
|
31871
|
+
if (rootPlan.changed || subPlans.some((plan) => plan.changed)) {
|
|
31872
|
+
await executeSceneRepair({ projectDir });
|
|
31873
|
+
autoSyncApplied = true;
|
|
31874
|
+
autoSyncWarning = "Narration-synced durations drifted from the composed timeline; root and scene durations were re-synced before rendering (same pass as `vibe build --stage sync`).";
|
|
31875
|
+
}
|
|
31876
|
+
} catch (error) {
|
|
31877
|
+
autoSyncWarning = `Narration drift check skipped: ${error instanceof Error ? error.message : String(error)}`;
|
|
31878
|
+
}
|
|
31879
|
+
}
|
|
31836
31880
|
let root = opts.root ?? projectConfig.config.composition.entry;
|
|
31837
31881
|
if (opts.beatId) {
|
|
31838
31882
|
const prepared = await prepareBeatRenderRoot({
|
|
@@ -31949,7 +31993,9 @@ async function executeSceneRender(opts = {}) {
|
|
|
31949
31993
|
format: config2.format,
|
|
31950
31994
|
audioCount,
|
|
31951
31995
|
audioMuxApplied,
|
|
31952
|
-
audioMuxWarning
|
|
31996
|
+
audioMuxWarning,
|
|
31997
|
+
...autoSyncApplied ? { autoSyncApplied } : {},
|
|
31998
|
+
...autoSyncWarning ? { autoSyncWarning } : {}
|
|
31953
31999
|
};
|
|
31954
32000
|
if (opts.revealInFinder) {
|
|
31955
32001
|
try {
|
|
@@ -32209,6 +32255,10 @@ var init_scene_render = __esm({
|
|
|
32209
32255
|
"src/commands/_shared/scene-render.ts"() {
|
|
32210
32256
|
"use strict";
|
|
32211
32257
|
init_chrome();
|
|
32258
|
+
init_ffmpeg_gate();
|
|
32259
|
+
init_root_sync();
|
|
32260
|
+
init_sub_comp_duration_sync();
|
|
32261
|
+
init_scene_repair();
|
|
32212
32262
|
init_scene_lint();
|
|
32213
32263
|
init_scene_audio_scan();
|
|
32214
32264
|
init_scene_audio_mux();
|
|
@@ -33659,6 +33709,28 @@ async function executeSceneBuild(opts) {
|
|
|
33659
33709
|
providerResolution: buildPlan.providerResolution,
|
|
33660
33710
|
...result
|
|
33661
33711
|
});
|
|
33712
|
+
if (shouldRunStage(selectedStage, "assets") || shouldRunStage(selectedStage, "sync")) {
|
|
33713
|
+
if (!ffmpegToolsAvailable()) {
|
|
33714
|
+
return finishBuildResult({
|
|
33715
|
+
success: false,
|
|
33716
|
+
phase: "failed",
|
|
33717
|
+
mode,
|
|
33718
|
+
selectedStage,
|
|
33719
|
+
code: "FFMPEG_MISSING",
|
|
33720
|
+
error: "ffprobe (part of FFmpeg) was not found on PATH. Narration-synced durations cannot be computed without it.",
|
|
33721
|
+
message: "ffprobe not found on PATH.",
|
|
33722
|
+
suggestion: "Install FFmpeg (macOS: `brew install ffmpeg`), then re-run the build.",
|
|
33723
|
+
recoverable: true,
|
|
33724
|
+
beats: [],
|
|
33725
|
+
stageReports,
|
|
33726
|
+
warnings,
|
|
33727
|
+
retryWith,
|
|
33728
|
+
status: "failed",
|
|
33729
|
+
currentStage: "assets",
|
|
33730
|
+
totalLatencyMs: Date.now() - startedAt
|
|
33731
|
+
});
|
|
33732
|
+
}
|
|
33733
|
+
}
|
|
33662
33734
|
if (!buildPlan.validation.ok) {
|
|
33663
33735
|
let invalidBeats = [];
|
|
33664
33736
|
if (existsSync19(storyboardPath)) {
|
|
@@ -35330,6 +35402,7 @@ var init_scene_build = __esm({
|
|
|
35330
35402
|
import_dotenv3 = __toESM(require_main(), 1);
|
|
35331
35403
|
init_dist2();
|
|
35332
35404
|
init_audio();
|
|
35405
|
+
init_ffmpeg_gate();
|
|
35333
35406
|
init_concurrency();
|
|
35334
35407
|
init_compose_scenes_skills();
|
|
35335
35408
|
init_compose_prompts();
|
|
@@ -53467,10 +53540,13 @@ function mapRenderResultToToolResult(result, projectDir) {
|
|
|
53467
53540
|
format: result.format,
|
|
53468
53541
|
audioCount: result.audioCount,
|
|
53469
53542
|
audioMuxApplied: result.audioMuxApplied,
|
|
53470
|
-
audioMuxWarning: result.audioMuxWarning
|
|
53543
|
+
audioMuxWarning: result.audioMuxWarning,
|
|
53544
|
+
...result.autoSyncApplied ? { autoSyncApplied: true } : {},
|
|
53545
|
+
...result.autoSyncWarning ? { autoSyncWarning: result.autoSyncWarning } : {}
|
|
53471
53546
|
},
|
|
53472
53547
|
humanLines: [
|
|
53473
53548
|
`\u2705 Render complete: ${result.absoluteOutputPath ?? result.outputPath}`,
|
|
53549
|
+
...result.autoSyncApplied ? [` auto-sync: narration-synced durations were re-applied before render`] : [],
|
|
53474
53550
|
` duration: ${((result.durationMs ?? 0) / 1e3).toFixed(1)}s`,
|
|
53475
53551
|
` frames: ${result.framesRendered ?? "?"}${result.totalFrames ? ` / ${result.totalFrames}` : ""}`,
|
|
53476
53552
|
` config: ${result.fps}fps \xB7 ${result.quality} \xB7 ${result.format}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibeframe/cli",
|
|
3
|
-
"version": "0.111.
|
|
3
|
+
"version": "0.111.3",
|
|
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/ai-providers": "0.111.
|
|
87
|
-
"@vibeframe/core": "0.111.
|
|
86
|
+
"@vibeframe/ai-providers": "0.111.3",
|
|
87
|
+
"@vibeframe/core": "0.111.3"
|
|
88
88
|
},
|
|
89
89
|
"engines": {
|
|
90
90
|
"node": ">=20"
|