@officexapp/vidfarm-devcli 0.21.10
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/dollarplatoon-skill/SKILL.md +1227 -0
- package/.agents/skills/editor-capabilities/SKILL.md +182 -0
- package/.agents/skills/editor-capabilities/references/re-theme-walkthrough.md +58 -0
- 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/farmville-saas-ux/SKILL.md +156 -0
- package/.agents/skills/farmville-saas-ux/assets/starter.html +294 -0
- package/.agents/skills/farmville-saas-ux/references/components.md +340 -0
- package/.agents/skills/farmville-saas-ux/references/porting-guide.md +121 -0
- package/.agents/skills/farmville-saas-ux/references/tokens.md +271 -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/music/SKILL.md +416 -0
- package/.agents/skills/music/references/api_reference.md +519 -0
- package/.agents/skills/music/references/installation.md +65 -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/text-to-speech/SKILL.md +226 -0
- package/.agents/skills/text-to-speech/references/installation.md +90 -0
- package/.agents/skills/text-to-speech/references/streaming.md +307 -0
- package/.agents/skills/text-to-speech/references/voice-settings.md +115 -0
- package/.agents/skills/vidfarm-director/SKILL.md +179 -0
- package/.agents/skills/vidfarm-director/recipes/find-and-fork-template.md +16 -0
- package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +13 -0
- package/.agents/skills/vidfarm-director/recipes/onboard-a-new-director.md +13 -0
- package/.agents/skills/vidfarm-director/recipes/retheme-template.md +17 -0
- package/.agents/skills/vidfarm-director/references/assets-and-sourcing.md +117 -0
- package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +276 -0
- package/.agents/skills/vidfarm-director/references/core-workflows.md +301 -0
- package/.agents/skills/vidfarm-director/references/editor-workflows.md +367 -0
- package/.agents/skills/vidfarm-director/references/onboarding.md +28 -0
- package/.agents/skills/vidfarm-director/references/primitives.md +307 -0
- package/.agents/skills/vidfarm-director/references/rest-api.md +84 -0
- package/.agents/skills/vidfarm-media/SKILL.md +158 -0
- package/.agents/skills/vidfarm-media/references/tts.md +111 -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/README.md +97 -0
- package/SKILL.director.md +1644 -0
- package/SKILL.md +83 -0
- package/demo/README.md +28 -0
- package/demo/dist/app.css +1 -0
- package/demo/dist/app.js +968 -0
- package/demo/dist/chunks/chunk-3OT2D42H.js +1 -0
- package/demo/dist/chunks/chunk-DXB73IDG.js +1 -0
- package/demo/dist/chunks/domEditingLayers-AT7G6F4L-ZTHJHB7R.js +1 -0
- package/demo/dist/chunks/hyperframes-player-7HKH6NSO.js +459 -0
- package/demo/dist/chunks/lib-XAQ37YOE.js +1 -0
- package/demo/dist/chunks/src-TJ2QYA4U.js +207 -0
- package/demo/dist/favicon.ico +0 -0
- package/demo/dist/icons/timeline/audio.svg +7 -0
- package/demo/dist/icons/timeline/captions.svg +5 -0
- package/demo/dist/icons/timeline/composition.svg +12 -0
- package/demo/dist/icons/timeline/image.svg +18 -0
- package/demo/dist/icons/timeline/music.svg +10 -0
- package/demo/dist/icons/timeline/text.svg +3 -0
- package/demo/dist/index.html +16 -0
- package/dist/src/cli.js +7258 -0
- package/dist/src/devcli/auth-store.js +67 -0
- package/dist/src/devcli/captions.js +310 -0
- package/dist/src/devcli/clip-store.js +368 -0
- package/dist/src/devcli/clips.js +1138 -0
- package/dist/src/devcli/composition-edit.js +1292 -0
- package/dist/src/devcli/cost-mode.js +149 -0
- package/dist/src/devcli/doctor.js +192 -0
- package/dist/src/devcli/hyperframes-cli.js +118 -0
- package/dist/src/devcli/local-backend.js +167 -0
- package/dist/src/devcli/local-frontend-server.js +681 -0
- package/dist/src/devcli/local-render.js +136 -0
- package/dist/src/devcli/skills.js +476 -0
- package/dist/src/devcli/speech.js +178 -0
- package/dist/src/devcli/stills.js +249 -0
- package/dist/src/devcli/telemetry.js +236 -0
- package/dist/src/devcli/timeline-edit.js +490 -0
- package/dist/src/devcli/transitions.js +205 -0
- package/dist/src/hyperframes/composition.js +780 -0
- package/dist/src/lib/crypto.js +45 -0
- package/dist/src/lib/dev-log.js +54 -0
- package/dist/src/lib/display-name.js +11 -0
- package/dist/src/lib/ids.js +24 -0
- package/dist/src/lib/images.js +19 -0
- package/dist/src/lib/json.js +15 -0
- package/dist/src/lib/package-root.js +47 -0
- package/dist/src/lib/render-media-prep.js +293 -0
- package/dist/src/lib/template-paths.js +28 -0
- package/dist/src/lib/time.js +7 -0
- package/dist/src/lib/url-clean.js +85 -0
- package/dist/src/services/captions.js +123 -0
- package/dist/src/services/clip-curation/cost.js +116 -0
- package/dist/src/services/clip-curation/ffmpeg.js +352 -0
- package/dist/src/services/clip-curation/gemini.js +418 -0
- package/dist/src/services/clip-curation/hunt.js +470 -0
- package/dist/src/services/clip-curation/index.js +19 -0
- package/dist/src/services/clip-curation/local-agent.js +248 -0
- package/dist/src/services/clip-curation/media-select.js +152 -0
- package/dist/src/services/clip-curation/presets.js +20 -0
- package/dist/src/services/clip-curation/presets.v1.json +59 -0
- package/dist/src/services/clip-curation/query.js +167 -0
- package/dist/src/services/clip-curation/refine.js +165 -0
- package/dist/src/services/clip-curation/scan.js +189 -0
- package/dist/src/services/clip-curation/source-naming.js +131 -0
- package/dist/src/services/clip-curation/taxonomy.js +73 -0
- package/dist/src/services/clip-curation/taxonomy.v1.json +102 -0
- package/dist/src/services/clip-curation/types.js +7 -0
- package/dist/src/services/composition-lint.js +199 -0
- package/dist/src/services/provider-errors.js +128 -0
- package/dist/src/services/speech.js +629 -0
- package/package.json +154 -0
- package/public/assets/discover-client-app.js +1 -0
- package/public/assets/favicon.ico +0 -0
- package/public/assets/file-directory-app.js +89 -0
- package/public/assets/homepage-app.js +54 -0
- package/public/assets/homepage-client-app.js +80 -0
- package/public/assets/landing-page-client-app.js +87 -0
- package/public/assets/logo-vidfarm.png +0 -0
- package/public/assets/page-runtime-client-app.js +94 -0
- package/public/assets/placeholders/scene-placeholder.png +0 -0
- package/public/serve-shells/editor.html +3146 -0
- package/public/serve-shells/library-files.html +3450 -0
- package/public/serve-shells/library-raws.html +4404 -0
- package/public/serve-shells/tools-clipper.html +3991 -0
- package/public/serve-shells/tools-image.html +5584 -0
- package/public/serve-shells/tools-video.html +4404 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Turns a transcript into animated-caption cues. Shared by the devcli
|
|
2
|
+
// `captions` command group, the /editor chat agent's set_captions action, and
|
|
3
|
+
// any server surface that wants captions from STT output. Pure — no network,
|
|
4
|
+
// no filesystem — so it is safe to import from the devcli.
|
|
5
|
+
//
|
|
6
|
+
// A "cue" is one caption layer on the timeline: a short run of words shown
|
|
7
|
+
// together (CapCut-style pages of ~3-5 words), with per-word windows relative
|
|
8
|
+
// to the cue start driving the animation. Word timings come from the provider
|
|
9
|
+
// when available (openai whisper-1) and are char-weight estimated inside each
|
|
10
|
+
// segment otherwise (gemini estimates, hand-typed text) — see
|
|
11
|
+
// estimateCaptionWords.
|
|
12
|
+
import { estimateCaptionWords } from "../hyperframes/composition.js";
|
|
13
|
+
const CUE_TAIL_SEC = 0.12;
|
|
14
|
+
const MIN_WORD_SPAN_SEC = 0.05;
|
|
15
|
+
/** Flattens transcript segments into absolute-timed words, estimating windows
|
|
16
|
+
* for segments the provider left word-less (or entirely time-less — those
|
|
17
|
+
* distribute across fallbackDurationSec). */
|
|
18
|
+
export function transcriptWordTimeline(segments, fallbackDurationSec) {
|
|
19
|
+
const words = [];
|
|
20
|
+
const timeless = segments.filter((segment) => typeof segment.start_sec !== "number");
|
|
21
|
+
for (const segment of segments) {
|
|
22
|
+
if (segment.words?.length) {
|
|
23
|
+
words.push(...segment.words.map((word) => ({ ...word, text: word.text.trim() })).filter((word) => word.text));
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
let base;
|
|
27
|
+
let span;
|
|
28
|
+
if (typeof segment.start_sec === "number") {
|
|
29
|
+
base = segment.start_sec;
|
|
30
|
+
span = typeof segment.end_sec === "number" && segment.end_sec > segment.start_sec
|
|
31
|
+
? segment.end_sec - segment.start_sec
|
|
32
|
+
: 0;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// No timings at all (plain-text providers): spread the blob across the
|
|
36
|
+
// caller-provided window so captions still land somewhere sensible.
|
|
37
|
+
base = 0;
|
|
38
|
+
span = Number.isFinite(fallbackDurationSec) && fallbackDurationSec > 0 && timeless.length
|
|
39
|
+
? fallbackDurationSec / timeless.length
|
|
40
|
+
: 0;
|
|
41
|
+
if (timeless.length > 1) {
|
|
42
|
+
base = (span || 0) * timeless.indexOf(segment);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const estimated = estimateCaptionWords(segment.text, span);
|
|
46
|
+
words.push(...estimated.map((word) => ({
|
|
47
|
+
text: word.text,
|
|
48
|
+
start_sec: base + word.start,
|
|
49
|
+
end_sec: base + word.end
|
|
50
|
+
})));
|
|
51
|
+
}
|
|
52
|
+
return words
|
|
53
|
+
.filter((word) => Number.isFinite(word.start_sec) && Number.isFinite(word.end_sec) && word.end_sec > word.start_sec)
|
|
54
|
+
.sort((a, b) => a.start_sec - b.start_sec);
|
|
55
|
+
}
|
|
56
|
+
export function buildCaptionCues(segments, options) {
|
|
57
|
+
const words = transcriptWordTimeline(segments, options?.fallbackDurationSec);
|
|
58
|
+
return cuesFromWordTimeline(words, options);
|
|
59
|
+
}
|
|
60
|
+
export function cuesFromWordTimeline(words, options) {
|
|
61
|
+
const maxWords = Math.max(1, Math.round(options?.maxWordsPerCue ?? 4));
|
|
62
|
+
const maxDuration = Math.max(0.4, options?.maxCueDurationSec ?? 2.6);
|
|
63
|
+
const gapBreak = Math.max(0.15, options?.gapBreakSec ?? 0.6);
|
|
64
|
+
const offset = Number.isFinite(options?.offsetSec) ? options?.offsetSec : 0;
|
|
65
|
+
const cues = [];
|
|
66
|
+
let bucket = [];
|
|
67
|
+
const flush = () => {
|
|
68
|
+
if (!bucket.length)
|
|
69
|
+
return;
|
|
70
|
+
const start = bucket[0].start_sec;
|
|
71
|
+
const end = bucket[bucket.length - 1].end_sec + CUE_TAIL_SEC;
|
|
72
|
+
cues.push({
|
|
73
|
+
text: bucket.map((word) => word.text).join(" "),
|
|
74
|
+
start: round3(start + offset),
|
|
75
|
+
duration: round3(Math.max(MIN_WORD_SPAN_SEC * bucket.length, end - start)),
|
|
76
|
+
words: bucket.map((word) => ({
|
|
77
|
+
text: word.text,
|
|
78
|
+
start: round3(Math.max(0, word.start_sec - start)),
|
|
79
|
+
end: round3(Math.max(word.start_sec - start + MIN_WORD_SPAN_SEC, word.end_sec - start))
|
|
80
|
+
}))
|
|
81
|
+
});
|
|
82
|
+
bucket = [];
|
|
83
|
+
};
|
|
84
|
+
for (const word of words) {
|
|
85
|
+
if (bucket.length) {
|
|
86
|
+
const cueStart = bucket[0].start_sec;
|
|
87
|
+
const previous = bucket[bucket.length - 1];
|
|
88
|
+
const silence = word.start_sec - previous.end_sec;
|
|
89
|
+
const wouldRun = word.end_sec - cueStart;
|
|
90
|
+
const sentenceBreak = /[.!?…]["')\]]?$/.test(previous.text);
|
|
91
|
+
if (bucket.length >= maxWords || silence > gapBreak || wouldRun > maxDuration || sentenceBreak) {
|
|
92
|
+
flush();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
bucket.push(word);
|
|
96
|
+
}
|
|
97
|
+
flush();
|
|
98
|
+
// Clamp each cue against the next so consecutive caption layers never
|
|
99
|
+
// overlap on the shared track (one-clip-per-(track,time) invariant).
|
|
100
|
+
for (let index = 0; index < cues.length - 1; index += 1) {
|
|
101
|
+
const cue = cues[index];
|
|
102
|
+
const next = cues[index + 1];
|
|
103
|
+
const maxEnd = next.start - 0.02;
|
|
104
|
+
if (cue.start + cue.duration > maxEnd) {
|
|
105
|
+
cue.duration = round3(Math.max(MIN_WORD_SPAN_SEC, maxEnd - cue.start));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return cues.filter((cue) => cue.duration > 0);
|
|
109
|
+
}
|
|
110
|
+
/** Cues straight from plain text spread across a window — the no-transcript
|
|
111
|
+
* path (AI-authored captions, manual cue text). */
|
|
112
|
+
export function cuesFromText(text, startSec, durationSec, options) {
|
|
113
|
+
const words = estimateCaptionWords(text, durationSec).map((word) => ({
|
|
114
|
+
text: word.text,
|
|
115
|
+
start_sec: startSec + word.start,
|
|
116
|
+
end_sec: startSec + word.end
|
|
117
|
+
}));
|
|
118
|
+
return cuesFromWordTimeline(words, options);
|
|
119
|
+
}
|
|
120
|
+
function round3(value) {
|
|
121
|
+
return Number(value.toFixed(3));
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=captions.js.map
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Cost estimation for a scan — surfaced BEFORE every scan (success criterion #6)
|
|
2
|
+
// and used by the devcli --dry-run gate. Provider/model-aware: prices the
|
|
3
|
+
// resolved tagging model (gemini / openai gpt-5.4-nano / openrouter gemini) plus
|
|
4
|
+
// the embedding model. BYOK: this is the LLM spend the user pays directly.
|
|
5
|
+
function envPrice(envKey, fallback) {
|
|
6
|
+
const v = Number(process.env[envKey]);
|
|
7
|
+
return Number.isFinite(v) && v > 0 ? v : fallback;
|
|
8
|
+
}
|
|
9
|
+
// Approximate list prices, USD per 1M tokens (overridable via env). Estimates —
|
|
10
|
+
// the real bill comes from the user's provider account.
|
|
11
|
+
const MODEL_PRICING = {
|
|
12
|
+
"gemini-2.5-flash-lite": { input: envPrice("VIDFARM_PRICE_FLASH_LITE_INPUT", 0.1), output: envPrice("VIDFARM_PRICE_FLASH_LITE_OUTPUT", 0.4) },
|
|
13
|
+
"gemini-2.5-flash": { input: envPrice("VIDFARM_PRICE_FLASH_INPUT", 0.3), output: envPrice("VIDFARM_PRICE_FLASH_OUTPUT", 2.5) },
|
|
14
|
+
// OpenRouter adds a small markup over Google's list price.
|
|
15
|
+
"google/gemini-2.5-flash": { input: envPrice("VIDFARM_PRICE_OR_FLASH_INPUT", 0.315), output: envPrice("VIDFARM_PRICE_OR_FLASH_OUTPUT", 2.625) },
|
|
16
|
+
"gpt-5.4-nano": { input: envPrice("VIDFARM_PRICE_GPT5_NANO_INPUT", 0.05), output: envPrice("VIDFARM_PRICE_GPT5_NANO_OUTPUT", 0.4) },
|
|
17
|
+
// Nvidia lite clipping model (minimaxai/minimax-m3) — approximate, env-overridable.
|
|
18
|
+
"minimaxai/minimax-m3": { input: envPrice("VIDFARM_PRICE_NVIDIA_MINIMAX_INPUT", 0.15), output: envPrice("VIDFARM_PRICE_NVIDIA_MINIMAX_OUTPUT", 0.6) }
|
|
19
|
+
};
|
|
20
|
+
const DEFAULT_TAG_PRICING = { input: 0.15, output: 0.6 };
|
|
21
|
+
const EMBED_PRICING = {
|
|
22
|
+
"gemini-embedding-001": envPrice("VIDFARM_PRICE_EMBEDDING_INPUT", 0.15),
|
|
23
|
+
"text-embedding-3-small": envPrice("VIDFARM_PRICE_OPENAI_EMBEDDING_INPUT", 0.02)
|
|
24
|
+
};
|
|
25
|
+
// Low-res image tokens: Gemini media_resolution LOW = 66/frame; OpenAI
|
|
26
|
+
// low-detail ≈ 85/frame. Audio ~32 tok/sec (Gemini only — OpenAI/OpenRouter
|
|
27
|
+
// chat models don't take the raw audio clip, so those skip it).
|
|
28
|
+
function imageTokensPerFrame(provider) {
|
|
29
|
+
return provider === "openai" ? 85 : 66;
|
|
30
|
+
}
|
|
31
|
+
const PROMPT_OVERHEAD_TOKENS = 280;
|
|
32
|
+
const AUDIO_TOKENS_PER_SEC = 32;
|
|
33
|
+
// OpenAI's nano reasoning model emits more output tokens than Gemini's terse JSON.
|
|
34
|
+
function outputTokensPerScene(provider) {
|
|
35
|
+
return provider === "openai" ? 350 : 150;
|
|
36
|
+
}
|
|
37
|
+
const EMBEDDING_TOKENS_PER_SCENE = 60;
|
|
38
|
+
const DEFAULT_AVG_SCENE_SEC = 4;
|
|
39
|
+
export function estimateScanCostFromScenes(scenes, opts) {
|
|
40
|
+
const durationSec = scenes.reduce((acc, s) => acc + s.duration_sec, 0);
|
|
41
|
+
const avgSceneSec = scenes.length ? durationSec / scenes.length : DEFAULT_AVG_SCENE_SEC;
|
|
42
|
+
return compute({ ...normalize(opts), durationSec, scenes: scenes.length, avgSceneSec });
|
|
43
|
+
}
|
|
44
|
+
export function estimateScanCostFromDuration(durationSec, opts) {
|
|
45
|
+
const avgSceneSec = opts.avgSceneSec ?? DEFAULT_AVG_SCENE_SEC;
|
|
46
|
+
const scenes = Math.max(1, Math.ceil(durationSec / avgSceneSec));
|
|
47
|
+
return compute({ ...normalize(opts), durationSec, scenes, avgSceneSec });
|
|
48
|
+
}
|
|
49
|
+
function normalize(opts) {
|
|
50
|
+
const provider = opts.provider ?? "gemini";
|
|
51
|
+
const tagModelId = opts.tagModelId ?? defaultTagModel(provider, opts.tier);
|
|
52
|
+
return {
|
|
53
|
+
tier: opts.tier,
|
|
54
|
+
provider,
|
|
55
|
+
tagModelId,
|
|
56
|
+
embedModelId: opts.embedModelId ?? null,
|
|
57
|
+
framesPerScene: opts.framesPerScene ?? 2,
|
|
58
|
+
includeAudio: opts.includeAudio ?? true
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function defaultTagModel(provider, tier) {
|
|
62
|
+
if (provider === "openai")
|
|
63
|
+
return "gpt-5.4-nano";
|
|
64
|
+
if (provider === "openrouter")
|
|
65
|
+
return "google/gemini-2.5-flash";
|
|
66
|
+
if (provider === "nvidia")
|
|
67
|
+
return "minimaxai/minimax-m3";
|
|
68
|
+
return tier === "flash" ? "gemini-2.5-flash" : "gemini-2.5-flash-lite";
|
|
69
|
+
}
|
|
70
|
+
function compute(input) {
|
|
71
|
+
const { provider, tagModelId, embedModelId, framesPerScene, includeAudio, durationSec, scenes, avgSceneSec } = input;
|
|
72
|
+
// Audio is only sent to Gemini (native audio input).
|
|
73
|
+
const audioTokensPerScene = includeAudio && provider === "gemini" ? Math.round(avgSceneSec * AUDIO_TOKENS_PER_SEC) : 0;
|
|
74
|
+
const taggingInputPerScene = framesPerScene * imageTokensPerFrame(provider) + PROMPT_OVERHEAD_TOKENS + audioTokensPerScene;
|
|
75
|
+
const taggingInputTokens = taggingInputPerScene * scenes;
|
|
76
|
+
const taggingOutputTokens = outputTokensPerScene(provider) * scenes;
|
|
77
|
+
const embeddingTokens = embedModelId ? EMBEDDING_TOKENS_PER_SCENE * scenes : 0;
|
|
78
|
+
const tagPrice = MODEL_PRICING[tagModelId] ?? DEFAULT_TAG_PRICING;
|
|
79
|
+
const embedPrice = embedModelId ? EMBED_PRICING[embedModelId] ?? 0.15 : 0;
|
|
80
|
+
const usd = (taggingInputTokens / 1_000_000) * tagPrice.input +
|
|
81
|
+
(taggingOutputTokens / 1_000_000) * tagPrice.output +
|
|
82
|
+
(embeddingTokens / 1_000_000) * embedPrice;
|
|
83
|
+
const hours = durationSec / 3600;
|
|
84
|
+
const usdPerHour = hours > 0 ? usd / hours : usd;
|
|
85
|
+
return {
|
|
86
|
+
tier: input.tier,
|
|
87
|
+
provider,
|
|
88
|
+
tag_model: tagModelId,
|
|
89
|
+
embed_model: embedModelId,
|
|
90
|
+
duration_sec: round2(durationSec),
|
|
91
|
+
estimated_scenes: scenes,
|
|
92
|
+
frames_per_scene: framesPerScene,
|
|
93
|
+
tagging_input_tokens: taggingInputTokens,
|
|
94
|
+
tagging_output_tokens: taggingOutputTokens,
|
|
95
|
+
embedding_tokens: embeddingTokens,
|
|
96
|
+
usd: round4(usd),
|
|
97
|
+
usd_per_source_hour: round4(usdPerHour)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** Human-readable one-liner for the CLI / API. */
|
|
101
|
+
export function formatCostEstimate(est) {
|
|
102
|
+
const mins = (est.duration_sec / 60).toFixed(1);
|
|
103
|
+
return [
|
|
104
|
+
`~$${est.usd.toFixed(3)} for ${mins} min`,
|
|
105
|
+
`(${est.estimated_scenes} scenes · ${est.tag_model}${est.embed_model ? ` + ${est.embed_model}` : " · no embeddings"})`,
|
|
106
|
+
`≈ $${est.usd_per_source_hour.toFixed(3)}/source-hour`,
|
|
107
|
+
`| tokens: ${est.tagging_input_tokens.toLocaleString()} in + ${est.tagging_output_tokens.toLocaleString()} out + ${est.embedding_tokens.toLocaleString()} embed`
|
|
108
|
+
].join(" ");
|
|
109
|
+
}
|
|
110
|
+
function round2(n) {
|
|
111
|
+
return Math.round(n * 100) / 100;
|
|
112
|
+
}
|
|
113
|
+
function round4(n) {
|
|
114
|
+
return Math.round(n * 10000) / 10000;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=cost.js.map
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
// ffmpeg/ffprobe helpers for clip curation — scene detection, keyframe/clip/
|
|
2
|
+
// thumbnail extraction, per-scene audio for ASR. Same flags on devcli (local
|
|
3
|
+
// ffmpeg-static) and cloud (bundled ffmpeg-static in the scan Lambda), so both
|
|
4
|
+
// targets cut identical clips. Resolution order matches the rest of the repo:
|
|
5
|
+
// HYPERFRAMES_FFMPEG_PATH/FFPROBE env → ffmpeg-static/ffprobe-static → PATH.
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
let cachedFfmpeg;
|
|
10
|
+
let cachedFfprobe;
|
|
11
|
+
export async function resolveFfmpeg() {
|
|
12
|
+
if (cachedFfmpeg !== undefined)
|
|
13
|
+
return cachedFfmpeg ?? "ffmpeg";
|
|
14
|
+
const envPath = process.env.HYPERFRAMES_FFMPEG_PATH?.trim();
|
|
15
|
+
if (envPath && existsSync(envPath))
|
|
16
|
+
return (cachedFfmpeg = envPath);
|
|
17
|
+
try {
|
|
18
|
+
const mod = (await import("ffmpeg-static"));
|
|
19
|
+
const resolved = typeof mod === "string" ? mod : mod.default;
|
|
20
|
+
if (typeof resolved === "string" && resolved && existsSync(resolved))
|
|
21
|
+
return (cachedFfmpeg = resolved);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
/* fall through to PATH */
|
|
25
|
+
}
|
|
26
|
+
cachedFfmpeg = null;
|
|
27
|
+
return "ffmpeg";
|
|
28
|
+
}
|
|
29
|
+
export async function resolveFfprobe() {
|
|
30
|
+
if (cachedFfprobe !== undefined)
|
|
31
|
+
return cachedFfprobe ?? "ffprobe";
|
|
32
|
+
const envPath = process.env.HYPERFRAMES_FFPROBE_PATH?.trim() ?? process.env.FFPROBE_PATH?.trim();
|
|
33
|
+
if (envPath && existsSync(envPath))
|
|
34
|
+
return (cachedFfprobe = envPath);
|
|
35
|
+
try {
|
|
36
|
+
const mod = (await import("ffprobe-static"));
|
|
37
|
+
const p = (mod.path ?? mod.default?.path);
|
|
38
|
+
if (typeof p === "string" && p && existsSync(p))
|
|
39
|
+
return (cachedFfprobe = p);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* fall through to PATH */
|
|
43
|
+
}
|
|
44
|
+
cachedFfprobe = null;
|
|
45
|
+
return "ffprobe";
|
|
46
|
+
}
|
|
47
|
+
/** True if a usable ffmpeg is available (bundled or on PATH). */
|
|
48
|
+
export async function hasFfmpeg() {
|
|
49
|
+
const bin = await resolveFfmpeg();
|
|
50
|
+
const { code } = await run(bin, ["-version"]).catch(() => ({ code: 1, stdout: "", stderr: "" }));
|
|
51
|
+
return code === 0;
|
|
52
|
+
}
|
|
53
|
+
function run(bin, args) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const child = spawn(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
56
|
+
let stdout = "";
|
|
57
|
+
let stderr = "";
|
|
58
|
+
child.stdout.on("data", (d) => (stdout += d.toString()));
|
|
59
|
+
child.stderr.on("data", (d) => (stderr += d.toString()));
|
|
60
|
+
child.on("error", reject);
|
|
61
|
+
child.on("close", (code) => resolve({ code: code ?? 0, stdout, stderr }));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** Probe a source video for duration/dimensions/codec/size/fps. */
|
|
65
|
+
export async function probeVideo(videoPath) {
|
|
66
|
+
const ffprobe = await resolveFfprobe();
|
|
67
|
+
const { stdout } = await run(ffprobe, [
|
|
68
|
+
"-v", "error",
|
|
69
|
+
"-print_format", "json",
|
|
70
|
+
"-show_format",
|
|
71
|
+
"-show_streams",
|
|
72
|
+
videoPath
|
|
73
|
+
]);
|
|
74
|
+
let parsed = {};
|
|
75
|
+
try {
|
|
76
|
+
parsed = JSON.parse(stdout || "{}");
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
parsed = {};
|
|
80
|
+
}
|
|
81
|
+
const streams = Array.isArray(parsed.streams) ? parsed.streams : [];
|
|
82
|
+
const video = streams.find((s) => s.codec_type === "video");
|
|
83
|
+
const format = parsed.format ?? {};
|
|
84
|
+
const duration = Number(format.duration ?? video?.duration ?? 0) || 0;
|
|
85
|
+
let fps = null;
|
|
86
|
+
if (video?.avg_frame_rate && typeof video.avg_frame_rate === "string") {
|
|
87
|
+
const [num, den] = video.avg_frame_rate.split("/").map(Number);
|
|
88
|
+
if (num && den)
|
|
89
|
+
fps = num / den;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
duration_sec: duration,
|
|
93
|
+
width: video?.width ?? null,
|
|
94
|
+
height: video?.height ?? null,
|
|
95
|
+
codec: video?.codec_name ?? null,
|
|
96
|
+
size_bytes: format.size ? Number(format.size) : null,
|
|
97
|
+
fps
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Detect scene boundaries with the portable `select='gt(scene,T)',showinfo`
|
|
102
|
+
* pass, parsing pts_time from ffmpeg's stderr. Falls back to fixed-interval
|
|
103
|
+
* chunking when nothing is detected (e.g. a single continuous take), so a scan
|
|
104
|
+
* always yields clips. Same command shape on devcli and cloud. With
|
|
105
|
+
* opts.windows set, each window is detected independently and scene times are
|
|
106
|
+
* offset back into source time.
|
|
107
|
+
*/
|
|
108
|
+
export async function detectScenes(videoPath, opts = {}) {
|
|
109
|
+
const duration = opts.durationSec ?? (await probeVideo(videoPath)).duration_sec;
|
|
110
|
+
if (!Number.isFinite(duration) || duration <= 0)
|
|
111
|
+
return [];
|
|
112
|
+
const windows = (opts.windows ?? [])
|
|
113
|
+
.map((w) => ({ start_sec: Math.max(0, w.start_sec), end_sec: Math.min(w.end_sec, duration) }))
|
|
114
|
+
.filter((w) => w.end_sec - w.start_sec > 0.25);
|
|
115
|
+
if (windows.length === 0) {
|
|
116
|
+
return detectScenesInSpan(videoPath, { ...opts, durationSec: duration, spanStart: 0, spanEnd: duration, indexOffset: 0 });
|
|
117
|
+
}
|
|
118
|
+
const scenes = [];
|
|
119
|
+
for (const window of windows) {
|
|
120
|
+
const detected = await detectScenesInSpan(videoPath, {
|
|
121
|
+
...opts,
|
|
122
|
+
durationSec: duration,
|
|
123
|
+
spanStart: window.start_sec,
|
|
124
|
+
spanEnd: window.end_sec,
|
|
125
|
+
indexOffset: scenes.length
|
|
126
|
+
});
|
|
127
|
+
scenes.push(...detected);
|
|
128
|
+
}
|
|
129
|
+
return scenes;
|
|
130
|
+
}
|
|
131
|
+
async function detectScenesInSpan(videoPath, opts) {
|
|
132
|
+
const threshold = opts.threshold ?? 0.3;
|
|
133
|
+
// Preserve very short detected shots as independent candidates. Absorbing a
|
|
134
|
+
// 2-10 frame flash into its neighbour makes it impossible for prompt/OCR
|
|
135
|
+
// refinement to reject that flash later. 50ms only de-duplicates effectively
|
|
136
|
+
// identical detector timestamps.
|
|
137
|
+
const minScene = opts.minSceneSec ?? 0.05;
|
|
138
|
+
const maxScene = opts.maxSceneSec ?? 8;
|
|
139
|
+
const { spanStart, spanEnd } = opts;
|
|
140
|
+
const spanDuration = spanEnd - spanStart;
|
|
141
|
+
if (!Number.isFinite(spanDuration) || spanDuration <= 0)
|
|
142
|
+
return [];
|
|
143
|
+
const ffmpeg = await resolveFfmpeg();
|
|
144
|
+
// -ss/-t BEFORE -i: input-seek so only the window is decoded. pts_time then
|
|
145
|
+
// restarts near 0, so cuts are offset back by spanStart below.
|
|
146
|
+
const seekArgs = spanStart > 0 || spanEnd < (opts.durationSec ?? Infinity)
|
|
147
|
+
? ["-ss", spanStart.toFixed(3), "-t", spanDuration.toFixed(3)]
|
|
148
|
+
: [];
|
|
149
|
+
const { stderr } = await run(ffmpeg, [
|
|
150
|
+
"-hide_banner",
|
|
151
|
+
...seekArgs,
|
|
152
|
+
"-i", videoPath,
|
|
153
|
+
"-filter:v", `select='gt(scene,${threshold})',showinfo`,
|
|
154
|
+
"-f", "null",
|
|
155
|
+
"-"
|
|
156
|
+
]);
|
|
157
|
+
const cuts = [];
|
|
158
|
+
const re = /pts_time:([0-9.]+)/g;
|
|
159
|
+
let m;
|
|
160
|
+
while ((m = re.exec(stderr)) !== null) {
|
|
161
|
+
const t = Number(m[1]) + (seekArgs.length ? spanStart : 0);
|
|
162
|
+
if (Number.isFinite(t) && t > spanStart && t < spanEnd)
|
|
163
|
+
cuts.push(t);
|
|
164
|
+
}
|
|
165
|
+
cuts.sort((a, b) => a - b);
|
|
166
|
+
// Build [spanStart, cut1, cut2, …, spanEnd] boundaries.
|
|
167
|
+
let bounds = [spanStart, ...cuts, spanEnd];
|
|
168
|
+
// If nothing detected, chunk at maxScene.
|
|
169
|
+
if (cuts.length === 0) {
|
|
170
|
+
bounds = [spanStart];
|
|
171
|
+
for (let t = spanStart + maxScene; t < spanEnd; t += maxScene)
|
|
172
|
+
bounds.push(t);
|
|
173
|
+
bounds.push(spanEnd);
|
|
174
|
+
}
|
|
175
|
+
// Merge boundaries closer than minScene.
|
|
176
|
+
const merged = [bounds[0]];
|
|
177
|
+
for (let i = 1; i < bounds.length; i++) {
|
|
178
|
+
if (bounds[i] - merged[merged.length - 1] >= minScene || i === bounds.length - 1) {
|
|
179
|
+
merged.push(bounds[i]);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const scenes = [];
|
|
183
|
+
for (let i = 0; i < merged.length - 1; i++) {
|
|
184
|
+
let start = merged[i];
|
|
185
|
+
const end = merged[i + 1];
|
|
186
|
+
// Split over-long scenes so clips stay usable and tagging stays cheap.
|
|
187
|
+
const span = end - start;
|
|
188
|
+
if (span > maxScene) {
|
|
189
|
+
const chunks = Math.ceil(span / maxScene);
|
|
190
|
+
const step = span / chunks;
|
|
191
|
+
for (let c = 0; c < chunks; c++) {
|
|
192
|
+
const s = start + c * step;
|
|
193
|
+
const e = c === chunks - 1 ? end : s + step;
|
|
194
|
+
scenes.push(makeScene(opts.indexOffset + scenes.length, s, e));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
scenes.push(makeScene(opts.indexOffset + scenes.length, start, end));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return scenes;
|
|
202
|
+
}
|
|
203
|
+
function makeScene(index, start, end) {
|
|
204
|
+
const s = Math.max(0, start);
|
|
205
|
+
const e = Math.max(s + 0.01, end);
|
|
206
|
+
return { index, start_time_sec: round3(s), end_time_sec: round3(e), duration_sec: round3(e - s) };
|
|
207
|
+
}
|
|
208
|
+
/** Extract up to `count` small keyframe JPEGs across a scene (for low-cost tagging). */
|
|
209
|
+
export async function extractKeyframes(input) {
|
|
210
|
+
const count = Math.max(1, Math.min(3, input.count ?? 2));
|
|
211
|
+
const width = input.width ?? 384;
|
|
212
|
+
mkdirSync(input.outDir, { recursive: true });
|
|
213
|
+
const ffmpeg = await resolveFfmpeg();
|
|
214
|
+
const { start_time_sec: start, duration_sec: dur } = input.scene;
|
|
215
|
+
// Sample points inside the clip, avoiding the very edges.
|
|
216
|
+
const points = count === 1
|
|
217
|
+
? [start + dur / 2]
|
|
218
|
+
: Array.from({ length: count }, (_, i) => start + dur * ((i + 1) / (count + 1)));
|
|
219
|
+
const vf = input.cropFilter ? `${input.cropFilter},scale=${width}:-2` : `scale=${width}:-2`;
|
|
220
|
+
const outPaths = [];
|
|
221
|
+
for (let i = 0; i < points.length; i++) {
|
|
222
|
+
const out = path.join(input.outDir, `scene-${input.scene.index}-kf${i}.jpg`);
|
|
223
|
+
const { code } = await run(ffmpeg, [
|
|
224
|
+
"-y",
|
|
225
|
+
"-ss", points[i].toFixed(3),
|
|
226
|
+
"-i", input.videoPath,
|
|
227
|
+
"-frames:v", "1",
|
|
228
|
+
"-vf", vf,
|
|
229
|
+
"-q:v", "5",
|
|
230
|
+
out
|
|
231
|
+
]);
|
|
232
|
+
if (code === 0 && existsSync(out))
|
|
233
|
+
outPaths.push(out);
|
|
234
|
+
}
|
|
235
|
+
return outPaths;
|
|
236
|
+
}
|
|
237
|
+
/** Cut the scene into a standalone mp4 (re-encoded for frame-accurate boundaries). */
|
|
238
|
+
export async function extractClip(input) {
|
|
239
|
+
mkdirSync(path.dirname(input.outPath), { recursive: true });
|
|
240
|
+
const ffmpeg = await resolveFfmpeg();
|
|
241
|
+
const { start_time_sec: start, duration_sec: dur } = input.scene;
|
|
242
|
+
// A crop cannot ride on stream-copy — force the re-encode path when present.
|
|
243
|
+
const args = input.reencode === false && !input.cropFilter
|
|
244
|
+
? ["-y", "-ss", start.toFixed(3), "-i", input.videoPath, "-t", dur.toFixed(3), "-c", "copy", "-avoid_negative_ts", "make_zero", input.outPath]
|
|
245
|
+
: [
|
|
246
|
+
"-y",
|
|
247
|
+
"-ss", start.toFixed(3),
|
|
248
|
+
"-i", input.videoPath,
|
|
249
|
+
"-t", dur.toFixed(3),
|
|
250
|
+
...(input.cropFilter ? ["-vf", input.cropFilter] : []),
|
|
251
|
+
"-c:v", "libx264", "-preset", "veryfast", "-crf", "23", "-pix_fmt", "yuv420p",
|
|
252
|
+
"-c:a", "aac", "-b:a", "128k",
|
|
253
|
+
"-movflags", "+faststart",
|
|
254
|
+
input.outPath
|
|
255
|
+
];
|
|
256
|
+
const { code, stderr } = await run(ffmpeg, args);
|
|
257
|
+
const ok = code === 0 && existsSync(input.outPath);
|
|
258
|
+
if (!ok) {
|
|
259
|
+
input.onError?.(`ffmpeg exit ${code}: ${stderr.slice(-500).replace(/\s+/g, " ").trim()}`);
|
|
260
|
+
}
|
|
261
|
+
return ok;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Trim EXACTLY [start, start+dur] of the source's AUDIO into a standalone
|
|
265
|
+
* `.m4a` (AAC) file — the audio-only twin of {@link extractClip}. The video
|
|
266
|
+
* track is dropped (`-vn`) so the result is a small, editor-placeable audio raw
|
|
267
|
+
* (e.g. clipping the soundtrack out of a copyright-free YouTube video). Returns
|
|
268
|
+
* false if the source has no audio track (ffmpeg exits non-zero with no output).
|
|
269
|
+
*/
|
|
270
|
+
export async function extractAudioClip(input) {
|
|
271
|
+
mkdirSync(path.dirname(input.outPath), { recursive: true });
|
|
272
|
+
const ffmpeg = await resolveFfmpeg();
|
|
273
|
+
const { start_time_sec: start, duration_sec: dur } = input.scene;
|
|
274
|
+
const { code, stderr } = await run(ffmpeg, [
|
|
275
|
+
"-y",
|
|
276
|
+
"-ss", start.toFixed(3),
|
|
277
|
+
"-i", input.videoPath,
|
|
278
|
+
"-t", dur.toFixed(3),
|
|
279
|
+
"-vn",
|
|
280
|
+
"-c:a", "aac", "-b:a", "192k",
|
|
281
|
+
"-movflags", "+faststart",
|
|
282
|
+
input.outPath
|
|
283
|
+
]);
|
|
284
|
+
const ok = code === 0 && existsSync(input.outPath);
|
|
285
|
+
if (!ok)
|
|
286
|
+
input.onError?.(`ffmpeg exit ${code}: ${stderr.slice(-500).replace(/\s+/g, " ").trim()}`);
|
|
287
|
+
return ok;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* A static waveform PNG for an audio clip — its "thumbnail" so an audio-only raw
|
|
291
|
+
* still shows something in the raws gallery (there is no video frame to grab).
|
|
292
|
+
* Best-effort; returns false if the source has no audio to plot.
|
|
293
|
+
*/
|
|
294
|
+
export async function extractWaveformThumbnail(input) {
|
|
295
|
+
mkdirSync(path.dirname(input.outPath), { recursive: true });
|
|
296
|
+
const ffmpeg = await resolveFfmpeg();
|
|
297
|
+
const { start_time_sec: start, duration_sec: dur } = input.scene;
|
|
298
|
+
const w = input.width ?? 320;
|
|
299
|
+
const h = input.height ?? 200;
|
|
300
|
+
const { code } = await run(ffmpeg, [
|
|
301
|
+
"-y",
|
|
302
|
+
"-ss", start.toFixed(3),
|
|
303
|
+
"-i", input.videoPath,
|
|
304
|
+
"-t", dur.toFixed(3),
|
|
305
|
+
"-filter_complex", `showwavespic=s=${w}x${h}:colors=#e2a63a`,
|
|
306
|
+
"-frames:v", "1",
|
|
307
|
+
input.outPath
|
|
308
|
+
]);
|
|
309
|
+
return code === 0 && existsSync(input.outPath);
|
|
310
|
+
}
|
|
311
|
+
/** Single representative thumbnail for the scene. */
|
|
312
|
+
export async function extractThumbnail(input) {
|
|
313
|
+
mkdirSync(path.dirname(input.outPath), { recursive: true });
|
|
314
|
+
const ffmpeg = await resolveFfmpeg();
|
|
315
|
+
const mid = input.scene.start_time_sec + input.scene.duration_sec / 2;
|
|
316
|
+
const vf = input.cropFilter
|
|
317
|
+
? `${input.cropFilter},scale=${input.width ?? 320}:-2`
|
|
318
|
+
: `scale=${input.width ?? 320}:-2`;
|
|
319
|
+
const { code } = await run(ffmpeg, [
|
|
320
|
+
"-y",
|
|
321
|
+
"-ss", mid.toFixed(3),
|
|
322
|
+
"-i", input.videoPath,
|
|
323
|
+
"-frames:v", "1",
|
|
324
|
+
"-vf", vf,
|
|
325
|
+
"-q:v", "3",
|
|
326
|
+
input.outPath
|
|
327
|
+
]);
|
|
328
|
+
return code === 0 && existsSync(input.outPath);
|
|
329
|
+
}
|
|
330
|
+
/** Extract a small mono/16k audio clip for the scene (feeds Gemini ASR). Returns null if no audio track. */
|
|
331
|
+
export async function extractSceneAudio(input) {
|
|
332
|
+
mkdirSync(path.dirname(input.outPath), { recursive: true });
|
|
333
|
+
const ffmpeg = await resolveFfmpeg();
|
|
334
|
+
const { start_time_sec: start, duration_sec: dur } = input.scene;
|
|
335
|
+
const { code } = await run(ffmpeg, [
|
|
336
|
+
"-y",
|
|
337
|
+
"-ss", start.toFixed(3),
|
|
338
|
+
"-i", input.videoPath,
|
|
339
|
+
"-t", dur.toFixed(3),
|
|
340
|
+
"-vn",
|
|
341
|
+
"-ac", "1",
|
|
342
|
+
"-ar", "16000",
|
|
343
|
+
"-c:a", "libmp3lame",
|
|
344
|
+
"-b:a", "32k",
|
|
345
|
+
input.outPath
|
|
346
|
+
]);
|
|
347
|
+
return code === 0 && existsSync(input.outPath) ? input.outPath : null;
|
|
348
|
+
}
|
|
349
|
+
function round3(n) {
|
|
350
|
+
return Math.round(n * 1000) / 1000;
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=ffmpeg.js.map
|