luckiest-co 1.0.19 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/commands/status.md +10 -0
- package/hooks/hooks.json +11 -1
- package/hooks/update-notice.mjs +56 -0
- package/package.json +1 -1
- package/skills/luckiest-status/SKILL.md +10 -0
- package/skills/luckiest-video-studio/ATTRIBUTION.md +47 -0
- package/skills/luckiest-video-studio/CHANGELOG.md +64 -0
- package/skills/luckiest-video-studio/LICENSE-brag +21 -0
- package/skills/luckiest-video-studio/LICENSE-diagram-design +21 -0
- package/skills/luckiest-video-studio/LICENSE-motion-graphics +25 -0
- package/skills/luckiest-video-studio/LICENSE-student-kit +32 -0
- package/skills/luckiest-video-studio/LICENSE-student-kit-pipeline +15 -0
- package/skills/luckiest-video-studio/SECURITY.md +74 -0
- package/skills/luckiest-video-studio/SKILL.md +257 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-fan-1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-fan-2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-place-1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-place-2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-place-3.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-place-4.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-shove-1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-shove-2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-shove-3.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-shove-4.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-shuffle.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-slide-1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-slide-2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-slide-3.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-slide-4.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-slide-5.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-slide-6.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-slide-7.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/card-slide-8.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/cards-pack-open-1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/cards-pack-open-2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chip-lay-1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chip-lay-2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chip-lay-3.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-collide-1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-collide-2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-collide-3.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-collide-4.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-handle-1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-handle-2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-handle-3.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-handle-4.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-handle-6.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-stack-1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-stack-2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-stack-3.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-stack-4.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-stack-5.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/chips-stack-6.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/dice-grab-1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/dice-grab-2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/dice-shake-1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/dice-shake-2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/dice-shake-3.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/dice-throw-1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/dice-throw-2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/dice-throw-3.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/die-throw-1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/die-throw-2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/die-throw-3.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/casino/die-throw-4.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_carpet_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_carpet_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_carpet_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_concrete_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_concrete_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_concrete_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_concrete_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_concrete_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_grass_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_grass_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_grass_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_grass_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_grass_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_snow_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_snow_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_snow_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_snow_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_snow_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_wood_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_wood_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_wood_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_wood_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/footstep_wood_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactBell_heavy_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactBell_heavy_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactBell_heavy_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactGeneric_light_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactGeneric_light_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactGeneric_light_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactGeneric_light_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactGeneric_light_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactGlass_heavy_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactGlass_light_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactGlass_light_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactGlass_light_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactGlass_medium_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactGlass_medium_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactGlass_medium_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactMetal_heavy_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactMetal_heavy_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactMetal_heavy_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactMetal_light_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactMetal_light_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactMetal_medium_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactMetal_medium_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactMetal_medium_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactMetal_medium_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactMetal_medium_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactMining_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlank_medium_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlank_medium_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlank_medium_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlank_medium_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlank_medium_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_heavy_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_heavy_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_heavy_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_heavy_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_heavy_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_light_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_light_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_light_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_light_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_light_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_medium_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_medium_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_medium_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_medium_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPlate_medium_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPunch_heavy_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPunch_heavy_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPunch_heavy_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPunch_heavy_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPunch_heavy_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPunch_medium_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPunch_medium_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPunch_medium_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPunch_medium_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactPunch_medium_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactSoft_heavy_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactSoft_heavy_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactSoft_heavy_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactSoft_heavy_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactSoft_heavy_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactSoft_medium_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactSoft_medium_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactSoft_medium_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactSoft_medium_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactSoft_medium_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactTin_medium_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactTin_medium_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactTin_medium_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactTin_medium_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactTin_medium_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_heavy_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_heavy_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_heavy_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_heavy_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_heavy_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_light_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_light_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_light_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_light_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_light_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_medium_000.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_medium_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_medium_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_medium_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/impact/impactWood_medium_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/bong_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/click_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/click_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/click_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/click_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/click_005.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/drop_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/drop_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/drop_003.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/error_005.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/error_006.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/glitch_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/glitch_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/select_008.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/switch_001.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/switch_002.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/switch_004.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/switch_005.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/switch_006.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/interface/switch_007.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-001.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-002.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-003.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-004.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-005.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-006.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-007.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-008.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-009.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-010.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-011.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-012.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-013.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-014.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-015.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-016.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-017.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-018.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-019.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-020.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-021.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-022.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-023.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-024.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-025.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-026.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-027.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-028.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-029.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-030.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-031.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/keyboard/keypress-032.wav +0 -0
- package/skills/luckiest-video-studio/assets/sfx/sfx-analysis.json +15584 -0
- package/skills/luckiest-video-studio/assets/sfx/sfx-analysis.md +75 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/click1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/click2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/click3.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/click4.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/click5.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/mouseclick1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/rollover1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/rollover2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/rollover4.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/rollover5.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch1.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch10.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch11.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch12.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch13.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch14.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch15.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch16.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch17.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch18.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch19.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch2.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch20.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch21.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch22.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch23.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch24.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch25.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch26.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch27.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch28.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch29.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch3.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch30.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch31.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch32.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch33.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch34.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch35.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch36.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch37.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch38.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch4.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch5.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch6.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch7.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch8.ogg +0 -0
- package/skills/luckiest-video-studio/assets/sfx/ui/switch9.ogg +0 -0
- package/skills/luckiest-video-studio/references/audio.md +285 -0
- package/skills/luckiest-video-studio/references/brand.md +55 -0
- package/skills/luckiest-video-studio/references/diffusion.md +75 -0
- package/skills/luckiest-video-studio/references/logo-recipes.md +77 -0
- package/skills/luckiest-video-studio/references/openscreen.md +60 -0
- package/skills/luckiest-video-studio/references/step-1-inspect.md +133 -0
- package/skills/luckiest-video-studio/references/step-2-plan.md +234 -0
- package/skills/luckiest-video-studio/references/step-3-compose.md +210 -0
- package/skills/luckiest-video-studio/references/step-4-deliver.md +168 -0
- package/skills/luckiest-video-studio/references/storyboard.md +78 -0
- package/skills/luckiest-video-studio/references/tones.md +231 -0
- package/skills/luckiest-video-studio/scripts/analyze_music_cues.py +309 -0
- package/skills/luckiest-video-studio/scripts/assemble-diffusion.mjs +161 -0
- package/skills/luckiest-video-studio/scripts/assemble-openscreen.mjs +74 -0
- package/skills/luckiest-video-studio/scripts/pyproject.toml +14 -0
- package/skills/luckiest-video-studio/scripts/qa.mjs +313 -0
- package/skills/luckiest-video-studio/scripts/qa.test.mjs +135 -0
- package/skills/luckiest-video-studio/scripts/render-scenes.mjs +125 -0
- package/skills/luckiest-video-studio/scripts/storyboard.schema.json +52 -0
- package/skills/luckiest-video-studio/scripts/uv.lock +819 -0
- package/skills/luckiest-video-studio/templates/diffusion/line-draw.tsx +132 -0
- package/skills/luckiest-video-studio/templates/logo-line-draw.html +38 -0
- package/skills/luckiest-video-studio-ae/ATTRIBUTION.md +9 -0
- package/skills/luckiest-video-studio-ae/CHANGELOG.md +7 -0
- package/skills/luckiest-video-studio-ae/SKILL.md +133 -0
- package/skills/luckiest-video-studio-cards/ATTRIBUTION.md +22 -0
- package/skills/luckiest-video-studio-cards/CHANGELOG.md +8 -0
- package/skills/luckiest-video-studio-cards/SKILL.md +114 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/DESIGN.md +50 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-agenda.html +180 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-bigtype.html +156 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-board.html +238 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-checklist.html +158 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-compare.html +205 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-deck.html +193 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-dossier.html +234 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-filmstrip.html +211 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-grid6.html +157 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-indexset.html +194 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-maplegend.html +243 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-matrix.html +198 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-numlist.html +180 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-outline.html +194 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-phases.html +208 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-quad.html +163 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-roadmap.html +197 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-stepper.html +193 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-toc.html +192 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-overview-vtimeline.html +166 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-bracket.html +88 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-circle.html +103 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-classified.html +112 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-collage.html +93 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-correction.html +93 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-crt.html +111 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-cutout.html +87 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-fullbleed.html +78 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-highlight.html +80 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-indexcard.html +85 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-map.html +96 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-markerbox.html +84 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-numeral.html +74 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-split.html +88 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-stacked.html +94 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-stamp.html +83 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-ticker.html +88 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-section-tornstrip.html +92 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-annotated.html +182 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-arc.html +105 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-axis.html +165 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-bar.html +111 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-bracket.html +124 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-calendar.html +219 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-callout.html +100 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-columns.html +171 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-corner.html +182 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-dots.html +102 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-engraving.html +160 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-footnote.html +125 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-frac.html +101 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-heat.html +179 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-isotype.html +133 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-ledger.html +106 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-left.html +109 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-magnifier.html +199 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-map.html +326 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-meter.html +153 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-multiplier.html +133 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-odo.html +93 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-pack.html +189 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-photo.html +93 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-pie.html +188 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-postage.html +191 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-pricetag.html +189 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-quote.html +113 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-ranking.html +171 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-receipt.html +182 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-ribbon.html +179 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-semi.html +171 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-spotlight.html +263 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-stamp.html +149 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-steps.html +123 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-tape.html +217 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-torn.html +240 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-trend.html +160 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-vs.html +101 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat-window.html +191 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-stat.html +97 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier1/t1-timeline.html +131 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-label.html +51 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lb-bracket.html +104 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lb-caption.html +129 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lb-circle.html +114 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lb-definition.html +152 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lb-footnote.html +113 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lb-magnify.html +157 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lb-pin.html +95 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lb-pullquote.html +135 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lb-spotlight.html +114 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lb-sticky.html +95 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lb-tag.html +129 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lb-underline.html +94 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lower-third.html +66 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lt-badge.html +133 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lt-barslab.html +126 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lt-bracket.html +146 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lt-clapper.html +144 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lt-highlight.html +128 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lt-indextab.html +134 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lt-rail.html +116 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lt-ribbon.html +140 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lt-stacked.html +143 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lt-stamp.html +133 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lt-sticky.html +154 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/cards/tier2/t2-lt-typecard.html +137 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/style.json +4463 -0
- package/skills/luckiest-video-studio-cards/packs/01-paper-collage/tokens.css +81 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/DESIGN.md +51 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-agenda.html +277 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-beforeafter.html +252 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-bento.html +269 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-bigleft.html +221 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-callouts.html +350 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-cardrow5.html +217 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-checklist.html +193 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-cycle.html +312 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-dashboard.html +414 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-deck2.html +223 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-deck3.html +200 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-duallist.html +323 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-featurecompare.html +244 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-featuregrid.html +235 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-flashcards.html +215 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-flow.html +287 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-funnel.html +246 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-gantt.html +290 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-grid6.html +193 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-grid9.html +217 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-iconlist.html +240 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-journey.html +305 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-kpirow.html +275 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-layers.html +199 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-legend.html +258 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-levels.html +199 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-logowall.html +278 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-mappoints.html +303 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-matrix.html +256 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-milestones.html +275 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-mindmap.html +247 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-nested.html +222 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-network.html +353 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-notifs.html +224 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-numlist.html +188 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-orgchart.html +240 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-outline.html +263 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-phases.html +250 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-pipeline.html +250 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-pricing.html +277 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-profiles.html +247 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-proscons.html +283 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-pyramid.html +181 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-quad.html +226 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-quotegrid.html +222 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-ranked.html +257 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-roadmap.html +251 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-spectrum.html +289 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-split3.html +209 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-spotlightcard.html +257 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-statgrid.html +252 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-steps.html +251 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-summary.html +231 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-tiercompare.html +295 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-timeline.html +229 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-toc.html +233 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-tree.html +250 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-tricards.html +217 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-venn.html +197 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-overview-vstable.html +273 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-ascend.html +180 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-aurora-bars.html +195 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-beam.html +197 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-boxed.html +172 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-breath.html +151 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-chaptercard.html +180 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-column-light.html +177 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-constellation.html +220 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-countdown.html +195 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-curtain.html +194 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-dawn.html +196 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-depth.html +186 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-divider.html +192 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-drift-grid.html +209 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-duotone.html +165 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-eclipse.html +209 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-emblem.html +222 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-flare-sweep.html +191 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-glow-rule.html +216 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-gradient-word.html +190 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-gridfloor.html +199 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-halo.html +173 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-horizon.html +217 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-index.html +202 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-kinetic.html +159 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-lensflare.html +231 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-liquid.html +181 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-marquee.html +177 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-mesh.html +225 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-monolith.html +204 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-node-title.html +208 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-numeral.html +174 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-orb.html +146 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-orbit.html +250 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-particles.html +188 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-pill.html +196 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-portal.html +222 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-prism.html +265 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-question.html +169 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-quoteopen.html +171 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-reveal-mask.html +174 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-ribbon.html +204 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-ripple.html +202 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-searchbar.html +208 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-slab.html +179 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-sphere-top.html +209 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-split-reveal.html +218 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-splitpanel.html +229 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-spotlight-cone.html +245 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-spotlight.html +179 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-stack.html +173 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-stacked-words.html +178 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-starfield.html +213 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-sweep.html +177 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-tunnel.html +193 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-type-scale.html +170 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-typed.html +177 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-underline-grow.html +209 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-waveform.html +242 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-section-wordmark.html +172 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-annot.html +248 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-beforeafter.html +293 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-bigpercent.html +228 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-bracketstat.html +210 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-bubble.html +256 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-cbars.html +313 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-clockface.html +296 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-column.html +382 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-compass.html +305 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-counterpill.html +216 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-curve.html +318 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-delta.html +300 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-dial.html +288 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-donut.html +307 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-dotgrid.html +241 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-figure.html +212 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-fillshape.html +232 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-fraction.html +263 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-funnel.html +285 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-growtharrow.html +242 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-heatcell.html +242 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-isotype.html +248 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-layered.html +198 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-mega.html +217 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-meter.html +263 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-milestone.html +260 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-money.html +253 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-multiplier.html +209 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-multiring.html +336 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-needle.html +253 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-nodes.html +259 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-odometer.html +301 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-orbitmoons.html +229 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-orbitstat.html +319 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-pair.html +244 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-percentile.html +287 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-pictobar.html +223 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-polar.html +256 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-quotestat.html +190 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-radialbars.html +387 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-range.html +300 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-rank.html +264 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-ratio.html +264 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-ring.html +274 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-risewall.html +234 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-score.html +273 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-segbar.html +262 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-semigauge.html +327 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-sparkline.html +291 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-spectrum.html +245 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-split.html +242 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-stackedbars.html +304 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-tickerstat.html +310 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-tiles.html +232 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-timer.html +248 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-trendline.html +343 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-underscore.html +236 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-versus.html +315 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-waffle.html +250 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier1/t1-stat-waterfall.html +273 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-badge.html +170 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-brace.html +239 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-bracket.html +195 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-bubble.html +179 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-byline.html +204 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-callout.html +137 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-category.html +178 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-checkmark.html +195 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-chipstat.html +150 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-comparemini.html +202 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-coord.html +271 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-counter.html +168 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-crosshair.html +243 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-dashed.html +168 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-definition.html +165 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-deltachip.html +184 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-eyebrow.html +186 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-fact.html +160 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-flag.html +232 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-formula.html +212 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-hash.html +175 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-highlightbox.html +176 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-iconchip.html +199 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-info.html +186 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-infocard.html +239 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-keycap.html +194 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-keyword.html +140 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-leader.html +201 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-legend.html +212 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-measure.html +201 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-meterchip.html +238 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-metric.html +149 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-micropie.html +233 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-minibar.html +190 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-node.html +225 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-note.html +183 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-numbercallout.html +175 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-percent.html +176 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-pointer.html +173 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-price.html +209 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-progress.html +233 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-pulse.html +206 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-qmark.html +164 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-quote.html +150 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-rating.html +252 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-ribbon.html +178 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-ringmark.html +229 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-search.html +174 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-sparkchip.html +243 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-spec.html +229 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-status.html +190 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-step.html +180 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-tag.html +134 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-takeaway.html +154 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-tcode.html +175 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-timestamp.html +185 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-toast.html +206 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-tooltip.html +175 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-underline.html +168 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lb-warning.html +193 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-avatar.html +202 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-badgecard.html +219 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-barslide.html +177 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-bracket.html +177 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-brandbar.html +229 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-bug.html +177 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-capsule.html +193 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-chapterbar.html +196 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-chip.html +162 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-cinemastrip.html +190 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-cite.html +179 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-clockname.html +192 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-coordbar.html +262 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-corner.html +206 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-cornerframe.html +251 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-countbar.html +209 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-dividername.html +211 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-dotline.html +209 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-doublecard.html +225 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-dual.html +233 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-edgebar.html +160 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-flipcard.html +198 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-geopin.html +198 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-glasscard.html +152 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-gradient.html +151 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-gradientbar.html +197 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-handlebar.html +192 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-infochips.html +182 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-lensbar.html +213 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-live.html +232 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-location.html +222 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-logolock.html +214 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-micname.html +208 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-minicard.html +176 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-minimal.html +149 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-nameplate3d.html +208 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-neonframe.html +208 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-neonpill.html +222 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-nowplaying.html +257 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-orbitbadge.html +232 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-pill.html +168 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-portraitframe.html +220 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-progressname.html +234 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-quote.html +202 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-ratingname.html +226 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-ribbon.html +198 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-sidebracket.html +195 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-slidetab.html +191 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-social.html +180 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-spotlightname.html +150 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-stacked.html +158 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-statline.html +223 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-tagrow.html +191 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-thinstrip.html +185 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-ticker.html +216 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-titlebar.html +188 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-trust.html +247 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-typedname.html +195 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-underbar.html +189 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/cards/tier2/t2-lt-waveband.html +235 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/style.json +8608 -0
- package/skills/luckiest-video-studio-cards/packs/02-aurora-glass/tokens.css +134 -0
- package/skills/luckiest-video-studio-cards/packs/_blueprint/DESIGN.md +41 -0
- package/skills/luckiest-video-studio-cards/packs/_blueprint/cards/tier1/t1-stat.html +57 -0
- package/skills/luckiest-video-studio-cards/packs/_blueprint/cards/tier2/t2-list.html +58 -0
- package/skills/luckiest-video-studio-cards/packs/_blueprint/style.json +88 -0
- package/skills/luckiest-video-studio-cards/packs/_blueprint/tokens.css +32 -0
- package/skills/luckiest-video-studio-cards/packs/registry.json +7789 -0
- package/skills/luckiest-video-studio-cards/references/card-contract.md +77 -0
- package/skills/luckiest-video-studio-cards/references/palette-leftovers.md +28 -0
- package/skills/luckiest-video-studio-cards/scripts/apply-brand.mjs +90 -0
- package/skills/luckiest-video-studio-cards/scripts/apply-brand.test.mjs +62 -0
- package/skills/luckiest-video-studio-cards/scripts/build-registry.mjs +54 -0
- package/skills/luckiest-video-studio-cards/scripts/new-pack.mjs +42 -0
- package/skills/luckiest-video-studio-cards/scripts/qa-cards.mjs +126 -0
- package/skills/luckiest-video-studio-cards/templates/glass-popout/DESIGN.md +24 -0
- package/skills/luckiest-video-studio-cards/templates/glass-popout/hyperframes.json +8 -0
- package/skills/luckiest-video-studio-cards/templates/glass-popout/index.html +349 -0
- package/skills/luckiest-video-studio-cards/templates/glass-popout/meta.json +8 -0
- package/skills/luckiest-video-studio-cards/templates/graph-paper/DESIGN.md +25 -0
- package/skills/luckiest-video-studio-cards/templates/graph-paper/hyperframes.json +8 -0
- package/skills/luckiest-video-studio-cards/templates/graph-paper/index.html +207 -0
- package/skills/luckiest-video-studio-cards/templates/graph-paper/meta.json +5 -0
- package/skills/luckiest-video-studio-cards/templates/graph-paper/package.json +10 -0
- package/skills/luckiest-video-studio-cuts/ATTRIBUTION.md +19 -0
- package/skills/luckiest-video-studio-cuts/CHANGELOG.md +6 -0
- package/skills/luckiest-video-studio-cuts/SKILL.md +129 -0
- package/skills/luckiest-video-studio-cuts/scripts/apply-cuts.mjs +174 -0
- package/skills/luckiest-video-studio-cuts/scripts/build-edl-review.mjs +114 -0
- package/skills/luckiest-video-studio-cuts/scripts/cut-silences.mjs +300 -0
- package/skills/luckiest-video-studio-cuts/scripts/find-cut-candidates.mjs +182 -0
- package/skills/luckiest-video-studio-reel/ATTRIBUTION.md +21 -0
- package/skills/luckiest-video-studio-reel/CHANGELOG.md +10 -0
- package/skills/luckiest-video-studio-reel/SKILL.md +177 -0
- package/skills/luckiest-video-studio-reel/references/chapters.md +64 -0
- package/skills/luckiest-video-studio-reel/scripts/beatgrid.mjs +39 -0
- package/skills/luckiest-video-studio-reel/scripts/mix.mjs +42 -0
- package/skills/luckiest-video-studio-reel/scripts/splice-music.mjs +38 -0
- package/skills/luckiest-video-studio-reel/templates/reel.html +116 -0
- package/skills/luckiest-video-studio-reference/ATTRIBUTION.md +7 -0
- package/skills/luckiest-video-studio-reference/CHANGELOG.md +8 -0
- package/skills/luckiest-video-studio-reference/SKILL.md +108 -0
- package/skills/luckiest-video-studio-reference/scripts/shots.mjs +58 -0
- package/skills/luckiest-video-studio-talk/ATTRIBUTION.md +19 -0
- package/skills/luckiest-video-studio-talk/CHANGELOG.md +18 -0
- package/skills/luckiest-video-studio-talk/SKILL.md +161 -0
- package/skills/luckiest-video-studio-talk/references/long-form.md +91 -0
- package/skills/luckiest-video-studio-talk/references/plan-schema.md +54 -0
- package/skills/luckiest-video-studio-talk/references/short-form.md +76 -0
- package/skills/luckiest-video-studio-talk/scripts/grab-evidence.mjs +38 -0
- package/skills/luckiest-video-studio-talk/scripts/validate-footage.mjs +25 -0
- package/skills/luckiest-video-studio-talk/scripts/validate-plan.mjs +49 -0
- package/skills/luckiest-video-studio-talk/templates/talk-overlay.html +40 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// QA gates for a luckiest-video-studio run. Each gate prints JSON and exits 1 on a fail.
|
|
3
|
+
// Needs Node 22+ and ffmpeg/ffprobe; nothing else is installed or downloaded.
|
|
4
|
+
//
|
|
5
|
+
// node qa.mjs tokens <composition-dir> [--tokens tokens.json] off-system stroke, type, radius
|
|
6
|
+
// node qa.mjs crossfade <composition-dir> full-frame layers tweening opacity
|
|
7
|
+
// node qa.mjs legibility <video> [--every 1] [--min 180] frames with nothing bright enough to read
|
|
8
|
+
// node qa.mjs pops <video> [--seams 5.2,9.8] one-frame pops and jumps (seams reported apart)
|
|
9
|
+
// node qa.mjs deadframes <video> [--freeze 3] black frames and long freezes
|
|
10
|
+
// node qa.mjs presence <run-dir> [--min 0] share of time the speaker is on screen
|
|
11
|
+
// node qa.mjs beatsync <talk-dir> plan anchors against the transcript words
|
|
12
|
+
// node qa.mjs beatgrid <video> --bpm 128 [--phase 0] [--tol 0.035] cuts on the beat or half beat
|
|
13
|
+
// node qa.mjs face <run-dir> [--max 18] cards covering the speaker's face (scene faceRect)
|
|
14
|
+
// node qa.mjs judge <new.mp4> <old.mp4> [--out dir] [--cmd "claude -p --model sonnet"] [--verdicts A,B]
|
|
15
|
+
// new vs old, asked twice with the order swapped; the new one is kept only if it wins both
|
|
16
|
+
//
|
|
17
|
+
// A gate that has never failed proves nothing: qa.test.mjs runs each one on a control
|
|
18
|
+
// built to fail it.
|
|
19
|
+
|
|
20
|
+
import { readFileSync, readdirSync, existsSync, statSync, mkdirSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { join, resolve, extname } from "node:path";
|
|
22
|
+
import { spawnSync } from "node:child_process";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
|
|
25
|
+
// ---------- shared ----------
|
|
26
|
+
|
|
27
|
+
const ff = (args) => spawnSync("ffmpeg", ["-hide_banner", "-nostats", ...args], { maxBuffer: 1 << 30 });
|
|
28
|
+
|
|
29
|
+
export function probe(video) {
|
|
30
|
+
const r = spawnSync("ffprobe", ["-v", "error", "-select_streams", "v:0", "-show_entries",
|
|
31
|
+
"stream=r_frame_rate:format=duration", "-of", "json", video], { encoding: "utf8" });
|
|
32
|
+
if (r.status !== 0) throw new Error(`ffprobe failed on ${video}: ${r.stderr}`);
|
|
33
|
+
const j = JSON.parse(r.stdout), [n, d] = j.streams[0].r_frame_rate.split("/").map(Number);
|
|
34
|
+
return { fps: n / d, duration: Number(j.format.duration) };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Every frame as a small grayscale buffer (w x h bytes).
|
|
38
|
+
export function grayFrames(video, w = 64, h = 36, { start = 0, duration } = {}) {
|
|
39
|
+
const r = ff(["-v", "error", "-ss", String(start), ...(duration ? ["-t", String(duration)] : []), "-i", video,
|
|
40
|
+
"-vf", `scale=${w}:${h}:flags=area,format=gray`, "-f", "rawvideo", "-"]);
|
|
41
|
+
if (r.status !== 0) throw new Error(`ffmpeg failed on ${video}: ${r.stderr}`);
|
|
42
|
+
const size = w * h, frames = [];
|
|
43
|
+
for (let o = 0; o + size <= r.stdout.length; o += size) frames.push(r.stdout.subarray(o, o + size));
|
|
44
|
+
return frames;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const meanDiff = (a, b) => { let s = 0; for (let i = 0; i < a.length; i++) s += Math.abs(a[i] - b[i]); return s / a.length; };
|
|
48
|
+
|
|
49
|
+
const htmlFiles = (dir) => readdirSync(dir, { recursive: true }).map(String)
|
|
50
|
+
.filter((f) => extname(f) === ".html").map((f) => join(dir, f));
|
|
51
|
+
|
|
52
|
+
// ---------- tokens ----------
|
|
53
|
+
|
|
54
|
+
export const DEFAULT_TOKENS = { stroke: [1.5, 2.5, 4, 7], type: [22, 30, 42, 60, 84], radius: [10, 22, 40] };
|
|
55
|
+
|
|
56
|
+
export function scanTokens(html, tokens = DEFAULT_TOKENS) {
|
|
57
|
+
const out = [];
|
|
58
|
+
const check = (kind, prop, raw) => {
|
|
59
|
+
const v = Number(raw);
|
|
60
|
+
if (Number.isFinite(v) && v > 0 && !tokens[kind].includes(v)) out.push({ kind, prop, value: v });
|
|
61
|
+
};
|
|
62
|
+
for (const m of html.matchAll(/stroke-width\s*[:=]\s*"?([\d.]+)/g)) check("stroke", "stroke-width", m[1]);
|
|
63
|
+
for (const m of html.matchAll(/font-size\s*:\s*([\d.]+)px/g)) check("type", "font-size", m[1]);
|
|
64
|
+
for (const m of html.matchAll(/border-radius\s*:\s*([\d.]+)px/g)) check("radius", "border-radius", m[1]);
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------- cross-fade ----------
|
|
69
|
+
|
|
70
|
+
// Selectors whose CSS makes them cover the whole frame.
|
|
71
|
+
export function fullFrameSelectors(html) {
|
|
72
|
+
const sels = new Set();
|
|
73
|
+
for (const m of html.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
|
|
74
|
+
const body = m[2].replace(/\s/g, "");
|
|
75
|
+
const full = /inset:0(;|$)/.test(body) || (/width:(100%|1920px)/.test(body) && /height:(100%|1080px)/.test(body));
|
|
76
|
+
if (full) for (const s of m[1].split(",")) { const t = s.trim().split(/[\s>]+/).pop(); if (/^[.#][\w-]+$/.test(t)) sels.add(t); }
|
|
77
|
+
}
|
|
78
|
+
return sels;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Tweens (not instant sets) that change opacity on a full-frame layer.
|
|
82
|
+
export function scanCrossfades(html) {
|
|
83
|
+
const full = fullFrameSelectors(html), out = [];
|
|
84
|
+
for (const m of html.matchAll(/\.(to|from|fromTo)\(\s*["'`]([^"'`]+)["'`]\s*,([^;]*?)\)\s*[;,]/gs)) {
|
|
85
|
+
const targets = m[2].split(",").map((s) => s.trim()).filter((s) => full.has(s));
|
|
86
|
+
if (targets.length && /opacity\s*:/.test(m[3]) && !/duration\s*:\s*0\s*[,}]/.test(m[3])) out.push({ tween: m[1], targets });
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---------- legibility ----------
|
|
92
|
+
|
|
93
|
+
// Brightest luma per sampled frame (0-255). A frame whose brightest pixel is dim has nothing lit to read.
|
|
94
|
+
export function legibility(video, { every = 1, min = 180 } = {}) {
|
|
95
|
+
const { duration } = probe(video);
|
|
96
|
+
const rows = [];
|
|
97
|
+
for (let t = 0; t < duration; t += every) {
|
|
98
|
+
// Read at 960x540, the size the storytelling rules judge legibility at.
|
|
99
|
+
const [f] = grayFrames(video, 960, 540, { start: t, duration: 0.05 });
|
|
100
|
+
if (!f) continue;
|
|
101
|
+
let max = 0; for (const v of f) if (v > max) max = v;
|
|
102
|
+
rows.push({ t: Number(t.toFixed(2)), brightest: max });
|
|
103
|
+
}
|
|
104
|
+
const dim = rows.filter((r) => r.brightest < min);
|
|
105
|
+
return { ok: dim.length === 0, min, dim, sampled: rows.length };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---------- pops ----------
|
|
109
|
+
|
|
110
|
+
// A pop is one frame unlike both neighbours while the neighbours match each other.
|
|
111
|
+
export function findPops(frames, fps, { ratio = 3, floor = 6 } = {}) {
|
|
112
|
+
const pops = [];
|
|
113
|
+
for (let i = 1; i < frames.length - 1; i++) {
|
|
114
|
+
const a = meanDiff(frames[i - 1], frames[i]), b = meanDiff(frames[i], frames[i + 1]), c = meanDiff(frames[i - 1], frames[i + 1]);
|
|
115
|
+
if (a > floor && b > floor && Math.min(a, b) > ratio * Math.max(c, 1)) pops.push({ frame: i, t: Number((i / fps).toFixed(3)), jump: Number(Math.min(a, b).toFixed(1)) });
|
|
116
|
+
}
|
|
117
|
+
return pops;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------- dead frames ----------
|
|
121
|
+
|
|
122
|
+
export function deadFrames(video, { freeze = 3, blackMin = 0.06 } = {}) {
|
|
123
|
+
const r = ff(["-i", video, "-vf", `blackdetect=d=${blackMin}:pix_th=0.02:pic_th=0.995,freezedetect=n=0.001:d=${freeze}`, "-an", "-f", "null", "-"]);
|
|
124
|
+
const log = r.stderr.toString();
|
|
125
|
+
const black = [...log.matchAll(/black_start:([\d.]+) black_end:([\d.]+)/g)].map((m) => ({ start: +m[1], end: +m[2] }));
|
|
126
|
+
const freezes = [...log.matchAll(/freeze_start: ([\d.]+)/g)].map((m) => ({ start: +m[1] }));
|
|
127
|
+
return { ok: black.length === 0 && freezes.length === 0, black, freezes };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------- presence ----------
|
|
131
|
+
|
|
132
|
+
// Compare each second of final.mp4 with the clip frame the storyboard says should be under it.
|
|
133
|
+
export function presence(runDir, { threshold = 12 } = {}) {
|
|
134
|
+
const sb = JSON.parse(readFileSync(join(runDir, "storyboard.json"), "utf8"));
|
|
135
|
+
const final = join(runDir, "final.mp4");
|
|
136
|
+
let t0 = 0, onScreen = 0, total = 0;
|
|
137
|
+
const cache = new Map();
|
|
138
|
+
for (const s of sb.scenes) {
|
|
139
|
+
const src = s.source ?? (s.assets ?? []).find((a) => a.endsWith(".mp4"));
|
|
140
|
+
for (let t = 0; t < s.duration; t += 1) {
|
|
141
|
+
total++;
|
|
142
|
+
if (!src || !existsSync(join(runDir, src))) continue;
|
|
143
|
+
const [f] = grayFrames(final, 64, 36, { start: t0 + t, duration: 0.05 });
|
|
144
|
+
const key = `${src}@${((s.sourceStart ?? 0) + t).toFixed(2)}`;
|
|
145
|
+
if (!cache.has(key)) cache.set(key, grayFrames(join(runDir, src), 64, 36, { start: (s.sourceStart ?? 0) + t, duration: 0.05 })[0]);
|
|
146
|
+
const g = cache.get(key);
|
|
147
|
+
if (f && g && meanDiff(f, g) < threshold) onScreen++;
|
|
148
|
+
}
|
|
149
|
+
t0 += s.duration;
|
|
150
|
+
}
|
|
151
|
+
return { share: total ? Number((onScreen / total).toFixed(3)) : 0, sampled: total };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---------- beat sync ----------
|
|
155
|
+
|
|
156
|
+
const norm = (s) => String(s).toLowerCase().replace(/[^a-z0-9' ]/g, "").trim();
|
|
157
|
+
|
|
158
|
+
// Start time of the first occurrence of an anchor phrase in the word list.
|
|
159
|
+
export function findAnchor(words, phrase) {
|
|
160
|
+
const want = norm(phrase).split(/\s+/).filter(Boolean);
|
|
161
|
+
const ws = words.map((w) => norm(w.text));
|
|
162
|
+
for (let i = 0; i + want.length <= ws.length; i++) if (want.every((x, k) => ws[i + k] === x)) return words[i].start;
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Every scene and event with an anchor must enter between 0.2 s after and 1.8 s before its word.
|
|
167
|
+
export function beatSync(plan, transcript, { lead = [-0.2, 1.8] } = {}) {
|
|
168
|
+
const words = (Array.isArray(transcript) ? transcript : transcript.words).filter((w) => w.type !== "spacing");
|
|
169
|
+
const rows = [];
|
|
170
|
+
for (const item of [...(plan.scenes ?? []).map((s) => ({ id: s.id, time: s.start, anchor: s.anchor })),
|
|
171
|
+
...(plan.events ?? []).map((e, i) => ({ id: `${e.visual}#${i}`, time: e.time, anchor: e.anchor }))]) {
|
|
172
|
+
if (!item.anchor) continue;
|
|
173
|
+
const at = findAnchor(words, item.anchor);
|
|
174
|
+
const diff = at === null ? null : Number((at - item.time).toFixed(3));
|
|
175
|
+
rows.push({ ...item, word: at, lead: diff, ok: diff !== null && diff >= lead[0] && diff <= lead[1] });
|
|
176
|
+
}
|
|
177
|
+
return { ok: rows.every((r) => r.ok), fails: rows.filter((r) => !r.ok), checked: rows.length };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---------- beat grid ----------
|
|
181
|
+
|
|
182
|
+
// Hard cuts are frames far from the previous one; each must sit near a beat or half beat.
|
|
183
|
+
export function cutTimes(frames, fps, { floor = 30 } = {}) {
|
|
184
|
+
const cuts = [];
|
|
185
|
+
for (let i = 1; i < frames.length; i++) if (meanDiff(frames[i - 1], frames[i]) > floor) cuts.push(i / fps);
|
|
186
|
+
return cuts;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function beatGrid(cuts, { bpm, phase = 0, tol = 0.035 }) {
|
|
190
|
+
const half = 60 / bpm / 2;
|
|
191
|
+
const rows = cuts.map((t) => {
|
|
192
|
+
const k = Math.round((t - phase) / half), off = t - phase - k * half;
|
|
193
|
+
return { t: Number(t.toFixed(3)), offset: Number(off.toFixed(3)), ok: Math.abs(off) <= tol };
|
|
194
|
+
});
|
|
195
|
+
return { ok: rows.every((r) => r.ok), off: rows.filter((r) => !r.ok), cuts: rows.length };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ---------- face ----------
|
|
199
|
+
|
|
200
|
+
// Crop a 0-1 rect [x, y, w, h] out of a w x h gray frame.
|
|
201
|
+
export const crop = (f, w, h, [x, y, cw, ch]) => {
|
|
202
|
+
const x0 = Math.floor(x * w), y0 = Math.floor(y * h), x1 = Math.ceil((x + cw) * w), y1 = Math.ceil((y + ch) * h), out = [];
|
|
203
|
+
for (let r = y0; r < y1; r++) for (let c = x0; c < x1; c++) out.push(f[r * w + c]);
|
|
204
|
+
return out;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// For scenes with faceRect, the face area in final.mp4 must still match the clip under it.
|
|
208
|
+
// A card over the face changes those pixels; the clip alone does not.
|
|
209
|
+
export function faceCover(runDir, { max = 18, w = 160, h = 90 } = {}) {
|
|
210
|
+
const sb = JSON.parse(readFileSync(join(runDir, "storyboard.json"), "utf8"));
|
|
211
|
+
const final = join(runDir, "final.mp4"), covered = [];
|
|
212
|
+
let t0 = 0, checked = 0;
|
|
213
|
+
for (const s of sb.scenes) {
|
|
214
|
+
if (s.faceRect && s.source && existsSync(join(runDir, s.source))) {
|
|
215
|
+
for (let t = 0; t < s.duration; t += 0.5) {
|
|
216
|
+
const [f] = grayFrames(final, w, h, { start: t0 + t, duration: 0.05 });
|
|
217
|
+
const [g] = grayFrames(join(runDir, s.source), w, h, { start: (s.sourceStart ?? 0) + t, duration: 0.05 });
|
|
218
|
+
if (!f || !g) continue;
|
|
219
|
+
checked++;
|
|
220
|
+
const d = meanDiff(crop(f, w, h, s.faceRect), crop(g, w, h, s.faceRect));
|
|
221
|
+
if (d > max) covered.push({ scene: s.id, t: Number((t0 + t).toFixed(2)), diff: Number(d.toFixed(1)) });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
t0 += s.duration;
|
|
225
|
+
}
|
|
226
|
+
return { ok: covered.length === 0, covered, checked };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ---------- judge ----------
|
|
230
|
+
|
|
231
|
+
// Pass 1 shows new as A, old as B. Pass 2 swaps them. Keep new only if it wins both,
|
|
232
|
+
// so a judge that just prefers whatever comes first can never promote a version.
|
|
233
|
+
export function pairVerdict(first, second) {
|
|
234
|
+
const keep = first === "A" && second === "B";
|
|
235
|
+
return { keep, winner: keep ? "new" : "old", passes: [first, second], consistent: (first === "A") === (second === "B") };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export const parseWinner = (text) => (String(text).match(/WINNER:\s*([AB])\b/gi) ?? []).pop()?.slice(-1).toUpperCase() ?? null;
|
|
239
|
+
|
|
240
|
+
// One tiled contact sheet per video, 2 frames a second, 6 across.
|
|
241
|
+
export function contactSheet(video, out, { rate = 2, cols = 6 } = {}) {
|
|
242
|
+
const { duration } = probe(video), rows = Math.max(1, Math.ceil((duration * rate) / cols));
|
|
243
|
+
const r = ff(["-v", "error", "-y", "-i", video, "-vf", `fps=${rate},scale=320:-2,tile=${cols}x${rows}`, "-frames:v", "1", out]);
|
|
244
|
+
if (r.status !== 0) throw new Error(`contact sheet failed on ${video}: ${r.stderr}`);
|
|
245
|
+
return out;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export const judgePrompt = (a, b) => `You are judging two versions of the same short video. Each image is a contact sheet: frames left to right, top to bottom, two per second.
|
|
249
|
+
A: ${a}
|
|
250
|
+
B: ${b}
|
|
251
|
+
Open both images. Judge which reads better to a viewer: every card legible, the speaker's face never covered, visuals change with what is said, no empty or cluttered stretches, real evidence instead of filler.
|
|
252
|
+
Name the two biggest differences, then end with exactly one line: WINNER: A or WINNER: B`;
|
|
253
|
+
|
|
254
|
+
// ---------- CLI ----------
|
|
255
|
+
|
|
256
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
257
|
+
const [gate, target, ...rest] = process.argv.slice(2);
|
|
258
|
+
if (gate === "judge" && rest[0] && !rest[0].startsWith("--")) rest.unshift("--old", rest.shift());
|
|
259
|
+
const opt = (n, d) => { const i = rest.indexOf(n); return i >= 0 ? rest[i + 1] : d; };
|
|
260
|
+
const num = (n, d) => Number(opt(n, d));
|
|
261
|
+
if (!gate || !target) { console.error("Usage: qa.mjs <tokens|crossfade|legibility|pops|deadframes|presence|beatsync|beatgrid|face|judge> <path> [options]"); process.exit(1); }
|
|
262
|
+
let result;
|
|
263
|
+
if (gate === "tokens") {
|
|
264
|
+
const tokens = opt("--tokens") ? JSON.parse(readFileSync(opt("--tokens"), "utf8")) : DEFAULT_TOKENS;
|
|
265
|
+
const files = statSync(target).isDirectory() ? htmlFiles(target) : [target];
|
|
266
|
+
const off = files.flatMap((f) => scanTokens(readFileSync(f, "utf8"), tokens).map((v) => ({ file: f, ...v })));
|
|
267
|
+
result = { ok: off.length === 0, off };
|
|
268
|
+
} else if (gate === "crossfade") {
|
|
269
|
+
const files = statSync(target).isDirectory() ? htmlFiles(target) : [target];
|
|
270
|
+
const found = files.flatMap((f) => scanCrossfades(readFileSync(f, "utf8")).map((v) => ({ file: f, ...v })));
|
|
271
|
+
result = { ok: found.length === 0, found };
|
|
272
|
+
} else if (gate === "legibility") result = legibility(target, { every: num("--every", 1), min: num("--min", 180) });
|
|
273
|
+
else if (gate === "pops") {
|
|
274
|
+
const { fps } = probe(target), pops = findPops(grayFrames(target), fps);
|
|
275
|
+
const seams = (opt("--seams", "") || "").split(",").filter(Boolean).map(Number);
|
|
276
|
+
const atSeam = (p) => seams.some((s) => Math.abs(p.t - s) < 2 / fps);
|
|
277
|
+
result = { ok: pops.length === 0, pops: pops.map((p) => ({ ...p, seam: atSeam(p) })) };
|
|
278
|
+
} else if (gate === "deadframes") result = deadFrames(target, { freeze: num("--freeze", 3) });
|
|
279
|
+
else if (gate === "presence") { result = presence(target); result.ok = result.share >= num("--min", 0); }
|
|
280
|
+
else if (gate === "beatsync") {
|
|
281
|
+
const read = (n) => JSON.parse(readFileSync(join(target, "assets", n), "utf8"));
|
|
282
|
+
result = beatSync(read("plan.json"), read("transcript.json"));
|
|
283
|
+
} else if (gate === "beatgrid") {
|
|
284
|
+
if (!opt("--bpm")) { console.error("--bpm is required"); process.exit(1); }
|
|
285
|
+
const { fps } = probe(target);
|
|
286
|
+
result = beatGrid(cutTimes(grayFrames(target), fps), { bpm: num("--bpm"), phase: num("--phase", 0), tol: num("--tol", 0.035) });
|
|
287
|
+
} else if (gate === "face") result = faceCover(target, { max: num("--max", 18) });
|
|
288
|
+
else if (gate === "judge") {
|
|
289
|
+
const old = opt("--old"), out = opt("--out", "qa-judge");
|
|
290
|
+
if (!old) { console.error("judge needs <new.mp4> <old.mp4>"); process.exit(1); }
|
|
291
|
+
mkdirSync(out, { recursive: true });
|
|
292
|
+
const a = contactSheet(target, resolve(out, "new.png")), b = contactSheet(old, resolve(out, "old.png"));
|
|
293
|
+
const prompts = [judgePrompt(a, b), judgePrompt(b, a)];
|
|
294
|
+
let passes = opt("--verdicts")?.split(",").map((v) => v.trim().toUpperCase());
|
|
295
|
+
if (!passes && opt("--cmd")) {
|
|
296
|
+
passes = prompts.map((p, i) => {
|
|
297
|
+
const r = spawnSync(opt("--cmd"), { shell: true, input: p, encoding: "utf8", maxBuffer: 1 << 26 });
|
|
298
|
+
writeFileSync(join(out, `pass${i + 1}.txt`), (r.stdout ?? "") + (r.stderr ?? ""));
|
|
299
|
+
return parseWinner(r.stdout);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
if (!passes) {
|
|
303
|
+
// No command: the agent that built the video judges. Answer each prompt, then rerun with --verdicts.
|
|
304
|
+
prompts.forEach((p, i) => writeFileSync(join(out, `prompt${i + 1}.md`), p));
|
|
305
|
+
console.log(JSON.stringify({ ok: false, pending: true, prompts: [join(out, "prompt1.md"), join(out, "prompt2.md")] }, null, 2));
|
|
306
|
+
process.exit(2);
|
|
307
|
+
}
|
|
308
|
+
if (passes.some((p) => p !== "A" && p !== "B")) { console.log(JSON.stringify({ ok: false, invalid: true, passes }, null, 2)); process.exit(2); }
|
|
309
|
+
result = { ...pairVerdict(...passes), ok: pairVerdict(...passes).keep };
|
|
310
|
+
} else { console.error(`unknown gate: ${gate}`); process.exit(1); }
|
|
311
|
+
console.log(JSON.stringify(result, null, 2));
|
|
312
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
313
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Every gate runs on a control built to fail it and one built to pass it.
|
|
2
|
+
// Fixtures are tiny ffmpeg lavfi videos in a temp folder; nothing is downloaded.
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { mkdtempSync, writeFileSync, readFileSync, copyFileSync, mkdirSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import {
|
|
10
|
+
scanTokens, scanCrossfades, legibility, findPops, deadFrames, presence,
|
|
11
|
+
beatSync, findAnchor, cutTimes, beatGrid, grayFrames, probe,
|
|
12
|
+
faceCover, pairVerdict, parseWinner,
|
|
13
|
+
} from "./qa.mjs";
|
|
14
|
+
|
|
15
|
+
const dir = mkdtempSync(join(tmpdir(), "qa-gates-"));
|
|
16
|
+
const QA = join(import.meta.dirname, "qa.mjs");
|
|
17
|
+
|
|
18
|
+
// Concatenate solid-color segments ([color, seconds]) or lavfi sources into one mp4.
|
|
19
|
+
function video(name, parts, { fps = 30, size = "160x90" } = {}) {
|
|
20
|
+
const out = join(dir, name);
|
|
21
|
+
const ins = parts.map(([src, d], i) => (src.startsWith("testsrc") ? `${src}=size=${size}:rate=${fps}:duration=${d}` : `color=c=${src}:size=${size}:rate=${fps}:duration=${d}`) + `[v${i}]`);
|
|
22
|
+
const graph = `${ins.join(";")};${parts.map((_, i) => `[v${i}]`).join("")}concat=n=${parts.length}:v=1:a=0,format=yuv420p[out]`;
|
|
23
|
+
const r = spawnSync("ffmpeg", ["-v", "error", "-y", "-f", "lavfi", "-i", "nullsrc=size=16x16:duration=0.01", "-filter_complex", graph, "-map", "[out]", "-c:v", "libx264", "-crf", "12", "-g", "1", out]);
|
|
24
|
+
assert.equal(r.status, 0, r.stderr?.toString());
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
const cli = (...a) => spawnSync(process.execPath, [QA, ...a], { encoding: "utf8" });
|
|
28
|
+
|
|
29
|
+
test("tokens: off-system values fail, system values pass", () => {
|
|
30
|
+
const bad = `<style>.a{font-size:31px;border-radius:12px}</style><path stroke-width="3"/>`;
|
|
31
|
+
const good = `<style>.a{font-size:30px;border-radius:22px}</style><path stroke-width="2.5"/>`;
|
|
32
|
+
assert.deepEqual(scanTokens(bad).map((v) => v.kind), ["stroke", "type", "radius"]);
|
|
33
|
+
assert.deepEqual(scanTokens(good), []);
|
|
34
|
+
assert.deepEqual(scanTokens(`<style>.a{font-size:31px}</style>`, { stroke: [], type: [31], radius: [] }), []);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("crossfade: tweening a full-frame layer's opacity fails, a cut or a small element passes", () => {
|
|
38
|
+
const css = `<style>.layer{position:absolute;inset:0}.chip{width:200px;height:40px}</style>`;
|
|
39
|
+
assert.equal(scanCrossfades(`${css}<script>tl.to(".layer",{opacity:0,duration:0.5},2);</script>`).length, 1);
|
|
40
|
+
assert.equal(scanCrossfades(`${css}<script>tl.fromTo(".layer",{opacity:0},{opacity:1,duration:0.4},1);</script>`).length, 1);
|
|
41
|
+
assert.equal(scanCrossfades(`${css}<script>tl.set(".layer",{opacity:0},2);</script>`).length, 0);
|
|
42
|
+
assert.equal(scanCrossfades(`${css}<script>tl.to(".chip",{opacity:0,duration:0.5},2);</script>`).length, 0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("crossfade: the shipped reel and talk templates pass", () => {
|
|
46
|
+
for (const f of ["../../luckiest-video-studio-reel/templates/reel.html", "../../luckiest-video-studio-talk/templates/talk-overlay.html"]) {
|
|
47
|
+
assert.deepEqual(scanCrossfades(readFileSync(join(import.meta.dirname, f), "utf8")), [], f);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("legibility: an all-dim video fails, a bright test pattern passes", () => {
|
|
52
|
+
assert.equal(legibility(video("dim.mp4", [["0x202020", 2]])).ok, false);
|
|
53
|
+
assert.equal(legibility(video("bright.mp4", [["testsrc2", 2]])).ok, true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("pops: a one-frame flash is found, a clean hard cut is not", () => {
|
|
57
|
+
const flash = video("flash.mp4", [["gray", 1], ["white", 1 / 30], ["gray", 1]]);
|
|
58
|
+
const pops = findPops(grayFrames(flash), 30);
|
|
59
|
+
assert.equal(pops.length, 1);
|
|
60
|
+
assert.ok(Math.abs(pops[0].t - 1) < 0.07);
|
|
61
|
+
assert.equal(findPops(grayFrames(video("cut.mp4", [["gray", 1], ["white", 1]])), 30).length, 0);
|
|
62
|
+
assert.equal(cli("pops", flash).status, 1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("deadframes: black and long freezes fail, moving footage passes", () => {
|
|
66
|
+
const r = deadFrames(video("black.mp4", [["testsrc2", 1], ["black", 0.5], ["testsrc2", 1]]));
|
|
67
|
+
assert.equal(r.black.length, 1);
|
|
68
|
+
assert.equal(deadFrames(video("frozen.mp4", [["0x808080", 4]]), { freeze: 3 }).freezes.length, 1);
|
|
69
|
+
assert.equal(deadFrames(video("moving.mp4", [["testsrc2", 3]])).ok, true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("presence: the clip itself reads as on screen, a black final does not", () => {
|
|
73
|
+
const run = join(dir, "run");
|
|
74
|
+
mkdirSync(join(run, "footage"), { recursive: true });
|
|
75
|
+
const clip = video("clip.mp4", [["testsrc2", 4]]);
|
|
76
|
+
copyFileSync(clip, join(run, "footage/clean.mp4"));
|
|
77
|
+
writeFileSync(join(run, "storyboard.json"), JSON.stringify({ scenes: [{ id: "talk", kind: "demo", duration: 4, source: "footage/clean.mp4", sourceStart: 0 }] }));
|
|
78
|
+
copyFileSync(clip, join(run, "final.mp4"));
|
|
79
|
+
assert.ok(presence(run).share > 0.9);
|
|
80
|
+
copyFileSync(video("allblack.mp4", [["black", 4]]), join(run, "final.mp4"));
|
|
81
|
+
assert.equal(presence(run).share, 0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("beatsync: anchors on their words pass, a late overlay and a missing phrase fail", () => {
|
|
85
|
+
const words = [{ text: "Grow", start: 0.1, end: 0.4 }, { text: "something.", start: 0.4, end: 1 }, { text: "Start", start: 2.1, end: 2.4 }];
|
|
86
|
+
assert.equal(findAnchor(words, "grow something"), 0.1);
|
|
87
|
+
const plan = (t) => ({ scenes: [{ id: "s00", start: 0, anchor: "Grow something" }], events: [{ time: t, visual: "s00", anchor: "Start" }] });
|
|
88
|
+
assert.equal(beatSync(plan(1.9), words).ok, true);
|
|
89
|
+
assert.equal(beatSync(plan(2.6), words).ok, false);
|
|
90
|
+
assert.equal(beatSync({ scenes: [{ id: "x", start: 0, anchor: "never said" }] }, words).ok, false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("beatgrid: cuts on the beat pass, cuts 0.1 s late fail", () => {
|
|
94
|
+
const beat = 60 / 120; // 15 frames at 30 fps, so the fixture has no rounding drift
|
|
95
|
+
const onGrid = video("grid.mp4", [["red", beat], ["blue", beat], ["red", beat], ["blue", beat]]);
|
|
96
|
+
const { fps } = probe(onGrid);
|
|
97
|
+
assert.equal(beatGrid(cutTimes(grayFrames(onGrid), fps), { bpm: 120 }).ok, true);
|
|
98
|
+
const late = video("late.mp4", [["red", beat + 0.1], ["blue", beat], ["red", beat]]);
|
|
99
|
+
assert.equal(beatGrid(cutTimes(grayFrames(late), fps), { bpm: 120 }).ok, false);
|
|
100
|
+
assert.equal(cli("beatgrid", late, "--bpm", "120").status, 1);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("face: the clip alone passes, a box over the face fails", () => {
|
|
104
|
+
const run = join(dir, "facerun");
|
|
105
|
+
mkdirSync(join(run, "footage"), { recursive: true });
|
|
106
|
+
const clip = video("face-clip.mp4", [["testsrc2", 2]]);
|
|
107
|
+
copyFileSync(clip, join(run, "footage/clean.mp4"));
|
|
108
|
+
writeFileSync(join(run, "storyboard.json"), JSON.stringify({ scenes: [{ id: "talk", kind: "demo", duration: 2, source: "footage/clean.mp4", faceRect: [0.3, 0.2, 0.4, 0.5] }] }));
|
|
109
|
+
copyFileSync(clip, join(run, "final.mp4"));
|
|
110
|
+
assert.equal(faceCover(run).ok, true);
|
|
111
|
+
const boxed = join(dir, "face-boxed.mp4");
|
|
112
|
+
const r = spawnSync("ffmpeg", ["-v", "error", "-y", "-i", clip, "-vf", "drawbox=x=iw*0.3:y=ih*0.2:w=iw*0.4:h=ih*0.5:color=white:t=fill", "-c:v", "libx264", "-crf", "12", boxed]);
|
|
113
|
+
assert.equal(r.status, 0, r.stderr?.toString());
|
|
114
|
+
copyFileSync(boxed, join(run, "final.mp4"));
|
|
115
|
+
assert.equal(faceCover(run).ok, false);
|
|
116
|
+
assert.equal(cli("face", run).status, 1);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("judge: new is kept only when it wins with the order swapped", () => {
|
|
120
|
+
assert.equal(pairVerdict("A", "B").keep, true);
|
|
121
|
+
assert.equal(pairVerdict("A", "A").keep, false); // always picks the first shown
|
|
122
|
+
assert.equal(pairVerdict("B", "B").keep, false); // always picks the second shown
|
|
123
|
+
assert.equal(pairVerdict("B", "A").keep, false);
|
|
124
|
+
assert.equal(pairVerdict("A", "A").consistent, false);
|
|
125
|
+
assert.equal(parseWinner("A is cleaner.\nWINNER: b"), "B");
|
|
126
|
+
assert.equal(parseWinner("no verdict"), null);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("judge CLI: pending without a verdict, keeps or rejects with verdicts, fake judge command", () => {
|
|
130
|
+
const n = video("j-new.mp4", [["testsrc2", 1]]), o = video("j-old.mp4", [["gray", 1]]), out = join(dir, "judge");
|
|
131
|
+
assert.equal(cli("judge", n, o, "--out", out).status, 2);
|
|
132
|
+
assert.equal(cli("judge", n, o, "--out", out, "--verdicts", "A,B").status, 0);
|
|
133
|
+
assert.equal(cli("judge", n, o, "--out", out, "--verdicts", "A,A").status, 1);
|
|
134
|
+
assert.equal(cli("judge", n, o, "--out", out, "--cmd", "echo WINNER: A").status, 1); // first-position bias
|
|
135
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Render each storyboard scene to clips/NN-id.mp4, skip unchanged scenes, then join final.mp4 and write TIMING.md.
|
|
3
|
+
// Usage: node render-scenes.mjs <run-dir> [--scene <id>]... [--quality draft|looks|delivery] [--no-join]
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const runDir = resolve(args.find((a, i) => !a.startsWith("--") && !["--scene", "--quality"].includes(args[i - 1])) ?? ".");
|
|
11
|
+
const forced = args.flatMap((a, i) => (a === "--scene" ? [args[i + 1]] : []));
|
|
12
|
+
const quality = args.includes("--quality") ? args[args.indexOf("--quality") + 1] : "looks";
|
|
13
|
+
const join_ = !args.includes("--no-join");
|
|
14
|
+
|
|
15
|
+
const sb = JSON.parse(readFileSync(join(runDir, "storyboard.json"), "utf8"));
|
|
16
|
+
const { width: W, height: H, fps } = sb.format;
|
|
17
|
+
const clipsDir = join(runDir, "clips");
|
|
18
|
+
const rawDir = join(clipsDir, ".raw");
|
|
19
|
+
mkdirSync(rawDir, { recursive: true });
|
|
20
|
+
const cachePath = join(clipsDir, ".cache.json");
|
|
21
|
+
const cache = existsSync(cachePath) ? JSON.parse(readFileSync(cachePath, "utf8")) : {};
|
|
22
|
+
|
|
23
|
+
const fail = (msg) => { console.error(`error: ${msg}`); process.exit(1); };
|
|
24
|
+
const run = (cmd, argv, opts = {}) => {
|
|
25
|
+
const r = spawnSync(cmd, argv, { stdio: ["ignore", "inherit", "inherit"], ...opts });
|
|
26
|
+
if (r.error) fail(`${cmd} not found (${r.error.code})`);
|
|
27
|
+
if (r.status !== 0) fail(`${cmd} ${argv.slice(0, 3).join(" ")} exited ${r.status}`);
|
|
28
|
+
};
|
|
29
|
+
const hasBin = (b) => spawnSync("which", [b], { stdio: "ignore" }).status === 0;
|
|
30
|
+
|
|
31
|
+
const ids = new Set();
|
|
32
|
+
for (const s of sb.scenes) {
|
|
33
|
+
if (ids.has(s.id)) fail(`duplicate scene id "${s.id}"`);
|
|
34
|
+
ids.add(s.id);
|
|
35
|
+
if (!["motion", "logo", "demo"].includes(s.kind)) fail(`scene ${s.id}: unknown kind "${s.kind}"`);
|
|
36
|
+
if (!(s.duration > 0)) fail(`scene ${s.id}: duration must be > 0`);
|
|
37
|
+
}
|
|
38
|
+
for (const id of forced) if (!ids.has(id)) fail(`--scene ${id}: no such scene`);
|
|
39
|
+
|
|
40
|
+
const inputsOf = (s) => {
|
|
41
|
+
const files = [...(s.assets ?? [])];
|
|
42
|
+
if (s.kind === "demo") {
|
|
43
|
+
if (!s.source) fail(`demo scene ${s.id} needs "source"`);
|
|
44
|
+
files.push(s.source);
|
|
45
|
+
} else files.push(join("composition", s.composition ?? `compositions/${s.id}.html`));
|
|
46
|
+
return files;
|
|
47
|
+
};
|
|
48
|
+
const hashOf = (s) => {
|
|
49
|
+
const h = createHash("sha256").update(JSON.stringify({ s, W, H, fps, quality }));
|
|
50
|
+
for (const f of inputsOf(s)) {
|
|
51
|
+
const p = join(runDir, f);
|
|
52
|
+
if (!existsSync(p)) fail(`scene ${s.id}: missing ${f}`);
|
|
53
|
+
h.update(f).update(readFileSync(p));
|
|
54
|
+
}
|
|
55
|
+
return h.digest("hex").slice(0, 16);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Every clip gets the same codec, size, fps and a stereo audio track so the join is a lossless concat.
|
|
59
|
+
const normalize = (input, out, s, seek = 0) => {
|
|
60
|
+
const probe = spawnSync("ffprobe", ["-v", "error", "-select_streams", "a", "-show_entries", "stream=index", "-of", "csv=p=0", input], { encoding: "utf8" });
|
|
61
|
+
const hasAudio = probe.stdout.trim().length > 0;
|
|
62
|
+
const vf = `scale=${W}:${H}:force_original_aspect_ratio=decrease,pad=${W}:${H}:(ow-iw)/2:(oh-ih)/2,fps=${fps},format=yuv420p`;
|
|
63
|
+
const a = ["-y", "-v", "error", "-ss", String(seek), "-t", String(s.duration), "-i", input];
|
|
64
|
+
if (!hasAudio) a.push("-f", "lavfi", "-t", String(s.duration), "-i", "anullsrc=r=48000:cl=stereo");
|
|
65
|
+
a.push("-map", "0:v:0", "-map", hasAudio ? "0:a:0" : "1:a:0", "-vf", vf, "-c:v", "libx264", "-crf", "18", "-preset", "medium",
|
|
66
|
+
"-c:a", "aac", "-ar", "48000", "-ac", "2", "-shortest", "-movflags", "+faststart", out);
|
|
67
|
+
run("ffmpeg", a);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const hf = hasBin("hyperframes") ? ["hyperframes"] : ["npx", `hyperframes@${sb.hyperframes ?? "latest"}`];
|
|
71
|
+
let rendered = 0;
|
|
72
|
+
const clips = sb.scenes.map((s, i) => {
|
|
73
|
+
const name = `${String(i + 1).padStart(2, "0")}-${s.id}.mp4`;
|
|
74
|
+
const out = join(clipsDir, name);
|
|
75
|
+
const hash = hashOf(s);
|
|
76
|
+
const prev = cache[s.id];
|
|
77
|
+
const force = forced.includes(s.id);
|
|
78
|
+
if (!force && prev?.hash === hash && existsSync(join(clipsDir, prev.file))) {
|
|
79
|
+
if (prev.file !== name) renameSync(join(clipsDir, prev.file), out);
|
|
80
|
+
console.log(`skip ${name} (unchanged)`);
|
|
81
|
+
} else if (forced.length && !force && prev && existsSync(join(clipsDir, prev.file))) {
|
|
82
|
+
if (prev.file !== name) renameSync(join(clipsDir, prev.file), out);
|
|
83
|
+
console.log(`keep ${name} (not selected)`);
|
|
84
|
+
return { s, name, hash: prev.hash };
|
|
85
|
+
} else {
|
|
86
|
+
console.log(`render ${name}`);
|
|
87
|
+
if (s.kind === "demo") normalize(join(runDir, s.source), out, s, s.sourceStart ?? 0);
|
|
88
|
+
else {
|
|
89
|
+
const raw = join(rawDir, name);
|
|
90
|
+
const comp = s.composition ?? `compositions/${s.id}.html`;
|
|
91
|
+
run(hf[0], [...hf.slice(1), "render", "-c", comp, "-o", raw, "--fps", String(fps), "--quality", quality], { cwd: join(runDir, "composition") });
|
|
92
|
+
normalize(raw, out, s);
|
|
93
|
+
}
|
|
94
|
+
rendered++;
|
|
95
|
+
}
|
|
96
|
+
return { s, name, hash };
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const nextCache = Object.fromEntries(clips.map((c) => [c.s.id, { hash: c.hash, file: c.name }]));
|
|
100
|
+
writeFileSync(cachePath, JSON.stringify(nextCache, null, 2));
|
|
101
|
+
|
|
102
|
+
let t = 0;
|
|
103
|
+
const rows = clips.map(({ s, name }, i) => {
|
|
104
|
+
const row = `| ${i + 1} | ${s.id} | ${s.kind} | ${t.toFixed(2)} | ${(t + s.duration).toFixed(2)} | \`clips/${name}\` | ${(s.line ?? "").replace(/\|/g, "/")} |`;
|
|
105
|
+
t += s.duration;
|
|
106
|
+
return row;
|
|
107
|
+
});
|
|
108
|
+
writeFileSync(join(runDir, "TIMING.md"), `# Timing: ${sb.title}\n\nTotal ${t.toFixed(2)}s at ${W}x${H}, ${fps} fps.\n\n| # | Scene | Kind | In (s) | Out (s) | Clip | Line |\n|---|---|---|---|---|---|---|\n${rows.join("\n")}\n`);
|
|
109
|
+
|
|
110
|
+
if (join_) {
|
|
111
|
+
const list = join(clipsDir, ".concat.txt");
|
|
112
|
+
writeFileSync(list, clips.map((c) => `file '${join(clipsDir, c.name).replace(/'/g, "'\\''")}'`).join("\n") + "\n");
|
|
113
|
+
const joined = join(clipsDir, ".joined.mp4");
|
|
114
|
+
run("ffmpeg", ["-y", "-v", "error", "-f", "concat", "-safe", "0", "-i", list, "-c", "copy", joined]);
|
|
115
|
+
const final = join(runDir, "final.mp4");
|
|
116
|
+
if (sb.music?.file) {
|
|
117
|
+
const vol = String(sb.music.volume ?? 0.35);
|
|
118
|
+
run("ffmpeg", ["-y", "-v", "error", "-i", joined, "-stream_loop", "-1", "-i", join(runDir, sb.music.file),
|
|
119
|
+
"-filter_complex", `[1:a]volume=${vol},afade=t=out:st=${Math.max(0, t - 1.5)}:d=1.5[m];[0:a][m]amix=inputs=2:duration=first:normalize=0[a]`,
|
|
120
|
+
"-map", "0:v", "-map", "[a]", "-c:v", "copy", "-c:a", "aac", "-t", String(t), "-movflags", "+faststart", final]);
|
|
121
|
+
rmSync(joined);
|
|
122
|
+
} else renameSync(joined, final);
|
|
123
|
+
console.log(`joined final.mp4 (${t.toFixed(2)}s)`);
|
|
124
|
+
}
|
|
125
|
+
console.log(`done: ${rendered} rendered, ${clips.length - rendered} reused`);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "luckiest-video-studio storyboard",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": ["version", "title", "mode", "format", "scenes"],
|
|
6
|
+
"properties": {
|
|
7
|
+
"version": { "const": 1 },
|
|
8
|
+
"title": { "type": "string", "minLength": 1 },
|
|
9
|
+
"mode": { "enum": ["launch", "demo", "logos"] },
|
|
10
|
+
"tone": { "type": "string" },
|
|
11
|
+
"format": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"required": ["width", "height", "fps"],
|
|
14
|
+
"properties": {
|
|
15
|
+
"width": { "type": "integer", "minimum": 16 },
|
|
16
|
+
"height": { "type": "integer", "minimum": 16 },
|
|
17
|
+
"fps": { "enum": [24, 30, 60] }
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"hyperframes": { "type": "string", "description": "Pinned HyperFrames version for npx, for example 0.8.84" },
|
|
21
|
+
"music": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"required": ["file"],
|
|
24
|
+
"properties": {
|
|
25
|
+
"file": { "type": "string", "description": "Path relative to the run folder" },
|
|
26
|
+
"volume": { "type": "number", "minimum": 0, "maximum": 0.5, "default": 0.35 },
|
|
27
|
+
"license": { "type": "string" }
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"scenes": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"minItems": 1,
|
|
33
|
+
"items": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"required": ["id", "kind", "duration"],
|
|
36
|
+
"properties": {
|
|
37
|
+
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" },
|
|
38
|
+
"kind": { "enum": ["motion", "logo", "demo"] },
|
|
39
|
+
"duration": { "type": "number", "exclusiveMinimum": 0 },
|
|
40
|
+
"line": { "type": "string", "description": "What this scene says or shows, one line, used in TIMING.md" },
|
|
41
|
+
"composition": { "type": "string", "description": "motion and logo: sub-composition path relative to composition/, default compositions/<id>.html" },
|
|
42
|
+
"recipe": { "type": "string", "description": "logo: recipe name from references/logo-recipes.md" },
|
|
43
|
+
"source": { "type": "string", "description": "demo: footage path relative to the run folder" },
|
|
44
|
+
"sourceStart": { "type": "number", "minimum": 0, "default": 0 },
|
|
45
|
+
"faceRect": { "type": "array", "items": { "type": "number", "minimum": 0, "maximum": 1 }, "minItems": 4, "maxItems": 4, "description": "demo: speaker's face as [x, y, w, h] fractions of the frame, checked by the face gate" },
|
|
46
|
+
"variables": { "type": "object", "description": "Values the composition reads: copy, colors, logo path" },
|
|
47
|
+
"assets": { "type": "array", "items": { "type": "string" }, "description": "Extra files (relative to the run folder) whose change should trigger a re-render" }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|