@amg555/frameforge-mcp 2.4.2 → 2.4.4

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/CHANGELOG.md CHANGED
@@ -4,6 +4,66 @@ All notable changes to FrameForge are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## [2.4.4] — 2026-11-09
8
+
9
+ ### Added
10
+ - **Optional Captions via Prompt / Flag (`src/video-renderer/scene-animator.js`, `src/video-renderer/renderer.js`, `src/cli/make.js`)**:
11
+ - Captions, subtitle pills, and on-screen text are now disabled by default (`SHOW_CAPTIONS = false`).
12
+ - Added `detectCaptionsRequested(text)` to automatically enable captions when explicitly asked in prompt (e.g. `with captions`, `show captions`, `add subtitles`, `subtitles: true`).
13
+ - Added CLI flags `--captions`, `--subtitles`, `--with-captions` to `src/cli/make.js`.
14
+ - Added `showCaptions` option to MCP tools `create_video_from_script` and `publish_video`.
15
+ - Guarded all FFmpeg video-clip subtitle burn-in filters with `effectiveShowCaptions`.
16
+
17
+ - **Multi-Shot Dynamic Camera Director (`src/video-renderer/scene-animator.js`)**:
18
+ - Scenes longer than 6.5s automatically trigger dynamic multi-shot camera cuts every 5.5s (`shotDur = 5.5`).
19
+ - Progresses cleanly through 4 cinematic angles: Wide establishing $\to$ Medium push-in $\to$ Action close-up $\to$ Dynamic high angle using spring transition interpolations (`shotCutSpring`), eliminating static camera stagnation.
20
+
21
+ - **Pacing & Duration Scaling (`src/workflows/script.js`)**:
22
+ - Upgraded target scene count formula to `targetDuration ? Math.max(8, Math.ceil(targetDuration / 4.5)) : Math.max(sceneCount, 55)` to guarantee tight 4-6 second scene pacing matching high-retention video essay benchmarks.
23
+ - LLM instructions explicitly require 12-25 words per scene to prevent monolithic 30-50s static scenes.
24
+
25
+ ### Enhanced
26
+ - **Visual Rendering Quality Polish (`src/video-renderer/scene-animator.js`)**:
27
+ - Ground contact drop shadows (`ellipse(0, legLength, 34, 7)`) anchor all characters to the ground plane.
28
+ - Specular eye highlights and warm rosy cheek blush (`rgba(251, 113, 133, 0.28)`) enhance character expressiveness.
29
+ - Round line caps (`ctx.lineCap = 'round'`, `ctx.lineJoin = 'round'`) for clean, hand-crafted doodle aesthetics.
30
+
31
+ - **Near-Lossless FFmpeg Encoding (`src/video-renderer/renderer.js`)**:
32
+ - Upgraded CRF from 20/22 to `17` (visually lossless).
33
+ - Upgraded encoding preset from `veryfast` to `fast` for superior inter-frame motion estimation.
34
+ - Added `-x264-params aq-mode=3:deblock=-1,-1` to eliminate gradient banding on dark animated canvas backgrounds while preserving razor-sharp vector line art.
35
+ - Upgraded audio output stream to 320 kbps AAC stereo.
36
+
37
+ ---
38
+
39
+ ## [2.4.3] — 2026-11-09
40
+
41
+ ### Enhanced
42
+ - **Sentence-Synchronized Subtitles (`src/video-renderer/scene-animator.js`)**:
43
+ - Replaced static slide titles, topic banners, and category tags (`// CHRONICLE`, `// HERITAGE`, top headlines) with sentence-synchronized broadcast subtitles centered at bottom with subtle frosted glass backing pills.
44
+ - Automatically slices narration scripts into sentence beats keyed to duration (`DURATION / sentenceCount`) with smooth spring entrance transitions (`spring(localT, 0.04, 210, 16)`).
45
+ - Word wrapping for long sentences to prevent clipping.
46
+
47
+ - **3-Beat Progressive Choreography & Dynamic Camera (`src/video-renderer/scene-animator.js`)**:
48
+ - Added multi-beat progression across all 9 doodle themes:
49
+ - `doodle_dressing`: Beat 1 browsing wardrobe $\to$ Beat 2 trying on shirt $\to$ Beat 3 smiling & waving in glowing amber mirror.
50
+ - `doodle_shiver_cold`: Beat 1 shivering alone $\to$ Beat 2 "NOT FOR COLD" stamp $\to$ Beat 3 "NOT FOR PROTECTION" stamp with character contemplating.
51
+ - `doodle_hunter_savannah`: Character walking with spear across savannah grass swatting mosquitoes under sun.
52
+ - `doodle_sewing_needle`: Campfire $\to$ loupe zoom into bone needle stitching animal hide $\to$ archaeological timeline badge.
53
+ - `doodle_shame_reflection`: Looking down $\to$ water reflection ripples clear $\to$ blush & Theory of Mind halo.
54
+ - `doodle_tribe_meeting`: Two clans approach $\to$ peace greeting $\to$ social language connection arc.
55
+ - `doodle_thinking_future`: Campfire thinker $\to$ thought cloud $\to$ planning for tomorrow tools & sunrise.
56
+ - `doodle_microscope_lice`: Character scratching itching pelt $\to$ microscope slide $\to$ head vs body lice divergence badge.
57
+ - Added subtle cinematic camera drift and breathing (`camZoom`, `camPanX`, `camPanY`) via progress $p = t / DURATION$.
58
+
59
+ ### Fixed
60
+ - **Regex Parsing Bug in Template Literal (`src/video-renderer/scene-animator.js`)**:
61
+ - Fixed syntax error caused by unescaped newline in regex literal inside template literal string.
62
+ - **Theme Detection Priority (`src/video-renderer/scene-animator.js`)**:
63
+ - Prioritized specific narrative themes (`doodle_microscope_lice`, `doodle_shiver_cold`, `doodle_hunter_savannah`, `doodle_tribe_meeting`, `doodle_thinking_future`, `doodle_sewing_needle`, `doodle_shame_reflection`) ahead of general wardrobe dressing to guarantee every scene gets its exact script-matched visual doodle.
64
+
65
+ ---
66
+
7
67
  ## [2.4.2] — 2026-11-09
8
68
 
9
69
  ### Added
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amg555/frameforge-mcp",
3
- "version": "2.4.2",
3
+ "version": "2.4.4",
4
4
  "description": "FrameForge — AI video production engine for agentic IDEs. npx @amg555/frameforge-mcp. FFmpeg + TTS run on your machine. 21 MCP tools.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -20,7 +20,7 @@ check('batch handling', code.includes('Array.isArray(body)'));
20
20
  check('ping handler', code.includes("method === 'ping'"));
21
21
  check('notifications/init', code.includes("notifications/initialized"));
22
22
  check('body size limit', code.includes('MAX_BODY_BYTES'));
23
- check('SERVER_INFO version 2.4.2', code.includes("version: '2.4.2'"));
23
+ check('SERVER_INFO version 2.4.4', code.includes("version: '2.4.4'"));
24
24
  check('18 tools defined (incl get_job_status)', code.includes('get_job_status'));
25
25
  check('export_to_editor tool defined', code.includes('export_to_editor'));
26
26
  check('edit_video tool defined', code.includes('edit_video'));
@@ -299,7 +299,7 @@ check('cf-worker has checkRateLimit', cfWorker.includes('checkRateLimit'));
299
299
  check('cf-worker hashes token (SHA-256)', cfWorker.includes('SHA-256'));
300
300
  check('cf-worker uses RATE_LIMITS KV', cfWorker.includes('env.RATE_LIMITS'));
301
301
  check('cf-worker returns 429 on limit', cfWorker.includes('429'));
302
- check('cf-worker version 2.4.2', cfWorker.includes("'2.4.2'"));
302
+ check('cf-worker version 2.4.4', cfWorker.includes("'2.4.4'"));
303
303
  check('wrangler.toml has kv_namespaces',fs.readFileSync('./cloudflare-mcp/wrangler.toml','utf8').includes('kv_namespaces'));
304
304
 
305
305
  // ── plugins/skills present ────────────────────────────────────────────────
package/src/cli/make.js CHANGED
@@ -99,6 +99,7 @@ function parseArgs(argv) {
99
99
  topic: '', topicsFile: '', language: 'en',
100
100
  style: 'educational comedy', channels: ['file'],
101
101
  orientation: 'landscape',
102
+ captions: false, subtitles: false,
102
103
  scenesFile: '', scriptFile: '', mediaDir: '',
103
104
  shorts: true, dryRun: false,
104
105
  noResearch: false, noScript: false, noImages: false, noTts: false, noPublish: false,
@@ -115,6 +116,9 @@ function parseArgs(argv) {
115
116
  case '--language': args.language = (argv[++i] || 'en').toLowerCase(); break;
116
117
  case '--orientation': args.orientation = (argv[++i] || 'landscape').toLowerCase(); break;
117
118
  case '--portrait': args.orientation = 'portrait'; break;
119
+ case '--captions':
120
+ case '--subtitles':
121
+ case '--with-captions': args.captions = true; break;
118
122
  case '--style': args.style = argv[++i] || args.style; break;
119
123
  case '--channels': args.channels = (argv[++i] || 'file').split(',').map(s => s.trim()); break;
120
124
  case '--no-research': args.noResearch = true; break;
@@ -436,6 +440,7 @@ async function runSingleVideo(args, topic, outDir, workflows) {
436
440
  title,
437
441
  style: args.style || '',
438
442
  language: args.language,
443
+ showCaptions: Boolean(args.captions || args.subtitles),
439
444
  renderWidth,
440
445
  renderHeight,
441
446
  });
@@ -420,6 +420,7 @@ const TOOLS = {
420
420
  theme,
421
421
  title,
422
422
  language,
423
+ showCaptions,
423
424
  });
424
425
 
425
426
  if (!renderResult.passed) {
@@ -584,10 +585,11 @@ Example scene:
584
585
  topic: { type: 'string', description: 'Overall topic/genre for visual aesthetic and background motion (e.g. "Kerala history", "crypto finance", "AI robotics")' },
585
586
  style: { type: 'string', description: 'Visual style/theme override (e.g. cinematic_documentary, finance_executive, cyber_tech, retro_synthwave, nature_organic, hype_sports, minimal_luxury, breaking_news, cosmic_space)' },
586
587
  theme: { type: 'string', description: 'Theme preset alias' },
588
+ showCaptions: { type: 'boolean', default: false, description: 'Display on-screen synchronized subtitles/captions. Default false (clean canvas without text unless explicitly requested in prompt).' },
587
589
  productionId: { type: 'string', description: 'Optional — reuse an existing productionId' },
588
590
  },
589
591
  },
590
- handler: async ({ scenes, title = 'FrameForge Video', orientation = 'landscape', voice, language, skipTts = false, parallelism = 1, withMusic = true, musicVolume = 0.07, topic = '', style = '', theme = '', productionId }) => {
592
+ handler: async ({ scenes, title = 'FrameForge Video', orientation = 'landscape', voice, language, skipTts = false, parallelism = 1, withMusic = true, musicVolume = 0.07, topic = '', style = '', theme = '', showCaptions = false, productionId }) => {
591
593
  const { runAssets } = require('../workflows/assets');
592
594
  const { runRender } = require('../workflows/render');
593
595
 
@@ -663,6 +665,7 @@ Example scene:
663
665
  theme,
664
666
  title,
665
667
  language,
668
+ showCaptions,
666
669
  });
667
670
  const renderResult = await runRender({
668
671
  productionId: pid,
@@ -676,6 +679,7 @@ Example scene:
676
679
  theme,
677
680
  title,
678
681
  language,
682
+ showCaptions,
679
683
  });
680
684
 
681
685
  if (!renderResult.passed) {
@@ -1633,7 +1637,7 @@ Allows the agent in Claude Code, Cursor, or any IDE to inspect the visual output
1633
1637
 
1634
1638
  const SERVER_INFO = {
1635
1639
  name: 'video-pipeline',
1636
- version: '2.4.2',
1640
+ version: '2.4.4',
1637
1641
  };
1638
1642
 
1639
1643
  const CAPABILITIES = {
@@ -171,7 +171,14 @@ class VideoRenderer {
171
171
  speed = 1.0,
172
172
  clipVolume = 0,
173
173
  colorGrade = null,
174
+ showCaptions = false,
174
175
  } = {}) {
176
+ const { detectCaptionsRequested } = require('./scene-animator');
177
+ const effectiveShowCaptions = Boolean(
178
+ showCaptions ||
179
+ detectCaptionsRequested(`${caption} ${narration} ${topic} ${style} ${visualDescription}`) ||
180
+ process.env.FRAMEFORGE_SHOW_CAPTIONS === '1'
181
+ );
175
182
  const outputPath = path.join(this.vdir, `scene_${sceneIndex}_out.mp4`);
176
183
 
177
184
  // Idempotency: skip if already rendered and manifest says so
@@ -197,7 +204,7 @@ class VideoRenderer {
197
204
  const startSec = parseTimestamp(trimStart != null ? trimStart : startTime);
198
205
  const sp = buildSpeedFilters(speed);
199
206
 
200
- const captionFilter = this._captionFilter(caption);
207
+ const captionFilter = effectiveShowCaptions ? this._captionFilter(caption) : '';
201
208
  const scaleFilter = `scale=${renderWidth}:${renderHeight}:force_original_aspect_ratio=increase:flags=lanczos,crop=${renderWidth}:${renderHeight}`;
202
209
  const colorFilter = buildColorFilter(colorGrade);
203
210
 
@@ -285,6 +292,7 @@ class VideoRenderer {
285
292
  outputPath,
286
293
  manifest,
287
294
  musicMood,
295
+ showCaptions: effectiveShowCaptions,
288
296
  });
289
297
  } catch (err) {
290
298
  console.warn(`[Renderer] Code-driven browser scene render failed (${err.message}), falling back to procedural animation`);
@@ -292,7 +300,7 @@ class VideoRenderer {
292
300
  }
293
301
 
294
302
  // Priority 3: Procedural dynamic animated fallback (dynamic moving gradients / camera drift)
295
- const captionFilter = this._captionFilter(caption);
303
+ const captionFilter = effectiveShowCaptions ? this._captionFilter(caption) : '';
296
304
  let inputArgs = [];
297
305
  let vf = '';
298
306
 
@@ -324,12 +332,12 @@ class VideoRenderer {
324
332
  '-af', `apad=whole_dur=${clampedDur}`,
325
333
  '-t', String(clampedDur),
326
334
  '-map', '0:v:0', '-map', '1:a:0',
327
- '-c:v', 'libx264', '-preset', 'veryfast', '-crf', '22',
335
+ '-c:v', 'libx264', '-preset', 'fast', '-crf', '17',
328
336
  '-profile:v', 'high', '-level', '4.1',
329
337
  '-color_range', 'tv', '-colorspace', 'bt709',
330
338
  '-color_primaries', 'bt709', '-color_trc', 'bt709',
331
- '-x264-params', 'colorprim=bt709:transfer=bt709:colormatrix=bt709',
332
- '-c:a', 'aac', '-b:a', '192k', '-ar', '48000', '-ac', '2',
339
+ '-x264-params', 'colorprim=bt709:transfer=bt709:colormatrix=bt709:aq-mode=3:deblock=-1,-1',
340
+ '-c:a', 'aac', '-b:a', '320k', '-ar', '48000', '-ac', '2',
333
341
  '-movflags', '+faststart',
334
342
  '-pix_fmt', 'yuv420p',
335
343
  outputPath,
@@ -366,6 +374,7 @@ class VideoRenderer {
366
374
  outputPath,
367
375
  manifest,
368
376
  musicMood,
377
+ showCaptions = false,
369
378
  }) {
370
379
  let htmlContent;
371
380
  if (html && typeof html === 'string' && html.trim().length > 0) {
@@ -399,6 +408,7 @@ class VideoRenderer {
399
408
  height: renderHeight,
400
409
  sceneIndex,
401
410
  musicMood,
411
+ showCaptions,
402
412
  });
403
413
  }
404
414
 
@@ -434,13 +444,13 @@ class VideoRenderer {
434
444
  '-i', framesPattern,
435
445
  '-i', voicePath,
436
446
  '-map', '0:v:0', '-map', '1:a:0',
437
- '-c:v', 'libx264', '-preset', 'veryfast', '-crf', '20',
447
+ '-c:v', 'libx264', '-preset', 'fast', '-crf', '17',
438
448
  '-profile:v', 'high', '-level', '4.1',
439
449
  '-color_range', 'tv', '-colorspace', 'bt709',
440
450
  '-color_primaries', 'bt709', '-color_trc', 'bt709',
441
- '-x264-params', 'colorprim=bt709:transfer=bt709:colormatrix=bt709',
451
+ '-x264-params', 'colorprim=bt709:transfer=bt709:colormatrix=bt709:aq-mode=3:deblock=-1,-1',
442
452
  '-pix_fmt', 'yuv420p',
443
- '-c:a', 'aac', '-b:a', '192k', '-ar', '48000', '-ac', '2',
453
+ '-c:a', 'aac', '-b:a', '320k', '-ar', '48000', '-ac', '2',
444
454
  '-af', `apad=whole_dur=${clampedDur}`,
445
455
  '-t', String(clampedDur),
446
456
  '-movflags', '+faststart',