kasaframe 0.1.1 → 0.1.4
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/NOTICE +10 -8
- package/README.md +16 -1
- package/app/server/engine.ts +133 -15
- package/app/server/main.ts +7 -3
- package/app/web/main.ts +218 -36
- package/app/web/style.css +37 -4
- package/bin/hooks.mjs +14 -1
- package/bin/kf.mjs +20 -2
- package/docs/AGENTS.md +64 -16
- package/music/README.md +31 -7
- package/package.json +2 -2
- package/packages/adapters/src/project.ts +5 -1
- package/packages/adapters/src/texture.ts +94 -0
- package/packages/check/src/check.ts +10 -3
- package/packages/check/src/rules/assets.ts +4 -1
- package/packages/check/src/rules/frames.ts +2 -1
- package/packages/check/src/rules/layout.ts +30 -6
- package/packages/cli/package.json +2 -1
- package/packages/cli/src/blur.ts +15 -3
- package/packages/cli/src/cells.ts +227 -0
- package/packages/cli/src/commands/bench.ts +123 -0
- package/packages/cli/src/commands/blender.ts +72 -49
- package/packages/cli/src/commands/direct.ts +210 -0
- package/packages/cli/src/commands/doctor.ts +9 -1
- package/packages/cli/src/commands/look.ts +9 -2
- package/packages/cli/src/commands/lyrics.ts +47 -1
- package/packages/cli/src/commands/make.ts +797 -150
- 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 +15 -6
- package/packages/cli/src/commands/render.ts +94 -60
- package/packages/cli/src/commands/series.ts +110 -0
- package/packages/cli/src/commands/setup.ts +42 -3
- package/packages/cli/src/commands/theme.ts +222 -4
- package/packages/cli/src/deps.ts +6 -6
- package/packages/cli/src/kf.ts +14 -3
- package/packages/cli/src/lock.ts +8 -6
- package/packages/cli/src/mix.ts +131 -0
- package/packages/cli/src/pick.ts +110 -0
- package/packages/cli/src/pool.ts +34 -0
- package/packages/cli/src/segments.ts +402 -0
- package/packages/cli/src/vary.ts +124 -0
- package/packages/core/src/index.ts +1 -1
- package/packages/core/src/project.ts +26 -0
- package/packages/director/package.json +23 -0
- package/packages/director/src/assemble.ts +364 -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 +39 -4
- 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 +4 -2
- package/packages/gen/src/image/codex.ts +117 -0
- package/packages/gen/src/index.ts +10 -2
- package/packages/gen/src/keys.ts +26 -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/registry.ts +6 -3
- 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 +4 -0
- package/packages/gen/src/video/h3.ts +5 -0
- package/packages/gen/src/video/http.ts +1 -1
- package/packages/jizura/src/index.ts +2 -2
- package/packages/jizura/src/install.ts +198 -155
- package/packages/jizura/src/layer.ts +75 -32
- package/packages/jizura/src/pin.ts +4 -1
- package/packages/jizura/src/vendor.ts +140 -0
- package/packages/jizura/src/vite.ts +34 -7
- 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 +11 -0
- package/packages/motion/package.json +1 -0
- package/packages/motion/src/captions.ts +32 -24
- package/packages/motion/src/moves/brushWipe.ts +14 -1
- package/packages/motion/src/moves/dress.ts +191 -0
- package/packages/motion/src/moves/echoStack.ts +3 -3
- package/packages/motion/src/moves/fit.ts +36 -0
- package/packages/motion/src/moves/ghostStack.ts +14 -5
- package/packages/motion/src/moves/imageSwarm.ts +8 -4
- package/packages/motion/src/moves/index.ts +23 -4
- package/packages/motion/src/moves/karaokeLine.ts +17 -3
- package/packages/motion/src/moves/menu.ts +139 -0
- package/packages/motion/src/moves/neonSign.ts +2 -1
- package/packages/motion/src/moves/slamWords.ts +14 -7
- package/packages/motion/src/moves/sparkTrail.ts +16 -0
- package/packages/motion/src/moves/splitFlap.ts +4 -2
- package/packages/motion/src/moves/stepWords.ts +19 -8
- package/packages/motion/src/moves/tickerRows.ts +13 -9
- package/packages/motion/src/moves/tokenPicker.ts +4 -3
- package/packages/motion/src/moves/wordWall.ts +23 -21
- package/packages/motion/src/moves/worldRoll.ts +2 -1
- package/packages/motion/src/transition3d/index.ts +10 -3
- package/packages/motion/src/transition3d/rig.ts +107 -9
- package/packages/motion/src/transition3d/shatter.ts +5 -2
- package/packages/motion/src/transition3d/space.ts +173 -0
- package/packages/motion/src/transition3d/whip.ts +19 -6
- package/packages/motion/src/transition3d/world.ts +54 -0
- package/packages/motion/src/transitions2d/catalog.ts +1 -1
- 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 +450 -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/src/asks.ts +265 -0
- package/packages/plan/src/brief.ts +136 -7
- package/packages/plan/src/captions.ts +5 -2
- package/packages/plan/src/index.ts +7 -3
- package/packages/plan/src/llm.ts +64 -2
- package/packages/plan/src/look.ts +3 -1
- package/packages/plan/src/plan.ts +70 -14
- package/packages/plan/src/series.ts +76 -0
- package/packages/plan/src/themes.ts +65 -9
- package/packages/plan/src/types.ts +56 -2
- package/packages/themes/kit/THEME-MAKE.md +1 -0
- package/packages/themes/kit/fonts.json +743 -0
- package/packages/themes/package.json +10 -2
- package/packages/themes/src/color.ts +152 -0
- package/packages/themes/src/fx.ts +4 -2
- package/packages/themes/src/index.ts +74 -1
- package/packages/themes/src/look.ts +223 -0
- package/packages/themes/src/name.ts +9 -0
- package/packages/themes/src/palette.ts +194 -0
- package/packages/themes/src/slotkit.ts +92 -0
- package/packages/themes/src/slots.ts +61 -0
- package/packages/themes/src/stage3d.ts +20 -0
- package/packages/themes/src/types.ts +84 -0
- package/packages/themes/src/vary.ts +290 -0
- package/series/og-shorts.json +14 -0
- package/skills/kasaframe/SKILL.md +4 -3
- package/templates/_shared/project/lib/stage.ts +70 -50
- package/templates/_shared/scenes/default.ts +10 -7
- package/templates/_shared/scenes/drop.ts +11 -8
- package/templates/_shared/scenes/end.ts +9 -6
- package/templates/_shared/scenes/hook.ts +8 -6
- package/templates/_shared/scenes/step.ts +13 -4
- package/templates/ad/template.json +20 -1
- package/templates/demo/template.json +11 -0
- package/templates/explainer/template.json +54 -3
- package/templates/launch/template.json +34 -1
- package/templates/mv/scenes/default.ts +11 -8
- package/templates/mv/template.json +4 -2
- package/templates/promo/template.json +42 -1
- package/templates/shorts/scenes/hook.ts +8 -5
- package/templates/shorts/template.json +72 -3
- package/templates/square/template.json +21 -1
- package/themes/blueprint/THEME.md +64 -0
- package/themes/blueprint/effects.ts +570 -0
- package/themes/blueprint/sample/assets/og/logo.svg +12 -0
- package/themes/blueprint/sample/beats.json +1005 -0
- package/themes/blueprint/sample/index.html +11 -0
- package/themes/blueprint/sample/kf.json +59 -0
- package/themes/blueprint/sample/lib/facts.ts +29 -0
- package/themes/blueprint/sample/lib/sheet.ts +55 -0
- package/themes/blueprint/sample/main.ts +9 -0
- package/themes/blueprint/sample/scenes/end.ts +91 -0
- package/themes/blueprint/sample/scenes/gather.ts +75 -0
- package/themes/blueprint/sample/scenes/models.ts +117 -0
- package/themes/blueprint/sample/scenes/price.ts +94 -0
- package/themes/blueprint/sample/style.json +114 -0
- package/themes/blueprint/slots.ts +88 -0
- package/themes/blueprint/still.png +0 -0
- package/themes/blueprint/style.json +114 -0
- package/themes/blueprint/theme.json +137 -0
- package/themes/blueprint/variants/deep-rose-2.json +90 -0
- package/themes/blueprint/variants/deep-rose-suit.json +117 -0
- package/themes/blueprint/variants/deep-rose.json +117 -0
- package/themes/blueprint/variants/navy-coral-2.json +118 -0
- package/themes/blueprint/variants/navy-coral-suit.json +118 -0
- package/themes/blueprint/variants/navy-coral.json +128 -0
- package/themes/blueprint/variants/navy-orange-2.json +118 -0
- package/themes/blueprint/variants/navy-orange-3.json +91 -0
- package/themes/blueprint/variants/navy-orange-4.json +128 -0
- package/themes/blueprint/variants/navy-orange-mono.json +128 -0
- package/themes/blueprint/variants/navy-orange-plex.json +128 -0
- package/themes/blueprint/variants/navy-orange-suit.json +118 -0
- package/themes/blueprint/variants/navy-orange.json +118 -0
- package/themes/blueprint/variants/navy-rose-plex.json +127 -0
- package/themes/blueprint/variants/navy-rose.json +127 -0
- package/themes/cartography/THEME.md +61 -0
- package/themes/cartography/effects.ts +845 -0
- package/themes/cartography/sample/assets/og/logo.svg +12 -0
- package/themes/cartography/sample/beats.json +1005 -0
- package/themes/cartography/sample/index.html +11 -0
- package/themes/cartography/sample/kf.json +59 -0
- package/themes/cartography/sample/lib/facts.ts +29 -0
- package/themes/cartography/sample/lib/map.ts +39 -0
- package/themes/cartography/sample/main.ts +9 -0
- package/themes/cartography/sample/scenes/end.ts +56 -0
- package/themes/cartography/sample/scenes/gather.ts +46 -0
- package/themes/cartography/sample/scenes/models.ts +43 -0
- package/themes/cartography/sample/scenes/price.ts +73 -0
- package/themes/cartography/sample/style.json +103 -0
- package/themes/cartography/slots.ts +87 -0
- package/themes/cartography/still.png +0 -0
- package/themes/cartography/style.json +103 -0
- package/themes/cartography/theme.json +160 -0
- package/themes/cartography/variants/blush-amber.json +128 -0
- package/themes/cartography/variants/blush-indigo.json +128 -0
- package/themes/cartography/variants/blush-pink.json +136 -0
- package/themes/cartography/variants/chalk-plum.json +137 -0
- package/themes/cartography/variants/chalk-sky.json +100 -0
- package/themes/cartography/variants/cocoa-amber.json +128 -0
- package/themes/cartography/variants/deep-plum.json +125 -0
- package/themes/cartography/variants/dusk-coral.json +128 -0
- package/themes/cartography/variants/forest-lime.json +131 -0
- package/themes/cartography/variants/frost-teal.json +128 -0
- package/themes/cartography/variants/navy-green.json +131 -0
- package/themes/cartography/variants/navy-indigo.json +138 -0
- package/themes/cartography/variants/navy-sky.json +100 -0
- package/themes/cartography/variants/sage-green.json +127 -0
- package/themes/cartography/variants/wine-teal.json +128 -0
- package/themes/circuit-board/THEME.md +61 -0
- package/themes/circuit-board/effects.ts +576 -0
- package/themes/circuit-board/pcb.ts +103 -0
- package/themes/circuit-board/sample/assets/og/logo.svg +12 -0
- package/themes/circuit-board/sample/beats.json +1005 -0
- package/themes/circuit-board/sample/index.html +11 -0
- package/themes/circuit-board/sample/kf.json +59 -0
- package/themes/circuit-board/sample/lib/facts.ts +29 -0
- package/themes/circuit-board/sample/lib/kit.ts +45 -0
- package/themes/circuit-board/sample/main.ts +9 -0
- package/themes/circuit-board/sample/scenes/end.ts +102 -0
- package/themes/circuit-board/sample/scenes/gather.ts +76 -0
- package/themes/circuit-board/sample/scenes/models.ts +101 -0
- package/themes/circuit-board/sample/scenes/price.ts +109 -0
- package/themes/circuit-board/sample/style.json +104 -0
- package/themes/circuit-board/slots.ts +108 -0
- package/themes/circuit-board/still.png +0 -0
- package/themes/circuit-board/style.json +104 -0
- package/themes/circuit-board/theme.json +149 -0
- package/themes/circuit-board/variants/deep-gold-plex.json +133 -0
- package/themes/circuit-board/variants/deep-gold.json +123 -0
- package/themes/circuit-board/variants/dusk-gold-grain.json +138 -0
- package/themes/circuit-board/variants/dusk-gold-pretendard.json +118 -0
- package/themes/circuit-board/variants/dusk-gold.json +97 -0
- package/themes/circuit-board/variants/forest-gold-2.json +96 -0
- package/themes/circuit-board/variants/forest-gold-grain.json +129 -0
- package/themes/circuit-board/variants/forest-gold-mono.json +124 -0
- package/themes/circuit-board/variants/forest-gold.json +128 -0
- package/themes/circuit-board/variants/navy-gold-grain.json +124 -0
- package/themes/circuit-board/variants/navy-gold.json +101 -0
- package/themes/circuit-board/variants/wine-gold-grain.json +128 -0
- package/themes/circuit-board/variants/wine-gold-plex.json +133 -0
- package/themes/circuit-board/variants/wine-gold-pretendard.json +118 -0
- package/themes/circuit-board/variants/wine-gold.json +128 -0
- package/themes/clay-toy/THEME.md +73 -0
- package/themes/clay-toy/effects.ts +643 -0
- package/themes/clay-toy/glyphs.ts +32 -0
- package/themes/clay-toy/sample/assets/og/logo.svg +12 -0
- package/themes/clay-toy/sample/beats.json +1005 -0
- package/themes/clay-toy/sample/index.html +11 -0
- package/themes/clay-toy/sample/kf.json +59 -0
- package/themes/clay-toy/sample/lib/clay.ts +20 -0
- package/themes/clay-toy/sample/lib/facts.ts +29 -0
- package/themes/clay-toy/sample/main.ts +9 -0
- package/themes/clay-toy/sample/scenes/end.ts +81 -0
- package/themes/clay-toy/sample/scenes/gather.ts +53 -0
- package/themes/clay-toy/sample/scenes/models.ts +77 -0
- package/themes/clay-toy/sample/scenes/price.ts +91 -0
- package/themes/clay-toy/sample/style.json +101 -0
- package/themes/clay-toy/slots.ts +132 -0
- package/themes/clay-toy/still.png +0 -0
- package/themes/clay-toy/style.json +101 -0
- package/themes/clay-toy/theme.json +146 -0
- package/themes/clay-toy/variants/blush-coral-grain.json +126 -0
- package/themes/clay-toy/variants/blush-coral.json +123 -0
- package/themes/clay-toy/variants/chalk-indigo.json +97 -0
- package/themes/clay-toy/variants/chalk-jade.json +125 -0
- package/themes/clay-toy/variants/chalk-teal.json +120 -0
- package/themes/clay-toy/variants/frost-orange.json +125 -0
- package/themes/clay-toy/variants/frost-sky-grain.json +126 -0
- package/themes/clay-toy/variants/frost-sky.json +121 -0
- package/themes/clay-toy/variants/frost-teal.json +120 -0
- package/themes/clay-toy/variants/lilac-green.json +120 -0
- package/themes/clay-toy/variants/lilac-plum-round.json +120 -0
- package/themes/clay-toy/variants/lilac-plum.json +124 -0
- package/themes/clay-toy/variants/sage-indigo.json +125 -0
- package/themes/clay-toy/variants/sage-magenta.json +119 -0
- package/themes/clay-toy/variants/sand-amber.json +125 -0
- package/themes/conveyor-factory/THEME.md +71 -0
- package/themes/conveyor-factory/effects.ts +744 -0
- package/themes/conveyor-factory/sample/assets/og/logo.svg +12 -0
- package/themes/conveyor-factory/sample/beats.json +1005 -0
- package/themes/conveyor-factory/sample/index.html +11 -0
- package/themes/conveyor-factory/sample/kf.json +59 -0
- package/themes/conveyor-factory/sample/lib/factory.ts +48 -0
- package/themes/conveyor-factory/sample/lib/facts.ts +29 -0
- package/themes/conveyor-factory/sample/main.ts +9 -0
- package/themes/conveyor-factory/sample/scenes/end.ts +44 -0
- package/themes/conveyor-factory/sample/scenes/gather.ts +47 -0
- package/themes/conveyor-factory/sample/scenes/models.ts +91 -0
- package/themes/conveyor-factory/sample/scenes/price.ts +77 -0
- package/themes/conveyor-factory/sample/style.json +107 -0
- package/themes/conveyor-factory/slots.ts +121 -0
- package/themes/conveyor-factory/still.png +0 -0
- package/themes/conveyor-factory/style.json +107 -0
- package/themes/conveyor-factory/theme.json +142 -0
- package/themes/conveyor-factory/variants/cocoa-gold.json +125 -0
- package/themes/conveyor-factory/variants/dusk-gold.json +135 -0
- package/themes/conveyor-factory/variants/forest-gold.json +135 -0
- package/themes/conveyor-factory/variants/navy-amber.json +93 -0
- package/themes/conveyor-factory/variants/navy-gold.json +120 -0
- package/themes/conveyor-factory/variants/slate-amber-2.json +120 -0
- package/themes/conveyor-factory/variants/slate-amber-grain.json +125 -0
- package/themes/conveyor-factory/variants/slate-amber-suit.json +120 -0
- package/themes/conveyor-factory/variants/slate-amber.json +125 -0
- package/themes/conveyor-factory/variants/slate-gold-bold.json +120 -0
- package/themes/conveyor-factory/variants/slate-gold.json +120 -0
- package/themes/data-journal/THEME.md +74 -0
- package/themes/data-journal/effects.ts +570 -0
- package/themes/data-journal/sample/assets/og/logo.svg +12 -0
- package/themes/data-journal/sample/beats.json +1005 -0
- package/themes/data-journal/sample/index.html +11 -0
- package/themes/data-journal/sample/kf.json +59 -0
- package/themes/data-journal/sample/lib/facts.ts +29 -0
- package/themes/data-journal/sample/lib/page.ts +25 -0
- package/themes/data-journal/sample/main.ts +9 -0
- package/themes/data-journal/sample/scenes/end.ts +69 -0
- package/themes/data-journal/sample/scenes/gather.ts +58 -0
- package/themes/data-journal/sample/scenes/models.ts +51 -0
- package/themes/data-journal/sample/scenes/price.ts +73 -0
- package/themes/data-journal/sample/style.json +114 -0
- package/themes/data-journal/slots.ts +119 -0
- package/themes/data-journal/still.png +0 -0
- package/themes/data-journal/style.json +114 -0
- package/themes/data-journal/theme.json +140 -0
- package/themes/data-journal/variants/blush-green.json +129 -0
- package/themes/data-journal/variants/chalk-magenta.json +138 -0
- package/themes/data-journal/variants/cocoa-amber.json +140 -0
- package/themes/data-journal/variants/cocoa-sky.json +130 -0
- package/themes/data-journal/variants/deep-coral.json +135 -0
- package/themes/data-journal/variants/dusk-plum.json +132 -0
- package/themes/data-journal/variants/forest-green.json +138 -0
- package/themes/data-journal/variants/frost-indigo.json +141 -0
- package/themes/data-journal/variants/lilac-amber.json +141 -0
- package/themes/data-journal/variants/navy-indigo.json +131 -0
- package/themes/data-journal/variants/navy-lime.json +128 -0
- package/themes/data-journal/variants/snow-sky.json +125 -0
- package/themes/data-journal/variants/wine-indigo.json +129 -0
- package/themes/data-journal/variants/wine-magenta.json +138 -0
- package/themes/data-journal/variants/wine-teal.json +97 -0
- package/themes/debug-hud/slots.ts +99 -0
- package/themes/debug-hud/theme.json +56 -0
- package/themes/debug-hud/variants/cocoa-gold.json +113 -0
- package/themes/debug-hud/variants/cocoa-teal.json +119 -0
- package/themes/debug-hud/variants/deep-cyan.json +85 -0
- package/themes/debug-hud/variants/deep-teal-scan.json +118 -0
- package/themes/debug-hud/variants/deep-teal.json +112 -0
- package/themes/debug-hud/variants/dusk-plum.json +116 -0
- package/themes/debug-hud/variants/dusk-violet.json +112 -0
- package/themes/debug-hud/variants/forest-amber.json +86 -0
- package/themes/debug-hud/variants/forest-lime.json +117 -0
- package/themes/debug-hud/variants/forest-magenta.json +111 -0
- package/themes/debug-hud/variants/navy-jade.json +79 -0
- package/themes/debug-hud/variants/slate-green.json +112 -0
- package/themes/debug-hud/variants/wine-gold.json +119 -0
- package/themes/debug-hud/variants/wine-jade.json +83 -0
- package/themes/debug-hud/variants/wine-sky.json +112 -0
- package/themes/depth-flight/THEME.md +54 -0
- package/themes/depth-flight/effects.ts +396 -0
- package/themes/depth-flight/sample/assets/og/logo.svg +12 -0
- package/themes/depth-flight/sample/beats.json +1005 -0
- package/themes/depth-flight/sample/index.html +11 -0
- package/themes/depth-flight/sample/kf.json +59 -0
- package/themes/depth-flight/sample/lib/facts.ts +29 -0
- package/themes/depth-flight/sample/lib/fly.ts +15 -0
- package/themes/depth-flight/sample/main.ts +9 -0
- package/themes/depth-flight/sample/scenes/end.ts +51 -0
- package/themes/depth-flight/sample/scenes/gather.ts +40 -0
- package/themes/depth-flight/sample/scenes/models.ts +75 -0
- package/themes/depth-flight/sample/scenes/price.ts +50 -0
- package/themes/depth-flight/sample/style.json +94 -0
- package/themes/depth-flight/slots.ts +196 -0
- package/themes/depth-flight/still.png +0 -0
- package/themes/depth-flight/style.json +94 -0
- package/themes/depth-flight/theme.json +119 -0
- package/themes/depth-flight/variants/cocoa-cyan.json +121 -0
- package/themes/depth-flight/variants/cocoa-indigo.json +121 -0
- package/themes/depth-flight/variants/cocoa-violet.json +122 -0
- package/themes/depth-flight/variants/deep-sky.json +126 -0
- package/themes/depth-flight/variants/dusk-violet-bold.json +117 -0
- package/themes/depth-flight/variants/dusk-violet.json +132 -0
- package/themes/depth-flight/variants/forest-blue.json +116 -0
- package/themes/depth-flight/variants/forest-cyan.json +122 -0
- package/themes/depth-flight/variants/navy-sky-grain.json +122 -0
- package/themes/depth-flight/variants/navy-sky.json +116 -0
- package/themes/depth-flight/variants/night-teal.json +116 -0
- package/themes/depth-flight/variants/slate-cyan.json +127 -0
- package/themes/depth-flight/variants/slate-indigo.json +116 -0
- package/themes/depth-flight/variants/slate-sky.json +94 -0
- package/themes/depth-flight/variants/slate-violet.json +121 -0
- package/themes/film-reel/THEME.md +60 -0
- package/themes/film-reel/effects.ts +477 -0
- package/themes/film-reel/sample/assets/og/logo.svg +12 -0
- package/themes/film-reel/sample/beats.json +1005 -0
- package/themes/film-reel/sample/index.html +11 -0
- package/themes/film-reel/sample/kf.json +59 -0
- package/themes/film-reel/sample/lib/facts.ts +29 -0
- package/themes/film-reel/sample/lib/reel.ts +9 -0
- package/themes/film-reel/sample/main.ts +9 -0
- package/themes/film-reel/sample/scenes/end.ts +31 -0
- package/themes/film-reel/sample/scenes/gather.ts +40 -0
- package/themes/film-reel/sample/scenes/models.ts +37 -0
- package/themes/film-reel/sample/scenes/price.ts +63 -0
- package/themes/film-reel/sample/style.json +103 -0
- package/themes/film-reel/slots.ts +75 -0
- package/themes/film-reel/still.png +0 -0
- package/themes/film-reel/style.json +103 -0
- package/themes/film-reel/theme.json +142 -0
- package/themes/film-reel/variants/chalk-amber.json +124 -0
- package/themes/film-reel/variants/chalk-pink.json +123 -0
- package/themes/film-reel/variants/cream-violet.json +121 -0
- package/themes/film-reel/variants/frost-blue.json +126 -0
- package/themes/film-reel/variants/frost-green.json +123 -0
- package/themes/film-reel/variants/frost-indigo.json +121 -0
- package/themes/film-reel/variants/frost-jade.json +120 -0
- package/themes/film-reel/variants/frost-plum.json +120 -0
- package/themes/film-reel/variants/frost-teal.json +93 -0
- package/themes/film-reel/variants/lilac-gold.json +120 -0
- package/themes/film-reel/variants/lilac-jade.json +123 -0
- package/themes/film-reel/variants/lilac-magenta.json +125 -0
- package/themes/film-reel/variants/lilac-violet.json +122 -0
- package/themes/film-reel/variants/sand-cyan.json +124 -0
- package/themes/film-reel/variants/sand-lime.json +96 -0
- package/themes/glass-light/THEME.md +55 -0
- package/themes/glass-light/effects.ts +433 -0
- package/themes/glass-light/sample/assets/og/logo.svg +12 -0
- package/themes/glass-light/sample/beats.json +1005 -0
- package/themes/glass-light/sample/index.html +11 -0
- package/themes/glass-light/sample/kf.json +59 -0
- package/themes/glass-light/sample/lib/facts.ts +29 -0
- package/themes/glass-light/sample/lib/glass.ts +81 -0
- package/themes/glass-light/sample/main.ts +9 -0
- package/themes/glass-light/sample/scenes/end.ts +43 -0
- package/themes/glass-light/sample/scenes/gather.ts +38 -0
- package/themes/glass-light/sample/scenes/models.ts +46 -0
- package/themes/glass-light/sample/scenes/price.ts +46 -0
- package/themes/glass-light/sample/style.json +94 -0
- package/themes/glass-light/slots.ts +198 -0
- package/themes/glass-light/still.png +0 -0
- package/themes/glass-light/style.json +94 -0
- package/themes/glass-light/theme.json +119 -0
- package/themes/glass-light/variants/blush-coral.json +116 -0
- package/themes/glass-light/variants/blush-orange.json +115 -0
- package/themes/glass-light/variants/blush-pink.json +119 -0
- package/themes/glass-light/variants/blush-plum.json +115 -0
- package/themes/glass-light/variants/blush-teal.json +115 -0
- package/themes/glass-light/variants/cream-blue.json +125 -0
- package/themes/glass-light/variants/cream-pink.json +87 -0
- package/themes/glass-light/variants/cream-violet.json +119 -0
- package/themes/glass-light/variants/frost-green.json +115 -0
- package/themes/glass-light/variants/frost-sky-suit.json +115 -0
- package/themes/glass-light/variants/frost-sky.json +121 -0
- package/themes/glass-light/variants/lilac-gold.json +126 -0
- package/themes/glass-light/variants/lilac-plum.json +114 -0
- package/themes/glass-light/variants/sage-green.json +119 -0
- package/themes/glass-light/variants/sage-rose.json +114 -0
- package/themes/ink-wash/THEME.md +59 -0
- package/themes/ink-wash/effects.ts +574 -0
- package/themes/ink-wash/sample/assets/og/logo.svg +12 -0
- package/themes/ink-wash/sample/beats.json +1005 -0
- package/themes/ink-wash/sample/index.html +11 -0
- package/themes/ink-wash/sample/kf.json +59 -0
- package/themes/ink-wash/sample/lib/facts.ts +29 -0
- package/themes/ink-wash/sample/lib/ink.ts +35 -0
- package/themes/ink-wash/sample/main.ts +9 -0
- package/themes/ink-wash/sample/scenes/end.ts +72 -0
- package/themes/ink-wash/sample/scenes/gather.ts +53 -0
- package/themes/ink-wash/sample/scenes/models.ts +58 -0
- package/themes/ink-wash/sample/scenes/price.ts +85 -0
- package/themes/ink-wash/sample/style.json +108 -0
- package/themes/ink-wash/slots.ts +150 -0
- package/themes/ink-wash/still.png +0 -0
- package/themes/ink-wash/style.json +108 -0
- package/themes/ink-wash/theme.json +131 -0
- package/themes/ink-wash/variants/blush-orange.json +125 -0
- package/themes/ink-wash/variants/blush-rose.json +124 -0
- package/themes/ink-wash/variants/chalk-coral-grain.json +125 -0
- package/themes/ink-wash/variants/chalk-coral-serif.json +118 -0
- package/themes/ink-wash/variants/chalk-coral.json +120 -0
- package/themes/ink-wash/variants/chalk-orange-grain.json +97 -0
- package/themes/ink-wash/variants/chalk-orange-serif.json +120 -0
- package/themes/ink-wash/variants/chalk-orange.json +120 -0
- package/themes/ink-wash/variants/frost-coral.json +119 -0
- package/themes/ink-wash/variants/frost-rose.json +91 -0
- package/themes/ink-wash/variants/lilac-coral.json +96 -0
- package/themes/ink-wash/variants/sage-coral.json +119 -0
- package/themes/ink-wash/variants/sage-rose.json +119 -0
- package/themes/ink-wash/variants/sand-coral.json +125 -0
- package/themes/ink-wash/variants/sand-orange.json +120 -0
- package/themes/iso-diorama/THEME.md +54 -0
- package/themes/iso-diorama/effects.ts +374 -0
- package/themes/iso-diorama/sample/assets/og/logo.svg +12 -0
- package/themes/iso-diorama/sample/beats.json +1005 -0
- package/themes/iso-diorama/sample/index.html +11 -0
- package/themes/iso-diorama/sample/kf.json +59 -0
- package/themes/iso-diorama/sample/lib/facts.ts +29 -0
- package/themes/iso-diorama/sample/lib/iso.ts +22 -0
- package/themes/iso-diorama/sample/main.ts +9 -0
- package/themes/iso-diorama/sample/scenes/end.ts +41 -0
- package/themes/iso-diorama/sample/scenes/gather.ts +39 -0
- package/themes/iso-diorama/sample/scenes/models.ts +55 -0
- package/themes/iso-diorama/sample/scenes/price.ts +49 -0
- package/themes/iso-diorama/sample/style.json +98 -0
- package/themes/iso-diorama/slots.ts +222 -0
- package/themes/iso-diorama/still.png +0 -0
- package/themes/iso-diorama/style.json +98 -0
- package/themes/iso-diorama/theme.json +131 -0
- package/themes/iso-diorama/variants/chalk-plum.json +121 -0
- package/themes/iso-diorama/variants/cocoa-gold.json +93 -0
- package/themes/iso-diorama/variants/cocoa-indigo.json +122 -0
- package/themes/iso-diorama/variants/cocoa-rose.json +125 -0
- package/themes/iso-diorama/variants/deep-green.json +125 -0
- package/themes/iso-diorama/variants/deep-plum.json +120 -0
- package/themes/iso-diorama/variants/dusk-blue.json +126 -0
- package/themes/iso-diorama/variants/dusk-coral.json +122 -0
- package/themes/iso-diorama/variants/dusk-jade.json +121 -0
- package/themes/iso-diorama/variants/forest-cyan.json +127 -0
- package/themes/iso-diorama/variants/frost-blue.json +126 -0
- package/themes/iso-diorama/variants/frost-green.json +126 -0
- package/themes/iso-diorama/variants/frost-orange.json +125 -0
- package/themes/iso-diorama/variants/frost-teal.json +123 -0
- package/themes/iso-diorama/variants/navy-amber.json +125 -0
- package/themes/isotype/THEME.md +58 -0
- package/themes/isotype/effects.ts +297 -0
- package/themes/isotype/kit.ts +102 -0
- package/themes/isotype/sample/assets/og/logo.svg +12 -0
- package/themes/isotype/sample/beats.json +1005 -0
- package/themes/isotype/sample/index.html +11 -0
- package/themes/isotype/sample/kf.json +59 -0
- package/themes/isotype/sample/lib/chart.ts +47 -0
- package/themes/isotype/sample/lib/facts.ts +29 -0
- package/themes/isotype/sample/main.ts +9 -0
- package/themes/isotype/sample/scenes/end.ts +80 -0
- package/themes/isotype/sample/scenes/gather.ts +65 -0
- package/themes/isotype/sample/scenes/models.ts +75 -0
- package/themes/isotype/sample/scenes/price.ts +90 -0
- package/themes/isotype/sample/style.json +101 -0
- package/themes/isotype/slots.ts +74 -0
- package/themes/isotype/still.png +0 -0
- package/themes/isotype/style.json +101 -0
- package/themes/isotype/theme.json +140 -0
- package/themes/isotype/variants/blush-rose.json +127 -0
- package/themes/isotype/variants/chalk-amber.json +123 -0
- package/themes/isotype/variants/chalk-coral-bold.json +118 -0
- package/themes/isotype/variants/chalk-coral.json +117 -0
- package/themes/isotype/variants/chalk-orange.json +128 -0
- package/themes/isotype/variants/frost-amber-plex.json +128 -0
- package/themes/isotype/variants/frost-amber.json +123 -0
- package/themes/isotype/variants/frost-coral-grain.json +95 -0
- package/themes/isotype/variants/frost-coral.json +123 -0
- package/themes/isotype/variants/frost-orange.json +123 -0
- package/themes/isotype/variants/lilac-rose.json +127 -0
- package/themes/isotype/variants/sage-coral.json +90 -0
- package/themes/isotype/variants/sand-amber.json +118 -0
- package/themes/isotype/variants/sand-orange.json +128 -0
- package/themes/kawaii-sticker/THEME.md +44 -0
- package/themes/kawaii-sticker/effects.ts +412 -0
- package/themes/kawaii-sticker/sample/assets/stickers/bolt.png +0 -0
- package/themes/kawaii-sticker/sample/assets/stickers/bubble.png +0 -0
- package/themes/kawaii-sticker/sample/assets/stickers/clock.png +0 -0
- package/themes/kawaii-sticker/sample/assets/stickers/coin.png +0 -0
- package/themes/kawaii-sticker/sample/assets/stickers/moon.png +0 -0
- package/themes/kawaii-sticker/sample/assets/stickers/og-logo.svg +12 -0
- package/themes/kawaii-sticker/sample/assets/stickers/server.png +0 -0
- package/themes/kawaii-sticker/sample/beats.json +1 -0
- package/themes/kawaii-sticker/sample/index.html +11 -0
- package/themes/kawaii-sticker/sample/kf.json +66 -0
- package/themes/kawaii-sticker/sample/lib/og.ts +54 -0
- package/themes/kawaii-sticker/sample/main.ts +9 -0
- package/themes/kawaii-sticker/sample/scenes/end.ts +37 -0
- package/themes/kawaii-sticker/sample/scenes/gather.ts +50 -0
- package/themes/kawaii-sticker/sample/scenes/models.ts +43 -0
- package/themes/kawaii-sticker/sample/scenes/one.ts +45 -0
- package/themes/kawaii-sticker/sample/scenes/price.ts +47 -0
- package/themes/kawaii-sticker/sample/style.json +99 -0
- package/themes/kawaii-sticker/slots.ts +95 -0
- package/themes/kawaii-sticker/still.png +0 -0
- package/themes/kawaii-sticker/style.json +99 -0
- package/themes/kawaii-sticker/theme.json +145 -0
- package/themes/kawaii-sticker/variants/blush-coral.json +134 -0
- package/themes/kawaii-sticker/variants/blush-teal.json +129 -0
- package/themes/kawaii-sticker/variants/blush-violet.json +134 -0
- package/themes/kawaii-sticker/variants/cream-gold.json +135 -0
- package/themes/kawaii-sticker/variants/deep-jade.json +134 -0
- package/themes/kawaii-sticker/variants/dusk-green.json +134 -0
- package/themes/kawaii-sticker/variants/dusk-indigo.json +130 -0
- package/themes/kawaii-sticker/variants/dusk-sky.json +135 -0
- package/themes/kawaii-sticker/variants/dusk-teal.json +134 -0
- package/themes/kawaii-sticker/variants/frost-blue.json +135 -0
- package/themes/kawaii-sticker/variants/frost-plum.json +133 -0
- package/themes/kawaii-sticker/variants/lilac-violet.json +133 -0
- package/themes/kawaii-sticker/variants/navy-gold.json +134 -0
- package/themes/kawaii-sticker/variants/navy-plum.json +134 -0
- package/themes/kawaii-sticker/variants/sage-sky.json +107 -0
- package/themes/lab-notebook/THEME.md +56 -0
- package/themes/lab-notebook/effects.ts +423 -0
- package/themes/lab-notebook/kit.ts +84 -0
- package/themes/lab-notebook/sample/assets/og/logo.svg +12 -0
- package/themes/lab-notebook/sample/beats.json +1005 -0
- package/themes/lab-notebook/sample/index.html +11 -0
- package/themes/lab-notebook/sample/kf.json +59 -0
- package/themes/lab-notebook/sample/lib/facts.ts +29 -0
- package/themes/lab-notebook/sample/lib/note.ts +53 -0
- package/themes/lab-notebook/sample/main.ts +9 -0
- package/themes/lab-notebook/sample/scenes/end.ts +127 -0
- package/themes/lab-notebook/sample/scenes/gather.ts +83 -0
- package/themes/lab-notebook/sample/scenes/models.ts +73 -0
- package/themes/lab-notebook/sample/scenes/price.ts +89 -0
- package/themes/lab-notebook/sample/style.json +109 -0
- package/themes/lab-notebook/slots.ts +75 -0
- package/themes/lab-notebook/still.png +0 -0
- package/themes/lab-notebook/style.json +109 -0
- package/themes/lab-notebook/theme.json +141 -0
- package/themes/lab-notebook/variants/blush-orange.json +125 -0
- package/themes/lab-notebook/variants/chalk-coral.json +126 -0
- package/themes/lab-notebook/variants/chalk-orange.json +129 -0
- package/themes/lab-notebook/variants/chalk-rose-2.json +96 -0
- package/themes/lab-notebook/variants/chalk-rose.json +125 -0
- package/themes/lab-notebook/variants/frost-orange.json +101 -0
- package/themes/lab-notebook/variants/frost-rose-grain.json +101 -0
- package/themes/lab-notebook/variants/frost-rose.json +96 -0
- package/themes/lab-notebook/variants/lilac-rose-2.json +96 -0
- package/themes/lab-notebook/variants/lilac-rose.json +128 -0
- package/themes/lab-notebook/variants/sage-rose.json +124 -0
- package/themes/lab-notebook/variants/sand-orange-grain.json +101 -0
- package/themes/lab-notebook/variants/sand-orange.json +129 -0
- package/themes/lab-notebook/variants/snow-orange.json +129 -0
- package/themes/lab-notebook/variants/snow-rose.json +124 -0
- package/themes/manga-panel/THEME.md +60 -0
- package/themes/manga-panel/effects.ts +791 -0
- package/themes/manga-panel/sample/assets/og/logo.svg +12 -0
- package/themes/manga-panel/sample/beats.json +1005 -0
- package/themes/manga-panel/sample/index.html +11 -0
- package/themes/manga-panel/sample/kf.json +59 -0
- package/themes/manga-panel/sample/lib/facts.ts +29 -0
- package/themes/manga-panel/sample/lib/manga.ts +58 -0
- package/themes/manga-panel/sample/main.ts +9 -0
- package/themes/manga-panel/sample/scenes/end.ts +96 -0
- package/themes/manga-panel/sample/scenes/gather.ts +100 -0
- package/themes/manga-panel/sample/scenes/models.ts +96 -0
- package/themes/manga-panel/sample/scenes/price.ts +154 -0
- package/themes/manga-panel/sample/style.json +105 -0
- package/themes/manga-panel/slots.ts +127 -0
- package/themes/manga-panel/still.png +0 -0
- package/themes/manga-panel/style.json +105 -0
- package/themes/manga-panel/theme.json +148 -0
- package/themes/manga-panel/variants/chalk-cyan-dots.json +122 -0
- package/themes/manga-panel/variants/chalk-cyan.json +122 -0
- package/themes/manga-panel/variants/chalk-magenta.json +121 -0
- package/themes/manga-panel/variants/chalk-orange-bold.json +117 -0
- package/themes/manga-panel/variants/chalk-orange.json +122 -0
- package/themes/manga-panel/variants/chalk-rose-dots.json +121 -0
- package/themes/manga-panel/variants/chalk-rose.json +116 -0
- package/themes/manga-panel/variants/chalk-sky-bold.json +117 -0
- package/themes/manga-panel/variants/chalk-sky.json +117 -0
- package/themes/manga-panel/variants/snow-cyan.json +117 -0
- package/themes/manga-panel/variants/snow-magenta-dots.json +121 -0
- package/themes/manga-panel/variants/snow-magenta.json +116 -0
- package/themes/manga-panel/variants/snow-rose-dots.json +121 -0
- package/themes/manga-panel/variants/snow-rose.json +116 -0
- package/themes/manga-panel/variants/snow-sky.json +95 -0
- package/themes/marble-run/THEME.md +73 -0
- package/themes/marble-run/effects.ts +678 -0
- package/themes/marble-run/sample/assets/og/logo.svg +12 -0
- package/themes/marble-run/sample/beats.json +1005 -0
- package/themes/marble-run/sample/index.html +11 -0
- package/themes/marble-run/sample/kf.json +59 -0
- package/themes/marble-run/sample/lib/facts.ts +29 -0
- package/themes/marble-run/sample/lib/run.ts +30 -0
- package/themes/marble-run/sample/main.ts +9 -0
- package/themes/marble-run/sample/scenes/end.ts +58 -0
- package/themes/marble-run/sample/scenes/gather.ts +51 -0
- package/themes/marble-run/sample/scenes/models.ts +73 -0
- package/themes/marble-run/sample/scenes/price.ts +51 -0
- package/themes/marble-run/sample/style.json +109 -0
- package/themes/marble-run/slots.ts +116 -0
- package/themes/marble-run/still.png +0 -0
- package/themes/marble-run/style.json +109 -0
- package/themes/marble-run/theme.json +145 -0
- package/themes/marble-run/variants/cocoa-amber.json +128 -0
- package/themes/marble-run/variants/cocoa-orange.json +142 -0
- package/themes/marble-run/variants/dusk-orange.json +133 -0
- package/themes/marble-run/variants/forest-gold.json +128 -0
- package/themes/marble-run/variants/navy-gold.json +137 -0
- package/themes/marble-run/variants/night-gold-grain.json +105 -0
- package/themes/marble-run/variants/night-gold.json +137 -0
- package/themes/marble-run/variants/slate-gold-grain.json +133 -0
- package/themes/marble-run/variants/slate-gold.json +133 -0
- package/themes/marble-run/variants/slate-orange-grain.json +105 -0
- package/themes/marble-run/variants/slate-orange-mono.json +128 -0
- package/themes/marble-run/variants/slate-orange-serif.json +128 -0
- package/themes/marble-run/variants/slate-orange.json +131 -0
- package/themes/neon-sign/THEME.md +59 -0
- package/themes/neon-sign/effects.ts +424 -0
- package/themes/neon-sign/sample/assets/og/logo.svg +12 -0
- package/themes/neon-sign/sample/beats.json +1005 -0
- package/themes/neon-sign/sample/index.html +11 -0
- package/themes/neon-sign/sample/kf.json +59 -0
- package/themes/neon-sign/sample/lib/facts.ts +29 -0
- package/themes/neon-sign/sample/lib/neon.ts +50 -0
- package/themes/neon-sign/sample/main.ts +9 -0
- package/themes/neon-sign/sample/scenes/end.ts +62 -0
- package/themes/neon-sign/sample/scenes/gather.ts +59 -0
- package/themes/neon-sign/sample/scenes/models.ts +63 -0
- package/themes/neon-sign/sample/scenes/price.ts +74 -0
- package/themes/neon-sign/sample/style.json +104 -0
- package/themes/neon-sign/slots.ts +110 -0
- package/themes/neon-sign/still.png +0 -0
- package/themes/neon-sign/style.json +104 -0
- package/themes/neon-sign/theme.json +136 -0
- package/themes/neon-sign/variants/deep-plum.json +121 -0
- package/themes/neon-sign/variants/deep-violet.json +121 -0
- package/themes/neon-sign/variants/dusk-plum.json +121 -0
- package/themes/neon-sign/variants/forest-magenta.json +126 -0
- package/themes/neon-sign/variants/forest-plum.json +98 -0
- package/themes/neon-sign/variants/night-coral.json +122 -0
- package/themes/neon-sign/variants/night-magenta-round.json +121 -0
- package/themes/neon-sign/variants/night-magenta.json +126 -0
- package/themes/neon-sign/variants/night-pink.json +125 -0
- package/themes/neon-sign/variants/night-violet.json +126 -0
- package/themes/neon-sign/variants/slate-plum.json +120 -0
- package/themes/neon-sign/variants/slate-violet.json +127 -0
- package/themes/neon-sign/variants/wine-coral.json +127 -0
- package/themes/neon-sign/variants/wine-pink.json +125 -0
- package/themes/neon-sign/variants/wine-plum.json +126 -0
- package/themes/newspaper/THEME.md +67 -0
- package/themes/newspaper/effects.ts +347 -0
- package/themes/newspaper/sample/assets/og/logo.svg +12 -0
- package/themes/newspaper/sample/beats.json +1005 -0
- package/themes/newspaper/sample/index.html +11 -0
- package/themes/newspaper/sample/kf.json +59 -0
- package/themes/newspaper/sample/lib/facts.ts +29 -0
- package/themes/newspaper/sample/lib/page.ts +27 -0
- package/themes/newspaper/sample/main.ts +9 -0
- package/themes/newspaper/sample/scenes/end.ts +62 -0
- package/themes/newspaper/sample/scenes/gather.ts +66 -0
- package/themes/newspaper/sample/scenes/models.ts +61 -0
- package/themes/newspaper/sample/scenes/price.ts +103 -0
- package/themes/newspaper/sample/style.json +100 -0
- package/themes/newspaper/slots.ts +73 -0
- package/themes/newspaper/still.png +0 -0
- package/themes/newspaper/style.json +100 -0
- package/themes/newspaper/theme.json +140 -0
- package/themes/newspaper/variants/blush-amber.json +123 -0
- package/themes/newspaper/variants/blush-orange.json +125 -0
- package/themes/newspaper/variants/chalk-cyan.json +119 -0
- package/themes/newspaper/variants/chalk-indigo.json +124 -0
- package/themes/newspaper/variants/chalk-jade.json +95 -0
- package/themes/newspaper/variants/chalk-magenta.json +127 -0
- package/themes/newspaper/variants/frost-plum.json +123 -0
- package/themes/newspaper/variants/frost-sky.json +129 -0
- package/themes/newspaper/variants/frost-violet.json +96 -0
- package/themes/newspaper/variants/lilac-coral.json +124 -0
- package/themes/newspaper/variants/lilac-green.json +127 -0
- package/themes/newspaper/variants/sage-blue.json +124 -0
- package/themes/newspaper/variants/sage-lime.json +117 -0
- package/themes/newspaper/variants/sand-gold.json +124 -0
- package/themes/newspaper/variants/sand-jade.json +123 -0
- package/themes/og-measure/slots.ts +88 -0
- package/themes/og-measure/theme.json +62 -0
- package/themes/og-measure/variants/chalk-orange.json +102 -0
- package/themes/og-measure/variants/cocoa-amber.json +138 -0
- package/themes/og-measure/variants/cocoa-cyan.json +138 -0
- package/themes/og-measure/variants/cocoa-gold.json +129 -0
- package/themes/og-measure/variants/cream-lime.json +137 -0
- package/themes/og-measure/variants/deep-jade.json +100 -0
- package/themes/og-measure/variants/dusk-violet.json +133 -0
- package/themes/og-measure/variants/forest-cyan.json +123 -0
- package/themes/og-measure/variants/frost-amber.json +129 -0
- package/themes/og-measure/variants/frost-cyan.json +133 -0
- package/themes/og-measure/variants/lilac-plum.json +127 -0
- package/themes/og-measure/variants/lilac-violet.json +139 -0
- package/themes/og-measure/variants/sage-green.json +137 -0
- package/themes/og-measure/variants/wine-jade.json +127 -0
- package/themes/og-measure/variants/wine-plum.json +127 -0
- package/themes/orbit-system/THEME.md +77 -0
- package/themes/orbit-system/effects.ts +665 -0
- package/themes/orbit-system/sample/assets/og/logo.svg +12 -0
- package/themes/orbit-system/sample/beats.json +1005 -0
- package/themes/orbit-system/sample/index.html +11 -0
- package/themes/orbit-system/sample/kf.json +59 -0
- package/themes/orbit-system/sample/lib/facts.ts +29 -0
- package/themes/orbit-system/sample/lib/orbit.ts +84 -0
- package/themes/orbit-system/sample/main.ts +9 -0
- package/themes/orbit-system/sample/scenes/end.ts +50 -0
- package/themes/orbit-system/sample/scenes/gather.ts +35 -0
- package/themes/orbit-system/sample/scenes/models.ts +49 -0
- package/themes/orbit-system/sample/scenes/price.ts +57 -0
- package/themes/orbit-system/sample/style.json +108 -0
- package/themes/orbit-system/slots.ts +174 -0
- package/themes/orbit-system/still.png +0 -0
- package/themes/orbit-system/style.json +108 -0
- package/themes/orbit-system/theme.json +142 -0
- package/themes/orbit-system/variants/cocoa-amber.json +122 -0
- package/themes/orbit-system/variants/cocoa-coral.json +127 -0
- package/themes/orbit-system/variants/cocoa-cyan.json +127 -0
- package/themes/orbit-system/variants/deep-cyan.json +132 -0
- package/themes/orbit-system/variants/deep-sky.json +122 -0
- package/themes/orbit-system/variants/dusk-magenta.json +121 -0
- package/themes/orbit-system/variants/dusk-plum-pretendard.json +121 -0
- package/themes/orbit-system/variants/dusk-plum.json +127 -0
- package/themes/orbit-system/variants/dusk-violet.json +122 -0
- package/themes/orbit-system/variants/forest-coral.json +126 -0
- package/themes/orbit-system/variants/forest-plum.json +94 -0
- package/themes/orbit-system/variants/navy-amber.json +127 -0
- package/themes/orbit-system/variants/wine-amber.json +127 -0
- package/themes/orbit-system/variants/wine-coral.json +123 -0
- package/themes/orbit-system/variants/wine-magenta.json +126 -0
- package/themes/origami-fold/THEME.md +74 -0
- package/themes/origami-fold/effects.ts +535 -0
- package/themes/origami-fold/sample/assets/og/logo.svg +12 -0
- package/themes/origami-fold/sample/beats.json +1005 -0
- package/themes/origami-fold/sample/index.html +11 -0
- package/themes/origami-fold/sample/kf.json +59 -0
- package/themes/origami-fold/sample/lib/facts.ts +29 -0
- package/themes/origami-fold/sample/lib/fold.ts +53 -0
- package/themes/origami-fold/sample/main.ts +9 -0
- package/themes/origami-fold/sample/scenes/end.ts +51 -0
- package/themes/origami-fold/sample/scenes/gather.ts +37 -0
- package/themes/origami-fold/sample/scenes/models.ts +46 -0
- package/themes/origami-fold/sample/scenes/price.ts +54 -0
- package/themes/origami-fold/sample/style.json +108 -0
- package/themes/origami-fold/slots.ts +176 -0
- package/themes/origami-fold/still.png +0 -0
- package/themes/origami-fold/style.json +108 -0
- package/themes/origami-fold/theme.json +123 -0
- package/themes/origami-fold/variants/deep-amber-sunflower.json +122 -0
- package/themes/origami-fold/variants/deep-amber.json +122 -0
- package/themes/origami-fold/variants/deep-orange.json +128 -0
- package/themes/origami-fold/variants/forest-coral.json +127 -0
- package/themes/origami-fold/variants/forest-pink.json +123 -0
- package/themes/origami-fold/variants/forest-rose.json +100 -0
- package/themes/origami-fold/variants/frost-amber-grain.json +127 -0
- package/themes/origami-fold/variants/frost-amber-paper.json +128 -0
- package/themes/origami-fold/variants/frost-amber.json +95 -0
- package/themes/origami-fold/variants/sage-coral-grain.json +100 -0
- package/themes/origami-fold/variants/sage-coral-sunflower.json +122 -0
- package/themes/origami-fold/variants/sage-coral.json +128 -0
- package/themes/origami-fold/variants/sage-pink-grain.json +127 -0
- package/themes/origami-fold/variants/sage-pink-round.json +124 -0
- package/themes/origami-fold/variants/sage-pink.json +127 -0
- package/themes/paper-cutout/THEME.md +63 -0
- package/themes/paper-cutout/effects.ts +609 -0
- package/themes/paper-cutout/sample/assets/og/logo.svg +12 -0
- package/themes/paper-cutout/sample/beats.json +1005 -0
- package/themes/paper-cutout/sample/index.html +11 -0
- package/themes/paper-cutout/sample/kf.json +59 -0
- package/themes/paper-cutout/sample/lib/facts.ts +29 -0
- package/themes/paper-cutout/sample/lib/paper.ts +91 -0
- package/themes/paper-cutout/sample/main.ts +9 -0
- package/themes/paper-cutout/sample/scenes/end.ts +54 -0
- package/themes/paper-cutout/sample/scenes/gather.ts +63 -0
- package/themes/paper-cutout/sample/scenes/models.ts +93 -0
- package/themes/paper-cutout/sample/scenes/price.ts +100 -0
- package/themes/paper-cutout/sample/style.json +104 -0
- package/themes/paper-cutout/slots.ts +87 -0
- package/themes/paper-cutout/still.png +0 -0
- package/themes/paper-cutout/style.json +104 -0
- package/themes/paper-cutout/theme.json +144 -0
- package/themes/paper-cutout/variants/blush-coral.json +122 -0
- package/themes/paper-cutout/variants/blush-violet.json +122 -0
- package/themes/paper-cutout/variants/chalk-teal.json +122 -0
- package/themes/paper-cutout/variants/cream-gold.json +122 -0
- package/themes/paper-cutout/variants/frost-blue.json +123 -0
- package/themes/paper-cutout/variants/frost-lime.json +121 -0
- package/themes/paper-cutout/variants/frost-plum.json +122 -0
- package/themes/paper-cutout/variants/lilac-blue.json +123 -0
- package/themes/paper-cutout/variants/lilac-gold.json +122 -0
- package/themes/paper-cutout/variants/sage-cyan.json +122 -0
- package/themes/paper-cutout/variants/sage-green.json +122 -0
- package/themes/paper-cutout/variants/sage-pink.json +122 -0
- package/themes/paper-cutout/variants/sand-blue.json +123 -0
- package/themes/paper-cutout/variants/sand-green.json +121 -0
- package/themes/paper-cutout/variants/sand-plum.json +120 -0
- package/themes/pixel-quest/THEME.md +57 -0
- package/themes/pixel-quest/effects.ts +491 -0
- package/themes/pixel-quest/kit.ts +232 -0
- package/themes/pixel-quest/sample/assets/og/logo.svg +12 -0
- package/themes/pixel-quest/sample/beats.json +1005 -0
- package/themes/pixel-quest/sample/index.html +11 -0
- package/themes/pixel-quest/sample/kf.json +59 -0
- package/themes/pixel-quest/sample/lib/facts.ts +29 -0
- package/themes/pixel-quest/sample/lib/game.ts +71 -0
- package/themes/pixel-quest/sample/lib/logo.ts +51 -0
- package/themes/pixel-quest/sample/main.ts +9 -0
- package/themes/pixel-quest/sample/scenes/end.ts +75 -0
- package/themes/pixel-quest/sample/scenes/gather.ts +65 -0
- package/themes/pixel-quest/sample/scenes/models.ts +104 -0
- package/themes/pixel-quest/sample/scenes/price.ts +118 -0
- package/themes/pixel-quest/sample/style.json +115 -0
- package/themes/pixel-quest/slots.ts +94 -0
- package/themes/pixel-quest/still.png +0 -0
- package/themes/pixel-quest/style.json +115 -0
- package/themes/pixel-quest/theme.json +143 -0
- package/themes/pixel-quest/variants/navy-gold-2.json +98 -0
- package/themes/pixel-quest/variants/navy-gold-3.json +127 -0
- package/themes/pixel-quest/variants/navy-gold-4.json +95 -0
- package/themes/pixel-quest/variants/navy-gold-pixel.json +132 -0
- package/themes/pixel-quest/variants/navy-gold-scan.json +99 -0
- package/themes/pixel-quest/variants/navy-gold.json +131 -0
- package/themes/pixel-quest/variants/navy-lime-2.json +93 -0
- package/themes/pixel-quest/variants/navy-lime-3.json +124 -0
- package/themes/pixel-quest/variants/navy-lime-pixel.json +126 -0
- package/themes/pixel-quest/variants/navy-lime-scan.json +97 -0
- package/themes/pixel-quest/variants/navy-lime.json +130 -0
- package/themes/receipt/THEME.md +72 -0
- package/themes/receipt/effects.ts +257 -0
- package/themes/receipt/sample/assets/og/logo.svg +12 -0
- package/themes/receipt/sample/beats.json +1005 -0
- package/themes/receipt/sample/index.html +11 -0
- package/themes/receipt/sample/kf.json +59 -0
- package/themes/receipt/sample/lib/facts.ts +29 -0
- package/themes/receipt/sample/lib/sheet.ts +52 -0
- package/themes/receipt/sample/main.ts +9 -0
- package/themes/receipt/sample/scenes/end.ts +44 -0
- package/themes/receipt/sample/scenes/gather.ts +57 -0
- package/themes/receipt/sample/scenes/models.ts +48 -0
- package/themes/receipt/sample/scenes/price.ts +64 -0
- package/themes/receipt/sample/style.json +104 -0
- package/themes/receipt/slots.ts +89 -0
- package/themes/receipt/still.png +0 -0
- package/themes/receipt/style.json +104 -0
- package/themes/receipt/theme.json +133 -0
- package/themes/receipt/variants/blush-coral.json +89 -0
- package/themes/receipt/variants/sand-coral-2.json +89 -0
- package/themes/receipt/variants/sand-coral-3.json +89 -0
- package/themes/receipt/variants/sand-coral-4.json +90 -0
- package/themes/receipt/variants/sand-coral-5.json +89 -0
- package/themes/receipt/variants/sand-coral-6.json +88 -0
- package/themes/receipt/variants/sand-coral-7.json +88 -0
- package/themes/receipt/variants/sand-coral-8.json +89 -0
- package/themes/receipt/variants/sand-coral-9.json +89 -0
- package/themes/receipt/variants/sand-coral.json +88 -0
- package/themes/receipt/variants/sand-orange-2.json +89 -0
- package/themes/receipt/variants/sand-orange-3.json +90 -0
- package/themes/receipt/variants/sand-orange-4.json +89 -0
- package/themes/receipt/variants/sand-orange-5.json +89 -0
- package/themes/receipt/variants/sand-orange.json +89 -0
- package/themes/riso-zine/slots.ts +81 -0
- package/themes/riso-zine/theme.json +66 -0
- package/themes/riso-zine/variants/blush-coral.json +120 -0
- package/themes/riso-zine/variants/blush-gold.json +120 -0
- package/themes/riso-zine/variants/blush-pink.json +115 -0
- package/themes/riso-zine/variants/chalk-cyan.json +120 -0
- package/themes/riso-zine/variants/chalk-lime.json +119 -0
- package/themes/riso-zine/variants/cream-amber.json +89 -0
- package/themes/riso-zine/variants/frost-blue.json +119 -0
- package/themes/riso-zine/variants/frost-coral.json +118 -0
- package/themes/riso-zine/variants/frost-cyan.json +120 -0
- package/themes/riso-zine/variants/frost-green.json +120 -0
- package/themes/riso-zine/variants/lilac-plum.json +118 -0
- package/themes/riso-zine/variants/lilac-violet.json +123 -0
- package/themes/riso-zine/variants/sage-green.json +119 -0
- package/themes/riso-zine/variants/sand-jade.json +120 -0
- package/themes/riso-zine/variants/sand-plum.json +113 -0
- package/themes/server-hall/THEME.md +58 -0
- package/themes/server-hall/effects.ts +455 -0
- package/themes/server-hall/kit.ts +138 -0
- package/themes/server-hall/sample/assets/og/logo.svg +12 -0
- package/themes/server-hall/sample/beats.json +1005 -0
- package/themes/server-hall/sample/index.html +11 -0
- package/themes/server-hall/sample/kf.json +59 -0
- package/themes/server-hall/sample/lib/facts.ts +29 -0
- package/themes/server-hall/sample/lib/hall.ts +81 -0
- package/themes/server-hall/sample/main.ts +9 -0
- package/themes/server-hall/sample/scenes/end.ts +57 -0
- package/themes/server-hall/sample/scenes/gather.ts +38 -0
- package/themes/server-hall/sample/scenes/models.ts +49 -0
- package/themes/server-hall/sample/scenes/price.ts +65 -0
- package/themes/server-hall/sample/style.json +107 -0
- package/themes/server-hall/slots.ts +106 -0
- package/themes/server-hall/still.png +0 -0
- package/themes/server-hall/style.json +107 -0
- package/themes/server-hall/theme.json +143 -0
- package/themes/server-hall/variants/navy-indigo.json +140 -0
- package/themes/server-hall/variants/night-indigo.json +120 -0
- package/themes/server-hall/variants/wine-plum.json +130 -0
- package/themes/server-hall/variants/wine-violet.json +103 -0
- package/themes/subway-map/THEME.md +64 -0
- package/themes/subway-map/effects.ts +662 -0
- package/themes/subway-map/sample/assets/og/logo.svg +12 -0
- package/themes/subway-map/sample/beats.json +1005 -0
- package/themes/subway-map/sample/index.html +11 -0
- package/themes/subway-map/sample/kf.json +59 -0
- package/themes/subway-map/sample/lib/facts.ts +29 -0
- package/themes/subway-map/sample/lib/map.ts +58 -0
- package/themes/subway-map/sample/main.ts +9 -0
- package/themes/subway-map/sample/scenes/end.ts +94 -0
- package/themes/subway-map/sample/scenes/gather.ts +64 -0
- package/themes/subway-map/sample/scenes/models.ts +117 -0
- package/themes/subway-map/sample/scenes/price.ts +119 -0
- package/themes/subway-map/sample/style.json +108 -0
- package/themes/subway-map/slots.ts +66 -0
- package/themes/subway-map/still.png +0 -0
- package/themes/subway-map/style.json +108 -0
- package/themes/subway-map/theme.json +161 -0
- package/themes/subway-map/variants/blush-jade.json +131 -0
- package/themes/subway-map/variants/chalk-jade.json +141 -0
- package/themes/subway-map/variants/deep-green.json +131 -0
- package/themes/subway-map/variants/deep-teal.json +132 -0
- package/themes/subway-map/variants/dusk-teal-bold.json +132 -0
- package/themes/subway-map/variants/dusk-teal.json +105 -0
- package/themes/subway-map/variants/forest-green.json +141 -0
- package/themes/subway-map/variants/forest-jade.json +131 -0
- package/themes/subway-map/variants/frost-jade.json +131 -0
- package/themes/subway-map/variants/frost-teal.json +137 -0
- package/themes/subway-map/variants/sage-teal.json +132 -0
- package/themes/subway-map/variants/slate-green.json +136 -0
- package/themes/subway-map/variants/slate-jade.json +131 -0
- package/themes/subway-map/variants/snow-jade.json +136 -0
- package/themes/subway-map/variants/wine-green.json +104 -0
- package/themes/swiss-poster/THEME.md +59 -0
- package/themes/swiss-poster/effects.ts +339 -0
- package/themes/swiss-poster/grid.ts +35 -0
- package/themes/swiss-poster/sample/assets/og/logo.svg +12 -0
- package/themes/swiss-poster/sample/beats.json +1005 -0
- package/themes/swiss-poster/sample/index.html +11 -0
- package/themes/swiss-poster/sample/kf.json +59 -0
- package/themes/swiss-poster/sample/lib/facts.ts +29 -0
- package/themes/swiss-poster/sample/lib/poster.ts +36 -0
- package/themes/swiss-poster/sample/main.ts +9 -0
- package/themes/swiss-poster/sample/scenes/end.ts +70 -0
- package/themes/swiss-poster/sample/scenes/gather.ts +68 -0
- package/themes/swiss-poster/sample/scenes/models.ts +63 -0
- package/themes/swiss-poster/sample/scenes/price.ts +74 -0
- package/themes/swiss-poster/sample/style.json +100 -0
- package/themes/swiss-poster/slots.ts +69 -0
- package/themes/swiss-poster/still.png +0 -0
- package/themes/swiss-poster/style.json +100 -0
- package/themes/swiss-poster/theme.json +131 -0
- package/themes/swiss-poster/variants/blush-green.json +116 -0
- package/themes/swiss-poster/variants/chalk-plum.json +121 -0
- package/themes/swiss-poster/variants/chalk-sky.json +90 -0
- package/themes/swiss-poster/variants/cocoa-cyan.json +117 -0
- package/themes/swiss-poster/variants/dusk-coral.json +89 -0
- package/themes/swiss-poster/variants/dusk-magenta.json +117 -0
- package/themes/swiss-poster/variants/forest-amber.json +127 -0
- package/themes/swiss-poster/variants/forest-blue.json +118 -0
- package/themes/swiss-poster/variants/forest-jade.json +89 -0
- package/themes/swiss-poster/variants/frost-gold.json +128 -0
- package/themes/swiss-poster/variants/frost-indigo.json +117 -0
- package/themes/swiss-poster/variants/sand-magenta.json +126 -0
- package/themes/swiss-poster/variants/slate-orange.json +122 -0
- package/themes/swiss-poster/variants/slate-pink.json +126 -0
- package/themes/swiss-poster/variants/slate-violet.json +89 -0
- package/themes/water-works/THEME.md +61 -0
- package/themes/water-works/effects.ts +609 -0
- package/themes/water-works/sample/assets/og/logo.svg +12 -0
- package/themes/water-works/sample/beats.json +1005 -0
- package/themes/water-works/sample/index.html +11 -0
- package/themes/water-works/sample/kf.json +59 -0
- package/themes/water-works/sample/lib/facts.ts +29 -0
- package/themes/water-works/sample/lib/hall.ts +22 -0
- package/themes/water-works/sample/main.ts +9 -0
- package/themes/water-works/sample/scenes/end.ts +55 -0
- package/themes/water-works/sample/scenes/gather.ts +42 -0
- package/themes/water-works/sample/scenes/models.ts +51 -0
- package/themes/water-works/sample/scenes/price.ts +66 -0
- package/themes/water-works/sample/style.json +105 -0
- package/themes/water-works/slots.ts +140 -0
- package/themes/water-works/still.png +0 -0
- package/themes/water-works/style.json +105 -0
- package/themes/water-works/theme.json +138 -0
- package/themes/water-works/variants/blush-rose.json +123 -0
- package/themes/water-works/variants/chalk-blue.json +124 -0
- package/themes/water-works/variants/chalk-cyan-2.json +96 -0
- package/themes/water-works/variants/chalk-cyan.json +123 -0
- package/themes/water-works/variants/chalk-gold-mono.json +122 -0
- package/themes/water-works/variants/chalk-gold.json +123 -0
- package/themes/water-works/variants/chalk-green.json +123 -0
- package/themes/water-works/variants/frost-indigo.json +123 -0
- package/themes/water-works/variants/frost-rose.json +132 -0
- package/themes/water-works/variants/lilac-violet-mono.json +124 -0
- package/themes/water-works/variants/lilac-violet.json +133 -0
- package/themes/water-works/variants/sage-amber.json +133 -0
- package/themes/water-works/variants/sage-green.json +133 -0
- package/themes/water-works/variants/sand-amber.json +123 -0
- package/themes/water-works/variants/sand-plum.json +121 -0
- package/themes/whiteboard/THEME.md +78 -0
- package/themes/whiteboard/effects.ts +561 -0
- package/themes/whiteboard/sample/assets/og/logo.svg +12 -0
- package/themes/whiteboard/sample/beats.json +1005 -0
- package/themes/whiteboard/sample/index.html +11 -0
- package/themes/whiteboard/sample/kf.json +59 -0
- package/themes/whiteboard/sample/lib/board.ts +93 -0
- package/themes/whiteboard/sample/lib/facts.ts +29 -0
- package/themes/whiteboard/sample/main.ts +9 -0
- package/themes/whiteboard/sample/scenes/end.ts +59 -0
- package/themes/whiteboard/sample/scenes/gather.ts +50 -0
- package/themes/whiteboard/sample/scenes/models.ts +56 -0
- package/themes/whiteboard/sample/scenes/price.ts +79 -0
- package/themes/whiteboard/sample/style.json +106 -0
- package/themes/whiteboard/slots.ts +152 -0
- package/themes/whiteboard/still.png +0 -0
- package/themes/whiteboard/style.json +106 -0
- package/themes/whiteboard/theme.json +143 -0
- package/themes/whiteboard/variants/chalk-coral-do.json +126 -0
- package/themes/whiteboard/variants/chalk-coral.json +132 -0
- package/themes/whiteboard/variants/cream-lime.json +131 -0
- package/themes/whiteboard/variants/cream-plum.json +103 -0
- package/themes/whiteboard/variants/frost-teal-do.json +127 -0
- package/themes/whiteboard/variants/frost-teal.json +127 -0
- package/themes/whiteboard/variants/lilac-indigo.json +132 -0
- package/themes/whiteboard/variants/lilac-jade.json +103 -0
- package/themes/whiteboard/variants/sage-green.json +130 -0
- package/themes/whiteboard/variants/sage-plum.json +126 -0
- package/themes/whiteboard/variants/snow-amber.json +99 -0
- package/themes/whiteboard/variants/snow-cyan.json +132 -0
- package/themes/whiteboard/variants/snow-green.json +126 -0
- package/themes/whiteboard/variants/snow-magenta.json +126 -0
- package/themes/whiteboard/variants/snow-plum.json +130 -0
|
@@ -1,179 +1,462 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { basename, extname, join, resolve } from "node:path";
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { genImage, genVideo, hasKey, NeedsApproval, seam, seamFrames, still, whereToPut } from "@kasaframe/gen";
|
|
7
|
-
import { attachStudio, chat, codeBlock, detect, formatLook, jsonBlock, listTemplates, loadTemplate, look, lookSheet, type Message, type Plan, projectBrief, PURPOSES, type Purpose, readProject, writeCaptions } from "@kasaframe/plan";
|
|
6
|
+
import { askTerminal, blenderLibDir, capabilities, genImage, genVideo, hasKey, NeedsApproval, parseAllow, type Question, questionsText, seam, seamFrames, settle, still, whereToPut } from "@kasaframe/gen";
|
|
7
|
+
import { applyAnswers, attachStudio, type LookInput, loadSeries, suggestThemes, usableTheme, type Series, seriesFromRequest, chat, codeBlock, detect, formatLook, jsonBlock, listTemplates, loadTemplate, look, lookSheet, makePlan, type Message, type Plan, planQuestions, premiumBlock, projectBrief, PURPOSES, type Purpose, readProject, spend, spentLine, WAIT, writeCaptions, writeStats } from "@kasaframe/plan";
|
|
8
|
+
import { checkContent, contentPrompt, directBrief } from "@kasaframe/director";
|
|
8
9
|
import { transform } from "esbuild";
|
|
9
|
-
import {
|
|
10
|
+
import { bundleFor, dressSeed, pickDress } from "@kasaframe/motion/menu";
|
|
11
|
+
import { themeStyle, worldOf } from "@kasaframe/themes";
|
|
10
12
|
import { captionMode } from "@kasaframe/motion/captions";
|
|
13
|
+
import { jizuraCheck } from "@kasaframe/jizura/install";
|
|
11
14
|
import { openPage } from "../browser.ts";
|
|
15
|
+
import { askThemeTerminal, candidateSheet, refLook } from "../pick.ts";
|
|
16
|
+
import { librosa } from "../deps.ts";
|
|
17
|
+
import { lyricsCheck } from "./lyrics.ts";
|
|
12
18
|
import { type Command, num, parseFlags } from "../command.ts";
|
|
19
|
+
import { acquireRenderLock } from "../lock.ts";
|
|
20
|
+
import { gate, jobLimit, pool } from "../pool.ts";
|
|
13
21
|
|
|
14
22
|
const kfBin = fileURLToPath(new URL("../kf.ts", import.meta.url));
|
|
23
|
+
const repo = fileURLToPath(new URL("../../../../", import.meta.url));
|
|
24
|
+
|
|
25
|
+
// 박자(librosa)·뮤비 가사(JIZURA·정렬) — 없으면 넣는 법·지금 되는 다른 길·빼기를 묻는다
|
|
26
|
+
function toolQuestions(purpose: Purpose, willSong: boolean): Question[] {
|
|
27
|
+
const out: Question[] = [];
|
|
28
|
+
const b = librosa();
|
|
29
|
+
if (willSong && !b.ok)
|
|
30
|
+
out.push({
|
|
31
|
+
id: "beats", cap: "beats", header: "박자 분석", flag: "--beats", missing: true, default: WAIT, skip: "none",
|
|
32
|
+
ask: `곡에 장면 경계·움직임을 맞출 박자 분석(librosa)이 없다 — ${b.detail}`,
|
|
33
|
+
choices: [{ value: WAIT, label: "깔고 다시", note: b.fix ?? "kf setup beats" }, { value: "none", label: "곡 없이", note: "곡을 빼고 화면 안 움직임으로만 — 박 없이 곡만 까는 길은 없다" }],
|
|
34
|
+
});
|
|
35
|
+
if (purpose === "mv") {
|
|
36
|
+
const j = jizuraCheck();
|
|
37
|
+
const l = lyricsCheck();
|
|
38
|
+
if (!j.ok || !l.ok)
|
|
39
|
+
out.push({
|
|
40
|
+
id: "lyrics", cap: "lyrics", header: "가사", flag: "--lyrics", missing: true, default: WAIT, skip: "none",
|
|
41
|
+
ask: `뮤비 가사에 쓸 ${[!j.ok ? "가사 층 엔진(JIZURA)" : "", !l.ok ? "가사 정렬(demucs·stable-ts)" : ""].filter(Boolean).join("·")} 이 없다`,
|
|
42
|
+
choices: [
|
|
43
|
+
{ value: WAIT, label: "깔고 다시", note: `kf setup ${[!j.ok ? "jizura" : "", !l.ok ? "lyrics" : ""].filter(Boolean).join(" ")}` },
|
|
44
|
+
...(l.ok && !j.ok ? [{ value: "words", label: "가사 층 없이 낱말로", note: "정렬한 낱말을 DOM 글로 띄운다 — JIZURA 레이아웃·효과는 없다" }] : []),
|
|
45
|
+
{ value: "none", label: "가사 없이", note: "곡과 화면만" },
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 시리즈가 박은 답(목소리·곡 파일·생성 답) — 질문 플래그 값으로 넣어 묻지 않는다
|
|
53
|
+
function seriesAnswers(s: Series | null): Record<string, string> {
|
|
54
|
+
if (!s) return {};
|
|
55
|
+
return Object.fromEntries(Object.entries({ ...s.answers, voice: s.voice, music: s.music?.file && resolve(repo, s.music.file) }).filter((e): e is [string, string] => typeof e[1] === "string"));
|
|
56
|
+
}
|
|
15
57
|
|
|
16
58
|
// Claude Code 를 OG 로 다른 모델(딥시크 등)에 붙이면 OG /v1/messages 가 그림을 버린다 — 세션이 kf sheet 를 Read 해도 못 본다.
|
|
17
59
|
// 그런 「눈 없는 세션」이면 --finish 끝에 --vision-model 을 알려 준다(그림 보는 모델은 기본으로 부르지 않는다)
|
|
18
60
|
export const eyelessSession = (): boolean =>
|
|
19
61
|
/opengateway/i.test(process.env.ANTHROPIC_BASE_URL ?? "") && !/^anthropic\//.test(process.env.ANTHROPIC_MODEL ?? "anthropic/");
|
|
20
62
|
const AUDIO = /\.(mp3|wav|m4a|aac|flac|ogg)$/i;
|
|
21
|
-
const DEFAULT_MODEL = "deepseek/deepseek-v4-pro";
|
|
22
63
|
const DEFAULT_VISION = "google/gemini-3.5-flash";
|
|
23
64
|
// 화면에 찍히면 안 되는 글 — 지난 실험에서 실제로 나온 것들
|
|
24
65
|
const BAD_TEXT = /\bundefined\b|\bNaN\b|\[object Object\]|\bnull\b|�/;
|
|
25
66
|
|
|
26
67
|
const cmd: Command = {
|
|
27
68
|
summary: "요청 한 줄로 끝까지 — 용도 판별·계획·생성 자산·장면 작성·렌더·검사·고치기",
|
|
28
|
-
usage: `kf make "<요청>" [--out 폴더] [--assets 폴더] [--music 곡] [--model claude-code|<OG 모델>] [--purpose 용도] [--title 이름] [--yes]
|
|
69
|
+
usage: `kf make "<요청>" [--out 폴더] [--assets 폴더] [--music 곡] [--model claude-code|<OG 모델>] [--purpose 용도] [--theme id|auto] [--title 이름] [--yes]
|
|
29
70
|
kf make --finish <폴더> [--model ...] 장면을 다 채운 뒤 렌더·검사·표지(Claude Code 모드의 둘째 단계)
|
|
30
|
-
kf make --rewrite <폴더> --scenes a,b [--model <OG 모델>] 고른 장면만 다시 쓰고
|
|
71
|
+
kf make --rewrite <폴더> --scenes a,b [--model <OG 모델>] 고른 장면만 다시 쓰고 그 조각만 다시 굽고 그 구간만 검사(작업실 장면 카드)
|
|
72
|
+
감독 프로젝트는 모델 없이 kf direct --scenes 로 다시 짓는다(--pattern a=<패턴>·--scene-seed a=<글> 로 다른 후보)
|
|
73
|
+
모델을 안 주면 그 일을 만든 모델(plan.json 의 model) — 장면 코드를 쓰는 길인데 그것도 없으면 멈춘다
|
|
74
|
+
kf make --finish <폴더> --scenes a,b 장면 파일을 손으로 고친 뒤 — 바뀐 조각만 다시 굽고 그 구간만 검사
|
|
75
|
+
kf make --answer <폴더> --image <값> --video <값> … Claude Code 모드에서 MAKE.md 맨 위 질문의 답을 넣는다(계획·지시서를 고쳐 쓴다)
|
|
76
|
+
|
|
77
|
+
생성 능력(kf doctor 「생성 능력」): 계획은 그림 컷·주인공 3D 샷(Blender)·이음매 3D 전환(three)·캐릭터가 움직이는 컷(영상 모델)을
|
|
78
|
+
기본으로 넣는다. 돈이 들거나 없는 능력은 만들기 전에 한 번에 묻는다 — Claude Code 모드는 MAKE.md 맨 위, OG 모드는 터미널에서
|
|
79
|
+
(터미널이 아니면 보기와 다시 부를 플래그를 찍고 종료 코드 3)
|
|
80
|
+
--image og-…|codex-imagegen|none · --video <공급자>|none · --blender three|none 그 질문에 미리 답한다
|
|
81
|
+
--voice <엔진>:<목소리>|none 내레이션 목소리(쇼츠·설명 — 보기는 로컬 Qwen3(KF_QWEN3_TTS_URL 있을 때)·ElevenLabs·자막만·그 밖 순, 키 없는 보기는 넣는 법. kf narrate --voices)
|
|
82
|
+
--music <곡 파일>|gen:<공급자>|none 곡(쇼츠·설명은 분위기 맞는 곡 모음 곡을 묻지 않고 고르고, 없을 때만 생성·곡 넣기·없음을 묻는다)
|
|
83
|
+
--allow image,video,… 그 능력은 권장 답으로, 목록에 없는 능력은 돈 안 드는 길로 — 묻지 않는다
|
|
31
84
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
85
|
+
감독 모드(기본, 뮤비 빼고): 모델은 content.json(장면마다 type·칸·들릴 말·출처)만 쓰고, kf direct 가 검증된 장면 문법을
|
|
86
|
+
골라 장면 파일을 조립한다(LLM 이 장면 코드를 안 쓴다 — 어떤 모델이든 content.json 만 맞으면 화면 품질이 같다)
|
|
87
|
+
--no-direct 감독 없이 모델이 장면 코드를 직접 쓰는 길(뮤비는 늘 이 길)
|
|
88
|
+
--model claude-code(기본, 어디서 부르든): 지시서(MAKE.md)까지 만들고 멈춘다 — 감독 모드면 세션이 content.json 을 쓰고
|
|
89
|
+
kf direct → --finish, --no-direct 면 세션이 장면을 채우고 --finish. OG 를 한 번도 부르지 않는다
|
|
90
|
+
<OG 모델>: 짚을 때만 — OG chat/completions 로 content.json(--no-direct 면 장면)을 쓰고 렌더·검사까지
|
|
35
91
|
--vision-model 그림 보는 검사 모델(기본 none — 세션이 kf look 의 장면 모음·확인표를 직접 본다). 그림을 못 보는 세션만 ${DEFAULT_VISION} 같은 모델을 준다
|
|
92
|
+
--allow-premium Opus·GPT 계열 OG 모델(--model·--vision-model·--rewrite)을 푼다(KF_ALLOW_PREMIUM=1 과 같다). 없으면 부르기 전에 막는다
|
|
93
|
+
끝날 때(성공·실패·중단) <작업 폴더>/make-stats.json 에 단계별 모델·ms·토큰·usd 와 합계를 남기고 「OG 호출 N번 · 토큰 · $」 한 줄을 찍는다
|
|
94
|
+
--series 시리즈 묶음(series/<id>.json) · none. 안 주면 요청이 짚은 시리즈(「OG 쇼츠」처럼) — 용도·테마·목소리·곡·자막·길이·끝 카드·생성 답을 묻지 않고 그 묶음대로
|
|
36
95
|
--purpose 용도를 못 박는다: ${PURPOSES.join(" · ")}
|
|
37
|
-
--theme 테마 id · auto(
|
|
38
|
-
|
|
96
|
+
--theme 테마 id·변주 「<세계>/<변주>」 · auto(요청이 「리소 느낌」처럼 짚은 테마, 아니면 요청·분위기에 맞는 approved 후보 중 첫째) · none(프리셋만)
|
|
97
|
+
안 주면(시리즈가 정하지도 않았으면) 묻는다 — 보기는 「알아서」(= auto)와 요청·용도·분위기 낱말에 맞는 approved 세계·변주 넷,
|
|
98
|
+
넷만 담은 견본 시트를 그 자리에서 만든다(칸은 캐시). 테마 이름이 아닌 말을 주면 분위기 한 마디로 읽는다
|
|
99
|
+
--theme-mood 분위기 한 마디(「따뜻한 종이 느낌」·「어두운 네온」) — 그걸로 후보 넷을 다시 고른다. --answer 와 함께 주면 다시 묻는다
|
|
100
|
+
--theme-ref 레퍼런스 그림 경로·그림 주소·웹 페이지 주소 — 바탕·강조색을 읽어 후보 넷을 다시 고른다
|
|
101
|
+
--yes 묻지 않고 권장 답으로 — 돈이 드는 그림·영상도 승인한다(지역 제한이 걸린 H3 는 --video h3-pod 로 짚어야만)
|
|
39
102
|
--max-usd 영상 하나 상한(기본 1)
|
|
40
103
|
--video-provider 영상 공급자를 못 박는다(예: h3-pod — KF_H3_URL·KF_H3_STEPS 필요). 기본은 KF_VIDEO_PROVIDER, 없으면 생성 담당 기본
|
|
41
104
|
--fix 검사가 떨어졌을 때 고치기 횟수(기본 2)
|
|
42
105
|
--captions on|off|fit 화면 자막(fit 은 장면을 자막 띠 위로 줄여 올린다) — 새로 만들 땐 kf.json 에 적고, --finish 에선 그 판만 덮는다(다시 만들지 않고 켜고 끄기). srt·vtt 는 둘 다 낸다
|
|
43
106
|
--suffix [글] --finish 결과 이름 뒤에 붙인다(값 없이면 -자막 · -자막없음) — 원본 video.mp4 를 덮지 않고 둘 다 남긴다
|
|
44
|
-
--no-gen 생성 모델을 부르지
|
|
107
|
+
--no-gen 생성 모델을 부르지 않는다 — 묻지 않고 그림·영상 모델은 빼고, Blender 가 없으면 three 로 대신한다
|
|
45
108
|
|
|
46
109
|
용도를 못 정하면 터미널에서 묻고, 터미널이 아니면 보기를 찍고 종료 코드 3.`,
|
|
47
110
|
async run(argv) {
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
111
|
+
// 장부는 어느 길로 끝나든(성공·실패·throw·Ctrl-C) 한 번 — 작업 폴더가 서기 전에 끝났으면 줄만 찍는다
|
|
112
|
+
const at: Track = {};
|
|
113
|
+
let code: number | string = "오류";
|
|
114
|
+
let closed = false;
|
|
115
|
+
const close = () => {
|
|
116
|
+
if (closed || at.quiet) return;
|
|
117
|
+
closed = true;
|
|
118
|
+
if (at.dir && existsSync(at.dir)) writeStats(at.dir, { command: at.command ?? "make", model: at.model ?? "", code });
|
|
119
|
+
console.log(`\n${spentLine()}`);
|
|
120
|
+
};
|
|
121
|
+
const stop = (sig: NodeJS.Signals) => {
|
|
122
|
+
code = sig;
|
|
123
|
+
close();
|
|
124
|
+
process.exit(sig === "SIGINT" ? 130 : 143);
|
|
125
|
+
};
|
|
126
|
+
process.once("SIGINT", stop);
|
|
127
|
+
process.once("SIGTERM", stop);
|
|
128
|
+
try {
|
|
129
|
+
code = await make(argv, at);
|
|
130
|
+
return code;
|
|
131
|
+
} finally {
|
|
132
|
+
process.off("SIGINT", stop);
|
|
133
|
+
process.off("SIGTERM", stop);
|
|
134
|
+
close();
|
|
57
135
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
export default cmd;
|
|
139
|
+
|
|
140
|
+
interface Track {
|
|
141
|
+
dir?: string;
|
|
142
|
+
model?: string;
|
|
143
|
+
command?: string;
|
|
144
|
+
quiet?: boolean;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function make(argv: string[], at: Track): Promise<number> {
|
|
148
|
+
const { args, opts } = parseFlags(argv);
|
|
149
|
+
const str = (k: string) => (typeof opts[k] === "string" ? (opts[k] as string) : undefined);
|
|
150
|
+
// OG 모델은 짚을 때만 — 어디서 부르든(Claude Code 안팎·작업실) 기본은 지시서까지 만들고 세션이 잇는 claude-code
|
|
151
|
+
const model = str("model") ?? "claude-code";
|
|
152
|
+
const vision = str("vision-model") ?? "none";
|
|
153
|
+
at.model = model;
|
|
154
|
+
if (opts["allow-premium"]) process.env.KF_ALLOW_PREMIUM = "1";
|
|
155
|
+
for (const m of [model, vision]) {
|
|
156
|
+
const why = m !== "claude-code" && m !== "none" ? premiumBlock(m) : null;
|
|
157
|
+
if (why) {
|
|
158
|
+
console.error(`kf make: ${why}`);
|
|
62
159
|
return 2;
|
|
63
160
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
161
|
+
}
|
|
162
|
+
// OG 모드는 장면 쓰기·그림 보기까지 OG 를 부른다 — 뼈대만 만들고 죽지 않게 처음에 막는다
|
|
163
|
+
if (model !== "claude-code" && !hasKey("opengateway")) {
|
|
164
|
+
console.error(`kf make: OG 키가 없다 — ${whereToPut("opengateway")}. 키 없이는 --model 을 빼고(claude-code) — 장면은 세션이 쓰고 그림 생성은 건너뛴다`);
|
|
165
|
+
return 2;
|
|
166
|
+
}
|
|
167
|
+
const fixes = num(opts.fix, 2);
|
|
168
|
+
const captions = str("captions");
|
|
169
|
+
if (captions !== undefined && captions !== "on" && captions !== "off" && captions !== "fit") {
|
|
170
|
+
console.error(`kf make: --captions 는 on·off·fit — ${captions}`);
|
|
171
|
+
return 2;
|
|
172
|
+
}
|
|
173
|
+
const suffix = str("suffix") ?? (opts.suffix ? (captions === "off" ? "-자막없음" : "-자막") : "");
|
|
174
|
+
|
|
175
|
+
if (opts.answer) {
|
|
176
|
+
at.quiet = true;
|
|
177
|
+
const dir = resolve(typeof opts.answer === "string" ? opts.answer : (args[0] ?? "."));
|
|
178
|
+
return answerProject(dir, opts);
|
|
179
|
+
}
|
|
180
|
+
if (opts.rewrite) {
|
|
181
|
+
const dir = resolve(typeof opts.rewrite === "string" ? opts.rewrite : (args[0] ?? "."));
|
|
182
|
+
Object.assign(at, { dir, command: "rewrite" });
|
|
183
|
+
const ids = (str("scenes") ?? "").split(",").filter(Boolean);
|
|
184
|
+
if (!ids.length) {
|
|
185
|
+
console.error("kf make --rewrite: --scenes a,b 가 필요하다");
|
|
186
|
+
return 2;
|
|
76
187
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
const code = await finish(dir, { model, vision, fixes, gen: opts["no-gen"] ? undefined : gen, captions, suffix });
|
|
86
|
-
await unmirror?.();
|
|
87
|
-
await studio?.send({ done: true, ok: code === 0 });
|
|
88
|
-
return code;
|
|
188
|
+
// 감독 프로젝트 — 모델 없이 그 장면만 다시 짓는다(다른 장면 파일은 바이트 그대로라 그 조각은 다시 안 굽는다)
|
|
189
|
+
if (isDirect(dir)) {
|
|
190
|
+
const pick = ["pattern", "scene-seed"].flatMap((k) => (str(k) ? [`--${k}`, str(k)!] : []));
|
|
191
|
+
// 들릴 말을 고쳤을 때만 다시 읽힌다 — 목소리가 장면 길이를 정해서 뒤 장면 시각이 움직인다(그 조각들도 다시 굽힌다)
|
|
192
|
+
const narrate = sayChanged(dir, ids) ? [] : ["--no-narrate"];
|
|
193
|
+
const d = await relay(["direct", dir, "--scenes", ids.join(","), ...narrate, "--no-check", ...pick]);
|
|
194
|
+
if (d !== 0) return d;
|
|
195
|
+
return finish(dir, { model: "direct", vision, fixes: 0, scenes: ids });
|
|
89
196
|
}
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
if (!
|
|
93
|
-
console.error(
|
|
197
|
+
// 다시 쓰기는 그 일을 만든 모델로 — 기본 모델로 몰래 바꾸지 않는다
|
|
198
|
+
const own = str("model") ?? readPlan(dir).model;
|
|
199
|
+
if (!own || own === "claude-code") {
|
|
200
|
+
console.error(`kf make --rewrite: 장면 코드를 다시 쓸 OG 모델이 없다 — --model <OG 모델>로 주거나, 세션이 ${ids.join(",")} 장면 파일을 고치고 kf make --finish ${dir} --scenes ${ids.join(",")}`);
|
|
94
201
|
return 2;
|
|
95
202
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (!purpose) {
|
|
102
|
-
if (det.ambiguous) {
|
|
103
|
-
purpose = await ask(det.ranked.slice(0, 4).map((r) => r.purpose));
|
|
104
|
-
if (!purpose) {
|
|
105
|
-
console.error(`kf make: 용도를 못 정했다(확신 ${det.confidence}). --purpose 로 골라 다시: ${det.ranked.slice(0, 4).map((r) => `${r.purpose}(${r.p})`).join(" · ")}`);
|
|
106
|
-
return 3;
|
|
107
|
-
}
|
|
108
|
-
} else purpose = det.purpose;
|
|
203
|
+
at.model = own;
|
|
204
|
+
// 그 일을 만든 모델이 비싼 계열이면 다시 쓰기도 같은 문에서 막는다
|
|
205
|
+
if (premiumBlock(own)) {
|
|
206
|
+
console.error(`kf make --rewrite: ${premiumBlock(own)}`);
|
|
207
|
+
return 2;
|
|
109
208
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const dir = resolve(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const theme = str("theme") ?? "auto";
|
|
121
|
-
if (theme !== "none") newArgs.push("--theme", theme);
|
|
122
|
-
if (opts.force) newArgs.push("--force");
|
|
123
|
-
const made = spawnSync(process.execPath, newArgs, { stdio: ["ignore", "inherit", "inherit"] });
|
|
124
|
-
if (made.status !== 0) return made.status ?? 1;
|
|
125
|
-
|
|
126
|
-
const studio = await attachStudio({ dir, request, model, purpose });
|
|
209
|
+
writeFileSync(join(dir, "MAKE.md"), projectBrief(dir));
|
|
210
|
+
await writeScenes(dir, own, ids);
|
|
211
|
+
return finish(dir, { model: own, vision, fixes, scenes: ids });
|
|
212
|
+
}
|
|
213
|
+
if (opts.finish) {
|
|
214
|
+
const dir = resolve(typeof opts.finish === "string" ? opts.finish : (args[0] ?? "."));
|
|
215
|
+
Object.assign(at, { dir, command: "finish" });
|
|
216
|
+
const plan = existsSync(join(dir, "plan.json")) ? JSON.parse(readFileSync(join(dir, "plan.json"), "utf8")) : {};
|
|
217
|
+
const gen = { approve: !!opts.yes || videoApproved(plan), maxUsd: num(opts["max-usd"], 1), videoProvider: str("video-provider") ?? process.env.KF_VIDEO_PROVIDER };
|
|
218
|
+
const studio = await attachStudio({ dir, request: plan.request ?? dir, model, purpose: plan.purpose });
|
|
127
219
|
const unmirror = studio?.mirror();
|
|
128
220
|
if (studio) console.log(`작업실: ${studio.url}`);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
221
|
+
await studio?.send({ phase: "render" });
|
|
222
|
+
const scenes = str("scenes")?.split(",").filter(Boolean);
|
|
223
|
+
const code = await finish(dir, { model, vision, fixes, gen: opts["no-gen"] ? undefined : gen, captions, suffix, ...(scenes?.length ? { scenes } : {}) });
|
|
224
|
+
await unmirror?.();
|
|
225
|
+
await studio?.send({ done: true, ok: code === 0 });
|
|
226
|
+
return code;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const request = args.join(" ").trim();
|
|
230
|
+
if (!request) {
|
|
231
|
+
console.error('kf make: 요청 글이 필요하다 — kf make "카사텀 홍보 영상 1분"');
|
|
232
|
+
return 2;
|
|
233
|
+
}
|
|
234
|
+
const assetFiles = str("assets") ? listFiles(resolve(str("assets")!)) : [];
|
|
235
|
+
let themeLook: LookInput;
|
|
236
|
+
try {
|
|
237
|
+
themeLook = await lookFlags(opts);
|
|
238
|
+
} catch (e) {
|
|
239
|
+
console.error(`kf make: ${(e as Error).message}`);
|
|
240
|
+
return 2;
|
|
241
|
+
}
|
|
242
|
+
// 시리즈가 먼저 — 같은 시리즈면 매번 같은 묶음. 사람이 따로 준 플래그는 그대로 이긴다
|
|
243
|
+
const series: Series | null = str("series") === "none" ? null : str("series") ? loadSeries(str("series")!) : seriesFromRequest(request);
|
|
244
|
+
if (series) console.error(`시리즈 ${series.id}(${series.label}) — 용도·테마·목소리·곡·자막·길이·끝 카드를 묶음대로`);
|
|
245
|
+
// --music 은 곡 파일, 아니면 음악 질문의 답(gen:<공급자>·none) — 답은 파일로 읽지 않는다
|
|
246
|
+
const musicFlag = str("music");
|
|
247
|
+
const musicAnswer = musicFlag !== undefined && /^(gen:|none$)/.test(musicFlag);
|
|
248
|
+
const given = musicFlag && !musicAnswer ? resolve(musicFlag) : (assetFiles.find((f) => AUDIO.test(f)) ?? (series?.music?.file ? resolve(repo, series.music.file) : undefined));
|
|
249
|
+
|
|
250
|
+
let purpose = (str("purpose") ?? series?.purpose) as Purpose | undefined;
|
|
251
|
+
const det = detect({ request, assets: assetFiles, music: given }, listTemplates());
|
|
252
|
+
if (!purpose) {
|
|
253
|
+
if (det.ambiguous) {
|
|
254
|
+
purpose = await ask(det.ranked.slice(0, 4).map((r) => r.purpose));
|
|
255
|
+
if (!purpose) {
|
|
256
|
+
console.error(`kf make: 용도를 못 정했다(확신 ${det.confidence}). --purpose 로 골라 다시: ${det.ranked.slice(0, 4).map((r) => `${r.purpose}(${r.p})`).join(" · ")}`);
|
|
257
|
+
return 3;
|
|
258
|
+
}
|
|
259
|
+
} else purpose = det.purpose;
|
|
260
|
+
}
|
|
261
|
+
const chosen = det.ranked.find((r) => r.purpose === purpose);
|
|
262
|
+
// 곡을 안 줬으면 곡이 끌고 가는 용도(홍보·공개)일 때만 곡 모음에서 고른다 — 쇼츠·설명에 아무 곡이나 깔리지 않게
|
|
263
|
+
// 분위기를 정한 틀(쇼츠·설명)은 곡을 묻기 틀이 고른다(분위기 맞는 곡 모음 곡 → 생성 → 없음)
|
|
264
|
+
const tplOf = loadTemplate(purpose);
|
|
265
|
+
const music = given ?? (!musicAnswer && tplOf.music === "required" && !tplOf.mood ? findLibraryMusic(request) : undefined);
|
|
266
|
+
console.error(`용도 ${purpose} (${series && !str("purpose") ? `시리즈 ${series.id}` : `확신 ${chosen?.p ?? 1}${chosen?.reasons.length ? ` — ${chosen.reasons.join(", ")}` : ""}`})`);
|
|
267
|
+
|
|
268
|
+
// 만들기 전에 묻는다 — 뼈대를 만들고 나서 멈추면 반쯤 만든 폴더가 남는다
|
|
269
|
+
const caps = await capabilities({ probe: true });
|
|
270
|
+
const draft = makePlan({ request, purpose, assets: assetFiles, music: given ?? null, series: series?.id ?? "none", look: themeLook });
|
|
271
|
+
const { questions, auto } = planQuestions(draft, caps);
|
|
272
|
+
// 테마를 물을 거면 후보 넷 견본 시트를 그 자리에서 — 질문 글에 시트 경로가 붙는다
|
|
273
|
+
const themeQ = questions.find((q) => q.id === "theme");
|
|
274
|
+
const flags = { ...seriesAnswers(series), ...opts };
|
|
275
|
+
const settled = settle(questions, { given: flags, yes: !!opts.yes, noGen: !!opts["no-gen"], allow: parseAllow(opts.allow) });
|
|
276
|
+
// 묻지 않고 정한 답(있는 공짜 능력)도 사람이 준 플래그·시리즈 답이 이긴다. --no-gen 이면 공짜라도 생성 모델(그림·영상)은 뺀다
|
|
277
|
+
const forced = Object.fromEntries(
|
|
278
|
+
Object.keys(auto).flatMap((id) => (typeof flags[id] === "string" ? [[id, flags[id] as string]] : opts["no-gen"] && (id === "image" || id === "video") ? [[id, "none"]] : [])),
|
|
279
|
+
);
|
|
280
|
+
let answers: Record<string, string> = { ...auto, ...forced, ...settled.answers };
|
|
281
|
+
let open = settled.open;
|
|
282
|
+
// 박자·가사 도구는 뼈대를 세울 때 쓴다 — 없으면 조용히 박 없이·낱말로 가지 않고, 모드와 상관없이 뼈대 전에 답을 받는다
|
|
283
|
+
const willSong = !!(music ?? (answers.music && !/^(gen:|none$|wait$)/.test(answers.music) ? answers.music : undefined));
|
|
284
|
+
const tools = toolQuestions(purpose, willSong);
|
|
285
|
+
const toolSettled = settle(tools, { given: flags, yes: !!opts.yes, noGen: !!opts["no-gen"], allow: parseAllow(opts.allow) });
|
|
286
|
+
questions.push(...tools);
|
|
287
|
+
answers = { ...answers, ...toolSettled.answers };
|
|
288
|
+
Object.assign(settled.how, toolSettled.how);
|
|
289
|
+
if (toolSettled.open.length) {
|
|
290
|
+
const asked = await askTerminal(toolSettled.open);
|
|
291
|
+
if (!asked) {
|
|
292
|
+
console.error(`kf make: 뼈대 전에 정할 것이 ${toolSettled.open.length}가지 — 사람에게 묻고 플래그로 답해 다시 부른다\n\n${questionsText(toolSettled.open)}`);
|
|
293
|
+
return 3;
|
|
140
294
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
295
|
+
answers = { ...answers, ...asked };
|
|
296
|
+
}
|
|
297
|
+
if (answers.beats === "none" && answers.music && !/^(gen:|none$)/.test(answers.music)) answers.music = "none";
|
|
298
|
+
if (themeQ && open.includes(themeQ)) await candidateSheet(themeQ, (l) => console.error(l));
|
|
299
|
+
// 터미널이면 테마는 따로 — 분위기 한 마디·그림 경로·주소를 치면 그걸로 후보를 다시 골라 다시 묻는다
|
|
300
|
+
if (themeQ && open.includes(themeQ) && model !== "claude-code") {
|
|
301
|
+
const picked = await askThemeTerminal(themeQ, async (r) => {
|
|
302
|
+
themeLook = { ...themeLook, ...r, ...(r.mood ? { mood: [themeLook.mood, r.mood].filter(Boolean).join(" ") } : {}) };
|
|
303
|
+
const q = planQuestions(makePlan({ request, purpose, assets: assetFiles, music: given ?? null, series: series?.id ?? "none", look: themeLook }), caps).questions.find((x) => x.id === "theme")!;
|
|
304
|
+
await candidateSheet(q, (l) => console.error(l));
|
|
305
|
+
return q;
|
|
306
|
+
});
|
|
307
|
+
if (picked) {
|
|
308
|
+
answers.theme = picked;
|
|
309
|
+
open = open.filter((q) => q !== themeQ);
|
|
144
310
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
311
|
+
}
|
|
312
|
+
if (open.length && model !== "claude-code") {
|
|
313
|
+
const asked = await askTerminal(open);
|
|
314
|
+
if (!asked) {
|
|
315
|
+
console.error(`kf make: 만들기 전에 정할 것이 ${open.length}가지 — 플래그로 답해 다시 부른다(--yes 면 권장, --no-gen 이면 돈 안 드는 길)\n\n${questionsText(open)}`);
|
|
316
|
+
return 3;
|
|
149
317
|
}
|
|
150
|
-
|
|
318
|
+
answers = { ...answers, ...asked };
|
|
319
|
+
open = [];
|
|
320
|
+
}
|
|
321
|
+
for (const [id, v] of Object.entries(answers)) console.error(`능력 ${id}: ${v}(${settled.how[id] === "flag" || forced[id] ? "플래그" : settled.how[id] ? `--${settled.how[id]}` : auto[id] ? "있어서 바로" : "물어서"})`);
|
|
322
|
+
const stops = questions.filter((q) => answers[q.id] === WAIT);
|
|
323
|
+
if (stops.length) {
|
|
324
|
+
console.error(`kf make: 켜고 다시 부른다 —\n${stops.map((q) => ` ${q.ask}: ${q.choices.find((c) => c.value === WAIT)?.note}`).join("\n")}`);
|
|
325
|
+
return 3;
|
|
326
|
+
}
|
|
151
327
|
|
|
152
|
-
|
|
328
|
+
const dir = resolve(str("out") ?? `${purpose}-${stamp()}`);
|
|
329
|
+
Object.assign(at, { dir, command: "make" });
|
|
330
|
+
const newArgs = [kfBin, "new", dir, "--template", purpose, "--request", request, "--series", series?.id ?? "none"];
|
|
331
|
+
if (series?.end?.line) newArgs.push("--line", series.end.line);
|
|
332
|
+
// 박자 분석을 빼기로 했으면 곡도 깔지 않는다 — 박 없이 곡만 까는 길은 없다
|
|
333
|
+
const song = answers.beats === "none" ? undefined : (music ?? (answers.music && !/^(gen:|none$|wait$)/.test(answers.music) ? answers.music : undefined));
|
|
334
|
+
if (song) newArgs.push("--music", song);
|
|
335
|
+
if (str("title")) newArgs.push("--title", str("title")!);
|
|
336
|
+
// 테마 질문의 답(플래그·터미널) → 아직 묻는 중이거나 묻지 않은 테마(시리즈·요청이 짚은 것)는 auto. 테마 없이 프리셋만 쓰려면 --theme none
|
|
337
|
+
// 「알아서」·묻는 중이면 후보 첫째 — 분위기·레퍼런스로 고른 넷이라 kf new 의 auto(요청 낱말만)와 다를 수 있다
|
|
338
|
+
const firstPick = themeQ?.choices.find((c) => c.value !== "auto")?.value;
|
|
339
|
+
const themeArg = answers.theme && answers.theme !== WAIT && answers.theme !== "auto" ? answers.theme : (str("theme") ?? (firstPick && (answers.theme === "auto" || open.includes(themeQ!)) ? firstPick : "auto"));
|
|
340
|
+
newArgs.push("--theme", themeArg);
|
|
341
|
+
if (opts.force) newArgs.push("--force");
|
|
342
|
+
const made = spawnSync(process.execPath, newArgs, { stdio: ["ignore", "inherit", "inherit"] });
|
|
343
|
+
if (made.status !== 0) return made.status ?? 1;
|
|
153
344
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
345
|
+
const studio = await attachStudio({ dir, request, model, purpose });
|
|
346
|
+
const unmirror = studio?.mirror();
|
|
347
|
+
if (studio) console.log(`작업실: ${studio.url}`);
|
|
348
|
+
const plan: Plan = { ...applyAnswers(JSON.parse(readFileSync(join(dir, "plan.json"), "utf8")), answers).plan, asks: open, ...(themeLook.mood || themeLook.ref ? { themeLook } : {}) };
|
|
349
|
+
// 새 프로젝트는 이 영상의 기본으로 적는다 — 계획 다듬기가 내레이션을 쓸지도 이걸 본다
|
|
350
|
+
if (captions) {
|
|
351
|
+
const kfPath = join(dir, "kf.json");
|
|
352
|
+
writeJson(kfPath, { ...JSON.parse(readFileSync(kfPath, "utf8")), captions });
|
|
353
|
+
plan.captions = captionMode(captions);
|
|
354
|
+
}
|
|
355
|
+
if (!str("purpose") && !series) {
|
|
356
|
+
plan.confidence = chosen?.p ?? det.confidence;
|
|
357
|
+
plan.alternatives = det.ranked.filter((r) => r.purpose !== purpose).slice(0, 2).map((r) => ({ purpose: r.purpose, p: r.p }));
|
|
358
|
+
plan.reasons = [...(chosen?.reasons ?? []), ...plan.reasons.filter((r) => r !== "사람이 정함")];
|
|
359
|
+
}
|
|
360
|
+
// 장면 다시 쓰기(--rewrite)가 같은 모델을 부른다
|
|
361
|
+
(plan as Plan & { model?: string }).model = model;
|
|
362
|
+
if (assetFiles.length) {
|
|
363
|
+
mkdirSync(join(dir, "assets", "in"), { recursive: true });
|
|
364
|
+
for (const f of assetFiles) if (!AUDIO.test(f)) copyFileSync(f, join(dir, "assets", "in", basename(f)));
|
|
365
|
+
}
|
|
366
|
+
writeJson(join(dir, "plan.json"), plan);
|
|
367
|
+
if (plan.theme) {
|
|
368
|
+
const pick = plan.themeCandidates?.find((c) => c.id === plan.theme);
|
|
369
|
+
console.error(`테마 ${plan.theme}${pick ? ` — ${pick.why}` : ""}${plan.themeCandidates && plan.themeCandidates.length > 1 ? ` (다른 후보: ${plan.themeCandidates.filter((c) => c.id !== plan.theme).map((c) => c.id).join(" · ")})` : ""}`);
|
|
370
|
+
}
|
|
371
|
+
if (plan.themeCandidates && plan.themeCandidates.length > 1) themeStills(dir, plan.themeCandidates.map((c) => c.id));
|
|
157
372
|
|
|
158
|
-
|
|
373
|
+
// 감독 모드 — 뮤비는 가사 층이 화면을 맡아 장면 문법을 안 쓴다
|
|
374
|
+
const direct = !opts["no-direct"] && purpose !== "mv";
|
|
375
|
+
if (direct) {
|
|
376
|
+
writeJson(join(dir, "plan.json"), { ...plan, direct: true });
|
|
377
|
+
writeFileSync(join(dir, "MAKE.md"), directBrief(dir));
|
|
159
378
|
if (model === "claude-code") {
|
|
160
|
-
|
|
161
|
-
console.log(`\n${dir}/MAKE.md 를 읽고
|
|
162
|
-
// 장면은 세션이 쓴다 — 작업실 미리보기가 저장할 때마다 따라온다
|
|
163
|
-
console.error("장면 쓰는 중(세션) — 저장할 때마다 작업실 미리보기에 바로 보인다");
|
|
379
|
+
if (open.length) console.log(`\n먼저 사람에게 물을 것 ${open.length}가지 — ${dir}/MAKE.md 맨 위 「사용자에게 먼저 물을 것」(AskUserQuestion), 답은 kf make --answer ${dir} ${open.map((q) => `${q.flag} <값>`).join(" ")}`);
|
|
380
|
+
console.log(`\n감독 모드 — ${dir}/MAKE.md 를 읽고 ${dir}/content.json 을 쓴다(장면 코드는 안 쓴다). 그다음:\n kf direct ${dir}\n kf look ${dir}\n kf make --finish ${dir}`);
|
|
164
381
|
await unmirror?.();
|
|
165
382
|
await studio?.send({ phase: "scenes" });
|
|
166
383
|
return 0;
|
|
167
384
|
}
|
|
168
|
-
|
|
169
|
-
|
|
385
|
+
if (!(await writeContent(dir, model))) {
|
|
386
|
+
console.error(`kf make: ${model} 이 맞는 content.json 을 못 냈다 — ${dir}/content.json 을 손보고 kf direct ${dir}`);
|
|
387
|
+
return 1;
|
|
388
|
+
}
|
|
389
|
+
const d = spawnSync(process.execPath, [kfBin, "direct", dir], { stdio: ["ignore", "inherit", "inherit"] });
|
|
390
|
+
if (d.status !== 0 && d.status !== 1) return d.status ?? 1;
|
|
391
|
+
const gen = { approve: !!opts.yes || videoApproved(plan), maxUsd: num(opts["max-usd"], 1), videoProvider: str("video-provider") ?? process.env.KF_VIDEO_PROVIDER };
|
|
170
392
|
const code = await finish(dir, { model, vision, fixes, gen: opts["no-gen"] ? undefined : gen });
|
|
171
393
|
await unmirror?.();
|
|
172
394
|
await studio?.send({ done: true, ok: code === 0 });
|
|
173
395
|
return code;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (model !== "claude-code") {
|
|
399
|
+
await refinePlan(dir, model, request);
|
|
400
|
+
// 내레이션 먼저 — 계획 다듬기가 쓴 장면별 문장을 읽혀 장면 길이를 목소리에 맞춘 뒤 장면을 쓴다
|
|
401
|
+
const code = narrateStep(dir);
|
|
402
|
+
if (code !== 0) return code;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const gen = { approve: !!opts.yes || videoApproved(plan), maxUsd: num(opts["max-usd"], 1), videoProvider: str("video-provider") ?? process.env.KF_VIDEO_PROVIDER };
|
|
406
|
+
// claude-code 모드는 그림·3D 샷을 세션이 만든다 — 막연한 자동 프롬프트로 엉뚱한 그림이 나왔다. 지시서에 목록과 고른 공급자로 낸다
|
|
407
|
+
if (model !== "claude-code") {
|
|
408
|
+
await genImages(dir);
|
|
409
|
+
await writeShots(dir, model);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
writeFileSync(join(dir, "MAKE.md"), projectBrief(dir));
|
|
413
|
+
if (model === "claude-code") {
|
|
414
|
+
const flags = [opts.yes ? "--yes" : "", gen.videoProvider ? `--video-provider ${gen.videoProvider}` : ""].filter(Boolean).join(" ");
|
|
415
|
+
if (open.length) console.log(`\n먼저 사람에게 물을 것 ${open.length}가지 — ${dir}/MAKE.md 맨 위 「사용자에게 먼저 물을 것」(AskUserQuestion), 답은 kf make --answer ${dir} ${open.map((q) => `${q.flag} <값>`).join(" ")}`);
|
|
416
|
+
console.log(`\n${dir}/MAKE.md 를 읽고 scenes/*.ts 를 채운다. 다 채우면(이음매 영상은 그때 앞뒤 장면 프레임으로 만든다):\n kf make --finish ${dir}${flags ? ` ${flags}` : ""}`);
|
|
417
|
+
// 장면은 세션이 쓴다 — 작업실 미리보기가 저장할 때마다 따라온다
|
|
418
|
+
console.error("장면 쓰는 중(세션) — 저장할 때마다 작업실 미리보기에 바로 보인다");
|
|
419
|
+
await unmirror?.();
|
|
420
|
+
await studio?.send({ phase: "scenes" });
|
|
421
|
+
return 0;
|
|
422
|
+
}
|
|
423
|
+
// 이음매는 틀의 3D 전환이 맡는다 — 모델이 다시 쓰지 않는다
|
|
424
|
+
await writeScenes(dir, model, readProject(dir).scenes.filter((s) => s.role !== "seam").map((s) => s.id));
|
|
425
|
+
const code = await finish(dir, { model, vision, fixes, gen: opts["no-gen"] ? undefined : gen });
|
|
426
|
+
await unmirror?.();
|
|
427
|
+
await studio?.send({ done: true, ok: code === 0 });
|
|
428
|
+
return code;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// --- 감독 모드: 모델은 content.json 하나만 — 칸이 틀리면 어느 칸인지 돌려줘 두 번까지 다시
|
|
432
|
+
|
|
433
|
+
function isDirect(dir: string): boolean {
|
|
434
|
+
const p = join(dir, "plan.json");
|
|
435
|
+
return existsSync(p) && JSON.parse(readFileSync(p, "utf8")).direct === true;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function writeContent(dir: string, model: string): Promise<boolean> {
|
|
439
|
+
const plan = JSON.parse(readFileSync(join(dir, "plan.json"), "utf8"));
|
|
440
|
+
let fix: string | undefined;
|
|
441
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
442
|
+
// 생각하는 모델은 생각에 토큰을 먼저 쓴다 — 상한이 낮으면 답 JSON 이 잘린다
|
|
443
|
+
const r = await chat(model, contentPrompt(dir, fix), { json: true, temperature: 0.5, maxTokens: 16000, stage: "content" });
|
|
444
|
+
const j = jsonBlock<Record<string, unknown>>(r.text);
|
|
445
|
+
if (!j) {
|
|
446
|
+
fix = "JSON 을 못 읽었다 — JSON 하나로만 답한다";
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
const c = checkContent({ ...j, request: plan.request });
|
|
450
|
+
console.error(`content.json ${attempt + 1}번째(${model}, ${(r.ms / 1000).toFixed(1)}초) — ${c.errors.length ? `칸 오류 ${c.errors.length}` : `장면 ${c.content.scenes.length}`}`);
|
|
451
|
+
if (c.errors.length) {
|
|
452
|
+
fix = c.errors.map((e) => `${e.at}: ${e.msg}`).join("\n");
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
writeJson(join(dir, "content.json"), { ...j, request: plan.request });
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
177
460
|
|
|
178
461
|
// --- 계획 다듬기: 틀이 정한 뼈대(시각·역할)는 그대로, 이름·끝 한 줄·장면 할 일·생성 프롬프트만 요청에 맞춘다
|
|
179
462
|
|
|
@@ -193,12 +476,12 @@ async function refinePlan(dir: string, model: string, request: string): Promise<
|
|
|
193
476
|
장면: ${JSON.stringify(plan.scenes.map((s) => ({ id: s.id, role: s.role, dur: s.dur, idea: s.idea })))}
|
|
194
477
|
생성 자산: ${JSON.stringify(plan.gen.map((g) => ({ id: g.id, kind: g.kind, scene: g.scene, prompt: g.prompt })))}
|
|
195
478
|
|
|
196
|
-
다음 꼴로: {"title": "제품·주제 이름(짧게)", "line": "끝 카드 한 줄(없으면 빈 글)", "scenes": [{"id": "...", "idea": "이 요청에 맞춘 구체적인 화면·움직임 한두 문장"}], "gen": [{"id": "...", "prompt": "영어 생성 프롬프트, 글자·로고 없이"}]${captionMode(kf.captions) !== "none" ? ', "captions": [{"scene": "장면 id", "text": "그 장면에서 들릴 한국어 내레이션 — 짧은 마디를
|
|
479
|
+
다음 꼴로: {"title": "제품·주제 이름(짧게)", "line": "끝 카드 한 줄(없으면 빈 글)", "scenes": [{"id": "...", "idea": "이 요청에 맞춘 구체적인 화면·움직임 한두 문장"}], "gen": [{"id": "...", "prompt": "영어 생성 프롬프트, 글자·로고 없이"}]${captionMode(kf.captions) !== "none" ? ', "captions": [{"scene": "장면 id", "text": "그 장면에서 들릴 한국어 내레이션 — 차분한 설명체, 짧은 마디를 쉼표로, 문장은 마침표로. 목소리가 장면 길이를 정하니 1초에 4–5글자(차분한 낭독)로 장면 길이에 가깝게, 훅은 한 마디(끝 장면·이음매는 비워도 된다)"}]' : ""}}`,
|
|
197
480
|
},
|
|
198
481
|
];
|
|
199
482
|
try {
|
|
200
483
|
// 생각하는 모델은 생각에 토큰을 먼저 쓴다 — 상한이 낮으면 답 JSON 이 잘린다
|
|
201
|
-
const r = await chat(model, ask, { json: true, temperature: 0.5, maxTokens: 16000 });
|
|
484
|
+
const r = await chat(model, ask, { json: true, temperature: 0.5, maxTokens: 16000, stage: "plan" });
|
|
202
485
|
const j = jsonBlock<{ title?: string; line?: string; scenes?: { id: string; idea: string }[]; gen?: { id: string; prompt: string }[]; captions?: { scene: string; text: string }[] }>(r.text);
|
|
203
486
|
if (!j) throw new Error(`JSON 을 못 읽었다(${r.text.length}자: ${r.text.slice(0, 160).replace(/\s+/g, " ")})`);
|
|
204
487
|
if (j.title) plan.title = kf.title = j.title;
|
|
@@ -221,19 +504,24 @@ async function refinePlan(dir: string, model: string, request: string): Promise<
|
|
|
221
504
|
}
|
|
222
505
|
}
|
|
223
506
|
|
|
224
|
-
// --- 장면 쓰기: 장면마다 파일 하나를 받아 놓고, 실제로 열어 오류·이상한 글이 없을 때까지
|
|
507
|
+
// --- 장면 쓰기: 장면마다 파일 하나를 받아 놓고, 실제로 열어 오류·이상한 글이 없을 때까지 돌려준다.
|
|
508
|
+
// 장면끼리는 동시에 쓴다(모델 호출이 대부분 기다림). 열어 보기는 한 번에 하나 — 페이지는 모든 장면을 같이 싣는다.
|
|
509
|
+
// 그래서 떨어진 판은 열어 본 뒤 바로 앞 파일로 되돌린다: 옆 장면의 열어 보기가 남의 깨진 판에 걸리지 않게
|
|
510
|
+
|
|
511
|
+
const probing = gate(1);
|
|
225
512
|
|
|
226
513
|
async function writeScenes(dir: string, model: string, ids: string[], feedback: Record<string, string> = {}): Promise<void> {
|
|
227
514
|
const brief = readFileSync(join(dir, "MAKE.md"), "utf8");
|
|
228
515
|
const kf = readProject(dir);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (!s) continue;
|
|
516
|
+
const scenes = ids.map((id) => kf.scenes.find((x) => x.id === id)).filter((s): s is NonNullable<typeof s> => !!s);
|
|
517
|
+
await pool(scenes, jobLimit(3), async (s) => {
|
|
232
518
|
const file = join(dir, s.file);
|
|
233
|
-
|
|
234
|
-
|
|
519
|
+
const before = existsSync(file) ? readFileSync(file, "utf8") : "";
|
|
520
|
+
let note = feedback[s.id] ?? "";
|
|
521
|
+
let tried = "";
|
|
522
|
+
console.error(`장면 ${s.id}: 쓰는 중 (${model})`);
|
|
235
523
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
236
|
-
const current =
|
|
524
|
+
const current = tried || before;
|
|
237
525
|
const messages: Message[] = [
|
|
238
526
|
{ role: "system", content: `너는 kasaframe 장면 파일을 쓴다. 아래 지시서를 지킨다. 답은 TypeScript 코드 블록 하나뿐.\n\n${brief}` },
|
|
239
527
|
{
|
|
@@ -241,7 +529,7 @@ async function writeScenes(dir: string, model: string, ids: string[], feedback:
|
|
|
241
529
|
content: `장면 ${s.id}(${s.file}, ${s.dur.toFixed(2)}초, 역할 ${s.role})의 파일 전체를 써라.\n할 일: ${s.idea}\n\n지금 파일:\n\`\`\`ts\n${current}\`\`\`${note ? `\n\n고칠 것:\n${note}` : ""}`,
|
|
242
530
|
},
|
|
243
531
|
];
|
|
244
|
-
const r = await chat(model, messages, { temperature: 0.6, maxTokens: 32000 });
|
|
532
|
+
const r = await chat(model, messages, { temperature: 0.6, maxTokens: 32000, stage: `scene:${s.id}` });
|
|
245
533
|
const code = r.finish === "length" ? null : codeBlock(r.text);
|
|
246
534
|
if (r.finish === "length") {
|
|
247
535
|
note = "답이 길이 상한에서 잘렸다. 군더더기 없이 더 짧게, ```ts 블록 하나로 파일 전체를 답하라.";
|
|
@@ -252,13 +540,25 @@ async function writeScenes(dir: string, model: string, ids: string[], feedback:
|
|
|
252
540
|
note = "코드 블록을 못 찾았다. ```ts 블록 하나로 파일 전체를 답하라.";
|
|
253
541
|
continue;
|
|
254
542
|
}
|
|
255
|
-
|
|
256
|
-
const problems = await
|
|
543
|
+
tried = code;
|
|
544
|
+
const problems = await probing(async () => {
|
|
545
|
+
writeFileSync(file, code);
|
|
546
|
+
const p = await probeScene(dir, s.id);
|
|
547
|
+
if (p.length) writeFileSync(file, before);
|
|
548
|
+
return p;
|
|
549
|
+
});
|
|
257
550
|
console.error(` ${s.id} ${attempt + 1}번째 ${(r.ms / 1000).toFixed(1)}초 — ${problems.length ? problems.join(" / ") : "통과"}`);
|
|
258
|
-
if (!problems.length)
|
|
551
|
+
if (!problems.length) {
|
|
552
|
+
console.error(`장면 ${s.id}: 씀`);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
259
555
|
note = problems.join("\n");
|
|
260
556
|
}
|
|
261
|
-
|
|
557
|
+
// 세 번 다 떨어지면 앞 파일 그대로 — 깨진 판이 통째 렌더를 멈추지 않게. 마지막 판은 out/failed 에 남긴다
|
|
558
|
+
mkdirSync(join(dir, "out", "failed"), { recursive: true });
|
|
559
|
+
if (tried) writeFileSync(join(dir, "out", "failed", `${s.id}.ts`), tried);
|
|
560
|
+
console.error(`장면 ${s.id}: 실패 세 번 다 검사에 걸림 — 앞 파일 그대로(마지막 판 out/failed/${s.id}.ts)`);
|
|
561
|
+
});
|
|
262
562
|
}
|
|
263
563
|
|
|
264
564
|
// 렌더 전에 장면 하나를 세 시각에서 열어 본다 — 통짜 렌더 1분을 쓰기 전에 잡을 것을 잡는다
|
|
@@ -312,6 +612,19 @@ async function probeScene(dir: string, id: string): Promise<string[]> {
|
|
|
312
612
|
return [...new Set(problems)];
|
|
313
613
|
}
|
|
314
614
|
|
|
615
|
+
// 테마 고르기 플래그 — --theme-mood·--theme-ref, 그리고 테마 이름이 아닌 --theme 값(분위기 한 마디로 읽는다)
|
|
616
|
+
async function lookFlags(opts: Record<string, string | boolean | undefined>): Promise<LookInput> {
|
|
617
|
+
const look: LookInput = {};
|
|
618
|
+
const theme = typeof opts.theme === "string" ? opts.theme : undefined;
|
|
619
|
+
if (theme && theme !== "auto" && theme !== "none" && !usableTheme(theme)) {
|
|
620
|
+
look.mood = theme;
|
|
621
|
+
delete opts.theme;
|
|
622
|
+
}
|
|
623
|
+
if (typeof opts["theme-mood"] === "string") look.mood = [look.mood, opts["theme-mood"]].filter(Boolean).join(" ");
|
|
624
|
+
if (typeof opts["theme-ref"] === "string") look.ref = await refLook(opts["theme-ref"]);
|
|
625
|
+
return look;
|
|
626
|
+
}
|
|
627
|
+
|
|
315
628
|
// 테마 후보마다 첫 장면을 그 테마로 한 장 — 사람이 셋을 나란히 보고 고른다. style.json 만 잠깐 바꿔 찍고 되돌린다
|
|
316
629
|
function themeStills(dir: string, ids: string[]): void {
|
|
317
630
|
const stylePath = join(dir, "style.json");
|
|
@@ -341,20 +654,29 @@ interface GenOpts {
|
|
|
341
654
|
|
|
342
655
|
const genLog = (l: string) => console.error(` ${l}`);
|
|
343
656
|
|
|
657
|
+
// 생성 자산도 make-stats.json 장부에 — 캐시에서 꺼낸 것은 이번에 돈을 안 썼다
|
|
658
|
+
function genSpent(a: Awaited<ReturnType<typeof genImage>>, stage: string): void {
|
|
659
|
+
if (a.cached) return;
|
|
660
|
+
spend({ model: `${a.meta.provider}/${a.meta.model}`, stage, ms: a.meta.elapsedMs, prompt: 0, completion: 0, cached: 0, usd: a.meta.costUsd, og: a.meta.provider.startsWith("og-") });
|
|
661
|
+
}
|
|
662
|
+
|
|
344
663
|
async function genImages(dir: string): Promise<void> {
|
|
345
664
|
const plan: Plan = JSON.parse(readFileSync(join(dir, "plan.json"), "utf8"));
|
|
346
665
|
const imgs = plan.gen.filter((g) => g.kind === "img");
|
|
347
666
|
if (!imgs.length) return;
|
|
348
667
|
console.error(`생성 자산 그림 ${imgs.length}개…`);
|
|
349
|
-
|
|
668
|
+
// 장당 상한·고른 공급자는 그대로, 부르기만 겹친다
|
|
669
|
+
await pool(imgs, jobLimit(3), async (g) => {
|
|
670
|
+
if (g.scene) console.error(`장면 ${g.scene}: 재료 — 그림 ${g.id}`);
|
|
350
671
|
try {
|
|
351
672
|
const size = g.size ?? (plan.aspect === "9:16" ? "1080x1920" : plan.aspect === "1:1" ? "1080x1080" : "1920x1080");
|
|
352
673
|
const a = await genImage(g.id, { prompt: g.prompt, size, transparent: g.transparent, quality: g.quality, refs: g.refs?.map((r) => join(dir, r)) }, { outDir: join(dir, "assets", "gen"), log: genLog }, g.provider);
|
|
674
|
+
genSpent(a, `gen:img:${g.id}`);
|
|
353
675
|
console.error(` gen:img:${g.id} ${a.cached ? "cached" : "made"} ${a.path} $${(a.cached ? 0 : a.meta.costUsd).toFixed(3)}`);
|
|
354
676
|
} catch (e) {
|
|
355
677
|
console.error(` gen:img:${g.id} ${e instanceof NeedsApproval ? "needs-approval" : "error"} ${e instanceof Error ? e.message : e}`);
|
|
356
678
|
}
|
|
357
|
-
}
|
|
679
|
+
});
|
|
358
680
|
}
|
|
359
681
|
|
|
360
682
|
async function genVideos(dir: string, o: GenOpts): Promise<void> {
|
|
@@ -363,25 +685,204 @@ async function genVideos(dir: string, o: GenOpts): Promise<void> {
|
|
|
363
685
|
if (!vids.length) return;
|
|
364
686
|
console.error(`생성 자산 영상 ${vids.length}개…`);
|
|
365
687
|
const ctx = { outDir: join(dir, "assets", "gen"), approve: o.approve, maxUsd: o.maxUsd, log: genLog };
|
|
366
|
-
|
|
688
|
+
// 영상 하나마다 승인·상한(maxUsd)을 따로 본다 — 겹쳐 불러도 묻기 틀은 그대로
|
|
689
|
+
await pool(vids, jobLimit(2), async (g) => {
|
|
367
690
|
const provider = o.videoProvider ?? g.provider;
|
|
691
|
+
console.error(`장면 ${g.scene ?? g.id}: 재료 — 영상 ${g.id}`);
|
|
368
692
|
try {
|
|
369
693
|
const a = g.seam
|
|
370
694
|
? await seam({ project: dir, scene: g.id, prompt: g.prompt, provider }, ctx)
|
|
371
695
|
: await genVideo(g.id, { prompt: g.prompt, duration: g.dur ?? 5, aspect: plan.aspect, firstFrame: g.first && join(dir, g.first), lastFrame: g.last && join(dir, g.last) }, ctx, provider);
|
|
696
|
+
genSpent(a, `gen:vid:${g.id}`);
|
|
372
697
|
console.error(` gen:vid:${g.id} ${a.cached ? "cached" : "made"} ${a.path} $${(a.cached ? 0 : a.meta.costUsd).toFixed(3)}`);
|
|
373
698
|
} catch (e) {
|
|
374
699
|
const tail = e instanceof NeedsApproval ? `needs-approval 견적 $${e.estimateUsd.toFixed(3)}(${e.provider}) — --yes 로 승인하면 만든다, 지금은 대체 화면` : `error ${e instanceof Error ? e.message : e}`;
|
|
375
700
|
console.error(` gen:vid:${g.id} ${tail}`);
|
|
376
701
|
}
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// 영상 모델은 사람이 공급자를 골랐을 때(또는 --yes)만 돈을 쓴다 — 아직 묻는 중이면 견적만
|
|
706
|
+
function videoApproved(plan: Partial<Plan>): boolean {
|
|
707
|
+
const v = plan.answers?.video;
|
|
708
|
+
return !!v && v !== "none" && v !== WAIT && !(plan.asks ?? []).some((q) => q.id === "video");
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// --- 주인공 3D 샷(OG 모드): 모델이 kfb 헬퍼로 샷 스크립트를 쓰고, 몇 장 찍어 보아 돌면 굽는다. 세 번 안에 못 서면 three 로 물러난다
|
|
712
|
+
|
|
713
|
+
function shotGuide(): string {
|
|
714
|
+
const lib = blenderLibDir();
|
|
715
|
+
const readme = readFileSync(join(lib, "README.md"), "utf8");
|
|
716
|
+
const how = readme.slice(readme.indexOf("## 샷 스크립트 짜기"), readme.indexOf("## 판마다 다른 곳"));
|
|
717
|
+
const api = readFileSync(join(lib, "kfb.py"), "utf8").split("\n").filter((l) => /^(def [a-z]\w*\(|class [A-Z])/.test(l)).join("\n");
|
|
718
|
+
return `${how}\n\nkfb 함수(이것만 쓴다):\n${api}\n\n예(등대 섬):\n\`\`\`python\n${readFileSync(join(lib, "shots", "lighthouse.py"), "utf8")}\n\`\`\``;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function blenderRun(dir: string, file: string, scene: string, id: string, extra: string[]): { ok: boolean; tail: string } {
|
|
722
|
+
const r = spawnSync(process.execPath, [kfBin, "blender", join(dir, file), "--project", dir, "--scene", scene, "--id", id, ...extra], { encoding: "utf8" });
|
|
723
|
+
const tail = `${r.stdout ?? ""}${r.stderr ?? ""}`.trim().split("\n").slice(-25).join("\n");
|
|
724
|
+
return { ok: r.status === 0, tail };
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async function writeShots(dir: string, model: string): Promise<void> {
|
|
728
|
+
const plan: Plan = JSON.parse(readFileSync(join(dir, "plan.json"), "utf8"));
|
|
729
|
+
const shots = (plan.shots ?? []).filter((s) => s.tool === "blender");
|
|
730
|
+
if (!shots.length) return;
|
|
731
|
+
const kf = readProject(dir);
|
|
732
|
+
const style = JSON.parse(readFileSync(join(dir, "style.json"), "utf8"));
|
|
733
|
+
const guide = shotGuide();
|
|
734
|
+
for (const shot of shots) {
|
|
735
|
+
const sc = kf.scenes.find((x) => x.id === shot.scene);
|
|
736
|
+
if (!sc) continue;
|
|
737
|
+
mkdirSync(join(dir, "shots"), { recursive: true });
|
|
738
|
+
const peek = [0.15, 0.5, 0.9].map((k) => +(sc.from + sc.dur * k).toFixed(2)).join(",");
|
|
739
|
+
let note = "";
|
|
740
|
+
let ok = false;
|
|
741
|
+
console.error(`3D 샷 ${shot.id} 쓰는 중(${model}) — 장면 ${sc.id} ${sc.dur.toFixed(2)}초`);
|
|
742
|
+
for (let attempt = 0; attempt < 3 && !ok; attempt++) {
|
|
743
|
+
const r = await chat(model, [
|
|
744
|
+
{ role: "system", content: `너는 Blender 툰 3D 샷 스크립트(파이썬, tools/blender/kfb.py 헬퍼)를 쓴다. 답은 python 코드 블록 하나뿐.\n\n${guide}` },
|
|
745
|
+
{
|
|
746
|
+
role: "user",
|
|
747
|
+
content: `샷: ${shot.idea}\n장면 ${sc.id}: 프로젝트 ${sc.from.toFixed(2)}–${(sc.from + sc.dur).toFixed(2)}초(update(t) 의 t 는 프로젝트 초), 화면 ${kf.width}x${kf.height}\n색(style.json): ${JSON.stringify(style.colors ?? {})}\n글자는 넣지 않는다(장면이 위에 얹는다). 주인공 하나를 화면 가운데 크게, 쉬지 않게 박에 뛰거나 도는 물체, 카메라 열쇠는 S.downbeats·S.drops 에 두고 도착하면 선다.${note ? `\n\n고칠 것:\n${note}` : ""}`,
|
|
748
|
+
},
|
|
749
|
+
], { temperature: 0.5, maxTokens: 16000, stage: `shot:${shot.id}` });
|
|
750
|
+
const code = codeBlock(r.text, "python|py");
|
|
751
|
+
if (!code) {
|
|
752
|
+
note = "코드 블록을 못 찾았다. ```python 블록 하나로 파일 전체를 답하라.";
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
writeFileSync(join(dir, shot.file), code);
|
|
756
|
+
// 스틸 몇 장은 kf still 처럼 잠금 없이 — 남의 긴 렌더 뒤에서 기다리지 않는다(굽기는 잠금을 쓴다)
|
|
757
|
+
const t = blenderRun(dir, shot.file, sc.id, shot.id, ["--still", peek, "--res", "50", "--no-lock"]);
|
|
758
|
+
ok = t.ok;
|
|
759
|
+
note = ok ? "" : `kf blender --still 이 실패했다:\n${t.tail}`;
|
|
760
|
+
console.error(` ${shot.id} ${attempt + 1}번째 ${(r.ms / 1000).toFixed(1)}초 — ${ok ? "스틸 통과" : "실패"}`);
|
|
761
|
+
}
|
|
762
|
+
// 절반 크기·두 장에 한 장 — 무거운 렌더를 짧게(페이지가 늘려 보인다)
|
|
763
|
+
const baked = ok && blenderRun(dir, shot.file, sc.id, shot.id, ["--res", "50", "--step", "2"]).ok;
|
|
764
|
+
if (!baked) {
|
|
765
|
+
console.error(` ${shot.id} 를 못 구웠다 — 장면 ${sc.id} 는 three.js(stage3d)로 3D 를 짓는다`);
|
|
766
|
+
plan.shots = (plan.shots ?? []).map((s) => (s.id === shot.id ? { ...s, tool: "three" } : s));
|
|
767
|
+
writeJson(join(dir, "plan.json"), plan);
|
|
768
|
+
} else console.error(` ${shot.id} → assets/blender/${shot.id}.mp4`);
|
|
377
769
|
}
|
|
378
770
|
}
|
|
379
771
|
|
|
772
|
+
// --- 묻기: Claude Code 모드에서 세션이 사람에게 물은 답을 넣는다 — 질문은 plan.json 의 asks 를 지금 능력으로 다시 만들어(함수 칸까지) 고른다
|
|
773
|
+
async function answerProject(dir: string, opts: Record<string, string | boolean | undefined>): Promise<number> {
|
|
774
|
+
const file = join(dir, "plan.json");
|
|
775
|
+
if (!existsSync(file)) {
|
|
776
|
+
console.error(`kf make --answer: ${dir} 에 plan.json 이 없다`);
|
|
777
|
+
return 2;
|
|
778
|
+
}
|
|
779
|
+
const plan: Plan = JSON.parse(readFileSync(file, "utf8"));
|
|
780
|
+
// 테마에 분위기 한 마디·레퍼런스를 줬으면 후보 넷을 다시 고르고 견본 시트를 새로 만들어 다시 묻는다 — 답은 그다음
|
|
781
|
+
let look: LookInput;
|
|
782
|
+
try {
|
|
783
|
+
look = await lookFlags(opts);
|
|
784
|
+
} catch (e) {
|
|
785
|
+
console.error(`kf make --answer: ${(e as Error).message}`);
|
|
786
|
+
return 2;
|
|
787
|
+
}
|
|
788
|
+
if (look.mood || look.ref) {
|
|
789
|
+
plan.themeLook = { ...plan.themeLook, ...look, ...(look.mood ? { mood: [plan.themeLook?.mood, look.mood].filter(Boolean).join(" ") } : {}) };
|
|
790
|
+
const q = planQuestions(plan, await capabilities({ probe: false })).questions.find((x) => x.id === "theme");
|
|
791
|
+
if (!q) {
|
|
792
|
+
console.error("kf make --answer: 이 프로젝트는 테마를 묻지 않는다(시리즈나 요청이 테마를 정했다) — --theme <id> 로 바꾼다");
|
|
793
|
+
return 2;
|
|
794
|
+
}
|
|
795
|
+
const sheet = await candidateSheet(q, (l) => console.error(l));
|
|
796
|
+
plan.asks = [...(plan.asks ?? []).filter((x) => x.id !== "theme"), q];
|
|
797
|
+
writeJson(file, plan);
|
|
798
|
+
writeFileSync(join(dir, "MAKE.md"), isDirect(dir) ? directBrief(dir) : projectBrief(dir));
|
|
799
|
+
console.log(`테마 후보를 다시 골랐다${sheet ? ` — 견본 시트 ${sheet}` : ""}:`);
|
|
800
|
+
for (const c of q.choices) console.log(` --theme ${c.value.padEnd(30)} ${c.label}${c.note ? ` — ${c.note}` : ""}`);
|
|
801
|
+
console.error("사람에게 다시 묻고 kf make --answer <폴더> --theme <값> 으로 답을 넣는다");
|
|
802
|
+
return 3;
|
|
803
|
+
}
|
|
804
|
+
const waiting = new Set((plan.asks ?? []).map((q) => q.id));
|
|
805
|
+
const fresh = planQuestions(plan, await capabilities({ probe: true })).questions.filter((q) => waiting.has(q.id));
|
|
806
|
+
const settled = settle(fresh, { given: opts, yes: !!opts.yes, noGen: !!opts["no-gen"], allow: parseAllow(opts.allow) });
|
|
807
|
+
const { plan: next, stop } = applyAnswers(plan, settled.answers, fresh);
|
|
808
|
+
next.asks = (plan.asks ?? []).filter((q) => !(q.id in settled.answers));
|
|
809
|
+
writeJson(file, next);
|
|
810
|
+
if (settled.answers.theme) {
|
|
811
|
+
const was = readProject(dir).theme ?? null;
|
|
812
|
+
const now = retheme(dir, settled.answers.theme);
|
|
813
|
+
console.log(now === was ? `테마 ${now ?? "없음(프리셋)"} 그대로` : `테마 ${was ?? "없음"} → ${now ?? "없음(프리셋)"} — style.json·kf.json·장면 뼈대 DRESS 를 바꿨다`);
|
|
814
|
+
}
|
|
815
|
+
writeFileSync(join(dir, "MAKE.md"), isDirect(dir) ? directBrief(dir) : projectBrief(dir));
|
|
816
|
+
for (const [id, v] of Object.entries(settled.answers)) console.log(`${id}: ${v}`);
|
|
817
|
+
if (stop.length) {
|
|
818
|
+
console.error(`켜고 다시 부른다 —\n${stop.map((x) => ` ${x}`).join("\n")}`);
|
|
819
|
+
return 3;
|
|
820
|
+
}
|
|
821
|
+
if (next.asks.length) {
|
|
822
|
+
console.error(`아직 답이 없다: ${next.asks.map((q) => `${q.flag} <${q.choices.map((c) => c.value).join("|")}>`).join(" ")}`);
|
|
823
|
+
return 3;
|
|
824
|
+
}
|
|
825
|
+
console.log(`${dir}/MAKE.md 를 고쳐 썼다 — 답대로 그림·3D 샷·영상 컷을 만들고 장면을 채운다`);
|
|
826
|
+
return 0;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// 맨 위 질문의 테마 답 — kf new 가 auto 로 깐 테마를 바꾼다(plan.json·kf.json·style.json, 장면 뼈대의 DRESS 한 줄).
|
|
830
|
+
// 감독 모드는 kf direct 가 kf.json 의 테마로 장면을 지으니 여기까지면 된다. 바뀐 테마 id(프리셋이면 null)를 돌려준다
|
|
831
|
+
function retheme(dir: string, answer: string): string | null {
|
|
832
|
+
const plan = readPlan(dir) as Plan;
|
|
833
|
+
const kfPath = join(dir, "kf.json");
|
|
834
|
+
const kf = JSON.parse(readFileSync(kfPath, "utf8"));
|
|
835
|
+
const id: string | null =
|
|
836
|
+
answer === "auto" ? (suggestThemes(plan.request, plan.purpose, plan.aspect, 1, plan.themeLook ?? {})[0]?.id ?? plan.themeCandidates?.[0]?.id ?? kf.theme ?? null) : answer === "none" ? null : answer;
|
|
837
|
+
if ((kf.theme ?? null) === id) return id;
|
|
838
|
+
const tpl = loadTemplate(plan.purpose);
|
|
839
|
+
// 프리셋으로 돌아가면 kf new 가 테마 없이 골랐을 프리셋 — 시리즈·요청 낱말·틀 차례
|
|
840
|
+
const preset = id ? null : makePlan({ request: plan.request, purpose: plan.purpose, theme: "none", series: plan.series ?? "none" }).style;
|
|
841
|
+
const style = id ? themeStyle(id) : JSON.parse(readFileSync(join(repo, "presets", `${existsSync(join(repo, "presets", `${preset}.json`)) ? preset : "neutral-dark"}.json`), "utf8"));
|
|
842
|
+
style.motion = { ...style.motion, ...tpl.motion };
|
|
843
|
+
writeJson(join(dir, "style.json"), style);
|
|
844
|
+
if (tpl.moves) {
|
|
845
|
+
const dress = pickDress(dressSeed(plan.request || plan.title, plan.purpose, worldOf(id) ?? preset!), plan.scenes, bundleFor(tpl.moves, id ?? undefined));
|
|
846
|
+
const byId = new Map(plan.scenes.map((s, i) => [s.id, dress[i]]));
|
|
847
|
+
for (const s of kf.scenes as { id: string; file: string; dress?: Record<string, string> }[]) {
|
|
848
|
+
const next = byId.get(s.id);
|
|
849
|
+
if (!s.dress || !next) continue;
|
|
850
|
+
const file = join(dir, s.file);
|
|
851
|
+
// 뼈대가 채운 값만 갈아 끼운다 — 세션이 손댄 줄·뼈대에 박힌 글자 값은 그대로
|
|
852
|
+
if (existsSync(file))
|
|
853
|
+
writeFileSync(file, readFileSync(file, "utf8").replace(/^const DRESS: Dress = \{.*\};$/m, (line) => Object.entries(next).reduce((l, [k, v]) => l.replace(`${k}: "${s.dress![k]}"`, `${k}: "${v}"`), line)));
|
|
854
|
+
s.dress = { ...next };
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (id) kf.theme = id;
|
|
858
|
+
else delete kf.theme;
|
|
859
|
+
writeJson(kfPath, kf);
|
|
860
|
+
writeJson(join(dir, "plan.json"), { ...readPlan(dir), theme: id, style: id ?? preset });
|
|
861
|
+
return id;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// 결과 메타 — 이 영상에 들어간 생성 자산과 회사·바깥 결과물에 걸리는 제한(H3 공개 가중치 등)을 한 파일에
|
|
865
|
+
function writeCredits(dir: string, video: string): void {
|
|
866
|
+
const read = (sub: string) =>
|
|
867
|
+
existsSync(join(dir, "assets", sub))
|
|
868
|
+
? readdirSync(join(dir, "assets", sub)).filter((f) => f.endsWith(".json")).map((f) => ({ file: join("assets", sub, f), ...JSON.parse(readFileSync(join(dir, "assets", sub, f), "utf8")) }))
|
|
869
|
+
: [];
|
|
870
|
+
const gen = read("gen").map((m) => ({ id: m.id, kind: m.kind, provider: m.provider, model: m.model, costUsd: m.costUsd, license: m.license, region: m.region, ...(m.restrict ? { restrict: m.restrict } : {}) }));
|
|
871
|
+
const blender = read("blender").map((m) => ({ id: m.file.replace(/^.*\/|\.json$/g, ""), kind: "3d", provider: "blender", model: `Blender ${m.blender}`, costUsd: 0, license: "출력물은 만든 사람 것(Blender GPL 은 출력물에 걸리지 않는다)" }));
|
|
872
|
+
const plan = existsSync(join(dir, "plan.json")) ? JSON.parse(readFileSync(join(dir, "plan.json"), "utf8")) : {};
|
|
873
|
+
const restricted = gen.filter((g) => g.restrict);
|
|
874
|
+
const credits = { video: basename(video), answers: plan.answers ?? {}, assets: [...gen, ...blender], costUsd: Number(gen.reduce((n, g) => n + (g.costUsd ?? 0), 0).toFixed(4)), restricted: restricted.map((g) => ({ id: g.id, provider: g.provider, restrict: g.restrict })) };
|
|
875
|
+
const path = join(dir, "out", `${basename(video, ".mp4")}.credits.json`);
|
|
876
|
+
writeJson(path, credits);
|
|
877
|
+
if (restricted.length) console.log(`\n제한 있는 자산 ${restricted.length}개 — 회사·바깥 결과물에 내기 전에 바꾼다:\n${restricted.map((g) => ` ${g.id}(${g.provider}): ${g.restrict}`).join("\n")}`);
|
|
878
|
+
console.log(`결과 메타: ${path}`);
|
|
879
|
+
}
|
|
880
|
+
|
|
380
881
|
// 이음매 장면의 모션그래픽 전환(transition3d)은 앞 장면 마지막 화면·뒤 장면 첫 화면 두 장을 받아 그 사이를 잇는다.
|
|
381
882
|
// 장면을 다 쓴 뒤에 찍어야 진짜 화면이 된다 — 그림이 없으면 전환이 style 색으로 대체 화면을 그린다
|
|
382
|
-
async function seamStills(dir: string): Promise<void> {
|
|
883
|
+
async function seamStills(dir: string, only?: string[]): Promise<void> {
|
|
383
884
|
const kf = JSON.parse(readFileSync(join(dir, "kf.json"), "utf8"));
|
|
384
|
-
const seams = (kf.scenes as { id: string; role: string }[]).filter((s) => s.role === "seam");
|
|
885
|
+
const seams = (kf.scenes as { id: string; role: string }[]).filter((s) => s.role === "seam" && (!only || only.includes(s.id)));
|
|
385
886
|
if (!seams.length) return;
|
|
386
887
|
mkdirSync(join(dir, "assets", "seam"), { recursive: true });
|
|
387
888
|
for (const s of seams) {
|
|
@@ -399,28 +900,46 @@ async function seamStills(dir: string): Promise<void> {
|
|
|
399
900
|
// --- 마무리: 렌더 → 검사 → (OG 모드면) 고치기 → 표지
|
|
400
901
|
|
|
401
902
|
// captions 를 주면 그 판만 화면 자막을 켜고 끈다(kf.json 은 그대로) — 굽기·검사·자막 트랙이 같은 값을 본다
|
|
402
|
-
|
|
903
|
+
// scenes 를 주면 그 장면 둘레(그 장면 + 붙은 이음매)만 — 이음 화면·검사를 그 구간에서. 굽기는 kf render 가 바뀐 조각만 다시 찍는다
|
|
904
|
+
async function finish(dir: string, o: { model: string; vision: string; fixes: number; gen?: GenOpts; captions?: "on" | "off" | "fit"; suffix?: string; scenes?: string[] }): Promise<number> {
|
|
403
905
|
if (!existsSync(join(dir, "kf.json"))) {
|
|
404
906
|
console.error(`kf make: ${dir} 에 kf.json 이 없다`);
|
|
405
907
|
return 2;
|
|
406
908
|
}
|
|
909
|
+
// 세션이 kf narrate 를 건너뛰었으면 여기서 — 목소리·곡을 깔고 장면 길이를 맞춘 뒤 굽는다
|
|
910
|
+
if (readProject(dir).voice === undefined) {
|
|
911
|
+
const code = narrateStep(dir);
|
|
912
|
+
if (code !== 0) return code;
|
|
913
|
+
}
|
|
407
914
|
if (o.gen) await genVideos(dir, o.gen);
|
|
408
|
-
|
|
915
|
+
const touched = o.scenes ? around(dir, o.scenes) : undefined;
|
|
916
|
+
await seamStills(dir, touched);
|
|
409
917
|
const out = join(dir, "out", `video${o.suffix ?? ""}.mp4`);
|
|
410
918
|
const capFlag = o.captions ? ["--captions", o.captions] : [];
|
|
411
919
|
let verdict = { code: 0, report: "" };
|
|
412
920
|
for (let round = 0; ; round++) {
|
|
413
|
-
if (round > 0) await seamStills(dir);
|
|
414
|
-
|
|
415
|
-
|
|
921
|
+
if (round > 0) await seamStills(dir, touched);
|
|
922
|
+
// 굽기와 검사 사이에 잠금을 놓으면 기다리던 남의 긴 렌더가 끼어든다 — 둘을 한 번에 쥐고 자식은 잠금 없이
|
|
923
|
+
const release = await acquireRenderLock(dir, 3600);
|
|
924
|
+
const r = await relay(["render", dir, "--out", out, "--tabs", "2", "--no-lock", ...capFlag]);
|
|
925
|
+
if (r !== 0) {
|
|
926
|
+
release();
|
|
416
927
|
console.error("kf make: 렌더가 실패했다");
|
|
417
|
-
return r
|
|
928
|
+
return r;
|
|
418
929
|
}
|
|
419
|
-
|
|
930
|
+
// 고친 둘레 + 실제로 다시 구운 조각(라이브러리·테마가 바뀌면 안 고친 장면도 다시 굽힌다) — 다 다시 구웠으면 통째 검사
|
|
931
|
+
const scope = touched && redone(out, touched);
|
|
932
|
+
for (const id of scope ?? readProject(dir).scenes.map((x) => x.id)) console.error(`장면 ${id}: 검사`);
|
|
933
|
+
verdict = scope ? checkPart(dir, scope, [...capFlag, "--no-lock"]) : check(dir, [...capFlag, "--no-lock"]);
|
|
934
|
+
release();
|
|
935
|
+
sceneVerdicts(dir, scope);
|
|
420
936
|
const review = o.vision !== "none" ? await visionReview(dir, o.vision) : {};
|
|
421
937
|
const toFix: Record<string, string> = { ...review };
|
|
422
938
|
for (const [id, note] of Object.entries(findingsByScene(dir))) toFix[id] = [toFix[id], note].filter(Boolean).join("\n");
|
|
423
|
-
|
|
939
|
+
// 고친 장면만 다시 굽는 길이면 고치기도 그 둘레 안에서 — 안 건드린 장면의 옛 지적으로 남의 장면을 다시 쓰지 않는다
|
|
940
|
+
if (touched) for (const id of Object.keys(toFix)) if (!touched.includes(id)) delete toFix[id];
|
|
941
|
+
// 감독 프로젝트는 kf direct 가 떨어진 장면을 다음 후보로 이미 다시 지었다 — 모델이 장면 코드를 덮어쓰지 않는다
|
|
942
|
+
if ((verdict.code === 0 && !Object.keys(review).length) || o.model === "claude-code" || isDirect(dir) || round >= o.fixes) break;
|
|
424
943
|
if (!Object.keys(toFix).length) break;
|
|
425
944
|
console.error(`고치기 ${round + 1}/${o.fixes}: ${Object.keys(toFix).join(" ")}`);
|
|
426
945
|
writeFileSync(join(dir, "MAKE.md"), projectBrief(dir));
|
|
@@ -428,19 +947,134 @@ async function finish(dir: string, o: { model: string; vision: string; fixes: nu
|
|
|
428
947
|
}
|
|
429
948
|
const subs = writeCaptions(dir, o.captions && captionMode(o.captions));
|
|
430
949
|
const cover = coverTime(dir);
|
|
431
|
-
|
|
950
|
+
if (!touched || touched.includes(cover.scene) || !existsSync(join(dir, "out", "cover.png")))
|
|
951
|
+
spawnSync(process.execPath, [kfBin, "still", dir, "--t", String(cover.t), "--out", join(dir, "out", "cover.png")], { stdio: "ignore" });
|
|
432
952
|
console.log(`\n${[out, join(dir, "out", "cover.png"), ...subs].join("\n")}`);
|
|
433
|
-
|
|
953
|
+
writeCredits(dir, out);
|
|
954
|
+
if (o.vision === "none" && !touched) lookHint(dir);
|
|
955
|
+
else if (o.vision === "none") console.log(`\n장면 모음: kf look ${dir}`);
|
|
434
956
|
if (verdict.report) console.log(`\n검사:\n${verdict.report}`);
|
|
435
957
|
return verdict.code;
|
|
436
958
|
}
|
|
437
959
|
|
|
960
|
+
// 틀이 내레이션·분위기 곡을 정한 용도(쇼츠·설명)만. 장면별 문장(captions.json)이 없으면 곡만 깐다
|
|
961
|
+
function narrateStep(dir: string): number {
|
|
962
|
+
const tpl = loadTemplate(readProject(dir).template);
|
|
963
|
+
if (!tpl.narration?.on && !tpl.mood) return 0;
|
|
964
|
+
const lines = existsSync(join(dir, "captions.json")) ? JSON.parse(readFileSync(join(dir, "captions.json"), "utf8")) : [];
|
|
965
|
+
const plan = existsSync(join(dir, "plan.json")) ? JSON.parse(readFileSync(join(dir, "plan.json"), "utf8")) : {};
|
|
966
|
+
const voice = plan.voice ?? plan.answers?.voice;
|
|
967
|
+
// 문장이 없다고 목소리를 조용히 빼지 않는다 — 사람이 목소리를 none 으로 고른 때만 곡만 깐다
|
|
968
|
+
if (!lines.length && tpl.narration?.on && voice !== "none") {
|
|
969
|
+
console.error(`kf make: 내레이션 문장(captions.json)이 없다 — 장면별 문장을 쓰고 kf narrate ${dir}, 목소리를 빼려면 kf make --answer ${dir} --voice none`);
|
|
970
|
+
return 3;
|
|
971
|
+
}
|
|
972
|
+
const r = spawnSync(process.execPath, [kfBin, "narrate", dir], { stdio: ["ignore", "inherit", "inherit"] });
|
|
973
|
+
if (r.status !== 0) console.error("kf make: kf narrate 가 실패했다 — 위 메시지를 보고 고친 뒤 kf narrate <폴더>");
|
|
974
|
+
return r.status ?? 1;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// 자식 kf 의 줄을 이 프로세스 console 로 다시 찍는다 — 작업실 거울(attachStudio.mirror)은 console.error 만 봐서, 물려준 출력(inherit)은 작업실에 안 닿는다
|
|
978
|
+
function relay(args: string[]): Promise<number> {
|
|
979
|
+
return new Promise((done) => {
|
|
980
|
+
const p = spawn(process.execPath, [kfBin, ...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
981
|
+
for (const [stream, out] of [[p.stdout, console.log], [p.stderr, console.error]] as const) {
|
|
982
|
+
let buf = "";
|
|
983
|
+
stream.setEncoding("utf8");
|
|
984
|
+
stream.on("data", (chunk: string) => {
|
|
985
|
+
buf += chunk;
|
|
986
|
+
const parts = buf.split(/\r?\n/);
|
|
987
|
+
buf = parts.pop() ?? "";
|
|
988
|
+
for (const l of parts) out(l);
|
|
989
|
+
});
|
|
990
|
+
stream.on("end", () => buf && out(buf));
|
|
991
|
+
}
|
|
992
|
+
p.on("close", (code) => done(code ?? 1));
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
|
|
438
996
|
// 폴더를 주면 kf check 가 페이지를 열어 프레임마다 훑는다 — mp4 를 OCR 하는 것보다 정확하다
|
|
439
997
|
function check(dir: string, extra: string[] = []): { code: number; report: string } {
|
|
440
998
|
const r = spawnSync(process.execPath, [kfBin, "check", dir, ...extra], { encoding: "utf8" });
|
|
441
999
|
return { code: r.status ?? 1, report: `${r.stdout ?? ""}${r.stderr ?? ""}`.trim() };
|
|
442
1000
|
}
|
|
443
1001
|
|
|
1002
|
+
// 굽기 보고서(kf render 가 영상 옆에 남긴 .json)의 다시 구운 조각을 더한다. 전부면 undefined — 통째로
|
|
1003
|
+
function redone(video: string, touched: string[]): string[] | undefined {
|
|
1004
|
+
const all = readProject(dirname(dirname(video))).scenes.map((s) => s.id);
|
|
1005
|
+
let rendered: string[] = [];
|
|
1006
|
+
try {
|
|
1007
|
+
rendered = JSON.parse(readFileSync(video.replace(/\.[^.]+$/, ".json"), "utf8")).segments?.rendered ?? [];
|
|
1008
|
+
} catch {}
|
|
1009
|
+
const scope = all.filter((id) => touched.includes(id) || rendered.includes(id));
|
|
1010
|
+
return scope.length === all.length ? undefined : scope;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// 고친 장면과 거기 붙은 이음매 — 이음매는 앞뒤 장면의 화면을 잇는다
|
|
1014
|
+
function around(dir: string, ids: string[]): string[] {
|
|
1015
|
+
const scenes = readProject(dir).scenes;
|
|
1016
|
+
const out = new Set(ids);
|
|
1017
|
+
for (const [i, s] of scenes.entries()) {
|
|
1018
|
+
if (!ids.includes(s.id)) continue;
|
|
1019
|
+
for (const n of [scenes[i - 1], scenes[i + 1]]) if (n?.role === "seam") out.add(n.id);
|
|
1020
|
+
}
|
|
1021
|
+
return scenes.filter((s) => out.has(s.id)).map((s) => s.id);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
interface CheckJson {
|
|
1025
|
+
ok: boolean;
|
|
1026
|
+
counts: { error: number; warn: number };
|
|
1027
|
+
findings: { rule: string; severity: string; from: number; to?: number; message: string }[];
|
|
1028
|
+
[k: string]: unknown;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// 고친 구간만 훑고 out/check.json 의 그 구간 지적만 갈아 끼운다 — 나머지 장면의 앞 검사 결과는 그대로 둔다
|
|
1032
|
+
function checkPart(dir: string, ids: string[], extra: string[] = []): { code: number; report: string } {
|
|
1033
|
+
const scenes = readProject(dir).scenes;
|
|
1034
|
+
const file = join(dir, "out", "check.json");
|
|
1035
|
+
const before: CheckJson | null = existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : null;
|
|
1036
|
+
// 붙어 있는 장면끼리 한 구간으로 — 페이지를 여는 값이 커서 장면마다 따로 열지 않는다
|
|
1037
|
+
const ranges: [number, number][] = [];
|
|
1038
|
+
for (const s of scenes) {
|
|
1039
|
+
if (!ids.includes(s.id)) continue;
|
|
1040
|
+
const last = ranges.at(-1);
|
|
1041
|
+
if (last && Math.abs(last[1] - s.from) < 1e-6) last[1] = s.from + s.dur;
|
|
1042
|
+
else ranges.push([s.from, s.from + s.dur]);
|
|
1043
|
+
}
|
|
1044
|
+
const found: CheckJson["findings"] = [];
|
|
1045
|
+
const reports: string[] = [];
|
|
1046
|
+
let failed = 0;
|
|
1047
|
+
let last: CheckJson | null = null;
|
|
1048
|
+
for (const [a, b] of ranges) {
|
|
1049
|
+
const part = join(dir, "out", "check-part.json");
|
|
1050
|
+
const r = spawnSync(process.execPath, [kfBin, "check", dir, "--from", a.toFixed(4), "--to", b.toFixed(4), "--json", part, ...extra], { encoding: "utf8" });
|
|
1051
|
+
reports.push(`${r.stdout ?? ""}${r.stderr ?? ""}`.trim());
|
|
1052
|
+
if (r.status === 2 || !existsSync(part)) failed = 2;
|
|
1053
|
+
else {
|
|
1054
|
+
last = JSON.parse(readFileSync(part, "utf8"));
|
|
1055
|
+
found.push(...last!.findings);
|
|
1056
|
+
rmSync(part, { force: true });
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (failed) return { code: 2, report: reports.join("\n\n") };
|
|
1060
|
+
const inside = (t: number) => ranges.some(([a, b]) => t >= a - 1e-6 && t < b - 1e-6);
|
|
1061
|
+
const findings = [...(before?.findings ?? []).filter((f) => !inside(f.from)), ...found].sort((x, y) => x.from - y.from);
|
|
1062
|
+
const counts = { error: findings.filter((f) => f.severity === "error").length, warn: findings.filter((f) => f.severity === "warn").length };
|
|
1063
|
+
writeJson(file, { ...(before ?? last), findings, counts, ok: counts.error === 0, part: ranges });
|
|
1064
|
+
return { code: counts.error ? 1 : 0, report: reports.join("\n\n") };
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// 장면마다 검사 결과 한 줄 — 작업실 타임라인이 「끝」·「실패」로 읽는다
|
|
1068
|
+
function sceneVerdicts(dir: string, only?: string[]): void {
|
|
1069
|
+
const file = join(dir, "out", "check.json");
|
|
1070
|
+
const findings: CheckJson["findings"] = existsSync(file) ? (JSON.parse(readFileSync(file, "utf8")).findings ?? []) : [];
|
|
1071
|
+
for (const s of readProject(dir).scenes) {
|
|
1072
|
+
if (only && !only.includes(s.id)) continue;
|
|
1073
|
+
const errs = findings.filter((f) => f.severity === "error" && f.from >= s.from - 1e-6 && f.from < s.from + s.dur);
|
|
1074
|
+
console.error(errs.length ? `장면 ${s.id}: 실패 검사 오류 ${errs.length} — ${[...new Set(errs.map((f) => f.rule))].join(", ")}` : `장면 ${s.id}: 끝`);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
444
1078
|
// out/check.json 의 오류를 시각으로 장면에 묶는다 — 장면마다 자기 몫의 지적만 받아 다시 쓴다
|
|
445
1079
|
function findingsByScene(dir: string): Record<string, string> {
|
|
446
1080
|
const file = join(dir, "out", "check.json");
|
|
@@ -479,10 +1113,10 @@ async function visionReview(dir: string, model: string): Promise<Record<string,
|
|
|
479
1113
|
}
|
|
480
1114
|
}
|
|
481
1115
|
|
|
482
|
-
function coverTime(dir: string): number {
|
|
1116
|
+
function coverTime(dir: string): { scene: string; t: number } {
|
|
483
1117
|
const kf = readProject(dir);
|
|
484
1118
|
const pick = kf.scenes.find((s) => s.role === "drop") ?? kf.scenes.find((s) => s.role === "hook" || s.role === "reveal") ?? kf.scenes[0];
|
|
485
|
-
return +(pick.from + Math.min(0.8, pick.dur / 2)).toFixed(3);
|
|
1119
|
+
return { scene: pick.id, t: +(pick.from + Math.min(0.8, pick.dur / 2)).toFixed(3) };
|
|
486
1120
|
}
|
|
487
1121
|
|
|
488
1122
|
// --- 그 밖
|
|
@@ -537,6 +1171,19 @@ function stamp(): string {
|
|
|
537
1171
|
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}`;
|
|
538
1172
|
}
|
|
539
1173
|
|
|
1174
|
+
// content.json 의 say 와 지금 깔린 목소리 문장(captions.json)이 다른 장면이 있나
|
|
1175
|
+
function sayChanged(dir: string, ids: string[]): boolean {
|
|
1176
|
+
const read = (f: string) => (existsSync(join(dir, f)) ? JSON.parse(readFileSync(join(dir, f), "utf8")) : null);
|
|
1177
|
+
const content = read("content.json") as { scenes?: { id: string; say?: string }[] } | null;
|
|
1178
|
+
const lines = (read("captions.json") ?? []) as { scene?: string; text?: string }[];
|
|
1179
|
+
return ids.some((id) => (content?.scenes?.find((s) => s.id === id)?.say ?? "").trim() !== lines.filter((l) => l.scene === id).map((l) => l.text?.trim() ?? "").join(" ").trim());
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function readPlan(dir: string): Partial<Plan> & { model?: string } {
|
|
1183
|
+
const p = join(dir, "plan.json");
|
|
1184
|
+
return existsSync(p) ? JSON.parse(readFileSync(p, "utf8")) : {};
|
|
1185
|
+
}
|
|
1186
|
+
|
|
540
1187
|
function writeJson(path: string, v: unknown): void {
|
|
541
1188
|
writeFileSync(path, `${JSON.stringify(v, null, 2)}\n`);
|
|
542
1189
|
}
|