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.
Files changed (442) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +148 -0
  3. package/README.md +92 -2
  4. package/app/package.json +12 -0
  5. package/app/server/engine.ts +533 -0
  6. package/app/server/main.ts +209 -0
  7. package/app/web/env.d.ts +2 -0
  8. package/app/web/icons.ts +31 -0
  9. package/app/web/index.html +21 -0
  10. package/app/web/main.ts +1195 -0
  11. package/app/web/style.css +463 -0
  12. package/bin/hooks.mjs +64 -0
  13. package/bin/kf.mjs +32 -0
  14. package/docs/AGENTS.md +286 -0
  15. package/music/README.md +37 -0
  16. package/package.json +44 -4
  17. package/packages/adapters/package.json +36 -0
  18. package/packages/adapters/src/css.ts +11 -0
  19. package/packages/adapters/src/gsap.ts +26 -0
  20. package/packages/adapters/src/index.ts +9 -0
  21. package/packages/adapters/src/lottie.ts +22 -0
  22. package/packages/adapters/src/media.ts +59 -0
  23. package/packages/adapters/src/page.ts +229 -0
  24. package/packages/adapters/src/project.ts +214 -0
  25. package/packages/adapters/src/react.ts +36 -0
  26. package/packages/adapters/src/svelte.ts +22 -0
  27. package/packages/adapters/src/three.ts +18 -0
  28. package/packages/adapters/src/vue.ts +27 -0
  29. package/packages/check/package.json +19 -0
  30. package/packages/check/src/check.ts +280 -0
  31. package/packages/check/src/cli.ts +70 -0
  32. package/packages/check/src/image.ts +146 -0
  33. package/packages/check/src/index.ts +9 -0
  34. package/packages/check/src/ocr.swift +37 -0
  35. package/packages/check/src/probe.ts +328 -0
  36. package/packages/check/src/report.ts +44 -0
  37. package/packages/check/src/rules/assets.ts +45 -0
  38. package/packages/check/src/rules/beats.ts +212 -0
  39. package/packages/check/src/rules/camera.ts +217 -0
  40. package/packages/check/src/rules/captions.ts +49 -0
  41. package/packages/check/src/rules/flash.ts +235 -0
  42. package/packages/check/src/rules/frames.ts +136 -0
  43. package/packages/check/src/rules/layout.ts +281 -0
  44. package/packages/check/src/rules/page.ts +87 -0
  45. package/packages/check/src/rules/runs.ts +20 -0
  46. package/packages/check/src/rules/text.ts +326 -0
  47. package/packages/check/src/scan.ts +240 -0
  48. package/packages/check/src/serve.ts +67 -0
  49. package/packages/check/src/types.ts +80 -0
  50. package/packages/check/src/video.ts +137 -0
  51. package/packages/cli/package.json +30 -0
  52. package/packages/cli/src/blur.ts +80 -0
  53. package/packages/cli/src/browser.ts +203 -0
  54. package/packages/cli/src/command.ts +44 -0
  55. package/packages/cli/src/commands/app.ts +25 -0
  56. package/packages/cli/src/commands/beats.ts +36 -0
  57. package/packages/cli/src/commands/bench.ts +123 -0
  58. package/packages/cli/src/commands/blender.ts +293 -0
  59. package/packages/cli/src/commands/check.ts +25 -0
  60. package/packages/cli/src/commands/direct.ts +210 -0
  61. package/packages/cli/src/commands/doctor.ts +30 -0
  62. package/packages/cli/src/commands/gen.ts +173 -0
  63. package/packages/cli/src/commands/look.ts +46 -0
  64. package/packages/cli/src/commands/lyrics.ts +157 -0
  65. package/packages/cli/src/commands/make.ts +1121 -0
  66. package/packages/cli/src/commands/music.ts +78 -0
  67. package/packages/cli/src/commands/narrate.ts +238 -0
  68. package/packages/cli/src/commands/new.ts +185 -0
  69. package/packages/cli/src/commands/preview.ts +43 -0
  70. package/packages/cli/src/commands/render.ts +250 -0
  71. package/packages/cli/src/commands/series.ts +110 -0
  72. package/packages/cli/src/commands/setup.ts +102 -0
  73. package/packages/cli/src/commands/sheet.ts +114 -0
  74. package/packages/cli/src/commands/skill.ts +43 -0
  75. package/packages/cli/src/commands/still.ts +40 -0
  76. package/packages/cli/src/commands/theme.ts +172 -0
  77. package/packages/cli/src/commands/transitions.ts +40 -0
  78. package/packages/cli/src/deps.ts +122 -0
  79. package/packages/cli/src/ffmpeg.ts +81 -0
  80. package/packages/cli/src/index.ts +8 -0
  81. package/packages/cli/src/kf.ts +64 -0
  82. package/packages/cli/src/lock.ts +72 -0
  83. package/packages/cli/src/mix.ts +131 -0
  84. package/packages/cli/src/out.ts +13 -0
  85. package/packages/cli/src/pool.ts +34 -0
  86. package/packages/cli/src/segments.ts +402 -0
  87. package/packages/cli/src/serve.ts +124 -0
  88. package/packages/clock/package.json +14 -0
  89. package/packages/clock/src/index.ts +25 -0
  90. package/packages/clock/src/install.ts +351 -0
  91. package/packages/clock/src/types.ts +45 -0
  92. package/packages/core/package.json +9 -0
  93. package/packages/core/src/beats.ts +283 -0
  94. package/packages/core/src/ease.ts +87 -0
  95. package/packages/core/src/frame.ts +99 -0
  96. package/packages/core/src/index.ts +27 -0
  97. package/packages/core/src/project.ts +157 -0
  98. package/packages/core/src/rand.ts +56 -0
  99. package/packages/core/src/spring.ts +52 -0
  100. package/packages/core/src/style.ts +100 -0
  101. package/packages/core/src/time.ts +88 -0
  102. package/packages/director/package.json +23 -0
  103. package/packages/director/src/assemble.ts +361 -0
  104. package/packages/director/src/brief.ts +122 -0
  105. package/packages/director/src/catalog.ts +49 -0
  106. package/packages/director/src/content.ts +343 -0
  107. package/packages/director/src/fallback.ts +58 -0
  108. package/packages/director/src/index.ts +13 -0
  109. package/packages/director/src/pattern.ts +90 -0
  110. package/packages/director/src/patterns/fill/cells.ts +115 -0
  111. package/packages/director/src/patterns/formula/stack.ts +93 -0
  112. package/packages/director/src/patterns/util.ts +58 -0
  113. package/packages/director/src/scene.ts +62 -0
  114. package/packages/director/src/select.ts +163 -0
  115. package/packages/director/src/transitions.ts +115 -0
  116. package/packages/gen/PROVIDERS.md +90 -0
  117. package/packages/gen/package.json +9 -0
  118. package/packages/gen/src/ask.ts +132 -0
  119. package/packages/gen/src/audio.ts +91 -0
  120. package/packages/gen/src/blender.ts +54 -0
  121. package/packages/gen/src/capabilities.ts +151 -0
  122. package/packages/gen/src/generate.ts +206 -0
  123. package/packages/gen/src/image/codex.ts +117 -0
  124. package/packages/gen/src/image/og.ts +133 -0
  125. package/packages/gen/src/index.ts +26 -0
  126. package/packages/gen/src/keys.ts +86 -0
  127. package/packages/gen/src/media.ts +150 -0
  128. package/packages/gen/src/music/elevenlabs.ts +41 -0
  129. package/packages/gen/src/music/index.ts +120 -0
  130. package/packages/gen/src/music/library.ts +73 -0
  131. package/packages/gen/src/music/lyria.ts +52 -0
  132. package/packages/gen/src/music/types.ts +28 -0
  133. package/packages/gen/src/plan.ts +106 -0
  134. package/packages/gen/src/registry.ts +54 -0
  135. package/packages/gen/src/seam.ts +103 -0
  136. package/packages/gen/src/tts/dashscope.ts +64 -0
  137. package/packages/gen/src/tts/elevenlabs.ts +84 -0
  138. package/packages/gen/src/tts/gemini.ts +73 -0
  139. package/packages/gen/src/tts/index.ts +241 -0
  140. package/packages/gen/src/tts/local.ts +42 -0
  141. package/packages/gen/src/tts/types.ts +44 -0
  142. package/packages/gen/src/types.ts +90 -0
  143. package/packages/gen/src/video/fal.ts +140 -0
  144. package/packages/gen/src/video/h3.ts +112 -0
  145. package/packages/gen/src/video/http.ts +44 -0
  146. package/packages/gen/src/video/ltx.ts +60 -0
  147. package/packages/gen/src/video/minimax.ts +55 -0
  148. package/packages/gen/src/video/veo.ts +71 -0
  149. package/packages/jizura/package.json +16 -0
  150. package/packages/jizura/src/deny.ts +73 -0
  151. package/packages/jizura/src/index.ts +7 -0
  152. package/packages/jizura/src/install.ts +269 -0
  153. package/packages/jizura/src/layer.ts +325 -0
  154. package/packages/jizura/src/lyrics.ts +81 -0
  155. package/packages/jizura/src/pin.ts +8 -0
  156. package/packages/jizura/src/vendor.ts +140 -0
  157. package/packages/jizura/src/vite.ts +63 -0
  158. package/packages/jizura/vendor/JIZURA_LICENSE.txt +21 -0
  159. package/packages/jizura/vendor/NOTICE +43 -0
  160. package/packages/jizura/vendor/engine.json +47 -0
  161. package/packages/jizura/vendor/fonts/BlackHanSans-Regular.woff2 +0 -0
  162. package/packages/jizura/vendor/fonts/DotGothic16-Regular.woff2 +0 -0
  163. package/packages/jizura/vendor/fonts/GowunBatang-Bold.woff2 +0 -0
  164. package/packages/jizura/vendor/fonts/IBMPlexMono-Medium.ttf +0 -0
  165. package/packages/jizura/vendor/fonts/IBMPlexMono-SemiBold.ttf +0 -0
  166. package/packages/jizura/vendor/fonts/NotoSansKR-VF.woff2 +0 -0
  167. package/packages/jizura/vendor/fonts/NotoSerifKR-VF.woff2 +0 -0
  168. package/packages/jizura/vendor/fonts/OFL-blackhansans.txt +93 -0
  169. package/packages/jizura/vendor/fonts/OFL-dotgothic16.txt +93 -0
  170. package/packages/jizura/vendor/fonts/OFL-gowunbatang.txt +94 -0
  171. package/packages/jizura/vendor/fonts/OFL-ibmplexmono.txt +93 -0
  172. package/packages/jizura/vendor/fonts/OFL-notosanskr.txt +93 -0
  173. package/packages/jizura/vendor/fonts/OFL-notoserifkr.txt +94 -0
  174. package/packages/jizura/vendor/fonts/fonts.json +52 -0
  175. package/packages/jizura/vendor/jizura_engine.js +35024 -0
  176. package/packages/motion/README.md +153 -0
  177. package/packages/motion/package.json +30 -0
  178. package/packages/motion/src/camera.ts +97 -0
  179. package/packages/motion/src/captions.ts +487 -0
  180. package/packages/motion/src/enter.ts +128 -0
  181. package/packages/motion/src/hangul.ts +129 -0
  182. package/packages/motion/src/impact.ts +100 -0
  183. package/packages/motion/src/index.ts +17 -0
  184. package/packages/motion/src/moves/brushWipe.ts +103 -0
  185. package/packages/motion/src/moves/cloneArray.ts +111 -0
  186. package/packages/motion/src/moves/curtains.ts +81 -0
  187. package/packages/motion/src/moves/dress.ts +191 -0
  188. package/packages/motion/src/moves/echoStack.ts +70 -0
  189. package/packages/motion/src/moves/fit.ts +70 -0
  190. package/packages/motion/src/moves/flood.ts +61 -0
  191. package/packages/motion/src/moves/ghostStack.ts +88 -0
  192. package/packages/motion/src/moves/gridFloor.ts +100 -0
  193. package/packages/motion/src/moves/imageSwarm.ts +113 -0
  194. package/packages/motion/src/moves/index.ts +38 -0
  195. package/packages/motion/src/moves/irisBlades.ts +113 -0
  196. package/packages/motion/src/moves/karaokeLine.ts +108 -0
  197. package/packages/motion/src/moves/ledBoard.ts +94 -0
  198. package/packages/motion/src/moves/liquidTab.ts +72 -0
  199. package/packages/motion/src/moves/markerWrite.ts +63 -0
  200. package/packages/motion/src/moves/menu.ts +138 -0
  201. package/packages/motion/src/moves/morphBox.ts +96 -0
  202. package/packages/motion/src/moves/neonSign.ts +78 -0
  203. package/packages/motion/src/moves/panelPop.ts +78 -0
  204. package/packages/motion/src/moves/paperTear.ts +75 -0
  205. package/packages/motion/src/moves/path.ts +64 -0
  206. package/packages/motion/src/moves/pathText.ts +107 -0
  207. package/packages/motion/src/moves/pointSphere.ts +109 -0
  208. package/packages/motion/src/moves/ransomLetters.ts +73 -0
  209. package/packages/motion/src/moves/shapeMorph.ts +143 -0
  210. package/packages/motion/src/moves/slamWords.ts +96 -0
  211. package/packages/motion/src/moves/sparkTrail.ts +130 -0
  212. package/packages/motion/src/moves/speedLines.ts +91 -0
  213. package/packages/motion/src/moves/splitFlap.ts +149 -0
  214. package/packages/motion/src/moves/stepWords.ts +128 -0
  215. package/packages/motion/src/moves/strikeOut.ts +98 -0
  216. package/packages/motion/src/moves/sunburst.ts +58 -0
  217. package/packages/motion/src/moves/tickerRows.ts +91 -0
  218. package/packages/motion/src/moves/tokenPicker.ts +115 -0
  219. package/packages/motion/src/moves/truchet.ts +85 -0
  220. package/packages/motion/src/moves/wordWall.ts +78 -0
  221. package/packages/motion/src/moves/worldRoll.ts +32 -0
  222. package/packages/motion/src/particles.ts +102 -0
  223. package/packages/motion/src/react/index.tsx +21 -0
  224. package/packages/motion/src/react/parts.tsx +431 -0
  225. package/packages/motion/src/react/time.tsx +97 -0
  226. package/packages/motion/src/text.ts +103 -0
  227. package/packages/motion/src/transition.ts +82 -0
  228. package/packages/motion/src/transition3d/cards.ts +65 -0
  229. package/packages/motion/src/transition3d/chain.ts +84 -0
  230. package/packages/motion/src/transition3d/dive.ts +181 -0
  231. package/packages/motion/src/transition3d/fold.ts +110 -0
  232. package/packages/motion/src/transition3d/gl.ts +219 -0
  233. package/packages/motion/src/transition3d/index.ts +235 -0
  234. package/packages/motion/src/transition3d/live.ts +70 -0
  235. package/packages/motion/src/transition3d/portal.ts +115 -0
  236. package/packages/motion/src/transition3d/react.tsx +29 -0
  237. package/packages/motion/src/transition3d/rig.ts +600 -0
  238. package/packages/motion/src/transition3d/shatter.ts +104 -0
  239. package/packages/motion/src/transition3d/space.ts +173 -0
  240. package/packages/motion/src/transition3d/whip.ts +78 -0
  241. package/packages/motion/src/transition3d/world.ts +737 -0
  242. package/packages/motion/src/transition3d/zoomOut.ts +114 -0
  243. package/packages/motion/src/transitions2d/catalog.ts +127 -0
  244. package/packages/motion/src/transitions2d/css.d.ts +2 -0
  245. package/packages/motion/src/transitions2d/cult-ui/LICENSE.md +9 -0
  246. package/packages/motion/src/transitions2d/cult-ui/RollingNumber.tsx +43 -0
  247. package/packages/motion/src/transitions2d/gl-transitions/LICENSE +25 -0
  248. package/packages/motion/src/transitions2d/gl-transitions/shaders.ts +1931 -0
  249. package/packages/motion/src/transitions2d/index.ts +2 -0
  250. package/packages/motion/src/transitions2d/kokonutui/LICENSE +21 -0
  251. package/packages/motion/src/transitions2d/kokonutui/MatrixText.css +16 -0
  252. package/packages/motion/src/transitions2d/kokonutui/MatrixText.tsx +94 -0
  253. package/packages/motion/src/transitions2d/magicui/HyperText.css +15 -0
  254. package/packages/motion/src/transitions2d/magicui/HyperText.tsx +115 -0
  255. package/packages/motion/src/transitions2d/magicui/LICENSE.md +21 -0
  256. package/packages/motion/src/transitions2d/magicui/MorphingText.css +25 -0
  257. package/packages/motion/src/transitions2d/magicui/MorphingText.tsx +130 -0
  258. package/packages/motion/src/transitions2d/magicui/WordRotate.tsx +42 -0
  259. package/packages/motion/src/transitions2d/react.ts +13 -0
  260. package/packages/motion/src/transitions2d/skiper-ui/ClipDiv.css +39 -0
  261. package/packages/motion/src/transitions2d/skiper-ui/ClipDiv.tsx +32 -0
  262. package/packages/motion/src/transitions2d/smoothui/LICENSE +21 -0
  263. package/packages/motion/src/transitions2d/smoothui/SharedAxisY.tsx +50 -0
  264. package/packages/motion/src/transitions2d/smoothui/TextMorph.tsx +90 -0
  265. package/packages/motion/src/transitions2d/smoothui/shaders.ts +223 -0
  266. package/packages/motion/src/transitions2d/unlumen/LICENSE +21 -0
  267. package/packages/motion/src/transitions2d/unlumen/ScrambleText.tsx +114 -0
  268. package/packages/motion/src/transitions2d/vengeance-ui/LICENSE +21 -0
  269. package/packages/motion/src/transitions2d/vengeance-ui/MorphText.css +69 -0
  270. package/packages/motion/src/transitions2d/vengeance-ui/MorphText.tsx +45 -0
  271. package/packages/motion/src/type.ts +131 -0
  272. package/packages/motion/src/ui.ts +172 -0
  273. package/packages/patterns/package.json +15 -0
  274. package/packages/patterns/src/bars/rank.ts +119 -0
  275. package/packages/patterns/src/before-after/shrink.ts +127 -0
  276. package/packages/patterns/src/before-after/strike.ts +125 -0
  277. package/packages/patterns/src/code/terminal.ts +133 -0
  278. package/packages/patterns/src/compare/bars.ts +142 -0
  279. package/packages/patterns/src/compare/duel.ts +103 -0
  280. package/packages/patterns/src/end/lockup.ts +85 -0
  281. package/packages/patterns/src/end/slam.ts +91 -0
  282. package/packages/patterns/src/flow/graph.ts +250 -0
  283. package/packages/patterns/src/hook-number/counter.ts +101 -0
  284. package/packages/patterns/src/hook-number/roll.ts +179 -0
  285. package/packages/patterns/src/hook-number/slam.ts +91 -0
  286. package/packages/patterns/src/index.ts +38 -0
  287. package/packages/patterns/src/kit.ts +449 -0
  288. package/packages/patterns/src/limit/gauge.ts +117 -0
  289. package/packages/patterns/src/list/cards.ts +131 -0
  290. package/packages/patterns/src/list/cycle.ts +22 -0
  291. package/packages/patterns/src/media/panels.ts +100 -0
  292. package/packages/patterns/src/objects.ts +234 -0
  293. package/packages/patterns/src/quote/karaoke.ts +126 -0
  294. package/packages/patterns/src/quote/slam.ts +22 -0
  295. package/packages/patterns/src/screen/focus.ts +85 -0
  296. package/packages/patterns/src/steps/path.ts +137 -0
  297. package/packages/patterns/src/steps/stack.ts +111 -0
  298. package/packages/patterns/src/term-reveal/chips.ts +92 -0
  299. package/packages/patterns/src/term-reveal/expand.ts +127 -0
  300. package/packages/patterns/src/timeline/axis.ts +144 -0
  301. package/packages/patterns/src/title/mask.ts +85 -0
  302. package/packages/patterns/src/title/slam.ts +23 -0
  303. package/packages/patterns/src/toggle-sim/switch.ts +92 -0
  304. package/packages/patterns/src/trend/columns.ts +108 -0
  305. package/packages/patterns/src/trend/draw.ts +140 -0
  306. package/packages/patterns/src/types.ts +67 -0
  307. package/packages/patterns/src/words/moves.ts +42 -0
  308. package/packages/patterns/src/words/slam.ts +4 -0
  309. package/packages/plan/package.json +15 -0
  310. package/packages/plan/src/asks.ts +256 -0
  311. package/packages/plan/src/brief.ts +383 -0
  312. package/packages/plan/src/captions.ts +78 -0
  313. package/packages/plan/src/detect.ts +93 -0
  314. package/packages/plan/src/index.ts +27 -0
  315. package/packages/plan/src/layout.ts +176 -0
  316. package/packages/plan/src/llm.ts +172 -0
  317. package/packages/plan/src/look.ts +222 -0
  318. package/packages/plan/src/plan.ts +179 -0
  319. package/packages/plan/src/request.ts +55 -0
  320. package/packages/plan/src/series.ts +76 -0
  321. package/packages/plan/src/studio.ts +53 -0
  322. package/packages/plan/src/templates.ts +42 -0
  323. package/packages/plan/src/themes.ts +48 -0
  324. package/packages/plan/src/types.ts +157 -0
  325. package/packages/studio/package.json +12 -0
  326. package/packages/studio/src/index.ts +1 -0
  327. package/packages/studio/src/plugin.ts +236 -0
  328. package/packages/studio/src/ui/main.ts +503 -0
  329. package/packages/studio/src/ui/studio.css +165 -0
  330. package/packages/studio/src/ui/timeline.ts +317 -0
  331. package/packages/themes/kit/THEME-MAKE.md +68 -0
  332. package/packages/themes/kit/THEME.md +30 -0
  333. package/packages/themes/kit/briefs/og-intro-15s/brief.json +16 -0
  334. package/packages/themes/kit/briefs/og-intro-15s/facts.ts +29 -0
  335. package/packages/themes/kit/briefs/og-intro-15s/logo.svg +12 -0
  336. package/packages/themes/kit/effects.ts +5 -0
  337. package/packages/themes/kit/sample/index.html +11 -0
  338. package/packages/themes/kit/sample/main.ts +9 -0
  339. package/packages/themes/kit/sample/scenes/scene.ts +15 -0
  340. package/packages/themes/package.json +19 -0
  341. package/packages/themes/src/factory.ts +296 -0
  342. package/packages/themes/src/fx.ts +14 -0
  343. package/packages/themes/src/index.ts +114 -0
  344. package/packages/themes/src/slotkit.ts +79 -0
  345. package/packages/themes/src/slots.ts +46 -0
  346. package/packages/themes/src/stage3d.ts +197 -0
  347. package/packages/themes/src/types.ts +58 -0
  348. package/presets/kawaii-pastel.json +94 -0
  349. package/presets/neutral-dark.json +82 -0
  350. package/presets/og.json +98 -0
  351. package/series/og-shorts.json +14 -0
  352. package/skills/kasaframe/SKILL.md +33 -0
  353. package/templates/_shared/project/index.html +11 -0
  354. package/templates/_shared/project/lib/react.ts +22 -0
  355. package/templates/_shared/project/lib/stage.ts +292 -0
  356. package/templates/_shared/project/main.ts +9 -0
  357. package/templates/_shared/scenes/default.ts +21 -0
  358. package/templates/_shared/scenes/drop.ts +33 -0
  359. package/templates/_shared/scenes/end.ts +26 -0
  360. package/templates/_shared/scenes/hook.ts +18 -0
  361. package/templates/_shared/scenes/seam.ts +41 -0
  362. package/templates/_shared/scenes/step.ts +49 -0
  363. package/templates/ad/preview.png +0 -0
  364. package/templates/ad/template.json +151 -0
  365. package/templates/demo/preview.png +0 -0
  366. package/templates/demo/template.json +141 -0
  367. package/templates/explainer/preview.png +0 -0
  368. package/templates/explainer/template.json +193 -0
  369. package/templates/launch/preview.png +0 -0
  370. package/templates/launch/template.json +163 -0
  371. package/templates/mv/preview.png +0 -0
  372. package/templates/mv/scenes/default.ts +98 -0
  373. package/templates/mv/template.json +178 -0
  374. package/templates/promo/preview.png +0 -0
  375. package/templates/promo/template.json +185 -0
  376. package/templates/shorts/preview.png +0 -0
  377. package/templates/shorts/scenes/hook.ts +37 -0
  378. package/templates/shorts/template.json +196 -0
  379. package/templates/square/preview.png +0 -0
  380. package/templates/square/template.json +133 -0
  381. package/themes/debug-hud/THEME.md +39 -0
  382. package/themes/debug-hud/effects.ts +389 -0
  383. package/themes/debug-hud/sample/assets/og/logo.svg +12 -0
  384. package/themes/debug-hud/sample/beats.json +1 -0
  385. package/themes/debug-hud/sample/index.html +11 -0
  386. package/themes/debug-hud/sample/kf.json +59 -0
  387. package/themes/debug-hud/sample/lib/hudkit.ts +49 -0
  388. package/themes/debug-hud/sample/lib/og.ts +29 -0
  389. package/themes/debug-hud/sample/main.ts +9 -0
  390. package/themes/debug-hud/sample/scenes/end.ts +62 -0
  391. package/themes/debug-hud/sample/scenes/gather.ts +67 -0
  392. package/themes/debug-hud/sample/scenes/models.ts +70 -0
  393. package/themes/debug-hud/sample/scenes/price.ts +79 -0
  394. package/themes/debug-hud/sample/style.json +91 -0
  395. package/themes/debug-hud/slots.ts +99 -0
  396. package/themes/debug-hud/still.png +0 -0
  397. package/themes/debug-hud/style.json +91 -0
  398. package/themes/debug-hud/theme.json +68 -0
  399. package/themes/og-measure/THEME.md +47 -0
  400. package/themes/og-measure/effects.ts +432 -0
  401. package/themes/og-measure/sample/assets/brand/og-logo-full.svg +12 -0
  402. package/themes/og-measure/sample/assets/brand/og-logo.svg +12 -0
  403. package/themes/og-measure/sample/assets/data/measure.json +353 -0
  404. package/themes/og-measure/sample/beats.json +1 -0
  405. package/themes/og-measure/sample/index.html +11 -0
  406. package/themes/og-measure/sample/kf.json +66 -0
  407. package/themes/og-measure/sample/lib/data.ts +62 -0
  408. package/themes/og-measure/sample/main.ts +9 -0
  409. package/themes/og-measure/sample/measure.ts +78 -0
  410. package/themes/og-measure/sample/scenes/end.ts +47 -0
  411. package/themes/og-measure/sample/scenes/gather.ts +51 -0
  412. package/themes/og-measure/sample/scenes/models.ts +34 -0
  413. package/themes/og-measure/sample/scenes/one.ts +39 -0
  414. package/themes/og-measure/sample/scenes/price.ts +56 -0
  415. package/themes/og-measure/sample/style.json +97 -0
  416. package/themes/og-measure/slots.ts +88 -0
  417. package/themes/og-measure/still.png +0 -0
  418. package/themes/og-measure/style.json +97 -0
  419. package/themes/og-measure/theme.json +79 -0
  420. package/themes/riso-zine/THEME.md +43 -0
  421. package/themes/riso-zine/effects.ts +321 -0
  422. package/themes/riso-zine/sample/assets/og/logo.svg +12 -0
  423. package/themes/riso-zine/sample/beats.json +1 -0
  424. package/themes/riso-zine/sample/index.html +11 -0
  425. package/themes/riso-zine/sample/kf.json +59 -0
  426. package/themes/riso-zine/sample/lib/og.ts +29 -0
  427. package/themes/riso-zine/sample/lib/shot.ts +86 -0
  428. package/themes/riso-zine/sample/lib/zine.ts +42 -0
  429. package/themes/riso-zine/sample/main.ts +9 -0
  430. package/themes/riso-zine/sample/scenes/end.ts +61 -0
  431. package/themes/riso-zine/sample/scenes/gather.ts +62 -0
  432. package/themes/riso-zine/sample/scenes/models.ts +52 -0
  433. package/themes/riso-zine/sample/scenes/price.ts +82 -0
  434. package/themes/riso-zine/sample/style.json +99 -0
  435. package/themes/riso-zine/slots.ts +81 -0
  436. package/themes/riso-zine/still.png +0 -0
  437. package/themes/riso-zine/style.json +99 -0
  438. package/themes/riso-zine/theme.json +75 -0
  439. package/tools/beats/beats.py +372 -0
  440. package/tools/beats/requirements.txt +3 -0
  441. package/tools/lyrics/align.py +164 -0
  442. 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;