hyper-animator-codex 0.7.0 → 0.8.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/README.md +67 -7
- package/bin/hyper-animator-codex.mjs +31 -9
- package/bin/postinstall.mjs +19 -0
- package/lib/install-options.mjs +3 -0
- package/lib/install-skill.mjs +84 -2
- package/lib/minimax-config.mjs +3 -3
- package/package.json +3 -2
- package/skills/hyper-animator-codex/SKILL.md +58 -20
- package/skills/hyper-animator-codex/agents/openai.yaml +2 -2
- package/skills/hyper-animator-codex/contracts/shot-plan.schema.json +214 -0
- package/skills/hyper-animator-codex/contracts/workflow-policy.json +85 -0
- package/skills/hyper-animator-codex/references/HyperFrames-AI-Generation-Patterns-codex.md +788 -0
- package/skills/hyper-animator-codex/references/beat-sync-workflow.md +7 -0
- package/skills/hyper-animator-codex/references/examples/shot-plan-dual-catalog.json +69 -0
- package/skills/hyper-animator-codex/references/hyperframes-agent-pseudocode.ts +1 -1
- package/skills/hyper-animator-codex/references/hyperframes-catalog-map.json +97 -230
- package/skills/hyper-animator-codex/references/hyperframes-intent-workflow.md +23 -7
- package/skills/hyper-animator-codex/references/minimax-music-workflow.md +24 -4
- package/skills/hyper-animator-codex/references/narration-audio-workflow.md +31 -1
- package/skills/hyper-animator-codex/references/shotcraft/LICENSE +202 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/ai-stream-response/README.md +46 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/autolayout-gap-dial/README.md +53 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/beat-cut-moves/README.md +54 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/beat-step-list-theme-cycle/README.md +59 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/before-after-slider-scrub/README.md +47 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/bottom-push-stack-wipe/README.md +58 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/brand-frame-snap/README.md +51 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/brand-ink-open/README.md +35 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/bubble-swarm-takeover/README.md +62 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/canvas-materialize-moves/README.md +55 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/card-flip-reveal/README.md +50 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/card-flock-tumble/README.md +54 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/cel-flash-stomp/README.md +54 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/chart-live-moves/README.md +51 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/circle-match-iris/README.md +53 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/cloner-depth-echo/README.md +38 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/collab-cursor-moves/README.md +48 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/color-block-step-wipe/README.md +54 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/command-palette-summon/README.md +45 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/crane-rise-reveal/README.md +47 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/crash-zoom-punch/README.md +38 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/dataviz-landscape-open/README.md +54 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/deck-deal-flyin/README.md +45 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/depth-layer-moves/README.md +40 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/document-typewriter-reveal/README.md +40 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/draw-svg-trace/README.md +50 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/edit-hook-moves/README.md +38 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/element-body-moves/README.md +49 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/fui-hud-moves/README.md +44 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/gauge-readout-moves/README.md +45 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/glow-flyline-moves/README.md +51 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/gradient-word-sweep/README.md +49 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/graze-face-tour/README.md +56 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/hashtag-to-pill-materialize/README.md +57 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/hires-rasterize-3d-text/README.md +34 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/icon-field-colorize/README.md +53 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/icon-performance-moves/README.md +46 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/impact-feedback/README.md +50 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/input-trigger-moves/README.md +49 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/integration-hub-map/README.md +54 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/letterspace-materialize/README.md +52 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/light-play-moves/README.md +49 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/line-boil/README.md +52 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/line-carry-transition/README.md +52 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/list-stack-press/README.md +43 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/magician-card-flourish/README.md +63 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/marker-underline-title/README.md +48 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/montage-rhythm-moves/README.md +55 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/morph-from-primitive/README.md +51 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/neon-frame-forerun/README.md +50 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/neon-frame-orbit-drop/README.md +50 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/neon-triple-marquee/README.md +55 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/odometer-digit-roll/README.md +54 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/outro-group-photo-launch/README.md +39 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/overhead-camera-moves/README.md +50 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/page-turn-transitions/README.md +48 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/page-waterfall-wall/README.md +52 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/panel-grid-moves/README.md +52 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/paper-craft-moves/README.md +46 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/paper-plane-messenger/README.md +62 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/paper-title-card/README.md +36 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/particle-celebrate-hits/README.md +42 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/particle-sand-fill/README.md +39 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/pill-slot-cycle/README.md +57 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/print-texture-transitions/README.md +40 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/rhythm-interrupt-moves/README.md +44 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/riso-print-hits/README.md +54 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/row-embed/README.md +36 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/runway-ground-skim/README.md +52 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/sakuga-timing-shift/README.md +50 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/scene-locked-title/README.md +43 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/scroll-brake-moves/README.md +45 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/segmented-thumb-hero/README.md +57 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/shot-transitions/README.md +60 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/skeleton-reveal/README.md +54 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/slam-entrance-moves/README.md +53 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/smear-multiples/README.md +49 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/space-camera-moves/README.md +53 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/spectrum-morph-ui/README.md +50 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/speed-ramp-freeze/README.md +42 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/split-flap-title/README.md +52 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/spotlight-hero-card/README.md +39 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/spotlight-sweep-moves/README.md +51 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/steep-tilt-glide/README.md +53 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/stroke-segment-build/README.md +43 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/tear-streak-transitions/README.md +43 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/tension-camera-moves/README.md +55 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/text-as-mask/README.md +50 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/text-column-converge/README.md +55 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/theme-switch-moves/README.md +44 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/timeline-travel/README.md +43 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/title-demote-to-label/README.md +58 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/trailer-grammar-moves/README.md +51 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/transition-hidden-cut/README.md +52 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/transition-travel/README.md +48 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/type-and-filter/README.md +37 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/type-assembly-moves/README.md +50 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/type-entrance-moves/README.md +46 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/type-rhythm-sync/README.md +47 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/typewriter-moves/README.md +50 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/ui-strip-away-outro/README.md +63 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/ui-to-brand-morph/README.md +52 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/voice-waveform-live/README.md +52 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/wall-reveal-moves/README.md +53 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/wipe-transitions/README.md +47 -0
- package/skills/hyper-animator-codex/references/shotcraft/cards/word-relay-filmstrip/README.md +50 -0
- package/skills/hyper-animator-codex/references/shotcraft/catalog.json +3946 -0
- package/skills/hyper-animator-codex/references/shotcraft/references/aesthetic-rules.md +157 -0
- package/skills/hyper-animator-codex/references/shotcraft/references/final-review.md +107 -0
- package/skills/hyper-animator-codex/references/shotcraft/references/guided-free-creation.md +197 -0
- package/skills/hyper-animator-codex/references/shotcraft/references/music-beat-sync.md +125 -0
- package/skills/hyper-animator-codex/references/shotcraft/references/pipeline.md +357 -0
- package/skills/hyper-animator-codex/references/shotcraft/references/sound-design.md +126 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/assets/lib/Caption.tsx +50 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/assets/lib/DigitRoll.tsx +45 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/assets/lib/FlashCut.tsx +20 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/assets/lib/FlatPanel.tsx +65 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/assets/lib/PageCam.tsx +151 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/assets/lib/VerticalTicker.tsx +129 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/assets/lib/helpers/camera.tsx +75 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/assets/lib/helpers/motion.ts +40 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/assets/lib/helpers/rand.ts +11 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/assets/lib/helpers/shake.ts +10 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/_fixtures/Fixtures.tsx +83 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/ai-stream-response/StreamResponse.tsx +146 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/autolayout-gap-dial/AutolayoutGapDial.tsx +174 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/beat-cut-moves/BeatCutAccelerando.tsx +77 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/beat-cut-moves/PaparazziFlash.tsx +115 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx +98 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/before-after-slider-scrub/BeforeAfterSliderScrub.tsx +99 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/bottom-push-stack-wipe/BottomPushStackWipe.tsx +79 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/brand-frame-snap/BrandFrameSnap.tsx +110 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/bubble-swarm-takeover/BubbleSwarmTakeover.tsx +189 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/canvas-materialize-moves/DiagramCascadeBuild.tsx +172 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/canvas-materialize-moves/PanelToCanvasMaterialize.tsx +254 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/card-flip-reveal/CardFlipReveal.tsx +139 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/card-flock-tumble/CardFlockTumble.tsx +382 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/cel-flash-stomp/CelFlashStomp.tsx +111 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/chart-live-moves/AxisRescaleShockV2.tsx +282 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/chart-live-moves/OscilloscopeStreamV2.tsx +227 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/chart-live-moves/UnitDotSwarmRegroupV2.tsx +248 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/circle-match-iris/CircleMatchIris.tsx +124 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/cloner-depth-echo/ClonerDepthEcho.tsx +90 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/collab-cursor-moves/CursorCastEnsemble.tsx +136 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/collab-cursor-moves/CursorDialogueDuet.tsx +145 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/color-block-step-wipe/ColorBlockStepWipe.tsx +157 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/command-palette-summon/CommandPaletteSummon.tsx +173 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/crane-rise-reveal/CraneRiseReveal.tsx +91 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/crash-zoom-punch/CrashImpactReal.tsx +63 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/crash-zoom-punch/CrashZoomReal.tsx +60 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/dataviz-landscape-open/DatavizLandscapeOpen.tsx +401 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/depth-layer-moves/DollyZoomReal.tsx +55 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/depth-layer-moves/MultiplaneReal.tsx +45 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/draw-svg-trace/DrawSvgTrace.tsx +199 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/edit-hook-moves/LogoStingButton.tsx +106 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/element-body-moves/AxialStretch.tsx +112 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/element-body-moves/ContactShadowLift.tsx +117 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/fui-hud-moves/LineUnfoldPanel.tsx +125 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/fui-hud-moves/ReticleLockOn.tsx +139 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/gauge-readout-moves/NeedleSweepSelftest.tsx +146 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/gauge-readout-moves/TapeScrollFixedPointer.tsx +120 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/glow-flyline-moves/FlylineArc.tsx +158 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/glow-flyline-moves/GlowOrbAmbient.tsx +131 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/glow-flyline-moves/OrbFlylineRelay.tsx +268 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/gradient-word-sweep/GradientWordSweep.tsx +307 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/graze-face-tour/GrazeFaceTour.tsx +469 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/hashtag-to-pill-materialize/HashtagToPillMaterialize.tsx +166 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/icon-field-colorize/IconFieldColorize.tsx +93 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/icon-performance-moves/AttentionBounce.tsx +193 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/icon-performance-moves/PopBurstConfirm.tsx +162 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/impact-feedback/AnimeImpact.tsx +149 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/impact-feedback/HitCounter.tsx +147 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/input-trigger-moves/CursorPerformancePunchIn.tsx +154 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/input-trigger-moves/KeycapSmashCut.tsx +217 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/integration-hub-map/IntegrationHubMap.tsx +554 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/letterspace-materialize/LetterspaceMaterialize.tsx +85 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/light-play-moves/HalationBloom.tsx +139 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/light-play-moves/SheenSweepRetry.tsx +85 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/light-play-moves/SpotlightSweepReveal.tsx +102 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/line-boil/LineBoil.tsx +140 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/line-carry-transition/LineCarryTransition.tsx +151 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/magician-card-flourish/MagicianCardFlourish.tsx +294 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/marker-underline-title/MarkerUnderlineTitle.tsx +87 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/montage-rhythm-moves/DominoCascade.tsx +125 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/montage-rhythm-moves/DropBlackoutSlam.tsx +135 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/montage-rhythm-moves/WrightTripleCut.tsx +175 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/morph-from-primitive/MorphFromPrimitive.tsx +190 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/neon-frame-forerun/NeonFrameForerun.tsx +279 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/neon-frame-orbit-drop/NeonFrameForerunOrbit.tsx +273 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/neon-triple-marquee/NeonTripleMarquee.tsx +111 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/odometer-digit-roll/OdometerDigitRoll.tsx +178 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/overhead-camera-moves/OverheadTabletopDrop.tsx +129 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/overhead-camera-moves/TiltReveal.tsx +55 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/page-turn-transitions/BarnDoorSplit.tsx +138 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/page-turn-transitions/CubeRotate.tsx +114 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/page-waterfall-wall/PageWaterfallWall.tsx +76 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/page-waterfall-wall/VerticalTicker.tsx +129 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/panel-grid-moves/ComicPanelSplit.tsx +171 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/panel-grid-moves/FlipGridReflow.tsx +121 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/panel-grid-moves/GridFlashMosaic.tsx +182 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/paper-craft-moves/MaskingTapeSlap.tsx +163 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/paper-craft-moves/PopupBookRise.tsx +134 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/paper-plane-messenger/PaperPlaneMessenger.tsx +183 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/particle-celebrate-hits/ConfettiCrossfire.tsx +115 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/particle-celebrate-hits/CounterTickSparks.tsx +103 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/particle-sand-fill/ParticleSandFill.tsx +119 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/pill-slot-cycle/PillSlotCycle.tsx +220 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/print-texture-transitions/InkBleedReveal.tsx +81 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/rhythm-interrupt-moves/JumpCutPunchIn.tsx +72 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/rhythm-interrupt-moves/StrobeBlackFrames.tsx +99 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/riso-print-hits/RisoBeatPump.tsx +155 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/riso-print-hits/RisoMisregistrationHit.tsx +120 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/runway-ground-skim/RunwayGroundSkim.tsx +258 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/sakuga-timing-shift/SakugaTimingShift.tsx +178 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/scene-locked-title/SceneLockedTitle.tsx +98 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/scroll-brake-moves/BrakeReticleLock.tsx +171 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/scroll-brake-moves/ChangelogScrollBrake.tsx +117 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/segmented-thumb-hero/SegmentedThumbHero.tsx +213 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/shot-transitions/MaskWipeReal.tsx +67 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/shot-transitions/PortalWipeV2.tsx +128 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/shot-transitions/WhipBrakeReal.tsx +60 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/shot-transitions/WhipPanReal.tsx +45 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/skeleton-reveal/SkeletonReveal.tsx +283 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/slam-entrance-moves/ImpactBurstKit.tsx +185 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/slam-entrance-moves/KanadaPerspectiveSnap.tsx +91 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/slam-entrance-moves/ScoreSlam.tsx +225 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/smear-multiples/SmearMultiples.tsx +88 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/space-camera-moves/DroneDiveLanding.tsx +91 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/space-camera-moves/ExplodedView.tsx +178 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/space-camera-moves/SnorricamLock.tsx +130 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/spectrum-morph-ui/SpectrumMorphUi.tsx +129 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/speed-ramp-freeze/FreezeAnnotateReal.tsx +70 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/speed-ramp-freeze/SpeedRampReal.tsx +58 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/split-flap-title/SplitFlapFlip.tsx +189 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/spotlight-sweep-moves/CornerSpotlightReveal.tsx +121 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/spotlight-sweep-moves/GlowWakeSleepPanel.tsx +183 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/spotlight-sweep-moves/SlideSpotlightPan.tsx +171 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/steep-tilt-glide/SteepTiltGlide.tsx +315 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/stroke-segment-build/StrokeSegmentBuild.tsx +126 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/tear-streak-transitions/GlitchDisplace.tsx +107 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/tension-camera-moves/BulletTimeFreezeOrbit.tsx +219 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/tension-camera-moves/DutchRollToLevel.tsx +110 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/tension-camera-moves/PullBackIsolation.tsx +120 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/tension-camera-moves/SlowPushIn.tsx +96 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/text-as-mask/TextAsMask.tsx +94 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/text-column-converge/TextColumnConverge.tsx +122 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/theme-switch-moves/PaletteThemeRipple.tsx +207 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/theme-switch-moves/ThemeSweepToggle.tsx +123 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/timeline-travel/TimelineTravel.tsx +123 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/title-demote-to-label/TitleDemoteToLabel.tsx +174 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/trailer-grammar-moves/CardFootageCadence.tsx +118 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/trailer-grammar-moves/SmashCut.tsx +105 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/trailer-grammar-moves/TrailerBumper.tsx +103 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/transition-hidden-cut/InvisibleCut.tsx +93 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/transition-hidden-cut/LightLeakBurn.tsx +122 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/transition-hidden-cut/VersusSlam.tsx +104 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/transition-travel/LetterformZoom.tsx +141 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/transition-travel/SharedElementMorph.tsx +101 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/type-assembly-moves/LetterformDriftAssembly.tsx +107 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/type-assembly-moves/SplitTextStagger.tsx +101 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/type-assembly-moves/TextOnPath.tsx +92 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/type-assembly-moves/TrackingExpandReveal.tsx +109 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/type-entrance-moves/LetterDropPhysics.tsx +100 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/type-entrance-moves/ScrambleDecode.tsx +90 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/type-rhythm-sync/FontWeightPump.tsx +124 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/type-rhythm-sync/KaraokeFillSync.tsx +93 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/typewriter-moves/TerminalTypewriter.tsx +122 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/typewriter-moves/TypewriterErrorRetype.tsx +109 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/ui-strip-away-outro/UiStripAwayOutro.tsx +218 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/ui-to-brand-morph/IconFlipBloomLogo.tsx +199 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/ui-to-brand-morph/InputMorphsIntoLogo.tsx +215 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/voice-waveform-live/VoiceWaveformLive.tsx +171 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/wall-reveal-moves/BentoLightUp.tsx +135 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/wipe-transitions/BlindsSlice.tsx +107 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/wipe-transitions/ClockWipe.tsx +83 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/demos/word-relay-filmstrip/WordRelayFilmstrip.tsx +300 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/Root.tsx +15 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/aifl/Caption.tsx +49 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/aifl/DigitRoll.tsx +44 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/aifl/FlashCut.tsx +19 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/aifl/Main.tsx +163 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/aifl/PaperTitleCard.tsx +89 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/aifl/live/PageCam.tsx +145 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/aifl/live/SceneDetail.tsx +146 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/aifl/live/SceneFlyIn.tsx +447 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/aifl/live/SceneOpen.tsx +532 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/aifl/live/SceneOutroLive.tsx +394 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/aifl/live/ScenePapers.tsx +185 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/aifl/live/SceneWbr.tsx +299 -0
- package/skills/hyper-animator-codex/references/shotcraft/source/template/src/index.ts +4 -0
- package/skills/hyper-animator-codex/references/shotcraft/source-notes.md +10 -0
- package/skills/hyper-animator-codex/scripts/generate_minimax_music.mjs +27 -10
- package/skills/hyper-animator-codex/scripts/generate_minimax_tts.mjs +142 -13
- package/skills/hyper-animator-codex/scripts/generate_subtitles.mjs +42 -3
- package/skills/hyper-animator-codex/scripts/minimax_runtime_config.mjs +3 -3
- package/skills/hyper-animator-codex/scripts/query_shotcraft.mjs +140 -0
- package/skills/hyper-animator-codex/scripts/validate_hyperframes_html.py +22 -0
- package/skills/hyper-animator-codex/scripts/validate_provider_policy.mjs +108 -0
- package/skills/hyper-animator-codex/scripts/validate_shot_plan.mjs +193 -0
- package/skills/hyper-animator-codex/vendor/music-beat-detector/beat_detector/analyzer.py +3 -1
- package/skills/hyper-animator-codex/vendor/music-beat-detector/beat_detector/beat.py +37 -1
|
@@ -15,6 +15,7 @@ function requireValue(args, index, flag) {
|
|
|
15
15
|
function parseArgs(args) {
|
|
16
16
|
const parsed = {
|
|
17
17
|
narration: undefined,
|
|
18
|
+
timing_manifest: undefined,
|
|
18
19
|
output_dir: join(process.cwd(), "hyper-animator-output", "subtitles"),
|
|
19
20
|
style: "default",
|
|
20
21
|
};
|
|
@@ -30,6 +31,9 @@ function parseArgs(args) {
|
|
|
30
31
|
} else if (arg === "--style") {
|
|
31
32
|
parsed.style = requireValue(args, index, arg);
|
|
32
33
|
index += 1;
|
|
34
|
+
} else if (arg === "--timing-manifest") {
|
|
35
|
+
parsed.timing_manifest = requireValue(args, index, arg);
|
|
36
|
+
index += 1;
|
|
33
37
|
} else if (arg === "--help" || arg === "-h") {
|
|
34
38
|
parsed.help = true;
|
|
35
39
|
} else {
|
|
@@ -71,14 +75,43 @@ function roundTime(value) {
|
|
|
71
75
|
return Number(value.toFixed(3));
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
function
|
|
78
|
+
function validateTimingManifest(manifest, narration) {
|
|
79
|
+
if (!manifest || typeof manifest !== "object" || !Array.isArray(manifest.scenes)) {
|
|
80
|
+
throw new Error("timing manifest must include scenes[]");
|
|
81
|
+
}
|
|
82
|
+
if (manifest.provider !== "minimax_cn") {
|
|
83
|
+
throw new Error("timing manifest provider must be minimax_cn");
|
|
84
|
+
}
|
|
85
|
+
if (manifest.scenes.length !== narration.scenes.length) {
|
|
86
|
+
throw new Error("timing manifest scene count must match narration scenes");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let expectedStart = 0;
|
|
90
|
+
manifest.scenes.forEach((scene, index) => {
|
|
91
|
+
if (!Number.isFinite(scene.start_seconds) || !Number.isFinite(scene.end_seconds)
|
|
92
|
+
|| scene.start_seconds < 0 || scene.end_seconds <= scene.start_seconds) {
|
|
93
|
+
throw new Error(`timing manifest scenes[${index}] must have valid start_seconds/end_seconds`);
|
|
94
|
+
}
|
|
95
|
+
if (Math.abs(scene.start_seconds - expectedStart) > 0.001) {
|
|
96
|
+
throw new Error(`timing manifest scenes[${index}] must be contiguous`);
|
|
97
|
+
}
|
|
98
|
+
expectedStart = scene.end_seconds;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function buildCues(narration, timingManifest) {
|
|
75
103
|
const cues = [];
|
|
76
104
|
let cursor = 0;
|
|
77
105
|
|
|
78
106
|
narration.scenes.forEach((scene, sceneIndex) => {
|
|
79
107
|
const parts = splitNarration(scene.narration);
|
|
80
|
-
const
|
|
108
|
+
const timing = timingManifest?.scenes?.[sceneIndex];
|
|
109
|
+
const sceneStart = timing?.start_seconds ?? cursor;
|
|
110
|
+
const duration = timing
|
|
111
|
+
? timing.end_seconds - timing.start_seconds
|
|
112
|
+
: Number(scene.duration_estimate) || Math.max(parts.length * 2, 2);
|
|
81
113
|
const cueDuration = duration / Math.max(parts.length, 1);
|
|
114
|
+
cursor = sceneStart;
|
|
82
115
|
|
|
83
116
|
parts.forEach((text) => {
|
|
84
117
|
const start = cursor;
|
|
@@ -124,8 +157,12 @@ async function generateSubtitles(options) {
|
|
|
124
157
|
|
|
125
158
|
const narration = JSON.parse(await readFile(options.narration, "utf8"));
|
|
126
159
|
validateNarration(narration);
|
|
160
|
+
const timingManifest = options.timing_manifest
|
|
161
|
+
? JSON.parse(await readFile(options.timing_manifest, "utf8"))
|
|
162
|
+
: null;
|
|
163
|
+
if (timingManifest) validateTimingManifest(timingManifest, narration);
|
|
127
164
|
|
|
128
|
-
const cues = buildCues(narration);
|
|
165
|
+
const cues = buildCues(narration, timingManifest);
|
|
129
166
|
await mkdir(options.output_dir, { recursive: true });
|
|
130
167
|
|
|
131
168
|
const jsonPath = join(options.output_dir, "subtitles.json");
|
|
@@ -134,6 +171,7 @@ async function generateSubtitles(options) {
|
|
|
134
171
|
ok: true,
|
|
135
172
|
style: options.style,
|
|
136
173
|
source: options.narration,
|
|
174
|
+
timing_source: options.timing_manifest ?? null,
|
|
137
175
|
cues,
|
|
138
176
|
};
|
|
139
177
|
|
|
@@ -153,6 +191,7 @@ function printHelp() {
|
|
|
153
191
|
|
|
154
192
|
Options:
|
|
155
193
|
--narration <file> Narration JSON file
|
|
194
|
+
--timing-manifest <file> MiniMax narration-manifest.json with exact scene timing
|
|
156
195
|
--output-dir <dir> Output directory, default hyper-animator-output/subtitles
|
|
157
196
|
--style <name> Subtitle style label for downstream HTML/render steps
|
|
158
197
|
`);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
|
-
export const DEFAULT_MINIMAX_MODEL = "music-2.6
|
|
5
|
-
export const TEXT_MUSIC_MODELS = new Set(["music-2.6", "music-2.6-free"]);
|
|
4
|
+
export const DEFAULT_MINIMAX_MODEL = "music-2.6";
|
|
5
|
+
export const TEXT_MUSIC_MODELS = new Set(["music-3.0", "music-2.6", "music-2.6-free"]);
|
|
6
6
|
|
|
7
7
|
async function pathExists(path) {
|
|
8
8
|
try {
|
|
@@ -61,7 +61,7 @@ export function validateMinimaxConfig(config = {}) {
|
|
|
61
61
|
const model = normalized.model || DEFAULT_MINIMAX_MODEL;
|
|
62
62
|
|
|
63
63
|
if (!TEXT_MUSIC_MODELS.has(model)) {
|
|
64
|
-
throw new Error(`Unsupported MiniMax text music model: ${model}. Use music-2.6 or music-2.6-free.`);
|
|
64
|
+
throw new Error(`Unsupported MiniMax text music model: ${model}. Use music-3.0, music-2.6, or music-2.6-free.`);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
return {
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const catalogPath = join(dirname(scriptDir), "references", "shotcraft", "catalog.json");
|
|
8
|
+
|
|
9
|
+
function requireValue(args, index, flag) {
|
|
10
|
+
const value = args[index + 1];
|
|
11
|
+
if (!value || value.startsWith("--")) throw new Error(`${flag} requires a value`);
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseArgs(args) {
|
|
16
|
+
const parsed = { limit: 8, json: false, include_reference_only: false };
|
|
17
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
18
|
+
const arg = args[index];
|
|
19
|
+
if (arg === "--query" || arg === "-q") {
|
|
20
|
+
parsed.query = requireValue(args, index, arg);
|
|
21
|
+
index += 1;
|
|
22
|
+
} else if (arg === "--limit") {
|
|
23
|
+
parsed.limit = Number.parseInt(requireValue(args, index, arg), 10);
|
|
24
|
+
index += 1;
|
|
25
|
+
} else if (arg === "--json") {
|
|
26
|
+
parsed.json = true;
|
|
27
|
+
} else if (arg === "--include-reference-only") {
|
|
28
|
+
parsed.include_reference_only = true;
|
|
29
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
30
|
+
parsed.help = true;
|
|
31
|
+
} else {
|
|
32
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function tokens(value) {
|
|
39
|
+
return String(value)
|
|
40
|
+
.toLowerCase()
|
|
41
|
+
.split(/[\s,,。::;;/|()[\]{}"'`_-]+/)
|
|
42
|
+
.map((token) => token.trim())
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function scoreText(queryTokens, fields) {
|
|
47
|
+
const haystack = fields.filter(Boolean).join("\n").toLowerCase();
|
|
48
|
+
return queryTokens.reduce((score, token) => {
|
|
49
|
+
if (!haystack.includes(token)) return score;
|
|
50
|
+
const exactWord = new RegExp(`(^|[^a-z0-9])${token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}([^a-z0-9]|$)`, "i");
|
|
51
|
+
return score + (exactWord.test(haystack) ? 4 : 2);
|
|
52
|
+
}, 0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function rankCard(card, queryTokens, includeReferenceOnly) {
|
|
56
|
+
const eligibleStyles = card.styles.filter((style) => (
|
|
57
|
+
includeReferenceOnly || style.implementation_status === "implemented"
|
|
58
|
+
));
|
|
59
|
+
if (eligibleStyles.length === 0) return null;
|
|
60
|
+
|
|
61
|
+
const styleScores = eligibleStyles.map((style) => ({
|
|
62
|
+
style,
|
|
63
|
+
score: scoreText(queryTokens, [
|
|
64
|
+
style.key,
|
|
65
|
+
style.label,
|
|
66
|
+
style.description,
|
|
67
|
+
style.use,
|
|
68
|
+
]),
|
|
69
|
+
})).sort((left, right) => right.score - left.score || left.style.key.localeCompare(right.style.key));
|
|
70
|
+
|
|
71
|
+
const cardScore = scoreText(queryTokens, [
|
|
72
|
+
card.name,
|
|
73
|
+
card.summary,
|
|
74
|
+
card.use,
|
|
75
|
+
card.energy,
|
|
76
|
+
card.intention,
|
|
77
|
+
]);
|
|
78
|
+
const bestStyle = styleScores[0];
|
|
79
|
+
const slugBonus = queryTokens.some((token) => card.name.includes(token)) ? 3 : 0;
|
|
80
|
+
return {
|
|
81
|
+
score: cardScore + bestStyle.score + slugBonus,
|
|
82
|
+
card: card.name,
|
|
83
|
+
summary: card.summary,
|
|
84
|
+
use: card.use,
|
|
85
|
+
duration: card.duration,
|
|
86
|
+
energy: card.energy,
|
|
87
|
+
code_status: card.code_status,
|
|
88
|
+
style: bestStyle.style.key,
|
|
89
|
+
implementation_status: bestStyle.style.implementation_status,
|
|
90
|
+
usage_document: bestStyle.style.usage_document || card.usage_document,
|
|
91
|
+
source_files: bestStyle.style.source_files,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function printHuman(results) {
|
|
96
|
+
for (const [index, result] of results.entries()) {
|
|
97
|
+
console.log(`${index + 1}. ${result.card} / ${result.style} (score ${result.score})`);
|
|
98
|
+
console.log(` ${result.summary}`);
|
|
99
|
+
console.log(` use: ${result.use}`);
|
|
100
|
+
console.log(` source: ${result.source_files.join(", ")}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function main() {
|
|
105
|
+
try {
|
|
106
|
+
const args = parseArgs(process.argv.slice(2));
|
|
107
|
+
if (args.help) {
|
|
108
|
+
console.log("Usage: query_shotcraft.mjs --query <text> [--limit 8] [--json] [--include-reference-only]");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!args.query?.trim()) throw new Error("--query is required");
|
|
112
|
+
if (!Number.isInteger(args.limit) || args.limit < 1 || args.limit > 50) {
|
|
113
|
+
throw new Error("--limit must be an integer from 1 to 50");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const catalog = JSON.parse(await readFile(catalogPath, "utf8"));
|
|
117
|
+
const queryTokens = tokens(args.query);
|
|
118
|
+
const results = catalog.cards
|
|
119
|
+
.map((card) => rankCard(card, queryTokens, args.include_reference_only))
|
|
120
|
+
.filter((result) => result && result.score > 0)
|
|
121
|
+
.sort((left, right) => right.score - left.score || left.card.localeCompare(right.card))
|
|
122
|
+
.slice(0, args.limit);
|
|
123
|
+
|
|
124
|
+
if (args.json) {
|
|
125
|
+
console.log(JSON.stringify({
|
|
126
|
+
ok: true,
|
|
127
|
+
query: args.query,
|
|
128
|
+
source_revision: catalog.source_revision,
|
|
129
|
+
results,
|
|
130
|
+
}, null, 2));
|
|
131
|
+
} else {
|
|
132
|
+
printHuman(results);
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.log(JSON.stringify({ ok: false, error: error.message }, null, 2));
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
main();
|
|
@@ -13,6 +13,20 @@ def has_attr(html: str, name: str) -> bool:
|
|
|
13
13
|
return re.search(rf"\b{name}\s*=\s*['\"]?[^'\"\s>]+", html, re.IGNORECASE) is not None
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
def numeric_attr(html: str, name: str) -> float | None:
|
|
17
|
+
match = re.search(
|
|
18
|
+
rf"\b{name}\s*=\s*(?:['\"]([^'\"]+)['\"]|([^\s>]+))",
|
|
19
|
+
html,
|
|
20
|
+
re.IGNORECASE,
|
|
21
|
+
)
|
|
22
|
+
if not match:
|
|
23
|
+
return None
|
|
24
|
+
try:
|
|
25
|
+
return float(match.group(1) or match.group(2))
|
|
26
|
+
except ValueError:
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
16
30
|
def check_html(
|
|
17
31
|
html: str,
|
|
18
32
|
*,
|
|
@@ -26,6 +40,11 @@ def check_html(
|
|
|
26
40
|
for attr in ("data-width", "data-height", "data-duration", "data-composition-id"):
|
|
27
41
|
if not has_attr(html, attr):
|
|
28
42
|
failures.append(f"missing {attr}")
|
|
43
|
+
for attr in ("data-width", "data-height", "data-duration"):
|
|
44
|
+
if has_attr(html, attr):
|
|
45
|
+
value = numeric_attr(html, attr)
|
|
46
|
+
if value is None or value <= 0:
|
|
47
|
+
failures.append(f"{attr} must be a positive number")
|
|
29
48
|
|
|
30
49
|
if "<!-- paste from " in html:
|
|
31
50
|
failures.append("unresolved component paste placeholder")
|
|
@@ -47,6 +66,9 @@ def check_html(
|
|
|
47
66
|
if re.search(r"\bsetInterval\s*\(", html):
|
|
48
67
|
failures.append("uses setInterval(); primary render progress must be timeline-driven")
|
|
49
68
|
|
|
69
|
+
if re.search(r"\bMath\s*\.\s*random\s*\(", html):
|
|
70
|
+
failures.append("uses Math.random(); visual generation must use a fixed-seed deterministic PRNG")
|
|
71
|
+
|
|
50
72
|
if re.search(r"\brequestAnimationFrame\s*\(", html) and "fallback" not in html.lower():
|
|
51
73
|
warnings.append("requestAnimationFrame found; ensure it is not the primary render clock and has render fallback")
|
|
52
74
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const policyPath = join(dirname(scriptDir), "contracts", "workflow-policy.json");
|
|
8
|
+
|
|
9
|
+
function requireValue(args, index, flag) {
|
|
10
|
+
const value = args[index + 1];
|
|
11
|
+
if (!value || value.startsWith("--")) throw new Error(`${flag} requires a value`);
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseArgs(args) {
|
|
16
|
+
const parsed = {};
|
|
17
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
18
|
+
const arg = args[index];
|
|
19
|
+
if (["--kind", "--provider", "--endpoint"].includes(arg)) {
|
|
20
|
+
parsed[arg.slice(2)] = requireValue(args, index, arg);
|
|
21
|
+
index += 1;
|
|
22
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
23
|
+
parsed.help = true;
|
|
24
|
+
} else {
|
|
25
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function output(payload, exitCode = 0) {
|
|
32
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
33
|
+
process.exitCode = exitCode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function reject(code, message, details = {}) {
|
|
37
|
+
output({ ok: false, error: { code, message, ...details } }, 1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function main() {
|
|
41
|
+
try {
|
|
42
|
+
const args = parseArgs(process.argv.slice(2));
|
|
43
|
+
if (args.help) {
|
|
44
|
+
console.log("Usage: validate_provider_policy.mjs --kind <narration|bgm> --provider <name> --endpoint <url>");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!["narration", "bgm"].includes(args.kind)) {
|
|
49
|
+
reject("invalid_kind", "--kind must be narration or bgm");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!args.provider) {
|
|
53
|
+
reject("missing_provider", "--provider is required");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!args.endpoint || !URL.canParse(args.endpoint)) {
|
|
57
|
+
reject("invalid_endpoint", "--endpoint must be a valid URL");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const policy = JSON.parse(await readFile(policyPath, "utf8"));
|
|
62
|
+
const normalizedProvider = args.provider.trim().toLowerCase();
|
|
63
|
+
if (policy.audio.forbidden_providers.includes(normalizedProvider)) {
|
|
64
|
+
reject("forbidden_provider", `${args.provider} is forbidden by the generated-audio policy`, {
|
|
65
|
+
provider: normalizedProvider,
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const route = args.kind === "narration"
|
|
71
|
+
? policy.audio.generated_narration
|
|
72
|
+
: policy.audio.generated_bgm;
|
|
73
|
+
if (normalizedProvider !== route.provider) {
|
|
74
|
+
reject("provider_mismatch", `${args.kind} generation requires provider ${route.provider}`, {
|
|
75
|
+
expected: route.provider,
|
|
76
|
+
actual: normalizedProvider,
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const actual = new URL(args.endpoint);
|
|
82
|
+
const expected = new URL(route.endpoint);
|
|
83
|
+
const allowedEndpoints = [route.endpoint, ...(route.backup_endpoints || [])]
|
|
84
|
+
.map((endpoint) => new URL(endpoint));
|
|
85
|
+
if (!allowedEndpoints.some((allowed) => (
|
|
86
|
+
actual.origin === allowed.origin && actual.pathname === allowed.pathname
|
|
87
|
+
))) {
|
|
88
|
+
reject("endpoint_mismatch", `${args.kind} generation requires the MiniMax mainland endpoint`, {
|
|
89
|
+
expected: route.endpoint,
|
|
90
|
+
actual: args.endpoint,
|
|
91
|
+
});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
output({
|
|
96
|
+
ok: true,
|
|
97
|
+
kind: args.kind,
|
|
98
|
+
provider: route.provider,
|
|
99
|
+
endpoint: args.endpoint,
|
|
100
|
+
primary_endpoint: route.endpoint,
|
|
101
|
+
region: "cn",
|
|
102
|
+
});
|
|
103
|
+
} catch (error) {
|
|
104
|
+
reject("validation", error.message);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
main();
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const skillRoot = dirname(scriptDir);
|
|
8
|
+
|
|
9
|
+
function isObject(value) {
|
|
10
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function validateAudioTrack(track, label, errors) {
|
|
14
|
+
if (!isObject(track)) {
|
|
15
|
+
errors.push(`audio.${label} must be an object`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!["generated", "user_local_file", "none"].includes(track.mode)) {
|
|
20
|
+
errors.push(`audio.${label}.mode must be generated, user_local_file, or none`);
|
|
21
|
+
}
|
|
22
|
+
if (track.mode === "generated" && track.provider !== "minimax_cn") {
|
|
23
|
+
errors.push(`audio.${label}.provider must be minimax_cn when mode is generated`);
|
|
24
|
+
}
|
|
25
|
+
if (track.mode === "user_local_file" && (typeof track.path !== "string" || !track.path.trim())) {
|
|
26
|
+
errors.push(`audio.${label}.path is required when mode is user_local_file`);
|
|
27
|
+
}
|
|
28
|
+
if (track.mode === "none" && track.provider !== undefined) {
|
|
29
|
+
errors.push(`audio.${label}.provider must be omitted when mode is none`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function validateShotPlan(plan, catalogs) {
|
|
34
|
+
const errors = [];
|
|
35
|
+
|
|
36
|
+
if (!isObject(plan)) return ["plan must be a JSON object"];
|
|
37
|
+
if (plan.schema_version !== "1.0.0") errors.push("schema_version must be 1.0.0");
|
|
38
|
+
if (!["assemble_existing_catalog_items", "generate_new_hyperframes_html"].includes(plan.generation_mode)) {
|
|
39
|
+
errors.push("generation_mode is invalid");
|
|
40
|
+
}
|
|
41
|
+
if (plan.catalog_strategy !== "dual_catalog") errors.push("catalog_strategy must be dual_catalog");
|
|
42
|
+
|
|
43
|
+
if (!isObject(plan.dimensions)
|
|
44
|
+
|| !Number.isInteger(plan.dimensions.width)
|
|
45
|
+
|| !Number.isInteger(plan.dimensions.height)
|
|
46
|
+
|| plan.dimensions.width <= 0
|
|
47
|
+
|| plan.dimensions.height <= 0) {
|
|
48
|
+
errors.push("dimensions.width and dimensions.height must be positive integers");
|
|
49
|
+
}
|
|
50
|
+
if (!Number.isFinite(plan.duration_seconds) || plan.duration_seconds <= 0) {
|
|
51
|
+
errors.push("duration_seconds must be a positive number");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!isObject(plan.audio)) {
|
|
55
|
+
errors.push("audio must be an object");
|
|
56
|
+
} else {
|
|
57
|
+
validateAudioTrack(plan.audio.narration, "narration", errors);
|
|
58
|
+
validateAudioTrack(plan.audio.bgm, "bgm", errors);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!isObject(plan.model_routing)) {
|
|
62
|
+
errors.push("model_routing must be an object");
|
|
63
|
+
} else {
|
|
64
|
+
if (plan.model_routing.page_animation_authoring !== "gpt-5.6-sol") {
|
|
65
|
+
errors.push("model_routing.page_animation_authoring must be gpt-5.6-sol");
|
|
66
|
+
}
|
|
67
|
+
if (!plan.model_routing.planning_and_validation) {
|
|
68
|
+
errors.push("model_routing.planning_and_validation is required");
|
|
69
|
+
} else if (plan.model_routing.planning_and_validation === "gpt-5.6-sol") {
|
|
70
|
+
errors.push("model_routing.planning_and_validation must not use gpt-5.6-sol");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!Array.isArray(plan.shots) || plan.shots.length === 0) {
|
|
75
|
+
errors.push("shots must contain at least one shot");
|
|
76
|
+
return errors;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const ids = new Set();
|
|
80
|
+
plan.shots.forEach((shot, index) => {
|
|
81
|
+
const label = `shots[${index}]`;
|
|
82
|
+
if (!isObject(shot)) {
|
|
83
|
+
errors.push(`${label} must be an object`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (typeof shot.id !== "string" || !/^[a-z][a-z0-9-]*$/.test(shot.id)) {
|
|
87
|
+
errors.push(`${label}.id must be a kebab-case identifier`);
|
|
88
|
+
} else if (ids.has(shot.id)) {
|
|
89
|
+
errors.push(`${label}.id must be unique`);
|
|
90
|
+
} else {
|
|
91
|
+
ids.add(shot.id);
|
|
92
|
+
}
|
|
93
|
+
if (!Number.isFinite(shot.start_seconds) || shot.start_seconds < 0) {
|
|
94
|
+
errors.push(`${label}.start_seconds must be zero or greater`);
|
|
95
|
+
}
|
|
96
|
+
if (!Number.isFinite(shot.end_seconds) || shot.end_seconds <= shot.start_seconds) {
|
|
97
|
+
errors.push(`${label}.end_seconds must be greater than start_seconds`);
|
|
98
|
+
}
|
|
99
|
+
if (typeof shot.purpose !== "string" || !shot.purpose.trim()) {
|
|
100
|
+
errors.push(`${label}.purpose is required`);
|
|
101
|
+
}
|
|
102
|
+
if (!Array.isArray(shot.hyperframes_refs)) {
|
|
103
|
+
errors.push(`${label}.hyperframes_refs must be an array`);
|
|
104
|
+
} else {
|
|
105
|
+
for (const reference of shot.hyperframes_refs) {
|
|
106
|
+
if (!catalogs.hyperframes.has(reference)) {
|
|
107
|
+
errors.push(`${label} has unknown HyperFrames reference: ${reference}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!Array.isArray(shot.shotcraft_refs)) {
|
|
112
|
+
errors.push(`${label}.shotcraft_refs must be an array`);
|
|
113
|
+
} else {
|
|
114
|
+
for (const reference of shot.shotcraft_refs) {
|
|
115
|
+
if (!catalogs.shotcraft.has(reference)) {
|
|
116
|
+
errors.push(`${label} has unknown ShotCraft reference: ${reference}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (Array.isArray(shot.hyperframes_refs)
|
|
121
|
+
&& Array.isArray(shot.shotcraft_refs)
|
|
122
|
+
&& shot.hyperframes_refs.length + shot.shotcraft_refs.length === 0) {
|
|
123
|
+
errors.push(`${label} must reference at least one catalog`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (index === 0 && shot.start_seconds !== 0) {
|
|
127
|
+
errors.push("shots must start at 0 seconds");
|
|
128
|
+
}
|
|
129
|
+
if (index > 0 && Number.isFinite(shot.start_seconds)) {
|
|
130
|
+
const previous = plan.shots[index - 1];
|
|
131
|
+
if (Number.isFinite(previous?.end_seconds) && Math.abs(shot.start_seconds - previous.end_seconds) > 0.001) {
|
|
132
|
+
errors.push(`shots must be contiguous: ${label}.start_seconds must equal shots[${index - 1}].end_seconds`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const lastEnd = plan.shots.at(-1)?.end_seconds;
|
|
138
|
+
if (Number.isFinite(lastEnd)
|
|
139
|
+
&& Number.isFinite(plan.duration_seconds)
|
|
140
|
+
&& Math.abs(lastEnd - plan.duration_seconds) > 0.001) {
|
|
141
|
+
errors.push("last shot end_seconds must equal duration_seconds");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return errors;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function output(payload, code = 0) {
|
|
148
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
149
|
+
process.exitCode = code;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function main() {
|
|
153
|
+
try {
|
|
154
|
+
const path = process.argv[2];
|
|
155
|
+
if (!path || path === "--help" || path === "-h") {
|
|
156
|
+
console.log("Usage: validate_shot_plan.mjs <shot-plan.json>");
|
|
157
|
+
process.exitCode = path ? 0 : 1;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const plan = JSON.parse(await readFile(path, "utf8"));
|
|
161
|
+
const hyperframesCatalog = JSON.parse(await readFile(
|
|
162
|
+
join(skillRoot, "references", "hyperframes-catalog-map.json"),
|
|
163
|
+
"utf8",
|
|
164
|
+
));
|
|
165
|
+
const shotcraftCatalog = JSON.parse(await readFile(
|
|
166
|
+
join(skillRoot, "references", "shotcraft", "catalog.json"),
|
|
167
|
+
"utf8",
|
|
168
|
+
));
|
|
169
|
+
const catalogs = {
|
|
170
|
+
hyperframes: new Set(hyperframesCatalog.items.map((item) => item.id)),
|
|
171
|
+
shotcraft: new Set(shotcraftCatalog.cards.flatMap((card) => [
|
|
172
|
+
card.name,
|
|
173
|
+
...card.styles.map((style) => style.key),
|
|
174
|
+
])),
|
|
175
|
+
};
|
|
176
|
+
const errors = validateShotPlan(plan, catalogs);
|
|
177
|
+
if (errors.length > 0) {
|
|
178
|
+
output({ ok: false, path, errors }, 1);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
output({
|
|
182
|
+
ok: true,
|
|
183
|
+
path,
|
|
184
|
+
shot_count: plan.shots.length,
|
|
185
|
+
duration_seconds: plan.duration_seconds,
|
|
186
|
+
catalog_strategy: plan.catalog_strategy,
|
|
187
|
+
});
|
|
188
|
+
} catch (error) {
|
|
189
|
+
output({ ok: false, errors: [error.message] }, 1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
main();
|
|
@@ -23,6 +23,7 @@ class MetaInfo:
|
|
|
23
23
|
sample_rate: int
|
|
24
24
|
bpm: float
|
|
25
25
|
time_signature: str = "4/4"
|
|
26
|
+
beat_grid: dict = field(default_factory=dict)
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
@dataclass
|
|
@@ -116,7 +117,8 @@ def analyze(
|
|
|
116
117
|
duration_ms=duration_ms,
|
|
117
118
|
sample_rate=sr,
|
|
118
119
|
bpm=beat_result.bpm,
|
|
119
|
-
time_signature=beat_result.time_signature
|
|
120
|
+
time_signature=beat_result.time_signature,
|
|
121
|
+
beat_grid=beat_result.beat_grid
|
|
120
122
|
)
|
|
121
123
|
|
|
122
124
|
result = AnalysisResult(
|
|
@@ -30,6 +30,41 @@ class BeatResult:
|
|
|
30
30
|
bpm: float
|
|
31
31
|
beats: List[Beat]
|
|
32
32
|
time_signature: str = "4/4"
|
|
33
|
+
beat_grid: dict = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _fit_beat_grid(beat_times: np.ndarray, fallback_bpm: float) -> tuple[float, dict]:
|
|
37
|
+
"""Fit t_i = phase + i * period and report grid reliability."""
|
|
38
|
+
if len(beat_times) < 2:
|
|
39
|
+
period = 60.0 / fallback_bpm if fallback_bpm > 0 else 0.0
|
|
40
|
+
phase = float(beat_times[0]) if len(beat_times) == 1 else 0.0
|
|
41
|
+
return fallback_bpm, {
|
|
42
|
+
"method": "insufficient_beats",
|
|
43
|
+
"phase_ms": round(phase * 1000, 3),
|
|
44
|
+
"period_ms": round(period * 1000, 3),
|
|
45
|
+
"max_residual_ms": None,
|
|
46
|
+
"rms_residual_ms": None,
|
|
47
|
+
"reliable": False,
|
|
48
|
+
"threshold_ms": 15.0,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
indexes = np.arange(len(beat_times), dtype=float)
|
|
52
|
+
design = np.vstack([indexes, np.ones_like(indexes)]).T
|
|
53
|
+
(period, phase), *_ = np.linalg.lstsq(design, beat_times, rcond=None)
|
|
54
|
+
residuals = beat_times - (phase + indexes * period)
|
|
55
|
+
max_residual_ms = float(np.max(np.abs(residuals)) * 1000)
|
|
56
|
+
rms_residual_ms = float(np.sqrt(np.mean(residuals ** 2)) * 1000)
|
|
57
|
+
fitted_bpm = 60.0 / period if period > 0 else fallback_bpm
|
|
58
|
+
|
|
59
|
+
return fitted_bpm, {
|
|
60
|
+
"method": "least_squares",
|
|
61
|
+
"phase_ms": round(float(phase) * 1000, 3),
|
|
62
|
+
"period_ms": round(float(period) * 1000, 3),
|
|
63
|
+
"max_residual_ms": round(max_residual_ms, 3),
|
|
64
|
+
"rms_residual_ms": round(rms_residual_ms, 3),
|
|
65
|
+
"reliable": max_residual_ms <= 15.0,
|
|
66
|
+
"threshold_ms": 15.0,
|
|
67
|
+
}
|
|
33
68
|
|
|
34
69
|
|
|
35
70
|
def detect_beats(
|
|
@@ -52,6 +87,7 @@ def detect_beats(
|
|
|
52
87
|
|
|
53
88
|
# 转换为时间(毫秒)
|
|
54
89
|
beat_times = librosa.frames_to_time(beat_frames, sr=sr)
|
|
90
|
+
bpm, beat_grid = _fit_beat_grid(beat_times, bpm)
|
|
55
91
|
beat_times_ms = [int(t * 1000) for t in beat_times]
|
|
56
92
|
|
|
57
93
|
# 计算每个beat的能量
|
|
@@ -82,7 +118,7 @@ def detect_beats(
|
|
|
82
118
|
if on_progress:
|
|
83
119
|
on_progress(100.0)
|
|
84
120
|
|
|
85
|
-
return BeatResult(bpm=bpm, beats=beats)
|
|
121
|
+
return BeatResult(bpm=bpm, beats=beats, beat_grid=beat_grid)
|
|
86
122
|
|
|
87
123
|
|
|
88
124
|
def _compute_beat_energies(
|