@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
|
@@ -3370,14 +3370,14 @@ async function loadKokoroRuntime() {
|
|
|
3370
3370
|
async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
|
|
3371
3371
|
try {
|
|
3372
3372
|
const { readFile: readFile45 } = await import("node:fs/promises");
|
|
3373
|
-
const { join:
|
|
3373
|
+
const { join: join31, resolve: resolve63 } = await import("node:path");
|
|
3374
3374
|
const { pathToFileURL } = await import("node:url");
|
|
3375
3375
|
const pkgDir = resolve63(baseDir, "node_modules", "kokoro-js");
|
|
3376
|
-
const pkg = JSON.parse(await readFile45(
|
|
3376
|
+
const pkg = JSON.parse(await readFile45(join31(pkgDir, "package.json"), "utf-8"));
|
|
3377
3377
|
const entry = pkg.exports?.node?.import ?? pkg.exports?.default ?? pkg.module ?? pkg.main;
|
|
3378
3378
|
if (!entry)
|
|
3379
3379
|
return null;
|
|
3380
|
-
const mod = await import(pathToFileURL(
|
|
3380
|
+
const mod = await import(pathToFileURL(join31(pkgDir, entry)).href);
|
|
3381
3381
|
return mod.KokoroTTS ?? null;
|
|
3382
3382
|
} catch {
|
|
3383
3383
|
return null;
|
|
@@ -3388,20 +3388,20 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
|
|
|
3388
3388
|
if (!base)
|
|
3389
3389
|
return null;
|
|
3390
3390
|
try {
|
|
3391
|
-
const { readFile: readFile45, writeFile: writeFile45, mkdir:
|
|
3392
|
-
const { join:
|
|
3391
|
+
const { readFile: readFile45, writeFile: writeFile45, mkdir: mkdir28 } = await import("node:fs/promises");
|
|
3392
|
+
const { join: join31, resolve: resolve63 } = await import("node:path");
|
|
3393
3393
|
const { pathToFileURL } = await import("node:url");
|
|
3394
3394
|
const { homedir: homedir5 } = await import("node:os");
|
|
3395
3395
|
const transformersDir = resolve63(base, "node_modules", "@huggingface", "transformers");
|
|
3396
3396
|
const ortDistDir = resolve63(base, "node_modules", "onnxruntime-web", "dist");
|
|
3397
|
-
const transformers = await import(pathToFileURL(
|
|
3398
|
-
const cacheRoot =
|
|
3397
|
+
const transformers = await import(pathToFileURL(join31(transformersDir, "dist", "transformers.web.js")).href);
|
|
3398
|
+
const cacheRoot = join31(homedir5(), ".cache", "vibeframe", "models");
|
|
3399
3399
|
transformers.env.allowLocalModels = false;
|
|
3400
3400
|
transformers.env.useCustomCache = true;
|
|
3401
3401
|
transformers.env.customCache = {
|
|
3402
3402
|
async match(key) {
|
|
3403
3403
|
try {
|
|
3404
|
-
return new Response(await readFile45(
|
|
3404
|
+
return new Response(await readFile45(join31(cacheRoot, encodeURIComponent(String(key)))));
|
|
3405
3405
|
} catch {
|
|
3406
3406
|
return void 0;
|
|
3407
3407
|
}
|
|
@@ -3409,8 +3409,8 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
|
|
|
3409
3409
|
async put(key, response) {
|
|
3410
3410
|
try {
|
|
3411
3411
|
const buf = Buffer.from(await response.arrayBuffer());
|
|
3412
|
-
await
|
|
3413
|
-
await writeFile45(
|
|
3412
|
+
await mkdir28(cacheRoot, { recursive: true });
|
|
3413
|
+
await writeFile45(join31(cacheRoot, encodeURIComponent(String(key))), buf);
|
|
3414
3414
|
} catch {
|
|
3415
3415
|
}
|
|
3416
3416
|
}
|
|
@@ -30224,20 +30224,95 @@ var init_cost_tier = __esm({
|
|
|
30224
30224
|
}
|
|
30225
30225
|
});
|
|
30226
30226
|
|
|
30227
|
-
// src/commands/_shared/
|
|
30228
|
-
import { readdir as readdir3, readFile as readFile8, writeFile as writeFile9, stat as stat2 } from "node:fs/promises";
|
|
30227
|
+
// src/commands/_shared/lottie.ts
|
|
30229
30228
|
import { existsSync as existsSync5 } from "node:fs";
|
|
30230
|
-
import {
|
|
30229
|
+
import { copyFile as copyFile2, mkdir as mkdir7, readFile as readFile8 } from "node:fs/promises";
|
|
30230
|
+
import { createRequire } from "node:module";
|
|
30231
|
+
import { basename as basename3, join as join5 } from "node:path";
|
|
30232
|
+
import { fileURLToPath } from "node:url";
|
|
30233
|
+
async function checkLottieAsset(path) {
|
|
30234
|
+
const bytes = await readFile8(path);
|
|
30235
|
+
const isZip = bytes.length > 4 && bytes[0] === 80 && bytes[1] === 75;
|
|
30236
|
+
if (isZip) return void 0;
|
|
30237
|
+
try {
|
|
30238
|
+
const json = JSON.parse(bytes.toString("utf-8"));
|
|
30239
|
+
if (Array.isArray(json.layers)) return void 0;
|
|
30240
|
+
} catch {
|
|
30241
|
+
}
|
|
30242
|
+
return `${basename3(path)} is not a Lottie animation (expected Lottie JSON or a .lottie archive).`;
|
|
30243
|
+
}
|
|
30244
|
+
function overlayBox(layer, position, scale) {
|
|
30245
|
+
if (position === "full") return { left: 0, top: 0, width: layer.width, height: layer.height };
|
|
30246
|
+
const width = Math.max(1, Math.round(layer.width * scale));
|
|
30247
|
+
const height = Math.max(1, Math.round(layer.height * scale));
|
|
30248
|
+
const marginX = Math.round(layer.width * 0.04);
|
|
30249
|
+
const marginY = Math.round(layer.height * 0.04);
|
|
30250
|
+
const left = position === "center" ? Math.round((layer.width - width) / 2) : position.endsWith("left") ? marginX : layer.width - marginX - width;
|
|
30251
|
+
const top = position === "center" ? Math.round((layer.height - height) / 2) : position.startsWith("top") ? marginY : layer.height - marginY - height;
|
|
30252
|
+
return { left, top, width, height };
|
|
30253
|
+
}
|
|
30254
|
+
function dotLottieDist() {
|
|
30255
|
+
try {
|
|
30256
|
+
return join5(createRequire(import.meta.url).resolve("@lottiefiles/dotlottie-web"), "..");
|
|
30257
|
+
} catch {
|
|
30258
|
+
const bundled = fileURLToPath(new URL(`./${BUNDLED_DOTLOTTIE_DIR}/`, import.meta.url));
|
|
30259
|
+
if (existsSync5(join5(bundled, DOTLOTTIE_WASM))) return bundled;
|
|
30260
|
+
throw new Error("The dotLottie runtime (@lottiefiles/dotlottie-web) is not installed. Reinstall VibeFrame.");
|
|
30261
|
+
}
|
|
30262
|
+
}
|
|
30263
|
+
async function copyDotLottieRuntime(dir) {
|
|
30264
|
+
const dist = dotLottieDist();
|
|
30265
|
+
await mkdir7(dir, { recursive: true });
|
|
30266
|
+
await copyFile2(join5(dist, "index.js"), join5(dir, DOTLOTTIE_SCRIPT));
|
|
30267
|
+
await copyFile2(join5(dist, DOTLOTTIE_WASM), join5(dir, DOTLOTTIE_WASM));
|
|
30268
|
+
}
|
|
30269
|
+
var LOTTIE_POSITIONS, DOTLOTTIE_SCRIPT, DOTLOTTIE_WASM, BUNDLED_DOTLOTTIE_DIR, DOTLOTTIE_BROWSER_HELPERS;
|
|
30270
|
+
var init_lottie = __esm({
|
|
30271
|
+
"src/commands/_shared/lottie.ts"() {
|
|
30272
|
+
"use strict";
|
|
30273
|
+
LOTTIE_POSITIONS = [
|
|
30274
|
+
"full",
|
|
30275
|
+
"center",
|
|
30276
|
+
"top-left",
|
|
30277
|
+
"top-right",
|
|
30278
|
+
"bottom-left",
|
|
30279
|
+
"bottom-right"
|
|
30280
|
+
];
|
|
30281
|
+
DOTLOTTIE_SCRIPT = "dotlottie-web.js";
|
|
30282
|
+
DOTLOTTIE_WASM = "dotlottie-player.wasm";
|
|
30283
|
+
BUNDLED_DOTLOTTIE_DIR = "dotlottie";
|
|
30284
|
+
DOTLOTTIE_BROWSER_HELPERS = `async function loadDotLottie(vendorDir) {
|
|
30285
|
+
const url = (file) => new URL(vendorDir + "/" + file, document.baseURI).href;
|
|
30286
|
+
const { DotLottie } = await import(url("${DOTLOTTIE_SCRIPT}"));
|
|
30287
|
+
const wasm = await (await fetch(url("${DOTLOTTIE_WASM}"))).arrayBuffer();
|
|
30288
|
+
DotLottie.setWasmUrl(URL.createObjectURL(new Blob([wasm], { type: "application/wasm" })));
|
|
30289
|
+
return DotLottie;
|
|
30290
|
+
}
|
|
30291
|
+
function setLottieTime(player, t, loop) {
|
|
30292
|
+
const length = player.duration;
|
|
30293
|
+
const frames = player.totalFrames;
|
|
30294
|
+
if (!(length > 0) || !(frames > 0)) return;
|
|
30295
|
+
const local = Math.max(0, Number(t) || 0);
|
|
30296
|
+
const at = loop ? local % length : Math.min(local, length);
|
|
30297
|
+
player.setFrame(Math.min(frames - 1, (at / length) * frames));
|
|
30298
|
+
}`;
|
|
30299
|
+
}
|
|
30300
|
+
});
|
|
30301
|
+
|
|
30302
|
+
// src/commands/_shared/scene-lint.ts
|
|
30303
|
+
import { readdir as readdir3, readFile as readFile9, writeFile as writeFile9, stat as stat2 } from "node:fs/promises";
|
|
30304
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
30305
|
+
import { resolve as resolve9, relative as relative2, join as join6 } from "node:path";
|
|
30231
30306
|
import {
|
|
30232
30307
|
runHyperframeLint
|
|
30233
30308
|
} from "@hyperframes/producer";
|
|
30234
30309
|
async function discoverSceneFiles(opts) {
|
|
30235
30310
|
const projectDir = resolve9(opts.projectDir);
|
|
30236
30311
|
const rootAbs = resolve9(projectDir, opts.rootRel ?? "index.html");
|
|
30237
|
-
const root =
|
|
30312
|
+
const root = existsSync6(rootAbs) ? rootAbs : null;
|
|
30238
30313
|
const compsDir = resolve9(projectDir, "compositions");
|
|
30239
30314
|
const subs = [];
|
|
30240
|
-
if (
|
|
30315
|
+
if (existsSync6(compsDir)) {
|
|
30241
30316
|
await collectHtmlRecursive(compsDir, subs);
|
|
30242
30317
|
subs.sort();
|
|
30243
30318
|
}
|
|
@@ -30246,7 +30321,7 @@ async function discoverSceneFiles(opts) {
|
|
|
30246
30321
|
async function collectHtmlRecursive(dir, into) {
|
|
30247
30322
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
30248
30323
|
for (const entry of entries) {
|
|
30249
|
-
const full =
|
|
30324
|
+
const full = join6(dir, entry.name);
|
|
30250
30325
|
if (entry.isDirectory()) {
|
|
30251
30326
|
await collectHtmlRecursive(full, into);
|
|
30252
30327
|
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".html")) {
|
|
@@ -30330,7 +30405,7 @@ async function runProjectLint(opts) {
|
|
|
30330
30405
|
for (const sub of subs) targets.push({ abs: sub, isSub: true });
|
|
30331
30406
|
for (const { abs, isSub } of targets) {
|
|
30332
30407
|
const rel = relative2(projectDir, abs) || abs;
|
|
30333
|
-
const html = await
|
|
30408
|
+
const html = await readFile9(abs, "utf-8");
|
|
30334
30409
|
const prepared = {
|
|
30335
30410
|
html,
|
|
30336
30411
|
entryFile: rel,
|
|
@@ -30537,8 +30612,8 @@ var init_image_file = __esm({
|
|
|
30537
30612
|
});
|
|
30538
30613
|
|
|
30539
30614
|
// src/commands/_shared/image-jobs.ts
|
|
30540
|
-
import { existsSync as
|
|
30541
|
-
import { mkdir as
|
|
30615
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
30616
|
+
import { mkdir as mkdir8, readFile as readFile10 } from "node:fs/promises";
|
|
30542
30617
|
import { dirname as dirname5, resolve as resolve10 } from "node:path";
|
|
30543
30618
|
async function openImageGenerator(provider, apiKey) {
|
|
30544
30619
|
const envVar = IMAGE_PROVIDER_ENV[provider];
|
|
@@ -30555,14 +30630,14 @@ async function openImageGenerator(provider, apiKey) {
|
|
|
30555
30630
|
}
|
|
30556
30631
|
async function readImageInput(path) {
|
|
30557
30632
|
const absPath = resolve10(process.cwd(), path);
|
|
30558
|
-
if (!
|
|
30633
|
+
if (!existsSync7(absPath)) {
|
|
30559
30634
|
throw new ProviderError({ kind: "invalid-request", provider: "local", message: `Image not found: ${absPath}` });
|
|
30560
30635
|
}
|
|
30561
|
-
return { bytes: new Uint8Array(await
|
|
30636
|
+
return { bytes: new Uint8Array(await readFile10(absPath)), mimeType: mimeTypeForPath(path, "image/png") };
|
|
30562
30637
|
}
|
|
30563
30638
|
async function saveImage(output, bytes) {
|
|
30564
30639
|
const outputPath = resolve10(process.cwd(), output);
|
|
30565
|
-
await
|
|
30640
|
+
await mkdir8(dirname5(outputPath), { recursive: true });
|
|
30566
30641
|
await writeImageFile(outputPath, Buffer.from(bytes));
|
|
30567
30642
|
return outputPath;
|
|
30568
30643
|
}
|
|
@@ -30620,19 +30695,19 @@ var init_audio = __esm({
|
|
|
30620
30695
|
});
|
|
30621
30696
|
|
|
30622
30697
|
// src/commands/_shared/install-skill.ts
|
|
30623
|
-
import { existsSync as
|
|
30698
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
30624
30699
|
import { homedir as homedir3 } from "node:os";
|
|
30625
|
-
import { join as
|
|
30700
|
+
import { join as join7, resolve as resolve11 } from "node:path";
|
|
30626
30701
|
function hasGlobalHyperframesSkill() {
|
|
30627
|
-
return
|
|
30702
|
+
return existsSync8(join7(homedir3(), ".claude", "skills", "hyperframes", "SKILL.md"));
|
|
30628
30703
|
}
|
|
30629
30704
|
function hasProjectHyperframesSkill(projectDir) {
|
|
30630
|
-
return
|
|
30705
|
+
return existsSync8(join7(resolve11(projectDir), PROJECT_SKILL_PATH));
|
|
30631
30706
|
}
|
|
30632
30707
|
function resolveSkillReference(projectDir) {
|
|
30633
30708
|
const dir = resolve11(projectDir);
|
|
30634
|
-
if (
|
|
30635
|
-
if (
|
|
30709
|
+
if (existsSync8(join7(dir, PROJECT_SKILL_PATH))) return PROJECT_SKILL_PATH;
|
|
30710
|
+
if (existsSync8(join7(dir, "SKILL.md"))) return "SKILL.md";
|
|
30636
30711
|
return null;
|
|
30637
30712
|
}
|
|
30638
30713
|
function hyperframesSkillAvailable(projectDir) {
|
|
@@ -30693,7 +30768,7 @@ var init_install_skill = __esm({
|
|
|
30693
30768
|
init_exec_safe();
|
|
30694
30769
|
HYPERFRAMES_SKILL_PACKAGE = "heygen-com/hyperframes";
|
|
30695
30770
|
HYPERFRAMES_SKILL_INSTALL_COMMAND = `npx skills add ${HYPERFRAMES_SKILL_PACKAGE}`;
|
|
30696
|
-
PROJECT_SKILL_PATH =
|
|
30771
|
+
PROJECT_SKILL_PATH = join7(".agents", "skills", "hyperframes", "SKILL.md");
|
|
30697
30772
|
}
|
|
30698
30773
|
});
|
|
30699
30774
|
|
|
@@ -30946,17 +31021,17 @@ var init_compose_beat_prompt = __esm({
|
|
|
30946
31021
|
});
|
|
30947
31022
|
|
|
30948
31023
|
// src/commands/_shared/root-sync.ts
|
|
30949
|
-
import { existsSync as
|
|
30950
|
-
import { readFile as
|
|
30951
|
-
import { join as
|
|
31024
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
31025
|
+
import { readFile as readFile11, writeFile as writeFile11 } from "node:fs/promises";
|
|
31026
|
+
import { join as join8, resolve as resolve12 } from "node:path";
|
|
30952
31027
|
async function createRootSyncPlan(opts) {
|
|
30953
31028
|
const projectDir = resolve12(opts.projectDir);
|
|
30954
31029
|
const rootRel = opts.rootRel ?? "index.html";
|
|
30955
31030
|
const rootPath = resolve12(projectDir, rootRel);
|
|
30956
|
-
if (!
|
|
31031
|
+
if (!existsSync9(rootPath)) {
|
|
30957
31032
|
return { rootPath, rootRel, exists: false, changed: false, fixCodes: [], issues: [] };
|
|
30958
31033
|
}
|
|
30959
|
-
const html = await
|
|
31034
|
+
const html = await readFile11(rootPath, "utf-8");
|
|
30960
31035
|
if (opts.beats.length === 0) {
|
|
30961
31036
|
return { rootPath, rootRel, exists: true, changed: false, fixCodes: [], issues: [] };
|
|
30962
31037
|
}
|
|
@@ -31015,8 +31090,8 @@ async function syncRootComposition(opts) {
|
|
|
31015
31090
|
}
|
|
31016
31091
|
async function loadProjectRootSyncBeats(projectDir) {
|
|
31017
31092
|
const root = resolve12(projectDir);
|
|
31018
|
-
const storyboardPath =
|
|
31019
|
-
if (!
|
|
31093
|
+
const storyboardPath = join8(root, "STORYBOARD.md");
|
|
31094
|
+
if (!existsSync9(storyboardPath)) return [];
|
|
31020
31095
|
const parsed = parseStoryboard(await loadStoryboardMarkdown(root));
|
|
31021
31096
|
const reportBeats = await readBuildReportBeats(root);
|
|
31022
31097
|
return parsed.beats.map((beat) => {
|
|
@@ -31084,7 +31159,7 @@ async function resolveSyncedBeatDuration(opts) {
|
|
|
31084
31159
|
if (!opts.narrationPath) return Number((reported ?? storyboardMin).toFixed(2));
|
|
31085
31160
|
const probe = opts.probeAudioDuration ?? getAudioDuration;
|
|
31086
31161
|
try {
|
|
31087
|
-
const audioDuration = await probe(
|
|
31162
|
+
const audioDuration = await probe(join8(opts.projectDir, opts.narrationPath));
|
|
31088
31163
|
return Number(Math.max(storyboardMin, reported ?? 0, audioDuration + 0.5).toFixed(2));
|
|
31089
31164
|
} catch {
|
|
31090
31165
|
return Number(Math.max(storyboardMin, reported ?? 0).toFixed(2));
|
|
@@ -31242,17 +31317,17 @@ function hasExpectedAudioRef(html, ref) {
|
|
|
31242
31317
|
return ref.volume === void 0 || tag.includes(`data-volume="${ref.volume}"`);
|
|
31243
31318
|
}
|
|
31244
31319
|
async function readBuildReportBeats(projectDir) {
|
|
31245
|
-
const reportPath =
|
|
31246
|
-
if (!
|
|
31320
|
+
const reportPath = join8(projectDir, "build-report.json");
|
|
31321
|
+
if (!existsSync9(reportPath)) return [];
|
|
31247
31322
|
try {
|
|
31248
|
-
const report = JSON.parse(await
|
|
31323
|
+
const report = JSON.parse(await readFile11(reportPath, "utf-8"));
|
|
31249
31324
|
return Array.isArray(report.beats) ? report.beats : [];
|
|
31250
31325
|
} catch {
|
|
31251
31326
|
return [];
|
|
31252
31327
|
}
|
|
31253
31328
|
}
|
|
31254
31329
|
function firstExisting(projectDir, rels) {
|
|
31255
|
-
return rels.find((rel) =>
|
|
31330
|
+
return rels.find((rel) => existsSync9(join8(projectDir, rel)));
|
|
31256
31331
|
}
|
|
31257
31332
|
function stringOrUndefined(value) {
|
|
31258
31333
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -31287,13 +31362,13 @@ var init_root_sync = __esm({
|
|
|
31287
31362
|
});
|
|
31288
31363
|
|
|
31289
31364
|
// src/commands/_shared/transcription.ts
|
|
31290
|
-
import { readFile as
|
|
31291
|
-
import { basename as
|
|
31365
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
31366
|
+
import { basename as basename4 } from "node:path";
|
|
31292
31367
|
async function transcribeAudioFile(path, options) {
|
|
31293
31368
|
try {
|
|
31294
31369
|
const transcriber = await createTranscriber("openai", options.apiKey);
|
|
31295
|
-
const audio = new Uint8Array(await
|
|
31296
|
-
const name =
|
|
31370
|
+
const audio = new Uint8Array(await readFile12(path));
|
|
31371
|
+
const name = basename4(path);
|
|
31297
31372
|
const transcript = await transcriber.transcribeAudio({
|
|
31298
31373
|
audio,
|
|
31299
31374
|
// A real extension helps Whisper; temp files without one are sniffed from the bytes.
|
|
@@ -31326,9 +31401,9 @@ var init_transcription2 = __esm({
|
|
|
31326
31401
|
});
|
|
31327
31402
|
|
|
31328
31403
|
// src/commands/_shared/transcribe-narration.ts
|
|
31329
|
-
import { existsSync as
|
|
31330
|
-
import { readFile as
|
|
31331
|
-
import { join as
|
|
31404
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
31405
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
31406
|
+
import { join as join9 } from "node:path";
|
|
31332
31407
|
async function transcribeNarrationWords(audioAbsPath, opts) {
|
|
31333
31408
|
try {
|
|
31334
31409
|
const transcript = await transcribeAudioFile(audioAbsPath, { apiKey: opts.apiKey, granularity: "word", language: opts.language });
|
|
@@ -31344,10 +31419,10 @@ function beatTranscriptRelPath(beatId) {
|
|
|
31344
31419
|
return `assets/transcript-${beatId}.json`;
|
|
31345
31420
|
}
|
|
31346
31421
|
async function readBeatTranscript(projectDir, beatId) {
|
|
31347
|
-
const abs =
|
|
31348
|
-
if (!
|
|
31422
|
+
const abs = join9(projectDir, beatTranscriptRelPath(beatId));
|
|
31423
|
+
if (!existsSync10(abs)) return void 0;
|
|
31349
31424
|
try {
|
|
31350
|
-
const parsed = JSON.parse(await
|
|
31425
|
+
const parsed = JSON.parse(await readFile13(abs, "utf-8"));
|
|
31351
31426
|
if (!Array.isArray(parsed)) return void 0;
|
|
31352
31427
|
const words = parsed.filter(
|
|
31353
31428
|
(w) => typeof w === "object" && w !== null && typeof w.text === "string" && Number.isFinite(w.start) && Number.isFinite(w.end)
|
|
@@ -31365,15 +31440,15 @@ var init_transcribe_narration = __esm({
|
|
|
31365
31440
|
});
|
|
31366
31441
|
|
|
31367
31442
|
// src/commands/_shared/compose-prompts.ts
|
|
31368
|
-
import { existsSync as
|
|
31369
|
-
import { join as
|
|
31443
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
31444
|
+
import { join as join10, relative as relative3, resolve as resolve13 } from "node:path";
|
|
31370
31445
|
async function getComposePrompts(opts) {
|
|
31371
31446
|
const projectDir = resolve13(opts.projectDir);
|
|
31372
|
-
const designPath =
|
|
31373
|
-
const compositionPath =
|
|
31374
|
-
const storyboardPath =
|
|
31375
|
-
const compositionsDir =
|
|
31376
|
-
const compositionReference =
|
|
31447
|
+
const designPath = join10(projectDir, "DESIGN.md");
|
|
31448
|
+
const compositionPath = join10(projectDir, "COMPOSITION.md");
|
|
31449
|
+
const storyboardPath = join10(projectDir, "STORYBOARD.md");
|
|
31450
|
+
const compositionsDir = join10(projectDir, "compositions");
|
|
31451
|
+
const compositionReference = existsSync11(compositionPath) ? "COMPOSITION.md" : null;
|
|
31377
31452
|
const warnings = [];
|
|
31378
31453
|
const baseError = (msg) => ({
|
|
31379
31454
|
success: false,
|
|
@@ -31388,10 +31463,10 @@ async function getComposePrompts(opts) {
|
|
|
31388
31463
|
warnings,
|
|
31389
31464
|
error: msg
|
|
31390
31465
|
});
|
|
31391
|
-
if (!
|
|
31466
|
+
if (!existsSync11(designPath)) {
|
|
31392
31467
|
return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe init <dir>\` first.`);
|
|
31393
31468
|
}
|
|
31394
|
-
if (!
|
|
31469
|
+
if (!existsSync11(storyboardPath)) {
|
|
31395
31470
|
return baseError(`STORYBOARD.md not found at ${storyboardPath}. Run \`vibe init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`);
|
|
31396
31471
|
}
|
|
31397
31472
|
if (!hyperframesSkillAvailable(projectDir)) {
|
|
@@ -31423,7 +31498,7 @@ async function getComposePrompts(opts) {
|
|
|
31423
31498
|
}
|
|
31424
31499
|
const result = await Promise.all(
|
|
31425
31500
|
beats.map(async (beat) => {
|
|
31426
|
-
const outputPathAbs =
|
|
31501
|
+
const outputPathAbs = join10(compositionsDir, `scene-${beat.id}.html`);
|
|
31427
31502
|
const outputPathRel = relative3(projectDir, outputPathAbs);
|
|
31428
31503
|
const finalDurationSec = finalDurations.get(beat.id);
|
|
31429
31504
|
const transcript = await readBeatTranscript(projectDir, beat.id);
|
|
@@ -31443,7 +31518,7 @@ async function getComposePrompts(opts) {
|
|
|
31443
31518
|
body: beat.body,
|
|
31444
31519
|
userPrompt,
|
|
31445
31520
|
transcriptPath: transcript ? beatTranscriptRelPath(beat.id) : void 0,
|
|
31446
|
-
exists:
|
|
31521
|
+
exists: existsSync11(outputPathAbs)
|
|
31447
31522
|
};
|
|
31448
31523
|
})
|
|
31449
31524
|
);
|
|
@@ -31510,9 +31585,9 @@ var init_compose_prompts = __esm({
|
|
|
31510
31585
|
});
|
|
31511
31586
|
|
|
31512
31587
|
// src/commands/_shared/sub-comp-duration-sync.ts
|
|
31513
|
-
import { existsSync as
|
|
31514
|
-
import { readFile as
|
|
31515
|
-
import { join as
|
|
31588
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
31589
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
31590
|
+
import { join as join12, resolve as resolve15 } from "node:path";
|
|
31516
31591
|
function syncSubCompositionDurationHtml(html, beatId, newDurationSec, fileRel) {
|
|
31517
31592
|
const compositionId = `scene-${beatId}`;
|
|
31518
31593
|
const file = fileRel ?? `compositions/${compositionId}.html`;
|
|
@@ -31604,15 +31679,15 @@ async function createSubCompDurationSyncPlans(opts) {
|
|
|
31604
31679
|
const results = [];
|
|
31605
31680
|
for (const beat of opts.beats) {
|
|
31606
31681
|
const fileRel = `compositions/scene-${beat.id}.html`;
|
|
31607
|
-
const filePath =
|
|
31608
|
-
if (!
|
|
31682
|
+
const filePath = join12(projectDir, fileRel);
|
|
31683
|
+
if (!existsSync13(filePath)) continue;
|
|
31609
31684
|
const newDurationSec = await resolveSyncedBeatDuration({
|
|
31610
31685
|
projectDir,
|
|
31611
31686
|
beatDuration: beat.duration,
|
|
31612
31687
|
narrationPath: beat.narrationPath,
|
|
31613
31688
|
sceneDurationSec: beat.sceneDurationSec
|
|
31614
31689
|
});
|
|
31615
|
-
const html = await
|
|
31690
|
+
const html = await readFile14(filePath, "utf-8");
|
|
31616
31691
|
const outcome = syncSubCompositionDurationHtml(html, beat.id, newDurationSec, fileRel);
|
|
31617
31692
|
results.push({ ...outcome, file: fileRel, beatId: beat.id });
|
|
31618
31693
|
}
|
|
@@ -31636,7 +31711,7 @@ var init_sub_comp_duration_sync = __esm({
|
|
|
31636
31711
|
});
|
|
31637
31712
|
|
|
31638
31713
|
// src/commands/_shared/scene-repair.ts
|
|
31639
|
-
import { readFile as
|
|
31714
|
+
import { readFile as readFile15, writeFile as writeFile13 } from "node:fs/promises";
|
|
31640
31715
|
import { relative as relative4, resolve as resolve16 } from "node:path";
|
|
31641
31716
|
import { runHyperframeLint as runHyperframeLint2 } from "@hyperframes/producer";
|
|
31642
31717
|
async function executeSceneRepair(opts) {
|
|
@@ -31682,7 +31757,7 @@ async function executeSceneRepair(opts) {
|
|
|
31682
31757
|
}
|
|
31683
31758
|
for (const target of targets) {
|
|
31684
31759
|
const rel = relative4(projectDir, target.abs) || target.abs;
|
|
31685
|
-
const html = await
|
|
31760
|
+
const html = await readFile15(target.abs, "utf-8");
|
|
31686
31761
|
const findings = await lintHtml(html, rel, target.isSub);
|
|
31687
31762
|
const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
|
|
31688
31763
|
if (fixedCodes.length > 0) {
|
|
@@ -31762,15 +31837,15 @@ var init_scene_repair = __esm({
|
|
|
31762
31837
|
});
|
|
31763
31838
|
|
|
31764
31839
|
// src/pipeline/renderers/chrome.ts
|
|
31765
|
-
import { existsSync as
|
|
31840
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
31766
31841
|
import { homedir as homedir4 } from "node:os";
|
|
31767
|
-
import { join as
|
|
31842
|
+
import { join as join13 } from "node:path";
|
|
31768
31843
|
function findChrome() {
|
|
31769
31844
|
const candidates = [
|
|
31770
31845
|
process.env.HYPERFRAMES_CHROME_PATH,
|
|
31771
31846
|
process.env.CHROME_PATH,
|
|
31772
31847
|
// puppeteer auto-downloaded headless shell
|
|
31773
|
-
|
|
31848
|
+
join13(
|
|
31774
31849
|
homedir4(),
|
|
31775
31850
|
".cache",
|
|
31776
31851
|
"puppeteer",
|
|
@@ -31788,7 +31863,7 @@ function findChrome() {
|
|
|
31788
31863
|
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
|
31789
31864
|
];
|
|
31790
31865
|
for (const c of candidates) {
|
|
31791
|
-
if (c &&
|
|
31866
|
+
if (c && existsSync15(c)) return c;
|
|
31792
31867
|
}
|
|
31793
31868
|
return void 0;
|
|
31794
31869
|
}
|
|
@@ -31836,7 +31911,7 @@ var init_ffmpeg_gate = __esm({
|
|
|
31836
31911
|
});
|
|
31837
31912
|
|
|
31838
31913
|
// src/commands/_shared/scene-audio-scan.ts
|
|
31839
|
-
import { readFile as
|
|
31914
|
+
import { readFile as readFile17 } from "node:fs/promises";
|
|
31840
31915
|
import { resolve as resolve18 } from "node:path";
|
|
31841
31916
|
function parseRootClips(rootHtml) {
|
|
31842
31917
|
const clips = [];
|
|
@@ -31877,7 +31952,7 @@ function makeFsCompositionReader(projectDir) {
|
|
|
31877
31952
|
return async (compositionSrcRel) => {
|
|
31878
31953
|
const abs = resolve18(projectDir, compositionSrcRel);
|
|
31879
31954
|
try {
|
|
31880
|
-
return await
|
|
31955
|
+
return await readFile17(abs, "utf-8");
|
|
31881
31956
|
} catch {
|
|
31882
31957
|
return null;
|
|
31883
31958
|
}
|
|
@@ -31944,7 +32019,7 @@ var init_scene_audio_scan = __esm({
|
|
|
31944
32019
|
|
|
31945
32020
|
// src/commands/_shared/scene-audio-mux.ts
|
|
31946
32021
|
import { rename as rename3, unlink } from "node:fs/promises";
|
|
31947
|
-
import { resolve as resolve19, dirname as dirname7, extname as extname3, basename as
|
|
32022
|
+
import { resolve as resolve19, dirname as dirname7, extname as extname3, basename as basename5 } from "node:path";
|
|
31948
32023
|
function buildAudioMuxFilter(audios) {
|
|
31949
32024
|
if (audios.length === 0) return null;
|
|
31950
32025
|
const labels = [];
|
|
@@ -32006,7 +32081,7 @@ async function muxAudioIntoVideo(opts) {
|
|
|
32006
32081
|
const ext = extname3(opts.videoPath) || `.${opts.format}`;
|
|
32007
32082
|
const tmpPath = resolve19(
|
|
32008
32083
|
dirname7(opts.videoPath),
|
|
32009
|
-
`.${
|
|
32084
|
+
`.${basename5(opts.videoPath, ext)}.muxing${ext}`
|
|
32010
32085
|
);
|
|
32011
32086
|
const args = ["-y", "-loglevel", "error", "-i", opts.videoPath];
|
|
32012
32087
|
for (const a of opts.audios) {
|
|
@@ -32067,15 +32142,15 @@ var init_scene_audio_mux = __esm({
|
|
|
32067
32142
|
});
|
|
32068
32143
|
|
|
32069
32144
|
// src/commands/_shared/scene-assemble.ts
|
|
32070
|
-
import { existsSync as
|
|
32071
|
-
import { readFile as
|
|
32145
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
32146
|
+
import { readFile as readFile18 } from "node:fs/promises";
|
|
32072
32147
|
import { resolve as resolve20, isAbsolute } from "node:path";
|
|
32073
32148
|
async function executeSceneAssemble(opts) {
|
|
32074
32149
|
const projectDir = resolve20(opts.projectDir);
|
|
32075
32150
|
const root = opts.root ?? "index.html";
|
|
32076
32151
|
const format = opts.format ?? "mp4";
|
|
32077
32152
|
const videoPath = isAbsolute(opts.videoPath) ? opts.videoPath : resolve20(projectDir, opts.videoPath);
|
|
32078
|
-
if (!
|
|
32153
|
+
if (!existsSync16(videoPath)) {
|
|
32079
32154
|
return {
|
|
32080
32155
|
success: false,
|
|
32081
32156
|
audioCount: 0,
|
|
@@ -32085,7 +32160,7 @@ async function executeSceneAssemble(opts) {
|
|
|
32085
32160
|
};
|
|
32086
32161
|
}
|
|
32087
32162
|
const rootPath = resolve20(projectDir, root);
|
|
32088
|
-
if (!
|
|
32163
|
+
if (!existsSync16(rootPath)) {
|
|
32089
32164
|
return {
|
|
32090
32165
|
success: false,
|
|
32091
32166
|
audioCount: 0,
|
|
@@ -32099,7 +32174,7 @@ async function executeSceneAssemble(opts) {
|
|
|
32099
32174
|
let audioMuxWarning;
|
|
32100
32175
|
try {
|
|
32101
32176
|
opts.onProgress?.(0.1, "Scanning audio");
|
|
32102
|
-
const rootHtml = await
|
|
32177
|
+
const rootHtml = await readFile18(rootPath, "utf-8");
|
|
32103
32178
|
const audios = await scanSceneAudio({ projectDir, rootHtml });
|
|
32104
32179
|
audioCount = audios.length;
|
|
32105
32180
|
if (audios.length > 0) {
|
|
@@ -32152,9 +32227,9 @@ __export(scene_render_exports, {
|
|
|
32152
32227
|
qualityToCrf: () => qualityToCrf
|
|
32153
32228
|
});
|
|
32154
32229
|
import { execFile as execFile2 } from "node:child_process";
|
|
32155
|
-
import { existsSync as
|
|
32156
|
-
import { mkdir as
|
|
32157
|
-
import { resolve as resolve21, relative as relative6, dirname as dirname8, basename as
|
|
32230
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
32231
|
+
import { mkdir as mkdir11, readFile as readFile19, stat as stat3, writeFile as writeFile15 } from "node:fs/promises";
|
|
32232
|
+
import { resolve as resolve21, relative as relative6, dirname as dirname8, basename as basename6, join as join14, isAbsolute as isAbsolute2 } from "node:path";
|
|
32158
32233
|
import { promisify as promisify2 } from "node:util";
|
|
32159
32234
|
import {
|
|
32160
32235
|
createRenderJob,
|
|
@@ -32167,7 +32242,7 @@ function defaultOutputPath(opts) {
|
|
|
32167
32242
|
const fmt = opts.format ?? "mp4";
|
|
32168
32243
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
32169
32244
|
const stamp = now.toISOString().replace(/[:T]/g, "-").replace(/\..+$/, "");
|
|
32170
|
-
const name = (opts.projectName ??
|
|
32245
|
+
const name = (opts.projectName ?? basename6(resolve21(opts.projectDir))) || "scene";
|
|
32171
32246
|
const beat = opts.beatId ? `-${sanitizeFileSegment(opts.beatId)}` : "";
|
|
32172
32247
|
return resolve21(opts.projectDir, "renders", `${name}${beat}-${stamp}.${fmt}`);
|
|
32173
32248
|
}
|
|
@@ -32246,7 +32321,7 @@ async function executeSceneRender(opts = {}) {
|
|
|
32246
32321
|
}
|
|
32247
32322
|
let autoSyncApplied = false;
|
|
32248
32323
|
let autoSyncWarning;
|
|
32249
|
-
if (!opts.beatId &&
|
|
32324
|
+
if (!opts.beatId && existsSync17(join14(projectDir, "STORYBOARD.md"))) {
|
|
32250
32325
|
try {
|
|
32251
32326
|
const rootPlan = await createProjectRootSyncPlan({ projectDir });
|
|
32252
32327
|
const subPlans = await createSubCompDurationSyncPlans({
|
|
@@ -32303,7 +32378,7 @@ async function executeSceneRender(opts = {}) {
|
|
|
32303
32378
|
}
|
|
32304
32379
|
const projectName = projectConfig.config.name;
|
|
32305
32380
|
const outputPath = opts.output ? resolve21(projectDir, opts.output) : defaultOutputPath({ projectDir, projectName, format: opts.format, beatId: opts.beatId });
|
|
32306
|
-
await
|
|
32381
|
+
await mkdir11(dirname8(outputPath), { recursive: true });
|
|
32307
32382
|
const config2 = buildRenderConfig({
|
|
32308
32383
|
fps: opts.fps,
|
|
32309
32384
|
quality: opts.quality,
|
|
@@ -32415,11 +32490,11 @@ async function safeStat(p) {
|
|
|
32415
32490
|
}
|
|
32416
32491
|
async function prepareBeatRenderRoot(opts) {
|
|
32417
32492
|
const root = `.vibeframe/tmp/render-beat-${sanitizeFileSegment(opts.beatId)}.html`;
|
|
32418
|
-
const reportPath =
|
|
32493
|
+
const reportPath = join14(opts.projectDir, "build-report.json");
|
|
32419
32494
|
const retryWith = [
|
|
32420
32495
|
`vibe build ${opts.projectDir} --beat ${opts.beatId} --stage sync --json`
|
|
32421
32496
|
];
|
|
32422
|
-
if (!
|
|
32497
|
+
if (!existsSync17(reportPath)) {
|
|
32423
32498
|
return {
|
|
32424
32499
|
success: false,
|
|
32425
32500
|
root,
|
|
@@ -32430,7 +32505,7 @@ async function prepareBeatRenderRoot(opts) {
|
|
|
32430
32505
|
}
|
|
32431
32506
|
let report;
|
|
32432
32507
|
try {
|
|
32433
|
-
report = JSON.parse(await
|
|
32508
|
+
report = JSON.parse(await readFile19(reportPath, "utf-8"));
|
|
32434
32509
|
} catch (error) {
|
|
32435
32510
|
return {
|
|
32436
32511
|
success: false,
|
|
@@ -32499,8 +32574,8 @@ async function prepareBeatRenderRoot(opts) {
|
|
|
32499
32574
|
durationSec,
|
|
32500
32575
|
narrationPath
|
|
32501
32576
|
});
|
|
32502
|
-
await
|
|
32503
|
-
await writeFile15(
|
|
32577
|
+
await mkdir11(dirname8(join14(opts.projectDir, root)), { recursive: true });
|
|
32578
|
+
await writeFile15(join14(opts.projectDir, root), html, "utf-8");
|
|
32504
32579
|
return {
|
|
32505
32580
|
success: true,
|
|
32506
32581
|
root,
|
|
@@ -32564,7 +32639,7 @@ function reportedBeatAssetPaths(beat) {
|
|
|
32564
32639
|
]);
|
|
32565
32640
|
}
|
|
32566
32641
|
async function writeRenderReport(projectDir, result) {
|
|
32567
|
-
const reportPath =
|
|
32642
|
+
const reportPath = join14(projectDir, "render-report.json");
|
|
32568
32643
|
try {
|
|
32569
32644
|
await writeFile15(
|
|
32570
32645
|
reportPath,
|
|
@@ -32606,7 +32681,7 @@ async function writeRenderReport(projectDir, result) {
|
|
|
32606
32681
|
}
|
|
32607
32682
|
function pathExists3(projectDir, value) {
|
|
32608
32683
|
const abs = isAbsolute2(value) ? value : resolve21(projectDir, value);
|
|
32609
|
-
return
|
|
32684
|
+
return existsSync17(abs);
|
|
32610
32685
|
}
|
|
32611
32686
|
function stringValue(value) {
|
|
32612
32687
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -32782,9 +32857,9 @@ var init_design_parse = __esm({
|
|
|
32782
32857
|
});
|
|
32783
32858
|
|
|
32784
32859
|
// src/commands/_shared/compose-aivideo.ts
|
|
32785
|
-
import { existsSync as
|
|
32786
|
-
import { mkdir as
|
|
32787
|
-
import { join as
|
|
32860
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
32861
|
+
import { mkdir as mkdir12, readFile as readFile20, writeFile as writeFile16 } from "node:fs/promises";
|
|
32862
|
+
import { join as join15, resolve as resolve22 } from "node:path";
|
|
32788
32863
|
function headingDescriptor(heading) {
|
|
32789
32864
|
const stripped = heading.replace(/^\s*beat\s+/i, "");
|
|
32790
32865
|
const m = stripped.match(/^[A-Za-z0-9][\w-]*(?:\s*[—:]\s*|\s+-\s+)(.+)$/);
|
|
@@ -32941,20 +33016,20 @@ async function composeAiVideo(opts) {
|
|
|
32941
33016
|
const clipRels = [];
|
|
32942
33017
|
for (const beat of beats) {
|
|
32943
33018
|
const rel = clipRelFor(beat);
|
|
32944
|
-
if (!
|
|
33019
|
+
if (!existsSync18(join15(projectDir, rel))) {
|
|
32945
33020
|
throw new Error(`Missing clip for beat "${beat.id}" at ${rel} \u2014 run the assets stage first.`);
|
|
32946
33021
|
}
|
|
32947
33022
|
clipRels.push(rel);
|
|
32948
33023
|
}
|
|
32949
|
-
await
|
|
33024
|
+
await mkdir12(join15(projectDir, "assets"), { recursive: true });
|
|
32950
33025
|
const bgRel = "assets/bg-full.mp4";
|
|
32951
33026
|
const concatArgs = buildBgConcatArgs(
|
|
32952
|
-
clipRels.map((r) =>
|
|
32953
|
-
|
|
33027
|
+
clipRels.map((r) => join15(projectDir, r)),
|
|
33028
|
+
join15(projectDir, bgRel)
|
|
32954
33029
|
);
|
|
32955
33030
|
await execSafe("ffmpeg", concatArgs, { timeout: 6e5 });
|
|
32956
|
-
const totalDurationSec = await probeDurationSec(
|
|
32957
|
-
await
|
|
33031
|
+
const totalDurationSec = await probeDurationSec(join15(projectDir, bgRel));
|
|
33032
|
+
await mkdir12(join15(projectDir, "compositions"), { recursive: true });
|
|
32958
33033
|
const sceneFiles = [];
|
|
32959
33034
|
for (let i = 0; i < beats.length; i++) {
|
|
32960
33035
|
const beat = beats[i];
|
|
@@ -32971,11 +33046,11 @@ async function composeAiVideo(opts) {
|
|
|
32971
33046
|
isLast: i === beats.length - 1
|
|
32972
33047
|
});
|
|
32973
33048
|
const rel = `compositions/scene-${beat.id}.html`;
|
|
32974
|
-
await writeFile16(
|
|
33049
|
+
await writeFile16(join15(projectDir, rel), html, "utf-8");
|
|
32975
33050
|
sceneFiles.push(rel);
|
|
32976
33051
|
}
|
|
32977
|
-
const indexPath =
|
|
32978
|
-
const next =
|
|
33052
|
+
const indexPath = join15(projectDir, "index.html");
|
|
33053
|
+
const next = existsSync18(indexPath) ? injectBgVideo(await readFile20(indexPath, "utf-8"), bgRel, totalDurationSec) : buildRootShell(tokens, bgRel, totalDurationSec);
|
|
32979
33054
|
await writeFile16(indexPath, next, "utf-8");
|
|
32980
33055
|
return { bgVideoRel: bgRel, sceneFiles, totalDurationSec, beats: beats.length };
|
|
32981
33056
|
}
|
|
@@ -33024,10 +33099,10 @@ async function probeDurationSec(absPath) {
|
|
|
33024
33099
|
}
|
|
33025
33100
|
}
|
|
33026
33101
|
async function loadProjectTokens(projectDir) {
|
|
33027
|
-
const designPath =
|
|
33028
|
-
if (!
|
|
33102
|
+
const designPath = join15(projectDir, "DESIGN.md");
|
|
33103
|
+
if (!existsSync18(designPath)) return DEFAULT_AIVIDEO_TOKENS;
|
|
33029
33104
|
try {
|
|
33030
|
-
return extractDesignTokens(await
|
|
33105
|
+
return extractDesignTokens(await readFile20(designPath, "utf-8"));
|
|
33031
33106
|
} catch {
|
|
33032
33107
|
return DEFAULT_AIVIDEO_TOKENS;
|
|
33033
33108
|
}
|
|
@@ -33102,9 +33177,9 @@ var init_compose_aivideo = __esm({
|
|
|
33102
33177
|
});
|
|
33103
33178
|
|
|
33104
33179
|
// src/commands/_shared/footage-assemble.ts
|
|
33105
|
-
import { existsSync as
|
|
33106
|
-
import { mkdir as
|
|
33107
|
-
import { dirname as dirname9, isAbsolute as isAbsolute3, join as
|
|
33180
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
33181
|
+
import { mkdir as mkdir13, writeFile as writeFile17 } from "node:fs/promises";
|
|
33182
|
+
import { dirname as dirname9, isAbsolute as isAbsolute3, join as join16, resolve as resolve23 } from "node:path";
|
|
33108
33183
|
function planFootageTimeline(inputs, opts = {}) {
|
|
33109
33184
|
if (inputs.length === 0) {
|
|
33110
33185
|
throw new Error("No clips to assemble.");
|
|
@@ -33330,12 +33405,12 @@ async function executeFootageAssemble(opts) {
|
|
|
33330
33405
|
const kenburnsBeatIds = [];
|
|
33331
33406
|
for (const beat of beats) {
|
|
33332
33407
|
const clipRel = `assets/video-${beat.id}.mp4`;
|
|
33333
|
-
if (
|
|
33408
|
+
if (existsSync19(join16(projectDir, clipRel))) {
|
|
33334
33409
|
sourceRels.push({ rel: clipRel, kind: "video" });
|
|
33335
33410
|
continue;
|
|
33336
33411
|
}
|
|
33337
33412
|
const keyframeRel = `assets/keyframe-${beat.id}.png`;
|
|
33338
|
-
if (
|
|
33413
|
+
if (existsSync19(join16(projectDir, keyframeRel))) {
|
|
33339
33414
|
sourceRels.push({ rel: keyframeRel, kind: "image" });
|
|
33340
33415
|
kenburnsBeatIds.push(beat.id);
|
|
33341
33416
|
continue;
|
|
@@ -33354,7 +33429,7 @@ async function executeFootageAssemble(opts) {
|
|
|
33354
33429
|
}
|
|
33355
33430
|
const narrationRels = /* @__PURE__ */ new Map();
|
|
33356
33431
|
for (let i = 0; i < beats.length; i++) {
|
|
33357
|
-
const rel = ["mp3", "wav"].map((ext) => `assets/narration-${beats[i].id}.${ext}`).find((r) =>
|
|
33432
|
+
const rel = ["mp3", "wav"].map((ext) => `assets/narration-${beats[i].id}.${ext}`).find((r) => existsSync19(join16(projectDir, r)));
|
|
33358
33433
|
if (rel) narrationRels.set(i, rel);
|
|
33359
33434
|
}
|
|
33360
33435
|
const music = resolveMusicBed(projectDir, beats[0]?.id, opts.music);
|
|
@@ -33368,9 +33443,9 @@ async function executeFootageAssemble(opts) {
|
|
|
33368
33443
|
}
|
|
33369
33444
|
const inputs = [];
|
|
33370
33445
|
for (let i = 0; i < beats.length; i++) {
|
|
33371
|
-
const clipDurationSec = sourceRels[i].kind === "image" ? beats[i].duration ?? FOOTAGE_DEFAULTS.kenburnsSec : round3(await ffprobeDuration(
|
|
33446
|
+
const clipDurationSec = sourceRels[i].kind === "image" ? beats[i].duration ?? FOOTAGE_DEFAULTS.kenburnsSec : round3(await ffprobeDuration(join16(projectDir, sourceRels[i].rel)));
|
|
33372
33447
|
const narrationRel = narrationRels.get(i);
|
|
33373
|
-
const narrationDurationSec = narrationRel ? round3(await ffprobeDuration(
|
|
33448
|
+
const narrationDurationSec = narrationRel ? round3(await ffprobeDuration(join16(projectDir, narrationRel))) : void 0;
|
|
33374
33449
|
inputs.push({ id: beats[i].id, clipDurationSec, narrationDurationSec });
|
|
33375
33450
|
}
|
|
33376
33451
|
const plan = planFootageTimeline(inputs, {
|
|
@@ -33433,14 +33508,14 @@ async function executeFootageAssemble(opts) {
|
|
|
33433
33508
|
if (opts.dryRun) {
|
|
33434
33509
|
return { success: true, dryRun: true, outputPath, report };
|
|
33435
33510
|
}
|
|
33436
|
-
await
|
|
33511
|
+
await mkdir13(dirname9(outputPath), { recursive: true });
|
|
33437
33512
|
const args = buildFootageAssembleArgs({
|
|
33438
|
-
sources: sourceRels.map((s) => ({ path:
|
|
33513
|
+
sources: sourceRels.map((s) => ({ path: join16(projectDir, s.rel), kind: s.kind })),
|
|
33439
33514
|
plan,
|
|
33440
33515
|
narrationPaths: new Map(
|
|
33441
|
-
[...narrationRels.entries()].map(([i, rel]) => [i,
|
|
33516
|
+
[...narrationRels.entries()].map(([i, rel]) => [i, join16(projectDir, rel)])
|
|
33442
33517
|
),
|
|
33443
|
-
musicPath: music.rel ? isAbsolute3(music.rel) ? music.rel :
|
|
33518
|
+
musicPath: music.rel ? isAbsolute3(music.rel) ? music.rel : join16(projectDir, music.rel) : void 0,
|
|
33444
33519
|
outputPath,
|
|
33445
33520
|
musicVolume,
|
|
33446
33521
|
finish: opts.finish
|
|
@@ -33457,7 +33532,7 @@ async function executeFootageAssemble(opts) {
|
|
|
33457
33532
|
suggestion: "Re-run with --dry-run --json to inspect the planned timeline."
|
|
33458
33533
|
};
|
|
33459
33534
|
}
|
|
33460
|
-
const reportPath =
|
|
33535
|
+
const reportPath = join16(projectDir, "assemble-report.json");
|
|
33461
33536
|
await writeFile17(reportPath, `${JSON.stringify(report, null, 2)}
|
|
33462
33537
|
`, "utf-8");
|
|
33463
33538
|
return { success: true, dryRun: false, outputPath, reportPath, report };
|
|
@@ -33465,15 +33540,15 @@ async function executeFootageAssemble(opts) {
|
|
|
33465
33540
|
function resolveMusicBed(projectDir, firstBeatId, flag) {
|
|
33466
33541
|
if (flag === "none") return { ok: true, source: "flag" };
|
|
33467
33542
|
if (flag) {
|
|
33468
|
-
const abs = isAbsolute3(flag) ? flag :
|
|
33469
|
-
if (!
|
|
33543
|
+
const abs = isAbsolute3(flag) ? flag : join16(projectDir, flag);
|
|
33544
|
+
if (!existsSync19(abs)) return { ok: false, error: `Music file not found: ${abs}` };
|
|
33470
33545
|
return { ok: true, rel: flag, source: "flag" };
|
|
33471
33546
|
}
|
|
33472
33547
|
const candidates = ["assets/music.mp3", "assets/music.wav"];
|
|
33473
33548
|
if (firstBeatId) {
|
|
33474
33549
|
candidates.push(`assets/music-${firstBeatId}.mp3`, `assets/music-${firstBeatId}.wav`);
|
|
33475
33550
|
}
|
|
33476
|
-
const rel = candidates.find((r) =>
|
|
33551
|
+
const rel = candidates.find((r) => existsSync19(join16(projectDir, r)));
|
|
33477
33552
|
return { ok: true, rel, source: "auto" };
|
|
33478
33553
|
}
|
|
33479
33554
|
var FOOTAGE_DEFAULTS, round3, round6, DEFAULT_FOOTAGE_OUTPUT;
|
|
@@ -33515,7 +33590,7 @@ var init_footage_assemble = __esm({
|
|
|
33515
33590
|
});
|
|
33516
33591
|
|
|
33517
33592
|
// src/commands/_shared/build-asset-reference.ts
|
|
33518
|
-
import { existsSync as
|
|
33593
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
33519
33594
|
import { extname as extname4, isAbsolute as isAbsolute4, relative as relative7, resolve as resolve24, sep } from "node:path";
|
|
33520
33595
|
function resolveTypedAssetReference(projectDir, kind, value) {
|
|
33521
33596
|
const raw = stringOrUndefined2(value);
|
|
@@ -33546,7 +33621,7 @@ function resolveReference(projectDir, kind, raw, source, forcedError) {
|
|
|
33546
33621
|
const absPath = isAbsolute4(raw) ? resolve24(raw) : resolve24(projectDir, raw);
|
|
33547
33622
|
const rel = relative7(resolve24(projectDir), absPath);
|
|
33548
33623
|
const withinProject = rel.length > 0 && !rel.startsWith("..") && !isAbsolute4(rel);
|
|
33549
|
-
const exists = withinProject &&
|
|
33624
|
+
const exists = withinProject && existsSync20(absPath);
|
|
33550
33625
|
let error = forcedError;
|
|
33551
33626
|
if (!error && inferred && inferred !== kind && !isNarrationAudioReference(kind, ext)) {
|
|
33552
33627
|
error = `Asset reference "${raw}" is a ${inferred} asset, not ${kind}.`;
|
|
@@ -33826,8 +33901,8 @@ var init_build_video_providers = __esm({
|
|
|
33826
33901
|
});
|
|
33827
33902
|
|
|
33828
33903
|
// src/commands/_shared/build-plan.ts
|
|
33829
|
-
import { existsSync as
|
|
33830
|
-
import { dirname as dirname10, join as
|
|
33904
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
33905
|
+
import { dirname as dirname10, join as join17, resolve as resolve25 } from "node:path";
|
|
33831
33906
|
function backdropCostUsd(quality) {
|
|
33832
33907
|
return quality === "hd" ? 0.2 : 0.05;
|
|
33833
33908
|
}
|
|
@@ -33846,11 +33921,11 @@ async function createBuildPlan(opts) {
|
|
|
33846
33921
|
const skipBackdrop = opts.skipBackdrop ?? (composerIsFootage ? true : kindPolicy.skipBackdrop ?? false);
|
|
33847
33922
|
const skipKeyframe = opts.skipKeyframe ?? kindPolicy.skipKeyframe ?? false;
|
|
33848
33923
|
const skipVideo = opts.skipVideo ?? kindPolicy.skipVideo ?? false;
|
|
33849
|
-
const storyboardPath =
|
|
33924
|
+
const storyboardPath = join17(projectDir, "STORYBOARD.md");
|
|
33850
33925
|
const warnings = [];
|
|
33851
33926
|
const retryWith = [];
|
|
33852
33927
|
const providerResolution = [];
|
|
33853
|
-
if (!
|
|
33928
|
+
if (!existsSync21(storyboardPath)) {
|
|
33854
33929
|
const validation2 = {
|
|
33855
33930
|
ok: false,
|
|
33856
33931
|
issues: [
|
|
@@ -33910,7 +33985,7 @@ async function createBuildPlan(opts) {
|
|
|
33910
33985
|
const imageQuality = opts.imageQuality ?? config2.config.build.imageQuality ?? "hd";
|
|
33911
33986
|
const imageSize2 = opts.imageSize ?? config2.config.build.imageSize ?? "1536x1024";
|
|
33912
33987
|
const imageRatio = imageRatioForSize(imageSize2);
|
|
33913
|
-
const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !
|
|
33988
|
+
const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync21(join17(projectDir, `compositions/scene-${beat.id}.html`)));
|
|
33914
33989
|
const resolved = await resolvePlanProviders({
|
|
33915
33990
|
projectDir,
|
|
33916
33991
|
config: config2,
|
|
@@ -34060,7 +34135,7 @@ async function createBuildPlan(opts) {
|
|
|
34060
34135
|
active: includeAssets
|
|
34061
34136
|
}) : null;
|
|
34062
34137
|
const compositionPath = `compositions/scene-${beat.id}.html`;
|
|
34063
|
-
const compositionExists =
|
|
34138
|
+
const compositionExists = existsSync21(join17(projectDir, compositionPath));
|
|
34064
34139
|
for (const asset of [narration, backdrop, keyframe, video, music]) {
|
|
34065
34140
|
if (!asset) continue;
|
|
34066
34141
|
if (asset.referenceError) {
|
|
@@ -34133,7 +34208,7 @@ async function createBuildPlan(opts) {
|
|
|
34133
34208
|
}
|
|
34134
34209
|
}
|
|
34135
34210
|
const validationOk = !validationIssues.some((issue) => issue.severity === "error");
|
|
34136
|
-
if (!
|
|
34211
|
+
if (!existsSync21(join17(projectDir, config2.config.composition.entry))) {
|
|
34137
34212
|
missing.add("root-composition");
|
|
34138
34213
|
if (validationOk) retryWith.push(`vibe build ${projectDir} --stage sync --json`);
|
|
34139
34214
|
}
|
|
@@ -34167,7 +34242,7 @@ async function createBuildPlan(opts) {
|
|
|
34167
34242
|
}
|
|
34168
34243
|
if (referenced.size > 0) {
|
|
34169
34244
|
const toGenerate = resolveCharacters(parsed.frontmatter).filter(
|
|
34170
|
-
(c) => referenced.has(c.name) && c.prompt && (opts.force || !
|
|
34245
|
+
(c) => referenced.has(c.name) && c.prompt && (opts.force || !existsSync21(join17(projectDir, `assets/character-${c.name}.png`)))
|
|
34171
34246
|
);
|
|
34172
34247
|
if (toGenerate.length > 0) {
|
|
34173
34248
|
estimatedCostUsd += toGenerate.length * backdropCostUsd(opts.imageQuality ?? "hd");
|
|
@@ -34280,8 +34355,8 @@ function assetPlan(opts) {
|
|
|
34280
34355
|
reason: ready ? "referenced-asset" : "invalid-reference"
|
|
34281
34356
|
};
|
|
34282
34357
|
}
|
|
34283
|
-
const canonicalExists =
|
|
34284
|
-
const cacheHit = opts.cache ?
|
|
34358
|
+
const canonicalExists = existsSync21(join17(opts.projectDir, opts.path));
|
|
34359
|
+
const cacheHit = opts.cache ? existsSync21(join17(opts.projectDir, opts.cache.path)) : false;
|
|
34285
34360
|
const freshness = assetFreshnessFromMetadata({
|
|
34286
34361
|
projectDir: opts.projectDir,
|
|
34287
34362
|
kind: opts.kind,
|
|
@@ -34490,7 +34565,7 @@ async function firstConfiguredComposer(projectDir) {
|
|
|
34490
34565
|
}
|
|
34491
34566
|
function firstExisting2(projectDir, paths) {
|
|
34492
34567
|
for (const path of paths) {
|
|
34493
|
-
if (
|
|
34568
|
+
if (existsSync21(join17(projectDir, path))) return path;
|
|
34494
34569
|
}
|
|
34495
34570
|
return null;
|
|
34496
34571
|
}
|
|
@@ -34526,12 +34601,12 @@ function unique3(items) {
|
|
|
34526
34601
|
return [...new Set(items.filter((item) => item.length > 0))];
|
|
34527
34602
|
}
|
|
34528
34603
|
function loadPlanEnv(projectDir) {
|
|
34529
|
-
(0, import_dotenv2.config)({ path:
|
|
34604
|
+
(0, import_dotenv2.config)({ path: join17(projectDir, ".env"), quiet: true });
|
|
34530
34605
|
(0, import_dotenv2.config)({ path: resolve25(process.cwd(), ".env"), quiet: true });
|
|
34531
34606
|
let dir = process.cwd();
|
|
34532
34607
|
while (dir !== dirname10(dir)) {
|
|
34533
|
-
if (
|
|
34534
|
-
(0, import_dotenv2.config)({ path:
|
|
34608
|
+
if (existsSync21(join17(dir, "pnpm-workspace.yaml"))) {
|
|
34609
|
+
(0, import_dotenv2.config)({ path: join17(dir, ".env"), quiet: true });
|
|
34535
34610
|
return;
|
|
34536
34611
|
}
|
|
34537
34612
|
dir = dirname10(dir);
|
|
@@ -34597,7 +34672,7 @@ var init_validate = __esm({
|
|
|
34597
34672
|
// src/commands/generate/music.ts
|
|
34598
34673
|
import { resolve as resolve27 } from "node:path";
|
|
34599
34674
|
import { writeFile as writeFile18 } from "node:fs/promises";
|
|
34600
|
-
import { existsSync as
|
|
34675
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
34601
34676
|
async function executeMusic(options) {
|
|
34602
34677
|
try {
|
|
34603
34678
|
const provider = options.provider || "elevenlabs";
|
|
@@ -34679,7 +34754,7 @@ function registerMusicCommand(parent) {
|
|
|
34679
34754
|
exitWithError(usageError(`Invalid provider: ${provider}`, "Available providers: elevenlabs, replicate"));
|
|
34680
34755
|
}
|
|
34681
34756
|
if (options.melody) {
|
|
34682
|
-
if (!
|
|
34757
|
+
if (!existsSync22(resolve27(process.cwd(), options.melody))) exitWithError(notFoundError(options.melody));
|
|
34683
34758
|
exitWithError(usageError("Melody conditioning requires a publicly accessible URL", "Please upload your melody file and provide the URL."));
|
|
34684
34759
|
}
|
|
34685
34760
|
if (options.dryRun) {
|
|
@@ -34784,12 +34859,12 @@ __export(scene_build_exports, {
|
|
|
34784
34859
|
isLikenessRejection: () => isLikenessRejection,
|
|
34785
34860
|
resolveSceneBuildMode: () => resolveSceneBuildMode
|
|
34786
34861
|
});
|
|
34787
|
-
import { existsSync as
|
|
34788
|
-
import { copyFile as
|
|
34789
|
-
import { dirname as dirname11, join as
|
|
34862
|
+
import { existsSync as existsSync23, readFileSync as readFileSync2 } from "node:fs";
|
|
34863
|
+
import { copyFile as copyFile4, mkdir as mkdir14, readFile as readFile21, rm as rm3, writeFile as writeFile19 } from "node:fs/promises";
|
|
34864
|
+
import { dirname as dirname11, join as join18, resolve as resolve28 } from "node:path";
|
|
34790
34865
|
function readProjectKindSync(projectDir) {
|
|
34791
34866
|
try {
|
|
34792
|
-
const raw = readFileSync2(
|
|
34867
|
+
const raw = readFileSync2(join18(projectDir, "vibe.config.json"), "utf-8");
|
|
34793
34868
|
const k = JSON.parse(raw).kind;
|
|
34794
34869
|
return typeof k === "string" && isSceneKind(k) ? k : "cinema";
|
|
34795
34870
|
} catch {
|
|
@@ -34821,7 +34896,7 @@ async function executeSceneBuild(opts) {
|
|
|
34821
34896
|
);
|
|
34822
34897
|
}
|
|
34823
34898
|
let sceneRepair = skippedSceneRepairSummary();
|
|
34824
|
-
const storyboardPath =
|
|
34899
|
+
const storyboardPath = join18(projectDir, "STORYBOARD.md");
|
|
34825
34900
|
const buildPlan = await createBuildPlan({
|
|
34826
34901
|
projectDir,
|
|
34827
34902
|
stage: selectedStage,
|
|
@@ -34893,7 +34968,7 @@ async function executeSceneBuild(opts) {
|
|
|
34893
34968
|
}
|
|
34894
34969
|
if (!buildPlan.validation.ok) {
|
|
34895
34970
|
let invalidBeats = [];
|
|
34896
|
-
if (
|
|
34971
|
+
if (existsSync23(storyboardPath)) {
|
|
34897
34972
|
invalidBeats = parseStoryboard(await loadStoryboardMarkdown(projectDir)).beats;
|
|
34898
34973
|
}
|
|
34899
34974
|
return finishBuildResult({
|
|
@@ -34918,7 +34993,7 @@ async function executeSceneBuild(opts) {
|
|
|
34918
34993
|
totalLatencyMs: Date.now() - startedAt
|
|
34919
34994
|
});
|
|
34920
34995
|
}
|
|
34921
|
-
if (!
|
|
34996
|
+
if (!existsSync23(storyboardPath)) {
|
|
34922
34997
|
return finishBuildResult({
|
|
34923
34998
|
success: false,
|
|
34924
34999
|
phase: "failed",
|
|
@@ -35154,9 +35229,9 @@ async function executeSceneBuild(opts) {
|
|
|
35154
35229
|
}
|
|
35155
35230
|
if (shouldRunStage(selectedStage, "compose")) {
|
|
35156
35231
|
if (mode === "agent") {
|
|
35157
|
-
const compositionsDir =
|
|
35232
|
+
const compositionsDir = join18(projectDir, "compositions");
|
|
35158
35233
|
const missingBeats = activeBeats.filter(
|
|
35159
|
-
(b) => !
|
|
35234
|
+
(b) => !existsSync23(join18(compositionsDir, `scene-${b.id}.html`))
|
|
35160
35235
|
);
|
|
35161
35236
|
if (missingBeats.length > 0) {
|
|
35162
35237
|
const plan = await getComposePrompts({ projectDir, beatId: opts.beatId });
|
|
@@ -35231,8 +35306,8 @@ async function executeSceneBuild(opts) {
|
|
|
35231
35306
|
if (stageReports.compose.status === "done") {
|
|
35232
35307
|
for (const beat of composer === "template" ? [] : activeBeats) {
|
|
35233
35308
|
const videoRel = `assets/video-${beat.id}.mp4`;
|
|
35234
|
-
const compositionAbs =
|
|
35235
|
-
if (!
|
|
35309
|
+
const compositionAbs = join18(projectDir, "compositions", `scene-${beat.id}.html`);
|
|
35310
|
+
if (!existsSync23(join18(projectDir, videoRel)) || !existsSync23(compositionAbs)) continue;
|
|
35236
35311
|
const html = readFileSync2(compositionAbs, "utf-8");
|
|
35237
35312
|
if (!html.includes(videoRel)) {
|
|
35238
35313
|
warnings.push(
|
|
@@ -35288,7 +35363,7 @@ async function executeSceneBuild(opts) {
|
|
|
35288
35363
|
});
|
|
35289
35364
|
}
|
|
35290
35365
|
if (composer !== "footage" && shouldRunStage(selectedStage, "sync")) {
|
|
35291
|
-
if (!
|
|
35366
|
+
if (!existsSync23(join18(projectDir, "index.html"))) {
|
|
35292
35367
|
await scaffoldSceneProject({
|
|
35293
35368
|
dir: projectDir,
|
|
35294
35369
|
name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
|
|
@@ -35590,7 +35665,7 @@ async function dispatchNarration(beat, ctx) {
|
|
|
35590
35665
|
return {
|
|
35591
35666
|
status: "cached",
|
|
35592
35667
|
path: existingRel,
|
|
35593
|
-
durationSec: await safeAudioDuration(
|
|
35668
|
+
durationSec: await safeAudioDuration(join18(ctx.projectDir, existingRel)),
|
|
35594
35669
|
provider: metadata?.provider,
|
|
35595
35670
|
cachePath: metadata?.cachePath,
|
|
35596
35671
|
cacheKey: metadata?.cacheKey,
|
|
@@ -35614,9 +35689,9 @@ async function dispatchNarration(beat, ctx) {
|
|
|
35614
35689
|
voice,
|
|
35615
35690
|
ext: resolution.audioExtension
|
|
35616
35691
|
});
|
|
35617
|
-
const cacheAbs =
|
|
35692
|
+
const cacheAbs = join18(ctx.projectDir, cache.path);
|
|
35618
35693
|
const rel = `assets/narration-${beat.id}.${resolution.audioExtension}`;
|
|
35619
|
-
const abs =
|
|
35694
|
+
const abs = join18(ctx.projectDir, rel);
|
|
35620
35695
|
if (existingRel && !ctx.force && isFreshCanonicalAsset({
|
|
35621
35696
|
projectDir: ctx.projectDir,
|
|
35622
35697
|
kind: "narration",
|
|
@@ -35630,7 +35705,7 @@ async function dispatchNarration(beat, ctx) {
|
|
|
35630
35705
|
return {
|
|
35631
35706
|
status: "cached",
|
|
35632
35707
|
path: existingRel,
|
|
35633
|
-
durationSec: await safeAudioDuration(
|
|
35708
|
+
durationSec: await safeAudioDuration(join18(ctx.projectDir, existingRel)),
|
|
35634
35709
|
provider: resolution.provider,
|
|
35635
35710
|
cachePath: cache.path,
|
|
35636
35711
|
cacheKey: cache.key,
|
|
@@ -35638,9 +35713,9 @@ async function dispatchNarration(beat, ctx) {
|
|
|
35638
35713
|
freshness: "fresh"
|
|
35639
35714
|
};
|
|
35640
35715
|
}
|
|
35641
|
-
if (
|
|
35642
|
-
await
|
|
35643
|
-
await
|
|
35716
|
+
if (existsSync23(cacheAbs) && !ctx.force) {
|
|
35717
|
+
await mkdir14(dirname11(abs), { recursive: true });
|
|
35718
|
+
await copyFile4(cacheAbs, abs);
|
|
35644
35719
|
await writeAssetMetadata({
|
|
35645
35720
|
projectDir: ctx.projectDir,
|
|
35646
35721
|
kind: "narration",
|
|
@@ -35670,13 +35745,13 @@ async function dispatchNarration(beat, ctx) {
|
|
|
35670
35745
|
ctx.onProgress({ type: "narration-failed", beatId: beat.id, error });
|
|
35671
35746
|
return { status: "failed", error };
|
|
35672
35747
|
}
|
|
35673
|
-
await
|
|
35748
|
+
await mkdir14(dirname11(abs), { recursive: true });
|
|
35674
35749
|
await writeFile19(abs, result.audioBuffer);
|
|
35675
35750
|
for (const otherExt of ["mp3", "wav"]) {
|
|
35676
|
-
const sibling =
|
|
35677
|
-
if (sibling !== abs &&
|
|
35751
|
+
const sibling = join18(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
|
|
35752
|
+
if (sibling !== abs && existsSync23(sibling)) await rm3(sibling, { force: true });
|
|
35678
35753
|
}
|
|
35679
|
-
await
|
|
35754
|
+
await mkdir14(dirname11(cacheAbs), { recursive: true });
|
|
35680
35755
|
await writeFile19(cacheAbs, result.audioBuffer);
|
|
35681
35756
|
await writeAssetMetadata({
|
|
35682
35757
|
projectDir: ctx.projectDir,
|
|
@@ -35709,8 +35784,8 @@ async function dispatchNarration(beat, ctx) {
|
|
|
35709
35784
|
async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
|
|
35710
35785
|
if (!narrationPath) return "skipped";
|
|
35711
35786
|
const relPath = beatTranscriptRelPath(beat.id);
|
|
35712
|
-
const abs =
|
|
35713
|
-
if (
|
|
35787
|
+
const abs = join18(ctx.projectDir, relPath);
|
|
35788
|
+
if (existsSync23(abs) && !ctx.force && !narrationFresh) {
|
|
35714
35789
|
ctx.onProgress({ type: "transcript-cached", beatId: beat.id, path: relPath });
|
|
35715
35790
|
return "cached";
|
|
35716
35791
|
}
|
|
@@ -35723,7 +35798,7 @@ async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
|
|
|
35723
35798
|
});
|
|
35724
35799
|
return "skipped";
|
|
35725
35800
|
}
|
|
35726
|
-
const words = await transcribeNarrationWords(
|
|
35801
|
+
const words = await transcribeNarrationWords(join18(ctx.projectDir, narrationPath), { apiKey });
|
|
35727
35802
|
if (words.length === 0) {
|
|
35728
35803
|
ctx.onProgress({
|
|
35729
35804
|
type: "transcript-skipped",
|
|
@@ -35733,7 +35808,7 @@ async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
|
|
|
35733
35808
|
return "skipped";
|
|
35734
35809
|
}
|
|
35735
35810
|
try {
|
|
35736
|
-
await
|
|
35811
|
+
await mkdir14(dirname11(abs), { recursive: true });
|
|
35737
35812
|
await writeFile19(abs, JSON.stringify(words, null, 2), "utf-8");
|
|
35738
35813
|
} catch (err) {
|
|
35739
35814
|
ctx.onProgress({
|
|
@@ -35781,7 +35856,7 @@ async function buildCharacters(characters, ctx) {
|
|
|
35781
35856
|
const size = ctx.imageSize ?? "1536x1024";
|
|
35782
35857
|
for (const character of characters) {
|
|
35783
35858
|
if (character.imagePath) {
|
|
35784
|
-
if (
|
|
35859
|
+
if (existsSync23(join18(ctx.projectDir, character.imagePath))) {
|
|
35785
35860
|
paths.set(character.name, character.imagePath);
|
|
35786
35861
|
ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
|
|
35787
35862
|
} else {
|
|
@@ -35794,7 +35869,7 @@ async function buildCharacters(characters, ctx) {
|
|
|
35794
35869
|
if (!character.prompt) continue;
|
|
35795
35870
|
const prompt3 = characterSheetPrompt(character.name, character.prompt);
|
|
35796
35871
|
const rel = `assets/character-${character.name}.png`;
|
|
35797
|
-
const abs =
|
|
35872
|
+
const abs = join18(ctx.projectDir, rel);
|
|
35798
35873
|
const metadataOptions = { quality: ctx.imageQuality, size };
|
|
35799
35874
|
const cache = characterCacheDescriptor({
|
|
35800
35875
|
name: character.name,
|
|
@@ -35804,7 +35879,7 @@ async function buildCharacters(characters, ctx) {
|
|
|
35804
35879
|
size,
|
|
35805
35880
|
model: ctx.imageModel
|
|
35806
35881
|
});
|
|
35807
|
-
if (
|
|
35882
|
+
if (existsSync23(abs) && !ctx.force) {
|
|
35808
35883
|
const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
|
|
35809
35884
|
if (!metadata || isFreshCanonicalAsset({
|
|
35810
35885
|
projectDir: ctx.projectDir,
|
|
@@ -35820,10 +35895,10 @@ async function buildCharacters(characters, ctx) {
|
|
|
35820
35895
|
continue;
|
|
35821
35896
|
}
|
|
35822
35897
|
}
|
|
35823
|
-
const cacheAbs =
|
|
35824
|
-
if (
|
|
35825
|
-
await
|
|
35826
|
-
await
|
|
35898
|
+
const cacheAbs = join18(ctx.projectDir, cache.path);
|
|
35899
|
+
if (existsSync23(cacheAbs) && !ctx.force) {
|
|
35900
|
+
await mkdir14(dirname11(abs), { recursive: true });
|
|
35901
|
+
await copyFile4(cacheAbs, abs);
|
|
35827
35902
|
await writeAssetMetadata({
|
|
35828
35903
|
projectDir: ctx.projectDir,
|
|
35829
35904
|
kind: "character",
|
|
@@ -35855,9 +35930,9 @@ async function buildCharacters(characters, ctx) {
|
|
|
35855
35930
|
ctx.onProgress({ type: "character-failed", name: character.name, error: generated.error });
|
|
35856
35931
|
continue;
|
|
35857
35932
|
}
|
|
35858
|
-
await
|
|
35933
|
+
await mkdir14(dirname11(abs), { recursive: true });
|
|
35859
35934
|
await writeImageFile(abs, generated.buffer);
|
|
35860
|
-
await
|
|
35935
|
+
await mkdir14(dirname11(cacheAbs), { recursive: true });
|
|
35861
35936
|
await writeImageFile(cacheAbs, generated.buffer);
|
|
35862
35937
|
await writeAssetMetadata({
|
|
35863
35938
|
projectDir: ctx.projectDir,
|
|
@@ -35888,7 +35963,7 @@ async function dispatchBackdrop(beat, ctx) {
|
|
|
35888
35963
|
if (!cue) return { status: "no-cue" };
|
|
35889
35964
|
const prompt3 = augmentBackdropPrompt(cue);
|
|
35890
35965
|
const rel = `assets/backdrop-${beat.id}.png`;
|
|
35891
|
-
const abs =
|
|
35966
|
+
const abs = join18(ctx.projectDir, rel);
|
|
35892
35967
|
const size = ctx.imageSize ?? "1536x1024";
|
|
35893
35968
|
const ratio = imageRatioForSize(size);
|
|
35894
35969
|
const metadataOptions = { quality: ctx.imageQuality, size, ratio };
|
|
@@ -35902,7 +35977,7 @@ async function dispatchBackdrop(beat, ctx) {
|
|
|
35902
35977
|
model: ctx.imageModel
|
|
35903
35978
|
});
|
|
35904
35979
|
const metadataPath = assetMetadataPath("backdrop", beat.id);
|
|
35905
|
-
if (
|
|
35980
|
+
if (existsSync23(abs) && !ctx.force) {
|
|
35906
35981
|
const metadata = readAssetMetadata(ctx.projectDir, "backdrop", beat.id);
|
|
35907
35982
|
if (!metadata || isFreshCanonicalAsset({
|
|
35908
35983
|
projectDir: ctx.projectDir,
|
|
@@ -35925,10 +36000,10 @@ async function dispatchBackdrop(beat, ctx) {
|
|
|
35925
36000
|
};
|
|
35926
36001
|
}
|
|
35927
36002
|
}
|
|
35928
|
-
const cacheAbs =
|
|
35929
|
-
if (
|
|
35930
|
-
await
|
|
35931
|
-
await
|
|
36003
|
+
const cacheAbs = join18(ctx.projectDir, cache.path);
|
|
36004
|
+
if (existsSync23(cacheAbs) && !ctx.force) {
|
|
36005
|
+
await mkdir14(dirname11(abs), { recursive: true });
|
|
36006
|
+
await copyFile4(cacheAbs, abs);
|
|
35932
36007
|
await writeAssetMetadata({
|
|
35933
36008
|
projectDir: ctx.projectDir,
|
|
35934
36009
|
kind: "backdrop",
|
|
@@ -35957,10 +36032,10 @@ async function dispatchBackdrop(beat, ctx) {
|
|
|
35957
36032
|
ctx.onProgress({ type: "backdrop-failed", beatId: beat.id, error });
|
|
35958
36033
|
return { status: "failed", error };
|
|
35959
36034
|
}
|
|
35960
|
-
await
|
|
36035
|
+
await mkdir14(dirname11(abs), { recursive: true });
|
|
35961
36036
|
const buffer = generated.buffer;
|
|
35962
36037
|
await writeImageFile(abs, buffer);
|
|
35963
|
-
await
|
|
36038
|
+
await mkdir14(dirname11(cacheAbs), { recursive: true });
|
|
35964
36039
|
await writeImageFile(cacheAbs, buffer);
|
|
35965
36040
|
await writeAssetMetadata({
|
|
35966
36041
|
projectDir: ctx.projectDir,
|
|
@@ -36023,7 +36098,7 @@ async function buildImage(prompt3, ctx, ratio, sheetBuffers) {
|
|
|
36023
36098
|
}
|
|
36024
36099
|
async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
|
|
36025
36100
|
const rel = `assets/keyframe-${beat.id}.png`;
|
|
36026
|
-
const abs =
|
|
36101
|
+
const abs = join18(ctx.projectDir, rel);
|
|
36027
36102
|
const size = ctx.imageSize ?? "1536x1024";
|
|
36028
36103
|
const ratio = imageRatioForSize(size);
|
|
36029
36104
|
const metadataOptions = { quality: ctx.imageQuality, size, ratio };
|
|
@@ -36037,7 +36112,7 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
|
|
|
36037
36112
|
characters: characterRefsRel,
|
|
36038
36113
|
model: ctx.imageModel
|
|
36039
36114
|
});
|
|
36040
|
-
if (
|
|
36115
|
+
if (existsSync23(abs) && !ctx.force) {
|
|
36041
36116
|
const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
|
|
36042
36117
|
if (!metadata || isFreshCanonicalAsset({
|
|
36043
36118
|
projectDir: ctx.projectDir,
|
|
@@ -36051,10 +36126,10 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
|
|
|
36051
36126
|
return { status: "cached", rel };
|
|
36052
36127
|
}
|
|
36053
36128
|
}
|
|
36054
|
-
const cacheAbs =
|
|
36055
|
-
if (
|
|
36056
|
-
await
|
|
36057
|
-
await
|
|
36129
|
+
const cacheAbs = join18(ctx.projectDir, cache.path);
|
|
36130
|
+
if (existsSync23(cacheAbs) && !ctx.force) {
|
|
36131
|
+
await mkdir14(dirname11(abs), { recursive: true });
|
|
36132
|
+
await copyFile4(cacheAbs, abs);
|
|
36058
36133
|
await writeAssetMetadata({
|
|
36059
36134
|
projectDir: ctx.projectDir,
|
|
36060
36135
|
kind: "keyframe",
|
|
@@ -36070,8 +36145,8 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
|
|
|
36070
36145
|
}
|
|
36071
36146
|
const sheetBuffers = [];
|
|
36072
36147
|
for (const relPath of characterRefsRel) {
|
|
36073
|
-
const sheetAbs =
|
|
36074
|
-
if (
|
|
36148
|
+
const sheetAbs = join18(ctx.projectDir, relPath);
|
|
36149
|
+
if (existsSync23(sheetAbs)) sheetBuffers.push(await readFile21(sheetAbs));
|
|
36075
36150
|
}
|
|
36076
36151
|
const imgCtx = {
|
|
36077
36152
|
projectDir: ctx.projectDir,
|
|
@@ -36083,9 +36158,9 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
|
|
|
36083
36158
|
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. ";
|
|
36084
36159
|
const generated = sheetBuffers.length > 0 ? await editKeyframeImage(`${identityLock}${keyframeCue}`, sheetBuffers, imgCtx, ratio) : await generateBackdropImage(keyframeCue, imgCtx, ratio);
|
|
36085
36160
|
if (!generated.success) return { status: "failed", error: generated.error };
|
|
36086
|
-
await
|
|
36161
|
+
await mkdir14(dirname11(abs), { recursive: true });
|
|
36087
36162
|
await writeImageFile(abs, generated.buffer);
|
|
36088
|
-
await
|
|
36163
|
+
await mkdir14(dirname11(cacheAbs), { recursive: true });
|
|
36089
36164
|
await writeImageFile(cacheAbs, generated.buffer);
|
|
36090
36165
|
await writeAssetMetadata({
|
|
36091
36166
|
projectDir: ctx.projectDir,
|
|
@@ -36128,9 +36203,9 @@ async function dispatchVideo(beat, ctx, keyframe) {
|
|
|
36128
36203
|
const providerCue = stringOrUndefined4(beat.cues?.provider);
|
|
36129
36204
|
const requestedProvider = providerCue ? resolveBuildVideoProvider(providerCue) : ctx.videoProvider;
|
|
36130
36205
|
const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
|
|
36131
|
-
const characterRefsAbs = characterRefsRel.map((p) =>
|
|
36206
|
+
const characterRefsAbs = characterRefsRel.map((p) => join18(ctx.projectDir, p));
|
|
36132
36207
|
const rel = `assets/video-${beat.id}.mp4`;
|
|
36133
|
-
const abs =
|
|
36208
|
+
const abs = join18(ctx.projectDir, rel);
|
|
36134
36209
|
const cache = videoCacheDescriptor({
|
|
36135
36210
|
beatId: beat.id,
|
|
36136
36211
|
cue: prompt3,
|
|
@@ -36142,7 +36217,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
|
|
|
36142
36217
|
keyframe: keyframeCue
|
|
36143
36218
|
});
|
|
36144
36219
|
const metadataPath = assetMetadataPath("video", beat.id);
|
|
36145
|
-
if (
|
|
36220
|
+
if (existsSync23(abs) && !ctx.force) {
|
|
36146
36221
|
const metadata = readAssetMetadata(ctx.projectDir, "video", beat.id);
|
|
36147
36222
|
if (!metadata || isFreshCanonicalAsset({
|
|
36148
36223
|
projectDir: ctx.projectDir,
|
|
@@ -36165,10 +36240,10 @@ async function dispatchVideo(beat, ctx, keyframe) {
|
|
|
36165
36240
|
};
|
|
36166
36241
|
}
|
|
36167
36242
|
}
|
|
36168
|
-
const cacheAbs =
|
|
36169
|
-
if (
|
|
36170
|
-
await
|
|
36171
|
-
await
|
|
36243
|
+
const cacheAbs = join18(ctx.projectDir, cache.path);
|
|
36244
|
+
if (existsSync23(cacheAbs) && !ctx.force) {
|
|
36245
|
+
await mkdir14(dirname11(abs), { recursive: true });
|
|
36246
|
+
await copyFile4(cacheAbs, abs);
|
|
36172
36247
|
await writeAssetMetadata({
|
|
36173
36248
|
projectDir: ctx.projectDir,
|
|
36174
36249
|
kind: "video",
|
|
@@ -36197,7 +36272,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
|
|
|
36197
36272
|
ctx.onProgress({ type: "video-skipped", beatId: beat.id, reason });
|
|
36198
36273
|
return { status: "skipped" };
|
|
36199
36274
|
}
|
|
36200
|
-
const keyframeImageAbs = keyframeImageRel ?
|
|
36275
|
+
const keyframeImageAbs = keyframeImageRel ? join18(ctx.projectDir, keyframeImageRel) : void 0;
|
|
36201
36276
|
loadSceneBuildEnv(ctx.projectDir);
|
|
36202
36277
|
let job;
|
|
36203
36278
|
const recordJob = async (provider, submitted) => {
|
|
@@ -36275,10 +36350,10 @@ async function dispatchVideo(beat, ctx, keyframe) {
|
|
|
36275
36350
|
return { status: "failed", error: fallbackError };
|
|
36276
36351
|
}
|
|
36277
36352
|
}
|
|
36278
|
-
if (result.status === "completed" &&
|
|
36353
|
+
if (result.status === "completed" && existsSync23(abs)) {
|
|
36279
36354
|
await settleJob("completed");
|
|
36280
|
-
await
|
|
36281
|
-
await
|
|
36355
|
+
await mkdir14(dirname11(cacheAbs), { recursive: true });
|
|
36356
|
+
await copyFile4(abs, cacheAbs);
|
|
36282
36357
|
await writeAssetMetadata({
|
|
36283
36358
|
projectDir: ctx.projectDir,
|
|
36284
36359
|
kind: "video",
|
|
@@ -36327,7 +36402,7 @@ async function dispatchMusic(beat, ctx) {
|
|
|
36327
36402
|
const prompt3 = stringOrUndefined4(beat.cues?.music);
|
|
36328
36403
|
if (!prompt3) return { status: "no-cue" };
|
|
36329
36404
|
const rel = `assets/music-${beat.id}.mp3`;
|
|
36330
|
-
const abs =
|
|
36405
|
+
const abs = join18(ctx.projectDir, rel);
|
|
36331
36406
|
const duration = normalizeMusicDuration(beat.duration);
|
|
36332
36407
|
const cache = musicCacheDescriptor({
|
|
36333
36408
|
beatId: beat.id,
|
|
@@ -36336,7 +36411,7 @@ async function dispatchMusic(beat, ctx) {
|
|
|
36336
36411
|
duration
|
|
36337
36412
|
});
|
|
36338
36413
|
const metadataPath = assetMetadataPath("music", beat.id);
|
|
36339
|
-
if (
|
|
36414
|
+
if (existsSync23(abs) && !ctx.force) {
|
|
36340
36415
|
const metadata = readAssetMetadata(ctx.projectDir, "music", beat.id);
|
|
36341
36416
|
if (!metadata || isFreshCanonicalAsset({
|
|
36342
36417
|
projectDir: ctx.projectDir,
|
|
@@ -36360,10 +36435,10 @@ async function dispatchMusic(beat, ctx) {
|
|
|
36360
36435
|
};
|
|
36361
36436
|
}
|
|
36362
36437
|
}
|
|
36363
|
-
const cacheAbs =
|
|
36364
|
-
if (
|
|
36365
|
-
await
|
|
36366
|
-
await
|
|
36438
|
+
const cacheAbs = join18(ctx.projectDir, cache.path);
|
|
36439
|
+
if (existsSync23(cacheAbs) && !ctx.force) {
|
|
36440
|
+
await mkdir14(dirname11(abs), { recursive: true });
|
|
36441
|
+
await copyFile4(cacheAbs, abs);
|
|
36367
36442
|
await writeAssetMetadata({
|
|
36368
36443
|
projectDir: ctx.projectDir,
|
|
36369
36444
|
kind: "music",
|
|
@@ -36435,13 +36510,13 @@ async function dispatchMusic(beat, ctx) {
|
|
|
36435
36510
|
metadataPath
|
|
36436
36511
|
};
|
|
36437
36512
|
}
|
|
36438
|
-
if (!
|
|
36513
|
+
if (!existsSync23(abs)) {
|
|
36439
36514
|
const error = result.outputPath ? `music output was not written at ${abs}` : "music generation did not return an output file";
|
|
36440
36515
|
ctx.onProgress({ type: "music-failed", beatId: beat.id, error });
|
|
36441
36516
|
return { status: "failed", error };
|
|
36442
36517
|
}
|
|
36443
|
-
await
|
|
36444
|
-
await
|
|
36518
|
+
await mkdir14(dirname11(cacheAbs), { recursive: true });
|
|
36519
|
+
await copyFile4(abs, cacheAbs);
|
|
36445
36520
|
await writeAssetMetadata({
|
|
36446
36521
|
projectDir: ctx.projectDir,
|
|
36447
36522
|
kind: "music",
|
|
@@ -36471,12 +36546,12 @@ async function dispatchMusic(beat, ctx) {
|
|
|
36471
36546
|
};
|
|
36472
36547
|
}
|
|
36473
36548
|
function loadSceneBuildEnv(projectDir) {
|
|
36474
|
-
(0, import_dotenv3.config)({ path:
|
|
36549
|
+
(0, import_dotenv3.config)({ path: join18(projectDir, ".env"), quiet: true });
|
|
36475
36550
|
(0, import_dotenv3.config)({ path: resolve28(process.cwd(), ".env"), quiet: true });
|
|
36476
36551
|
let dir = process.cwd();
|
|
36477
36552
|
while (dir !== dirname11(dir)) {
|
|
36478
|
-
if (
|
|
36479
|
-
(0, import_dotenv3.config)({ path:
|
|
36553
|
+
if (existsSync23(join18(dir, "pnpm-workspace.yaml"))) {
|
|
36554
|
+
(0, import_dotenv3.config)({ path: join18(dir, ".env"), quiet: true });
|
|
36480
36555
|
return;
|
|
36481
36556
|
}
|
|
36482
36557
|
dir = dirname11(dir);
|
|
@@ -36693,7 +36768,7 @@ function mergeBeatOutcomes(base, updates) {
|
|
|
36693
36768
|
}
|
|
36694
36769
|
function firstExisting3(projectDir, relPaths) {
|
|
36695
36770
|
for (const rel of relPaths) {
|
|
36696
|
-
if (
|
|
36771
|
+
if (existsSync23(join18(projectDir, rel))) return rel;
|
|
36697
36772
|
}
|
|
36698
36773
|
return null;
|
|
36699
36774
|
}
|
|
@@ -36718,7 +36793,7 @@ async function apiKeyForVideoProvider(provider, projectDir) {
|
|
|
36718
36793
|
return getApiKeyFromConfig(videoProviderConfigKey(provider), { cwd: projectDir });
|
|
36719
36794
|
}
|
|
36720
36795
|
async function finalizeBuildResult(projectDir, startedAt, result, reportResult = result) {
|
|
36721
|
-
const reportPath =
|
|
36796
|
+
const reportPath = join18(projectDir, "build-report.json");
|
|
36722
36797
|
for (const source of /* @__PURE__ */ new Set([result, reportResult])) {
|
|
36723
36798
|
if (source.stageReports) {
|
|
36724
36799
|
for (const report of Object.values(source.stageReports)) {
|
|
@@ -36881,7 +36956,7 @@ function normalizeReportDuration(value) {
|
|
|
36881
36956
|
}
|
|
36882
36957
|
function buildReportComposition(projectDir, result, beatId) {
|
|
36883
36958
|
const path = `compositions/scene-${beatId}.html`;
|
|
36884
|
-
const exists =
|
|
36959
|
+
const exists = existsSync23(join18(projectDir, path));
|
|
36885
36960
|
if (result.stageReports?.compose.status === "failed") return { path, exists, status: "failed" };
|
|
36886
36961
|
if (result.phase === "needs-author" && !exists) return { path, exists, status: "needs-author" };
|
|
36887
36962
|
if (exists) return { path, exists, status: "exists" };
|
|
@@ -36928,8 +37003,8 @@ function summarizeBuildBeats(projectDir, beats) {
|
|
|
36928
37003
|
let compositionsReady = 0;
|
|
36929
37004
|
for (const beat of beats) {
|
|
36930
37005
|
if (beatAssetsReady(beat)) assetsReady += 1;
|
|
36931
|
-
const compositionPath =
|
|
36932
|
-
if (
|
|
37006
|
+
const compositionPath = join18(projectDir, "compositions", `scene-${beat.beatId}.html`);
|
|
37007
|
+
if (existsSync23(compositionPath)) {
|
|
36933
37008
|
compositionsReady += 1;
|
|
36934
37009
|
} else {
|
|
36935
37010
|
needsAuthor.push(beat.beatId);
|
|
@@ -37182,15 +37257,15 @@ __export(ai_audio_exports, {
|
|
|
37182
37257
|
executeTranscribe: () => executeTranscribe,
|
|
37183
37258
|
executeVoiceClone: () => executeVoiceClone
|
|
37184
37259
|
});
|
|
37185
|
-
import { resolve as resolve30, dirname as dirname12, basename as
|
|
37186
|
-
import { mkdir as
|
|
37260
|
+
import { resolve as resolve30, dirname as dirname12, basename as basename7, extname as extname5, join as join19 } from "node:path";
|
|
37261
|
+
import { mkdir as mkdir15, mkdtemp, readFile as readFile22, rm as rm4, writeFile as writeFile20 } from "node:fs/promises";
|
|
37187
37262
|
import { tmpdir } from "node:os";
|
|
37188
|
-
import { existsSync as
|
|
37263
|
+
import { existsSync as existsSync24 } from "node:fs";
|
|
37189
37264
|
async function executeTranscribe(options) {
|
|
37190
37265
|
const { audioPath, language, output, format, apiKey } = options;
|
|
37191
37266
|
try {
|
|
37192
37267
|
const absPath = resolve30(process.cwd(), audioPath);
|
|
37193
|
-
if (!
|
|
37268
|
+
if (!existsSync24(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
|
|
37194
37269
|
const key = await getConfiguredApiKey("OPENAI_API_KEY", apiKey);
|
|
37195
37270
|
if (!key) return { success: false, error: "OPENAI_API_KEY required", errorKind: "auth" };
|
|
37196
37271
|
const result = await transcribeAudioFile(absPath, { apiKey: key, language });
|
|
@@ -37220,10 +37295,10 @@ async function executeIsolate(options) {
|
|
|
37220
37295
|
const { audioPath, output = "vocals.mp3", apiKey } = options;
|
|
37221
37296
|
try {
|
|
37222
37297
|
const absPath = resolve30(process.cwd(), audioPath);
|
|
37223
|
-
if (!
|
|
37298
|
+
if (!existsSync24(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
|
|
37224
37299
|
const key = await getConfiguredApiKey("ELEVENLABS_API_KEY", apiKey);
|
|
37225
37300
|
if (!key) return { success: false, error: "ELEVENLABS_API_KEY required", errorKind: "auth" };
|
|
37226
|
-
const audioBuffer = await
|
|
37301
|
+
const audioBuffer = await readFile22(absPath);
|
|
37227
37302
|
const elevenlabs = new ElevenLabsProvider();
|
|
37228
37303
|
await elevenlabs.initialize({ apiKey: key });
|
|
37229
37304
|
const result = await elevenlabs.isolateVocals(audioBuffer);
|
|
@@ -37248,8 +37323,8 @@ async function executeVoiceClone(options) {
|
|
|
37248
37323
|
const audioBuffers = [];
|
|
37249
37324
|
for (const samplePath of samplePaths) {
|
|
37250
37325
|
const absPath = resolve30(process.cwd(), samplePath);
|
|
37251
|
-
if (!
|
|
37252
|
-
const buffer = await
|
|
37326
|
+
if (!existsSync24(absPath)) return { success: false, error: `File not found: ${samplePath}`, errorKind: "not-found" };
|
|
37327
|
+
const buffer = await readFile22(absPath);
|
|
37253
37328
|
audioBuffers.push(buffer);
|
|
37254
37329
|
}
|
|
37255
37330
|
const elevenlabs = new ElevenLabsProvider();
|
|
@@ -37269,16 +37344,16 @@ async function executeVoiceClone(options) {
|
|
|
37269
37344
|
async function executeDub(options) {
|
|
37270
37345
|
const { mediaPath, language, source, voice, analyzeOnly, output } = options;
|
|
37271
37346
|
const absPath = resolve30(process.cwd(), mediaPath);
|
|
37272
|
-
if (!
|
|
37347
|
+
if (!existsSync24(absPath)) return { success: false, error: `File not found: ${absPath}`, errorKind: "not-found" };
|
|
37273
37348
|
const openaiKey = await getConfiguredApiKey("OPENAI_API_KEY");
|
|
37274
37349
|
if (!openaiKey) return { success: false, error: "OPENAI_API_KEY required for Whisper transcription", errorKind: "auth" };
|
|
37275
|
-
const workDir = await mkdtemp(
|
|
37350
|
+
const workDir = await mkdtemp(join19(tmpdir(), "vibe-dub-"));
|
|
37276
37351
|
try {
|
|
37277
37352
|
const ext = extname5(absPath).toLowerCase();
|
|
37278
37353
|
const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
|
|
37279
37354
|
let audioPath = absPath;
|
|
37280
37355
|
if (isVideo) {
|
|
37281
|
-
audioPath =
|
|
37356
|
+
audioPath = join19(workDir, "source.mp3");
|
|
37282
37357
|
await execSafe("ffmpeg", ["-y", "-loglevel", "error", "-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "4", audioPath]);
|
|
37283
37358
|
}
|
|
37284
37359
|
const transcript = await transcribeAudioFile(audioPath, { apiKey: openaiKey, language: source });
|
|
@@ -37319,7 +37394,7 @@ async function executeDub(options) {
|
|
|
37319
37394
|
if (!spoken.success || !spoken.audioBuffer) {
|
|
37320
37395
|
return { success: false, error: `Speech failed on segment ${i + 1}: ${spoken.error}`, errorKind: spoken.errorKind };
|
|
37321
37396
|
}
|
|
37322
|
-
const clipPath =
|
|
37397
|
+
const clipPath = join19(workDir, `line-${i}.${tts.audioExtension}`);
|
|
37323
37398
|
await writeFile20(clipPath, spoken.audioBuffer);
|
|
37324
37399
|
const slot = Math.max(0.1, (report[i + 1]?.startTime ?? seg.endTime) - seg.startTime);
|
|
37325
37400
|
const length = await getAudioDuration(clipPath);
|
|
@@ -37328,7 +37403,7 @@ async function executeDub(options) {
|
|
|
37328
37403
|
const outputExt = isVideo ? ".mp3" : ext || ".mp3";
|
|
37329
37404
|
const finalOutputPath = resolve30(
|
|
37330
37405
|
process.cwd(),
|
|
37331
|
-
output || resolve30(dirname12(absPath), `${
|
|
37406
|
+
output || resolve30(dirname12(absPath), `${basename7(absPath, extname5(absPath))}-${language}${outputExt}`)
|
|
37332
37407
|
);
|
|
37333
37408
|
await mixAtOffsets(clips, finalOutputPath);
|
|
37334
37409
|
return { ...base, outputPath: finalOutputPath };
|
|
@@ -37346,7 +37421,7 @@ async function mixAtOffsets(clips, outputPath) {
|
|
|
37346
37421
|
return `[${i}:a]${tempo}adelay=${delay}|${delay}[a${i}]`;
|
|
37347
37422
|
});
|
|
37348
37423
|
const mix = `${clips.map((_, i) => `[a${i}]`).join("")}amix=inputs=${clips.length}:normalize=0:dropout_transition=0[out]`;
|
|
37349
|
-
await
|
|
37424
|
+
await mkdir15(dirname12(outputPath), { recursive: true });
|
|
37350
37425
|
await execSafe("ffmpeg", ["-y", "-loglevel", "error", ...inputs, "-filter_complex", [...chains, mix].join(";"), "-map", "[out]", outputPath]);
|
|
37351
37426
|
}
|
|
37352
37427
|
async function executeDuck(options) {
|
|
@@ -37363,9 +37438,9 @@ async function executeDuck(options) {
|
|
|
37363
37438
|
if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
|
|
37364
37439
|
const absMusicPath = resolve30(process.cwd(), musicPath);
|
|
37365
37440
|
const absVoicePath = resolve30(process.cwd(), voicePath);
|
|
37366
|
-
if (!
|
|
37367
|
-
if (!
|
|
37368
|
-
const defaultOutput = resolve30(dirname12(absMusicPath), `${
|
|
37441
|
+
if (!existsSync24(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
|
|
37442
|
+
if (!existsSync24(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
|
|
37443
|
+
const defaultOutput = resolve30(dirname12(absMusicPath), `${basename7(absMusicPath, extname5(absMusicPath))}-ducked${extname5(absMusicPath)}`);
|
|
37369
37444
|
const outputPath = resolve30(process.cwd(), output || defaultOutput);
|
|
37370
37445
|
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`;
|
|
37371
37446
|
await execSafe("ffmpeg", [
|
|
@@ -37378,7 +37453,7 @@ async function executeDuck(options) {
|
|
|
37378
37453
|
"-y",
|
|
37379
37454
|
outputPath
|
|
37380
37455
|
], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
|
|
37381
|
-
if (!
|
|
37456
|
+
if (!existsSync24(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
|
|
37382
37457
|
return { success: true, outputPath };
|
|
37383
37458
|
} catch (error) {
|
|
37384
37459
|
return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
@@ -37401,8 +37476,8 @@ var init_ai_audio = __esm({
|
|
|
37401
37476
|
});
|
|
37402
37477
|
|
|
37403
37478
|
// src/commands/_shared/edit/silence-cut.ts
|
|
37404
|
-
import { existsSync as
|
|
37405
|
-
import { readFile as
|
|
37479
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
37480
|
+
import { readFile as readFile23 } from "node:fs/promises";
|
|
37406
37481
|
async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
|
|
37407
37482
|
const totalDuration = await getVideoDuration(videoPath);
|
|
37408
37483
|
const { stdout, stderr } = await execSafe(
|
|
@@ -37452,7 +37527,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
|
|
|
37452
37527
|
}
|
|
37453
37528
|
const gemini = new GeminiProvider();
|
|
37454
37529
|
await gemini.initialize({ apiKey: geminiApiKey });
|
|
37455
|
-
const videoBuffer = await
|
|
37530
|
+
const videoBuffer = await readFile23(videoPath);
|
|
37456
37531
|
const modelId = options.model ? resolveGeminiTextModel(options.model) : void 0;
|
|
37457
37532
|
const prompt3 = `Analyze this video and identify all silent or dead segments where there is NO meaningful content.
|
|
37458
37533
|
|
|
@@ -37528,7 +37603,7 @@ async function executeSilenceCut(options) {
|
|
|
37528
37603
|
analyzeOnly = false,
|
|
37529
37604
|
useGemini = false
|
|
37530
37605
|
} = options;
|
|
37531
|
-
if (!
|
|
37606
|
+
if (!existsSync25(videoPath)) {
|
|
37532
37607
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
37533
37608
|
}
|
|
37534
37609
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37636,9 +37711,9 @@ var init_silence_cut = __esm({
|
|
|
37636
37711
|
});
|
|
37637
37712
|
|
|
37638
37713
|
// src/commands/_shared/edit/jump-cut.ts
|
|
37639
|
-
import { existsSync as
|
|
37640
|
-
import { writeFile as writeFile21, mkdir as
|
|
37641
|
-
import { join as
|
|
37714
|
+
import { existsSync as existsSync26 } from "node:fs";
|
|
37715
|
+
import { writeFile as writeFile21, mkdir as mkdir16 } from "node:fs/promises";
|
|
37716
|
+
import { join as join20 } from "node:path";
|
|
37642
37717
|
async function transcribeWithWords(audioPath, apiKey, language) {
|
|
37643
37718
|
const result = await transcribeAudioFile(audioPath, { apiKey, language, granularity: "word" });
|
|
37644
37719
|
if (result.status !== "completed") {
|
|
@@ -37681,7 +37756,7 @@ async function executeJumpCut(options) {
|
|
|
37681
37756
|
analyzeOnly = false,
|
|
37682
37757
|
apiKey
|
|
37683
37758
|
} = options;
|
|
37684
|
-
if (!
|
|
37759
|
+
if (!existsSync26(videoPath)) {
|
|
37685
37760
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
37686
37761
|
}
|
|
37687
37762
|
if (!commandExists("ffmpeg")) {
|
|
@@ -37699,8 +37774,8 @@ async function executeJumpCut(options) {
|
|
|
37699
37774
|
}
|
|
37700
37775
|
try {
|
|
37701
37776
|
const tmpDir = `/tmp/vibe_jumpcut_${Date.now()}`;
|
|
37702
|
-
await
|
|
37703
|
-
const audioPath =
|
|
37777
|
+
await mkdir16(tmpDir, { recursive: true });
|
|
37778
|
+
const audioPath = join20(tmpDir, "audio.wav");
|
|
37704
37779
|
try {
|
|
37705
37780
|
await execSafe(
|
|
37706
37781
|
"ffmpeg",
|
|
@@ -37754,7 +37829,7 @@ async function executeJumpCut(options) {
|
|
|
37754
37829
|
const segmentPaths = [];
|
|
37755
37830
|
for (let i = 0; i < segments.length; i++) {
|
|
37756
37831
|
const seg = segments[i];
|
|
37757
|
-
const segPath =
|
|
37832
|
+
const segPath = join20(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
|
|
37758
37833
|
const duration = seg.end - seg.start;
|
|
37759
37834
|
await execSafe(
|
|
37760
37835
|
"ffmpeg",
|
|
@@ -37777,7 +37852,7 @@ async function executeJumpCut(options) {
|
|
|
37777
37852
|
segmentPaths.push(segPath);
|
|
37778
37853
|
}
|
|
37779
37854
|
const concatList = segmentPaths.map((p) => `file '${p}'`).join("\n");
|
|
37780
|
-
const listPath =
|
|
37855
|
+
const listPath = join20(tmpDir, "concat.txt");
|
|
37781
37856
|
await writeFile21(listPath, concatList);
|
|
37782
37857
|
await execSafe(
|
|
37783
37858
|
"ffmpeg",
|
|
@@ -37883,9 +37958,9 @@ __export(remotion_exports, {
|
|
|
37883
37958
|
wrapComponentWithImage: () => wrapComponentWithImage,
|
|
37884
37959
|
wrapComponentWithVideo: () => wrapComponentWithVideo
|
|
37885
37960
|
});
|
|
37886
|
-
import { writeFile as writeFile22, mkdir as
|
|
37887
|
-
import { existsSync as
|
|
37888
|
-
import { join as
|
|
37961
|
+
import { writeFile as writeFile22, mkdir as mkdir17, rm as rm5, copyFile as copyFile5 } from "node:fs/promises";
|
|
37962
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
37963
|
+
import { join as join21 } from "node:path";
|
|
37889
37964
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
37890
37965
|
import { createHash as createHash4 } from "node:crypto";
|
|
37891
37966
|
async function ensureRemotionInstalled() {
|
|
@@ -37902,8 +37977,8 @@ async function ensureRemotionInstalled() {
|
|
|
37902
37977
|
}
|
|
37903
37978
|
}
|
|
37904
37979
|
async function scaffoldRemotionProject(componentCode, componentName, opts) {
|
|
37905
|
-
const dir =
|
|
37906
|
-
await
|
|
37980
|
+
const dir = join21(tmpdir2(), `vibe_motion_${Date.now()}`);
|
|
37981
|
+
await mkdir17(dir, { recursive: true });
|
|
37907
37982
|
const deps = {
|
|
37908
37983
|
remotion: REMOTION_VERSION,
|
|
37909
37984
|
"@remotion/cli": REMOTION_VERSION,
|
|
@@ -37921,7 +37996,7 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
|
|
|
37921
37996
|
dependencies: deps
|
|
37922
37997
|
};
|
|
37923
37998
|
const packageJsonStr = JSON.stringify(packageJson, null, 2);
|
|
37924
|
-
await writeFile22(
|
|
37999
|
+
await writeFile22(join21(dir, "package.json"), packageJsonStr);
|
|
37925
38000
|
const tsconfig = {
|
|
37926
38001
|
compilerOptions: {
|
|
37927
38002
|
target: "ES2020",
|
|
@@ -37933,8 +38008,8 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
|
|
|
37933
38008
|
skipLibCheck: true
|
|
37934
38009
|
}
|
|
37935
38010
|
};
|
|
37936
|
-
await writeFile22(
|
|
37937
|
-
await writeFile22(
|
|
38011
|
+
await writeFile22(join21(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
|
|
38012
|
+
await writeFile22(join21(dir, "Component.tsx"), componentCode);
|
|
37938
38013
|
const rootCode = `import { registerRoot, Composition } from "remotion";
|
|
37939
38014
|
import { ${componentName} } from "./Component";
|
|
37940
38015
|
|
|
@@ -37953,27 +38028,27 @@ const Root = () => {
|
|
|
37953
38028
|
|
|
37954
38029
|
registerRoot(Root);
|
|
37955
38030
|
`;
|
|
37956
|
-
await writeFile22(
|
|
38031
|
+
await writeFile22(join21(dir, "Root.tsx"), rootCode);
|
|
37957
38032
|
const depsHash = createHash4("md5").update(packageJsonStr).digest("hex").slice(0, 12);
|
|
37958
|
-
const cacheMarker =
|
|
37959
|
-
const cachedModules =
|
|
37960
|
-
if (
|
|
38033
|
+
const cacheMarker = join21(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
|
|
38034
|
+
const cachedModules = join21(REMOTION_CACHE_DIR, "node_modules");
|
|
38035
|
+
if (existsSync27(cacheMarker) && existsSync27(cachedModules)) {
|
|
37961
38036
|
const { symlink } = await import("node:fs/promises");
|
|
37962
38037
|
try {
|
|
37963
|
-
await symlink(cachedModules,
|
|
38038
|
+
await symlink(cachedModules, join21(dir, "node_modules"), "dir");
|
|
37964
38039
|
} catch {
|
|
37965
38040
|
await this_npmInstall(dir);
|
|
37966
38041
|
}
|
|
37967
38042
|
} else {
|
|
37968
38043
|
await this_npmInstall(dir);
|
|
37969
38044
|
try {
|
|
37970
|
-
await
|
|
37971
|
-
await writeFile22(
|
|
37972
|
-
if (
|
|
38045
|
+
await mkdir17(REMOTION_CACHE_DIR, { recursive: true });
|
|
38046
|
+
await writeFile22(join21(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
|
|
38047
|
+
if (existsSync27(join21(dir, "node_modules"))) {
|
|
37973
38048
|
const { rename: rename5, symlink } = await import("node:fs/promises");
|
|
37974
|
-
await rename5(
|
|
38049
|
+
await rename5(join21(dir, "node_modules"), cachedModules).catch(() => {
|
|
37975
38050
|
});
|
|
37976
|
-
await symlink(cachedModules,
|
|
38051
|
+
await symlink(cachedModules, join21(dir, "node_modules"), "dir").catch(() => {
|
|
37977
38052
|
});
|
|
37978
38053
|
await writeFile22(cacheMarker, depsHash);
|
|
37979
38054
|
}
|
|
@@ -38010,7 +38085,7 @@ async function renderMotion(options) {
|
|
|
38010
38085
|
}
|
|
38011
38086
|
);
|
|
38012
38087
|
try {
|
|
38013
|
-
const entryPoint =
|
|
38088
|
+
const entryPoint = join21(dir, "Root.tsx");
|
|
38014
38089
|
const { execFile: execFile3 } = await import("node:child_process");
|
|
38015
38090
|
const { promisify: promisify3 } = await import("node:util");
|
|
38016
38091
|
const execFileAsync3 = promisify3(execFile3);
|
|
@@ -38124,10 +38199,10 @@ async function renderWithEmbeddedImage(options) {
|
|
|
38124
38199
|
}
|
|
38125
38200
|
);
|
|
38126
38201
|
try {
|
|
38127
|
-
const publicDir =
|
|
38128
|
-
await
|
|
38129
|
-
await
|
|
38130
|
-
const entryPoint =
|
|
38202
|
+
const publicDir = join21(dir, "public");
|
|
38203
|
+
await mkdir17(publicDir, { recursive: true });
|
|
38204
|
+
await copyFile5(options.imagePath, join21(publicDir, options.imageFileName));
|
|
38205
|
+
const entryPoint = join21(dir, "Root.tsx");
|
|
38131
38206
|
const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
|
|
38132
38207
|
const { execFile: execFileImg } = await import("node:child_process");
|
|
38133
38208
|
const { promisify: promisifyImg } = await import("node:util");
|
|
@@ -38188,10 +38263,10 @@ async function renderWithEmbeddedVideo(options) {
|
|
|
38188
38263
|
}
|
|
38189
38264
|
);
|
|
38190
38265
|
try {
|
|
38191
|
-
const publicDir =
|
|
38192
|
-
await
|
|
38193
|
-
await
|
|
38194
|
-
const entryPoint =
|
|
38266
|
+
const publicDir = join21(dir, "public");
|
|
38267
|
+
await mkdir17(publicDir, { recursive: true });
|
|
38268
|
+
await copyFile5(options.videoPath, join21(publicDir, options.videoFileName));
|
|
38269
|
+
const entryPoint = join21(dir, "Root.tsx");
|
|
38195
38270
|
const mp4VideoOnly = options.outputPath.replace(/\.\w+$/, "_video_only.mp4");
|
|
38196
38271
|
const { execFile: execFileVid } = await import("node:child_process");
|
|
38197
38272
|
const { promisify: promisifyVid } = await import("node:util");
|
|
@@ -38607,14 +38682,14 @@ var init_remotion = __esm({
|
|
|
38607
38682
|
"use strict";
|
|
38608
38683
|
init_exec_safe();
|
|
38609
38684
|
REMOTION_VERSION = "4.0.447";
|
|
38610
|
-
REMOTION_CACHE_DIR =
|
|
38685
|
+
REMOTION_CACHE_DIR = join21(tmpdir2(), "vibe_remotion_cache");
|
|
38611
38686
|
}
|
|
38612
38687
|
});
|
|
38613
38688
|
|
|
38614
38689
|
// src/commands/_shared/edit/caption.ts
|
|
38615
|
-
import { existsSync as
|
|
38616
|
-
import { writeFile as writeFile23, mkdir as
|
|
38617
|
-
import { dirname as dirname13, basename as
|
|
38690
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
38691
|
+
import { writeFile as writeFile23, mkdir as mkdir18 } from "node:fs/promises";
|
|
38692
|
+
import { dirname as dirname13, basename as basename8, extname as extname6, join as join22 } from "node:path";
|
|
38618
38693
|
function getCaptionForceStyle(style, fontSize, fontColor, position) {
|
|
38619
38694
|
const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
|
|
38620
38695
|
const marginV = position === "center" ? 0 : 30;
|
|
@@ -38642,7 +38717,7 @@ async function executeCaption(options) {
|
|
|
38642
38717
|
position = "bottom",
|
|
38643
38718
|
apiKey
|
|
38644
38719
|
} = options;
|
|
38645
|
-
if (!
|
|
38720
|
+
if (!existsSync28(videoPath)) {
|
|
38646
38721
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
38647
38722
|
}
|
|
38648
38723
|
if (!commandExists("ffmpeg")) {
|
|
@@ -38660,9 +38735,9 @@ async function executeCaption(options) {
|
|
|
38660
38735
|
}
|
|
38661
38736
|
try {
|
|
38662
38737
|
const tmpDir = `/tmp/vibe_caption_${Date.now()}`;
|
|
38663
|
-
await
|
|
38664
|
-
const audioPath =
|
|
38665
|
-
const srtPath =
|
|
38738
|
+
await mkdir18(tmpDir, { recursive: true });
|
|
38739
|
+
const audioPath = join22(tmpDir, "audio.wav");
|
|
38740
|
+
const srtPath = join22(tmpDir, "captions.srt");
|
|
38666
38741
|
try {
|
|
38667
38742
|
await execSafe(
|
|
38668
38743
|
"ffmpeg",
|
|
@@ -38727,9 +38802,9 @@ async function executeCaption(options) {
|
|
|
38727
38802
|
const remotionErr = await ensureRemotionInstalled2();
|
|
38728
38803
|
if (remotionErr) {
|
|
38729
38804
|
const outputDir2 = dirname13(outputPath);
|
|
38730
|
-
const outputSrtPath2 =
|
|
38805
|
+
const outputSrtPath2 = join22(
|
|
38731
38806
|
outputDir2,
|
|
38732
|
-
|
|
38807
|
+
basename8(outputPath, extname6(outputPath)) + ".srt"
|
|
38733
38808
|
);
|
|
38734
38809
|
await writeFile23(outputSrtPath2, srtContent);
|
|
38735
38810
|
return { success: false, error: `${remotionErr}
|
|
@@ -38766,9 +38841,9 @@ SRT saved to: ${outputSrtPath2}` };
|
|
|
38766
38841
|
});
|
|
38767
38842
|
if (!renderResult.success) {
|
|
38768
38843
|
const outputDir2 = dirname13(outputPath);
|
|
38769
|
-
const outputSrtPath2 =
|
|
38844
|
+
const outputSrtPath2 = join22(
|
|
38770
38845
|
outputDir2,
|
|
38771
|
-
|
|
38846
|
+
basename8(outputPath, extname6(outputPath)) + ".srt"
|
|
38772
38847
|
);
|
|
38773
38848
|
await writeFile23(outputSrtPath2, srtContent);
|
|
38774
38849
|
return {
|
|
@@ -38779,9 +38854,9 @@ SRT saved to: ${outputSrtPath2}`
|
|
|
38779
38854
|
}
|
|
38780
38855
|
}
|
|
38781
38856
|
const outputDir = dirname13(outputPath);
|
|
38782
|
-
const outputSrtPath =
|
|
38857
|
+
const outputSrtPath = join22(
|
|
38783
38858
|
outputDir,
|
|
38784
|
-
|
|
38859
|
+
basename8(outputPath, extname6(outputPath)) + ".srt"
|
|
38785
38860
|
);
|
|
38786
38861
|
await writeFile23(outputSrtPath, srtContent);
|
|
38787
38862
|
return {
|
|
@@ -38816,10 +38891,10 @@ var init_caption = __esm({
|
|
|
38816
38891
|
});
|
|
38817
38892
|
|
|
38818
38893
|
// src/commands/_shared/edit/noise-reduce.ts
|
|
38819
|
-
import { existsSync as
|
|
38894
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
38820
38895
|
async function executeNoiseReduce(options) {
|
|
38821
38896
|
const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
|
|
38822
|
-
if (!
|
|
38897
|
+
if (!existsSync29(inputPath)) {
|
|
38823
38898
|
return { success: false, error: `File not found: ${inputPath}` };
|
|
38824
38899
|
}
|
|
38825
38900
|
if (!commandExists("ffmpeg")) {
|
|
@@ -38876,7 +38951,7 @@ var init_noise_reduce = __esm({
|
|
|
38876
38951
|
});
|
|
38877
38952
|
|
|
38878
38953
|
// src/commands/_shared/edit/fade.ts
|
|
38879
|
-
import { existsSync as
|
|
38954
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
38880
38955
|
async function executeFade(options) {
|
|
38881
38956
|
const {
|
|
38882
38957
|
videoPath,
|
|
@@ -38886,7 +38961,7 @@ async function executeFade(options) {
|
|
|
38886
38961
|
audioOnly = false,
|
|
38887
38962
|
videoOnly = false
|
|
38888
38963
|
} = options;
|
|
38889
|
-
if (!
|
|
38964
|
+
if (!existsSync30(videoPath)) {
|
|
38890
38965
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
38891
38966
|
}
|
|
38892
38967
|
if (!commandExists("ffmpeg")) {
|
|
@@ -38953,8 +39028,8 @@ var init_fade = __esm({
|
|
|
38953
39028
|
});
|
|
38954
39029
|
|
|
38955
39030
|
// src/commands/_shared/edit/translate-srt.ts
|
|
38956
|
-
import { existsSync as
|
|
38957
|
-
import { readFile as
|
|
39031
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
39032
|
+
import { readFile as readFile24, writeFile as writeFile24 } from "node:fs/promises";
|
|
38958
39033
|
async function executeTranslateSrt(options) {
|
|
38959
39034
|
const {
|
|
38960
39035
|
srtPath,
|
|
@@ -38964,11 +39039,11 @@ async function executeTranslateSrt(options) {
|
|
|
38964
39039
|
sourceLanguage,
|
|
38965
39040
|
apiKey
|
|
38966
39041
|
} = options;
|
|
38967
|
-
if (!
|
|
39042
|
+
if (!existsSync31(srtPath)) {
|
|
38968
39043
|
return { success: false, error: `SRT file not found: ${srtPath}` };
|
|
38969
39044
|
}
|
|
38970
39045
|
try {
|
|
38971
|
-
const srtContent = await
|
|
39046
|
+
const srtContent = await readFile24(srtPath, "utf-8");
|
|
38972
39047
|
const segments = parseSRT(srtContent);
|
|
38973
39048
|
if (segments.length === 0) {
|
|
38974
39049
|
return { success: false, error: "No subtitle segments found in SRT file" };
|
|
@@ -39008,7 +39083,7 @@ var init_translate_srt = __esm({
|
|
|
39008
39083
|
});
|
|
39009
39084
|
|
|
39010
39085
|
// src/commands/_shared/edit/text-overlay.ts
|
|
39011
|
-
import { existsSync as
|
|
39086
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
39012
39087
|
import { resolve as resolve31 } from "node:path";
|
|
39013
39088
|
function detectSystemFont() {
|
|
39014
39089
|
const platform = process.platform;
|
|
@@ -39019,7 +39094,7 @@ function detectSystemFont() {
|
|
|
39019
39094
|
"/Library/Fonts/Arial.ttf"
|
|
39020
39095
|
];
|
|
39021
39096
|
for (const f of candidates) {
|
|
39022
|
-
if (
|
|
39097
|
+
if (existsSync32(f)) return f;
|
|
39023
39098
|
}
|
|
39024
39099
|
} else if (platform === "linux") {
|
|
39025
39100
|
const candidates = [
|
|
@@ -39028,7 +39103,7 @@ function detectSystemFont() {
|
|
|
39028
39103
|
"/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
|
|
39029
39104
|
];
|
|
39030
39105
|
for (const f of candidates) {
|
|
39031
|
-
if (
|
|
39106
|
+
if (existsSync32(f)) return f;
|
|
39032
39107
|
}
|
|
39033
39108
|
} else if (platform === "win32") {
|
|
39034
39109
|
const candidates = [
|
|
@@ -39036,7 +39111,7 @@ function detectSystemFont() {
|
|
|
39036
39111
|
"C:\\Windows\\Fonts\\segoeui.ttf"
|
|
39037
39112
|
];
|
|
39038
39113
|
for (const f of candidates) {
|
|
39039
|
-
if (
|
|
39114
|
+
if (existsSync32(f)) return f;
|
|
39040
39115
|
}
|
|
39041
39116
|
}
|
|
39042
39117
|
return null;
|
|
@@ -39057,7 +39132,7 @@ async function applyTextOverlays(options) {
|
|
|
39057
39132
|
}
|
|
39058
39133
|
const absVideoPath = resolve31(process.cwd(), videoPath);
|
|
39059
39134
|
const absOutputPath = resolve31(process.cwd(), outputPath);
|
|
39060
|
-
if (!
|
|
39135
|
+
if (!existsSync32(absVideoPath)) {
|
|
39061
39136
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
39062
39137
|
}
|
|
39063
39138
|
if (!commandExists("ffmpeg")) {
|
|
@@ -39288,8 +39363,8 @@ var init_ai_helpers = __esm({
|
|
|
39288
39363
|
});
|
|
39289
39364
|
|
|
39290
39365
|
// src/commands/ai-edit-cli.ts
|
|
39291
|
-
import { resolve as resolve32, extname as extname7, basename as
|
|
39292
|
-
import { existsSync as
|
|
39366
|
+
import { resolve as resolve32, extname as extname7, basename as basename9 } from "node:path";
|
|
39367
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
39293
39368
|
function registerEditCommands(aiCommand) {
|
|
39294
39369
|
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", `
|
|
39295
39370
|
Examples:
|
|
@@ -39306,14 +39381,14 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
39306
39381
|
validateOutputPath(options.output);
|
|
39307
39382
|
}
|
|
39308
39383
|
const absVideoPath = resolve32(process.cwd(), videoPath);
|
|
39309
|
-
if (!
|
|
39384
|
+
if (!existsSync33(absVideoPath)) {
|
|
39310
39385
|
exitWithError(notFoundError(absVideoPath));
|
|
39311
39386
|
}
|
|
39312
39387
|
if (!commandExists("ffmpeg")) {
|
|
39313
39388
|
exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
|
|
39314
39389
|
}
|
|
39315
39390
|
const ext = extname7(videoPath);
|
|
39316
|
-
const name =
|
|
39391
|
+
const name = basename9(videoPath, ext);
|
|
39317
39392
|
const outputPath = options.output || `${name}-cut${ext}`;
|
|
39318
39393
|
const useGemini = options.useGemini || false;
|
|
39319
39394
|
if (options.analyzeOnly) {
|
|
@@ -39414,7 +39489,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39414
39489
|
validateOutputPath(options.output);
|
|
39415
39490
|
}
|
|
39416
39491
|
const absVideoPath = resolve32(process.cwd(), videoPath);
|
|
39417
|
-
if (!
|
|
39492
|
+
if (!existsSync33(absVideoPath)) {
|
|
39418
39493
|
exitWithError(notFoundError(absVideoPath));
|
|
39419
39494
|
}
|
|
39420
39495
|
if (!commandExists("ffmpeg")) {
|
|
@@ -39443,7 +39518,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39443
39518
|
exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
|
|
39444
39519
|
}
|
|
39445
39520
|
const ext = extname7(videoPath);
|
|
39446
|
-
const name =
|
|
39521
|
+
const name = basename9(videoPath, ext);
|
|
39447
39522
|
const outputPath = options.output || `${name}-captioned${ext}`;
|
|
39448
39523
|
const spinner2 = ora("Starting caption process...").start();
|
|
39449
39524
|
const result = await executeCaption({
|
|
@@ -39495,7 +39570,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39495
39570
|
validateOutputPath(options.output);
|
|
39496
39571
|
}
|
|
39497
39572
|
const absInputPath = resolve32(process.cwd(), inputPath);
|
|
39498
|
-
if (!
|
|
39573
|
+
if (!existsSync33(absInputPath)) {
|
|
39499
39574
|
exitWithError(notFoundError(absInputPath));
|
|
39500
39575
|
}
|
|
39501
39576
|
if (!commandExists("ffmpeg")) {
|
|
@@ -39517,7 +39592,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39517
39592
|
return;
|
|
39518
39593
|
}
|
|
39519
39594
|
const ext = extname7(inputPath);
|
|
39520
|
-
const name =
|
|
39595
|
+
const name = basename9(inputPath, ext);
|
|
39521
39596
|
const outputPath = options.output || `${name}-denoised${ext}`;
|
|
39522
39597
|
const spinner2 = ora("Applying noise reduction...").start();
|
|
39523
39598
|
const result = await executeNoiseReduce({
|
|
@@ -39561,7 +39636,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39561
39636
|
validateOutputPath(options.output);
|
|
39562
39637
|
}
|
|
39563
39638
|
const absVideoPath = resolve32(process.cwd(), videoPath);
|
|
39564
|
-
if (!
|
|
39639
|
+
if (!existsSync33(absVideoPath)) {
|
|
39565
39640
|
exitWithError(notFoundError(absVideoPath));
|
|
39566
39641
|
}
|
|
39567
39642
|
if (!commandExists("ffmpeg")) {
|
|
@@ -39585,7 +39660,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39585
39660
|
return;
|
|
39586
39661
|
}
|
|
39587
39662
|
const ext = extname7(videoPath);
|
|
39588
|
-
const name =
|
|
39663
|
+
const name = basename9(videoPath, ext);
|
|
39589
39664
|
const outputPath = options.output || `${name}-faded${ext}`;
|
|
39590
39665
|
const spinner2 = ora("Applying fade effects...").start();
|
|
39591
39666
|
const result = await executeFade({
|
|
@@ -39636,7 +39711,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39636
39711
|
exitWithError(usageError("Target language required. Use -t or --target"));
|
|
39637
39712
|
}
|
|
39638
39713
|
const absSrtPath = resolve32(process.cwd(), srtPath);
|
|
39639
|
-
if (!
|
|
39714
|
+
if (!existsSync33(absSrtPath)) {
|
|
39640
39715
|
exitWithError(notFoundError(absSrtPath));
|
|
39641
39716
|
}
|
|
39642
39717
|
if (options.dryRun) {
|
|
@@ -39663,7 +39738,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39663
39738
|
exitWithError(authError(envKey, providerName));
|
|
39664
39739
|
}
|
|
39665
39740
|
const ext = extname7(srtPath);
|
|
39666
|
-
const name =
|
|
39741
|
+
const name = basename9(srtPath, ext);
|
|
39667
39742
|
const outputPath = options.output || `${name}-${options.target}${ext}`;
|
|
39668
39743
|
const spinner2 = ora(`Translating to ${options.target}...`).start();
|
|
39669
39744
|
const result = await executeTranslateSrt({
|
|
@@ -39712,7 +39787,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39712
39787
|
}
|
|
39713
39788
|
if (options.fillers) rejectControlChars(options.fillers);
|
|
39714
39789
|
const absVideoPath = resolve32(process.cwd(), videoPath);
|
|
39715
|
-
if (!
|
|
39790
|
+
if (!existsSync33(absVideoPath)) {
|
|
39716
39791
|
exitWithError(notFoundError(absVideoPath));
|
|
39717
39792
|
}
|
|
39718
39793
|
if (!commandExists("ffmpeg")) {
|
|
@@ -39741,7 +39816,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
39741
39816
|
exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
|
|
39742
39817
|
}
|
|
39743
39818
|
const ext = extname7(videoPath);
|
|
39744
|
-
const name =
|
|
39819
|
+
const name = basename9(videoPath, ext);
|
|
39745
39820
|
const outputPath = options.output || `${name}-jumpcut${ext}`;
|
|
39746
39821
|
const fillers = options.fillers ? options.fillers.split(",").map((f) => f.trim()) : void 0;
|
|
39747
39822
|
const spinner2 = ora("Transcribing with word-level timestamps...").start();
|
|
@@ -40200,8 +40275,8 @@ var init_project_resolver = __esm({
|
|
|
40200
40275
|
});
|
|
40201
40276
|
|
|
40202
40277
|
// src/commands/_shared/execute-fill-gaps.ts
|
|
40203
|
-
import { existsSync as
|
|
40204
|
-
import { readFile as
|
|
40278
|
+
import { existsSync as existsSync34 } from "node:fs";
|
|
40279
|
+
import { readFile as readFile25, writeFile as writeFile25, mkdir as mkdir19, rename as renameFs } from "node:fs/promises";
|
|
40205
40280
|
import { resolve as resolve34, dirname as dirname14 } from "node:path";
|
|
40206
40281
|
function detectVideoGaps(videoClips, totalDuration) {
|
|
40207
40282
|
const gaps = [];
|
|
@@ -40258,7 +40333,7 @@ function analyzeGapFillability(gaps, videoClips, sources) {
|
|
|
40258
40333
|
}
|
|
40259
40334
|
async function uploadFrame(host, framePath) {
|
|
40260
40335
|
try {
|
|
40261
|
-
const upload = await host.uploadImage(await
|
|
40336
|
+
const upload = await host.uploadImage(await readFile25(framePath), {
|
|
40262
40337
|
filename: framePath,
|
|
40263
40338
|
mimeType: "image/png"
|
|
40264
40339
|
});
|
|
@@ -40291,14 +40366,14 @@ async function executeFillGaps(options) {
|
|
|
40291
40366
|
try {
|
|
40292
40367
|
onProgress("Loading timeline...");
|
|
40293
40368
|
const filePath = await resolveTimelineFile(options.projectPath);
|
|
40294
|
-
if (!
|
|
40369
|
+
if (!existsSync34(filePath)) {
|
|
40295
40370
|
return {
|
|
40296
40371
|
success: false,
|
|
40297
40372
|
error: `Timeline file not found: ${filePath}`,
|
|
40298
40373
|
humanLines
|
|
40299
40374
|
};
|
|
40300
40375
|
}
|
|
40301
|
-
const content = await
|
|
40376
|
+
const content = await readFile25(filePath, "utf-8");
|
|
40302
40377
|
const data = JSON.parse(content);
|
|
40303
40378
|
const project = Project.fromJSON(data);
|
|
40304
40379
|
const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
|
|
@@ -40418,8 +40493,8 @@ async function executeFillGaps(options) {
|
|
|
40418
40493
|
const kling = await openVideoGenerator("kling", apiKey);
|
|
40419
40494
|
const projectDir = dirname14(filePath);
|
|
40420
40495
|
const footageDir = options.dir ? resolve34(process.cwd(), options.dir) : resolve34(projectDir, "footage");
|
|
40421
|
-
if (!
|
|
40422
|
-
await
|
|
40496
|
+
if (!existsSync34(footageDir)) {
|
|
40497
|
+
await mkdir19(footageDir, { recursive: true });
|
|
40423
40498
|
}
|
|
40424
40499
|
humanLines.push("Generating AI Videos");
|
|
40425
40500
|
let uploadHost;
|
|
@@ -40716,8 +40791,8 @@ __export(ai_motion_exports, {
|
|
|
40716
40791
|
registerMotionCommand: () => registerMotionCommand
|
|
40717
40792
|
});
|
|
40718
40793
|
import { resolve as resolve35 } from "node:path";
|
|
40719
|
-
import { existsSync as
|
|
40720
|
-
import { readFile as
|
|
40794
|
+
import { existsSync as existsSync35 } from "node:fs";
|
|
40795
|
+
import { readFile as readFile26, writeFile as writeFile26 } from "node:fs/promises";
|
|
40721
40796
|
function resolveMotionModel(alias) {
|
|
40722
40797
|
const claude = findModel("claude", "llm", alias);
|
|
40723
40798
|
if (claude) return { provider: "claude", modelId: claude.id };
|
|
@@ -40771,7 +40846,7 @@ async function executeMotion(options) {
|
|
|
40771
40846
|
};
|
|
40772
40847
|
}
|
|
40773
40848
|
const imagePath = resolve35(process.cwd(), options.image);
|
|
40774
|
-
const imageBuffer = await
|
|
40849
|
+
const imageBuffer = await readFile26(imagePath);
|
|
40775
40850
|
const gemini = new GeminiProvider();
|
|
40776
40851
|
await gemini.initialize({ apiKey: geminiApiKey });
|
|
40777
40852
|
const analysisResult = await gemini.analyzeImage(
|
|
@@ -40808,7 +40883,7 @@ Use this image analysis to inform the color palette, typography placement, and o
|
|
|
40808
40883
|
}
|
|
40809
40884
|
if (geminiApiKey) {
|
|
40810
40885
|
const videoPath = resolve35(process.cwd(), options.video);
|
|
40811
|
-
const videoBuffer = await
|
|
40886
|
+
const videoBuffer = await readFile26(videoPath);
|
|
40812
40887
|
const gemini = new GeminiProvider();
|
|
40813
40888
|
await gemini.initialize({ apiKey: geminiApiKey });
|
|
40814
40889
|
const analysisPrompt = options.understandingPrompt || `Analyze this video for motion graphics composition. Return concise production guidance:
|
|
@@ -40839,10 +40914,10 @@ Use this video understanding to place motion graphics in safe zones, match the c
|
|
|
40839
40914
|
let result;
|
|
40840
40915
|
if (options.fromTsx) {
|
|
40841
40916
|
const tsxPath = resolve35(process.cwd(), options.fromTsx);
|
|
40842
|
-
if (!
|
|
40917
|
+
if (!existsSync35(tsxPath)) {
|
|
40843
40918
|
return { success: false, error: `TSX file not found: ${tsxPath}` };
|
|
40844
40919
|
}
|
|
40845
|
-
const existingCode = await
|
|
40920
|
+
const existingCode = await readFile26(tsxPath, "utf-8");
|
|
40846
40921
|
if (useGemini) {
|
|
40847
40922
|
const gemini = new GeminiProvider();
|
|
40848
40923
|
await gemini.initialize({ apiKey });
|
|
@@ -41132,10 +41207,9 @@ var init_ai_motion = __esm({
|
|
|
41132
41207
|
});
|
|
41133
41208
|
|
|
41134
41209
|
// src/commands/edit/lottie-overlay.ts
|
|
41135
|
-
import {
|
|
41136
|
-
import { copyFile as copyFile5, mkdir as mkdir19, mkdtemp as mkdtemp2, readFile as readFile26, rm as rm6, writeFile as writeFile27 } from "node:fs/promises";
|
|
41210
|
+
import { copyFile as copyFile6, mkdir as mkdir20, mkdtemp as mkdtemp2, rm as rm6, writeFile as writeFile27 } from "node:fs/promises";
|
|
41137
41211
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
41138
|
-
import { basename as
|
|
41212
|
+
import { basename as basename10, extname as extname8, join as join23 } from "node:path";
|
|
41139
41213
|
async function probeVideoStream(path) {
|
|
41140
41214
|
const { stdout } = await execSafe("ffprobe", [
|
|
41141
41215
|
"-v",
|
|
@@ -41162,27 +41236,6 @@ async function probeVideoStream(path) {
|
|
|
41162
41236
|
duration: Number(info.format?.duration ?? 0)
|
|
41163
41237
|
};
|
|
41164
41238
|
}
|
|
41165
|
-
async function checkLottieAsset(path) {
|
|
41166
|
-
const bytes = await readFile26(path);
|
|
41167
|
-
const isZip = bytes.length > 4 && bytes[0] === 80 && bytes[1] === 75;
|
|
41168
|
-
if (isZip) return void 0;
|
|
41169
|
-
try {
|
|
41170
|
-
const json = JSON.parse(bytes.toString("utf-8"));
|
|
41171
|
-
if (Array.isArray(json.layers)) return void 0;
|
|
41172
|
-
} catch {
|
|
41173
|
-
}
|
|
41174
|
-
return `${basename9(path)} is not a Lottie animation (expected Lottie JSON or a .lottie archive).`;
|
|
41175
|
-
}
|
|
41176
|
-
function overlayBox(layer, position, scale) {
|
|
41177
|
-
if (position === "full") return { left: 0, top: 0, width: layer.width, height: layer.height };
|
|
41178
|
-
const width = Math.max(1, Math.round(layer.width * scale));
|
|
41179
|
-
const height = Math.max(1, Math.round(layer.height * scale));
|
|
41180
|
-
const marginX = Math.round(layer.width * 0.04);
|
|
41181
|
-
const marginY = Math.round(layer.height * 0.04);
|
|
41182
|
-
const left = position === "center" ? Math.round((layer.width - width) / 2) : position.endsWith("left") ? marginX : layer.width - marginX - width;
|
|
41183
|
-
const top = position === "center" ? Math.round((layer.height - height) / 2) : position.startsWith("top") ? marginY : layer.height - marginY - height;
|
|
41184
|
-
return { left, top, width, height };
|
|
41185
|
-
}
|
|
41186
41239
|
function lottieLayerHtml(opts) {
|
|
41187
41240
|
const { box } = opts;
|
|
41188
41241
|
return `<!DOCTYPE html>
|
|
@@ -41195,11 +41248,8 @@ function lottieLayerHtml(opts) {
|
|
|
41195
41248
|
</head><body>
|
|
41196
41249
|
<canvas id="lottie" width="${box.width}" height="${box.height}"></canvas>
|
|
41197
41250
|
<script type="module">
|
|
41198
|
-
|
|
41199
|
-
|
|
41200
|
-
// WebAssembly.instantiateStreaming reject it; a typed Blob URL avoids that.
|
|
41201
|
-
const wasm = await (await fetch("./vendor/dotlottie-player.wasm")).arrayBuffer();
|
|
41202
|
-
DotLottie.setWasmUrl(URL.createObjectURL(new Blob([wasm], { type: "application/wasm" })));
|
|
41251
|
+
${DOTLOTTIE_BROWSER_HELPERS}
|
|
41252
|
+
const DotLottie = await loadDotLottie("./vendor");
|
|
41203
41253
|
const LOOP = ${opts.loop};
|
|
41204
41254
|
const DURATION = ${opts.duration};
|
|
41205
41255
|
const player = new DotLottie({
|
|
@@ -41216,12 +41266,7 @@ function lottieLayerHtml(opts) {
|
|
|
41216
41266
|
window.__hf = {
|
|
41217
41267
|
duration: DURATION,
|
|
41218
41268
|
seek(t) {
|
|
41219
|
-
|
|
41220
|
-
const frames = player.totalFrames;
|
|
41221
|
-
if (!(length > 0) || !(frames > 0)) return;
|
|
41222
|
-
const local = Math.max(0, Number(t) || 0);
|
|
41223
|
-
const at = LOOP ? local % length : Math.min(local, length);
|
|
41224
|
-
player.setFrame(Math.min(frames - 1, (at / length) * frames));
|
|
41269
|
+
setLottieTime(player, t, LOOP);
|
|
41225
41270
|
},
|
|
41226
41271
|
};
|
|
41227
41272
|
window.__hf.seek(0);
|
|
@@ -41230,21 +41275,12 @@ function lottieLayerHtml(opts) {
|
|
|
41230
41275
|
</body></html>
|
|
41231
41276
|
`;
|
|
41232
41277
|
}
|
|
41233
|
-
function vendorPaths() {
|
|
41234
|
-
const require2 = createRequire(import.meta.url);
|
|
41235
|
-
const script = require2.resolve("@lottiefiles/dotlottie-web");
|
|
41236
|
-
const dist = join22(script, "..");
|
|
41237
|
-
return { script: join22(dist, "index.js"), wasm: join22(dist, "dotlottie-player.wasm") };
|
|
41238
|
-
}
|
|
41239
41278
|
async function renderLayer(opts) {
|
|
41240
41279
|
const { dir, info, options } = opts;
|
|
41241
|
-
|
|
41242
|
-
await
|
|
41243
|
-
await mkdir19(join22(dir, "assets"), { recursive: true });
|
|
41244
|
-
await copyFile5(vendor.script, join22(dir, "vendor", "dotlottie-web.js"));
|
|
41245
|
-
await copyFile5(vendor.wasm, join22(dir, "vendor", "dotlottie-player.wasm"));
|
|
41280
|
+
await copyDotLottieRuntime(join23(dir, "vendor"));
|
|
41281
|
+
await mkdir20(join23(dir, "assets"), { recursive: true });
|
|
41246
41282
|
const assetName = `animation${extname8(options.assetPath).toLowerCase()}`;
|
|
41247
|
-
await
|
|
41283
|
+
await copyFile6(options.assetPath, join23(dir, "assets", assetName));
|
|
41248
41284
|
const html = lottieLayerHtml({
|
|
41249
41285
|
width: info.width,
|
|
41250
41286
|
height: info.height,
|
|
@@ -41254,8 +41290,8 @@ async function renderLayer(opts) {
|
|
|
41254
41290
|
opacity: options.opacity,
|
|
41255
41291
|
loop: options.loop
|
|
41256
41292
|
});
|
|
41257
|
-
await writeFile27(
|
|
41258
|
-
const layerPath =
|
|
41293
|
+
await writeFile27(join23(dir, "index.html"), html, "utf-8");
|
|
41294
|
+
const layerPath = join23(dir, "layer.mov");
|
|
41259
41295
|
const { createRenderJob: createRenderJob2, executeRenderJob: executeRenderJob2 } = await import("@hyperframes/producer");
|
|
41260
41296
|
const job = createRenderJob2({
|
|
41261
41297
|
fps: Math.min(60, Math.max(1, Math.round(info.fps))),
|
|
@@ -41308,7 +41344,7 @@ async function renderLottieOverlay(options) {
|
|
|
41308
41344
|
const assetProblem = await checkLottieAsset(options.assetPath);
|
|
41309
41345
|
if (assetProblem) return { success: false, error: assetProblem };
|
|
41310
41346
|
const info = await probeVideoStream(options.videoPath);
|
|
41311
|
-
const dir = await mkdtemp2(
|
|
41347
|
+
const dir = await mkdtemp2(join23(tmpdir3(), "vibeframe-lottie-"));
|
|
41312
41348
|
try {
|
|
41313
41349
|
let layerPath;
|
|
41314
41350
|
try {
|
|
@@ -41318,7 +41354,7 @@ async function renderLottieOverlay(options) {
|
|
|
41318
41354
|
if (/__hf not ready/.test(message)) {
|
|
41319
41355
|
return {
|
|
41320
41356
|
success: false,
|
|
41321
|
-
error: `Could not load the Lottie animation ${
|
|
41357
|
+
error: `Could not load the Lottie animation ${basename10(options.assetPath)}.`,
|
|
41322
41358
|
suggestion: "Check that it opens in a Lottie player (https://lottiefiles.com/preview)."
|
|
41323
41359
|
};
|
|
41324
41360
|
}
|
|
@@ -41339,6 +41375,7 @@ var init_lottie_overlay = __esm({
|
|
|
41339
41375
|
init_chrome();
|
|
41340
41376
|
init_exec_safe();
|
|
41341
41377
|
init_stdout_guard();
|
|
41378
|
+
init_lottie();
|
|
41342
41379
|
FFMPEG_MISSING = "FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details.";
|
|
41343
41380
|
}
|
|
41344
41381
|
});
|
|
@@ -41349,8 +41386,8 @@ __export(motion_overlay_exports, {
|
|
|
41349
41386
|
executeMotionOverlay: () => executeMotionOverlay,
|
|
41350
41387
|
registerMotionOverlayCommand: () => registerMotionOverlayCommand
|
|
41351
41388
|
});
|
|
41352
|
-
import { mkdir as
|
|
41353
|
-
import { existsSync as
|
|
41389
|
+
import { mkdir as mkdir21 } from "node:fs/promises";
|
|
41390
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
41354
41391
|
import { dirname as dirname15, extname as extname9, resolve as resolve36 } from "node:path";
|
|
41355
41392
|
function normalizeUnderstand2(value) {
|
|
41356
41393
|
return value === "off" || value === "required" || value === "auto" ? value : "auto";
|
|
@@ -41385,11 +41422,11 @@ async function executeMotionOverlay(options) {
|
|
|
41385
41422
|
const validationError = assertValidRequest(options);
|
|
41386
41423
|
if (validationError) return { success: false, error: validationError };
|
|
41387
41424
|
const absVideo = resolve36(process.cwd(), options.videoPath);
|
|
41388
|
-
if (!
|
|
41425
|
+
if (!existsSync36(absVideo)) return { success: false, error: `Video file not found: ${absVideo}` };
|
|
41389
41426
|
const outputPath = options.output ? resolve36(process.cwd(), options.output) : defaultOverlayOutput(options.videoPath);
|
|
41390
41427
|
if (options.asset) {
|
|
41391
41428
|
const absAsset = resolve36(process.cwd(), options.asset);
|
|
41392
|
-
if (!
|
|
41429
|
+
if (!existsSync36(absAsset)) return { success: false, error: `Lottie asset not found: ${absAsset}` };
|
|
41393
41430
|
const videoDuration = await ffprobeDuration(absVideo);
|
|
41394
41431
|
const start = Math.max(0, options.start ?? 0);
|
|
41395
41432
|
if (start >= videoDuration) {
|
|
@@ -41397,7 +41434,7 @@ async function executeMotionOverlay(options) {
|
|
|
41397
41434
|
}
|
|
41398
41435
|
const remaining = videoDuration - start;
|
|
41399
41436
|
const position = normalizePosition(options.position);
|
|
41400
|
-
await
|
|
41437
|
+
await mkdir21(dirname15(outputPath), { recursive: true });
|
|
41401
41438
|
const result2 = await renderLottieOverlay({
|
|
41402
41439
|
videoPath: absVideo,
|
|
41403
41440
|
assetPath: absAsset,
|
|
@@ -41415,7 +41452,7 @@ async function executeMotionOverlay(options) {
|
|
|
41415
41452
|
return { success: true, outputPath, provider: "lottie" };
|
|
41416
41453
|
}
|
|
41417
41454
|
const duration = options.duration ?? await ffprobeDuration(absVideo).catch(() => void 0);
|
|
41418
|
-
await
|
|
41455
|
+
await mkdir21(dirname15(outputPath), { recursive: true });
|
|
41419
41456
|
const result = await executeMotion({
|
|
41420
41457
|
description: options.description.trim(),
|
|
41421
41458
|
video: absVideo,
|
|
@@ -41542,7 +41579,7 @@ __export(ai_image_exports, {
|
|
|
41542
41579
|
executeThumbnailBestFrame: () => executeThumbnailBestFrame
|
|
41543
41580
|
});
|
|
41544
41581
|
import { readFile as readFile27 } from "node:fs/promises";
|
|
41545
|
-
import { existsSync as
|
|
41582
|
+
import { existsSync as existsSync37 } from "node:fs";
|
|
41546
41583
|
function numberedPath(path, n) {
|
|
41547
41584
|
const dot = path.lastIndexOf(".");
|
|
41548
41585
|
return dot > path.lastIndexOf("/") ? `${path.slice(0, dot)}-${n}${path.slice(dot)}` : `${path}-${n}`;
|
|
@@ -41614,7 +41651,7 @@ async function executeThumbnailBestFrame(options) {
|
|
|
41614
41651
|
model = "flash",
|
|
41615
41652
|
apiKey
|
|
41616
41653
|
} = options;
|
|
41617
|
-
if (!
|
|
41654
|
+
if (!existsSync37(videoPath)) {
|
|
41618
41655
|
return { success: false, error: `Video not found: ${videoPath}` };
|
|
41619
41656
|
}
|
|
41620
41657
|
if (!commandExists("ffmpeg")) {
|
|
@@ -41648,7 +41685,7 @@ async function executeThumbnailBestFrame(options) {
|
|
|
41648
41685
|
reason = reasonMatch[1];
|
|
41649
41686
|
}
|
|
41650
41687
|
await execSafe("ffmpeg", ["-ss", String(timestamp), "-i", videoPath, "-frames:v", "1", "-q:v", "2", outputPath, "-y"], { timeout: 6e4, maxBuffer: 50 * 1024 * 1024 });
|
|
41651
|
-
if (!
|
|
41688
|
+
if (!existsSync37(outputPath)) {
|
|
41652
41689
|
return { success: false, error: "FFmpeg failed to extract frame" };
|
|
41653
41690
|
}
|
|
41654
41691
|
return {
|
|
@@ -42589,7 +42626,7 @@ __export(ai_analyze_exports, {
|
|
|
42589
42626
|
});
|
|
42590
42627
|
import { readFile as readFile29 } from "node:fs/promises";
|
|
42591
42628
|
import { extname as extname10, resolve as resolve39 } from "node:path";
|
|
42592
|
-
import { existsSync as
|
|
42629
|
+
import { existsSync as existsSync39 } from "node:fs";
|
|
42593
42630
|
async function executeGeminiVideo(options) {
|
|
42594
42631
|
try {
|
|
42595
42632
|
const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
|
|
@@ -42603,7 +42640,7 @@ async function executeGeminiVideo(options) {
|
|
|
42603
42640
|
videoData = options.source;
|
|
42604
42641
|
} else {
|
|
42605
42642
|
const absPath = resolve39(process.cwd(), options.source);
|
|
42606
|
-
if (!
|
|
42643
|
+
if (!existsSync39(absPath)) {
|
|
42607
42644
|
return { success: false, error: `File not found: ${absPath}` };
|
|
42608
42645
|
}
|
|
42609
42646
|
videoData = await readFile29(absPath);
|
|
@@ -42671,7 +42708,7 @@ async function executeAnalyze(options) {
|
|
|
42671
42708
|
imageBuffer = Buffer.from(await response.arrayBuffer());
|
|
42672
42709
|
} else {
|
|
42673
42710
|
const absPath = resolve39(process.cwd(), source);
|
|
42674
|
-
if (!
|
|
42711
|
+
if (!existsSync39(absPath)) {
|
|
42675
42712
|
return { success: false, error: `File not found: ${absPath}` };
|
|
42676
42713
|
}
|
|
42677
42714
|
imageBuffer = await readFile29(absPath);
|
|
@@ -42706,7 +42743,7 @@ async function executeAnalyze(options) {
|
|
|
42706
42743
|
videoData = Buffer.from(await response.arrayBuffer());
|
|
42707
42744
|
} else {
|
|
42708
42745
|
const absPath = resolve39(process.cwd(), source);
|
|
42709
|
-
if (!
|
|
42746
|
+
if (!existsSync39(absPath)) {
|
|
42710
42747
|
return { success: false, error: `File not found: ${absPath}` };
|
|
42711
42748
|
}
|
|
42712
42749
|
videoData = await readFile29(absPath);
|
|
@@ -42752,7 +42789,7 @@ __export(ai_review_exports, {
|
|
|
42752
42789
|
});
|
|
42753
42790
|
import { readFile as readFile32, rename as rename4 } from "node:fs/promises";
|
|
42754
42791
|
import { resolve as resolve42 } from "node:path";
|
|
42755
|
-
import { existsSync as
|
|
42792
|
+
import { existsSync as existsSync41 } from "node:fs";
|
|
42756
42793
|
function parseReviewFeedback(response) {
|
|
42757
42794
|
let cleaned = response.trim();
|
|
42758
42795
|
if (cleaned.startsWith("```json")) {
|
|
@@ -42784,7 +42821,7 @@ async function executeReview(options) {
|
|
|
42784
42821
|
model = "flash"
|
|
42785
42822
|
} = options;
|
|
42786
42823
|
const absVideoPath = resolve42(process.cwd(), videoPath);
|
|
42787
|
-
if (!
|
|
42824
|
+
if (!existsSync41(absVideoPath)) {
|
|
42788
42825
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
42789
42826
|
}
|
|
42790
42827
|
const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
|
|
@@ -42797,7 +42834,7 @@ async function executeReview(options) {
|
|
|
42797
42834
|
let storyboardContext = "";
|
|
42798
42835
|
if (storyboardPath) {
|
|
42799
42836
|
const absStoryboardPath = resolve42(process.cwd(), storyboardPath);
|
|
42800
|
-
if (
|
|
42837
|
+
if (existsSync41(absStoryboardPath)) {
|
|
42801
42838
|
const content = await readFile32(absStoryboardPath, "utf-8");
|
|
42802
42839
|
storyboardContext = `
|
|
42803
42840
|
|
|
@@ -43100,10 +43137,10 @@ var init_tty = __esm({
|
|
|
43100
43137
|
});
|
|
43101
43138
|
|
|
43102
43139
|
// src/utils/audio-file.ts
|
|
43103
|
-
import { mkdir as
|
|
43140
|
+
import { mkdir as mkdir23, rm as rm8, writeFile as writeFile32 } from "node:fs/promises";
|
|
43104
43141
|
import { dirname as dirname17, extname as extname11 } from "node:path";
|
|
43105
43142
|
async function writeAudioFile(path, bytes, container) {
|
|
43106
|
-
await
|
|
43143
|
+
await mkdir23(dirname17(path), { recursive: true });
|
|
43107
43144
|
const wanted = extname11(path).toLowerCase();
|
|
43108
43145
|
if (!CONVERTIBLE.has(wanted) || wanted === `.${container}`) {
|
|
43109
43146
|
await writeFile32(path, bytes);
|
|
@@ -43218,9 +43255,9 @@ var init_speech2 = __esm({
|
|
|
43218
43255
|
});
|
|
43219
43256
|
|
|
43220
43257
|
// src/commands/generate/thumbnail.ts
|
|
43221
|
-
import { resolve as resolve47, dirname as dirname18, basename as
|
|
43222
|
-
import { existsSync as
|
|
43223
|
-
import { writeFile as writeFile33, mkdir as
|
|
43258
|
+
import { resolve as resolve47, dirname as dirname18, basename as basename13, extname as extname12 } from "node:path";
|
|
43259
|
+
import { existsSync as existsSync43 } from "node:fs";
|
|
43260
|
+
import { writeFile as writeFile33, mkdir as mkdir24 } from "node:fs/promises";
|
|
43224
43261
|
function registerThumbnailCommand(parent) {
|
|
43225
43262
|
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) => {
|
|
43226
43263
|
const startedAt = Date.now();
|
|
@@ -43231,7 +43268,7 @@ function registerThumbnailCommand(parent) {
|
|
|
43231
43268
|
}
|
|
43232
43269
|
if (options.bestFrame) {
|
|
43233
43270
|
const absVideoPath = resolve47(process.cwd(), options.bestFrame);
|
|
43234
|
-
if (!
|
|
43271
|
+
if (!existsSync43(absVideoPath)) {
|
|
43235
43272
|
exitWithError(notFoundError(absVideoPath));
|
|
43236
43273
|
}
|
|
43237
43274
|
if (!commandExists("ffmpeg")) {
|
|
@@ -43243,7 +43280,7 @@ function registerThumbnailCommand(parent) {
|
|
|
43243
43280
|
);
|
|
43244
43281
|
}
|
|
43245
43282
|
const apiKey2 = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
|
|
43246
|
-
const name =
|
|
43283
|
+
const name = basename13(options.bestFrame, extname12(options.bestFrame));
|
|
43247
43284
|
const outputPath = options.output || `${name}-thumbnail.png`;
|
|
43248
43285
|
const spinner3 = ora("Analyzing video for best frame...").start();
|
|
43249
43286
|
const result2 = await executeThumbnailBestFrame({
|
|
@@ -43311,7 +43348,7 @@ function registerThumbnailCommand(parent) {
|
|
|
43311
43348
|
throw new Error("No image data available");
|
|
43312
43349
|
}
|
|
43313
43350
|
outputPath = resolve47(process.cwd(), options.output);
|
|
43314
|
-
await
|
|
43351
|
+
await mkdir24(dirname18(outputPath), { recursive: true });
|
|
43315
43352
|
await writeFile33(outputPath, buffer);
|
|
43316
43353
|
}
|
|
43317
43354
|
outputSuccess({
|
|
@@ -43342,7 +43379,7 @@ function registerThumbnailCommand(parent) {
|
|
|
43342
43379
|
throw new Error("No image data available");
|
|
43343
43380
|
}
|
|
43344
43381
|
const outputPath = resolve47(process.cwd(), options.output);
|
|
43345
|
-
await
|
|
43382
|
+
await mkdir24(dirname18(outputPath), { recursive: true });
|
|
43346
43383
|
await writeFile33(outputPath, buffer);
|
|
43347
43384
|
saveSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
|
|
43348
43385
|
} catch {
|
|
@@ -45958,7 +45995,7 @@ __export(detect_exports, {
|
|
|
45958
45995
|
executeDetectSilence: () => executeDetectSilence
|
|
45959
45996
|
});
|
|
45960
45997
|
import { readFile as readFile39, writeFile as writeFile38 } from "node:fs/promises";
|
|
45961
|
-
import { resolve as resolve54, basename as
|
|
45998
|
+
import { resolve as resolve54, basename as basename15 } from "node:path";
|
|
45962
45999
|
async function executeDetectScenes(options) {
|
|
45963
46000
|
try {
|
|
45964
46001
|
if (!commandExists("ffmpeg")) {
|
|
@@ -46208,7 +46245,7 @@ var init_detect = __esm({
|
|
|
46208
46245
|
let source = project.getSources().find((s) => s.url === absPath);
|
|
46209
46246
|
if (!source) {
|
|
46210
46247
|
source = project.addSource({
|
|
46211
|
-
name:
|
|
46248
|
+
name: basename15(absPath),
|
|
46212
46249
|
type: "video",
|
|
46213
46250
|
url: absPath,
|
|
46214
46251
|
duration: totalDuration
|
|
@@ -50657,8 +50694,9 @@ async function runWithMcpPromotion(work, opts) {
|
|
|
50657
50694
|
let lastRecordWriteAt = 0;
|
|
50658
50695
|
let heartbeat = null;
|
|
50659
50696
|
let writes = Promise.resolve();
|
|
50697
|
+
let finished = false;
|
|
50660
50698
|
const writeRecord = (patch) => {
|
|
50661
|
-
if (!record) return writes;
|
|
50699
|
+
if (!record || finished) return writes;
|
|
50662
50700
|
record = {
|
|
50663
50701
|
...record,
|
|
50664
50702
|
...patch,
|
|
@@ -50721,8 +50759,14 @@ async function runWithMcpPromotion(work, opts) {
|
|
|
50721
50759
|
void writeRecord({ heartbeatAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
50722
50760
|
}, opts.heartbeatMs ?? DEFAULT_HEARTBEAT_MS);
|
|
50723
50761
|
heartbeat.unref?.();
|
|
50762
|
+
const finish = (patch) => {
|
|
50763
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
50764
|
+
const terminal2 = writeRecord(patch);
|
|
50765
|
+
finished = true;
|
|
50766
|
+
return terminal2;
|
|
50767
|
+
};
|
|
50724
50768
|
void workPromise.then(
|
|
50725
|
-
(result) =>
|
|
50769
|
+
(result) => finish(
|
|
50726
50770
|
result.success ? {
|
|
50727
50771
|
status: "completed",
|
|
50728
50772
|
progress: 100,
|
|
@@ -50734,13 +50778,11 @@ async function runWithMcpPromotion(work, opts) {
|
|
|
50734
50778
|
resultPayload: result.data
|
|
50735
50779
|
}
|
|
50736
50780
|
),
|
|
50737
|
-
(error) =>
|
|
50781
|
+
(error) => finish({
|
|
50738
50782
|
status: "failed",
|
|
50739
50783
|
error: error instanceof Error ? error.message : String(error)
|
|
50740
50784
|
})
|
|
50741
|
-
)
|
|
50742
|
-
if (heartbeat) clearInterval(heartbeat);
|
|
50743
|
-
});
|
|
50785
|
+
);
|
|
50744
50786
|
return {
|
|
50745
50787
|
success: true,
|
|
50746
50788
|
data: {
|
|
@@ -51022,10 +51064,11 @@ init_scene_project();
|
|
|
51022
51064
|
init_project_config();
|
|
51023
51065
|
init_cost_tier();
|
|
51024
51066
|
import { resolve as resolve17, relative as relative5, dirname as dirname6 } from "node:path";
|
|
51025
|
-
import { mkdir as
|
|
51026
|
-
import { existsSync as
|
|
51067
|
+
import { mkdir as mkdir10, readFile as readFile16, writeFile as writeFile14, access as access4, copyFile as copyFile3 } from "node:fs/promises";
|
|
51068
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
51027
51069
|
|
|
51028
51070
|
// src/commands/_shared/scene-html-emit.ts
|
|
51071
|
+
init_lottie();
|
|
51029
51072
|
var SCENE_PRESETS = [
|
|
51030
51073
|
"simple",
|
|
51031
51074
|
"announcement",
|
|
@@ -51033,14 +51076,7 @@ var SCENE_PRESETS = [
|
|
|
51033
51076
|
"kinetic-type",
|
|
51034
51077
|
"product-shot"
|
|
51035
51078
|
];
|
|
51036
|
-
var
|
|
51037
|
-
"full",
|
|
51038
|
-
"center",
|
|
51039
|
-
"top-left",
|
|
51040
|
-
"top-right",
|
|
51041
|
-
"bottom-left",
|
|
51042
|
-
"bottom-right"
|
|
51043
|
-
];
|
|
51079
|
+
var LOTTIE_RUNTIME_DIR = "assets/vendor/dotlottie";
|
|
51044
51080
|
var GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js";
|
|
51045
51081
|
function esc(text) {
|
|
51046
51082
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -51339,55 +51375,45 @@ function buildPreset(input) {
|
|
|
51339
51375
|
}
|
|
51340
51376
|
}
|
|
51341
51377
|
}
|
|
51342
|
-
|
|
51343
|
-
|
|
51344
|
-
|
|
51345
|
-
|
|
51346
|
-
function lottieOverlayStyle(input) {
|
|
51378
|
+
function jsString(value) {
|
|
51379
|
+
return JSON.stringify(value).replace(/</g, "\\u003c");
|
|
51380
|
+
}
|
|
51381
|
+
function buildLottieOverlay(input, canvas, sceneId, duration) {
|
|
51347
51382
|
const pos = input.position ?? "full";
|
|
51348
|
-
const
|
|
51349
|
-
const scale = Math.max(0.01, Math.min(2, rawScale));
|
|
51383
|
+
const scale = Math.max(0.01, Math.min(2, input.scale ?? (pos === "full" ? 1 : 0.25)));
|
|
51350
51384
|
const opacity = Math.max(0, Math.min(1, input.opacity ?? 1));
|
|
51351
|
-
const
|
|
51352
|
-
"position:absolute",
|
|
51353
|
-
"pointer-events:none",
|
|
51354
|
-
`opacity:${opacity}`
|
|
51355
|
-
];
|
|
51356
|
-
if (pos === "full") {
|
|
51357
|
-
base.push("inset:0", "width:100%", "height:100%");
|
|
51358
|
-
} else {
|
|
51359
|
-
const pct = scale * 100;
|
|
51360
|
-
base.push(`width:${pct}%`, `height:${pct}%`);
|
|
51361
|
-
switch (pos) {
|
|
51362
|
-
case "center":
|
|
51363
|
-
base.push("top:50%", "left:50%", "transform:translate(-50%,-50%)");
|
|
51364
|
-
break;
|
|
51365
|
-
case "top-left":
|
|
51366
|
-
base.push("top:4%", "left:4%");
|
|
51367
|
-
break;
|
|
51368
|
-
case "top-right":
|
|
51369
|
-
base.push("top:4%", "right:4%");
|
|
51370
|
-
break;
|
|
51371
|
-
case "bottom-left":
|
|
51372
|
-
base.push("bottom:4%", "left:4%");
|
|
51373
|
-
break;
|
|
51374
|
-
case "bottom-right":
|
|
51375
|
-
base.push("bottom:4%", "right:4%");
|
|
51376
|
-
break;
|
|
51377
|
-
}
|
|
51378
|
-
}
|
|
51379
|
-
return base.join(";");
|
|
51380
|
-
}
|
|
51381
|
-
function buildLottieOverlay(input, sceneId) {
|
|
51382
|
-
const loop = input.loop ?? true ? " loop" : "";
|
|
51383
|
-
const style = lottieOverlayStyle(input);
|
|
51385
|
+
const box = overlayBox(canvas, pos, scale);
|
|
51384
51386
|
const overlayId = `lottie-overlay-${sceneId}`;
|
|
51385
|
-
const markup = `<
|
|
51386
|
-
const
|
|
51387
|
-
|
|
51388
|
-
|
|
51389
|
-
|
|
51390
|
-
|
|
51387
|
+
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>`;
|
|
51388
|
+
const register = `${DOTLOTTIE_BROWSER_HELPERS}
|
|
51389
|
+
(async () => {
|
|
51390
|
+
const DotLottie = await loadDotLottie(${jsString(LOTTIE_RUNTIME_DIR)});
|
|
51391
|
+
const player = new DotLottie({
|
|
51392
|
+
canvas: document.getElementById(${jsString(overlayId)}),
|
|
51393
|
+
src: ${jsString(input.src)},
|
|
51394
|
+
autoplay: false,
|
|
51395
|
+
loop: false,
|
|
51396
|
+
renderConfig: { devicePixelRatio: 1, autoResize: false, freezeOnOffscreen: false },
|
|
51397
|
+
});
|
|
51398
|
+
await new Promise((resolve, reject) => {
|
|
51399
|
+
player.addEventListener("load", resolve);
|
|
51400
|
+
player.addEventListener("loadError", (event) => reject(event && event.error));
|
|
51401
|
+
});
|
|
51402
|
+
let sceneTime = 0;
|
|
51403
|
+
const lottieClock = {
|
|
51404
|
+
time(t) {
|
|
51405
|
+
if (t === undefined) return sceneTime;
|
|
51406
|
+
sceneTime = t;
|
|
51407
|
+
setLottieTime(player, t, ${input.loop ?? true});
|
|
51408
|
+
},
|
|
51409
|
+
};
|
|
51410
|
+
tl.fromTo(lottieClock, { time: 0 }, { time: ${duration}, duration: ${duration}, ease: "none" }, 0);
|
|
51411
|
+
})()
|
|
51412
|
+
.catch((error) => console.error("[vibe] Lottie overlay failed to load:", ${jsString(input.src)}, error))
|
|
51413
|
+
.finally(() => {
|
|
51414
|
+
window.__timelines["${sceneId}"] = tl;
|
|
51415
|
+
});`;
|
|
51416
|
+
return { markup, register };
|
|
51391
51417
|
}
|
|
51392
51418
|
function emitSceneHtml(input) {
|
|
51393
51419
|
if (input.duration <= 0) {
|
|
@@ -51410,11 +51436,10 @@ function emitSceneHtml(input) {
|
|
|
51410
51436
|
data-volume="1"
|
|
51411
51437
|
></audio>
|
|
51412
51438
|
` : "";
|
|
51413
|
-
const lottieLayer = input.lottie ? buildLottieOverlay(input.lottie, id) : null;
|
|
51439
|
+
const lottieLayer = input.lottie ? buildLottieOverlay(input.lottie, input, id, dur) : null;
|
|
51414
51440
|
const lottieMarkup = lottieLayer ? `
|
|
51415
51441
|
${lottieLayer.markup}` : "";
|
|
51416
|
-
const
|
|
51417
|
-
${lottieLayer.script}` : "";
|
|
51442
|
+
const register = lottieLayer?.register ?? `window.__timelines["${id}"] = tl;`;
|
|
51418
51443
|
return `<template id="${id}-template">
|
|
51419
51444
|
<div data-composition-id="${id}" data-start="0" data-duration="${dur}" data-width="${input.width}" data-height="${input.height}">
|
|
51420
51445
|
<style>
|
|
@@ -51423,14 +51448,14 @@ function emitSceneHtml(input) {
|
|
|
51423
51448
|
|
|
51424
51449
|
${parts.body}${lottieMarkup}
|
|
51425
51450
|
${audioBlock}
|
|
51426
|
-
<script src="${GSAP_CDN}"></script
|
|
51451
|
+
<script src="${GSAP_CDN}"></script>
|
|
51427
51452
|
<script>
|
|
51428
51453
|
window.__timelines = window.__timelines || {};
|
|
51429
51454
|
const tl = gsap.timeline({ paused: true });
|
|
51430
51455
|
|
|
51431
51456
|
${parts.timeline}
|
|
51432
51457
|
|
|
51433
|
-
|
|
51458
|
+
${register}
|
|
51434
51459
|
</script>
|
|
51435
51460
|
</div>
|
|
51436
51461
|
</template>
|
|
@@ -51488,6 +51513,7 @@ function readRootDims(rootHtml) {
|
|
|
51488
51513
|
}
|
|
51489
51514
|
|
|
51490
51515
|
// src/commands/scene.ts
|
|
51516
|
+
init_lottie();
|
|
51491
51517
|
init_scene_lint();
|
|
51492
51518
|
init_output();
|
|
51493
51519
|
init_api_key();
|
|
@@ -51503,9 +51529,9 @@ init_scene_lint();
|
|
|
51503
51529
|
init_storyboard_parse();
|
|
51504
51530
|
init_storyboard_source();
|
|
51505
51531
|
init_root_sync();
|
|
51506
|
-
import { existsSync as
|
|
51507
|
-
import { mkdir as
|
|
51508
|
-
import { join as
|
|
51532
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
51533
|
+
import { mkdir as mkdir9, writeFile as writeFile12 } from "node:fs/promises";
|
|
51534
|
+
import { join as join11, resolve as resolve14 } from "node:path";
|
|
51509
51535
|
async function executeSceneSubmit(opts) {
|
|
51510
51536
|
const projectDir = resolve14(opts.projectDir);
|
|
51511
51537
|
const beatId = opts.beatId.trim();
|
|
@@ -51519,8 +51545,8 @@ async function executeSceneSubmit(opts) {
|
|
|
51519
51545
|
warnings: [],
|
|
51520
51546
|
retryWith: []
|
|
51521
51547
|
};
|
|
51522
|
-
const storyboardPath =
|
|
51523
|
-
if (!
|
|
51548
|
+
const storyboardPath = join11(projectDir, "STORYBOARD.md");
|
|
51549
|
+
if (!existsSync12(storyboardPath)) {
|
|
51524
51550
|
return {
|
|
51525
51551
|
...base,
|
|
51526
51552
|
success: false,
|
|
@@ -51597,8 +51623,8 @@ async function executeSceneSubmit(opts) {
|
|
|
51597
51623
|
if (opts.validateOnly) {
|
|
51598
51624
|
return { ...base, success: true };
|
|
51599
51625
|
}
|
|
51600
|
-
const absPath =
|
|
51601
|
-
await
|
|
51626
|
+
const absPath = join11(projectDir, scenePath);
|
|
51627
|
+
await mkdir9(join11(projectDir, "compositions"), { recursive: true });
|
|
51602
51628
|
await writeFile12(absPath, html.endsWith("\n") ? html : `${html}
|
|
51603
51629
|
`, "utf-8");
|
|
51604
51630
|
return {
|
|
@@ -51793,7 +51819,7 @@ sceneCommand.command("submit").description(
|
|
|
51793
51819
|
}
|
|
51794
51820
|
html = Buffer.concat(chunks).toString("utf-8");
|
|
51795
51821
|
} else if (options.file) {
|
|
51796
|
-
html = await
|
|
51822
|
+
html = await readFile16(resolve17(String(options.file)), "utf-8");
|
|
51797
51823
|
} else {
|
|
51798
51824
|
exitWithError(
|
|
51799
51825
|
usageError(
|
|
@@ -52123,8 +52149,8 @@ function readClipStart(rootHtml, beatId) {
|
|
|
52123
52149
|
async function resolveNarrationText(value) {
|
|
52124
52150
|
if (!value) return void 0;
|
|
52125
52151
|
const looksLikePath = /\.[a-z]{2,4}$/i.test(value) || value.includes("/") || value.includes("\\");
|
|
52126
|
-
if (looksLikePath &&
|
|
52127
|
-
return (await
|
|
52152
|
+
if (looksLikePath && existsSync14(value)) {
|
|
52153
|
+
return (await readFile16(value, "utf-8")).trim();
|
|
52128
52154
|
}
|
|
52129
52155
|
return value.trim();
|
|
52130
52156
|
}
|
|
@@ -52161,7 +52187,7 @@ async function executeSceneAdd(opts) {
|
|
|
52161
52187
|
if (!opts.force && await pathExists2(scenePath)) {
|
|
52162
52188
|
return errResult(`Scene already exists: ${sceneRel}. Re-run with --force to overwrite.`);
|
|
52163
52189
|
}
|
|
52164
|
-
const rootHtmlBefore = await
|
|
52190
|
+
const rootHtmlBefore = await readFile16(rootPath, "utf-8");
|
|
52165
52191
|
let dims = readRootDims(rootHtmlBefore);
|
|
52166
52192
|
if (!dims) {
|
|
52167
52193
|
const cfg2 = await loadVibeProjectConfig(projectDir);
|
|
@@ -52185,8 +52211,8 @@ async function executeSceneAdd(opts) {
|
|
|
52185
52211
|
}
|
|
52186
52212
|
audioRelPath = `assets/narration-${id}.${ext}`;
|
|
52187
52213
|
audioAbsPath = resolve17(projectDir, audioRelPath);
|
|
52188
|
-
await
|
|
52189
|
-
await
|
|
52214
|
+
await mkdir10(dirname6(audioAbsPath), { recursive: true });
|
|
52215
|
+
await copyFile3(sourceAbs, audioAbsPath);
|
|
52190
52216
|
try {
|
|
52191
52217
|
narrationDuration = await getAudioDuration(audioAbsPath);
|
|
52192
52218
|
} catch {
|
|
@@ -52218,7 +52244,7 @@ async function executeSceneAdd(opts) {
|
|
|
52218
52244
|
}
|
|
52219
52245
|
audioRelPath = `assets/narration-${id}.${resolution.audioExtension}`;
|
|
52220
52246
|
audioAbsPath = resolve17(projectDir, audioRelPath);
|
|
52221
|
-
await
|
|
52247
|
+
await mkdir10(dirname6(audioAbsPath), { recursive: true });
|
|
52222
52248
|
await writeFile14(audioAbsPath, tts.audioBuffer);
|
|
52223
52249
|
try {
|
|
52224
52250
|
narrationDuration = await getAudioDuration(audioAbsPath);
|
|
@@ -52276,7 +52302,7 @@ async function executeSceneAdd(opts) {
|
|
|
52276
52302
|
});
|
|
52277
52303
|
imageRelPath = `assets/scene-${id}.png`;
|
|
52278
52304
|
imageAbsPath = resolve17(projectDir, imageRelPath);
|
|
52279
|
-
await
|
|
52305
|
+
await mkdir10(dirname6(imageAbsPath), { recursive: true });
|
|
52280
52306
|
await writeImageFile(imageAbsPath, Buffer.from(result.images[0].bytes));
|
|
52281
52307
|
} catch (error) {
|
|
52282
52308
|
return errResult(`${label} image generation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -52294,10 +52320,13 @@ async function executeSceneAdd(opts) {
|
|
|
52294
52320
|
if (ext !== "json" && ext !== "lottie") {
|
|
52295
52321
|
return errResult(`Unsupported Lottie file extension: .${ext}. Use .json or .lottie.`);
|
|
52296
52322
|
}
|
|
52323
|
+
const assetProblem = await checkLottieAsset(sourceAbs);
|
|
52324
|
+
if (assetProblem) return errResult(assetProblem);
|
|
52297
52325
|
lottieRelPath = `assets/lottie-${id}.${ext}`;
|
|
52298
52326
|
lottieAbsPath = resolve17(projectDir, lottieRelPath);
|
|
52299
|
-
await
|
|
52300
|
-
await
|
|
52327
|
+
await mkdir10(dirname6(lottieAbsPath), { recursive: true });
|
|
52328
|
+
await copyFile3(sourceAbs, lottieAbsPath);
|
|
52329
|
+
await copyDotLottieRuntime(resolve17(projectDir, LOTTIE_RUNTIME_DIR));
|
|
52301
52330
|
lottieInput = {
|
|
52302
52331
|
src: lottieRelPath,
|
|
52303
52332
|
position: opts.lottie.position,
|
|
@@ -52341,7 +52370,7 @@ async function executeSceneAdd(opts) {
|
|
|
52341
52370
|
storyboardAction = synced.action;
|
|
52342
52371
|
} else {
|
|
52343
52372
|
opts.onProgress?.("Syncing STORYBOARD.md...");
|
|
52344
|
-
const before = await
|
|
52373
|
+
const before = await readFile16(storyboardAbsPath, "utf-8");
|
|
52345
52374
|
const synced = upsertStoryboardBeat(before, upsert);
|
|
52346
52375
|
if (synced.markdown !== before) {
|
|
52347
52376
|
await writeFile14(storyboardAbsPath, synced.markdown, "utf-8");
|
|
@@ -52366,7 +52395,7 @@ async function executeSceneAdd(opts) {
|
|
|
52366
52395
|
transcript: transcriptWords,
|
|
52367
52396
|
lottie: lottieInput
|
|
52368
52397
|
});
|
|
52369
|
-
await
|
|
52398
|
+
await mkdir10(dirname6(scenePath), { recursive: true });
|
|
52370
52399
|
await writeFile14(scenePath, sceneHtml, "utf-8");
|
|
52371
52400
|
opts.onProgress?.("Updating root composition...");
|
|
52372
52401
|
let start = 0;
|
|
@@ -52378,7 +52407,7 @@ async function executeSceneAdd(opts) {
|
|
|
52378
52407
|
});
|
|
52379
52408
|
const blockingIssue = syncPlan.issues.find((issue) => issue.severity === "error");
|
|
52380
52409
|
if (blockingIssue) return errResult(blockingIssue.message);
|
|
52381
|
-
const rootHtmlAfterSync = await
|
|
52410
|
+
const rootHtmlAfterSync = await readFile16(rootPath, "utf-8");
|
|
52382
52411
|
start = readClipStart(rootHtmlAfterSync, id) ?? 0;
|
|
52383
52412
|
} else {
|
|
52384
52413
|
start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
|
|
@@ -52557,7 +52586,7 @@ async function resolveSceneRepairTarget(rootArg = "index.html", projectArg = "."
|
|
|
52557
52586
|
const root = rootArg || "index.html";
|
|
52558
52587
|
const project = projectArg || ".";
|
|
52559
52588
|
const defaultProject = project === ".";
|
|
52560
|
-
if (defaultProject && root !== "index.html" && !root.toLowerCase().endsWith(".html") &&
|
|
52589
|
+
if (defaultProject && root !== "index.html" && !root.toLowerCase().endsWith(".html") && existsSync14(resolve17(root, "index.html"))) {
|
|
52561
52590
|
return { projectDir: resolve17(root), rootRel: "index.html" };
|
|
52562
52591
|
}
|
|
52563
52592
|
return { projectDir: resolve17(project), rootRel: root };
|
|
@@ -53419,9 +53448,9 @@ init_ai_edit();
|
|
|
53419
53448
|
init_api_key();
|
|
53420
53449
|
init_exec_safe();
|
|
53421
53450
|
init_remotion();
|
|
53422
|
-
import { resolve as resolve38, dirname as dirname16, basename as
|
|
53423
|
-
import { writeFile as writeFile29, mkdir as
|
|
53424
|
-
import { existsSync as
|
|
53451
|
+
import { resolve as resolve38, dirname as dirname16, basename as basename11 } from "node:path";
|
|
53452
|
+
import { writeFile as writeFile29, mkdir as mkdir22, rm as rm7 } from "node:fs/promises";
|
|
53453
|
+
import { existsSync as existsSync38 } from "node:fs";
|
|
53425
53454
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
53426
53455
|
var ASS_STYLES = ["karaoke-sweep", "typewriter"];
|
|
53427
53456
|
var SENTENCE_BREAKS = /[.!?]/;
|
|
@@ -53572,7 +53601,7 @@ async function executeAnimatedCaption(options) {
|
|
|
53572
53601
|
}
|
|
53573
53602
|
const effectiveFontSize = fontSize ?? Math.round(height * 0.04);
|
|
53574
53603
|
const tmpAudioDir = resolve38(tmpdir4(), `vf-ac-${Date.now()}`);
|
|
53575
|
-
await
|
|
53604
|
+
await mkdir22(tmpAudioDir, { recursive: true });
|
|
53576
53605
|
const audioPath = resolve38(tmpAudioDir, "audio.wav");
|
|
53577
53606
|
await execSafe("ffmpeg", [
|
|
53578
53607
|
"-y",
|
|
@@ -53602,8 +53631,8 @@ async function executeAnimatedCaption(options) {
|
|
|
53602
53631
|
const groups = groupWords(transcript.words, { wordsPerGroup, maxChars });
|
|
53603
53632
|
const absOutputPath = resolve38(process.cwd(), outputPath);
|
|
53604
53633
|
const outDir = dirname16(absOutputPath);
|
|
53605
|
-
if (!
|
|
53606
|
-
await
|
|
53634
|
+
if (!existsSync38(outDir)) {
|
|
53635
|
+
await mkdir22(outDir, { recursive: true });
|
|
53607
53636
|
}
|
|
53608
53637
|
if (tier === "ass") {
|
|
53609
53638
|
const assContent = generateASS(
|
|
@@ -53634,7 +53663,7 @@ async function executeAnimatedCaption(options) {
|
|
|
53634
53663
|
width,
|
|
53635
53664
|
height,
|
|
53636
53665
|
fps: videoFps,
|
|
53637
|
-
videoFileName:
|
|
53666
|
+
videoFileName: basename11(videoPath)
|
|
53638
53667
|
});
|
|
53639
53668
|
const durationInFrames = Math.ceil(duration * videoFps);
|
|
53640
53669
|
const renderResult = await renderWithEmbeddedVideo({
|
|
@@ -53645,7 +53674,7 @@ async function executeAnimatedCaption(options) {
|
|
|
53645
53674
|
fps: videoFps,
|
|
53646
53675
|
durationInFrames,
|
|
53647
53676
|
videoPath,
|
|
53648
|
-
videoFileName:
|
|
53677
|
+
videoFileName: basename11(videoPath),
|
|
53649
53678
|
outputPath: absOutputPath
|
|
53650
53679
|
});
|
|
53651
53680
|
if (!renderResult.success) {
|
|
@@ -54196,9 +54225,9 @@ init_project_config();
|
|
|
54196
54225
|
init_scene_lint();
|
|
54197
54226
|
init_root_sync();
|
|
54198
54227
|
init_review_report();
|
|
54199
|
-
import { existsSync as
|
|
54228
|
+
import { existsSync as existsSync40 } from "node:fs";
|
|
54200
54229
|
import { readdir as readdir4, readFile as readFile31, stat as stat5 } from "node:fs/promises";
|
|
54201
|
-
import { isAbsolute as isAbsolute6, join as
|
|
54230
|
+
import { isAbsolute as isAbsolute6, join as join24, resolve as resolve41 } from "node:path";
|
|
54202
54231
|
async function inspectProject(opts) {
|
|
54203
54232
|
const projectDir = resolve41(opts.projectDir);
|
|
54204
54233
|
const issues = [];
|
|
@@ -54212,7 +54241,7 @@ async function inspectProject(opts) {
|
|
|
54212
54241
|
assets: { checked: 0, missing: [], stale: [], unknownFreshness: [] },
|
|
54213
54242
|
rootSync: { ok: null, issueCount: 0, issues: [] }
|
|
54214
54243
|
};
|
|
54215
|
-
if (!
|
|
54244
|
+
if (!existsSync40(projectDir) || !await isDirectory(projectDir)) {
|
|
54216
54245
|
issues.push({
|
|
54217
54246
|
severity: "error",
|
|
54218
54247
|
code: "PROJECT_NOT_FOUND",
|
|
@@ -54227,15 +54256,15 @@ async function inspectProject(opts) {
|
|
|
54227
54256
|
);
|
|
54228
54257
|
}
|
|
54229
54258
|
const coreFiles = {
|
|
54230
|
-
storyboard:
|
|
54231
|
-
design:
|
|
54232
|
-
config:
|
|
54233
|
-
legacyConfig:
|
|
54234
|
-
buildReport:
|
|
54235
|
-
root:
|
|
54259
|
+
storyboard: join24(projectDir, "STORYBOARD.md"),
|
|
54260
|
+
design: join24(projectDir, "DESIGN.md"),
|
|
54261
|
+
config: join24(projectDir, "vibe.config.json"),
|
|
54262
|
+
legacyConfig: join24(projectDir, "vibe.project.yaml"),
|
|
54263
|
+
buildReport: join24(projectDir, "build-report.json"),
|
|
54264
|
+
root: join24(projectDir, "index.html")
|
|
54236
54265
|
};
|
|
54237
54266
|
for (const [name, path] of Object.entries(coreFiles)) {
|
|
54238
|
-
checks.files[name] =
|
|
54267
|
+
checks.files[name] = existsSync40(path);
|
|
54239
54268
|
}
|
|
54240
54269
|
let beatIds = [];
|
|
54241
54270
|
let inspectedBeatIds = [];
|
|
@@ -54316,20 +54345,20 @@ async function inspectProject(opts) {
|
|
|
54316
54345
|
});
|
|
54317
54346
|
retryWith.push(`vibe build ${projectDir} --stage sync --json`);
|
|
54318
54347
|
}
|
|
54319
|
-
const compositionsDir =
|
|
54320
|
-
const existingComps =
|
|
54348
|
+
const compositionsDir = join24(projectDir, "compositions");
|
|
54349
|
+
const existingComps = existsSync40(compositionsDir) ? new Set(await listHtmlBasenames(compositionsDir)) : /* @__PURE__ */ new Set();
|
|
54321
54350
|
checks.compositions.expected = inspectedBeatIds.length;
|
|
54322
54351
|
for (const beatId of inspectedBeatIds) {
|
|
54323
54352
|
const file = `scene-${beatId}.html`;
|
|
54324
54353
|
if (existingComps.has(file)) {
|
|
54325
54354
|
checks.compositions.found++;
|
|
54326
54355
|
} else {
|
|
54327
|
-
checks.compositions.missing.push(
|
|
54356
|
+
checks.compositions.missing.push(join24("compositions", file));
|
|
54328
54357
|
issues.push({
|
|
54329
54358
|
severity: "error",
|
|
54330
54359
|
code: "MISSING_COMPOSITION",
|
|
54331
54360
|
message: `Composition for beat "${beatId}" is missing.`,
|
|
54332
|
-
file:
|
|
54361
|
+
file: join24("compositions", file),
|
|
54333
54362
|
scene: beatId,
|
|
54334
54363
|
beatId,
|
|
54335
54364
|
suggestedFix: "Run `vibe build --stage compose --json`."
|
|
@@ -54391,7 +54420,7 @@ async function inspectProject(opts) {
|
|
|
54391
54420
|
try {
|
|
54392
54421
|
const lint = await runProjectLint({ projectDir });
|
|
54393
54422
|
const lintFiles = opts.beatId ? lint.files.filter(
|
|
54394
|
-
(file) => file.file === "index.html" || file.file ===
|
|
54423
|
+
(file) => file.file === "index.html" || file.file === join24("compositions", `scene-${opts.beatId}.html`)
|
|
54395
54424
|
) : lint.files;
|
|
54396
54425
|
const lintCounts = countLintFindings(lintFiles);
|
|
54397
54426
|
checks.lint = {
|
|
@@ -54674,7 +54703,7 @@ function inspectReportedAsset(opts) {
|
|
|
54674
54703
|
opts.checks.assets.checked++;
|
|
54675
54704
|
if (isExternalRef2(opts.value)) return;
|
|
54676
54705
|
const abs = isAbsolute6(opts.value) ? opts.value : resolve41(opts.projectDir, opts.value);
|
|
54677
|
-
if (
|
|
54706
|
+
if (existsSync40(abs)) return;
|
|
54678
54707
|
opts.checks.assets.missing.push(opts.value);
|
|
54679
54708
|
opts.issues.push({
|
|
54680
54709
|
severity: "warning",
|
|
@@ -54768,9 +54797,9 @@ init_exec_safe();
|
|
|
54768
54797
|
init_project_config();
|
|
54769
54798
|
init_storyboard_parse();
|
|
54770
54799
|
init_review_report();
|
|
54771
|
-
import { existsSync as
|
|
54800
|
+
import { existsSync as existsSync42 } from "node:fs";
|
|
54772
54801
|
import { readdir as readdir5, readFile as readFile33, stat as stat6 } from "node:fs/promises";
|
|
54773
|
-
import { basename as
|
|
54802
|
+
import { basename as basename12, isAbsolute as isAbsolute7, join as join25, relative as relative10, resolve as resolve43 } from "node:path";
|
|
54774
54803
|
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".m4v"]);
|
|
54775
54804
|
var DEFAULT_AI_MODEL = "flash";
|
|
54776
54805
|
var BLACK_FRAME_MIN_DURATION_SEC = 1;
|
|
@@ -55271,11 +55300,11 @@ async function inspectRender(opts) {
|
|
|
55271
55300
|
}
|
|
55272
55301
|
async function renderExpectsAudio(projectDir, beatId) {
|
|
55273
55302
|
const root = resolve43(projectDir);
|
|
55274
|
-
const rootHtml = await readTextIfExists(
|
|
55303
|
+
const rootHtml = await readTextIfExists(join25(root, "index.html"));
|
|
55275
55304
|
if (rootHtml && rootHtmlExpectsAudio(rootHtml, beatId)) return true;
|
|
55276
|
-
const buildReport = await readJsonIfExists(
|
|
55305
|
+
const buildReport = await readJsonIfExists(join25(root, "build-report.json"));
|
|
55277
55306
|
if (buildReportExpectsAudio(buildReport, beatId)) return true;
|
|
55278
|
-
const storyboard = await readTextIfExists(
|
|
55307
|
+
const storyboard = await readTextIfExists(join25(root, "STORYBOARD.md"));
|
|
55279
55308
|
if (!storyboard) return false;
|
|
55280
55309
|
try {
|
|
55281
55310
|
const parsed = parseStoryboard(storyboard);
|
|
@@ -55286,7 +55315,7 @@ async function renderExpectsAudio(projectDir, beatId) {
|
|
|
55286
55315
|
`assets/narration-${beat.id}.wav`,
|
|
55287
55316
|
`assets/music-${beat.id}.mp3`,
|
|
55288
55317
|
`assets/music-${beat.id}.wav`
|
|
55289
|
-
].some((asset) =>
|
|
55318
|
+
].some((asset) => existsSync42(join25(root, asset)))
|
|
55290
55319
|
);
|
|
55291
55320
|
} catch {
|
|
55292
55321
|
return false;
|
|
@@ -55394,8 +55423,8 @@ async function maybeWriteRenderReport(projectDir, opts, result) {
|
|
|
55394
55423
|
}
|
|
55395
55424
|
function renderSourceReports(projectDir, videoPath, checks) {
|
|
55396
55425
|
const reports = [];
|
|
55397
|
-
if (
|
|
55398
|
-
if (
|
|
55426
|
+
if (existsSync42(join25(projectDir, "build-report.json"))) reports.push("build-report.json");
|
|
55427
|
+
if (existsSync42(join25(projectDir, "render-report.json"))) reports.push("render-report.json");
|
|
55399
55428
|
if (videoPath) reports.push(displayPath(videoPath));
|
|
55400
55429
|
if (checks.durationSec !== void 0 || checks.width !== void 0 || checks.height !== void 0) {
|
|
55401
55430
|
reports.push("ffprobe");
|
|
@@ -55408,31 +55437,31 @@ function renderSourceReports(projectDir, videoPath, checks) {
|
|
|
55408
55437
|
async function resolveRenderVideoPath(projectDir, explicit, beatId) {
|
|
55409
55438
|
if (explicit) {
|
|
55410
55439
|
const candidate = resolve43(process.cwd(), explicit);
|
|
55411
|
-
return
|
|
55440
|
+
return existsSync42(candidate) ? candidate : null;
|
|
55412
55441
|
}
|
|
55413
|
-
const renderReportPath =
|
|
55414
|
-
if (
|
|
55442
|
+
const renderReportPath = join25(projectDir, "render-report.json");
|
|
55443
|
+
if (existsSync42(renderReportPath)) {
|
|
55415
55444
|
try {
|
|
55416
55445
|
const report = JSON.parse(await readFile33(renderReportPath, "utf-8"));
|
|
55417
55446
|
const reportBeat = typeof report.beat === "string" ? report.beat : null;
|
|
55418
55447
|
if (!beatId && reportBeat === null || beatId && reportBeat === beatId) {
|
|
55419
55448
|
const candidate = resolveReportedPath(projectDir, report.outputPath);
|
|
55420
|
-
if (candidate &&
|
|
55449
|
+
if (candidate && existsSync42(candidate)) return candidate;
|
|
55421
55450
|
}
|
|
55422
55451
|
} catch {
|
|
55423
55452
|
}
|
|
55424
55453
|
}
|
|
55425
|
-
const reportPath =
|
|
55426
|
-
if (!beatId &&
|
|
55454
|
+
const reportPath = join25(projectDir, "build-report.json");
|
|
55455
|
+
if (!beatId && existsSync42(reportPath)) {
|
|
55427
55456
|
try {
|
|
55428
55457
|
const report = JSON.parse(await readFile33(reportPath, "utf-8"));
|
|
55429
55458
|
const candidate = resolveReportedPath(projectDir, report.outputPath);
|
|
55430
|
-
if (candidate &&
|
|
55459
|
+
if (candidate && existsSync42(candidate)) return candidate;
|
|
55431
55460
|
} catch {
|
|
55432
55461
|
}
|
|
55433
55462
|
}
|
|
55434
|
-
const rendersDir =
|
|
55435
|
-
if (!
|
|
55463
|
+
const rendersDir = join25(projectDir, "renders");
|
|
55464
|
+
if (!existsSync42(rendersDir)) return null;
|
|
55436
55465
|
const entries = await readdir5(rendersDir, { withFileTypes: true });
|
|
55437
55466
|
const candidates = [];
|
|
55438
55467
|
for (const entry of entries) {
|
|
@@ -55440,7 +55469,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
|
|
|
55440
55469
|
const ext = entry.name.slice(entry.name.lastIndexOf(".")).toLowerCase();
|
|
55441
55470
|
if (!VIDEO_EXTENSIONS.has(ext)) continue;
|
|
55442
55471
|
if (beatId && !entry.name.includes(sanitizeFileSegment2(beatId))) continue;
|
|
55443
|
-
const full =
|
|
55472
|
+
const full = join25(rendersDir, entry.name);
|
|
55444
55473
|
const info = await stat6(full);
|
|
55445
55474
|
candidates.push({ path: full, mtimeMs: info.mtimeMs });
|
|
55446
55475
|
}
|
|
@@ -55458,12 +55487,12 @@ async function runAiRenderReview(projectDir, videoPath, model) {
|
|
|
55458
55487
|
});
|
|
55459
55488
|
}
|
|
55460
55489
|
function resolveStoryboardPath(projectDir) {
|
|
55461
|
-
const storyboardPath =
|
|
55462
|
-
return
|
|
55490
|
+
const storyboardPath = join25(projectDir, "STORYBOARD.md");
|
|
55491
|
+
return existsSync42(storyboardPath) ? storyboardPath : null;
|
|
55463
55492
|
}
|
|
55464
55493
|
async function expectedDurationFromBuildReport(projectDir, beatId) {
|
|
55465
|
-
const reportPath =
|
|
55466
|
-
if (!
|
|
55494
|
+
const reportPath = join25(projectDir, "build-report.json");
|
|
55495
|
+
if (!existsSync42(reportPath)) return void 0;
|
|
55467
55496
|
try {
|
|
55468
55497
|
const report = JSON.parse(await readFile33(reportPath, "utf-8"));
|
|
55469
55498
|
const beats = beatId ? (report.beats ?? []).filter((beat) => beat.id === beatId) : report.beats ?? [];
|
|
@@ -55477,8 +55506,8 @@ async function expectedDurationFromBuildReport(projectDir, beatId) {
|
|
|
55477
55506
|
}
|
|
55478
55507
|
}
|
|
55479
55508
|
async function beatTimingsFromBuildReport(projectDir, beatId) {
|
|
55480
|
-
const reportPath =
|
|
55481
|
-
if (!
|
|
55509
|
+
const reportPath = join25(projectDir, "build-report.json");
|
|
55510
|
+
if (!existsSync42(reportPath)) return [];
|
|
55482
55511
|
try {
|
|
55483
55512
|
const report = JSON.parse(await readFile33(reportPath, "utf-8"));
|
|
55484
55513
|
const source = report.beats ?? [];
|
|
@@ -55506,17 +55535,17 @@ async function beatTimingsFromBuildReport(projectDir, beatId) {
|
|
|
55506
55535
|
}
|
|
55507
55536
|
async function buildRenderReviewProjectContext(projectDir) {
|
|
55508
55537
|
const parts = [];
|
|
55509
|
-
const storyboard = await readTextIfExists(
|
|
55538
|
+
const storyboard = await readTextIfExists(join25(projectDir, "STORYBOARD.md"));
|
|
55510
55539
|
if (storyboard) {
|
|
55511
55540
|
parts.push(`STORYBOARD.md:
|
|
55512
55541
|
${truncateText(storyboard, 4e3)}`);
|
|
55513
55542
|
parts.push(`Storyboard beat cue summary:
|
|
55514
55543
|
${storyboardBeatCueSummary(storyboard)}`);
|
|
55515
55544
|
}
|
|
55516
|
-
const design = await readTextIfExists(
|
|
55545
|
+
const design = await readTextIfExists(join25(projectDir, "DESIGN.md"));
|
|
55517
55546
|
if (design) parts.push(`DESIGN.md:
|
|
55518
55547
|
${truncateText(design, 4e3)}`);
|
|
55519
|
-
const buildReport = await readTextIfExists(
|
|
55548
|
+
const buildReport = await readTextIfExists(join25(projectDir, "build-report.json"));
|
|
55520
55549
|
if (buildReport) parts.push(`build-report.json:
|
|
55521
55550
|
${truncateText(buildReport, 6e3)}`);
|
|
55522
55551
|
const timings = await beatTimingsFromBuildReport(projectDir);
|
|
@@ -55544,7 +55573,7 @@ function storyboardBeatCueSummary(storyboard) {
|
|
|
55544
55573
|
}
|
|
55545
55574
|
}
|
|
55546
55575
|
async function readTextIfExists(path) {
|
|
55547
|
-
if (!
|
|
55576
|
+
if (!existsSync42(path)) return null;
|
|
55548
55577
|
try {
|
|
55549
55578
|
return await readFile33(path, "utf-8");
|
|
55550
55579
|
} catch {
|
|
@@ -55644,7 +55673,7 @@ function aspectRatio(aspect) {
|
|
|
55644
55673
|
return w / h;
|
|
55645
55674
|
}
|
|
55646
55675
|
function displayPath(path) {
|
|
55647
|
-
return relative10(process.cwd(), path) ||
|
|
55676
|
+
return relative10(process.cwd(), path) || basename12(path);
|
|
55648
55677
|
}
|
|
55649
55678
|
function toSnakeCase(value) {
|
|
55650
55679
|
return value.replace(/[A-Z]/g, (match) => `_${match}`).toUpperCase();
|
|
@@ -56172,16 +56201,16 @@ var generateTools = [
|
|
|
56172
56201
|
];
|
|
56173
56202
|
|
|
56174
56203
|
// src/tools/manifest/storyboard.ts
|
|
56175
|
-
import { existsSync as
|
|
56204
|
+
import { existsSync as existsSync45 } from "node:fs";
|
|
56176
56205
|
import { readFile as readFile36, writeFile as writeFile36 } from "node:fs/promises";
|
|
56177
56206
|
import { resolve as resolve51 } from "node:path";
|
|
56178
56207
|
init_storyboard_parse();
|
|
56179
56208
|
init_storyboard_edit();
|
|
56180
56209
|
|
|
56181
56210
|
// src/commands/_shared/storyboard-revise.ts
|
|
56182
|
-
import { existsSync as
|
|
56211
|
+
import { existsSync as existsSync44 } from "node:fs";
|
|
56183
56212
|
import { readFile as readFile35, writeFile as writeFile35 } from "node:fs/promises";
|
|
56184
|
-
import { join as
|
|
56213
|
+
import { join as join26, resolve as resolve50 } from "node:path";
|
|
56185
56214
|
|
|
56186
56215
|
// src/agent/adapters/index.ts
|
|
56187
56216
|
init_openai2();
|
|
@@ -56234,9 +56263,9 @@ init_storyboard_source();
|
|
|
56234
56263
|
init_storyboard_edit();
|
|
56235
56264
|
async function executeStoryboardRevision(opts) {
|
|
56236
56265
|
const projectDir = resolve50(opts.projectDir);
|
|
56237
|
-
const storyboardPath =
|
|
56266
|
+
const storyboardPath = join26(projectDir, "STORYBOARD.md");
|
|
56238
56267
|
const retryWith = [`vibe storyboard revise ${projectDir} --from "<request>" --dry-run --json`];
|
|
56239
|
-
if (!
|
|
56268
|
+
if (!existsSync44(storyboardPath)) {
|
|
56240
56269
|
return failureResult({
|
|
56241
56270
|
projectDir,
|
|
56242
56271
|
storyboardPath,
|
|
@@ -56477,16 +56506,16 @@ function diffChangedBeats(before, after) {
|
|
|
56477
56506
|
return ids.filter((id) => JSON.stringify(beforeBeats.get(id) ?? null) !== JSON.stringify(afterBeats.get(id) ?? null));
|
|
56478
56507
|
}
|
|
56479
56508
|
async function readRevisionContext(projectDir) {
|
|
56480
|
-
const designMd = await readOptionalText(
|
|
56481
|
-
const buildReportJson = await readOptionalJsonText(
|
|
56509
|
+
const designMd = await readOptionalText(join26(projectDir, "DESIGN.md"));
|
|
56510
|
+
const buildReportJson = await readOptionalJsonText(join26(projectDir, "build-report.json"));
|
|
56482
56511
|
return { designMd, buildReportJson };
|
|
56483
56512
|
}
|
|
56484
56513
|
async function readOptionalText(path) {
|
|
56485
|
-
if (!
|
|
56514
|
+
if (!existsSync44(path)) return null;
|
|
56486
56515
|
return readFile35(path, "utf-8");
|
|
56487
56516
|
}
|
|
56488
56517
|
async function readOptionalJsonText(path) {
|
|
56489
|
-
if (!
|
|
56518
|
+
if (!existsSync44(path)) return null;
|
|
56490
56519
|
try {
|
|
56491
56520
|
const parsed = JSON.parse(await readFile35(path, "utf-8"));
|
|
56492
56521
|
return JSON.stringify(parsed, null, 2);
|
|
@@ -56534,7 +56563,7 @@ var projectDirSchema = external_exports.object({
|
|
|
56534
56563
|
});
|
|
56535
56564
|
async function readStoryboard(projectDir) {
|
|
56536
56565
|
const path = resolve51(projectDir, "STORYBOARD.md");
|
|
56537
|
-
if (!
|
|
56566
|
+
if (!existsSync45(path)) return null;
|
|
56538
56567
|
return { path, markdown: await readFile36(path, "utf-8") };
|
|
56539
56568
|
}
|
|
56540
56569
|
var storyboardListTool = defineTool({
|
|
@@ -56792,9 +56821,9 @@ var storyboardTools = [
|
|
|
56792
56821
|
];
|
|
56793
56822
|
|
|
56794
56823
|
// src/tools/manifest/design.ts
|
|
56795
|
-
import { existsSync as
|
|
56824
|
+
import { existsSync as existsSync46 } from "node:fs";
|
|
56796
56825
|
import { readFile as readFile37 } from "node:fs/promises";
|
|
56797
|
-
import { resolve as resolve52, join as
|
|
56826
|
+
import { resolve as resolve52, join as join27 } from "node:path";
|
|
56798
56827
|
init_design_parse();
|
|
56799
56828
|
var PROJECT_DIR_DESCRIPTION4 = "Project directory. Defaults to the surface's cwd; in MCP hosts, relative paths resolve under the configured server workspace.";
|
|
56800
56829
|
var projectDirSchema2 = external_exports.object({
|
|
@@ -56810,8 +56839,8 @@ var designValidateTool = defineTool({
|
|
|
56810
56839
|
schema: projectDirSchema2,
|
|
56811
56840
|
async execute(args, ctx) {
|
|
56812
56841
|
const projectDir = args.projectDir ? resolve52(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
|
|
56813
|
-
const path =
|
|
56814
|
-
if (!
|
|
56842
|
+
const path = join27(projectDir, "DESIGN.md");
|
|
56843
|
+
if (!existsSync46(path)) return { success: false, error: `DESIGN.md not found in ${projectDir}` };
|
|
56815
56844
|
const md = await readFile37(path, "utf-8");
|
|
56816
56845
|
const issues = validateDesignMarkdown(md);
|
|
56817
56846
|
const design = parseDesign(md);
|
|
@@ -56835,12 +56864,12 @@ var designTools = [designValidateTool];
|
|
|
56835
56864
|
// src/tools/manifest/remix.ts
|
|
56836
56865
|
import { writeFile as writeFile40 } from "node:fs/promises";
|
|
56837
56866
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
56838
|
-
import { join as
|
|
56867
|
+
import { join as join28 } from "node:path";
|
|
56839
56868
|
|
|
56840
56869
|
// src/commands/ai-highlights.ts
|
|
56841
|
-
import { readFile as readFile38, writeFile as writeFile37, mkdir as
|
|
56842
|
-
import { resolve as resolve53, dirname as dirname19, basename as
|
|
56843
|
-
import { existsSync as
|
|
56870
|
+
import { readFile as readFile38, writeFile as writeFile37, mkdir as mkdir25 } from "node:fs/promises";
|
|
56871
|
+
import { resolve as resolve53, dirname as dirname19, basename as basename14, extname as extname13 } from "node:path";
|
|
56872
|
+
import { existsSync as existsSync47 } from "node:fs";
|
|
56844
56873
|
init_dist2();
|
|
56845
56874
|
init_engine();
|
|
56846
56875
|
init_api_key();
|
|
@@ -56872,7 +56901,7 @@ function filterHighlights(highlights, options) {
|
|
|
56872
56901
|
async function executeHighlights(options) {
|
|
56873
56902
|
try {
|
|
56874
56903
|
const absPath = resolve53(process.cwd(), options.media);
|
|
56875
|
-
if (!
|
|
56904
|
+
if (!existsSync47(absPath)) {
|
|
56876
56905
|
return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
|
|
56877
56906
|
}
|
|
56878
56907
|
const ext = extname13(absPath).toLowerCase();
|
|
@@ -56984,7 +57013,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
56984
57013
|
sourceDuration = await ffprobeDuration(absPath);
|
|
56985
57014
|
}
|
|
56986
57015
|
const transcriptResult = await transcribeAudioFile(audioPath, { apiKey: openaiApiKey, language: options.language });
|
|
56987
|
-
if (tempAudioPath &&
|
|
57016
|
+
if (tempAudioPath && existsSync47(tempAudioPath)) {
|
|
56988
57017
|
const { unlink: unlinkFile } = await import("node:fs/promises");
|
|
56989
57018
|
await unlinkFile(tempAudioPath).catch(() => {
|
|
56990
57019
|
});
|
|
@@ -57030,7 +57059,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
57030
57059
|
if (options.project) {
|
|
57031
57060
|
const project = new Project("Highlight Reel");
|
|
57032
57061
|
const source = project.addSource({
|
|
57033
|
-
name:
|
|
57062
|
+
name: basename14(absPath),
|
|
57034
57063
|
url: absPath,
|
|
57035
57064
|
type: isVideo ? "video" : "audio",
|
|
57036
57065
|
duration: sourceDuration
|
|
@@ -57071,7 +57100,7 @@ async function executeAutoShorts(options) {
|
|
|
57071
57100
|
return { success: false, shorts: [], error: "FFmpeg not found" };
|
|
57072
57101
|
}
|
|
57073
57102
|
const absPath = resolve53(process.cwd(), options.video);
|
|
57074
|
-
if (!
|
|
57103
|
+
if (!existsSync47(absPath)) {
|
|
57075
57104
|
return { success: false, shorts: [], error: `File not found: ${absPath}` };
|
|
57076
57105
|
}
|
|
57077
57106
|
const targetDuration = options.duration ?? 60;
|
|
@@ -57195,8 +57224,8 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
57195
57224
|
};
|
|
57196
57225
|
}
|
|
57197
57226
|
const outputDir = options.outputDir ? resolve53(process.cwd(), options.outputDir) : dirname19(absPath);
|
|
57198
|
-
if (options.outputDir && !
|
|
57199
|
-
await
|
|
57227
|
+
if (options.outputDir && !existsSync47(outputDir)) {
|
|
57228
|
+
await mkdir25(outputDir, { recursive: true });
|
|
57200
57229
|
}
|
|
57201
57230
|
const result = {
|
|
57202
57231
|
success: true,
|
|
@@ -57204,7 +57233,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
57204
57233
|
};
|
|
57205
57234
|
for (let i = 0; i < selectedHighlights.length; i++) {
|
|
57206
57235
|
const h = selectedHighlights[i];
|
|
57207
|
-
const baseName =
|
|
57236
|
+
const baseName = basename14(absPath, extname13(absPath));
|
|
57208
57237
|
const outputPath = resolve53(outputDir, `${baseName}-short-${i + 1}.mp4`);
|
|
57209
57238
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
57210
57239
|
"-v",
|
|
@@ -57274,8 +57303,8 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
57274
57303
|
// src/pipeline/executor.ts
|
|
57275
57304
|
var import_yaml8 = __toESM(require_dist2(), 1);
|
|
57276
57305
|
import { resolve as resolve55 } from "node:path";
|
|
57277
|
-
import { readFile as readFile40, writeFile as writeFile39, mkdir as
|
|
57278
|
-
import { existsSync as
|
|
57306
|
+
import { readFile as readFile40, writeFile as writeFile39, mkdir as mkdir26 } from "node:fs/promises";
|
|
57307
|
+
import { existsSync as existsSync48 } from "node:fs";
|
|
57279
57308
|
|
|
57280
57309
|
// src/pipeline/resolver.ts
|
|
57281
57310
|
function resolveStepParams(params, completedSteps) {
|
|
@@ -57673,7 +57702,7 @@ async function ensureActionsRegistered() {
|
|
|
57673
57702
|
}
|
|
57674
57703
|
async function loadPipeline(filePath) {
|
|
57675
57704
|
const absPath = resolve55(process.cwd(), filePath);
|
|
57676
|
-
if (!
|
|
57705
|
+
if (!existsSync48(absPath)) {
|
|
57677
57706
|
throw new Error(`Pipeline file not found: ${absPath}`);
|
|
57678
57707
|
}
|
|
57679
57708
|
const content = await readFile40(absPath, "utf-8");
|
|
@@ -57693,13 +57722,13 @@ var CHECKPOINT_FILE = ".pipeline-state.yaml";
|
|
|
57693
57722
|
async function executePipeline(manifest2, options = {}) {
|
|
57694
57723
|
await ensureActionsRegistered();
|
|
57695
57724
|
const outputDir = resolve55(process.cwd(), options.outputDir || `${manifest2.name}-output`);
|
|
57696
|
-
await
|
|
57725
|
+
await mkdir26(outputDir, { recursive: true });
|
|
57697
57726
|
const completedSteps = /* @__PURE__ */ new Map();
|
|
57698
57727
|
const results = [];
|
|
57699
57728
|
const startTime = Date.now();
|
|
57700
57729
|
if (options.resume) {
|
|
57701
57730
|
const checkpointPath = resolve55(outputDir, CHECKPOINT_FILE);
|
|
57702
|
-
if (
|
|
57731
|
+
if (existsSync48(checkpointPath)) {
|
|
57703
57732
|
const checkpointContent = await readFile40(checkpointPath, "utf-8");
|
|
57704
57733
|
const checkpoint = (0, import_yaml8.parse)(checkpointContent);
|
|
57705
57734
|
for (const cs of checkpoint.completedSteps) {
|
|
@@ -57972,7 +58001,7 @@ var pipelineRunTool = defineTool({
|
|
|
57972
58001
|
let resolvedPath = args.pipelinePath;
|
|
57973
58002
|
let tempPath;
|
|
57974
58003
|
if (args.pipelineYaml) {
|
|
57975
|
-
tempPath =
|
|
58004
|
+
tempPath = join28(tmpdir5(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
|
|
57976
58005
|
await writeFile40(tempPath, args.pipelineYaml, "utf-8");
|
|
57977
58006
|
resolvedPath = tempPath;
|
|
57978
58007
|
}
|
|
@@ -58108,8 +58137,8 @@ var detectTools = [
|
|
|
58108
58137
|
];
|
|
58109
58138
|
|
|
58110
58139
|
// src/tools/manifest/timeline.ts
|
|
58111
|
-
import { mkdir as
|
|
58112
|
-
import { basename as
|
|
58140
|
+
import { mkdir as mkdir27, writeFile as writeFile42 } from "node:fs/promises";
|
|
58141
|
+
import { basename as basename16, resolve as resolve56 } from "node:path";
|
|
58113
58142
|
init_engine();
|
|
58114
58143
|
init_project_resolver();
|
|
58115
58144
|
|
|
@@ -58156,7 +58185,7 @@ var timelineCreateTool = defineTool({
|
|
|
58156
58185
|
fps: external_exports.number().optional().describe("Frames per second (default: 30)")
|
|
58157
58186
|
}),
|
|
58158
58187
|
async execute(args, ctx) {
|
|
58159
|
-
const projectName = args.name === "." ?
|
|
58188
|
+
const projectName = args.name === "." ? basename16(ctx.workingDirectory) : basename16(resolve56(ctx.workingDirectory, args.name));
|
|
58160
58189
|
const project = new Project(projectName);
|
|
58161
58190
|
if (args.fps) project.setFrameRate(args.fps);
|
|
58162
58191
|
let outputPath;
|
|
@@ -58164,7 +58193,7 @@ var timelineCreateTool = defineTool({
|
|
|
58164
58193
|
outputPath = resolve56(ctx.workingDirectory, args.outputPath);
|
|
58165
58194
|
} else {
|
|
58166
58195
|
const dirPath = resolve56(ctx.workingDirectory, args.name);
|
|
58167
|
-
await
|
|
58196
|
+
await mkdir27(dirPath, { recursive: true });
|
|
58168
58197
|
outputPath = resolve56(dirPath, TIMELINE_FILENAME);
|
|
58169
58198
|
}
|
|
58170
58199
|
await writeFile42(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
@@ -58471,11 +58500,11 @@ init_engine();
|
|
|
58471
58500
|
init_storyboard_parse();
|
|
58472
58501
|
init_storyboard_source();
|
|
58473
58502
|
import { readdir as readdir6, readFile as readFile42, stat as stat7 } from "node:fs/promises";
|
|
58474
|
-
import { existsSync as
|
|
58475
|
-
import { join as
|
|
58503
|
+
import { existsSync as existsSync49 } from "node:fs";
|
|
58504
|
+
import { join as join29, resolve as resolve57 } from "node:path";
|
|
58476
58505
|
async function executeProjectList(opts = {}) {
|
|
58477
58506
|
const workspaceDir = resolve57(opts.workspaceDir ?? ".");
|
|
58478
|
-
if (!
|
|
58507
|
+
if (!existsSync49(workspaceDir)) {
|
|
58479
58508
|
return {
|
|
58480
58509
|
success: false,
|
|
58481
58510
|
workspaceDir,
|
|
@@ -58492,11 +58521,11 @@ async function executeProjectList(opts = {}) {
|
|
|
58492
58521
|
if (entry.name === "node_modules") continue;
|
|
58493
58522
|
if (entry.name.startsWith(".")) continue;
|
|
58494
58523
|
if (entry.name.startsWith("_")) {
|
|
58495
|
-
archivedCount += await countProjects(
|
|
58524
|
+
archivedCount += await countProjects(join29(workspaceDir, entry.name));
|
|
58496
58525
|
continue;
|
|
58497
58526
|
}
|
|
58498
|
-
const projectDir =
|
|
58499
|
-
if (!
|
|
58527
|
+
const projectDir = join29(workspaceDir, entry.name);
|
|
58528
|
+
if (!existsSync49(join29(projectDir, "STORYBOARD.md"))) continue;
|
|
58500
58529
|
projects.push(await describeProject(entry.name, projectDir));
|
|
58501
58530
|
}
|
|
58502
58531
|
projects.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
@@ -58506,7 +58535,7 @@ async function countProjects(dir) {
|
|
|
58506
58535
|
try {
|
|
58507
58536
|
const entries = await readdir6(dir, { withFileTypes: true });
|
|
58508
58537
|
return entries.filter(
|
|
58509
|
-
(e) => e.isDirectory() &&
|
|
58538
|
+
(e) => e.isDirectory() && existsSync49(join29(dir, e.name, "STORYBOARD.md"))
|
|
58510
58539
|
).length;
|
|
58511
58540
|
} catch {
|
|
58512
58541
|
return 0;
|
|
@@ -58517,7 +58546,7 @@ async function describeProject(name, projectDir) {
|
|
|
58517
58546
|
let storyboardDurationSec = 0;
|
|
58518
58547
|
const timestamps = [];
|
|
58519
58548
|
try {
|
|
58520
|
-
const storyboardPath =
|
|
58549
|
+
const storyboardPath = join29(projectDir, "STORYBOARD.md");
|
|
58521
58550
|
const parsed = parseStoryboard(await loadStoryboardMarkdown(projectDir));
|
|
58522
58551
|
beats = parsed.beats.length;
|
|
58523
58552
|
storyboardDurationSec = Number(
|
|
@@ -58529,7 +58558,7 @@ async function describeProject(name, projectDir) {
|
|
|
58529
58558
|
let buildStatus;
|
|
58530
58559
|
let buildUpdatedAt;
|
|
58531
58560
|
try {
|
|
58532
|
-
const reportPath =
|
|
58561
|
+
const reportPath = join29(projectDir, "build-report.json");
|
|
58533
58562
|
const report = JSON.parse(await readFile42(reportPath, "utf-8"));
|
|
58534
58563
|
buildStatus = report.status ?? report.phase;
|
|
58535
58564
|
buildUpdatedAt = report.updatedAt;
|
|
@@ -58538,12 +58567,12 @@ async function describeProject(name, projectDir) {
|
|
|
58538
58567
|
}
|
|
58539
58568
|
let latestRender;
|
|
58540
58569
|
try {
|
|
58541
|
-
const rendersDir =
|
|
58570
|
+
const rendersDir = join29(projectDir, "renders");
|
|
58542
58571
|
const files = await readdir6(rendersDir);
|
|
58543
58572
|
let newest;
|
|
58544
58573
|
for (const file of files) {
|
|
58545
58574
|
if (!/\.(mp4|webm|mov)$/i.test(file)) continue;
|
|
58546
|
-
const { mtimeMs } = await stat7(
|
|
58575
|
+
const { mtimeMs } = await stat7(join29(rendersDir, file));
|
|
58547
58576
|
if (!newest || mtimeMs > newest.mtimeMs) newest = { file, mtimeMs };
|
|
58548
58577
|
}
|
|
58549
58578
|
if (newest) {
|
|
@@ -59343,7 +59372,7 @@ var exportTools = [exportVideoTool];
|
|
|
59343
59372
|
|
|
59344
59373
|
// src/tools/manifest/agent-only.ts
|
|
59345
59374
|
import { readFile as readFile44, writeFile as writeFile44, readdir as readdir7, stat as stat8, access as access7, unlink as unlink2 } from "node:fs/promises";
|
|
59346
|
-
import { resolve as resolve61, join as
|
|
59375
|
+
import { resolve as resolve61, join as join30, basename as basename17, extname as extname14 } from "node:path";
|
|
59347
59376
|
init_engine();
|
|
59348
59377
|
init_exec_safe();
|
|
59349
59378
|
init_project_resolver();
|
|
@@ -59421,7 +59450,7 @@ var fsListTool = defineTool({
|
|
|
59421
59450
|
const results = [];
|
|
59422
59451
|
for (const entry of entries) {
|
|
59423
59452
|
if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
|
|
59424
|
-
const fullPath =
|
|
59453
|
+
const fullPath = join30(absPath, entry.name);
|
|
59425
59454
|
const stats = await stat8(fullPath);
|
|
59426
59455
|
if (entry.isDirectory()) {
|
|
59427
59456
|
results.push(`[DIR] ${entry.name}/`);
|
|
@@ -59534,7 +59563,7 @@ var batchImportTool = defineTool({
|
|
|
59534
59563
|
const scanDir = async (dir) => {
|
|
59535
59564
|
const entries = await readdir7(dir, { withFileTypes: true });
|
|
59536
59565
|
for (const entry of entries) {
|
|
59537
|
-
const entryPath =
|
|
59566
|
+
const entryPath = join30(dir, entry.name);
|
|
59538
59567
|
if (entry.isDirectory() && recursive) {
|
|
59539
59568
|
await scanDir(entryPath);
|
|
59540
59569
|
} else if (entry.isFile()) {
|
|
@@ -59551,7 +59580,7 @@ var batchImportTool = defineTool({
|
|
|
59551
59580
|
mediaFiles.sort();
|
|
59552
59581
|
const addedSources = [];
|
|
59553
59582
|
for (const mediaFile of mediaFiles) {
|
|
59554
|
-
const mediaName =
|
|
59583
|
+
const mediaName = basename17(mediaFile);
|
|
59555
59584
|
const mediaType = detectMediaType(mediaFile);
|
|
59556
59585
|
let duration = imageDuration;
|
|
59557
59586
|
if (mediaType !== "image") {
|