@vibeframe/cli 0.77.0 → 0.79.0
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/index.js +279 -202
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -29396,7 +29396,7 @@ var init_scene = __esm({
|
|
|
29396
29396
|
init_agent_host_detect();
|
|
29397
29397
|
init_install_skill();
|
|
29398
29398
|
init_compose_prompts();
|
|
29399
|
-
sceneCommand = new Command("scene").description("Lower-level scene authoring (add, lint, styles). For project flow use `vibe init` / `vibe build` / `vibe render`.").addHelpText("after", `
|
|
29399
|
+
sceneCommand = new Command("scene").description("Lower-level scene authoring (add, lint, list-styles). For project flow use `vibe init` / `vibe build` / `vibe render`.").addHelpText("after", `
|
|
29400
29400
|
Examples:
|
|
29401
29401
|
$ vibe scene add intro --style announcement \\
|
|
29402
29402
|
--headline "Welcome to VibeFrame" # Headline-only scene
|
|
@@ -29771,7 +29771,7 @@ __export(remotion_exports, {
|
|
|
29771
29771
|
wrapComponentWithVideo: () => wrapComponentWithVideo
|
|
29772
29772
|
});
|
|
29773
29773
|
import { writeFile as writeFile10, mkdir as mkdir6, rm, copyFile as copyFile2 } from "node:fs/promises";
|
|
29774
|
-
import { existsSync as
|
|
29774
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
29775
29775
|
import { join as join7 } from "node:path";
|
|
29776
29776
|
import { tmpdir } from "node:os";
|
|
29777
29777
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -29846,7 +29846,7 @@ registerRoot(Root);
|
|
|
29846
29846
|
const depsHash = createHash3("md5").update(packageJsonStr).digest("hex").slice(0, 12);
|
|
29847
29847
|
const cacheMarker = join7(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
|
|
29848
29848
|
const cachedModules = join7(REMOTION_CACHE_DIR, "node_modules");
|
|
29849
|
-
if (
|
|
29849
|
+
if (existsSync9(cacheMarker) && existsSync9(cachedModules)) {
|
|
29850
29850
|
const { symlink } = await import("node:fs/promises");
|
|
29851
29851
|
try {
|
|
29852
29852
|
await symlink(cachedModules, join7(dir, "node_modules"), "dir");
|
|
@@ -29858,7 +29858,7 @@ registerRoot(Root);
|
|
|
29858
29858
|
try {
|
|
29859
29859
|
await mkdir6(REMOTION_CACHE_DIR, { recursive: true });
|
|
29860
29860
|
await writeFile10(join7(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
|
|
29861
|
-
if (
|
|
29861
|
+
if (existsSync9(join7(dir, "node_modules"))) {
|
|
29862
29862
|
const { rename: rename5, symlink } = await import("node:fs/promises");
|
|
29863
29863
|
await rename5(join7(dir, "node_modules"), cachedModules).catch(() => {
|
|
29864
29864
|
});
|
|
@@ -30526,7 +30526,7 @@ __export(ai_motion_exports, {
|
|
|
30526
30526
|
registerMotionCommand: () => registerMotionCommand
|
|
30527
30527
|
});
|
|
30528
30528
|
import { resolve as resolve12 } from "node:path";
|
|
30529
|
-
import { existsSync as
|
|
30529
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
30530
30530
|
import { readFile as readFile10, writeFile as writeFile11 } from "node:fs/promises";
|
|
30531
30531
|
async function executeMotion(options) {
|
|
30532
30532
|
const modelAlias = options.model || "sonnet";
|
|
@@ -30574,7 +30574,7 @@ Use this image analysis to inform the color palette, typography placement, and o
|
|
|
30574
30574
|
let result;
|
|
30575
30575
|
if (options.fromTsx) {
|
|
30576
30576
|
const tsxPath = resolve12(process.cwd(), options.fromTsx);
|
|
30577
|
-
if (!
|
|
30577
|
+
if (!existsSync10(tsxPath)) {
|
|
30578
30578
|
return { success: false, error: `TSX file not found: ${tsxPath}` };
|
|
30579
30579
|
}
|
|
30580
30580
|
const existingCode = await readFile10(tsxPath, "utf-8");
|
|
@@ -30703,7 +30703,7 @@ Use this image analysis to inform the color palette, typography placement, and o
|
|
|
30703
30703
|
return { success: true, codePath, componentName: component.name, renderedPath: renderResult.outputPath };
|
|
30704
30704
|
}
|
|
30705
30705
|
function registerMotionCommand(aiCommand) {
|
|
30706
|
-
aiCommand.command("motion").description("Generate motion graphics using Claude + Remotion (render & composite)").argument("<description>", "Natural language description of the motion graphic").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output file path", "motion.tsx").option("-d, --duration <sec>", "Duration in seconds", "5").option("
|
|
30706
|
+
aiCommand.command("motion").description("Generate motion graphics using Claude + Remotion (render & composite)").argument("<description>", "Natural language description of the motion graphic").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output file path", "motion.tsx").option("-d, --duration <sec>", "Duration in seconds", "5").option("--width <px>", "Width in pixels", "1920").option("--height <px>", "Height in pixels", "1080").option("--fps <fps>", "Frame rate", "30").option("--style <style>", "Style preset: minimal, corporate, playful, cinematic").option("--render", "Render the generated code with Remotion (output .webm)").option("--video <path>", "Base video to composite the motion graphic onto").option("--image <path>", "Image to analyze with Gemini \u2014 color/mood fed into Claude prompt").option("--from-tsx <path>", "Refine an existing TSX file instead of generating from scratch").option("-m, --model <alias>", "LLM model: sonnet (default), opus, gemini, gemini-3.1-pro", "sonnet").option("--dry-run", "Preview parameters without executing").action(async (description, options) => {
|
|
30707
30707
|
const startedAt = Date.now();
|
|
30708
30708
|
try {
|
|
30709
30709
|
if (options.output) {
|
|
@@ -31307,7 +31307,7 @@ async function executeStoryboard(options) {
|
|
|
31307
31307
|
}
|
|
31308
31308
|
}
|
|
31309
31309
|
function registerStoryboardCommand(parent) {
|
|
31310
|
-
parent.command("storyboard").description("Generate video storyboard from content using Claude").argument("<content>", "Content to analyze (text or file path)").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output JSON file path").option("-d, --duration <sec>", "Target total duration in seconds").option("
|
|
31310
|
+
parent.command("storyboard").description("Generate video storyboard from content using Claude").argument("<content>", "Content to analyze (text or file path)").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output JSON file path").option("-d, --duration <sec>", "Target total duration in seconds").option("--file", "Treat content argument as file path").option("--creativity <level>", "Creativity level: low (default, consistent) or high (varied, unexpected)", "low").option("--dry-run", "Preview parameters without executing").action(async (content, options) => {
|
|
31311
31311
|
const startedAt = Date.now();
|
|
31312
31312
|
try {
|
|
31313
31313
|
rejectControlChars(content);
|
|
@@ -31625,7 +31625,7 @@ async function executeSpeech(options) {
|
|
|
31625
31625
|
}
|
|
31626
31626
|
}
|
|
31627
31627
|
function registerSpeechCommand(parent) {
|
|
31628
|
-
parent.command("speech").alias("tts").description("Generate speech from text using ElevenLabs").argument("[text]", "Text to convert to speech (interactive if omitted)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-o, --output <path>", "Output audio file path", "output.mp3").option("
|
|
31628
|
+
parent.command("speech").alias("tts").description("Generate speech from text using ElevenLabs").argument("[text]", "Text to convert to speech (interactive if omitted)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-o, --output <path>", "Output audio file path", "output.mp3").option("--voice <id>", "Voice ID (default: Rachel)", "21m00Tcm4TlvDq8ikWAM").option("--list-voices", "List available voices").option("--fit-duration <seconds>", "Speed up audio to fit target duration (via FFmpeg atempo)", parseFloat).option("--dry-run", "Preview parameters without executing").action(async (text, options) => {
|
|
31629
31629
|
const startedAt = Date.now();
|
|
31630
31630
|
try {
|
|
31631
31631
|
if (!text) {
|
|
@@ -31778,7 +31778,7 @@ var init_speech = __esm({
|
|
|
31778
31778
|
// src/commands/generate/music.ts
|
|
31779
31779
|
import { resolve as resolve17 } from "node:path";
|
|
31780
31780
|
import { writeFile as writeFile16 } from "node:fs/promises";
|
|
31781
|
-
import { existsSync as
|
|
31781
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
31782
31782
|
async function executeMusic(options) {
|
|
31783
31783
|
try {
|
|
31784
31784
|
const provider = options.provider || "elevenlabs";
|
|
@@ -31835,7 +31835,7 @@ async function executeMusic(options) {
|
|
|
31835
31835
|
}
|
|
31836
31836
|
}
|
|
31837
31837
|
function registerMusicCommand(parent) {
|
|
31838
|
-
parent.command("music").description("Generate background music from a text prompt (ElevenLabs or Replicate MusicGen)").argument("<prompt>", "Description of the music to generate").option("-p, --provider <provider>", "Provider: elevenlabs (default, up to 10min), replicate (MusicGen, max 30s)", "elevenlabs").option("-k, --api-key <key>", "API key (or set ELEVENLABS_API_KEY / REPLICATE_API_TOKEN env)").option("-d, --duration <seconds>", "Duration in seconds (elevenlabs: 3-600, replicate: 1-30)", "8").option("--instrumental", "Force instrumental music, no vocals (ElevenLabs only)").option("
|
|
31838
|
+
parent.command("music").description("Generate background music from a text prompt (ElevenLabs or Replicate MusicGen)").argument("<prompt>", "Description of the music to generate").option("-p, --provider <provider>", "Provider: elevenlabs (default, up to 10min), replicate (MusicGen, max 30s)", "elevenlabs").option("-k, --api-key <key>", "API key (or set ELEVENLABS_API_KEY / REPLICATE_API_TOKEN env)").option("-d, --duration <seconds>", "Duration in seconds (elevenlabs: 3-600, replicate: 1-30)", "8").option("--instrumental", "Force instrumental music, no vocals (ElevenLabs only)").option("--melody <file>", "Reference melody audio file for conditioning (Replicate only)").option("-m, --model <model>", "Model variant (Replicate only): large, stereo-large, melody-large, stereo-melody-large", "stereo-large").option("-o, --output <path>", "Output audio file path", "music.mp3").option("--no-wait", "Don't wait for generation to complete (Replicate async mode)").option("--dry-run", "Preview parameters without executing").action(async (prompt3, options) => {
|
|
31839
31839
|
const startedAt = Date.now();
|
|
31840
31840
|
try {
|
|
31841
31841
|
rejectControlChars(prompt3);
|
|
@@ -31913,7 +31913,7 @@ function registerMusicCommand(parent) {
|
|
|
31913
31913
|
if (options.melody) {
|
|
31914
31914
|
spinner2.text = "Uploading melody reference...";
|
|
31915
31915
|
const absPath = resolve17(process.cwd(), options.melody);
|
|
31916
|
-
if (!
|
|
31916
|
+
if (!existsSync11(absPath)) {
|
|
31917
31917
|
spinner2.fail(`Melody file not found: ${options.melody}`);
|
|
31918
31918
|
exitWithError(notFoundError(options.melody));
|
|
31919
31919
|
}
|
|
@@ -32004,7 +32004,7 @@ __export(ai_image_exports, {
|
|
|
32004
32004
|
});
|
|
32005
32005
|
import { resolve as resolve18, dirname as dirname5 } from "node:path";
|
|
32006
32006
|
import { readFile as readFile12, writeFile as writeFile17, mkdir as mkdir8 } from "node:fs/promises";
|
|
32007
|
-
import { existsSync as
|
|
32007
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
32008
32008
|
async function executeImageGenerate(options) {
|
|
32009
32009
|
const {
|
|
32010
32010
|
prompt: prompt3,
|
|
@@ -32148,7 +32148,7 @@ async function executeGeminiEdit(options) {
|
|
|
32148
32148
|
const imageBuffers = [];
|
|
32149
32149
|
for (const imagePath of imagePaths) {
|
|
32150
32150
|
const absPath = resolve18(process.cwd(), imagePath);
|
|
32151
|
-
if (!
|
|
32151
|
+
if (!existsSync12(absPath)) {
|
|
32152
32152
|
return { success: false, error: `Image not found: ${absPath}` };
|
|
32153
32153
|
}
|
|
32154
32154
|
const buffer = await readFile12(absPath);
|
|
@@ -32196,7 +32196,7 @@ async function executeThumbnailBestFrame(options) {
|
|
|
32196
32196
|
model = "flash",
|
|
32197
32197
|
apiKey
|
|
32198
32198
|
} = options;
|
|
32199
|
-
if (!
|
|
32199
|
+
if (!existsSync12(videoPath)) {
|
|
32200
32200
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
32201
32201
|
}
|
|
32202
32202
|
if (!commandExists("ffmpeg")) {
|
|
@@ -32236,7 +32236,7 @@ async function executeThumbnailBestFrame(options) {
|
|
|
32236
32236
|
reason = reasonMatch[1];
|
|
32237
32237
|
}
|
|
32238
32238
|
await execSafe("ffmpeg", ["-ss", String(timestamp), "-i", videoPath, "-frames:v", "1", "-q:v", "2", outputPath, "-y"], { timeout: 6e4, maxBuffer: 50 * 1024 * 1024 });
|
|
32239
|
-
if (!
|
|
32239
|
+
if (!existsSync12(outputPath)) {
|
|
32240
32240
|
return { success: false, error: "FFmpeg failed to extract frame" };
|
|
32241
32241
|
}
|
|
32242
32242
|
return {
|
|
@@ -32262,10 +32262,10 @@ var init_ai_image = __esm({
|
|
|
32262
32262
|
|
|
32263
32263
|
// src/commands/generate/thumbnail.ts
|
|
32264
32264
|
import { resolve as resolve19, dirname as dirname6, basename as basename3, extname as extname2 } from "node:path";
|
|
32265
|
-
import { existsSync as
|
|
32265
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
32266
32266
|
import { writeFile as writeFile18, mkdir as mkdir9 } from "node:fs/promises";
|
|
32267
32267
|
function registerThumbnailCommand(parent) {
|
|
32268
|
-
parent.command("thumbnail").description("Generate video thumbnail (DALL-E) or extract best frame from video (Gemini)").argument("[description]", "Thumbnail description (for DALL-E generation)").option("-k, --api-key <key>", "API key (OpenAI for generation, Google for best-frame)").option("-o, --output <path>", "Output file path").option("
|
|
32268
|
+
parent.command("thumbnail").description("Generate video thumbnail (DALL-E) or extract best frame from video (Gemini)").argument("[description]", "Thumbnail description (for DALL-E generation)").option("-k, --api-key <key>", "API key (OpenAI for generation, Google for best-frame)").option("-o, --output <path>", "Output file path").option("--style <style>", "Platform style: youtube, instagram, tiktok, twitter").option("--best-frame <video>", "Extract best thumbnail frame from video using Gemini AI").option("--prompt <prompt>", "Custom prompt for best-frame analysis").option("--model <model>", "Gemini model: flash, latest, pro (default: flash)", "flash").action(async (description, options) => {
|
|
32269
32269
|
const startedAt = Date.now();
|
|
32270
32270
|
try {
|
|
32271
32271
|
if (description) rejectControlChars(description);
|
|
@@ -32274,7 +32274,7 @@ function registerThumbnailCommand(parent) {
|
|
|
32274
32274
|
}
|
|
32275
32275
|
if (options.bestFrame) {
|
|
32276
32276
|
const absVideoPath = resolve19(process.cwd(), options.bestFrame);
|
|
32277
|
-
if (!
|
|
32277
|
+
if (!existsSync13(absVideoPath)) {
|
|
32278
32278
|
exitWithError(notFoundError(absVideoPath));
|
|
32279
32279
|
}
|
|
32280
32280
|
if (!commandExists("ffmpeg")) {
|
|
@@ -32431,7 +32431,7 @@ function getStatusColor(status) {
|
|
|
32431
32431
|
}
|
|
32432
32432
|
}
|
|
32433
32433
|
function registerVideoStatusCommand(parent) {
|
|
32434
|
-
parent.command("video-status", { hidden: true }).description("Check video generation status (Grok, Runway, or Kling)").argument("<task-id>", "Task ID from video generation").option("-p, --provider <provider>", "Provider: grok, runway, kling", "grok").option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY env)").option("
|
|
32434
|
+
parent.command("video-status", { hidden: true }).description("Check video generation status (Grok, Runway, or Kling)").argument("<task-id>", "Task ID from video generation").option("-p, --provider <provider>", "Provider: grok, runway, kling", "grok").option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY env)").option("--type <type>", "Task type: text2video or image2video (Kling only)", "text2video").option("--wait", "Wait for completion").option("-o, --output <path>", "Download video when complete").action(async (taskId, options) => {
|
|
32435
32435
|
const startedAt = Date.now();
|
|
32436
32436
|
try {
|
|
32437
32437
|
const provider = (options.provider || "grok").toLowerCase();
|
|
@@ -32664,7 +32664,7 @@ var init_video_status = __esm({
|
|
|
32664
32664
|
import { resolve as resolve21 } from "node:path";
|
|
32665
32665
|
import { writeFile as writeFile20 } from "node:fs/promises";
|
|
32666
32666
|
function registerVideoExtendCommand(parent) {
|
|
32667
|
-
parent.command("video-extend", { hidden: true }).description("Extend video duration (Kling by video ID, Veo by operation name)").argument("<id>", "Kling video ID or Veo operation name").option("-p, --provider <provider>", "Provider: kling, veo", "kling").option("-k, --api-key <key>", "API key (KLING_API_KEY or GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path").option("--prompt <text>", "Continuation prompt").option("-d, --duration <sec>", "Duration: 5 or 10 (Kling), 4/6/8 (Veo)", "5").option("
|
|
32667
|
+
parent.command("video-extend", { hidden: true }).description("Extend video duration (Kling by video ID, Veo by operation name)").argument("<id>", "Kling video ID or Veo operation name").option("-p, --provider <provider>", "Provider: kling, veo", "kling").option("-k, --api-key <key>", "API key (KLING_API_KEY or GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path").option("--prompt <text>", "Continuation prompt").option("-d, --duration <sec>", "Duration: 5 or 10 (Kling), 4/6/8 (Veo)", "5").option("--negative <prompt>", "Negative prompt (what to avoid, Kling only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast", "3.1").option("--no-wait", "Start extension and return task ID without waiting").option("--dry-run", "Preview parameters without executing").action(async (id, options) => {
|
|
32668
32668
|
const startedAt = Date.now();
|
|
32669
32669
|
try {
|
|
32670
32670
|
const provider = (options.provider || "kling").toLowerCase();
|
|
@@ -32947,7 +32947,7 @@ import { resolve as resolve22, dirname as dirname7 } from "node:path";
|
|
|
32947
32947
|
import { fileURLToPath } from "node:url";
|
|
32948
32948
|
import { writeFile as writeFile21, mkdir as mkdir10 } from "node:fs/promises";
|
|
32949
32949
|
function registerImageCommand(parent) {
|
|
32950
|
-
parent.command("image").alias("img").description("Generate image using AI (Gemini, OpenAI gpt-image, Grok, or Runway)").argument("[prompt]", "Image description prompt (interactive if omitted)").option("-p, --provider <provider>", "Provider: openai (default when OPENAI_API_KEY set), gemini, grok, runway").option("-k, --api-key <key>", "API key (or set env: OPENAI_API_KEY, GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path (downloads image)").option("
|
|
32950
|
+
parent.command("image").alias("img").description("Generate image using AI (Gemini, OpenAI gpt-image, Grok, or Runway)").argument("[prompt]", "Image description prompt (interactive if omitted)").option("-p, --provider <provider>", "Provider: openai (default when OPENAI_API_KEY set), gemini, grok, runway").option("-k, --api-key <key>", "API key (or set env: OPENAI_API_KEY, GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path (downloads image)").option("--size <size>", "Image size (openai: 1024x1024, 1536x1024, 1024x1536)", "1024x1024").option("-r, --ratio <ratio>", "Aspect ratio (gemini: 1:1, 1:4, 1:8, 4:1, 8:1, 16:9, 9:16, 3:4, 4:3, etc.)", "1:1").option("--quality <quality>", "Quality: standard, hd (openai only)", "standard").option("--style <style>", "Style: vivid, natural (openai only)", "vivid").option("--count <n>", "Number of images to generate", "1").option("-m, --model <model>", "Model. Gemini: flash, 3.1-flash, latest, pro. OpenAI: 1.5 (default), 2 (gpt-image-2)").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
|
|
32951
32951
|
Examples:
|
|
32952
32952
|
$ vibe generate image "a sunset over the ocean" -o sunset.png
|
|
32953
32953
|
$ vibe gen img "logo design" -o logo.png -p openai
|
|
@@ -32975,6 +32975,15 @@ Examples:
|
|
|
32975
32975
|
if (options.output) {
|
|
32976
32976
|
validateOutputPath(options.output);
|
|
32977
32977
|
}
|
|
32978
|
+
if (options.count !== void 0) {
|
|
32979
|
+
const n = parseInt(options.count, 10);
|
|
32980
|
+
if (!Number.isFinite(n) || n < 1 || n > 10) {
|
|
32981
|
+
exitWithError(usageError(
|
|
32982
|
+
`Invalid --count: ${options.count}`,
|
|
32983
|
+
"Must be an integer between 1 and 10."
|
|
32984
|
+
));
|
|
32985
|
+
}
|
|
32986
|
+
}
|
|
32978
32987
|
const imageRegistry = getProvidersFor("image");
|
|
32979
32988
|
const validProviders = [...imageRegistry.map((p) => p.name), "runway"];
|
|
32980
32989
|
const providerEnvMap = Object.fromEntries(
|
|
@@ -34654,7 +34663,7 @@ var init_video_providers = __esm({
|
|
|
34654
34663
|
// src/commands/ai-script-pipeline.ts
|
|
34655
34664
|
import { readFile as readFile13, writeFile as writeFile23, unlink as unlink2, rename as rename2 } from "node:fs/promises";
|
|
34656
34665
|
import { resolve as resolve24, extname as extname3 } from "node:path";
|
|
34657
|
-
import { existsSync as
|
|
34666
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
34658
34667
|
async function executeRegenerateScene(options) {
|
|
34659
34668
|
const result = {
|
|
34660
34669
|
success: false,
|
|
@@ -34663,12 +34672,12 @@ async function executeRegenerateScene(options) {
|
|
|
34663
34672
|
};
|
|
34664
34673
|
try {
|
|
34665
34674
|
const outputDir = resolve24(process.cwd(), options.projectDir);
|
|
34666
|
-
if (!
|
|
34675
|
+
if (!existsSync14(outputDir)) {
|
|
34667
34676
|
return { ...result, error: `Project directory not found: ${outputDir}` };
|
|
34668
34677
|
}
|
|
34669
34678
|
const yamlPath = resolve24(outputDir, "storyboard.yaml");
|
|
34670
34679
|
const jsonPath = resolve24(outputDir, "storyboard.json");
|
|
34671
|
-
const storyboardPath =
|
|
34680
|
+
const storyboardPath = existsSync14(yamlPath) ? yamlPath : existsSync14(jsonPath) ? jsonPath : null;
|
|
34672
34681
|
if (!storyboardPath) {
|
|
34673
34682
|
return { ...result, error: `Storyboard not found in: ${outputDir} (expected storyboard.yaml or storyboard.json)` };
|
|
34674
34683
|
}
|
|
@@ -34761,14 +34770,14 @@ IMPORTANT - Character appearance must match exactly: ${characterDesc}`;
|
|
|
34761
34770
|
const refSceneNum = options.referenceScene;
|
|
34762
34771
|
if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
|
|
34763
34772
|
const refImagePath = resolve24(outputDir, `scene-${refSceneNum}.png`);
|
|
34764
|
-
if (
|
|
34773
|
+
if (existsSync14(refImagePath)) {
|
|
34765
34774
|
referenceImageBuffer = await readFile13(refImagePath);
|
|
34766
34775
|
}
|
|
34767
34776
|
} else if (!refSceneNum) {
|
|
34768
34777
|
for (let i = 1; i <= segments.length; i++) {
|
|
34769
34778
|
if (i !== sceneNum) {
|
|
34770
34779
|
const otherImagePath = resolve24(outputDir, `scene-${i}.png`);
|
|
34771
|
-
if (
|
|
34780
|
+
if (existsSync14(otherImagePath)) {
|
|
34772
34781
|
referenceImageBuffer = await readFile13(otherImagePath);
|
|
34773
34782
|
break;
|
|
34774
34783
|
}
|
|
@@ -34857,7 +34866,7 @@ Generate the single-person scene image now.`;
|
|
|
34857
34866
|
}
|
|
34858
34867
|
if (regenerateVideo && videoApiKey) {
|
|
34859
34868
|
options.onProgress?.(`Scene ${sceneNum}: regenerating video (${options.generator || "grok"})...`);
|
|
34860
|
-
if (!
|
|
34869
|
+
if (!existsSync14(imagePath)) {
|
|
34861
34870
|
result.failedScenes.push(sceneNum);
|
|
34862
34871
|
continue;
|
|
34863
34872
|
}
|
|
@@ -35090,7 +35099,7 @@ function registerVideoCommand(parent) {
|
|
|
35090
35099
|
"-d, --duration <sec>",
|
|
35091
35100
|
"Duration in seconds. Seedance accepts 4-15 (`fal` alias supported); Kling accepts 5 or 10; Veo maps to 6 or 8.",
|
|
35092
35101
|
"5"
|
|
35093
|
-
).option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)").option("
|
|
35102
|
+
).option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)").option("--seed <number>", "Random seed for reproducibility (Runway only)").option("--mode <mode>", "Generation mode: std or pro (Kling only)", "std").option("--seedance-model <model>", "Seedance variant: quality or fast (fal.ai only)", "quality").option("--negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)").option("--resolution <res>", "Video resolution: 720p, 1080p, 4k (Veo only)").option("--last-frame <path>", "Last frame image for frame interpolation (Veo only)").option("--ref-images <paths...>", "Reference images for character consistency (Veo 3.1 only, max 3)").option("--person <mode>", "Person generation: allow_all, allow_adult (Veo only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast (default: 3.1-fast)", "3.1-fast").option("--runway-model <model>", "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)", "gen4.5").option("--no-wait", "Start generation and return task ID without waiting").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
|
|
35094
35103
|
Examples:
|
|
35095
35104
|
$ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_KEY is set
|
|
35096
35105
|
$ vibe gen vid "cinematic city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
|
|
@@ -35119,6 +35128,15 @@ Examples:
|
|
|
35119
35128
|
if (options.output) {
|
|
35120
35129
|
validateOutputPath(options.output);
|
|
35121
35130
|
}
|
|
35131
|
+
if (options.duration !== void 0) {
|
|
35132
|
+
const d = parseFloat(options.duration);
|
|
35133
|
+
if (!Number.isFinite(d) || d <= 0 || d > 60) {
|
|
35134
|
+
exitWithError(usageError(
|
|
35135
|
+
`Invalid --duration: ${options.duration}`,
|
|
35136
|
+
"Must be a positive number \u2264 60 seconds."
|
|
35137
|
+
));
|
|
35138
|
+
}
|
|
35139
|
+
}
|
|
35122
35140
|
const validProviders = ["runway", "kling", "veo", "grok", "seedance", "fal"];
|
|
35123
35141
|
const videoEnvMap = {
|
|
35124
35142
|
grok: "XAI_API_KEY",
|
|
@@ -35631,7 +35649,7 @@ Run 'vibe schema generate.<command>' for structured parameter info.
|
|
|
35631
35649
|
});
|
|
35632
35650
|
|
|
35633
35651
|
// src/commands/_shared/edit/silence-cut.ts
|
|
35634
|
-
import { existsSync as
|
|
35652
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
35635
35653
|
import { readFile as readFile15 } from "node:fs/promises";
|
|
35636
35654
|
async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
|
|
35637
35655
|
const totalDuration = await getVideoDuration(videoPath);
|
|
@@ -35763,7 +35781,7 @@ async function executeSilenceCut(options) {
|
|
|
35763
35781
|
analyzeOnly = false,
|
|
35764
35782
|
useGemini = false
|
|
35765
35783
|
} = options;
|
|
35766
|
-
if (!
|
|
35784
|
+
if (!existsSync15(videoPath)) {
|
|
35767
35785
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
35768
35786
|
}
|
|
35769
35787
|
if (!commandExists("ffmpeg")) {
|
|
@@ -35871,7 +35889,7 @@ var init_silence_cut = __esm({
|
|
|
35871
35889
|
});
|
|
35872
35890
|
|
|
35873
35891
|
// src/commands/_shared/edit/jump-cut.ts
|
|
35874
|
-
import { existsSync as
|
|
35892
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
35875
35893
|
import { readFile as readFile16, writeFile as writeFile25, mkdir as mkdir11 } from "node:fs/promises";
|
|
35876
35894
|
import { join as join8 } from "node:path";
|
|
35877
35895
|
async function transcribeWithWords(audioPath, apiKey, language) {
|
|
@@ -35934,7 +35952,7 @@ async function executeJumpCut(options) {
|
|
|
35934
35952
|
analyzeOnly = false,
|
|
35935
35953
|
apiKey
|
|
35936
35954
|
} = options;
|
|
35937
|
-
if (!
|
|
35955
|
+
if (!existsSync16(videoPath)) {
|
|
35938
35956
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
35939
35957
|
}
|
|
35940
35958
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36220,7 +36238,7 @@ var init_helpers = __esm({
|
|
|
36220
36238
|
});
|
|
36221
36239
|
|
|
36222
36240
|
// src/commands/_shared/edit/caption.ts
|
|
36223
|
-
import { existsSync as
|
|
36241
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
36224
36242
|
import { readFile as readFile17, writeFile as writeFile26, mkdir as mkdir12 } from "node:fs/promises";
|
|
36225
36243
|
import { dirname as dirname8, basename as basename5, extname as extname4, join as join9 } from "node:path";
|
|
36226
36244
|
function getCaptionForceStyle(style, fontSize, fontColor, position) {
|
|
@@ -36250,7 +36268,7 @@ async function executeCaption(options) {
|
|
|
36250
36268
|
position = "bottom",
|
|
36251
36269
|
apiKey
|
|
36252
36270
|
} = options;
|
|
36253
|
-
if (!
|
|
36271
|
+
if (!existsSync17(videoPath)) {
|
|
36254
36272
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
36255
36273
|
}
|
|
36256
36274
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36428,10 +36446,10 @@ var init_caption = __esm({
|
|
|
36428
36446
|
});
|
|
36429
36447
|
|
|
36430
36448
|
// src/commands/_shared/edit/noise-reduce.ts
|
|
36431
|
-
import { existsSync as
|
|
36449
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
36432
36450
|
async function executeNoiseReduce(options) {
|
|
36433
36451
|
const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
|
|
36434
|
-
if (!
|
|
36452
|
+
if (!existsSync18(inputPath)) {
|
|
36435
36453
|
return { success: false, error: `File not found: ${inputPath}` };
|
|
36436
36454
|
}
|
|
36437
36455
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36488,7 +36506,7 @@ var init_noise_reduce = __esm({
|
|
|
36488
36506
|
});
|
|
36489
36507
|
|
|
36490
36508
|
// src/commands/_shared/edit/fade.ts
|
|
36491
|
-
import { existsSync as
|
|
36509
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
36492
36510
|
async function executeFade(options) {
|
|
36493
36511
|
const {
|
|
36494
36512
|
videoPath,
|
|
@@ -36498,7 +36516,7 @@ async function executeFade(options) {
|
|
|
36498
36516
|
audioOnly = false,
|
|
36499
36517
|
videoOnly = false
|
|
36500
36518
|
} = options;
|
|
36501
|
-
if (!
|
|
36519
|
+
if (!existsSync19(videoPath)) {
|
|
36502
36520
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
36503
36521
|
}
|
|
36504
36522
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36565,7 +36583,7 @@ var init_fade = __esm({
|
|
|
36565
36583
|
});
|
|
36566
36584
|
|
|
36567
36585
|
// src/commands/_shared/edit/translate-srt.ts
|
|
36568
|
-
import { existsSync as
|
|
36586
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
36569
36587
|
import { readFile as readFile18, writeFile as writeFile27 } from "node:fs/promises";
|
|
36570
36588
|
async function executeTranslateSrt(options) {
|
|
36571
36589
|
const {
|
|
@@ -36576,7 +36594,7 @@ async function executeTranslateSrt(options) {
|
|
|
36576
36594
|
sourceLanguage,
|
|
36577
36595
|
apiKey
|
|
36578
36596
|
} = options;
|
|
36579
|
-
if (!
|
|
36597
|
+
if (!existsSync20(srtPath)) {
|
|
36580
36598
|
return { success: false, error: `SRT file not found: ${srtPath}` };
|
|
36581
36599
|
}
|
|
36582
36600
|
try {
|
|
@@ -36693,7 +36711,7 @@ var init_translate_srt = __esm({
|
|
|
36693
36711
|
});
|
|
36694
36712
|
|
|
36695
36713
|
// src/commands/_shared/edit/text-overlay.ts
|
|
36696
|
-
import { existsSync as
|
|
36714
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
36697
36715
|
import { resolve as resolve26 } from "node:path";
|
|
36698
36716
|
function detectSystemFont() {
|
|
36699
36717
|
const platform = process.platform;
|
|
@@ -36704,7 +36722,7 @@ function detectSystemFont() {
|
|
|
36704
36722
|
"/Library/Fonts/Arial.ttf"
|
|
36705
36723
|
];
|
|
36706
36724
|
for (const f of candidates) {
|
|
36707
|
-
if (
|
|
36725
|
+
if (existsSync21(f)) return f;
|
|
36708
36726
|
}
|
|
36709
36727
|
} else if (platform === "linux") {
|
|
36710
36728
|
const candidates = [
|
|
@@ -36713,7 +36731,7 @@ function detectSystemFont() {
|
|
|
36713
36731
|
"/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
|
|
36714
36732
|
];
|
|
36715
36733
|
for (const f of candidates) {
|
|
36716
|
-
if (
|
|
36734
|
+
if (existsSync21(f)) return f;
|
|
36717
36735
|
}
|
|
36718
36736
|
} else if (platform === "win32") {
|
|
36719
36737
|
const candidates = [
|
|
@@ -36721,7 +36739,7 @@ function detectSystemFont() {
|
|
|
36721
36739
|
"C:\\Windows\\Fonts\\segoeui.ttf"
|
|
36722
36740
|
];
|
|
36723
36741
|
for (const f of candidates) {
|
|
36724
|
-
if (
|
|
36742
|
+
if (existsSync21(f)) return f;
|
|
36725
36743
|
}
|
|
36726
36744
|
}
|
|
36727
36745
|
return null;
|
|
@@ -36742,7 +36760,7 @@ async function applyTextOverlays(options) {
|
|
|
36742
36760
|
}
|
|
36743
36761
|
const absVideoPath = resolve26(process.cwd(), videoPath);
|
|
36744
36762
|
const absOutputPath = resolve26(process.cwd(), outputPath);
|
|
36745
|
-
if (!
|
|
36763
|
+
if (!existsSync21(absVideoPath)) {
|
|
36746
36764
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
36747
36765
|
}
|
|
36748
36766
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36923,12 +36941,12 @@ var init_ai_edit = __esm({
|
|
|
36923
36941
|
|
|
36924
36942
|
// src/commands/ai-edit-cli.ts
|
|
36925
36943
|
import { resolve as resolve27, extname as extname5, basename as basename6 } from "node:path";
|
|
36926
|
-
import { existsSync as
|
|
36944
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
36927
36945
|
function registerEditCommands(aiCommand) {
|
|
36928
|
-
aiCommand.command("silence-cut").alias("sc").description("Remove silent segments from video (FFmpeg default, or Gemini for smart detection)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-cut.<ext>)").option("
|
|
36946
|
+
aiCommand.command("silence-cut").alias("sc").description("Remove silent segments from video (FFmpeg default, or Gemini for smart detection)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-cut.<ext>)").option("--noise <dB>", "Silence threshold in dB (default: -30)", "-30").option("-d, --min-duration <seconds>", "Minimum silence duration to cut (default: 0.5)", "0.5").option("--padding <seconds>", "Padding around non-silent segments (default: 0.1)", "0.1").option("--analyze-only", "Only detect silence, don't cut").option("--use-gemini", "Use Gemini Video Understanding for context-aware silence detection").option("-m, --model <model>", "Gemini model (default: flash)").option("--low-res", "Low resolution mode for longer videos (Gemini only)").option("-k, --api-key <key>", "Google API key override (or set GOOGLE_API_KEY env)").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
|
|
36929
36947
|
Examples:
|
|
36930
36948
|
$ vibe edit silence-cut interview.mp4 -o clean.mp4
|
|
36931
|
-
$ vibe ed sc podcast.mp4 -o trimmed.mp4
|
|
36949
|
+
$ vibe ed sc podcast.mp4 -o trimmed.mp4 --noise -25 --min-duration 1.0
|
|
36932
36950
|
$ vibe ed sc video.mp4 --use-gemini -o smart-cut.mp4 # AI-powered detection
|
|
36933
36951
|
$ vibe ed sc video.mp4 --analyze-only # Detect only, no cut
|
|
36934
36952
|
$ vibe ed sc video.mp4 --dry-run --json
|
|
@@ -36940,7 +36958,7 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
36940
36958
|
validateOutputPath(options.output);
|
|
36941
36959
|
}
|
|
36942
36960
|
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
36943
|
-
if (!
|
|
36961
|
+
if (!existsSync22(absVideoPath)) {
|
|
36944
36962
|
exitWithError(notFoundError(absVideoPath));
|
|
36945
36963
|
}
|
|
36946
36964
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37026,12 +37044,12 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
37026
37044
|
exitWithError(apiError(`Silence cut failed: ${error instanceof Error ? error.message : String(error)}`, true));
|
|
37027
37045
|
}
|
|
37028
37046
|
});
|
|
37029
|
-
aiCommand.command("caption").alias("cap").description("Transcribe and burn styled captions onto video (Whisper + FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-captioned.<ext>)").option("
|
|
37047
|
+
aiCommand.command("caption").alias("cap").description("Transcribe and burn styled captions onto video (Whisper + FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-captioned.<ext>)").option("--style <style>", "Caption style: minimal, bold, outline, karaoke (default: bold)", "bold").option("--font-size <pixels>", "Override auto-calculated font size").option("--color <color>", "Font color (default: white)", "white").option("-l, --language <lang>", "Language code for transcription (e.g., en, ko)").option("--position <pos>", "Caption position: top, center, bottom (default: bottom)", "bottom").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
|
|
37030
37048
|
Examples:
|
|
37031
37049
|
$ vibe edit caption video.mp4 -o captioned.mp4
|
|
37032
|
-
$ vibe ed cap video.mp4 -o out.mp4
|
|
37050
|
+
$ vibe ed cap video.mp4 -o out.mp4 --style bold --position top
|
|
37033
37051
|
$ vibe ed cap video.mp4 -o out.mp4 -l ko # Korean transcription
|
|
37034
|
-
$ vibe ed cap video.mp4 -o out.mp4
|
|
37052
|
+
$ vibe ed cap video.mp4 -o out.mp4 --style karaoke # Karaoke-style
|
|
37035
37053
|
$ vibe ed cap video.mp4 --dry-run --json
|
|
37036
37054
|
|
|
37037
37055
|
Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoPath, options) => {
|
|
@@ -37041,7 +37059,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37041
37059
|
validateOutputPath(options.output);
|
|
37042
37060
|
}
|
|
37043
37061
|
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37044
|
-
if (!
|
|
37062
|
+
if (!existsSync22(absVideoPath)) {
|
|
37045
37063
|
exitWithError(notFoundError(absVideoPath));
|
|
37046
37064
|
}
|
|
37047
37065
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37115,14 +37133,14 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37115
37133
|
exitWithError(apiError(`Caption failed: ${error instanceof Error ? error.message : String(error)}`, true));
|
|
37116
37134
|
}
|
|
37117
37135
|
});
|
|
37118
|
-
aiCommand.command("noise-reduce").description("Remove background noise from audio/video using FFmpeg (no API key needed)").argument("<input>", "Audio or video file path").option("-o, --output <path>", "Output file path (default: <name>-denoised.<ext>)").option("
|
|
37136
|
+
aiCommand.command("noise-reduce").description("Remove background noise from audio/video using FFmpeg (no API key needed)").argument("<input>", "Audio or video file path").option("-o, --output <path>", "Output file path (default: <name>-denoised.<ext>)").option("--strength <level>", "Noise reduction strength: low, medium, high (default: medium)", "medium").option("--noise-floor <dB>", "Custom noise floor in dB (overrides strength preset)").option("--dry-run", "Preview parameters without executing").action(async (inputPath, options) => {
|
|
37119
37137
|
const startedAt = Date.now();
|
|
37120
37138
|
try {
|
|
37121
37139
|
if (options.output) {
|
|
37122
37140
|
validateOutputPath(options.output);
|
|
37123
37141
|
}
|
|
37124
37142
|
const absInputPath = resolve27(process.cwd(), inputPath);
|
|
37125
|
-
if (!
|
|
37143
|
+
if (!existsSync22(absInputPath)) {
|
|
37126
37144
|
exitWithError(notFoundError(absInputPath));
|
|
37127
37145
|
}
|
|
37128
37146
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37188,7 +37206,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37188
37206
|
validateOutputPath(options.output);
|
|
37189
37207
|
}
|
|
37190
37208
|
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37191
|
-
if (!
|
|
37209
|
+
if (!existsSync22(absVideoPath)) {
|
|
37192
37210
|
exitWithError(notFoundError(absVideoPath));
|
|
37193
37211
|
}
|
|
37194
37212
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37253,7 +37271,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37253
37271
|
exitWithError(generalError(`Fade failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
37254
37272
|
}
|
|
37255
37273
|
});
|
|
37256
|
-
aiCommand.command("translate-srt").description("Translate SRT subtitle file to another language (Claude
|
|
37274
|
+
aiCommand.command("translate-srt").description("Translate SRT subtitle file to another language (Claude or OpenAI)").argument("<srt>", "SRT file path").option("--target <language>", "Target language (e.g., ko, es, fr, ja, zh)").option("-o, --output <path>", "Output file path (default: <name>-<target>.srt)").option("-p, --provider <provider>", "Translation provider: claude, openai (default: claude)", "claude").option("--source <language>", "Source language (auto-detected if omitted)").option("-k, --api-key <key>", "API key (or set ANTHROPIC_API_KEY / OPENAI_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (srtPath, options) => {
|
|
37257
37275
|
const startedAt = Date.now();
|
|
37258
37276
|
try {
|
|
37259
37277
|
if (options.output) {
|
|
@@ -37263,7 +37281,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37263
37281
|
exitWithError(usageError("Target language required. Use -t or --target"));
|
|
37264
37282
|
}
|
|
37265
37283
|
const absSrtPath = resolve27(process.cwd(), srtPath);
|
|
37266
|
-
if (!
|
|
37284
|
+
if (!existsSync22(absSrtPath)) {
|
|
37267
37285
|
exitWithError(notFoundError(absSrtPath));
|
|
37268
37286
|
}
|
|
37269
37287
|
if (options.dryRun) {
|
|
@@ -37339,7 +37357,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37339
37357
|
}
|
|
37340
37358
|
if (options.fillers) rejectControlChars(options.fillers);
|
|
37341
37359
|
const absVideoPath = resolve27(process.cwd(), videoPath);
|
|
37342
|
-
if (!
|
|
37360
|
+
if (!existsSync22(absVideoPath)) {
|
|
37343
37361
|
exitWithError(notFoundError(absVideoPath));
|
|
37344
37362
|
}
|
|
37345
37363
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37439,7 +37457,7 @@ var init_ai_edit_cli = __esm({
|
|
|
37439
37457
|
});
|
|
37440
37458
|
|
|
37441
37459
|
// src/commands/_shared/execute-fill-gaps.ts
|
|
37442
|
-
import { existsSync as
|
|
37460
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
37443
37461
|
import { readFile as readFile19, writeFile as writeFile28, mkdir as mkdir13, rename as renameFs } from "node:fs/promises";
|
|
37444
37462
|
import { resolve as resolve28, dirname as dirname9 } from "node:path";
|
|
37445
37463
|
function detectVideoGaps(videoClips, totalDuration) {
|
|
@@ -37522,7 +37540,7 @@ async function executeFillGaps(options) {
|
|
|
37522
37540
|
try {
|
|
37523
37541
|
onProgress("Loading project...");
|
|
37524
37542
|
const filePath = resolve28(process.cwd(), options.projectPath);
|
|
37525
|
-
if (!
|
|
37543
|
+
if (!existsSync23(filePath)) {
|
|
37526
37544
|
return {
|
|
37527
37545
|
success: false,
|
|
37528
37546
|
error: `Project file not found: ${filePath}`,
|
|
@@ -37657,7 +37675,7 @@ async function executeFillGaps(options) {
|
|
|
37657
37675
|
}
|
|
37658
37676
|
const projectDir = dirname9(filePath);
|
|
37659
37677
|
const footageDir = options.dir ? resolve28(process.cwd(), options.dir) : resolve28(projectDir, "footage");
|
|
37660
|
-
if (!
|
|
37678
|
+
if (!existsSync23(footageDir)) {
|
|
37661
37679
|
await mkdir13(footageDir, { recursive: true });
|
|
37662
37680
|
}
|
|
37663
37681
|
humanLines.push("Generating AI Videos");
|
|
@@ -37919,7 +37937,7 @@ var init_execute_fill_gaps = __esm({
|
|
|
37919
37937
|
|
|
37920
37938
|
// src/commands/ai-fill-gaps.ts
|
|
37921
37939
|
function registerFillGapsCommand(aiCommand) {
|
|
37922
|
-
aiCommand.command("fill-gaps").description("Fill timeline gaps with AI-generated video (Kling image-to-video)").argument("<project>", "Project file path").option("-p, --provider <provider>", "AI provider (kling)", "kling").option("-o, --output <path>", "Output project path (default: overwrite)").option("-d, --dir <path>", "Directory to save generated videos").option("--prompt <text>", "Custom prompt for video generation").option("--dry-run", "Show gaps without generating").option("
|
|
37940
|
+
aiCommand.command("fill-gaps").description("Fill timeline gaps with AI-generated video (Kling image-to-video)").argument("<project>", "Project file path").option("-p, --provider <provider>", "AI provider (kling)", "kling").option("-o, --output <path>", "Output project path (default: overwrite)").option("-d, --dir <path>", "Directory to save generated videos").option("--prompt <text>", "Custom prompt for video generation").option("--dry-run", "Show gaps without generating").option("--mode <mode>", "Generation mode: std or pro (Kling)", "std").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1", "16:9").action(async (projectPath, options) => {
|
|
37923
37941
|
try {
|
|
37924
37942
|
if (options.output) {
|
|
37925
37943
|
validateOutputPath(options.output);
|
|
@@ -38178,12 +38196,12 @@ var init_edit_cmd = __esm({
|
|
|
38178
38196
|
`
|
|
38179
38197
|
Examples:
|
|
38180
38198
|
$ vibe edit silence-cut interview.mp4 -o clean.mp4
|
|
38181
|
-
$ vibe edit caption video.mp4 -o captioned.mp4
|
|
38199
|
+
$ vibe edit caption video.mp4 -o captioned.mp4 --style bold
|
|
38182
38200
|
$ vibe edit grade video.mp4 -o graded.mp4 --preset cinematic-warm
|
|
38183
38201
|
$ vibe edit reframe landscape.mp4 -o vertical.mp4 -a 9:16
|
|
38184
38202
|
$ vibe edit image photo.png "add sunset background" -o edited.png
|
|
38185
|
-
$ vibe edit text-overlay video.mp4
|
|
38186
|
-
$ vibe edit noise-reduce noisy.mp4 -o clean.mp4
|
|
38203
|
+
$ vibe edit text-overlay video.mp4 --text "Title" --style center-bold -o out.mp4
|
|
38204
|
+
$ vibe edit noise-reduce noisy.mp4 -o clean.mp4 --strength high
|
|
38187
38205
|
$ vibe edit fade video.mp4 -o faded.mp4 --fade-in 1 --fade-out 1
|
|
38188
38206
|
|
|
38189
38207
|
API Keys (varies by subcommand):
|
|
@@ -38197,7 +38215,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38197
38215
|
);
|
|
38198
38216
|
registerEditCommands(editCommand);
|
|
38199
38217
|
registerFillGapsCommand(editCommand);
|
|
38200
|
-
editCommand.command("grade").description("Apply AI-generated color grading (Claude + FFmpeg)").argument("<video>", "Video file path").option("
|
|
38218
|
+
editCommand.command("grade").description("Apply AI-generated color grading (Claude + FFmpeg)").argument("<video>", "Video file path").option("--style <prompt>", "Style description (e.g., 'cinematic warm')").option("--preset <name>", "Built-in preset: film-noir, vintage, cinematic-warm, cool-tones, high-contrast, pastel, cyberpunk, horror").option("-o, --output <path>", "Output video file path").option("--analyze-only", "Show filter without applying").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
38201
38219
|
const startedAt = Date.now();
|
|
38202
38220
|
try {
|
|
38203
38221
|
if (options.style) rejectControlChars(options.style);
|
|
@@ -38285,7 +38303,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38285
38303
|
exitWithError(apiError(`Color grading failed: ${error.message}`));
|
|
38286
38304
|
}
|
|
38287
38305
|
});
|
|
38288
|
-
editCommand.command("text-overlay").description("Apply text overlays to video (FFmpeg drawtext)").argument("<video>", "Video file path").option("
|
|
38306
|
+
editCommand.command("text-overlay").description("Apply text overlays to video (FFmpeg drawtext)").argument("<video>", "Video file path").option("--text <texts...>", "Text lines to overlay (repeat for multiple)").option("--style <style>", "Overlay style: lower-third, center-bold, subtitle, minimal", "lower-third").option("--font-size <size>", "Font size in pixels (auto-calculated if omitted)").option("--font-color <color>", "Font color (default: white)", "white").option("--fade <seconds>", "Fade in/out duration in seconds", "0.3").option("--start <seconds>", "Start time in seconds", "0").option("--end <seconds>", "End time in seconds (default: video duration)").option("-o, --output <path>", "Output video file path").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
38289
38307
|
const startedAt = Date.now();
|
|
38290
38308
|
try {
|
|
38291
38309
|
if (!options.text || options.text.length === 0) {
|
|
@@ -38361,7 +38379,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38361
38379
|
exitWithError(generalError(`Text overlay failed: ${msg}`));
|
|
38362
38380
|
}
|
|
38363
38381
|
});
|
|
38364
|
-
editCommand.command("speed-ramp").description("Apply content-aware speed ramping (Whisper + Claude + FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output video file path").option("
|
|
38382
|
+
editCommand.command("speed-ramp").description("Apply content-aware speed ramping (Whisper + Claude + FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output video file path").option("--style <style>", "Style: dramatic, smooth, action", "dramatic").option("--min-speed <factor>", "Minimum speed factor", "0.25").option("--max-speed <factor>", "Maximum speed factor", "4.0").option("--analyze-only", "Show keyframes without applying").option("-l, --language <lang>", "Language code for transcription").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
38365
38383
|
const startedAt = Date.now();
|
|
38366
38384
|
try {
|
|
38367
38385
|
if (options.output) {
|
|
@@ -38481,7 +38499,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38481
38499
|
exitWithError(generalError(`Speed ramping failed: ${msg}`));
|
|
38482
38500
|
}
|
|
38483
38501
|
});
|
|
38484
|
-
editCommand.command("reframe").description("Auto-reframe video to different aspect ratio (Claude Vision + FFmpeg)").argument("<video>", "Video file path").option("-a, --aspect <ratio>", "Target aspect ratio: 9:16, 1:1, 4:5", "9:16").option("
|
|
38502
|
+
editCommand.command("reframe").description("Auto-reframe video to different aspect ratio (Claude Vision + FFmpeg)").argument("<video>", "Video file path").option("-a, --aspect <ratio>", "Target aspect ratio: 9:16, 1:1, 4:5", "9:16").option("--focus <mode>", "Focus mode: auto, face, center, action", "auto").option("-o, --output <path>", "Output video file path").option("--analyze-only", "Show crop regions without applying").option("--keyframes <path>", "Export keyframes to JSON file").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
38485
38503
|
const startedAt = Date.now();
|
|
38486
38504
|
try {
|
|
38487
38505
|
if (options.output) {
|
|
@@ -38628,7 +38646,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38628
38646
|
exitWithError(generalError(`Reframe failed: ${msg}`));
|
|
38629
38647
|
}
|
|
38630
38648
|
});
|
|
38631
|
-
editCommand.command("image").description("Edit image(s) using AI (Gemini
|
|
38649
|
+
editCommand.command("image").description("Edit image(s) using AI (Gemini, OpenAI, or Grok)").argument("<images...>", "Input image file(s) followed by edit prompt").option("-p, --provider <provider>", "Provider: gemini (default), openai, grok", "gemini").option("-k, --api-key <key>", "API key (or set env variable)").option("-o, --output <path>", "Output file path", "edited.png").option("-m, --model <model>", "Model: flash/3.1-flash/latest/pro (Gemini only)", "flash").option("-r, --ratio <ratio>", "Output aspect ratio").option("--size <resolution>", "Resolution: 1K, 2K, 4K (Gemini Pro only)").option("--dry-run", "Preview parameters without executing").action(async (args, options) => {
|
|
38632
38650
|
const startedAt = Date.now();
|
|
38633
38651
|
try {
|
|
38634
38652
|
if (args.length < 2) {
|
|
@@ -38757,7 +38775,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38757
38775
|
exitWithError(apiError(`Image editing failed: ${msg}`, true));
|
|
38758
38776
|
}
|
|
38759
38777
|
});
|
|
38760
|
-
editCommand.command("interpolate").description("Create slow motion with frame interpolation (FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("
|
|
38778
|
+
editCommand.command("interpolate").description("Create slow motion with frame interpolation (FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("--factor <number>", "Slow motion factor: 2, 4, or 8", "2").option("--fps <number>", "Target output FPS").option("--mode <mode>", "Speed/quality tradeoff: fast or quality", "quality").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
38761
38779
|
const startedAt = Date.now();
|
|
38762
38780
|
try {
|
|
38763
38781
|
if (options.output) {
|
|
@@ -38778,7 +38796,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38778
38796
|
videoPath: absPath,
|
|
38779
38797
|
factor,
|
|
38780
38798
|
fps: options.fps ? parseInt(options.fps) : void 0,
|
|
38781
|
-
|
|
38799
|
+
mode: options.mode
|
|
38782
38800
|
}
|
|
38783
38801
|
}
|
|
38784
38802
|
});
|
|
@@ -38801,7 +38819,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38801
38819
|
const [num, den] = fpsOut.trim().split("/").map(Number);
|
|
38802
38820
|
const originalFps = num / (den || 1);
|
|
38803
38821
|
const targetFps = options.fps ? parseInt(options.fps) : originalFps * factor;
|
|
38804
|
-
const mi = options.
|
|
38822
|
+
const mi = options.mode === "fast" ? "mi_mode=mci" : "mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1";
|
|
38805
38823
|
spinner2.text = `Interpolating frames (${originalFps.toFixed(1)} \u2192 ${targetFps}fps)...`;
|
|
38806
38824
|
await execSafe("ffmpeg", ["-i", absPath, "-filter:v", `minterpolate='${mi}:fps=${targetFps}',setpts=${factor}*PTS`, "-an", outputPath, "-y"], { timeout: 6e5 });
|
|
38807
38825
|
spinner2.succeed(source_default.green(`Created ${factor}x slow motion`));
|
|
@@ -38839,7 +38857,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38839
38857
|
exitWithError(generalError(`Frame interpolation failed: ${msg}`));
|
|
38840
38858
|
}
|
|
38841
38859
|
});
|
|
38842
|
-
editCommand.command("upscale").description("Upscale video resolution using AI or FFmpeg").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("
|
|
38860
|
+
editCommand.command("upscale").description("Upscale video resolution using AI or FFmpeg").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("--scale <factor>", "Scale factor: 2 or 4", "2").option("-m, --model <model>", "Model: real-esrgan, topaz", "real-esrgan").option("--ffmpeg", "Use FFmpeg lanczos (free, no API)").option("-k, --api-key <key>", "Replicate API token (or set REPLICATE_API_TOKEN env)").option("--no-wait", "Start processing and return task ID without waiting").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
38843
38861
|
const startedAt = Date.now();
|
|
38844
38862
|
try {
|
|
38845
38863
|
if (options.output) {
|
|
@@ -38930,7 +38948,7 @@ __export(ai_analyze_exports, {
|
|
|
38930
38948
|
});
|
|
38931
38949
|
import { readFile as readFile21 } from "node:fs/promises";
|
|
38932
38950
|
import { extname as extname6, resolve as resolve30 } from "node:path";
|
|
38933
|
-
import { existsSync as
|
|
38951
|
+
import { existsSync as existsSync24 } from "node:fs";
|
|
38934
38952
|
async function executeGeminiVideo(options) {
|
|
38935
38953
|
try {
|
|
38936
38954
|
const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
|
|
@@ -38949,7 +38967,7 @@ async function executeGeminiVideo(options) {
|
|
|
38949
38967
|
videoData = options.source;
|
|
38950
38968
|
} else {
|
|
38951
38969
|
const absPath = resolve30(process.cwd(), options.source);
|
|
38952
|
-
if (!
|
|
38970
|
+
if (!existsSync24(absPath)) {
|
|
38953
38971
|
return { success: false, error: `File not found: ${absPath}` };
|
|
38954
38972
|
}
|
|
38955
38973
|
videoData = await readFile21(absPath);
|
|
@@ -39022,7 +39040,7 @@ async function executeAnalyze(options) {
|
|
|
39022
39040
|
imageBuffer = Buffer.from(await response.arrayBuffer());
|
|
39023
39041
|
} else {
|
|
39024
39042
|
const absPath = resolve30(process.cwd(), source);
|
|
39025
|
-
if (!
|
|
39043
|
+
if (!existsSync24(absPath)) {
|
|
39026
39044
|
return { success: false, error: `File not found: ${absPath}` };
|
|
39027
39045
|
}
|
|
39028
39046
|
imageBuffer = await readFile21(absPath);
|
|
@@ -39057,7 +39075,7 @@ async function executeAnalyze(options) {
|
|
|
39057
39075
|
videoData = Buffer.from(await response.arrayBuffer());
|
|
39058
39076
|
} else {
|
|
39059
39077
|
const absPath = resolve30(process.cwd(), source);
|
|
39060
|
-
if (!
|
|
39078
|
+
if (!existsSync24(absPath)) {
|
|
39061
39079
|
return { success: false, error: `File not found: ${absPath}` };
|
|
39062
39080
|
}
|
|
39063
39081
|
videoData = await readFile21(absPath);
|
|
@@ -39196,7 +39214,7 @@ __export(ai_review_exports, {
|
|
|
39196
39214
|
});
|
|
39197
39215
|
import { readFile as readFile22, rename as rename3 } from "node:fs/promises";
|
|
39198
39216
|
import { resolve as resolve31 } from "node:path";
|
|
39199
|
-
import { existsSync as
|
|
39217
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
39200
39218
|
function parseReviewFeedback(response) {
|
|
39201
39219
|
let cleaned = response.trim();
|
|
39202
39220
|
if (cleaned.startsWith("```json")) {
|
|
@@ -39221,7 +39239,7 @@ function parseReviewFeedback(response) {
|
|
|
39221
39239
|
async function executeReview(options) {
|
|
39222
39240
|
const { videoPath, storyboardPath, autoApply = false, verify = false, model = "flash" } = options;
|
|
39223
39241
|
const absVideoPath = resolve31(process.cwd(), videoPath);
|
|
39224
|
-
if (!
|
|
39242
|
+
if (!existsSync25(absVideoPath)) {
|
|
39225
39243
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
39226
39244
|
}
|
|
39227
39245
|
const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
|
|
@@ -39231,7 +39249,7 @@ async function executeReview(options) {
|
|
|
39231
39249
|
let storyboardContext = "";
|
|
39232
39250
|
if (storyboardPath) {
|
|
39233
39251
|
const absStoryboardPath = resolve31(process.cwd(), storyboardPath);
|
|
39234
|
-
if (
|
|
39252
|
+
if (existsSync25(absStoryboardPath)) {
|
|
39235
39253
|
const content = await readFile22(absStoryboardPath, "utf-8");
|
|
39236
39254
|
storyboardContext = `
|
|
39237
39255
|
|
|
@@ -39332,7 +39350,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
39332
39350
|
return result;
|
|
39333
39351
|
}
|
|
39334
39352
|
function registerReviewCommand(aiCommand) {
|
|
39335
|
-
aiCommand.command("review").description("Review video quality using Gemini AI and optionally auto-fix issues").argument("<
|
|
39353
|
+
aiCommand.command("review").description("Review video quality using Gemini AI and optionally auto-fix issues").argument("<source>", "Video file path").option("--storyboard <path>", "Storyboard JSON file for context").option("--auto-apply", "Automatically apply fixable corrections").option("--verify", "Run verification pass after applying fixes").option("-m, --model <model>", "Gemini model: flash (default), flash-2.5, pro", "flash").option("-o, --output <path>", "Output video file path (for auto-apply)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
39336
39354
|
const startedAt = Date.now();
|
|
39337
39355
|
try {
|
|
39338
39356
|
if (options.output) {
|
|
@@ -39340,7 +39358,7 @@ function registerReviewCommand(aiCommand) {
|
|
|
39340
39358
|
}
|
|
39341
39359
|
if (options.dryRun) {
|
|
39342
39360
|
outputSuccess({
|
|
39343
|
-
command: "
|
|
39361
|
+
command: "inspect review",
|
|
39344
39362
|
startedAt,
|
|
39345
39363
|
dryRun: true,
|
|
39346
39364
|
data: {
|
|
@@ -39437,7 +39455,7 @@ var init_ai_review = __esm({
|
|
|
39437
39455
|
// src/commands/ai-highlights.ts
|
|
39438
39456
|
import { readFile as readFile26, writeFile as writeFile33, mkdir as mkdir15 } from "node:fs/promises";
|
|
39439
39457
|
import { resolve as resolve35, dirname as dirname12, basename as basename8, extname as extname8 } from "node:path";
|
|
39440
|
-
import { existsSync as
|
|
39458
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
39441
39459
|
function filterHighlights(highlights, options) {
|
|
39442
39460
|
let filtered = highlights.filter((h) => h.confidence >= options.threshold);
|
|
39443
39461
|
filtered.sort((a, b) => b.confidence - a.confidence);
|
|
@@ -39477,7 +39495,7 @@ function truncate(text, maxLength) {
|
|
|
39477
39495
|
async function executeHighlights(options) {
|
|
39478
39496
|
try {
|
|
39479
39497
|
const absPath = resolve35(process.cwd(), options.media);
|
|
39480
|
-
if (!
|
|
39498
|
+
if (!existsSync28(absPath)) {
|
|
39481
39499
|
return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
|
|
39482
39500
|
}
|
|
39483
39501
|
const ext = extname8(absPath).toLowerCase();
|
|
@@ -39593,7 +39611,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
39593
39611
|
const audioBuffer = await readFile26(audioPath);
|
|
39594
39612
|
const audioBlob = new Blob([audioBuffer]);
|
|
39595
39613
|
const transcriptResult = await whisper.transcribe(audioBlob, options.language);
|
|
39596
|
-
if (tempAudioPath &&
|
|
39614
|
+
if (tempAudioPath && existsSync28(tempAudioPath)) {
|
|
39597
39615
|
const { unlink: unlinkFile } = await import("node:fs/promises");
|
|
39598
39616
|
await unlinkFile(tempAudioPath).catch(() => {
|
|
39599
39617
|
});
|
|
@@ -39680,7 +39698,7 @@ async function executeAutoShorts(options) {
|
|
|
39680
39698
|
return { success: false, shorts: [], error: "FFmpeg not found" };
|
|
39681
39699
|
}
|
|
39682
39700
|
const absPath = resolve35(process.cwd(), options.video);
|
|
39683
|
-
if (!
|
|
39701
|
+
if (!existsSync28(absPath)) {
|
|
39684
39702
|
return { success: false, shorts: [], error: `File not found: ${absPath}` };
|
|
39685
39703
|
}
|
|
39686
39704
|
const targetDuration = options.duration ?? 60;
|
|
@@ -39808,7 +39826,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
39808
39826
|
};
|
|
39809
39827
|
}
|
|
39810
39828
|
const outputDir = options.outputDir ? resolve35(process.cwd(), options.outputDir) : dirname12(absPath);
|
|
39811
|
-
if (options.outputDir && !
|
|
39829
|
+
if (options.outputDir && !existsSync28(outputDir)) {
|
|
39812
39830
|
await mkdir15(outputDir, { recursive: true });
|
|
39813
39831
|
}
|
|
39814
39832
|
const result = {
|
|
@@ -39884,14 +39902,14 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
39884
39902
|
}
|
|
39885
39903
|
}
|
|
39886
39904
|
function registerHighlightsCommands(aiCommand) {
|
|
39887
|
-
aiCommand.command("highlights").description("Extract highlights from long-form video/audio content").argument("<media>", "Video or audio file path").option("-o, --output <path>", "Output JSON file with highlights").option("--project <path>", "Create project with highlight clips").option("-d, --duration <seconds>", "Target highlight reel duration", "60").option("
|
|
39905
|
+
aiCommand.command("highlights").description("Extract highlights from long-form video/audio content").argument("<media>", "Video or audio file path").option("-o, --output <path>", "Output JSON file with highlights").option("--project <path>", "Create project with highlight clips").option("-d, --duration <seconds>", "Target highlight reel duration", "60").option("--count <number>", "Maximum number of highlights").option("--threshold <value>", "Confidence threshold (0-1)", "0.7").option("--criteria <type>", "Selection criteria: emotional | informative | funny | all", "all").option("-l, --language <lang>", "Language code for transcription (e.g., en, ko)").option("--use-gemini", "Use Gemini Video Understanding for enhanced visual+audio analysis").option("--low-res", "Use low resolution mode for longer videos (Gemini only)").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
|
|
39888
39906
|
const startedAt = Date.now();
|
|
39889
39907
|
try {
|
|
39890
39908
|
if (options.output) {
|
|
39891
39909
|
validateOutputPath(options.output);
|
|
39892
39910
|
}
|
|
39893
39911
|
const absPath = resolve35(process.cwd(), mediaPath);
|
|
39894
|
-
if (!
|
|
39912
|
+
if (!existsSync28(absPath)) {
|
|
39895
39913
|
exitWithError(notFoundError(absPath));
|
|
39896
39914
|
}
|
|
39897
39915
|
if (options.dryRun) {
|
|
@@ -40078,7 +40096,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
40078
40096
|
const transcriptResult = await whisper.transcribe(audioBlob, options.language);
|
|
40079
40097
|
if (transcriptResult.status === "failed" || !transcriptResult.segments) {
|
|
40080
40098
|
transcribeSpinner.fail("Transcription failed");
|
|
40081
|
-
if (tempAudioPath &&
|
|
40099
|
+
if (tempAudioPath && existsSync28(tempAudioPath)) {
|
|
40082
40100
|
const { unlink: unlinkFile } = await import("node:fs/promises");
|
|
40083
40101
|
await unlinkFile(tempAudioPath).catch(() => {
|
|
40084
40102
|
});
|
|
@@ -40086,7 +40104,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
40086
40104
|
exitWithError(apiError(`Transcription failed: ${transcriptResult.error}`, true));
|
|
40087
40105
|
}
|
|
40088
40106
|
transcribeSpinner.succeed(source_default.green(`Transcribed ${transcriptResult.segments.length} segments`));
|
|
40089
|
-
if (tempAudioPath &&
|
|
40107
|
+
if (tempAudioPath && existsSync28(tempAudioPath)) {
|
|
40090
40108
|
const { unlink: unlinkFile } = await import("node:fs/promises");
|
|
40091
40109
|
await unlinkFile(tempAudioPath).catch(() => {
|
|
40092
40110
|
});
|
|
@@ -40185,14 +40203,14 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
40185
40203
|
exitWithError(apiError(`Highlight extraction failed: ${error instanceof Error ? error.message : String(error)}`, true));
|
|
40186
40204
|
}
|
|
40187
40205
|
});
|
|
40188
|
-
aiCommand.command("auto-shorts").alias("shorts").description("Auto-generate shorts from long-form video").argument("<video>", "Video file path").option("-o, --output <path>", "Output file (single) or directory (multiple)").option("-d, --duration <seconds>", "Target duration in seconds (15-60)", "60").option("
|
|
40206
|
+
aiCommand.command("auto-shorts").alias("shorts").description("Auto-generate shorts from long-form video").argument("<video>", "Video file path").option("-o, --output <path>", "Output file (single) or directory (multiple)").option("-d, --duration <seconds>", "Target duration in seconds (15-60)", "60").option("--count <number>", "Number of shorts to generate", "1").option("-a, --aspect <ratio>", "Aspect ratio: 9:16, 1:1", "9:16").option("--output-dir <dir>", "Output directory for multiple shorts").option("--add-captions", "Add auto-generated captions").option("--caption-style <style>", "Caption style: minimal, bold, animated", "bold").option("--analyze-only", "Show segments without generating").option("-l, --language <lang>", "Language code for transcription").option("--use-gemini", "Use Gemini Video Understanding for enhanced visual+audio analysis").option("--low-res", "Use low resolution mode for longer videos (Gemini only)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
40189
40207
|
const startedAt = Date.now();
|
|
40190
40208
|
try {
|
|
40191
40209
|
if (options.output) {
|
|
40192
40210
|
validateOutputPath(options.output);
|
|
40193
40211
|
}
|
|
40194
40212
|
const absPath = resolve35(process.cwd(), videoPath);
|
|
40195
|
-
if (!
|
|
40213
|
+
if (!existsSync28(absPath)) {
|
|
40196
40214
|
exitWithError(notFoundError(absPath));
|
|
40197
40215
|
}
|
|
40198
40216
|
if (options.dryRun) {
|
|
@@ -40385,7 +40403,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
40385
40403
|
return;
|
|
40386
40404
|
}
|
|
40387
40405
|
const outputDir = options.outputDir ? resolve35(process.cwd(), options.outputDir) : dirname12(absPath);
|
|
40388
|
-
if (options.outputDir && !
|
|
40406
|
+
if (options.outputDir && !existsSync28(outputDir)) {
|
|
40389
40407
|
await mkdir15(outputDir, { recursive: true });
|
|
40390
40408
|
}
|
|
40391
40409
|
for (let i = 0; i < selectedHighlights.length; i++) {
|
|
@@ -40402,7 +40420,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
40402
40420
|
outputPath = resolve35(outputDir, `${baseName}-short-${i + 1}.mp4`);
|
|
40403
40421
|
}
|
|
40404
40422
|
const parentDir = dirname12(outputPath);
|
|
40405
|
-
if (!
|
|
40423
|
+
if (!existsSync28(parentDir)) {
|
|
40406
40424
|
await mkdir15(parentDir, { recursive: true });
|
|
40407
40425
|
}
|
|
40408
40426
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
@@ -40477,7 +40495,7 @@ var init_ai_highlights = __esm({
|
|
|
40477
40495
|
// src/commands/ai-animated-caption.ts
|
|
40478
40496
|
import { resolve as resolve36, dirname as dirname13, basename as basename9 } from "node:path";
|
|
40479
40497
|
import { writeFile as writeFile34, mkdir as mkdir16, rm as rm2 } from "node:fs/promises";
|
|
40480
|
-
import { existsSync as
|
|
40498
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
40481
40499
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
40482
40500
|
function groupWords(words, options = {}) {
|
|
40483
40501
|
if (words.length === 0) return [];
|
|
@@ -40654,7 +40672,7 @@ async function executeAnimatedCaption(options) {
|
|
|
40654
40672
|
const groups = groupWords(transcript.words, { wordsPerGroup, maxChars });
|
|
40655
40673
|
const absOutputPath = resolve36(process.cwd(), outputPath);
|
|
40656
40674
|
const outDir = dirname13(absOutputPath);
|
|
40657
|
-
if (!
|
|
40675
|
+
if (!existsSync29(outDir)) {
|
|
40658
40676
|
await mkdir16(outDir, { recursive: true });
|
|
40659
40677
|
}
|
|
40660
40678
|
if (tier === "ass") {
|
|
@@ -50304,15 +50322,14 @@ var init_detect = __esm({
|
|
|
50304
50322
|
"use strict";
|
|
50305
50323
|
init_esm();
|
|
50306
50324
|
init_source();
|
|
50307
|
-
init_ora();
|
|
50308
50325
|
init_engine();
|
|
50309
50326
|
init_exec_safe();
|
|
50310
50327
|
init_output();
|
|
50311
50328
|
init_validate();
|
|
50312
50329
|
detectCommand = new Command("detect").description("Auto-detect scenes, beats, and silences in media");
|
|
50313
|
-
detectCommand.command("scenes").description("Detect scene changes in video").argument("<video>", "Video file path").option("
|
|
50330
|
+
detectCommand.command("scenes").description("Detect scene changes in video").argument("<video>", "Video file path").option("--threshold <value>", "Scene change threshold (0-1)", "0.3").option("-o, --output <path>", "Output JSON file with timestamps").option("--project <path>", "Add scenes as clips to project").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
50314
50331
|
const startedAt = Date.now();
|
|
50315
|
-
const spinner2 =
|
|
50332
|
+
const spinner2 = spinner("Detecting scenes...").start();
|
|
50316
50333
|
try {
|
|
50317
50334
|
if (options.output) {
|
|
50318
50335
|
validateOutputPath(options.output);
|
|
@@ -50429,9 +50446,9 @@ var init_detect = __esm({
|
|
|
50429
50446
|
exitWithError(generalError(`Scene detection failed: ${msg}`));
|
|
50430
50447
|
}
|
|
50431
50448
|
});
|
|
50432
|
-
detectCommand.command("silence").description("Detect silence in audio/video").argument("<media>", "Media file path").option("
|
|
50449
|
+
detectCommand.command("silence").description("Detect silence in audio/video").argument("<media>", "Media file path").option("--noise <dB>", "Noise threshold in dB", "-30").option("-d, --duration <sec>", "Minimum silence duration", "0.5").option("-o, --output <path>", "Output JSON file with timestamps").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
|
|
50433
50450
|
const startedAt = Date.now();
|
|
50434
|
-
const spinner2 =
|
|
50451
|
+
const spinner2 = spinner("Detecting silence...").start();
|
|
50435
50452
|
try {
|
|
50436
50453
|
if (options.output) {
|
|
50437
50454
|
validateOutputPath(options.output);
|
|
@@ -50517,7 +50534,7 @@ var init_detect = __esm({
|
|
|
50517
50534
|
});
|
|
50518
50535
|
detectCommand.command("beats").description("Detect beats in audio (for music sync)").argument("<audio>", "Audio file path").option("-o, --output <path>", "Output JSON file with timestamps").option("--dry-run", "Preview parameters without executing").action(async (audioPath, options) => {
|
|
50519
50536
|
const startedAt = Date.now();
|
|
50520
|
-
const spinner2 =
|
|
50537
|
+
const spinner2 = spinner("Detecting beats...").start();
|
|
50521
50538
|
try {
|
|
50522
50539
|
if (options.output) {
|
|
50523
50540
|
validateOutputPath(options.output);
|
|
@@ -50604,7 +50621,7 @@ var init_detect = __esm({
|
|
|
50604
50621
|
});
|
|
50605
50622
|
|
|
50606
50623
|
// src/pipeline/renderers/chrome.ts
|
|
50607
|
-
import { existsSync as
|
|
50624
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
50608
50625
|
import { homedir as homedir5 } from "node:os";
|
|
50609
50626
|
import { join as join11 } from "node:path";
|
|
50610
50627
|
function findChrome() {
|
|
@@ -50630,7 +50647,7 @@ function findChrome() {
|
|
|
50630
50647
|
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
|
50631
50648
|
];
|
|
50632
50649
|
for (const c of candidates) {
|
|
50633
|
-
if (c &&
|
|
50650
|
+
if (c && existsSync32(c)) return c;
|
|
50634
50651
|
}
|
|
50635
50652
|
return void 0;
|
|
50636
50653
|
}
|
|
@@ -50885,7 +50902,7 @@ __export(scene_render_exports, {
|
|
|
50885
50902
|
qualityToCrf: () => qualityToCrf
|
|
50886
50903
|
});
|
|
50887
50904
|
import { mkdir as mkdir18, readFile as readFile32, stat as stat4 } from "node:fs/promises";
|
|
50888
|
-
import { existsSync as
|
|
50905
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
50889
50906
|
import { resolve as resolve45, relative as relative6, dirname as dirname16, basename as basename16 } from "node:path";
|
|
50890
50907
|
import {
|
|
50891
50908
|
createRenderJob,
|
|
@@ -50903,7 +50920,7 @@ function defaultOutputPath(opts) {
|
|
|
50903
50920
|
}
|
|
50904
50921
|
async function readProjectName(projectDir) {
|
|
50905
50922
|
const cfgPath = resolve45(projectDir, "vibe.project.yaml");
|
|
50906
|
-
if (!
|
|
50923
|
+
if (!existsSync33(cfgPath)) return void 0;
|
|
50907
50924
|
try {
|
|
50908
50925
|
const raw = await (await import("node:fs/promises")).readFile(cfgPath, "utf-8");
|
|
50909
50926
|
const parsed = (0, import_yaml7.parse)(raw);
|
|
@@ -51033,7 +51050,7 @@ __export(scene_build_exports, {
|
|
|
51033
51050
|
executeSceneBuild: () => executeSceneBuild,
|
|
51034
51051
|
resolveSceneBuildMode: () => resolveSceneBuildMode
|
|
51035
51052
|
});
|
|
51036
|
-
import { existsSync as
|
|
51053
|
+
import { existsSync as existsSync34 } from "node:fs";
|
|
51037
51054
|
import { mkdir as mkdir19, readFile as readFile33, writeFile as writeFile38 } from "node:fs/promises";
|
|
51038
51055
|
import { dirname as dirname17, join as join12, resolve as resolve46 } from "node:path";
|
|
51039
51056
|
async function executeSceneBuild(opts) {
|
|
@@ -51043,7 +51060,7 @@ async function executeSceneBuild(opts) {
|
|
|
51043
51060
|
});
|
|
51044
51061
|
const mode = resolveSceneBuildMode(opts);
|
|
51045
51062
|
const storyboardPath = join12(projectDir, "STORYBOARD.md");
|
|
51046
|
-
if (!
|
|
51063
|
+
if (!existsSync34(storyboardPath)) {
|
|
51047
51064
|
return failBeforePrimitives(
|
|
51048
51065
|
`STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`,
|
|
51049
51066
|
startedAt
|
|
@@ -51079,7 +51096,7 @@ async function executeSceneBuild(opts) {
|
|
|
51079
51096
|
if (mode === "agent") {
|
|
51080
51097
|
const compositionsDir = join12(projectDir, "compositions");
|
|
51081
51098
|
const missingBeats = parsed.beats.filter(
|
|
51082
|
-
(b) => !
|
|
51099
|
+
(b) => !existsSync34(join12(compositionsDir, `scene-${b.id}.html`))
|
|
51083
51100
|
);
|
|
51084
51101
|
if (missingBeats.length > 0) {
|
|
51085
51102
|
const plan = await getComposePrompts({ projectDir });
|
|
@@ -51127,7 +51144,7 @@ async function executeSceneBuild(opts) {
|
|
|
51127
51144
|
}
|
|
51128
51145
|
composeData = composeResult.data;
|
|
51129
51146
|
}
|
|
51130
|
-
if (!
|
|
51147
|
+
if (!existsSync34(join12(projectDir, "index.html"))) {
|
|
51131
51148
|
await scaffoldSceneProject({
|
|
51132
51149
|
dir: projectDir,
|
|
51133
51150
|
name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
|
|
@@ -51187,7 +51204,7 @@ async function dispatchNarration(beat, ctx) {
|
|
|
51187
51204
|
if (!text) return { status: "no-cue" };
|
|
51188
51205
|
for (const ext of ["mp3", "wav"]) {
|
|
51189
51206
|
const rel2 = `assets/narration-${beat.id}.${ext}`;
|
|
51190
|
-
if (
|
|
51207
|
+
if (existsSync34(join12(ctx.projectDir, rel2)) && !ctx.force) {
|
|
51191
51208
|
ctx.onProgress({ type: "narration-cached", beatId: beat.id, path: rel2 });
|
|
51192
51209
|
return { status: "cached", path: rel2 };
|
|
51193
51210
|
}
|
|
@@ -51228,7 +51245,7 @@ async function dispatchBackdrop(beat, ctx) {
|
|
|
51228
51245
|
}
|
|
51229
51246
|
const rel = `assets/backdrop-${beat.id}.png`;
|
|
51230
51247
|
const abs = join12(ctx.projectDir, rel);
|
|
51231
|
-
if (
|
|
51248
|
+
if (existsSync34(abs) && !ctx.force) {
|
|
51232
51249
|
ctx.onProgress({ type: "backdrop-cached", beatId: beat.id, path: rel });
|
|
51233
51250
|
return { status: "cached", path: rel };
|
|
51234
51251
|
}
|
|
@@ -51266,7 +51283,7 @@ function loadSceneBuildEnv(projectDir) {
|
|
|
51266
51283
|
(0, import_dotenv3.config)({ path: resolve46(process.cwd(), ".env"), quiet: true });
|
|
51267
51284
|
let dir = process.cwd();
|
|
51268
51285
|
while (dir !== dirname17(dir)) {
|
|
51269
|
-
if (
|
|
51286
|
+
if (existsSync34(join12(dir, "pnpm-workspace.yaml"))) {
|
|
51270
51287
|
(0, import_dotenv3.config)({ path: join12(dir, ".env"), quiet: true });
|
|
51271
51288
|
return;
|
|
51272
51289
|
}
|
|
@@ -51297,7 +51314,7 @@ function resolveSceneBuildMode(opts) {
|
|
|
51297
51314
|
}
|
|
51298
51315
|
async function syncRootClipReferences(beats, projectDir, outcomes) {
|
|
51299
51316
|
const rootPath = join12(projectDir, "index.html");
|
|
51300
|
-
if (!
|
|
51317
|
+
if (!existsSync34(rootPath)) return;
|
|
51301
51318
|
const html = await readFile33(rootPath, "utf-8");
|
|
51302
51319
|
let cursor = 0;
|
|
51303
51320
|
const clipLines = [];
|
|
@@ -51748,14 +51765,14 @@ __export(ai_audio_exports, {
|
|
|
51748
51765
|
});
|
|
51749
51766
|
import { resolve as resolve53, dirname as dirname19, basename as basename17, extname as extname12 } from "node:path";
|
|
51750
51767
|
import { readFile as readFile36, writeFile as writeFile40 } from "node:fs/promises";
|
|
51751
|
-
import { existsSync as
|
|
51768
|
+
import { existsSync as existsSync37 } from "node:fs";
|
|
51752
51769
|
async function executeTranscribe(options) {
|
|
51753
51770
|
const { audioPath, language, output, format, apiKey } = options;
|
|
51754
51771
|
try {
|
|
51755
51772
|
const key = apiKey || process.env.OPENAI_API_KEY;
|
|
51756
51773
|
if (!key) return { success: false, error: "OPENAI_API_KEY required" };
|
|
51757
51774
|
const absPath = resolve53(process.cwd(), audioPath);
|
|
51758
|
-
if (!
|
|
51775
|
+
if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
|
|
51759
51776
|
const whisper = new WhisperProvider();
|
|
51760
51777
|
await whisper.initialize({ apiKey: key });
|
|
51761
51778
|
const audioBuffer = await readFile36(absPath);
|
|
@@ -51788,7 +51805,7 @@ async function executeIsolate(options) {
|
|
|
51788
51805
|
const key = apiKey || process.env.ELEVENLABS_API_KEY;
|
|
51789
51806
|
if (!key) return { success: false, error: "ELEVENLABS_API_KEY required" };
|
|
51790
51807
|
const absPath = resolve53(process.cwd(), audioPath);
|
|
51791
|
-
if (!
|
|
51808
|
+
if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
|
|
51792
51809
|
const audioBuffer = await readFile36(absPath);
|
|
51793
51810
|
const elevenlabs = new ElevenLabsProvider();
|
|
51794
51811
|
await elevenlabs.initialize({ apiKey: key });
|
|
@@ -51814,7 +51831,7 @@ async function executeVoiceClone(options) {
|
|
|
51814
51831
|
const audioBuffers = [];
|
|
51815
51832
|
for (const samplePath of samplePaths) {
|
|
51816
51833
|
const absPath = resolve53(process.cwd(), samplePath);
|
|
51817
|
-
if (!
|
|
51834
|
+
if (!existsSync37(absPath)) return { success: false, error: `File not found: ${samplePath}` };
|
|
51818
51835
|
const buffer = await readFile36(absPath);
|
|
51819
51836
|
audioBuffers.push(buffer);
|
|
51820
51837
|
}
|
|
@@ -51836,7 +51853,7 @@ async function executeDub(options) {
|
|
|
51836
51853
|
const { mediaPath, language, source, voice, analyzeOnly, output } = options;
|
|
51837
51854
|
try {
|
|
51838
51855
|
const absPath = resolve53(process.cwd(), mediaPath);
|
|
51839
|
-
if (!
|
|
51856
|
+
if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
|
|
51840
51857
|
const openaiKey = process.env.OPENAI_API_KEY;
|
|
51841
51858
|
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
51842
51859
|
const elevenlabsKey = process.env.ELEVENLABS_API_KEY;
|
|
@@ -51964,8 +51981,8 @@ async function executeDuck(options) {
|
|
|
51964
51981
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
51965
51982
|
const absMusicPath = resolve53(process.cwd(), musicPath);
|
|
51966
51983
|
const absVoicePath = resolve53(process.cwd(), voicePath);
|
|
51967
|
-
if (!
|
|
51968
|
-
if (!
|
|
51984
|
+
if (!existsSync37(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
|
|
51985
|
+
if (!existsSync37(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
|
|
51969
51986
|
const defaultOutput = resolve53(dirname19(absMusicPath), `${basename17(absMusicPath, extname12(absMusicPath))}-ducked${extname12(absMusicPath)}`);
|
|
51970
51987
|
const outputPath = resolve53(process.cwd(), output || defaultOutput);
|
|
51971
51988
|
const filter = `[1:a]asplit=2[sc][mix];[0:a][sc]sidechaincompress=threshold=${threshold}dB:ratio=${ratio}:attack=${attack}:release=${release}[ducked];[ducked][mix]amix=inputs=2:duration=longest`;
|
|
@@ -51979,7 +51996,7 @@ async function executeDuck(options) {
|
|
|
51979
51996
|
"-y",
|
|
51980
51997
|
outputPath
|
|
51981
51998
|
], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
|
|
51982
|
-
if (!
|
|
51999
|
+
if (!existsSync37(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
|
|
51983
52000
|
return { success: true, outputPath };
|
|
51984
52001
|
} catch (error) {
|
|
51985
52002
|
return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
@@ -51997,7 +52014,7 @@ var init_ai_audio = __esm({
|
|
|
51997
52014
|
// src/pipeline/executor.ts
|
|
51998
52015
|
import { resolve as resolve54 } from "node:path";
|
|
51999
52016
|
import { readFile as readFile37, writeFile as writeFile41, mkdir as mkdir21 } from "node:fs/promises";
|
|
52000
|
-
import { existsSync as
|
|
52017
|
+
import { existsSync as existsSync38 } from "node:fs";
|
|
52001
52018
|
function maxCostFor(action) {
|
|
52002
52019
|
const cmd = ACTION_METADATA[action]?.command;
|
|
52003
52020
|
if (!cmd) return 0;
|
|
@@ -52214,7 +52231,7 @@ async function ensureActionsRegistered() {
|
|
|
52214
52231
|
}
|
|
52215
52232
|
async function loadPipeline(filePath) {
|
|
52216
52233
|
const absPath = resolve54(process.cwd(), filePath);
|
|
52217
|
-
if (!
|
|
52234
|
+
if (!existsSync38(absPath)) {
|
|
52218
52235
|
throw new Error(`Pipeline file not found: ${absPath}`);
|
|
52219
52236
|
}
|
|
52220
52237
|
const content = await readFile37(absPath, "utf-8");
|
|
@@ -52239,7 +52256,7 @@ async function executePipeline(manifest2, options = {}) {
|
|
|
52239
52256
|
const startTime = Date.now();
|
|
52240
52257
|
if (options.resume) {
|
|
52241
52258
|
const checkpointPath = resolve54(outputDir, CHECKPOINT_FILE);
|
|
52242
|
-
if (
|
|
52259
|
+
if (existsSync38(checkpointPath)) {
|
|
52243
52260
|
const checkpointContent = await readFile37(checkpointPath, "utf-8");
|
|
52244
52261
|
const checkpoint = (0, import_yaml8.parse)(checkpointContent);
|
|
52245
52262
|
for (const cs of checkpoint.completedSteps) {
|
|
@@ -59645,7 +59662,7 @@ __export(project_builder_exports, {
|
|
|
59645
59662
|
resolveSourceUrl: () => resolveSourceUrl
|
|
59646
59663
|
});
|
|
59647
59664
|
import { mkdtemp, mkdir as mkdir22, copyFile as copyFile3, writeFile as writeFile46, rm as rm4, readdir as readdir3 } from "node:fs/promises";
|
|
59648
|
-
import { existsSync as
|
|
59665
|
+
import { existsSync as existsSync39 } from "node:fs";
|
|
59649
59666
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
59650
59667
|
import { createRequire } from "node:module";
|
|
59651
59668
|
import * as path from "node:path";
|
|
@@ -59657,7 +59674,7 @@ async function buildTempProject(state, projectFileDir) {
|
|
|
59657
59674
|
for (const source of state.sources) {
|
|
59658
59675
|
if (copied.has(source.id)) continue;
|
|
59659
59676
|
const resolved = resolveSourceUrl(source.url, projectFileDir);
|
|
59660
|
-
if (!
|
|
59677
|
+
if (!existsSync39(resolved)) {
|
|
59661
59678
|
throw new Error(`Source file not found: ${resolved} (from source id=${source.id})`);
|
|
59662
59679
|
}
|
|
59663
59680
|
const dest = path.join(assetsDir, path.basename(resolved));
|
|
@@ -60488,18 +60505,18 @@ var init_export = __esm({
|
|
|
60488
60505
|
BITRATE_PATTERN = /^\d+(?:\.\d+)?[kKmMgG]?$/;
|
|
60489
60506
|
RESOLUTION_PATTERN = /^\d+x\d+$/;
|
|
60490
60507
|
VALID_CODECS = ["h264", "h265", "vp9"];
|
|
60491
|
-
exportCommand = new Command("export").description("Export project to video file").argument("<project>", "Project file path").option("-o, --output <path>", "Output file path").option("
|
|
60492
|
-
"
|
|
60508
|
+
exportCommand = new Command("export").description("Export project to video file").argument("<project>", "Project file path").option("-o, --output <path>", "Output file path").option("--format <format>", "Output format (mp4, webm, mov, gif)", "mp4").option(
|
|
60509
|
+
"--preset <preset>",
|
|
60493
60510
|
"Quality preset (draft, standard, high, ultra)",
|
|
60494
60511
|
"standard"
|
|
60495
|
-
).option("
|
|
60512
|
+
).option("--overwrite", "Overwrite output file if exists", false).option("--gap-fill <strategy>", "Gap filling strategy (black, extend)", "extend").option("--backend <name>", "Render backend: ffmpeg (default) | hyperframes (experimental)", "ffmpeg").option("--bitrate <value>", "Video bitrate (e.g. 5000k, 8M) \u2014 overrides preset").option("--fps <number>", "Frames per second (e.g. 24, 30, 60) \u2014 overrides preset").option("--resolution <WxH>", "Output resolution (e.g. 1920x1080) \u2014 overrides preset").option("--codec <codec>", "Video codec: h264 (default) | h265 | vp9 \u2014 overrides preset").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
|
|
60496
60513
|
Examples:
|
|
60497
60514
|
$ vibe export project.vibe.json -o output.mp4
|
|
60498
|
-
$ vibe export project.vibe.json -o output.mp4
|
|
60499
|
-
$ vibe export project.vibe.json -o output.webm
|
|
60500
|
-
$ vibe export project.vibe.json -o output.gif
|
|
60515
|
+
$ vibe export project.vibe.json -o output.mp4 --preset high --overwrite
|
|
60516
|
+
$ vibe export project.vibe.json -o output.webm --format webm
|
|
60517
|
+
$ vibe export project.vibe.json -o output.gif --format gif
|
|
60501
60518
|
$ vibe export project.vibe.json -o out.mp4 --bitrate 5000k --fps 24 --codec h265
|
|
60502
|
-
$ vibe export project.vibe.json -o out.mp4
|
|
60519
|
+
$ vibe export project.vibe.json -o out.mp4 --preset high --fps 60
|
|
60503
60520
|
|
|
60504
60521
|
Cost: Free (no API keys needed). Requires FFmpeg.
|
|
60505
60522
|
GIF format: 15fps, no audio, looping. Good for previews and sharing.
|
|
@@ -61635,28 +61652,31 @@ narration / backdrop intent.
|
|
|
61635
61652
|
## Quick-draft path
|
|
61636
61653
|
|
|
61637
61654
|
\`\`\`bash
|
|
61638
|
-
vibe
|
|
61655
|
+
vibe init my-promo -r 16:9 -d 30
|
|
61639
61656
|
vibe scene add intro --style announcement \\
|
|
61640
61657
|
--headline "Ship videos, not clicks"
|
|
61641
61658
|
vibe scene lint
|
|
61642
61659
|
vibe render my-promo
|
|
61643
61660
|
\`\`\`
|
|
61644
61661
|
|
|
61645
|
-
\`vibe
|
|
61646
|
-
|
|
61647
|
-
|
|
61662
|
+
\`vibe init\` is **idempotent** \u2014 running it on an existing Hyperframes
|
|
61663
|
+
directory merges \`hyperframes.json\` instead of clobbering it. Safe to
|
|
61664
|
+
invoke on user-provided projects.
|
|
61648
61665
|
|
|
61649
61666
|
## Subcommands
|
|
61650
61667
|
|
|
61651
61668
|
\`\`\`bash
|
|
61652
|
-
|
|
61669
|
+
# Project flow (top-level \u2014 preferred entry points)
|
|
61670
|
+
vibe init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
|
|
61671
|
+
vibe build [<dir>] [--mode agent|batch|auto] # H3 dispatch
|
|
61672
|
+
vibe render [<dir>] [--fps 30] [--quality standard] [--format mp4]
|
|
61673
|
+
|
|
61674
|
+
# Lower-level scene authoring
|
|
61653
61675
|
vibe scene list-styles [<name>] # list / show vendored visual identities
|
|
61654
61676
|
vibe scene install-skill [<dir>] [--host all] # retroactive composition-rules install
|
|
61655
61677
|
vibe scene add <name> --style <preset> [...]
|
|
61656
61678
|
vibe scene compose-prompts [<dir>] [--beat <id>] # H2: emit plan, no LLM call
|
|
61657
61679
|
vibe scene lint [<root>] [--json] [--fix]
|
|
61658
|
-
vibe scene render [<root>] [--fps 30] [--quality standard] [--format mp4]
|
|
61659
|
-
vibe build [<dir>] [--mode agent|batch|auto] # H3 dispatch
|
|
61660
61680
|
\`\`\`
|
|
61661
61681
|
|
|
61662
61682
|
## Style presets (for \`vibe scene add --style\`)
|
|
@@ -61710,7 +61730,7 @@ and surface the error to the user.
|
|
|
61710
61730
|
| Generate narration + image, then author scene | \`vibe scene add\` |
|
|
61711
61731
|
| Generate a full scenes project from a STORYBOARD | \`vibe build\` |
|
|
61712
61732
|
| Hand-tweak a single scene's animation | edit \`compositions/<file>.html\` directly |
|
|
61713
|
-
| Render the project | \`vibe render\`
|
|
61733
|
+
| Render the project | \`vibe render\` (one canonical entry point) |
|
|
61714
61734
|
| Lint | \`vibe scene lint\` *or* \`npx hyperframes lint\` (equivalent) |
|
|
61715
61735
|
|
|
61716
61736
|
The \`vibe\` CLI adds asset generation, AI orchestration, and pipeline
|
|
@@ -61838,12 +61858,12 @@ overlay, etc.) \u2014 useful at the tail of a pipeline.
|
|
|
61838
61858
|
],
|
|
61839
61859
|
relatedCommands: [
|
|
61840
61860
|
"vibe init",
|
|
61861
|
+
"vibe build",
|
|
61862
|
+
"vibe render",
|
|
61841
61863
|
"vibe scene list-styles",
|
|
61842
61864
|
"vibe scene install-skill",
|
|
61843
61865
|
"vibe scene compose-prompts",
|
|
61844
|
-
"vibe build",
|
|
61845
61866
|
"vibe scene lint",
|
|
61846
|
-
"vibe scene render",
|
|
61847
61867
|
"vibe scene add"
|
|
61848
61868
|
]
|
|
61849
61869
|
},
|
|
@@ -62167,7 +62187,7 @@ init_source();
|
|
|
62167
62187
|
// package.json
|
|
62168
62188
|
var package_default = {
|
|
62169
62189
|
name: "@vibeframe/cli",
|
|
62170
|
-
version: "0.
|
|
62190
|
+
version: "0.79.0",
|
|
62171
62191
|
description: "VibeFrame CLI - natural language video editing from the terminal",
|
|
62172
62192
|
private: false,
|
|
62173
62193
|
license: "MIT",
|
|
@@ -62389,7 +62409,7 @@ async function resolveProjectPath(inputPath) {
|
|
|
62389
62409
|
}
|
|
62390
62410
|
return filePath;
|
|
62391
62411
|
}
|
|
62392
|
-
var projectCommand = new Command("project").description("
|
|
62412
|
+
var projectCommand = new Command("project").description("Manage VibeFrame .vibe.json project files (create, info, set)").addHelpText("after", `
|
|
62393
62413
|
Examples:
|
|
62394
62414
|
$ vibe project create "my-video" -o project.vibe.json
|
|
62395
62415
|
$ vibe project create "shorts" -r 9:16 -o vertical.vibe.json
|
|
@@ -62398,7 +62418,7 @@ Examples:
|
|
|
62398
62418
|
|
|
62399
62419
|
Cost: Free (no API keys needed). Projects are saved as .vibe.json files.
|
|
62400
62420
|
Run 'vibe schema project.<command>' for structured parameter info.`);
|
|
62401
|
-
projectCommand.command("create").description("Create a new project").argument("<name>", "Project name or path (e.g., 'my-project' or 'output/my-project')").option("-o, --output <path>", "Output file path (overrides name-based path)").option("-r, --ratio <ratio>", "Aspect ratio (16:9, 9:16, 1:1, 4:5)", "16:9").option("
|
|
62421
|
+
projectCommand.command("create").description("Create a new project").argument("<name>", "Project name or path (e.g., 'my-project' or 'output/my-project')").option("-o, --output <path>", "Output file path (overrides name-based path)").option("-r, --ratio <ratio>", "Aspect ratio (16:9, 9:16, 1:1, 4:5)", "16:9").option("--fps <fps>", "Frame rate", "30").option("--dry-run", "Preview parameters without executing").action(async (name, options) => {
|
|
62402
62422
|
const startedAt = Date.now();
|
|
62403
62423
|
const spinner2 = ora("Creating project...").start();
|
|
62404
62424
|
try {
|
|
@@ -62512,7 +62532,7 @@ projectCommand.command("info").description("Show project information").argument(
|
|
|
62512
62532
|
exitWithError(generalError(`Failed to load project: ${msg}`));
|
|
62513
62533
|
}
|
|
62514
62534
|
});
|
|
62515
|
-
projectCommand.command("set").description("Update project settings").argument("<file>", "Project file path").option("
|
|
62535
|
+
projectCommand.command("set").description("Update project settings").argument("<file>", "Project file path").option("--name <name>", "Project name").option("-r, --ratio <ratio>", "Aspect ratio (16:9, 9:16, 1:1, 4:5)").option("--fps <fps>", "Frame rate").option("--dry-run", "Preview parameters without executing").action(async (file, options) => {
|
|
62516
62536
|
const startedAt = Date.now();
|
|
62517
62537
|
const spinner2 = ora("Updating project...").start();
|
|
62518
62538
|
try {
|
|
@@ -62579,6 +62599,7 @@ init_engine();
|
|
|
62579
62599
|
init_validate();
|
|
62580
62600
|
init_output();
|
|
62581
62601
|
import { readFile as readFile9, writeFile as writeFile9 } from "node:fs/promises";
|
|
62602
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
62582
62603
|
import { resolve as resolve11, basename as basename2, extname } from "node:path";
|
|
62583
62604
|
var timelineCommand = new Command("timeline").description("Timeline editing commands").addHelpText("after", `
|
|
62584
62605
|
Examples:
|
|
@@ -62592,7 +62613,7 @@ Examples:
|
|
|
62592
62613
|
Typical workflow: create project \u2192 add-source \u2192 add-clip \u2192 trim-clip/split-clip \u2192 export
|
|
62593
62614
|
Cost: Free (no API keys needed).
|
|
62594
62615
|
Run 'vibe schema timeline.<command>' for structured parameter info.`);
|
|
62595
|
-
timelineCommand.command("add-source").description("Add a media source to the project").argument("<project>", "Project file path").argument("<media>", "Media file path").option("
|
|
62616
|
+
timelineCommand.command("add-source").description("Add a media source to the project").argument("<project>", "Project file path").argument("<media>", "Media file path").option("--name <name>", "Source name (defaults to filename)").option("--type <type>", "Media type (video, audio, image, lottie)").option("-d, --duration <seconds>", "Duration in seconds (required for images)").option("--dry-run", "Preview parameters without executing").action(async (projectPath, mediaPath, options) => {
|
|
62596
62617
|
const startedAt = Date.now();
|
|
62597
62618
|
const spinner2 = ora("Adding source...").start();
|
|
62598
62619
|
try {
|
|
@@ -62654,7 +62675,7 @@ timelineCommand.command("add-source").description("Add a media source to the pro
|
|
|
62654
62675
|
exitWithError(generalError(`Failed to add source: ${msg}`));
|
|
62655
62676
|
}
|
|
62656
62677
|
});
|
|
62657
|
-
timelineCommand.command("add-clip").description("Add a clip to the timeline").argument("<project>", "Project file path").argument("<source-id>", "Source ID to use").option("
|
|
62678
|
+
timelineCommand.command("add-clip").description("Add a clip to the timeline").argument("<project>", "Project file path").argument("<source-id>", "Source ID to use").option("--track <id>", "Track ID (defaults to first matching track)").option("--start <seconds>", "Start time in timeline", "0").option("-d, --duration <seconds>", "Clip duration (defaults to source duration)").option("--offset <seconds>", "Source start offset", "0").option("--dry-run", "Preview parameters without executing").action(async (projectPath, sourceId, options) => {
|
|
62658
62679
|
const startedAt = Date.now();
|
|
62659
62680
|
const spinner2 = ora("Adding clip...").start();
|
|
62660
62681
|
try {
|
|
@@ -62736,7 +62757,7 @@ timelineCommand.command("add-clip").description("Add a clip to the timeline").ar
|
|
|
62736
62757
|
exitWithError(generalError(`Failed to add clip: ${msg}`));
|
|
62737
62758
|
}
|
|
62738
62759
|
});
|
|
62739
|
-
timelineCommand.command("add-track").description("Add a new track").argument("<project>", "Project file path").argument("<type>", "Track type (video, audio)").option("
|
|
62760
|
+
timelineCommand.command("add-track").description("Add a new track").argument("<project>", "Project file path").argument("<type>", "Track type (video, audio)").option("--name <name>", "Track name").option("--dry-run", "Preview parameters without executing").action(async (projectPath, type, options) => {
|
|
62740
62761
|
const startedAt = Date.now();
|
|
62741
62762
|
const spinner2 = ora("Adding track...").start();
|
|
62742
62763
|
try {
|
|
@@ -62793,7 +62814,7 @@ timelineCommand.command("add-track").description("Add a new track").argument("<p
|
|
|
62793
62814
|
exitWithError(generalError(`Failed to add track: ${msg}`));
|
|
62794
62815
|
}
|
|
62795
62816
|
});
|
|
62796
|
-
timelineCommand.command("add-effect").description("Add an effect to a clip").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID").argument("<effect-type>", "Effect type (fadeIn, fadeOut, blur, brightness, contrast, saturation, speed, volume)").option("
|
|
62817
|
+
timelineCommand.command("add-effect").description("Add an effect to a clip").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID").argument("<effect-type>", "Effect type (fadeIn, fadeOut, blur, brightness, contrast, saturation, speed, volume)").option("--start <seconds>", "Effect start time (relative to clip)", "0").option("-d, --duration <seconds>", "Effect duration (defaults to clip duration)").option("--params <json>", "Effect parameters as JSON", "{}").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, effectType, options) => {
|
|
62797
62818
|
const startedAt = Date.now();
|
|
62798
62819
|
const spinner2 = ora("Adding effect...").start();
|
|
62799
62820
|
try {
|
|
@@ -62929,6 +62950,9 @@ timelineCommand.command("list").description("List timeline contents").argument("
|
|
|
62929
62950
|
const startedAt = Date.now();
|
|
62930
62951
|
try {
|
|
62931
62952
|
const filePath = resolve11(process.cwd(), projectPath);
|
|
62953
|
+
if (!existsSync8(filePath)) {
|
|
62954
|
+
exitWithError(notFoundError(projectPath));
|
|
62955
|
+
}
|
|
62932
62956
|
const content = await readFile9(filePath, "utf-8");
|
|
62933
62957
|
const data = JSON.parse(content);
|
|
62934
62958
|
const project = Project.fromJSON(data);
|
|
@@ -63027,7 +63051,7 @@ timelineCommand.command("list").description("List timeline contents").argument("
|
|
|
63027
63051
|
exitWithError(generalError(`Failed to list timeline: ${msg}`));
|
|
63028
63052
|
}
|
|
63029
63053
|
});
|
|
63030
|
-
timelineCommand.command("split-clip").description("Split a clip at a specific time").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to split").option("
|
|
63054
|
+
timelineCommand.command("split-clip").description("Split a clip at a specific time").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to split").option("--time <seconds>", "Split time relative to clip start", "0").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
|
|
63031
63055
|
const startedAt = Date.now();
|
|
63032
63056
|
const spinner2 = ora("Splitting clip...").start();
|
|
63033
63057
|
try {
|
|
@@ -63089,7 +63113,7 @@ timelineCommand.command("split-clip").description("Split a clip at a specific ti
|
|
|
63089
63113
|
exitWithError(generalError(`Failed to split clip: ${msg}`));
|
|
63090
63114
|
}
|
|
63091
63115
|
});
|
|
63092
|
-
timelineCommand.command("duplicate-clip").description("Duplicate a clip").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to duplicate").option("
|
|
63116
|
+
timelineCommand.command("duplicate-clip").description("Duplicate a clip").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to duplicate").option("--time <seconds>", "Start time for duplicate (default: after original)").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
|
|
63093
63117
|
const startedAt = Date.now();
|
|
63094
63118
|
const spinner2 = ora("Duplicating clip...").start();
|
|
63095
63119
|
try {
|
|
@@ -63200,7 +63224,7 @@ timelineCommand.command("delete-clip").description("Delete a clip from the timel
|
|
|
63200
63224
|
exitWithError(generalError(`Failed to delete clip: ${msg}`));
|
|
63201
63225
|
}
|
|
63202
63226
|
});
|
|
63203
|
-
timelineCommand.command("move-clip").description("Move a clip to a new position").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to move").option("
|
|
63227
|
+
timelineCommand.command("move-clip").description("Move a clip to a new position").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to move").option("--time <seconds>", "New start time").option("--track <track-id>", "Move to different track").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
|
|
63204
63228
|
const startedAt = Date.now();
|
|
63205
63229
|
const spinner2 = ora("Moving clip...").start();
|
|
63206
63230
|
try {
|
|
@@ -63311,10 +63335,29 @@ Use '--fields response,model' to limit output size.
|
|
|
63311
63335
|
Run 'vibe schema inspect.<command>' for structured parameter info.
|
|
63312
63336
|
`
|
|
63313
63337
|
);
|
|
63314
|
-
analyzeCommand.command("media").description("Analyze any media: images, videos, or YouTube URLs using Gemini").argument("<source>", "Image/video file path, image URL, or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Describe this image', 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second for video (default: 1)").option("--start <seconds>", "Start offset in seconds (video only)").option("--end <seconds>", "End offset in seconds (video only)").option("--low-res", "Use low resolution mode (fewer tokens)").option("
|
|
63338
|
+
analyzeCommand.command("media").description("Analyze any media: images, videos, or YouTube URLs using Gemini").argument("<source>", "Image/video file path, image URL, or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Describe this image', 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second for video (default: 1)").option("--start <seconds>", "Start offset in seconds (video only)").option("--end <seconds>", "End offset in seconds (video only)").option("--low-res", "Use low resolution mode (fewer tokens)").option("--verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").option("--dry-run", "Preview parameters without executing").action(async (source, prompt3, options) => {
|
|
63315
63339
|
const startedAt = Date.now();
|
|
63316
63340
|
try {
|
|
63317
63341
|
rejectControlChars(prompt3);
|
|
63342
|
+
if (options.dryRun) {
|
|
63343
|
+
outputSuccess({
|
|
63344
|
+
command: "inspect media",
|
|
63345
|
+
startedAt,
|
|
63346
|
+
dryRun: true,
|
|
63347
|
+
data: {
|
|
63348
|
+
params: {
|
|
63349
|
+
source,
|
|
63350
|
+
prompt: prompt3,
|
|
63351
|
+
model: options.model,
|
|
63352
|
+
fps: options.fps,
|
|
63353
|
+
start: options.start,
|
|
63354
|
+
end: options.end,
|
|
63355
|
+
lowRes: options.lowRes ?? false
|
|
63356
|
+
}
|
|
63357
|
+
}
|
|
63358
|
+
});
|
|
63359
|
+
return;
|
|
63360
|
+
}
|
|
63318
63361
|
if (options.apiKey) {
|
|
63319
63362
|
process.env.GOOGLE_API_KEY = options.apiKey;
|
|
63320
63363
|
} else {
|
|
@@ -63344,7 +63387,7 @@ analyzeCommand.command("media").description("Analyze any media: images, videos,
|
|
|
63344
63387
|
data.totalTokens = result.totalTokens;
|
|
63345
63388
|
}
|
|
63346
63389
|
outputSuccess({
|
|
63347
|
-
command: "
|
|
63390
|
+
command: "inspect media",
|
|
63348
63391
|
startedAt,
|
|
63349
63392
|
data
|
|
63350
63393
|
});
|
|
@@ -63369,10 +63412,29 @@ analyzeCommand.command("media").description("Analyze any media: images, videos,
|
|
|
63369
63412
|
exitWithError(apiError(`Analysis failed: ${error.message}`));
|
|
63370
63413
|
}
|
|
63371
63414
|
});
|
|
63372
|
-
analyzeCommand.command("video").description("Analyze video using Gemini (summarize, Q&A, extract info)").argument("<source>", "Video file path or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second (default: 1, higher for action)").option("--start <seconds>", "Start offset in seconds (for clipping)").option("--end <seconds>", "End offset in seconds (for clipping)").option("--low-res", "Use low resolution mode (fewer tokens, longer videos)").option("
|
|
63415
|
+
analyzeCommand.command("video").description("Analyze video using Gemini (summarize, Q&A, extract info)").argument("<source>", "Video file path or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second (default: 1, higher for action)").option("--start <seconds>", "Start offset in seconds (for clipping)").option("--end <seconds>", "End offset in seconds (for clipping)").option("--low-res", "Use low resolution mode (fewer tokens, longer videos)").option("--verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").option("--dry-run", "Preview parameters without executing").action(async (source, prompt3, options) => {
|
|
63373
63416
|
const startedAt = Date.now();
|
|
63374
63417
|
try {
|
|
63375
63418
|
rejectControlChars(prompt3);
|
|
63419
|
+
if (options.dryRun) {
|
|
63420
|
+
outputSuccess({
|
|
63421
|
+
command: "inspect video",
|
|
63422
|
+
startedAt,
|
|
63423
|
+
dryRun: true,
|
|
63424
|
+
data: {
|
|
63425
|
+
params: {
|
|
63426
|
+
source,
|
|
63427
|
+
prompt: prompt3,
|
|
63428
|
+
model: options.model,
|
|
63429
|
+
fps: options.fps,
|
|
63430
|
+
start: options.start,
|
|
63431
|
+
end: options.end,
|
|
63432
|
+
lowRes: options.lowRes ?? false
|
|
63433
|
+
}
|
|
63434
|
+
}
|
|
63435
|
+
});
|
|
63436
|
+
return;
|
|
63437
|
+
}
|
|
63376
63438
|
if (options.apiKey) {
|
|
63377
63439
|
process.env.GOOGLE_API_KEY = options.apiKey;
|
|
63378
63440
|
} else {
|
|
@@ -63402,7 +63464,7 @@ analyzeCommand.command("video").description("Analyze video using Gemini (summari
|
|
|
63402
63464
|
data.totalTokens = result.totalTokens;
|
|
63403
63465
|
}
|
|
63404
63466
|
outputSuccess({
|
|
63405
|
-
command: "
|
|
63467
|
+
command: "inspect video",
|
|
63406
63468
|
startedAt,
|
|
63407
63469
|
data
|
|
63408
63470
|
});
|
|
@@ -63427,10 +63489,25 @@ analyzeCommand.command("video").description("Analyze video using Gemini (summari
|
|
|
63427
63489
|
}
|
|
63428
63490
|
});
|
|
63429
63491
|
registerReviewCommand(analyzeCommand);
|
|
63430
|
-
analyzeCommand.command("suggest").description("Get AI edit suggestions using Gemini").argument("<project>", "Project file path").argument("<instruction>", "Natural language instruction").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("--apply", "Apply the first suggestion automatically").action(async (projectPath, instruction, options) => {
|
|
63492
|
+
analyzeCommand.command("suggest").description("Get AI edit suggestions using Gemini").argument("<project>", "Project file path").argument("<instruction>", "Natural language instruction").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("--apply", "Apply the first suggestion automatically").option("--dry-run", "Preview parameters without executing").action(async (projectPath, instruction, options) => {
|
|
63431
63493
|
const startedAt = Date.now();
|
|
63432
63494
|
try {
|
|
63433
63495
|
rejectControlChars(instruction);
|
|
63496
|
+
if (options.dryRun) {
|
|
63497
|
+
outputSuccess({
|
|
63498
|
+
command: "inspect suggest",
|
|
63499
|
+
startedAt,
|
|
63500
|
+
dryRun: true,
|
|
63501
|
+
data: {
|
|
63502
|
+
params: {
|
|
63503
|
+
project: projectPath,
|
|
63504
|
+
instruction,
|
|
63505
|
+
apply: options.apply ?? false
|
|
63506
|
+
}
|
|
63507
|
+
}
|
|
63508
|
+
});
|
|
63509
|
+
return;
|
|
63510
|
+
}
|
|
63434
63511
|
const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
|
|
63435
63512
|
const spinner2 = ora("Initializing Gemini...").start();
|
|
63436
63513
|
const filePath = resolve32(process.cwd(), projectPath);
|
|
@@ -63445,7 +63522,7 @@ analyzeCommand.command("suggest").description("Get AI edit suggestions using Gem
|
|
|
63445
63522
|
spinner2.succeed(source_default.green(`Found ${suggestions.length} suggestion(s)`));
|
|
63446
63523
|
if (isJsonMode()) {
|
|
63447
63524
|
outputSuccess({
|
|
63448
|
-
command: "
|
|
63525
|
+
command: "inspect suggest",
|
|
63449
63526
|
startedAt,
|
|
63450
63527
|
data: {
|
|
63451
63528
|
suggestions: suggestions.map((s) => ({ type: s.type, description: s.description, confidence: s.confidence, clipIds: s.clipIds, params: s.params }))
|
|
@@ -63496,16 +63573,16 @@ init_output();
|
|
|
63496
63573
|
init_validate();
|
|
63497
63574
|
import { resolve as resolve33, dirname as dirname11, basename as basename7, extname as extname7 } from "node:path";
|
|
63498
63575
|
import { readFile as readFile24, writeFile as writeFile31 } from "node:fs/promises";
|
|
63499
|
-
import { existsSync as
|
|
63576
|
+
import { existsSync as existsSync26 } from "node:fs";
|
|
63500
63577
|
var audioCommand = new Command("audio").alias("au").description("Audio operations (transcribe, TTS, voice clone, ducking)").addHelpText(
|
|
63501
63578
|
"after",
|
|
63502
63579
|
`
|
|
63503
63580
|
Examples:
|
|
63504
|
-
$ vibe audio transcribe interview.mp3 -o transcript.srt
|
|
63581
|
+
$ vibe audio transcribe interview.mp3 -o transcript.srt --format srt
|
|
63505
63582
|
$ vibe audio transcribe video.mp4 -l ko # Specify language
|
|
63506
63583
|
$ vibe audio list-voices # List available voices
|
|
63507
63584
|
$ vibe audio isolate song.mp3 -o vocals.mp3
|
|
63508
|
-
$ vibe audio clone-voice sample.mp3
|
|
63585
|
+
$ vibe audio clone-voice sample.mp3 --name "my-voice"
|
|
63509
63586
|
$ vibe audio dub video.mp4 -l ko -o dubbed.mp4
|
|
63510
63587
|
$ vibe audio duck music.mp3 --voice narration.mp3 -o ducked.mp3
|
|
63511
63588
|
|
|
@@ -63518,7 +63595,7 @@ API Keys:
|
|
|
63518
63595
|
Run 'vibe schema audio.<command>' for structured parameter info.
|
|
63519
63596
|
`
|
|
63520
63597
|
);
|
|
63521
|
-
audioCommand.command("transcribe").description("Transcribe audio using Whisper").argument("<audio>", "Audio file path").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("-l, --language <lang>", "Language code (e.g., en, ko)").option("-o, --output <path>", "Output file path").option("
|
|
63598
|
+
audioCommand.command("transcribe").description("Transcribe audio using Whisper").argument("<audio>", "Audio file path").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("-l, --language <lang>", "Language code (e.g., en, ko)").option("-o, --output <path>", "Output file path").option("--format <format>", "Output format: json, srt, vtt (auto-detected from extension)").action(async (audioPath, options) => {
|
|
63522
63599
|
const startedAt = Date.now();
|
|
63523
63600
|
try {
|
|
63524
63601
|
if (options.output) {
|
|
@@ -63656,7 +63733,7 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
|
|
|
63656
63733
|
exitWithError(apiError(`Audio isolation failed: ${msg}`, true));
|
|
63657
63734
|
}
|
|
63658
63735
|
});
|
|
63659
|
-
audioCommand.command("clone-voice").description("Clone a voice from audio samples using ElevenLabs").argument("[samples...]", "Audio sample files (1-25 files)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("
|
|
63736
|
+
audioCommand.command("clone-voice").description("Clone a voice from audio samples using ElevenLabs").argument("[samples...]", "Audio sample files (1-25 files)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("--name <name>", "Voice name (required)").option("-d, --description <desc>", "Voice description").option("--labels <json>", `Labels as JSON (e.g., '{"accent": "american"}')`).option("--remove-noise", "Remove background noise from samples").option("--list", "List all available voices").option("--dry-run", "Preview parameters without executing").action(async (samples, options) => {
|
|
63660
63737
|
const startedAt = Date.now();
|
|
63661
63738
|
try {
|
|
63662
63739
|
if (options.dryRun) {
|
|
@@ -63700,7 +63777,7 @@ audioCommand.command("clone-voice").description("Clone a voice from audio sample
|
|
|
63700
63777
|
const audioBuffers = [];
|
|
63701
63778
|
for (const samplePath of samples) {
|
|
63702
63779
|
const absPath = resolve33(process.cwd(), samplePath);
|
|
63703
|
-
if (!
|
|
63780
|
+
if (!existsSync26(absPath)) {
|
|
63704
63781
|
spinner2.fail(`File not found: ${samplePath}`);
|
|
63705
63782
|
exitWithError(notFoundError(samplePath));
|
|
63706
63783
|
}
|
|
@@ -63742,7 +63819,7 @@ audioCommand.command("clone-voice").description("Clone a voice from audio sample
|
|
|
63742
63819
|
exitWithError(apiError(`Voice cloning failed: ${msg}`, true));
|
|
63743
63820
|
}
|
|
63744
63821
|
});
|
|
63745
|
-
audioCommand.command("dub").description("Dub audio/video to another language (transcribe, translate, TTS)").argument("<media>", "Input media file (video or audio)").option("-l, --language <lang>", "Target language code (e.g., es, ko, ja) (required)").option("--source <lang>", "Source language code (default: auto-detect)").option("
|
|
63822
|
+
audioCommand.command("dub").description("Dub audio/video to another language (transcribe, translate, TTS)").argument("<media>", "Input media file (video or audio)").option("-l, --language <lang>", "Target language code (e.g., es, ko, ja) (required)").option("--source <lang>", "Source language code (default: auto-detect)").option("--voice <id>", "ElevenLabs voice ID for output").option("--analyze-only", "Only analyze and show timing, don't generate audio").option("-o, --output <path>", "Output file path").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
|
|
63746
63823
|
const startedAt = Date.now();
|
|
63747
63824
|
try {
|
|
63748
63825
|
if (options.dryRun) {
|
|
@@ -63761,7 +63838,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
|
|
|
63761
63838
|
exitWithError(usageError("Target language is required. Use -l or --language"));
|
|
63762
63839
|
}
|
|
63763
63840
|
const absPath = resolve33(process.cwd(), mediaPath);
|
|
63764
|
-
if (!
|
|
63841
|
+
if (!existsSync26(absPath)) {
|
|
63765
63842
|
exitWithError(notFoundError(mediaPath));
|
|
63766
63843
|
}
|
|
63767
63844
|
const openaiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI");
|
|
@@ -63938,7 +64015,7 @@ ${segmentTexts}`,
|
|
|
63938
64015
|
exitWithError(apiError(`Dubbing failed: ${msg}`, true));
|
|
63939
64016
|
}
|
|
63940
64017
|
});
|
|
63941
|
-
audioCommand.command("duck").description("Auto-duck background music when voice is present (FFmpeg)").argument("<music>", "Background music file path").option("
|
|
64018
|
+
audioCommand.command("duck").description("Auto-duck background music when voice is present (FFmpeg)").argument("<music>", "Background music file path").option("--voice <path>", "Voice/narration track (required)").option("-o, --output <path>", "Output audio file path").option("--threshold <dB>", "Sidechain threshold in dB", "-30").option("-r, --ratio <ratio>", "Compression ratio", "3").option("-a, --attack <ms>", "Attack time in ms", "20").option("-l, --release <ms>", "Release time in ms", "200").option("--dry-run", "Preview parameters without executing").action(async (musicPath, options) => {
|
|
63942
64019
|
const startedAt = Date.now();
|
|
63943
64020
|
try {
|
|
63944
64021
|
if (options.dryRun) {
|
|
@@ -64004,7 +64081,7 @@ init_esm();
|
|
|
64004
64081
|
init_source();
|
|
64005
64082
|
init_ora();
|
|
64006
64083
|
import { resolve as resolve37, basename as basename10 } from "node:path";
|
|
64007
|
-
import { existsSync as
|
|
64084
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
64008
64085
|
|
|
64009
64086
|
// src/commands/ai-script-pipeline-cli.ts
|
|
64010
64087
|
init_source();
|
|
@@ -64015,19 +64092,19 @@ init_ai_script_pipeline();
|
|
|
64015
64092
|
init_output();
|
|
64016
64093
|
import { readFile as readFile25, writeFile as writeFile32 } from "node:fs/promises";
|
|
64017
64094
|
import { resolve as resolve34 } from "node:path";
|
|
64018
|
-
import { existsSync as
|
|
64095
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
64019
64096
|
function registerScriptPipelineCommands(aiCommand) {
|
|
64020
|
-
aiCommand.command("regenerate-scene").description("Regenerate a specific scene in a script-to-video output directory").argument("<project-dir>", "Path to the script-to-video output directory").requiredOption("--scene <numbers>", "Scene number(s) to regenerate (1-based), e.g., 3 or 3,4,5").option("--video-only", "Only regenerate video").option("--narration-only", "Only regenerate narration").option("--image-only", "Only regenerate image").option("
|
|
64097
|
+
aiCommand.command("regenerate-scene").description("Regenerate a specific scene in a script-to-video output directory").argument("<project-dir>", "Path to the script-to-video output directory").requiredOption("--scene <numbers>", "Scene number(s) to regenerate (1-based), e.g., 3 or 3,4,5").option("--video-only", "Only regenerate video").option("--narration-only", "Only regenerate narration").option("--image-only", "Only regenerate image").option("--generator <engine>", "Video generator: grok | kling | runway | veo", "grok").option("-i, --image-provider <provider>", "Image provider: gemini | openai | grok", "gemini").option("--voice <id>", "ElevenLabs voice ID for narration").option("-a, --aspect-ratio <ratio>", "Aspect ratio: 16:9 | 9:16 | 1:1", "16:9").option("--retries <count>", "Number of retries for video generation failures", String(DEFAULT_VIDEO_RETRIES)).option("--reference-scene <num>", "Use another scene's image as reference for character consistency").option("--dry-run", "Preview parameters without executing").action(async (projectDir, options) => {
|
|
64021
64098
|
const startedAt = Date.now();
|
|
64022
64099
|
try {
|
|
64023
64100
|
const outputDir = resolve34(process.cwd(), projectDir);
|
|
64024
64101
|
const projectPath = resolve34(outputDir, "project.vibe.json");
|
|
64025
|
-
if (!
|
|
64102
|
+
if (!existsSync27(outputDir)) {
|
|
64026
64103
|
exitWithError(notFoundError(outputDir));
|
|
64027
64104
|
}
|
|
64028
64105
|
const yamlPath = resolve34(outputDir, "storyboard.yaml");
|
|
64029
64106
|
const jsonPath = resolve34(outputDir, "storyboard.json");
|
|
64030
|
-
const storyboardPath =
|
|
64107
|
+
const storyboardPath = existsSync27(yamlPath) ? yamlPath : existsSync27(jsonPath) ? jsonPath : null;
|
|
64031
64108
|
const storyboardIsYaml = storyboardPath === yamlPath;
|
|
64032
64109
|
if (!storyboardPath) {
|
|
64033
64110
|
exitWithError(notFoundError(`${outputDir}/storyboard.{yaml,json}`));
|
|
@@ -64125,7 +64202,7 @@ function registerScriptPipelineCommands(aiCommand) {
|
|
|
64125
64202
|
exitWithError(apiError(result.error || "Scene regeneration failed", true));
|
|
64126
64203
|
}
|
|
64127
64204
|
spinner2.succeed(source_default.green(`Regenerated ${result.regeneratedScenes.length} scene(s)`));
|
|
64128
|
-
if (
|
|
64205
|
+
if (existsSync27(projectPath) && result.regeneratedScenes.length > 0) {
|
|
64129
64206
|
const updateSpinner = ora("\u{1F4E6} Updating project file...").start();
|
|
64130
64207
|
try {
|
|
64131
64208
|
const updatedContent = await readFile25(storyboardPath, "utf-8");
|
|
@@ -64139,7 +64216,7 @@ function registerScriptPipelineCommands(aiCommand) {
|
|
|
64139
64216
|
const imagePath = resolve34(outputDir, `scene-${sceneNum}.png`);
|
|
64140
64217
|
const videoSource = projectData.state.sources.find((s) => s.name === `Scene ${sceneNum}`);
|
|
64141
64218
|
if (videoSource) {
|
|
64142
|
-
const hasVideo =
|
|
64219
|
+
const hasVideo = existsSync27(videoPath);
|
|
64143
64220
|
videoSource.url = hasVideo ? videoPath : imagePath;
|
|
64144
64221
|
videoSource.type = hasVideo ? "video" : "image";
|
|
64145
64222
|
videoSource.duration = segment.duration;
|
|
@@ -64203,9 +64280,9 @@ Two flows \u2014 pick by intent:
|
|
|
64203
64280
|
|
|
64204
64281
|
Examples (REMIX):
|
|
64205
64282
|
$ vibe remix highlights long-video.mp4 -o highlights.json -d 60
|
|
64206
|
-
$ vibe remix auto-shorts long-video.mp4 -o shorts/
|
|
64207
|
-
$ vibe remix animated-caption video.mp4 -o captioned.mp4
|
|
64208
|
-
$ vibe remix animated-caption video.mp4 -o out.mp4
|
|
64283
|
+
$ vibe remix auto-shorts long-video.mp4 -o shorts/ --count 3 --add-captions
|
|
64284
|
+
$ vibe remix animated-caption video.mp4 -o captioned.mp4 --style highlight
|
|
64285
|
+
$ vibe remix animated-caption video.mp4 -o out.mp4 --style karaoke-sweep --fast
|
|
64209
64286
|
|
|
64210
64287
|
Provider API Keys:
|
|
64211
64288
|
highlights: GOOGLE_API_KEY (Gemini analysis)
|
|
@@ -64225,13 +64302,13 @@ Run 'vibe schema remix.<command>' for structured parameter info.
|
|
|
64225
64302
|
registerScriptPipelineCommands(pipelineCommand);
|
|
64226
64303
|
registerHighlightsCommands(pipelineCommand);
|
|
64227
64304
|
var ANIMATED_CAPTION_STYLES = ["highlight", "bounce", "pop-in", "neon", "karaoke-sweep", "typewriter"];
|
|
64228
|
-
pipelineCommand.command("animated-caption").description("Add animated captions with word-by-word effects (Whisper + Remotion/ASS)").argument("<video>", "Video file path").option("
|
|
64305
|
+
pipelineCommand.command("animated-caption").description("Add animated captions with word-by-word effects (Whisper + Remotion/ASS)").argument("<video>", "Video file path").option("--style <preset>", "Style preset (default: highlight)", "highlight").option("--highlight-color <color>", "Active word highlight color", "#FFFF00").option("--font-size <px>", "Font size (default: auto based on resolution)").option("--position <pos>", "Caption position: top, center, bottom", "bottom").option("--words-per-group <n>", "Words shown at once (default: auto 3-5)").option("--max-chars <n>", "Max characters per group").option("-l, --language <lang>", "Whisper language hint").option("--fast", "Use ASS/FFmpeg only (no Remotion, forces ASS tier styles)").option("-o, --output <path>", "Output file path").option("--dry-run", "Preview parameters without executing").addHelpText(
|
|
64229
64306
|
"after",
|
|
64230
64307
|
`
|
|
64231
64308
|
Examples:
|
|
64232
64309
|
$ vibe remix animated-caption video.mp4 -o captioned.mp4
|
|
64233
|
-
$ vibe remix animated-caption video.mp4 -o out.mp4
|
|
64234
|
-
$ vibe remix animated-caption video.mp4 -o out.mp4
|
|
64310
|
+
$ vibe remix animated-caption video.mp4 -o out.mp4 --style bounce
|
|
64311
|
+
$ vibe remix animated-caption video.mp4 -o out.mp4 --style karaoke-sweep --fast
|
|
64235
64312
|
|
|
64236
64313
|
Styles:
|
|
64237
64314
|
highlight (default) TikTok-style background highlight on active word (Remotion)
|
|
@@ -64247,7 +64324,7 @@ Required API Key: OPENAI_API_KEY (Whisper transcription)
|
|
|
64247
64324
|
const startedAt = Date.now();
|
|
64248
64325
|
try {
|
|
64249
64326
|
const absVideoPath = resolve37(process.cwd(), videoPath);
|
|
64250
|
-
if (!
|
|
64327
|
+
if (!existsSync30(absVideoPath)) {
|
|
64251
64328
|
exitWithError(notFoundError(absVideoPath));
|
|
64252
64329
|
}
|
|
64253
64330
|
if (!ANIMATED_CAPTION_STYLES.includes(options.style)) {
|
|
@@ -68747,7 +68824,7 @@ batchCommand.command("concat").description("Concatenate multiple sources into se
|
|
|
68747
68824
|
exitWithError(generalError(`Concat failed: ${msg}`));
|
|
68748
68825
|
}
|
|
68749
68826
|
});
|
|
68750
|
-
batchCommand.command("apply-effect").description("Apply an effect to multiple clips").argument("<project>", "Project file path").argument("<effect-type>", "Effect type (fadeIn, fadeOut, blur, etc.)").argument("[clip-ids...]", "Clip IDs to apply effect to (or --all)").option("--all", "Apply to all clips", false).option("-d, --duration <seconds>", "Effect duration", "1").option("
|
|
68827
|
+
batchCommand.command("apply-effect").description("Apply an effect to multiple clips").argument("<project>", "Project file path").argument("<effect-type>", "Effect type (fadeIn, fadeOut, blur, etc.)").argument("[clip-ids...]", "Clip IDs to apply effect to (or --all)").option("--all", "Apply to all clips", false).option("-d, --duration <seconds>", "Effect duration", "1").option("--start <seconds>", "Effect start time (relative to clip)", "0").option("--intensity <value>", "Effect intensity (0-1)", "1").option("--dry-run", "Preview parameters without executing").action(
|
|
68751
68828
|
async (projectPath, effectType, clipIds, options) => {
|
|
68752
68829
|
const startedAt = Date.now();
|
|
68753
68830
|
const spinner2 = ora("Applying effects...").start();
|
|
@@ -69348,7 +69425,7 @@ init_agent_host_detect();
|
|
|
69348
69425
|
init_scene_project();
|
|
69349
69426
|
init_visual_styles();
|
|
69350
69427
|
init_install_skill();
|
|
69351
|
-
import { existsSync as
|
|
69428
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
69352
69429
|
import { mkdir as mkdir17, readFile as readFile30, writeFile as writeFile37 } from "node:fs/promises";
|
|
69353
69430
|
import { basename as basename14, dirname as dirname14, resolve as resolve42 } from "node:path";
|
|
69354
69431
|
|
|
@@ -69639,7 +69716,7 @@ var initCommand = new Command("init").description("Scaffold a VibeFrame project
|
|
|
69639
69716
|
const wantsGemini = targetHosts.includes("gemini-cli");
|
|
69640
69717
|
const wantsCrossTool = targetHosts.some((h) => h !== "claude-code");
|
|
69641
69718
|
const actions = [];
|
|
69642
|
-
if (!
|
|
69719
|
+
if (!existsSync31(projectDir)) {
|
|
69643
69720
|
if (options.dryRun) {
|
|
69644
69721
|
actions.push({ path: projectDir, status: "would-write", reason: "(would create directory)" });
|
|
69645
69722
|
} else {
|
|
@@ -69885,7 +69962,7 @@ function resolveTargets(agent) {
|
|
|
69885
69962
|
return [agent];
|
|
69886
69963
|
}
|
|
69887
69964
|
async function writeIfMissing(absPath, content, force, dryRun) {
|
|
69888
|
-
const exists =
|
|
69965
|
+
const exists = existsSync31(absPath);
|
|
69889
69966
|
if (exists && !force) {
|
|
69890
69967
|
return { path: absPath, status: "skipped-exists", reason: "(use --force to overwrite)" };
|
|
69891
69968
|
}
|
|
@@ -69898,7 +69975,7 @@ async function writeIfMissing(absPath, content, force, dryRun) {
|
|
|
69898
69975
|
}
|
|
69899
69976
|
async function mergeGitignore(absPath, additions, dryRun) {
|
|
69900
69977
|
const marker = "# VibeFrame";
|
|
69901
|
-
if (!
|
|
69978
|
+
if (!existsSync31(absPath)) {
|
|
69902
69979
|
if (dryRun) return { path: absPath, status: "would-write" };
|
|
69903
69980
|
await writeFile37(absPath, additions.trimStart(), "utf-8");
|
|
69904
69981
|
return { path: absPath, status: "wrote" };
|
|
@@ -70241,7 +70318,7 @@ init_composer_resolve();
|
|
|
70241
70318
|
init_scene_build();
|
|
70242
70319
|
init_output();
|
|
70243
70320
|
import { access as access6 } from "node:fs/promises";
|
|
70244
|
-
import { existsSync as
|
|
70321
|
+
import { existsSync as existsSync35 } from "node:fs";
|
|
70245
70322
|
import { resolve as resolve49 } from "node:path";
|
|
70246
70323
|
var COMMAND_KEY_MAP = getCommandKeyMap();
|
|
70247
70324
|
var FREE_COMMANDS = [
|
|
@@ -70355,7 +70432,7 @@ async function runDiagnostics() {
|
|
|
70355
70432
|
installed = false;
|
|
70356
70433
|
}
|
|
70357
70434
|
} else if (toolName === "chrome") {
|
|
70358
|
-
const { existsSync:
|
|
70435
|
+
const { existsSync: existsSync40 } = await import("node:fs");
|
|
70359
70436
|
const { homedir: homedir6 } = await import("node:os");
|
|
70360
70437
|
const { join: join16 } = await import("node:path");
|
|
70361
70438
|
const chromePaths = [
|
|
@@ -70368,7 +70445,7 @@ async function runDiagnostics() {
|
|
|
70368
70445
|
"/usr/bin/chromium",
|
|
70369
70446
|
"/usr/bin/chromium-browser"
|
|
70370
70447
|
];
|
|
70371
|
-
installed = chromePaths.some((p) => p &&
|
|
70448
|
+
installed = chromePaths.some((p) => p && existsSync40(p));
|
|
70372
70449
|
} else {
|
|
70373
70450
|
installed = commandExists(toolName);
|
|
70374
70451
|
}
|
|
@@ -70399,7 +70476,7 @@ async function runDiagnostics() {
|
|
|
70399
70476
|
const cwd = process.cwd();
|
|
70400
70477
|
const projectFiles = ["AGENTS.md", "CLAUDE.md", "vibe.project.yaml"].map((rel) => ({
|
|
70401
70478
|
path: rel,
|
|
70402
|
-
exists:
|
|
70479
|
+
exists: existsSync35(resolve49(cwd, rel))
|
|
70403
70480
|
}));
|
|
70404
70481
|
const projectInitialized = projectFiles.some((f) => f.exists);
|
|
70405
70482
|
const hosts = detectAgentHosts();
|
|
@@ -70414,8 +70491,8 @@ async function runDiagnostics() {
|
|
|
70414
70491
|
} catch (err) {
|
|
70415
70492
|
if (!(err instanceof ComposerResolveError)) throw err;
|
|
70416
70493
|
}
|
|
70417
|
-
const sceneProjectInCwd =
|
|
70418
|
-
const skillInstalled =
|
|
70494
|
+
const sceneProjectInCwd = existsSync35(resolve49(cwd, "STORYBOARD.md"));
|
|
70495
|
+
const skillInstalled = existsSync35(resolve49(cwd, "SKILL.md"));
|
|
70419
70496
|
return {
|
|
70420
70497
|
system: {
|
|
70421
70498
|
node: { version: nodeVersion, ok: true },
|
|
@@ -70581,7 +70658,7 @@ init_detect();
|
|
|
70581
70658
|
init_ai_edit();
|
|
70582
70659
|
init_output();
|
|
70583
70660
|
import { resolve as resolve50 } from "node:path";
|
|
70584
|
-
import { existsSync as
|
|
70661
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
70585
70662
|
import { mkdir as mkdir20, rm as rm3 } from "node:fs/promises";
|
|
70586
70663
|
var DEMO_DIR = resolve50(process.cwd(), ".vibeframe-demo");
|
|
70587
70664
|
async function generateTestVideo(outputPath) {
|
|
@@ -70612,7 +70689,7 @@ async function generateTestVideo(outputPath) {
|
|
|
70612
70689
|
"-y",
|
|
70613
70690
|
outputPath
|
|
70614
70691
|
], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
|
|
70615
|
-
return
|
|
70692
|
+
return existsSync36(outputPath);
|
|
70616
70693
|
} catch {
|
|
70617
70694
|
try {
|
|
70618
70695
|
await execSafe("ffmpeg", [
|
|
@@ -70638,7 +70715,7 @@ async function generateTestVideo(outputPath) {
|
|
|
70638
70715
|
"-y",
|
|
70639
70716
|
outputPath
|
|
70640
70717
|
], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
|
|
70641
|
-
return
|
|
70718
|
+
return existsSync36(outputPath);
|
|
70642
70719
|
} catch {
|
|
70643
70720
|
return false;
|
|
70644
70721
|
}
|