@amg555/frameforge-mcp 2.7.8 → 2.7.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.
@@ -2,7 +2,7 @@
2
2
  "name": "frameforge",
3
3
  "displayName": "FrameForge — AI Video Production",
4
4
  "description": "Full AI video production engine. Produce short-form reels (1080×1920), YouTube videos (1920×1080), or code-driven animated explainers — directly from your IDE. Playwright + FFmpeg renders every frame. 34 MCP tools. Whisper, yt-dlp, broadcast audio, 24 film styles, ClaudeAnimationBase, onetake 39-move library, HyperFrames motion craft. 62 Motion Graphics Styles.",
5
- "version": "2.7.8",
5
+ "version": "2.7.9",
6
6
  "homepage": "https://github.com/AMG555/FrameForge",
7
7
  "plugins": [
8
8
  {
package/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ All notable changes to FrameForge are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## [2.7.9] — 2026-11-13
8
+
9
+ ### Fixed
10
+ - **TTS language reliability**: `selectNarratorVoice` now accepts a `langHint` param (BCP-47) and covers 25 languages with the correct edge-tts Neural voice. Added Unicode-range detection for Korean (`\uAC00-\uD7AF`), Kannada (`\u0C80-\u0CFF`), Bengali (`\u0980-\u09FF`), and Cyrillic/Russian (`\u0400-\u04FF`). Punjabi entry corrected to gender-neutral `pa-IN-OjasvNeural` (edge-tts only ships one Punjabi voice).
11
+ - **Language propagated end-to-end**: `render_tts` tool now accepts a `language` field; `runAssets` passes `sceneLang` to every `tts.synthesize()` call; `_edgeTTS` defensive `safeVoice` fallback now uses `_overrides.language`. `TTSProvider` constructor JSDoc documents `opts.language`.
12
+ - **Voice Consistency Lock**: `runAssets` pre-resolves a single canonical narrator voice across all narration text before the scene loop — no more mid-video voice drift from punctuation/script variations. Per-scene `scene.voice` still overrides for intentional multi-speaker dialogue.
13
+ - **Parallelism default**: `runAssets` function default raised from 1 → 4 (matching `create_video_from_script` and `render_from_scenes`); TTS now runs 4 scenes concurrently by default.
14
+ - **TTS retry delay**: `baseDelayMs` in `runAssets` reduced from 8000ms → 2000ms; inter-batch pause skipped entirely for `edge-tts` (local subprocess, no rate limit).
15
+
16
+ ---
17
+
7
18
  ## [2.7.8] — 2026-11-12
8
19
 
9
20
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amg555/frameforge-mcp",
3
- "version": "2.7.8",
3
+ "version": "2.7.9",
4
4
  "description": "FrameForge — Universal AI video creation & autonomous editing engine for any AI agent, CLI, or IDE. npx @amg555/frameforge-mcp. FFmpeg + TTS on your machine. 34 MCP tools. Whisper transcription, yt-dlp ingestion, broadcast-grade audio, 62 procedural motion styles, 6 viral faceless shorts engines, autonomous video editor with retake/silence cutting, J/L-cuts, timeline export to DaVinci/Premiere.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -166,16 +166,17 @@ const TOOLS = {
166
166
  sceneIndex: { type: 'number' },
167
167
  text: { type: 'string' },
168
168
  voice: { type: 'string', description: 'Voice ID (e.g. en-US-GuyNeural). Overrides TTS_VOICE env var.' },
169
+ language: { type: 'string', description: 'BCP-47 language hint (e.g. "ml", "hi", "fr"). Auto-selects best voice when voice is not set.' },
169
170
  },
170
171
  },
171
- handler: async ({ productionId, sceneIndex, text, voice }) => {
172
+ handler: async ({ productionId, sceneIndex, text, voice, language = '' }) => {
172
173
  const vdir = path.join(FILES_DIR, 'video_tmp');
173
174
  const outputPath = path.join(vdir, `voice_${sceneIndex}.m4a`);
174
175
  await fs.mkdir(vdir, { recursive: true });
175
176
 
176
177
  await retryPol.executeStage('TTS', async () => {
177
- // voice arg overrides env; rate is read from TTS_RATE env inside synthesize()
178
- return tts.synthesize({ text, voice, outputPath });
178
+ // voice arg overrides env; language enables BCP-47 auto-voice selection
179
+ return tts.synthesize({ text, voice, outputPath, language });
179
180
  });
180
181
 
181
182
  const stat = await fs.stat(outputPath);
@@ -2124,7 +2125,7 @@ Professional audio rules applied:
2124
2125
  planMd += `- L-cuts: current audio extends 0.3s into next clip's video\n\n`;
2125
2126
  planMd += `## 🚀 Execution & Approval\n\n`;
2126
2127
  planMd += `Review this plan, then execute with \`edit_video\` using the payload in \`edit.json\`.\n`;
2127
- planMd += `\nGenerated by FrameForge v2.7.8 — plan_video_edit with J/L-cut support (lemo-opuscar DIRECTOR.md)\n`;
2128
+ planMd += `\nGenerated by FrameForge v2.7.9 — plan_video_edit with J/L-cut support (lemo-opuscar DIRECTOR.md)\n`;
2128
2129
 
2129
2130
  // 8. Build TREATMENT.md (lemo-opuscar DIRECTOR.md — 8-section format)
2130
2131
  let treatmentMd = '';
@@ -2165,7 +2166,7 @@ Professional audio rules applied:
2165
2166
  treatmentMd += `## 8. Director Notes\n\n`;
2166
2167
  treatmentMd += `_Add creative direction, mood references, or client notes here._\n\n`;
2167
2168
  treatmentMd += `---\n\n`;
2168
- treatmentMd += `*Generated by FrameForge v2.7.8 — lemo-opuscar DIRECTOR.md format*\n`;
2169
+ treatmentMd += `*Generated by FrameForge v2.7.9 — lemo-opuscar DIRECTOR.md format*\n`;
2169
2170
  }
2170
2171
 
2171
2172
  // 9. Write outputs
@@ -2246,7 +2247,7 @@ Professional audio rules applied:
2246
2247
  handler: async () => {
2247
2248
  const syncFs = require('fs');
2248
2249
  const { MANIFEST_PATH, REPOSITORIES } = require('../skills/sync-upstream');
2249
- let manifest = { lastSync: null, repos: {}, version: '2.7.8', pendingUpdates: [] };
2250
+ let manifest = { lastSync: null, repos: {}, version: '2.7.9', pendingUpdates: [] };
2250
2251
  if (syncFs.existsSync(MANIFEST_PATH)) {
2251
2252
  try { manifest = JSON.parse(syncFs.readFileSync(MANIFEST_PATH, 'utf8')); } catch (_) {}
2252
2253
  }
@@ -2293,7 +2294,7 @@ Professional audio rules applied:
2293
2294
  return {
2294
2295
  success: true,
2295
2296
  lastSync: manifest.lastSync,
2296
- manifestVersion: manifest.version || '2.7.8',
2297
+ manifestVersion: manifest.version || '2.7.9',
2297
2298
  totalRepos: repos.length,
2298
2299
  syncedCount,
2299
2300
  pendingUpdates,
@@ -3536,7 +3537,7 @@ Get a style id from list_motion_styles first. Source: FrameForge Motion Library
3536
3537
 
3537
3538
  const SERVER_INFO = {
3538
3539
  name: 'video-pipeline',
3539
- version: '2.7.8',
3540
+ version: '2.7.9',
3540
3541
  };
3541
3542
 
3542
3543
  const CAPABILITIES = {
@@ -370,7 +370,10 @@ class TTSProvider {
370
370
  * picks by gender preference (TTS_VOICE_GENDER=male|female|default).
371
371
  *
372
372
  * @param {string} [explicitVoice] voice passed by caller (takes priority)
373
- * @param {string} [text] narration text for language detection
373
+ * @param {string} [text] narration text — Unicode range detection selects script-appropriate voice
374
+ * @param {string} [langHint] BCP-47 language code (e.g. 'fr', 'pt') — used when text is Latin-script
375
+ * but the production language is known. Falls back to _overrides.language
376
+ * and then to TTS_LANGUAGE env var.
374
377
  * @returns {string}
375
378
  */
376
379
  selectNarratorVoice(explicitVoice, text = '', langHint = '') {
@@ -430,7 +433,7 @@ class TTSProvider {
430
433
  bn: gender === 'female' ? 'bn-IN-TanishaaNeural' : 'bn-IN-BashkarNeural',
431
434
  mr: gender === 'female' ? 'mr-IN-AarohiNeural' : 'mr-IN-ManoharNeural',
432
435
  gu: gender === 'female' ? 'gu-IN-DhwaniNeural' : 'gu-IN-NiranjanNeural',
433
- pa: gender === 'female' ? 'pa-IN-OjasvNeural' : 'pa-IN-OjasvNeural',
436
+ pa: 'pa-IN-OjasvNeural', // edge-tts only has one Punjabi voice (gender-neutral)
434
437
  ur: gender === 'female' ? 'ur-PK-UzmaNeural' : 'ur-PK-AsadNeural',
435
438
  zh: gender === 'female' ? 'zh-CN-XiaoxiaoNeural' : 'zh-CN-YunjianNeural',
436
439
  ja: gender === 'female' ? 'ja-JP-NanamiNeural' : 'ja-JP-KeitaNeural',
@@ -552,7 +555,8 @@ class TTSProvider {
552
555
 
553
556
  const rawPath = outputPath.replace(/\.[^.]+$/, '_raw.mp3');
554
557
  const txtPath = rawPath + '.txt';
555
- const safeVoice = voice || this.selectNarratorVoice(null, text);
558
+ // voice is always pre-resolved by synthesize() — this fallback is purely defensive
559
+ const safeVoice = voice || this.selectNarratorVoice(null, text, this._overrides.language || '');
556
560
  // No artificial rate manipulation — use the voice's natural cadence.
557
561
  // Only a mild rate adjustment from env (e.g. TTS_RATE='+0%') is supported.
558
562
  const rate = this._overrides.rate || process.env.TTS_RATE || '+0%';
@@ -117,7 +117,7 @@ function fetchJson(url) {
117
117
  return new Promise((resolve, reject) => {
118
118
  const opts = {
119
119
  headers: {
120
- 'User-Agent': 'FrameForge-Skill-Sync/2.7.8',
120
+ 'User-Agent': 'FrameForge-Skill-Sync/2.7.9',
121
121
  'Accept': 'application/vnd.github.v3+json',
122
122
  },
123
123
  };
@@ -188,7 +188,7 @@ async function syncSkills(opts = {}) {
188
188
  const updatedResults = results.filter(r => r.updated);
189
189
 
190
190
  manifest.lastSync = new Date().toISOString();
191
- manifest.version = '2.7.8';
191
+ manifest.version = '2.7.9';
192
192
  manifest.updatedCount = updatedCount;
193
193
  if (updatedResults.length > 0) {
194
194
  manifest.pendingUpdates = updatedResults.map(r => {
@@ -278,7 +278,7 @@ function getUpstreamSummary() {
278
278
  return {
279
279
  repoCount,
280
280
  lastSync: manifest.lastSync,
281
- version: manifest.version || '2.7.8',
281
+ version: manifest.version || '2.7.9',
282
282
  pendingUpdates: pending,
283
283
  hasUpdates: pending.length > 0,
284
284
  repos: manifest.repos || {},
@@ -27,7 +27,7 @@ const VDIR = `${FILES_DIR}/video_tmp`;
27
27
  async function runAssets({
28
28
  productionId,
29
29
  scenes,
30
- parallelism = 1,
30
+ parallelism = 4,
31
31
  skipTts = false,
32
32
  voice = null,
33
33
  topic = '',