@mevdragon/vidfarm-devcli 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/embedded-captions/CATALOG.md +93 -0
- package/.agents/skills/embedded-captions/SKILL.md +286 -0
- package/.agents/skills/embedded-captions/assets/brand/CDPR-fankit-terms.txt +35 -0
- package/.agents/skills/embedded-captions/assets/brand/cyberpunk-widths.json +115 -0
- package/.agents/skills/embedded-captions/assets/fonts/char-widths.json +2211 -0
- package/.agents/skills/embedded-captions/assets/strokefonts/HersheyScript1.svg +242 -0
- package/.agents/skills/embedded-captions/assets/strokefonts/HersheyScriptMed.svg +242 -0
- package/.agents/skills/embedded-captions/dna/README.md +148 -0
- package/.agents/skills/embedded-captions/dna/chrome.json +68 -0
- package/.agents/skills/embedded-captions/dna/cream.json +69 -0
- package/.agents/skills/embedded-captions/dna/documentary.json +62 -0
- package/.agents/skills/embedded-captions/dna/editorial.json +70 -0
- package/.agents/skills/embedded-captions/dna/glitch.json +82 -0
- package/.agents/skills/embedded-captions/dna/ink.json +65 -0
- package/.agents/skills/embedded-captions/dna/keynote.json +62 -0
- package/.agents/skills/embedded-captions/dna/loud.json +75 -0
- package/.agents/skills/embedded-captions/dna/neon.json +58 -0
- package/.agents/skills/embedded-captions/dna/velocity.json +89 -0
- package/.agents/skills/embedded-captions/modes/cinematic/README.md +48 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/champion/spec.md +98 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/champion/template.html +188 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/memory-wall/spec.md +134 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/memory-wall/template.html +179 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/portrait-header/spec.md +81 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/portrait-header/template.html +170 -0
- package/.agents/skills/embedded-captions/modes/cinematic/cinematic-cream/spec.md +16 -0
- package/.agents/skills/embedded-captions/modes/cinematic/cinematic-cream/template.html +226 -0
- package/.agents/skills/embedded-captions/modes/cinematic/engine.html +355 -0
- package/.agents/skills/embedded-captions/modes/standard/_anatomy.md +233 -0
- package/.agents/skills/embedded-captions/modes/standard/_motion.md +192 -0
- package/.agents/skills/embedded-captions/modes/standard/fonts/build-fonts-css.cjs +97 -0
- package/.agents/skills/embedded-captions/references/aesthetic-principles.md +168 -0
- package/.agents/skills/embedded-captions/references/anti-patterns.md +188 -0
- package/.agents/skills/embedded-captions/references/bespoke-vs-presets.md +178 -0
- package/.agents/skills/embedded-captions/references/caption-grouping.md +130 -0
- package/.agents/skills/embedded-captions/references/composition-craft.md +522 -0
- package/.agents/skills/embedded-captions/references/direction-catalog.md +138 -0
- package/.agents/skills/embedded-captions/references/example-renders/champion.html +371 -0
- package/.agents/skills/embedded-captions/references/example-renders/memory-wall.html +334 -0
- package/.agents/skills/embedded-captions/references/failure-modes.md +145 -0
- package/.agents/skills/embedded-captions/references/layout-heuristics.md +238 -0
- package/.agents/skills/embedded-captions/references/motion-vocabulary.md +148 -0
- package/.agents/skills/embedded-captions/references/rail.md +68 -0
- package/.agents/skills/embedded-captions/references/reference-bar.md +47 -0
- package/.agents/skills/embedded-captions/references/scene-types.md +94 -0
- package/.agents/skills/embedded-captions/references/test-set.md +66 -0
- package/.agents/skills/embedded-captions/references/typographic-moves.md +266 -0
- package/.agents/skills/embedded-captions/references/typography-presets.md +63 -0
- package/.agents/skills/embedded-captions/scripts/audio-envelope.cjs +95 -0
- package/.agents/skills/embedded-captions/scripts/check-occlusion.cjs +250 -0
- package/.agents/skills/embedded-captions/scripts/check-overflow.cjs +194 -0
- package/.agents/skills/embedded-captions/scripts/check-rail-climax.cjs +233 -0
- package/.agents/skills/embedded-captions/scripts/check-timing.cjs +173 -0
- package/.agents/skills/embedded-captions/scripts/fill-timings.cjs +124 -0
- package/.agents/skills/embedded-captions/scripts/fit-fonts.cjs +158 -0
- package/.agents/skills/embedded-captions/scripts/gen-stroke-path.py +46 -0
- package/.agents/skills/embedded-captions/scripts/inject-fonts.cjs +169 -0
- package/.agents/skills/embedded-captions/scripts/lib-dna.cjs +201 -0
- package/.agents/skills/embedded-captions/scripts/make-cinematic.cjs +1205 -0
- package/.agents/skills/embedded-captions/scripts/make-composition.cjs +404 -0
- package/.agents/skills/embedded-captions/scripts/make-theme.cjs +8781 -0
- package/.agents/skills/embedded-captions/scripts/matte.cjs +294 -0
- package/.agents/skills/embedded-captions/scripts/measure-layout.cjs +293 -0
- package/.agents/skills/embedded-captions/scripts/prepare.sh +39 -0
- package/.agents/skills/embedded-captions/scripts/preview-frames.cjs +268 -0
- package/.agents/skills/embedded-captions/scripts/render-and-composite.sh +509 -0
- package/.agents/skills/embedded-captions/scripts/render-theme.sh +34 -0
- package/.agents/skills/embedded-captions/scripts/safe-zones.cjs +813 -0
- package/.agents/skills/embedded-captions/scripts/transcribe.cjs +349 -0
- package/.agents/skills/embedded-captions/themes/PORTING.md +49 -0
- package/.agents/skills/embedded-captions/themes/README.md +244 -0
- package/.agents/skills/embedded-captions/themes/anchor.json +27 -0
- package/.agents/skills/embedded-captions/themes/arcade.json +51 -0
- package/.agents/skills/embedded-captions/themes/aurora.json +41 -0
- package/.agents/skills/embedded-captions/themes/biolume.json +35 -0
- package/.agents/skills/embedded-captions/themes/brush.json +74 -0
- package/.agents/skills/embedded-captions/themes/chalkboard.json +52 -0
- package/.agents/skills/embedded-captions/themes/dossier.json +56 -0
- package/.agents/skills/embedded-captions/themes/graffiti.json +62 -0
- package/.agents/skills/embedded-captions/themes/hologram.json +41 -0
- package/.agents/skills/embedded-captions/themes/inkwater.json +43 -0
- package/.agents/skills/embedded-captions/themes/laser.json +46 -0
- package/.agents/skills/embedded-captions/themes/lastpage.json +33 -0
- package/.agents/skills/embedded-captions/themes/neonsign.json +53 -0
- package/.agents/skills/embedded-captions/themes/nightcity.json +56 -0
- package/.agents/skills/embedded-captions/themes/ordnance.json +54 -0
- package/.agents/skills/embedded-captions/themes/papercut.json +37 -0
- package/.agents/skills/embedded-captions/themes/popup.json +46 -0
- package/.agents/skills/embedded-captions/themes/ransom.json +62 -0
- package/.agents/skills/embedded-captions/themes/scoreboard.json +54 -0
- package/.agents/skills/embedded-captions/themes/spectrum.json +46 -0
- package/.agents/skills/embedded-captions/themes/stardust.json +29 -0
- package/.agents/skills/embedded-captions/themes/stomp.json +43 -0
- package/.agents/skills/embedded-captions/themes/terminal.json +49 -0
- package/.agents/skills/embedded-captions/themes/thunder.json +44 -0
- package/.agents/skills/embedded-captions/themes/transit.json +44 -0
- package/.agents/skills/embedded-captions/themes/vhs.json +47 -0
- package/.agents/skills/faceless-explainer/SKILL.md +209 -0
- package/.agents/skills/faceless-explainer/references/cut-catalog.md +215 -0
- package/.agents/skills/faceless-explainer/references/motion-language.md +156 -0
- package/.agents/skills/faceless-explainer/references/story-design.md +248 -0
- package/.agents/skills/faceless-explainer/references/visual-design.md +146 -0
- package/.agents/skills/faceless-explainer/scripts/assemble-index.mjs +570 -0
- package/.agents/skills/faceless-explainer/scripts/audio.mjs +260 -0
- package/.agents/skills/faceless-explainer/scripts/build-frame.mjs +536 -0
- package/.agents/skills/faceless-explainer/scripts/captions.mjs +508 -0
- package/.agents/skills/faceless-explainer/scripts/lib/assets.mjs +55 -0
- package/.agents/skills/faceless-explainer/scripts/lib/dimensions.mjs +45 -0
- package/.agents/skills/faceless-explainer/scripts/lib/storyboard.mjs +249 -0
- package/.agents/skills/faceless-explainer/scripts/lib/tokens.mjs +204 -0
- package/.agents/skills/faceless-explainer/scripts/lib/transition-registry.mjs +38 -0
- package/.agents/skills/faceless-explainer/scripts/lib/transitions.json +71 -0
- package/.agents/skills/faceless-explainer/scripts/transitions.mjs +320 -0
- package/.agents/skills/faceless-explainer/sub-agents/frame-worker.md +75 -0
- package/.agents/skills/flockposter/LICENSE +17 -0
- package/.agents/skills/flockposter/README.md +72 -0
- package/.agents/skills/flockposter/SKILL.md +562 -0
- package/.agents/skills/flockposter/examples/cli-instagram-reel.sh +21 -0
- package/.agents/skills/flockposter/examples/cli-multi-platform.json +69 -0
- package/.agents/skills/flockposter/examples/cli-tiktok-upload-only.sh +22 -0
- package/.agents/skills/flockposter/examples/cli-x-thread.sh +17 -0
- package/.agents/skills/flockposter/examples/cli-youtube-short.sh +21 -0
- package/.agents/skills/flockposter/examples/public-api-instagram-reel.json +28 -0
- package/.agents/skills/general-video/SKILL.md +143 -0
- package/.agents/skills/hyperframes/SKILL.md +160 -0
- package/.agents/skills/hyperframes-animation/SKILL.md +84 -0
- package/.agents/skills/hyperframes-animation/adapters/animate-text.md +64 -0
- package/.agents/skills/hyperframes-animation/adapters/animejs.md +114 -0
- package/.agents/skills/hyperframes-animation/adapters/css-animations.md +143 -0
- package/.agents/skills/hyperframes-animation/adapters/gsap-easing-and-stagger.md +118 -0
- package/.agents/skills/hyperframes-animation/adapters/gsap-timeline-and-labels.md +96 -0
- package/.agents/skills/hyperframes-animation/adapters/gsap-transforms-and-perf.md +96 -0
- package/.agents/skills/hyperframes-animation/adapters/gsap.md +105 -0
- package/.agents/skills/hyperframes-animation/adapters/html-in-canvas-patterns.md +507 -0
- package/.agents/skills/hyperframes-animation/adapters/lottie.md +117 -0
- package/.agents/skills/hyperframes-animation/adapters/three.md +131 -0
- package/.agents/skills/hyperframes-animation/adapters/typegpu.md +178 -0
- package/.agents/skills/hyperframes-animation/adapters/waapi.md +101 -0
- package/.agents/skills/hyperframes-animation/blueprints/comparison-split.md +27 -0
- package/.agents/skills/hyperframes-animation/blueprints/constellation-hub.md +44 -0
- package/.agents/skills/hyperframes-animation/blueprints/cta-morph-press.md +28 -0
- package/.agents/skills/hyperframes-animation/blueprints/cursor-ui-demo.md +57 -0
- package/.agents/skills/hyperframes-animation/blueprints/dataviz-countup.md +46 -0
- package/.agents/skills/hyperframes-animation/blueprints/device-surface-showcase.md +53 -0
- package/.agents/skills/hyperframes-animation/blueprints/grid-card-assemble.md +66 -0
- package/.agents/skills/hyperframes-animation/blueprints/kinetic-type-beats.md +78 -0
- package/.agents/skills/hyperframes-animation/blueprints/logo-assemble-lockup.md +69 -0
- package/.agents/skills/hyperframes-animation/blueprints/overwhelm-surround.md +28 -0
- package/.agents/skills/hyperframes-animation/blueprints/spatial-pan-stations.md +37 -0
- package/.agents/skills/hyperframes-animation/blueprints/ticker-takeover.md +29 -0
- package/.agents/skills/hyperframes-animation/blueprints/titlecard-reveal.md +42 -0
- package/.agents/skills/hyperframes-animation/blueprints/typewriter-reveal.md +51 -0
- package/.agents/skills/hyperframes-animation/blueprints/video-text-pivot.md +30 -0
- package/.agents/skills/hyperframes-animation/blueprints-index.md +146 -0
- package/.agents/skills/hyperframes-animation/examples/assets/avatars/02.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/assets/brands/github.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/assets/brands/nvidia.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/assets/brands/visa.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/assets/brands/zoominfo.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/brand-reveal-assemble-zoom.html +382 -0
- package/.agents/skills/hyperframes-animation/examples/comparison-split-cards.html +649 -0
- package/.agents/skills/hyperframes-animation/examples/concept-demo-decode-pan.html +520 -0
- package/.agents/skills/hyperframes-animation/examples/cta-morph-press.html +468 -0
- package/.agents/skills/hyperframes-animation/examples/cta-orbit-collapse.html +1298 -0
- package/.agents/skills/hyperframes-animation/examples/demo-page-scroll-spotlight.html +759 -0
- package/.agents/skills/hyperframes-animation/examples/hook-counter-burst.html +729 -0
- package/.agents/skills/hyperframes-animation/examples/messaging-multi-phrase.html +352 -0
- package/.agents/skills/hyperframes-animation/examples/metric-video-text-pivot.html +779 -0
- package/.agents/skills/hyperframes-animation/examples/problem-mockup-overwhelm.html +1374 -0
- package/.agents/skills/hyperframes-animation/examples/proof-logo-chain.html +861 -0
- package/.agents/skills/hyperframes-animation/examples/takeover-ticker-displace.html +347 -0
- package/.agents/skills/hyperframes-animation/examples/workflow-approve-press.html +606 -0
- package/.agents/skills/hyperframes-animation/rules/3d-page-scroll.md +227 -0
- package/.agents/skills/hyperframes-animation/rules/3d-text-depth-layers.md +297 -0
- package/.agents/skills/hyperframes-animation/rules/ai-tracking-box.md +382 -0
- package/.agents/skills/hyperframes-animation/rules/ambient-glow-bloom.md +305 -0
- package/.agents/skills/hyperframes-animation/rules/asr-keyword-glow.md +286 -0
- package/.agents/skills/hyperframes-animation/rules/avatar-cloud-network.md +371 -0
- package/.agents/skills/hyperframes-animation/rules/camera-cursor-tracking.md +246 -0
- package/.agents/skills/hyperframes-animation/rules/card-morph-anchor.md +267 -0
- package/.agents/skills/hyperframes-animation/rules/center-outward-expansion.md +227 -0
- package/.agents/skills/hyperframes-animation/rules/context-sensitive-cursor.md +257 -0
- package/.agents/skills/hyperframes-animation/rules/coordinate-target-zoom.md +332 -0
- package/.agents/skills/hyperframes-animation/rules/counting-dynamic-scale.md +283 -0
- package/.agents/skills/hyperframes-animation/rules/css-marker-patterns.md +373 -0
- package/.agents/skills/hyperframes-animation/rules/cursor-click-ripple.md +262 -0
- package/.agents/skills/hyperframes-animation/rules/depth-of-field-blur.md +313 -0
- package/.agents/skills/hyperframes-animation/rules/depth-scatter-assemble.md +303 -0
- package/.agents/skills/hyperframes-animation/rules/discrete-text-sequence.md +273 -0
- package/.agents/skills/hyperframes-animation/rules/dynamic-content-sequencing.md +307 -0
- package/.agents/skills/hyperframes-animation/rules/gsap-effects.md +299 -0
- package/.agents/skills/hyperframes-animation/rules/hacker-flip-3d.md +223 -0
- package/.agents/skills/hyperframes-animation/rules/kinetic-beat-slam.md +183 -0
- package/.agents/skills/hyperframes-animation/rules/motion-blur-streak.md +328 -0
- package/.agents/skills/hyperframes-animation/rules/multi-phase-camera.md +273 -0
- package/.agents/skills/hyperframes-animation/rules/orbit-3d-entry.md +301 -0
- package/.agents/skills/hyperframes-animation/rules/physics-press-reaction.md +350 -0
- package/.agents/skills/hyperframes-animation/rules/press-release-spring.md +296 -0
- package/.agents/skills/hyperframes-animation/rules/reactive-displacement.md +277 -0
- package/.agents/skills/hyperframes-animation/rules/scale-swap-transition.md +298 -0
- package/.agents/skills/hyperframes-animation/rules/sine-wave-loop.md +278 -0
- package/.agents/skills/hyperframes-animation/rules/split-tilt-cards.md +277 -0
- package/.agents/skills/hyperframes-animation/rules/spring-pop-entrance.md +273 -0
- package/.agents/skills/hyperframes-animation/rules/stat-bars-and-fills.md +156 -0
- package/.agents/skills/hyperframes-animation/rules/svg-icon-enrichment.md +329 -0
- package/.agents/skills/hyperframes-animation/rules/svg-path-draw.md +274 -0
- package/.agents/skills/hyperframes-animation/rules/vertical-spring-ticker.md +239 -0
- package/.agents/skills/hyperframes-animation/rules/viewport-change.md +349 -0
- package/.agents/skills/hyperframes-animation/rules-index.md +86 -0
- package/.agents/skills/hyperframes-animation/scripts/animation-map.mjs +606 -0
- package/.agents/skills/hyperframes-animation/scripts/package-loader.mjs +288 -0
- package/.agents/skills/hyperframes-animation/scripts/package-loader.test.mjs +62 -0
- package/.agents/skills/hyperframes-animation/techniques.md +507 -0
- package/.agents/skills/hyperframes-animation/transitions/TRANSITION-REGISTRY.md +167 -0
- package/.agents/skills/hyperframes-animation/transitions/catalog.md +127 -0
- package/.agents/skills/hyperframes-animation/transitions/css-3d.md +12 -0
- package/.agents/skills/hyperframes-animation/transitions/css-blur.md +51 -0
- package/.agents/skills/hyperframes-animation/transitions/css-cover.md +43 -0
- package/.agents/skills/hyperframes-animation/transitions/css-destruction.md +95 -0
- package/.agents/skills/hyperframes-animation/transitions/css-dissolve.md +66 -0
- package/.agents/skills/hyperframes-animation/transitions/css-distortion.md +45 -0
- package/.agents/skills/hyperframes-animation/transitions/css-grid.md +10 -0
- package/.agents/skills/hyperframes-animation/transitions/css-light.md +49 -0
- package/.agents/skills/hyperframes-animation/transitions/css-mechanical.md +30 -0
- package/.agents/skills/hyperframes-animation/transitions/css-other.md +25 -0
- package/.agents/skills/hyperframes-animation/transitions/css-push.md +41 -0
- package/.agents/skills/hyperframes-animation/transitions/css-radial.md +37 -0
- package/.agents/skills/hyperframes-animation/transitions/css-scale.md +24 -0
- package/.agents/skills/hyperframes-animation/transitions/overview.md +153 -0
- package/.agents/skills/hyperframes-cli/SKILL.md +113 -0
- package/.agents/skills/hyperframes-cli/references/doctor-browser.md +45 -0
- package/.agents/skills/hyperframes-cli/references/init-and-scaffold.md +55 -0
- package/.agents/skills/hyperframes-cli/references/lambda.md +132 -0
- package/.agents/skills/hyperframes-cli/references/lint-validate-inspect.md +121 -0
- package/.agents/skills/hyperframes-cli/references/preview-render.md +149 -0
- package/.agents/skills/hyperframes-cli/references/upgrade-info-misc.md +75 -0
- package/.agents/skills/hyperframes-core/SKILL.md +78 -0
- package/.agents/skills/hyperframes-core/references/composition-patterns.md +263 -0
- package/.agents/skills/hyperframes-core/references/data-attributes.md +70 -0
- package/.agents/skills/hyperframes-core/references/determinism-rules.md +68 -0
- package/.agents/skills/hyperframes-core/references/full-screen-motion.md +62 -0
- package/.agents/skills/hyperframes-core/references/minimal-composition.md +66 -0
- package/.agents/skills/hyperframes-core/references/script-format.md +49 -0
- package/.agents/skills/hyperframes-core/references/storyboard-format.md +95 -0
- package/.agents/skills/hyperframes-core/references/sub-compositions.md +237 -0
- package/.agents/skills/hyperframes-core/references/subagent-dispatch.md +41 -0
- package/.agents/skills/hyperframes-core/references/tailwind.md +125 -0
- package/.agents/skills/hyperframes-core/references/tracks-and-clips.md +76 -0
- package/.agents/skills/hyperframes-core/references/variables-and-media.md +90 -0
- package/.agents/skills/hyperframes-creative/SKILL.md +70 -0
- package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/FRAME.md +289 -0
- package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/caption-skin.html +221 -0
- package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/frame-showcase.html +1214 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blockframe/FRAME.md +275 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blockframe/caption-skin.html +224 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blockframe/frame-showcase.html +1347 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/FRAME.md +307 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/caption-skin.html +223 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/frame-showcase.html +1372 -0
- package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/FRAME.md +275 -0
- package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/caption-skin.html +230 -0
- package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/frame-showcase.html +1140 -0
- package/.agents/skills/hyperframes-creative/frame-presets/broadside/FRAME.md +284 -0
- package/.agents/skills/hyperframes-creative/frame-presets/broadside/caption-skin.html +225 -0
- package/.agents/skills/hyperframes-creative/frame-presets/broadside/frame-showcase.html +1166 -0
- package/.agents/skills/hyperframes-creative/frame-presets/capsule/FRAME.md +284 -0
- package/.agents/skills/hyperframes-creative/frame-presets/capsule/caption-skin.html +224 -0
- package/.agents/skills/hyperframes-creative/frame-presets/capsule/frame-showcase.html +1465 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cartesian/FRAME.md +278 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cartesian/caption-skin.html +222 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cartesian/frame-showcase.html +1263 -0
- package/.agents/skills/hyperframes-creative/frame-presets/claude/FRAME.md +277 -0
- package/.agents/skills/hyperframes-creative/frame-presets/claude/caption-skin.html +224 -0
- package/.agents/skills/hyperframes-creative/frame-presets/claude/frame-showcase.html +1439 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/FRAME.md +265 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/caption-skin.html +238 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/frame-showcase.html +1468 -0
- package/.agents/skills/hyperframes-creative/frame-presets/coral/FRAME.md +314 -0
- package/.agents/skills/hyperframes-creative/frame-presets/coral/caption-skin.html +226 -0
- package/.agents/skills/hyperframes-creative/frame-presets/coral/frame-showcase.html +1268 -0
- package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/FRAME.md +333 -0
- package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/caption-skin.html +227 -0
- package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/frame-showcase.html +1330 -0
- package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/FRAME.md +274 -0
- package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/caption-skin.html +230 -0
- package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/frame-showcase.html +1444 -0
- package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/FRAME.md +265 -0
- package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/caption-skin.html +223 -0
- package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/frame-showcase.html +1273 -0
- package/.agents/skills/hyperframes-creative/palettes/bold-energetic.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/clean-corporate.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/dark-premium.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/jewel-rich.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/monochrome.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/nature-earth.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/neon-electric.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/pastel-soft.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/warm-editorial.md +14 -0
- package/.agents/skills/hyperframes-creative/references/audio-reactive.md +76 -0
- package/.agents/skills/hyperframes-creative/references/beat-direction.md +164 -0
- package/.agents/skills/hyperframes-creative/references/composition-patterns.md +199 -0
- package/.agents/skills/hyperframes-creative/references/data-in-motion.md +19 -0
- package/.agents/skills/hyperframes-creative/references/design-adherence.md +19 -0
- package/.agents/skills/hyperframes-creative/references/design-picker.md +123 -0
- package/.agents/skills/hyperframes-creative/references/design-spec.md +55 -0
- package/.agents/skills/hyperframes-creative/references/house-style.md +73 -0
- package/.agents/skills/hyperframes-creative/references/motion-principles.md +150 -0
- package/.agents/skills/hyperframes-creative/references/narration.md +92 -0
- package/.agents/skills/hyperframes-creative/references/prompt-expansion.md +68 -0
- package/.agents/skills/hyperframes-creative/references/typography.md +218 -0
- package/.agents/skills/hyperframes-creative/references/video-composition.md +63 -0
- package/.agents/skills/hyperframes-creative/references/visual-styles.md +457 -0
- package/.agents/skills/hyperframes-creative/scripts/contrast-report.mjs +353 -0
- package/.agents/skills/hyperframes-creative/scripts/extract-audio-data.py +188 -0
- package/.agents/skills/hyperframes-creative/scripts/package-loader.mjs +288 -0
- package/.agents/skills/hyperframes-creative/scripts/package-loader.test.mjs +62 -0
- package/.agents/skills/hyperframes-creative/templates/design-picker.html +1432 -0
- package/.agents/skills/hyperframes-keyframes/SKILL.md +237 -0
- package/.agents/skills/hyperframes-keyframes/agents/openai.yaml +4 -0
- package/.agents/skills/hyperframes-keyframes/references/keyframe-patterns.md +106 -0
- package/.agents/skills/motion-graphics/SKILL.md +172 -0
- package/.agents/skills/motion-graphics/agents/builder.md +40 -0
- package/.agents/skills/motion-graphics/agents/director.md +53 -0
- package/.agents/skills/motion-graphics/agents/finalize.md +17 -0
- package/.agents/skills/motion-graphics/catalog-map.md +40 -0
- package/.agents/skills/motion-graphics/categories/asset-fusion/module.md +37 -0
- package/.agents/skills/motion-graphics/categories/charts/module.md +21 -0
- package/.agents/skills/motion-graphics/categories/kinetic-type/module.md +19 -0
- package/.agents/skills/motion-graphics/categories/logo-reveal/module.md +17 -0
- package/.agents/skills/motion-graphics/categories/lower-thirds/module.md +16 -0
- package/.agents/skills/motion-graphics/categories/maps/bake-basemap.mjs +273 -0
- package/.agents/skills/motion-graphics/categories/maps/module.md +61 -0
- package/.agents/skills/motion-graphics/categories/news/module.md +56 -0
- package/.agents/skills/motion-graphics/categories/stat/module.md +21 -0
- package/.agents/skills/motion-graphics/categories/tweet/module.md +16 -0
- package/.agents/skills/motion-graphics/categories/webpage/module.md +21 -0
- package/.agents/skills/motion-graphics/grounding/PROTOCOL.md +64 -0
- package/.agents/skills/motion-graphics/grounding/locate.mjs +227 -0
- package/.agents/skills/motion-graphics/phases/source/guide.md +21 -0
- package/.agents/skills/motion-graphics/references/builder-contract.md +38 -0
- package/.agents/skills/motion-graphics/references/motion-vocabulary.md +43 -0
- package/.agents/skills/motion-graphics/references/shot-plan-ir.md +54 -0
- package/.agents/skills/motion-graphics/samples/asset-fusion/_ref-circle-highlight.html +306 -0
- package/.agents/skills/product-launch-video/SKILL.md +208 -0
- package/.agents/skills/product-launch-video/references/cut-catalog.md +220 -0
- package/.agents/skills/product-launch-video/references/motion-language.md +156 -0
- package/.agents/skills/product-launch-video/references/story-design.md +373 -0
- package/.agents/skills/product-launch-video/references/visual-design.md +126 -0
- package/.agents/skills/product-launch-video/scripts/assemble-index.mjs +570 -0
- package/.agents/skills/product-launch-video/scripts/audio.mjs +260 -0
- package/.agents/skills/product-launch-video/scripts/build-frame.mjs +536 -0
- package/.agents/skills/product-launch-video/scripts/captions.mjs +508 -0
- package/.agents/skills/product-launch-video/scripts/lib/assets.mjs +55 -0
- package/.agents/skills/product-launch-video/scripts/lib/dimensions.mjs +45 -0
- package/.agents/skills/product-launch-video/scripts/lib/pad-frame-duration.mjs +36 -0
- package/.agents/skills/product-launch-video/scripts/lib/pad-frame-duration.test.mjs +76 -0
- package/.agents/skills/product-launch-video/scripts/lib/storyboard.mjs +249 -0
- package/.agents/skills/product-launch-video/scripts/lib/tokens.mjs +204 -0
- package/.agents/skills/product-launch-video/scripts/lib/transition-registry.mjs +38 -0
- package/.agents/skills/product-launch-video/scripts/lib/transitions.json +71 -0
- package/.agents/skills/product-launch-video/scripts/stage-assets.mjs +39 -0
- package/.agents/skills/product-launch-video/scripts/transitions.mjs +327 -0
- package/.agents/skills/product-launch-video/sub-agents/frame-worker.md +75 -0
- package/.agents/skills/slideshow/SKILL.md +550 -0
- package/.agents/skills/slideshow/references/standalone-harness.md +1020 -0
- package/.agents/skills/talking-head-recut/NOTICE.md +39 -0
- package/.agents/skills/talking-head-recut/SKILL.md +1195 -0
- package/.agents/skills/talking-head-recut/assets/vendor/gsap.min.js +10 -0
- package/.agents/skills/talking-head-recut/references/DESIGN_INDEX.md +145 -0
- package/.agents/skills/talking-head-recut/references/frames/clean.html +92 -0
- package/.agents/skills/talking-head-recut/references/frames/hairline.html +171 -0
- package/.agents/skills/talking-head-recut/references/frames/polaroid.html +131 -0
- package/.agents/skills/talking-head-recut/references/layouts/overlay.html +164 -0
- package/.agents/skills/talking-head-recut/references/layouts/pip.html +143 -0
- package/.agents/skills/talking-head-recut/references/layouts/split.html +121 -0
- package/.agents/skills/talking-head-recut/references/layouts/stack.html +109 -0
- package/.agents/skills/talking-head-recut/references/styles/academic.html +172 -0
- package/.agents/skills/talking-head-recut/references/styles/audit.html +170 -0
- package/.agents/skills/talking-head-recut/references/styles/editorial.html +189 -0
- package/.agents/skills/talking-head-recut/references/styles/geom.html +181 -0
- package/.agents/skills/talking-head-recut/references/styles/minimal.html +130 -0
- package/.agents/skills/talking-head-recut/references/styles/spotlight.html +142 -0
- package/.agents/skills/talking-head-recut/references/styles/swiss.html +188 -0
- package/.agents/skills/talking-head-recut/references/styles/terminal.html +196 -0
- package/.agents/skills/talking-head-recut/references/styles/whiteboard.html +170 -0
- package/.agents/skills/talking-head-recut/references/styles/xhs.html +206 -0
- package/.agents/skills/vidfarm-media/SKILL.md +85 -0
- package/.agents/skills/vidfarm-media/references/tts.md +54 -0
- package/.agents/skills/vidfarm-media/scripts/audio.mjs +628 -0
- package/.agents/skills/vidfarm-media/scripts/wait-bgm.mjs +31 -0
- package/.agents/skills/website-to-video/SKILL.md +145 -0
- package/.agents/skills/website-to-video/assets/sfx/CREDITS.md +35 -0
- package/.agents/skills/website-to-video/assets/sfx/manifest.json +97 -0
- package/.agents/skills/website-to-video/references/beat-builder-guide.md +263 -0
- package/.agents/skills/website-to-video/references/capabilities.md +713 -0
- package/.agents/skills/website-to-video/references/step-0-capture.md +55 -0
- package/.agents/skills/website-to-video/references/step-1-design.md +333 -0
- package/.agents/skills/website-to-video/references/step-2-brief.md +178 -0
- package/.agents/skills/website-to-video/references/step-3-storyboard.md +572 -0
- package/.agents/skills/website-to-video/references/step-4-vo.md +225 -0
- package/.agents/skills/website-to-video/references/step-5-build.md +483 -0
- package/.agents/skills/website-to-video/references/step-6-validate.md +341 -0
- package/.agents/skills/website-to-video/scripts/w2h-verify.mjs +759 -0
- package/SKILL.director.md +37 -0
- package/SKILL.platform.md +6 -1
- package/demo/dist/app.js +295 -11
- package/dist/src/app.js +317 -133
- package/dist/src/cli.js +529 -12
- package/dist/src/composition-runtime.js +262 -3
- package/dist/src/devcli/doctor.js +192 -0
- package/dist/src/devcli/hyperframes-cli.js +118 -0
- package/dist/src/devcli/skills.js +476 -0
- package/dist/src/devcli/stills.js +249 -0
- package/dist/src/editor-chat.js +255 -0
- package/dist/src/hyperframes/composition.js +5 -5
- package/dist/src/services/composition-lint.js +199 -0
- package/dist/src/services/hyperframes.js +67 -13
- package/package.json +2 -1
package/dist/src/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { copyFileSync, createWriteStream, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { copyFileSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { homedir } from "node:os";
|
|
4
|
+
import { homedir, tmpdir } from "node:os";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { parseArgs } from "node:util";
|
|
@@ -13,7 +13,13 @@ import { runCaptionsCommand } from "./devcli/captions.js";
|
|
|
13
13
|
import { runClipsCommand } from "./devcli/clips.js";
|
|
14
14
|
import { runTransitionsCommand } from "./devcli/transitions.js";
|
|
15
15
|
import { MAX_LOCAL_TRANSCRIBE_AUDIO_BYTES, loadSpeechAudioLocally, localGenerateSpeech, localTranscribeSpeech, resolveLocalSpeechAuth } from "./devcli/speech.js";
|
|
16
|
-
import { inferSpeechProviderForVoice } from "./services/speech.js";
|
|
16
|
+
import { buildSrtFromSegments, inferSpeechProviderForVoice } from "./services/speech.js";
|
|
17
|
+
import { formatCompositionLintIssues, lintCompositionHtml } from "./services/composition-lint.js";
|
|
18
|
+
import { resolveFfmpeg } from "./services/clip-curation/ffmpeg.js";
|
|
19
|
+
import { parseHyperframesJson, runHyperframesCommand } from "./devcli/hyperframes-cli.js";
|
|
20
|
+
import { renderCompositionStills } from "./devcli/stills.js";
|
|
21
|
+
import { runDoctorCommand } from "./devcli/doctor.js";
|
|
22
|
+
import { runSkillsCommand } from "./devcli/skills.js";
|
|
17
23
|
import { initTelemetry, reportCliCrash } from "./devcli/telemetry.js";
|
|
18
24
|
// vidfarm-devcli — command-line bridge for the Vidfarm video studio. The
|
|
19
25
|
// `serve` command boots the FULL editor locally (single origin, disk-backed
|
|
@@ -189,6 +195,9 @@ Speech (TTS/STT) — LOCAL-FIRST on your own AI key; --cloud is the explicit bac
|
|
|
189
195
|
a voice from the wrong provider family is rejected with a clear error.
|
|
190
196
|
--out <file> Output audio path (default tts-<id>.mp3|wav)
|
|
191
197
|
--provider <p> --model <m> --format mp3|wav
|
|
198
|
+
--engine local KEYLESS local Kokoro-82M engine (free, no account) — fixed voice
|
|
199
|
+
presets (af_heart, am_adam, bf_emma, …), no --style. When NO
|
|
200
|
+
provider key is found the default byok engine auto-falls back here.
|
|
192
201
|
--cloud BACKUP: saved-key platform job → POST /api/v1/primitives/audio/speech (+ poll)
|
|
193
202
|
stt <file|url> Video or audio → transcript (alias: transcribe). LOCAL: local ffmpeg demux +
|
|
194
203
|
Returns BOTH formats: the simple subtitle your provider key (gemini labels
|
|
@@ -200,6 +209,10 @@ Speech (TTS/STT) — LOCAL-FIRST on your own AI key; --cloud is the explicit bac
|
|
|
200
209
|
--language <code> Expected language hint (e.g. en, es)
|
|
201
210
|
--prompt "<context>" Domain/vocabulary hint for the transcriber
|
|
202
211
|
--provider <p> --model <m>
|
|
212
|
+
--engine whisper KEYLESS local whisper.cpp engine (free, no account) — WORD-level
|
|
213
|
+
timestamps (words[] in the .json/--json output), no speaker labels.
|
|
214
|
+
When NO provider key is found the default byok engine auto-falls
|
|
215
|
+
back here. --model then picks the whisper model (tiny.en…large-v3).
|
|
203
216
|
--cloud BACKUP: saved-key platform job → POST /api/v1/primitives/audio/transcribe (+ poll)
|
|
204
217
|
(a local file uploads to your temp folder first — 30-day TTL)
|
|
205
218
|
|
|
@@ -233,6 +246,37 @@ Scene transitions (between clips on a pulled/served composition — local file w
|
|
|
233
246
|
clone <forkId> Clone a fork as a new starting point → POST .../compositions/:forkId/clone
|
|
234
247
|
share-link <forkId> Mint a share-token URL for a fork → POST .../compositions/:forkId/share-links
|
|
235
248
|
|
|
249
|
+
Local media engines & toolchain (all local, free, no account — no cloud key needed):
|
|
250
|
+
remove-background <file|url> Cut the background out of a video/image (local ONNX — free, no account)
|
|
251
|
+
→ transparent WebM (video) / PNG (image)
|
|
252
|
+
--out <path> Output path .webm|.mov|.png (default <name>.transparent.webm|.png)
|
|
253
|
+
--background-out <path> Also write the inverse background plate (.webm|.mov)
|
|
254
|
+
capture <url> Capture a website: screenshots + assets + (local headless Chrome — free,
|
|
255
|
+
video manifest, for site-to-video work no account)
|
|
256
|
+
--out <dir> Output directory (default ./capture)
|
|
257
|
+
--max-screenshots <n> --timeout <ms> --skip-assets
|
|
258
|
+
beats <audioFile|url> Detect musical beats → timestamps JSON to (local — free, no account)
|
|
259
|
+
sync transitions/captions timing against
|
|
260
|
+
--out <path> Output JSON path (default <audio>.beats.json)
|
|
261
|
+
lint <dir|composition.html> Validate a composition on disk (same (local)
|
|
262
|
+
checks the /editor chat preflights); prints
|
|
263
|
+
errors/warnings, exit 1 on errors [--json]
|
|
264
|
+
stills <dir|composition.html> Render PNG stills of the CURRENT (local in-process render)
|
|
265
|
+
composition — the visual self-check loop
|
|
266
|
+
("did my edit look right") without a full render
|
|
267
|
+
--at 0,2.5,7 Timestamps (default: midpoint of each scene clip, cap 8)
|
|
268
|
+
--out <dir> Output dir (default <dir>/stills)
|
|
269
|
+
doctor Health-check the local toolchain: node, (local)
|
|
270
|
+
ffmpeg, hyperframes engines, Chrome, API key
|
|
271
|
+
(whoami), provider keys, agent CLI, poisoned
|
|
272
|
+
env, port 3000, installed skills [--json]
|
|
273
|
+
skills <list|add <names…>|update> Install/refresh agent skills into → GET /skill-pack/* (or the
|
|
274
|
+
<root>/.agents/skills/<name> + a relative copies bundled in this repo)
|
|
275
|
+
.claude/skills symlink; skills-lock.json is
|
|
276
|
+
merged, never clobbered [--json]
|
|
277
|
+
--dir <root> Project root (default: walk up from cwd to .agents/skills or .git)
|
|
278
|
+
--host <url> Skill-pack host (default VIDFARM_SKILLS_HOST/VIDFARM_HOST or ${DEFAULT_HOST})
|
|
279
|
+
|
|
236
280
|
Approve & schedule (publish a finished MP4 as a shareable post):
|
|
237
281
|
approve Approve media into a preview/share page → POST /api/v1/approved/posts
|
|
238
282
|
--video <url|file> Primary MP4 (URL, or a local file auto-uploaded to temp/)
|
|
@@ -493,6 +537,30 @@ async function main() {
|
|
|
493
537
|
case "transitions":
|
|
494
538
|
await runTransitionsCommand(rest);
|
|
495
539
|
return;
|
|
540
|
+
// Local media engines + toolchain (bundled HyperFrames local commands —
|
|
541
|
+
// free, no account — plus vidfarm-native lint/stills/doctor/skills).
|
|
542
|
+
case "remove-background":
|
|
543
|
+
case "rmbg":
|
|
544
|
+
await runRemoveBackgroundCommand(rest);
|
|
545
|
+
return;
|
|
546
|
+
case "capture":
|
|
547
|
+
await runCaptureCommand(rest);
|
|
548
|
+
return;
|
|
549
|
+
case "beats":
|
|
550
|
+
await runBeatsCommand(rest);
|
|
551
|
+
return;
|
|
552
|
+
case "lint":
|
|
553
|
+
await runLintCommand(rest);
|
|
554
|
+
return;
|
|
555
|
+
case "stills":
|
|
556
|
+
await runStillsCommand(rest);
|
|
557
|
+
return;
|
|
558
|
+
case "doctor":
|
|
559
|
+
await runDoctorCommand(rest);
|
|
560
|
+
return;
|
|
561
|
+
case "skills":
|
|
562
|
+
await runSkillsCommand(rest);
|
|
563
|
+
return;
|
|
496
564
|
default:
|
|
497
565
|
if (!command.startsWith("-")) {
|
|
498
566
|
// Positional template id → default to booting the local editor.
|
|
@@ -669,6 +737,10 @@ async function runServeCommand(argv) {
|
|
|
669
737
|
process.env.PORT = String(port);
|
|
670
738
|
if (!process.env.NODE_ENV)
|
|
671
739
|
process.env.NODE_ENV = "development";
|
|
740
|
+
// Harden any hyperframes tooling the serve runtime (or its children) may
|
|
741
|
+
// shell out to: no telemetry pings, no skill-install prompts.
|
|
742
|
+
process.env.HYPERFRAMES_NO_TELEMETRY ||= "1";
|
|
743
|
+
process.env.HYPERFRAMES_SKIP_SKILLS ||= "1";
|
|
672
744
|
// Cloud passthrough: only the editor is local — /discover and /library keep
|
|
673
745
|
// listing the upstream (cloud) catalog, templates seed onto disk on demand,
|
|
674
746
|
// and the editor's Render button can hand off to the cloud renderer. The
|
|
@@ -2218,6 +2290,7 @@ async function runTtsCommand(argv) {
|
|
|
2218
2290
|
model: { type: "string" },
|
|
2219
2291
|
format: { type: "string" },
|
|
2220
2292
|
out: { type: "string" },
|
|
2293
|
+
engine: { type: "string" },
|
|
2221
2294
|
cloud: { type: "boolean", default: false },
|
|
2222
2295
|
"gemini-key": { type: "string" },
|
|
2223
2296
|
"openai-key": { type: "string" },
|
|
@@ -2230,19 +2303,48 @@ async function runTtsCommand(argv) {
|
|
|
2230
2303
|
if (!text)
|
|
2231
2304
|
throw new Error('tts requires text: vidfarm tts "Welcome back to the channel" --style "calm, warm narrator".');
|
|
2232
2305
|
const style = (parsed.values.instructions ?? parsed.values.style)?.trim() || undefined;
|
|
2233
|
-
const
|
|
2306
|
+
const explicitFormat = parsed.values.format?.trim().toLowerCase();
|
|
2307
|
+
const rawFormat = explicitFormat ?? "mp3";
|
|
2234
2308
|
if (rawFormat !== "mp3" && rawFormat !== "wav")
|
|
2235
2309
|
throw new Error("tts --format must be mp3 or wav.");
|
|
2236
2310
|
const format = rawFormat;
|
|
2237
2311
|
const voice = parsed.values.voice;
|
|
2238
2312
|
const json = Boolean(parsed.values.json);
|
|
2239
|
-
|
|
2313
|
+
// Engine selection: byok (default — the user's own provider key), local
|
|
2314
|
+
// (keyless Kokoro-82M via the bundled hyperframes engine — free, no
|
|
2315
|
+
// account), cloud (the platform primitive job, same as --cloud).
|
|
2316
|
+
const engine = parsed.values.engine?.trim().toLowerCase();
|
|
2317
|
+
if (engine && engine !== "local" && engine !== "byok" && engine !== "cloud") {
|
|
2318
|
+
throw new Error("tts --engine must be local (keyless Kokoro), byok (your provider key — default), or cloud.");
|
|
2319
|
+
}
|
|
2320
|
+
const wantCloud = Boolean(parsed.values.cloud) || engine === "cloud";
|
|
2321
|
+
if (engine === "local") {
|
|
2322
|
+
await runLocalKokoroTts({ text, voice, style, out: parsed.values.out, format: explicitFormat, json });
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
if (!wantCloud) {
|
|
2240
2326
|
// A Gemini voice preset ("Kore", "Puck", …) should find a Gemini-capable
|
|
2241
2327
|
// key first — openrouter's default TTS model is Gemini-family too.
|
|
2242
|
-
const
|
|
2328
|
+
const voiceProvider = inferSpeechProviderForVoice(voice);
|
|
2329
|
+
const keyPreference = voiceProvider === "gemini" ? ["gemini", "openrouter", "openai"] : ["openai", "gemini", "openrouter"];
|
|
2243
2330
|
const auth = resolveLocalSpeechAuth(parsed.values, keyPreference, parsed.values.provider);
|
|
2244
|
-
if (!auth)
|
|
2245
|
-
|
|
2331
|
+
if (!auth) {
|
|
2332
|
+
// EXPLICIT intent (--provider, or a provider voice preset like "alloy"/
|
|
2333
|
+
// "Kore") must never be silently downgraded to Kokoro — hard-error with
|
|
2334
|
+
// the key hint so the user knows the named provider was not used.
|
|
2335
|
+
const requestedProvider = parsed.values.provider?.trim();
|
|
2336
|
+
if (requestedProvider || voiceProvider) {
|
|
2337
|
+
const wanted = requestedProvider ?? `${voiceProvider} (implied by --voice "${voice}")`;
|
|
2338
|
+
throw new Error(`tts: provider ${wanted} was requested but no matching key is configured. ${speechKeyHint("tts")}`);
|
|
2339
|
+
}
|
|
2340
|
+
// Keyless fallback (no explicit provider/voice): rather than erroring
|
|
2341
|
+
// (speechKeyHint), speak through the local Kokoro engine so agents
|
|
2342
|
+
// always get audio out of the box.
|
|
2343
|
+
if (!json)
|
|
2344
|
+
console.log(`${DIM}No provider key found — using the local Kokoro engine; pass --engine cloud + a key for BYOK voices. (${speechKeyHint("tts")})${RESET}`);
|
|
2345
|
+
await runLocalKokoroTts({ text, voice, style, out: parsed.values.out, format: explicitFormat, json, fallback: true });
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2246
2348
|
if (!json)
|
|
2247
2349
|
console.log(`${DIM}Generating speech locally on your ${auth.provider} key…${RESET}`);
|
|
2248
2350
|
const result = await localGenerateSpeech({
|
|
@@ -2312,6 +2414,52 @@ async function runTtsCommand(argv) {
|
|
|
2312
2414
|
console.log(`${GREEN}Wrote ${outPath}${RESET} ${DIM}(${mediaUrl})${RESET}`);
|
|
2313
2415
|
}
|
|
2314
2416
|
}
|
|
2417
|
+
// Keyless local TTS via the bundled hyperframes engine (Kokoro-82M ONNX —
|
|
2418
|
+
// free, no account, no HeyGen). Used by `--engine local` and as the automatic
|
|
2419
|
+
// fallback when no provider key is configured. Kokoro emits WAV natively; an
|
|
2420
|
+
// mp3 request transcodes through the bundled ffmpeg.
|
|
2421
|
+
async function runLocalKokoroTts(input) {
|
|
2422
|
+
const wantMp3 = input.format === "mp3" || /\.mp3(\?|#|$)/i.test(input.out ?? "");
|
|
2423
|
+
// Kokoro voice ids look like af_heart / am_adam / bf_emma. A provider voice
|
|
2424
|
+
// (openai "alloy", gemini "Kore") passed through the fallback path would
|
|
2425
|
+
// just error — drop it to the default with a note instead.
|
|
2426
|
+
let voice = input.voice?.trim() || undefined;
|
|
2427
|
+
if (voice && !/^[a-z]{2}_[a-z0-9]+$/i.test(voice)) {
|
|
2428
|
+
if (!input.json)
|
|
2429
|
+
console.log(`${DIM}Voice "${voice}" is a provider voice — the local Kokoro engine has its own presets (af_heart, af_nova, am_adam, bf_emma, …). Using the default voice.${RESET}`);
|
|
2430
|
+
voice = undefined;
|
|
2431
|
+
}
|
|
2432
|
+
if (input.style && !input.json) {
|
|
2433
|
+
console.log(`${DIM}Note: --style voice direction is BYOK-only; the local Kokoro engine uses fixed voice presets.${RESET}`);
|
|
2434
|
+
}
|
|
2435
|
+
const outPath = path.resolve(process.cwd(), input.out ?? `tts-${Date.now().toString(36)}.${wantMp3 ? "mp3" : "wav"}`);
|
|
2436
|
+
mkdirSync(path.dirname(outPath), { recursive: true });
|
|
2437
|
+
const wavPath = wantMp3 ? path.join(mkdtempSync(path.join(tmpdir(), "vidfarm-tts-")), "speech.wav") : outPath;
|
|
2438
|
+
if (!input.json)
|
|
2439
|
+
console.log(`${DIM}Generating speech with the local Kokoro-82M engine (free, no account)…${RESET}`);
|
|
2440
|
+
const run = await runHyperframesCommand("tts", [input.text, "-o", wavPath, ...(voice ? ["-v", voice] : [])], { stdio: input.json ? "capture" : "inherit" });
|
|
2441
|
+
if (run.code !== 0 || !existsSync(wavPath)) {
|
|
2442
|
+
const detail = run.stderr.trim().split("\n").pop() ?? "";
|
|
2443
|
+
throw new Error(`local Kokoro tts failed (exit ${run.code})${detail ? `: ${detail}` : " — see the engine output above"}. ` +
|
|
2444
|
+
"First use needs `pip install kokoro-onnx soundfile` and downloads the model (~340MB); or pass a provider key for BYOK tts.");
|
|
2445
|
+
}
|
|
2446
|
+
if (wantMp3) {
|
|
2447
|
+
const ffmpeg = await resolveFfmpeg();
|
|
2448
|
+
const transcode = spawnSync(ffmpeg, ["-y", "-hide_banner", "-loglevel", "error", "-i", wavPath, "-b:a", "128k", outPath], { stdio: "ignore" });
|
|
2449
|
+
rmSync(path.dirname(wavPath), { recursive: true, force: true });
|
|
2450
|
+
if (transcode.status !== 0 || !existsSync(outPath)) {
|
|
2451
|
+
throw new Error("ffmpeg mp3 transcode failed after local TTS — pass --format wav to keep the native WAV.");
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
const bytes = safeSize(outPath);
|
|
2455
|
+
if (input.json) {
|
|
2456
|
+
printJson({ ok: true, mode: "local-engine", engine: "kokoro-82m", provider: null, voice: voice ?? "af_heart", fallback: Boolean(input.fallback), content_type: wantMp3 ? "audio/mpeg" : "audio/wav", bytes, out: outPath });
|
|
2457
|
+
}
|
|
2458
|
+
else {
|
|
2459
|
+
console.log(`${GREEN}Wrote ${outPath}${RESET} ${DIM}(local Kokoro-82M — free, no account, ${formatBytes(bytes)})${RESET}`);
|
|
2460
|
+
console.log(`${DIM}Get a shareable URL with: vidfarm upload "${outPath}" — then add it to a composition as an audio layer.${RESET}`);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2315
2463
|
function formatSpeechClock(totalSeconds) {
|
|
2316
2464
|
const minutes = Math.floor(totalSeconds / 60);
|
|
2317
2465
|
const seconds = Math.floor(totalSeconds % 60);
|
|
@@ -2339,7 +2487,8 @@ function emitSttResult(input) {
|
|
|
2339
2487
|
diarization: input.result.diarization,
|
|
2340
2488
|
text: input.result.text,
|
|
2341
2489
|
speakers: [...new Set((input.result.segments ?? []).map((segment) => segment.speaker))],
|
|
2342
|
-
segments: input.result.segments ?? []
|
|
2490
|
+
segments: input.result.segments ?? [],
|
|
2491
|
+
...(input.result.words ? { words: input.result.words } : {})
|
|
2343
2492
|
}, null, 2)}\n`);
|
|
2344
2493
|
written.push(dest);
|
|
2345
2494
|
}
|
|
@@ -2372,6 +2521,7 @@ function emitSttResult(input) {
|
|
|
2372
2521
|
diarization: input.result.diarization,
|
|
2373
2522
|
text: input.result.text,
|
|
2374
2523
|
segments: input.result.segments ?? [],
|
|
2524
|
+
...(input.result.words ? { words: input.result.words } : {}),
|
|
2375
2525
|
srt: input.result.srt,
|
|
2376
2526
|
written
|
|
2377
2527
|
});
|
|
@@ -2402,6 +2552,7 @@ async function runSttCommand(argv) {
|
|
|
2402
2552
|
provider: { type: "string" },
|
|
2403
2553
|
model: { type: "string" },
|
|
2404
2554
|
out: { type: "string" },
|
|
2555
|
+
engine: { type: "string" },
|
|
2405
2556
|
cloud: { type: "boolean", default: false },
|
|
2406
2557
|
"gemini-key": { type: "string" },
|
|
2407
2558
|
"openai-key": { type: "string" },
|
|
@@ -2418,10 +2569,42 @@ async function runSttCommand(argv) {
|
|
|
2418
2569
|
const json = Boolean(parsed.values.json);
|
|
2419
2570
|
const language = parsed.values.language?.trim() || undefined;
|
|
2420
2571
|
const prompt = parsed.values.prompt?.trim() || undefined;
|
|
2421
|
-
|
|
2572
|
+
// Engine selection: byok (default — the user's own provider key), whisper
|
|
2573
|
+
// (keyless local whisper.cpp via the bundled hyperframes engine — word-level
|
|
2574
|
+
// timestamps, free, no account), cloud (the platform primitive job).
|
|
2575
|
+
const engine = parsed.values.engine?.trim().toLowerCase();
|
|
2576
|
+
if (engine && engine !== "whisper" && engine !== "byok" && engine !== "cloud") {
|
|
2577
|
+
throw new Error("stt --engine must be whisper (keyless local whisper.cpp), byok (your provider key — default), or cloud.");
|
|
2578
|
+
}
|
|
2579
|
+
const wantCloud = Boolean(parsed.values.cloud) || engine === "cloud";
|
|
2580
|
+
if (engine === "whisper") {
|
|
2581
|
+
await runLocalWhisperStt({
|
|
2582
|
+
target,
|
|
2583
|
+
out: parsed.values.out,
|
|
2584
|
+
json,
|
|
2585
|
+
language,
|
|
2586
|
+
model: parsed.values.model
|
|
2587
|
+
});
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
if (!wantCloud) {
|
|
2422
2591
|
const auth = resolveLocalSpeechAuth(parsed.values, ["gemini", "openai", "openrouter"], parsed.values.provider);
|
|
2423
|
-
if (!auth)
|
|
2424
|
-
|
|
2592
|
+
if (!auth) {
|
|
2593
|
+
// EXPLICIT intent (--provider) must never be silently downgraded to the
|
|
2594
|
+
// local whisper engine (no speaker labels, different quality) — hard-error
|
|
2595
|
+
// with the key hint so the user knows the named provider was not used.
|
|
2596
|
+
const requestedProvider = parsed.values.provider?.trim();
|
|
2597
|
+
if (requestedProvider) {
|
|
2598
|
+
throw new Error(`stt: provider ${requestedProvider} was requested but no matching key is configured. ${speechKeyHint("stt")}`);
|
|
2599
|
+
}
|
|
2600
|
+
// Keyless fallback (no explicit provider): rather than erroring
|
|
2601
|
+
// (speechKeyHint), transcribe with the local whisper.cpp engine (word
|
|
2602
|
+
// timestamps, no speaker labels).
|
|
2603
|
+
if (!json)
|
|
2604
|
+
console.log(`${DIM}No provider key found — using the local whisper.cpp engine; pass --engine cloud + a key for BYOK transcription (gemini adds speaker labels). (${speechKeyHint("stt")})${RESET}`);
|
|
2605
|
+
await runLocalWhisperStt({ target, out: parsed.values.out, json, language, fallback: true });
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2425
2608
|
if (diarize && auth.provider !== "gemini" && !json) {
|
|
2426
2609
|
console.log(`${DIM}Note: speaker labels need a Gemini key; ${auth.provider} returns a plain single-speaker transcript.${RESET}`);
|
|
2427
2610
|
}
|
|
@@ -2534,6 +2717,108 @@ async function runSttCommand(argv) {
|
|
|
2534
2717
|
}
|
|
2535
2718
|
});
|
|
2536
2719
|
}
|
|
2720
|
+
// Group whisper's word-level timings into subtitle-sized segments (new cue on
|
|
2721
|
+
// a >0.8s silence gap, sentence-ending punctuation, or 12 words) so the
|
|
2722
|
+
// existing SttEmitInput/SRT plumbing consumes whisper output unchanged.
|
|
2723
|
+
function groupWhisperWordsIntoSegments(words) {
|
|
2724
|
+
const segments = [];
|
|
2725
|
+
let current = null;
|
|
2726
|
+
const flush = () => {
|
|
2727
|
+
if (!current || current.parts.length === 0)
|
|
2728
|
+
return;
|
|
2729
|
+
segments.push({
|
|
2730
|
+
speaker: "speaker_1",
|
|
2731
|
+
start_sec: Number(current.start.toFixed(3)),
|
|
2732
|
+
end_sec: Number(current.end.toFixed(3)),
|
|
2733
|
+
text: current.parts.join(" ").replace(/\s+/g, " ").trim()
|
|
2734
|
+
});
|
|
2735
|
+
current = null;
|
|
2736
|
+
};
|
|
2737
|
+
for (const word of words) {
|
|
2738
|
+
if (!word || typeof word.text !== "string" || !Number.isFinite(word.start) || !Number.isFinite(word.end))
|
|
2739
|
+
continue;
|
|
2740
|
+
const endsSentence = current ? /[.!?]["')\]]?$/.test(current.parts[current.parts.length - 1] ?? "") : false;
|
|
2741
|
+
if (current && (word.start - current.end > 0.8 || current.parts.length >= 12 || endsSentence))
|
|
2742
|
+
flush();
|
|
2743
|
+
if (!current)
|
|
2744
|
+
current = { start: word.start, end: word.end, parts: [] };
|
|
2745
|
+
current.parts.push(word.text.trim());
|
|
2746
|
+
current.end = Math.max(current.end, word.end);
|
|
2747
|
+
}
|
|
2748
|
+
flush();
|
|
2749
|
+
return segments;
|
|
2750
|
+
}
|
|
2751
|
+
// Keyless local STT via the bundled hyperframes engine (whisper.cpp — word-level
|
|
2752
|
+
// timestamps, free, no account, no HeyGen). Used by `--engine whisper` and as
|
|
2753
|
+
// the automatic fallback when no provider key is configured. IMPORTANT: the
|
|
2754
|
+
// hyperframes transcribe command writes transcript.json into --dir AND patches
|
|
2755
|
+
// `const script = [...]` blocks in any HTML under that dir, so it always runs
|
|
2756
|
+
// against a fresh temp dir here — never the user's own directory.
|
|
2757
|
+
async function runLocalWhisperStt(input) {
|
|
2758
|
+
let localPath = input.target;
|
|
2759
|
+
let downloadDir = null;
|
|
2760
|
+
if (/^https?:\/\//i.test(input.target)) {
|
|
2761
|
+
downloadDir = mkdtempSync(path.join(tmpdir(), "vidfarm-whisper-dl-"));
|
|
2762
|
+
localPath = path.join(downloadDir, path.basename(new URL(input.target).pathname) || "media.bin");
|
|
2763
|
+
if (!input.json)
|
|
2764
|
+
console.log(`${DIM}Downloading source for the local whisper engine…${RESET}`);
|
|
2765
|
+
await downloadUrlToFile(input.target, localPath);
|
|
2766
|
+
}
|
|
2767
|
+
const absPath = path.resolve(process.cwd(), localPath);
|
|
2768
|
+
if (!existsSync(absPath))
|
|
2769
|
+
throw new Error(`No such local file: ${absPath}`);
|
|
2770
|
+
const workDir = mkdtempSync(path.join(tmpdir(), "vidfarm-whisper-"));
|
|
2771
|
+
try {
|
|
2772
|
+
if (!input.json)
|
|
2773
|
+
console.log(`${DIM}Transcribing with the local whisper.cpp engine (word-level timestamps — free, no account)…${RESET}`);
|
|
2774
|
+
const run = await runHyperframesCommand("transcribe", [
|
|
2775
|
+
absPath,
|
|
2776
|
+
"--dir", workDir,
|
|
2777
|
+
"--json",
|
|
2778
|
+
...(input.language ? ["--language", input.language] : []),
|
|
2779
|
+
...(input.model ? ["--model", input.model] : [])
|
|
2780
|
+
], { stdio: "capture" });
|
|
2781
|
+
const summary = parseHyperframesJson(run.stdout) ?? parseHyperframesJson(run.stderr);
|
|
2782
|
+
if (run.code !== 0 || summary?.ok === false) {
|
|
2783
|
+
const reason = (typeof summary?.error === "string" && summary.error)
|
|
2784
|
+
|| (typeof summary?.reason === "string" && summary.reason)
|
|
2785
|
+
|| run.stderr.trim().split("\n").pop()
|
|
2786
|
+
|| "";
|
|
2787
|
+
throw new Error(`local whisper transcription failed${reason ? `: ${reason}` : ""}. First run downloads whisper.cpp + the model — check network/disk, or pass a provider key for BYOK stt.`);
|
|
2788
|
+
}
|
|
2789
|
+
const transcriptPath = typeof summary?.transcriptPath === "string" && existsSync(summary.transcriptPath)
|
|
2790
|
+
? summary.transcriptPath
|
|
2791
|
+
: path.join(workDir, "transcript.json");
|
|
2792
|
+
if (!existsSync(transcriptPath))
|
|
2793
|
+
throw new Error("local whisper transcription produced no transcript.json.");
|
|
2794
|
+
const words = JSON.parse(readFileSync(transcriptPath, "utf8"))
|
|
2795
|
+
.filter((word) => word && typeof word.text === "string" && Number.isFinite(word.start) && Number.isFinite(word.end))
|
|
2796
|
+
.map((word) => ({ text: String(word.text).trim(), start: Number(word.start), end: Number(word.end) }));
|
|
2797
|
+
const segments = groupWhisperWordsIntoSegments(words);
|
|
2798
|
+
const text = words.map((word) => word.text).join(" ").replace(/\s+/g, " ").trim();
|
|
2799
|
+
emitSttResult({
|
|
2800
|
+
json: input.json,
|
|
2801
|
+
out: input.out,
|
|
2802
|
+
source: input.target,
|
|
2803
|
+
mode: "local",
|
|
2804
|
+
provider: "whisper.cpp",
|
|
2805
|
+
model: (typeof summary?.model === "string" && summary.model) || input.model || "small.en",
|
|
2806
|
+
result: {
|
|
2807
|
+
text,
|
|
2808
|
+
language: input.language ?? null,
|
|
2809
|
+
diarization: "none",
|
|
2810
|
+
segments,
|
|
2811
|
+
srt: buildSrtFromSegments(segments),
|
|
2812
|
+
words
|
|
2813
|
+
}
|
|
2814
|
+
});
|
|
2815
|
+
}
|
|
2816
|
+
finally {
|
|
2817
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
2818
|
+
if (downloadDir)
|
|
2819
|
+
rmSync(downloadDir, { recursive: true, force: true });
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2537
2822
|
async function runPlaceCommand(argv) {
|
|
2538
2823
|
const parsed = parseArgs({
|
|
2539
2824
|
args: argv,
|
|
@@ -3092,6 +3377,238 @@ async function runPutFileCommand(argv) {
|
|
|
3092
3377
|
assertApiOk(result, "put-file");
|
|
3093
3378
|
emitResult(result, ctx.json, [["My Files URL", result.json?.attachment?.viewUrl]]);
|
|
3094
3379
|
}
|
|
3380
|
+
// ── Local media engines + toolchain ─────────────────────────────────────────
|
|
3381
|
+
// Wrappers over the bundled hyperframes LOCAL commands (free, no account, no
|
|
3382
|
+
// HeyGen — see src/devcli/hyperframes-cli.ts for the account/telemetry guard)
|
|
3383
|
+
// plus vidfarm-native lint/stills. These give a local coding agent the full
|
|
3384
|
+
// media toolchain without any cloud credential.
|
|
3385
|
+
// `vidfarm remove-background <file|url>` → transparent WebM (video) or PNG
|
|
3386
|
+
// (image) via the local ONNX matting model.
|
|
3387
|
+
async function runRemoveBackgroundCommand(argv) {
|
|
3388
|
+
const parsed = parseArgs({
|
|
3389
|
+
args: argv,
|
|
3390
|
+
allowPositionals: true,
|
|
3391
|
+
options: {
|
|
3392
|
+
out: { type: "string" },
|
|
3393
|
+
"background-out": { type: "string" },
|
|
3394
|
+
device: { type: "string" },
|
|
3395
|
+
quality: { type: "string" },
|
|
3396
|
+
json: { type: "boolean", default: false }
|
|
3397
|
+
}
|
|
3398
|
+
});
|
|
3399
|
+
const target = parsed.positionals[0];
|
|
3400
|
+
if (!target)
|
|
3401
|
+
throw new Error("remove-background requires a source: `vidfarm remove-background <video-or-image file|url> [--out <path>]`.");
|
|
3402
|
+
const json = Boolean(parsed.values.json);
|
|
3403
|
+
let localPath = target;
|
|
3404
|
+
let downloadDir = null;
|
|
3405
|
+
if (/^https?:\/\//i.test(target)) {
|
|
3406
|
+
downloadDir = mkdtempSync(path.join(tmpdir(), "vidfarm-rmbg-dl-"));
|
|
3407
|
+
localPath = path.join(downloadDir, path.basename(new URL(target).pathname) || "media.bin");
|
|
3408
|
+
if (!json)
|
|
3409
|
+
console.log(`${DIM}Downloading source…${RESET}`);
|
|
3410
|
+
await downloadUrlToFile(target, localPath);
|
|
3411
|
+
}
|
|
3412
|
+
const absPath = path.resolve(process.cwd(), localPath);
|
|
3413
|
+
if (!existsSync(absPath))
|
|
3414
|
+
throw new Error(`No such local file: ${absPath}`);
|
|
3415
|
+
const isImage = /\.(png|jpe?g|webp)(\?|#|$)/i.test(absPath);
|
|
3416
|
+
const stem = path.basename(absPath).replace(/\.[^.]+$/, "");
|
|
3417
|
+
const outPath = path.resolve(process.cwd(), parsed.values.out ?? `${stem}.transparent.${isImage ? "png" : "webm"}`);
|
|
3418
|
+
mkdirSync(path.dirname(outPath), { recursive: true });
|
|
3419
|
+
try {
|
|
3420
|
+
if (!json)
|
|
3421
|
+
console.log(`${DIM}Removing background with the local ONNX matting model (free, no account)…${RESET}`);
|
|
3422
|
+
const run = await runHyperframesCommand("remove-background", [
|
|
3423
|
+
absPath,
|
|
3424
|
+
"-o", outPath,
|
|
3425
|
+
...(parsed.values["background-out"] ? ["--background-output", path.resolve(process.cwd(), String(parsed.values["background-out"]))] : []),
|
|
3426
|
+
...(parsed.values.device ? ["--device", String(parsed.values.device)] : []),
|
|
3427
|
+
...(parsed.values.quality ? ["--quality", String(parsed.values.quality)] : []),
|
|
3428
|
+
...(json ? ["--json"] : [])
|
|
3429
|
+
], { stdio: json ? "capture" : "inherit" });
|
|
3430
|
+
if (run.code !== 0 || !existsSync(outPath)) {
|
|
3431
|
+
const summary = parseHyperframesJson(run.stdout) ?? parseHyperframesJson(run.stderr);
|
|
3432
|
+
const reason = (typeof summary?.error === "string" && summary.error) || run.stderr.trim().split("\n").pop() || "";
|
|
3433
|
+
throw new Error(`remove-background failed (exit ${run.code})${reason ? `: ${reason}` : ""}.`);
|
|
3434
|
+
}
|
|
3435
|
+
if (json) {
|
|
3436
|
+
const summary = parseHyperframesJson(run.stdout);
|
|
3437
|
+
printJson({ ok: true, mode: "local-engine", source: target, out: outPath, bytes: safeSize(outPath), ...(summary ?? {}) });
|
|
3438
|
+
}
|
|
3439
|
+
else {
|
|
3440
|
+
console.log(`${GREEN}Wrote ${outPath}${RESET} ${DIM}(${formatBytes(safeSize(outPath))} — drop it on a composition with \`vidfarm place <dir> --src ${outPath}\`)${RESET}`);
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
finally {
|
|
3444
|
+
if (downloadDir)
|
|
3445
|
+
rmSync(downloadDir, { recursive: true, force: true });
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
// `vidfarm capture <url>` → screenshots + assets + video manifest of a website
|
|
3449
|
+
// via local headless Chrome (for website-to-video style workflows).
|
|
3450
|
+
async function runCaptureCommand(argv) {
|
|
3451
|
+
const parsed = parseArgs({
|
|
3452
|
+
args: argv,
|
|
3453
|
+
allowPositionals: true,
|
|
3454
|
+
options: {
|
|
3455
|
+
out: { type: "string" },
|
|
3456
|
+
"max-screenshots": { type: "string" },
|
|
3457
|
+
timeout: { type: "string" },
|
|
3458
|
+
"skip-assets": { type: "boolean", default: false },
|
|
3459
|
+
json: { type: "boolean", default: false }
|
|
3460
|
+
}
|
|
3461
|
+
});
|
|
3462
|
+
const url = parsed.positionals[0];
|
|
3463
|
+
if (!url || !/^https?:\/\//i.test(url))
|
|
3464
|
+
throw new Error("capture requires a website URL: `vidfarm capture <url> [--out <dir>]`.");
|
|
3465
|
+
const json = Boolean(parsed.values.json);
|
|
3466
|
+
const outDir = parsed.values.out ? path.resolve(process.cwd(), String(parsed.values.out)) : undefined;
|
|
3467
|
+
const run = await runHyperframesCommand("capture", [
|
|
3468
|
+
url,
|
|
3469
|
+
...(outDir ? ["-o", outDir] : []),
|
|
3470
|
+
...(parsed.values["max-screenshots"] ? ["--max-screenshots", String(parsed.values["max-screenshots"])] : []),
|
|
3471
|
+
...(parsed.values.timeout ? ["--timeout", String(parsed.values.timeout)] : []),
|
|
3472
|
+
...(parsed.values["skip-assets"] ? ["--skip-assets"] : []),
|
|
3473
|
+
...(json ? ["--json"] : [])
|
|
3474
|
+
], { stdio: json ? "capture" : "inherit" });
|
|
3475
|
+
if (json) {
|
|
3476
|
+
const summary = parseHyperframesJson(run.stdout);
|
|
3477
|
+
printJson(summary ?? { ok: run.code === 0, out: outDir ?? null });
|
|
3478
|
+
}
|
|
3479
|
+
if (run.code !== 0) {
|
|
3480
|
+
process.exitCode = 1;
|
|
3481
|
+
if (!json)
|
|
3482
|
+
console.log(`${RED}capture failed (exit ${run.code}).${RESET}`);
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
// `vidfarm beats <audioFile>` → beat-timestamp JSON an agent can feed into
|
|
3486
|
+
// transition/caption timing. The hyperframes beats command analyzes a PROJECT
|
|
3487
|
+
// dir's music track, so stage a throwaway project around the audio file.
|
|
3488
|
+
async function runBeatsCommand(argv) {
|
|
3489
|
+
const parsed = parseArgs({
|
|
3490
|
+
args: argv,
|
|
3491
|
+
allowPositionals: true,
|
|
3492
|
+
options: {
|
|
3493
|
+
out: { type: "string" },
|
|
3494
|
+
json: { type: "boolean", default: false }
|
|
3495
|
+
}
|
|
3496
|
+
});
|
|
3497
|
+
const target = parsed.positionals[0];
|
|
3498
|
+
if (!target)
|
|
3499
|
+
throw new Error("beats requires an audio file: `vidfarm beats <audioFile> [--out <path>]`.");
|
|
3500
|
+
const json = Boolean(parsed.values.json);
|
|
3501
|
+
let localPath = target;
|
|
3502
|
+
let downloadDir = null;
|
|
3503
|
+
if (/^https?:\/\//i.test(target)) {
|
|
3504
|
+
downloadDir = mkdtempSync(path.join(tmpdir(), "vidfarm-beats-dl-"));
|
|
3505
|
+
localPath = path.join(downloadDir, path.basename(new URL(target).pathname) || "audio.mp3");
|
|
3506
|
+
if (!json)
|
|
3507
|
+
console.log(`${DIM}Downloading audio…${RESET}`);
|
|
3508
|
+
await downloadUrlToFile(target, localPath);
|
|
3509
|
+
}
|
|
3510
|
+
const absPath = path.resolve(process.cwd(), localPath);
|
|
3511
|
+
if (!existsSync(absPath))
|
|
3512
|
+
throw new Error(`No such local file: ${absPath}`);
|
|
3513
|
+
const audioName = path.basename(absPath);
|
|
3514
|
+
const stem = audioName.replace(/\.[^.]+$/, "");
|
|
3515
|
+
const projectDir = mkdtempSync(path.join(tmpdir(), "vidfarm-beats-"));
|
|
3516
|
+
try {
|
|
3517
|
+
copyFileSync(absPath, path.join(projectDir, audioName));
|
|
3518
|
+
// Minimal project whose music track is the given audio file (the beats
|
|
3519
|
+
// command locates it via data-timeline-role="music").
|
|
3520
|
+
writeFileSync(path.join(projectDir, "index.html"), `<!doctype html>\n<html><body data-composition-id="vidfarm-beats" data-width="720" data-height="1280" data-duration="1">` +
|
|
3521
|
+
`<audio id="music" data-timeline-role="music" src="${audioName}" data-start="0" data-track-index="0"></audio>` +
|
|
3522
|
+
`</body></html>\n`, "utf8");
|
|
3523
|
+
if (!json)
|
|
3524
|
+
console.log(`${DIM}Detecting beats locally (free, no account)…${RESET}`);
|
|
3525
|
+
const run = await runHyperframesCommand("beats", [projectDir, "--json"], { stdio: "capture" });
|
|
3526
|
+
const beatsDir = path.join(projectDir, "beats");
|
|
3527
|
+
const beatsFile = existsSync(beatsDir)
|
|
3528
|
+
? readdirSync(beatsDir).filter((name) => name.toLowerCase().endsWith(".json")).map((name) => path.join(beatsDir, name))[0]
|
|
3529
|
+
: undefined;
|
|
3530
|
+
if (run.code !== 0 || !beatsFile) {
|
|
3531
|
+
const summary = parseHyperframesJson(run.stdout) ?? parseHyperframesJson(run.stderr);
|
|
3532
|
+
const reason = (typeof summary?.error === "string" && summary.error) || run.stderr.trim().split("\n").pop() || run.stdout.trim().split("\n").pop() || "";
|
|
3533
|
+
throw new Error(`beat detection failed (exit ${run.code})${reason ? `: ${reason}` : ""}.`);
|
|
3534
|
+
}
|
|
3535
|
+
const beatsJson = JSON.parse(readFileSync(beatsFile, "utf8"));
|
|
3536
|
+
const outPath = path.resolve(process.cwd(), parsed.values.out ?? `${stem}.beats.json`);
|
|
3537
|
+
mkdirSync(path.dirname(outPath), { recursive: true });
|
|
3538
|
+
writeFileSync(outPath, `${JSON.stringify(beatsJson, null, 2)}\n`, "utf8");
|
|
3539
|
+
const beatCount = Array.isArray(beatsJson?.beats) ? beatsJson.beats.length : Array.isArray(beatsJson) ? beatsJson.length : null;
|
|
3540
|
+
if (json) {
|
|
3541
|
+
printJson({ ok: true, mode: "local-engine", source: target, out: outPath, beat_count: beatCount, beats: beatsJson });
|
|
3542
|
+
}
|
|
3543
|
+
else {
|
|
3544
|
+
console.log(`${GREEN}Wrote ${outPath}${RESET} ${DIM}(${beatCount ?? "?"} beats — sync transitions/captions to these timestamps)${RESET}`);
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
finally {
|
|
3548
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
3549
|
+
if (downloadDir)
|
|
3550
|
+
rmSync(downloadDir, { recursive: true, force: true });
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
// `vidfarm lint <dir|composition.html>` — static composition validation (the
|
|
3554
|
+
// same checks the /editor chat runs before accepting replacement HTML).
|
|
3555
|
+
async function runLintCommand(argv) {
|
|
3556
|
+
const parsed = parseArgs({
|
|
3557
|
+
args: argv,
|
|
3558
|
+
allowPositionals: true,
|
|
3559
|
+
options: { json: { type: "boolean", default: false } }
|
|
3560
|
+
});
|
|
3561
|
+
const target = parsed.positionals[0];
|
|
3562
|
+
if (!target)
|
|
3563
|
+
throw new Error("lint requires a composition path: `vidfarm lint <dir-or-composition.html>`.");
|
|
3564
|
+
const htmlPath = resolveCompositionHtmlPath(target);
|
|
3565
|
+
const result = lintCompositionHtml(readFileSync(htmlPath, "utf8"));
|
|
3566
|
+
if (parsed.values.json) {
|
|
3567
|
+
printJson(result);
|
|
3568
|
+
if (!result.ok)
|
|
3569
|
+
process.exitCode = 1;
|
|
3570
|
+
return;
|
|
3571
|
+
}
|
|
3572
|
+
const formatted = formatCompositionLintIssues(result);
|
|
3573
|
+
if (formatted)
|
|
3574
|
+
console.log(formatted);
|
|
3575
|
+
const mark = result.ok ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;
|
|
3576
|
+
console.log(`${mark} ${result.errors.length} error(s), ${result.warnings.length} warning(s) ${DIM}— ${htmlPath}${RESET}`);
|
|
3577
|
+
if (!result.ok)
|
|
3578
|
+
process.exitCode = 1;
|
|
3579
|
+
}
|
|
3580
|
+
// `vidfarm stills <dir|composition.html>` — render PNG stills of the CURRENT
|
|
3581
|
+
// composition in-process (the agent's visual self-verification loop).
|
|
3582
|
+
async function runStillsCommand(argv) {
|
|
3583
|
+
const parsed = parseArgs({
|
|
3584
|
+
args: argv,
|
|
3585
|
+
allowPositionals: true,
|
|
3586
|
+
options: {
|
|
3587
|
+
at: { type: "string" },
|
|
3588
|
+
out: { type: "string" },
|
|
3589
|
+
json: { type: "boolean", default: false }
|
|
3590
|
+
}
|
|
3591
|
+
});
|
|
3592
|
+
const target = parsed.positionals[0];
|
|
3593
|
+
if (!target)
|
|
3594
|
+
throw new Error("stills requires a composition path: `vidfarm stills <dir-or-composition.html> [--at 0,2.5,7] [--out <dir>]`.");
|
|
3595
|
+
const htmlPath = resolveCompositionHtmlPath(target);
|
|
3596
|
+
const json = Boolean(parsed.values.json);
|
|
3597
|
+
const at = parsed.values.at
|
|
3598
|
+
? String(parsed.values.at).split(",").map((value) => parseTimeToSeconds(value.trim())).filter((value) => Number.isFinite(value))
|
|
3599
|
+
: undefined;
|
|
3600
|
+
if (!json)
|
|
3601
|
+
console.log(`${DIM}Rendering stills in-process (free, local Chrome capture)…${RESET}`);
|
|
3602
|
+
const result = await renderCompositionStills({ htmlPath, at, outDir: parsed.values.out, quiet: json });
|
|
3603
|
+
if (json) {
|
|
3604
|
+
printJson(result);
|
|
3605
|
+
return;
|
|
3606
|
+
}
|
|
3607
|
+
for (const still of result.stills) {
|
|
3608
|
+
console.log(` ${GREEN}${still.path}${RESET} ${DIM}(requested ${still.requested_sec}s → captured ${still.captured_sec}s)${RESET}`);
|
|
3609
|
+
}
|
|
3610
|
+
console.log(`${DIM}${result.stills.length} still(s) at ${result.grid_fps}fps grid accuracy — open them to verify the edit before rendering.${RESET}`);
|
|
3611
|
+
}
|
|
3095
3612
|
// ── Agent skill ───────────────────────────────────────────────────────────────
|
|
3096
3613
|
// Install the latest director skill onto disk as a Claude Code / agent skill so
|
|
3097
3614
|
// the user's AI agent can read SKILL.director.md and act. We pull the freshest
|