@amg555/frameforge-mcp 2.4.9 → 2.7.0
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/.claude-plugin/marketplace.json +22 -4
- package/CHANGELOG.md +174 -0
- package/CLAUDE.md +192 -9
- package/README.md +47 -6
- package/output/harness_test_1794418727139/frames/f000000.jpg +1 -0
- package/package.json +4 -3
- package/plugins/video-pipeline/.claude-plugin/plugin.json +8 -2
- package/plugins/video-pipeline/skills/.upstream-skills.json +143 -0
- package/plugins/video-pipeline/skills/html-renderer/SKILL.md +59 -15
- package/plugins/video-pipeline/skills/html-renderer/references/animation-patterns.md +609 -0
- package/plugins/video-pipeline/skills/html-renderer/references/character-animation.md +388 -0
- package/plugins/video-pipeline/skills/html-renderer/references/collage-broll.md +41 -0
- package/plugins/video-pipeline/skills/html-renderer/references/design.md +217 -0
- package/plugins/video-pipeline/skills/html-renderer/references/directing.md +165 -0
- package/plugins/video-pipeline/skills/html-renderer/references/earshot-audio.md +201 -0
- package/plugins/video-pipeline/skills/html-renderer/references/engine-api.md +480 -0
- package/plugins/video-pipeline/skills/html-renderer/references/film-styles.md +95 -0
- package/plugins/video-pipeline/skills/html-renderer/references/hyperframes-motion.md +208 -0
- package/plugins/video-pipeline/skills/html-renderer/references/motion-craft.md +1 -0
- package/plugins/video-pipeline/skills/html-renderer/references/onetake-camera.md +59 -0
- package/plugins/video-pipeline/skills/html-renderer/references/procedural-sfx.md +135 -0
- package/plugins/video-pipeline/skills/html-renderer/references/remotion-patterns.md +159 -0
- package/plugins/video-pipeline/skills/html-renderer/references/render-contract.md +1 -0
- package/plugins/video-pipeline/skills/video-pipeline/SKILL.md +19 -2
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/orchestrator.md +15 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/scene-author.md +21 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.js +1293 -20
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/virtual-clock.js +36 -6
- package/plugins/video-pipeline/skills/video-pipeline/references/mcp-tools.md +126 -2
- package/scripts/deep-validate.js +52 -2
- package/src/audio-mixer/mixer.js +594 -63
- package/src/cli/doctor.js +36 -2
- package/src/cli/make.js +113 -9
- package/src/harness/bin/harness.mjs +127 -5
- package/src/harness/lib/capture.mjs +215 -192
- package/src/harness/lib/verify-quality.mjs +48 -7
- package/src/mcp-server/server.js +1020 -61
- package/src/skills/sync-upstream.js +290 -0
- package/src/utils/media-downloader.js +162 -0
- package/src/utils/transcribe.js +341 -0
- package/src/video-editor/editor.js +96 -11
- package/src/video-renderer/renderer.js +7 -1
- package/src/video-renderer/scene-animator.js +143 -1
- package/src/workflows/assets.js +4 -0
- package/src/workflows/ingest.js +10 -0
- package/src/workflows/render.js +2 -0
- package/src/workflows/script.js +18 -10
|
@@ -279,15 +279,147 @@ function detectAesthetic({
|
|
|
279
279
|
return 'cyber_tech';
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
// ── Anchor Requested Detector ───────────────────────────────────────────────
|
|
283
|
+
/** Returns true ONLY if caller explicitly asks for an on-screen anchor, presenter, host, avatar, or talking head in prompt. */
|
|
284
|
+
function detectAnchorRequested(text = '') {
|
|
285
|
+
if (!text || typeof text !== 'string') return false;
|
|
286
|
+
return /(?:with|show|display|include|enable|add)\s+(?:an?\s+)?(?:anchor|presenter|host|avatar|spokesperson|talking\s+head|narrator\s+character)|(?:anchor|presenter|host)\s*[:=]\s*(?:true|yes|1|on|always)/i.test(text);
|
|
287
|
+
}
|
|
288
|
+
|
|
282
289
|
// ── Component Theme Detection ──────────────────────────────────────────────────
|
|
283
290
|
/**
|
|
284
291
|
* Detect the graphic entity to render on the full canvas for a given scene.
|
|
285
292
|
*/
|
|
286
|
-
function detectComponentTheme({
|
|
293
|
+
function detectComponentTheme({
|
|
294
|
+
visualDescription = '',
|
|
295
|
+
visual_description = '',
|
|
296
|
+
prompt = '',
|
|
297
|
+
caption = '',
|
|
298
|
+
narration = '',
|
|
299
|
+
topic = '',
|
|
300
|
+
imagePath = null,
|
|
301
|
+
aesthetic = 'cyber_tech',
|
|
302
|
+
anchorWanted = true,
|
|
303
|
+
} = {}) {
|
|
287
304
|
if (imagePath) return 'image';
|
|
288
305
|
const visDesc = visualDescription || visual_description || prompt || '';
|
|
289
306
|
const text = `${visDesc} ${caption} ${narration} ${topic}`.toLowerCase();
|
|
290
307
|
|
|
308
|
+
// If anchor is NOT requested, suppress all humanoid doodle anchor characters
|
|
309
|
+
// and route to non-anchor procedural visual archetypes
|
|
310
|
+
if (!anchorWanted) {
|
|
311
|
+
// 1. Versus / Comparison / Split Screen
|
|
312
|
+
if (/(?:^|[^\w])(versus|vs|comparison|compare|pros and cons|pros vs cons|difference between|dichotomy|contrast|side-by-side|before and after|before vs after|split-screen|split screen|modern vs ancient|modern vs prehistoric|then and now|evolution of human)(?:[^\w]|$)/i.test(text)
|
|
313
|
+
|| /വ്യത്യാസം|താരതമ്യം|എതിര്|പോരാട്ടം|തമ്മിൽ|തുലനം|താരതമ്യപ്പെടുത്തൽ/.test(text)) {
|
|
314
|
+
return 'versus_comparison';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 2. Magnification / Microscope / Fiber / Macro Inspection / Lice
|
|
318
|
+
if (/(?:^|[^\w])(magnify|magnification|zoom|microscopic|microscope|macro|close up|closeup|inspection|loupe|examine|scrutiny|detail|focal point|focus in|specimen|fiber|weave|parasite|lice|louse)(?:[^\w]|$)/i.test(text)
|
|
319
|
+
|| /സൂക്ഷ്മ|പരിശോധന|വലുതാക്കി കാണുക|സൂക്ഷ്മമായി|നിരീക്ഷണം|മൈക്രോസ്കോപ്പ്|പേൻ/.test(text)) {
|
|
320
|
+
return 'magnification_focus';
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// 3. Cognitive Insight / Psychology / Brain / Eureka / Thought / Future Planning / Shame Reflection
|
|
324
|
+
if (/(?:^|[^\w])(mind|psychology|cognitive|brain|insight|revelation|eureka|intuition|subconscious|thinking|thought|contemplation|intelligence|mental|awareness|neuroscience|concept|idea|epiphany|planning for tomorrow|future planning|foresight|shame|blush|embarrass|mirror|reflection|theory of mind|self-aware|identity)(?:[^\w]|$)/i.test(text)
|
|
325
|
+
|| /മനസ്സ്|ബുദ്ധി|ചിന്ത|മനഃശാസ്ത്രം|ജ്ഞാനം|മസ്തിഷ്കം|ബോധം|കണ്ണാടി|പ്രതിഫലനം|നാണം/.test(text)) {
|
|
326
|
+
return 'cognitive_insight';
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 4. Mechanism / Physics / Machinery / Engine / Needle / Stitching / Cold
|
|
330
|
+
if (/(?:^|[^\w])(engine|gear|gears|motor|physics|aerodynamics|thrust|turbine|propulsion|piston|hydraulic|kinetic|machine|machinery|circuit|mechanics|thermodynamics|levers|pulley|needle|bone needle|sew|sewing|stitch|stitching|shiver|shivering|cold|freeze|freezing|ice|winter|protection flaw)(?:[^\w]|$)/i.test(text)
|
|
331
|
+
|| /യന്ത്രം|എഞ്ചിൻ|ഭൗതികശാസ്ത്രം|പ്രവർത്തനം|ചലനം|മെക്കാനിസം|ഭൗതികം|സൂചി|തുന്ന|വിറയ്ക്കുക|തണുപ്പ്/.test(text)) {
|
|
332
|
+
return 'mechanism_physics';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 5. Process / Pipeline / Stages / Assembly / Tribe / Community / Hierarchy / Royal Status
|
|
336
|
+
if (/(?:^|[^\w])(pipeline|workflow|step by step|steps|stages|phase|phases|production line|assembly|manufacturing|lifecycle|funnel|conveyor|sequence|how it works|how it is made|procedure|tribe|tribal|clan|stranger|meeting strangers|social language|status in society|hierarchy|royal robe|royal robes|crown|sceptre|ornaments|status and power|wealth authority)(?:[^\w]|$)/i.test(text)
|
|
337
|
+
|| /ഘട്ടങ്ങൾ|പ്രക്രിയ|ഘട്ടം|ഉൽപ്പാദനം|വഴികൾ|പ്രവർത്തനരീതി|കൂട്ടമായി|സമൂഹം|ഗോത്രം|സാമൂഹിക സ്ഥാനം/.test(text)) {
|
|
338
|
+
return 'process_pipeline';
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 6. Radar Globe / World Map / Migration
|
|
342
|
+
if (/(?:^|[^\w])(globe|map|world|geography|radar|international|continent|territory|country|border|location|migration|migrating|migrate|africa highlighted|colder regions|out of africa)(?:[^\w]|$)/i.test(text)
|
|
343
|
+
|| /ഭൂപടം|ലോകം|ഭൂഖണ്ഡം|ഭൂപട|നക്ശാ|ക്ലോബ്|കുടിയേറ്റം|മൈഗ്രേഷൻ/.test(text)) {
|
|
344
|
+
return 'radar_globe';
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 7. Celestial / Orbit
|
|
348
|
+
if (/(?:^|[^\w])(orbit|satellite|satellites|gps|planet|earth|mars|moon|galaxy|universe|solar|astronomy|gravity|space|telescope|interstellar)(?:[^\w]|$)/i.test(text)
|
|
349
|
+
|| /ബഹിരാകാശ|ഗ്രഹം|ഉപഗ്രഹ|ഭൂമി|നക്ഷത്ര|പ്രപഞ്ച|अंतरिक्ष|ग्रह|तारा|വിൺവെളി/.test(text)) {
|
|
350
|
+
return 'celestial_orbit';
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// 8. DNA / Biology
|
|
354
|
+
if (/(?:^|[^\w])(dna|helix|gene|cell|molecule|biology|organism|protein|virus|bacteria|medical|genetic|chromosome)(?:[^\w]|$)/i.test(text)
|
|
355
|
+
|| /ഡിഎൻഎ|കോശം|തന്മാത്ര|ജീവശാസ്ത്ര|डीएनए|कोशिका/.test(text)) {
|
|
356
|
+
return 'dna_helix';
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// 9. Code / Programming
|
|
360
|
+
if (/(?:^|[^\w])(code|function|syntax|api|javascript|python|typescript|json|sql|query|endpoint|algorithm|struct|cli|terminal|bash|shell|npm|git|compiler|linux|rust)(?:[^\w]|$)/.test(text)
|
|
361
|
+
|| /കോഡിംഗ്|സോഫ്റ്റ്വെയർ|പ്രോഗ്രാമിംഗ്|कोड|सॉफ्टवेयर/.test(text)) {
|
|
362
|
+
return 'code';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// 10. Chart / Finance
|
|
366
|
+
if (/(?:^|[^\w])(chart|data|analytics|growth|graph|statistic|bars|volume|distribution|stock|crypto|bitcoin|market|invest|trading|inflation|economy|gdp|finance|gain|loss|bull|bear|candlestick|price)(?:[^\w]|$)/.test(text)
|
|
367
|
+
|| /നിക്ഷേപ|ഓഹരി|വിപണി|പണം|ചാർട്ട്|शेयर|बाज़ार/.test(text)) {
|
|
368
|
+
return 'chart';
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 11. Nodes / Neural Net
|
|
372
|
+
if (/(?:^|[^\w])(network|nodes|neural network|neural net|distributed system|server mesh|deep learning)(?:[^\w]|$)/.test(text)
|
|
373
|
+
|| /നെറ്റ്വർക്ക്|ശൃംഖല|नेटवर्क/.test(text)) {
|
|
374
|
+
return 'nodes';
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// 12. 3D Isometric Geometry
|
|
378
|
+
if (/(?:^|[^\w])(cube|geometry|3d|wireframe|isometric|matrix|crystal|structure|polyhedron|dimension|architect|blueprint|math)(?:[^\w]|$)/.test(text)
|
|
379
|
+
|| /ജ്യാമിതി|രൂപം|ത്രിമാന|संरचना/.test(text)) {
|
|
380
|
+
return 'isometric_geometry';
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// 13. Speed Action / Hunter
|
|
384
|
+
if (/(?:^|[^\w])(speed|sprint|velocity|fast|run|race|motion|hunter|hunting|spear|savannah|blazing sun|mosquito)(?:[^\w]|$)/.test(text)
|
|
385
|
+
|| /വേഗത|ഓട്ടം|കുന്തം|വേഗം/.test(text)) {
|
|
386
|
+
return 'speed_action';
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 14. Quote / Metric
|
|
390
|
+
if (/(?:^|[^\w])(quote|saying|proverb|statement|speech|wisdom)(?:[^\w]|$)/.test(text)) return 'quote';
|
|
391
|
+
if (/(?:^|[^\w])(metric|kpi|target|counter|percentage|ratio|score)(?:[^\w]|$)/.test(text)) return 'metric';
|
|
392
|
+
|
|
393
|
+
// 15. Timeline (strictly explicit roadmap/milestone chart requests only)
|
|
394
|
+
if (/(?:^|[^\w])(timeline|roadmap|milestone chart|milestone roadmap)(?:[^\w]|$)/i.test(text)
|
|
395
|
+
|| /ടൈംലൈൻ|റോഡ്മാപ്പ്/.test(text)) {
|
|
396
|
+
return 'timeline';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// 16. Explainer Presentation (General visual lecture, whiteboard, tutorial, clothing topic, subscribe)
|
|
400
|
+
if (/(?:^|[^\w])(explain|explainer|presentation|lecture|teach|tutorial|whiteboard|blackboard|breakdown|guide|overview|theory|principles|fundamentals|introduction|summary|lesson|masterclass|subscribe|notification bell|outro|dress|dressing|wearing clothes|shirt|wardrobe|closet|hanger|outfit|wedding dress)(?:[^\w]|$)/i.test(text)
|
|
401
|
+
|| /വിവരണം|പഠിപ്പിക്കുക|ക്ലാസ്|പാഠം|തത്വം|വിവരിക്കുക|സബ്സ്ക്രൈബ്|വസ്ത്രധാരണം/.test(text)) {
|
|
402
|
+
return 'explainer_presentation';
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// 17. Waveform
|
|
406
|
+
if (/(?:^|[^\w])(waveform|audio|sound|equalizer|frequency|acoustics|hz|decibel|music spectrum|voice waveform)(?:[^\w]|$)/i.test(text)
|
|
407
|
+
|| /ശബ്ദതരംഗം|തരംഗം|സംഗീതം|ധ്വനി/.test(text)) {
|
|
408
|
+
return 'waveform';
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Clean aesthetic fallbacks — strictly non-anchor
|
|
412
|
+
if (aesthetic === 'cosmic_space') return 'celestial_orbit';
|
|
413
|
+
if (aesthetic === 'cinematic_documentary') return 'explainer_presentation';
|
|
414
|
+
if (aesthetic === 'finance_executive') return 'chart';
|
|
415
|
+
if (aesthetic === 'nature_organic') return 'dna_helix';
|
|
416
|
+
if (aesthetic === 'breaking_news') return 'radar_globe';
|
|
417
|
+
if (aesthetic === 'cyber_tech') return 'nodes';
|
|
418
|
+
if (aesthetic === 'minimal_luxury') return 'explainer_presentation';
|
|
419
|
+
if (aesthetic === 'hype_sports') return 'speed_action';
|
|
420
|
+
return 'explainer_presentation';
|
|
421
|
+
}
|
|
422
|
+
|
|
291
423
|
// ── Doodled Character Actions (Human Stories, History, Evolution, Daily Life) ──
|
|
292
424
|
// 1. Microscope / Lice Theory / Parasite / Genetics (Scene 7)
|
|
293
425
|
if (/(?:^|[^\w])(lice|louse|microscope|fiber|weave|parasite|lice evolution|genetic biology proof)(?:[^\w]|$)/i.test(text)
|
|
@@ -616,12 +748,18 @@ function generateSceneHtml({
|
|
|
616
748
|
height = 1080,
|
|
617
749
|
sceneIndex = 0,
|
|
618
750
|
showCaptions = false,
|
|
751
|
+
showAnchor = false,
|
|
619
752
|
} = {}) {
|
|
620
753
|
const captionsWanted = Boolean(
|
|
621
754
|
showCaptions ||
|
|
622
755
|
detectCaptionsRequested(prompt || topic) ||
|
|
623
756
|
process.env.FRAMEFORGE_SHOW_CAPTIONS === '1'
|
|
624
757
|
);
|
|
758
|
+
const anchorWanted = Boolean(
|
|
759
|
+
showAnchor ||
|
|
760
|
+
detectAnchorRequested(prompt || topic) ||
|
|
761
|
+
process.env.FRAMEFORGE_SHOW_ANCHOR === '1'
|
|
762
|
+
);
|
|
625
763
|
const aestheticKey = detectAesthetic({
|
|
626
764
|
topic,
|
|
627
765
|
style,
|
|
@@ -642,6 +780,7 @@ function generateSceneHtml({
|
|
|
642
780
|
topic,
|
|
643
781
|
imagePath,
|
|
644
782
|
aesthetic: aestheticKey,
|
|
783
|
+
anchorWanted,
|
|
645
784
|
});
|
|
646
785
|
const clampedDur = Math.max(1, Number(duration) || 3);
|
|
647
786
|
|
|
@@ -734,6 +873,7 @@ function spring(t, delay = 0, tension = ${aesthetic.springTension}, friction = $
|
|
|
734
873
|
}
|
|
735
874
|
|
|
736
875
|
const SHOW_CAPTIONS = ${JSON.stringify(captionsWanted)};
|
|
876
|
+
const SHOW_ANCHOR = ${JSON.stringify(anchorWanted)};
|
|
737
877
|
const AESTHETIC = ${JSON.stringify(aestheticKey)};
|
|
738
878
|
const BG_TYPE = ${JSON.stringify(aesthetic.backgroundType)};
|
|
739
879
|
const COMP_THEME = ${JSON.stringify(compTheme)};
|
|
@@ -1006,6 +1146,7 @@ function drawMainScene(t) {
|
|
|
1006
1146
|
showSpecular = false,
|
|
1007
1147
|
seed = 100,
|
|
1008
1148
|
} = {}) {
|
|
1149
|
+
if (!SHOW_ANCHOR) return;
|
|
1009
1150
|
ctx.save();
|
|
1010
1151
|
ctx.translate(x, y);
|
|
1011
1152
|
ctx.scale(scale, scale);
|
|
@@ -4345,6 +4486,7 @@ module.exports = {
|
|
|
4345
4486
|
detectAesthetic,
|
|
4346
4487
|
detectComponentTheme,
|
|
4347
4488
|
detectCaptionsRequested,
|
|
4489
|
+
detectAnchorRequested,
|
|
4348
4490
|
getScriptFontStack,
|
|
4349
4491
|
AESTHETICS,
|
|
4350
4492
|
REGIONAL_FONTS,
|
package/src/workflows/assets.js
CHANGED
|
@@ -35,6 +35,8 @@ async function runAssets({
|
|
|
35
35
|
theme = '',
|
|
36
36
|
title = '',
|
|
37
37
|
language = '',
|
|
38
|
+
showCaptions = false,
|
|
39
|
+
showAnchor = false,
|
|
38
40
|
}) {
|
|
39
41
|
const ttsGen = new TTSProvider();
|
|
40
42
|
const sm = new StateMachine({ mode: 'file', stateDir: STATE_DIR });
|
|
@@ -132,6 +134,8 @@ async function runAssets({
|
|
|
132
134
|
speed: scene.speed != null ? scene.speed : 1.0,
|
|
133
135
|
clipVolume: scene.clip_volume != null ? scene.clip_volume : (scene.clipVolume != null ? scene.clipVolume : 0),
|
|
134
136
|
colorGrade: scene.color_grade || scene.colorGrade || null,
|
|
137
|
+
showCaptions: scene.show_captions !== undefined ? scene.show_captions : (scene.showCaptions !== undefined ? scene.showCaptions : showCaptions),
|
|
138
|
+
showAnchor: scene.show_anchor !== undefined ? scene.show_anchor : (scene.showAnchor !== undefined ? scene.showAnchor : showAnchor),
|
|
135
139
|
};
|
|
136
140
|
}));
|
|
137
141
|
results.push(...batchResults);
|
package/src/workflows/ingest.js
CHANGED
|
@@ -49,6 +49,7 @@ const fs = require('fs');
|
|
|
49
49
|
const fsp = require('fs').promises;
|
|
50
50
|
const path = require('path');
|
|
51
51
|
const crypto = require('crypto');
|
|
52
|
+
const { isWebUrl, downloadMediaWithYtDlp } = require('../utils/media-downloader');
|
|
52
53
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
53
54
|
|
|
54
55
|
const VIDEO_EXTS = new Set(['.mp4','.mov','.avi','.mkv','.webm','.m4v','.flv','.ts']);
|
|
@@ -213,6 +214,15 @@ function normaliseIngestScene(raw, index, baseDir = process.cwd()) {
|
|
|
213
214
|
|
|
214
215
|
function resolveMedia(p) {
|
|
215
216
|
if (!p) return null;
|
|
217
|
+
if (isWebUrl(p)) {
|
|
218
|
+
try {
|
|
219
|
+
const dl = downloadMediaWithYtDlp(p);
|
|
220
|
+
return dl.localPath;
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.warn(` ⚠️ Scene ${sceneId}: failed to download media URL ${p}: ${err.message}`);
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
216
226
|
const resolved = path.isAbsolute(p) ? p : path.resolve(baseDir, p);
|
|
217
227
|
if (!fs.existsSync(resolved)) {
|
|
218
228
|
console.warn(` ⚠️ Scene ${sceneId}: media file not found: ${resolved}`);
|
package/src/workflows/render.js
CHANGED
|
@@ -37,6 +37,7 @@ async function runRender({
|
|
|
37
37
|
title = '',
|
|
38
38
|
language = '',
|
|
39
39
|
showCaptions = false,
|
|
40
|
+
showAnchor = false,
|
|
40
41
|
}) {
|
|
41
42
|
const sm = new StateMachine({ mode: 'file', stateDir: STATE_DIR });
|
|
42
43
|
const retryPol = new RetryPolicy({ maxAttempts: 2, baseDelayMs: 15000 });
|
|
@@ -81,6 +82,7 @@ async function runRender({
|
|
|
81
82
|
clipVolume: asset.clipVolume,
|
|
82
83
|
colorGrade: asset.colorGrade,
|
|
83
84
|
showCaptions: asset.showCaptions !== undefined ? asset.showCaptions : showCaptions,
|
|
85
|
+
showAnchor: asset.showAnchor !== undefined ? asset.showAnchor : showAnchor,
|
|
84
86
|
renderWidth,
|
|
85
87
|
renderHeight,
|
|
86
88
|
});
|
package/src/workflows/script.js
CHANGED
|
@@ -15,6 +15,7 @@ const { LLMProvider } = require('../pipeline/providers');
|
|
|
15
15
|
const { StateMachine } = require('../pipeline/state-machine');
|
|
16
16
|
const { RetryPolicy } = require('../pipeline/retry-policy');
|
|
17
17
|
const { Telemetry } = require('../pipeline/telemetry');
|
|
18
|
+
const { detectAnchorRequested } = require('../video-renderer/scene-animator');
|
|
18
19
|
|
|
19
20
|
const os = require('os');
|
|
20
21
|
const FILES_DIR = process.env.N8N_FILES_DIR || require('path').join(os.homedir(), '.frameforge-files');
|
|
@@ -98,7 +99,7 @@ async function runScript({
|
|
|
98
99
|
claims,
|
|
99
100
|
topic,
|
|
100
101
|
style = 'informative',
|
|
101
|
-
persona = '
|
|
102
|
+
persona = 'narrator',
|
|
102
103
|
format = 'YouTube video',
|
|
103
104
|
language = 'en',
|
|
104
105
|
sceneCount = 55,
|
|
@@ -116,13 +117,16 @@ async function runScript({
|
|
|
116
117
|
|
|
117
118
|
const facts = claims.slice(0, 8).map(c => c.safe_wording || c.claim).join('; ');
|
|
118
119
|
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
const
|
|
120
|
+
// Detect whether caller explicitly requested an on-screen anchor / presenter character
|
|
121
|
+
const anchorWanted = detectAnchorRequested(topic);
|
|
122
|
+
const effectivePersona = persona !== 'video host' ? persona : (anchorWanted ? 'video host' : 'narrator');
|
|
123
|
+
|
|
124
|
+
// Fast visual pacing: ≈ 2.5-3.2s per scene cut (never monolithic drags)
|
|
125
|
+
const targetScenes = targetDuration ? Math.max(8, Math.ceil(targetDuration / 2.8)) : Math.max(sceneCount, 55);
|
|
122
126
|
|
|
123
127
|
const raw = await retryPol.executeStage('Script', async () => {
|
|
124
128
|
const system = [
|
|
125
|
-
`You are a ${
|
|
129
|
+
`You are a ${effectivePersona} writing a ${format} script.`,
|
|
126
130
|
`Style: ${style}. Language: ${language}.`,
|
|
127
131
|
`Visual aesthetic for image prompts: ${imageStyle}.`,
|
|
128
132
|
'Output ONLY raw JSON. No markdown. Start with { end with }.',
|
|
@@ -136,12 +140,16 @@ async function runScript({
|
|
|
136
140
|
`STYLE: ${style}`,
|
|
137
141
|
`LANGUAGE: ${language}`,
|
|
138
142
|
'',
|
|
139
|
-
`Write exactly ${targetScenes} scenes (schema_version "2.0.0").
|
|
143
|
+
`Write exactly ${targetScenes} scenes (schema_version "2.0.0"). Fast cinematic visual pacing: 2-3.5 seconds per scene (8-16 spoken words). Scene visual cuts must change frequently. Never write long monolithic scenes. Each scene duration matches the spoken voiceover audio.`,
|
|
144
|
+
anchorWanted
|
|
145
|
+
? 'Include an on-screen host/anchor character in the scenes where appropriate.'
|
|
146
|
+
: 'Do NOT include on-screen host or anchor characters unless asked. Set character to { character_id: "none", pose: "none", emotion: "none" }. Visuals must be dynamic cinematic b-roll, motion graphics, animated diagrams, comparisons, processes, and data visualizations.',
|
|
147
|
+
'Each scene must have:',
|
|
140
148
|
'scene_id (integer), schema_version "2.0.0",',
|
|
141
|
-
'narration (
|
|
142
|
-
'caption (
|
|
143
|
-
'visual_description (vivid description of
|
|
144
|
-
'character { character_id (
|
|
149
|
+
'narration (8-18 words in the specified language, spoken aloud),',
|
|
150
|
+
'caption (optional 2-4 uppercase words text overlay only if captions requested, otherwise empty string),',
|
|
151
|
+
'visual_description (vivid description of the visual motion graphics / b-roll / diagram / scene — used for code-driven animation),',
|
|
152
|
+
'character { character_id ("none" or identifier), pose, emotion, position {x,y,scale} },',
|
|
145
153
|
'focal_point {x,y,weight},',
|
|
146
154
|
'overlay { type: "prop"|"effect"|"text"|"none" },',
|
|
147
155
|
'sound_effects [ { effect_id, timing: "start"|"middle"|"end", volume } ],',
|