@amg555/frameforge-mcp 2.4.5 → 2.4.7
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 +25 -0
- package/package.json +2 -2
- package/scripts/deep-validate.js +2 -2
- package/src/cli/make.js +26 -11
- package/src/mcp-server/server.js +6 -2
- package/src/video-renderer/renderer.js +5 -5
- package/src/video-renderer/scene-animator.js +1081 -384
- package/src/workflows/assets.js +2 -1
- package/src/workflows/ingest.js +81 -6
- package/src/workflows/render.js +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ All notable changes to FrameForge are documented here.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [2.4.6] — 2026-11-10
|
|
8
|
+
|
|
9
|
+
### Enhanced
|
|
10
|
+
- **High-Quality Volumetric 2D Character Anatomy (`src/video-renderer/scene-animator.js`)**:
|
|
11
|
+
- Replaced all single-stroke stick limbs with solid anatomical contoured polygons:
|
|
12
|
+
- Contoured muscular thighs (quadriceps curve), articulated knee joints, gastrocnemius calf curves, and realistic ankles.
|
|
13
|
+
- Solid contoured upper arms and forearms tapering to wrists; fully articulated hands with solid palms, opposable thumbs, and 4 individual fingers for each pose (`natural`, `dress_shirt`, `hold_spear`, `sewing`, `thinker`, `peace_gesture`, `shiver`, `cover_body`).
|
|
14
|
+
- Anatomical head contour with cranium dome, temples, cheekbones, sculpted jawline, chin, and ears with antihelix and lobe.
|
|
15
|
+
- Almond eyes with sclera, colored iris, pupil, specular catchlights, eyelid crease, and blinking animation over pure-$t$.
|
|
16
|
+
- Sculpted nose bridge and nostril curves; expressive lips with oral cavity and teeth line modulated by sine speech waves.
|
|
17
|
+
- Layered volumetric styled hair: modern textured pompadour/fade, or lush flowing prehistoric locks.
|
|
18
|
+
- Modern tailored suit jacket/blazer with notched lapels, shirt collar, silk tie, belt with polished buckle, trousers with center pressed crease, and leather oxford shoes.
|
|
19
|
+
- Prehistoric animal hide tunic with ragged deckle hem, rawhide stitching, and carved bone toggle buckle, or bare human body with natural skin contours and 5 distinct toes.
|
|
20
|
+
- **Production-Grade Environmental Backdrops**:
|
|
21
|
+
- `doodle_dressing`: Hardwood herringbone parquet floor with individual planks and specular reflection sheen, architectural wainscoting trim, luxury dark walnut wardrobe closet with warm internal LED downlight, upper storage shelf, and 4 styled garments on wooden hangers, alongside an arched full-length brass mirror with crystal reflection.
|
|
22
|
+
- `doodle_modern_vs_prehistoric`: Split-screen comparison featuring a modern penthouse with twilight city skyline and illuminated skyscrapers on the left, an African savannah sunset with volcanic mountain ridges and flat-canopy acacia trees on the right, joined by a central glowing timeline rift (`300,000 BCE ───▶ PRESENT`).
|
|
23
|
+
- `doodle_shiver_cold`: Frigid Ice Age glacial valley with jagged snow-covered crags, wind-carved snow dunes, howling blizzard streaks, breath condensation plumes, and volumetric golden dawn sunbeams.
|
|
24
|
+
- `doodle_hunter_savannah`: Wide African savannah horizon under glowing sun with swaying golden grass stalks and flaked obsidian/flint spear.
|
|
25
|
+
- `doodle_sewing_needle`: Prehistoric cavern shelter with stone cave walls, ancient cave paintings, glowing campfire hearth with red embers, and high-precision inspection loupe showing extreme close-up of drilled ivory bone needle and sinew stitching.
|
|
26
|
+
- `doodle_shame_reflection`: Tranquil forest pool with mossy river rocks, water ripples, mirrored reflection, and radiant cognitive awareness halo.
|
|
27
|
+
- `doodle_tribe_meeting`: Misty valley with monolithic standing stones, two tribal figures meeting in peace, and glowing golden connection arc.
|
|
28
|
+
- `doodle_thinking_future`: Primeval starlit night sky with crescent moon, granite boulder, and luminous thought constellation.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
7
32
|
## [2.4.5] — 2026-11-10
|
|
8
33
|
|
|
9
34
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amg555/frameforge-mcp",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.7",
|
|
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"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"start:local-render": "cross-env RENDER_MODE=full WORKER_PORT=3211 node src/worker/server-render-free.js",
|
|
32
32
|
"db:migrate": "node scripts/db-migrate.js",
|
|
33
33
|
"validate": "node scripts/deep-validate.js",
|
|
34
|
-
"test": "node --test src/pipeline/retry-policy.test.js src/pipeline/telemetry.test.js src/pipeline/artifact-manifest.test.js src/pipeline/artifact-retention.test.js src/pipeline/external-adapter.test.js src/workflows/publishing.test.js src/workflows/script.test.js src/animation/animation-system.test.js src/fact-checker/review-gate.test.js src/fact-checker/checker.test.js src/utils/log-redaction.test.js src/worker/server-render-free.test.js src/worker/server.test.js src/mcp-server/server.test.js src/quality-gate/gate.test.js src/shorts-extractor/extractor.test.js src/audio-mixer/mixer.test.js src/video-renderer/scene-animator.test.js src/video-renderer/renderer.test.js src/video-editor/editor.test.js plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.test.js src/harness/lib/pixels.test.js src/harness/lib/verify-purity.test.js src/harness/harness.test.js src/integration.test.js",
|
|
34
|
+
"test": "node --test src/pipeline/retry-policy.test.js src/pipeline/telemetry.test.js src/pipeline/artifact-manifest.test.js src/pipeline/artifact-retention.test.js src/pipeline/external-adapter.test.js src/workflows/publishing.test.js src/workflows/script.test.js src/workflows/ingest.test.js src/animation/animation-system.test.js src/fact-checker/review-gate.test.js src/fact-checker/checker.test.js src/utils/log-redaction.test.js src/worker/server-render-free.test.js src/worker/server.test.js src/mcp-server/server.test.js src/quality-gate/gate.test.js src/shorts-extractor/extractor.test.js src/audio-mixer/mixer.test.js src/video-renderer/scene-animator.test.js src/video-renderer/renderer.test.js src/video-editor/editor.test.js plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.test.js src/harness/lib/pixels.test.js src/harness/lib/verify-purity.test.js src/harness/harness.test.js src/integration.test.js",
|
|
35
35
|
"lint": "echo 'No linter configured — add eslint for production'",
|
|
36
36
|
"check-credentials": "node src/utils/log-redaction.js",
|
|
37
37
|
"generate-assets": "node scripts/generate-initial-assets.js",
|
package/scripts/deep-validate.js
CHANGED
|
@@ -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.
|
|
23
|
+
check('SERVER_INFO version 2.4.7', code.includes("version: '2.4.7'"));
|
|
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.
|
|
302
|
+
check('cf-worker version 2.4.7', cfWorker.includes("'2.4.7'"));
|
|
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
|
@@ -354,21 +354,36 @@ async function runSingleVideo(args, topic, outDir, workflows) {
|
|
|
354
354
|
}
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
// ── Script file bypass (--script-file): skip research, load approved JSON ──
|
|
357
|
+
// ── Script file bypass (--script-file): skip research, load approved JSON or Markdown ──
|
|
358
358
|
if (args.scriptFile && !scenes) {
|
|
359
359
|
logSection('Loading Approved Script');
|
|
360
360
|
const absScript = path.resolve(process.cwd(), args.scriptFile);
|
|
361
361
|
if (!fs.existsSync(absScript)) throw new Error(`Script file not found: ${absScript}`);
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
362
|
+
const content = fs.readFileSync(absScript, 'utf8');
|
|
363
|
+
|
|
364
|
+
if (/\.(md|markdown)$/i.test(absScript) || (!content.trim().startsWith('{') && !content.trim().startsWith('['))) {
|
|
365
|
+
const { parseMarkdownScript } = require('../workflows/ingest');
|
|
366
|
+
const parsedMd = parseMarkdownScript(content);
|
|
367
|
+
scenes = parsedMd.scenes;
|
|
368
|
+
productionId = `script_${Date.now()}`;
|
|
369
|
+
title = parsedMd.title || topic || 'Video';
|
|
370
|
+
description = '';
|
|
371
|
+
tags = [];
|
|
372
|
+
thumbnailPrompt = null;
|
|
373
|
+
totalWords = scenes?.reduce((n, s) => n + (s.narration || '').split(/\s+/).filter(Boolean).length, 0) || 0;
|
|
374
|
+
log('Script', `✅ Parsed ${scenes?.length} scenes from Markdown: ${absScript}`);
|
|
375
|
+
} else {
|
|
376
|
+
const raw = JSON.parse(content);
|
|
377
|
+
const scriptData = raw.script || raw;
|
|
378
|
+
scenes = scriptData.scenes;
|
|
379
|
+
productionId = scriptData.productionId || raw.productionId || `script_${Date.now()}`;
|
|
380
|
+
title = scriptData.title || topic || 'Video';
|
|
381
|
+
description = scriptData.description || '';
|
|
382
|
+
tags = scriptData.tags || [];
|
|
383
|
+
thumbnailPrompt = scriptData.thumbnailPrompt || null;
|
|
384
|
+
totalWords = scriptData.totalWords || scenes?.reduce((n, s) => n + (s.narration || '').split(/\s+/).filter(Boolean).length, 0) || 0;
|
|
385
|
+
log('Script', `✅ Loaded ${scenes?.length} scenes from: ${absScript}`);
|
|
386
|
+
}
|
|
372
387
|
|
|
373
388
|
if (args.dryRun) {
|
|
374
389
|
logSection('Dry Run Complete');
|
package/src/mcp-server/server.js
CHANGED
|
@@ -355,9 +355,10 @@ const TOOLS = {
|
|
|
355
355
|
theme: { type: 'string', description: 'Theme preset alias' },
|
|
356
356
|
title: { type: 'string', description: 'Video title' },
|
|
357
357
|
language: { type: 'string', description: 'BCP-47 language hint (e.g. ml, hi, en, ta)' },
|
|
358
|
+
showCaptions: { type: 'boolean', default: false, description: 'Display on-screen synchronized subtitles/captions. Default false.' },
|
|
358
359
|
},
|
|
359
360
|
},
|
|
360
|
-
handler: async ({ productionId, scenes, orientation = 'landscape', skipImages = false, skipTts = false, withMusic = true, musicVolume = 0.07, topic = '', style = '', theme = '', title = '', language = '' }) => {
|
|
361
|
+
handler: async ({ productionId, scenes, orientation = 'landscape', skipImages = false, skipTts = false, withMusic = true, musicVolume = 0.07, topic = '', style = '', theme = '', title = '', language = '', showCaptions = false }) => {
|
|
361
362
|
const syncFs = require('fs');
|
|
362
363
|
const { runAssets } = require('../workflows/assets');
|
|
363
364
|
const { runRender } = require('../workflows/render');
|
|
@@ -641,6 +642,9 @@ Example scene:
|
|
|
641
642
|
style: s.style || style || '',
|
|
642
643
|
theme: s.theme || theme || '',
|
|
643
644
|
language: s.language || language || '',
|
|
645
|
+
duration: s.duration != null ? Number(s.duration) : null,
|
|
646
|
+
start_time: s.start_time != null ? Number(s.start_time) : (s.startTime != null ? Number(s.startTime) : null),
|
|
647
|
+
end_time: s.end_time != null ? Number(s.end_time) : (s.endTime != null ? Number(s.endTime) : null),
|
|
644
648
|
character: s.character || { character_id: 'none', pose: 'none', emotion: 'neutral', position: { x: 0.5, y: 0.8, scale: 1.0 } },
|
|
645
649
|
focal_point: s.focal_point || { x: 0.5, y: 0.35, weight: 0.8 },
|
|
646
650
|
overlay: s.overlay || { type: 'none' },
|
|
@@ -1637,7 +1641,7 @@ Allows the agent in Claude Code, Cursor, or any IDE to inspect the visual output
|
|
|
1637
1641
|
|
|
1638
1642
|
const SERVER_INFO = {
|
|
1639
1643
|
name: 'video-pipeline',
|
|
1640
|
-
version: '2.4.
|
|
1644
|
+
version: '2.4.7',
|
|
1641
1645
|
};
|
|
1642
1646
|
|
|
1643
1647
|
const CAPABILITIES = {
|
|
@@ -167,16 +167,15 @@ class VideoRenderer {
|
|
|
167
167
|
startTime = 0,
|
|
168
168
|
trimStart,
|
|
169
169
|
endTime,
|
|
170
|
+
duration = null,
|
|
170
171
|
trimEnd,
|
|
171
172
|
speed = 1.0,
|
|
172
173
|
clipVolume = 0,
|
|
173
174
|
colorGrade = null,
|
|
174
175
|
showCaptions = false,
|
|
175
176
|
} = {}) {
|
|
176
|
-
const { detectCaptionsRequested } = require('./scene-animator');
|
|
177
177
|
const effectiveShowCaptions = Boolean(
|
|
178
178
|
showCaptions ||
|
|
179
|
-
detectCaptionsRequested(`${caption} ${narration} ${topic} ${style} ${visualDescription}`) ||
|
|
180
179
|
process.env.FRAMEFORGE_SHOW_CAPTIONS === '1'
|
|
181
180
|
);
|
|
182
181
|
const outputPath = path.join(this.vdir, `scene_${sceneIndex}_out.mp4`);
|
|
@@ -189,12 +188,13 @@ class VideoRenderer {
|
|
|
189
188
|
|
|
190
189
|
fs.mkdirSync(this.vdir, { recursive: true });
|
|
191
190
|
|
|
192
|
-
// Duration from voice file.
|
|
191
|
+
// Duration from voice file or explicit scene duration.
|
|
193
192
|
if (!voicePath || !fs.existsSync(voicePath)) {
|
|
194
193
|
throw new Error(`Scene ${sceneIndex} render failed: voicePath is missing or does not exist (${voicePath})`);
|
|
195
194
|
}
|
|
196
|
-
const
|
|
197
|
-
const
|
|
195
|
+
const voiceDur = this._getAudioDuration(voicePath);
|
|
196
|
+
const dur = (typeof duration === 'number' && duration > 0) ? Math.max(duration, voiceDur) : voiceDur;
|
|
197
|
+
const clampedDur = Math.max(1, Number.isFinite(dur) ? dur + 0.15 : 3.15);
|
|
198
198
|
|
|
199
199
|
const isVideoInput = Boolean(videoPath) || (videoPath && /\.(mp4|mov|webm|mkv|m4v)$/i.test(videoPath));
|
|
200
200
|
|