@mevdragon/vidfarm-devcli 0.13.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 +60 -4
- package/SKILL.platform.md +7 -2
- package/demo/dist/app.css +1 -1
- package/demo/dist/app.js +485 -86
- package/dist/src/account-pages-legacy.js +9 -14
- package/dist/src/app.js +659 -159
- package/dist/src/cli.js +618 -17
- package/dist/src/composition-runtime.js +286 -6
- package/dist/src/devcli/composition-edit.js +176 -8
- 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/devcli/transitions.js +205 -0
- package/dist/src/editor-chat.js +260 -4
- package/dist/src/frontend/template-editor-chat.js +6 -5
- package/dist/src/homepage.js +2 -48
- package/dist/src/hyperframes/composition.js +188 -7
- package/dist/src/services/composition-lint.js +199 -0
- package/dist/src/services/hyperframes.js +127 -28
- package/dist/src/services/serverless-records.js +7 -0
- package/dist/src/template-editor-shell.js +87 -48
- package/package.json +2 -1
- package/public/assets/page-runtime-client-app.js +31 -31
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// audio.mjs — vidfarm-media shared audio ENGINE: TTS + word timings + BGM + SFX.
|
|
3
|
+
// The workflow skills (faceless-explainer, product-launch-video) call this via
|
|
4
|
+
// thin adapters; general-video calls it directly. Everything resolves on the
|
|
5
|
+
// user's own machine or the user's own provider keys — no third-party media
|
|
6
|
+
// account, ever. The keyless local path (Kokoro TTS + whisper.cpp timings)
|
|
7
|
+
// must always work with zero keys.
|
|
8
|
+
//
|
|
9
|
+
// USAGE
|
|
10
|
+
// node audio.mjs --request ./audio_request.json --hyperframes . --out ./audio_meta.json \
|
|
11
|
+
// [--only tts,bgm,sfx] [--no-transcribe]
|
|
12
|
+
//
|
|
13
|
+
// --request neutral request JSON (schema below). Required.
|
|
14
|
+
// --hyperframes project dir; all output paths in the meta are RELATIVE to it. Default ".".
|
|
15
|
+
// --only comma list of sections to (re)compute: tts, bgm, sfx. Default: all.
|
|
16
|
+
// Sections NOT listed are PRESERVED from an existing --out meta —
|
|
17
|
+
// `--only sfx` after `--only tts,bgm` merges, never overwrites.
|
|
18
|
+
// --no-transcribe skip whisper word timings; estimate words instead (also $VF_MEDIA_NO_TRANSCRIBE=1).
|
|
19
|
+
// --out meta JSON path. Default <hyperframes>/audio_meta.json.
|
|
20
|
+
//
|
|
21
|
+
// audio_request.json
|
|
22
|
+
// {
|
|
23
|
+
// "provider": "auto" | "local" | "openai" | "gemini" | "openrouter", // default auto (= local Kokoro)
|
|
24
|
+
// "voice": "af_heart" | "alloy" | "Kore" | ..., // Kokoro voice id, or a cloud preset
|
|
25
|
+
// "style": "calm, warm narrator", // cloud only — promptable voice direction
|
|
26
|
+
// "speed": 1.0, // Kokoro only
|
|
27
|
+
// "lines": [ { "id": "01", "text": "Spoken line.", "sfx": ["whoosh"] } ],
|
|
28
|
+
// "bgm": {
|
|
29
|
+
// "mode": "retrieve" | "none", // omitted = retrieve; there is NO generate mode
|
|
30
|
+
// "query": "calm cinematic underscore",
|
|
31
|
+
// "file": "./music/track.mp3", // explicit local file wins
|
|
32
|
+
// "url": "https://.../track.mp3", // ...or an explicit URL
|
|
33
|
+
// "dir": "./music", // ...or a local music dir (also $MUSIC_DIR)
|
|
34
|
+
// "volume": 0.8 // optional; omitted = assembler default
|
|
35
|
+
// },
|
|
36
|
+
// "sfx_dir": "./sfx" // optional local SFX dir (also $SFX_DIR)
|
|
37
|
+
// }
|
|
38
|
+
// `id` is opaque and joins the meta back to the caller's model. Lines may carry
|
|
39
|
+
// only `sfx` (no text) — that is how adapters fetch SFX after narration exists.
|
|
40
|
+
//
|
|
41
|
+
// Output meta (--out), id-keyed:
|
|
42
|
+
// {
|
|
43
|
+
// "voices": [ { "id": "01", "path": "assets/voice/01.wav", "duration_s": 4.21,
|
|
44
|
+
// "words": [ { "id": "w0", "text": "Spoken", "start": 0.02, "end": 0.31 } ] } ],
|
|
45
|
+
// "bgm": { "path": "assets/bgm/track.mp3", "query": "...", "duration_s": 22.4 } | null,
|
|
46
|
+
// "sfx": [ { "id": "01", "file": "assets/sfx/whoosh.mp3", "offset_s": 0,
|
|
47
|
+
// "duration_s": 0.9, "volume": 0.35 } ],
|
|
48
|
+
// "notes": [ "bgm: skipped — ..." ]
|
|
49
|
+
// }
|
|
50
|
+
// BGM is NOT loop-extended here: assemble-index loop-extends short beds at
|
|
51
|
+
// mount time. `bgm_pending` is never set — there is no detached generate step.
|
|
52
|
+
//
|
|
53
|
+
// ENGINES
|
|
54
|
+
// TTS keyless local Kokoro-82M via `hyperframes tts` (DEFAULT — zero keys).
|
|
55
|
+
// Cloud `vidfarm tts` (promptable voice, BYOK) only when the request
|
|
56
|
+
// names a cloud voice/provider AND a provider key is in the env AND
|
|
57
|
+
// the vidfarm CLI is on PATH; falls back to Kokoro with a note.
|
|
58
|
+
// Timings `hyperframes transcribe` (local whisper.cpp, word timestamps);
|
|
59
|
+
// on any failure, words are estimated by proportional character
|
|
60
|
+
// weighting across the line's duration. Never fatal.
|
|
61
|
+
// BGM request file/url → My Files (`vidfarm files --search`, needs
|
|
62
|
+
// $VIDFARM_API_KEY) → local music dir keyword match → skip w/ note.
|
|
63
|
+
// SFX local sfx dir ($SFX_DIR / request sfx_dir / skill assets/sfx) →
|
|
64
|
+
// My Files search → skip that cue w/ note.
|
|
65
|
+
|
|
66
|
+
import { spawnSync } from "node:child_process";
|
|
67
|
+
import {
|
|
68
|
+
copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync,
|
|
69
|
+
statSync, writeFileSync
|
|
70
|
+
} from "node:fs";
|
|
71
|
+
import { createRequire } from "node:module";
|
|
72
|
+
import { tmpdir } from "node:os";
|
|
73
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
74
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
75
|
+
|
|
76
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
77
|
+
const SKILL_DIR = dirname(HERE);
|
|
78
|
+
const AUDIO_EXT = new Set([".mp3", ".wav", ".m4a", ".aac", ".ogg", ".flac"]);
|
|
79
|
+
const r3 = (n) => Math.round(n * 1000) / 1000;
|
|
80
|
+
|
|
81
|
+
// Known cloud voice presets (mirrors vidfarm's speech service). A voice from
|
|
82
|
+
// one of these families signals intent to use the BYOK cloud path.
|
|
83
|
+
const OPENAI_VOICES = new Set([
|
|
84
|
+
"alloy", "ash", "ballad", "cedar", "coral", "echo", "fable", "marin",
|
|
85
|
+
"nova", "onyx", "sage", "shimmer", "verse"
|
|
86
|
+
]);
|
|
87
|
+
const GEMINI_VOICES = new Set([
|
|
88
|
+
"achernar", "achird", "algenib", "algieba", "alnilam", "aoede", "autonoe",
|
|
89
|
+
"callirrhoe", "charon", "despina", "enceladus", "erinome", "fenrir", "gacrux",
|
|
90
|
+
"iapetus", "kore", "laomedeia", "leda", "orus", "puck", "pulcherrima",
|
|
91
|
+
"rasalgethi", "sadachbia", "sadaltager", "schedar", "sulafat", "umbriel",
|
|
92
|
+
"vindemiatrix", "zephyr", "zubenelgenubi"
|
|
93
|
+
]);
|
|
94
|
+
const KOKORO_VOICE_RE = /^[a-z]{2}_[a-z0-9]+$/;
|
|
95
|
+
|
|
96
|
+
// ── tiny CLI plumbing ─────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
const HELP = `vidfarm-media audio engine — TTS + word timings + BGM + SFX, no media accounts.
|
|
99
|
+
|
|
100
|
+
Usage:
|
|
101
|
+
node audio.mjs --request <audio_request.json> [--hyperframes <dir>] [--out <meta.json>]
|
|
102
|
+
[--only tts,bgm,sfx] [--no-transcribe]
|
|
103
|
+
|
|
104
|
+
Flags:
|
|
105
|
+
--request <path> Neutral request JSON (required). Schema: header comment of this file.
|
|
106
|
+
--hyperframes <d> Project dir; meta paths are relative to it (default ".").
|
|
107
|
+
--out <path> Output meta JSON (default <hyperframes>/audio_meta.json).
|
|
108
|
+
--only <list> Recompute only these sections (tts, bgm, sfx); others are
|
|
109
|
+
preserved from an existing --out meta. Default: all three.
|
|
110
|
+
--no-transcribe Skip whisper word timings; estimate by character weighting.
|
|
111
|
+
-h, --help This text.
|
|
112
|
+
|
|
113
|
+
Engines: local Kokoro TTS (\`hyperframes tts\`, zero keys, default) · cloud
|
|
114
|
+
\`vidfarm tts\` when a cloud voice/provider is requested and a key exists ·
|
|
115
|
+
whisper.cpp word timings (\`hyperframes transcribe\`) with estimation fallback ·
|
|
116
|
+
BGM/SFX from user files, $MUSIC_DIR/$SFX_DIR, or My Files search ($VIDFARM_API_KEY).`;
|
|
117
|
+
|
|
118
|
+
const argv = process.argv.slice(2);
|
|
119
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
120
|
+
console.log(HELP);
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
const flag = (name, def) => {
|
|
124
|
+
const i = argv.indexOf(`--${name}`);
|
|
125
|
+
return i >= 0 && i + 1 < argv.length ? argv[i + 1] : def;
|
|
126
|
+
};
|
|
127
|
+
const die = (m) => {
|
|
128
|
+
console.error(`✗ media audio: ${m}`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const requestPath = flag("request", null);
|
|
133
|
+
if (!requestPath) die("--request <audio_request.json> is required (see --help)");
|
|
134
|
+
const hyperframesDir = resolve(flag("hyperframes", "."));
|
|
135
|
+
const outPath = resolve(flag("out", join(hyperframesDir, "audio_meta.json")));
|
|
136
|
+
const only = new Set(
|
|
137
|
+
(flag("only", "tts,bgm,sfx") || "tts,bgm,sfx").split(",").map((s) => s.trim()).filter(Boolean)
|
|
138
|
+
);
|
|
139
|
+
const noTranscribe = argv.includes("--no-transcribe") || process.env.VF_MEDIA_NO_TRANSCRIBE === "1";
|
|
140
|
+
|
|
141
|
+
if (!existsSync(requestPath)) die(`request not found at ${requestPath}`);
|
|
142
|
+
let request;
|
|
143
|
+
try {
|
|
144
|
+
request = JSON.parse(readFileSync(requestPath, "utf8"));
|
|
145
|
+
} catch (e) {
|
|
146
|
+
die(`request parse: ${e.message}`);
|
|
147
|
+
}
|
|
148
|
+
const lines = Array.isArray(request.lines) ? request.lines : [];
|
|
149
|
+
|
|
150
|
+
// ── spawn helpers ─────────────────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
// Env for every hyperframes CLI spawn: telemetry off, skill self-update off,
|
|
153
|
+
// and HEYGEN_CONFIG_DIR pointed at a fresh empty dir so the CLI can never pick
|
|
154
|
+
// up a stray credential and silently prefer a non-local voice path.
|
|
155
|
+
let scrubbedEnvCache = null;
|
|
156
|
+
function scrubbedEnv() {
|
|
157
|
+
if (!scrubbedEnvCache) {
|
|
158
|
+
scrubbedEnvCache = {
|
|
159
|
+
...process.env,
|
|
160
|
+
HYPERFRAMES_NO_TELEMETRY: "1",
|
|
161
|
+
HYPERFRAMES_SKIP_SKILLS: "1",
|
|
162
|
+
HEYGEN_CONFIG_DIR: mkdtempSync(join(tmpdir(), "vf-media-empty-"))
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return scrubbedEnvCache;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Prefer a locally installed hyperframes (resolved from this script's dir, then
|
|
169
|
+
// the project dir, then cwd) over `npx -y hyperframes` — faster, offline-safe.
|
|
170
|
+
let hfCliCache = null;
|
|
171
|
+
function hyperframesCli() {
|
|
172
|
+
if (hfCliCache) return hfCliCache;
|
|
173
|
+
const bases = [
|
|
174
|
+
import.meta.url,
|
|
175
|
+
pathToFileURL(join(hyperframesDir, "__resolve__.js")).href,
|
|
176
|
+
pathToFileURL(join(process.cwd(), "__resolve__.js")).href
|
|
177
|
+
];
|
|
178
|
+
for (const base of bases) {
|
|
179
|
+
try {
|
|
180
|
+
const cliJs = createRequire(base).resolve("hyperframes/dist/cli.js");
|
|
181
|
+
hfCliCache = { cmd: process.execPath, prefix: [cliJs] };
|
|
182
|
+
return hfCliCache;
|
|
183
|
+
} catch { /* keep walking */ }
|
|
184
|
+
}
|
|
185
|
+
hfCliCache = { cmd: "npx", prefix: ["-y", "hyperframes"] };
|
|
186
|
+
return hfCliCache;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function hasCommand(name) {
|
|
190
|
+
const probe = process.platform === "win32" ? "where" : "which";
|
|
191
|
+
try {
|
|
192
|
+
return spawnSync(probe, [name], { stdio: "ignore" }).status === 0;
|
|
193
|
+
} catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Extract JSON from captured stdout, tolerating BOTH shapes we spawn:
|
|
199
|
+
// the vidfarm devcli pretty-prints MULTI-LINE JSON (JSON.stringify(v, null, 2))
|
|
200
|
+
// while the hyperframes CLI prints a single JSON line after log lines. Try:
|
|
201
|
+
// (1) the whole trimmed stdout, (2) the first '{'/'[' through the last
|
|
202
|
+
// '}'/']' (pretty JSON wrapped in log noise), (3) line-by-line, last first.
|
|
203
|
+
function extractJson(out) {
|
|
204
|
+
if (!out) return null;
|
|
205
|
+
try {
|
|
206
|
+
return JSON.parse(out);
|
|
207
|
+
} catch { /* not bare JSON */ }
|
|
208
|
+
const starts = [out.indexOf("{"), out.indexOf("[")].filter((i) => i >= 0);
|
|
209
|
+
const first = starts.length ? Math.min(...starts) : -1;
|
|
210
|
+
const last = Math.max(out.lastIndexOf("}"), out.lastIndexOf("]"));
|
|
211
|
+
if (first >= 0 && last > first) {
|
|
212
|
+
try {
|
|
213
|
+
return JSON.parse(out.slice(first, last + 1));
|
|
214
|
+
} catch { /* not a clean block */ }
|
|
215
|
+
}
|
|
216
|
+
for (const line of out.split(/\r?\n/).reverse()) {
|
|
217
|
+
const t = line.trim();
|
|
218
|
+
if (!t.startsWith("{") && !t.startsWith("[")) continue;
|
|
219
|
+
try {
|
|
220
|
+
return JSON.parse(t);
|
|
221
|
+
} catch { /* not this line */ }
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Run a command, capture stdout, extract whatever JSON it printed (see extractJson).
|
|
227
|
+
function runJson(cmd, args, env) {
|
|
228
|
+
const r = spawnSync(cmd, args, { encoding: "utf8", env, maxBuffer: 64 * 1024 * 1024 });
|
|
229
|
+
const out = (r.stdout ?? "").trim();
|
|
230
|
+
return { status: r.status, json: extractJson(out), stdout: out, stderr: (r.stderr ?? "").trim(), error: r.error };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ── duration probing (never required, never fatal) ────────────────────────────
|
|
234
|
+
|
|
235
|
+
function wavDurationSeconds(absPath) {
|
|
236
|
+
try {
|
|
237
|
+
const buf = readFileSync(absPath);
|
|
238
|
+
if (buf.length < 44 || buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WAVE") return null;
|
|
239
|
+
let pos = 12;
|
|
240
|
+
let byteRate = 0;
|
|
241
|
+
let dataSize = 0;
|
|
242
|
+
while (pos + 8 <= buf.length) {
|
|
243
|
+
const id = buf.toString("ascii", pos, pos + 4);
|
|
244
|
+
const size = buf.readUInt32LE(pos + 4);
|
|
245
|
+
if (id === "fmt ") byteRate = buf.readUInt32LE(pos + 16);
|
|
246
|
+
if (id === "data") dataSize = size;
|
|
247
|
+
pos += 8 + size + (size % 2);
|
|
248
|
+
}
|
|
249
|
+
return byteRate > 0 && dataSize > 0 ? dataSize / byteRate : null;
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function probeDurationSeconds(absPath) {
|
|
256
|
+
const ffprobe = process.env.HYPERFRAMES_FFPROBE_PATH || process.env.FFPROBE_PATH || "ffprobe";
|
|
257
|
+
try {
|
|
258
|
+
const r = spawnSync(ffprobe, ["-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", absPath], { encoding: "utf8" });
|
|
259
|
+
if (r.status === 0) {
|
|
260
|
+
const dur = parseFloat((r.stdout ?? "").trim());
|
|
261
|
+
if (Number.isFinite(dur) && dur > 0) return dur;
|
|
262
|
+
}
|
|
263
|
+
} catch { /* no ffprobe */ }
|
|
264
|
+
if (extname(absPath).toLowerCase() === ".wav") return wavDurationSeconds(absPath);
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ── word timings ──────────────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
// Estimation fallback: spread words across the audio duration by proportional
|
|
271
|
+
// character weighting (the same approach vidfarm's caption system uses when a
|
|
272
|
+
// provider returns no word timestamps).
|
|
273
|
+
function estimateWords(text, durationS) {
|
|
274
|
+
const tokens = text.split(/\s+/).filter(Boolean);
|
|
275
|
+
if (!tokens.length) return [];
|
|
276
|
+
const weights = tokens.map((t) => Math.max(1, t.replace(/[^\p{L}\p{N}]/gu, "").length));
|
|
277
|
+
const total = weights.reduce((a, b) => a + b, 0);
|
|
278
|
+
const words = [];
|
|
279
|
+
let cursor = 0;
|
|
280
|
+
tokens.forEach((t, i) => {
|
|
281
|
+
const span = (durationS * weights[i]) / total;
|
|
282
|
+
words.push({ id: `w${i}`, text: t, start: r3(cursor), end: r3(cursor + span) });
|
|
283
|
+
cursor += span;
|
|
284
|
+
});
|
|
285
|
+
return words;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Real timings via `hyperframes transcribe` (local whisper.cpp). Runs against
|
|
289
|
+
// an ISOLATED temp dir — transcribe patches caption HTML under --dir, so it
|
|
290
|
+
// must never see the project. Any failure → estimation, never a crash.
|
|
291
|
+
function transcribeWords(absAudioPath, text, durationS, notes, lineId) {
|
|
292
|
+
if (noTranscribe) return estimateWords(text, durationS);
|
|
293
|
+
const { cmd, prefix } = hyperframesCli();
|
|
294
|
+
const dir = mkdtempSync(join(tmpdir(), "vf-media-stt-"));
|
|
295
|
+
const r = runJson(cmd, [...prefix, "transcribe", absAudioPath, "--dir", dir, "--json", "--optional"], scrubbedEnv());
|
|
296
|
+
if (r.json?.ok && r.json.transcriptPath && existsSync(r.json.transcriptPath)) {
|
|
297
|
+
try {
|
|
298
|
+
const words = JSON.parse(readFileSync(r.json.transcriptPath, "utf8"));
|
|
299
|
+
if (Array.isArray(words) && words.length) {
|
|
300
|
+
return words.map((w, i) => ({
|
|
301
|
+
id: w.id || `w${i}`,
|
|
302
|
+
text: String(w.text ?? "").trim(),
|
|
303
|
+
start: r3(Number(w.start) || 0),
|
|
304
|
+
end: r3(Number(w.end) || 0)
|
|
305
|
+
})).filter((w) => w.text);
|
|
306
|
+
}
|
|
307
|
+
} catch { /* fall through to estimate */ }
|
|
308
|
+
}
|
|
309
|
+
notes.push(`tts: line ${lineId} — whisper timings unavailable (${r.json?.reason || r.stderr.slice(0, 120) || "no output"}); estimated by character weighting`);
|
|
310
|
+
return estimateWords(text, durationS);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ── TTS ───────────────────────────────────────────────────────────────────────
|
|
314
|
+
|
|
315
|
+
const sanitize = (s) => String(s).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "line";
|
|
316
|
+
|
|
317
|
+
// Env keys per cloud provider (mirrors the devcli's resolveLocalSpeechAuth).
|
|
318
|
+
// synthesizeCloud passes --provider explicitly, so ONLY a key for THAT provider
|
|
319
|
+
// counts — a stray GEMINI key must not green-light an openai voice (that made
|
|
320
|
+
// every line fail slowly in the cloud path and fall back one by one).
|
|
321
|
+
const TTS_KEY_ENV = {
|
|
322
|
+
openai: ["OPENAI_API_KEY"],
|
|
323
|
+
gemini: ["GEMINI_API_KEY", "GOOGLE_GENERATIVE_AI_API_KEY"],
|
|
324
|
+
openrouter: ["OPENROUTER_API_KEY"]
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
function pickTtsEngine(notes) {
|
|
328
|
+
const provider = String(request.provider ?? "auto").toLowerCase();
|
|
329
|
+
const voice = typeof request.voice === "string" ? request.voice.trim() : "";
|
|
330
|
+
const voiceKey = voice.toLowerCase();
|
|
331
|
+
if (provider === "local" || provider === "kokoro") return { kind: "local" };
|
|
332
|
+
const cloudProvider = ["openai", "gemini", "openrouter"].includes(provider)
|
|
333
|
+
? provider
|
|
334
|
+
: OPENAI_VOICES.has(voiceKey) ? "openai" : GEMINI_VOICES.has(voiceKey) ? "gemini" : null;
|
|
335
|
+
if (!cloudProvider) return { kind: "local" }; // auto + no cloud voice → keyless local
|
|
336
|
+
const hasKey = (TTS_KEY_ENV[cloudProvider] ?? []).some((name) => Boolean(process.env[name]?.trim()));
|
|
337
|
+
if (hasKey && hasCommand("vidfarm")) return { kind: "cloud", provider: cloudProvider };
|
|
338
|
+
notes.push(`tts: cloud voice/provider requested (${voice || cloudProvider}) but ${hasKey ? "no vidfarm CLI on PATH" : `no ${cloudProvider} key in env`} — using local Kokoro`);
|
|
339
|
+
return { kind: "local" };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function synthesizeLocal(text, outAbs, voice, speed) {
|
|
343
|
+
const { cmd, prefix } = hyperframesCli();
|
|
344
|
+
// Pass text via a temp .txt file (the CLI reads .txt inputs) — immune to
|
|
345
|
+
// leading-dash text and shell-length limits.
|
|
346
|
+
const txt = join(mkdtempSync(join(tmpdir(), "vf-media-tts-")), "line.txt");
|
|
347
|
+
writeFileSync(txt, text);
|
|
348
|
+
const args = [...prefix, "tts", txt, "-o", outAbs, "--json"];
|
|
349
|
+
if (voice && KOKORO_VOICE_RE.test(voice)) args.push("-v", voice);
|
|
350
|
+
if (Number.isFinite(speed) && speed > 0 && speed !== 1) args.push("-s", String(speed));
|
|
351
|
+
const r = runJson(cmd, args, scrubbedEnv());
|
|
352
|
+
if (!r.json?.ok) {
|
|
353
|
+
throw new Error(`local TTS failed: ${r.json?.error || r.stderr.slice(0, 200) || r.error?.message || `exit ${r.status}`}`);
|
|
354
|
+
}
|
|
355
|
+
return { path: r.json.outputPath || outAbs, duration: r.json.durationSeconds ?? null, engine: `kokoro/${r.json.voice ?? "af_heart"}` };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function synthesizeCloud(text, outAbs, provider) {
|
|
359
|
+
const args = ["tts", "--text", text, "--out", outAbs, "--json"];
|
|
360
|
+
if (request.voice) args.push("--voice", String(request.voice));
|
|
361
|
+
const style = request.style ?? request.instructions;
|
|
362
|
+
if (style) args.push("--style", String(style));
|
|
363
|
+
if (provider) args.push("--provider", provider);
|
|
364
|
+
const r = runJson("vidfarm", args, process.env);
|
|
365
|
+
if (!r.json?.ok) {
|
|
366
|
+
throw new Error(`vidfarm tts failed: ${r.json?.error || r.stderr.slice(0, 200) || `exit ${r.status}`}`);
|
|
367
|
+
}
|
|
368
|
+
const written = r.json.out || outAbs;
|
|
369
|
+
return { path: written, duration: null, engine: `vidfarm:${r.json.provider ?? provider}` };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function runTts(meta, notes) {
|
|
373
|
+
const spoken = lines.filter((l) => typeof l.text === "string" && l.text.trim());
|
|
374
|
+
meta.voices = [];
|
|
375
|
+
if (!spoken.length) return;
|
|
376
|
+
const engine = pickTtsEngine(notes);
|
|
377
|
+
const voiceDir = join(hyperframesDir, "assets", "voice");
|
|
378
|
+
mkdirSync(voiceDir, { recursive: true });
|
|
379
|
+
const speed = Number(request.speed) || 1;
|
|
380
|
+
|
|
381
|
+
for (const line of spoken) {
|
|
382
|
+
const id = String(line.id ?? meta.voices.length);
|
|
383
|
+
const text = line.text.trim();
|
|
384
|
+
const ext = engine.kind === "cloud" ? "mp3" : "wav";
|
|
385
|
+
const outAbs = join(voiceDir, `${sanitize(id)}.${ext}`);
|
|
386
|
+
let result;
|
|
387
|
+
if (engine.kind === "cloud") {
|
|
388
|
+
try {
|
|
389
|
+
result = synthesizeCloud(text, outAbs, engine.provider);
|
|
390
|
+
} catch (e) {
|
|
391
|
+
notes.push(`tts: line ${id} — ${e.message}; falling back to local Kokoro`);
|
|
392
|
+
result = synthesizeLocal(text, join(voiceDir, `${sanitize(id)}.wav`), request.voice, speed);
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
result = synthesizeLocal(text, outAbs, request.voice, speed);
|
|
396
|
+
}
|
|
397
|
+
const abs = isAbsolute(result.path) ? result.path : resolve(result.path);
|
|
398
|
+
const duration = result.duration ?? probeDurationSeconds(abs) ?? Math.max(0.4, text.length / 15);
|
|
399
|
+
const words = transcribeWords(abs, text, duration, notes, id);
|
|
400
|
+
meta.voices.push({
|
|
401
|
+
id,
|
|
402
|
+
path: relative(hyperframesDir, abs),
|
|
403
|
+
duration_s: r3(duration),
|
|
404
|
+
words,
|
|
405
|
+
engine: result.engine
|
|
406
|
+
});
|
|
407
|
+
console.error(`· tts ${id}: ${r3(duration)}s (${result.engine}, ${words.length} words)`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ── shared retrieval helpers (BGM + SFX) ──────────────────────────────────────
|
|
412
|
+
|
|
413
|
+
const STOPWORDS = new Set(["a", "an", "and", "the", "of", "for", "with", "music", "track", "sound", "effect", "sfx", "audio"]);
|
|
414
|
+
const tokensOf = (s) => String(s ?? "").toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 1 && !STOPWORDS.has(t));
|
|
415
|
+
|
|
416
|
+
function listAudioFiles(dirAbs) {
|
|
417
|
+
try {
|
|
418
|
+
return readdirSync(dirAbs, { withFileTypes: true, recursive: true })
|
|
419
|
+
.filter((e) => e.isFile() && AUDIO_EXT.has(extname(e.name).toLowerCase()))
|
|
420
|
+
.map((e) => join(e.parentPath ?? e.path ?? dirAbs, e.name));
|
|
421
|
+
} catch {
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function keywordPick(files, query) {
|
|
427
|
+
const qTokens = tokensOf(query);
|
|
428
|
+
if (!qTokens.length || !files.length) return null;
|
|
429
|
+
let best = null;
|
|
430
|
+
let bestScore = 0;
|
|
431
|
+
for (const f of files) {
|
|
432
|
+
const fTokens = new Set(tokensOf(basename(f)));
|
|
433
|
+
const score = qTokens.reduce((n, t) => n + (fTokens.has(t) ? 1 : 0), 0);
|
|
434
|
+
if (score > bestScore) {
|
|
435
|
+
best = f;
|
|
436
|
+
bestScore = score;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return best; // null when nothing shares a keyword — a random pick is worse than a skip
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const myFilesAvailable = () => Boolean(process.env.VIDFARM_API_KEY) && hasCommand("vidfarm");
|
|
443
|
+
|
|
444
|
+
// Search My Files and download the best audio hit into destDir. Returns the
|
|
445
|
+
// absolute path or null. Optional — requires $VIDFARM_API_KEY.
|
|
446
|
+
function myFilesFetchAudio(query, destDir) {
|
|
447
|
+
if (!myFilesAvailable()) return null;
|
|
448
|
+
const search = runJson("vidfarm", ["files", "--search", query, "--json"], process.env);
|
|
449
|
+
const results = Array.isArray(search.json?.results) ? search.json.results : [];
|
|
450
|
+
const hit = results.find((r) => AUDIO_EXT.has(extname(String(r.fileName ?? "")).toLowerCase()));
|
|
451
|
+
if (!hit) return null;
|
|
452
|
+
mkdirSync(destDir, { recursive: true });
|
|
453
|
+
const dest = join(destDir, sanitize(hit.fileName));
|
|
454
|
+
const dl = runJson("vidfarm", ["get-file", String(hit.id), dest, "--json"], process.env);
|
|
455
|
+
return dl.status === 0 && existsSync(dest) ? dest : null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async function downloadUrl(url, destDir) {
|
|
459
|
+
const res = await fetch(url);
|
|
460
|
+
if (!res.ok) throw new Error(`download failed (${res.status}) for ${url}`);
|
|
461
|
+
const urlName = basename(new URL(url).pathname) || "bgm.mp3";
|
|
462
|
+
const name = AUDIO_EXT.has(extname(urlName).toLowerCase()) ? urlName : `${urlName}.mp3`;
|
|
463
|
+
mkdirSync(destDir, { recursive: true });
|
|
464
|
+
const dest = join(destDir, sanitize(name));
|
|
465
|
+
writeFileSync(dest, Buffer.from(await res.arrayBuffer()));
|
|
466
|
+
return dest;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function copyIn(srcAbs, destDir) {
|
|
470
|
+
mkdirSync(destDir, { recursive: true });
|
|
471
|
+
const dest = join(destDir, sanitize(basename(srcAbs)));
|
|
472
|
+
if (resolve(srcAbs) !== resolve(dest)) copyFileSync(srcAbs, dest);
|
|
473
|
+
return dest;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ── BGM ───────────────────────────────────────────────────────────────────────
|
|
477
|
+
// Resolution order: explicit file → explicit URL → My Files search → local
|
|
478
|
+
// music dir keyword match → skip with a note. Never an account, never generated
|
|
479
|
+
// off the user's machine. Looping to the video length is the assembler's job.
|
|
480
|
+
|
|
481
|
+
async function runBgm(meta, notes) {
|
|
482
|
+
const bgm = request.bgm ?? {};
|
|
483
|
+
const mode = String(bgm.mode ?? "retrieve").toLowerCase();
|
|
484
|
+
meta.bgm = null;
|
|
485
|
+
if (mode === "none") return;
|
|
486
|
+
if (mode === "generate") notes.push("bgm: generate mode is not supported (no media accounts) — retrieving instead");
|
|
487
|
+
const query = String(bgm.query ?? "").trim();
|
|
488
|
+
const destDir = join(hyperframesDir, "assets", "bgm");
|
|
489
|
+
let abs = null;
|
|
490
|
+
let source = null;
|
|
491
|
+
|
|
492
|
+
const explicit = bgm.file ?? bgm.path;
|
|
493
|
+
if (explicit) {
|
|
494
|
+
const srcAbs = isAbsolute(explicit) ? explicit : resolve(hyperframesDir, explicit);
|
|
495
|
+
if (existsSync(srcAbs) && statSync(srcAbs).isFile()) {
|
|
496
|
+
abs = copyIn(srcAbs, destDir);
|
|
497
|
+
source = "file";
|
|
498
|
+
} else if (/^https?:\/\//i.test(String(explicit))) {
|
|
499
|
+
abs = await downloadUrl(String(explicit), destDir).catch((e) => (notes.push(`bgm: ${e.message}`), null));
|
|
500
|
+
source = abs ? "url" : null;
|
|
501
|
+
} else {
|
|
502
|
+
notes.push(`bgm: requested file not found at ${srcAbs}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (!abs && bgm.url) {
|
|
506
|
+
abs = await downloadUrl(String(bgm.url), destDir).catch((e) => (notes.push(`bgm: ${e.message}`), null));
|
|
507
|
+
source = abs ? "url" : null;
|
|
508
|
+
}
|
|
509
|
+
if (!abs && query && myFilesAvailable()) {
|
|
510
|
+
abs = myFilesFetchAudio(`${query} music`, destDir);
|
|
511
|
+
source = abs ? "my-files" : null;
|
|
512
|
+
}
|
|
513
|
+
if (!abs) {
|
|
514
|
+
const musicDir = bgm.dir ? (isAbsolute(bgm.dir) ? bgm.dir : resolve(hyperframesDir, bgm.dir)) : process.env.MUSIC_DIR;
|
|
515
|
+
if (musicDir && existsSync(musicDir)) {
|
|
516
|
+
const pick = keywordPick(listAudioFiles(musicDir), query || "underscore");
|
|
517
|
+
if (pick) {
|
|
518
|
+
abs = copyIn(pick, destDir);
|
|
519
|
+
source = "music-dir";
|
|
520
|
+
} else {
|
|
521
|
+
notes.push(`bgm: no filename in ${musicDir} matches "${query}"`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (!abs) {
|
|
526
|
+
notes.push(`bgm: skipped — no user file/URL, no My Files match${process.env.VIDFARM_API_KEY ? "" : " (no $VIDFARM_API_KEY)"}, no local music dir. The video stays voice-only; pass bgm.file/bgm.url or set $MUSIC_DIR.`);
|
|
527
|
+
console.error("· bgm: skipped (see notes)");
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
meta.bgm = {
|
|
531
|
+
path: relative(hyperframesDir, abs),
|
|
532
|
+
query: query || null,
|
|
533
|
+
duration_s: (() => {
|
|
534
|
+
const d = probeDurationSeconds(abs);
|
|
535
|
+
return d ? r3(d) : null;
|
|
536
|
+
})(),
|
|
537
|
+
source
|
|
538
|
+
};
|
|
539
|
+
if (bgm.volume != null) meta.bgm.volume = Number(bgm.volume);
|
|
540
|
+
console.error(`· bgm: ${meta.bgm.path} (${source})`);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// ── SFX ───────────────────────────────────────────────────────────────────────
|
|
544
|
+
// Named cues per line. Order: local sfx dir(s) → My Files search → skip the cue
|
|
545
|
+
// with a note. Unresolved cues never fail the run.
|
|
546
|
+
|
|
547
|
+
function runSfx(meta, notes) {
|
|
548
|
+
meta.sfx = [];
|
|
549
|
+
const cueLines = lines.filter((l) => Array.isArray(l.sfx) && l.sfx.length);
|
|
550
|
+
if (!cueLines.length) return;
|
|
551
|
+
const destDir = join(hyperframesDir, "assets", "sfx");
|
|
552
|
+
const localDirs = [
|
|
553
|
+
request.sfx_dir ? (isAbsolute(request.sfx_dir) ? request.sfx_dir : resolve(hyperframesDir, request.sfx_dir)) : null,
|
|
554
|
+
process.env.SFX_DIR,
|
|
555
|
+
join(SKILL_DIR, "assets", "sfx")
|
|
556
|
+
].filter((d) => d && existsSync(d));
|
|
557
|
+
const localFiles = localDirs.flatMap((d) => listAudioFiles(d));
|
|
558
|
+
const cache = new Map(); // cue name → abs path | null
|
|
559
|
+
|
|
560
|
+
const resolveCue = (cue) => {
|
|
561
|
+
const key = cue.toLowerCase();
|
|
562
|
+
if (cache.has(key)) return cache.get(key);
|
|
563
|
+
let abs = null;
|
|
564
|
+
const local = keywordPick(localFiles, cue);
|
|
565
|
+
if (local) abs = copyIn(local, destDir);
|
|
566
|
+
if (!abs && myFilesAvailable()) abs = myFilesFetchAudio(`${cue} sound effect`, destDir);
|
|
567
|
+
cache.set(key, abs);
|
|
568
|
+
return abs;
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
for (const line of cueLines) {
|
|
572
|
+
const id = String(line.id ?? "");
|
|
573
|
+
for (const rawCue of line.sfx) {
|
|
574
|
+
const cue = String(rawCue).trim();
|
|
575
|
+
if (!cue) continue;
|
|
576
|
+
const abs = resolveCue(cue);
|
|
577
|
+
if (!abs) {
|
|
578
|
+
notes.push(`sfx: "${cue}" (line ${id}) — no match in ${localDirs.length ? localDirs.join(", ") : "$SFX_DIR (unset)"}${myFilesAvailable() ? " or My Files" : ""}; cue skipped`);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
meta.sfx.push({
|
|
582
|
+
id,
|
|
583
|
+
file: relative(hyperframesDir, abs),
|
|
584
|
+
offset_s: 0,
|
|
585
|
+
duration_s: r3(probeDurationSeconds(abs) ?? 1),
|
|
586
|
+
volume: 0.35,
|
|
587
|
+
cue
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
console.error(`· sfx: ${meta.sfx.length} cue(s) resolved`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ── main: merge-aware section runner ─────────────────────────────────────────
|
|
595
|
+
|
|
596
|
+
async function main() {
|
|
597
|
+
// Sections not in --only are preserved from an existing meta (this is what
|
|
598
|
+
// makes the adapters' generate → fetch-sfx sequence accumulate).
|
|
599
|
+
let meta = { voices: [], bgm: null, sfx: [], notes: [] };
|
|
600
|
+
if (existsSync(outPath)) {
|
|
601
|
+
try {
|
|
602
|
+
const prev = JSON.parse(readFileSync(outPath, "utf8"));
|
|
603
|
+
meta = {
|
|
604
|
+
voices: Array.isArray(prev.voices) ? prev.voices : [],
|
|
605
|
+
bgm: prev.bgm ?? null,
|
|
606
|
+
sfx: Array.isArray(prev.sfx) ? prev.sfx : [],
|
|
607
|
+
notes: Array.isArray(prev.notes) ? prev.notes : []
|
|
608
|
+
};
|
|
609
|
+
} catch { /* corrupt sidecar — start fresh */ }
|
|
610
|
+
}
|
|
611
|
+
// Drop stale notes for the sections being recomputed.
|
|
612
|
+
meta.notes = meta.notes.filter((n) => ![...only].some((s) => String(n).startsWith(`${s}:`)));
|
|
613
|
+
const notes = meta.notes;
|
|
614
|
+
|
|
615
|
+
if (only.has("tts")) runTts(meta, notes);
|
|
616
|
+
if (only.has("bgm")) await runBgm(meta, notes);
|
|
617
|
+
if (only.has("sfx")) runSfx(meta, notes);
|
|
618
|
+
|
|
619
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
620
|
+
writeFileSync(outPath, JSON.stringify(meta, null, 2));
|
|
621
|
+
console.log(
|
|
622
|
+
`✓ media audio: ${meta.voices.length} voice line(s), ` +
|
|
623
|
+
`${meta.bgm ? "1 bgm" : "no bgm"}, ${meta.sfx.length} sfx → ${outPath}` +
|
|
624
|
+
(notes.length ? ` (${notes.length} note(s) in meta)` : "")
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
main().catch((e) => die(e?.message ?? String(e)));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// wait-bgm.mjs — compatibility shim. The vidfarm-media engine resolves BGM
|
|
3
|
+
// synchronously (user file / URL / My Files / local dir) and never sets
|
|
4
|
+
// `bgm_pending`, so there is nothing to wait for. Kept so workflows that call
|
|
5
|
+
// wait-bgm unconditionally keep working: report the BGM state and exit 0.
|
|
6
|
+
//
|
|
7
|
+
// node wait-bgm.mjs [--meta ./audio_meta.json]
|
|
8
|
+
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
|
|
12
|
+
const argv = process.argv.slice(2);
|
|
13
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
14
|
+
console.log("Usage: node wait-bgm.mjs [--meta ./audio_meta.json]\nNo-op: the vidfarm-media engine never defers BGM (bgm_pending is never set).");
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
const i = argv.indexOf("--meta");
|
|
18
|
+
const metaPath = resolve(i >= 0 && argv[i + 1] ? argv[i + 1] : "./audio_meta.json");
|
|
19
|
+
|
|
20
|
+
if (!existsSync(metaPath)) {
|
|
21
|
+
console.log(`· wait-bgm: no meta at ${metaPath} — nothing pending.`);
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const meta = JSON.parse(readFileSync(metaPath, "utf8"));
|
|
26
|
+
if (meta.bgm?.path) console.log(`✓ wait-bgm: bgm ready at ${meta.bgm.path}.`);
|
|
27
|
+
else console.log("· wait-bgm: no bgm in meta (skipped at generate time — see meta notes). Nothing pending.");
|
|
28
|
+
} catch {
|
|
29
|
+
console.log("· wait-bgm: meta unreadable — nothing pending.");
|
|
30
|
+
}
|
|
31
|
+
process.exit(0);
|