@amg555/frameforge-mcp 2.7.3 → 2.7.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/.claude-plugin/marketplace.json +3 -3
- package/AGENTS.md +33 -0
- package/CHANGELOG.md +49 -0
- package/CLAUDE.md +17 -6
- package/GEMINI.md +63 -0
- package/README.md +216 -426
- package/START_HERE.md +4 -2
- package/package.json +5 -3
- package/plugins/ai-video-editor/README.md +32 -0
- package/plugins/ai-video-editor/SKILL.md +152 -0
- package/plugins/ai-video-editor/assets/fonts/fonts.css +2409 -0
- package/plugins/ai-video-editor/assets/fonts/ttf/anton-400.ttf +0 -0
- package/plugins/ai-video-editor/assets/fonts/ttf/archivo-800.ttf +0 -0
- package/plugins/ai-video-editor/assets/fonts/ttf/cabin-sketch-700.ttf +0 -0
- package/plugins/ai-video-editor/assets/fonts/ttf/dm-sans-700.ttf +0 -0
- package/plugins/ai-video-editor/assets/fonts/ttf/fraunces-600.ttf +0 -0
- package/plugins/ai-video-editor/assets/fonts/ttf/fraunces-700.ttf +0 -0
- package/plugins/ai-video-editor/assets/fonts/ttf/fredoka-700.ttf +0 -0
- package/plugins/ai-video-editor/assets/fonts/ttf/inter-700.ttf +0 -0
- package/plugins/ai-video-editor/assets/fonts/ttf/inter-800.ttf +0 -0
- package/plugins/ai-video-editor/assets/fonts/ttf/inter-tight-800.ttf +0 -0
- package/plugins/ai-video-editor/assets/fonts/ttf/kalam-700.ttf +0 -0
- package/plugins/ai-video-editor/assets/fonts/ttf/noto-sans-devanagari-700.ttf +0 -0
- package/plugins/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-400-normal.woff2 +0 -0
- package/plugins/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-500-normal.woff2 +0 -0
- package/plugins/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-600-normal.woff2 +0 -0
- package/plugins/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-700-normal.woff2 +0 -0
- package/plugins/ai-video-editor/assets/fonts/web/noto-sans-devanagari-devanagari-800-normal.woff2 +0 -0
- package/plugins/ai-video-editor/engine/hidpi.js +117 -0
- package/plugins/ai-video-editor/engine/page.html +43 -0
- package/plugins/ai-video-editor/engine/player.js +145 -0
- package/plugins/ai-video-editor/references/example-story-warm-canvas.js +95 -0
- package/plugins/ai-video-editor/references/story-guide.md +112 -0
- package/plugins/ai-video-editor/requirements.txt +4 -0
- package/plugins/ai-video-editor/styles/chalk-talk/base.js +490 -0
- package/plugins/ai-video-editor/styles/chalk-talk/style.json +14 -0
- package/plugins/ai-video-editor/styles/chalk-talk/style.md +41 -0
- package/plugins/ai-video-editor/styles/clean-tech/base.js +447 -0
- package/plugins/ai-video-editor/styles/clean-tech/style.json +15 -0
- package/plugins/ai-video-editor/styles/clean-tech/style.md +38 -0
- package/plugins/ai-video-editor/styles/creator-motion-graphics/base.js +698 -0
- package/plugins/ai-video-editor/styles/creator-motion-graphics/style.json +16 -0
- package/plugins/ai-video-editor/styles/creator-motion-graphics/style.md +39 -0
- package/plugins/ai-video-editor/styles/data-story/base.js +432 -0
- package/plugins/ai-video-editor/styles/data-story/style.json +14 -0
- package/plugins/ai-video-editor/styles/data-story/style.md +38 -0
- package/plugins/ai-video-editor/styles/index.json +103 -0
- package/plugins/ai-video-editor/styles/pop-info/base.js +570 -0
- package/plugins/ai-video-editor/styles/pop-info/style.json +13 -0
- package/plugins/ai-video-editor/styles/pop-info/style.md +37 -0
- package/plugins/ai-video-editor/styles/software-walkthrough/base.js +544 -0
- package/plugins/ai-video-editor/styles/software-walkthrough/style.json +15 -0
- package/plugins/ai-video-editor/styles/software-walkthrough/style.md +38 -0
- package/plugins/ai-video-editor/styles/warm-canvas/base.js +610 -0
- package/plugins/ai-video-editor/styles/warm-canvas/style.json +14 -0
- package/plugins/ai-video-editor/styles/warm-canvas/style.md +37 -0
- package/plugins/aiinsight-shorts/CHANGELOG.md +6 -0
- package/plugins/aiinsight-shorts/README.md +43 -0
- package/plugins/aiinsight-shorts/SKILL.md +138 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/STYLE.md +58 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/data/cues.json +140 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/data/lines.json +67 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/data/words.json +614 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/data/words.txt +102 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/fonts/Inter-Black.otf +0 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/fonts/Inter-ExtraBold.otf +0 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/index.html +17 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/script.json +27 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/sheet.html +11 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/src/core/board.js +62 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/src/core/captions.js +68 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/src/core/engine.js +65 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/src/core/human.js +128 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/src/core/lib.js +44 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/src/core/look2d.js +33 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/src/core/sdf.js +79 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/src/core/sets.js +107 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/src/core/timing.js +23 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/src/main.js +56 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/src/sheet.js +79 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/src/shots.js +136 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/tools/capture.mjs +122 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/tools/mix.py +172 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/LICENSE-three.txt +21 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/addons/environments/RoomEnvironment.js +144 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/addons/postprocessing/BokehPass.js +141 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/addons/postprocessing/EffectComposer.js +231 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/addons/postprocessing/MaskPass.js +104 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/addons/postprocessing/OutputPass.js +97 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/addons/postprocessing/Pass.js +95 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/addons/postprocessing/RenderPass.js +99 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/addons/postprocessing/ShaderPass.js +77 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/addons/postprocessing/UnrealBloomPass.js +415 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/addons/shaders/BokehShader.js +145 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/addons/shaders/CopyShader.js +45 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/addons/shaders/LuminosityHighPassShader.js +64 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/addons/shaders/OutputShader.js +85 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/example/vendor/three.module.js +6 -0
- package/plugins/aiinsight-shorts/styles/deep-dive/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/inside-out/STYLE.md +97 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/data/cues.json +178 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/data/lines.json +73 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/data/words.json +662 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/fonts/Inter-Black.otf +0 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/index.html +17 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/script.json +26 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/src/core/captions.js +68 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/src/core/kit.js +345 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/src/core/lib.js +44 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/src/core/timing.js +23 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/src/main.js +62 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/src/shots.js +195 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/tools/capture.mjs +122 -0
- package/plugins/aiinsight-shorts/styles/inside-out/example/tools/mix.py +172 -0
- package/plugins/aiinsight-shorts/styles/inside-out/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/night-file/STYLE.md +36 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/data/cues.json +146 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/data/lines.json +73 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/data/words.json +572 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/data/words.txt +95 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/fonts/FreeMonoBold.ttf +0 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/fonts/Inter-Black.otf +0 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/index.html +18 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/script.json +26 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/sheet.html +13 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/src/core/board.js +62 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/src/core/captions.js +49 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/src/core/engine.js +65 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/src/core/human.js +128 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/src/core/kit.js +151 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/src/core/lib.js +44 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/src/core/sdf.js +79 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/src/core/sets3d.js +106 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/src/core/timing.js +23 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/src/main.js +56 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/src/sheet.js +86 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/src/shots.js +130 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/tools/capture.mjs +122 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/tools/mix.py +172 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/LICENSE-three.txt +21 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/addons/environments/RoomEnvironment.js +144 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/addons/postprocessing/BokehPass.js +141 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/addons/postprocessing/EffectComposer.js +231 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/addons/postprocessing/MaskPass.js +104 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/addons/postprocessing/OutputPass.js +97 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/addons/postprocessing/Pass.js +95 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/addons/postprocessing/RenderPass.js +99 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/addons/postprocessing/ShaderPass.js +77 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/addons/postprocessing/UnrealBloomPass.js +415 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/addons/shaders/BokehShader.js +145 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/addons/shaders/CopyShader.js +45 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/addons/shaders/LuminosityHighPassShader.js +64 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/addons/shaders/OutputShader.js +85 -0
- package/plugins/aiinsight-shorts/styles/night-file/example/vendor/three.module.js +6 -0
- package/plugins/aiinsight-shorts/styles/night-file/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/STYLE.md +30 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/data/cues.json +167 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/data/lines.json +67 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/data/words.json +614 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/data/words.txt +102 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/fonts/Caladea-Bold.ttf +0 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/fonts/Inter-Black.otf +0 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/index.html +15 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/script.json +26 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/sheet.html +12 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/src/core/board.js +62 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/src/core/captions.js +47 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/src/core/kit.js +152 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/src/core/lib.js +44 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/src/core/timing.js +23 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/src/main.js +62 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/src/sheet.js +61 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/src/shots.js +139 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/tools/capture.mjs +122 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/example/tools/mix.py +172 -0
- package/plugins/aiinsight-shorts/styles/paper-atlas/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/stick-story/STYLE.md +32 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/data/cues.json +182 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/data/lines.json +79 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/data/words.json +662 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/data/words.txt +110 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/fonts/Inter-Black.otf +0 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/fonts/Inter-ExtraBold.otf +0 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/index.html +13 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/script.json +27 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/sheet.html +10 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/src/core/board.js +62 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/src/core/captions.js +47 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/src/core/kit.js +166 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/src/core/lib.js +44 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/src/core/timing.js +23 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/src/main.js +62 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/src/sheet.js +64 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/src/shots.js +148 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/tools/capture.mjs +122 -0
- package/plugins/aiinsight-shorts/styles/stick-story/example/tools/mix.py +172 -0
- package/plugins/aiinsight-shorts/styles/stick-story/style-sheet.png +0 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/STYLE.md +51 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/data/cues.json +271 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/data/lines.json +127 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/data/words.json +908 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/data/words.txt +151 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/fonts/Caladea-Bold.ttf +0 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/fonts/Inter-Black.otf +0 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/fonts/Inter-ExtraBold.otf +0 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/index.html +19 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/script.json +37 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/sheet.html +13 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/src/core/board.js +62 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/src/core/captions.js +56 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/src/core/engine.js +65 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/src/core/lib.js +44 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/src/core/props.js +166 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/src/core/timing.js +23 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/src/core/toon.js +352 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/src/core/world.js +340 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/src/main.js +56 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/src/sheet.js +99 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/src/shots.js +377 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/tools/capture.mjs +122 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/tools/mix.py +204 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/LICENSE-three.txt +21 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/addons/environments/RoomEnvironment.js +144 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/addons/postprocessing/BokehPass.js +141 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/addons/postprocessing/EffectComposer.js +231 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/addons/postprocessing/MaskPass.js +104 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/addons/postprocessing/OutputPass.js +97 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/addons/postprocessing/Pass.js +95 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/addons/postprocessing/RenderPass.js +99 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/addons/postprocessing/ShaderPass.js +77 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/addons/postprocessing/UnrealBloomPass.js +415 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/addons/shaders/BokehShader.js +145 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/addons/shaders/CopyShader.js +45 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/addons/shaders/LuminosityHighPassShader.js +64 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/addons/shaders/OutputShader.js +85 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/example/vendor/three.module.js +6 -0
- package/plugins/aiinsight-shorts/styles/tall-tale/style-sheet.png +0 -0
- package/plugins/faceless-shorts/README.md +34 -0
- package/plugins/faceless-shorts/SKILL.md +124 -0
- package/plugins/faceless-shorts/styles/deep-dive/STYLE.md +58 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/data/cues.json +140 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/data/lines.json +67 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/data/words.json +614 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/data/words.txt +102 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/fonts/Inter-Black.otf +0 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/fonts/Inter-ExtraBold.otf +0 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/index.html +16 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/script.json +27 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/sheet.html +10 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/src/core/board.js +62 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/src/core/captions.js +68 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/src/core/engine.js +65 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/src/core/human.js +128 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/src/core/lib.js +44 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/src/core/look2d.js +33 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/src/core/sdf.js +79 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/src/core/sets.js +107 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/src/core/timing.js +23 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/src/main.js +56 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/src/sheet.js +79 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/src/shots.js +136 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/tools/capture.mjs +122 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/tools/mix.py +172 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/LICENSE-three.txt +21 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/addons/environments/RoomEnvironment.js +144 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/BokehPass.js +141 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/EffectComposer.js +231 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/MaskPass.js +104 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/OutputPass.js +97 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/Pass.js +95 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/RenderPass.js +99 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/ShaderPass.js +77 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/addons/postprocessing/UnrealBloomPass.js +415 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/BokehShader.js +145 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/CopyShader.js +45 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/LuminosityHighPassShader.js +64 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/addons/shaders/OutputShader.js +85 -0
- package/plugins/faceless-shorts/styles/deep-dive/example/vendor/three.module.js +6 -0
- package/plugins/faceless-shorts/styles/deep-dive/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/inside-out/STYLE.md +97 -0
- package/plugins/faceless-shorts/styles/inside-out/example/data/cues.json +178 -0
- package/plugins/faceless-shorts/styles/inside-out/example/data/lines.json +73 -0
- package/plugins/faceless-shorts/styles/inside-out/example/data/words.json +662 -0
- package/plugins/faceless-shorts/styles/inside-out/example/fonts/Inter-Black.otf +0 -0
- package/plugins/faceless-shorts/styles/inside-out/example/index.html +16 -0
- package/plugins/faceless-shorts/styles/inside-out/example/script.json +26 -0
- package/plugins/faceless-shorts/styles/inside-out/example/src/core/captions.js +68 -0
- package/plugins/faceless-shorts/styles/inside-out/example/src/core/kit.js +345 -0
- package/plugins/faceless-shorts/styles/inside-out/example/src/core/lib.js +44 -0
- package/plugins/faceless-shorts/styles/inside-out/example/src/core/timing.js +23 -0
- package/plugins/faceless-shorts/styles/inside-out/example/src/main.js +62 -0
- package/plugins/faceless-shorts/styles/inside-out/example/src/shots.js +195 -0
- package/plugins/faceless-shorts/styles/inside-out/example/tools/capture.mjs +122 -0
- package/plugins/faceless-shorts/styles/inside-out/example/tools/mix.py +172 -0
- package/plugins/faceless-shorts/styles/inside-out/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/night-file/STYLE.md +36 -0
- package/plugins/faceless-shorts/styles/night-file/example/data/cues.json +146 -0
- package/plugins/faceless-shorts/styles/night-file/example/data/lines.json +73 -0
- package/plugins/faceless-shorts/styles/night-file/example/data/words.json +572 -0
- package/plugins/faceless-shorts/styles/night-file/example/data/words.txt +95 -0
- package/plugins/faceless-shorts/styles/night-file/example/fonts/FreeMonoBold.ttf +0 -0
- package/plugins/faceless-shorts/styles/night-file/example/fonts/Inter-Black.otf +0 -0
- package/plugins/faceless-shorts/styles/night-file/example/index.html +16 -0
- package/plugins/faceless-shorts/styles/night-file/example/script.json +26 -0
- package/plugins/faceless-shorts/styles/night-file/example/sheet.html +11 -0
- package/plugins/faceless-shorts/styles/night-file/example/src/core/board.js +62 -0
- package/plugins/faceless-shorts/styles/night-file/example/src/core/captions.js +49 -0
- package/plugins/faceless-shorts/styles/night-file/example/src/core/engine.js +65 -0
- package/plugins/faceless-shorts/styles/night-file/example/src/core/human.js +128 -0
- package/plugins/faceless-shorts/styles/night-file/example/src/core/kit.js +151 -0
- package/plugins/faceless-shorts/styles/night-file/example/src/core/lib.js +44 -0
- package/plugins/faceless-shorts/styles/night-file/example/src/core/sdf.js +79 -0
- package/plugins/faceless-shorts/styles/night-file/example/src/core/sets3d.js +106 -0
- package/plugins/faceless-shorts/styles/night-file/example/src/core/timing.js +23 -0
- package/plugins/faceless-shorts/styles/night-file/example/src/main.js +56 -0
- package/plugins/faceless-shorts/styles/night-file/example/src/sheet.js +86 -0
- package/plugins/faceless-shorts/styles/night-file/example/src/shots.js +130 -0
- package/plugins/faceless-shorts/styles/night-file/example/tools/capture.mjs +122 -0
- package/plugins/faceless-shorts/styles/night-file/example/tools/mix.py +172 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/LICENSE-three.txt +21 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/addons/environments/RoomEnvironment.js +144 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/BokehPass.js +141 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/EffectComposer.js +231 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/MaskPass.js +104 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/OutputPass.js +97 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/Pass.js +95 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/RenderPass.js +99 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/ShaderPass.js +77 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/addons/postprocessing/UnrealBloomPass.js +415 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/addons/shaders/BokehShader.js +145 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/addons/shaders/CopyShader.js +45 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/addons/shaders/LuminosityHighPassShader.js +64 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/addons/shaders/OutputShader.js +85 -0
- package/plugins/faceless-shorts/styles/night-file/example/vendor/three.module.js +6 -0
- package/plugins/faceless-shorts/styles/night-file/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/paper-atlas/STYLE.md +30 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/data/cues.json +167 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/data/lines.json +67 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/data/words.json +614 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/data/words.txt +102 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/fonts/Caladea-Bold.ttf +0 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/fonts/Inter-Black.otf +0 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/index.html +13 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/script.json +26 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/sheet.html +10 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/src/core/board.js +62 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/src/core/captions.js +47 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/src/core/kit.js +152 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/src/core/lib.js +44 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/src/core/timing.js +23 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/src/main.js +62 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/src/sheet.js +61 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/src/shots.js +139 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/tools/capture.mjs +122 -0
- package/plugins/faceless-shorts/styles/paper-atlas/example/tools/mix.py +172 -0
- package/plugins/faceless-shorts/styles/paper-atlas/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/stick-story/STYLE.md +32 -0
- package/plugins/faceless-shorts/styles/stick-story/example/data/cues.json +182 -0
- package/plugins/faceless-shorts/styles/stick-story/example/data/lines.json +79 -0
- package/plugins/faceless-shorts/styles/stick-story/example/data/words.json +662 -0
- package/plugins/faceless-shorts/styles/stick-story/example/data/words.txt +110 -0
- package/plugins/faceless-shorts/styles/stick-story/example/fonts/Inter-Black.otf +0 -0
- package/plugins/faceless-shorts/styles/stick-story/example/fonts/Inter-ExtraBold.otf +0 -0
- package/plugins/faceless-shorts/styles/stick-story/example/index.html +12 -0
- package/plugins/faceless-shorts/styles/stick-story/example/script.json +27 -0
- package/plugins/faceless-shorts/styles/stick-story/example/sheet.html +9 -0
- package/plugins/faceless-shorts/styles/stick-story/example/src/core/board.js +62 -0
- package/plugins/faceless-shorts/styles/stick-story/example/src/core/captions.js +47 -0
- package/plugins/faceless-shorts/styles/stick-story/example/src/core/kit.js +166 -0
- package/plugins/faceless-shorts/styles/stick-story/example/src/core/lib.js +44 -0
- package/plugins/faceless-shorts/styles/stick-story/example/src/core/timing.js +23 -0
- package/plugins/faceless-shorts/styles/stick-story/example/src/main.js +62 -0
- package/plugins/faceless-shorts/styles/stick-story/example/src/sheet.js +64 -0
- package/plugins/faceless-shorts/styles/stick-story/example/src/shots.js +148 -0
- package/plugins/faceless-shorts/styles/stick-story/example/tools/capture.mjs +122 -0
- package/plugins/faceless-shorts/styles/stick-story/example/tools/mix.py +172 -0
- package/plugins/faceless-shorts/styles/stick-story/style-sheet.png +0 -0
- package/plugins/faceless-shorts/styles/tall-tale/STYLE.md +51 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/data/cues.json +271 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/data/lines.json +127 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/data/words.json +908 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/data/words.txt +151 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/fonts/Caladea-Bold.ttf +0 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/fonts/Inter-Black.otf +0 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/fonts/Inter-ExtraBold.otf +0 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/index.html +17 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/script.json +37 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/sheet.html +11 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/src/core/board.js +62 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/src/core/captions.js +56 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/src/core/engine.js +65 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/src/core/lib.js +44 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/src/core/props.js +166 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/src/core/timing.js +23 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/src/core/toon.js +352 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/src/core/world.js +340 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/src/main.js +56 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/src/sheet.js +99 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/src/shots.js +377 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/tools/capture.mjs +122 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/tools/mix.py +204 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/LICENSE-three.txt +21 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/addons/environments/RoomEnvironment.js +144 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/BokehPass.js +141 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/EffectComposer.js +231 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/MaskPass.js +104 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/OutputPass.js +97 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/Pass.js +95 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/RenderPass.js +99 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/ShaderPass.js +77 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/addons/postprocessing/UnrealBloomPass.js +415 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/BokehShader.js +145 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/CopyShader.js +45 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/LuminosityHighPassShader.js +64 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/addons/shaders/OutputShader.js +85 -0
- package/plugins/faceless-shorts/styles/tall-tale/example/vendor/three.module.js +6 -0
- package/plugins/faceless-shorts/styles/tall-tale/style-sheet.png +0 -0
- package/plugins/motion-styles/LIBRARY.md +896 -0
- package/plugins/motion-styles/README.md +29 -0
- package/plugins/motion-styles/SKILL.md +70 -0
- package/plugins/motion-styles/catalog.json +2253 -0
- package/plugins/motion-styles/prompts/bio-glow.md +149 -0
- package/plugins/motion-styles/prompts/blueprint.md +145 -0
- package/plugins/motion-styles/prompts/breaking-desk.md +147 -0
- package/plugins/motion-styles/prompts/callout-kit.md +147 -0
- package/plugins/motion-styles/prompts/chalk-talk.md +153 -0
- package/plugins/motion-styles/prompts/chat-story.md +143 -0
- package/plugins/motion-styles/prompts/clay-diorama.md +149 -0
- package/plugins/motion-styles/prompts/clean-tech.md +147 -0
- package/plugins/motion-styles/prompts/comic-panels.md +145 -0
- package/plugins/motion-styles/prompts/cosmic-scale.md +147 -0
- package/plugins/motion-styles/prompts/creator-motion.md +149 -0
- package/plugins/motion-styles/prompts/data-city.md +149 -0
- package/plugins/motion-styles/prompts/data-story.md +147 -0
- package/plugins/motion-styles/prompts/deep-layers.md +147 -0
- package/plugins/motion-styles/prompts/doc-cinema.md +145 -0
- package/plugins/motion-styles/prompts/evidence-board.md +145 -0
- package/plugins/motion-styles/prompts/exploded-view.md +149 -0
- package/plugins/motion-styles/prompts/glass-icons.md +147 -0
- package/plugins/motion-styles/prompts/glitch-pack.md +145 -0
- package/plugins/motion-styles/prompts/globe-arcs.md +147 -0
- package/plugins/motion-styles/prompts/headline-backdrop.md +145 -0
- package/plugins/motion-styles/prompts/iso-cutaway.md +147 -0
- package/plugins/motion-styles/prompts/isotype.md +145 -0
- package/plugins/motion-styles/prompts/karaoke-subs.md +145 -0
- package/plugins/motion-styles/prompts/low-poly-atlas.md +149 -0
- package/plugins/motion-styles/prompts/lower-thirds.md +147 -0
- package/plugins/motion-styles/prompts/marker-board.md +145 -0
- package/plugins/motion-styles/prompts/market-tape.md +145 -0
- package/plugins/motion-styles/prompts/math-canvas.md +145 -0
- package/plugins/motion-styles/prompts/myth-or-fact.md +145 -0
- package/plugins/motion-styles/prompts/neon-line.md +145 -0
- package/plugins/motion-styles/prompts/paper-craft.md +145 -0
- package/plugins/motion-styles/prompts/parchment-map.md +147 -0
- package/plugins/motion-styles/prompts/physics-toybox.md +147 -0
- package/plugins/motion-styles/prompts/pixel-quest.md +145 -0
- package/plugins/motion-styles/prompts/podcast-clip.md +147 -0
- package/plugins/motion-styles/prompts/pop-info.md +145 -0
- package/plugins/motion-styles/prompts/product-turntable.md +149 -0
- package/plugins/motion-styles/prompts/punch-captions.md +145 -0
- package/plugins/motion-styles/prompts/quiz-countdown.md +145 -0
- package/plugins/motion-styles/prompts/quote-cinema.md +145 -0
- package/plugins/motion-styles/prompts/recipe-steps.md +145 -0
- package/plugins/motion-styles/prompts/rubber-hose-reel.md +145 -0
- package/plugins/motion-styles/prompts/shadow-puppets.md +145 -0
- package/plugins/motion-styles/prompts/size-lineup.md +149 -0
- package/plugins/motion-styles/prompts/software-walkthrough.md +147 -0
- package/plugins/motion-styles/prompts/stack-split.md +145 -0
- package/plugins/motion-styles/prompts/sticker-pop.md +145 -0
- package/plugins/motion-styles/prompts/storybook.md +145 -0
- package/plugins/motion-styles/prompts/swiss-grid.md +145 -0
- package/plugins/motion-styles/prompts/synth-grid.md +147 -0
- package/plugins/motion-styles/prompts/tactics-board.md +145 -0
- package/plugins/motion-styles/prompts/terminal.md +145 -0
- package/plugins/motion-styles/prompts/tier-list.md +143 -0
- package/plugins/motion-styles/prompts/timeline-rail.md +145 -0
- package/plugins/motion-styles/prompts/top-5-countdown.md +145 -0
- package/plugins/motion-styles/prompts/versus-split.md +145 -0
- package/plugins/motion-styles/prompts/vhs-camcorder.md +145 -0
- package/plugins/motion-styles/prompts/visual-novel.md +145 -0
- package/plugins/motion-styles/prompts/warm-canvas.md +145 -0
- package/plugins/motion-styles/prompts/woodcut-tale.md +145 -0
- package/plugins/motion-styles/prompts/zine-collage.md +145 -0
- package/plugins/motion-styles/sources/bio-glow.html +639 -0
- package/plugins/motion-styles/sources/blueprint.html +617 -0
- package/plugins/motion-styles/sources/breaking-desk.html +585 -0
- package/plugins/motion-styles/sources/callout-kit.html +586 -0
- package/plugins/motion-styles/sources/chalk-talk.html +597 -0
- package/plugins/motion-styles/sources/chat-story.html +597 -0
- package/plugins/motion-styles/sources/clay-diorama.html +681 -0
- package/plugins/motion-styles/sources/clean-tech.html +554 -0
- package/plugins/motion-styles/sources/comic-panels.html +608 -0
- package/plugins/motion-styles/sources/cosmic-scale.html +628 -0
- package/plugins/motion-styles/sources/creator-motion.html +805 -0
- package/plugins/motion-styles/sources/data-city.html +613 -0
- package/plugins/motion-styles/sources/data-story.html +539 -0
- package/plugins/motion-styles/sources/deep-layers.html +624 -0
- package/plugins/motion-styles/sources/doc-cinema.html +590 -0
- package/plugins/motion-styles/sources/evidence-board.html +610 -0
- package/plugins/motion-styles/sources/exploded-view.html +618 -0
- package/plugins/motion-styles/sources/glass-icons.html +612 -0
- package/plugins/motion-styles/sources/glitch-pack.html +583 -0
- package/plugins/motion-styles/sources/globe-arcs.html +621 -0
- package/plugins/motion-styles/sources/headline-backdrop.html +584 -0
- package/plugins/motion-styles/sources/iso-cutaway.html +621 -0
- package/plugins/motion-styles/sources/isotype.html +588 -0
- package/plugins/motion-styles/sources/karaoke-subs.html +577 -0
- package/plugins/motion-styles/sources/low-poly-atlas.html +621 -0
- package/plugins/motion-styles/sources/lower-thirds.html +582 -0
- package/plugins/motion-styles/sources/marker-board.html +598 -0
- package/plugins/motion-styles/sources/market-tape.html +598 -0
- package/plugins/motion-styles/sources/math-canvas.html +592 -0
- package/plugins/motion-styles/sources/myth-or-fact.html +587 -0
- package/plugins/motion-styles/sources/neon-line.html +597 -0
- package/plugins/motion-styles/sources/paper-craft.html +612 -0
- package/plugins/motion-styles/sources/parchment-map.html +624 -0
- package/plugins/motion-styles/sources/physics-toybox.html +617 -0
- package/plugins/motion-styles/sources/pixel-quest.html +609 -0
- package/plugins/motion-styles/sources/podcast-clip.html +585 -0
- package/plugins/motion-styles/sources/pop-info.html +677 -0
- package/plugins/motion-styles/sources/product-turntable.html +611 -0
- package/plugins/motion-styles/sources/punch-captions.html +583 -0
- package/plugins/motion-styles/sources/quiz-countdown.html +593 -0
- package/plugins/motion-styles/sources/quote-cinema.html +581 -0
- package/plugins/motion-styles/sources/recipe-steps.html +599 -0
- package/plugins/motion-styles/sources/rubber-hose-reel.html +627 -0
- package/plugins/motion-styles/sources/shadow-puppets.html +609 -0
- package/plugins/motion-styles/sources/size-lineup.html +620 -0
- package/plugins/motion-styles/sources/software-walkthrough.html +651 -0
- package/plugins/motion-styles/sources/stack-split.html +588 -0
- package/plugins/motion-styles/sources/sticker-pop.html +587 -0
- package/plugins/motion-styles/sources/storybook.html +600 -0
- package/plugins/motion-styles/sources/swiss-grid.html +590 -0
- package/plugins/motion-styles/sources/synth-grid.html +614 -0
- package/plugins/motion-styles/sources/tactics-board.html +608 -0
- package/plugins/motion-styles/sources/terminal.html +593 -0
- package/plugins/motion-styles/sources/tier-list.html +600 -0
- package/plugins/motion-styles/sources/timeline-rail.html +598 -0
- package/plugins/motion-styles/sources/top-5-countdown.html +589 -0
- package/plugins/motion-styles/sources/versus-split.html +592 -0
- package/plugins/motion-styles/sources/vhs-camcorder.html +587 -0
- package/plugins/motion-styles/sources/visual-novel.html +623 -0
- package/plugins/motion-styles/sources/warm-canvas.html +742 -0
- package/plugins/motion-styles/sources/woodcut-tale.html +598 -0
- package/plugins/motion-styles/sources/zine-collage.html +594 -0
- package/plugins/video-pipeline/.claude-plugin/plugin.json +2 -2
- package/plugins/video-pipeline/skills/.upstream-skills.json +156 -143
- package/plugins/video-pipeline/skills/video-pipeline/references/mcp-tools.md +3 -3
- package/scripts/deep-validate.js +3 -2
- package/src/audio-mixer/mixer.js +69 -0
- package/src/cli/make.js +19 -0
- package/src/harness/lib/browser.mjs +22 -1
- package/src/harness/lib/capture.mjs +33 -11
- package/src/harness/lib/jobs.mjs +16 -1
- package/src/mcp-server/server.js +726 -10
- package/src/pipeline/providers.js +77 -17
- package/src/skills/sync-upstream.js +15 -3
- package/src/utils/media-downloader.js +7 -0
- package/src/utils/tool-installer.js +212 -0
- package/src/utils/transcribe.js +9 -1
- package/src/video-renderer/renderer.js +68 -38
- package/src/video-renderer/scene-animator.js +50 -15
- package/src/workflows/assets.js +49 -5
- package/src/workflows/render.js +2 -0
- package/src/workflows/script.js +14 -3
package/src/mcp-server/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FrameForge MCP Server — MCP 2024-11-05 spec compliant
|
|
3
3
|
*
|
|
4
|
-
* Exposes
|
|
4
|
+
* Exposes 34 video production & autonomous editing tools via MCP.
|
|
5
5
|
* Works with IBM Bob, Cursor, Windsurf, Codex, opencode, Claude Code, or any MCP-capable IDE.
|
|
6
6
|
* Primary connection: Cloudflare Remote MCP (cloudflare-mcp/).
|
|
7
7
|
* Alternative: stdio (src/mcp-server/stdio.js) or HTTP (this file).
|
|
@@ -356,9 +356,10 @@ const TOOLS = {
|
|
|
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
358
|
showCaptions: { type: 'boolean', default: false, description: 'Display on-screen synchronized subtitles/captions. Default false.' },
|
|
359
|
+
showAnchor: { type: 'boolean', default: false, description: 'Display on-screen anchor/presenter character. Default false.' },
|
|
359
360
|
},
|
|
360
361
|
},
|
|
361
|
-
handler: async ({ productionId, scenes, orientation = 'landscape', skipImages = false, skipTts = false, withMusic = true, musicVolume = 0.07, topic = '', style = '', theme = '', title = '', language = '', showCaptions = false }) => {
|
|
362
|
+
handler: async ({ productionId, scenes, orientation = 'landscape', skipImages = false, skipTts = false, withMusic = true, musicVolume = 0.07, topic = '', style = '', theme = '', title = '', language = '', showCaptions = false, showAnchor = false }) => {
|
|
362
363
|
const syncFs = require('fs');
|
|
363
364
|
const { runAssets } = require('../workflows/assets');
|
|
364
365
|
const { runRender } = require('../workflows/render');
|
|
@@ -404,6 +405,8 @@ const TOOLS = {
|
|
|
404
405
|
theme,
|
|
405
406
|
title,
|
|
406
407
|
language,
|
|
408
|
+
showCaptions,
|
|
409
|
+
showAnchor,
|
|
407
410
|
imageWidth: renderWidth,
|
|
408
411
|
imageHeight: renderHeight,
|
|
409
412
|
});
|
|
@@ -422,6 +425,7 @@ const TOOLS = {
|
|
|
422
425
|
title,
|
|
423
426
|
language,
|
|
424
427
|
showCaptions,
|
|
428
|
+
showAnchor,
|
|
425
429
|
});
|
|
426
430
|
|
|
427
431
|
if (!renderResult.passed) {
|
|
@@ -580,7 +584,7 @@ Example scene:
|
|
|
580
584
|
voice: { type: 'string', description: 'edge-tts voice ID for all scenes, e.g. "ml-IN-MidhunNeural" (Malayalam male), "en-US-BrianNeural" (English). Per-scene voice field overrides this. Full list: edge-tts --list-voices' },
|
|
581
585
|
language: { type: 'string', description: 'BCP-47 language hint, e.g. "ml" (Malayalam), "hi" (Hindi), "ta" (Tamil), "te" (Telugu), "en" (English). Auto-selects a default voice for the language when voice is not set.' },
|
|
582
586
|
skipTts: { type: 'boolean', default: false, description: 'Set true if all scenes already have audio_path' },
|
|
583
|
-
parallelism: { type: 'number', default:
|
|
587
|
+
parallelism: { type: 'number', default: 4, description: 'Concurrent TTS synthesis requests (default: 4 for fast multi-scene production).' },
|
|
584
588
|
withMusic: { type: 'boolean', default: true },
|
|
585
589
|
musicVolume: { type: 'number', default: 0.07 },
|
|
586
590
|
topic: { type: 'string', description: 'Overall topic/genre for visual aesthetic and background motion (e.g. "Kerala history", "crypto finance", "AI robotics")' },
|
|
@@ -591,7 +595,7 @@ Example scene:
|
|
|
591
595
|
productionId: { type: 'string', description: 'Optional — reuse an existing productionId' },
|
|
592
596
|
},
|
|
593
597
|
},
|
|
594
|
-
handler: async ({ scenes, title = 'FrameForge Video', orientation = 'landscape', voice, language, skipTts = false, parallelism =
|
|
598
|
+
handler: async ({ scenes, title = 'FrameForge Video', orientation = 'landscape', voice, language, skipTts = false, parallelism = 4, withMusic = true, musicVolume = 0.07, topic = '', style = '', theme = '', showCaptions = false, showAnchor = false, productionId }) => {
|
|
595
599
|
const { runAssets } = require('../workflows/assets');
|
|
596
600
|
const { runRender } = require('../workflows/render');
|
|
597
601
|
|
|
@@ -2120,7 +2124,7 @@ Professional audio rules applied:
|
|
|
2120
2124
|
planMd += `- L-cuts: current audio extends 0.3s into next clip's video\n\n`;
|
|
2121
2125
|
planMd += `## 🚀 Execution & Approval\n\n`;
|
|
2122
2126
|
planMd += `Review this plan, then execute with \`edit_video\` using the payload in \`edit.json\`.\n`;
|
|
2123
|
-
planMd += `\nGenerated by FrameForge v2.7.
|
|
2127
|
+
planMd += `\nGenerated by FrameForge v2.7.7 — plan_video_edit with J/L-cut support (lemo-opuscar DIRECTOR.md)\n`;
|
|
2124
2128
|
|
|
2125
2129
|
// 8. Build TREATMENT.md (lemo-opuscar DIRECTOR.md — 8-section format)
|
|
2126
2130
|
let treatmentMd = '';
|
|
@@ -2161,7 +2165,7 @@ Professional audio rules applied:
|
|
|
2161
2165
|
treatmentMd += `## 8. Director Notes\n\n`;
|
|
2162
2166
|
treatmentMd += `_Add creative direction, mood references, or client notes here._\n\n`;
|
|
2163
2167
|
treatmentMd += `---\n\n`;
|
|
2164
|
-
treatmentMd += `*Generated by FrameForge v2.7.
|
|
2168
|
+
treatmentMd += `*Generated by FrameForge v2.7.7 — lemo-opuscar DIRECTOR.md format*\n`;
|
|
2165
2169
|
}
|
|
2166
2170
|
|
|
2167
2171
|
// 9. Write outputs
|
|
@@ -2237,12 +2241,12 @@ Professional audio rules applied:
|
|
|
2237
2241
|
// ── Tool 28: Check Upstream Updates (fast, offline) ───────────────────────
|
|
2238
2242
|
// Reads last sync manifest without network requests — returns update status instantly
|
|
2239
2243
|
check_upstream_updates: {
|
|
2240
|
-
description: `Check the last sync state of all upstream reference repositories (HyperFrames, OneTake, LemoOpuscar, CollageBroll, Remotion, Earshot, ClaudeAnimationBase, ClaudeRemotionSkill, FFmpegSkill). Fast, offline — reads the local manifest only (no network requests). Shows what was last synced, when, which repos had updates, and a human-readable changelog of what improved. To actually fetch new commits and update the manifest, use sync_upstream_skills. Agents and users can call this anytime to see if upstream skills have been updated since the last sync.`,
|
|
2244
|
+
description: `Check the last sync state of all upstream reference repositories (HyperFrames, OneTake, LemoOpuscar, CollageBroll, Remotion, Earshot, ClaudeAnimationBase, ClaudeRemotionSkill, FFmpegSkill, AiinsightShorts). Fast, offline — reads the local manifest only (no network requests). Shows what was last synced, when, which repos had updates, and a human-readable changelog of what improved. To actually fetch new commits and update the manifest, use sync_upstream_skills. Agents and users can call this anytime to see if upstream skills have been updated since the last sync.`,
|
|
2241
2245
|
inputSchema: { type: 'object', properties: {} },
|
|
2242
2246
|
handler: async () => {
|
|
2243
2247
|
const syncFs = require('fs');
|
|
2244
2248
|
const { MANIFEST_PATH, REPOSITORIES } = require('../skills/sync-upstream');
|
|
2245
|
-
let manifest = { lastSync: null, repos: {}, version: '2.7.
|
|
2249
|
+
let manifest = { lastSync: null, repos: {}, version: '2.7.7', pendingUpdates: [] };
|
|
2246
2250
|
if (syncFs.existsSync(MANIFEST_PATH)) {
|
|
2247
2251
|
try { manifest = JSON.parse(syncFs.readFileSync(MANIFEST_PATH, 'utf8')); } catch (_) {}
|
|
2248
2252
|
}
|
|
@@ -2289,7 +2293,7 @@ Professional audio rules applied:
|
|
|
2289
2293
|
return {
|
|
2290
2294
|
success: true,
|
|
2291
2295
|
lastSync: manifest.lastSync,
|
|
2292
|
-
manifestVersion: manifest.version || '2.7.
|
|
2296
|
+
manifestVersion: manifest.version || '2.7.7',
|
|
2293
2297
|
totalRepos: repos.length,
|
|
2294
2298
|
syncedCount,
|
|
2295
2299
|
pendingUpdates,
|
|
@@ -2814,13 +2818,725 @@ Outputs broadcast-ready 48kHz/192k AAC stereo.`,
|
|
|
2814
2818
|
},
|
|
2815
2819
|
},
|
|
2816
2820
|
|
|
2821
|
+
// ── Tool 29: List Faceless Short Styles ─────────────────────────────────
|
|
2822
|
+
// Returns the 6 aiinsight-shorts styles with descriptions, palettes, and use-cases.
|
|
2823
|
+
list_short_styles: {
|
|
2824
|
+
description: 'List all available faceless-shorts styles from the aiinsight-shorts library. Returns name, description, look rules, typical length, and when to use each style. Six styles: Deep Dive 3D (Zack D. Films-like body/science explainer), Night File 3D (true crime/mysteries), Inside Out (flat 2D x-ray explainer), Stick Story (stick-figure survival steps), Paper Atlas (history in cut paper), Tall Tale 3D (funny true stories).',
|
|
2825
|
+
inputSchema: { type: 'object', properties: {} },
|
|
2826
|
+
handler: async () => {
|
|
2827
|
+
const syncFs = require('fs');
|
|
2828
|
+
const pathMod = require('path');
|
|
2829
|
+
const SHORTS_DIR = pathMod.resolve(__dirname, '../../plugins/aiinsight-shorts/styles');
|
|
2830
|
+
const STYLES = [
|
|
2831
|
+
{ id: 'deep-dive', label: 'Deep Dive 3D', when: 'body / health / science "what happens if" → flagship 3D', length: '30-45 s', dim: '1080×1920 · 24 fps', voice: 'am_michael', music: 'tension' },
|
|
2832
|
+
{ id: 'night-file', label: 'Night File 3D', when: 'creepy / unexplained / true crime / "this happened in 1971"', length: '30-45 s', dim: '1080×1920 · 24 fps', voice: 'am_onyx', music: 'tension' },
|
|
2833
|
+
{ id: 'inside-out', label: 'Inside Out', when: 'flat 2D "what happens if" explainer — lighter and faster', length: '25-40 s', dim: '1080×1920 · 30 fps', voice: 'am_michael', music: 'bed' },
|
|
2834
|
+
{ id: 'stick-story', label: 'Stick Story', when: 'survival / life hacks / "here\'s what you do"', length: '30-45 s', dim: '1080×1920 · 30 fps', voice: 'am_puck', music: 'bed' },
|
|
2835
|
+
{ id: 'paper-atlas', label: 'Paper Atlas', when: 'historical event / "how X happened"', length: '30-45 s', dim: '1080×1920 · 30 fps', voice: 'bm_george', music: 'tension' },
|
|
2836
|
+
{ id: 'tall-tale', label: 'Tall Tale 3D', when: 'funny / clever true story / con / loophole / "this guy did X"', length: '40-60 s', dim: '1080×1920 · 30 fps', voice: 'am_michael', music: 'bounce' },
|
|
2837
|
+
];
|
|
2838
|
+
|
|
2839
|
+
const result = [];
|
|
2840
|
+
for (const s of STYLES) {
|
|
2841
|
+
const styleMdPath = pathMod.join(SHORTS_DIR, s.id, 'STYLE.md');
|
|
2842
|
+
let rules = '';
|
|
2843
|
+
try { rules = syncFs.readFileSync(styleMdPath, 'utf8'); } catch (_) {}
|
|
2844
|
+
const sheetPath = pathMod.join(SHORTS_DIR, s.id, 'style-sheet.png');
|
|
2845
|
+
result.push({
|
|
2846
|
+
...s,
|
|
2847
|
+
styleGuide: styleMdPath,
|
|
2848
|
+
styleSheetPng: syncFs.existsSync(sheetPath) ? sheetPath : null,
|
|
2849
|
+
exampleDir: pathMod.join(SHORTS_DIR, s.id, 'example'),
|
|
2850
|
+
styleMarkdown: rules,
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
return {
|
|
2855
|
+
styles: result,
|
|
2856
|
+
count: result.length,
|
|
2857
|
+
skillDir: pathMod.resolve(__dirname, '../../plugins/aiinsight-shorts'),
|
|
2858
|
+
newVideoScript: pathMod.resolve(__dirname, '../../plugins/aiinsight-shorts/scripts/new_video.sh'),
|
|
2859
|
+
voiceScript: pathMod.resolve(__dirname, '../../plugins/aiinsight-shorts/scripts/voice.py'),
|
|
2860
|
+
wordsScript: pathMod.resolve(__dirname, '../../plugins/aiinsight-shorts/scripts/words.py'),
|
|
2861
|
+
estimateScript: pathMod.resolve(__dirname, '../../plugins/aiinsight-shorts/scripts/estimate.py'),
|
|
2862
|
+
message: `${result.length} faceless-short styles available. Call create_short to scaffold a new project from any style.`,
|
|
2863
|
+
};
|
|
2864
|
+
},
|
|
2865
|
+
},
|
|
2866
|
+
|
|
2867
|
+
// ── Tool 30: Create Faceless Short Project ──────────────────────────────
|
|
2868
|
+
// Scaffolds a new short video project from one of the 6 aiinsight-shorts styles.
|
|
2869
|
+
create_short: {
|
|
2870
|
+
description: `Scaffold a new faceless YouTube Short / TikTok / Reel project from the aiinsight-shorts library.
|
|
2871
|
+
Copies the chosen style's full working example (engine, shots, fonts, tools) into a project folder, removes the example narration audio so it renders fresh, and prints the next steps.
|
|
2872
|
+
|
|
2873
|
+
Six styles: deep-dive (3D body/science "what happens if"), night-file (3D true crime/mystery), inside-out (flat 2D x-ray explainer), stick-story (stick-figure survival), paper-atlas (history in cut paper), tall-tale (3D funny true story).
|
|
2874
|
+
|
|
2875
|
+
After scaffolding: edit script.json (narration lines + caption chunks), run voice.py → words.py to generate TTS + word timing, rewrite src/shots.js for your content, then node tools/capture.mjs video to render the MP4.`,
|
|
2876
|
+
inputSchema: {
|
|
2877
|
+
type: 'object',
|
|
2878
|
+
required: ['style', 'shortName'],
|
|
2879
|
+
properties: {
|
|
2880
|
+
style: { type: 'string', enum: ['deep-dive', 'night-file', 'inside-out', 'stick-story', 'paper-atlas', 'tall-tale'], description: 'Style to use' },
|
|
2881
|
+
shortName: { type: 'string', description: 'URL-safe project name, e.g. "what-if-you-swallow-gum"' },
|
|
2882
|
+
outputDir: { type: 'string', description: 'Parent directory for the project. Defaults to ~/faceless-shorts/<shortName>/' },
|
|
2883
|
+
},
|
|
2884
|
+
},
|
|
2885
|
+
handler: async ({ style, shortName, outputDir }) => {
|
|
2886
|
+
const syncFs = require('fs');
|
|
2887
|
+
const pathMod = require('path');
|
|
2888
|
+
const { spawnSync } = require('child_process');
|
|
2889
|
+
|
|
2890
|
+
const VALID_STYLES = ['deep-dive', 'night-file', 'inside-out', 'stick-story', 'paper-atlas', 'tall-tale'];
|
|
2891
|
+
if (!VALID_STYLES.includes(style)) {
|
|
2892
|
+
throw new Error(`Unknown style "${style}". Choose one of: ${VALID_STYLES.join(', ')}`);
|
|
2893
|
+
}
|
|
2894
|
+
if (!shortName || !/^[a-z0-9-_]+$/i.test(shortName)) {
|
|
2895
|
+
throw new Error('shortName must be URL-safe (letters, digits, hyphens, underscores only)');
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
const skillDir = pathMod.resolve(__dirname, '../../plugins/aiinsight-shorts');
|
|
2899
|
+
const srcExample = pathMod.join(skillDir, 'styles', style, 'example');
|
|
2900
|
+
if (!syncFs.existsSync(srcExample)) {
|
|
2901
|
+
throw new Error(`Style example not found at: ${srcExample}`);
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
const parent = outputDir
|
|
2905
|
+
? pathMod.resolve(outputDir)
|
|
2906
|
+
: pathMod.join(require('os').homedir(), 'faceless-shorts');
|
|
2907
|
+
const dst = pathMod.join(parent, shortName);
|
|
2908
|
+
|
|
2909
|
+
if (syncFs.existsSync(dst)) {
|
|
2910
|
+
throw new Error(`Project already exists: ${dst}. Choose a different shortName or remove it first.`);
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
// Copy example recursively
|
|
2914
|
+
const copyDir = (src, dest) => {
|
|
2915
|
+
syncFs.mkdirSync(dest, { recursive: true });
|
|
2916
|
+
for (const entry of syncFs.readdirSync(src, { withFileTypes: true })) {
|
|
2917
|
+
const s = pathMod.join(src, entry.name);
|
|
2918
|
+
const d = pathMod.join(dest, entry.name);
|
|
2919
|
+
if (entry.isDirectory()) { copyDir(s, d); }
|
|
2920
|
+
else { syncFs.copyFileSync(s, d); }
|
|
2921
|
+
}
|
|
2922
|
+
};
|
|
2923
|
+
copyDir(srcExample, dst);
|
|
2924
|
+
|
|
2925
|
+
// Create renders/ and review/ dirs (new_video.sh does this too)
|
|
2926
|
+
syncFs.mkdirSync(pathMod.join(dst, 'renders'), { recursive: true });
|
|
2927
|
+
syncFs.mkdirSync(pathMod.join(dst, 'review'), { recursive: true });
|
|
2928
|
+
|
|
2929
|
+
// Remove example audio so it renders fresh
|
|
2930
|
+
for (const f of ['data/narration.wav', 'data/mix.wav']) {
|
|
2931
|
+
const p = pathMod.join(dst, f);
|
|
2932
|
+
try { syncFs.unlinkSync(p); } catch (_) {}
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
const styleMdPath = pathMod.join(skillDir, 'styles', style, 'STYLE.md');
|
|
2936
|
+
const styleMd = syncFs.existsSync(styleMdPath) ? syncFs.readFileSync(styleMdPath, 'utf8') : '';
|
|
2937
|
+
|
|
2938
|
+
return {
|
|
2939
|
+
success: true,
|
|
2940
|
+
style,
|
|
2941
|
+
shortName,
|
|
2942
|
+
projectDir: dst,
|
|
2943
|
+
scriptJson: pathMod.join(dst, 'script.json'),
|
|
2944
|
+
shotsJs: pathMod.join(dst, 'src', 'shots.js'),
|
|
2945
|
+
captureCmd: `node ${pathMod.join(dst, 'tools', 'capture.mjs')} check`,
|
|
2946
|
+
voiceCmd: `python ${pathMod.join(skillDir, 'scripts', 'voice.py')} ${dst}`,
|
|
2947
|
+
wordsCmd: `python ${pathMod.join(skillDir, 'scripts', 'words.py')} ${dst}`,
|
|
2948
|
+
renderCmd: `node ${pathMod.join(dst, 'tools', 'capture.mjs')} video ${pathMod.join(dst, 'renders', shortName + '.mp4')}`,
|
|
2949
|
+
styleGuide: styleMd,
|
|
2950
|
+
message: [
|
|
2951
|
+
`✅ Project scaffolded: ${dst}`,
|
|
2952
|
+
``,
|
|
2953
|
+
`Next steps:`,
|
|
2954
|
+
`1. Edit ${pathMod.join(dst, 'script.json')} — write your narration lines and caption chunks`,
|
|
2955
|
+
`2. Run: python ${pathMod.join(skillDir, 'scripts', 'voice.py')} "${dst}"`,
|
|
2956
|
+
`3. Run: python ${pathMod.join(skillDir, 'scripts', 'words.py')} "${dst}"`,
|
|
2957
|
+
`4. Rewrite ${pathMod.join(dst, 'src', 'shots.js')} for your content (follow STYLE.md)`,
|
|
2958
|
+
`5. Run: node ${pathMod.join(dst, 'tools', 'capture.mjs')} check (must say CHECK OK)`,
|
|
2959
|
+
`6. Run: node ${pathMod.join(dst, 'tools', 'capture.mjs')} cues`,
|
|
2960
|
+
`7. Run: python ${pathMod.join(dst, 'tools', 'mix.py')} "${dst}"`,
|
|
2961
|
+
`8. Run: node ${pathMod.join(dst, 'tools', 'capture.mjs')} video ${pathMod.join(dst, 'renders', shortName + '.mp4')}`,
|
|
2962
|
+
``,
|
|
2963
|
+
`Style guide is included in this response (styleGuide field). Read it fully before writing shots.js.`,
|
|
2964
|
+
].join('\n'),
|
|
2965
|
+
};
|
|
2966
|
+
},
|
|
2967
|
+
},
|
|
2968
|
+
|
|
2969
|
+
// ── Tool 31: Detect Viral Clips ──────────────────────────────────────────
|
|
2970
|
+
// Transcribes a long video, scores transcript windows with an LLM, and
|
|
2971
|
+
// returns ranked clip timestamps ready to pass to edit_video.
|
|
2972
|
+
detect_viral_clips: {
|
|
2973
|
+
description: `Analyze a long video (podcast, webinar, stream, interview) and detect the most viral-worthy moments. Returns ranked clip candidates with start/end timestamps, titles, and hook suggestions — ready to feed into edit_video or reframe.
|
|
2974
|
+
|
|
2975
|
+
How it works:
|
|
2976
|
+
1. Transcribes the video with Whisper (local, free — same engine as transcribe_media).
|
|
2977
|
+
2. Splits the transcript into overlapping windows (default 45s, 10s overlap).
|
|
2978
|
+
3. Scores each window with an LLM (Gemini Flash via GEMINI_API_KEY, or any OpenAI-compatible endpoint via LLM_BASE_URL — including Ollama, LM Studio, vLLM).
|
|
2979
|
+
4. Returns the top N clip candidates sorted by viral score.
|
|
2980
|
+
|
|
2981
|
+
No captions are added or burned into anything. This tool only detects moments and returns timestamps — use edit_video or karaoke_subtitles afterwards if you want subtitles on the clips.`,
|
|
2982
|
+
inputSchema: {
|
|
2983
|
+
type: 'object',
|
|
2984
|
+
required: ['videoPath'],
|
|
2985
|
+
properties: {
|
|
2986
|
+
videoPath: { type: 'string', description: 'Path to the local video file to analyze (MP4, MOV, MKV, etc.)' },
|
|
2987
|
+
targetClips: { type: 'number', default: 5, description: 'Target number of clips to return (1-15, default 5). Actual count may be lower if the video lacks enough strong moments.' },
|
|
2988
|
+
clipMinSeconds: { type: 'number', default: 15, description: 'Minimum clip length in seconds (default 15)' },
|
|
2989
|
+
clipMaxSeconds: { type: 'number', default: 60, description: 'Maximum clip length in seconds (default 60)' },
|
|
2990
|
+
windowSeconds: { type: 'number', default: 45, description: 'Transcript window size for scoring (default 45s)' },
|
|
2991
|
+
language: { type: 'string', description: 'BCP-47 language hint for Whisper transcription (e.g. "en", "es"). Auto-detected if omitted.' },
|
|
2992
|
+
whisperModel: { type: 'string', default: 'base', enum: ['tiny','base','small','medium'], description: 'Whisper model size (base = fast, small/medium = more accurate)' },
|
|
2993
|
+
llmBaseUrl: { type: 'string', description: 'OpenAI-compatible LLM endpoint (e.g. http://localhost:11434/v1 for Ollama). Overrides GEMINI_API_KEY when set.' },
|
|
2994
|
+
llmModel: { type: 'string', description: 'LLM model name for local endpoint (e.g. "llama3.1", "qwen2.5:7b"). Ignored when using Gemini.' },
|
|
2995
|
+
geminiApiKey: { type: 'string', description: 'Google Gemini API key. Falls back to GEMINI_API_KEY env var.' },
|
|
2996
|
+
},
|
|
2997
|
+
},
|
|
2998
|
+
handler: async ({
|
|
2999
|
+
videoPath, targetClips = 5, clipMinSeconds = 15, clipMaxSeconds = 60,
|
|
3000
|
+
windowSeconds = 45, language, whisperModel = 'base',
|
|
3001
|
+
llmBaseUrl, llmModel, geminiApiKey,
|
|
3002
|
+
}) => {
|
|
3003
|
+
const syncFs = require('fs');
|
|
3004
|
+
const pathMod = require('path');
|
|
3005
|
+
const https = require('https');
|
|
3006
|
+
const http = require('http');
|
|
3007
|
+
|
|
3008
|
+
if (!videoPath || !syncFs.existsSync(videoPath)) {
|
|
3009
|
+
throw new Error(`videoPath does not exist: ${videoPath}`);
|
|
3010
|
+
}
|
|
3011
|
+
const clampedTarget = Math.max(1, Math.min(15, Math.round(targetClips)));
|
|
3012
|
+
|
|
3013
|
+
// ── Step 1: Transcribe ────────────────────────────────────────────────
|
|
3014
|
+
const { transcribeAudio, detectWhisper } = require('../utils/transcribe');
|
|
3015
|
+
const engine = detectWhisper();
|
|
3016
|
+
if (!engine.available) {
|
|
3017
|
+
throw new Error(
|
|
3018
|
+
'Whisper is not installed. Install it with: pip install openai-whisper (or: pip install faster-whisper)'
|
|
3019
|
+
);
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
const transcriptResult = await transcribeAudio(videoPath, {
|
|
3023
|
+
model: whisperModel,
|
|
3024
|
+
language,
|
|
3025
|
+
format: 'json',
|
|
3026
|
+
});
|
|
3027
|
+
if (!transcriptResult || !Array.isArray(transcriptResult.words) || transcriptResult.words.length === 0) {
|
|
3028
|
+
throw new Error('Transcription returned no words. Check that the video has speech.');
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
const totalDuration = transcriptResult.duration || 0;
|
|
3032
|
+
const words = transcriptResult.words; // [{ word, start, end }]
|
|
3033
|
+
|
|
3034
|
+
// ── Step 2: Build overlapping windows ────────────────────────────────
|
|
3035
|
+
const overlap = Math.round(windowSeconds * 0.22); // ~22% overlap
|
|
3036
|
+
const windows = [];
|
|
3037
|
+
let winStart = 0;
|
|
3038
|
+
while (winStart < totalDuration - clipMinSeconds) {
|
|
3039
|
+
const winEnd = Math.min(winStart + windowSeconds, totalDuration);
|
|
3040
|
+
const winWords = words.filter(w => w.start >= winStart && w.end <= winEnd + 1);
|
|
3041
|
+
if (winWords.length >= 5) {
|
|
3042
|
+
windows.push({
|
|
3043
|
+
index: windows.length,
|
|
3044
|
+
start: winStart,
|
|
3045
|
+
end: winEnd,
|
|
3046
|
+
text: winWords.map(w => w.word).join(' ').trim(),
|
|
3047
|
+
});
|
|
3048
|
+
}
|
|
3049
|
+
winStart += windowSeconds - overlap;
|
|
3050
|
+
}
|
|
3051
|
+
if (windows.length === 0) {
|
|
3052
|
+
return {
|
|
3053
|
+
clips: [],
|
|
3054
|
+
totalDuration,
|
|
3055
|
+
message: 'No scoreable windows found — video may be too short or silent.',
|
|
3056
|
+
};
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
// ── Step 3: Score windows with LLM ───────────────────────────────────
|
|
3060
|
+
const resolvedGeminiKey = geminiApiKey || process.env.GEMINI_API_KEY;
|
|
3061
|
+
const resolvedLlmUrl = llmBaseUrl || process.env.LLM_BASE_URL;
|
|
3062
|
+
|
|
3063
|
+
// Score in batches of 3 (safe for small context local LLMs) or 8 (Gemini)
|
|
3064
|
+
const batchSize = resolvedLlmUrl ? 3 : 8;
|
|
3065
|
+
const scoredWindows = [];
|
|
3066
|
+
|
|
3067
|
+
const scorePrompt = (batch) => {
|
|
3068
|
+
const items = batch.map((w, i) =>
|
|
3069
|
+
`[${i}] ${w.start.toFixed(1)}s-${w.end.toFixed(1)}s:\n"${w.text.slice(0, 400)}"`
|
|
3070
|
+
).join('\n\n');
|
|
3071
|
+
return `You are a viral short-form video editor. Score each transcript window 1-10 for its potential as a standalone viral clip (hook strength, emotion, surprise, clarity). For the best windows (score >= 7), suggest a punchy 5-8 word title and a one-sentence hook.
|
|
3072
|
+
|
|
3073
|
+
Respond ONLY with a JSON array, one object per window, in the same order:
|
|
3074
|
+
[{"index":0,"score":7,"title":"Optional title","hook":"Optional hook sentence"},...]
|
|
3075
|
+
|
|
3076
|
+
Transcript windows:
|
|
3077
|
+
${items}`;
|
|
3078
|
+
};
|
|
3079
|
+
|
|
3080
|
+
const callLlm = (prompt) => new Promise((resolve, reject) => {
|
|
3081
|
+
const body = JSON.stringify(
|
|
3082
|
+
resolvedLlmUrl
|
|
3083
|
+
? { model: llmModel || 'llama3.1', messages: [{ role: 'user', content: prompt }], response_format: { type: 'json_object' } }
|
|
3084
|
+
: { contents: [{ parts: [{ text: prompt }] }], generationConfig: { responseMimeType: 'application/json' } }
|
|
3085
|
+
);
|
|
3086
|
+
const url = resolvedLlmUrl
|
|
3087
|
+
? new URL('/v1/chat/completions', resolvedLlmUrl)
|
|
3088
|
+
: new URL(`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=${resolvedGeminiKey}`);
|
|
3089
|
+
const isHttps = url.protocol === 'https:';
|
|
3090
|
+
const lib = isHttps ? https : http;
|
|
3091
|
+
const options = {
|
|
3092
|
+
hostname: url.hostname,
|
|
3093
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
3094
|
+
path: url.pathname + url.search,
|
|
3095
|
+
method: 'POST',
|
|
3096
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
3097
|
+
};
|
|
3098
|
+
const req = lib.request(options, (res) => {
|
|
3099
|
+
let data = '';
|
|
3100
|
+
res.on('data', d => { data += d; });
|
|
3101
|
+
res.on('end', () => {
|
|
3102
|
+
try {
|
|
3103
|
+
const parsed = JSON.parse(data);
|
|
3104
|
+
// Extract text from either Gemini or OpenAI-compat response
|
|
3105
|
+
const text = parsed?.candidates?.[0]?.content?.parts?.[0]?.text
|
|
3106
|
+
|| parsed?.choices?.[0]?.message?.content
|
|
3107
|
+
|| '[]';
|
|
3108
|
+
// Extract JSON array from the text
|
|
3109
|
+
const match = text.match(/\[[\s\S]*\]/);
|
|
3110
|
+
resolve(match ? JSON.parse(match[0]) : []);
|
|
3111
|
+
} catch (e) { resolve([]); }
|
|
3112
|
+
});
|
|
3113
|
+
});
|
|
3114
|
+
req.on('error', reject);
|
|
3115
|
+
req.setTimeout(30000, () => { req.destroy(); reject(new Error('LLM request timed out')); });
|
|
3116
|
+
req.write(body);
|
|
3117
|
+
req.end();
|
|
3118
|
+
});
|
|
3119
|
+
|
|
3120
|
+
for (let i = 0; i < windows.length; i += batchSize) {
|
|
3121
|
+
const batch = windows.slice(i, i + batchSize);
|
|
3122
|
+
try {
|
|
3123
|
+
const scores = await callLlm(scorePrompt(batch));
|
|
3124
|
+
for (const s of scores) {
|
|
3125
|
+
const w = batch[s.index ?? 0];
|
|
3126
|
+
if (w) {
|
|
3127
|
+
scoredWindows.push({
|
|
3128
|
+
...w,
|
|
3129
|
+
score: Number(s.score) || 5,
|
|
3130
|
+
title: s.title || null,
|
|
3131
|
+
hook: s.hook || null,
|
|
3132
|
+
});
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
} catch (_) {
|
|
3136
|
+
// If LLM fails, push with neutral score so the tool still returns something
|
|
3137
|
+
for (const w of batch) scoredWindows.push({ ...w, score: 5, title: null, hook: null });
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
// ── Step 4: Select top N, de-overlap, clamp to clip length ───────────
|
|
3142
|
+
scoredWindows.sort((a, b) => b.score - a.score);
|
|
3143
|
+
const selected = [];
|
|
3144
|
+
for (const w of scoredWindows) {
|
|
3145
|
+
if (selected.length >= clampedTarget) break;
|
|
3146
|
+
// De-overlap: skip if too close to an already-selected clip
|
|
3147
|
+
const tooClose = selected.some(s =>
|
|
3148
|
+
Math.abs(s.start - w.start) < clipMinSeconds * 0.75
|
|
3149
|
+
);
|
|
3150
|
+
if (tooClose) continue;
|
|
3151
|
+
// Clamp to requested length constraints
|
|
3152
|
+
const rawLen = w.end - w.start;
|
|
3153
|
+
const clamped = Math.min(Math.max(rawLen, clipMinSeconds), clipMaxSeconds);
|
|
3154
|
+
selected.push({
|
|
3155
|
+
rank: selected.length + 1,
|
|
3156
|
+
score: w.score,
|
|
3157
|
+
start: parseFloat(w.start.toFixed(2)),
|
|
3158
|
+
end: parseFloat(Math.min(w.start + clamped, totalDuration).toFixed(2)),
|
|
3159
|
+
duration: parseFloat(clamped.toFixed(2)),
|
|
3160
|
+
title: w.title || `Clip ${selected.length + 1}`,
|
|
3161
|
+
hook: w.hook || null,
|
|
3162
|
+
transcript_preview: w.text.slice(0, 120) + (w.text.length > 120 ? '…' : ''),
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
// Sort final output by start time (chronological)
|
|
3167
|
+
selected.sort((a, b) => a.start - b.start);
|
|
3168
|
+
selected.forEach((c, i) => { c.rank = i + 1; });
|
|
3169
|
+
|
|
3170
|
+
return {
|
|
3171
|
+
clips: selected,
|
|
3172
|
+
clipCount: selected.length,
|
|
3173
|
+
totalDuration: parseFloat(totalDuration.toFixed(2)),
|
|
3174
|
+
windowsScored: scoredWindows.length,
|
|
3175
|
+
transcriptWords: words.length,
|
|
3176
|
+
llmBackend: resolvedLlmUrl ? 'local' : (resolvedGeminiKey ? 'gemini' : 'none — scores are neutral 5'),
|
|
3177
|
+
message: selected.length > 0
|
|
3178
|
+
? `Found ${selected.length} viral clip candidate(s) in ${totalDuration.toFixed(0)}s video. Pass each clip's start/end to edit_video (start_time/end_time) to cut them. Add karaoke_subtitles afterwards if you want word-synced captions on the clips.`
|
|
3179
|
+
: 'No high-scoring clips found. Try lowering clipMinSeconds or providing a longer source video.',
|
|
3180
|
+
editVideoHint: selected.map(c =>
|
|
3181
|
+
`{ "video_path": "${videoPath}", "start_time": ${c.start}, "end_time": ${c.end}, "caption": "${c.title}" }`
|
|
3182
|
+
),
|
|
3183
|
+
};
|
|
3184
|
+
},
|
|
3185
|
+
},
|
|
3186
|
+
|
|
3187
|
+
// ── Tool 32: Karaoke Subtitles ───────────────────────────────────────────
|
|
3188
|
+
// Burns word-synced subtitles into a video clip. Strictly opt-in —
|
|
3189
|
+
// never called automatically; only when the user explicitly asks for captions.
|
|
3190
|
+
karaoke_subtitles: {
|
|
3191
|
+
description: `Burn word-level karaoke-style subtitles into a video clip. Strictly opt-in — only call this when the user explicitly asks for subtitles or captions on a clip. Never add captions automatically.
|
|
3192
|
+
|
|
3193
|
+
Uses Whisper to get word-level timestamps, then generates an ASS subtitle file with word-by-word highlighting and burns it into the video with ffmpeg.
|
|
3194
|
+
|
|
3195
|
+
Subtitle styles:
|
|
3196
|
+
classic — white text, black border, bottom-center (TikTok-style)
|
|
3197
|
+
karaoke — word-by-word highlight: active word is accent colour, rest are white
|
|
3198
|
+
bold_pop — each word pops in bold with a slight scale (energetic, Reels-style)
|
|
3199
|
+
minimal — small clean white text, no border (for aesthetic/lo-fi content)
|
|
3200
|
+
|
|
3201
|
+
Output is a new video file with subtitles burned in. The source file is NOT modified.`,
|
|
3202
|
+
inputSchema: {
|
|
3203
|
+
type: 'object',
|
|
3204
|
+
required: ['videoPath', 'outputPath'],
|
|
3205
|
+
properties: {
|
|
3206
|
+
videoPath: { type: 'string', description: 'Path to the source video clip' },
|
|
3207
|
+
outputPath: { type: 'string', description: 'Path for the output video with burned subtitles' },
|
|
3208
|
+
style: { type: 'string', enum: ['classic', 'karaoke', 'bold_pop', 'minimal'], default: 'karaoke', description: 'Subtitle visual style (default: karaoke)' },
|
|
3209
|
+
language: { type: 'string', description: 'BCP-47 language hint for Whisper (e.g. "en", "hi", "ml"). Auto-detected if omitted.' },
|
|
3210
|
+
whisperModel: { type: 'string', default: 'base', enum: ['tiny','base','small','medium'], description: 'Whisper model size' },
|
|
3211
|
+
fontSize: { type: 'number', default: 72, description: 'Font size in pixels (default 72 for 1080p portrait)' },
|
|
3212
|
+
accentColor: { type: 'string', default: '#FFD700', description: 'Highlight colour for the active word in karaoke style (hex, default gold)' },
|
|
3213
|
+
position: { type: 'string', enum: ['bottom', 'center', 'top'], default: 'bottom', description: 'Vertical position of subtitles' },
|
|
3214
|
+
maxWordsPerLine: { type: 'number', default: 4, description: 'Max words per subtitle line (default 4 — short punchy lines like TikTok)' },
|
|
3215
|
+
},
|
|
3216
|
+
},
|
|
3217
|
+
handler: async ({
|
|
3218
|
+
videoPath, outputPath, style = 'karaoke', language, whisperModel = 'base',
|
|
3219
|
+
fontSize = 72, accentColor = '#FFD700', position = 'bottom', maxWordsPerLine = 4,
|
|
3220
|
+
}) => {
|
|
3221
|
+
const syncFs = require('fs');
|
|
3222
|
+
const pathMod = require('path');
|
|
3223
|
+
const os = require('os');
|
|
3224
|
+
const { spawnSync } = require('child_process');
|
|
3225
|
+
|
|
3226
|
+
if (!videoPath || !syncFs.existsSync(videoPath)) {
|
|
3227
|
+
throw new Error(`videoPath does not exist: ${videoPath}`);
|
|
3228
|
+
}
|
|
3229
|
+
syncFs.mkdirSync(pathMod.dirname(pathMod.resolve(outputPath)), { recursive: true });
|
|
3230
|
+
|
|
3231
|
+
// ── Step 1: Transcribe with word-level timestamps ─────────────────────
|
|
3232
|
+
const { transcribeAudio, detectWhisper } = require('../utils/transcribe');
|
|
3233
|
+
const engine = detectWhisper();
|
|
3234
|
+
if (!engine.available) {
|
|
3235
|
+
throw new Error(
|
|
3236
|
+
'Whisper is not installed. Run: pip install openai-whisper (or: pip install faster-whisper)'
|
|
3237
|
+
);
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
const transcriptResult = await transcribeAudio(videoPath, {
|
|
3241
|
+
model: whisperModel,
|
|
3242
|
+
language,
|
|
3243
|
+
format: 'json',
|
|
3244
|
+
});
|
|
3245
|
+
if (!transcriptResult?.words?.length) {
|
|
3246
|
+
throw new Error('Transcription returned no words. Check that the clip has speech.');
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
const words = transcriptResult.words; // [{ word, start, end }]
|
|
3250
|
+
|
|
3251
|
+
// ── Step 2: Group words into short lines ──────────────────────────────
|
|
3252
|
+
// Each ASS "event" is one line; we highlight the active word within it.
|
|
3253
|
+
const lines = [];
|
|
3254
|
+
let chunk = [];
|
|
3255
|
+
for (const w of words) {
|
|
3256
|
+
chunk.push(w);
|
|
3257
|
+
if (chunk.length >= maxWordsPerLine) {
|
|
3258
|
+
lines.push([...chunk]);
|
|
3259
|
+
chunk = [];
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
if (chunk.length) lines.push(chunk);
|
|
3263
|
+
|
|
3264
|
+
// ── Step 3: Build ASS subtitle file ──────────────────────────────────
|
|
3265
|
+
// Convert hex color to ASS BGR format (&H00BBGGRR)
|
|
3266
|
+
const hexToAssBgr = (hex) => {
|
|
3267
|
+
const h = hex.replace('#', '').padEnd(6, '0');
|
|
3268
|
+
const r = h.slice(0, 2); const g = h.slice(2, 4); const b = h.slice(4, 6);
|
|
3269
|
+
return `&H00${b}${g}${r}`.toUpperCase();
|
|
3270
|
+
};
|
|
3271
|
+
const toAssTime = (s) => {
|
|
3272
|
+
const h = Math.floor(s / 3600);
|
|
3273
|
+
const m = Math.floor((s % 3600) / 60);
|
|
3274
|
+
const sec = (s % 60).toFixed(2).padStart(5, '0');
|
|
3275
|
+
return `${h}:${String(m).padStart(2, '0')}:${sec}`;
|
|
3276
|
+
};
|
|
3277
|
+
|
|
3278
|
+
const WHITE = '&H00FFFFFF';
|
|
3279
|
+
const BLACK = '&H00000000';
|
|
3280
|
+
const ACCENT = hexToAssBgr(accentColor);
|
|
3281
|
+
const SHADOW = style === 'minimal' ? 0 : 1;
|
|
3282
|
+
const BORDER = style === 'minimal' ? 0 : 3;
|
|
3283
|
+
const BOLD = style === 'bold_pop' ? -1 : 0;
|
|
3284
|
+
const ALIGN = position === 'top' ? 8 : position === 'center' ? 5 : 2;
|
|
3285
|
+
const marginV = position === 'top' ? 60 : position === 'center' ? 0 : 80;
|
|
3286
|
+
|
|
3287
|
+
const assHeader = `[Script Info]
|
|
3288
|
+
ScriptType: v4.00+
|
|
3289
|
+
PlayResX: 1080
|
|
3290
|
+
PlayResY: 1920
|
|
3291
|
+
WrapStyle: 1
|
|
3292
|
+
|
|
3293
|
+
[V4+ Styles]
|
|
3294
|
+
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
|
3295
|
+
Style: Default,Arial,${fontSize},${WHITE},${ACCENT},${BLACK},&H80000000,${BOLD},0,0,0,100,100,0,0,1,${BORDER},${SHADOW},${ALIGN},60,60,${marginV},1
|
|
3296
|
+
|
|
3297
|
+
[Events]
|
|
3298
|
+
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|
3299
|
+
`;
|
|
3300
|
+
|
|
3301
|
+
let assEvents = '';
|
|
3302
|
+
for (const line of lines) {
|
|
3303
|
+
const lineStart = line[0].start;
|
|
3304
|
+
const lineEnd = line[line.length - 1].end;
|
|
3305
|
+
|
|
3306
|
+
if (style === 'karaoke' || style === 'bold_pop') {
|
|
3307
|
+
// One event per word showing the whole line, with the active word highlighted
|
|
3308
|
+
for (let wi = 0; wi < line.length; wi++) {
|
|
3309
|
+
const w = line[wi];
|
|
3310
|
+
const text = line.map((ww, i) => {
|
|
3311
|
+
const clean = ww.word.replace(/[{}\\]/g, '').trim();
|
|
3312
|
+
if (i === wi) {
|
|
3313
|
+
return style === 'bold_pop'
|
|
3314
|
+
? `{\\c${ACCENT}\\b-1\\fscx115\\fscy115}${clean}{\\b0\\fscx100\\fscy100\\c${WHITE}}`
|
|
3315
|
+
: `{\\c${ACCENT}}${clean}{\\c${WHITE}}`;
|
|
3316
|
+
}
|
|
3317
|
+
return clean;
|
|
3318
|
+
}).join(' ');
|
|
3319
|
+
assEvents += `Dialogue: 0,${toAssTime(w.start)},${toAssTime(w.end)},Default,,0,0,0,,${text}\n`;
|
|
3320
|
+
}
|
|
3321
|
+
} else {
|
|
3322
|
+
// classic / minimal: one event per line, no highlight
|
|
3323
|
+
const text = line.map(w => w.word.replace(/[{}\\]/g, '').trim()).join(' ');
|
|
3324
|
+
assEvents += `Dialogue: 0,${toAssTime(lineStart)},${toAssTime(lineEnd)},Default,,0,0,0,,${text}\n`;
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
const assContent = assHeader + assEvents;
|
|
3329
|
+
const tmpDir = require('os').tmpdir();
|
|
3330
|
+
const assPath = pathMod.join(tmpDir, `ff_karaoke_${Date.now()}.ass`);
|
|
3331
|
+
syncFs.writeFileSync(assPath, assContent, 'utf8');
|
|
3332
|
+
|
|
3333
|
+
// ── Step 4: Burn subtitles with ffmpeg ────────────────────────────────
|
|
3334
|
+
// Escape path for ffmpeg ass filter (Windows backslashes + colon)
|
|
3335
|
+
const escapedAss = assPath.replace(/\\/g, '/').replace(/:/g, '\\:');
|
|
3336
|
+
|
|
3337
|
+
const result = spawnSync('ffmpeg', [
|
|
3338
|
+
'-nostdin', '-y',
|
|
3339
|
+
'-i', videoPath,
|
|
3340
|
+
'-vf', `ass='${escapedAss}'`,
|
|
3341
|
+
'-c:v', 'libx264', '-preset', 'fast', '-crf', '18',
|
|
3342
|
+
'-profile:v', 'high', '-pix_fmt', 'yuv420p',
|
|
3343
|
+
'-c:a', 'copy',
|
|
3344
|
+
'-movflags', '+faststart',
|
|
3345
|
+
outputPath,
|
|
3346
|
+
], { timeout: 5 * 60 * 1000, encoding: 'utf8' });
|
|
3347
|
+
|
|
3348
|
+
// Cleanup temp ASS file
|
|
3349
|
+
try { syncFs.unlinkSync(assPath); } catch (_) {}
|
|
3350
|
+
|
|
3351
|
+
if (result.status !== 0) {
|
|
3352
|
+
throw new Error(`ffmpeg subtitle burn failed: ${(result.stderr || '').slice(-400)}`);
|
|
3353
|
+
}
|
|
3354
|
+
|
|
3355
|
+
const stat = syncFs.statSync(outputPath);
|
|
3356
|
+
return {
|
|
3357
|
+
outputPath,
|
|
3358
|
+
style,
|
|
3359
|
+
linesCount: lines.length,
|
|
3360
|
+
wordsCount: words.length,
|
|
3361
|
+
duration: parseFloat((transcriptResult.duration || 0).toFixed(2)),
|
|
3362
|
+
sizeBytes: stat.size,
|
|
3363
|
+
message: `Subtitles burned into ${pathMod.basename(outputPath)} (${lines.length} lines, ${words.length} words, style: ${style}). Source file was NOT modified.`,
|
|
3364
|
+
};
|
|
3365
|
+
},
|
|
3366
|
+
},
|
|
3367
|
+
|
|
3368
|
+
// ── Tool 33: List Motion Graphics Styles ────────────────────────────────────────
|
|
3369
|
+
// Returns all 62 styles from FrameForge Motion Library, optionally filtered by family.
|
|
3370
|
+
list_motion_styles: {
|
|
3371
|
+
description: `List all 62 motion-graphics styles from the Motion Graphics Style Library (FrameForge Motion Library/\#library). Each style is a full canvas animation with sound, drawn in code by Claude — zero AI images, zero API keys.
|
|
3372
|
+
|
|
3373
|
+
Families: Kinetic Type (3) · Hand-Drawn (3) · Screen and UI (1) · Data (6) · Talking Head (1) · Illustrated (7) · 3D (14) · Retro (2) · Shorts Format (10) · Edit Overlay (10) · Character Story (5).
|
|
3374
|
+
|
|
3375
|
+
Use list_motion_styles to discover which style fits the content, then call get_motion_style_prompt to get the full Claude prompt for that style.`,
|
|
3376
|
+
inputSchema: {
|
|
3377
|
+
type: 'object',
|
|
3378
|
+
properties: {
|
|
3379
|
+
family: {
|
|
3380
|
+
type: 'string',
|
|
3381
|
+
description: 'Filter by family (case-insensitive). One of: Kinetic Type, Hand-Drawn, Screen and UI, Data, Talking Head, Illustrated, 3D, Retro, Shorts Format, Edit Overlay, Character Story. Omit to return all 62.',
|
|
3382
|
+
},
|
|
3383
|
+
search: {
|
|
3384
|
+
type: 'string',
|
|
3385
|
+
description: 'Free-text search in style name or look description (case-insensitive).',
|
|
3386
|
+
},
|
|
3387
|
+
},
|
|
3388
|
+
},
|
|
3389
|
+
handler: ({ family, search } = {}) => {
|
|
3390
|
+
const syncFs = require('fs');
|
|
3391
|
+
const pathMod = require('path');
|
|
3392
|
+
const catalogPath = pathMod.resolve(__dirname, '../../plugins/motion-styles/catalog.json');
|
|
3393
|
+
if (!syncFs.existsSync(catalogPath)) {
|
|
3394
|
+
throw new Error(`Motion Graphics catalog not found at ${catalogPath}. Re-clone the FrameForge repo.`);
|
|
3395
|
+
}
|
|
3396
|
+
let styles = JSON.parse(syncFs.readFileSync(catalogPath, 'utf8'));
|
|
3397
|
+
if (family) {
|
|
3398
|
+
const fam = family.trim().toLowerCase();
|
|
3399
|
+
styles = styles.filter(s => s.family.toLowerCase().includes(fam));
|
|
3400
|
+
}
|
|
3401
|
+
if (search) {
|
|
3402
|
+
const q = search.trim().toLowerCase();
|
|
3403
|
+
styles = styles.filter(s =>
|
|
3404
|
+
s.name.toLowerCase().includes(q) ||
|
|
3405
|
+
(s.look || '').toLowerCase().includes(q) ||
|
|
3406
|
+
(s.how || '').toLowerCase().includes(q)
|
|
3407
|
+
);
|
|
3408
|
+
}
|
|
3409
|
+
return {
|
|
3410
|
+
styles: styles.map(s => ({
|
|
3411
|
+
id: s.id,
|
|
3412
|
+
num: s.num,
|
|
3413
|
+
name: s.name,
|
|
3414
|
+
family: s.family,
|
|
3415
|
+
look: s.look,
|
|
3416
|
+
canvas: s.canvas || '1280x720',
|
|
3417
|
+
three: Boolean(s.three),
|
|
3418
|
+
footage: Boolean(s.footage),
|
|
3419
|
+
demo: s.demo || null,
|
|
3420
|
+
fonts: s.fonts || null,
|
|
3421
|
+
promptFile: s.promptFile || `prompts/${s.id}.md`,
|
|
3422
|
+
sourceFile: s.sourceFile || `sources/${s.id}.html`,
|
|
3423
|
+
})),
|
|
3424
|
+
count: styles.length,
|
|
3425
|
+
totalInLibrary: 62,
|
|
3426
|
+
catalogPath,
|
|
3427
|
+
message: `${styles.length} style(s) matched. Call get_motion_style_prompt with a style id to get the full Claude prompt.`,
|
|
3428
|
+
};
|
|
3429
|
+
},
|
|
3430
|
+
},
|
|
3431
|
+
|
|
3432
|
+
// ── Tool 34: Get Motion Style Prompt ──────────────────────────────────────
|
|
3433
|
+
// Returns the complete Claude prompt for one Motion graphics style — paste it into
|
|
3434
|
+
// a new Claude chat to get that exact look for your own content.
|
|
3435
|
+
get_motion_style_prompt: {
|
|
3436
|
+
description: `Get the complete Claude prompt for a Motion graphics style. Paste it into a new Claude chat to get that exact motion-graphics look built around your own content or timing.
|
|
3437
|
+
|
|
3438
|
+
The prompt includes: the full look description, how each beat works, font specs, what to replace (your data), what to keep (the kit helpers), size rules, voiceover timing rules, sheet (visual) rules, and sound design rules. Optionally includes full reference source HTML code.
|
|
3439
|
+
|
|
3440
|
+
Get a style id from list_motion_styles first. Source: FrameForge Motion Library — prompts are free to use for your own videos.`,
|
|
3441
|
+
inputSchema: {
|
|
3442
|
+
type: 'object',
|
|
3443
|
+
required: ['styleId'],
|
|
3444
|
+
properties: {
|
|
3445
|
+
styleId: {
|
|
3446
|
+
type: 'string',
|
|
3447
|
+
description: 'Style id from list_motion_styles (e.g. "creator-motion", "chalk-talk", "clay-diorama"). Use the exact id field, not the name.',
|
|
3448
|
+
},
|
|
3449
|
+
topic: {
|
|
3450
|
+
type: 'string',
|
|
3451
|
+
description: 'Optional: your topic or voiceover subject. If provided, it is injected into the prompt as context.',
|
|
3452
|
+
},
|
|
3453
|
+
mode: {
|
|
3454
|
+
type: 'string',
|
|
3455
|
+
enum: ['standard', 'vo'],
|
|
3456
|
+
description: 'Optional: "standard" for direct build or "vo" for word-level voiceover timing and narration mix.',
|
|
3457
|
+
},
|
|
3458
|
+
storyboard: {
|
|
3459
|
+
type: 'boolean',
|
|
3460
|
+
description: 'Optional: if true, prompt requests a 6-10 panel storyboard first before building full animation.',
|
|
3461
|
+
},
|
|
3462
|
+
includeSource: {
|
|
3463
|
+
type: 'boolean',
|
|
3464
|
+
description: 'Optional: if true, embeds the complete standalone reference source HTML file in the prompt.',
|
|
3465
|
+
},
|
|
3466
|
+
},
|
|
3467
|
+
},
|
|
3468
|
+
handler: ({ styleId, topic, mode = 'standard', storyboard = false, includeSource = false } = {}) => {
|
|
3469
|
+
const syncFs = require('fs');
|
|
3470
|
+
const pathMod = require('path');
|
|
3471
|
+
const catalogPath = pathMod.resolve(__dirname, '../../plugins/motion-styles/catalog.json');
|
|
3472
|
+
if (!syncFs.existsSync(catalogPath)) {
|
|
3473
|
+
throw new Error(`Motion Graphics catalog not found at ${catalogPath}. Re-clone the FrameForge repo.`);
|
|
3474
|
+
}
|
|
3475
|
+
const styles = JSON.parse(syncFs.readFileSync(catalogPath, 'utf8'));
|
|
3476
|
+
const style = styles.find(s => s.id === styleId || s.num === styleId || s.name.toLowerCase() === (styleId || '').toLowerCase());
|
|
3477
|
+
if (!style) {
|
|
3478
|
+
const ids = styles.map(s => s.id).join(', ');
|
|
3479
|
+
throw new Error(`Style "${styleId}" not found. Available ids: ${ids}`);
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3482
|
+
// Check for standalone prompt file or source HTML
|
|
3483
|
+
const promptFilePath = pathMod.resolve(__dirname, `../../plugins/motion-styles/prompts/${style.id}.md`);
|
|
3484
|
+
const sourceFilePath = pathMod.resolve(__dirname, `../../plugins/motion-styles/sources/${style.id}.html`);
|
|
3485
|
+
let sourceCode = '';
|
|
3486
|
+
if (includeSource && syncFs.existsSync(sourceFilePath)) {
|
|
3487
|
+
sourceCode = syncFs.readFileSync(sourceFilePath, 'utf8');
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
// Build the prompt matching FrameForge Motion Library builder
|
|
3491
|
+
const isVo = mode === 'vo';
|
|
3492
|
+
const intro = `I want an animated video in the "${style.name}" style (${style.family}) from the Motion Graphics Style Library. Below is a complete, working reference animation in this style: its core helpers (CM), a synthesized sound kit (SFX), the style's scenes, its sound score and the player page. Treat that file as the ground truth for how this style looks, moves and sounds, and rebuild it around my content.`;
|
|
3493
|
+
const topicText = topic ? topic.trim() : '[No topic yet. Ask me for my topic or script before building anything.]';
|
|
3494
|
+
const timingSection = isVo ? `\nVOICEOVER TIMING\n[No timing pasted yet. Before building, ask me for a word-level SRT or word timestamps of my voiceover. If I only have the audio file, offer to transcribe it into word timings.]\n\nHow to use the timing:\n- Turn it into a list of words with start and end times in seconds. If the timing is per line only, spread each line's words across its span in proportion to their length.\n- Set the duration to the last word's end plus about 1.2 seconds.\n- Build the timeline (TL) from these times. ${style.voLine || ''}\n- Cut between scenes only at idea boundaries (a new step, a new claim, a reveal), never mid-word, and finish each cut just before the next idea's first spoken word.\n- Put the emphasis moves on stressed words. A counter lands at the exact moment its number is spoken.\n- Keep the player's "Load voiceover" button working so I can drop in my audio and hear it in sync.\n` : '';
|
|
3495
|
+
|
|
3496
|
+
const workflowSection = storyboard ? `STORYBOARD FIRST\nBefore building, show me a storyboard and wait for my OK:\n- One HTML page (an artifact if you can) with a grid of 6 to 10 panels, one per beat. Draw each panel on a canvas with the reference's own helpers, scene drawing and finishing pass, so the stills already look like the finished piece. No animation or sound yet.\n- Under each panel: the beat number, its start and end time, the exact on-screen words, the camera move or emphasis, how it gets there from the previous beat (the same scene continuing, or a cut and which kind), and its key sounds.\n- Below the grid, list any facts or numbers you had to assume.\nWhen I approve it (or after my changes), build the full animation and reuse the storyboard's drawing code.` : `BUILD DIRECTLY\nSkip the storyboard and build the full animation now. Afterwards, list any facts or numbers you assumed.`;
|
|
3497
|
+
|
|
3498
|
+
const buildSection = `WHAT TO BUILD\nOne self-contained HTML file (an artifact if you can), built on the reference file below:\n- Keep the page, the player (play/pause, scrub, time, sound toggle, loop, Load voiceover${style.footage ? ', Load footage' : ''}) and the CM and SFX blocks unchanged.${style.lib ? ' Also keep the shared library helpers block (track, txt, chip, slam, rise, wordLine, hd, footage, composite, cam, punch, makeGL and the sound helpers) unchanged.' : ''}${style.canvas ? `\\n- Keep the canvas at ${style.canvas} (9:16, for Shorts, Reels and TikTok).` : ''}${style.three ? '\\n- Keep the Three.js r128 script tag and build every 3D object in code: no models or textures from the web.' : ''}${style.footage ? '\\n- Keep window.loadFootage(url) and the Load footage button working so I can drop in my own clip and see the overlay on it; until then the stand-in presenter shows where I sit in the frame.' : ''}\n- Replace the demo's story: ${style.replace || ''}. Keep and reuse the style's helpers (${style.keep || ''}).\n- Aim for 15 to 40 seconds unless my content needs more, and set the duration in TL.D.\n- Keep window.seek(t), window.DURATION, window.scoreSound and window.SFX exposed, so the video can be rendered frame by frame.\n- Load the style's fonts before the first draw, as the reference does: ${style.fonts || ''}.`;
|
|
3499
|
+
|
|
3500
|
+
const sheetRules = Array.isArray(style.sheet) ? '\nTHE STYLE\n' + style.sheet.map(r => '- ' + r).join('\n') : '';
|
|
3501
|
+
const sizeRules = `\nRULES THAT KEEP IT IN STYLE\n- Change the story, the words and the props; do not change the medium. Draw every subject accurately for my topic and double-check any facts and numbers you show.\n- Every frame is a pure function of t. Use CM.rng(seed) and CM.noise1(seed) for all randomness, never Math.random. Caching static layers is fine.\n- It will be watched on phones: at ${style.canvas || '1280x720'} ${style.sizes || ''}. Fill the frame.\n- Keep each frame fast, well under 50 ms.\n- Give my topic its own demo content; do not reuse the reference's story ("${style.demo || ''}") unless my content asks for it.`;
|
|
3502
|
+
|
|
3503
|
+
const soundRules = Array.isArray(style.sound) ? '\nSOUND\n' + style.sound.map(r => '- ' + r).join('\n') + '\n' + (isVo ? (style.voMix || '') : (style.full || '')) + '\n- Sync is everything: the sound score reads the same TL times as the picture.' : '';
|
|
3504
|
+
|
|
3505
|
+
const renderSection = `\nIF YOU CAN RUN CODE\nRender an MP4: open the file in headless Chrome, call window.seek(i / 30) for every frame, capture the canvas and pipe the frames into ffmpeg. Render the effects with SFX.render(window.scoreSound, window.DURATION, 48000, { voiceover: false }), which returns interleaved stereo floats, and mux them in. With a voiceover, render the effects with { voiceover: true } and mix them under my audio file.`;
|
|
3506
|
+
|
|
3507
|
+
const styleCard = `\nSTYLE CARD\nName: ${style.name}\nFamily: ${style.family}\nLook: ${style.look}\nHow it explains: ${style.how || ''}`;
|
|
3508
|
+
|
|
3509
|
+
let prompt = `${intro}\n\nMY CONTENT\n${topicText}\n${timingSection}\n${workflowSection}\n\n${buildSection}\n${sheetRules}\n${sizeRules}\n${soundRules}\n${renderSection}\n${styleCard}`;
|
|
3510
|
+
|
|
3511
|
+
if (includeSource && sourceCode) {
|
|
3512
|
+
prompt += `\n\nREFERENCE SOURCE (the complete working file)\n\`\`\`html\n${sourceCode.trim()}\n\`\`\``;
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
return {
|
|
3516
|
+
styleId: style.id,
|
|
3517
|
+
num: style.num,
|
|
3518
|
+
name: style.name,
|
|
3519
|
+
family: style.family,
|
|
3520
|
+
canvas: style.canvas || '1280x720',
|
|
3521
|
+
three: Boolean(style.three),
|
|
3522
|
+
footage: Boolean(style.footage),
|
|
3523
|
+
prompt,
|
|
3524
|
+
promptLength: prompt.length,
|
|
3525
|
+
promptPath: `plugins/motion-styles/prompts/${style.id}.md`,
|
|
3526
|
+
sourcePath: `plugins/motion-styles/sources/${style.id}.html`,
|
|
3527
|
+
source: 'FrameForge Motion Library — free to use for your own videos',
|
|
3528
|
+
message: `Prompt for "${style.name}" ready (${prompt.length} chars). Copy it and paste into a new Claude chat${topic ? ` with topic: "${topic}"` : ''}.`,
|
|
3529
|
+
};
|
|
3530
|
+
},
|
|
3531
|
+
},
|
|
3532
|
+
|
|
2817
3533
|
};
|
|
2818
3534
|
|
|
2819
3535
|
// ── MCP protocol handler (MCP 2024-11-05 spec) ───────────────────────────
|
|
2820
3536
|
|
|
2821
3537
|
const SERVER_INFO = {
|
|
2822
3538
|
name: 'video-pipeline',
|
|
2823
|
-
version: '2.7.
|
|
3539
|
+
version: '2.7.7',
|
|
2824
3540
|
};
|
|
2825
3541
|
|
|
2826
3542
|
const CAPABILITIES = {
|