@vibeframe/mcp-server 0.33.0 → 0.33.1
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 +35 -3
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1556,6 +1556,16 @@ async function resolveProjectPath(inputPath) {
|
|
|
1556
1556
|
}
|
|
1557
1557
|
return filePath;
|
|
1558
1558
|
}
|
|
1559
|
+
async function getMediaDuration(filePath, mediaType, defaultImageDuration = 5) {
|
|
1560
|
+
if (mediaType === "image") {
|
|
1561
|
+
return defaultImageDuration;
|
|
1562
|
+
}
|
|
1563
|
+
try {
|
|
1564
|
+
return await ffprobeDuration(filePath);
|
|
1565
|
+
} catch {
|
|
1566
|
+
return defaultImageDuration;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1559
1569
|
async function checkHasAudio(filePath) {
|
|
1560
1570
|
try {
|
|
1561
1571
|
const { stdout } = await execSafe("ffprobe", [
|
|
@@ -1600,11 +1610,19 @@ async function runExport(projectPath, outputPath, options = {}) {
|
|
|
1600
1610
|
const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
|
|
1601
1611
|
const sources = project.getSources();
|
|
1602
1612
|
const sourceAudioMap = /* @__PURE__ */ new Map();
|
|
1613
|
+
const sourceActualDurationMap = /* @__PURE__ */ new Map();
|
|
1603
1614
|
for (const clip of clips) {
|
|
1604
1615
|
const source = sources.find((s) => s.id === clip.sourceId);
|
|
1605
1616
|
if (source) {
|
|
1606
1617
|
try {
|
|
1607
1618
|
await access(source.url);
|
|
1619
|
+
if (!sourceActualDurationMap.has(source.id)) {
|
|
1620
|
+
try {
|
|
1621
|
+
const dur = await getMediaDuration(source.url, source.type);
|
|
1622
|
+
if (dur > 0) sourceActualDurationMap.set(source.id, dur);
|
|
1623
|
+
} catch {
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1608
1626
|
if (source.type === "video" && !sourceAudioMap.has(source.id)) {
|
|
1609
1627
|
sourceAudioMap.set(source.id, await checkHasAudio(source.url));
|
|
1610
1628
|
}
|
|
@@ -1616,7 +1634,7 @@ async function runExport(projectPath, outputPath, options = {}) {
|
|
|
1616
1634
|
}
|
|
1617
1635
|
}
|
|
1618
1636
|
}
|
|
1619
|
-
const ffmpegArgs = buildFFmpegArgs(clips, sources, presetSettings, finalOutputPath, { overwrite, format, gapFill }, sourceAudioMap);
|
|
1637
|
+
const ffmpegArgs = buildFFmpegArgs(clips, sources, presetSettings, finalOutputPath, { overwrite, format, gapFill }, sourceAudioMap, sourceActualDurationMap);
|
|
1620
1638
|
await runFFmpegProcess(ffmpegPath, ffmpegArgs, () => {
|
|
1621
1639
|
});
|
|
1622
1640
|
return {
|
|
@@ -1687,11 +1705,19 @@ No API keys needed. Requires FFmpeg.`).action(async (projectPath, options) => {
|
|
|
1687
1705
|
const sources = project.getSources();
|
|
1688
1706
|
spinner.text = "Verifying source files...";
|
|
1689
1707
|
const sourceAudioMap = /* @__PURE__ */ new Map();
|
|
1708
|
+
const sourceActualDurationMap = /* @__PURE__ */ new Map();
|
|
1690
1709
|
for (const clip of clips) {
|
|
1691
1710
|
const source = sources.find((s) => s.id === clip.sourceId);
|
|
1692
1711
|
if (source) {
|
|
1693
1712
|
try {
|
|
1694
1713
|
await access(source.url);
|
|
1714
|
+
if (!sourceActualDurationMap.has(source.id)) {
|
|
1715
|
+
try {
|
|
1716
|
+
const dur = await getMediaDuration(source.url, source.type);
|
|
1717
|
+
if (dur > 0) sourceActualDurationMap.set(source.id, dur);
|
|
1718
|
+
} catch {
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1695
1721
|
if (source.type === "video" && !sourceAudioMap.has(source.id)) {
|
|
1696
1722
|
sourceAudioMap.set(source.id, await checkHasAudio(source.url));
|
|
1697
1723
|
}
|
|
@@ -1703,7 +1729,7 @@ No API keys needed. Requires FFmpeg.`).action(async (projectPath, options) => {
|
|
|
1703
1729
|
}
|
|
1704
1730
|
spinner.text = "Building export command...";
|
|
1705
1731
|
const gapFillStrategy = options.gapFill === "black" ? "black" : "extend";
|
|
1706
|
-
const ffmpegArgs = buildFFmpegArgs(clips, sources, presetSettings, outputPath, { ...options, gapFill: gapFillStrategy }, sourceAudioMap);
|
|
1732
|
+
const ffmpegArgs = buildFFmpegArgs(clips, sources, presetSettings, outputPath, { ...options, gapFill: gapFillStrategy }, sourceAudioMap, sourceActualDurationMap);
|
|
1707
1733
|
if (process.env.DEBUG) {
|
|
1708
1734
|
console.log("\nFFmpeg command:");
|
|
1709
1735
|
console.log("ffmpeg", ffmpegArgs.join(" "));
|
|
@@ -1827,7 +1853,7 @@ function createGapFillPlans(gaps, clips, sources) {
|
|
|
1827
1853
|
return { gap, fills };
|
|
1828
1854
|
});
|
|
1829
1855
|
}
|
|
1830
|
-
function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, sourceAudioMap = /* @__PURE__ */ new Map()) {
|
|
1856
|
+
function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, sourceAudioMap = /* @__PURE__ */ new Map(), sourceActualDurationMap = /* @__PURE__ */ new Map()) {
|
|
1831
1857
|
const args = [];
|
|
1832
1858
|
if (options.overwrite) {
|
|
1833
1859
|
args.push("-y");
|
|
@@ -1912,6 +1938,12 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
|
|
|
1912
1938
|
const trimStart = clip.sourceStartOffset;
|
|
1913
1939
|
const trimEnd = clip.sourceStartOffset + clip.duration;
|
|
1914
1940
|
videoFilter = `[${srcIdx}:v]trim=start=${trimStart}:end=${trimEnd},setpts=PTS-STARTPTS`;
|
|
1941
|
+
const sourceDuration = sourceActualDurationMap.get(source.id) || source.duration || 0;
|
|
1942
|
+
const availableDuration = sourceDuration - clip.sourceStartOffset;
|
|
1943
|
+
if (availableDuration > 0 && availableDuration < clip.duration - 0.1) {
|
|
1944
|
+
const padDuration = clip.duration - availableDuration;
|
|
1945
|
+
videoFilter += `,tpad=stop_mode=clone:stop_duration=${padDuration.toFixed(3)}`;
|
|
1946
|
+
}
|
|
1915
1947
|
}
|
|
1916
1948
|
videoFilter += `,scale=${targetWidth}:${targetHeight}:force_original_aspect_ratio=decrease,pad=${targetWidth}:${targetHeight}:(ow-iw)/2:(oh-ih)/2,setsar=1`;
|
|
1917
1949
|
for (const effect of clip.effects || []) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibeframe/mcp-server",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.1",
|
|
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.33.
|
|
61
|
-
"@vibeframe/core": "0.33.
|
|
60
|
+
"@vibeframe/cli": "0.33.1",
|
|
61
|
+
"@vibeframe/core": "0.33.1"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|
|
64
64
|
"node": ">=20"
|