kasaframe 0.0.0-stage โ 0.1.3
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/LICENSE +21 -0
- package/NOTICE +148 -0
- package/README.md +92 -2
- package/app/package.json +12 -0
- package/app/server/engine.ts +533 -0
- package/app/server/main.ts +209 -0
- package/app/web/env.d.ts +2 -0
- package/app/web/icons.ts +31 -0
- package/app/web/index.html +21 -0
- package/app/web/main.ts +1195 -0
- package/app/web/style.css +463 -0
- package/bin/hooks.mjs +64 -0
- package/bin/kf.mjs +32 -0
- package/docs/AGENTS.md +286 -0
- package/music/README.md +37 -0
- package/package.json +44 -4
- package/packages/adapters/package.json +36 -0
- package/packages/adapters/src/css.ts +11 -0
- package/packages/adapters/src/gsap.ts +26 -0
- package/packages/adapters/src/index.ts +9 -0
- package/packages/adapters/src/lottie.ts +22 -0
- package/packages/adapters/src/media.ts +59 -0
- package/packages/adapters/src/page.ts +229 -0
- package/packages/adapters/src/project.ts +214 -0
- package/packages/adapters/src/react.ts +36 -0
- package/packages/adapters/src/svelte.ts +22 -0
- package/packages/adapters/src/three.ts +18 -0
- package/packages/adapters/src/vue.ts +27 -0
- package/packages/check/package.json +19 -0
- package/packages/check/src/check.ts +280 -0
- package/packages/check/src/cli.ts +70 -0
- package/packages/check/src/image.ts +146 -0
- package/packages/check/src/index.ts +9 -0
- package/packages/check/src/ocr.swift +37 -0
- package/packages/check/src/probe.ts +328 -0
- package/packages/check/src/report.ts +44 -0
- package/packages/check/src/rules/assets.ts +45 -0
- package/packages/check/src/rules/beats.ts +212 -0
- package/packages/check/src/rules/camera.ts +217 -0
- package/packages/check/src/rules/captions.ts +49 -0
- package/packages/check/src/rules/flash.ts +235 -0
- package/packages/check/src/rules/frames.ts +136 -0
- package/packages/check/src/rules/layout.ts +281 -0
- package/packages/check/src/rules/page.ts +87 -0
- package/packages/check/src/rules/runs.ts +20 -0
- package/packages/check/src/rules/text.ts +326 -0
- package/packages/check/src/scan.ts +240 -0
- package/packages/check/src/serve.ts +67 -0
- package/packages/check/src/types.ts +80 -0
- package/packages/check/src/video.ts +137 -0
- package/packages/cli/package.json +30 -0
- package/packages/cli/src/blur.ts +80 -0
- package/packages/cli/src/browser.ts +203 -0
- package/packages/cli/src/command.ts +44 -0
- package/packages/cli/src/commands/app.ts +25 -0
- package/packages/cli/src/commands/beats.ts +36 -0
- package/packages/cli/src/commands/bench.ts +123 -0
- package/packages/cli/src/commands/blender.ts +293 -0
- package/packages/cli/src/commands/check.ts +25 -0
- package/packages/cli/src/commands/direct.ts +210 -0
- package/packages/cli/src/commands/doctor.ts +30 -0
- package/packages/cli/src/commands/gen.ts +173 -0
- package/packages/cli/src/commands/look.ts +46 -0
- package/packages/cli/src/commands/lyrics.ts +157 -0
- package/packages/cli/src/commands/make.ts +1121 -0
- package/packages/cli/src/commands/music.ts +78 -0
- package/packages/cli/src/commands/narrate.ts +238 -0
- package/packages/cli/src/commands/new.ts +185 -0
- package/packages/cli/src/commands/preview.ts +43 -0
- package/packages/cli/src/commands/render.ts +250 -0
- package/packages/cli/src/commands/series.ts +110 -0
- package/packages/cli/src/commands/setup.ts +102 -0
- package/packages/cli/src/commands/sheet.ts +114 -0
- package/packages/cli/src/commands/skill.ts +43 -0
- package/packages/cli/src/commands/still.ts +40 -0
- package/packages/cli/src/commands/theme.ts +172 -0
- package/packages/cli/src/commands/transitions.ts +40 -0
- package/packages/cli/src/deps.ts +122 -0
- package/packages/cli/src/ffmpeg.ts +81 -0
- package/packages/cli/src/index.ts +8 -0
- package/packages/cli/src/kf.ts +64 -0
- package/packages/cli/src/lock.ts +72 -0
- package/packages/cli/src/mix.ts +131 -0
- package/packages/cli/src/out.ts +13 -0
- package/packages/cli/src/pool.ts +34 -0
- package/packages/cli/src/segments.ts +402 -0
- package/packages/cli/src/serve.ts +124 -0
- package/packages/clock/package.json +14 -0
- package/packages/clock/src/index.ts +25 -0
- package/packages/clock/src/install.ts +351 -0
- package/packages/clock/src/types.ts +45 -0
- package/packages/core/package.json +9 -0
- package/packages/core/src/beats.ts +283 -0
- package/packages/core/src/ease.ts +87 -0
- package/packages/core/src/frame.ts +99 -0
- package/packages/core/src/index.ts +27 -0
- package/packages/core/src/project.ts +157 -0
- package/packages/core/src/rand.ts +56 -0
- package/packages/core/src/spring.ts +52 -0
- package/packages/core/src/style.ts +100 -0
- package/packages/core/src/time.ts +88 -0
- package/packages/director/package.json +23 -0
- package/packages/director/src/assemble.ts +361 -0
- package/packages/director/src/brief.ts +122 -0
- package/packages/director/src/catalog.ts +49 -0
- package/packages/director/src/content.ts +343 -0
- package/packages/director/src/fallback.ts +58 -0
- package/packages/director/src/index.ts +13 -0
- package/packages/director/src/pattern.ts +90 -0
- package/packages/director/src/patterns/fill/cells.ts +115 -0
- package/packages/director/src/patterns/formula/stack.ts +93 -0
- package/packages/director/src/patterns/util.ts +58 -0
- package/packages/director/src/scene.ts +62 -0
- package/packages/director/src/select.ts +163 -0
- package/packages/director/src/transitions.ts +115 -0
- package/packages/gen/PROVIDERS.md +90 -0
- package/packages/gen/package.json +9 -0
- package/packages/gen/src/ask.ts +132 -0
- package/packages/gen/src/audio.ts +91 -0
- package/packages/gen/src/blender.ts +54 -0
- package/packages/gen/src/capabilities.ts +151 -0
- package/packages/gen/src/generate.ts +206 -0
- package/packages/gen/src/image/codex.ts +117 -0
- package/packages/gen/src/image/og.ts +133 -0
- package/packages/gen/src/index.ts +26 -0
- package/packages/gen/src/keys.ts +86 -0
- package/packages/gen/src/media.ts +150 -0
- package/packages/gen/src/music/elevenlabs.ts +41 -0
- package/packages/gen/src/music/index.ts +120 -0
- package/packages/gen/src/music/library.ts +73 -0
- package/packages/gen/src/music/lyria.ts +52 -0
- package/packages/gen/src/music/types.ts +28 -0
- package/packages/gen/src/plan.ts +106 -0
- package/packages/gen/src/registry.ts +54 -0
- package/packages/gen/src/seam.ts +103 -0
- package/packages/gen/src/tts/dashscope.ts +64 -0
- package/packages/gen/src/tts/elevenlabs.ts +84 -0
- package/packages/gen/src/tts/gemini.ts +73 -0
- package/packages/gen/src/tts/index.ts +241 -0
- package/packages/gen/src/tts/local.ts +42 -0
- package/packages/gen/src/tts/types.ts +44 -0
- package/packages/gen/src/types.ts +90 -0
- package/packages/gen/src/video/fal.ts +140 -0
- package/packages/gen/src/video/h3.ts +112 -0
- package/packages/gen/src/video/http.ts +44 -0
- package/packages/gen/src/video/ltx.ts +60 -0
- package/packages/gen/src/video/minimax.ts +55 -0
- package/packages/gen/src/video/veo.ts +71 -0
- package/packages/jizura/package.json +16 -0
- package/packages/jizura/src/deny.ts +73 -0
- package/packages/jizura/src/index.ts +7 -0
- package/packages/jizura/src/install.ts +269 -0
- package/packages/jizura/src/layer.ts +325 -0
- package/packages/jizura/src/lyrics.ts +81 -0
- package/packages/jizura/src/pin.ts +8 -0
- package/packages/jizura/src/vendor.ts +140 -0
- package/packages/jizura/src/vite.ts +63 -0
- package/packages/jizura/vendor/JIZURA_LICENSE.txt +21 -0
- package/packages/jizura/vendor/NOTICE +43 -0
- package/packages/jizura/vendor/engine.json +47 -0
- package/packages/jizura/vendor/fonts/BlackHanSans-Regular.woff2 +0 -0
- package/packages/jizura/vendor/fonts/DotGothic16-Regular.woff2 +0 -0
- package/packages/jizura/vendor/fonts/GowunBatang-Bold.woff2 +0 -0
- package/packages/jizura/vendor/fonts/IBMPlexMono-Medium.ttf +0 -0
- package/packages/jizura/vendor/fonts/IBMPlexMono-SemiBold.ttf +0 -0
- package/packages/jizura/vendor/fonts/NotoSansKR-VF.woff2 +0 -0
- package/packages/jizura/vendor/fonts/NotoSerifKR-VF.woff2 +0 -0
- package/packages/jizura/vendor/fonts/OFL-blackhansans.txt +93 -0
- package/packages/jizura/vendor/fonts/OFL-dotgothic16.txt +93 -0
- package/packages/jizura/vendor/fonts/OFL-gowunbatang.txt +94 -0
- package/packages/jizura/vendor/fonts/OFL-ibmplexmono.txt +93 -0
- package/packages/jizura/vendor/fonts/OFL-notosanskr.txt +93 -0
- package/packages/jizura/vendor/fonts/OFL-notoserifkr.txt +94 -0
- package/packages/jizura/vendor/fonts/fonts.json +52 -0
- package/packages/jizura/vendor/jizura_engine.js +35024 -0
- package/packages/motion/README.md +153 -0
- package/packages/motion/package.json +30 -0
- package/packages/motion/src/camera.ts +97 -0
- package/packages/motion/src/captions.ts +487 -0
- package/packages/motion/src/enter.ts +128 -0
- package/packages/motion/src/hangul.ts +129 -0
- package/packages/motion/src/impact.ts +100 -0
- package/packages/motion/src/index.ts +17 -0
- package/packages/motion/src/moves/brushWipe.ts +103 -0
- package/packages/motion/src/moves/cloneArray.ts +111 -0
- package/packages/motion/src/moves/curtains.ts +81 -0
- package/packages/motion/src/moves/dress.ts +191 -0
- package/packages/motion/src/moves/echoStack.ts +70 -0
- package/packages/motion/src/moves/fit.ts +70 -0
- package/packages/motion/src/moves/flood.ts +61 -0
- package/packages/motion/src/moves/ghostStack.ts +88 -0
- package/packages/motion/src/moves/gridFloor.ts +100 -0
- package/packages/motion/src/moves/imageSwarm.ts +113 -0
- package/packages/motion/src/moves/index.ts +38 -0
- package/packages/motion/src/moves/irisBlades.ts +113 -0
- package/packages/motion/src/moves/karaokeLine.ts +108 -0
- package/packages/motion/src/moves/ledBoard.ts +94 -0
- package/packages/motion/src/moves/liquidTab.ts +72 -0
- package/packages/motion/src/moves/markerWrite.ts +63 -0
- package/packages/motion/src/moves/menu.ts +138 -0
- package/packages/motion/src/moves/morphBox.ts +96 -0
- package/packages/motion/src/moves/neonSign.ts +78 -0
- package/packages/motion/src/moves/panelPop.ts +78 -0
- package/packages/motion/src/moves/paperTear.ts +75 -0
- package/packages/motion/src/moves/path.ts +64 -0
- package/packages/motion/src/moves/pathText.ts +107 -0
- package/packages/motion/src/moves/pointSphere.ts +109 -0
- package/packages/motion/src/moves/ransomLetters.ts +73 -0
- package/packages/motion/src/moves/shapeMorph.ts +143 -0
- package/packages/motion/src/moves/slamWords.ts +96 -0
- package/packages/motion/src/moves/sparkTrail.ts +130 -0
- package/packages/motion/src/moves/speedLines.ts +91 -0
- package/packages/motion/src/moves/splitFlap.ts +149 -0
- package/packages/motion/src/moves/stepWords.ts +128 -0
- package/packages/motion/src/moves/strikeOut.ts +98 -0
- package/packages/motion/src/moves/sunburst.ts +58 -0
- package/packages/motion/src/moves/tickerRows.ts +91 -0
- package/packages/motion/src/moves/tokenPicker.ts +115 -0
- package/packages/motion/src/moves/truchet.ts +85 -0
- package/packages/motion/src/moves/wordWall.ts +78 -0
- package/packages/motion/src/moves/worldRoll.ts +32 -0
- package/packages/motion/src/particles.ts +102 -0
- package/packages/motion/src/react/index.tsx +21 -0
- package/packages/motion/src/react/parts.tsx +431 -0
- package/packages/motion/src/react/time.tsx +97 -0
- package/packages/motion/src/text.ts +103 -0
- package/packages/motion/src/transition.ts +82 -0
- package/packages/motion/src/transition3d/cards.ts +65 -0
- package/packages/motion/src/transition3d/chain.ts +84 -0
- package/packages/motion/src/transition3d/dive.ts +181 -0
- package/packages/motion/src/transition3d/fold.ts +110 -0
- package/packages/motion/src/transition3d/gl.ts +219 -0
- package/packages/motion/src/transition3d/index.ts +235 -0
- package/packages/motion/src/transition3d/live.ts +70 -0
- package/packages/motion/src/transition3d/portal.ts +115 -0
- package/packages/motion/src/transition3d/react.tsx +29 -0
- package/packages/motion/src/transition3d/rig.ts +600 -0
- package/packages/motion/src/transition3d/shatter.ts +104 -0
- package/packages/motion/src/transition3d/space.ts +173 -0
- package/packages/motion/src/transition3d/whip.ts +78 -0
- package/packages/motion/src/transition3d/world.ts +737 -0
- package/packages/motion/src/transition3d/zoomOut.ts +114 -0
- package/packages/motion/src/transitions2d/catalog.ts +127 -0
- package/packages/motion/src/transitions2d/css.d.ts +2 -0
- package/packages/motion/src/transitions2d/cult-ui/LICENSE.md +9 -0
- package/packages/motion/src/transitions2d/cult-ui/RollingNumber.tsx +43 -0
- package/packages/motion/src/transitions2d/gl-transitions/LICENSE +25 -0
- package/packages/motion/src/transitions2d/gl-transitions/shaders.ts +1931 -0
- package/packages/motion/src/transitions2d/index.ts +2 -0
- package/packages/motion/src/transitions2d/kokonutui/LICENSE +21 -0
- package/packages/motion/src/transitions2d/kokonutui/MatrixText.css +16 -0
- package/packages/motion/src/transitions2d/kokonutui/MatrixText.tsx +94 -0
- package/packages/motion/src/transitions2d/magicui/HyperText.css +15 -0
- package/packages/motion/src/transitions2d/magicui/HyperText.tsx +115 -0
- package/packages/motion/src/transitions2d/magicui/LICENSE.md +21 -0
- package/packages/motion/src/transitions2d/magicui/MorphingText.css +25 -0
- package/packages/motion/src/transitions2d/magicui/MorphingText.tsx +130 -0
- package/packages/motion/src/transitions2d/magicui/WordRotate.tsx +42 -0
- package/packages/motion/src/transitions2d/react.ts +13 -0
- package/packages/motion/src/transitions2d/skiper-ui/ClipDiv.css +39 -0
- package/packages/motion/src/transitions2d/skiper-ui/ClipDiv.tsx +32 -0
- package/packages/motion/src/transitions2d/smoothui/LICENSE +21 -0
- package/packages/motion/src/transitions2d/smoothui/SharedAxisY.tsx +50 -0
- package/packages/motion/src/transitions2d/smoothui/TextMorph.tsx +90 -0
- package/packages/motion/src/transitions2d/smoothui/shaders.ts +223 -0
- package/packages/motion/src/transitions2d/unlumen/LICENSE +21 -0
- package/packages/motion/src/transitions2d/unlumen/ScrambleText.tsx +114 -0
- package/packages/motion/src/transitions2d/vengeance-ui/LICENSE +21 -0
- package/packages/motion/src/transitions2d/vengeance-ui/MorphText.css +69 -0
- package/packages/motion/src/transitions2d/vengeance-ui/MorphText.tsx +45 -0
- package/packages/motion/src/type.ts +131 -0
- package/packages/motion/src/ui.ts +172 -0
- package/packages/patterns/package.json +15 -0
- package/packages/patterns/src/bars/rank.ts +119 -0
- package/packages/patterns/src/before-after/shrink.ts +127 -0
- package/packages/patterns/src/before-after/strike.ts +125 -0
- package/packages/patterns/src/code/terminal.ts +133 -0
- package/packages/patterns/src/compare/bars.ts +142 -0
- package/packages/patterns/src/compare/duel.ts +103 -0
- package/packages/patterns/src/end/lockup.ts +85 -0
- package/packages/patterns/src/end/slam.ts +91 -0
- package/packages/patterns/src/flow/graph.ts +250 -0
- package/packages/patterns/src/hook-number/counter.ts +101 -0
- package/packages/patterns/src/hook-number/roll.ts +179 -0
- package/packages/patterns/src/hook-number/slam.ts +91 -0
- package/packages/patterns/src/index.ts +38 -0
- package/packages/patterns/src/kit.ts +449 -0
- package/packages/patterns/src/limit/gauge.ts +117 -0
- package/packages/patterns/src/list/cards.ts +131 -0
- package/packages/patterns/src/list/cycle.ts +22 -0
- package/packages/patterns/src/media/panels.ts +100 -0
- package/packages/patterns/src/objects.ts +234 -0
- package/packages/patterns/src/quote/karaoke.ts +126 -0
- package/packages/patterns/src/quote/slam.ts +22 -0
- package/packages/patterns/src/screen/focus.ts +85 -0
- package/packages/patterns/src/steps/path.ts +137 -0
- package/packages/patterns/src/steps/stack.ts +111 -0
- package/packages/patterns/src/term-reveal/chips.ts +92 -0
- package/packages/patterns/src/term-reveal/expand.ts +127 -0
- package/packages/patterns/src/timeline/axis.ts +144 -0
- package/packages/patterns/src/title/mask.ts +85 -0
- package/packages/patterns/src/title/slam.ts +23 -0
- package/packages/patterns/src/toggle-sim/switch.ts +92 -0
- package/packages/patterns/src/trend/columns.ts +108 -0
- package/packages/patterns/src/trend/draw.ts +140 -0
- package/packages/patterns/src/types.ts +67 -0
- package/packages/patterns/src/words/moves.ts +42 -0
- package/packages/patterns/src/words/slam.ts +4 -0
- package/packages/plan/package.json +15 -0
- package/packages/plan/src/asks.ts +256 -0
- package/packages/plan/src/brief.ts +383 -0
- package/packages/plan/src/captions.ts +78 -0
- package/packages/plan/src/detect.ts +93 -0
- package/packages/plan/src/index.ts +27 -0
- package/packages/plan/src/layout.ts +176 -0
- package/packages/plan/src/llm.ts +172 -0
- package/packages/plan/src/look.ts +222 -0
- package/packages/plan/src/plan.ts +179 -0
- package/packages/plan/src/request.ts +55 -0
- package/packages/plan/src/series.ts +76 -0
- package/packages/plan/src/studio.ts +53 -0
- package/packages/plan/src/templates.ts +42 -0
- package/packages/plan/src/themes.ts +48 -0
- package/packages/plan/src/types.ts +157 -0
- package/packages/studio/package.json +12 -0
- package/packages/studio/src/index.ts +1 -0
- package/packages/studio/src/plugin.ts +236 -0
- package/packages/studio/src/ui/main.ts +503 -0
- package/packages/studio/src/ui/studio.css +165 -0
- package/packages/studio/src/ui/timeline.ts +317 -0
- package/packages/themes/kit/THEME-MAKE.md +68 -0
- package/packages/themes/kit/THEME.md +30 -0
- package/packages/themes/kit/briefs/og-intro-15s/brief.json +16 -0
- package/packages/themes/kit/briefs/og-intro-15s/facts.ts +29 -0
- package/packages/themes/kit/briefs/og-intro-15s/logo.svg +12 -0
- package/packages/themes/kit/effects.ts +5 -0
- package/packages/themes/kit/sample/index.html +11 -0
- package/packages/themes/kit/sample/main.ts +9 -0
- package/packages/themes/kit/sample/scenes/scene.ts +15 -0
- package/packages/themes/package.json +19 -0
- package/packages/themes/src/factory.ts +296 -0
- package/packages/themes/src/fx.ts +14 -0
- package/packages/themes/src/index.ts +114 -0
- package/packages/themes/src/slotkit.ts +79 -0
- package/packages/themes/src/slots.ts +46 -0
- package/packages/themes/src/stage3d.ts +197 -0
- package/packages/themes/src/types.ts +58 -0
- package/presets/kawaii-pastel.json +94 -0
- package/presets/neutral-dark.json +82 -0
- package/presets/og.json +98 -0
- package/series/og-shorts.json +14 -0
- package/skills/kasaframe/SKILL.md +33 -0
- package/templates/_shared/project/index.html +11 -0
- package/templates/_shared/project/lib/react.ts +22 -0
- package/templates/_shared/project/lib/stage.ts +292 -0
- package/templates/_shared/project/main.ts +9 -0
- package/templates/_shared/scenes/default.ts +21 -0
- package/templates/_shared/scenes/drop.ts +33 -0
- package/templates/_shared/scenes/end.ts +26 -0
- package/templates/_shared/scenes/hook.ts +18 -0
- package/templates/_shared/scenes/seam.ts +41 -0
- package/templates/_shared/scenes/step.ts +49 -0
- package/templates/ad/preview.png +0 -0
- package/templates/ad/template.json +151 -0
- package/templates/demo/preview.png +0 -0
- package/templates/demo/template.json +141 -0
- package/templates/explainer/preview.png +0 -0
- package/templates/explainer/template.json +193 -0
- package/templates/launch/preview.png +0 -0
- package/templates/launch/template.json +163 -0
- package/templates/mv/preview.png +0 -0
- package/templates/mv/scenes/default.ts +98 -0
- package/templates/mv/template.json +178 -0
- package/templates/promo/preview.png +0 -0
- package/templates/promo/template.json +185 -0
- package/templates/shorts/preview.png +0 -0
- package/templates/shorts/scenes/hook.ts +37 -0
- package/templates/shorts/template.json +196 -0
- package/templates/square/preview.png +0 -0
- package/templates/square/template.json +133 -0
- package/themes/debug-hud/THEME.md +39 -0
- package/themes/debug-hud/effects.ts +389 -0
- package/themes/debug-hud/sample/assets/og/logo.svg +12 -0
- package/themes/debug-hud/sample/beats.json +1 -0
- package/themes/debug-hud/sample/index.html +11 -0
- package/themes/debug-hud/sample/kf.json +59 -0
- package/themes/debug-hud/sample/lib/hudkit.ts +49 -0
- package/themes/debug-hud/sample/lib/og.ts +29 -0
- package/themes/debug-hud/sample/main.ts +9 -0
- package/themes/debug-hud/sample/scenes/end.ts +62 -0
- package/themes/debug-hud/sample/scenes/gather.ts +67 -0
- package/themes/debug-hud/sample/scenes/models.ts +70 -0
- package/themes/debug-hud/sample/scenes/price.ts +79 -0
- package/themes/debug-hud/sample/style.json +91 -0
- package/themes/debug-hud/slots.ts +99 -0
- package/themes/debug-hud/still.png +0 -0
- package/themes/debug-hud/style.json +91 -0
- package/themes/debug-hud/theme.json +68 -0
- package/themes/og-measure/THEME.md +47 -0
- package/themes/og-measure/effects.ts +432 -0
- package/themes/og-measure/sample/assets/brand/og-logo-full.svg +12 -0
- package/themes/og-measure/sample/assets/brand/og-logo.svg +12 -0
- package/themes/og-measure/sample/assets/data/measure.json +353 -0
- package/themes/og-measure/sample/beats.json +1 -0
- package/themes/og-measure/sample/index.html +11 -0
- package/themes/og-measure/sample/kf.json +66 -0
- package/themes/og-measure/sample/lib/data.ts +62 -0
- package/themes/og-measure/sample/main.ts +9 -0
- package/themes/og-measure/sample/measure.ts +78 -0
- package/themes/og-measure/sample/scenes/end.ts +47 -0
- package/themes/og-measure/sample/scenes/gather.ts +51 -0
- package/themes/og-measure/sample/scenes/models.ts +34 -0
- package/themes/og-measure/sample/scenes/one.ts +39 -0
- package/themes/og-measure/sample/scenes/price.ts +56 -0
- package/themes/og-measure/sample/style.json +97 -0
- package/themes/og-measure/slots.ts +88 -0
- package/themes/og-measure/still.png +0 -0
- package/themes/og-measure/style.json +97 -0
- package/themes/og-measure/theme.json +79 -0
- package/themes/riso-zine/THEME.md +43 -0
- package/themes/riso-zine/effects.ts +321 -0
- package/themes/riso-zine/sample/assets/og/logo.svg +12 -0
- package/themes/riso-zine/sample/beats.json +1 -0
- package/themes/riso-zine/sample/index.html +11 -0
- package/themes/riso-zine/sample/kf.json +59 -0
- package/themes/riso-zine/sample/lib/og.ts +29 -0
- package/themes/riso-zine/sample/lib/shot.ts +86 -0
- package/themes/riso-zine/sample/lib/zine.ts +42 -0
- package/themes/riso-zine/sample/main.ts +9 -0
- package/themes/riso-zine/sample/scenes/end.ts +61 -0
- package/themes/riso-zine/sample/scenes/gather.ts +62 -0
- package/themes/riso-zine/sample/scenes/models.ts +52 -0
- package/themes/riso-zine/sample/scenes/price.ts +82 -0
- package/themes/riso-zine/sample/style.json +99 -0
- package/themes/riso-zine/slots.ts +81 -0
- package/themes/riso-zine/still.png +0 -0
- package/themes/riso-zine/style.json +99 -0
- package/themes/riso-zine/theme.json +75 -0
- package/tools/beats/beats.py +372 -0
- package/tools/beats/requirements.txt +3 -0
- package/tools/lyrics/align.py +164 -0
- package/tools/lyrics/requirements.txt +9 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, join, resolve } from "node:path";
|
|
3
|
+
import { addToLibrary, byMood, genMusic, listLibrary, MOOD_PROMPT, MOOD_TAGS, musicDir, musicProviders } from "@kasaframe/gen";
|
|
4
|
+
import { type Command, num, parseFlags } from "../command.ts";
|
|
5
|
+
|
|
6
|
+
const cmd: Command = {
|
|
7
|
+
summary: "๊ณก ๋ชจ์ โ ๋ถ์๊ธฐ ํ๊ทธ๋ก ๋ณด๊ธฐยท๋ฃ๊ธฐ, ์์
์์ฑ(LyriaยทEleven Music)",
|
|
8
|
+
usage: `kf music list [--mood deep]
|
|
9
|
+
kf music add <๊ณก ํ์ผ> --mood deep,ambient --source <์๋ณธ ์ฃผ์> --license "<๋ผ์ด์ ์ค>" [--title ์ด๋ฆ] [--genre ์ฅ๋ฅด] [--bpm 70]
|
|
10
|
+
kf music gen --mood deep --dur 45 [--provider lyria-3.5|elevenlabs-music|lyria-3-clip-preview] [--out ํด๋] [--add]
|
|
11
|
+
|
|
12
|
+
๊ณก ๋ชจ์์ ${"KF_MUSIC_DIR"}(์์ผ๋ฉด ๋ ํฌ music/). ๊ณก๋ง๋ค ๋ถ์๊ธฐยท์๋ณธยท๋ผ์ด์ ์ค๋ ๊ทธ ํด๋ tags.json, ์ฌ๋์ฉ ํ๋ README.md.
|
|
13
|
+
๋ถ์๊ธฐ: ${Object.keys(MOOD_PROMPT).join(" ยท ")} โ ํ(template.json mood)์ด ๊ณ ๋ฅธ๋ค. ์๋ณธยท๋ผ์ด์ ์ค๋ฅผ ๋ชจ๋ฅด๋ ๊ณก์ ๋ฃ์ง ์๋๋ค.
|
|
14
|
+
gen ์ ๋ถ์๊ธฐยท๊ธธ์ด ์นธ(30ยท45ยท60ยท90ยท120โฆ์ด)์ด ๊ฐ์ผ๋ฉด ์บ์์์ ๊ฐ์ ๊ณก์ 0์์ผ๋ก ๊บผ๋ธ๋ค. --add ๋ฉด ๊ณก ๋ชจ์์ ํ๊ทธ์ ํจ๊ป ๋ฃ๋๋ค.`,
|
|
15
|
+
async run(argv) {
|
|
16
|
+
const [sub, ...rest] = argv;
|
|
17
|
+
const { args, opts } = parseFlags(rest);
|
|
18
|
+
const str = (k: string) => (typeof opts[k] === "string" ? (opts[k] as string) : undefined);
|
|
19
|
+
|
|
20
|
+
if (sub === "list" || !sub) {
|
|
21
|
+
const tracks = str("mood") ? byMood(str("mood")!) : listLibrary();
|
|
22
|
+
console.log(`๊ณก ๋ชจ์ ${musicDir()}${str("mood") ? ` โ ๋ถ์๊ธฐ ${str("mood")}` : ""}`);
|
|
23
|
+
for (const t of tracks) console.log(` ${t.file.padEnd(34)} ${(t.mood.join("ยท") || "ํ๊ทธ ์์").padEnd(32)} ${t.license ? t.license.slice(0, 60) : "๋ผ์ด์ ์ค ๋ชจ๋ฆ โ ๊ณ ๋ฅด๋ ๋ฐ์ ๋น ์ง๋ค"}`);
|
|
24
|
+
if (!tracks.length) console.log(" (์์)");
|
|
25
|
+
console.log(`\n์์ฑ: ${musicProviders().map((p) => `${p.id}(${p.ready() ? "ํค ์์" : "ํค ์์"})`).join(" ยท ")}`);
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (sub === "add") {
|
|
30
|
+
const src = args[0] ? resolve(args[0]) : undefined;
|
|
31
|
+
if (!src || !existsSync(src) || !str("mood") || !str("source") || !str("license")) {
|
|
32
|
+
console.error("kf music add: <๊ณก ํ์ผ> --mood --source --license ๊ฐ ํ์ํ๋ค");
|
|
33
|
+
return 2;
|
|
34
|
+
}
|
|
35
|
+
const t = addToLibrary(src, {
|
|
36
|
+
title: str("title") ?? basename(src).replace(/\.[^.]+$/, ""),
|
|
37
|
+
mood: str("mood")!.split(",").map((m) => m.trim()).filter(Boolean),
|
|
38
|
+
...(str("genre") ? { genre: str("genre") } : {}),
|
|
39
|
+
...(str("bpm") ? { bpm: num(opts.bpm, 0) } : {}),
|
|
40
|
+
source: str("source")!,
|
|
41
|
+
license: str("license")!,
|
|
42
|
+
});
|
|
43
|
+
readmeRow(t.file, t.title, t.mood, t.source, t.license);
|
|
44
|
+
console.log(t.path);
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (sub === "gen") {
|
|
49
|
+
const mood = str("mood") ?? "deep";
|
|
50
|
+
const dur = num(opts.dur, 45);
|
|
51
|
+
const outDir = resolve(str("out") ?? ".");
|
|
52
|
+
const m = await genMusic(`${mood}-${dur}`, { mood, duration: dur }, { outDir, log: (l) => console.error(l) }, str("provider"));
|
|
53
|
+
console.error(`${m.provider} ยท ${m.seconds}์ด ยท ${m.cached ? "์บ์(0)" : `$${m.costUsd.toFixed(3)}`} ยท ${m.license}`);
|
|
54
|
+
if (opts.add) {
|
|
55
|
+
const file = `${mood}-${m.provider}-${Math.round(m.seconds)}s.${m.path.split(".").pop()}`;
|
|
56
|
+
const t = addToLibrary(m.path, { title: `${mood} ${Math.round(m.seconds)}์ด (${m.provider} ์์ฑ)`, mood: [...new Set([mood, ...(MOOD_TAGS[mood] ?? []).slice(0, 3)])], source: `${m.provider} ์์ฑ(MOOD_PROMPT.${mood}, ${new Date().toISOString().slice(0, 10)})`, license: m.license }, musicDir(), file);
|
|
57
|
+
readmeRow(t.file, t.title, t.mood, t.source, t.license);
|
|
58
|
+
console.log(t.path);
|
|
59
|
+
} else console.log(m.path);
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.error(this.usage);
|
|
64
|
+
return 2;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
export default cmd;
|
|
68
|
+
|
|
69
|
+
// README ํ์ ํ ์ค โ ๊ฐ์ ํ์ผ ์ค์ด ์์ผ๋ฉด ๋์ง ์๋๋ค. ํ ๋(๋ง์ง๋ง ํ ์ค ๋ค์)์ ๋ผ์ด๋ค
|
|
70
|
+
function readmeRow(file: string, title: string, mood: string[], source: string, license: string): void {
|
|
71
|
+
const readme = join(musicDir(), "README.md");
|
|
72
|
+
if (!existsSync(readme)) return;
|
|
73
|
+
const lines = readFileSync(readme, "utf8").split("\n");
|
|
74
|
+
if (lines.some((l) => l.startsWith(`| \`${file}\` |`))) return;
|
|
75
|
+
const last = lines.map((l, i) => (l.startsWith("| `") ? i : -1)).filter((i) => i >= 0).at(-1) ?? lines.length - 1;
|
|
76
|
+
lines.splice(last + 1, 0, `| \`${file}\` | ${title} | ${mood.join("ยท")} | ${source} ยท ${license} |`);
|
|
77
|
+
writeFileSync(readme, lines.join("\n"));
|
|
78
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { cutVoice, DEFAULT_STYLE, genMusic, howToGet, layVoices, parseVoice, pickTts, speak, ttsProviders, type Speech } from "@kasaframe/gen";
|
|
6
|
+
import { loadTemplate, parseDuration, type Template } from "@kasaframe/plan";
|
|
7
|
+
import { type Command, parseFlags } from "../command.ts";
|
|
8
|
+
import { jobLimit, pool } from "../pool.ts";
|
|
9
|
+
|
|
10
|
+
const kfBin = fileURLToPath(new URL("../kf.ts", import.meta.url));
|
|
11
|
+
// ํ ์ฅ๋ฉด์ ๋ฌธ์ฅ์ด ๋ ๋์ผ๋ฉด ๊ทธ ์ฌ์ด ์จ
|
|
12
|
+
const GAP = 0.35;
|
|
13
|
+
const MIN_SCENE = 0.75;
|
|
14
|
+
// ๋ฐ์ด ์ด๋ณด๋ค ๋ฉ๋ฉด ๋ถ์ด์ง ์๋๋ค(๋๋ฆฐ ๊ณก์ ๋ง๋ ์ฌ์ด์์ ์ฅ๋ฉด์ด ๋์ด์ง์ง ์๊ฒ)
|
|
15
|
+
const SNAP_REACH = 0.8;
|
|
16
|
+
|
|
17
|
+
const NARRATION_DEFAULT: NonNullable<Template["narration"]> = { on: true, lang: "ko", style: DEFAULT_STYLE, voice: "elevenlabs:George", voices: {}, lead: 0.25, tail: 0.5 };
|
|
18
|
+
|
|
19
|
+
interface Line {
|
|
20
|
+
scene: string;
|
|
21
|
+
text: string;
|
|
22
|
+
from?: number;
|
|
23
|
+
to?: number;
|
|
24
|
+
words?: { text: string; from: number; to: number }[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const cmd: Command = {
|
|
28
|
+
summary: "๋ด๋ ์ด์
๋จผ์ โ ์ฅ๋ฉด๋ณ ๋ฌธ์ฅ์ ์ฝํ ์ฅ๋ฉด ๊ธธ์ด๋ฅผ ๋ชฉ์๋ฆฌ์ ๋ง์ถ๊ณ (๊ณก์ด ์์ผ๋ฉด ๋ฐ์), ์๋ง ์๊ฐ์ ๋ชฉ์๋ฆฌ ์๊ฐ์ผ๋ก",
|
|
29
|
+
usage: `kf narrate <ํด๋> [--voice <์์ง>:<๋ชฉ์๋ฆฌ>|none] [--music <๊ณก ํ์ผ>|gen:<๊ณต๊ธ์>|none]
|
|
30
|
+
kf narrate --voices ํค๊ฐ ์๋ ๋ชฉ์๋ฆฌ ์์งยท๋ชฉ์๋ฆฌ ๋ชฉ๋ก
|
|
31
|
+
kf narrate --say "<๊ธ>" --voice gemini:Charon [--out x.wav] ํ ๋ฌธ์ฅ๋ง ์ฝํ ๋ณธ๋ค
|
|
32
|
+
|
|
33
|
+
captions.json([{"scene": "<์ฅ๋ฉด id>", "text": "๊ทธ ์ฅ๋ฉด์์ ๋ค๋ฆด ๋ง"}])์ ์ฅ๋ฉด๋ง๋ค ์ฝํ๋ค โ ์ฅ๋ฉด ๊ธธ์ด = ์ ์จ + ๋ง + ๋ค ์จ(ํ narration.leadยทtail).
|
|
34
|
+
๊ณก์ ์ฃผ๋ฉด(๋๋ plan.json ๋ต) ๊ณก์ ๊น๊ณ kf beats ๋ก ์ฌ์ ์ฅ๋ฉด ๊ฒฝ๊ณ๋ฅผ ๋ค์ ๋ฐ์ ๋ถ์ธ๋ค. gen:<๊ณต๊ธ์> ๋ฉด ํ ๋ถ์๊ธฐ(mood)๋ก ์์ ๊ธธ์ด์ ๋ง์ถ ๊ณก์ ๋ง๋ ๋ค.
|
|
35
|
+
์์ฒญ ๊ธ์ ๊ธธ์ด(ใ45์ดใ)๊ฐ ์์ผ๋ฉด ๋ชฉ์๋ฆฌ ๋น ๋ฅด๊ธฐ๋ฅผ 0.9โ1.1 ์์์๋ง ๋ง์ถ๊ณ ๋จ๋ ์๊ฐ์ ์ฅ๋ฉด ๋ค ์จ์ผ๋ก ํธ๋ค. ๊ทธ ๋ฐ์ ๋น ๋ฅด๊ธฐ๋ฅผ ๊ฑด๋๋ฆฌ์ง ์๋๋ค.
|
|
36
|
+
๊ฒฐ๊ณผ: assets/voice/<์ฅ๋ฉด>-<n>.wav ยท assets/voice/narration.wav(์์ ๊ธธ์ด ํ ์ค) ยท captions.json ์ ๋ฑ๋ง ์๊ฐ ยท kf.json ์ ์ฅ๋ฉด ์๊ฐยทnarrationยทaudio(์๊ธฐ ๊ฐ).
|
|
37
|
+
kf render ๊ฐ narrationยท๊ณก์ ์๋๋ค โ ๊ณก์ ๋งํ ๋ ๋ํน, ๋ ํ์ด๋, ํ์ LUFS ๋ก ์ ๊ทํ.
|
|
38
|
+
--voiceยท--music ์ ์ ์ฃผ๋ฉด plan.json ์ voiceยทmusic(๋๋ answers)์, ๊ทธ๊ฒ๋ ์์ผ๋ฉด ํ์ด ์์ง๋ง๋ค ๋ฐ์ ๋ชฉ์๋ฆฌ๋ฅผ ์ด๋ค.`,
|
|
39
|
+
async run(argv) {
|
|
40
|
+
const { args, opts } = parseFlags(argv);
|
|
41
|
+
const str = (k: string) => (typeof opts[k] === "string" ? (opts[k] as string) : undefined);
|
|
42
|
+
if (opts.voices) return voices();
|
|
43
|
+
if (str("say")) return say(str("say")!, str("voice") ?? "gemini", str("out"));
|
|
44
|
+
const dir = args[0] ? resolve(args[0]) : undefined;
|
|
45
|
+
if (!dir || !existsSync(join(dir, "kf.json"))) {
|
|
46
|
+
console.error("kf narrate: kf.json ์ด ์๋ ํ๋ก์ ํธ ํด๋๊ฐ ํ์ํ๋ค โ kf narrate --help");
|
|
47
|
+
return 2;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return await narrate(dir, { voice: str("voice"), music: str("music") });
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error(`kf narrate: ${e instanceof Error ? e.message : e}`);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
export default cmd;
|
|
58
|
+
|
|
59
|
+
function voices(): number {
|
|
60
|
+
for (const p of ttsProviders()) {
|
|
61
|
+
const state = p.ready() ? "์ธ ์ ์์" : "ํค ์์";
|
|
62
|
+
console.log(`${p.id.padEnd(11)} ${p.label} ยท ${p.model} ยท ${state}${p.verified ? "" : " ยท ์คํธ์ถ ๋ฏธ๊ฒ์ฆ"}${p.timestamps ? " ยท ๋ฑ๋ง ์๊ฐ ์ ํ" : ""}`);
|
|
63
|
+
for (const v of p.voices) console.log(` --voice ${`${p.id}:${v.id}`.padEnd(26)} ${v.label}`);
|
|
64
|
+
}
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function say(text: string, voice: string, out?: string): Promise<number> {
|
|
69
|
+
const v = parseVoice(voice);
|
|
70
|
+
if (!v) return 2;
|
|
71
|
+
const dst = resolve(out ?? "say.wav");
|
|
72
|
+
const s = await speak("say", { text, voice: v.voice, lang: "ko", style: DEFAULT_STYLE }, { outDir: join(dst, ".."), log: (l) => console.error(l) }, v.engine);
|
|
73
|
+
await cutVoice(s.path, dst, { from: s.voiced.from, to: s.voiced.to });
|
|
74
|
+
console.error(`${s.engine} ${s.voice} ยท ${(s.voiced.to - s.voiced.from).toFixed(2)}์ด ยท ${s.cached ? "์บ์(0)" : `$${s.costUsd.toFixed(4)}`}`);
|
|
75
|
+
console.log(dst);
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const readJson = (p: string) => JSON.parse(readFileSync(p, "utf8"));
|
|
80
|
+
const writeJson = (p: string, v: unknown) => writeFileSync(p, `${JSON.stringify(v, null, 2)}\n`);
|
|
81
|
+
|
|
82
|
+
// ๋ต ๊ณ ๋ฅด๋ ์์: ๋ช
๋ น ์ค > plan.json ์ voiceยทanswers > ๋ฌป๊ธฐ์ ๊ถ์ฅ๊ณผ ๊ฐ์ ๊ฒ(๋ก์ปฌ Qwen3 ์ฃผ์๊ฐ ์์ผ๋ฉด ๊ทธ ๋ชฉ์๋ฆฌ, ์์ผ๋ฉด ํ์ด ๋ชป ๋ฐ์ ๋ชฉ์๋ฆฌ).
|
|
83
|
+
// ๋ค๋ฅธ ์์งยทใ์๋ง๋งใ์ผ๋ก ์กฐ์ฉํ ๋์ด๊ฐ์ง ์๋๋ค
|
|
84
|
+
function pickVoice(given: string | undefined, plan: Record<string, any>, n: Template["narration"]): string {
|
|
85
|
+
const v = given ?? plan.voice ?? plan.answers?.voice;
|
|
86
|
+
if (v) return v;
|
|
87
|
+
if (!n?.on) return "none";
|
|
88
|
+
return pickTts("local").ready() ? `local:${n.voices.local ?? pickTts("local").defaultVoice}` : n.voice;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function narrate(dir: string, o: { voice?: string; music?: string } = {}): Promise<number> {
|
|
92
|
+
const kfPath = join(dir, "kf.json");
|
|
93
|
+
const planPath = join(dir, "plan.json");
|
|
94
|
+
const kf = readJson(kfPath);
|
|
95
|
+
const plan = existsSync(planPath) ? readJson(planPath) : {};
|
|
96
|
+
const tpl = loadTemplate(kf.template);
|
|
97
|
+
const n = { ...NARRATION_DEFAULT, ...tpl.narration };
|
|
98
|
+
const fps: number = kf.fps;
|
|
99
|
+
const f = (x: number) => Math.round(x * fps) / fps;
|
|
100
|
+
const log = (l: string) => console.error(l);
|
|
101
|
+
|
|
102
|
+
// 1. ๋ชฉ์๋ฆฌ โ ์ฅ๋ฉด๋ง๋ค ๋ฌธ์ฅ์ ์ฝํ๋ค
|
|
103
|
+
const voice = pickVoice(o.voice, plan, tpl.narration);
|
|
104
|
+
const v = parseVoice(voice);
|
|
105
|
+
if (v && !pickTts(v.engine).ready()) {
|
|
106
|
+
console.error(`kf narrate: ๋ชฉ์๋ฆฌ ${voice} ์ ์์ง ${v.engine} ์ ์ด ๊ธฐ๊ณ์์ ๋ชป ์ด๋ค โ ๊ณ ๋ฅด๊ณ ๋ค์:\n ํค ๋ฃ๊ธฐ โ ${howToGet(pickTts(v.engine).keyName)}\n ๋ค๋ฅธ ์์ง: ${ttsProviders().filter((p) => p.ready()).map((p) => `--voice ${p.id}:${n.voices[p.id] ?? p.defaultVoice}`).join(" ยท ") || "(์ธ ์ ์๋ ์์ง ์์)"}\n ๋ชฉ์๋ฆฌ ๋นผ๊ธฐ: --voice none(์๋ง๋ง)`);
|
|
107
|
+
return 3;
|
|
108
|
+
}
|
|
109
|
+
const capPath = join(dir, "captions.json");
|
|
110
|
+
const lines: Line[] = existsSync(capPath) ? readJson(capPath) : [];
|
|
111
|
+
if (v && !lines.length) throw new Error("captions.json ์ ์ฅ๋ฉด๋ณ ๋ฌธ์ฅ์ด ์๋ค โ [{\"scene\": \"hook\", \"text\": \"...\"}] ๋ก ๋จผ์ ์ด๋ค");
|
|
112
|
+
const spoken = new Map<string, { line: Line; s: Speech }[]>();
|
|
113
|
+
let costUsd = 0;
|
|
114
|
+
if (v) {
|
|
115
|
+
mkdirSync(join(dir, "assets", "voice", "raw"), { recursive: true });
|
|
116
|
+
const count: Record<string, number> = {};
|
|
117
|
+
const todo = lines.filter((line) => line.text?.trim() && kf.scenes.some((s: { id: string }) => s.id === line.scene)).map((line) => ({ line, k: (count[line.scene] = (count[line.scene] ?? 0) + 1) }));
|
|
118
|
+
// ์ค๋ง๋ค ๋ฐ๋ก ์ฝํ๋ค โ ๋์์ ๋ถ๋ฅด๊ณ ์ฅ๋ฉด ์ฐจ๋ก๋๋ก ๋ชจ์๋ค
|
|
119
|
+
const said = await pool(todo, jobLimit(4), ({ line, k }) => speak(`${line.scene}-${k}`, { text: line.text.trim(), voice: v.voice, lang: n.lang, style: n.style }, { outDir: join(dir, "assets", "voice", "raw"), log }, v.engine));
|
|
120
|
+
for (const [i, { line }] of todo.entries()) {
|
|
121
|
+
const s = said[i];
|
|
122
|
+
if (!s.cached) costUsd += s.costUsd;
|
|
123
|
+
spoken.set(line.scene, [...(spoken.get(line.scene) ?? []), { line, s }]);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const talk = (id: string) => (spoken.get(id) ?? []).reduce((a, x, i) => a + (x.s.voiced.to - x.s.voiced.from) + (i ? GAP : 0), 0);
|
|
127
|
+
const leadOf = (s: { role: string }) => (s.role === "hook" ? Math.min(0.1, n.lead) : n.lead);
|
|
128
|
+
|
|
129
|
+
// 2. ๋น ๋ฅด๊ธฐ โ ์์ฒญ์ด ๊ธธ์ด๋ฅผ ๋งํ์ผ๋ฉด ๊ทธ ๊ธธ์ด์, ์๋๋ฉด ํ ์ํ์ ๋์ ๋๋ง. ๋ 0.9โ1.1 ์
|
|
130
|
+
const voiceSec = kf.scenes.reduce((a: number, s: { id: string }) => a + talk(s.id), 0);
|
|
131
|
+
const restSec = kf.scenes.reduce((a: number, s: { id: string; role: string; dur: number }) => a + (spoken.has(s.id) ? leadOf(s) + n.tail : s.dur), 0);
|
|
132
|
+
const target = parseDuration(plan.request ?? "");
|
|
133
|
+
const clamp = (x: number) => Math.min(1.1, Math.max(0.9, x));
|
|
134
|
+
let tempo = 1;
|
|
135
|
+
if (voiceSec > 0) {
|
|
136
|
+
if (target !== undefined && target > restSec) tempo = clamp(voiceSec / (target - restSec));
|
|
137
|
+
else if (voiceSec + restSec > tpl.duration.max) tempo = clamp(voiceSec / Math.max(1, tpl.duration.max - restSec));
|
|
138
|
+
if (Math.abs(tempo - 1) < 0.02) tempo = 1;
|
|
139
|
+
}
|
|
140
|
+
const need = kf.scenes.map((s: { id: string; role: string; dur: number }) => (spoken.has(s.id) ? Math.max(MIN_SCENE, leadOf(s) + talk(s.id) / tempo + n.tail) : s.dur));
|
|
141
|
+
// ๋จ๋ ์๊ฐ์ ๋งํ ์ฅ๋ฉด ๋ค ์จ์ผ๋ก ๊ณ ๋ฅด๊ฒ(์์ฒญ ๊ธธ์ด๊ฐ ์์ ๋๋ง)
|
|
142
|
+
const short = target !== undefined ? target - need.reduce((a: number, b: number) => a + b, 0) : 0;
|
|
143
|
+
const voiced = kf.scenes.filter((s: { id: string }) => spoken.has(s.id)).length;
|
|
144
|
+
if (short > 0 && voiced) kf.scenes.forEach((s: { id: string }, i: number) => spoken.has(s.id) && (need[i] += short / voiced));
|
|
145
|
+
let total = need.reduce((a: number, b: number) => a + b, 0);
|
|
146
|
+
|
|
147
|
+
// 3. ๊ณก โ ๊ณก ๋ชจ์ ํ์ผยท์์ฑยท์์. ๋ฐ๋์์ผ๋ฉด kf beats ๋ก ๋ค์ ์ฐ๋ค
|
|
148
|
+
const music = o.music ?? plan.answers?.music ?? undefined;
|
|
149
|
+
if (music === "none") {
|
|
150
|
+
kf.music = null;
|
|
151
|
+
kf.beats = null;
|
|
152
|
+
} else if (music?.startsWith("gen:")) {
|
|
153
|
+
// ์๋ฆฌ์ฆ ๋ฌถ์์ ๋ถ์๊ธฐ๊ฐ ํ ๋ถ์๊ธฐ๋ฅผ ๋ฎ๋๋ค
|
|
154
|
+
const m = await genMusic("score", { mood: plan.mood ?? tpl.mood ?? "deep", duration: total }, { outDir: join(dir, "assets", "music"), log }, music.slice(4));
|
|
155
|
+
if (!m.cached) costUsd += m.costUsd;
|
|
156
|
+
kf.music = `assets/music/${basename(m.path)}`;
|
|
157
|
+
log(`๊ณก ${m.provider} ${m.seconds}์ด ${m.cached ? "์บ์(0)" : `$${m.costUsd.toFixed(3)}`} โ ${m.license}`);
|
|
158
|
+
} else if (music) {
|
|
159
|
+
const src = resolve(music);
|
|
160
|
+
if (!existsSync(src)) throw new Error(`๊ณก์ด ์๋ค: ${src}`);
|
|
161
|
+
mkdirSync(join(dir, "assets", "music"), { recursive: true });
|
|
162
|
+
const rel = `assets/music/${basename(src)}`;
|
|
163
|
+
if (resolve(dir, rel) !== src) copyFileSync(src, join(dir, rel));
|
|
164
|
+
kf.music = rel;
|
|
165
|
+
}
|
|
166
|
+
if (kf.music && (music !== undefined || !kf.beats || !existsSync(join(dir, kf.beats)))) {
|
|
167
|
+
const r = spawnSync(process.execPath, [kfBin, "beats", join(dir, kf.music), "--out", join(dir, "beats.json")], { stdio: ["ignore", "ignore", "inherit"] });
|
|
168
|
+
if (r.status !== 0) throw new Error("๊ณก ๋ถ์(kf beats)์ด ์คํจํ๋ค");
|
|
169
|
+
kf.beats = "beats.json";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 4. ๊ฒฝ๊ณ โ ๋ง์ด ๋๋ ๋ค ๋ค์ ๋ฐ์(๊ณก์ด ์์ผ๋ฉด), ๊ทธ๋ค์ ํ๋ ์์
|
|
173
|
+
const beats: number[] = kf.beats && existsSync(join(dir, kf.beats)) ? (readJson(join(dir, kf.beats)).beats ?? []) : [];
|
|
174
|
+
let t = 0;
|
|
175
|
+
for (const [i, s] of kf.scenes.entries()) {
|
|
176
|
+
s.from = +f(t).toFixed(4);
|
|
177
|
+
let end = t + need[i];
|
|
178
|
+
if (i < kf.scenes.length - 1) {
|
|
179
|
+
const next = beats.find((b) => b >= end - 0.02);
|
|
180
|
+
if (next !== undefined && next - end <= SNAP_REACH) end = next;
|
|
181
|
+
}
|
|
182
|
+
end = f(end);
|
|
183
|
+
s.dur = +(end - s.from).toFixed(4);
|
|
184
|
+
t = end;
|
|
185
|
+
}
|
|
186
|
+
total = f(t);
|
|
187
|
+
kf.duration = +total.toFixed(4);
|
|
188
|
+
|
|
189
|
+
// 5. ๋ชฉ์๋ฆฌ ํ ์ค โ ์กฐ๊ฐ์ ์ฅ๋ฉด ์๊ฐ์ ๊น๊ณ , ์๋ง์ ๋ฑ๋ง ์๊ฐ์ ์์ ๊ธฐ์ค์ผ๋ก ์ ๋๋ค
|
|
190
|
+
const clips: { file: string; at: number }[] = [];
|
|
191
|
+
const outLines: Line[] = [];
|
|
192
|
+
for (const s of kf.scenes) {
|
|
193
|
+
let at = s.from + leadOf(s);
|
|
194
|
+
for (const [k, { line, s: sp }] of (spoken.get(s.id) ?? []).entries()) {
|
|
195
|
+
const file = join(dir, "assets", "voice", `${s.id}-${k + 1}.wav`);
|
|
196
|
+
await cutVoice(sp.path, file, { from: sp.voiced.from, to: sp.voiced.to, tempo });
|
|
197
|
+
clips.push({ file, at });
|
|
198
|
+
const words = sp.words.map((w) => ({ text: w.text, from: +(at + (w.from - sp.voiced.from) / tempo).toFixed(3), to: +(at + (w.to - sp.voiced.from) / tempo).toFixed(3) }));
|
|
199
|
+
const len = (sp.voiced.to - sp.voiced.from) / tempo;
|
|
200
|
+
outLines.push({ scene: s.id, text: line.text, from: +at.toFixed(3), to: +(at + len).toFixed(3), words });
|
|
201
|
+
at += len + GAP;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (v) {
|
|
205
|
+
await layVoices(clips, total, join(dir, "assets", "voice", "narration.wav"));
|
|
206
|
+
// ๋ชฉ์๋ฆฌ๋ฅผ ์ ์ฝํ ์ค(์ฅ๋ฉด id ๊ฐ ์๋ ์ค)๋ ๋ฒ๋ฆฌ์ง ์๋๋ค
|
|
207
|
+
for (const l of lines) if (!outLines.some((x) => x.scene === l.scene && x.text === l.text)) outLines.push({ scene: l.scene, text: l.text });
|
|
208
|
+
writeJson(capPath, outLines);
|
|
209
|
+
writeJson(join(dir, "assets", "voice", "narration.json"), {
|
|
210
|
+
voice,
|
|
211
|
+
engine: v.engine,
|
|
212
|
+
model: pickTts(v.engine).model,
|
|
213
|
+
license: pickTts(v.engine).license,
|
|
214
|
+
tempo,
|
|
215
|
+
clips: clips.map((c, i) => ({ file: basename(c.file), at: +c.at.toFixed(3), text: outLines[i]?.text })),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
kf.narration = v ? "assets/voice/narration.wav" : null;
|
|
219
|
+
kf.voice = voice;
|
|
220
|
+
if (tpl.audio) kf.audio = tpl.audio;
|
|
221
|
+
writeJson(kfPath, kf);
|
|
222
|
+
if (existsSync(planPath)) {
|
|
223
|
+
plan.voice = voice;
|
|
224
|
+
plan.music = kf.music;
|
|
225
|
+
plan.duration = kf.duration;
|
|
226
|
+
for (const s of plan.scenes ?? []) {
|
|
227
|
+
const k = kf.scenes.find((x: { id: string }) => x.id === s.id);
|
|
228
|
+
if (k) Object.assign(s, { from: k.from, dur: k.dur });
|
|
229
|
+
}
|
|
230
|
+
writeJson(planPath, plan);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log(`${dir} โ ๋ชฉ์๋ฆฌ ${voice}${tempo !== 1 ? ` ยท ๋น ๋ฅด๊ธฐ ${tempo.toFixed(3)}` : ""} ยท ๊ณก ${kf.music ?? "์์"}${beats.length ? "(๋ฐ์ ๋ถ์)" : ""} ยท ${kf.duration}์ด ยท ์ด๋ฒ์ ์ด ๋ $${costUsd.toFixed(4)}`);
|
|
234
|
+
for (const s of kf.scenes) console.log(` ${s.from.toFixed(2).padStart(6)}โ${(s.from + s.dur).toFixed(2).padEnd(6)} ${s.id.padEnd(9)} ${spoken.has(s.id) ? `๋ง ${(talk(s.id) / tempo).toFixed(2)}์ด` : "๋ง ์์"}`);
|
|
235
|
+
if (target !== undefined && Math.abs(kf.duration - target) > 2) console.log(` ยท ์์ฒญ ${target}์ด์ ${(kf.duration - target).toFixed(1)}์ด ์ฐจ์ด โ ๋น ๋ฅด๊ธฐ๋ 0.9โ1.1 ๊น์ง๋ง ๋ฐ๊พผ๋ค. ๋๋ณธ์ ${kf.duration > target ? "์ค์ด๋ฉด" : "๋๋ฆฌ๋ฉด"} ๋ง๋๋ค`);
|
|
236
|
+
else if (target === undefined && kf.duration > tpl.duration.max) console.log(` ยท ํ ์ํ ${tpl.duration.max}์ด๋ฅผ ๋๋๋ค โ ๋๋ณธ์ ์ค์ธ๋ค`);
|
|
237
|
+
return 0;
|
|
238
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, join, relative, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { bundleFor, dressSeed, NO_DRESS, pickDress } from "@kasaframe/motion/menu";
|
|
6
|
+
import { themeStyle } from "@kasaframe/themes";
|
|
7
|
+
import { type Aspect, type BeatsLike, type Plan, type Purpose, listTemplates, loadTemplate, makePlan, parseTitle, PURPOSES, sceneSkeleton, templatesDir } from "@kasaframe/plan";
|
|
8
|
+
import { type Command, num, parseFlags } from "../command.ts";
|
|
9
|
+
|
|
10
|
+
const repo = fileURLToPath(new URL("../../../../", import.meta.url));
|
|
11
|
+
const kfBin = fileURLToPath(new URL("../kf.ts", import.meta.url));
|
|
12
|
+
|
|
13
|
+
const cmd: Command = {
|
|
14
|
+
summary: "์ฉ๋ ํ์์ ํ๋ก์ ํธ ๋ผ๋ โ ๊ณก ๋ถ์ยท๋ฐ์ ๋ง์ถ ์ฅ๋ฉด ์๊ฐํยท์ฅ๋ฉด ํ์ผ",
|
|
15
|
+
usage: `kf new <ํด๋> --template <์ฉ๋>(๋๋ --purpose) [--music ๊ณก.mp3] [--style ํ๋ฆฌ์
] [--duration ์ด] [--aspect 9:16] [--title ์ด๋ฆ] [--line ๋ ์นด๋ ํ ์ค]
|
|
16
|
+
kf new <ํด๋> --request "์์ฒญ ๊ธ" [--music ๊ณก.mp3] ์ฉ๋๋ฅผ ์์ฒญ ๊ธ๋ก ๊ณ ๋ฅธ๋ค
|
|
17
|
+
kf new <ํด๋> --plan plan.json kf make ๊ฐ ์ธ์ด ๊ณํ ๊ทธ๋๋ก
|
|
18
|
+
|
|
19
|
+
์ฉ๋: ${PURPOSES.join(" ยท ")}
|
|
20
|
+
--theme ํ
๋ง id(themes/<id>, approvedยทreview) ยท auto(๋ด์ฉ์ ๋ง๋ approved ํ๋ณด ์
์ค ์ฒซ์งธ) ยท none(ํ๋ฆฌ์
๋ง) โ ๊ณ ๋ฅด๋ฉด ๊ทธ ํ
๋ง style ์ ์ด๋ค
|
|
21
|
+
--series ์๋ฆฌ์ฆ ๋ฌถ์ id ยท none. ์ ์ฃผ๋ฉด ์์ฒญ ๊ธ์ด ์ง์ ์๋ฆฌ์ฆ
|
|
22
|
+
--force ๋น์ด ์์ง ์์ ํด๋์๋ ์ด๋ค(์๋ ์ฅ๋ฉด ํ์ผ์ ๊ทธ๋๋ก ๋๋ค)
|
|
23
|
+
--preview ๋ค ๋ง๋ค๊ณ kf preview ๋ฅผ ๋์ด๋ค
|
|
24
|
+
|
|
25
|
+
๋ง๋ ๋ค: scenes/<id>.ts ๋ฅผ kf.json ์ idea ๋๋ก ์ฑ์ด๋ค โ kf preview <ํด๋> โ kf render <ํด๋> โ kf check <ํด๋>`,
|
|
26
|
+
async run(argv) {
|
|
27
|
+
const { args, opts } = parseFlags(argv);
|
|
28
|
+
const dir = args[0] ? resolve(args[0]) : undefined;
|
|
29
|
+
if (!dir) {
|
|
30
|
+
console.error("kf new: ํด๋๊ฐ ํ์ํ๋ค. kf new --help");
|
|
31
|
+
return 2;
|
|
32
|
+
}
|
|
33
|
+
if (existsSync(dir) && readdirSync(dir).length > 0 && !opts.force) {
|
|
34
|
+
console.error(`kf new: ${dir} ๊ฐ ๋น์ด ์์ง ์๋ค. ๋ฎ์ด์ฐ๋ ค๋ฉด --force`);
|
|
35
|
+
return 2;
|
|
36
|
+
}
|
|
37
|
+
const str = (k: string) => (typeof opts[k] === "string" ? (opts[k] as string) : undefined);
|
|
38
|
+
// --purpose ๋ kf make ์ ๊ฐ์ ์ด๋ฆ โ ๋ ๋ค ๋ฐ๋๋ค
|
|
39
|
+
const template = str("template") ?? str("purpose");
|
|
40
|
+
if (template && !PURPOSES.includes(template as Purpose)) {
|
|
41
|
+
console.error(`kf new: ๋ชจ๋ฅด๋ ์ฉ๋ "${template}". ์๋ ์ฉ๋: ${listTemplates().map((t) => `${t.id}(${t.label})`).join(" ")}`);
|
|
42
|
+
return 2;
|
|
43
|
+
}
|
|
44
|
+
const planFile = str("plan");
|
|
45
|
+
const request = str("request");
|
|
46
|
+
if (!template && !planFile && !request) {
|
|
47
|
+
console.error(`kf new: --template ยท --request ยท --plan ์ค ํ๋๊ฐ ํ์ํ๋ค. ์ฉ๋: ${PURPOSES.join(" ยท ")}`);
|
|
48
|
+
return 2;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
mkdirSync(join(dir, "scenes"), { recursive: true });
|
|
52
|
+
mkdirSync(join(dir, "assets", "gen"), { recursive: true });
|
|
53
|
+
mkdirSync(join(dir, "out"), { recursive: true });
|
|
54
|
+
|
|
55
|
+
const given: Plan | null = planFile ? JSON.parse(readFileSync(resolve(planFile), "utf8")) : null;
|
|
56
|
+
const musicSrc = str("music") ?? (given?.music && planFile ? resolve(planFile, "..", given.music) : undefined);
|
|
57
|
+
let music: string | null = null;
|
|
58
|
+
let beats: BeatsLike | null = null;
|
|
59
|
+
if (musicSrc) {
|
|
60
|
+
if (!existsSync(musicSrc)) {
|
|
61
|
+
console.error(`kf new: ๊ณก์ด ์๋ค: ${musicSrc}`);
|
|
62
|
+
return 2;
|
|
63
|
+
}
|
|
64
|
+
music = `assets/music/${basename(musicSrc)}`;
|
|
65
|
+
mkdirSync(join(dir, "assets", "music"), { recursive: true });
|
|
66
|
+
if (resolve(musicSrc) !== join(dir, music)) copyFileSync(musicSrc, join(dir, music));
|
|
67
|
+
const r = spawnSync(process.execPath, [kfBin, "beats", join(dir, music), "--out", join(dir, "beats.json")], { stdio: ["ignore", "inherit", "inherit"] });
|
|
68
|
+
if (r.status !== 0) {
|
|
69
|
+
console.error("kf new: ๊ณก ๋ถ์(kf beats)์ด ์คํจํ๋ค โ ์ ๋ฉ์์ง๋ฅผ ๋ณด๊ณ ๊ณ ์น ๋ค ๋ค์");
|
|
70
|
+
return r.status ?? 1;
|
|
71
|
+
}
|
|
72
|
+
beats = JSON.parse(readFileSync(join(dir, "beats.json"), "utf8"));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const plan: Plan = given
|
|
76
|
+
? { ...given, music }
|
|
77
|
+
: makePlan({
|
|
78
|
+
request: request ?? "",
|
|
79
|
+
purpose: template as Purpose | undefined,
|
|
80
|
+
music,
|
|
81
|
+
beats,
|
|
82
|
+
duration: opts.duration !== undefined ? num(opts.duration, 0) : undefined,
|
|
83
|
+
aspect: str("aspect") as Aspect | undefined,
|
|
84
|
+
style: str("style"),
|
|
85
|
+
title: str("title") ?? parseTitle(request ?? "") ?? basename(dir),
|
|
86
|
+
theme: str("theme"),
|
|
87
|
+
series: str("series"),
|
|
88
|
+
});
|
|
89
|
+
const purposeQ = plan.questions?.find((q) => q.id === "purpose");
|
|
90
|
+
if (purposeQ && !template) {
|
|
91
|
+
console.error(`kf new: ์ฉ๋๋ฅผ ๋ชป ์ ํ๋ค(ํ์ ${plan.confidence}). --template ์ผ๋ก ๊ณจ๋ผ ๋ค์:\n ${purposeQ.choices.join("\n ")}`);
|
|
92
|
+
return 3;
|
|
93
|
+
}
|
|
94
|
+
if (!music && plan.questions?.some((q) => q.id === "music")) console.error(`kf new: ์ฃผ์ โ ${plan.questions.find((q) => q.id === "music")!.ask} (--music ์ผ๋ก ์ฃผ๋ฉด ๋ฐ์ ๋ง์ถ๋ค)`);
|
|
95
|
+
|
|
96
|
+
const tpl = loadTemplate(plan.purpose);
|
|
97
|
+
let style;
|
|
98
|
+
if (plan.theme) style = themeStyle(plan.theme);
|
|
99
|
+
else {
|
|
100
|
+
let preset = join(repo, "presets", `${plan.style}.json`);
|
|
101
|
+
// ํยท์์ฒญ ๋ฑ๋ง์ด ๊ณ ๋ฅธ ํ๋ฆฌ์
์ด ์ด ์ค์น์ ์์ ์ ์๋ค(npm ํจํค์ง๋ ๊ฐ์ธ ํ๋ฆฌ์
์ ์ ์ฃ๋๋ค) โ ์ฌ๋์ด ๊ณ ๋ฅธ ๊ฒ ์๋๋ฉด ๊ธฐ๋ณธ์ผ๋ก
|
|
102
|
+
if (!existsSync(preset) && !str("style")) {
|
|
103
|
+
console.error(`kf new: ํ๋ฆฌ์
"${plan.style}" ๊ฐ ์์ด neutral-dark ๋ก`);
|
|
104
|
+
plan.style = "neutral-dark";
|
|
105
|
+
preset = join(repo, "presets", "neutral-dark.json");
|
|
106
|
+
}
|
|
107
|
+
if (!existsSync(preset)) {
|
|
108
|
+
const have = readdirSync(join(repo, "presets")).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5));
|
|
109
|
+
console.error(`kf new: ํ๋ฆฌ์
"${plan.style}" ๊ฐ ์๋ค. ์๋ ํ๋ฆฌ์
: ${have.join(" ")}`);
|
|
110
|
+
return 2;
|
|
111
|
+
}
|
|
112
|
+
style = JSON.parse(readFileSync(preset, "utf8"));
|
|
113
|
+
}
|
|
114
|
+
// ํ์ ์์ง์ ๊ท์น์ด ํ๋ฆฌ์
์ ์ด๊ธด๋ค โ ์ผ์ธ ์ ๋ฐ ํ์ค๊ฐ ์ผ์ง ํ๋ฆฌ์
์ ๊ณจ๋ผ๋ ์ผ์ธ ๊ท์น๋๋ก
|
|
115
|
+
style.motion = { ...style.motion, ...tpl.motion };
|
|
116
|
+
|
|
117
|
+
// ์ฅ๋ฉด๋ง๋ค ์ฐ์ถ ์(๋ฐํยท๊ธยท๊ฒฝ๊ณ ๋ฎ๊ธฐ) โ ์ฉ๋ ํยทํ
๋ง ๋ฌถ์์์ ์์ฒญ ๊ธ์ ์จ์์ผ๋ก ๊ณ ๋ฅธ๋ค. ๊ฐ์ ์์ฒญ์ด๋ฉด ๊ฐ์ ์๊ฐ ๊ฑธ๋ฆฐ๋ค
|
|
118
|
+
// ํ์ ๋ฌถ์์ด ์์ผ๋ฉด(๋ฎค๋น โ ๊ฐ์ฌ ์ธต์ด ํ๋ฉด์ ๋งก๋๋ค) ๋ฌด๋ ๊ธฐ๋ณธ ๊ทธ๋๋ก
|
|
119
|
+
const dress = tpl.moves ? pickDress(dressSeed(plan.request || plan.title, plan.purpose, plan.theme ?? plan.style), plan.scenes, bundleFor(tpl.moves, plan.theme ?? undefined)) : plan.scenes.map(() => ({ ...NO_DRESS }));
|
|
120
|
+
|
|
121
|
+
const frame = { width: plan.width, height: plan.height };
|
|
122
|
+
const safeArea = plan.aspect === tpl.aspect ? tpl.safeArea : (tpl.variants?.[plan.aspect]?.safeArea ?? tpl.safeArea);
|
|
123
|
+
const kf = {
|
|
124
|
+
template: plan.purpose,
|
|
125
|
+
title: plan.title,
|
|
126
|
+
...(plan.theme ? { theme: plan.theme } : {}),
|
|
127
|
+
fps: plan.fps,
|
|
128
|
+
...frame,
|
|
129
|
+
duration: plan.duration,
|
|
130
|
+
mode: "seek",
|
|
131
|
+
style: "style.json",
|
|
132
|
+
music,
|
|
133
|
+
beats: beats ? "beats.json" : null,
|
|
134
|
+
captions: plan.captions,
|
|
135
|
+
...(tpl.render ? { render: tpl.render } : {}),
|
|
136
|
+
safeArea,
|
|
137
|
+
text: tpl.text,
|
|
138
|
+
scenes: plan.scenes.map((s, i) => ({ id: s.id, from: s.from, dur: s.dur, file: `scenes/${s.id}.ts`, role: s.role, idea: s.idea, assets: s.assets, ...(s.role === "seam" || !tpl.moves ? {} : { dress: dress[i] }) })),
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const write = (rel: string, body: string) => writeFileSync(join(dir, rel), body);
|
|
142
|
+
write("kf.json", `${JSON.stringify(kf, null, 2)}\n`);
|
|
143
|
+
write("plan.json", `${JSON.stringify(plan, null, 2)}\n`);
|
|
144
|
+
write("style.json", `${JSON.stringify(style, null, 2)}\n`);
|
|
145
|
+
const fill = (src: string, vars: Record<string, string>) => src.replace(/\{\{(\w+)\}\}/g, (_, k) => (vars[k] ?? "").replace(/["\\]/g, "\\$&"));
|
|
146
|
+
const shared = join(templatesDir, "_shared", "project");
|
|
147
|
+
for (const f of ["index.html", "main.ts"]) write(f, fill(readFileSync(join(shared, f), "utf8"), { title: plan.title }));
|
|
148
|
+
// ๋ผ๋๊ฐ ๊ฐ์ด ์ฐ๋ ๋ฌด๋(ํ๋ฉด ์ฑ์ยทํ๋ฅด๋ ๋ฐํยท์ด์ ์์ ๋๋ง ์์ง์ด๋ ์นด๋ฉ๋ผ) โ ์ฅ๋ฉด ํ์ผ์ด ../lib/ ๋ก ๊ฐ์ ธ๊ฐ๋ค
|
|
149
|
+
mkdirSync(join(dir, "lib"), { recursive: true });
|
|
150
|
+
for (const f of readdirSync(join(shared, "lib"))) if (!existsSync(join(dir, "lib", f))) copyFileSync(join(shared, "lib", f), join(dir, "lib", f));
|
|
151
|
+
|
|
152
|
+
const kept: string[] = [];
|
|
153
|
+
// ์ด์ด์ง๋ ์ฅ๋ฉด์ด ๊ฐ์ ๋ฑ์ฅ์ ์ฐ์ง ์๊ฒ ์๋ฒ์ผ๋ก ๋๋ฆฐ๋ค(style.json repeatSameMove)
|
|
154
|
+
const MOVES = ["rise", "pop", "blurIn", "scaleIn", "maskWipe", "drop"];
|
|
155
|
+
for (const [i, s] of plan.scenes.entries()) {
|
|
156
|
+
const rel = `scenes/${s.id}.ts`;
|
|
157
|
+
if (existsSync(join(dir, rel))) {
|
|
158
|
+
kept.push(rel);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const step = plan.scenes.slice(0, i + 1).filter((x) => x.role === "step").length;
|
|
162
|
+
const word = s.role === "end" || s.role === "hook" || s.role === "brand" ? plan.title : s.role === "step" ? `${step}๋จ๊ณ` : s.id.replace(/\d+$/, "").toUpperCase();
|
|
163
|
+
write(rel, fill(sceneSkeleton(plan.purpose, s.id, s.role), { word, title: plan.title, line: str("line") ?? "", move: MOVES[i % MOVES.length], transition: tpl.scenes.find((x) => x.id === s.id)?.transition ?? "dive", ...dress[i] }));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const near = relative(process.cwd(), dir);
|
|
167
|
+
const rel = !near ? "." : near.startsWith("..") ? dir : near;
|
|
168
|
+
console.log(`${rel}/ โ ${tpl.label}(${plan.purpose}) ${plan.aspect} ${plan.width}ร${plan.height} ยท ${+plan.duration.toFixed(3)}์ด ยท ${plan.theme ? `ํ
๋ง ${plan.theme}` : `์คํ์ผ ${plan.style}`}${music ? ` ยท ๊ณก ${music}` : ""}`);
|
|
169
|
+
if (plan.themeCandidates?.length) console.log(` ํ
๋ง ํ๋ณด: ${plan.themeCandidates.map((c) => c.id).join(" ยท ")}`);
|
|
170
|
+
else if (str("theme") === "auto" && !plan.theme) console.log(" ๋ง๋ approved ํ
๋ง๊ฐ ์์ด ํ๋ฆฌ์
์ผ๋ก ๊ฐ๋ค");
|
|
171
|
+
for (const s of kf.scenes) console.log(` ${s.from.toFixed(2).padStart(6)}โ${(s.from + s.dur).toFixed(2).padEnd(6)} ${s.id.padEnd(9)} ${s.idea}${s.dress ? ` [${s.dress.bg}ยท${s.dress.text}${s.dress.wipeOut !== "none" ? `โ${s.dress.wipeOut}` : ""}]` : ""}`);
|
|
172
|
+
if (tpl.render?.motionBlur) console.log(` ๊ตฝ๊ธฐ: ๋ชจ์
๋ธ๋ฌ ${tpl.render.motionBlur}์ฅยท์
ํฐ ${tpl.render.shutter ?? 180}ยฐ (kf.json render โ ๋๋ ค๋ฉด kf render --motion-blur 0)`);
|
|
173
|
+
for (const r of plan.reasons.filter((x) => !x.startsWith('"'))) console.log(` ยท ${r}`);
|
|
174
|
+
if (kept.length) console.log(` ์๋ ์ฅ๋ฉด ํ์ผ์ ๊ทธ๋๋ก ๋๋ค: ${kept.join(" ")}`);
|
|
175
|
+
if (plan.gen?.length) console.log(` ๋ง๋ค ์ ์๋ ์์ฐ(์์ง ์ ๋ง๋ฆ, ๋์ด ๋ ๋ค): ${plan.gen.map((g) => `gen:${g.kind}:${g.id}`).join(" ")} โ ๊ตฌ์ฒด์ ์ธ ํ๋กฌํํธ๋ก kf gen img --id <id> --project ${rel}`);
|
|
176
|
+
console.log(`\n๋ค์: scenes/*.ts ๋ฅผ kf.json ์ idea ๋๋ก ์ฑ์ด๋ค โ kf preview ${rel} โ kf render ${rel} โ kf check ${rel}`);
|
|
177
|
+
|
|
178
|
+
if (opts.preview) {
|
|
179
|
+
const r = spawnSync(process.execPath, [kfBin, "preview", dir], { stdio: "inherit" });
|
|
180
|
+
return r.status ?? 0;
|
|
181
|
+
}
|
|
182
|
+
return 0;
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
export default cmd;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { studioPlugin } from "@kasaframe/studio";
|
|
4
|
+
import type { Command } from "../command.ts";
|
|
5
|
+
import { num, parseFlags } from "../command.ts";
|
|
6
|
+
import { openProject } from "../serve.ts";
|
|
7
|
+
|
|
8
|
+
const cmd: Command = {
|
|
9
|
+
summary: "๋ฏธ๋ฆฌ๋ณด๊ธฐ โ ๊ณก์ด ๊ธฐ์ค ์๊ณ, ํ์๋ผ์ธยท์ฅ๋ฉด/๋ฐ/๋๋กญยทํ๋ ์ ์ด๋, ์ ์ฅํ๋ฉด ๋ฐ์",
|
|
10
|
+
usage: `kf preview <ํด๋> [--port 5120] [--t ์ด] [--captions on|off]
|
|
11
|
+
|
|
12
|
+
์ฃผ์ / ๊ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ, /index.html ์ด ํ๋ก์ ํธ ํ์ด์ง ๊ทธ๋๋ก๋ค. ?t=12.3 ์ผ๋ก ๊ทธ ์๊ฐ์์ ์ฐ๋ค.
|
|
13
|
+
scenes/*.ts ๋ฅผ ์ ์ฅํ๋ฉด ํ๋ก์ ํธ ํ์ด์ง๋ง ์๋ก ๊ณ ์น๊ณ ๋ณด๋ ์๊ฐ์ผ๋ก ๋์๊ฐ๋ค(kf.jsonยทbeats.jsonยทstyle.json ๋).
|
|
14
|
+
--captions ํ๋ฉด ์๋ง์ ์ผ๊ณ (๋๊ณ ) ์ฐ๋ค โ kf.json ์ ๊ทธ๋๋ก. ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋จ์ถ(C)๋ก๋ ๋ฐ๊พผ๋ค
|
|
15
|
+
์กฐ์: Space ์ฌ์ ยท โโ ํ ํ๋ ์ ยท Shift+โโ 1์ด ยท Alt+โโ ๋ฐ ยท โโ ์ฅ๋ฉดยท๊ตฌ๊ฐยท๋๋กญ ๊ฒฝ๊ณ ยท L ๋ฐ๋ณต
|
|
16
|
+
ํ์๋ผ์ธ ๋๊ธฐ(Shift ๋๋ฅด๋ฉด ๋ฐยท๋ง์ปค์ ๋ถ์) ยท ์ธ๋ก ํ ํ๋ ยท ๊ฐ๋ก ํ ์ด๋ ยท ๋ ๋ฒ ๋๋ฌ ์ ์ฒด
|
|
17
|
+
์ฐฝ์ ๋์ฐ์ง ์๋๋ค. ๋๋ด๋ ค๋ฉด Ctrl+C.`,
|
|
18
|
+
async run(argv) {
|
|
19
|
+
const { args, opts } = parseFlags(argv);
|
|
20
|
+
const dir = args[0];
|
|
21
|
+
if (!dir) {
|
|
22
|
+
console.error("kf preview: ํด๋๋ฅผ ์ค๋ผ โ kf preview <ํด๋>");
|
|
23
|
+
return 2;
|
|
24
|
+
}
|
|
25
|
+
if (!existsSync(join(resolve(dir), "index.html"))) {
|
|
26
|
+
console.error(`kf preview: ${resolve(dir)} ์ index.html ์ด ์๋ค โ kf new ๋ก ๋ง๋ ํ๋ก์ ํธ ํด๋๋ฅผ ์ค๋ผ`);
|
|
27
|
+
return 2;
|
|
28
|
+
}
|
|
29
|
+
const p = await openProject(dir, { port: num(opts.port, 5120), hmr: true, plugins: [studioPlugin(dir)] });
|
|
30
|
+
const q = new URLSearchParams();
|
|
31
|
+
if (opts.t !== undefined) q.set("t", String(num(opts.t, 0)));
|
|
32
|
+
if (opts.captions === "on" || opts.captions === "off" || opts.captions === "fit") q.set("captions", opts.captions);
|
|
33
|
+
const at = q.size ? `?${q}` : "";
|
|
34
|
+
console.log(`๋ฏธ๋ฆฌ๋ณด๊ธฐ: ${p.url}${at}`);
|
|
35
|
+
await new Promise<void>((done) => {
|
|
36
|
+
const stop = () => void p.close().then(done, done);
|
|
37
|
+
process.once("SIGINT", stop);
|
|
38
|
+
process.once("SIGTERM", stop);
|
|
39
|
+
});
|
|
40
|
+
return 0;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
export default cmd;
|