@vibeframe/cli 0.115.8 → 0.115.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/_shared/lottie.d.ts +40 -0
- package/dist/commands/_shared/lottie.test.d.ts +1 -0
- package/dist/commands/_shared/scene-html-emit.d.ts +8 -9
- package/dist/commands/_shared/scene-lottie.integration.test.d.ts +1 -0
- package/dist/commands/edit/lottie-overlay.d.ts +2 -13
- package/dist/index.js +784 -755
- package/dist/tools/manifest/index.js +636 -607
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -10198,14 +10198,14 @@ async function loadKokoroRuntime() {
|
|
|
10198
10198
|
async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
|
|
10199
10199
|
try {
|
|
10200
10200
|
const { readFile: readFile55 } = await import("node:fs/promises");
|
|
10201
|
-
const { join:
|
|
10201
|
+
const { join: join36, resolve: resolve85 } = await import("node:path");
|
|
10202
10202
|
const { pathToFileURL } = await import("node:url");
|
|
10203
10203
|
const pkgDir = resolve85(baseDir, "node_modules", "kokoro-js");
|
|
10204
|
-
const pkg = JSON.parse(await readFile55(
|
|
10204
|
+
const pkg = JSON.parse(await readFile55(join36(pkgDir, "package.json"), "utf-8"));
|
|
10205
10205
|
const entry = pkg.exports?.node?.import ?? pkg.exports?.default ?? pkg.module ?? pkg.main;
|
|
10206
10206
|
if (!entry)
|
|
10207
10207
|
return null;
|
|
10208
|
-
const mod = await import(pathToFileURL(
|
|
10208
|
+
const mod = await import(pathToFileURL(join36(pkgDir, entry)).href);
|
|
10209
10209
|
return mod.KokoroTTS ?? null;
|
|
10210
10210
|
} catch {
|
|
10211
10211
|
return null;
|
|
@@ -10216,20 +10216,20 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
|
|
|
10216
10216
|
if (!base)
|
|
10217
10217
|
return null;
|
|
10218
10218
|
try {
|
|
10219
|
-
const { readFile: readFile55, writeFile: writeFile53, mkdir:
|
|
10220
|
-
const { join:
|
|
10219
|
+
const { readFile: readFile55, writeFile: writeFile53, mkdir: mkdir33 } = await import("node:fs/promises");
|
|
10220
|
+
const { join: join36, resolve: resolve85 } = await import("node:path");
|
|
10221
10221
|
const { pathToFileURL } = await import("node:url");
|
|
10222
10222
|
const { homedir: homedir8 } = await import("node:os");
|
|
10223
10223
|
const transformersDir = resolve85(base, "node_modules", "@huggingface", "transformers");
|
|
10224
10224
|
const ortDistDir = resolve85(base, "node_modules", "onnxruntime-web", "dist");
|
|
10225
|
-
const transformers = await import(pathToFileURL(
|
|
10226
|
-
const cacheRoot =
|
|
10225
|
+
const transformers = await import(pathToFileURL(join36(transformersDir, "dist", "transformers.web.js")).href);
|
|
10226
|
+
const cacheRoot = join36(homedir8(), ".cache", "vibeframe", "models");
|
|
10227
10227
|
transformers.env.allowLocalModels = false;
|
|
10228
10228
|
transformers.env.useCustomCache = true;
|
|
10229
10229
|
transformers.env.customCache = {
|
|
10230
10230
|
async match(key) {
|
|
10231
10231
|
try {
|
|
10232
|
-
return new Response(await readFile55(
|
|
10232
|
+
return new Response(await readFile55(join36(cacheRoot, encodeURIComponent(String(key)))));
|
|
10233
10233
|
} catch {
|
|
10234
10234
|
return void 0;
|
|
10235
10235
|
}
|
|
@@ -10237,8 +10237,8 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
|
|
|
10237
10237
|
async put(key, response) {
|
|
10238
10238
|
try {
|
|
10239
10239
|
const buf = Buffer.from(await response.arrayBuffer());
|
|
10240
|
-
await
|
|
10241
|
-
await writeFile53(
|
|
10240
|
+
await mkdir33(cacheRoot, { recursive: true });
|
|
10241
|
+
await writeFile53(join36(cacheRoot, encodeURIComponent(String(key))), buf);
|
|
10242
10242
|
} catch {
|
|
10243
10243
|
}
|
|
10244
10244
|
}
|
|
@@ -28909,6 +28909,81 @@ var init_visual_styles = __esm({
|
|
|
28909
28909
|
}
|
|
28910
28910
|
});
|
|
28911
28911
|
|
|
28912
|
+
// src/commands/_shared/lottie.ts
|
|
28913
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
28914
|
+
import { copyFile, mkdir as mkdir5, readFile as readFile7 } from "node:fs/promises";
|
|
28915
|
+
import { createRequire } from "node:module";
|
|
28916
|
+
import { basename as basename4, join as join2 } from "node:path";
|
|
28917
|
+
import { fileURLToPath } from "node:url";
|
|
28918
|
+
async function checkLottieAsset(path) {
|
|
28919
|
+
const bytes = await readFile7(path);
|
|
28920
|
+
const isZip = bytes.length > 4 && bytes[0] === 80 && bytes[1] === 75;
|
|
28921
|
+
if (isZip) return void 0;
|
|
28922
|
+
try {
|
|
28923
|
+
const json = JSON.parse(bytes.toString("utf-8"));
|
|
28924
|
+
if (Array.isArray(json.layers)) return void 0;
|
|
28925
|
+
} catch {
|
|
28926
|
+
}
|
|
28927
|
+
return `${basename4(path)} is not a Lottie animation (expected Lottie JSON or a .lottie archive).`;
|
|
28928
|
+
}
|
|
28929
|
+
function overlayBox(layer, position, scale) {
|
|
28930
|
+
if (position === "full") return { left: 0, top: 0, width: layer.width, height: layer.height };
|
|
28931
|
+
const width = Math.max(1, Math.round(layer.width * scale));
|
|
28932
|
+
const height = Math.max(1, Math.round(layer.height * scale));
|
|
28933
|
+
const marginX = Math.round(layer.width * 0.04);
|
|
28934
|
+
const marginY = Math.round(layer.height * 0.04);
|
|
28935
|
+
const left = position === "center" ? Math.round((layer.width - width) / 2) : position.endsWith("left") ? marginX : layer.width - marginX - width;
|
|
28936
|
+
const top = position === "center" ? Math.round((layer.height - height) / 2) : position.startsWith("top") ? marginY : layer.height - marginY - height;
|
|
28937
|
+
return { left, top, width, height };
|
|
28938
|
+
}
|
|
28939
|
+
function dotLottieDist() {
|
|
28940
|
+
try {
|
|
28941
|
+
return join2(createRequire(import.meta.url).resolve("@lottiefiles/dotlottie-web"), "..");
|
|
28942
|
+
} catch {
|
|
28943
|
+
const bundled = fileURLToPath(new URL(`./${BUNDLED_DOTLOTTIE_DIR}/`, import.meta.url));
|
|
28944
|
+
if (existsSync3(join2(bundled, DOTLOTTIE_WASM))) return bundled;
|
|
28945
|
+
throw new Error("The dotLottie runtime (@lottiefiles/dotlottie-web) is not installed. Reinstall VibeFrame.");
|
|
28946
|
+
}
|
|
28947
|
+
}
|
|
28948
|
+
async function copyDotLottieRuntime(dir) {
|
|
28949
|
+
const dist = dotLottieDist();
|
|
28950
|
+
await mkdir5(dir, { recursive: true });
|
|
28951
|
+
await copyFile(join2(dist, "index.js"), join2(dir, DOTLOTTIE_SCRIPT));
|
|
28952
|
+
await copyFile(join2(dist, DOTLOTTIE_WASM), join2(dir, DOTLOTTIE_WASM));
|
|
28953
|
+
}
|
|
28954
|
+
var LOTTIE_POSITIONS, DOTLOTTIE_SCRIPT, DOTLOTTIE_WASM, BUNDLED_DOTLOTTIE_DIR, DOTLOTTIE_BROWSER_HELPERS;
|
|
28955
|
+
var init_lottie = __esm({
|
|
28956
|
+
"src/commands/_shared/lottie.ts"() {
|
|
28957
|
+
"use strict";
|
|
28958
|
+
LOTTIE_POSITIONS = [
|
|
28959
|
+
"full",
|
|
28960
|
+
"center",
|
|
28961
|
+
"top-left",
|
|
28962
|
+
"top-right",
|
|
28963
|
+
"bottom-left",
|
|
28964
|
+
"bottom-right"
|
|
28965
|
+
];
|
|
28966
|
+
DOTLOTTIE_SCRIPT = "dotlottie-web.js";
|
|
28967
|
+
DOTLOTTIE_WASM = "dotlottie-player.wasm";
|
|
28968
|
+
BUNDLED_DOTLOTTIE_DIR = "dotlottie";
|
|
28969
|
+
DOTLOTTIE_BROWSER_HELPERS = `async function loadDotLottie(vendorDir) {
|
|
28970
|
+
const url = (file) => new URL(vendorDir + "/" + file, document.baseURI).href;
|
|
28971
|
+
const { DotLottie } = await import(url("${DOTLOTTIE_SCRIPT}"));
|
|
28972
|
+
const wasm = await (await fetch(url("${DOTLOTTIE_WASM}"))).arrayBuffer();
|
|
28973
|
+
DotLottie.setWasmUrl(URL.createObjectURL(new Blob([wasm], { type: "application/wasm" })));
|
|
28974
|
+
return DotLottie;
|
|
28975
|
+
}
|
|
28976
|
+
function setLottieTime(player, t, loop) {
|
|
28977
|
+
const length = player.duration;
|
|
28978
|
+
const frames = player.totalFrames;
|
|
28979
|
+
if (!(length > 0) || !(frames > 0)) return;
|
|
28980
|
+
const local = Math.max(0, Number(t) || 0);
|
|
28981
|
+
const at = loop ? local % length : Math.min(local, length);
|
|
28982
|
+
player.setFrame(Math.min(frames - 1, (at / length) * frames));
|
|
28983
|
+
}`;
|
|
28984
|
+
}
|
|
28985
|
+
});
|
|
28986
|
+
|
|
28912
28987
|
// src/commands/_shared/scene-html-emit.ts
|
|
28913
28988
|
function esc(text) {
|
|
28914
28989
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -29206,51 +29281,45 @@ function buildPreset(input) {
|
|
|
29206
29281
|
}
|
|
29207
29282
|
}
|
|
29208
29283
|
}
|
|
29209
|
-
function
|
|
29284
|
+
function jsString(value) {
|
|
29285
|
+
return JSON.stringify(value).replace(/</g, "\\u003c");
|
|
29286
|
+
}
|
|
29287
|
+
function buildLottieOverlay(input, canvas, sceneId, duration) {
|
|
29210
29288
|
const pos = input.position ?? "full";
|
|
29211
|
-
const
|
|
29212
|
-
const scale = Math.max(0.01, Math.min(2, rawScale));
|
|
29289
|
+
const scale = Math.max(0.01, Math.min(2, input.scale ?? (pos === "full" ? 1 : 0.25)));
|
|
29213
29290
|
const opacity = Math.max(0, Math.min(1, input.opacity ?? 1));
|
|
29214
|
-
const
|
|
29215
|
-
"position:absolute",
|
|
29216
|
-
"pointer-events:none",
|
|
29217
|
-
`opacity:${opacity}`
|
|
29218
|
-
];
|
|
29219
|
-
if (pos === "full") {
|
|
29220
|
-
base.push("inset:0", "width:100%", "height:100%");
|
|
29221
|
-
} else {
|
|
29222
|
-
const pct = scale * 100;
|
|
29223
|
-
base.push(`width:${pct}%`, `height:${pct}%`);
|
|
29224
|
-
switch (pos) {
|
|
29225
|
-
case "center":
|
|
29226
|
-
base.push("top:50%", "left:50%", "transform:translate(-50%,-50%)");
|
|
29227
|
-
break;
|
|
29228
|
-
case "top-left":
|
|
29229
|
-
base.push("top:4%", "left:4%");
|
|
29230
|
-
break;
|
|
29231
|
-
case "top-right":
|
|
29232
|
-
base.push("top:4%", "right:4%");
|
|
29233
|
-
break;
|
|
29234
|
-
case "bottom-left":
|
|
29235
|
-
base.push("bottom:4%", "left:4%");
|
|
29236
|
-
break;
|
|
29237
|
-
case "bottom-right":
|
|
29238
|
-
base.push("bottom:4%", "right:4%");
|
|
29239
|
-
break;
|
|
29240
|
-
}
|
|
29241
|
-
}
|
|
29242
|
-
return base.join(";");
|
|
29243
|
-
}
|
|
29244
|
-
function buildLottieOverlay(input, sceneId) {
|
|
29245
|
-
const loop = input.loop ?? true ? " loop" : "";
|
|
29246
|
-
const style = lottieOverlayStyle(input);
|
|
29291
|
+
const box = overlayBox(canvas, pos, scale);
|
|
29247
29292
|
const overlayId = `lottie-overlay-${sceneId}`;
|
|
29248
|
-
const markup = `<
|
|
29249
|
-
const
|
|
29250
|
-
|
|
29251
|
-
|
|
29252
|
-
|
|
29253
|
-
|
|
29293
|
+
const markup = `<canvas id="${overlayId}" class="lottie-overlay" width="${box.width}" height="${box.height}" style="position:absolute;left:${box.left}px;top:${box.top}px;width:${box.width}px;height:${box.height}px;pointer-events:none;opacity:${opacity}"></canvas>`;
|
|
29294
|
+
const register = `${DOTLOTTIE_BROWSER_HELPERS}
|
|
29295
|
+
(async () => {
|
|
29296
|
+
const DotLottie = await loadDotLottie(${jsString(LOTTIE_RUNTIME_DIR)});
|
|
29297
|
+
const player = new DotLottie({
|
|
29298
|
+
canvas: document.getElementById(${jsString(overlayId)}),
|
|
29299
|
+
src: ${jsString(input.src)},
|
|
29300
|
+
autoplay: false,
|
|
29301
|
+
loop: false,
|
|
29302
|
+
renderConfig: { devicePixelRatio: 1, autoResize: false, freezeOnOffscreen: false },
|
|
29303
|
+
});
|
|
29304
|
+
await new Promise((resolve, reject) => {
|
|
29305
|
+
player.addEventListener("load", resolve);
|
|
29306
|
+
player.addEventListener("loadError", (event) => reject(event && event.error));
|
|
29307
|
+
});
|
|
29308
|
+
let sceneTime = 0;
|
|
29309
|
+
const lottieClock = {
|
|
29310
|
+
time(t) {
|
|
29311
|
+
if (t === undefined) return sceneTime;
|
|
29312
|
+
sceneTime = t;
|
|
29313
|
+
setLottieTime(player, t, ${input.loop ?? true});
|
|
29314
|
+
},
|
|
29315
|
+
};
|
|
29316
|
+
tl.fromTo(lottieClock, { time: 0 }, { time: ${duration}, duration: ${duration}, ease: "none" }, 0);
|
|
29317
|
+
})()
|
|
29318
|
+
.catch((error) => console.error("[vibe] Lottie overlay failed to load:", ${jsString(input.src)}, error))
|
|
29319
|
+
.finally(() => {
|
|
29320
|
+
window.__timelines["${sceneId}"] = tl;
|
|
29321
|
+
});`;
|
|
29322
|
+
return { markup, register };
|
|
29254
29323
|
}
|
|
29255
29324
|
function emitSceneHtml(input) {
|
|
29256
29325
|
if (input.duration <= 0) {
|
|
@@ -29273,11 +29342,10 @@ function emitSceneHtml(input) {
|
|
|
29273
29342
|
data-volume="1"
|
|
29274
29343
|
></audio>
|
|
29275
29344
|
` : "";
|
|
29276
|
-
const lottieLayer = input.lottie ? buildLottieOverlay(input.lottie, id) : null;
|
|
29345
|
+
const lottieLayer = input.lottie ? buildLottieOverlay(input.lottie, input, id, dur) : null;
|
|
29277
29346
|
const lottieMarkup = lottieLayer ? `
|
|
29278
29347
|
${lottieLayer.markup}` : "";
|
|
29279
|
-
const
|
|
29280
|
-
${lottieLayer.script}` : "";
|
|
29348
|
+
const register = lottieLayer?.register ?? `window.__timelines["${id}"] = tl;`;
|
|
29281
29349
|
return `<template id="${id}-template">
|
|
29282
29350
|
<div data-composition-id="${id}" data-start="0" data-duration="${dur}" data-width="${input.width}" data-height="${input.height}">
|
|
29283
29351
|
<style>
|
|
@@ -29286,14 +29354,14 @@ function emitSceneHtml(input) {
|
|
|
29286
29354
|
|
|
29287
29355
|
${parts.body}${lottieMarkup}
|
|
29288
29356
|
${audioBlock}
|
|
29289
|
-
<script src="${GSAP_CDN}"></script
|
|
29357
|
+
<script src="${GSAP_CDN}"></script>
|
|
29290
29358
|
<script>
|
|
29291
29359
|
window.__timelines = window.__timelines || {};
|
|
29292
29360
|
const tl = gsap.timeline({ paused: true });
|
|
29293
29361
|
|
|
29294
29362
|
${parts.timeline}
|
|
29295
29363
|
|
|
29296
|
-
|
|
29364
|
+
${register}
|
|
29297
29365
|
</script>
|
|
29298
29366
|
</div>
|
|
29299
29367
|
</template>
|
|
@@ -29349,10 +29417,11 @@ function readRootDims(rootHtml) {
|
|
|
29349
29417
|
if (!widthMatch || !heightMatch) return null;
|
|
29350
29418
|
return { width: parseInt(widthMatch[1], 10), height: parseInt(heightMatch[1], 10) };
|
|
29351
29419
|
}
|
|
29352
|
-
var SCENE_PRESETS,
|
|
29420
|
+
var SCENE_PRESETS, LOTTIE_RUNTIME_DIR, GSAP_CDN, SCENE_OVERLAP_SECONDS;
|
|
29353
29421
|
var init_scene_html_emit = __esm({
|
|
29354
29422
|
"src/commands/_shared/scene-html-emit.ts"() {
|
|
29355
29423
|
"use strict";
|
|
29424
|
+
init_lottie();
|
|
29356
29425
|
SCENE_PRESETS = [
|
|
29357
29426
|
"simple",
|
|
29358
29427
|
"announcement",
|
|
@@ -29360,37 +29429,26 @@ var init_scene_html_emit = __esm({
|
|
|
29360
29429
|
"kinetic-type",
|
|
29361
29430
|
"product-shot"
|
|
29362
29431
|
];
|
|
29363
|
-
|
|
29364
|
-
"full",
|
|
29365
|
-
"center",
|
|
29366
|
-
"top-left",
|
|
29367
|
-
"top-right",
|
|
29368
|
-
"bottom-left",
|
|
29369
|
-
"bottom-right"
|
|
29370
|
-
];
|
|
29432
|
+
LOTTIE_RUNTIME_DIR = "assets/vendor/dotlottie";
|
|
29371
29433
|
GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js";
|
|
29372
29434
|
SCENE_OVERLAP_SECONDS = 0.4;
|
|
29373
|
-
DOTLOTTIE_WC_VERSION = "0.9.12";
|
|
29374
|
-
DOTLOTTIE_WEB_VERSION = "0.71.0";
|
|
29375
|
-
DOTLOTTIE_WC_CDN = `https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-wc@${DOTLOTTIE_WC_VERSION}/dist/index.js`;
|
|
29376
|
-
DOTLOTTIE_WASM_CDN = `https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-web@${DOTLOTTIE_WEB_VERSION}/dist/dotlottie-player.wasm`;
|
|
29377
29435
|
}
|
|
29378
29436
|
});
|
|
29379
29437
|
|
|
29380
29438
|
// src/commands/_shared/scene-lint.ts
|
|
29381
|
-
import { readdir as readdir2, readFile as
|
|
29382
|
-
import { existsSync as
|
|
29383
|
-
import { resolve as resolve8, relative as relative2, join as
|
|
29439
|
+
import { readdir as readdir2, readFile as readFile8, writeFile as writeFile6, stat as stat3 } from "node:fs/promises";
|
|
29440
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
29441
|
+
import { resolve as resolve8, relative as relative2, join as join3 } from "node:path";
|
|
29384
29442
|
import {
|
|
29385
29443
|
runHyperframeLint
|
|
29386
29444
|
} from "@hyperframes/producer";
|
|
29387
29445
|
async function discoverSceneFiles(opts) {
|
|
29388
29446
|
const projectDir = resolve8(opts.projectDir);
|
|
29389
29447
|
const rootAbs = resolve8(projectDir, opts.rootRel ?? "index.html");
|
|
29390
|
-
const root =
|
|
29448
|
+
const root = existsSync4(rootAbs) ? rootAbs : null;
|
|
29391
29449
|
const compsDir = resolve8(projectDir, "compositions");
|
|
29392
29450
|
const subs = [];
|
|
29393
|
-
if (
|
|
29451
|
+
if (existsSync4(compsDir)) {
|
|
29394
29452
|
await collectHtmlRecursive(compsDir, subs);
|
|
29395
29453
|
subs.sort();
|
|
29396
29454
|
}
|
|
@@ -29399,7 +29457,7 @@ async function discoverSceneFiles(opts) {
|
|
|
29399
29457
|
async function collectHtmlRecursive(dir, into) {
|
|
29400
29458
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
29401
29459
|
for (const entry of entries) {
|
|
29402
|
-
const full =
|
|
29460
|
+
const full = join3(dir, entry.name);
|
|
29403
29461
|
if (entry.isDirectory()) {
|
|
29404
29462
|
await collectHtmlRecursive(full, into);
|
|
29405
29463
|
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".html")) {
|
|
@@ -29483,7 +29541,7 @@ async function runProjectLint(opts) {
|
|
|
29483
29541
|
for (const sub of subs) targets.push({ abs: sub, isSub: true });
|
|
29484
29542
|
for (const { abs, isSub } of targets) {
|
|
29485
29543
|
const rel = relative2(projectDir, abs) || abs;
|
|
29486
|
-
const html = await
|
|
29544
|
+
const html = await readFile8(abs, "utf-8");
|
|
29487
29545
|
const prepared = {
|
|
29488
29546
|
html,
|
|
29489
29547
|
entryFile: rel,
|
|
@@ -29908,7 +29966,7 @@ var init_upload_host = __esm({
|
|
|
29908
29966
|
});
|
|
29909
29967
|
|
|
29910
29968
|
// src/commands/_shared/video-jobs.ts
|
|
29911
|
-
import { readFile as
|
|
29969
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
29912
29970
|
import { resolve as resolve9 } from "node:path";
|
|
29913
29971
|
function canonicalVideoProvider(provider) {
|
|
29914
29972
|
const id = provider.toLowerCase();
|
|
@@ -29941,7 +29999,7 @@ function mimeTypeForPath(path, fallback2) {
|
|
|
29941
29999
|
}
|
|
29942
30000
|
async function fileToUrlOrDataUri(input, fallbackMimeType) {
|
|
29943
30001
|
if (/^(https?:|data:)/.test(input)) return input;
|
|
29944
|
-
const buffer = await
|
|
30002
|
+
const buffer = await readFile9(resolve9(process.cwd(), input));
|
|
29945
30003
|
return `data:${mimeTypeForPath(input, fallbackMimeType)};base64,${buffer.toString("base64")}`;
|
|
29946
30004
|
}
|
|
29947
30005
|
async function videoImageInput(generator, input) {
|
|
@@ -29956,7 +30014,7 @@ async function videoImageInput(generator, input) {
|
|
|
29956
30014
|
if (generator.imageInput !== "url" || isUrl) {
|
|
29957
30015
|
return fileToUrlOrDataUri(input, "image/png");
|
|
29958
30016
|
}
|
|
29959
|
-
const buffer = input.startsWith("data:") ? Buffer.from(input.slice(input.indexOf(",") + 1), "base64") : await
|
|
30017
|
+
const buffer = input.startsWith("data:") ? Buffer.from(input.slice(input.indexOf(",") + 1), "base64") : await readFile9(resolve9(process.cwd(), input));
|
|
29960
30018
|
const host = await resolveUploadHost();
|
|
29961
30019
|
const upload = await host.uploadImage(buffer, {
|
|
29962
30020
|
filename: input.startsWith("data:") ? "frame.png" : input,
|
|
@@ -29995,8 +30053,8 @@ var init_video_jobs = __esm({
|
|
|
29995
30053
|
});
|
|
29996
30054
|
|
|
29997
30055
|
// src/commands/_shared/image-jobs.ts
|
|
29998
|
-
import { existsSync as
|
|
29999
|
-
import { mkdir as
|
|
30056
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
30057
|
+
import { mkdir as mkdir6, readFile as readFile10 } from "node:fs/promises";
|
|
30000
30058
|
import { dirname as dirname2, resolve as resolve10 } from "node:path";
|
|
30001
30059
|
async function openImageGenerator(provider, apiKey) {
|
|
30002
30060
|
const envVar = IMAGE_PROVIDER_ENV[provider];
|
|
@@ -30013,14 +30071,14 @@ async function openImageGenerator(provider, apiKey) {
|
|
|
30013
30071
|
}
|
|
30014
30072
|
async function readImageInput(path) {
|
|
30015
30073
|
const absPath = resolve10(process.cwd(), path);
|
|
30016
|
-
if (!
|
|
30074
|
+
if (!existsSync5(absPath)) {
|
|
30017
30075
|
throw new ProviderError({ kind: "invalid-request", provider: "local", message: `Image not found: ${absPath}` });
|
|
30018
30076
|
}
|
|
30019
|
-
return { bytes: new Uint8Array(await
|
|
30077
|
+
return { bytes: new Uint8Array(await readFile10(absPath)), mimeType: mimeTypeForPath(path, "image/png") };
|
|
30020
30078
|
}
|
|
30021
30079
|
async function saveImage(output, bytes) {
|
|
30022
30080
|
const outputPath = resolve10(process.cwd(), output);
|
|
30023
|
-
await
|
|
30081
|
+
await mkdir6(dirname2(outputPath), { recursive: true });
|
|
30024
30082
|
await writeImageFile(outputPath, Buffer.from(bytes));
|
|
30025
30083
|
return outputPath;
|
|
30026
30084
|
}
|
|
@@ -30078,19 +30136,19 @@ var init_audio = __esm({
|
|
|
30078
30136
|
});
|
|
30079
30137
|
|
|
30080
30138
|
// src/commands/_shared/install-skill.ts
|
|
30081
|
-
import { existsSync as
|
|
30139
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
30082
30140
|
import { homedir as homedir3 } from "node:os";
|
|
30083
|
-
import { join as
|
|
30141
|
+
import { join as join4, resolve as resolve11 } from "node:path";
|
|
30084
30142
|
function hasGlobalHyperframesSkill() {
|
|
30085
|
-
return
|
|
30143
|
+
return existsSync6(join4(homedir3(), ".claude", "skills", "hyperframes", "SKILL.md"));
|
|
30086
30144
|
}
|
|
30087
30145
|
function hasProjectHyperframesSkill(projectDir) {
|
|
30088
|
-
return
|
|
30146
|
+
return existsSync6(join4(resolve11(projectDir), PROJECT_SKILL_PATH));
|
|
30089
30147
|
}
|
|
30090
30148
|
function resolveSkillReference(projectDir) {
|
|
30091
30149
|
const dir = resolve11(projectDir);
|
|
30092
|
-
if (
|
|
30093
|
-
if (
|
|
30150
|
+
if (existsSync6(join4(dir, PROJECT_SKILL_PATH))) return PROJECT_SKILL_PATH;
|
|
30151
|
+
if (existsSync6(join4(dir, "SKILL.md"))) return "SKILL.md";
|
|
30094
30152
|
return null;
|
|
30095
30153
|
}
|
|
30096
30154
|
function hyperframesSkillAvailable(projectDir) {
|
|
@@ -30151,7 +30209,7 @@ var init_install_skill = __esm({
|
|
|
30151
30209
|
init_exec_safe();
|
|
30152
30210
|
HYPERFRAMES_SKILL_PACKAGE = "heygen-com/hyperframes";
|
|
30153
30211
|
HYPERFRAMES_SKILL_INSTALL_COMMAND = `npx skills add ${HYPERFRAMES_SKILL_PACKAGE}`;
|
|
30154
|
-
PROJECT_SKILL_PATH =
|
|
30212
|
+
PROJECT_SKILL_PATH = join4(".agents", "skills", "hyperframes", "SKILL.md");
|
|
30155
30213
|
}
|
|
30156
30214
|
});
|
|
30157
30215
|
|
|
@@ -30404,17 +30462,17 @@ var init_compose_beat_prompt = __esm({
|
|
|
30404
30462
|
});
|
|
30405
30463
|
|
|
30406
30464
|
// src/commands/_shared/root-sync.ts
|
|
30407
|
-
import { existsSync as
|
|
30408
|
-
import { readFile as
|
|
30409
|
-
import { join as
|
|
30465
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
30466
|
+
import { readFile as readFile11, writeFile as writeFile8 } from "node:fs/promises";
|
|
30467
|
+
import { join as join5, resolve as resolve12 } from "node:path";
|
|
30410
30468
|
async function createRootSyncPlan(opts) {
|
|
30411
30469
|
const projectDir = resolve12(opts.projectDir);
|
|
30412
30470
|
const rootRel = opts.rootRel ?? "index.html";
|
|
30413
30471
|
const rootPath = resolve12(projectDir, rootRel);
|
|
30414
|
-
if (!
|
|
30472
|
+
if (!existsSync7(rootPath)) {
|
|
30415
30473
|
return { rootPath, rootRel, exists: false, changed: false, fixCodes: [], issues: [] };
|
|
30416
30474
|
}
|
|
30417
|
-
const html = await
|
|
30475
|
+
const html = await readFile11(rootPath, "utf-8");
|
|
30418
30476
|
if (opts.beats.length === 0) {
|
|
30419
30477
|
return { rootPath, rootRel, exists: true, changed: false, fixCodes: [], issues: [] };
|
|
30420
30478
|
}
|
|
@@ -30473,8 +30531,8 @@ async function syncRootComposition(opts) {
|
|
|
30473
30531
|
}
|
|
30474
30532
|
async function loadProjectRootSyncBeats(projectDir) {
|
|
30475
30533
|
const root = resolve12(projectDir);
|
|
30476
|
-
const storyboardPath2 =
|
|
30477
|
-
if (!
|
|
30534
|
+
const storyboardPath2 = join5(root, "STORYBOARD.md");
|
|
30535
|
+
if (!existsSync7(storyboardPath2)) return [];
|
|
30478
30536
|
const parsed = parseStoryboard(await loadStoryboardMarkdown(root));
|
|
30479
30537
|
const reportBeats = await readBuildReportBeats(root);
|
|
30480
30538
|
return parsed.beats.map((beat) => {
|
|
@@ -30542,7 +30600,7 @@ async function resolveSyncedBeatDuration(opts) {
|
|
|
30542
30600
|
if (!opts.narrationPath) return Number((reported ?? storyboardMin).toFixed(2));
|
|
30543
30601
|
const probe = opts.probeAudioDuration ?? getAudioDuration;
|
|
30544
30602
|
try {
|
|
30545
|
-
const audioDuration = await probe(
|
|
30603
|
+
const audioDuration = await probe(join5(opts.projectDir, opts.narrationPath));
|
|
30546
30604
|
return Number(Math.max(storyboardMin, reported ?? 0, audioDuration + 0.5).toFixed(2));
|
|
30547
30605
|
} catch {
|
|
30548
30606
|
return Number(Math.max(storyboardMin, reported ?? 0).toFixed(2));
|
|
@@ -30700,17 +30758,17 @@ function hasExpectedAudioRef(html, ref) {
|
|
|
30700
30758
|
return ref.volume === void 0 || tag.includes(`data-volume="${ref.volume}"`);
|
|
30701
30759
|
}
|
|
30702
30760
|
async function readBuildReportBeats(projectDir) {
|
|
30703
|
-
const reportPath =
|
|
30704
|
-
if (!
|
|
30761
|
+
const reportPath = join5(projectDir, "build-report.json");
|
|
30762
|
+
if (!existsSync7(reportPath)) return [];
|
|
30705
30763
|
try {
|
|
30706
|
-
const report = JSON.parse(await
|
|
30764
|
+
const report = JSON.parse(await readFile11(reportPath, "utf-8"));
|
|
30707
30765
|
return Array.isArray(report.beats) ? report.beats : [];
|
|
30708
30766
|
} catch {
|
|
30709
30767
|
return [];
|
|
30710
30768
|
}
|
|
30711
30769
|
}
|
|
30712
30770
|
function firstExisting(projectDir, rels) {
|
|
30713
|
-
return rels.find((rel) =>
|
|
30771
|
+
return rels.find((rel) => existsSync7(join5(projectDir, rel)));
|
|
30714
30772
|
}
|
|
30715
30773
|
function stringOrUndefined(value) {
|
|
30716
30774
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -30745,13 +30803,13 @@ var init_root_sync = __esm({
|
|
|
30745
30803
|
});
|
|
30746
30804
|
|
|
30747
30805
|
// src/commands/_shared/transcription.ts
|
|
30748
|
-
import { readFile as
|
|
30749
|
-
import { basename as
|
|
30806
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
30807
|
+
import { basename as basename5 } from "node:path";
|
|
30750
30808
|
async function transcribeAudioFile(path, options) {
|
|
30751
30809
|
try {
|
|
30752
30810
|
const transcriber = await createTranscriber("openai", options.apiKey);
|
|
30753
|
-
const audio = new Uint8Array(await
|
|
30754
|
-
const name =
|
|
30811
|
+
const audio = new Uint8Array(await readFile12(path));
|
|
30812
|
+
const name = basename5(path);
|
|
30755
30813
|
const transcript = await transcriber.transcribeAudio({
|
|
30756
30814
|
audio,
|
|
30757
30815
|
// A real extension helps Whisper; temp files without one are sniffed from the bytes.
|
|
@@ -30784,9 +30842,9 @@ var init_transcription2 = __esm({
|
|
|
30784
30842
|
});
|
|
30785
30843
|
|
|
30786
30844
|
// src/commands/_shared/transcribe-narration.ts
|
|
30787
|
-
import { existsSync as
|
|
30788
|
-
import { readFile as
|
|
30789
|
-
import { join as
|
|
30845
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
30846
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
30847
|
+
import { join as join6 } from "node:path";
|
|
30790
30848
|
async function transcribeNarrationWords(audioAbsPath, opts) {
|
|
30791
30849
|
try {
|
|
30792
30850
|
const transcript = await transcribeAudioFile(audioAbsPath, { apiKey: opts.apiKey, granularity: "word", language: opts.language });
|
|
@@ -30802,10 +30860,10 @@ function beatTranscriptRelPath(beatId) {
|
|
|
30802
30860
|
return `assets/transcript-${beatId}.json`;
|
|
30803
30861
|
}
|
|
30804
30862
|
async function readBeatTranscript(projectDir, beatId) {
|
|
30805
|
-
const abs =
|
|
30806
|
-
if (!
|
|
30863
|
+
const abs = join6(projectDir, beatTranscriptRelPath(beatId));
|
|
30864
|
+
if (!existsSync8(abs)) return void 0;
|
|
30807
30865
|
try {
|
|
30808
|
-
const parsed = JSON.parse(await
|
|
30866
|
+
const parsed = JSON.parse(await readFile13(abs, "utf-8"));
|
|
30809
30867
|
if (!Array.isArray(parsed)) return void 0;
|
|
30810
30868
|
const words = parsed.filter(
|
|
30811
30869
|
(w) => typeof w === "object" && w !== null && typeof w.text === "string" && Number.isFinite(w.start) && Number.isFinite(w.end)
|
|
@@ -30823,15 +30881,15 @@ var init_transcribe_narration = __esm({
|
|
|
30823
30881
|
});
|
|
30824
30882
|
|
|
30825
30883
|
// src/commands/_shared/compose-prompts.ts
|
|
30826
|
-
import { existsSync as
|
|
30827
|
-
import { join as
|
|
30884
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
30885
|
+
import { join as join7, relative as relative3, resolve as resolve13 } from "node:path";
|
|
30828
30886
|
async function getComposePrompts(opts) {
|
|
30829
30887
|
const projectDir = resolve13(opts.projectDir);
|
|
30830
|
-
const designPath =
|
|
30831
|
-
const compositionPath =
|
|
30832
|
-
const storyboardPath2 =
|
|
30833
|
-
const compositionsDir =
|
|
30834
|
-
const compositionReference =
|
|
30888
|
+
const designPath = join7(projectDir, "DESIGN.md");
|
|
30889
|
+
const compositionPath = join7(projectDir, "COMPOSITION.md");
|
|
30890
|
+
const storyboardPath2 = join7(projectDir, "STORYBOARD.md");
|
|
30891
|
+
const compositionsDir = join7(projectDir, "compositions");
|
|
30892
|
+
const compositionReference = existsSync9(compositionPath) ? "COMPOSITION.md" : null;
|
|
30835
30893
|
const warnings = [];
|
|
30836
30894
|
const baseError = (msg) => ({
|
|
30837
30895
|
success: false,
|
|
@@ -30846,10 +30904,10 @@ async function getComposePrompts(opts) {
|
|
|
30846
30904
|
warnings,
|
|
30847
30905
|
error: msg
|
|
30848
30906
|
});
|
|
30849
|
-
if (!
|
|
30907
|
+
if (!existsSync9(designPath)) {
|
|
30850
30908
|
return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe init <dir>\` first.`);
|
|
30851
30909
|
}
|
|
30852
|
-
if (!
|
|
30910
|
+
if (!existsSync9(storyboardPath2)) {
|
|
30853
30911
|
return baseError(`STORYBOARD.md not found at ${storyboardPath2}. Run \`vibe init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`);
|
|
30854
30912
|
}
|
|
30855
30913
|
if (!hyperframesSkillAvailable(projectDir)) {
|
|
@@ -30881,7 +30939,7 @@ async function getComposePrompts(opts) {
|
|
|
30881
30939
|
}
|
|
30882
30940
|
const result = await Promise.all(
|
|
30883
30941
|
beats.map(async (beat) => {
|
|
30884
|
-
const outputPathAbs =
|
|
30942
|
+
const outputPathAbs = join7(compositionsDir, `scene-${beat.id}.html`);
|
|
30885
30943
|
const outputPathRel = relative3(projectDir, outputPathAbs);
|
|
30886
30944
|
const finalDurationSec = finalDurations.get(beat.id);
|
|
30887
30945
|
const transcript = await readBeatTranscript(projectDir, beat.id);
|
|
@@ -30901,7 +30959,7 @@ async function getComposePrompts(opts) {
|
|
|
30901
30959
|
body: beat.body,
|
|
30902
30960
|
userPrompt,
|
|
30903
30961
|
transcriptPath: transcript ? beatTranscriptRelPath(beat.id) : void 0,
|
|
30904
|
-
exists:
|
|
30962
|
+
exists: existsSync9(outputPathAbs)
|
|
30905
30963
|
};
|
|
30906
30964
|
})
|
|
30907
30965
|
);
|
|
@@ -30968,9 +31026,9 @@ var init_compose_prompts = __esm({
|
|
|
30968
31026
|
});
|
|
30969
31027
|
|
|
30970
31028
|
// src/commands/_shared/scene-submit.ts
|
|
30971
|
-
import { existsSync as
|
|
30972
|
-
import { mkdir as
|
|
30973
|
-
import { join as
|
|
31029
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
31030
|
+
import { mkdir as mkdir7, writeFile as writeFile9 } from "node:fs/promises";
|
|
31031
|
+
import { join as join8, resolve as resolve14 } from "node:path";
|
|
30974
31032
|
async function executeSceneSubmit(opts) {
|
|
30975
31033
|
const projectDir = resolve14(opts.projectDir);
|
|
30976
31034
|
const beatId = opts.beatId.trim();
|
|
@@ -30984,8 +31042,8 @@ async function executeSceneSubmit(opts) {
|
|
|
30984
31042
|
warnings: [],
|
|
30985
31043
|
retryWith: []
|
|
30986
31044
|
};
|
|
30987
|
-
const storyboardPath2 =
|
|
30988
|
-
if (!
|
|
31045
|
+
const storyboardPath2 = join8(projectDir, "STORYBOARD.md");
|
|
31046
|
+
if (!existsSync10(storyboardPath2)) {
|
|
30989
31047
|
return {
|
|
30990
31048
|
...base,
|
|
30991
31049
|
success: false,
|
|
@@ -31062,8 +31120,8 @@ async function executeSceneSubmit(opts) {
|
|
|
31062
31120
|
if (opts.validateOnly) {
|
|
31063
31121
|
return { ...base, success: true };
|
|
31064
31122
|
}
|
|
31065
|
-
const absPath =
|
|
31066
|
-
await
|
|
31123
|
+
const absPath = join8(projectDir, scenePath);
|
|
31124
|
+
await mkdir7(join8(projectDir, "compositions"), { recursive: true });
|
|
31067
31125
|
await writeFile9(absPath, html.endsWith("\n") ? html : `${html}
|
|
31068
31126
|
`, "utf-8");
|
|
31069
31127
|
return {
|
|
@@ -31087,9 +31145,9 @@ var init_scene_submit = __esm({
|
|
|
31087
31145
|
});
|
|
31088
31146
|
|
|
31089
31147
|
// src/commands/_shared/sub-comp-duration-sync.ts
|
|
31090
|
-
import { existsSync as
|
|
31091
|
-
import { readFile as
|
|
31092
|
-
import { join as
|
|
31148
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
31149
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
31150
|
+
import { join as join9, resolve as resolve15 } from "node:path";
|
|
31093
31151
|
function syncSubCompositionDurationHtml(html, beatId, newDurationSec, fileRel) {
|
|
31094
31152
|
const compositionId = `scene-${beatId}`;
|
|
31095
31153
|
const file = fileRel ?? `compositions/${compositionId}.html`;
|
|
@@ -31181,15 +31239,15 @@ async function createSubCompDurationSyncPlans(opts) {
|
|
|
31181
31239
|
const results = [];
|
|
31182
31240
|
for (const beat of opts.beats) {
|
|
31183
31241
|
const fileRel = `compositions/scene-${beat.id}.html`;
|
|
31184
|
-
const filePath =
|
|
31185
|
-
if (!
|
|
31242
|
+
const filePath = join9(projectDir, fileRel);
|
|
31243
|
+
if (!existsSync11(filePath)) continue;
|
|
31186
31244
|
const newDurationSec = await resolveSyncedBeatDuration({
|
|
31187
31245
|
projectDir,
|
|
31188
31246
|
beatDuration: beat.duration,
|
|
31189
31247
|
narrationPath: beat.narrationPath,
|
|
31190
31248
|
sceneDurationSec: beat.sceneDurationSec
|
|
31191
31249
|
});
|
|
31192
|
-
const html = await
|
|
31250
|
+
const html = await readFile14(filePath, "utf-8");
|
|
31193
31251
|
const outcome = syncSubCompositionDurationHtml(html, beat.id, newDurationSec, fileRel);
|
|
31194
31252
|
results.push({ ...outcome, file: fileRel, beatId: beat.id });
|
|
31195
31253
|
}
|
|
@@ -31214,7 +31272,7 @@ var init_sub_comp_duration_sync = __esm({
|
|
|
31214
31272
|
|
|
31215
31273
|
// src/commands/_shared/review-report.ts
|
|
31216
31274
|
import { writeFile as writeFile10 } from "node:fs/promises";
|
|
31217
|
-
import { join as
|
|
31275
|
+
import { join as join10, resolve as resolve16 } from "node:path";
|
|
31218
31276
|
function statusFromIssues(issues) {
|
|
31219
31277
|
if (issues.some((issue) => issue.severity === "error")) return "fail";
|
|
31220
31278
|
if (issues.some((issue) => issue.severity === "warning")) return "warn";
|
|
@@ -31392,7 +31450,7 @@ function buildReviewReport(opts) {
|
|
|
31392
31450
|
});
|
|
31393
31451
|
}
|
|
31394
31452
|
function defaultReviewReportPath(projectDir) {
|
|
31395
|
-
return
|
|
31453
|
+
return join10(resolve16(projectDir), "review-report.json");
|
|
31396
31454
|
}
|
|
31397
31455
|
async function writeReviewReport(path, report) {
|
|
31398
31456
|
await writeFile10(path, JSON.stringify(report, null, 2) + "\n", "utf-8");
|
|
@@ -31707,7 +31765,7 @@ var init_review_report = __esm({
|
|
|
31707
31765
|
});
|
|
31708
31766
|
|
|
31709
31767
|
// src/commands/_shared/scene-repair.ts
|
|
31710
|
-
import { readFile as
|
|
31768
|
+
import { readFile as readFile15, writeFile as writeFile11 } from "node:fs/promises";
|
|
31711
31769
|
import { relative as relative4, resolve as resolve17 } from "node:path";
|
|
31712
31770
|
import { runHyperframeLint as runHyperframeLint2 } from "@hyperframes/producer";
|
|
31713
31771
|
async function executeSceneRepair(opts) {
|
|
@@ -31753,7 +31811,7 @@ async function executeSceneRepair(opts) {
|
|
|
31753
31811
|
}
|
|
31754
31812
|
for (const target of targets) {
|
|
31755
31813
|
const rel = relative4(projectDir, target.abs) || target.abs;
|
|
31756
|
-
const html = await
|
|
31814
|
+
const html = await readFile15(target.abs, "utf-8");
|
|
31757
31815
|
const findings = await lintHtml(html, rel, target.isSub);
|
|
31758
31816
|
const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
|
|
31759
31817
|
if (fixedCodes.length > 0) {
|
|
@@ -31834,8 +31892,8 @@ var init_scene_repair = __esm({
|
|
|
31834
31892
|
|
|
31835
31893
|
// src/commands/scene.ts
|
|
31836
31894
|
import { resolve as resolve18, relative as relative5, dirname as dirname3 } from "node:path";
|
|
31837
|
-
import { mkdir as
|
|
31838
|
-
import { existsSync as
|
|
31895
|
+
import { mkdir as mkdir8, readFile as readFile16, writeFile as writeFile12, access as access5, copyFile as copyFile2 } from "node:fs/promises";
|
|
31896
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
31839
31897
|
function validateDuration(value) {
|
|
31840
31898
|
const n = parseFloat(value);
|
|
31841
31899
|
if (!Number.isFinite(n) || n <= 0 || n > 3600) {
|
|
@@ -31899,8 +31957,8 @@ function readClipStart(rootHtml, beatId) {
|
|
|
31899
31957
|
async function resolveNarrationText(value) {
|
|
31900
31958
|
if (!value) return void 0;
|
|
31901
31959
|
const looksLikePath = /\.[a-z]{2,4}$/i.test(value) || value.includes("/") || value.includes("\\");
|
|
31902
|
-
if (looksLikePath &&
|
|
31903
|
-
return (await
|
|
31960
|
+
if (looksLikePath && existsSync12(value)) {
|
|
31961
|
+
return (await readFile16(value, "utf-8")).trim();
|
|
31904
31962
|
}
|
|
31905
31963
|
return value.trim();
|
|
31906
31964
|
}
|
|
@@ -31937,7 +31995,7 @@ async function executeSceneAdd(opts) {
|
|
|
31937
31995
|
if (!opts.force && await pathExists2(scenePath)) {
|
|
31938
31996
|
return errResult(`Scene already exists: ${sceneRel}. Re-run with --force to overwrite.`);
|
|
31939
31997
|
}
|
|
31940
|
-
const rootHtmlBefore = await
|
|
31998
|
+
const rootHtmlBefore = await readFile16(rootPath, "utf-8");
|
|
31941
31999
|
let dims = readRootDims(rootHtmlBefore);
|
|
31942
32000
|
if (!dims) {
|
|
31943
32001
|
const cfg2 = await loadVibeProjectConfig(projectDir);
|
|
@@ -31961,8 +32019,8 @@ async function executeSceneAdd(opts) {
|
|
|
31961
32019
|
}
|
|
31962
32020
|
audioRelPath = `assets/narration-${id}.${ext}`;
|
|
31963
32021
|
audioAbsPath = resolve18(projectDir, audioRelPath);
|
|
31964
|
-
await
|
|
31965
|
-
await
|
|
32022
|
+
await mkdir8(dirname3(audioAbsPath), { recursive: true });
|
|
32023
|
+
await copyFile2(sourceAbs, audioAbsPath);
|
|
31966
32024
|
try {
|
|
31967
32025
|
narrationDuration = await getAudioDuration(audioAbsPath);
|
|
31968
32026
|
} catch {
|
|
@@ -31994,7 +32052,7 @@ async function executeSceneAdd(opts) {
|
|
|
31994
32052
|
}
|
|
31995
32053
|
audioRelPath = `assets/narration-${id}.${resolution.audioExtension}`;
|
|
31996
32054
|
audioAbsPath = resolve18(projectDir, audioRelPath);
|
|
31997
|
-
await
|
|
32055
|
+
await mkdir8(dirname3(audioAbsPath), { recursive: true });
|
|
31998
32056
|
await writeFile12(audioAbsPath, tts.audioBuffer);
|
|
31999
32057
|
try {
|
|
32000
32058
|
narrationDuration = await getAudioDuration(audioAbsPath);
|
|
@@ -32052,7 +32110,7 @@ async function executeSceneAdd(opts) {
|
|
|
32052
32110
|
});
|
|
32053
32111
|
imageRelPath = `assets/scene-${id}.png`;
|
|
32054
32112
|
imageAbsPath = resolve18(projectDir, imageRelPath);
|
|
32055
|
-
await
|
|
32113
|
+
await mkdir8(dirname3(imageAbsPath), { recursive: true });
|
|
32056
32114
|
await writeImageFile(imageAbsPath, Buffer.from(result.images[0].bytes));
|
|
32057
32115
|
} catch (error) {
|
|
32058
32116
|
return errResult(`${label} image generation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -32070,10 +32128,13 @@ async function executeSceneAdd(opts) {
|
|
|
32070
32128
|
if (ext !== "json" && ext !== "lottie") {
|
|
32071
32129
|
return errResult(`Unsupported Lottie file extension: .${ext}. Use .json or .lottie.`);
|
|
32072
32130
|
}
|
|
32131
|
+
const assetProblem = await checkLottieAsset(sourceAbs);
|
|
32132
|
+
if (assetProblem) return errResult(assetProblem);
|
|
32073
32133
|
lottieRelPath = `assets/lottie-${id}.${ext}`;
|
|
32074
32134
|
lottieAbsPath = resolve18(projectDir, lottieRelPath);
|
|
32075
|
-
await
|
|
32076
|
-
await
|
|
32135
|
+
await mkdir8(dirname3(lottieAbsPath), { recursive: true });
|
|
32136
|
+
await copyFile2(sourceAbs, lottieAbsPath);
|
|
32137
|
+
await copyDotLottieRuntime(resolve18(projectDir, LOTTIE_RUNTIME_DIR));
|
|
32077
32138
|
lottieInput = {
|
|
32078
32139
|
src: lottieRelPath,
|
|
32079
32140
|
position: opts.lottie.position,
|
|
@@ -32117,7 +32178,7 @@ async function executeSceneAdd(opts) {
|
|
|
32117
32178
|
storyboardAction = synced.action;
|
|
32118
32179
|
} else {
|
|
32119
32180
|
opts.onProgress?.("Syncing STORYBOARD.md...");
|
|
32120
|
-
const before = await
|
|
32181
|
+
const before = await readFile16(storyboardAbsPath, "utf-8");
|
|
32121
32182
|
const synced = upsertStoryboardBeat(before, upsert);
|
|
32122
32183
|
if (synced.markdown !== before) {
|
|
32123
32184
|
await writeFile12(storyboardAbsPath, synced.markdown, "utf-8");
|
|
@@ -32142,7 +32203,7 @@ async function executeSceneAdd(opts) {
|
|
|
32142
32203
|
transcript: transcriptWords,
|
|
32143
32204
|
lottie: lottieInput
|
|
32144
32205
|
});
|
|
32145
|
-
await
|
|
32206
|
+
await mkdir8(dirname3(scenePath), { recursive: true });
|
|
32146
32207
|
await writeFile12(scenePath, sceneHtml, "utf-8");
|
|
32147
32208
|
opts.onProgress?.("Updating root composition...");
|
|
32148
32209
|
let start = 0;
|
|
@@ -32154,7 +32215,7 @@ async function executeSceneAdd(opts) {
|
|
|
32154
32215
|
});
|
|
32155
32216
|
const blockingIssue = syncPlan.issues.find((issue) => issue.severity === "error");
|
|
32156
32217
|
if (blockingIssue) return errResult(blockingIssue.message);
|
|
32157
|
-
const rootHtmlAfterSync = await
|
|
32218
|
+
const rootHtmlAfterSync = await readFile16(rootPath, "utf-8");
|
|
32158
32219
|
start = readClipStart(rootHtmlAfterSync, id) ?? 0;
|
|
32159
32220
|
} else {
|
|
32160
32221
|
start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
|
|
@@ -32186,7 +32247,7 @@ async function resolveSceneRepairTarget(rootArg = "index.html", projectArg = "."
|
|
|
32186
32247
|
const root = rootArg || "index.html";
|
|
32187
32248
|
const project = projectArg || ".";
|
|
32188
32249
|
const defaultProject = project === ".";
|
|
32189
|
-
if (defaultProject && root !== "index.html" && !root.toLowerCase().endsWith(".html") &&
|
|
32250
|
+
if (defaultProject && root !== "index.html" && !root.toLowerCase().endsWith(".html") && existsSync12(resolve18(root, "index.html"))) {
|
|
32190
32251
|
return { projectDir: resolve18(root), rootRel: "index.html" };
|
|
32191
32252
|
}
|
|
32192
32253
|
return { projectDir: resolve18(project), rootRel: root };
|
|
@@ -32210,6 +32271,7 @@ var init_scene = __esm({
|
|
|
32210
32271
|
init_cost_tier();
|
|
32211
32272
|
init_visual_styles();
|
|
32212
32273
|
init_scene_html_emit();
|
|
32274
|
+
init_lottie();
|
|
32213
32275
|
init_scene_lint();
|
|
32214
32276
|
init_output();
|
|
32215
32277
|
init_api_key();
|
|
@@ -32382,7 +32444,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
|
|
|
32382
32444
|
}
|
|
32383
32445
|
html = Buffer.concat(chunks).toString("utf-8");
|
|
32384
32446
|
} else if (options.file) {
|
|
32385
|
-
html = await
|
|
32447
|
+
html = await readFile16(resolve18(String(options.file)), "utf-8");
|
|
32386
32448
|
} else {
|
|
32387
32449
|
exitWithError(
|
|
32388
32450
|
usageError(
|
|
@@ -32839,9 +32901,9 @@ __export(remotion_exports, {
|
|
|
32839
32901
|
wrapComponentWithImage: () => wrapComponentWithImage,
|
|
32840
32902
|
wrapComponentWithVideo: () => wrapComponentWithVideo
|
|
32841
32903
|
});
|
|
32842
|
-
import { writeFile as writeFile14, mkdir as
|
|
32843
|
-
import { existsSync as
|
|
32844
|
-
import { join as
|
|
32904
|
+
import { writeFile as writeFile14, mkdir as mkdir9, rm as rm3, copyFile as copyFile3 } from "node:fs/promises";
|
|
32905
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
32906
|
+
import { join as join11 } from "node:path";
|
|
32845
32907
|
import { tmpdir } from "node:os";
|
|
32846
32908
|
import { createHash as createHash2 } from "node:crypto";
|
|
32847
32909
|
async function ensureRemotionInstalled() {
|
|
@@ -32858,8 +32920,8 @@ async function ensureRemotionInstalled() {
|
|
|
32858
32920
|
}
|
|
32859
32921
|
}
|
|
32860
32922
|
async function scaffoldRemotionProject(componentCode, componentName, opts) {
|
|
32861
|
-
const dir =
|
|
32862
|
-
await
|
|
32923
|
+
const dir = join11(tmpdir(), `vibe_motion_${Date.now()}`);
|
|
32924
|
+
await mkdir9(dir, { recursive: true });
|
|
32863
32925
|
const deps = {
|
|
32864
32926
|
remotion: REMOTION_VERSION,
|
|
32865
32927
|
"@remotion/cli": REMOTION_VERSION,
|
|
@@ -32877,7 +32939,7 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
|
|
|
32877
32939
|
dependencies: deps
|
|
32878
32940
|
};
|
|
32879
32941
|
const packageJsonStr = JSON.stringify(packageJson, null, 2);
|
|
32880
|
-
await writeFile14(
|
|
32942
|
+
await writeFile14(join11(dir, "package.json"), packageJsonStr);
|
|
32881
32943
|
const tsconfig = {
|
|
32882
32944
|
compilerOptions: {
|
|
32883
32945
|
target: "ES2020",
|
|
@@ -32889,8 +32951,8 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
|
|
|
32889
32951
|
skipLibCheck: true
|
|
32890
32952
|
}
|
|
32891
32953
|
};
|
|
32892
|
-
await writeFile14(
|
|
32893
|
-
await writeFile14(
|
|
32954
|
+
await writeFile14(join11(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
|
|
32955
|
+
await writeFile14(join11(dir, "Component.tsx"), componentCode);
|
|
32894
32956
|
const rootCode = `import { registerRoot, Composition } from "remotion";
|
|
32895
32957
|
import { ${componentName} } from "./Component";
|
|
32896
32958
|
|
|
@@ -32909,27 +32971,27 @@ const Root = () => {
|
|
|
32909
32971
|
|
|
32910
32972
|
registerRoot(Root);
|
|
32911
32973
|
`;
|
|
32912
|
-
await writeFile14(
|
|
32974
|
+
await writeFile14(join11(dir, "Root.tsx"), rootCode);
|
|
32913
32975
|
const depsHash = createHash2("md5").update(packageJsonStr).digest("hex").slice(0, 12);
|
|
32914
|
-
const cacheMarker =
|
|
32915
|
-
const cachedModules =
|
|
32916
|
-
if (
|
|
32976
|
+
const cacheMarker = join11(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
|
|
32977
|
+
const cachedModules = join11(REMOTION_CACHE_DIR, "node_modules");
|
|
32978
|
+
if (existsSync14(cacheMarker) && existsSync14(cachedModules)) {
|
|
32917
32979
|
const { symlink } = await import("node:fs/promises");
|
|
32918
32980
|
try {
|
|
32919
|
-
await symlink(cachedModules,
|
|
32981
|
+
await symlink(cachedModules, join11(dir, "node_modules"), "dir");
|
|
32920
32982
|
} catch {
|
|
32921
32983
|
await this_npmInstall(dir);
|
|
32922
32984
|
}
|
|
32923
32985
|
} else {
|
|
32924
32986
|
await this_npmInstall(dir);
|
|
32925
32987
|
try {
|
|
32926
|
-
await
|
|
32927
|
-
await writeFile14(
|
|
32928
|
-
if (
|
|
32988
|
+
await mkdir9(REMOTION_CACHE_DIR, { recursive: true });
|
|
32989
|
+
await writeFile14(join11(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
|
|
32990
|
+
if (existsSync14(join11(dir, "node_modules"))) {
|
|
32929
32991
|
const { rename: rename5, symlink } = await import("node:fs/promises");
|
|
32930
|
-
await rename5(
|
|
32992
|
+
await rename5(join11(dir, "node_modules"), cachedModules).catch(() => {
|
|
32931
32993
|
});
|
|
32932
|
-
await symlink(cachedModules,
|
|
32994
|
+
await symlink(cachedModules, join11(dir, "node_modules"), "dir").catch(() => {
|
|
32933
32995
|
});
|
|
32934
32996
|
await writeFile14(cacheMarker, depsHash);
|
|
32935
32997
|
}
|
|
@@ -32966,7 +33028,7 @@ async function renderMotion(options) {
|
|
|
32966
33028
|
}
|
|
32967
33029
|
);
|
|
32968
33030
|
try {
|
|
32969
|
-
const entryPoint =
|
|
33031
|
+
const entryPoint = join11(dir, "Root.tsx");
|
|
32970
33032
|
const { execFile: execFile3 } = await import("node:child_process");
|
|
32971
33033
|
const { promisify: promisify3 } = await import("node:util");
|
|
32972
33034
|
const execFileAsync3 = promisify3(execFile3);
|
|
@@ -33080,10 +33142,10 @@ async function renderWithEmbeddedImage(options) {
|
|
|
33080
33142
|
}
|
|
33081
33143
|
);
|
|
33082
33144
|
try {
|
|
33083
|
-
const publicDir =
|
|
33084
|
-
await
|
|
33085
|
-
await
|
|
33086
|
-
const entryPoint =
|
|
33145
|
+
const publicDir = join11(dir, "public");
|
|
33146
|
+
await mkdir9(publicDir, { recursive: true });
|
|
33147
|
+
await copyFile3(options.imagePath, join11(publicDir, options.imageFileName));
|
|
33148
|
+
const entryPoint = join11(dir, "Root.tsx");
|
|
33087
33149
|
const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
|
|
33088
33150
|
const { execFile: execFileImg } = await import("node:child_process");
|
|
33089
33151
|
const { promisify: promisifyImg } = await import("node:util");
|
|
@@ -33144,10 +33206,10 @@ async function renderWithEmbeddedVideo(options) {
|
|
|
33144
33206
|
}
|
|
33145
33207
|
);
|
|
33146
33208
|
try {
|
|
33147
|
-
const publicDir =
|
|
33148
|
-
await
|
|
33149
|
-
await
|
|
33150
|
-
const entryPoint =
|
|
33209
|
+
const publicDir = join11(dir, "public");
|
|
33210
|
+
await mkdir9(publicDir, { recursive: true });
|
|
33211
|
+
await copyFile3(options.videoPath, join11(publicDir, options.videoFileName));
|
|
33212
|
+
const entryPoint = join11(dir, "Root.tsx");
|
|
33151
33213
|
const mp4VideoOnly = options.outputPath.replace(/\.\w+$/, "_video_only.mp4");
|
|
33152
33214
|
const { execFile: execFileVid } = await import("node:child_process");
|
|
33153
33215
|
const { promisify: promisifyVid } = await import("node:util");
|
|
@@ -33563,7 +33625,7 @@ var init_remotion = __esm({
|
|
|
33563
33625
|
"use strict";
|
|
33564
33626
|
init_exec_safe();
|
|
33565
33627
|
REMOTION_VERSION = "4.0.447";
|
|
33566
|
-
REMOTION_CACHE_DIR =
|
|
33628
|
+
REMOTION_CACHE_DIR = join11(tmpdir(), "vibe_remotion_cache");
|
|
33567
33629
|
}
|
|
33568
33630
|
});
|
|
33569
33631
|
|
|
@@ -33574,8 +33636,8 @@ __export(ai_motion_exports, {
|
|
|
33574
33636
|
registerMotionCommand: () => registerMotionCommand
|
|
33575
33637
|
});
|
|
33576
33638
|
import { resolve as resolve20 } from "node:path";
|
|
33577
|
-
import { existsSync as
|
|
33578
|
-
import { readFile as
|
|
33639
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
33640
|
+
import { readFile as readFile18, writeFile as writeFile15 } from "node:fs/promises";
|
|
33579
33641
|
function resolveMotionModel(alias) {
|
|
33580
33642
|
const claude = findModel("claude", "llm", alias);
|
|
33581
33643
|
if (claude) return { provider: "claude", modelId: claude.id };
|
|
@@ -33629,7 +33691,7 @@ async function executeMotion(options) {
|
|
|
33629
33691
|
};
|
|
33630
33692
|
}
|
|
33631
33693
|
const imagePath = resolve20(process.cwd(), options.image);
|
|
33632
|
-
const imageBuffer = await
|
|
33694
|
+
const imageBuffer = await readFile18(imagePath);
|
|
33633
33695
|
const gemini = new GeminiProvider();
|
|
33634
33696
|
await gemini.initialize({ apiKey: geminiApiKey });
|
|
33635
33697
|
const analysisResult = await gemini.analyzeImage(
|
|
@@ -33666,7 +33728,7 @@ Use this image analysis to inform the color palette, typography placement, and o
|
|
|
33666
33728
|
}
|
|
33667
33729
|
if (geminiApiKey) {
|
|
33668
33730
|
const videoPath = resolve20(process.cwd(), options.video);
|
|
33669
|
-
const videoBuffer = await
|
|
33731
|
+
const videoBuffer = await readFile18(videoPath);
|
|
33670
33732
|
const gemini = new GeminiProvider();
|
|
33671
33733
|
await gemini.initialize({ apiKey: geminiApiKey });
|
|
33672
33734
|
const analysisPrompt = options.understandingPrompt || `Analyze this video for motion graphics composition. Return concise production guidance:
|
|
@@ -33697,10 +33759,10 @@ Use this video understanding to place motion graphics in safe zones, match the c
|
|
|
33697
33759
|
let result;
|
|
33698
33760
|
if (options.fromTsx) {
|
|
33699
33761
|
const tsxPath = resolve20(process.cwd(), options.fromTsx);
|
|
33700
|
-
if (!
|
|
33762
|
+
if (!existsSync15(tsxPath)) {
|
|
33701
33763
|
return { success: false, error: `TSX file not found: ${tsxPath}` };
|
|
33702
33764
|
}
|
|
33703
|
-
const existingCode = await
|
|
33765
|
+
const existingCode = await readFile18(tsxPath, "utf-8");
|
|
33704
33766
|
if (useGemini) {
|
|
33705
33767
|
const gemini = new GeminiProvider();
|
|
33706
33768
|
await gemini.initialize({ apiKey });
|
|
@@ -34158,11 +34220,11 @@ __export(ai_video_exports, {
|
|
|
34158
34220
|
executeVideoGenerate: () => executeVideoGenerate,
|
|
34159
34221
|
executeVideoStatus: () => executeVideoStatus
|
|
34160
34222
|
});
|
|
34161
|
-
import { mkdir as
|
|
34223
|
+
import { mkdir as mkdir10, writeFile as writeFile17 } from "node:fs/promises";
|
|
34162
34224
|
import { dirname as dirname4, resolve as resolve22 } from "node:path";
|
|
34163
34225
|
async function saveOutput(output, bytes) {
|
|
34164
34226
|
const outputPath = resolve22(process.cwd(), output);
|
|
34165
|
-
await
|
|
34227
|
+
await mkdir10(dirname4(outputPath), { recursive: true });
|
|
34166
34228
|
await writeFile17(outputPath, bytes);
|
|
34167
34229
|
return outputPath;
|
|
34168
34230
|
}
|
|
@@ -34789,10 +34851,10 @@ var init_tty = __esm({
|
|
|
34789
34851
|
});
|
|
34790
34852
|
|
|
34791
34853
|
// src/utils/audio-file.ts
|
|
34792
|
-
import { mkdir as
|
|
34854
|
+
import { mkdir as mkdir11, rm as rm4, writeFile as writeFile18 } from "node:fs/promises";
|
|
34793
34855
|
import { dirname as dirname5, extname as extname4 } from "node:path";
|
|
34794
34856
|
async function writeAudioFile(path, bytes, container) {
|
|
34795
|
-
await
|
|
34857
|
+
await mkdir11(dirname5(path), { recursive: true });
|
|
34796
34858
|
const wanted = extname4(path).toLowerCase();
|
|
34797
34859
|
if (!CONVERTIBLE.has(wanted) || wanted === `.${container}`) {
|
|
34798
34860
|
await writeFile18(path, bytes);
|
|
@@ -34939,15 +35001,15 @@ var init_music_status = __esm({
|
|
|
34939
35001
|
});
|
|
34940
35002
|
|
|
34941
35003
|
// src/commands/_shared/build-asset-metadata.ts
|
|
34942
|
-
import { existsSync as
|
|
34943
|
-
import { mkdir as
|
|
34944
|
-
import { dirname as dirname6, join as
|
|
35004
|
+
import { existsSync as existsSync16, readFileSync } from "node:fs";
|
|
35005
|
+
import { mkdir as mkdir12, writeFile as writeFile19 } from "node:fs/promises";
|
|
35006
|
+
import { dirname as dirname6, join as join12 } from "node:path";
|
|
34945
35007
|
function assetMetadataPath(kind, beatId) {
|
|
34946
35008
|
return `.vibeframe/assets/${kind}-${beatId}.json`;
|
|
34947
35009
|
}
|
|
34948
35010
|
function readAssetMetadata(projectDir, kind, beatId) {
|
|
34949
|
-
const path =
|
|
34950
|
-
if (!
|
|
35011
|
+
const path = join12(projectDir, assetMetadataPath(kind, beatId));
|
|
35012
|
+
if (!existsSync16(path)) return null;
|
|
34951
35013
|
try {
|
|
34952
35014
|
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
34953
35015
|
if (parsed.schemaVersion !== "1" || parsed.kind !== kind || parsed.beatId !== beatId || typeof parsed.cacheKey !== "string") {
|
|
@@ -34976,7 +35038,7 @@ function isFreshCanonicalAsset(opts) {
|
|
|
34976
35038
|
}
|
|
34977
35039
|
async function writeAssetMetadata(opts) {
|
|
34978
35040
|
const rel = assetMetadataPath(opts.kind, opts.beatId);
|
|
34979
|
-
const abs =
|
|
35041
|
+
const abs = join12(opts.projectDir, rel);
|
|
34980
35042
|
const metadata = {
|
|
34981
35043
|
schemaVersion: "1",
|
|
34982
35044
|
kind: opts.kind,
|
|
@@ -34989,7 +35051,7 @@ async function writeAssetMetadata(opts) {
|
|
|
34989
35051
|
cachePath: opts.cachePath,
|
|
34990
35052
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
34991
35053
|
};
|
|
34992
|
-
await
|
|
35054
|
+
await mkdir12(dirname6(abs), { recursive: true });
|
|
34993
35055
|
await writeFile19(abs, JSON.stringify(metadata, null, 2) + "\n", "utf-8");
|
|
34994
35056
|
}
|
|
34995
35057
|
var init_build_asset_metadata = __esm({
|
|
@@ -35000,20 +35062,20 @@ var init_build_asset_metadata = __esm({
|
|
|
35000
35062
|
|
|
35001
35063
|
// src/commands/_shared/status-jobs.ts
|
|
35002
35064
|
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
35003
|
-
import { existsSync as
|
|
35004
|
-
import { copyFile as
|
|
35005
|
-
import { dirname as dirname7, join as
|
|
35065
|
+
import { existsSync as existsSync17, statSync } from "node:fs";
|
|
35066
|
+
import { copyFile as copyFile4, mkdir as mkdir13, readFile as readFile19, readdir as readdir3, rename as rename2, stat as stat4, writeFile as writeFile20 } from "node:fs/promises";
|
|
35067
|
+
import { dirname as dirname7, join as join13, parse as parse2, relative as relative6, resolve as resolve24 } from "node:path";
|
|
35006
35068
|
function isLocalJob(record) {
|
|
35007
35069
|
return record.jobType === "build" || record.jobType === "render";
|
|
35008
35070
|
}
|
|
35009
35071
|
function findProjectRoot(start = process.cwd()) {
|
|
35010
35072
|
let dir = resolve24(start);
|
|
35011
35073
|
try {
|
|
35012
|
-
if (
|
|
35074
|
+
if (existsSync17(dir) && statSync(dir).isFile()) dir = dirname7(dir);
|
|
35013
35075
|
} catch {
|
|
35014
35076
|
}
|
|
35015
35077
|
for (; ; ) {
|
|
35016
|
-
if (
|
|
35078
|
+
if (existsSync17(join13(dir, "vibe.config.json")) || existsSync17(join13(dir, "STORYBOARD.md"))) {
|
|
35017
35079
|
return dir;
|
|
35018
35080
|
}
|
|
35019
35081
|
const parent = dirname7(dir);
|
|
@@ -35022,7 +35084,7 @@ function findProjectRoot(start = process.cwd()) {
|
|
|
35022
35084
|
}
|
|
35023
35085
|
}
|
|
35024
35086
|
function jobsDir(projectDir) {
|
|
35025
|
-
return
|
|
35087
|
+
return join13(resolve24(projectDir), ".vibeframe", "jobs");
|
|
35026
35088
|
}
|
|
35027
35089
|
function normalizeJobStatus(status) {
|
|
35028
35090
|
const value = String(status ?? "").toLowerCase();
|
|
@@ -35084,7 +35146,7 @@ async function createAndWriteJobRecord(opts) {
|
|
|
35084
35146
|
}
|
|
35085
35147
|
async function writeJobRecord(record) {
|
|
35086
35148
|
const dir = jobsDir(record.projectDir);
|
|
35087
|
-
await
|
|
35149
|
+
await mkdir13(dir, { recursive: true });
|
|
35088
35150
|
const path = jobRecordPath(record.id, record.projectDir);
|
|
35089
35151
|
const tmp = `${path}.${randomUUID2()}.tmp`;
|
|
35090
35152
|
await writeFile20(tmp, JSON.stringify(stripUndefined2(record), null, 2) + "\n", "utf-8");
|
|
@@ -35094,18 +35156,18 @@ async function readJobRecord(jobId, projectDir) {
|
|
|
35094
35156
|
const id = normalizeJobId(jobId);
|
|
35095
35157
|
const project = resolve24(projectDir ?? findProjectRoot());
|
|
35096
35158
|
const path = jobRecordPath(id, project);
|
|
35097
|
-
if (!
|
|
35098
|
-
return parseJobRecord(await
|
|
35159
|
+
if (!existsSync17(path)) return null;
|
|
35160
|
+
return parseJobRecord(await readFile19(path, "utf-8"));
|
|
35099
35161
|
}
|
|
35100
35162
|
async function listJobRecords(projectDir) {
|
|
35101
35163
|
const dir = jobsDir(projectDir);
|
|
35102
|
-
if (!
|
|
35164
|
+
if (!existsSync17(dir)) return [];
|
|
35103
35165
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
35104
35166
|
const records = [];
|
|
35105
35167
|
for (const entry of entries) {
|
|
35106
35168
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
35107
35169
|
try {
|
|
35108
|
-
const parsed = parseJobRecord(await
|
|
35170
|
+
const parsed = parseJobRecord(await readFile19(join13(dir, entry.name), "utf-8"));
|
|
35109
35171
|
if (parsed) records.push(parsed);
|
|
35110
35172
|
} catch {
|
|
35111
35173
|
}
|
|
@@ -35132,7 +35194,7 @@ async function refreshJobRecord(record, opts = {}) {
|
|
|
35132
35194
|
try {
|
|
35133
35195
|
if (record.jobType === "generate-video") {
|
|
35134
35196
|
const output = resolveRefreshOutput(record, opts);
|
|
35135
|
-
if (output) await
|
|
35197
|
+
if (output) await mkdir13(dirname7(output), { recursive: true });
|
|
35136
35198
|
const result = await executeVideoStatus({
|
|
35137
35199
|
taskId: record.providerTaskId,
|
|
35138
35200
|
provider: record.provider,
|
|
@@ -35200,7 +35262,7 @@ async function refreshLocalJobRecord(record, opts) {
|
|
|
35200
35262
|
const heartbeatAge = record.heartbeatAt ? Date.now() - new Date(record.heartbeatAt).getTime() : Number.POSITIVE_INFINITY;
|
|
35201
35263
|
const orphaned = !isPidAlive(record.pid) || heartbeatAge > LOCAL_JOB_ORPHAN_AFTER_MS;
|
|
35202
35264
|
if (orphaned) {
|
|
35203
|
-
const hint = `The process that ran this ${record.jobType} job is gone. Check ${
|
|
35265
|
+
const hint = `The process that ran this ${record.jobType} job is gone. Check ${join13(record.projectDir, record.jobType === "render" ? "render-report.json" : "build-report.json")} \u2014 the work may have completed before the process exited.`;
|
|
35204
35266
|
warnings.push(hint);
|
|
35205
35267
|
updated = stripUndefined2({
|
|
35206
35268
|
...record,
|
|
@@ -35360,7 +35422,7 @@ function normalizeJobId(jobId) {
|
|
|
35360
35422
|
return id;
|
|
35361
35423
|
}
|
|
35362
35424
|
function jobRecordPath(jobId, projectDir) {
|
|
35363
|
-
return
|
|
35425
|
+
return join13(jobsDir(projectDir), `${normalizeJobId(jobId)}.json`);
|
|
35364
35426
|
}
|
|
35365
35427
|
function parseJobRecord(raw) {
|
|
35366
35428
|
const parsed = JSON.parse(raw);
|
|
@@ -35413,21 +35475,21 @@ async function downloadUrl(url, outputPath) {
|
|
|
35413
35475
|
const response = await fetch(url);
|
|
35414
35476
|
if (!response.ok) throw new Error(`Download failed: HTTP ${response.status}`);
|
|
35415
35477
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
35416
|
-
await
|
|
35478
|
+
await mkdir13(dirname7(outputPath), { recursive: true });
|
|
35417
35479
|
await writeFile20(outputPath, buffer);
|
|
35418
35480
|
return outputPath;
|
|
35419
35481
|
}
|
|
35420
35482
|
async function maybeCacheOutput(record) {
|
|
35421
35483
|
if (record.status !== "completed" || !record.outputPath || !record.cachePath) return;
|
|
35422
35484
|
try {
|
|
35423
|
-
await
|
|
35424
|
-
await
|
|
35485
|
+
await mkdir13(dirname7(record.cachePath), { recursive: true });
|
|
35486
|
+
await copyFile4(record.outputPath, record.cachePath);
|
|
35425
35487
|
} catch {
|
|
35426
35488
|
}
|
|
35427
35489
|
}
|
|
35428
35490
|
async function maybeWriteCompletedAssetMetadata(record) {
|
|
35429
35491
|
const kind = assetKindForJob(record);
|
|
35430
|
-
if (record.status !== "completed" || !kind || !record.beatId || !record.assetCue || !record.cacheKey || !record.outputPath || !
|
|
35492
|
+
if (record.status !== "completed" || !kind || !record.beatId || !record.assetCue || !record.cacheKey || !record.outputPath || !existsSync17(record.outputPath)) {
|
|
35431
35493
|
return;
|
|
35432
35494
|
}
|
|
35433
35495
|
try {
|
|
@@ -35446,7 +35508,7 @@ async function maybeWriteCompletedAssetMetadata(record) {
|
|
|
35446
35508
|
}
|
|
35447
35509
|
}
|
|
35448
35510
|
async function refreshBuildReportFromJobs(projectDir, records) {
|
|
35449
|
-
const reportPath =
|
|
35511
|
+
const reportPath = join13(projectDir, "build-report.json");
|
|
35450
35512
|
const report = await readJson(reportPath);
|
|
35451
35513
|
if (!report || !Array.isArray(report.beats)) return;
|
|
35452
35514
|
let changed = false;
|
|
@@ -35499,7 +35561,7 @@ async function refreshBuildReportFromJobs(projectDir, records) {
|
|
|
35499
35561
|
}
|
|
35500
35562
|
}
|
|
35501
35563
|
async function readBuildSummary(projectDir) {
|
|
35502
|
-
const reportPath =
|
|
35564
|
+
const reportPath = join13(projectDir, "build-report.json");
|
|
35503
35565
|
const report = await readJson(reportPath);
|
|
35504
35566
|
if (!report) return null;
|
|
35505
35567
|
const info = await statOrNull(reportPath);
|
|
@@ -35522,7 +35584,7 @@ async function readBuildSummary(projectDir) {
|
|
|
35522
35584
|
});
|
|
35523
35585
|
}
|
|
35524
35586
|
async function readReviewSummary(projectDir) {
|
|
35525
|
-
const reportPath =
|
|
35587
|
+
const reportPath = join13(projectDir, "review-report.json");
|
|
35526
35588
|
const report = await readJson(reportPath);
|
|
35527
35589
|
if (!report) return null;
|
|
35528
35590
|
const info = await statOrNull(reportPath);
|
|
@@ -35611,8 +35673,8 @@ async function readProjectBeatReadiness(projectDir, build2, records) {
|
|
|
35611
35673
|
if (fromBeats) return fromBeats;
|
|
35612
35674
|
if (build2.beats) return augmentBeatReadinessFromJobs(build2.beats, records, report?.jobs);
|
|
35613
35675
|
}
|
|
35614
|
-
const storyboardPath2 =
|
|
35615
|
-
if (!
|
|
35676
|
+
const storyboardPath2 = join13(projectDir, "STORYBOARD.md");
|
|
35677
|
+
if (!existsSync17(storyboardPath2)) {
|
|
35616
35678
|
return { total: 0, assetsReady: 0, compositionsReady: 0, needsAuthor: [] };
|
|
35617
35679
|
}
|
|
35618
35680
|
try {
|
|
@@ -35620,7 +35682,7 @@ async function readProjectBeatReadiness(projectDir, build2, records) {
|
|
|
35620
35682
|
const needsAuthor = [];
|
|
35621
35683
|
let compositionsReady = 0;
|
|
35622
35684
|
for (const beat of parsed.beats) {
|
|
35623
|
-
if (
|
|
35685
|
+
if (existsSync17(join13(projectDir, "compositions", `scene-${beat.id}.html`))) {
|
|
35624
35686
|
compositionsReady += 1;
|
|
35625
35687
|
} else {
|
|
35626
35688
|
needsAuthor.push(beat.id);
|
|
@@ -35638,7 +35700,7 @@ async function readProjectBeatReadiness(projectDir, build2, records) {
|
|
|
35638
35700
|
}
|
|
35639
35701
|
function deriveProjectWorkflow(opts) {
|
|
35640
35702
|
const retryWith = [];
|
|
35641
|
-
const hasStoryboard =
|
|
35703
|
+
const hasStoryboard = existsSync17(join13(opts.project, "STORYBOARD.md"));
|
|
35642
35704
|
if (!hasStoryboard && !opts.build) {
|
|
35643
35705
|
retryWith.push(`vibe init ${opts.project} --from "<brief>" --json`);
|
|
35644
35706
|
return { status: "empty", currentStage: "init", retryWith };
|
|
@@ -35734,7 +35796,7 @@ function beatReadinessFromBuildBeats(projectDir, beats, records = [], reportJobs
|
|
|
35734
35796
|
if (buildBeatAssetsReady(record) || (id ? buildBeatJobsReady(id, records, reportJobs) : false))
|
|
35735
35797
|
assetsReady += 1;
|
|
35736
35798
|
const compositionPath = typeof record.compositionPath === "string" ? record.compositionPath : id ? `compositions/scene-${id}.html` : void 0;
|
|
35737
|
-
if (compositionPath &&
|
|
35799
|
+
if (compositionPath && existsSync17(resolve24(projectDir, compositionPath))) {
|
|
35738
35800
|
compositionsReady += 1;
|
|
35739
35801
|
} else if (id) {
|
|
35740
35802
|
needsAuthor.push(id);
|
|
@@ -35829,7 +35891,7 @@ function projectRelativePath(projectDir, path) {
|
|
|
35829
35891
|
}
|
|
35830
35892
|
async function readJson(path) {
|
|
35831
35893
|
try {
|
|
35832
|
-
return JSON.parse(await
|
|
35894
|
+
return JSON.parse(await readFile19(path, "utf-8"));
|
|
35833
35895
|
} catch {
|
|
35834
35896
|
return null;
|
|
35835
35897
|
}
|
|
@@ -35870,7 +35932,7 @@ var init_status_jobs = __esm({
|
|
|
35870
35932
|
// src/commands/generate/music.ts
|
|
35871
35933
|
import { resolve as resolve25 } from "node:path";
|
|
35872
35934
|
import { writeFile as writeFile21 } from "node:fs/promises";
|
|
35873
|
-
import { existsSync as
|
|
35935
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
35874
35936
|
async function executeMusic(options) {
|
|
35875
35937
|
try {
|
|
35876
35938
|
const provider = options.provider || "elevenlabs";
|
|
@@ -35952,7 +36014,7 @@ function registerMusicCommand(parent) {
|
|
|
35952
36014
|
exitWithError(usageError(`Invalid provider: ${provider}`, "Available providers: elevenlabs, replicate"));
|
|
35953
36015
|
}
|
|
35954
36016
|
if (options.melody) {
|
|
35955
|
-
if (!
|
|
36017
|
+
if (!existsSync18(resolve25(process.cwd(), options.melody))) exitWithError(notFoundError(options.melody));
|
|
35956
36018
|
exitWithError(usageError("Melody conditioning requires a publicly accessible URL", "Please upload your melody file and provide the URL."));
|
|
35957
36019
|
}
|
|
35958
36020
|
if (options.dryRun) {
|
|
@@ -36047,8 +36109,8 @@ __export(ai_image_exports, {
|
|
|
36047
36109
|
executeImageGenerate: () => executeImageGenerate,
|
|
36048
36110
|
executeThumbnailBestFrame: () => executeThumbnailBestFrame
|
|
36049
36111
|
});
|
|
36050
|
-
import { readFile as
|
|
36051
|
-
import { existsSync as
|
|
36112
|
+
import { readFile as readFile20 } from "node:fs/promises";
|
|
36113
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
36052
36114
|
function numberedPath(path, n) {
|
|
36053
36115
|
const dot = path.lastIndexOf(".");
|
|
36054
36116
|
return dot > path.lastIndexOf("/") ? `${path.slice(0, dot)}-${n}${path.slice(dot)}` : `${path}-${n}`;
|
|
@@ -36120,7 +36182,7 @@ async function executeThumbnailBestFrame(options) {
|
|
|
36120
36182
|
model = "flash",
|
|
36121
36183
|
apiKey
|
|
36122
36184
|
} = options;
|
|
36123
|
-
if (!
|
|
36185
|
+
if (!existsSync19(videoPath)) {
|
|
36124
36186
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
36125
36187
|
}
|
|
36126
36188
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36133,7 +36195,7 @@ async function executeThumbnailBestFrame(options) {
|
|
|
36133
36195
|
try {
|
|
36134
36196
|
const gemini = new GeminiProvider();
|
|
36135
36197
|
await gemini.initialize({ apiKey: googleKey });
|
|
36136
|
-
const videoData = await
|
|
36198
|
+
const videoData = await readFile20(videoPath);
|
|
36137
36199
|
const analysisPrompt = prompt3 || 'Analyze this video and find the single best frame for a thumbnail. Look for frames that are visually striking, well-composed, emotionally engaging, and representative of the video content. Avoid blurry frames, transitions, or dark scenes. Return ONLY a JSON object: {"timestamp": <seconds as number>, "reason": "<brief explanation>"}';
|
|
36138
36200
|
const modelId = resolveGeminiTextModel(model);
|
|
36139
36201
|
const result = await gemini.analyzeVideo(videoData, analysisPrompt, {
|
|
@@ -36154,7 +36216,7 @@ async function executeThumbnailBestFrame(options) {
|
|
|
36154
36216
|
reason = reasonMatch[1];
|
|
36155
36217
|
}
|
|
36156
36218
|
await execSafe("ffmpeg", ["-ss", String(timestamp2), "-i", videoPath, "-frames:v", "1", "-q:v", "2", outputPath, "-y"], { timeout: 6e4, maxBuffer: 50 * 1024 * 1024 });
|
|
36157
|
-
if (!
|
|
36219
|
+
if (!existsSync19(outputPath)) {
|
|
36158
36220
|
return { success: false, error: "FFmpeg failed to extract frame" };
|
|
36159
36221
|
}
|
|
36160
36222
|
return {
|
|
@@ -36181,9 +36243,9 @@ var init_ai_image = __esm({
|
|
|
36181
36243
|
});
|
|
36182
36244
|
|
|
36183
36245
|
// src/commands/generate/thumbnail.ts
|
|
36184
|
-
import { resolve as resolve26, dirname as dirname8, basename as
|
|
36185
|
-
import { existsSync as
|
|
36186
|
-
import { writeFile as writeFile22, mkdir as
|
|
36246
|
+
import { resolve as resolve26, dirname as dirname8, basename as basename7, extname as extname5 } from "node:path";
|
|
36247
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
36248
|
+
import { writeFile as writeFile22, mkdir as mkdir14 } from "node:fs/promises";
|
|
36187
36249
|
function registerThumbnailCommand(parent) {
|
|
36188
36250
|
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: ${GEMINI_TEXT_MODEL_HELP}`, "flash").action(async (description, options) => {
|
|
36189
36251
|
const startedAt = Date.now();
|
|
@@ -36194,7 +36256,7 @@ function registerThumbnailCommand(parent) {
|
|
|
36194
36256
|
}
|
|
36195
36257
|
if (options.bestFrame) {
|
|
36196
36258
|
const absVideoPath = resolve26(process.cwd(), options.bestFrame);
|
|
36197
|
-
if (!
|
|
36259
|
+
if (!existsSync20(absVideoPath)) {
|
|
36198
36260
|
exitWithError(notFoundError(absVideoPath));
|
|
36199
36261
|
}
|
|
36200
36262
|
if (!commandExists("ffmpeg")) {
|
|
@@ -36206,7 +36268,7 @@ function registerThumbnailCommand(parent) {
|
|
|
36206
36268
|
);
|
|
36207
36269
|
}
|
|
36208
36270
|
const apiKey2 = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
|
|
36209
|
-
const name =
|
|
36271
|
+
const name = basename7(options.bestFrame, extname5(options.bestFrame));
|
|
36210
36272
|
const outputPath = options.output || `${name}-thumbnail.png`;
|
|
36211
36273
|
const spinner3 = ora("Analyzing video for best frame...").start();
|
|
36212
36274
|
const result2 = await executeThumbnailBestFrame({
|
|
@@ -36274,7 +36336,7 @@ function registerThumbnailCommand(parent) {
|
|
|
36274
36336
|
throw new Error("No image data available");
|
|
36275
36337
|
}
|
|
36276
36338
|
outputPath = resolve26(process.cwd(), options.output);
|
|
36277
|
-
await
|
|
36339
|
+
await mkdir14(dirname8(outputPath), { recursive: true });
|
|
36278
36340
|
await writeFile22(outputPath, buffer);
|
|
36279
36341
|
}
|
|
36280
36342
|
outputSuccess({
|
|
@@ -36305,7 +36367,7 @@ function registerThumbnailCommand(parent) {
|
|
|
36305
36367
|
throw new Error("No image data available");
|
|
36306
36368
|
}
|
|
36307
36369
|
const outputPath = resolve26(process.cwd(), options.output);
|
|
36308
|
-
await
|
|
36370
|
+
await mkdir14(dirname8(outputPath), { recursive: true });
|
|
36309
36371
|
await writeFile22(outputPath, buffer);
|
|
36310
36372
|
saveSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
|
|
36311
36373
|
} catch {
|
|
@@ -37567,7 +37629,7 @@ var init_dist3 = __esm({
|
|
|
37567
37629
|
|
|
37568
37630
|
// src/commands/generate/video.ts
|
|
37569
37631
|
import { resolve as resolve27 } from "node:path";
|
|
37570
|
-
import { readFile as
|
|
37632
|
+
import { readFile as readFile21 } from "node:fs/promises";
|
|
37571
37633
|
function registerVideoCommand(parent) {
|
|
37572
37634
|
parent.command("video").alias("vid").description("Generate video using AI (Seedance, Grok, Kling, Runway, Veo, or Gemini Omni)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option(
|
|
37573
37635
|
"-p, --provider <provider>",
|
|
@@ -37674,7 +37736,7 @@ Examples:
|
|
|
37674
37736
|
provider = resolved?.name ?? "grok";
|
|
37675
37737
|
}
|
|
37676
37738
|
if (options.image) {
|
|
37677
|
-
const imageBuffer = await
|
|
37739
|
+
const imageBuffer = await readFile21(resolve27(process.cwd(), options.image));
|
|
37678
37740
|
if (!options.ratio) {
|
|
37679
37741
|
const dimensions = imageSize(imageBuffer);
|
|
37680
37742
|
if (dimensions.width && dimensions.height) {
|
|
@@ -38076,8 +38138,8 @@ var init_ai_helpers = __esm({
|
|
|
38076
38138
|
});
|
|
38077
38139
|
|
|
38078
38140
|
// src/commands/_shared/edit/silence-cut.ts
|
|
38079
|
-
import { existsSync as
|
|
38080
|
-
import { readFile as
|
|
38141
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
38142
|
+
import { readFile as readFile22 } from "node:fs/promises";
|
|
38081
38143
|
async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
|
|
38082
38144
|
const totalDuration = await getVideoDuration(videoPath);
|
|
38083
38145
|
const { stdout, stderr } = await execSafe(
|
|
@@ -38127,7 +38189,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
|
|
|
38127
38189
|
}
|
|
38128
38190
|
const gemini = new GeminiProvider();
|
|
38129
38191
|
await gemini.initialize({ apiKey: geminiApiKey });
|
|
38130
|
-
const videoBuffer = await
|
|
38192
|
+
const videoBuffer = await readFile22(videoPath);
|
|
38131
38193
|
const modelId = options.model ? resolveGeminiTextModel(options.model) : void 0;
|
|
38132
38194
|
const prompt3 = `Analyze this video and identify all silent or dead segments where there is NO meaningful content.
|
|
38133
38195
|
|
|
@@ -38203,7 +38265,7 @@ async function executeSilenceCut(options) {
|
|
|
38203
38265
|
analyzeOnly = false,
|
|
38204
38266
|
useGemini = false
|
|
38205
38267
|
} = options;
|
|
38206
|
-
if (!
|
|
38268
|
+
if (!existsSync21(videoPath)) {
|
|
38207
38269
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
38208
38270
|
}
|
|
38209
38271
|
if (!commandExists("ffmpeg")) {
|
|
@@ -38311,9 +38373,9 @@ var init_silence_cut = __esm({
|
|
|
38311
38373
|
});
|
|
38312
38374
|
|
|
38313
38375
|
// src/commands/_shared/edit/jump-cut.ts
|
|
38314
|
-
import { existsSync as
|
|
38315
|
-
import { writeFile as writeFile24, mkdir as
|
|
38316
|
-
import { join as
|
|
38376
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
38377
|
+
import { writeFile as writeFile24, mkdir as mkdir15 } from "node:fs/promises";
|
|
38378
|
+
import { join as join14 } from "node:path";
|
|
38317
38379
|
async function transcribeWithWords(audioPath, apiKey, language) {
|
|
38318
38380
|
const result = await transcribeAudioFile(audioPath, { apiKey, language, granularity: "word" });
|
|
38319
38381
|
if (result.status !== "completed") {
|
|
@@ -38356,7 +38418,7 @@ async function executeJumpCut(options) {
|
|
|
38356
38418
|
analyzeOnly = false,
|
|
38357
38419
|
apiKey
|
|
38358
38420
|
} = options;
|
|
38359
|
-
if (!
|
|
38421
|
+
if (!existsSync22(videoPath)) {
|
|
38360
38422
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
38361
38423
|
}
|
|
38362
38424
|
if (!commandExists("ffmpeg")) {
|
|
@@ -38374,8 +38436,8 @@ async function executeJumpCut(options) {
|
|
|
38374
38436
|
}
|
|
38375
38437
|
try {
|
|
38376
38438
|
const tmpDir = `/tmp/vibe_jumpcut_${Date.now()}`;
|
|
38377
|
-
await
|
|
38378
|
-
const audioPath =
|
|
38439
|
+
await mkdir15(tmpDir, { recursive: true });
|
|
38440
|
+
const audioPath = join14(tmpDir, "audio.wav");
|
|
38379
38441
|
try {
|
|
38380
38442
|
await execSafe(
|
|
38381
38443
|
"ffmpeg",
|
|
@@ -38429,7 +38491,7 @@ async function executeJumpCut(options) {
|
|
|
38429
38491
|
const segmentPaths = [];
|
|
38430
38492
|
for (let i = 0; i < segments.length; i++) {
|
|
38431
38493
|
const seg = segments[i];
|
|
38432
|
-
const segPath =
|
|
38494
|
+
const segPath = join14(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
|
|
38433
38495
|
const duration = seg.end - seg.start;
|
|
38434
38496
|
await execSafe(
|
|
38435
38497
|
"ffmpeg",
|
|
@@ -38452,7 +38514,7 @@ async function executeJumpCut(options) {
|
|
|
38452
38514
|
segmentPaths.push(segPath);
|
|
38453
38515
|
}
|
|
38454
38516
|
const concatList = segmentPaths.map((p) => `file '${p}'`).join("\n");
|
|
38455
|
-
const listPath =
|
|
38517
|
+
const listPath = join14(tmpDir, "concat.txt");
|
|
38456
38518
|
await writeFile24(listPath, concatList);
|
|
38457
38519
|
await execSafe(
|
|
38458
38520
|
"ffmpeg",
|
|
@@ -38643,9 +38705,9 @@ var init_helpers = __esm({
|
|
|
38643
38705
|
});
|
|
38644
38706
|
|
|
38645
38707
|
// src/commands/_shared/edit/caption.ts
|
|
38646
|
-
import { existsSync as
|
|
38647
|
-
import { writeFile as writeFile25, mkdir as
|
|
38648
|
-
import { dirname as dirname9, basename as
|
|
38708
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
38709
|
+
import { writeFile as writeFile25, mkdir as mkdir16 } from "node:fs/promises";
|
|
38710
|
+
import { dirname as dirname9, basename as basename8, extname as extname6, join as join15 } from "node:path";
|
|
38649
38711
|
function getCaptionForceStyle(style, fontSize, fontColor, position) {
|
|
38650
38712
|
const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
|
|
38651
38713
|
const marginV = position === "center" ? 0 : 30;
|
|
@@ -38673,7 +38735,7 @@ async function executeCaption(options) {
|
|
|
38673
38735
|
position = "bottom",
|
|
38674
38736
|
apiKey
|
|
38675
38737
|
} = options;
|
|
38676
|
-
if (!
|
|
38738
|
+
if (!existsSync23(videoPath)) {
|
|
38677
38739
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
38678
38740
|
}
|
|
38679
38741
|
if (!commandExists("ffmpeg")) {
|
|
@@ -38691,9 +38753,9 @@ async function executeCaption(options) {
|
|
|
38691
38753
|
}
|
|
38692
38754
|
try {
|
|
38693
38755
|
const tmpDir = `/tmp/vibe_caption_${Date.now()}`;
|
|
38694
|
-
await
|
|
38695
|
-
const audioPath =
|
|
38696
|
-
const srtPath =
|
|
38756
|
+
await mkdir16(tmpDir, { recursive: true });
|
|
38757
|
+
const audioPath = join15(tmpDir, "audio.wav");
|
|
38758
|
+
const srtPath = join15(tmpDir, "captions.srt");
|
|
38697
38759
|
try {
|
|
38698
38760
|
await execSafe(
|
|
38699
38761
|
"ffmpeg",
|
|
@@ -38758,9 +38820,9 @@ async function executeCaption(options) {
|
|
|
38758
38820
|
const remotionErr = await ensureRemotionInstalled2();
|
|
38759
38821
|
if (remotionErr) {
|
|
38760
38822
|
const outputDir2 = dirname9(outputPath);
|
|
38761
|
-
const outputSrtPath2 =
|
|
38823
|
+
const outputSrtPath2 = join15(
|
|
38762
38824
|
outputDir2,
|
|
38763
|
-
|
|
38825
|
+
basename8(outputPath, extname6(outputPath)) + ".srt"
|
|
38764
38826
|
);
|
|
38765
38827
|
await writeFile25(outputSrtPath2, srtContent);
|
|
38766
38828
|
return { success: false, error: `${remotionErr}
|
|
@@ -38797,9 +38859,9 @@ SRT saved to: ${outputSrtPath2}` };
|
|
|
38797
38859
|
});
|
|
38798
38860
|
if (!renderResult.success) {
|
|
38799
38861
|
const outputDir2 = dirname9(outputPath);
|
|
38800
|
-
const outputSrtPath2 =
|
|
38862
|
+
const outputSrtPath2 = join15(
|
|
38801
38863
|
outputDir2,
|
|
38802
|
-
|
|
38864
|
+
basename8(outputPath, extname6(outputPath)) + ".srt"
|
|
38803
38865
|
);
|
|
38804
38866
|
await writeFile25(outputSrtPath2, srtContent);
|
|
38805
38867
|
return {
|
|
@@ -38810,9 +38872,9 @@ SRT saved to: ${outputSrtPath2}`
|
|
|
38810
38872
|
}
|
|
38811
38873
|
}
|
|
38812
38874
|
const outputDir = dirname9(outputPath);
|
|
38813
|
-
const outputSrtPath =
|
|
38875
|
+
const outputSrtPath = join15(
|
|
38814
38876
|
outputDir,
|
|
38815
|
-
|
|
38877
|
+
basename8(outputPath, extname6(outputPath)) + ".srt"
|
|
38816
38878
|
);
|
|
38817
38879
|
await writeFile25(outputSrtPath, srtContent);
|
|
38818
38880
|
return {
|
|
@@ -38847,10 +38909,10 @@ var init_caption = __esm({
|
|
|
38847
38909
|
});
|
|
38848
38910
|
|
|
38849
38911
|
// src/commands/_shared/edit/noise-reduce.ts
|
|
38850
|
-
import { existsSync as
|
|
38912
|
+
import { existsSync as existsSync24 } from "node:fs";
|
|
38851
38913
|
async function executeNoiseReduce(options) {
|
|
38852
38914
|
const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
|
|
38853
|
-
if (!
|
|
38915
|
+
if (!existsSync24(inputPath)) {
|
|
38854
38916
|
return { success: false, error: `File not found: ${inputPath}` };
|
|
38855
38917
|
}
|
|
38856
38918
|
if (!commandExists("ffmpeg")) {
|
|
@@ -38907,7 +38969,7 @@ var init_noise_reduce = __esm({
|
|
|
38907
38969
|
});
|
|
38908
38970
|
|
|
38909
38971
|
// src/commands/_shared/edit/fade.ts
|
|
38910
|
-
import { existsSync as
|
|
38972
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
38911
38973
|
async function executeFade(options) {
|
|
38912
38974
|
const {
|
|
38913
38975
|
videoPath,
|
|
@@ -38917,7 +38979,7 @@ async function executeFade(options) {
|
|
|
38917
38979
|
audioOnly = false,
|
|
38918
38980
|
videoOnly = false
|
|
38919
38981
|
} = options;
|
|
38920
|
-
if (!
|
|
38982
|
+
if (!existsSync25(videoPath)) {
|
|
38921
38983
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
38922
38984
|
}
|
|
38923
38985
|
if (!commandExists("ffmpeg")) {
|
|
@@ -39060,8 +39122,8 @@ var init_translate_texts = __esm({
|
|
|
39060
39122
|
});
|
|
39061
39123
|
|
|
39062
39124
|
// src/commands/_shared/edit/translate-srt.ts
|
|
39063
|
-
import { existsSync as
|
|
39064
|
-
import { readFile as
|
|
39125
|
+
import { existsSync as existsSync26 } from "node:fs";
|
|
39126
|
+
import { readFile as readFile23, writeFile as writeFile26 } from "node:fs/promises";
|
|
39065
39127
|
async function executeTranslateSrt(options) {
|
|
39066
39128
|
const {
|
|
39067
39129
|
srtPath,
|
|
@@ -39071,11 +39133,11 @@ async function executeTranslateSrt(options) {
|
|
|
39071
39133
|
sourceLanguage,
|
|
39072
39134
|
apiKey
|
|
39073
39135
|
} = options;
|
|
39074
|
-
if (!
|
|
39136
|
+
if (!existsSync26(srtPath)) {
|
|
39075
39137
|
return { success: false, error: `SRT file not found: ${srtPath}` };
|
|
39076
39138
|
}
|
|
39077
39139
|
try {
|
|
39078
|
-
const srtContent = await
|
|
39140
|
+
const srtContent = await readFile23(srtPath, "utf-8");
|
|
39079
39141
|
const segments = parseSRT(srtContent);
|
|
39080
39142
|
if (segments.length === 0) {
|
|
39081
39143
|
return { success: false, error: "No subtitle segments found in SRT file" };
|
|
@@ -39115,7 +39177,7 @@ var init_translate_srt = __esm({
|
|
|
39115
39177
|
});
|
|
39116
39178
|
|
|
39117
39179
|
// src/commands/_shared/edit/text-overlay.ts
|
|
39118
|
-
import { existsSync as
|
|
39180
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
39119
39181
|
import { resolve as resolve29 } from "node:path";
|
|
39120
39182
|
function detectSystemFont() {
|
|
39121
39183
|
const platform3 = process.platform;
|
|
@@ -39126,7 +39188,7 @@ function detectSystemFont() {
|
|
|
39126
39188
|
"/Library/Fonts/Arial.ttf"
|
|
39127
39189
|
];
|
|
39128
39190
|
for (const f of candidates) {
|
|
39129
|
-
if (
|
|
39191
|
+
if (existsSync27(f)) return f;
|
|
39130
39192
|
}
|
|
39131
39193
|
} else if (platform3 === "linux") {
|
|
39132
39194
|
const candidates = [
|
|
@@ -39135,7 +39197,7 @@ function detectSystemFont() {
|
|
|
39135
39197
|
"/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
|
|
39136
39198
|
];
|
|
39137
39199
|
for (const f of candidates) {
|
|
39138
|
-
if (
|
|
39200
|
+
if (existsSync27(f)) return f;
|
|
39139
39201
|
}
|
|
39140
39202
|
} else if (platform3 === "win32") {
|
|
39141
39203
|
const candidates = [
|
|
@@ -39143,7 +39205,7 @@ function detectSystemFont() {
|
|
|
39143
39205
|
"C:\\Windows\\Fonts\\segoeui.ttf"
|
|
39144
39206
|
];
|
|
39145
39207
|
for (const f of candidates) {
|
|
39146
|
-
if (
|
|
39208
|
+
if (existsSync27(f)) return f;
|
|
39147
39209
|
}
|
|
39148
39210
|
}
|
|
39149
39211
|
return null;
|
|
@@ -39164,7 +39226,7 @@ async function applyTextOverlays(options) {
|
|
|
39164
39226
|
}
|
|
39165
39227
|
const absVideoPath = resolve29(process.cwd(), videoPath);
|
|
39166
39228
|
const absOutputPath = resolve29(process.cwd(), outputPath);
|
|
39167
|
-
if (!
|
|
39229
|
+
if (!existsSync27(absVideoPath)) {
|
|
39168
39230
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
39169
39231
|
}
|
|
39170
39232
|
if (!commandExists("ffmpeg")) {
|
|
@@ -39344,8 +39406,8 @@ var init_ai_edit = __esm({
|
|
|
39344
39406
|
});
|
|
39345
39407
|
|
|
39346
39408
|
// src/commands/ai-edit-cli.ts
|
|
39347
|
-
import { resolve as resolve30, extname as extname7, basename as
|
|
39348
|
-
import { existsSync as
|
|
39409
|
+
import { resolve as resolve30, extname as extname7, basename as basename9 } from "node:path";
|
|
39410
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
39349
39411
|
function registerEditCommands(aiCommand) {
|
|
39350
39412
|
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", "(deprecated - use `vibe detect silence`) 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", `
|
|
39351
39413
|
Examples:
|
|
@@ -39362,14 +39424,14 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
39362
39424
|
validateOutputPath(options.output);
|
|
39363
39425
|
}
|
|
39364
39426
|
const absVideoPath = resolve30(process.cwd(), videoPath);
|
|
39365
|
-
if (!
|
|
39427
|
+
if (!existsSync28(absVideoPath)) {
|
|
39366
39428
|
exitWithError(notFoundError(absVideoPath));
|
|
39367
39429
|
}
|
|
39368
39430
|
if (!commandExists("ffmpeg")) {
|
|
39369
39431
|
exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
|
|
39370
39432
|
}
|
|
39371
39433
|
const ext = extname7(videoPath);
|
|
39372
|
-
const name =
|
|
39434
|
+
const name = basename9(videoPath, ext);
|
|
39373
39435
|
const outputPath = options.output || `${name}-cut${ext}`;
|
|
39374
39436
|
const useGemini = options.useGemini || false;
|
|
39375
39437
|
if (options.analyzeOnly) {
|
|
@@ -39470,7 +39532,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39470
39532
|
validateOutputPath(options.output);
|
|
39471
39533
|
}
|
|
39472
39534
|
const absVideoPath = resolve30(process.cwd(), videoPath);
|
|
39473
|
-
if (!
|
|
39535
|
+
if (!existsSync28(absVideoPath)) {
|
|
39474
39536
|
exitWithError(notFoundError(absVideoPath));
|
|
39475
39537
|
}
|
|
39476
39538
|
if (!commandExists("ffmpeg")) {
|
|
@@ -39499,7 +39561,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39499
39561
|
exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
|
|
39500
39562
|
}
|
|
39501
39563
|
const ext = extname7(videoPath);
|
|
39502
|
-
const name =
|
|
39564
|
+
const name = basename9(videoPath, ext);
|
|
39503
39565
|
const outputPath = options.output || `${name}-captioned${ext}`;
|
|
39504
39566
|
const spinner2 = ora("Starting caption process...").start();
|
|
39505
39567
|
const result = await executeCaption({
|
|
@@ -39551,7 +39613,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39551
39613
|
validateOutputPath(options.output);
|
|
39552
39614
|
}
|
|
39553
39615
|
const absInputPath = resolve30(process.cwd(), inputPath);
|
|
39554
|
-
if (!
|
|
39616
|
+
if (!existsSync28(absInputPath)) {
|
|
39555
39617
|
exitWithError(notFoundError(absInputPath));
|
|
39556
39618
|
}
|
|
39557
39619
|
if (!commandExists("ffmpeg")) {
|
|
@@ -39573,7 +39635,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39573
39635
|
return;
|
|
39574
39636
|
}
|
|
39575
39637
|
const ext = extname7(inputPath);
|
|
39576
|
-
const name =
|
|
39638
|
+
const name = basename9(inputPath, ext);
|
|
39577
39639
|
const outputPath = options.output || `${name}-denoised${ext}`;
|
|
39578
39640
|
const spinner2 = ora("Applying noise reduction...").start();
|
|
39579
39641
|
const result = await executeNoiseReduce({
|
|
@@ -39617,7 +39679,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39617
39679
|
validateOutputPath(options.output);
|
|
39618
39680
|
}
|
|
39619
39681
|
const absVideoPath = resolve30(process.cwd(), videoPath);
|
|
39620
|
-
if (!
|
|
39682
|
+
if (!existsSync28(absVideoPath)) {
|
|
39621
39683
|
exitWithError(notFoundError(absVideoPath));
|
|
39622
39684
|
}
|
|
39623
39685
|
if (!commandExists("ffmpeg")) {
|
|
@@ -39641,7 +39703,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39641
39703
|
return;
|
|
39642
39704
|
}
|
|
39643
39705
|
const ext = extname7(videoPath);
|
|
39644
|
-
const name =
|
|
39706
|
+
const name = basename9(videoPath, ext);
|
|
39645
39707
|
const outputPath = options.output || `${name}-faded${ext}`;
|
|
39646
39708
|
const spinner2 = ora("Applying fade effects...").start();
|
|
39647
39709
|
const result = await executeFade({
|
|
@@ -39692,7 +39754,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39692
39754
|
exitWithError(usageError("Target language required. Use -t or --target"));
|
|
39693
39755
|
}
|
|
39694
39756
|
const absSrtPath = resolve30(process.cwd(), srtPath);
|
|
39695
|
-
if (!
|
|
39757
|
+
if (!existsSync28(absSrtPath)) {
|
|
39696
39758
|
exitWithError(notFoundError(absSrtPath));
|
|
39697
39759
|
}
|
|
39698
39760
|
if (options.dryRun) {
|
|
@@ -39719,7 +39781,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39719
39781
|
exitWithError(authError(envKey, providerName));
|
|
39720
39782
|
}
|
|
39721
39783
|
const ext = extname7(srtPath);
|
|
39722
|
-
const name =
|
|
39784
|
+
const name = basename9(srtPath, ext);
|
|
39723
39785
|
const outputPath = options.output || `${name}-${options.target}${ext}`;
|
|
39724
39786
|
const spinner2 = ora(`Translating to ${options.target}...`).start();
|
|
39725
39787
|
const result = await executeTranslateSrt({
|
|
@@ -39768,7 +39830,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39768
39830
|
}
|
|
39769
39831
|
if (options.fillers) rejectControlChars(options.fillers);
|
|
39770
39832
|
const absVideoPath = resolve30(process.cwd(), videoPath);
|
|
39771
|
-
if (!
|
|
39833
|
+
if (!existsSync28(absVideoPath)) {
|
|
39772
39834
|
exitWithError(notFoundError(absVideoPath));
|
|
39773
39835
|
}
|
|
39774
39836
|
if (!commandExists("ffmpeg")) {
|
|
@@ -39797,7 +39859,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39797
39859
|
exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
|
|
39798
39860
|
}
|
|
39799
39861
|
const ext = extname7(videoPath);
|
|
39800
|
-
const name =
|
|
39862
|
+
const name = basename9(videoPath, ext);
|
|
39801
39863
|
const outputPath = options.output || `${name}-jumpcut${ext}`;
|
|
39802
39864
|
const fillers = options.fillers ? options.fillers.split(",").map((f) => f.trim()) : void 0;
|
|
39803
39865
|
const spinner2 = ora("Transcribing with word-level timestamps...").start();
|
|
@@ -39868,8 +39930,8 @@ var init_ai_edit_cli = __esm({
|
|
|
39868
39930
|
});
|
|
39869
39931
|
|
|
39870
39932
|
// src/commands/_shared/execute-fill-gaps.ts
|
|
39871
|
-
import { existsSync as
|
|
39872
|
-
import { readFile as
|
|
39933
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
39934
|
+
import { readFile as readFile24, writeFile as writeFile27, mkdir as mkdir17, rename as renameFs } from "node:fs/promises";
|
|
39873
39935
|
import { resolve as resolve31, dirname as dirname10 } from "node:path";
|
|
39874
39936
|
function detectVideoGaps(videoClips, totalDuration) {
|
|
39875
39937
|
const gaps = [];
|
|
@@ -39926,7 +39988,7 @@ function analyzeGapFillability(gaps, videoClips, sources) {
|
|
|
39926
39988
|
}
|
|
39927
39989
|
async function uploadFrame(host, framePath) {
|
|
39928
39990
|
try {
|
|
39929
|
-
const upload = await host.uploadImage(await
|
|
39991
|
+
const upload = await host.uploadImage(await readFile24(framePath), {
|
|
39930
39992
|
filename: framePath,
|
|
39931
39993
|
mimeType: "image/png"
|
|
39932
39994
|
});
|
|
@@ -39959,14 +40021,14 @@ async function executeFillGaps(options) {
|
|
|
39959
40021
|
try {
|
|
39960
40022
|
onProgress("Loading timeline...");
|
|
39961
40023
|
const filePath = await resolveTimelineFile(options.projectPath);
|
|
39962
|
-
if (!
|
|
40024
|
+
if (!existsSync29(filePath)) {
|
|
39963
40025
|
return {
|
|
39964
40026
|
success: false,
|
|
39965
40027
|
error: `Timeline file not found: ${filePath}`,
|
|
39966
40028
|
humanLines
|
|
39967
40029
|
};
|
|
39968
40030
|
}
|
|
39969
|
-
const content = await
|
|
40031
|
+
const content = await readFile24(filePath, "utf-8");
|
|
39970
40032
|
const data = JSON.parse(content);
|
|
39971
40033
|
const project = Project.fromJSON(data);
|
|
39972
40034
|
const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
|
|
@@ -40086,8 +40148,8 @@ async function executeFillGaps(options) {
|
|
|
40086
40148
|
const kling = await openVideoGenerator("kling", apiKey);
|
|
40087
40149
|
const projectDir = dirname10(filePath);
|
|
40088
40150
|
const footageDir = options.dir ? resolve31(process.cwd(), options.dir) : resolve31(projectDir, "footage");
|
|
40089
|
-
if (!
|
|
40090
|
-
await
|
|
40151
|
+
if (!existsSync29(footageDir)) {
|
|
40152
|
+
await mkdir17(footageDir, { recursive: true });
|
|
40091
40153
|
}
|
|
40092
40154
|
humanLines.push("Generating AI Videos");
|
|
40093
40155
|
let uploadHost;
|
|
@@ -40378,15 +40440,15 @@ var init_ai_fill_gaps = __esm({
|
|
|
40378
40440
|
});
|
|
40379
40441
|
|
|
40380
40442
|
// src/pipeline/renderers/chrome.ts
|
|
40381
|
-
import { existsSync as
|
|
40443
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
40382
40444
|
import { homedir as homedir4 } from "node:os";
|
|
40383
|
-
import { join as
|
|
40445
|
+
import { join as join16 } from "node:path";
|
|
40384
40446
|
function findChrome() {
|
|
40385
40447
|
const candidates = [
|
|
40386
40448
|
process.env.HYPERFRAMES_CHROME_PATH,
|
|
40387
40449
|
process.env.CHROME_PATH,
|
|
40388
40450
|
// puppeteer auto-downloaded headless shell
|
|
40389
|
-
|
|
40451
|
+
join16(
|
|
40390
40452
|
homedir4(),
|
|
40391
40453
|
".cache",
|
|
40392
40454
|
"puppeteer",
|
|
@@ -40404,7 +40466,7 @@ function findChrome() {
|
|
|
40404
40466
|
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
|
40405
40467
|
];
|
|
40406
40468
|
for (const c of candidates) {
|
|
40407
|
-
if (c &&
|
|
40469
|
+
if (c && existsSync30(c)) return c;
|
|
40408
40470
|
}
|
|
40409
40471
|
return void 0;
|
|
40410
40472
|
}
|
|
@@ -40436,10 +40498,9 @@ var init_stdout_guard = __esm({
|
|
|
40436
40498
|
});
|
|
40437
40499
|
|
|
40438
40500
|
// src/commands/edit/lottie-overlay.ts
|
|
40439
|
-
import {
|
|
40440
|
-
import { copyFile as copyFile4, mkdir as mkdir17, mkdtemp, readFile as readFile24, rm as rm5, writeFile as writeFile28 } from "node:fs/promises";
|
|
40501
|
+
import { copyFile as copyFile5, mkdir as mkdir18, mkdtemp, rm as rm5, writeFile as writeFile28 } from "node:fs/promises";
|
|
40441
40502
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
40442
|
-
import { basename as
|
|
40503
|
+
import { basename as basename10, extname as extname8, join as join17 } from "node:path";
|
|
40443
40504
|
async function probeVideoStream(path) {
|
|
40444
40505
|
const { stdout } = await execSafe("ffprobe", [
|
|
40445
40506
|
"-v",
|
|
@@ -40466,27 +40527,6 @@ async function probeVideoStream(path) {
|
|
|
40466
40527
|
duration: Number(info.format?.duration ?? 0)
|
|
40467
40528
|
};
|
|
40468
40529
|
}
|
|
40469
|
-
async function checkLottieAsset(path) {
|
|
40470
|
-
const bytes = await readFile24(path);
|
|
40471
|
-
const isZip = bytes.length > 4 && bytes[0] === 80 && bytes[1] === 75;
|
|
40472
|
-
if (isZip) return void 0;
|
|
40473
|
-
try {
|
|
40474
|
-
const json = JSON.parse(bytes.toString("utf-8"));
|
|
40475
|
-
if (Array.isArray(json.layers)) return void 0;
|
|
40476
|
-
} catch {
|
|
40477
|
-
}
|
|
40478
|
-
return `${basename9(path)} is not a Lottie animation (expected Lottie JSON or a .lottie archive).`;
|
|
40479
|
-
}
|
|
40480
|
-
function overlayBox(layer, position, scale) {
|
|
40481
|
-
if (position === "full") return { left: 0, top: 0, width: layer.width, height: layer.height };
|
|
40482
|
-
const width = Math.max(1, Math.round(layer.width * scale));
|
|
40483
|
-
const height = Math.max(1, Math.round(layer.height * scale));
|
|
40484
|
-
const marginX = Math.round(layer.width * 0.04);
|
|
40485
|
-
const marginY = Math.round(layer.height * 0.04);
|
|
40486
|
-
const left = position === "center" ? Math.round((layer.width - width) / 2) : position.endsWith("left") ? marginX : layer.width - marginX - width;
|
|
40487
|
-
const top = position === "center" ? Math.round((layer.height - height) / 2) : position.startsWith("top") ? marginY : layer.height - marginY - height;
|
|
40488
|
-
return { left, top, width, height };
|
|
40489
|
-
}
|
|
40490
40530
|
function lottieLayerHtml(opts) {
|
|
40491
40531
|
const { box } = opts;
|
|
40492
40532
|
return `<!DOCTYPE html>
|
|
@@ -40499,11 +40539,8 @@ function lottieLayerHtml(opts) {
|
|
|
40499
40539
|
</head><body>
|
|
40500
40540
|
<canvas id="lottie" width="${box.width}" height="${box.height}"></canvas>
|
|
40501
40541
|
<script type="module">
|
|
40502
|
-
|
|
40503
|
-
|
|
40504
|
-
// WebAssembly.instantiateStreaming reject it; a typed Blob URL avoids that.
|
|
40505
|
-
const wasm = await (await fetch("./vendor/dotlottie-player.wasm")).arrayBuffer();
|
|
40506
|
-
DotLottie.setWasmUrl(URL.createObjectURL(new Blob([wasm], { type: "application/wasm" })));
|
|
40542
|
+
${DOTLOTTIE_BROWSER_HELPERS}
|
|
40543
|
+
const DotLottie = await loadDotLottie("./vendor");
|
|
40507
40544
|
const LOOP = ${opts.loop};
|
|
40508
40545
|
const DURATION = ${opts.duration};
|
|
40509
40546
|
const player = new DotLottie({
|
|
@@ -40520,12 +40557,7 @@ function lottieLayerHtml(opts) {
|
|
|
40520
40557
|
window.__hf = {
|
|
40521
40558
|
duration: DURATION,
|
|
40522
40559
|
seek(t) {
|
|
40523
|
-
|
|
40524
|
-
const frames = player.totalFrames;
|
|
40525
|
-
if (!(length > 0) || !(frames > 0)) return;
|
|
40526
|
-
const local = Math.max(0, Number(t) || 0);
|
|
40527
|
-
const at = LOOP ? local % length : Math.min(local, length);
|
|
40528
|
-
player.setFrame(Math.min(frames - 1, (at / length) * frames));
|
|
40560
|
+
setLottieTime(player, t, LOOP);
|
|
40529
40561
|
},
|
|
40530
40562
|
};
|
|
40531
40563
|
window.__hf.seek(0);
|
|
@@ -40534,21 +40566,12 @@ function lottieLayerHtml(opts) {
|
|
|
40534
40566
|
</body></html>
|
|
40535
40567
|
`;
|
|
40536
40568
|
}
|
|
40537
|
-
function vendorPaths() {
|
|
40538
|
-
const require2 = createRequire(import.meta.url);
|
|
40539
|
-
const script = require2.resolve("@lottiefiles/dotlottie-web");
|
|
40540
|
-
const dist = join16(script, "..");
|
|
40541
|
-
return { script: join16(dist, "index.js"), wasm: join16(dist, "dotlottie-player.wasm") };
|
|
40542
|
-
}
|
|
40543
40569
|
async function renderLayer(opts) {
|
|
40544
40570
|
const { dir, info, options } = opts;
|
|
40545
|
-
|
|
40546
|
-
await
|
|
40547
|
-
await mkdir17(join16(dir, "assets"), { recursive: true });
|
|
40548
|
-
await copyFile4(vendor.script, join16(dir, "vendor", "dotlottie-web.js"));
|
|
40549
|
-
await copyFile4(vendor.wasm, join16(dir, "vendor", "dotlottie-player.wasm"));
|
|
40571
|
+
await copyDotLottieRuntime(join17(dir, "vendor"));
|
|
40572
|
+
await mkdir18(join17(dir, "assets"), { recursive: true });
|
|
40550
40573
|
const assetName = `animation${extname8(options.assetPath).toLowerCase()}`;
|
|
40551
|
-
await
|
|
40574
|
+
await copyFile5(options.assetPath, join17(dir, "assets", assetName));
|
|
40552
40575
|
const html = lottieLayerHtml({
|
|
40553
40576
|
width: info.width,
|
|
40554
40577
|
height: info.height,
|
|
@@ -40558,8 +40581,8 @@ async function renderLayer(opts) {
|
|
|
40558
40581
|
opacity: options.opacity,
|
|
40559
40582
|
loop: options.loop
|
|
40560
40583
|
});
|
|
40561
|
-
await writeFile28(
|
|
40562
|
-
const layerPath =
|
|
40584
|
+
await writeFile28(join17(dir, "index.html"), html, "utf-8");
|
|
40585
|
+
const layerPath = join17(dir, "layer.mov");
|
|
40563
40586
|
const { createRenderJob: createRenderJob2, executeRenderJob: executeRenderJob2 } = await import("@hyperframes/producer");
|
|
40564
40587
|
const job = createRenderJob2({
|
|
40565
40588
|
fps: Math.min(60, Math.max(1, Math.round(info.fps))),
|
|
@@ -40612,7 +40635,7 @@ async function renderLottieOverlay(options) {
|
|
|
40612
40635
|
const assetProblem = await checkLottieAsset(options.assetPath);
|
|
40613
40636
|
if (assetProblem) return { success: false, error: assetProblem };
|
|
40614
40637
|
const info = await probeVideoStream(options.videoPath);
|
|
40615
|
-
const dir = await mkdtemp(
|
|
40638
|
+
const dir = await mkdtemp(join17(tmpdir2(), "vibeframe-lottie-"));
|
|
40616
40639
|
try {
|
|
40617
40640
|
let layerPath;
|
|
40618
40641
|
try {
|
|
@@ -40622,7 +40645,7 @@ async function renderLottieOverlay(options) {
|
|
|
40622
40645
|
if (/__hf not ready/.test(message)) {
|
|
40623
40646
|
return {
|
|
40624
40647
|
success: false,
|
|
40625
|
-
error: `Could not load the Lottie animation ${
|
|
40648
|
+
error: `Could not load the Lottie animation ${basename10(options.assetPath)}.`,
|
|
40626
40649
|
suggestion: "Check that it opens in a Lottie player (https://lottiefiles.com/preview)."
|
|
40627
40650
|
};
|
|
40628
40651
|
}
|
|
@@ -40643,6 +40666,7 @@ var init_lottie_overlay = __esm({
|
|
|
40643
40666
|
init_chrome();
|
|
40644
40667
|
init_exec_safe();
|
|
40645
40668
|
init_stdout_guard();
|
|
40669
|
+
init_lottie();
|
|
40646
40670
|
FFMPEG_MISSING = "FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details.";
|
|
40647
40671
|
}
|
|
40648
40672
|
});
|
|
@@ -40653,8 +40677,8 @@ __export(motion_overlay_exports, {
|
|
|
40653
40677
|
executeMotionOverlay: () => executeMotionOverlay,
|
|
40654
40678
|
registerMotionOverlayCommand: () => registerMotionOverlayCommand
|
|
40655
40679
|
});
|
|
40656
|
-
import { mkdir as
|
|
40657
|
-
import { existsSync as
|
|
40680
|
+
import { mkdir as mkdir19 } from "node:fs/promises";
|
|
40681
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
40658
40682
|
import { dirname as dirname11, extname as extname9, resolve as resolve32 } from "node:path";
|
|
40659
40683
|
function normalizeUnderstand2(value) {
|
|
40660
40684
|
return value === "off" || value === "required" || value === "auto" ? value : "auto";
|
|
@@ -40689,11 +40713,11 @@ async function executeMotionOverlay(options) {
|
|
|
40689
40713
|
const validationError = assertValidRequest(options);
|
|
40690
40714
|
if (validationError) return { success: false, error: validationError };
|
|
40691
40715
|
const absVideo = resolve32(process.cwd(), options.videoPath);
|
|
40692
|
-
if (!
|
|
40716
|
+
if (!existsSync31(absVideo)) return { success: false, error: `Video file not found: ${absVideo}` };
|
|
40693
40717
|
const outputPath = options.output ? resolve32(process.cwd(), options.output) : defaultOverlayOutput(options.videoPath);
|
|
40694
40718
|
if (options.asset) {
|
|
40695
40719
|
const absAsset = resolve32(process.cwd(), options.asset);
|
|
40696
|
-
if (!
|
|
40720
|
+
if (!existsSync31(absAsset)) return { success: false, error: `Lottie asset not found: ${absAsset}` };
|
|
40697
40721
|
const videoDuration = await ffprobeDuration(absVideo);
|
|
40698
40722
|
const start = Math.max(0, options.start ?? 0);
|
|
40699
40723
|
if (start >= videoDuration) {
|
|
@@ -40701,7 +40725,7 @@ async function executeMotionOverlay(options) {
|
|
|
40701
40725
|
}
|
|
40702
40726
|
const remaining = videoDuration - start;
|
|
40703
40727
|
const position = normalizePosition(options.position);
|
|
40704
|
-
await
|
|
40728
|
+
await mkdir19(dirname11(outputPath), { recursive: true });
|
|
40705
40729
|
const result2 = await renderLottieOverlay({
|
|
40706
40730
|
videoPath: absVideo,
|
|
40707
40731
|
assetPath: absAsset,
|
|
@@ -40719,7 +40743,7 @@ async function executeMotionOverlay(options) {
|
|
|
40719
40743
|
return { success: true, outputPath, provider: "lottie" };
|
|
40720
40744
|
}
|
|
40721
40745
|
const duration = options.duration ?? await ffprobeDuration(absVideo).catch(() => void 0);
|
|
40722
|
-
await
|
|
40746
|
+
await mkdir19(dirname11(outputPath), { recursive: true });
|
|
40723
40747
|
const result = await executeMotion({
|
|
40724
40748
|
description: options.description.trim(),
|
|
40725
40749
|
video: absVideo,
|
|
@@ -41753,7 +41777,7 @@ __export(ai_analyze_exports, {
|
|
|
41753
41777
|
});
|
|
41754
41778
|
import { readFile as readFile26 } from "node:fs/promises";
|
|
41755
41779
|
import { extname as extname10, resolve as resolve34 } from "node:path";
|
|
41756
|
-
import { existsSync as
|
|
41780
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
41757
41781
|
async function executeGeminiVideo(options) {
|
|
41758
41782
|
try {
|
|
41759
41783
|
const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
|
|
@@ -41767,7 +41791,7 @@ async function executeGeminiVideo(options) {
|
|
|
41767
41791
|
videoData = options.source;
|
|
41768
41792
|
} else {
|
|
41769
41793
|
const absPath = resolve34(process.cwd(), options.source);
|
|
41770
|
-
if (!
|
|
41794
|
+
if (!existsSync32(absPath)) {
|
|
41771
41795
|
return { success: false, error: `File not found: ${absPath}` };
|
|
41772
41796
|
}
|
|
41773
41797
|
videoData = await readFile26(absPath);
|
|
@@ -41835,7 +41859,7 @@ async function executeAnalyze(options) {
|
|
|
41835
41859
|
imageBuffer = Buffer.from(await response.arrayBuffer());
|
|
41836
41860
|
} else {
|
|
41837
41861
|
const absPath = resolve34(process.cwd(), source);
|
|
41838
|
-
if (!
|
|
41862
|
+
if (!existsSync32(absPath)) {
|
|
41839
41863
|
return { success: false, error: `File not found: ${absPath}` };
|
|
41840
41864
|
}
|
|
41841
41865
|
imageBuffer = await readFile26(absPath);
|
|
@@ -41870,7 +41894,7 @@ async function executeAnalyze(options) {
|
|
|
41870
41894
|
videoData = Buffer.from(await response.arrayBuffer());
|
|
41871
41895
|
} else {
|
|
41872
41896
|
const absPath = resolve34(process.cwd(), source);
|
|
41873
|
-
if (!
|
|
41897
|
+
if (!existsSync32(absPath)) {
|
|
41874
41898
|
return { success: false, error: `File not found: ${absPath}` };
|
|
41875
41899
|
}
|
|
41876
41900
|
videoData = await readFile26(absPath);
|
|
@@ -41910,9 +41934,9 @@ var init_ai_analyze = __esm({
|
|
|
41910
41934
|
});
|
|
41911
41935
|
|
|
41912
41936
|
// src/commands/_shared/scene-inspect.ts
|
|
41913
|
-
import { existsSync as
|
|
41937
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
41914
41938
|
import { readdir as readdir4, readFile as readFile27, stat as stat5 } from "node:fs/promises";
|
|
41915
|
-
import { isAbsolute as isAbsolute2, join as
|
|
41939
|
+
import { isAbsolute as isAbsolute2, join as join18, resolve as resolve35 } from "node:path";
|
|
41916
41940
|
async function inspectProject(opts) {
|
|
41917
41941
|
const projectDir = resolve35(opts.projectDir);
|
|
41918
41942
|
const issues = [];
|
|
@@ -41926,7 +41950,7 @@ async function inspectProject(opts) {
|
|
|
41926
41950
|
assets: { checked: 0, missing: [], stale: [], unknownFreshness: [] },
|
|
41927
41951
|
rootSync: { ok: null, issueCount: 0, issues: [] }
|
|
41928
41952
|
};
|
|
41929
|
-
if (!
|
|
41953
|
+
if (!existsSync33(projectDir) || !await isDirectory(projectDir)) {
|
|
41930
41954
|
issues.push({
|
|
41931
41955
|
severity: "error",
|
|
41932
41956
|
code: "PROJECT_NOT_FOUND",
|
|
@@ -41941,15 +41965,15 @@ async function inspectProject(opts) {
|
|
|
41941
41965
|
);
|
|
41942
41966
|
}
|
|
41943
41967
|
const coreFiles = {
|
|
41944
|
-
storyboard:
|
|
41945
|
-
design:
|
|
41946
|
-
config:
|
|
41947
|
-
legacyConfig:
|
|
41948
|
-
buildReport:
|
|
41949
|
-
root:
|
|
41968
|
+
storyboard: join18(projectDir, "STORYBOARD.md"),
|
|
41969
|
+
design: join18(projectDir, "DESIGN.md"),
|
|
41970
|
+
config: join18(projectDir, "vibe.config.json"),
|
|
41971
|
+
legacyConfig: join18(projectDir, "vibe.project.yaml"),
|
|
41972
|
+
buildReport: join18(projectDir, "build-report.json"),
|
|
41973
|
+
root: join18(projectDir, "index.html")
|
|
41950
41974
|
};
|
|
41951
41975
|
for (const [name, path] of Object.entries(coreFiles)) {
|
|
41952
|
-
checks.files[name] =
|
|
41976
|
+
checks.files[name] = existsSync33(path);
|
|
41953
41977
|
}
|
|
41954
41978
|
let beatIds = [];
|
|
41955
41979
|
let inspectedBeatIds = [];
|
|
@@ -42030,20 +42054,20 @@ async function inspectProject(opts) {
|
|
|
42030
42054
|
});
|
|
42031
42055
|
retryWith.push(`vibe build ${projectDir} --stage sync --json`);
|
|
42032
42056
|
}
|
|
42033
|
-
const compositionsDir =
|
|
42034
|
-
const existingComps =
|
|
42057
|
+
const compositionsDir = join18(projectDir, "compositions");
|
|
42058
|
+
const existingComps = existsSync33(compositionsDir) ? new Set(await listHtmlBasenames(compositionsDir)) : /* @__PURE__ */ new Set();
|
|
42035
42059
|
checks.compositions.expected = inspectedBeatIds.length;
|
|
42036
42060
|
for (const beatId of inspectedBeatIds) {
|
|
42037
42061
|
const file = `scene-${beatId}.html`;
|
|
42038
42062
|
if (existingComps.has(file)) {
|
|
42039
42063
|
checks.compositions.found++;
|
|
42040
42064
|
} else {
|
|
42041
|
-
checks.compositions.missing.push(
|
|
42065
|
+
checks.compositions.missing.push(join18("compositions", file));
|
|
42042
42066
|
issues.push({
|
|
42043
42067
|
severity: "error",
|
|
42044
42068
|
code: "MISSING_COMPOSITION",
|
|
42045
42069
|
message: `Composition for beat "${beatId}" is missing.`,
|
|
42046
|
-
file:
|
|
42070
|
+
file: join18("compositions", file),
|
|
42047
42071
|
scene: beatId,
|
|
42048
42072
|
beatId,
|
|
42049
42073
|
suggestedFix: "Run `vibe build --stage compose --json`."
|
|
@@ -42105,7 +42129,7 @@ async function inspectProject(opts) {
|
|
|
42105
42129
|
try {
|
|
42106
42130
|
const lint = await runProjectLint({ projectDir });
|
|
42107
42131
|
const lintFiles = opts.beatId ? lint.files.filter(
|
|
42108
|
-
(file) => file.file === "index.html" || file.file ===
|
|
42132
|
+
(file) => file.file === "index.html" || file.file === join18("compositions", `scene-${opts.beatId}.html`)
|
|
42109
42133
|
) : lint.files;
|
|
42110
42134
|
const lintCounts = countLintFindings(lintFiles);
|
|
42111
42135
|
checks.lint = {
|
|
@@ -42388,7 +42412,7 @@ function inspectReportedAsset(opts) {
|
|
|
42388
42412
|
opts.checks.assets.checked++;
|
|
42389
42413
|
if (isExternalRef(opts.value)) return;
|
|
42390
42414
|
const abs = isAbsolute2(opts.value) ? opts.value : resolve35(opts.projectDir, opts.value);
|
|
42391
|
-
if (
|
|
42415
|
+
if (existsSync33(abs)) return;
|
|
42392
42416
|
opts.checks.assets.missing.push(opts.value);
|
|
42393
42417
|
opts.issues.push({
|
|
42394
42418
|
severity: "warning",
|
|
@@ -42493,7 +42517,7 @@ __export(ai_review_exports, {
|
|
|
42493
42517
|
});
|
|
42494
42518
|
import { readFile as readFile28, rename as rename3 } from "node:fs/promises";
|
|
42495
42519
|
import { resolve as resolve36 } from "node:path";
|
|
42496
|
-
import { existsSync as
|
|
42520
|
+
import { existsSync as existsSync34 } from "node:fs";
|
|
42497
42521
|
function parseReviewFeedback(response) {
|
|
42498
42522
|
let cleaned = response.trim();
|
|
42499
42523
|
if (cleaned.startsWith("```json")) {
|
|
@@ -42525,7 +42549,7 @@ async function executeReview(options) {
|
|
|
42525
42549
|
model = "flash"
|
|
42526
42550
|
} = options;
|
|
42527
42551
|
const absVideoPath = resolve36(process.cwd(), videoPath);
|
|
42528
|
-
if (!
|
|
42552
|
+
if (!existsSync34(absVideoPath)) {
|
|
42529
42553
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
42530
42554
|
}
|
|
42531
42555
|
const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
|
|
@@ -42538,7 +42562,7 @@ async function executeReview(options) {
|
|
|
42538
42562
|
let storyboardContext = "";
|
|
42539
42563
|
if (storyboardPath2) {
|
|
42540
42564
|
const absStoryboardPath = resolve36(process.cwd(), storyboardPath2);
|
|
42541
|
-
if (
|
|
42565
|
+
if (existsSync34(absStoryboardPath)) {
|
|
42542
42566
|
const content = await readFile28(absStoryboardPath, "utf-8");
|
|
42543
42567
|
storyboardContext = `
|
|
42544
42568
|
|
|
@@ -42666,9 +42690,9 @@ var init_ai_review = __esm({
|
|
|
42666
42690
|
});
|
|
42667
42691
|
|
|
42668
42692
|
// src/commands/_shared/render-inspect.ts
|
|
42669
|
-
import { existsSync as
|
|
42693
|
+
import { existsSync as existsSync35 } from "node:fs";
|
|
42670
42694
|
import { readdir as readdir5, readFile as readFile29, stat as stat6 } from "node:fs/promises";
|
|
42671
|
-
import { basename as
|
|
42695
|
+
import { basename as basename11, isAbsolute as isAbsolute3, join as join19, relative as relative7, resolve as resolve37 } from "node:path";
|
|
42672
42696
|
function parseBlackdetectOutput(output) {
|
|
42673
42697
|
const ranges = [];
|
|
42674
42698
|
const regex2 = /black_start:(-?\d+(?:\.\d+)?)\s+black_end:(-?\d+(?:\.\d+)?)\s+black_duration:(-?\d+(?:\.\d+)?)/g;
|
|
@@ -43154,11 +43178,11 @@ async function inspectRender(opts) {
|
|
|
43154
43178
|
}
|
|
43155
43179
|
async function renderExpectsAudio(projectDir, beatId) {
|
|
43156
43180
|
const root = resolve37(projectDir);
|
|
43157
|
-
const rootHtml = await readTextIfExists(
|
|
43181
|
+
const rootHtml = await readTextIfExists(join19(root, "index.html"));
|
|
43158
43182
|
if (rootHtml && rootHtmlExpectsAudio(rootHtml, beatId)) return true;
|
|
43159
|
-
const buildReport = await readJsonIfExists(
|
|
43183
|
+
const buildReport = await readJsonIfExists(join19(root, "build-report.json"));
|
|
43160
43184
|
if (buildReportExpectsAudio(buildReport, beatId)) return true;
|
|
43161
|
-
const storyboard = await readTextIfExists(
|
|
43185
|
+
const storyboard = await readTextIfExists(join19(root, "STORYBOARD.md"));
|
|
43162
43186
|
if (!storyboard) return false;
|
|
43163
43187
|
try {
|
|
43164
43188
|
const parsed = parseStoryboard(storyboard);
|
|
@@ -43169,7 +43193,7 @@ async function renderExpectsAudio(projectDir, beatId) {
|
|
|
43169
43193
|
`assets/narration-${beat.id}.wav`,
|
|
43170
43194
|
`assets/music-${beat.id}.mp3`,
|
|
43171
43195
|
`assets/music-${beat.id}.wav`
|
|
43172
|
-
].some((asset) =>
|
|
43196
|
+
].some((asset) => existsSync35(join19(root, asset)))
|
|
43173
43197
|
);
|
|
43174
43198
|
} catch {
|
|
43175
43199
|
return false;
|
|
@@ -43277,8 +43301,8 @@ async function maybeWriteRenderReport(projectDir, opts, result) {
|
|
|
43277
43301
|
}
|
|
43278
43302
|
function renderSourceReports(projectDir, videoPath, checks) {
|
|
43279
43303
|
const reports = [];
|
|
43280
|
-
if (
|
|
43281
|
-
if (
|
|
43304
|
+
if (existsSync35(join19(projectDir, "build-report.json"))) reports.push("build-report.json");
|
|
43305
|
+
if (existsSync35(join19(projectDir, "render-report.json"))) reports.push("render-report.json");
|
|
43282
43306
|
if (videoPath) reports.push(displayPath(videoPath));
|
|
43283
43307
|
if (checks.durationSec !== void 0 || checks.width !== void 0 || checks.height !== void 0) {
|
|
43284
43308
|
reports.push("ffprobe");
|
|
@@ -43291,31 +43315,31 @@ function renderSourceReports(projectDir, videoPath, checks) {
|
|
|
43291
43315
|
async function resolveRenderVideoPath(projectDir, explicit, beatId) {
|
|
43292
43316
|
if (explicit) {
|
|
43293
43317
|
const candidate = resolve37(process.cwd(), explicit);
|
|
43294
|
-
return
|
|
43318
|
+
return existsSync35(candidate) ? candidate : null;
|
|
43295
43319
|
}
|
|
43296
|
-
const renderReportPath =
|
|
43297
|
-
if (
|
|
43320
|
+
const renderReportPath = join19(projectDir, "render-report.json");
|
|
43321
|
+
if (existsSync35(renderReportPath)) {
|
|
43298
43322
|
try {
|
|
43299
43323
|
const report = JSON.parse(await readFile29(renderReportPath, "utf-8"));
|
|
43300
43324
|
const reportBeat = typeof report.beat === "string" ? report.beat : null;
|
|
43301
43325
|
if (!beatId && reportBeat === null || beatId && reportBeat === beatId) {
|
|
43302
43326
|
const candidate = resolveReportedPath(projectDir, report.outputPath);
|
|
43303
|
-
if (candidate &&
|
|
43327
|
+
if (candidate && existsSync35(candidate)) return candidate;
|
|
43304
43328
|
}
|
|
43305
43329
|
} catch {
|
|
43306
43330
|
}
|
|
43307
43331
|
}
|
|
43308
|
-
const reportPath =
|
|
43309
|
-
if (!beatId &&
|
|
43332
|
+
const reportPath = join19(projectDir, "build-report.json");
|
|
43333
|
+
if (!beatId && existsSync35(reportPath)) {
|
|
43310
43334
|
try {
|
|
43311
43335
|
const report = JSON.parse(await readFile29(reportPath, "utf-8"));
|
|
43312
43336
|
const candidate = resolveReportedPath(projectDir, report.outputPath);
|
|
43313
|
-
if (candidate &&
|
|
43337
|
+
if (candidate && existsSync35(candidate)) return candidate;
|
|
43314
43338
|
} catch {
|
|
43315
43339
|
}
|
|
43316
43340
|
}
|
|
43317
|
-
const rendersDir =
|
|
43318
|
-
if (!
|
|
43341
|
+
const rendersDir = join19(projectDir, "renders");
|
|
43342
|
+
if (!existsSync35(rendersDir)) return null;
|
|
43319
43343
|
const entries = await readdir5(rendersDir, { withFileTypes: true });
|
|
43320
43344
|
const candidates = [];
|
|
43321
43345
|
for (const entry of entries) {
|
|
@@ -43323,7 +43347,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
|
|
|
43323
43347
|
const ext = entry.name.slice(entry.name.lastIndexOf(".")).toLowerCase();
|
|
43324
43348
|
if (!VIDEO_EXTENSIONS.has(ext)) continue;
|
|
43325
43349
|
if (beatId && !entry.name.includes(sanitizeFileSegment(beatId))) continue;
|
|
43326
|
-
const full =
|
|
43350
|
+
const full = join19(rendersDir, entry.name);
|
|
43327
43351
|
const info = await stat6(full);
|
|
43328
43352
|
candidates.push({ path: full, mtimeMs: info.mtimeMs });
|
|
43329
43353
|
}
|
|
@@ -43341,12 +43365,12 @@ async function runAiRenderReview(projectDir, videoPath, model) {
|
|
|
43341
43365
|
});
|
|
43342
43366
|
}
|
|
43343
43367
|
function resolveStoryboardPath(projectDir) {
|
|
43344
|
-
const storyboardPath2 =
|
|
43345
|
-
return
|
|
43368
|
+
const storyboardPath2 = join19(projectDir, "STORYBOARD.md");
|
|
43369
|
+
return existsSync35(storyboardPath2) ? storyboardPath2 : null;
|
|
43346
43370
|
}
|
|
43347
43371
|
async function expectedDurationFromBuildReport(projectDir, beatId) {
|
|
43348
|
-
const reportPath =
|
|
43349
|
-
if (!
|
|
43372
|
+
const reportPath = join19(projectDir, "build-report.json");
|
|
43373
|
+
if (!existsSync35(reportPath)) return void 0;
|
|
43350
43374
|
try {
|
|
43351
43375
|
const report = JSON.parse(await readFile29(reportPath, "utf-8"));
|
|
43352
43376
|
const beats = beatId ? (report.beats ?? []).filter((beat) => beat.id === beatId) : report.beats ?? [];
|
|
@@ -43360,8 +43384,8 @@ async function expectedDurationFromBuildReport(projectDir, beatId) {
|
|
|
43360
43384
|
}
|
|
43361
43385
|
}
|
|
43362
43386
|
async function beatTimingsFromBuildReport(projectDir, beatId) {
|
|
43363
|
-
const reportPath =
|
|
43364
|
-
if (!
|
|
43387
|
+
const reportPath = join19(projectDir, "build-report.json");
|
|
43388
|
+
if (!existsSync35(reportPath)) return [];
|
|
43365
43389
|
try {
|
|
43366
43390
|
const report = JSON.parse(await readFile29(reportPath, "utf-8"));
|
|
43367
43391
|
const source = report.beats ?? [];
|
|
@@ -43389,17 +43413,17 @@ async function beatTimingsFromBuildReport(projectDir, beatId) {
|
|
|
43389
43413
|
}
|
|
43390
43414
|
async function buildRenderReviewProjectContext(projectDir) {
|
|
43391
43415
|
const parts = [];
|
|
43392
|
-
const storyboard = await readTextIfExists(
|
|
43416
|
+
const storyboard = await readTextIfExists(join19(projectDir, "STORYBOARD.md"));
|
|
43393
43417
|
if (storyboard) {
|
|
43394
43418
|
parts.push(`STORYBOARD.md:
|
|
43395
43419
|
${truncateText(storyboard, 4e3)}`);
|
|
43396
43420
|
parts.push(`Storyboard beat cue summary:
|
|
43397
43421
|
${storyboardBeatCueSummary(storyboard)}`);
|
|
43398
43422
|
}
|
|
43399
|
-
const design = await readTextIfExists(
|
|
43423
|
+
const design = await readTextIfExists(join19(projectDir, "DESIGN.md"));
|
|
43400
43424
|
if (design) parts.push(`DESIGN.md:
|
|
43401
43425
|
${truncateText(design, 4e3)}`);
|
|
43402
|
-
const buildReport = await readTextIfExists(
|
|
43426
|
+
const buildReport = await readTextIfExists(join19(projectDir, "build-report.json"));
|
|
43403
43427
|
if (buildReport) parts.push(`build-report.json:
|
|
43404
43428
|
${truncateText(buildReport, 6e3)}`);
|
|
43405
43429
|
const timings = await beatTimingsFromBuildReport(projectDir);
|
|
@@ -43427,7 +43451,7 @@ function storyboardBeatCueSummary(storyboard) {
|
|
|
43427
43451
|
}
|
|
43428
43452
|
}
|
|
43429
43453
|
async function readTextIfExists(path) {
|
|
43430
|
-
if (!
|
|
43454
|
+
if (!existsSync35(path)) return null;
|
|
43431
43455
|
try {
|
|
43432
43456
|
return await readFile29(path, "utf-8");
|
|
43433
43457
|
} catch {
|
|
@@ -43527,7 +43551,7 @@ function aspectRatio(aspect) {
|
|
|
43527
43551
|
return w / h;
|
|
43528
43552
|
}
|
|
43529
43553
|
function displayPath(path) {
|
|
43530
|
-
return relative7(process.cwd(), path) ||
|
|
43554
|
+
return relative7(process.cwd(), path) || basename11(path);
|
|
43531
43555
|
}
|
|
43532
43556
|
function toSnakeCase(value) {
|
|
43533
43557
|
return value.replace(/[A-Z]/g, (match) => `_${match}`).toUpperCase();
|
|
@@ -43578,15 +43602,15 @@ __export(ai_audio_exports, {
|
|
|
43578
43602
|
executeTranscribe: () => executeTranscribe,
|
|
43579
43603
|
executeVoiceClone: () => executeVoiceClone
|
|
43580
43604
|
});
|
|
43581
|
-
import { resolve as resolve39, dirname as dirname12, basename as
|
|
43582
|
-
import { mkdir as
|
|
43605
|
+
import { resolve as resolve39, dirname as dirname12, basename as basename12, extname as extname11, join as join20 } from "node:path";
|
|
43606
|
+
import { mkdir as mkdir20, mkdtemp as mkdtemp2, readFile as readFile31, rm as rm6, writeFile as writeFile31 } from "node:fs/promises";
|
|
43583
43607
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
43584
|
-
import { existsSync as
|
|
43608
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
43585
43609
|
async function executeTranscribe(options) {
|
|
43586
43610
|
const { audioPath, language, output, format, apiKey } = options;
|
|
43587
43611
|
try {
|
|
43588
43612
|
const absPath = resolve39(process.cwd(), audioPath);
|
|
43589
|
-
if (!
|
|
43613
|
+
if (!existsSync36(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
|
|
43590
43614
|
const key = await getConfiguredApiKey("OPENAI_API_KEY", apiKey);
|
|
43591
43615
|
if (!key) return { success: false, error: "OPENAI_API_KEY required", errorKind: "auth" };
|
|
43592
43616
|
const result = await transcribeAudioFile(absPath, { apiKey: key, language });
|
|
@@ -43616,7 +43640,7 @@ async function executeIsolate(options) {
|
|
|
43616
43640
|
const { audioPath, output = "vocals.mp3", apiKey } = options;
|
|
43617
43641
|
try {
|
|
43618
43642
|
const absPath = resolve39(process.cwd(), audioPath);
|
|
43619
|
-
if (!
|
|
43643
|
+
if (!existsSync36(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
|
|
43620
43644
|
const key = await getConfiguredApiKey("ELEVENLABS_API_KEY", apiKey);
|
|
43621
43645
|
if (!key) return { success: false, error: "ELEVENLABS_API_KEY required", errorKind: "auth" };
|
|
43622
43646
|
const audioBuffer = await readFile31(absPath);
|
|
@@ -43644,7 +43668,7 @@ async function executeVoiceClone(options) {
|
|
|
43644
43668
|
const audioBuffers = [];
|
|
43645
43669
|
for (const samplePath of samplePaths) {
|
|
43646
43670
|
const absPath = resolve39(process.cwd(), samplePath);
|
|
43647
|
-
if (!
|
|
43671
|
+
if (!existsSync36(absPath)) return { success: false, error: `File not found: ${samplePath}`, errorKind: "not-found" };
|
|
43648
43672
|
const buffer = await readFile31(absPath);
|
|
43649
43673
|
audioBuffers.push(buffer);
|
|
43650
43674
|
}
|
|
@@ -43665,16 +43689,16 @@ async function executeVoiceClone(options) {
|
|
|
43665
43689
|
async function executeDub(options) {
|
|
43666
43690
|
const { mediaPath, language, source, voice, analyzeOnly, output } = options;
|
|
43667
43691
|
const absPath = resolve39(process.cwd(), mediaPath);
|
|
43668
|
-
if (!
|
|
43692
|
+
if (!existsSync36(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
|
|
43669
43693
|
const openaiKey = await getConfiguredApiKey("OPENAI_API_KEY");
|
|
43670
43694
|
if (!openaiKey) return { success: false, error: "OPENAI_API_KEY required for Whisper transcription", errorKind: "auth" };
|
|
43671
|
-
const workDir = await mkdtemp2(
|
|
43695
|
+
const workDir = await mkdtemp2(join20(tmpdir3(), "vibe-dub-"));
|
|
43672
43696
|
try {
|
|
43673
43697
|
const ext = extname11(absPath).toLowerCase();
|
|
43674
43698
|
const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
|
|
43675
43699
|
let audioPath = absPath;
|
|
43676
43700
|
if (isVideo) {
|
|
43677
|
-
audioPath =
|
|
43701
|
+
audioPath = join20(workDir, "source.mp3");
|
|
43678
43702
|
await execSafe("ffmpeg", ["-y", "-loglevel", "error", "-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "4", audioPath]);
|
|
43679
43703
|
}
|
|
43680
43704
|
const transcript = await transcribeAudioFile(audioPath, { apiKey: openaiKey, language: source });
|
|
@@ -43715,7 +43739,7 @@ async function executeDub(options) {
|
|
|
43715
43739
|
if (!spoken.success || !spoken.audioBuffer) {
|
|
43716
43740
|
return { success: false, error: `Speech failed on segment ${i + 1}: ${spoken.error}`, errorKind: spoken.errorKind };
|
|
43717
43741
|
}
|
|
43718
|
-
const clipPath =
|
|
43742
|
+
const clipPath = join20(workDir, `line-${i}.${tts.audioExtension}`);
|
|
43719
43743
|
await writeFile31(clipPath, spoken.audioBuffer);
|
|
43720
43744
|
const slot = Math.max(0.1, (report[i + 1]?.startTime ?? seg.endTime) - seg.startTime);
|
|
43721
43745
|
const length = await getAudioDuration(clipPath);
|
|
@@ -43724,7 +43748,7 @@ async function executeDub(options) {
|
|
|
43724
43748
|
const outputExt = isVideo ? ".mp3" : ext || ".mp3";
|
|
43725
43749
|
const finalOutputPath = resolve39(
|
|
43726
43750
|
process.cwd(),
|
|
43727
|
-
output || resolve39(dirname12(absPath), `${
|
|
43751
|
+
output || resolve39(dirname12(absPath), `${basename12(absPath, extname11(absPath))}-${language}${outputExt}`)
|
|
43728
43752
|
);
|
|
43729
43753
|
await mixAtOffsets(clips, finalOutputPath);
|
|
43730
43754
|
return { ...base, outputPath: finalOutputPath };
|
|
@@ -43742,7 +43766,7 @@ async function mixAtOffsets(clips, outputPath) {
|
|
|
43742
43766
|
return `[${i}:a]${tempo}adelay=${delay}|${delay}[a${i}]`;
|
|
43743
43767
|
});
|
|
43744
43768
|
const mix = `${clips.map((_, i) => `[a${i}]`).join("")}amix=inputs=${clips.length}:normalize=0:dropout_transition=0[out]`;
|
|
43745
|
-
await
|
|
43769
|
+
await mkdir20(dirname12(outputPath), { recursive: true });
|
|
43746
43770
|
await execSafe("ffmpeg", ["-y", "-loglevel", "error", ...inputs, "-filter_complex", [...chains, mix].join(";"), "-map", "[out]", outputPath]);
|
|
43747
43771
|
}
|
|
43748
43772
|
async function executeDuck(options) {
|
|
@@ -43759,9 +43783,9 @@ async function executeDuck(options) {
|
|
|
43759
43783
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
43760
43784
|
const absMusicPath = resolve39(process.cwd(), musicPath);
|
|
43761
43785
|
const absVoicePath = resolve39(process.cwd(), voicePath);
|
|
43762
|
-
if (!
|
|
43763
|
-
if (!
|
|
43764
|
-
const defaultOutput = resolve39(dirname12(absMusicPath), `${
|
|
43786
|
+
if (!existsSync36(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
|
|
43787
|
+
if (!existsSync36(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
|
|
43788
|
+
const defaultOutput = resolve39(dirname12(absMusicPath), `${basename12(absMusicPath, extname11(absMusicPath))}-ducked${extname11(absMusicPath)}`);
|
|
43765
43789
|
const outputPath = resolve39(process.cwd(), output || defaultOutput);
|
|
43766
43790
|
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`;
|
|
43767
43791
|
await execSafe("ffmpeg", [
|
|
@@ -43774,7 +43798,7 @@ async function executeDuck(options) {
|
|
|
43774
43798
|
"-y",
|
|
43775
43799
|
outputPath
|
|
43776
43800
|
], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
|
|
43777
|
-
if (!
|
|
43801
|
+
if (!existsSync36(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
|
|
43778
43802
|
return { success: true, outputPath };
|
|
43779
43803
|
} catch (error) {
|
|
43780
43804
|
return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
@@ -43797,9 +43821,9 @@ var init_ai_audio = __esm({
|
|
|
43797
43821
|
});
|
|
43798
43822
|
|
|
43799
43823
|
// src/commands/ai-highlights.ts
|
|
43800
|
-
import { readFile as readFile32, writeFile as writeFile32, mkdir as
|
|
43801
|
-
import { resolve as resolve41, dirname as dirname13, basename as
|
|
43802
|
-
import { existsSync as
|
|
43824
|
+
import { readFile as readFile32, writeFile as writeFile32, mkdir as mkdir21 } from "node:fs/promises";
|
|
43825
|
+
import { resolve as resolve41, dirname as dirname13, basename as basename13, extname as extname12 } from "node:path";
|
|
43826
|
+
import { existsSync as existsSync38 } from "node:fs";
|
|
43803
43827
|
function filterHighlights(highlights, options) {
|
|
43804
43828
|
let filtered = highlights.filter((h) => h.confidence >= options.threshold);
|
|
43805
43829
|
filtered.sort((a, b) => b.confidence - a.confidence);
|
|
@@ -43839,7 +43863,7 @@ function truncate(text, maxLength) {
|
|
|
43839
43863
|
async function executeHighlights(options) {
|
|
43840
43864
|
try {
|
|
43841
43865
|
const absPath = resolve41(process.cwd(), options.media);
|
|
43842
|
-
if (!
|
|
43866
|
+
if (!existsSync38(absPath)) {
|
|
43843
43867
|
return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
|
|
43844
43868
|
}
|
|
43845
43869
|
const ext = extname12(absPath).toLowerCase();
|
|
@@ -43951,7 +43975,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
43951
43975
|
sourceDuration = await ffprobeDuration(absPath);
|
|
43952
43976
|
}
|
|
43953
43977
|
const transcriptResult = await transcribeAudioFile(audioPath, { apiKey: openaiApiKey, language: options.language });
|
|
43954
|
-
if (tempAudioPath &&
|
|
43978
|
+
if (tempAudioPath && existsSync38(tempAudioPath)) {
|
|
43955
43979
|
const { unlink: unlinkFile } = await import("node:fs/promises");
|
|
43956
43980
|
await unlinkFile(tempAudioPath).catch(() => {
|
|
43957
43981
|
});
|
|
@@ -43997,7 +44021,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
43997
44021
|
if (options.project) {
|
|
43998
44022
|
const project = new Project("Highlight Reel");
|
|
43999
44023
|
const source = project.addSource({
|
|
44000
|
-
name:
|
|
44024
|
+
name: basename13(absPath),
|
|
44001
44025
|
url: absPath,
|
|
44002
44026
|
type: isVideo ? "video" : "audio",
|
|
44003
44027
|
duration: sourceDuration
|
|
@@ -44038,7 +44062,7 @@ async function executeAutoShorts(options) {
|
|
|
44038
44062
|
return { success: false, shorts: [], error: "FFmpeg not found" };
|
|
44039
44063
|
}
|
|
44040
44064
|
const absPath = resolve41(process.cwd(), options.video);
|
|
44041
|
-
if (!
|
|
44065
|
+
if (!existsSync38(absPath)) {
|
|
44042
44066
|
return { success: false, shorts: [], error: `File not found: ${absPath}` };
|
|
44043
44067
|
}
|
|
44044
44068
|
const targetDuration = options.duration ?? 60;
|
|
@@ -44162,8 +44186,8 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
44162
44186
|
};
|
|
44163
44187
|
}
|
|
44164
44188
|
const outputDir = options.outputDir ? resolve41(process.cwd(), options.outputDir) : dirname13(absPath);
|
|
44165
|
-
if (options.outputDir && !
|
|
44166
|
-
await
|
|
44189
|
+
if (options.outputDir && !existsSync38(outputDir)) {
|
|
44190
|
+
await mkdir21(outputDir, { recursive: true });
|
|
44167
44191
|
}
|
|
44168
44192
|
const result = {
|
|
44169
44193
|
success: true,
|
|
@@ -44171,7 +44195,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
44171
44195
|
};
|
|
44172
44196
|
for (let i = 0; i < selectedHighlights.length; i++) {
|
|
44173
44197
|
const h = selectedHighlights[i];
|
|
44174
|
-
const baseName =
|
|
44198
|
+
const baseName = basename13(absPath, extname12(absPath));
|
|
44175
44199
|
const outputPath = resolve41(outputDir, `${baseName}-short-${i + 1}.mp4`);
|
|
44176
44200
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
44177
44201
|
"-v",
|
|
@@ -44245,7 +44269,7 @@ function registerHighlightsCommands(aiCommand) {
|
|
|
44245
44269
|
validateOutputPath(options.output);
|
|
44246
44270
|
}
|
|
44247
44271
|
const absPath = resolve41(process.cwd(), mediaPath);
|
|
44248
|
-
if (!
|
|
44272
|
+
if (!existsSync38(absPath)) {
|
|
44249
44273
|
exitWithError(notFoundError(absPath));
|
|
44250
44274
|
}
|
|
44251
44275
|
if (options.dryRun) {
|
|
@@ -44428,7 +44452,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
44428
44452
|
const transcriptResult = await transcribeAudioFile(audioPath, { apiKey: openaiApiKey, language: options.language });
|
|
44429
44453
|
if (transcriptResult.status === "failed" || !transcriptResult.segments) {
|
|
44430
44454
|
transcribeSpinner.fail("Transcription failed");
|
|
44431
|
-
if (tempAudioPath &&
|
|
44455
|
+
if (tempAudioPath && existsSync38(tempAudioPath)) {
|
|
44432
44456
|
const { unlink: unlinkFile } = await import("node:fs/promises");
|
|
44433
44457
|
await unlinkFile(tempAudioPath).catch(() => {
|
|
44434
44458
|
});
|
|
@@ -44436,7 +44460,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
44436
44460
|
exitWithError(apiError(`Transcription failed: ${transcriptResult.error}`, true));
|
|
44437
44461
|
}
|
|
44438
44462
|
transcribeSpinner.succeed(source_default.green(`Transcribed ${transcriptResult.segments.length} segments`));
|
|
44439
|
-
if (tempAudioPath &&
|
|
44463
|
+
if (tempAudioPath && existsSync38(tempAudioPath)) {
|
|
44440
44464
|
const { unlink: unlinkFile } = await import("node:fs/promises");
|
|
44441
44465
|
await unlinkFile(tempAudioPath).catch(() => {
|
|
44442
44466
|
});
|
|
@@ -44502,7 +44526,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
44502
44526
|
const projectSpinner = ora("\u{1F4E6} Creating project...").start();
|
|
44503
44527
|
const project = new Project("Highlight Reel");
|
|
44504
44528
|
const source = project.addSource({
|
|
44505
|
-
name:
|
|
44529
|
+
name: basename13(absPath),
|
|
44506
44530
|
url: absPath,
|
|
44507
44531
|
type: isVideo ? "video" : "audio",
|
|
44508
44532
|
duration: sourceDuration
|
|
@@ -44542,7 +44566,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
44542
44566
|
validateOutputPath(options.output);
|
|
44543
44567
|
}
|
|
44544
44568
|
const absPath = resolve41(process.cwd(), videoPath);
|
|
44545
|
-
if (!
|
|
44569
|
+
if (!existsSync38(absPath)) {
|
|
44546
44570
|
exitWithError(notFoundError(absPath));
|
|
44547
44571
|
}
|
|
44548
44572
|
if (options.dryRun) {
|
|
@@ -44731,8 +44755,8 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
44731
44755
|
return;
|
|
44732
44756
|
}
|
|
44733
44757
|
const outputDir = options.outputDir ? resolve41(process.cwd(), options.outputDir) : dirname13(absPath);
|
|
44734
|
-
if (options.outputDir && !
|
|
44735
|
-
await
|
|
44758
|
+
if (options.outputDir && !existsSync38(outputDir)) {
|
|
44759
|
+
await mkdir21(outputDir, { recursive: true });
|
|
44736
44760
|
}
|
|
44737
44761
|
for (let i = 0; i < selectedHighlights.length; i++) {
|
|
44738
44762
|
const h = selectedHighlights[i];
|
|
@@ -44744,12 +44768,12 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
44744
44768
|
outputPath += ".mp4";
|
|
44745
44769
|
}
|
|
44746
44770
|
} else {
|
|
44747
|
-
const baseName =
|
|
44771
|
+
const baseName = basename13(absPath, extname12(absPath));
|
|
44748
44772
|
outputPath = resolve41(outputDir, `${baseName}-short-${i + 1}.mp4`);
|
|
44749
44773
|
}
|
|
44750
44774
|
const parentDir = dirname13(outputPath);
|
|
44751
|
-
if (!
|
|
44752
|
-
await
|
|
44775
|
+
if (!existsSync38(parentDir)) {
|
|
44776
|
+
await mkdir21(parentDir, { recursive: true });
|
|
44753
44777
|
}
|
|
44754
44778
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
44755
44779
|
"-v",
|
|
@@ -44822,9 +44846,9 @@ var init_ai_highlights = __esm({
|
|
|
44822
44846
|
});
|
|
44823
44847
|
|
|
44824
44848
|
// src/commands/ai-animated-caption.ts
|
|
44825
|
-
import { resolve as resolve42, dirname as dirname14, basename as
|
|
44826
|
-
import { writeFile as writeFile33, mkdir as
|
|
44827
|
-
import { existsSync as
|
|
44849
|
+
import { resolve as resolve42, dirname as dirname14, basename as basename14 } from "node:path";
|
|
44850
|
+
import { writeFile as writeFile33, mkdir as mkdir22, rm as rm7 } from "node:fs/promises";
|
|
44851
|
+
import { existsSync as existsSync39 } from "node:fs";
|
|
44828
44852
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
44829
44853
|
function groupWords(words, options = {}) {
|
|
44830
44854
|
if (words.length === 0) return [];
|
|
@@ -44971,7 +44995,7 @@ async function executeAnimatedCaption(options) {
|
|
|
44971
44995
|
}
|
|
44972
44996
|
const effectiveFontSize = fontSize ?? Math.round(height * 0.04);
|
|
44973
44997
|
const tmpAudioDir = resolve42(tmpdir4(), `vf-ac-${Date.now()}`);
|
|
44974
|
-
await
|
|
44998
|
+
await mkdir22(tmpAudioDir, { recursive: true });
|
|
44975
44999
|
const audioPath = resolve42(tmpAudioDir, "audio.wav");
|
|
44976
45000
|
await execSafe("ffmpeg", [
|
|
44977
45001
|
"-y",
|
|
@@ -45001,8 +45025,8 @@ async function executeAnimatedCaption(options) {
|
|
|
45001
45025
|
const groups = groupWords(transcript.words, { wordsPerGroup, maxChars });
|
|
45002
45026
|
const absOutputPath = resolve42(process.cwd(), outputPath);
|
|
45003
45027
|
const outDir = dirname14(absOutputPath);
|
|
45004
|
-
if (!
|
|
45005
|
-
await
|
|
45028
|
+
if (!existsSync39(outDir)) {
|
|
45029
|
+
await mkdir22(outDir, { recursive: true });
|
|
45006
45030
|
}
|
|
45007
45031
|
if (tier === "ass") {
|
|
45008
45032
|
const assContent = generateASS(
|
|
@@ -45033,7 +45057,7 @@ async function executeAnimatedCaption(options) {
|
|
|
45033
45057
|
width,
|
|
45034
45058
|
height,
|
|
45035
45059
|
fps: videoFps,
|
|
45036
|
-
videoFileName:
|
|
45060
|
+
videoFileName: basename14(videoPath)
|
|
45037
45061
|
});
|
|
45038
45062
|
const durationInFrames = Math.ceil(duration * videoFps);
|
|
45039
45063
|
const renderResult = await renderWithEmbeddedVideo({
|
|
@@ -45044,7 +45068,7 @@ async function executeAnimatedCaption(options) {
|
|
|
45044
45068
|
fps: videoFps,
|
|
45045
45069
|
durationInFrames,
|
|
45046
45070
|
videoPath,
|
|
45047
|
-
videoFileName:
|
|
45071
|
+
videoFileName: basename14(videoPath),
|
|
45048
45072
|
outputPath: absOutputPath
|
|
45049
45073
|
});
|
|
45050
45074
|
if (!renderResult.success) {
|
|
@@ -54649,7 +54673,7 @@ __export(detect_exports, {
|
|
|
54649
54673
|
executeDetectSilence: () => executeDetectSilence
|
|
54650
54674
|
});
|
|
54651
54675
|
import { readFile as readFile34, writeFile as writeFile35 } from "node:fs/promises";
|
|
54652
|
-
import { resolve as resolve46, basename as
|
|
54676
|
+
import { resolve as resolve46, basename as basename18 } from "node:path";
|
|
54653
54677
|
async function executeDetectScenes(options) {
|
|
54654
54678
|
try {
|
|
54655
54679
|
if (!commandExists("ffmpeg")) {
|
|
@@ -54899,7 +54923,7 @@ var init_detect = __esm({
|
|
|
54899
54923
|
let source = project.getSources().find((s) => s.url === absPath);
|
|
54900
54924
|
if (!source) {
|
|
54901
54925
|
source = project.addSource({
|
|
54902
|
-
name:
|
|
54926
|
+
name: basename18(absPath),
|
|
54903
54927
|
type: "video",
|
|
54904
54928
|
url: absPath,
|
|
54905
54929
|
duration: totalDuration
|
|
@@ -56707,14 +56731,14 @@ var init_composer_resolve = __esm({
|
|
|
56707
56731
|
});
|
|
56708
56732
|
|
|
56709
56733
|
// src/commands/_shared/storyboard-revise.ts
|
|
56710
|
-
import { existsSync as
|
|
56734
|
+
import { existsSync as existsSync44 } from "node:fs";
|
|
56711
56735
|
import { readFile as readFile38, writeFile as writeFile38 } from "node:fs/promises";
|
|
56712
|
-
import { join as
|
|
56736
|
+
import { join as join24, resolve as resolve50 } from "node:path";
|
|
56713
56737
|
async function executeStoryboardRevision(opts) {
|
|
56714
56738
|
const projectDir = resolve50(opts.projectDir);
|
|
56715
|
-
const storyboardPath2 =
|
|
56739
|
+
const storyboardPath2 = join24(projectDir, "STORYBOARD.md");
|
|
56716
56740
|
const retryWith = [`vibe storyboard revise ${projectDir} --from "<request>" --dry-run --json`];
|
|
56717
|
-
if (!
|
|
56741
|
+
if (!existsSync44(storyboardPath2)) {
|
|
56718
56742
|
return failureResult({
|
|
56719
56743
|
projectDir,
|
|
56720
56744
|
storyboardPath: storyboardPath2,
|
|
@@ -56955,16 +56979,16 @@ function diffChangedBeats(before, after) {
|
|
|
56955
56979
|
return ids.filter((id) => JSON.stringify(beforeBeats.get(id) ?? null) !== JSON.stringify(afterBeats.get(id) ?? null));
|
|
56956
56980
|
}
|
|
56957
56981
|
async function readRevisionContext(projectDir) {
|
|
56958
|
-
const designMd = await readOptionalText(
|
|
56959
|
-
const buildReportJson = await readOptionalJsonText(
|
|
56982
|
+
const designMd = await readOptionalText(join24(projectDir, "DESIGN.md"));
|
|
56983
|
+
const buildReportJson = await readOptionalJsonText(join24(projectDir, "build-report.json"));
|
|
56960
56984
|
return { designMd, buildReportJson };
|
|
56961
56985
|
}
|
|
56962
56986
|
async function readOptionalText(path) {
|
|
56963
|
-
if (!
|
|
56987
|
+
if (!existsSync44(path)) return null;
|
|
56964
56988
|
return readFile38(path, "utf-8");
|
|
56965
56989
|
}
|
|
56966
56990
|
async function readOptionalJsonText(path) {
|
|
56967
|
-
if (!
|
|
56991
|
+
if (!existsSync44(path)) return null;
|
|
56968
56992
|
try {
|
|
56969
56993
|
const parsed = JSON.parse(await readFile38(path, "utf-8"));
|
|
56970
56994
|
return JSON.stringify(parsed, null, 2);
|
|
@@ -57118,7 +57142,7 @@ var init_design_parse = __esm({
|
|
|
57118
57142
|
});
|
|
57119
57143
|
|
|
57120
57144
|
// src/commands/_shared/build-asset-reference.ts
|
|
57121
|
-
import { existsSync as
|
|
57145
|
+
import { existsSync as existsSync47 } from "node:fs";
|
|
57122
57146
|
import { extname as extname15, isAbsolute as isAbsolute4, relative as relative8, resolve as resolve53, sep as sep2 } from "node:path";
|
|
57123
57147
|
function resolveTypedAssetReference(projectDir, kind, value) {
|
|
57124
57148
|
const raw = stringOrUndefined2(value);
|
|
@@ -57149,7 +57173,7 @@ function resolveReference(projectDir, kind, raw, source, forcedError) {
|
|
|
57149
57173
|
const absPath = isAbsolute4(raw) ? resolve53(raw) : resolve53(projectDir, raw);
|
|
57150
57174
|
const rel = relative8(resolve53(projectDir), absPath);
|
|
57151
57175
|
const withinProject = rel.length > 0 && !rel.startsWith("..") && !isAbsolute4(rel);
|
|
57152
|
-
const exists = withinProject &&
|
|
57176
|
+
const exists = withinProject && existsSync47(absPath);
|
|
57153
57177
|
let error = forcedError;
|
|
57154
57178
|
if (!error && inferred && inferred !== kind && !isNarrationAudioReference(kind, ext)) {
|
|
57155
57179
|
error = `Asset reference "${raw}" is a ${inferred} asset, not ${kind}.`;
|
|
@@ -57370,8 +57394,8 @@ var init_build_video_providers = __esm({
|
|
|
57370
57394
|
});
|
|
57371
57395
|
|
|
57372
57396
|
// src/commands/_shared/build-plan.ts
|
|
57373
|
-
import { existsSync as
|
|
57374
|
-
import { dirname as dirname17, join as
|
|
57397
|
+
import { existsSync as existsSync48 } from "node:fs";
|
|
57398
|
+
import { dirname as dirname17, join as join26, resolve as resolve54 } from "node:path";
|
|
57375
57399
|
function backdropCostUsd(quality) {
|
|
57376
57400
|
return quality === "hd" ? 0.2 : 0.05;
|
|
57377
57401
|
}
|
|
@@ -57390,11 +57414,11 @@ async function createBuildPlan(opts) {
|
|
|
57390
57414
|
const skipBackdrop = opts.skipBackdrop ?? (composerIsFootage ? true : kindPolicy.skipBackdrop ?? false);
|
|
57391
57415
|
const skipKeyframe = opts.skipKeyframe ?? kindPolicy.skipKeyframe ?? false;
|
|
57392
57416
|
const skipVideo = opts.skipVideo ?? kindPolicy.skipVideo ?? false;
|
|
57393
|
-
const storyboardPath2 =
|
|
57417
|
+
const storyboardPath2 = join26(projectDir, "STORYBOARD.md");
|
|
57394
57418
|
const warnings = [];
|
|
57395
57419
|
const retryWith = [];
|
|
57396
57420
|
const providerResolution = [];
|
|
57397
|
-
if (!
|
|
57421
|
+
if (!existsSync48(storyboardPath2)) {
|
|
57398
57422
|
const validation2 = {
|
|
57399
57423
|
ok: false,
|
|
57400
57424
|
issues: [
|
|
@@ -57454,7 +57478,7 @@ async function createBuildPlan(opts) {
|
|
|
57454
57478
|
const imageQuality = opts.imageQuality ?? config2.config.build.imageQuality ?? "hd";
|
|
57455
57479
|
const imageSize2 = opts.imageSize ?? config2.config.build.imageSize ?? "1536x1024";
|
|
57456
57480
|
const imageRatio = imageRatioForSize(imageSize2);
|
|
57457
|
-
const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !
|
|
57481
|
+
const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync48(join26(projectDir, `compositions/scene-${beat.id}.html`)));
|
|
57458
57482
|
const resolved = await resolvePlanProviders({
|
|
57459
57483
|
projectDir,
|
|
57460
57484
|
config: config2,
|
|
@@ -57604,7 +57628,7 @@ async function createBuildPlan(opts) {
|
|
|
57604
57628
|
active: includeAssets
|
|
57605
57629
|
}) : null;
|
|
57606
57630
|
const compositionPath = `compositions/scene-${beat.id}.html`;
|
|
57607
|
-
const compositionExists =
|
|
57631
|
+
const compositionExists = existsSync48(join26(projectDir, compositionPath));
|
|
57608
57632
|
for (const asset of [narration, backdrop, keyframe, video, music]) {
|
|
57609
57633
|
if (!asset) continue;
|
|
57610
57634
|
if (asset.referenceError) {
|
|
@@ -57677,7 +57701,7 @@ async function createBuildPlan(opts) {
|
|
|
57677
57701
|
}
|
|
57678
57702
|
}
|
|
57679
57703
|
const validationOk = !validationIssues.some((issue) => issue.severity === "error");
|
|
57680
|
-
if (!
|
|
57704
|
+
if (!existsSync48(join26(projectDir, config2.config.composition.entry))) {
|
|
57681
57705
|
missing.add("root-composition");
|
|
57682
57706
|
if (validationOk) retryWith.push(`vibe build ${projectDir} --stage sync --json`);
|
|
57683
57707
|
}
|
|
@@ -57711,7 +57735,7 @@ async function createBuildPlan(opts) {
|
|
|
57711
57735
|
}
|
|
57712
57736
|
if (referenced.size > 0) {
|
|
57713
57737
|
const toGenerate = resolveCharacters(parsed.frontmatter).filter(
|
|
57714
|
-
(c) => referenced.has(c.name) && c.prompt && (opts.force || !
|
|
57738
|
+
(c) => referenced.has(c.name) && c.prompt && (opts.force || !existsSync48(join26(projectDir, `assets/character-${c.name}.png`)))
|
|
57715
57739
|
);
|
|
57716
57740
|
if (toGenerate.length > 0) {
|
|
57717
57741
|
estimatedCostUsd += toGenerate.length * backdropCostUsd(opts.imageQuality ?? "hd");
|
|
@@ -57824,8 +57848,8 @@ function assetPlan(opts) {
|
|
|
57824
57848
|
reason: ready ? "referenced-asset" : "invalid-reference"
|
|
57825
57849
|
};
|
|
57826
57850
|
}
|
|
57827
|
-
const canonicalExists =
|
|
57828
|
-
const cacheHit = opts.cache ?
|
|
57851
|
+
const canonicalExists = existsSync48(join26(opts.projectDir, opts.path));
|
|
57852
|
+
const cacheHit = opts.cache ? existsSync48(join26(opts.projectDir, opts.cache.path)) : false;
|
|
57829
57853
|
const freshness = assetFreshnessFromMetadata({
|
|
57830
57854
|
projectDir: opts.projectDir,
|
|
57831
57855
|
kind: opts.kind,
|
|
@@ -58034,7 +58058,7 @@ async function firstConfiguredComposer(projectDir) {
|
|
|
58034
58058
|
}
|
|
58035
58059
|
function firstExisting2(projectDir, paths) {
|
|
58036
58060
|
for (const path of paths) {
|
|
58037
|
-
if (
|
|
58061
|
+
if (existsSync48(join26(projectDir, path))) return path;
|
|
58038
58062
|
}
|
|
58039
58063
|
return null;
|
|
58040
58064
|
}
|
|
@@ -58070,12 +58094,12 @@ function unique3(items) {
|
|
|
58070
58094
|
return [...new Set(items.filter((item) => item.length > 0))];
|
|
58071
58095
|
}
|
|
58072
58096
|
function loadPlanEnv(projectDir) {
|
|
58073
|
-
(0, import_dotenv3.config)({ path:
|
|
58097
|
+
(0, import_dotenv3.config)({ path: join26(projectDir, ".env"), quiet: true });
|
|
58074
58098
|
(0, import_dotenv3.config)({ path: resolve54(process.cwd(), ".env"), quiet: true });
|
|
58075
58099
|
let dir = process.cwd();
|
|
58076
58100
|
while (dir !== dirname17(dir)) {
|
|
58077
|
-
if (
|
|
58078
|
-
(0, import_dotenv3.config)({ path:
|
|
58101
|
+
if (existsSync48(join26(dir, "pnpm-workspace.yaml"))) {
|
|
58102
|
+
(0, import_dotenv3.config)({ path: join26(dir, ".env"), quiet: true });
|
|
58079
58103
|
return;
|
|
58080
58104
|
}
|
|
58081
58105
|
dir = dirname17(dir);
|
|
@@ -58160,9 +58184,9 @@ var init_concurrency = __esm({
|
|
|
58160
58184
|
});
|
|
58161
58185
|
|
|
58162
58186
|
// src/commands/_shared/compose-aivideo.ts
|
|
58163
|
-
import { existsSync as
|
|
58164
|
-
import { mkdir as
|
|
58165
|
-
import { join as
|
|
58187
|
+
import { existsSync as existsSync49 } from "node:fs";
|
|
58188
|
+
import { mkdir as mkdir25, readFile as readFile41, writeFile as writeFile40 } from "node:fs/promises";
|
|
58189
|
+
import { join as join27, resolve as resolve56 } from "node:path";
|
|
58166
58190
|
function headingDescriptor(heading) {
|
|
58167
58191
|
const stripped = heading.replace(/^\s*beat\s+/i, "");
|
|
58168
58192
|
const m = stripped.match(/^[A-Za-z0-9][\w-]*(?:\s*[—:]\s*|\s+-\s+)(.+)$/);
|
|
@@ -58319,20 +58343,20 @@ async function composeAiVideo(opts) {
|
|
|
58319
58343
|
const clipRels = [];
|
|
58320
58344
|
for (const beat of beats) {
|
|
58321
58345
|
const rel = clipRelFor(beat);
|
|
58322
|
-
if (!
|
|
58346
|
+
if (!existsSync49(join27(projectDir, rel))) {
|
|
58323
58347
|
throw new Error(`Missing clip for beat "${beat.id}" at ${rel} \u2014 run the assets stage first.`);
|
|
58324
58348
|
}
|
|
58325
58349
|
clipRels.push(rel);
|
|
58326
58350
|
}
|
|
58327
|
-
await
|
|
58351
|
+
await mkdir25(join27(projectDir, "assets"), { recursive: true });
|
|
58328
58352
|
const bgRel = "assets/bg-full.mp4";
|
|
58329
58353
|
const concatArgs = buildBgConcatArgs(
|
|
58330
|
-
clipRels.map((r) =>
|
|
58331
|
-
|
|
58354
|
+
clipRels.map((r) => join27(projectDir, r)),
|
|
58355
|
+
join27(projectDir, bgRel)
|
|
58332
58356
|
);
|
|
58333
58357
|
await execSafe("ffmpeg", concatArgs, { timeout: 6e5 });
|
|
58334
|
-
const totalDurationSec = await probeDurationSec(
|
|
58335
|
-
await
|
|
58358
|
+
const totalDurationSec = await probeDurationSec(join27(projectDir, bgRel));
|
|
58359
|
+
await mkdir25(join27(projectDir, "compositions"), { recursive: true });
|
|
58336
58360
|
const sceneFiles = [];
|
|
58337
58361
|
for (let i = 0; i < beats.length; i++) {
|
|
58338
58362
|
const beat = beats[i];
|
|
@@ -58349,11 +58373,11 @@ async function composeAiVideo(opts) {
|
|
|
58349
58373
|
isLast: i === beats.length - 1
|
|
58350
58374
|
});
|
|
58351
58375
|
const rel = `compositions/scene-${beat.id}.html`;
|
|
58352
|
-
await writeFile40(
|
|
58376
|
+
await writeFile40(join27(projectDir, rel), html, "utf-8");
|
|
58353
58377
|
sceneFiles.push(rel);
|
|
58354
58378
|
}
|
|
58355
|
-
const indexPath =
|
|
58356
|
-
const next =
|
|
58379
|
+
const indexPath = join27(projectDir, "index.html");
|
|
58380
|
+
const next = existsSync49(indexPath) ? injectBgVideo(await readFile41(indexPath, "utf-8"), bgRel, totalDurationSec) : buildRootShell(tokens, bgRel, totalDurationSec);
|
|
58357
58381
|
await writeFile40(indexPath, next, "utf-8");
|
|
58358
58382
|
return { bgVideoRel: bgRel, sceneFiles, totalDurationSec, beats: beats.length };
|
|
58359
58383
|
}
|
|
@@ -58402,8 +58426,8 @@ async function probeDurationSec(absPath) {
|
|
|
58402
58426
|
}
|
|
58403
58427
|
}
|
|
58404
58428
|
async function loadProjectTokens(projectDir) {
|
|
58405
|
-
const designPath =
|
|
58406
|
-
if (!
|
|
58429
|
+
const designPath = join27(projectDir, "DESIGN.md");
|
|
58430
|
+
if (!existsSync49(designPath)) return DEFAULT_AIVIDEO_TOKENS;
|
|
58407
58431
|
try {
|
|
58408
58432
|
return extractDesignTokens(await readFile41(designPath, "utf-8"));
|
|
58409
58433
|
} catch {
|
|
@@ -58480,9 +58504,9 @@ var init_compose_aivideo = __esm({
|
|
|
58480
58504
|
});
|
|
58481
58505
|
|
|
58482
58506
|
// src/commands/_shared/footage-assemble.ts
|
|
58483
|
-
import { existsSync as
|
|
58484
|
-
import { mkdir as
|
|
58485
|
-
import { dirname as dirname18, isAbsolute as isAbsolute5, join as
|
|
58507
|
+
import { existsSync as existsSync50 } from "node:fs";
|
|
58508
|
+
import { mkdir as mkdir26, writeFile as writeFile41 } from "node:fs/promises";
|
|
58509
|
+
import { dirname as dirname18, isAbsolute as isAbsolute5, join as join28, resolve as resolve57 } from "node:path";
|
|
58486
58510
|
function planFootageTimeline(inputs, opts = {}) {
|
|
58487
58511
|
if (inputs.length === 0) {
|
|
58488
58512
|
throw new Error("No clips to assemble.");
|
|
@@ -58708,12 +58732,12 @@ async function executeFootageAssemble(opts) {
|
|
|
58708
58732
|
const kenburnsBeatIds = [];
|
|
58709
58733
|
for (const beat of beats) {
|
|
58710
58734
|
const clipRel = `assets/video-${beat.id}.mp4`;
|
|
58711
|
-
if (
|
|
58735
|
+
if (existsSync50(join28(projectDir, clipRel))) {
|
|
58712
58736
|
sourceRels.push({ rel: clipRel, kind: "video" });
|
|
58713
58737
|
continue;
|
|
58714
58738
|
}
|
|
58715
58739
|
const keyframeRel = `assets/keyframe-${beat.id}.png`;
|
|
58716
|
-
if (
|
|
58740
|
+
if (existsSync50(join28(projectDir, keyframeRel))) {
|
|
58717
58741
|
sourceRels.push({ rel: keyframeRel, kind: "image" });
|
|
58718
58742
|
kenburnsBeatIds.push(beat.id);
|
|
58719
58743
|
continue;
|
|
@@ -58732,7 +58756,7 @@ async function executeFootageAssemble(opts) {
|
|
|
58732
58756
|
}
|
|
58733
58757
|
const narrationRels = /* @__PURE__ */ new Map();
|
|
58734
58758
|
for (let i = 0; i < beats.length; i++) {
|
|
58735
|
-
const rel = ["mp3", "wav"].map((ext) => `assets/narration-${beats[i].id}.${ext}`).find((r) =>
|
|
58759
|
+
const rel = ["mp3", "wav"].map((ext) => `assets/narration-${beats[i].id}.${ext}`).find((r) => existsSync50(join28(projectDir, r)));
|
|
58736
58760
|
if (rel) narrationRels.set(i, rel);
|
|
58737
58761
|
}
|
|
58738
58762
|
const music = resolveMusicBed(projectDir, beats[0]?.id, opts.music);
|
|
@@ -58746,9 +58770,9 @@ async function executeFootageAssemble(opts) {
|
|
|
58746
58770
|
}
|
|
58747
58771
|
const inputs = [];
|
|
58748
58772
|
for (let i = 0; i < beats.length; i++) {
|
|
58749
|
-
const clipDurationSec = sourceRels[i].kind === "image" ? beats[i].duration ?? FOOTAGE_DEFAULTS.kenburnsSec : round3(await ffprobeDuration(
|
|
58773
|
+
const clipDurationSec = sourceRels[i].kind === "image" ? beats[i].duration ?? FOOTAGE_DEFAULTS.kenburnsSec : round3(await ffprobeDuration(join28(projectDir, sourceRels[i].rel)));
|
|
58750
58774
|
const narrationRel = narrationRels.get(i);
|
|
58751
|
-
const narrationDurationSec = narrationRel ? round3(await ffprobeDuration(
|
|
58775
|
+
const narrationDurationSec = narrationRel ? round3(await ffprobeDuration(join28(projectDir, narrationRel))) : void 0;
|
|
58752
58776
|
inputs.push({ id: beats[i].id, clipDurationSec, narrationDurationSec });
|
|
58753
58777
|
}
|
|
58754
58778
|
const plan = planFootageTimeline(inputs, {
|
|
@@ -58811,14 +58835,14 @@ async function executeFootageAssemble(opts) {
|
|
|
58811
58835
|
if (opts.dryRun) {
|
|
58812
58836
|
return { success: true, dryRun: true, outputPath, report };
|
|
58813
58837
|
}
|
|
58814
|
-
await
|
|
58838
|
+
await mkdir26(dirname18(outputPath), { recursive: true });
|
|
58815
58839
|
const args = buildFootageAssembleArgs({
|
|
58816
|
-
sources: sourceRels.map((s) => ({ path:
|
|
58840
|
+
sources: sourceRels.map((s) => ({ path: join28(projectDir, s.rel), kind: s.kind })),
|
|
58817
58841
|
plan,
|
|
58818
58842
|
narrationPaths: new Map(
|
|
58819
|
-
[...narrationRels.entries()].map(([i, rel]) => [i,
|
|
58843
|
+
[...narrationRels.entries()].map(([i, rel]) => [i, join28(projectDir, rel)])
|
|
58820
58844
|
),
|
|
58821
|
-
musicPath: music.rel ? isAbsolute5(music.rel) ? music.rel :
|
|
58845
|
+
musicPath: music.rel ? isAbsolute5(music.rel) ? music.rel : join28(projectDir, music.rel) : void 0,
|
|
58822
58846
|
outputPath,
|
|
58823
58847
|
musicVolume,
|
|
58824
58848
|
finish: opts.finish
|
|
@@ -58835,7 +58859,7 @@ async function executeFootageAssemble(opts) {
|
|
|
58835
58859
|
suggestion: "Re-run with --dry-run --json to inspect the planned timeline."
|
|
58836
58860
|
};
|
|
58837
58861
|
}
|
|
58838
|
-
const reportPath =
|
|
58862
|
+
const reportPath = join28(projectDir, "assemble-report.json");
|
|
58839
58863
|
await writeFile41(reportPath, `${JSON.stringify(report, null, 2)}
|
|
58840
58864
|
`, "utf-8");
|
|
58841
58865
|
return { success: true, dryRun: false, outputPath, reportPath, report };
|
|
@@ -58843,15 +58867,15 @@ async function executeFootageAssemble(opts) {
|
|
|
58843
58867
|
function resolveMusicBed(projectDir, firstBeatId, flag) {
|
|
58844
58868
|
if (flag === "none") return { ok: true, source: "flag" };
|
|
58845
58869
|
if (flag) {
|
|
58846
|
-
const abs = isAbsolute5(flag) ? flag :
|
|
58847
|
-
if (!
|
|
58870
|
+
const abs = isAbsolute5(flag) ? flag : join28(projectDir, flag);
|
|
58871
|
+
if (!existsSync50(abs)) return { ok: false, error: `Music file not found: ${abs}` };
|
|
58848
58872
|
return { ok: true, rel: flag, source: "flag" };
|
|
58849
58873
|
}
|
|
58850
58874
|
const candidates = ["assets/music.mp3", "assets/music.wav"];
|
|
58851
58875
|
if (firstBeatId) {
|
|
58852
58876
|
candidates.push(`assets/music-${firstBeatId}.mp3`, `assets/music-${firstBeatId}.wav`);
|
|
58853
58877
|
}
|
|
58854
|
-
const rel = candidates.find((r) =>
|
|
58878
|
+
const rel = candidates.find((r) => existsSync50(join28(projectDir, r)));
|
|
58855
58879
|
return { ok: true, rel, source: "auto" };
|
|
58856
58880
|
}
|
|
58857
58881
|
var FOOTAGE_DEFAULTS, round3, round6, DEFAULT_FOOTAGE_OUTPUT;
|
|
@@ -59001,7 +59025,7 @@ var init_scene_audio_scan = __esm({
|
|
|
59001
59025
|
|
|
59002
59026
|
// src/commands/_shared/scene-audio-mux.ts
|
|
59003
59027
|
import { rename as rename4, unlink } from "node:fs/promises";
|
|
59004
|
-
import { resolve as resolve59, dirname as dirname19, extname as extname16, basename as
|
|
59028
|
+
import { resolve as resolve59, dirname as dirname19, extname as extname16, basename as basename20 } from "node:path";
|
|
59005
59029
|
function buildAudioMuxFilter(audios) {
|
|
59006
59030
|
if (audios.length === 0) return null;
|
|
59007
59031
|
const labels = [];
|
|
@@ -59063,7 +59087,7 @@ async function muxAudioIntoVideo(opts) {
|
|
|
59063
59087
|
const ext = extname16(opts.videoPath) || `.${opts.format}`;
|
|
59064
59088
|
const tmpPath = resolve59(
|
|
59065
59089
|
dirname19(opts.videoPath),
|
|
59066
|
-
`.${
|
|
59090
|
+
`.${basename20(opts.videoPath, ext)}.muxing${ext}`
|
|
59067
59091
|
);
|
|
59068
59092
|
const args = ["-y", "-loglevel", "error", "-i", opts.videoPath];
|
|
59069
59093
|
for (const a of opts.audios) {
|
|
@@ -59124,7 +59148,7 @@ var init_scene_audio_mux = __esm({
|
|
|
59124
59148
|
});
|
|
59125
59149
|
|
|
59126
59150
|
// src/commands/_shared/scene-assemble.ts
|
|
59127
|
-
import { existsSync as
|
|
59151
|
+
import { existsSync as existsSync51 } from "node:fs";
|
|
59128
59152
|
import { readFile as readFile43 } from "node:fs/promises";
|
|
59129
59153
|
import { resolve as resolve60, isAbsolute as isAbsolute6 } from "node:path";
|
|
59130
59154
|
async function executeSceneAssemble(opts) {
|
|
@@ -59132,7 +59156,7 @@ async function executeSceneAssemble(opts) {
|
|
|
59132
59156
|
const root = opts.root ?? "index.html";
|
|
59133
59157
|
const format = opts.format ?? "mp4";
|
|
59134
59158
|
const videoPath = isAbsolute6(opts.videoPath) ? opts.videoPath : resolve60(projectDir, opts.videoPath);
|
|
59135
|
-
if (!
|
|
59159
|
+
if (!existsSync51(videoPath)) {
|
|
59136
59160
|
return {
|
|
59137
59161
|
success: false,
|
|
59138
59162
|
audioCount: 0,
|
|
@@ -59142,7 +59166,7 @@ async function executeSceneAssemble(opts) {
|
|
|
59142
59166
|
};
|
|
59143
59167
|
}
|
|
59144
59168
|
const rootPath = resolve60(projectDir, root);
|
|
59145
|
-
if (!
|
|
59169
|
+
if (!existsSync51(rootPath)) {
|
|
59146
59170
|
return {
|
|
59147
59171
|
success: false,
|
|
59148
59172
|
audioCount: 0,
|
|
@@ -59209,9 +59233,9 @@ __export(scene_render_exports, {
|
|
|
59209
59233
|
qualityToCrf: () => qualityToCrf
|
|
59210
59234
|
});
|
|
59211
59235
|
import { execFile as execFile2 } from "node:child_process";
|
|
59212
|
-
import { existsSync as
|
|
59213
|
-
import { mkdir as
|
|
59214
|
-
import { resolve as resolve61, relative as relative9, dirname as dirname20, basename as
|
|
59236
|
+
import { existsSync as existsSync52 } from "node:fs";
|
|
59237
|
+
import { mkdir as mkdir27, readFile as readFile44, stat as stat8, writeFile as writeFile42 } from "node:fs/promises";
|
|
59238
|
+
import { resolve as resolve61, relative as relative9, dirname as dirname20, basename as basename21, join as join29, isAbsolute as isAbsolute7 } from "node:path";
|
|
59215
59239
|
import { promisify as promisify2 } from "node:util";
|
|
59216
59240
|
import {
|
|
59217
59241
|
createRenderJob,
|
|
@@ -59224,7 +59248,7 @@ function defaultOutputPath(opts) {
|
|
|
59224
59248
|
const fmt = opts.format ?? "mp4";
|
|
59225
59249
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
59226
59250
|
const stamp = now.toISOString().replace(/[:T]/g, "-").replace(/\..+$/, "");
|
|
59227
|
-
const name = (opts.projectName ??
|
|
59251
|
+
const name = (opts.projectName ?? basename21(resolve61(opts.projectDir))) || "scene";
|
|
59228
59252
|
const beat = opts.beatId ? `-${sanitizeFileSegment2(opts.beatId)}` : "";
|
|
59229
59253
|
return resolve61(opts.projectDir, "renders", `${name}${beat}-${stamp}.${fmt}`);
|
|
59230
59254
|
}
|
|
@@ -59303,7 +59327,7 @@ async function executeSceneRender(opts = {}) {
|
|
|
59303
59327
|
}
|
|
59304
59328
|
let autoSyncApplied = false;
|
|
59305
59329
|
let autoSyncWarning;
|
|
59306
|
-
if (!opts.beatId &&
|
|
59330
|
+
if (!opts.beatId && existsSync52(join29(projectDir, "STORYBOARD.md"))) {
|
|
59307
59331
|
try {
|
|
59308
59332
|
const rootPlan = await createProjectRootSyncPlan({ projectDir });
|
|
59309
59333
|
const subPlans = await createSubCompDurationSyncPlans({
|
|
@@ -59360,7 +59384,7 @@ async function executeSceneRender(opts = {}) {
|
|
|
59360
59384
|
}
|
|
59361
59385
|
const projectName = projectConfig.config.name;
|
|
59362
59386
|
const outputPath = opts.output ? resolve61(projectDir, opts.output) : defaultOutputPath({ projectDir, projectName, format: opts.format, beatId: opts.beatId });
|
|
59363
|
-
await
|
|
59387
|
+
await mkdir27(dirname20(outputPath), { recursive: true });
|
|
59364
59388
|
const config2 = buildRenderConfig({
|
|
59365
59389
|
fps: opts.fps,
|
|
59366
59390
|
quality: opts.quality,
|
|
@@ -59472,11 +59496,11 @@ async function safeStat(p) {
|
|
|
59472
59496
|
}
|
|
59473
59497
|
async function prepareBeatRenderRoot(opts) {
|
|
59474
59498
|
const root = `.vibeframe/tmp/render-beat-${sanitizeFileSegment2(opts.beatId)}.html`;
|
|
59475
|
-
const reportPath =
|
|
59499
|
+
const reportPath = join29(opts.projectDir, "build-report.json");
|
|
59476
59500
|
const retryWith = [
|
|
59477
59501
|
`vibe build ${opts.projectDir} --beat ${opts.beatId} --stage sync --json`
|
|
59478
59502
|
];
|
|
59479
|
-
if (!
|
|
59503
|
+
if (!existsSync52(reportPath)) {
|
|
59480
59504
|
return {
|
|
59481
59505
|
success: false,
|
|
59482
59506
|
root,
|
|
@@ -59556,8 +59580,8 @@ async function prepareBeatRenderRoot(opts) {
|
|
|
59556
59580
|
durationSec,
|
|
59557
59581
|
narrationPath
|
|
59558
59582
|
});
|
|
59559
|
-
await
|
|
59560
|
-
await writeFile42(
|
|
59583
|
+
await mkdir27(dirname20(join29(opts.projectDir, root)), { recursive: true });
|
|
59584
|
+
await writeFile42(join29(opts.projectDir, root), html, "utf-8");
|
|
59561
59585
|
return {
|
|
59562
59586
|
success: true,
|
|
59563
59587
|
root,
|
|
@@ -59621,7 +59645,7 @@ function reportedBeatAssetPaths(beat) {
|
|
|
59621
59645
|
]);
|
|
59622
59646
|
}
|
|
59623
59647
|
async function writeRenderReport(projectDir, result) {
|
|
59624
|
-
const reportPath =
|
|
59648
|
+
const reportPath = join29(projectDir, "render-report.json");
|
|
59625
59649
|
try {
|
|
59626
59650
|
await writeFile42(
|
|
59627
59651
|
reportPath,
|
|
@@ -59663,7 +59687,7 @@ async function writeRenderReport(projectDir, result) {
|
|
|
59663
59687
|
}
|
|
59664
59688
|
function pathExists3(projectDir, value) {
|
|
59665
59689
|
const abs = isAbsolute7(value) ? value : resolve61(projectDir, value);
|
|
59666
|
-
return
|
|
59690
|
+
return existsSync52(abs);
|
|
59667
59691
|
}
|
|
59668
59692
|
function stringValue(value) {
|
|
59669
59693
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -59719,12 +59743,12 @@ __export(scene_build_exports, {
|
|
|
59719
59743
|
isLikenessRejection: () => isLikenessRejection,
|
|
59720
59744
|
resolveSceneBuildMode: () => resolveSceneBuildMode
|
|
59721
59745
|
});
|
|
59722
|
-
import { existsSync as
|
|
59723
|
-
import { copyFile as
|
|
59724
|
-
import { dirname as dirname21, join as
|
|
59746
|
+
import { existsSync as existsSync53, readFileSync as readFileSync2 } from "node:fs";
|
|
59747
|
+
import { copyFile as copyFile6, mkdir as mkdir28, readFile as readFile45, rm as rm8, writeFile as writeFile43 } from "node:fs/promises";
|
|
59748
|
+
import { dirname as dirname21, join as join30, resolve as resolve62 } from "node:path";
|
|
59725
59749
|
function readProjectKindSync(projectDir) {
|
|
59726
59750
|
try {
|
|
59727
|
-
const raw = readFileSync2(
|
|
59751
|
+
const raw = readFileSync2(join30(projectDir, "vibe.config.json"), "utf-8");
|
|
59728
59752
|
const k = JSON.parse(raw).kind;
|
|
59729
59753
|
return typeof k === "string" && isSceneKind(k) ? k : "cinema";
|
|
59730
59754
|
} catch {
|
|
@@ -59756,7 +59780,7 @@ async function executeSceneBuild(opts) {
|
|
|
59756
59780
|
);
|
|
59757
59781
|
}
|
|
59758
59782
|
let sceneRepair = skippedSceneRepairSummary();
|
|
59759
|
-
const storyboardPath2 =
|
|
59783
|
+
const storyboardPath2 = join30(projectDir, "STORYBOARD.md");
|
|
59760
59784
|
const buildPlan = await createBuildPlan({
|
|
59761
59785
|
projectDir,
|
|
59762
59786
|
stage: selectedStage,
|
|
@@ -59828,7 +59852,7 @@ async function executeSceneBuild(opts) {
|
|
|
59828
59852
|
}
|
|
59829
59853
|
if (!buildPlan.validation.ok) {
|
|
59830
59854
|
let invalidBeats = [];
|
|
59831
|
-
if (
|
|
59855
|
+
if (existsSync53(storyboardPath2)) {
|
|
59832
59856
|
invalidBeats = parseStoryboard(await loadStoryboardMarkdown(projectDir)).beats;
|
|
59833
59857
|
}
|
|
59834
59858
|
return finishBuildResult({
|
|
@@ -59853,7 +59877,7 @@ async function executeSceneBuild(opts) {
|
|
|
59853
59877
|
totalLatencyMs: Date.now() - startedAt
|
|
59854
59878
|
});
|
|
59855
59879
|
}
|
|
59856
|
-
if (!
|
|
59880
|
+
if (!existsSync53(storyboardPath2)) {
|
|
59857
59881
|
return finishBuildResult({
|
|
59858
59882
|
success: false,
|
|
59859
59883
|
phase: "failed",
|
|
@@ -60089,9 +60113,9 @@ async function executeSceneBuild(opts) {
|
|
|
60089
60113
|
}
|
|
60090
60114
|
if (shouldRunStage(selectedStage, "compose")) {
|
|
60091
60115
|
if (mode === "agent") {
|
|
60092
|
-
const compositionsDir =
|
|
60116
|
+
const compositionsDir = join30(projectDir, "compositions");
|
|
60093
60117
|
const missingBeats = activeBeats.filter(
|
|
60094
|
-
(b) => !
|
|
60118
|
+
(b) => !existsSync53(join30(compositionsDir, `scene-${b.id}.html`))
|
|
60095
60119
|
);
|
|
60096
60120
|
if (missingBeats.length > 0) {
|
|
60097
60121
|
const plan = await getComposePrompts({ projectDir, beatId: opts.beatId });
|
|
@@ -60166,8 +60190,8 @@ async function executeSceneBuild(opts) {
|
|
|
60166
60190
|
if (stageReports.compose.status === "done") {
|
|
60167
60191
|
for (const beat of composer === "template" ? [] : activeBeats) {
|
|
60168
60192
|
const videoRel = `assets/video-${beat.id}.mp4`;
|
|
60169
|
-
const compositionAbs =
|
|
60170
|
-
if (!
|
|
60193
|
+
const compositionAbs = join30(projectDir, "compositions", `scene-${beat.id}.html`);
|
|
60194
|
+
if (!existsSync53(join30(projectDir, videoRel)) || !existsSync53(compositionAbs)) continue;
|
|
60171
60195
|
const html = readFileSync2(compositionAbs, "utf-8");
|
|
60172
60196
|
if (!html.includes(videoRel)) {
|
|
60173
60197
|
warnings.push(
|
|
@@ -60223,7 +60247,7 @@ async function executeSceneBuild(opts) {
|
|
|
60223
60247
|
});
|
|
60224
60248
|
}
|
|
60225
60249
|
if (composer !== "footage" && shouldRunStage(selectedStage, "sync")) {
|
|
60226
|
-
if (!
|
|
60250
|
+
if (!existsSync53(join30(projectDir, "index.html"))) {
|
|
60227
60251
|
await scaffoldSceneProject({
|
|
60228
60252
|
dir: projectDir,
|
|
60229
60253
|
name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
|
|
@@ -60525,7 +60549,7 @@ async function dispatchNarration(beat, ctx) {
|
|
|
60525
60549
|
return {
|
|
60526
60550
|
status: "cached",
|
|
60527
60551
|
path: existingRel,
|
|
60528
|
-
durationSec: await safeAudioDuration(
|
|
60552
|
+
durationSec: await safeAudioDuration(join30(ctx.projectDir, existingRel)),
|
|
60529
60553
|
provider: metadata?.provider,
|
|
60530
60554
|
cachePath: metadata?.cachePath,
|
|
60531
60555
|
cacheKey: metadata?.cacheKey,
|
|
@@ -60549,9 +60573,9 @@ async function dispatchNarration(beat, ctx) {
|
|
|
60549
60573
|
voice,
|
|
60550
60574
|
ext: resolution.audioExtension
|
|
60551
60575
|
});
|
|
60552
|
-
const cacheAbs =
|
|
60576
|
+
const cacheAbs = join30(ctx.projectDir, cache.path);
|
|
60553
60577
|
const rel = `assets/narration-${beat.id}.${resolution.audioExtension}`;
|
|
60554
|
-
const abs =
|
|
60578
|
+
const abs = join30(ctx.projectDir, rel);
|
|
60555
60579
|
if (existingRel && !ctx.force && isFreshCanonicalAsset({
|
|
60556
60580
|
projectDir: ctx.projectDir,
|
|
60557
60581
|
kind: "narration",
|
|
@@ -60565,7 +60589,7 @@ async function dispatchNarration(beat, ctx) {
|
|
|
60565
60589
|
return {
|
|
60566
60590
|
status: "cached",
|
|
60567
60591
|
path: existingRel,
|
|
60568
|
-
durationSec: await safeAudioDuration(
|
|
60592
|
+
durationSec: await safeAudioDuration(join30(ctx.projectDir, existingRel)),
|
|
60569
60593
|
provider: resolution.provider,
|
|
60570
60594
|
cachePath: cache.path,
|
|
60571
60595
|
cacheKey: cache.key,
|
|
@@ -60573,9 +60597,9 @@ async function dispatchNarration(beat, ctx) {
|
|
|
60573
60597
|
freshness: "fresh"
|
|
60574
60598
|
};
|
|
60575
60599
|
}
|
|
60576
|
-
if (
|
|
60577
|
-
await
|
|
60578
|
-
await
|
|
60600
|
+
if (existsSync53(cacheAbs) && !ctx.force) {
|
|
60601
|
+
await mkdir28(dirname21(abs), { recursive: true });
|
|
60602
|
+
await copyFile6(cacheAbs, abs);
|
|
60579
60603
|
await writeAssetMetadata({
|
|
60580
60604
|
projectDir: ctx.projectDir,
|
|
60581
60605
|
kind: "narration",
|
|
@@ -60605,13 +60629,13 @@ async function dispatchNarration(beat, ctx) {
|
|
|
60605
60629
|
ctx.onProgress({ type: "narration-failed", beatId: beat.id, error });
|
|
60606
60630
|
return { status: "failed", error };
|
|
60607
60631
|
}
|
|
60608
|
-
await
|
|
60632
|
+
await mkdir28(dirname21(abs), { recursive: true });
|
|
60609
60633
|
await writeFile43(abs, result.audioBuffer);
|
|
60610
60634
|
for (const otherExt of ["mp3", "wav"]) {
|
|
60611
|
-
const sibling =
|
|
60612
|
-
if (sibling !== abs &&
|
|
60635
|
+
const sibling = join30(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
|
|
60636
|
+
if (sibling !== abs && existsSync53(sibling)) await rm8(sibling, { force: true });
|
|
60613
60637
|
}
|
|
60614
|
-
await
|
|
60638
|
+
await mkdir28(dirname21(cacheAbs), { recursive: true });
|
|
60615
60639
|
await writeFile43(cacheAbs, result.audioBuffer);
|
|
60616
60640
|
await writeAssetMetadata({
|
|
60617
60641
|
projectDir: ctx.projectDir,
|
|
@@ -60644,8 +60668,8 @@ async function dispatchNarration(beat, ctx) {
|
|
|
60644
60668
|
async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
|
|
60645
60669
|
if (!narrationPath) return "skipped";
|
|
60646
60670
|
const relPath = beatTranscriptRelPath(beat.id);
|
|
60647
|
-
const abs =
|
|
60648
|
-
if (
|
|
60671
|
+
const abs = join30(ctx.projectDir, relPath);
|
|
60672
|
+
if (existsSync53(abs) && !ctx.force && !narrationFresh) {
|
|
60649
60673
|
ctx.onProgress({ type: "transcript-cached", beatId: beat.id, path: relPath });
|
|
60650
60674
|
return "cached";
|
|
60651
60675
|
}
|
|
@@ -60658,7 +60682,7 @@ async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
|
|
|
60658
60682
|
});
|
|
60659
60683
|
return "skipped";
|
|
60660
60684
|
}
|
|
60661
|
-
const words = await transcribeNarrationWords(
|
|
60685
|
+
const words = await transcribeNarrationWords(join30(ctx.projectDir, narrationPath), { apiKey });
|
|
60662
60686
|
if (words.length === 0) {
|
|
60663
60687
|
ctx.onProgress({
|
|
60664
60688
|
type: "transcript-skipped",
|
|
@@ -60668,7 +60692,7 @@ async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
|
|
|
60668
60692
|
return "skipped";
|
|
60669
60693
|
}
|
|
60670
60694
|
try {
|
|
60671
|
-
await
|
|
60695
|
+
await mkdir28(dirname21(abs), { recursive: true });
|
|
60672
60696
|
await writeFile43(abs, JSON.stringify(words, null, 2), "utf-8");
|
|
60673
60697
|
} catch (err) {
|
|
60674
60698
|
ctx.onProgress({
|
|
@@ -60716,7 +60740,7 @@ async function buildCharacters(characters, ctx) {
|
|
|
60716
60740
|
const size = ctx.imageSize ?? "1536x1024";
|
|
60717
60741
|
for (const character of characters) {
|
|
60718
60742
|
if (character.imagePath) {
|
|
60719
|
-
if (
|
|
60743
|
+
if (existsSync53(join30(ctx.projectDir, character.imagePath))) {
|
|
60720
60744
|
paths.set(character.name, character.imagePath);
|
|
60721
60745
|
ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
|
|
60722
60746
|
} else {
|
|
@@ -60729,7 +60753,7 @@ async function buildCharacters(characters, ctx) {
|
|
|
60729
60753
|
if (!character.prompt) continue;
|
|
60730
60754
|
const prompt3 = characterSheetPrompt(character.name, character.prompt);
|
|
60731
60755
|
const rel = `assets/character-${character.name}.png`;
|
|
60732
|
-
const abs =
|
|
60756
|
+
const abs = join30(ctx.projectDir, rel);
|
|
60733
60757
|
const metadataOptions = { quality: ctx.imageQuality, size };
|
|
60734
60758
|
const cache = characterCacheDescriptor({
|
|
60735
60759
|
name: character.name,
|
|
@@ -60739,7 +60763,7 @@ async function buildCharacters(characters, ctx) {
|
|
|
60739
60763
|
size,
|
|
60740
60764
|
model: ctx.imageModel
|
|
60741
60765
|
});
|
|
60742
|
-
if (
|
|
60766
|
+
if (existsSync53(abs) && !ctx.force) {
|
|
60743
60767
|
const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
|
|
60744
60768
|
if (!metadata || isFreshCanonicalAsset({
|
|
60745
60769
|
projectDir: ctx.projectDir,
|
|
@@ -60755,10 +60779,10 @@ async function buildCharacters(characters, ctx) {
|
|
|
60755
60779
|
continue;
|
|
60756
60780
|
}
|
|
60757
60781
|
}
|
|
60758
|
-
const cacheAbs =
|
|
60759
|
-
if (
|
|
60760
|
-
await
|
|
60761
|
-
await
|
|
60782
|
+
const cacheAbs = join30(ctx.projectDir, cache.path);
|
|
60783
|
+
if (existsSync53(cacheAbs) && !ctx.force) {
|
|
60784
|
+
await mkdir28(dirname21(abs), { recursive: true });
|
|
60785
|
+
await copyFile6(cacheAbs, abs);
|
|
60762
60786
|
await writeAssetMetadata({
|
|
60763
60787
|
projectDir: ctx.projectDir,
|
|
60764
60788
|
kind: "character",
|
|
@@ -60790,9 +60814,9 @@ async function buildCharacters(characters, ctx) {
|
|
|
60790
60814
|
ctx.onProgress({ type: "character-failed", name: character.name, error: generated.error });
|
|
60791
60815
|
continue;
|
|
60792
60816
|
}
|
|
60793
|
-
await
|
|
60817
|
+
await mkdir28(dirname21(abs), { recursive: true });
|
|
60794
60818
|
await writeImageFile(abs, generated.buffer);
|
|
60795
|
-
await
|
|
60819
|
+
await mkdir28(dirname21(cacheAbs), { recursive: true });
|
|
60796
60820
|
await writeImageFile(cacheAbs, generated.buffer);
|
|
60797
60821
|
await writeAssetMetadata({
|
|
60798
60822
|
projectDir: ctx.projectDir,
|
|
@@ -60823,7 +60847,7 @@ async function dispatchBackdrop(beat, ctx) {
|
|
|
60823
60847
|
if (!cue) return { status: "no-cue" };
|
|
60824
60848
|
const prompt3 = augmentBackdropPrompt(cue);
|
|
60825
60849
|
const rel = `assets/backdrop-${beat.id}.png`;
|
|
60826
|
-
const abs =
|
|
60850
|
+
const abs = join30(ctx.projectDir, rel);
|
|
60827
60851
|
const size = ctx.imageSize ?? "1536x1024";
|
|
60828
60852
|
const ratio = imageRatioForSize(size);
|
|
60829
60853
|
const metadataOptions = { quality: ctx.imageQuality, size, ratio };
|
|
@@ -60837,7 +60861,7 @@ async function dispatchBackdrop(beat, ctx) {
|
|
|
60837
60861
|
model: ctx.imageModel
|
|
60838
60862
|
});
|
|
60839
60863
|
const metadataPath = assetMetadataPath("backdrop", beat.id);
|
|
60840
|
-
if (
|
|
60864
|
+
if (existsSync53(abs) && !ctx.force) {
|
|
60841
60865
|
const metadata = readAssetMetadata(ctx.projectDir, "backdrop", beat.id);
|
|
60842
60866
|
if (!metadata || isFreshCanonicalAsset({
|
|
60843
60867
|
projectDir: ctx.projectDir,
|
|
@@ -60860,10 +60884,10 @@ async function dispatchBackdrop(beat, ctx) {
|
|
|
60860
60884
|
};
|
|
60861
60885
|
}
|
|
60862
60886
|
}
|
|
60863
|
-
const cacheAbs =
|
|
60864
|
-
if (
|
|
60865
|
-
await
|
|
60866
|
-
await
|
|
60887
|
+
const cacheAbs = join30(ctx.projectDir, cache.path);
|
|
60888
|
+
if (existsSync53(cacheAbs) && !ctx.force) {
|
|
60889
|
+
await mkdir28(dirname21(abs), { recursive: true });
|
|
60890
|
+
await copyFile6(cacheAbs, abs);
|
|
60867
60891
|
await writeAssetMetadata({
|
|
60868
60892
|
projectDir: ctx.projectDir,
|
|
60869
60893
|
kind: "backdrop",
|
|
@@ -60892,10 +60916,10 @@ async function dispatchBackdrop(beat, ctx) {
|
|
|
60892
60916
|
ctx.onProgress({ type: "backdrop-failed", beatId: beat.id, error });
|
|
60893
60917
|
return { status: "failed", error };
|
|
60894
60918
|
}
|
|
60895
|
-
await
|
|
60919
|
+
await mkdir28(dirname21(abs), { recursive: true });
|
|
60896
60920
|
const buffer = generated.buffer;
|
|
60897
60921
|
await writeImageFile(abs, buffer);
|
|
60898
|
-
await
|
|
60922
|
+
await mkdir28(dirname21(cacheAbs), { recursive: true });
|
|
60899
60923
|
await writeImageFile(cacheAbs, buffer);
|
|
60900
60924
|
await writeAssetMetadata({
|
|
60901
60925
|
projectDir: ctx.projectDir,
|
|
@@ -60958,7 +60982,7 @@ async function buildImage(prompt3, ctx, ratio, sheetBuffers) {
|
|
|
60958
60982
|
}
|
|
60959
60983
|
async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
|
|
60960
60984
|
const rel = `assets/keyframe-${beat.id}.png`;
|
|
60961
|
-
const abs =
|
|
60985
|
+
const abs = join30(ctx.projectDir, rel);
|
|
60962
60986
|
const size = ctx.imageSize ?? "1536x1024";
|
|
60963
60987
|
const ratio = imageRatioForSize(size);
|
|
60964
60988
|
const metadataOptions = { quality: ctx.imageQuality, size, ratio };
|
|
@@ -60972,7 +60996,7 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
|
|
|
60972
60996
|
characters: characterRefsRel,
|
|
60973
60997
|
model: ctx.imageModel
|
|
60974
60998
|
});
|
|
60975
|
-
if (
|
|
60999
|
+
if (existsSync53(abs) && !ctx.force) {
|
|
60976
61000
|
const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
|
|
60977
61001
|
if (!metadata || isFreshCanonicalAsset({
|
|
60978
61002
|
projectDir: ctx.projectDir,
|
|
@@ -60986,10 +61010,10 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
|
|
|
60986
61010
|
return { status: "cached", rel };
|
|
60987
61011
|
}
|
|
60988
61012
|
}
|
|
60989
|
-
const cacheAbs =
|
|
60990
|
-
if (
|
|
60991
|
-
await
|
|
60992
|
-
await
|
|
61013
|
+
const cacheAbs = join30(ctx.projectDir, cache.path);
|
|
61014
|
+
if (existsSync53(cacheAbs) && !ctx.force) {
|
|
61015
|
+
await mkdir28(dirname21(abs), { recursive: true });
|
|
61016
|
+
await copyFile6(cacheAbs, abs);
|
|
60993
61017
|
await writeAssetMetadata({
|
|
60994
61018
|
projectDir: ctx.projectDir,
|
|
60995
61019
|
kind: "keyframe",
|
|
@@ -61005,8 +61029,8 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
|
|
|
61005
61029
|
}
|
|
61006
61030
|
const sheetBuffers = [];
|
|
61007
61031
|
for (const relPath of characterRefsRel) {
|
|
61008
|
-
const sheetAbs =
|
|
61009
|
-
if (
|
|
61032
|
+
const sheetAbs = join30(ctx.projectDir, relPath);
|
|
61033
|
+
if (existsSync53(sheetAbs)) sheetBuffers.push(await readFile45(sheetAbs));
|
|
61010
61034
|
}
|
|
61011
61035
|
const imgCtx = {
|
|
61012
61036
|
projectDir: ctx.projectDir,
|
|
@@ -61018,9 +61042,9 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
|
|
|
61018
61042
|
const identityLock = "Keep the EXACT same person and face as the reference image(s): identical facial features (eyes, nose shape, jawline), skin tone and complexion, hairstyle, hair color, and wardrobe. Do not restyle or change the face. Change only the scene, pose, framing, and lighting. ";
|
|
61019
61043
|
const generated = sheetBuffers.length > 0 ? await editKeyframeImage(`${identityLock}${keyframeCue}`, sheetBuffers, imgCtx, ratio) : await generateBackdropImage(keyframeCue, imgCtx, ratio);
|
|
61020
61044
|
if (!generated.success) return { status: "failed", error: generated.error };
|
|
61021
|
-
await
|
|
61045
|
+
await mkdir28(dirname21(abs), { recursive: true });
|
|
61022
61046
|
await writeImageFile(abs, generated.buffer);
|
|
61023
|
-
await
|
|
61047
|
+
await mkdir28(dirname21(cacheAbs), { recursive: true });
|
|
61024
61048
|
await writeImageFile(cacheAbs, generated.buffer);
|
|
61025
61049
|
await writeAssetMetadata({
|
|
61026
61050
|
projectDir: ctx.projectDir,
|
|
@@ -61063,9 +61087,9 @@ async function dispatchVideo(beat, ctx, keyframe) {
|
|
|
61063
61087
|
const providerCue = stringOrUndefined4(beat.cues?.provider);
|
|
61064
61088
|
const requestedProvider = providerCue ? resolveBuildVideoProvider(providerCue) : ctx.videoProvider;
|
|
61065
61089
|
const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
|
|
61066
|
-
const characterRefsAbs = characterRefsRel.map((p) =>
|
|
61090
|
+
const characterRefsAbs = characterRefsRel.map((p) => join30(ctx.projectDir, p));
|
|
61067
61091
|
const rel = `assets/video-${beat.id}.mp4`;
|
|
61068
|
-
const abs =
|
|
61092
|
+
const abs = join30(ctx.projectDir, rel);
|
|
61069
61093
|
const cache = videoCacheDescriptor({
|
|
61070
61094
|
beatId: beat.id,
|
|
61071
61095
|
cue: prompt3,
|
|
@@ -61077,7 +61101,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
|
|
|
61077
61101
|
keyframe: keyframeCue
|
|
61078
61102
|
});
|
|
61079
61103
|
const metadataPath = assetMetadataPath("video", beat.id);
|
|
61080
|
-
if (
|
|
61104
|
+
if (existsSync53(abs) && !ctx.force) {
|
|
61081
61105
|
const metadata = readAssetMetadata(ctx.projectDir, "video", beat.id);
|
|
61082
61106
|
if (!metadata || isFreshCanonicalAsset({
|
|
61083
61107
|
projectDir: ctx.projectDir,
|
|
@@ -61100,10 +61124,10 @@ async function dispatchVideo(beat, ctx, keyframe) {
|
|
|
61100
61124
|
};
|
|
61101
61125
|
}
|
|
61102
61126
|
}
|
|
61103
|
-
const cacheAbs =
|
|
61104
|
-
if (
|
|
61105
|
-
await
|
|
61106
|
-
await
|
|
61127
|
+
const cacheAbs = join30(ctx.projectDir, cache.path);
|
|
61128
|
+
if (existsSync53(cacheAbs) && !ctx.force) {
|
|
61129
|
+
await mkdir28(dirname21(abs), { recursive: true });
|
|
61130
|
+
await copyFile6(cacheAbs, abs);
|
|
61107
61131
|
await writeAssetMetadata({
|
|
61108
61132
|
projectDir: ctx.projectDir,
|
|
61109
61133
|
kind: "video",
|
|
@@ -61132,7 +61156,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
|
|
|
61132
61156
|
ctx.onProgress({ type: "video-skipped", beatId: beat.id, reason });
|
|
61133
61157
|
return { status: "skipped" };
|
|
61134
61158
|
}
|
|
61135
|
-
const keyframeImageAbs = keyframeImageRel ?
|
|
61159
|
+
const keyframeImageAbs = keyframeImageRel ? join30(ctx.projectDir, keyframeImageRel) : void 0;
|
|
61136
61160
|
loadSceneBuildEnv(ctx.projectDir);
|
|
61137
61161
|
let job;
|
|
61138
61162
|
const recordJob = async (provider, submitted) => {
|
|
@@ -61210,10 +61234,10 @@ async function dispatchVideo(beat, ctx, keyframe) {
|
|
|
61210
61234
|
return { status: "failed", error: fallbackError };
|
|
61211
61235
|
}
|
|
61212
61236
|
}
|
|
61213
|
-
if (result.status === "completed" &&
|
|
61237
|
+
if (result.status === "completed" && existsSync53(abs)) {
|
|
61214
61238
|
await settleJob("completed");
|
|
61215
|
-
await
|
|
61216
|
-
await
|
|
61239
|
+
await mkdir28(dirname21(cacheAbs), { recursive: true });
|
|
61240
|
+
await copyFile6(abs, cacheAbs);
|
|
61217
61241
|
await writeAssetMetadata({
|
|
61218
61242
|
projectDir: ctx.projectDir,
|
|
61219
61243
|
kind: "video",
|
|
@@ -61262,7 +61286,7 @@ async function dispatchMusic(beat, ctx) {
|
|
|
61262
61286
|
const prompt3 = stringOrUndefined4(beat.cues?.music);
|
|
61263
61287
|
if (!prompt3) return { status: "no-cue" };
|
|
61264
61288
|
const rel = `assets/music-${beat.id}.mp3`;
|
|
61265
|
-
const abs =
|
|
61289
|
+
const abs = join30(ctx.projectDir, rel);
|
|
61266
61290
|
const duration = normalizeMusicDuration(beat.duration);
|
|
61267
61291
|
const cache = musicCacheDescriptor({
|
|
61268
61292
|
beatId: beat.id,
|
|
@@ -61271,7 +61295,7 @@ async function dispatchMusic(beat, ctx) {
|
|
|
61271
61295
|
duration
|
|
61272
61296
|
});
|
|
61273
61297
|
const metadataPath = assetMetadataPath("music", beat.id);
|
|
61274
|
-
if (
|
|
61298
|
+
if (existsSync53(abs) && !ctx.force) {
|
|
61275
61299
|
const metadata = readAssetMetadata(ctx.projectDir, "music", beat.id);
|
|
61276
61300
|
if (!metadata || isFreshCanonicalAsset({
|
|
61277
61301
|
projectDir: ctx.projectDir,
|
|
@@ -61295,10 +61319,10 @@ async function dispatchMusic(beat, ctx) {
|
|
|
61295
61319
|
};
|
|
61296
61320
|
}
|
|
61297
61321
|
}
|
|
61298
|
-
const cacheAbs =
|
|
61299
|
-
if (
|
|
61300
|
-
await
|
|
61301
|
-
await
|
|
61322
|
+
const cacheAbs = join30(ctx.projectDir, cache.path);
|
|
61323
|
+
if (existsSync53(cacheAbs) && !ctx.force) {
|
|
61324
|
+
await mkdir28(dirname21(abs), { recursive: true });
|
|
61325
|
+
await copyFile6(cacheAbs, abs);
|
|
61302
61326
|
await writeAssetMetadata({
|
|
61303
61327
|
projectDir: ctx.projectDir,
|
|
61304
61328
|
kind: "music",
|
|
@@ -61370,13 +61394,13 @@ async function dispatchMusic(beat, ctx) {
|
|
|
61370
61394
|
metadataPath
|
|
61371
61395
|
};
|
|
61372
61396
|
}
|
|
61373
|
-
if (!
|
|
61397
|
+
if (!existsSync53(abs)) {
|
|
61374
61398
|
const error = result.outputPath ? `music output was not written at ${abs}` : "music generation did not return an output file";
|
|
61375
61399
|
ctx.onProgress({ type: "music-failed", beatId: beat.id, error });
|
|
61376
61400
|
return { status: "failed", error };
|
|
61377
61401
|
}
|
|
61378
|
-
await
|
|
61379
|
-
await
|
|
61402
|
+
await mkdir28(dirname21(cacheAbs), { recursive: true });
|
|
61403
|
+
await copyFile6(abs, cacheAbs);
|
|
61380
61404
|
await writeAssetMetadata({
|
|
61381
61405
|
projectDir: ctx.projectDir,
|
|
61382
61406
|
kind: "music",
|
|
@@ -61406,12 +61430,12 @@ async function dispatchMusic(beat, ctx) {
|
|
|
61406
61430
|
};
|
|
61407
61431
|
}
|
|
61408
61432
|
function loadSceneBuildEnv(projectDir) {
|
|
61409
|
-
(0, import_dotenv4.config)({ path:
|
|
61433
|
+
(0, import_dotenv4.config)({ path: join30(projectDir, ".env"), quiet: true });
|
|
61410
61434
|
(0, import_dotenv4.config)({ path: resolve62(process.cwd(), ".env"), quiet: true });
|
|
61411
61435
|
let dir = process.cwd();
|
|
61412
61436
|
while (dir !== dirname21(dir)) {
|
|
61413
|
-
if (
|
|
61414
|
-
(0, import_dotenv4.config)({ path:
|
|
61437
|
+
if (existsSync53(join30(dir, "pnpm-workspace.yaml"))) {
|
|
61438
|
+
(0, import_dotenv4.config)({ path: join30(dir, ".env"), quiet: true });
|
|
61415
61439
|
return;
|
|
61416
61440
|
}
|
|
61417
61441
|
dir = dirname21(dir);
|
|
@@ -61628,7 +61652,7 @@ function mergeBeatOutcomes(base, updates) {
|
|
|
61628
61652
|
}
|
|
61629
61653
|
function firstExisting3(projectDir, relPaths) {
|
|
61630
61654
|
for (const rel of relPaths) {
|
|
61631
|
-
if (
|
|
61655
|
+
if (existsSync53(join30(projectDir, rel))) return rel;
|
|
61632
61656
|
}
|
|
61633
61657
|
return null;
|
|
61634
61658
|
}
|
|
@@ -61653,7 +61677,7 @@ async function apiKeyForVideoProvider(provider, projectDir) {
|
|
|
61653
61677
|
return getApiKeyFromConfig(videoProviderConfigKey(provider), { cwd: projectDir });
|
|
61654
61678
|
}
|
|
61655
61679
|
async function finalizeBuildResult(projectDir, startedAt, result, reportResult = result) {
|
|
61656
|
-
const reportPath =
|
|
61680
|
+
const reportPath = join30(projectDir, "build-report.json");
|
|
61657
61681
|
for (const source of /* @__PURE__ */ new Set([result, reportResult])) {
|
|
61658
61682
|
if (source.stageReports) {
|
|
61659
61683
|
for (const report of Object.values(source.stageReports)) {
|
|
@@ -61816,7 +61840,7 @@ function normalizeReportDuration(value) {
|
|
|
61816
61840
|
}
|
|
61817
61841
|
function buildReportComposition(projectDir, result, beatId) {
|
|
61818
61842
|
const path = `compositions/scene-${beatId}.html`;
|
|
61819
|
-
const exists =
|
|
61843
|
+
const exists = existsSync53(join30(projectDir, path));
|
|
61820
61844
|
if (result.stageReports?.compose.status === "failed") return { path, exists, status: "failed" };
|
|
61821
61845
|
if (result.phase === "needs-author" && !exists) return { path, exists, status: "needs-author" };
|
|
61822
61846
|
if (exists) return { path, exists, status: "exists" };
|
|
@@ -61863,8 +61887,8 @@ function summarizeBuildBeats(projectDir, beats) {
|
|
|
61863
61887
|
let compositionsReady = 0;
|
|
61864
61888
|
for (const beat of beats) {
|
|
61865
61889
|
if (beatAssetsReady(beat)) assetsReady += 1;
|
|
61866
|
-
const compositionPath =
|
|
61867
|
-
if (
|
|
61890
|
+
const compositionPath = join30(projectDir, "compositions", `scene-${beat.beatId}.html`);
|
|
61891
|
+
if (existsSync53(compositionPath)) {
|
|
61868
61892
|
compositionsReady += 1;
|
|
61869
61893
|
} else {
|
|
61870
61894
|
needsAuthor.push(beat.beatId);
|
|
@@ -61996,8 +62020,8 @@ var init_resolver = __esm({
|
|
|
61996
62020
|
|
|
61997
62021
|
// src/pipeline/executor.ts
|
|
61998
62022
|
import { resolve as resolve72 } from "node:path";
|
|
61999
|
-
import { readFile as readFile47, writeFile as writeFile44, mkdir as
|
|
62000
|
-
import { existsSync as
|
|
62023
|
+
import { readFile as readFile47, writeFile as writeFile44, mkdir as mkdir30 } from "node:fs/promises";
|
|
62024
|
+
import { existsSync as existsSync58 } from "node:fs";
|
|
62001
62025
|
function maxCostFor(action) {
|
|
62002
62026
|
const cmd = ACTION_METADATA[action]?.command;
|
|
62003
62027
|
if (!cmd) return 0;
|
|
@@ -62297,7 +62321,7 @@ async function ensureActionsRegistered() {
|
|
|
62297
62321
|
}
|
|
62298
62322
|
async function loadPipeline(filePath) {
|
|
62299
62323
|
const absPath = resolve72(process.cwd(), filePath);
|
|
62300
|
-
if (!
|
|
62324
|
+
if (!existsSync58(absPath)) {
|
|
62301
62325
|
throw new Error(`Pipeline file not found: ${absPath}`);
|
|
62302
62326
|
}
|
|
62303
62327
|
const content = await readFile47(absPath, "utf-8");
|
|
@@ -62316,13 +62340,13 @@ async function loadPipeline(filePath) {
|
|
|
62316
62340
|
async function executePipeline(manifest2, options = {}) {
|
|
62317
62341
|
await ensureActionsRegistered();
|
|
62318
62342
|
const outputDir = resolve72(process.cwd(), options.outputDir || `${manifest2.name}-output`);
|
|
62319
|
-
await
|
|
62343
|
+
await mkdir30(outputDir, { recursive: true });
|
|
62320
62344
|
const completedSteps = /* @__PURE__ */ new Map();
|
|
62321
62345
|
const results = [];
|
|
62322
62346
|
const startTime = Date.now();
|
|
62323
62347
|
if (options.resume) {
|
|
62324
62348
|
const checkpointPath = resolve72(outputDir, CHECKPOINT_FILE);
|
|
62325
|
-
if (
|
|
62349
|
+
if (existsSync58(checkpointPath)) {
|
|
62326
62350
|
const checkpointContent = await readFile47(checkpointPath, "utf-8");
|
|
62327
62351
|
const checkpoint = (0, import_yaml8.parse)(checkpointContent);
|
|
62328
62352
|
for (const cs of checkpoint.completedSteps) {
|
|
@@ -66713,8 +66737,9 @@ async function runWithMcpPromotion(work, opts) {
|
|
|
66713
66737
|
let lastRecordWriteAt = 0;
|
|
66714
66738
|
let heartbeat = null;
|
|
66715
66739
|
let writes = Promise.resolve();
|
|
66740
|
+
let finished = false;
|
|
66716
66741
|
const writeRecord = (patch) => {
|
|
66717
|
-
if (!record) return writes;
|
|
66742
|
+
if (!record || finished) return writes;
|
|
66718
66743
|
record = {
|
|
66719
66744
|
...record,
|
|
66720
66745
|
...patch,
|
|
@@ -66777,8 +66802,14 @@ async function runWithMcpPromotion(work, opts) {
|
|
|
66777
66802
|
void writeRecord({ heartbeatAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
66778
66803
|
}, opts.heartbeatMs ?? DEFAULT_HEARTBEAT_MS);
|
|
66779
66804
|
heartbeat.unref?.();
|
|
66805
|
+
const finish = (patch) => {
|
|
66806
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
66807
|
+
const terminal2 = writeRecord(patch);
|
|
66808
|
+
finished = true;
|
|
66809
|
+
return terminal2;
|
|
66810
|
+
};
|
|
66780
66811
|
void workPromise.then(
|
|
66781
|
-
(result) =>
|
|
66812
|
+
(result) => finish(
|
|
66782
66813
|
result.success ? {
|
|
66783
66814
|
status: "completed",
|
|
66784
66815
|
progress: 100,
|
|
@@ -66790,13 +66821,11 @@ async function runWithMcpPromotion(work, opts) {
|
|
|
66790
66821
|
resultPayload: result.data
|
|
66791
66822
|
}
|
|
66792
66823
|
),
|
|
66793
|
-
(error) =>
|
|
66824
|
+
(error) => finish({
|
|
66794
66825
|
status: "failed",
|
|
66795
66826
|
error: error instanceof Error ? error.message : String(error)
|
|
66796
66827
|
})
|
|
66797
|
-
)
|
|
66798
|
-
if (heartbeat) clearInterval(heartbeat);
|
|
66799
|
-
});
|
|
66828
|
+
);
|
|
66800
66829
|
return {
|
|
66801
66830
|
success: true,
|
|
66802
66831
|
data: {
|
|
@@ -68833,12 +68862,12 @@ var init_generate2 = __esm({
|
|
|
68833
68862
|
});
|
|
68834
68863
|
|
|
68835
68864
|
// src/tools/manifest/storyboard.ts
|
|
68836
|
-
import { existsSync as
|
|
68865
|
+
import { existsSync as existsSync59 } from "node:fs";
|
|
68837
68866
|
import { readFile as readFile49, writeFile as writeFile46 } from "node:fs/promises";
|
|
68838
68867
|
import { resolve as resolve76 } from "node:path";
|
|
68839
68868
|
async function readStoryboard2(projectDir) {
|
|
68840
68869
|
const path = resolve76(projectDir, "STORYBOARD.md");
|
|
68841
|
-
if (!
|
|
68870
|
+
if (!existsSync59(path)) return null;
|
|
68842
68871
|
return { path, markdown: await readFile49(path, "utf-8") };
|
|
68843
68872
|
}
|
|
68844
68873
|
var PROJECT_DIR_DESCRIPTION3, projectDirSchema, storyboardListTool, storyboardValidateTool, storyboardGetTool, storyboardSetTool, storyboardMoveTool, storyboardReviseTool, planTool, storyboardTools;
|
|
@@ -69113,9 +69142,9 @@ var init_storyboard2 = __esm({
|
|
|
69113
69142
|
});
|
|
69114
69143
|
|
|
69115
69144
|
// src/tools/manifest/design.ts
|
|
69116
|
-
import { existsSync as
|
|
69145
|
+
import { existsSync as existsSync60 } from "node:fs";
|
|
69117
69146
|
import { readFile as readFile50 } from "node:fs/promises";
|
|
69118
|
-
import { resolve as resolve77, join as
|
|
69147
|
+
import { resolve as resolve77, join as join32 } from "node:path";
|
|
69119
69148
|
var PROJECT_DIR_DESCRIPTION4, projectDirSchema2, designValidateTool, designTools;
|
|
69120
69149
|
var init_design = __esm({
|
|
69121
69150
|
"src/tools/manifest/design.ts"() {
|
|
@@ -69137,8 +69166,8 @@ var init_design = __esm({
|
|
|
69137
69166
|
schema: projectDirSchema2,
|
|
69138
69167
|
async execute(args, ctx) {
|
|
69139
69168
|
const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
|
|
69140
|
-
const path =
|
|
69141
|
-
if (!
|
|
69169
|
+
const path = join32(projectDir, "DESIGN.md");
|
|
69170
|
+
if (!existsSync60(path)) return { success: false, error: `DESIGN.md not found in ${projectDir}` };
|
|
69142
69171
|
const md = await readFile50(path, "utf-8");
|
|
69143
69172
|
const issues = validateDesignMarkdown(md);
|
|
69144
69173
|
const design = parseDesign(md);
|
|
@@ -69164,7 +69193,7 @@ var init_design = __esm({
|
|
|
69164
69193
|
// src/tools/manifest/remix.ts
|
|
69165
69194
|
import { writeFile as writeFile47 } from "node:fs/promises";
|
|
69166
69195
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
69167
|
-
import { join as
|
|
69196
|
+
import { join as join33 } from "node:path";
|
|
69168
69197
|
var pipelineHighlightsTool, pipelineAutoShortsTool, pipelineRunTool, remixTools;
|
|
69169
69198
|
var init_remix = __esm({
|
|
69170
69199
|
"src/tools/manifest/remix.ts"() {
|
|
@@ -69277,7 +69306,7 @@ var init_remix = __esm({
|
|
|
69277
69306
|
let resolvedPath = args.pipelinePath;
|
|
69278
69307
|
let tempPath;
|
|
69279
69308
|
if (args.pipelineYaml) {
|
|
69280
|
-
tempPath =
|
|
69309
|
+
tempPath = join33(tmpdir5(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
|
|
69281
69310
|
await writeFile47(tempPath, args.pipelineYaml, "utf-8");
|
|
69282
69311
|
resolvedPath = tempPath;
|
|
69283
69312
|
}
|
|
@@ -69442,8 +69471,8 @@ var init_project_io = __esm({
|
|
|
69442
69471
|
});
|
|
69443
69472
|
|
|
69444
69473
|
// src/tools/manifest/timeline.ts
|
|
69445
|
-
import { mkdir as
|
|
69446
|
-
import { basename as
|
|
69474
|
+
import { mkdir as mkdir31, writeFile as writeFile49 } from "node:fs/promises";
|
|
69475
|
+
import { basename as basename22, resolve as resolve78 } from "node:path";
|
|
69447
69476
|
var MEDIA_TYPES, timelineCreateTool, timelineInfoTool, timelineAddSourceTool, timelineAddClipTool, timelineSplitClipTool, timelineTrimClipTool, timelineMoveClipTool, timelineDeleteClipTool, timelineDuplicateClipTool, timelineAddEffectTool, timelineAddTrackTool, timelineListTool, timelineTools;
|
|
69448
69477
|
var init_timeline = __esm({
|
|
69449
69478
|
"src/tools/manifest/timeline.ts"() {
|
|
@@ -69481,7 +69510,7 @@ var init_timeline = __esm({
|
|
|
69481
69510
|
fps: external_exports.number().optional().describe("Frames per second (default: 30)")
|
|
69482
69511
|
}),
|
|
69483
69512
|
async execute(args, ctx) {
|
|
69484
|
-
const projectName = args.name === "." ?
|
|
69513
|
+
const projectName = args.name === "." ? basename22(ctx.workingDirectory) : basename22(resolve78(ctx.workingDirectory, args.name));
|
|
69485
69514
|
const project = new Project(projectName);
|
|
69486
69515
|
if (args.fps) project.setFrameRate(args.fps);
|
|
69487
69516
|
let outputPath;
|
|
@@ -69489,7 +69518,7 @@ var init_timeline = __esm({
|
|
|
69489
69518
|
outputPath = resolve78(ctx.workingDirectory, args.outputPath);
|
|
69490
69519
|
} else {
|
|
69491
69520
|
const dirPath = resolve78(ctx.workingDirectory, args.name);
|
|
69492
|
-
await
|
|
69521
|
+
await mkdir31(dirPath, { recursive: true });
|
|
69493
69522
|
outputPath = resolve78(dirPath, TIMELINE_FILENAME);
|
|
69494
69523
|
}
|
|
69495
69524
|
await writeFile49(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
@@ -69791,11 +69820,11 @@ var init_timeline = __esm({
|
|
|
69791
69820
|
|
|
69792
69821
|
// src/commands/_shared/project-list.ts
|
|
69793
69822
|
import { readdir as readdir7, readFile as readFile52, stat as stat9 } from "node:fs/promises";
|
|
69794
|
-
import { existsSync as
|
|
69795
|
-
import { join as
|
|
69823
|
+
import { existsSync as existsSync61 } from "node:fs";
|
|
69824
|
+
import { join as join34, resolve as resolve79 } from "node:path";
|
|
69796
69825
|
async function executeProjectList(opts = {}) {
|
|
69797
69826
|
const workspaceDir = resolve79(opts.workspaceDir ?? ".");
|
|
69798
|
-
if (!
|
|
69827
|
+
if (!existsSync61(workspaceDir)) {
|
|
69799
69828
|
return {
|
|
69800
69829
|
success: false,
|
|
69801
69830
|
workspaceDir,
|
|
@@ -69812,11 +69841,11 @@ async function executeProjectList(opts = {}) {
|
|
|
69812
69841
|
if (entry.name === "node_modules") continue;
|
|
69813
69842
|
if (entry.name.startsWith(".")) continue;
|
|
69814
69843
|
if (entry.name.startsWith("_")) {
|
|
69815
|
-
archivedCount += await countProjects(
|
|
69844
|
+
archivedCount += await countProjects(join34(workspaceDir, entry.name));
|
|
69816
69845
|
continue;
|
|
69817
69846
|
}
|
|
69818
|
-
const projectDir =
|
|
69819
|
-
if (!
|
|
69847
|
+
const projectDir = join34(workspaceDir, entry.name);
|
|
69848
|
+
if (!existsSync61(join34(projectDir, "STORYBOARD.md"))) continue;
|
|
69820
69849
|
projects.push(await describeProject(entry.name, projectDir));
|
|
69821
69850
|
}
|
|
69822
69851
|
projects.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
@@ -69826,7 +69855,7 @@ async function countProjects(dir) {
|
|
|
69826
69855
|
try {
|
|
69827
69856
|
const entries = await readdir7(dir, { withFileTypes: true });
|
|
69828
69857
|
return entries.filter(
|
|
69829
|
-
(e) => e.isDirectory() &&
|
|
69858
|
+
(e) => e.isDirectory() && existsSync61(join34(dir, e.name, "STORYBOARD.md"))
|
|
69830
69859
|
).length;
|
|
69831
69860
|
} catch {
|
|
69832
69861
|
return 0;
|
|
@@ -69837,7 +69866,7 @@ async function describeProject(name, projectDir) {
|
|
|
69837
69866
|
let storyboardDurationSec = 0;
|
|
69838
69867
|
const timestamps = [];
|
|
69839
69868
|
try {
|
|
69840
|
-
const storyboardPath2 =
|
|
69869
|
+
const storyboardPath2 = join34(projectDir, "STORYBOARD.md");
|
|
69841
69870
|
const parsed = parseStoryboard(await loadStoryboardMarkdown(projectDir));
|
|
69842
69871
|
beats = parsed.beats.length;
|
|
69843
69872
|
storyboardDurationSec = Number(
|
|
@@ -69849,7 +69878,7 @@ async function describeProject(name, projectDir) {
|
|
|
69849
69878
|
let buildStatus;
|
|
69850
69879
|
let buildUpdatedAt;
|
|
69851
69880
|
try {
|
|
69852
|
-
const reportPath =
|
|
69881
|
+
const reportPath = join34(projectDir, "build-report.json");
|
|
69853
69882
|
const report = JSON.parse(await readFile52(reportPath, "utf-8"));
|
|
69854
69883
|
buildStatus = report.status ?? report.phase;
|
|
69855
69884
|
buildUpdatedAt = report.updatedAt;
|
|
@@ -69858,12 +69887,12 @@ async function describeProject(name, projectDir) {
|
|
|
69858
69887
|
}
|
|
69859
69888
|
let latestRender;
|
|
69860
69889
|
try {
|
|
69861
|
-
const rendersDir =
|
|
69890
|
+
const rendersDir = join34(projectDir, "renders");
|
|
69862
69891
|
const files = await readdir7(rendersDir);
|
|
69863
69892
|
let newest;
|
|
69864
69893
|
for (const file of files) {
|
|
69865
69894
|
if (!/\.(mp4|webm|mov)$/i.test(file)) continue;
|
|
69866
|
-
const { mtimeMs } = await stat9(
|
|
69895
|
+
const { mtimeMs } = await stat9(join34(rendersDir, file));
|
|
69867
69896
|
if (!newest || mtimeMs > newest.mtimeMs) newest = { file, mtimeMs };
|
|
69868
69897
|
}
|
|
69869
69898
|
if (newest) {
|
|
@@ -70696,7 +70725,7 @@ var init_export2 = __esm({
|
|
|
70696
70725
|
|
|
70697
70726
|
// src/tools/manifest/agent-only.ts
|
|
70698
70727
|
import { readFile as readFile54, writeFile as writeFile51, readdir as readdir8, stat as stat10, access as access8, unlink as unlink2 } from "node:fs/promises";
|
|
70699
|
-
import { resolve as resolve83, join as
|
|
70728
|
+
import { resolve as resolve83, join as join35, basename as basename23, extname as extname17 } from "node:path";
|
|
70700
70729
|
function matchPattern(filename, pattern) {
|
|
70701
70730
|
const regex2 = new RegExp(
|
|
70702
70731
|
"^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
|
|
@@ -70781,7 +70810,7 @@ var init_agent_only = __esm({
|
|
|
70781
70810
|
const results = [];
|
|
70782
70811
|
for (const entry of entries) {
|
|
70783
70812
|
if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
|
|
70784
|
-
const fullPath =
|
|
70813
|
+
const fullPath = join35(absPath, entry.name);
|
|
70785
70814
|
const stats = await stat10(fullPath);
|
|
70786
70815
|
if (entry.isDirectory()) {
|
|
70787
70816
|
results.push(`[DIR] ${entry.name}/`);
|
|
@@ -70894,7 +70923,7 @@ var init_agent_only = __esm({
|
|
|
70894
70923
|
const scanDir = async (dir) => {
|
|
70895
70924
|
const entries = await readdir8(dir, { withFileTypes: true });
|
|
70896
70925
|
for (const entry of entries) {
|
|
70897
|
-
const entryPath =
|
|
70926
|
+
const entryPath = join35(dir, entry.name);
|
|
70898
70927
|
if (entry.isDirectory() && recursive) {
|
|
70899
70928
|
await scanDir(entryPath);
|
|
70900
70929
|
} else if (entry.isFile()) {
|
|
@@ -70911,7 +70940,7 @@ var init_agent_only = __esm({
|
|
|
70911
70940
|
mediaFiles.sort();
|
|
70912
70941
|
const addedSources = [];
|
|
70913
70942
|
for (const mediaFile of mediaFiles) {
|
|
70914
|
-
const mediaName =
|
|
70943
|
+
const mediaName = basename23(mediaFile);
|
|
70915
70944
|
const mediaType = detectMediaType4(mediaFile);
|
|
70916
70945
|
let duration = imageDuration;
|
|
70917
70946
|
if (mediaType !== "image") {
|
|
@@ -71987,7 +72016,7 @@ init_source();
|
|
|
71987
72016
|
// package.json
|
|
71988
72017
|
var package_default = {
|
|
71989
72018
|
name: "@vibeframe/cli",
|
|
71990
|
-
version: "0.115.
|
|
72019
|
+
version: "0.115.9",
|
|
71991
72020
|
description: "Frontier AI video generation for coding agents - Seedance, Runway, Veo, and Kling on your own keys, behind a hard cost cap",
|
|
71992
72021
|
private: false,
|
|
71993
72022
|
license: "MIT",
|
|
@@ -72338,9 +72367,9 @@ init_validate();
|
|
|
72338
72367
|
init_output();
|
|
72339
72368
|
init_cost_tier();
|
|
72340
72369
|
init_project_resolver();
|
|
72341
|
-
import { readFile as
|
|
72342
|
-
import { existsSync as
|
|
72343
|
-
import { resolve as resolve19, basename as
|
|
72370
|
+
import { readFile as readFile17, writeFile as writeFile13 } from "node:fs/promises";
|
|
72371
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
72372
|
+
import { resolve as resolve19, basename as basename6, extname as extname3 } from "node:path";
|
|
72344
72373
|
var timelineCommand = new Command("timeline").description("Low-level timeline JSON commands").addHelpText(
|
|
72345
72374
|
"after",
|
|
72346
72375
|
`
|
|
@@ -72389,11 +72418,11 @@ timelineCommand.command("add-source").description("Add a media source to the tim
|
|
|
72389
72418
|
return;
|
|
72390
72419
|
}
|
|
72391
72420
|
const filePath = await resolveTimelineFile(projectPath);
|
|
72392
|
-
const content = await
|
|
72421
|
+
const content = await readFile17(filePath, "utf-8");
|
|
72393
72422
|
const data = JSON.parse(content);
|
|
72394
72423
|
const project = Project.fromJSON(data);
|
|
72395
72424
|
const absMediaPath = resolve19(process.cwd(), mediaPath);
|
|
72396
|
-
const mediaName = options.name ||
|
|
72425
|
+
const mediaName = options.name || basename6(mediaPath);
|
|
72397
72426
|
const mediaType = options.type || detectMediaType(mediaPath);
|
|
72398
72427
|
const duration = parseFloat(options.duration) || 5;
|
|
72399
72428
|
const source = project.addSource({
|
|
@@ -72455,7 +72484,7 @@ timelineCommand.command("add-clip").description("Add a clip to the timeline").ar
|
|
|
72455
72484
|
return;
|
|
72456
72485
|
}
|
|
72457
72486
|
const filePath = await resolveTimelineFile(projectPath);
|
|
72458
|
-
const content = await
|
|
72487
|
+
const content = await readFile17(filePath, "utf-8");
|
|
72459
72488
|
const data = JSON.parse(content);
|
|
72460
72489
|
const project = Project.fromJSON(data);
|
|
72461
72490
|
const source = project.getSource(sourceId);
|
|
@@ -72533,7 +72562,7 @@ timelineCommand.command("add-track").description("Add a new track").argument("<p
|
|
|
72533
72562
|
return;
|
|
72534
72563
|
}
|
|
72535
72564
|
const filePath = await resolveTimelineFile(projectPath);
|
|
72536
|
-
const content = await
|
|
72565
|
+
const content = await readFile17(filePath, "utf-8");
|
|
72537
72566
|
const data = JSON.parse(content);
|
|
72538
72567
|
const project = Project.fromJSON(data);
|
|
72539
72568
|
const existingTracks = project.getTracksByType(type);
|
|
@@ -72598,7 +72627,7 @@ timelineCommand.command("add-effect").description("Add an effect to a clip").arg
|
|
|
72598
72627
|
return;
|
|
72599
72628
|
}
|
|
72600
72629
|
const filePath = await resolveTimelineFile(projectPath);
|
|
72601
|
-
const content = await
|
|
72630
|
+
const content = await readFile17(filePath, "utf-8");
|
|
72602
72631
|
const data = JSON.parse(content);
|
|
72603
72632
|
const project = Project.fromJSON(data);
|
|
72604
72633
|
const clip = project.getClip(clipId);
|
|
@@ -72669,7 +72698,7 @@ timelineCommand.command("trim-clip").description("Trim a clip").argument("<proje
|
|
|
72669
72698
|
return;
|
|
72670
72699
|
}
|
|
72671
72700
|
const filePath = await resolveTimelineFile(projectPath);
|
|
72672
|
-
const content = await
|
|
72701
|
+
const content = await readFile17(filePath, "utf-8");
|
|
72673
72702
|
const data = JSON.parse(content);
|
|
72674
72703
|
const project = Project.fromJSON(data);
|
|
72675
72704
|
const clip = project.getClip(clipId);
|
|
@@ -72711,10 +72740,10 @@ timelineCommand.command("list").description("List timeline contents").argument("
|
|
|
72711
72740
|
const startedAt = Date.now();
|
|
72712
72741
|
try {
|
|
72713
72742
|
const filePath = await resolveTimelineFile(projectPath);
|
|
72714
|
-
if (!
|
|
72743
|
+
if (!existsSync13(filePath)) {
|
|
72715
72744
|
exitWithError(notFoundError(projectPath));
|
|
72716
72745
|
}
|
|
72717
|
-
const content = await
|
|
72746
|
+
const content = await readFile17(filePath, "utf-8");
|
|
72718
72747
|
const data = JSON.parse(content);
|
|
72719
72748
|
const project = Project.fromJSON(data);
|
|
72720
72749
|
const showAll = !options.sources && !options.tracks && !options.clips;
|
|
@@ -72836,7 +72865,7 @@ timelineCommand.command("split-clip").description("Split a clip at a specific ti
|
|
|
72836
72865
|
return;
|
|
72837
72866
|
}
|
|
72838
72867
|
const filePath = await resolveTimelineFile(projectPath);
|
|
72839
|
-
const content = await
|
|
72868
|
+
const content = await readFile17(filePath, "utf-8");
|
|
72840
72869
|
const data = JSON.parse(content);
|
|
72841
72870
|
const project = Project.fromJSON(data);
|
|
72842
72871
|
const clip = project.getClip(clipId);
|
|
@@ -72899,7 +72928,7 @@ timelineCommand.command("duplicate-clip").description("Duplicate a clip").argume
|
|
|
72899
72928
|
return;
|
|
72900
72929
|
}
|
|
72901
72930
|
const filePath = await resolveTimelineFile(projectPath);
|
|
72902
|
-
const content = await
|
|
72931
|
+
const content = await readFile17(filePath, "utf-8");
|
|
72903
72932
|
const data = JSON.parse(content);
|
|
72904
72933
|
const project = Project.fromJSON(data);
|
|
72905
72934
|
const clip = project.getClip(clipId);
|
|
@@ -72958,7 +72987,7 @@ timelineCommand.command("delete-clip").description("Delete a clip from the timel
|
|
|
72958
72987
|
return;
|
|
72959
72988
|
}
|
|
72960
72989
|
const filePath = await resolveTimelineFile(projectPath);
|
|
72961
|
-
const content = await
|
|
72990
|
+
const content = await readFile17(filePath, "utf-8");
|
|
72962
72991
|
const data = JSON.parse(content);
|
|
72963
72992
|
const project = Project.fromJSON(data);
|
|
72964
72993
|
const clip = project.getClip(clipId);
|
|
@@ -73014,7 +73043,7 @@ timelineCommand.command("move-clip").description("Move a clip to a new position"
|
|
|
73014
73043
|
return;
|
|
73015
73044
|
}
|
|
73016
73045
|
const filePath = await resolveTimelineFile(projectPath);
|
|
73017
|
-
const content = await
|
|
73046
|
+
const content = await readFile17(filePath, "utf-8");
|
|
73018
73047
|
const data = JSON.parse(content);
|
|
73019
73048
|
const project = Project.fromJSON(data);
|
|
73020
73049
|
const clip = project.getClip(clipId);
|
|
@@ -73440,7 +73469,7 @@ init_output();
|
|
|
73440
73469
|
init_validate();
|
|
73441
73470
|
init_ai_audio();
|
|
73442
73471
|
import { resolve as resolve40 } from "node:path";
|
|
73443
|
-
import { existsSync as
|
|
73472
|
+
import { existsSync as existsSync37 } from "node:fs";
|
|
73444
73473
|
var audioCommand = new Command("audio").alias("au").description("Audio operations (transcribe, dub, duck, isolate, voice clone)").addHelpText(
|
|
73445
73474
|
"after",
|
|
73446
73475
|
`
|
|
@@ -73468,7 +73497,7 @@ Run 'vibe schema audio.<command>' for structured parameter info.
|
|
|
73468
73497
|
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) => {
|
|
73469
73498
|
const startedAt = Date.now();
|
|
73470
73499
|
if (options.output) validateOutputPath(options.output);
|
|
73471
|
-
if (!
|
|
73500
|
+
if (!existsSync37(resolve40(process.cwd(), audioPath))) {
|
|
73472
73501
|
exitWithError(notFoundError(resolve40(process.cwd(), audioPath)));
|
|
73473
73502
|
}
|
|
73474
73503
|
const apiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI", options.apiKey);
|
|
@@ -73560,7 +73589,7 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
|
|
|
73560
73589
|
return;
|
|
73561
73590
|
}
|
|
73562
73591
|
if (options.output) validateOutputPath(options.output);
|
|
73563
|
-
if (!
|
|
73592
|
+
if (!existsSync37(resolve40(process.cwd(), audioPath))) {
|
|
73564
73593
|
exitWithError(notFoundError(resolve40(process.cwd(), audioPath)));
|
|
73565
73594
|
}
|
|
73566
73595
|
const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
|
|
@@ -73612,7 +73641,7 @@ audioCommand.command("clone-voice").description("Clone a voice from audio sample
|
|
|
73612
73641
|
rejectControlChars(options.name);
|
|
73613
73642
|
if (!samples || samples.length === 0) exitWithError(usageError("At least one audio sample is required"));
|
|
73614
73643
|
for (const samplePath of samples) {
|
|
73615
|
-
if (!
|
|
73644
|
+
if (!existsSync37(resolve40(process.cwd(), samplePath))) exitWithError(notFoundError(samplePath));
|
|
73616
73645
|
}
|
|
73617
73646
|
let labels;
|
|
73618
73647
|
try {
|
|
@@ -73660,7 +73689,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
|
|
|
73660
73689
|
}
|
|
73661
73690
|
if (options.output) validateOutputPath(options.output);
|
|
73662
73691
|
if (!options.language) exitWithError(usageError("Target language is required. Use -l or --language"));
|
|
73663
|
-
if (!
|
|
73692
|
+
if (!existsSync37(resolve40(process.cwd(), mediaPath))) exitWithError(notFoundError(mediaPath));
|
|
73664
73693
|
await requireApiKey("OPENAI_API_KEY", "OpenAI");
|
|
73665
73694
|
const spinner2 = isJsonMode() ? null : ora("Transcribing, translating, and dubbing...").start();
|
|
73666
73695
|
const result = await executeDub({
|
|
@@ -73812,8 +73841,8 @@ init_ai_highlights();
|
|
|
73812
73841
|
init_ai_animated_caption();
|
|
73813
73842
|
init_output();
|
|
73814
73843
|
init_cost_tier();
|
|
73815
|
-
import { resolve as resolve43, basename as
|
|
73816
|
-
import { existsSync as
|
|
73844
|
+
import { resolve as resolve43, basename as basename15 } from "node:path";
|
|
73845
|
+
import { existsSync as existsSync40 } from "node:fs";
|
|
73817
73846
|
var remixCommand = new Command("remix").description("Repurpose existing media (highlights, auto-shorts, animated captions)").addHelpText(
|
|
73818
73847
|
"after",
|
|
73819
73848
|
`
|
|
@@ -73876,7 +73905,7 @@ Required API Key: OPENAI_API_KEY (Whisper transcription)
|
|
|
73876
73905
|
const startedAt = Date.now();
|
|
73877
73906
|
try {
|
|
73878
73907
|
const absVideoPath = resolve43(process.cwd(), videoPath);
|
|
73879
|
-
if (!
|
|
73908
|
+
if (!existsSync40(absVideoPath)) {
|
|
73880
73909
|
exitWithError(notFoundError(absVideoPath));
|
|
73881
73910
|
}
|
|
73882
73911
|
if (!ANIMATED_CAPTION_STYLES.includes(options.style)) {
|
|
@@ -73913,7 +73942,7 @@ Required API Key: OPENAI_API_KEY (Whisper transcription)
|
|
|
73913
73942
|
console.log();
|
|
73914
73943
|
console.log(source_default.bold.cyan("Animated Caption Pipeline"));
|
|
73915
73944
|
console.log(source_default.dim("\u2500".repeat(60)));
|
|
73916
|
-
console.log(` Video: ${source_default.bold(
|
|
73945
|
+
console.log(` Video: ${source_default.bold(basename15(absVideoPath))}`);
|
|
73917
73946
|
console.log(` Style: ${source_default.bold(options.style)}`);
|
|
73918
73947
|
console.log(` Mode: ${source_default.bold(options.fast ? "ASS (fast)" : "Remotion")}`);
|
|
73919
73948
|
console.log();
|
|
@@ -74240,7 +74269,7 @@ init_esm();
|
|
|
74240
74269
|
init_source();
|
|
74241
74270
|
init_ora();
|
|
74242
74271
|
import { stat as stat7 } from "node:fs/promises";
|
|
74243
|
-
import { resolve as resolve44, extname as extname13, basename as
|
|
74272
|
+
import { resolve as resolve44, extname as extname13, basename as basename16 } from "node:path";
|
|
74244
74273
|
|
|
74245
74274
|
// ../../node_modules/.pnpm/music-metadata@11.11.1/node_modules/music-metadata/lib/index.js
|
|
74246
74275
|
init_lib();
|
|
@@ -78118,7 +78147,7 @@ mediaCommand.command("info").description("Get media file information").argument(
|
|
|
78118
78147
|
try {
|
|
78119
78148
|
const filePath = resolve44(process.cwd(), file);
|
|
78120
78149
|
const ext = extname13(filePath).toLowerCase();
|
|
78121
|
-
const fileName =
|
|
78150
|
+
const fileName = basename16(filePath);
|
|
78122
78151
|
const fileStat = await stat7(filePath);
|
|
78123
78152
|
const mediaType = detectMediaType2(ext);
|
|
78124
78153
|
spinner2.stop();
|
|
@@ -78259,7 +78288,7 @@ init_engine();
|
|
|
78259
78288
|
init_output();
|
|
78260
78289
|
init_cost_tier();
|
|
78261
78290
|
import { readFile as readFile33, writeFile as writeFile34, readdir as readdir6 } from "node:fs/promises";
|
|
78262
|
-
import { resolve as resolve45, basename as
|
|
78291
|
+
import { resolve as resolve45, basename as basename17, extname as extname14, join as join21 } from "node:path";
|
|
78263
78292
|
var batchCommand = new Command("batch").description("Batch operations for processing multiple items");
|
|
78264
78293
|
function detectMediaType3(filePath) {
|
|
78265
78294
|
const ext = extname14(filePath).toLowerCase();
|
|
@@ -78327,7 +78356,7 @@ batchCommand.command("import").description("Import multiple media files from a d
|
|
|
78327
78356
|
const scanDir = async (dir) => {
|
|
78328
78357
|
const entries = await readdir6(dir, { withFileTypes: true });
|
|
78329
78358
|
for (const entry of entries) {
|
|
78330
|
-
const entryPath =
|
|
78359
|
+
const entryPath = join21(dir, entry.name);
|
|
78331
78360
|
if (entry.isDirectory() && options.recursive) {
|
|
78332
78361
|
await scanDir(entryPath);
|
|
78333
78362
|
} else if (entry.isFile()) {
|
|
@@ -78349,7 +78378,7 @@ batchCommand.command("import").description("Import multiple media files from a d
|
|
|
78349
78378
|
const addedSources = [];
|
|
78350
78379
|
const defaultDuration = parseFloat(options.duration);
|
|
78351
78380
|
for (const mediaFile of mediaFiles) {
|
|
78352
|
-
const mediaName =
|
|
78381
|
+
const mediaName = basename17(mediaFile);
|
|
78353
78382
|
const mediaType = detectMediaType3(mediaFile);
|
|
78354
78383
|
const duration = mediaType === "image" ? defaultDuration : 0;
|
|
78355
78384
|
const source = project.addSource({
|
|
@@ -78364,7 +78393,7 @@ batchCommand.command("import").description("Import multiple media files from a d
|
|
|
78364
78393
|
spinner2.succeed(source_default.green(`Imported ${addedSources.length} media files`));
|
|
78365
78394
|
console.log();
|
|
78366
78395
|
for (const file of mediaFiles) {
|
|
78367
|
-
console.log(source_default.dim(" +"),
|
|
78396
|
+
console.log(source_default.dim(" +"), basename17(file));
|
|
78368
78397
|
}
|
|
78369
78398
|
console.log();
|
|
78370
78399
|
} catch (error) {
|
|
@@ -78705,9 +78734,9 @@ async function copyToClipboard(text) {
|
|
|
78705
78734
|
}
|
|
78706
78735
|
|
|
78707
78736
|
// src/utils/agent-host-detect.ts
|
|
78708
|
-
import { existsSync as
|
|
78737
|
+
import { existsSync as existsSync41 } from "node:fs";
|
|
78709
78738
|
import { homedir as homedir5 } from "node:os";
|
|
78710
|
-
import { join as
|
|
78739
|
+
import { join as join22 } from "node:path";
|
|
78711
78740
|
function detectAgentHosts(env2 = process.env) {
|
|
78712
78741
|
const home = homedir5();
|
|
78713
78742
|
return [
|
|
@@ -78767,8 +78796,8 @@ function detectAgentHosts(env2 = process.env) {
|
|
|
78767
78796
|
}
|
|
78768
78797
|
const configDirRel = HOST_CONFIG_DIRS[host.id];
|
|
78769
78798
|
if (configDirRel) {
|
|
78770
|
-
const path =
|
|
78771
|
-
if (
|
|
78799
|
+
const path = join22(home, configDirRel);
|
|
78800
|
+
if (existsSync41(path)) {
|
|
78772
78801
|
signals2.push({ kind: "configDir", path });
|
|
78773
78802
|
}
|
|
78774
78803
|
}
|
|
@@ -78822,8 +78851,8 @@ function isOnPath(binary, env2) {
|
|
|
78822
78851
|
const sep3 = process.platform === "win32" ? ";" : ":";
|
|
78823
78852
|
for (const dir of path.split(sep3)) {
|
|
78824
78853
|
if (!dir) continue;
|
|
78825
|
-
if (
|
|
78826
|
-
if (process.platform === "win32" &&
|
|
78854
|
+
if (existsSync41(join22(dir, binary))) return true;
|
|
78855
|
+
if (process.platform === "win32" && existsSync41(join22(dir, `${binary}.exe`))) return true;
|
|
78827
78856
|
}
|
|
78828
78857
|
return false;
|
|
78829
78858
|
}
|
|
@@ -80064,9 +80093,9 @@ function pickCompletionHint(shellPath) {
|
|
|
80064
80093
|
// src/commands/init.ts
|
|
80065
80094
|
init_esm();
|
|
80066
80095
|
init_source();
|
|
80067
|
-
import { existsSync as
|
|
80068
|
-
import { mkdir as
|
|
80069
|
-
import { basename as
|
|
80096
|
+
import { existsSync as existsSync43 } from "node:fs";
|
|
80097
|
+
import { mkdir as mkdir24, readFile as readFile37, writeFile as writeFile37 } from "node:fs/promises";
|
|
80098
|
+
import { basename as basename19, dirname as dirname16, resolve as resolve49, sep } from "node:path";
|
|
80070
80099
|
init_scene_project();
|
|
80071
80100
|
init_visual_styles();
|
|
80072
80101
|
|
|
@@ -80232,10 +80261,10 @@ init_project_config();
|
|
|
80232
80261
|
init_install_skill();
|
|
80233
80262
|
|
|
80234
80263
|
// src/commands/_shared/host-integration.ts
|
|
80235
|
-
import { existsSync as
|
|
80236
|
-
import { mkdir as
|
|
80264
|
+
import { existsSync as existsSync42 } from "node:fs";
|
|
80265
|
+
import { mkdir as mkdir23, readFile as readFile36, writeFile as writeFile36 } from "node:fs/promises";
|
|
80237
80266
|
import { homedir as homedir7 } from "node:os";
|
|
80238
|
-
import { dirname as dirname15, join as
|
|
80267
|
+
import { dirname as dirname15, join as join23, resolve as resolve48 } from "node:path";
|
|
80239
80268
|
var HOST_SELECTIONS = [
|
|
80240
80269
|
"codex",
|
|
80241
80270
|
"claude",
|
|
@@ -80262,7 +80291,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
|
|
|
80262
80291
|
label: "OpenAI Codex",
|
|
80263
80292
|
surfaces: ["shell", "project-guidance", "mcp"],
|
|
80264
80293
|
guidanceFiles: ["AGENTS.md"],
|
|
80265
|
-
configPath: (dir) =>
|
|
80294
|
+
configPath: (dir) => join23(resolve48(dir), ".codex", "config.toml"),
|
|
80266
80295
|
configKind: "codex-toml",
|
|
80267
80296
|
notes: [
|
|
80268
80297
|
"Codex reads AGENTS.md and can also load project-scoped .codex/config.toml after the project is trusted.",
|
|
@@ -80275,7 +80304,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
|
|
|
80275
80304
|
label: "Claude Code",
|
|
80276
80305
|
surfaces: ["shell", "project-guidance", "mcp"],
|
|
80277
80306
|
guidanceFiles: ["AGENTS.md", "CLAUDE.md", ".claude/skills/hyperframes/"],
|
|
80278
|
-
configPath: (dir) =>
|
|
80307
|
+
configPath: (dir) => join23(resolve48(dir), ".mcp.json"),
|
|
80279
80308
|
configKind: "mcp-json",
|
|
80280
80309
|
notes: [
|
|
80281
80310
|
"Claude Code can drive vibe directly through shell plus AGENTS.md/CLAUDE.md.",
|
|
@@ -80300,7 +80329,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
|
|
|
80300
80329
|
label: "Cursor",
|
|
80301
80330
|
surfaces: ["shell", "project-guidance", "mcp"],
|
|
80302
80331
|
guidanceFiles: ["AGENTS.md", ".cursor/rules/hyperframes.mdc"],
|
|
80303
|
-
configPath: (dir) =>
|
|
80332
|
+
configPath: (dir) => join23(resolve48(dir), ".cursor", "mcp.json"),
|
|
80304
80333
|
configKind: "mcp-json",
|
|
80305
80334
|
notes: [
|
|
80306
80335
|
"Cursor can use AGENTS.md/rules for shell-driven work and .cursor/mcp.json for typed MCP tools."
|
|
@@ -80341,11 +80370,11 @@ async function planHostSetup(host, projectDir, opts) {
|
|
|
80341
80370
|
const warnings = [];
|
|
80342
80371
|
const nextSteps = hostNextSteps(host);
|
|
80343
80372
|
for (const relPath of host.guidanceFiles.filter((p) => !p.endsWith("/"))) {
|
|
80344
|
-
const absPath =
|
|
80373
|
+
const absPath = join23(resolve48(projectDir), relPath);
|
|
80345
80374
|
files.push({
|
|
80346
80375
|
path: absPath,
|
|
80347
|
-
status:
|
|
80348
|
-
reason:
|
|
80376
|
+
status: existsSync42(absPath) ? "skipped-exists" : "missing",
|
|
80377
|
+
reason: existsSync42(absPath) ? "already present" : "run vibe init --agent all first"
|
|
80349
80378
|
});
|
|
80350
80379
|
}
|
|
80351
80380
|
if (!opts.write || opts.dryRun) {
|
|
@@ -80389,15 +80418,15 @@ async function inspectHost(host, projectDir) {
|
|
|
80389
80418
|
const warnings = [];
|
|
80390
80419
|
const nextSteps = hostNextSteps(host);
|
|
80391
80420
|
const guidanceFiles = host.guidanceFiles.map((relPath) => {
|
|
80392
|
-
const absPath = relPath.endsWith("/") ?
|
|
80421
|
+
const absPath = relPath.endsWith("/") ? join23(resolve48(projectDir), relPath) : join23(resolve48(projectDir), relPath);
|
|
80393
80422
|
return {
|
|
80394
80423
|
path: absPath,
|
|
80395
|
-
status:
|
|
80396
|
-
reason:
|
|
80424
|
+
status: existsSync42(absPath) ? "skipped-exists" : "missing",
|
|
80425
|
+
reason: existsSync42(absPath) ? "present" : "missing"
|
|
80397
80426
|
};
|
|
80398
80427
|
});
|
|
80399
80428
|
let configured = false;
|
|
80400
|
-
if (
|
|
80429
|
+
if (existsSync42(configPath)) {
|
|
80401
80430
|
try {
|
|
80402
80431
|
const content = await readFile36(configPath, "utf-8");
|
|
80403
80432
|
if (host.configKind === "codex-toml") {
|
|
@@ -80420,7 +80449,7 @@ async function inspectHost(host, projectDir) {
|
|
|
80420
80449
|
}
|
|
80421
80450
|
async function mergeMcpJson(configPath, opts) {
|
|
80422
80451
|
let root = {};
|
|
80423
|
-
const exists =
|
|
80452
|
+
const exists = existsSync42(configPath);
|
|
80424
80453
|
if (exists) {
|
|
80425
80454
|
const raw = await readFile36(configPath, "utf-8");
|
|
80426
80455
|
try {
|
|
@@ -80439,7 +80468,7 @@ async function mergeMcpJson(configPath, opts) {
|
|
|
80439
80468
|
...mcpServers,
|
|
80440
80469
|
vibeframe: opts.server
|
|
80441
80470
|
};
|
|
80442
|
-
await
|
|
80471
|
+
await mkdir23(dirname15(configPath), { recursive: true });
|
|
80443
80472
|
if (exists && opts.backup) {
|
|
80444
80473
|
await writeFile36(
|
|
80445
80474
|
`${configPath}.bak-${timestamp()}`,
|
|
@@ -80461,7 +80490,7 @@ async function mergeMcpJson(configPath, opts) {
|
|
|
80461
80490
|
};
|
|
80462
80491
|
}
|
|
80463
80492
|
root.mcpServers = { ...mcpServers, vibeframe: opts.server };
|
|
80464
|
-
await
|
|
80493
|
+
await mkdir23(dirname15(configPath), { recursive: true });
|
|
80465
80494
|
if (exists && opts.backup) {
|
|
80466
80495
|
await writeFile36(
|
|
80467
80496
|
`${configPath}.bak-${timestamp()}`,
|
|
@@ -80473,11 +80502,11 @@ async function mergeMcpJson(configPath, opts) {
|
|
|
80473
80502
|
return { path: configPath, status: exists ? "merged" : "wrote" };
|
|
80474
80503
|
}
|
|
80475
80504
|
async function mergeCodexToml(configPath, opts) {
|
|
80476
|
-
const exists =
|
|
80505
|
+
const exists = existsSync42(configPath);
|
|
80477
80506
|
const block = `${CODEX_MANAGED_START}
|
|
80478
80507
|
${CODEX_MCP_BLOCK}${CODEX_MANAGED_END}`;
|
|
80479
80508
|
if (!exists) {
|
|
80480
|
-
await
|
|
80509
|
+
await mkdir23(dirname15(configPath), { recursive: true });
|
|
80481
80510
|
await writeFile36(
|
|
80482
80511
|
configPath,
|
|
80483
80512
|
`#:schema https://developers.openai.com/codex/config-schema.json
|
|
@@ -80577,16 +80606,16 @@ function hostNextSteps(host) {
|
|
|
80577
80606
|
function claudeDesktopConfigPath() {
|
|
80578
80607
|
const home = homedir7();
|
|
80579
80608
|
if (process.platform === "darwin") {
|
|
80580
|
-
return
|
|
80609
|
+
return join23(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
80581
80610
|
}
|
|
80582
80611
|
if (process.platform === "win32") {
|
|
80583
|
-
return
|
|
80584
|
-
process.env.APPDATA ??
|
|
80612
|
+
return join23(
|
|
80613
|
+
process.env.APPDATA ?? join23(home, "AppData", "Roaming"),
|
|
80585
80614
|
"Claude",
|
|
80586
80615
|
"claude_desktop_config.json"
|
|
80587
80616
|
);
|
|
80588
80617
|
}
|
|
80589
|
-
return
|
|
80618
|
+
return join23(home, ".config", "Claude", "claude_desktop_config.json");
|
|
80590
80619
|
}
|
|
80591
80620
|
function timestamp() {
|
|
80592
80621
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -81023,13 +81052,13 @@ var initCommand = new Command("init").description("Scaffold a VibeFrame project
|
|
|
81023
81052
|
);
|
|
81024
81053
|
}
|
|
81025
81054
|
const projectDir = resolve49(projectDirArg);
|
|
81026
|
-
const projectName =
|
|
81055
|
+
const projectName = basename19(projectDir);
|
|
81027
81056
|
const targetHosts = resolveTargets(agent);
|
|
81028
81057
|
const wantsClaude = targetHosts.includes("claude-code");
|
|
81029
81058
|
const wantsGemini = targetHosts.includes("gemini-cli");
|
|
81030
81059
|
const wantsCrossTool = targetHosts.some((h) => h !== "claude-code");
|
|
81031
81060
|
const actions = [];
|
|
81032
|
-
if (!
|
|
81061
|
+
if (!existsSync43(projectDir)) {
|
|
81033
81062
|
if (options.dryRun) {
|
|
81034
81063
|
actions.push({
|
|
81035
81064
|
path: projectDir,
|
|
@@ -81037,7 +81066,7 @@ var initCommand = new Command("init").description("Scaffold a VibeFrame project
|
|
|
81037
81066
|
reason: "(would create directory)"
|
|
81038
81067
|
});
|
|
81039
81068
|
} else {
|
|
81040
|
-
await
|
|
81069
|
+
await mkdir24(projectDir, { recursive: true });
|
|
81041
81070
|
}
|
|
81042
81071
|
}
|
|
81043
81072
|
if (wantsCrossTool || !wantsClaude) {
|
|
@@ -81189,7 +81218,7 @@ async function runSceneInit(projectDirArg, options, startedAt) {
|
|
|
81189
81218
|
);
|
|
81190
81219
|
}
|
|
81191
81220
|
const projectDir = resolve49(projectDirArg);
|
|
81192
|
-
const projectName =
|
|
81221
|
+
const projectName = basename19(projectDir);
|
|
81193
81222
|
const groups = describeSceneScaffold({ dir: projectDir, profile, kind });
|
|
81194
81223
|
const fromBrief = typeof options.from === "string" && options.from.trim() ? await readBriefOrLiteral(String(options.from), process.cwd()) : null;
|
|
81195
81224
|
if (options.dryRun) {
|
|
@@ -81245,7 +81274,7 @@ async function runSceneInit(projectDirArg, options, startedAt) {
|
|
|
81245
81274
|
});
|
|
81246
81275
|
const storyboardPath2 = resolve49(projectDir, "STORYBOARD.md");
|
|
81247
81276
|
const designPath = resolve49(projectDir, "DESIGN.md");
|
|
81248
|
-
if (options.force || result.created.includes(storyboardPath2) || !
|
|
81277
|
+
if (options.force || result.created.includes(storyboardPath2) || !existsSync43(storyboardPath2)) {
|
|
81249
81278
|
const plan = await rewriteBundleFromMarkdown(projectDir, draft.storyboardMd);
|
|
81250
81279
|
if (!result.created.includes(storyboardPath2)) result.created.push(storyboardPath2);
|
|
81251
81280
|
const planScenePaths = new Set(plan.scenes.map((file) => resolve49(projectDir, file.path)));
|
|
@@ -81257,7 +81286,7 @@ async function runSceneInit(projectDirArg, options, startedAt) {
|
|
|
81257
81286
|
if (!result.created.includes(scenePath)) result.created.push(scenePath);
|
|
81258
81287
|
}
|
|
81259
81288
|
}
|
|
81260
|
-
if (options.force || result.created.includes(designPath) || !
|
|
81289
|
+
if (options.force || result.created.includes(designPath) || !existsSync43(designPath)) {
|
|
81261
81290
|
await writeFile37(designPath, draft.designMd, "utf-8");
|
|
81262
81291
|
if (!result.created.includes(designPath)) result.created.push(designPath);
|
|
81263
81292
|
}
|
|
@@ -81380,7 +81409,7 @@ function printGroup(label, files, projectDir) {
|
|
|
81380
81409
|
}
|
|
81381
81410
|
async function readBriefOrLiteral(value, cwd) {
|
|
81382
81411
|
const candidate = resolve49(cwd, value);
|
|
81383
|
-
if (
|
|
81412
|
+
if (existsSync43(candidate)) {
|
|
81384
81413
|
return readFile37(candidate, "utf-8");
|
|
81385
81414
|
}
|
|
81386
81415
|
return value;
|
|
@@ -81434,20 +81463,20 @@ function normalizeHostAction(status) {
|
|
|
81434
81463
|
return "would-write";
|
|
81435
81464
|
}
|
|
81436
81465
|
async function writeIfMissing(absPath, content, force, dryRun) {
|
|
81437
|
-
const exists =
|
|
81466
|
+
const exists = existsSync43(absPath);
|
|
81438
81467
|
if (exists && !force) {
|
|
81439
81468
|
return { path: absPath, status: "skipped-exists", reason: "(use --force to overwrite)" };
|
|
81440
81469
|
}
|
|
81441
81470
|
if (dryRun) {
|
|
81442
81471
|
return { path: absPath, status: "would-write" };
|
|
81443
81472
|
}
|
|
81444
|
-
await
|
|
81473
|
+
await mkdir24(dirname16(absPath), { recursive: true });
|
|
81445
81474
|
await writeFile37(absPath, content, "utf-8");
|
|
81446
81475
|
return { path: absPath, status: "wrote" };
|
|
81447
81476
|
}
|
|
81448
81477
|
async function mergeGitignore(absPath, additions, dryRun) {
|
|
81449
81478
|
const marker = "# VibeFrame";
|
|
81450
|
-
if (!
|
|
81479
|
+
if (!existsSync43(absPath)) {
|
|
81451
81480
|
if (dryRun) return { path: absPath, status: "would-write" };
|
|
81452
81481
|
await writeFile37(absPath, additions.trimStart(), "utf-8");
|
|
81453
81482
|
return { path: absPath, status: "wrote" };
|
|
@@ -81487,7 +81516,7 @@ init_composer_resolve();
|
|
|
81487
81516
|
init_cost_tier();
|
|
81488
81517
|
init_output();
|
|
81489
81518
|
import { readFile as readFile39, writeFile as writeFile39 } from "node:fs/promises";
|
|
81490
|
-
import { existsSync as
|
|
81519
|
+
import { existsSync as existsSync45 } from "node:fs";
|
|
81491
81520
|
import { resolve as resolve51 } from "node:path";
|
|
81492
81521
|
var storyboardCommand = new Command("storyboard").description("Read, validate, and safely mutate STORYBOARD.md cue blocks").addHelpText("after", `
|
|
81493
81522
|
Examples:
|
|
@@ -81754,7 +81783,7 @@ function parseComposer(value) {
|
|
|
81754
81783
|
}
|
|
81755
81784
|
async function readBrief(value, cwd) {
|
|
81756
81785
|
const candidate = resolve51(cwd, value);
|
|
81757
|
-
if (
|
|
81786
|
+
if (existsSync45(candidate)) {
|
|
81758
81787
|
return readFile39(candidate, "utf-8");
|
|
81759
81788
|
}
|
|
81760
81789
|
return value;
|
|
@@ -81791,8 +81820,8 @@ init_design_parse();
|
|
|
81791
81820
|
init_cost_tier();
|
|
81792
81821
|
init_output();
|
|
81793
81822
|
import { readFile as readFile40 } from "node:fs/promises";
|
|
81794
|
-
import { existsSync as
|
|
81795
|
-
import { resolve as resolve52, join as
|
|
81823
|
+
import { existsSync as existsSync46 } from "node:fs";
|
|
81824
|
+
import { resolve as resolve52, join as join25 } from "node:path";
|
|
81796
81825
|
var designCommand = new Command("design").description("Read and validate DESIGN.md (google-labs design.md format)").addHelpText(
|
|
81797
81826
|
"after",
|
|
81798
81827
|
`
|
|
@@ -81804,8 +81833,8 @@ Examples:
|
|
|
81804
81833
|
designCommand.command("validate").description("Validate DESIGN.md front-matter tokens and sections").argument("[project-dir]", "Project directory", ".").action(async (projectDirArg) => {
|
|
81805
81834
|
const startedAt = Date.now();
|
|
81806
81835
|
const projectDir = resolve52(projectDirArg);
|
|
81807
|
-
const path =
|
|
81808
|
-
if (!
|
|
81836
|
+
const path = join25(projectDir, "DESIGN.md");
|
|
81837
|
+
if (!existsSync46(path)) {
|
|
81809
81838
|
exitWithError(generalError(`DESIGN.md not found in ${projectDir}`));
|
|
81810
81839
|
return;
|
|
81811
81840
|
}
|
|
@@ -82027,8 +82056,8 @@ init_build_plan();
|
|
|
82027
82056
|
init_storyboard_parse();
|
|
82028
82057
|
init_output();
|
|
82029
82058
|
init_build_video_providers();
|
|
82030
|
-
import { resolve as resolve63, join as
|
|
82031
|
-
import { readFileSync as readFileSync3, existsSync as
|
|
82059
|
+
import { resolve as resolve63, join as join31 } from "node:path";
|
|
82060
|
+
import { readFileSync as readFileSync3, existsSync as existsSync54 } from "node:fs";
|
|
82032
82061
|
var VALID_MODES2 = ["agent", "batch", "auto"];
|
|
82033
82062
|
var VALID_STAGES2 = ["assets", "transcript", "compose", "sync", "render", "all"];
|
|
82034
82063
|
var VALID_IMAGE_PROVIDERS2 = ["openai", "gemini", "grok"];
|
|
@@ -82392,8 +82421,8 @@ function buildPlanValidationFailureData(plan) {
|
|
|
82392
82421
|
};
|
|
82393
82422
|
}
|
|
82394
82423
|
function estimateBuildCost(params) {
|
|
82395
|
-
const storyboardPath2 =
|
|
82396
|
-
if (!
|
|
82424
|
+
const storyboardPath2 = join31(params.projectDir, "STORYBOARD.md");
|
|
82425
|
+
if (!existsSync54(storyboardPath2)) return null;
|
|
82397
82426
|
let beats = 0;
|
|
82398
82427
|
try {
|
|
82399
82428
|
const md = readFileSync3(storyboardPath2, "utf-8");
|
|
@@ -83148,7 +83177,7 @@ init_dist2();
|
|
|
83148
83177
|
init_exec_safe();
|
|
83149
83178
|
init_exec_safe();
|
|
83150
83179
|
init_api_key();
|
|
83151
|
-
import { existsSync as
|
|
83180
|
+
import { existsSync as existsSync55 } from "node:fs";
|
|
83152
83181
|
import { resolve as resolve68 } from "node:path";
|
|
83153
83182
|
init_composer_resolve();
|
|
83154
83183
|
init_cost_tier();
|
|
@@ -83280,13 +83309,13 @@ async function runDiagnostics() {
|
|
|
83280
83309
|
installed = false;
|
|
83281
83310
|
}
|
|
83282
83311
|
} else if (toolName === "chrome") {
|
|
83283
|
-
const { existsSync:
|
|
83312
|
+
const { existsSync: existsSync62 } = await import("node:fs");
|
|
83284
83313
|
const { homedir: homedir8 } = await import("node:os");
|
|
83285
|
-
const { join:
|
|
83314
|
+
const { join: join36 } = await import("node:path");
|
|
83286
83315
|
const chromePaths = [
|
|
83287
83316
|
process.env.HYPERFRAMES_CHROME_PATH,
|
|
83288
83317
|
process.env.CHROME_PATH,
|
|
83289
|
-
|
|
83318
|
+
join36(
|
|
83290
83319
|
homedir8(),
|
|
83291
83320
|
".cache",
|
|
83292
83321
|
"puppeteer",
|
|
@@ -83301,7 +83330,7 @@ async function runDiagnostics() {
|
|
|
83301
83330
|
"/usr/bin/chromium",
|
|
83302
83331
|
"/usr/bin/chromium-browser"
|
|
83303
83332
|
];
|
|
83304
|
-
installed = chromePaths.some((p) => p &&
|
|
83333
|
+
installed = chromePaths.some((p) => p && existsSync62(p));
|
|
83305
83334
|
} else {
|
|
83306
83335
|
installed = commandExists(toolName);
|
|
83307
83336
|
}
|
|
@@ -83341,7 +83370,7 @@ async function runDiagnostics() {
|
|
|
83341
83370
|
}
|
|
83342
83371
|
const projectFiles = ["AGENTS.md", "CLAUDE.md", "vibe.project.yaml"].map((rel) => ({
|
|
83343
83372
|
path: rel,
|
|
83344
|
-
exists:
|
|
83373
|
+
exists: existsSync55(resolve68(cwd, rel))
|
|
83345
83374
|
}));
|
|
83346
83375
|
const projectInitialized = projectFiles.some((f) => f.exists);
|
|
83347
83376
|
const nearestProjectConfigPath = await findProjectConfigPath(cwd);
|
|
@@ -83359,7 +83388,7 @@ async function runDiagnostics() {
|
|
|
83359
83388
|
} catch (err) {
|
|
83360
83389
|
if (!(err instanceof ComposerResolveError)) throw err;
|
|
83361
83390
|
}
|
|
83362
|
-
const sceneProjectInCwd =
|
|
83391
|
+
const sceneProjectInCwd = existsSync55(resolve68(cwd, "STORYBOARD.md"));
|
|
83363
83392
|
const skillInstalled = hyperframesSkillAvailable(cwd);
|
|
83364
83393
|
const skillPath = resolveSkillReference(cwd);
|
|
83365
83394
|
return {
|
|
@@ -83908,8 +83937,8 @@ init_detect();
|
|
|
83908
83937
|
init_ai_edit();
|
|
83909
83938
|
init_output();
|
|
83910
83939
|
import { resolve as resolve70 } from "node:path";
|
|
83911
|
-
import { existsSync as
|
|
83912
|
-
import { mkdir as
|
|
83940
|
+
import { existsSync as existsSync56 } from "node:fs";
|
|
83941
|
+
import { mkdir as mkdir29, rm as rm9 } from "node:fs/promises";
|
|
83913
83942
|
var DEMO_DIR = resolve70(process.cwd(), ".vibeframe-demo");
|
|
83914
83943
|
async function generateTestVideo(outputPath) {
|
|
83915
83944
|
const filter = [
|
|
@@ -83939,7 +83968,7 @@ async function generateTestVideo(outputPath) {
|
|
|
83939
83968
|
"-y",
|
|
83940
83969
|
outputPath
|
|
83941
83970
|
], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
|
|
83942
|
-
return
|
|
83971
|
+
return existsSync56(outputPath);
|
|
83943
83972
|
} catch {
|
|
83944
83973
|
try {
|
|
83945
83974
|
await execSafe("ffmpeg", [
|
|
@@ -83965,7 +83994,7 @@ async function generateTestVideo(outputPath) {
|
|
|
83965
83994
|
"-y",
|
|
83966
83995
|
outputPath
|
|
83967
83996
|
], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
|
|
83968
|
-
return
|
|
83997
|
+
return existsSync56(outputPath);
|
|
83969
83998
|
} catch {
|
|
83970
83999
|
return false;
|
|
83971
84000
|
}
|
|
@@ -83989,7 +84018,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
|
|
|
83989
84018
|
console.log(source_default.dim(" " + "\u2500".repeat(50)));
|
|
83990
84019
|
console.log();
|
|
83991
84020
|
}
|
|
83992
|
-
await
|
|
84021
|
+
await mkdir29(DEMO_DIR, { recursive: true });
|
|
83993
84022
|
const testVideo = resolve70(DEMO_DIR, "test-input.mp4");
|
|
83994
84023
|
const genSpinner = !isJson ? ora("Generating test video...").start() : null;
|
|
83995
84024
|
const generated = await generateTestVideo(testVideo);
|
|
@@ -84107,11 +84136,11 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
|
|
|
84107
84136
|
|
|
84108
84137
|
// src/commands/context.ts
|
|
84109
84138
|
init_esm();
|
|
84110
|
-
import { existsSync as
|
|
84139
|
+
import { existsSync as existsSync57 } from "node:fs";
|
|
84111
84140
|
import { readFile as readFile46 } from "node:fs/promises";
|
|
84112
84141
|
import { resolve as resolve71, dirname as dirname22 } from "node:path";
|
|
84113
|
-
import { fileURLToPath } from "node:url";
|
|
84114
|
-
var __filename =
|
|
84142
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
84143
|
+
var __filename = fileURLToPath2(import.meta.url);
|
|
84115
84144
|
var __dirname = dirname22(__filename);
|
|
84116
84145
|
function resolveContextPath(baseDir = __dirname) {
|
|
84117
84146
|
const candidates = [
|
|
@@ -84120,7 +84149,7 @@ function resolveContextPath(baseDir = __dirname) {
|
|
|
84120
84149
|
// Source/dev layout: packages/cli/src/commands/context.ts -> packages/cli/CONTEXT.md
|
|
84121
84150
|
resolve71(baseDir, "../../CONTEXT.md")
|
|
84122
84151
|
];
|
|
84123
|
-
return candidates.find((candidate) =>
|
|
84152
|
+
return candidates.find((candidate) => existsSync57(candidate)) ?? null;
|
|
84124
84153
|
}
|
|
84125
84154
|
var contextCommand = new Command("context").description("Print CLI context/guidelines for AI agent integration").option("--format <format>", "Output format: markdown | json", "markdown").action(async (_options, cmd) => {
|
|
84126
84155
|
const options = {
|
|
@@ -85559,7 +85588,7 @@ init_source();
|
|
|
85559
85588
|
init_config();
|
|
85560
85589
|
init_schema();
|
|
85561
85590
|
init_api_key();
|
|
85562
|
-
import { access as access9, writeFile as writeFile52, mkdir as
|
|
85591
|
+
import { access as access9, writeFile as writeFile52, mkdir as mkdir32 } from "node:fs/promises";
|
|
85563
85592
|
import { dirname as dirname23 } from "node:path";
|
|
85564
85593
|
var BANNER_SHOWN_PATH = `${USER_CONFIG_DIR}/.banner-shown`;
|
|
85565
85594
|
async function isFirstRun() {
|
|
@@ -85579,7 +85608,7 @@ async function isFirstRun() {
|
|
|
85579
85608
|
}
|
|
85580
85609
|
async function markBannerShown() {
|
|
85581
85610
|
try {
|
|
85582
|
-
await
|
|
85611
|
+
await mkdir32(dirname23(BANNER_SHOWN_PATH), { recursive: true });
|
|
85583
85612
|
await writeFile52(BANNER_SHOWN_PATH, (/* @__PURE__ */ new Date()).toISOString());
|
|
85584
85613
|
} catch {
|
|
85585
85614
|
}
|