@vibeframe/mcp-server 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +369 -171
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1464,17 +1464,93 @@ async function handleTimelineToolCall(name, args) {
|
|
|
1464
1464
|
// ../cli/src/commands/export.ts
|
|
1465
1465
|
import { Command } from "commander";
|
|
1466
1466
|
import { readFile as readFile2, access, stat } from "node:fs/promises";
|
|
1467
|
-
import { resolve as
|
|
1467
|
+
import { resolve as resolve4, basename } from "node:path";
|
|
1468
1468
|
import { spawn } from "node:child_process";
|
|
1469
|
+
import chalk2 from "chalk";
|
|
1470
|
+
import ora2 from "ora";
|
|
1471
|
+
init_exec_safe();
|
|
1472
|
+
|
|
1473
|
+
// ../cli/src/commands/output.ts
|
|
1469
1474
|
import chalk from "chalk";
|
|
1470
1475
|
import ora from "ora";
|
|
1471
|
-
|
|
1476
|
+
function usageError(msg, suggestion) {
|
|
1477
|
+
return { success: false, error: msg, code: "USAGE_ERROR", exitCode: 2 /* USAGE */, suggestion, retryable: false };
|
|
1478
|
+
}
|
|
1479
|
+
function notFoundError(path) {
|
|
1480
|
+
return { success: false, error: `File not found: ${path}`, code: "NOT_FOUND", exitCode: 3 /* NOT_FOUND */, retryable: false };
|
|
1481
|
+
}
|
|
1482
|
+
function generalError(msg, suggestion) {
|
|
1483
|
+
return { success: false, error: msg, code: "ERROR", exitCode: 1 /* GENERAL */, suggestion, retryable: false };
|
|
1484
|
+
}
|
|
1485
|
+
function exitWithError(err) {
|
|
1486
|
+
if (isJsonMode()) {
|
|
1487
|
+
console.error(JSON.stringify(err, null, 2));
|
|
1488
|
+
} else {
|
|
1489
|
+
console.error(chalk.red(`
|
|
1490
|
+
${err.error}`));
|
|
1491
|
+
if (err.suggestion) {
|
|
1492
|
+
console.error(chalk.dim(` ${err.suggestion}`));
|
|
1493
|
+
}
|
|
1494
|
+
console.error();
|
|
1495
|
+
}
|
|
1496
|
+
process.exit(err.exitCode);
|
|
1497
|
+
}
|
|
1498
|
+
function isJsonMode() {
|
|
1499
|
+
return process.env.VIBE_JSON_OUTPUT === "1";
|
|
1500
|
+
}
|
|
1501
|
+
function isQuietMode() {
|
|
1502
|
+
return process.env.VIBE_QUIET_OUTPUT === "1";
|
|
1503
|
+
}
|
|
1504
|
+
function outputResult(result) {
|
|
1505
|
+
if (isJsonMode()) {
|
|
1506
|
+
const fields = process.env.VIBE_OUTPUT_FIELDS;
|
|
1507
|
+
if (fields) {
|
|
1508
|
+
const keys = fields.split(",").map((k) => k.trim());
|
|
1509
|
+
const filtered = {};
|
|
1510
|
+
for (const key of keys) {
|
|
1511
|
+
if (key in result) filtered[key] = result[key];
|
|
1512
|
+
}
|
|
1513
|
+
if ("success" in result) filtered.success = result.success;
|
|
1514
|
+
console.log(JSON.stringify(filtered, null, 2));
|
|
1515
|
+
} else {
|
|
1516
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1517
|
+
}
|
|
1518
|
+
} else if (isQuietMode()) {
|
|
1519
|
+
const primary = result.output ?? result.path ?? result.url ?? result.id ?? result.result;
|
|
1520
|
+
if (primary !== void 0) console.log(String(primary));
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// ../cli/src/commands/validate.ts
|
|
1525
|
+
import { resolve as resolve3, relative, isAbsolute } from "node:path";
|
|
1526
|
+
function validateOutputPath(path, cwd = process.cwd()) {
|
|
1527
|
+
rejectControlChars(path);
|
|
1528
|
+
if (isAbsolute(path)) {
|
|
1529
|
+
return resolve3(path);
|
|
1530
|
+
}
|
|
1531
|
+
const resolved = resolve3(cwd, path);
|
|
1532
|
+
const rel = relative(cwd, resolved);
|
|
1533
|
+
if (rel.startsWith("..")) {
|
|
1534
|
+
throw new Error(
|
|
1535
|
+
`Output path "${path}" escapes the working directory. Use a path within "${cwd}".`
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
return resolved;
|
|
1539
|
+
}
|
|
1540
|
+
function rejectControlChars(input) {
|
|
1541
|
+
if (/[\x00-\x1f\x7f-\x9f]/.test(input)) {
|
|
1542
|
+
throw new Error("Input contains invalid control characters.");
|
|
1543
|
+
}
|
|
1544
|
+
return input;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// ../cli/src/commands/export.ts
|
|
1472
1548
|
async function resolveProjectPath(inputPath) {
|
|
1473
|
-
const filePath =
|
|
1549
|
+
const filePath = resolve4(process.cwd(), inputPath);
|
|
1474
1550
|
try {
|
|
1475
1551
|
const stats = await stat(filePath);
|
|
1476
1552
|
if (stats.isDirectory()) {
|
|
1477
|
-
return
|
|
1553
|
+
return resolve4(filePath, "project.vibe.json");
|
|
1478
1554
|
}
|
|
1479
1555
|
} catch {
|
|
1480
1556
|
}
|
|
@@ -1519,7 +1595,7 @@ async function runExport(projectPath, outputPath, options = {}) {
|
|
|
1519
1595
|
message: "Project has no clips to export"
|
|
1520
1596
|
};
|
|
1521
1597
|
}
|
|
1522
|
-
const finalOutputPath =
|
|
1598
|
+
const finalOutputPath = resolve4(process.cwd(), outputPath);
|
|
1523
1599
|
const presetSettings = getPresetSettings(preset, summary.aspectRatio);
|
|
1524
1600
|
const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
|
|
1525
1601
|
const sources = project.getSources();
|
|
@@ -1560,24 +1636,37 @@ var exportCommand = new Command("export").description("Export project to video f
|
|
|
1560
1636
|
"-p, --preset <preset>",
|
|
1561
1637
|
"Quality preset (draft, standard, high, ultra)",
|
|
1562
1638
|
"standard"
|
|
1563
|
-
).option("-y, --overwrite", "Overwrite output file if exists", false).option("-g, --gap-fill <strategy>", "Gap filling strategy (black, extend)", "extend").addHelpText("after", `
|
|
1639
|
+
).option("-y, --overwrite", "Overwrite output file if exists", false).option("-g, --gap-fill <strategy>", "Gap filling strategy (black, extend)", "extend").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
|
|
1564
1640
|
Examples:
|
|
1565
1641
|
$ vibe export project.vibe.json -o output.mp4
|
|
1566
1642
|
$ vibe export project.vibe.json -o output.mp4 -p high -y
|
|
1567
1643
|
$ vibe export project.vibe.json -o output.webm -f webm
|
|
1568
1644
|
|
|
1569
1645
|
No API keys needed. Requires FFmpeg.`).action(async (projectPath, options) => {
|
|
1570
|
-
const spinner =
|
|
1646
|
+
const spinner = ora2("Checking FFmpeg...").start();
|
|
1571
1647
|
try {
|
|
1648
|
+
if (options.output) {
|
|
1649
|
+
validateOutputPath(options.output);
|
|
1650
|
+
}
|
|
1651
|
+
if (options.dryRun) {
|
|
1652
|
+
outputResult({
|
|
1653
|
+
dryRun: true,
|
|
1654
|
+
command: "export",
|
|
1655
|
+
params: {
|
|
1656
|
+
project: projectPath,
|
|
1657
|
+
output: options.output || null,
|
|
1658
|
+
format: options.format,
|
|
1659
|
+
preset: options.preset,
|
|
1660
|
+
overwrite: options.overwrite,
|
|
1661
|
+
gapFill: options.gapFill
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1572
1666
|
const ffmpegPath = await findFFmpeg();
|
|
1573
1667
|
if (!ffmpegPath) {
|
|
1574
|
-
spinner.fail(
|
|
1575
|
-
|
|
1576
|
-
console.error(chalk.yellow("Please install FFmpeg:"));
|
|
1577
|
-
console.error(chalk.dim(" macOS: brew install ffmpeg"));
|
|
1578
|
-
console.error(chalk.dim(" Ubuntu: sudo apt install ffmpeg"));
|
|
1579
|
-
console.error(chalk.dim(" Windows: winget install ffmpeg"));
|
|
1580
|
-
process.exit(1);
|
|
1668
|
+
spinner.fail("FFmpeg not found");
|
|
1669
|
+
exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS), apt install ffmpeg (Linux), or winget install ffmpeg (Windows)"));
|
|
1581
1670
|
}
|
|
1582
1671
|
spinner.text = "Loading project...";
|
|
1583
1672
|
const filePath = await resolveProjectPath(projectPath);
|
|
@@ -1586,10 +1675,10 @@ No API keys needed. Requires FFmpeg.`).action(async (projectPath, options) => {
|
|
|
1586
1675
|
const project = Project.fromJSON(data);
|
|
1587
1676
|
const summary = project.getSummary();
|
|
1588
1677
|
if (summary.clipCount === 0) {
|
|
1589
|
-
spinner.fail(
|
|
1590
|
-
|
|
1678
|
+
spinner.fail("Project has no clips to export");
|
|
1679
|
+
exitWithError(usageError("Project has no clips to export"));
|
|
1591
1680
|
}
|
|
1592
|
-
const outputPath = options.output ?
|
|
1681
|
+
const outputPath = options.output ? resolve4(process.cwd(), options.output) : resolve4(
|
|
1593
1682
|
process.cwd(),
|
|
1594
1683
|
`${basename(projectPath, ".vibe.json")}.${options.format}`
|
|
1595
1684
|
);
|
|
@@ -1607,8 +1696,8 @@ No API keys needed. Requires FFmpeg.`).action(async (projectPath, options) => {
|
|
|
1607
1696
|
sourceAudioMap.set(source.id, await checkHasAudio(source.url));
|
|
1608
1697
|
}
|
|
1609
1698
|
} catch {
|
|
1610
|
-
spinner.fail(
|
|
1611
|
-
|
|
1699
|
+
spinner.fail(`Source file not found: ${source.url}`);
|
|
1700
|
+
exitWithError(notFoundError(source.url));
|
|
1612
1701
|
}
|
|
1613
1702
|
}
|
|
1614
1703
|
}
|
|
@@ -1624,23 +1713,18 @@ No API keys needed. Requires FFmpeg.`).action(async (projectPath, options) => {
|
|
|
1624
1713
|
await runFFmpegProcess(ffmpegPath, ffmpegArgs, (progress) => {
|
|
1625
1714
|
spinner.text = `Encoding... ${progress}%`;
|
|
1626
1715
|
});
|
|
1627
|
-
spinner.succeed(
|
|
1716
|
+
spinner.succeed(chalk2.green(`Exported: ${outputPath}`));
|
|
1628
1717
|
console.log();
|
|
1629
|
-
console.log(
|
|
1630
|
-
console.log(
|
|
1631
|
-
console.log(
|
|
1632
|
-
console.log(
|
|
1633
|
-
console.log(
|
|
1718
|
+
console.log(chalk2.dim(" Duration:"), `${summary.duration.toFixed(1)}s`);
|
|
1719
|
+
console.log(chalk2.dim(" Clips:"), summary.clipCount);
|
|
1720
|
+
console.log(chalk2.dim(" Format:"), options.format);
|
|
1721
|
+
console.log(chalk2.dim(" Preset:"), options.preset);
|
|
1722
|
+
console.log(chalk2.dim(" Resolution:"), presetSettings.resolution);
|
|
1634
1723
|
console.log();
|
|
1635
1724
|
} catch (error) {
|
|
1636
|
-
spinner.fail(
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
if (process.env.DEBUG) {
|
|
1640
|
-
console.error(error.stack);
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
process.exit(1);
|
|
1725
|
+
spinner.fail("Export failed");
|
|
1726
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1727
|
+
exitWithError(generalError(`Export failed: ${msg}`));
|
|
1644
1728
|
}
|
|
1645
1729
|
});
|
|
1646
1730
|
async function findFFmpeg() {
|
|
@@ -1862,62 +1946,95 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
|
|
|
1862
1946
|
videoStreamIdx++;
|
|
1863
1947
|
}
|
|
1864
1948
|
}
|
|
1865
|
-
const
|
|
1866
|
-
const audioSegments = [];
|
|
1949
|
+
const audioTrackMap = /* @__PURE__ */ new Map();
|
|
1867
1950
|
for (const clip of audioClips) {
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1951
|
+
const trackId = clip.trackId || "audio-track-1";
|
|
1952
|
+
if (!audioTrackMap.has(trackId)) {
|
|
1953
|
+
audioTrackMap.set(trackId, []);
|
|
1954
|
+
}
|
|
1955
|
+
audioTrackMap.get(trackId).push(clip);
|
|
1872
1956
|
}
|
|
1873
|
-
|
|
1874
|
-
const
|
|
1957
|
+
const videoDuration = videoClips.length > 0 ? Math.max(...videoClips.map((c) => c.startTime + c.duration)) : 0;
|
|
1958
|
+
const audioDuration = audioClips.length > 0 ? Math.max(...audioClips.map((c) => c.startTime + c.duration)) : 0;
|
|
1959
|
+
const timelineDuration = totalDuration || Math.max(videoDuration, audioDuration);
|
|
1960
|
+
const trackOutputLabels = [];
|
|
1875
1961
|
let audioStreamIdx = 0;
|
|
1876
|
-
for (const
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1962
|
+
for (const [, trackClips] of audioTrackMap) {
|
|
1963
|
+
const sorted = [...trackClips].sort((a, b) => a.startTime - b.startTime);
|
|
1964
|
+
const trackGaps = detectTimelineGaps(sorted, timelineDuration);
|
|
1965
|
+
const segments = [];
|
|
1966
|
+
for (const clip of sorted) {
|
|
1967
|
+
segments.push({ type: "clip", clip, startTime: clip.startTime });
|
|
1968
|
+
}
|
|
1969
|
+
for (const gap of trackGaps) {
|
|
1970
|
+
segments.push({ type: "gap", gap, startTime: gap.start });
|
|
1971
|
+
}
|
|
1972
|
+
segments.sort((a, b) => a.startTime - b.startTime);
|
|
1973
|
+
const segmentLabels = [];
|
|
1974
|
+
for (const segment of segments) {
|
|
1975
|
+
if (segment.type === "clip" && segment.clip) {
|
|
1976
|
+
const clip = segment.clip;
|
|
1977
|
+
const source = sources.find((s) => s.id === clip.sourceId);
|
|
1978
|
+
if (!source) continue;
|
|
1979
|
+
const srcIdx = sourceMap.get(source.id);
|
|
1980
|
+
if (srcIdx === void 0) continue;
|
|
1981
|
+
const hasAudio = source.type === "audio" || sourceAudioMap.get(source.id) === true;
|
|
1982
|
+
let audioFilter;
|
|
1983
|
+
if (hasAudio) {
|
|
1984
|
+
const audioTrimStart = clip.sourceStartOffset;
|
|
1985
|
+
const audioTrimEnd = clip.sourceStartOffset + clip.duration;
|
|
1986
|
+
const sourceDuration = source.duration || 0;
|
|
1987
|
+
const clipDuration = clip.duration;
|
|
1988
|
+
if (source.type === "audio" && sourceDuration > clipDuration && audioTrimStart === 0) {
|
|
1989
|
+
const tempo = sourceDuration / clipDuration;
|
|
1990
|
+
if (tempo <= 2) {
|
|
1991
|
+
audioFilter = `[${srcIdx}:a]atempo=${tempo.toFixed(4)},asetpts=PTS-STARTPTS`;
|
|
1992
|
+
} else {
|
|
1993
|
+
audioFilter = `[${srcIdx}:a]atrim=start=${audioTrimStart}:end=${audioTrimEnd},asetpts=PTS-STARTPTS`;
|
|
1994
|
+
}
|
|
1894
1995
|
} else {
|
|
1895
1996
|
audioFilter = `[${srcIdx}:a]atrim=start=${audioTrimStart}:end=${audioTrimEnd},asetpts=PTS-STARTPTS`;
|
|
1896
1997
|
}
|
|
1897
1998
|
} else {
|
|
1898
|
-
audioFilter = `
|
|
1999
|
+
audioFilter = `anullsrc=r=48000:cl=stereo,atrim=0:${clip.duration},asetpts=PTS-STARTPTS`;
|
|
1899
2000
|
}
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
2001
|
+
const clipVolume = clip.volume;
|
|
2002
|
+
if (clipVolume !== void 0 && clipVolume !== 1) {
|
|
2003
|
+
audioFilter += `,volume=${clipVolume.toFixed(2)}`;
|
|
2004
|
+
}
|
|
2005
|
+
for (const effect of clip.effects || []) {
|
|
2006
|
+
if (effect.type === "fadeIn") {
|
|
2007
|
+
audioFilter += `,afade=t=in:st=0:d=${effect.duration}`;
|
|
2008
|
+
} else if (effect.type === "fadeOut") {
|
|
2009
|
+
const fadeStart = clip.duration - effect.duration;
|
|
2010
|
+
audioFilter += `,afade=t=out:st=${fadeStart}:d=${effect.duration}`;
|
|
2011
|
+
} else if (effect.type === "volume" && effect.params?.level !== void 0) {
|
|
2012
|
+
audioFilter += `,volume=${effect.params.level}`;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
const label = `a${audioStreamIdx}`;
|
|
2016
|
+
audioFilter += `[${label}]`;
|
|
2017
|
+
filterParts.push(audioFilter);
|
|
2018
|
+
segmentLabels.push(`[${label}]`);
|
|
2019
|
+
audioStreamIdx++;
|
|
2020
|
+
} else if (segment.type === "gap" && segment.gap) {
|
|
2021
|
+
const gapDuration = segment.gap.end - segment.gap.start;
|
|
2022
|
+
if (gapDuration > 1e-3) {
|
|
2023
|
+
const label = `a${audioStreamIdx}`;
|
|
2024
|
+
filterParts.push(`anullsrc=r=48000:cl=stereo,atrim=0:${gapDuration.toFixed(4)},asetpts=PTS-STARTPTS[${label}]`);
|
|
2025
|
+
segmentLabels.push(`[${label}]`);
|
|
2026
|
+
audioStreamIdx++;
|
|
1909
2027
|
}
|
|
1910
2028
|
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
const
|
|
1918
|
-
filterParts.push(
|
|
1919
|
-
|
|
1920
|
-
audioStreamIdx++;
|
|
2029
|
+
}
|
|
2030
|
+
if (segmentLabels.length > 1) {
|
|
2031
|
+
const trackLabel = `atrack${trackOutputLabels.length}`;
|
|
2032
|
+
filterParts.push(`${segmentLabels.join("")}concat=n=${segmentLabels.length}:v=0:a=1[${trackLabel}]`);
|
|
2033
|
+
trackOutputLabels.push(`[${trackLabel}]`);
|
|
2034
|
+
} else if (segmentLabels.length === 1) {
|
|
2035
|
+
const trackLabel = `atrack${trackOutputLabels.length}`;
|
|
2036
|
+
filterParts.push(`${segmentLabels[0]}acopy[${trackLabel}]`);
|
|
2037
|
+
trackOutputLabels.push(`[${trackLabel}]`);
|
|
1921
2038
|
}
|
|
1922
2039
|
}
|
|
1923
2040
|
if (videoStreams.length > 1) {
|
|
@@ -1927,16 +2044,16 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
|
|
|
1927
2044
|
} else if (videoStreams.length === 1) {
|
|
1928
2045
|
filterParts.push(`${videoStreams[0]}copy[outv]`);
|
|
1929
2046
|
}
|
|
1930
|
-
if (
|
|
2047
|
+
if (trackOutputLabels.length > 1) {
|
|
1931
2048
|
filterParts.push(
|
|
1932
|
-
`${
|
|
2049
|
+
`${trackOutputLabels.join("")}amix=inputs=${trackOutputLabels.length}:duration=longest:normalize=0[outa]`
|
|
1933
2050
|
);
|
|
1934
|
-
} else if (
|
|
1935
|
-
filterParts.push(`${
|
|
2051
|
+
} else if (trackOutputLabels.length === 1) {
|
|
2052
|
+
filterParts.push(`${trackOutputLabels[0]}acopy[outa]`);
|
|
1936
2053
|
}
|
|
1937
2054
|
args.push("-filter_complex", filterParts.join(";"));
|
|
1938
2055
|
args.push("-map", "[outv]");
|
|
1939
|
-
if (
|
|
2056
|
+
if (trackOutputLabels.length > 0) {
|
|
1940
2057
|
args.push("-map", "[outa]");
|
|
1941
2058
|
}
|
|
1942
2059
|
args.push(...presetSettings.ffmpegArgs);
|
|
@@ -1944,7 +2061,7 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
|
|
|
1944
2061
|
return args;
|
|
1945
2062
|
}
|
|
1946
2063
|
function runFFmpegProcess(ffmpegPath, args, onProgress) {
|
|
1947
|
-
return new Promise((
|
|
2064
|
+
return new Promise((resolve13, reject) => {
|
|
1948
2065
|
const ffmpeg = spawn(ffmpegPath, args, {
|
|
1949
2066
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1950
2067
|
});
|
|
@@ -1968,7 +2085,7 @@ function runFFmpegProcess(ffmpegPath, args, onProgress) {
|
|
|
1968
2085
|
});
|
|
1969
2086
|
ffmpeg.on("close", (code) => {
|
|
1970
2087
|
if (code === 0) {
|
|
1971
|
-
|
|
2088
|
+
resolve13();
|
|
1972
2089
|
} else {
|
|
1973
2090
|
const errorMatch = stderr.match(/Error.*$/m);
|
|
1974
2091
|
const errorMsg = errorMatch ? errorMatch[0] : `FFmpeg exited with code ${code}`;
|
|
@@ -2108,7 +2225,7 @@ async function handleExportToolCall(name, args) {
|
|
|
2108
2225
|
}
|
|
2109
2226
|
|
|
2110
2227
|
// ../cli/src/commands/ai-edit.ts
|
|
2111
|
-
import { resolve as
|
|
2228
|
+
import { resolve as resolve7, dirname, basename as basename2, extname, join as join2 } from "node:path";
|
|
2112
2229
|
import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir3 } from "node:fs/promises";
|
|
2113
2230
|
import { existsSync as existsSync2 } from "node:fs";
|
|
2114
2231
|
|
|
@@ -2913,7 +3030,7 @@ var GeminiProvider = class {
|
|
|
2913
3030
|
* Sleep helper
|
|
2914
3031
|
*/
|
|
2915
3032
|
sleep(ms) {
|
|
2916
|
-
return new Promise((
|
|
3033
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
2917
3034
|
}
|
|
2918
3035
|
/**
|
|
2919
3036
|
* Extend a previously generated Veo video
|
|
@@ -6756,7 +6873,7 @@ var RunwayProvider = class {
|
|
|
6756
6873
|
* Sleep helper
|
|
6757
6874
|
*/
|
|
6758
6875
|
sleep(ms) {
|
|
6759
|
-
return new Promise((
|
|
6876
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
6760
6877
|
}
|
|
6761
6878
|
};
|
|
6762
6879
|
var runwayProvider = new RunwayProvider();
|
|
@@ -7172,7 +7289,7 @@ var KlingProvider = class {
|
|
|
7172
7289
|
* Sleep helper
|
|
7173
7290
|
*/
|
|
7174
7291
|
sleep(ms) {
|
|
7175
|
-
return new Promise((
|
|
7292
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
7176
7293
|
}
|
|
7177
7294
|
};
|
|
7178
7295
|
var klingProvider = new KlingProvider();
|
|
@@ -7467,7 +7584,7 @@ var GrokProvider = class {
|
|
|
7467
7584
|
}
|
|
7468
7585
|
}
|
|
7469
7586
|
sleep(ms) {
|
|
7470
|
-
return new Promise((
|
|
7587
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
7471
7588
|
}
|
|
7472
7589
|
};
|
|
7473
7590
|
var grokProvider = new GrokProvider();
|
|
@@ -7726,7 +7843,7 @@ var ReplicateProvider = class {
|
|
|
7726
7843
|
* Sleep helper
|
|
7727
7844
|
*/
|
|
7728
7845
|
sleep(ms) {
|
|
7729
|
-
return new Promise((
|
|
7846
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
7730
7847
|
}
|
|
7731
7848
|
/**
|
|
7732
7849
|
* Generate music from text prompt using MusicGen
|
|
@@ -8110,12 +8227,12 @@ var replicateProvider = new ReplicateProvider();
|
|
|
8110
8227
|
// ../cli/src/utils/api-key.ts
|
|
8111
8228
|
import { createInterface } from "node:readline";
|
|
8112
8229
|
import { readFile as readFile4, writeFile as writeFile3, access as access3 } from "node:fs/promises";
|
|
8113
|
-
import { resolve as
|
|
8230
|
+
import { resolve as resolve6 } from "node:path";
|
|
8114
8231
|
import { config } from "dotenv";
|
|
8115
|
-
import
|
|
8232
|
+
import chalk3 from "chalk";
|
|
8116
8233
|
|
|
8117
8234
|
// ../cli/src/config/index.ts
|
|
8118
|
-
import { resolve as
|
|
8235
|
+
import { resolve as resolve5 } from "node:path";
|
|
8119
8236
|
import { homedir } from "node:os";
|
|
8120
8237
|
import { readFile as readFile3, writeFile as writeFile2, mkdir, access as access2 } from "node:fs/promises";
|
|
8121
8238
|
import { parse, stringify } from "yaml";
|
|
@@ -8151,8 +8268,8 @@ function createDefaultConfig() {
|
|
|
8151
8268
|
}
|
|
8152
8269
|
|
|
8153
8270
|
// ../cli/src/config/index.ts
|
|
8154
|
-
var CONFIG_DIR =
|
|
8155
|
-
var CONFIG_PATH =
|
|
8271
|
+
var CONFIG_DIR = resolve5(homedir(), ".vibeframe");
|
|
8272
|
+
var CONFIG_PATH = resolve5(CONFIG_DIR, "config.yaml");
|
|
8156
8273
|
async function loadConfig() {
|
|
8157
8274
|
try {
|
|
8158
8275
|
await access2(CONFIG_PATH);
|
|
@@ -8185,20 +8302,20 @@ async function getApiKeyFromConfig(providerKey) {
|
|
|
8185
8302
|
|
|
8186
8303
|
// ../cli/src/utils/api-key.ts
|
|
8187
8304
|
function loadEnv() {
|
|
8188
|
-
config({ path:
|
|
8305
|
+
config({ path: resolve6(process.cwd(), ".env"), debug: false, quiet: true });
|
|
8189
8306
|
const monorepoRoot = findMonorepoRoot();
|
|
8190
8307
|
if (monorepoRoot && monorepoRoot !== process.cwd()) {
|
|
8191
|
-
config({ path:
|
|
8308
|
+
config({ path: resolve6(monorepoRoot, ".env"), debug: false, quiet: true });
|
|
8192
8309
|
}
|
|
8193
8310
|
}
|
|
8194
8311
|
function findMonorepoRoot() {
|
|
8195
8312
|
let dir = process.cwd();
|
|
8196
8313
|
while (dir !== "/") {
|
|
8197
8314
|
try {
|
|
8198
|
-
__require.resolve(
|
|
8315
|
+
__require.resolve(resolve6(dir, "pnpm-workspace.yaml"));
|
|
8199
8316
|
return dir;
|
|
8200
8317
|
} catch {
|
|
8201
|
-
dir =
|
|
8318
|
+
dir = resolve6(dir, "..");
|
|
8202
8319
|
}
|
|
8203
8320
|
}
|
|
8204
8321
|
return null;
|
|
@@ -8208,7 +8325,7 @@ async function prompt(question, hidden = false) {
|
|
|
8208
8325
|
input: process.stdin,
|
|
8209
8326
|
output: process.stdout
|
|
8210
8327
|
});
|
|
8211
|
-
return new Promise((
|
|
8328
|
+
return new Promise((resolve13) => {
|
|
8212
8329
|
if (hidden && process.stdin.isTTY) {
|
|
8213
8330
|
process.stdout.write(question);
|
|
8214
8331
|
let input = "";
|
|
@@ -8222,7 +8339,7 @@ async function prompt(question, hidden = false) {
|
|
|
8222
8339
|
process.stdin.removeListener("data", onData);
|
|
8223
8340
|
process.stdout.write("\n");
|
|
8224
8341
|
rl.close();
|
|
8225
|
-
|
|
8342
|
+
resolve13(input);
|
|
8226
8343
|
} else if (char === "") {
|
|
8227
8344
|
process.exit(1);
|
|
8228
8345
|
} else if (char === "\x7F" || char === "\b") {
|
|
@@ -8237,7 +8354,7 @@ async function prompt(question, hidden = false) {
|
|
|
8237
8354
|
} else {
|
|
8238
8355
|
rl.question(question, (answer) => {
|
|
8239
8356
|
rl.close();
|
|
8240
|
-
|
|
8357
|
+
resolve13(answer);
|
|
8241
8358
|
});
|
|
8242
8359
|
}
|
|
8243
8360
|
});
|
|
@@ -8250,9 +8367,11 @@ async function getApiKey(envVar, providerName, optionValue) {
|
|
|
8250
8367
|
ANTHROPIC_API_KEY: "anthropic",
|
|
8251
8368
|
OPENAI_API_KEY: "openai",
|
|
8252
8369
|
GOOGLE_API_KEY: "google",
|
|
8370
|
+
XAI_API_KEY: "xai",
|
|
8253
8371
|
ELEVENLABS_API_KEY: "elevenlabs",
|
|
8254
8372
|
RUNWAY_API_SECRET: "runway",
|
|
8255
8373
|
KLING_API_KEY: "kling",
|
|
8374
|
+
OPENROUTER_API_KEY: "openrouter",
|
|
8256
8375
|
IMGBB_API_KEY: "imgbb",
|
|
8257
8376
|
REPLICATE_API_TOKEN: "replicate"
|
|
8258
8377
|
};
|
|
@@ -8272,22 +8391,22 @@ async function getApiKey(envVar, providerName, optionValue) {
|
|
|
8272
8391
|
return null;
|
|
8273
8392
|
}
|
|
8274
8393
|
console.log();
|
|
8275
|
-
console.log(
|
|
8276
|
-
console.log(
|
|
8394
|
+
console.log(chalk3.yellow(`${providerName} API key not found.`));
|
|
8395
|
+
console.log(chalk3.dim(`Set ${envVar} in .env (current directory), run 'vibe setup', or enter below.`));
|
|
8277
8396
|
console.log();
|
|
8278
|
-
const apiKey = await prompt(
|
|
8397
|
+
const apiKey = await prompt(chalk3.cyan(`Enter ${providerName} API key: `), true);
|
|
8279
8398
|
if (!apiKey || apiKey.trim() === "") {
|
|
8280
8399
|
return null;
|
|
8281
8400
|
}
|
|
8282
|
-
const save = await prompt(
|
|
8401
|
+
const save = await prompt(chalk3.cyan("Save to .env for future use? (y/N): "));
|
|
8283
8402
|
if (save.toLowerCase() === "y" || save.toLowerCase() === "yes") {
|
|
8284
8403
|
await saveApiKeyToEnv(envVar, apiKey.trim());
|
|
8285
|
-
console.log(
|
|
8404
|
+
console.log(chalk3.green("API key saved to .env"));
|
|
8286
8405
|
}
|
|
8287
8406
|
return apiKey.trim();
|
|
8288
8407
|
}
|
|
8289
8408
|
async function saveApiKeyToEnv(envVar, apiKey) {
|
|
8290
|
-
const envPath =
|
|
8409
|
+
const envPath = resolve6(process.cwd(), ".env");
|
|
8291
8410
|
let content = "";
|
|
8292
8411
|
try {
|
|
8293
8412
|
await access3(envPath);
|
|
@@ -9256,8 +9375,8 @@ async function applyTextOverlays(options) {
|
|
|
9256
9375
|
if (!texts || texts.length === 0) {
|
|
9257
9376
|
return { success: false, error: "No texts provided" };
|
|
9258
9377
|
}
|
|
9259
|
-
const absVideoPath =
|
|
9260
|
-
const absOutputPath =
|
|
9378
|
+
const absVideoPath = resolve7(process.cwd(), videoPath);
|
|
9379
|
+
const absOutputPath = resolve7(process.cwd(), outputPath);
|
|
9261
9380
|
if (!existsSync2(absVideoPath)) {
|
|
9262
9381
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
9263
9382
|
}
|
|
@@ -9628,10 +9747,10 @@ async function handleAiEditingToolCall(name, args) {
|
|
|
9628
9747
|
|
|
9629
9748
|
// ../cli/src/commands/ai-analyze.ts
|
|
9630
9749
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
9631
|
-
import { extname as extname2, resolve as
|
|
9750
|
+
import { extname as extname2, resolve as resolve8 } from "node:path";
|
|
9632
9751
|
import { existsSync as existsSync3 } from "node:fs";
|
|
9633
|
-
import
|
|
9634
|
-
import
|
|
9752
|
+
import chalk4 from "chalk";
|
|
9753
|
+
import ora3 from "ora";
|
|
9635
9754
|
async function executeGeminiVideo(options) {
|
|
9636
9755
|
try {
|
|
9637
9756
|
const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
|
|
@@ -9649,7 +9768,7 @@ async function executeGeminiVideo(options) {
|
|
|
9649
9768
|
if (isYouTube) {
|
|
9650
9769
|
videoData = options.source;
|
|
9651
9770
|
} else {
|
|
9652
|
-
const absPath =
|
|
9771
|
+
const absPath = resolve8(process.cwd(), options.source);
|
|
9653
9772
|
if (!existsSync3(absPath)) {
|
|
9654
9773
|
return { success: false, error: `File not found: ${absPath}` };
|
|
9655
9774
|
}
|
|
@@ -9722,7 +9841,7 @@ async function executeAnalyze(options) {
|
|
|
9722
9841
|
}
|
|
9723
9842
|
imageBuffer = Buffer.from(await response.arrayBuffer());
|
|
9724
9843
|
} else {
|
|
9725
|
-
const absPath =
|
|
9844
|
+
const absPath = resolve8(process.cwd(), source);
|
|
9726
9845
|
if (!existsSync3(absPath)) {
|
|
9727
9846
|
return { success: false, error: `File not found: ${absPath}` };
|
|
9728
9847
|
}
|
|
@@ -9757,7 +9876,7 @@ async function executeAnalyze(options) {
|
|
|
9757
9876
|
}
|
|
9758
9877
|
videoData = Buffer.from(await response.arrayBuffer());
|
|
9759
9878
|
} else {
|
|
9760
|
-
const absPath =
|
|
9879
|
+
const absPath = resolve8(process.cwd(), source);
|
|
9761
9880
|
if (!existsSync3(absPath)) {
|
|
9762
9881
|
return { success: false, error: `File not found: ${absPath}` };
|
|
9763
9882
|
}
|
|
@@ -9792,10 +9911,10 @@ async function executeAnalyze(options) {
|
|
|
9792
9911
|
|
|
9793
9912
|
// ../cli/src/commands/ai-review.ts
|
|
9794
9913
|
import { readFile as readFile7, rename } from "node:fs/promises";
|
|
9795
|
-
import { resolve as
|
|
9914
|
+
import { resolve as resolve9 } from "node:path";
|
|
9796
9915
|
import { existsSync as existsSync4 } from "node:fs";
|
|
9797
|
-
import
|
|
9798
|
-
import
|
|
9916
|
+
import chalk5 from "chalk";
|
|
9917
|
+
import ora4 from "ora";
|
|
9799
9918
|
init_exec_safe();
|
|
9800
9919
|
function parseReviewFeedback(response) {
|
|
9801
9920
|
let cleaned = response.trim();
|
|
@@ -9820,7 +9939,7 @@ function parseReviewFeedback(response) {
|
|
|
9820
9939
|
}
|
|
9821
9940
|
async function executeReview(options) {
|
|
9822
9941
|
const { videoPath, storyboardPath, autoApply = false, verify = false, model = "flash" } = options;
|
|
9823
|
-
const absVideoPath =
|
|
9942
|
+
const absVideoPath = resolve9(process.cwd(), videoPath);
|
|
9824
9943
|
if (!existsSync4(absVideoPath)) {
|
|
9825
9944
|
return { success: false, error: `Video not found: ${absVideoPath}` };
|
|
9826
9945
|
}
|
|
@@ -9830,7 +9949,7 @@ async function executeReview(options) {
|
|
|
9830
9949
|
}
|
|
9831
9950
|
let storyboardContext = "";
|
|
9832
9951
|
if (storyboardPath) {
|
|
9833
|
-
const absStoryboardPath =
|
|
9952
|
+
const absStoryboardPath = resolve9(process.cwd(), storyboardPath);
|
|
9834
9953
|
if (existsSync4(absStoryboardPath)) {
|
|
9835
9954
|
const content = await readFile7(absStoryboardPath, "utf-8");
|
|
9836
9955
|
storyboardContext = `
|
|
@@ -9886,7 +10005,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
9886
10005
|
};
|
|
9887
10006
|
if (autoApply && feedback.autoFixable.length > 0) {
|
|
9888
10007
|
let currentInput = absVideoPath;
|
|
9889
|
-
const outputBase = options.outputPath ?
|
|
10008
|
+
const outputBase = options.outputPath ? resolve9(process.cwd(), options.outputPath) : absVideoPath.replace(/(\.[^.]+)$/, "-reviewed$1");
|
|
9890
10009
|
for (const fix of feedback.autoFixable) {
|
|
9891
10010
|
if (fix.type === "color_grade" && fix.ffmpegFilter) {
|
|
9892
10011
|
try {
|
|
@@ -9935,8 +10054,8 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
9935
10054
|
// ../cli/src/commands/ai-image.ts
|
|
9936
10055
|
import { readFile as readFile8, writeFile as writeFile6, mkdir as mkdir4 } from "node:fs/promises";
|
|
9937
10056
|
import { existsSync as existsSync5 } from "node:fs";
|
|
9938
|
-
import
|
|
9939
|
-
import
|
|
10057
|
+
import chalk6 from "chalk";
|
|
10058
|
+
import ora5 from "ora";
|
|
9940
10059
|
init_exec_safe();
|
|
9941
10060
|
async function executeThumbnailBestFrame(options) {
|
|
9942
10061
|
const {
|
|
@@ -10158,9 +10277,9 @@ async function handleAiAnalysisToolCall(name, args) {
|
|
|
10158
10277
|
|
|
10159
10278
|
// ../cli/src/commands/ai-script-pipeline.ts
|
|
10160
10279
|
import { readFile as readFile9, writeFile as writeFile7, mkdir as mkdir5, unlink, rename as rename2 } from "node:fs/promises";
|
|
10161
|
-
import { resolve as
|
|
10280
|
+
import { resolve as resolve10, basename as basename3, extname as extname3 } from "node:path";
|
|
10162
10281
|
import { existsSync as existsSync6 } from "node:fs";
|
|
10163
|
-
import
|
|
10282
|
+
import chalk7 from "chalk";
|
|
10164
10283
|
init_exec_safe();
|
|
10165
10284
|
|
|
10166
10285
|
// ../cli/src/commands/ai-helpers.ts
|
|
@@ -10180,7 +10299,37 @@ async function downloadVideo(url, apiKey) {
|
|
|
10180
10299
|
var DEFAULT_VIDEO_RETRIES = 2;
|
|
10181
10300
|
var RETRY_DELAY_MS = 5e3;
|
|
10182
10301
|
function sleep(ms) {
|
|
10183
|
-
return new Promise((
|
|
10302
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
10303
|
+
}
|
|
10304
|
+
async function generateVideoWithRetryGrok(grok, segment, options, maxRetries, onProgress) {
|
|
10305
|
+
const prompt2 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
|
|
10306
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
10307
|
+
try {
|
|
10308
|
+
const result = await grok.generateVideo(prompt2, {
|
|
10309
|
+
prompt: prompt2,
|
|
10310
|
+
duration: options.duration,
|
|
10311
|
+
aspectRatio: options.aspectRatio,
|
|
10312
|
+
referenceImage: options.referenceImage
|
|
10313
|
+
});
|
|
10314
|
+
if (result.status !== "failed" && result.id) {
|
|
10315
|
+
return { requestId: result.id };
|
|
10316
|
+
}
|
|
10317
|
+
if (attempt < maxRetries) {
|
|
10318
|
+
onProgress?.(`\u26A0 Retry ${attempt + 1}/${maxRetries}...`);
|
|
10319
|
+
await sleep(RETRY_DELAY_MS);
|
|
10320
|
+
}
|
|
10321
|
+
} catch (err) {
|
|
10322
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
10323
|
+
if (attempt < maxRetries) {
|
|
10324
|
+
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
10325
|
+
await sleep(RETRY_DELAY_MS);
|
|
10326
|
+
} else {
|
|
10327
|
+
console.error(chalk7.dim(`
|
|
10328
|
+
[Grok error: ${errMsg}]`));
|
|
10329
|
+
}
|
|
10330
|
+
}
|
|
10331
|
+
}
|
|
10332
|
+
return null;
|
|
10184
10333
|
}
|
|
10185
10334
|
async function generateVideoWithRetryKling(kling, segment, options, maxRetries, onProgress) {
|
|
10186
10335
|
const prompt2 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
|
|
@@ -10211,7 +10360,7 @@ async function generateVideoWithRetryKling(kling, segment, options, maxRetries,
|
|
|
10211
10360
|
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
10212
10361
|
await sleep(RETRY_DELAY_MS);
|
|
10213
10362
|
} else {
|
|
10214
|
-
console.error(
|
|
10363
|
+
console.error(chalk7.dim(`
|
|
10215
10364
|
[Kling error: ${errMsg}]`));
|
|
10216
10365
|
}
|
|
10217
10366
|
}
|
|
@@ -10240,7 +10389,7 @@ async function generateVideoWithRetryRunway(runway, segment, referenceImage, opt
|
|
|
10240
10389
|
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
10241
10390
|
await sleep(RETRY_DELAY_MS);
|
|
10242
10391
|
} else {
|
|
10243
|
-
console.error(
|
|
10392
|
+
console.error(chalk7.dim(`
|
|
10244
10393
|
[Runway error: ${errMsg}]`));
|
|
10245
10394
|
}
|
|
10246
10395
|
}
|
|
@@ -10271,7 +10420,7 @@ async function generateVideoWithRetryVeo(gemini, segment, options, maxRetries, o
|
|
|
10271
10420
|
onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
|
|
10272
10421
|
await sleep(RETRY_DELAY_MS);
|
|
10273
10422
|
} else {
|
|
10274
|
-
console.error(
|
|
10423
|
+
console.error(chalk7.dim(`
|
|
10275
10424
|
[Veo error: ${errMsg}]`));
|
|
10276
10425
|
}
|
|
10277
10426
|
}
|
|
@@ -10326,24 +10475,23 @@ async function executeScriptToVideo(options) {
|
|
|
10326
10475
|
}
|
|
10327
10476
|
let videoApiKey;
|
|
10328
10477
|
if (!options.imagesOnly) {
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
}
|
|
10334
|
-
}
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
}
|
|
10339
|
-
}
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
}
|
|
10478
|
+
const generatorKeyMap = {
|
|
10479
|
+
grok: { envVar: "XAI_API_KEY", name: "xAI (Grok)" },
|
|
10480
|
+
kling: { envVar: "KLING_API_KEY", name: "Kling" },
|
|
10481
|
+
runway: { envVar: "RUNWAY_API_SECRET", name: "Runway" },
|
|
10482
|
+
veo: { envVar: "GOOGLE_API_KEY", name: "Google (Veo)" }
|
|
10483
|
+
};
|
|
10484
|
+
const generator = options.generator || "grok";
|
|
10485
|
+
const generatorInfo = generatorKeyMap[generator];
|
|
10486
|
+
if (!generatorInfo) {
|
|
10487
|
+
return { success: false, outputDir, scenes: 0, error: `Invalid generator: ${options.generator}. Available: ${Object.keys(generatorKeyMap).join(", ")}` };
|
|
10488
|
+
}
|
|
10489
|
+
videoApiKey = await getApiKey(generatorInfo.envVar, generatorInfo.name) ?? void 0;
|
|
10490
|
+
if (!videoApiKey) {
|
|
10491
|
+
return { success: false, outputDir, scenes: 0, error: `${generatorInfo.name} API key required (or use imagesOnly option). Run 'vibe setup' or set ${generatorInfo.envVar} in .env` };
|
|
10344
10492
|
}
|
|
10345
10493
|
}
|
|
10346
|
-
const absOutputDir =
|
|
10494
|
+
const absOutputDir = resolve10(process.cwd(), outputDir);
|
|
10347
10495
|
if (!existsSync6(absOutputDir)) {
|
|
10348
10496
|
await mkdir5(absOutputDir, { recursive: true });
|
|
10349
10497
|
}
|
|
@@ -10365,7 +10513,7 @@ async function executeScriptToVideo(options) {
|
|
|
10365
10513
|
if (segments.length === 0) {
|
|
10366
10514
|
return { success: false, outputDir, scenes: 0, error: "Failed to generate storyboard" };
|
|
10367
10515
|
}
|
|
10368
|
-
const storyboardPath =
|
|
10516
|
+
const storyboardPath = resolve10(absOutputDir, "storyboard.json");
|
|
10369
10517
|
await writeFile7(storyboardPath, JSON.stringify(segments, null, 2), "utf-8");
|
|
10370
10518
|
const result = {
|
|
10371
10519
|
success: true,
|
|
@@ -10399,7 +10547,7 @@ async function executeScriptToVideo(options) {
|
|
|
10399
10547
|
voiceId: options.voice
|
|
10400
10548
|
});
|
|
10401
10549
|
if (ttsResult.success && ttsResult.audioBuffer) {
|
|
10402
|
-
const audioPath =
|
|
10550
|
+
const audioPath = resolve10(absOutputDir, `narration-${i + 1}.mp3`);
|
|
10403
10551
|
await writeFile7(audioPath, ttsResult.audioBuffer);
|
|
10404
10552
|
const actualDuration = await getAudioDuration(audioPath);
|
|
10405
10553
|
segment.duration = actualDuration;
|
|
@@ -10487,7 +10635,7 @@ async function executeScriptToVideo(options) {
|
|
|
10487
10635
|
}
|
|
10488
10636
|
}
|
|
10489
10637
|
}
|
|
10490
|
-
const imagePath =
|
|
10638
|
+
const imagePath = resolve10(absOutputDir, `scene-${i + 1}.png`);
|
|
10491
10639
|
if (imageBuffer) {
|
|
10492
10640
|
await writeFile7(imagePath, imageBuffer);
|
|
10493
10641
|
imagePaths.push(imagePath);
|
|
@@ -10511,7 +10659,57 @@ async function executeScriptToVideo(options) {
|
|
|
10511
10659
|
const videoPaths = [];
|
|
10512
10660
|
const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
|
|
10513
10661
|
if (!options.imagesOnly && videoApiKey) {
|
|
10514
|
-
if (options.generator === "
|
|
10662
|
+
if (options.generator === "grok") {
|
|
10663
|
+
const grok = new GrokProvider();
|
|
10664
|
+
await grok.initialize({ apiKey: videoApiKey });
|
|
10665
|
+
for (let i = 0; i < segments.length; i++) {
|
|
10666
|
+
if (!imagePaths[i]) {
|
|
10667
|
+
videoPaths.push("");
|
|
10668
|
+
continue;
|
|
10669
|
+
}
|
|
10670
|
+
const segment = segments[i];
|
|
10671
|
+
const videoDuration = Math.min(15, Math.max(1, segment.duration));
|
|
10672
|
+
const imageBuffer = await readFile9(imagePaths[i]);
|
|
10673
|
+
const ext = extname3(imagePaths[i]).toLowerCase().slice(1);
|
|
10674
|
+
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
10675
|
+
const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
10676
|
+
const taskResult = await generateVideoWithRetryGrok(
|
|
10677
|
+
grok,
|
|
10678
|
+
segment,
|
|
10679
|
+
{ duration: videoDuration, aspectRatio: options.aspectRatio || "16:9", referenceImage },
|
|
10680
|
+
maxRetries
|
|
10681
|
+
);
|
|
10682
|
+
if (taskResult) {
|
|
10683
|
+
try {
|
|
10684
|
+
const waitResult = await grok.waitForCompletion(taskResult.requestId, void 0, 3e5);
|
|
10685
|
+
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
10686
|
+
const videoPath = resolve10(absOutputDir, `scene-${i + 1}.mp4`);
|
|
10687
|
+
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
10688
|
+
await writeFile7(videoPath, buffer);
|
|
10689
|
+
const targetDuration = segment.duration;
|
|
10690
|
+
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
10691
|
+
if (actualVideoDuration < targetDuration - 0.1) {
|
|
10692
|
+
const extendedPath = resolve10(absOutputDir, `scene-${i + 1}-extended.mp4`);
|
|
10693
|
+
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
10694
|
+
await unlink(videoPath);
|
|
10695
|
+
await rename2(extendedPath, videoPath);
|
|
10696
|
+
}
|
|
10697
|
+
videoPaths.push(videoPath);
|
|
10698
|
+
result.videos.push(videoPath);
|
|
10699
|
+
} else {
|
|
10700
|
+
videoPaths.push("");
|
|
10701
|
+
result.failedScenes.push(i + 1);
|
|
10702
|
+
}
|
|
10703
|
+
} catch {
|
|
10704
|
+
videoPaths.push("");
|
|
10705
|
+
result.failedScenes.push(i + 1);
|
|
10706
|
+
}
|
|
10707
|
+
} else {
|
|
10708
|
+
videoPaths.push("");
|
|
10709
|
+
result.failedScenes.push(i + 1);
|
|
10710
|
+
}
|
|
10711
|
+
}
|
|
10712
|
+
} else if (options.generator === "kling") {
|
|
10515
10713
|
const kling = new KlingProvider();
|
|
10516
10714
|
await kling.initialize({ apiKey: videoApiKey });
|
|
10517
10715
|
if (!kling.isConfigured()) {
|
|
@@ -10534,13 +10732,13 @@ async function executeScriptToVideo(options) {
|
|
|
10534
10732
|
try {
|
|
10535
10733
|
const waitResult = await kling.waitForCompletion(taskResult.taskId, taskResult.type, void 0, 6e5);
|
|
10536
10734
|
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
10537
|
-
const videoPath =
|
|
10735
|
+
const videoPath = resolve10(absOutputDir, `scene-${i + 1}.mp4`);
|
|
10538
10736
|
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
10539
10737
|
await writeFile7(videoPath, buffer);
|
|
10540
10738
|
const targetDuration = segment.duration;
|
|
10541
10739
|
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
10542
10740
|
if (actualVideoDuration < targetDuration - 0.1) {
|
|
10543
|
-
const extendedPath =
|
|
10741
|
+
const extendedPath = resolve10(absOutputDir, `scene-${i + 1}-extended.mp4`);
|
|
10544
10742
|
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
10545
10743
|
await unlink(videoPath);
|
|
10546
10744
|
await rename2(extendedPath, videoPath);
|
|
@@ -10580,13 +10778,13 @@ async function executeScriptToVideo(options) {
|
|
|
10580
10778
|
try {
|
|
10581
10779
|
const waitResult = await veo.waitForVideoCompletion(taskResult.operationName, void 0, 3e5);
|
|
10582
10780
|
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
10583
|
-
const videoPath =
|
|
10781
|
+
const videoPath = resolve10(absOutputDir, `scene-${i + 1}.mp4`);
|
|
10584
10782
|
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
10585
10783
|
await writeFile7(videoPath, buffer);
|
|
10586
10784
|
const targetDuration = segment.duration;
|
|
10587
10785
|
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
10588
10786
|
if (actualVideoDuration < targetDuration - 0.1) {
|
|
10589
|
-
const extendedPath =
|
|
10787
|
+
const extendedPath = resolve10(absOutputDir, `scene-${i + 1}-extended.mp4`);
|
|
10590
10788
|
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
10591
10789
|
await unlink(videoPath);
|
|
10592
10790
|
await rename2(extendedPath, videoPath);
|
|
@@ -10632,13 +10830,13 @@ async function executeScriptToVideo(options) {
|
|
|
10632
10830
|
try {
|
|
10633
10831
|
const waitResult = await runway.waitForCompletion(taskResult.taskId, void 0, 3e5);
|
|
10634
10832
|
if (waitResult.status === "completed" && waitResult.videoUrl) {
|
|
10635
|
-
const videoPath =
|
|
10833
|
+
const videoPath = resolve10(absOutputDir, `scene-${i + 1}.mp4`);
|
|
10636
10834
|
const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
|
|
10637
10835
|
await writeFile7(videoPath, buffer);
|
|
10638
10836
|
const targetDuration = segment.duration;
|
|
10639
10837
|
const actualVideoDuration = await getVideoDuration(videoPath);
|
|
10640
10838
|
if (actualVideoDuration < targetDuration - 0.1) {
|
|
10641
|
-
const extendedPath =
|
|
10839
|
+
const extendedPath = resolve10(absOutputDir, `scene-${i + 1}-extended.mp4`);
|
|
10642
10840
|
await extendVideoNaturally(videoPath, targetDuration, extendedPath);
|
|
10643
10841
|
await unlink(videoPath);
|
|
10644
10842
|
await rename2(extendedPath, videoPath);
|
|
@@ -10751,13 +10949,13 @@ async function executeScriptToVideo(options) {
|
|
|
10751
10949
|
});
|
|
10752
10950
|
currentTime += actualDuration;
|
|
10753
10951
|
}
|
|
10754
|
-
const projectPath =
|
|
10952
|
+
const projectPath = resolve10(absOutputDir, "project.vibe.json");
|
|
10755
10953
|
await writeFile7(projectPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
10756
10954
|
result.projectPath = projectPath;
|
|
10757
10955
|
result.totalDuration = currentTime;
|
|
10758
10956
|
if (options.review) {
|
|
10759
10957
|
try {
|
|
10760
|
-
const storyboardFile =
|
|
10958
|
+
const storyboardFile = resolve10(absOutputDir, "storyboard.json");
|
|
10761
10959
|
const reviewTarget = videoPaths.find((p) => p && p !== "") || imagePaths.find((p) => p && p !== "");
|
|
10762
10960
|
if (reviewTarget) {
|
|
10763
10961
|
const reviewResult = await executeReview({
|
|
@@ -10788,10 +10986,10 @@ async function executeScriptToVideo(options) {
|
|
|
10788
10986
|
|
|
10789
10987
|
// ../cli/src/commands/ai-highlights.ts
|
|
10790
10988
|
import { readFile as readFile10, writeFile as writeFile8, mkdir as mkdir6 } from "node:fs/promises";
|
|
10791
|
-
import { resolve as
|
|
10989
|
+
import { resolve as resolve11, dirname as dirname2, basename as basename4, extname as extname4 } from "node:path";
|
|
10792
10990
|
import { existsSync as existsSync7 } from "node:fs";
|
|
10793
|
-
import
|
|
10794
|
-
import
|
|
10991
|
+
import chalk8 from "chalk";
|
|
10992
|
+
import ora6 from "ora";
|
|
10795
10993
|
init_exec_safe();
|
|
10796
10994
|
function filterHighlights(highlights, options) {
|
|
10797
10995
|
let filtered = highlights.filter((h) => h.confidence >= options.threshold);
|
|
@@ -10815,7 +11013,7 @@ function filterHighlights(highlights, options) {
|
|
|
10815
11013
|
}
|
|
10816
11014
|
async function executeHighlights(options) {
|
|
10817
11015
|
try {
|
|
10818
|
-
const absPath =
|
|
11016
|
+
const absPath = resolve11(process.cwd(), options.media);
|
|
10819
11017
|
if (!existsSync7(absPath)) {
|
|
10820
11018
|
return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
|
|
10821
11019
|
}
|
|
@@ -10963,7 +11161,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
10963
11161
|
totalHighlightDuration
|
|
10964
11162
|
};
|
|
10965
11163
|
if (options.output) {
|
|
10966
|
-
const outputPath =
|
|
11164
|
+
const outputPath = resolve11(process.cwd(), options.output);
|
|
10967
11165
|
await writeFile8(outputPath, JSON.stringify({
|
|
10968
11166
|
sourceFile: absPath,
|
|
10969
11167
|
totalDuration: sourceDuration,
|
|
@@ -10998,7 +11196,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
10998
11196
|
currentTime += highlight.duration;
|
|
10999
11197
|
}
|
|
11000
11198
|
}
|
|
11001
|
-
const projectPath =
|
|
11199
|
+
const projectPath = resolve11(process.cwd(), options.project);
|
|
11002
11200
|
await writeFile8(projectPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
|
|
11003
11201
|
extractResult.projectPath = projectPath;
|
|
11004
11202
|
}
|
|
@@ -11018,7 +11216,7 @@ async function executeAutoShorts(options) {
|
|
|
11018
11216
|
if (!commandExists("ffmpeg")) {
|
|
11019
11217
|
return { success: false, shorts: [], error: "FFmpeg not found" };
|
|
11020
11218
|
}
|
|
11021
|
-
const absPath =
|
|
11219
|
+
const absPath = resolve11(process.cwd(), options.video);
|
|
11022
11220
|
if (!existsSync7(absPath)) {
|
|
11023
11221
|
return { success: false, shorts: [], error: `File not found: ${absPath}` };
|
|
11024
11222
|
}
|
|
@@ -11146,7 +11344,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
11146
11344
|
}))
|
|
11147
11345
|
};
|
|
11148
11346
|
}
|
|
11149
|
-
const outputDir = options.outputDir ?
|
|
11347
|
+
const outputDir = options.outputDir ? resolve11(process.cwd(), options.outputDir) : dirname2(absPath);
|
|
11150
11348
|
if (options.outputDir && !existsSync7(outputDir)) {
|
|
11151
11349
|
await mkdir6(outputDir, { recursive: true });
|
|
11152
11350
|
}
|
|
@@ -11157,7 +11355,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
11157
11355
|
for (let i = 0; i < selectedHighlights.length; i++) {
|
|
11158
11356
|
const h = selectedHighlights[i];
|
|
11159
11357
|
const baseName = basename4(absPath, extname4(absPath));
|
|
11160
|
-
const outputPath =
|
|
11358
|
+
const outputPath = resolve11(outputDir, `${baseName}-short-${i + 1}.mp4`);
|
|
11161
11359
|
const { stdout: probeOut } = await execSafe("ffprobe", [
|
|
11162
11360
|
"-v",
|
|
11163
11361
|
"error",
|
|
@@ -11449,7 +11647,7 @@ async function handleToolCall(name, args) {
|
|
|
11449
11647
|
|
|
11450
11648
|
// src/resources/index.ts
|
|
11451
11649
|
import { readFile as readFile11 } from "node:fs/promises";
|
|
11452
|
-
import { resolve as
|
|
11650
|
+
import { resolve as resolve12 } from "node:path";
|
|
11453
11651
|
var resources = [
|
|
11454
11652
|
{
|
|
11455
11653
|
uri: "vibe://project/current",
|
|
@@ -11484,7 +11682,7 @@ var resources = [
|
|
|
11484
11682
|
];
|
|
11485
11683
|
var currentProjectPath = process.env.VIBE_PROJECT_PATH || null;
|
|
11486
11684
|
async function loadProject2(projectPath) {
|
|
11487
|
-
const absPath =
|
|
11685
|
+
const absPath = resolve12(process.cwd(), projectPath);
|
|
11488
11686
|
const content = await readFile11(absPath, "utf-8");
|
|
11489
11687
|
const data = JSON.parse(content);
|
|
11490
11688
|
return Project.fromJSON(data);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibeframe/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "VibeFrame MCP Server - AI-native video editing via Model Context Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"tsx": "^4.21.0",
|
|
58
58
|
"typescript": "^5.3.3",
|
|
59
59
|
"vitest": "^1.2.2",
|
|
60
|
-
"@vibeframe/cli": "0.
|
|
61
|
-
"@vibeframe/core": "0.
|
|
60
|
+
"@vibeframe/cli": "0.33.0",
|
|
61
|
+
"@vibeframe/core": "0.33.0"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|
|
64
64
|
"node": ">=20"
|