@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.
Files changed (2) hide show
  1. package/dist/index.js +35 -3
  2. 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.0",
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.0",
61
- "@vibeframe/core": "0.33.0"
60
+ "@vibeframe/cli": "0.33.1",
61
+ "@vibeframe/core": "0.33.1"
62
62
  },
63
63
  "engines": {
64
64
  "node": ">=20"