@kenkaiiii/gg-editor 0.7.0 → 0.7.2
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/dist/cli.js +64 -1
- package/dist/cli.js.map +1 -1
- package/dist/core/audio-mix.d.ts.map +1 -1
- package/dist/core/audio-mix.js +8 -1
- package/dist/core/audio-mix.js.map +1 -1
- package/dist/core/audio-mix.test.js +1 -1
- package/dist/core/audio-mix.test.js.map +1 -1
- package/dist/core/auth/api-keys.d.ts +1 -1
- package/dist/core/auth/api-keys.d.ts.map +1 -1
- package/dist/core/auth/api-keys.js +2 -1
- package/dist/core/auth/api-keys.js.map +1 -1
- package/dist/core/auth/login.d.ts.map +1 -1
- package/dist/core/auth/login.js.map +1 -1
- package/dist/core/beats.d.ts +59 -0
- package/dist/core/beats.d.ts.map +1 -0
- package/dist/core/beats.js +122 -0
- package/dist/core/beats.js.map +1 -0
- package/dist/core/beats.test.d.ts +2 -0
- package/dist/core/beats.test.d.ts.map +1 -0
- package/dist/core/beats.test.js +86 -0
- package/dist/core/beats.test.js.map +1 -0
- package/dist/core/brand-kit.d.ts +80 -0
- package/dist/core/brand-kit.d.ts.map +1 -0
- package/dist/core/brand-kit.js +96 -0
- package/dist/core/brand-kit.js.map +1 -0
- package/dist/core/brand-kit.test.d.ts +2 -0
- package/dist/core/brand-kit.test.d.ts.map +1 -0
- package/dist/core/brand-kit.test.js +76 -0
- package/dist/core/brand-kit.test.js.map +1 -0
- package/dist/core/bundled-sfx.d.ts +64 -0
- package/dist/core/bundled-sfx.d.ts.map +1 -0
- package/dist/core/bundled-sfx.js +218 -0
- package/dist/core/bundled-sfx.js.map +1 -0
- package/dist/core/bundled-sfx.test.d.ts +2 -0
- package/dist/core/bundled-sfx.test.d.ts.map +1 -0
- package/dist/core/bundled-sfx.test.js +81 -0
- package/dist/core/bundled-sfx.test.js.map +1 -0
- package/dist/core/child-abort.d.ts +57 -0
- package/dist/core/child-abort.d.ts.map +1 -0
- package/dist/core/child-abort.js +95 -0
- package/dist/core/child-abort.js.map +1 -0
- package/dist/core/child-abort.test.d.ts +2 -0
- package/dist/core/child-abort.test.d.ts.map +1 -0
- package/dist/core/child-abort.test.js +88 -0
- package/dist/core/child-abort.test.js.map +1 -0
- package/dist/core/clip-scoring.d.ts +44 -0
- package/dist/core/clip-scoring.d.ts.map +1 -0
- package/dist/core/clip-scoring.js +165 -0
- package/dist/core/clip-scoring.js.map +1 -0
- package/dist/core/clip-scoring.test.d.ts +2 -0
- package/dist/core/clip-scoring.test.d.ts.map +1 -0
- package/dist/core/clip-scoring.test.js +113 -0
- package/dist/core/clip-scoring.test.js.map +1 -0
- package/dist/core/emoji-captions.d.ts +45 -0
- package/dist/core/emoji-captions.d.ts.map +1 -0
- package/dist/core/emoji-captions.js +121 -0
- package/dist/core/emoji-captions.js.map +1 -0
- package/dist/core/face-reframe.d.ts +91 -0
- package/dist/core/face-reframe.d.ts.map +1 -0
- package/dist/core/face-reframe.js +141 -0
- package/dist/core/face-reframe.js.map +1 -0
- package/dist/core/face-reframe.test.d.ts +2 -0
- package/dist/core/face-reframe.test.d.ts.map +1 -0
- package/dist/core/face-reframe.test.js +171 -0
- package/dist/core/face-reframe.test.js.map +1 -0
- package/dist/core/filler-words.d.ts +57 -9
- package/dist/core/filler-words.d.ts.map +1 -1
- package/dist/core/filler-words.js +61 -9
- package/dist/core/filler-words.js.map +1 -1
- package/dist/core/filler-words.test.js +91 -17
- package/dist/core/filler-words.test.js.map +1 -1
- package/dist/core/hook-rewrite.d.ts +48 -0
- package/dist/core/hook-rewrite.d.ts.map +1 -0
- package/dist/core/hook-rewrite.js +151 -0
- package/dist/core/hook-rewrite.js.map +1 -0
- package/dist/core/hook-rewrite.test.d.ts +2 -0
- package/dist/core/hook-rewrite.test.d.ts.map +1 -0
- package/dist/core/hook-rewrite.test.js +58 -0
- package/dist/core/hook-rewrite.test.js.map +1 -0
- package/dist/core/hosts/lazy.d.ts.map +1 -1
- package/dist/core/hosts/lazy.js +2 -0
- package/dist/core/hosts/lazy.js.map +1 -1
- package/dist/core/hosts/premiere/adapter.d.ts +1 -0
- package/dist/core/hosts/premiere/adapter.d.ts.map +1 -1
- package/dist/core/hosts/premiere/adapter.js.map +1 -1
- package/dist/core/hosts/premiere/bridge-source.d.ts.map +1 -1
- package/dist/core/hosts/premiere/bridge-source.js +6 -3
- package/dist/core/hosts/premiere/bridge-source.js.map +1 -1
- package/dist/core/hosts/resolve/adapter.d.ts +1 -0
- package/dist/core/hosts/resolve/adapter.d.ts.map +1 -1
- package/dist/core/hosts/resolve/adapter.js.map +1 -1
- package/dist/core/hosts/resolve/bridge-source.d.ts.map +1 -1
- package/dist/core/hosts/resolve/bridge-source.js +31 -4
- package/dist/core/hosts/resolve/bridge-source.js.map +1 -1
- package/dist/core/hosts/resolve/bridge.d.ts +2 -19
- package/dist/core/hosts/resolve/bridge.d.ts.map +1 -1
- package/dist/core/hosts/resolve/bridge.js +70 -41
- package/dist/core/hosts/resolve/bridge.js.map +1 -1
- package/dist/core/hosts/resolve/bridge.test.js +130 -0
- package/dist/core/hosts/resolve/bridge.test.js.map +1 -1
- package/dist/core/hosts/types.d.ts +6 -0
- package/dist/core/hosts/types.d.ts.map +1 -1
- package/dist/core/hosts/types.js.map +1 -1
- package/dist/core/logger.d.ts +32 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +188 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/loop-match.d.ts +57 -0
- package/dist/core/loop-match.d.ts.map +1 -0
- package/dist/core/loop-match.js +91 -0
- package/dist/core/loop-match.js.map +1 -0
- package/dist/core/media/ffmpeg.d.ts.map +1 -1
- package/dist/core/media/ffmpeg.js +14 -3
- package/dist/core/media/ffmpeg.js.map +1 -1
- package/dist/core/multi-format.d.ts +67 -0
- package/dist/core/multi-format.d.ts.map +1 -0
- package/dist/core/multi-format.js +127 -0
- package/dist/core/multi-format.js.map +1 -0
- package/dist/core/multi-format.test.d.ts +2 -0
- package/dist/core/multi-format.test.d.ts.map +1 -0
- package/dist/core/multi-format.test.js +151 -0
- package/dist/core/multi-format.test.js.map +1 -0
- package/dist/core/python/beats.py +61 -0
- package/dist/core/python/face_reframe.py +163 -0
- package/dist/core/python/sidecar-path.d.ts +13 -0
- package/dist/core/python/sidecar-path.d.ts.map +1 -0
- package/dist/core/python/sidecar-path.js +24 -0
- package/dist/core/python/sidecar-path.js.map +1 -0
- package/dist/core/python.d.ts +57 -0
- package/dist/core/python.d.ts.map +1 -0
- package/dist/core/python.js +107 -0
- package/dist/core/python.js.map +1 -0
- package/dist/core/python.test.d.ts +2 -0
- package/dist/core/python.test.d.ts.map +1 -0
- package/dist/core/python.test.js +129 -0
- package/dist/core/python.test.js.map +1 -0
- package/dist/core/retention-structure.d.ts +81 -0
- package/dist/core/retention-structure.d.ts.map +1 -0
- package/dist/core/retention-structure.js +206 -0
- package/dist/core/retention-structure.js.map +1 -0
- package/dist/core/retention-structure.test.d.ts +2 -0
- package/dist/core/retention-structure.test.d.ts.map +1 -0
- package/dist/core/retention-structure.test.js +88 -0
- package/dist/core/retention-structure.test.js.map +1 -0
- package/dist/core/review.d.ts +17 -0
- package/dist/core/review.d.ts.map +1 -1
- package/dist/core/review.js +20 -24
- package/dist/core/review.js.map +1 -1
- package/dist/core/safe-paths.d.ts +11 -0
- package/dist/core/safe-paths.d.ts.map +1 -1
- package/dist/core/safe-paths.js +26 -10
- package/dist/core/safe-paths.js.map +1 -1
- package/dist/core/safe-paths.test.js +16 -0
- package/dist/core/safe-paths.test.js.map +1 -1
- package/dist/core/skills-loader.d.ts +48 -2
- package/dist/core/skills-loader.d.ts.map +1 -1
- package/dist/core/skills-loader.js +97 -19
- package/dist/core/skills-loader.js.map +1 -1
- package/dist/core/skills-loader.test.js +63 -1
- package/dist/core/skills-loader.test.js.map +1 -1
- package/dist/core/srt.d.ts +42 -7
- package/dist/core/srt.d.ts.map +1 -1
- package/dist/core/srt.js +101 -32
- package/dist/core/srt.js.map +1 -1
- package/dist/core/srt.test.js +54 -1
- package/dist/core/srt.test.js.map +1 -1
- package/dist/core/thumbnail-compose.d.ts +58 -0
- package/dist/core/thumbnail-compose.d.ts.map +1 -0
- package/dist/core/thumbnail-compose.js +101 -0
- package/dist/core/thumbnail-compose.js.map +1 -0
- package/dist/core/thumbnail-promise.d.ts +46 -0
- package/dist/core/thumbnail-promise.d.ts.map +1 -0
- package/dist/core/thumbnail-promise.js +133 -0
- package/dist/core/thumbnail-promise.js.map +1 -0
- package/dist/core/thumbnail-promise.test.d.ts +2 -0
- package/dist/core/thumbnail-promise.test.d.ts.map +1 -0
- package/dist/core/thumbnail-promise.test.js +52 -0
- package/dist/core/thumbnail-promise.test.js.map +1 -0
- package/dist/core/viral-moments.d.ts +70 -0
- package/dist/core/viral-moments.d.ts.map +1 -0
- package/dist/core/viral-moments.js +192 -0
- package/dist/core/viral-moments.js.map +1 -0
- package/dist/core/viral-moments.test.d.ts +2 -0
- package/dist/core/viral-moments.test.d.ts.map +1 -0
- package/dist/core/viral-moments.test.js +153 -0
- package/dist/core/viral-moments.test.js.map +1 -0
- package/dist/core/whisper.d.ts +16 -0
- package/dist/core/whisper.d.ts.map +1 -1
- package/dist/core/whisper.js +72 -5
- package/dist/core/whisper.js.map +1 -1
- package/dist/core/whisper.test.js +111 -1
- package/dist/core/whisper.test.js.map +1 -1
- package/dist/core/youtube-metadata.d.ts +44 -0
- package/dist/core/youtube-metadata.d.ts.map +1 -0
- package/dist/core/youtube-metadata.js +168 -0
- package/dist/core/youtube-metadata.js.map +1 -0
- package/dist/core/youtube-metadata.test.d.ts +2 -0
- package/dist/core/youtube-metadata.test.d.ts.map +1 -0
- package/dist/core/youtube-metadata.test.js +132 -0
- package/dist/core/youtube-metadata.test.js.map +1 -0
- package/dist/prompt-commands.d.ts +24 -0
- package/dist/prompt-commands.d.ts.map +1 -0
- package/dist/prompt-commands.js +243 -0
- package/dist/prompt-commands.js.map +1 -0
- package/dist/prompt-commands.test.d.ts +2 -0
- package/dist/prompt-commands.test.d.ts.map +1 -0
- package/dist/prompt-commands.test.js +46 -0
- package/dist/prompt-commands.test.js.map +1 -0
- package/dist/skills.d.ts +6 -6
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +1426 -445
- package/dist/skills.js.map +1 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +108 -0
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/add-fades.d.ts.map +1 -1
- package/dist/tools/add-fades.js +2 -1
- package/dist/tools/add-fades.js.map +1 -1
- package/dist/tools/add-sfx-at-cuts.d.ts.map +1 -1
- package/dist/tools/add-sfx-at-cuts.js +36 -11
- package/dist/tools/add-sfx-at-cuts.js.map +1 -1
- package/dist/tools/add-sfx-to-timeline.d.ts +34 -0
- package/dist/tools/add-sfx-to-timeline.d.ts.map +1 -0
- package/dist/tools/add-sfx-to-timeline.js +169 -0
- package/dist/tools/add-sfx-to-timeline.js.map +1 -0
- package/dist/tools/add-sfx-to-timeline.test.d.ts +2 -0
- package/dist/tools/add-sfx-to-timeline.test.d.ts.map +1 -0
- package/dist/tools/add-sfx-to-timeline.test.js +181 -0
- package/dist/tools/add-sfx-to-timeline.test.js.map +1 -0
- package/dist/tools/audit-first-frame.d.ts +36 -0
- package/dist/tools/audit-first-frame.d.ts.map +1 -0
- package/dist/tools/audit-first-frame.js +181 -0
- package/dist/tools/audit-first-frame.js.map +1 -0
- package/dist/tools/audit-retention-structure.d.ts +20 -0
- package/dist/tools/audit-retention-structure.d.ts.map +1 -0
- package/dist/tools/audit-retention-structure.js +95 -0
- package/dist/tools/audit-retention-structure.js.map +1 -0
- package/dist/tools/audit-retention-structure.test.d.ts +2 -0
- package/dist/tools/audit-retention-structure.test.d.ts.map +1 -0
- package/dist/tools/audit-retention-structure.test.js +93 -0
- package/dist/tools/audit-retention-structure.test.js.map +1 -0
- package/dist/tools/bleep-words.d.ts +59 -0
- package/dist/tools/bleep-words.d.ts.map +1 -0
- package/dist/tools/bleep-words.js +211 -0
- package/dist/tools/bleep-words.js.map +1 -0
- package/dist/tools/bleep-words.test.d.ts +2 -0
- package/dist/tools/bleep-words.test.d.ts.map +1 -0
- package/dist/tools/bleep-words.test.js +96 -0
- package/dist/tools/bleep-words.test.js.map +1 -0
- package/dist/tools/burn-subtitles.d.ts.map +1 -1
- package/dist/tools/burn-subtitles.js +10 -5
- package/dist/tools/burn-subtitles.js.map +1 -1
- package/dist/tools/clean-audio.d.ts.map +1 -1
- package/dist/tools/clean-audio.js +2 -1
- package/dist/tools/clean-audio.js.map +1 -1
- package/dist/tools/cluster-takes.js +2 -1
- package/dist/tools/cluster-takes.js.map +1 -1
- package/dist/tools/compose-thumbnail-variants.d.ts +70 -0
- package/dist/tools/compose-thumbnail-variants.d.ts.map +1 -0
- package/dist/tools/compose-thumbnail-variants.js +274 -0
- package/dist/tools/compose-thumbnail-variants.js.map +1 -0
- package/dist/tools/compose-thumbnail.d.ts +6 -13
- package/dist/tools/compose-thumbnail.d.ts.map +1 -1
- package/dist/tools/compose-thumbnail.js +44 -81
- package/dist/tools/compose-thumbnail.js.map +1 -1
- package/dist/tools/concat-videos.d.ts.map +1 -1
- package/dist/tools/concat-videos.js +12 -5
- package/dist/tools/concat-videos.js.map +1 -1
- package/dist/tools/concat-videos.test.d.ts +2 -0
- package/dist/tools/concat-videos.test.d.ts.map +1 -0
- package/dist/tools/concat-videos.test.js +103 -0
- package/dist/tools/concat-videos.test.js.map +1 -0
- package/dist/tools/crossfade-videos.d.ts.map +1 -1
- package/dist/tools/crossfade-videos.js +2 -1
- package/dist/tools/crossfade-videos.js.map +1 -1
- package/dist/tools/cut-filler-words.d.ts.map +1 -1
- package/dist/tools/cut-filler-words.js +24 -8
- package/dist/tools/cut-filler-words.js.map +1 -1
- package/dist/tools/detect-speaker-changes.js +2 -1
- package/dist/tools/detect-speaker-changes.js.map +1 -1
- package/dist/tools/extract-audio.d.ts.map +1 -1
- package/dist/tools/extract-audio.js +13 -7
- package/dist/tools/extract-audio.js.map +1 -1
- package/dist/tools/face-reframe.d.ts +30 -0
- package/dist/tools/face-reframe.d.ts.map +1 -0
- package/dist/tools/face-reframe.js +143 -0
- package/dist/tools/face-reframe.js.map +1 -0
- package/dist/tools/face-reframe.test.d.ts +2 -0
- package/dist/tools/face-reframe.test.d.ts.map +1 -0
- package/dist/tools/face-reframe.test.js +139 -0
- package/dist/tools/face-reframe.test.js.map +1 -0
- package/dist/tools/find-viral-moments.d.ts +23 -0
- package/dist/tools/find-viral-moments.d.ts.map +1 -0
- package/dist/tools/find-viral-moments.js +176 -0
- package/dist/tools/find-viral-moments.js.map +1 -0
- package/dist/tools/find-viral-moments.test.d.ts +2 -0
- package/dist/tools/find-viral-moments.test.d.ts.map +1 -0
- package/dist/tools/find-viral-moments.test.js +144 -0
- package/dist/tools/find-viral-moments.test.js.map +1 -0
- package/dist/tools/generate-gif.d.ts.map +1 -1
- package/dist/tools/generate-gif.js +47 -40
- package/dist/tools/generate-gif.js.map +1 -1
- package/dist/tools/generate-gif.test.d.ts +2 -0
- package/dist/tools/generate-gif.test.d.ts.map +1 -0
- package/dist/tools/generate-gif.test.js +115 -0
- package/dist/tools/generate-gif.test.js.map +1 -0
- package/dist/tools/generate-outro.d.ts +18 -0
- package/dist/tools/generate-outro.d.ts.map +1 -0
- package/dist/tools/generate-outro.js +175 -0
- package/dist/tools/generate-outro.js.map +1 -0
- package/dist/tools/generate-youtube-metadata.d.ts +23 -0
- package/dist/tools/generate-youtube-metadata.d.ts.map +1 -0
- package/dist/tools/generate-youtube-metadata.js +103 -0
- package/dist/tools/generate-youtube-metadata.js.map +1 -0
- package/dist/tools/generate-youtube-metadata.test.d.ts +2 -0
- package/dist/tools/generate-youtube-metadata.test.d.ts.map +1 -0
- package/dist/tools/generate-youtube-metadata.test.js +118 -0
- package/dist/tools/generate-youtube-metadata.test.js.map +1 -0
- package/dist/tools/index.d.ts +14 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +130 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/index.test.js +27 -1
- package/dist/tools/index.test.js.map +1 -1
- package/dist/tools/ken-burns.d.ts.map +1 -1
- package/dist/tools/ken-burns.js +2 -1
- package/dist/tools/ken-burns.js.map +1 -1
- package/dist/tools/loop-match-short.d.ts +22 -0
- package/dist/tools/loop-match-short.d.ts.map +1 -0
- package/dist/tools/loop-match-short.js +107 -0
- package/dist/tools/loop-match-short.js.map +1 -0
- package/dist/tools/mix-audio.d.ts.map +1 -1
- package/dist/tools/mix-audio.js +2 -1
- package/dist/tools/mix-audio.js.map +1 -1
- package/dist/tools/normalize-loudness.d.ts.map +1 -1
- package/dist/tools/normalize-loudness.js +2 -1
- package/dist/tools/normalize-loudness.js.map +1 -1
- package/dist/tools/path-traversal.test.d.ts +15 -0
- package/dist/tools/path-traversal.test.d.ts.map +1 -0
- package/dist/tools/path-traversal.test.js +223 -0
- package/dist/tools/path-traversal.test.js.map +1 -0
- package/dist/tools/pick-best-takes.js +2 -1
- package/dist/tools/pick-best-takes.js.map +1 -1
- package/dist/tools/punch-in.d.ts.map +1 -1
- package/dist/tools/punch-in.js +2 -1
- package/dist/tools/punch-in.js.map +1 -1
- package/dist/tools/read-transcript.js +2 -1
- package/dist/tools/read-transcript.js.map +1 -1
- package/dist/tools/render-multi-format.d.ts +35 -0
- package/dist/tools/render-multi-format.d.ts.map +1 -0
- package/dist/tools/render-multi-format.js +206 -0
- package/dist/tools/render-multi-format.js.map +1 -0
- package/dist/tools/render-multi-format.test.d.ts +2 -0
- package/dist/tools/render-multi-format.test.d.ts.map +1 -0
- package/dist/tools/render-multi-format.test.js +312 -0
- package/dist/tools/render-multi-format.test.js.map +1 -0
- package/dist/tools/render.d.ts.map +1 -1
- package/dist/tools/render.js +2 -2
- package/dist/tools/render.js.map +1 -1
- package/dist/tools/rewrite-hook.d.ts +32 -0
- package/dist/tools/rewrite-hook.d.ts.map +1 -0
- package/dist/tools/rewrite-hook.js +65 -0
- package/dist/tools/rewrite-hook.js.map +1 -0
- package/dist/tools/score-clip.d.ts +30 -0
- package/dist/tools/score-clip.d.ts.map +1 -0
- package/dist/tools/score-clip.js +109 -0
- package/dist/tools/score-clip.js.map +1 -0
- package/dist/tools/score-clip.test.d.ts +2 -0
- package/dist/tools/score-clip.test.d.ts.map +1 -0
- package/dist/tools/score-clip.test.js +110 -0
- package/dist/tools/score-clip.test.js.map +1 -0
- package/dist/tools/search-tools.d.ts +34 -0
- package/dist/tools/search-tools.d.ts.map +1 -0
- package/dist/tools/search-tools.js +86 -0
- package/dist/tools/search-tools.js.map +1 -0
- package/dist/tools/search-tools.test.d.ts +2 -0
- package/dist/tools/search-tools.test.d.ts.map +1 -0
- package/dist/tools/search-tools.test.js +60 -0
- package/dist/tools/search-tools.test.js.map +1 -0
- package/dist/tools/snap-cuts-to-beats.d.ts +18 -0
- package/dist/tools/snap-cuts-to-beats.d.ts.map +1 -0
- package/dist/tools/snap-cuts-to-beats.js +110 -0
- package/dist/tools/snap-cuts-to-beats.js.map +1 -0
- package/dist/tools/snap-cuts-to-beats.test.d.ts +2 -0
- package/dist/tools/snap-cuts-to-beats.test.d.ts.map +1 -0
- package/dist/tools/snap-cuts-to-beats.test.js +99 -0
- package/dist/tools/snap-cuts-to-beats.test.js.map +1 -0
- package/dist/tools/speed-ramp.d.ts.map +1 -1
- package/dist/tools/speed-ramp.js +2 -1
- package/dist/tools/speed-ramp.js.map +1 -1
- package/dist/tools/stabilize-video.d.ts.map +1 -1
- package/dist/tools/stabilize-video.js +2 -1
- package/dist/tools/stabilize-video.js.map +1 -1
- package/dist/tools/suggest-broll.d.ts +34 -0
- package/dist/tools/suggest-broll.d.ts.map +1 -0
- package/dist/tools/suggest-broll.js +367 -0
- package/dist/tools/suggest-broll.js.map +1 -0
- package/dist/tools/suggest-broll.test.d.ts +2 -0
- package/dist/tools/suggest-broll.test.d.ts.map +1 -0
- package/dist/tools/suggest-broll.test.js +217 -0
- package/dist/tools/suggest-broll.test.js.map +1 -0
- package/dist/tools/text-based-cut.d.ts +33 -0
- package/dist/tools/text-based-cut.d.ts.map +1 -0
- package/dist/tools/text-based-cut.js +172 -0
- package/dist/tools/text-based-cut.js.map +1 -0
- package/dist/tools/text-based-cut.test.d.ts +2 -0
- package/dist/tools/text-based-cut.test.d.ts.map +1 -0
- package/dist/tools/text-based-cut.test.js +32 -0
- package/dist/tools/text-based-cut.test.js.map +1 -0
- package/dist/tools/transcribe.d.ts +1 -1
- package/dist/tools/transition-videos.d.ts +1 -1
- package/dist/tools/transition-videos.d.ts.map +1 -1
- package/dist/tools/transition-videos.js +2 -1
- package/dist/tools/transition-videos.js.map +1 -1
- package/dist/tools/trim-dead-air.d.ts +59 -0
- package/dist/tools/trim-dead-air.d.ts.map +1 -0
- package/dist/tools/trim-dead-air.js +215 -0
- package/dist/tools/trim-dead-air.js.map +1 -0
- package/dist/tools/trim-dead-air.test.d.ts +2 -0
- package/dist/tools/trim-dead-air.test.d.ts.map +1 -0
- package/dist/tools/trim-dead-air.test.js +75 -0
- package/dist/tools/trim-dead-air.test.js.map +1 -0
- package/dist/tools/verify-thumbnail-promise.d.ts +33 -0
- package/dist/tools/verify-thumbnail-promise.d.ts.map +1 -0
- package/dist/tools/verify-thumbnail-promise.js +112 -0
- package/dist/tools/verify-thumbnail-promise.js.map +1 -0
- package/dist/tools/verify-thumbnail-promise.test.d.ts +2 -0
- package/dist/tools/verify-thumbnail-promise.test.d.ts.map +1 -0
- package/dist/tools/verify-thumbnail-promise.test.js +38 -0
- package/dist/tools/verify-thumbnail-promise.test.js.map +1 -0
- package/dist/tools/write-keyword-captions.d.ts +7 -0
- package/dist/tools/write-keyword-captions.d.ts.map +1 -1
- package/dist/tools/write-keyword-captions.js +35 -4
- package/dist/tools/write-keyword-captions.js.map +1 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +75 -11
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/tool-formatters.d.ts +30 -0
- package/dist/ui/tool-formatters.d.ts.map +1 -0
- package/dist/ui/tool-formatters.js +461 -0
- package/dist/ui/tool-formatters.js.map +1 -0
- package/dist/ui/tool-formatters.test.d.ts +2 -0
- package/dist/ui/tool-formatters.test.d.ts.map +1 -0
- package/dist/ui/tool-formatters.test.js +143 -0
- package/dist/ui/tool-formatters.test.js.map +1 -0
- package/package.json +10 -9
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { combineClipScore, parseClipScoreResponse, scoreClipInternal } from "./clip-scoring.js";
|
|
3
|
+
describe("parseClipScoreResponse", () => {
|
|
4
|
+
it("parses a well-formed response", () => {
|
|
5
|
+
const r = parseClipScoreResponse(JSON.stringify({ hook: 0.8, flow: 0.7, engagement: 0.9, trend: 0.4, why: "strong open" }));
|
|
6
|
+
expect(r.hook).toBe(0.8);
|
|
7
|
+
expect(r.flow).toBe(0.7);
|
|
8
|
+
expect(r.engagement).toBe(0.9);
|
|
9
|
+
expect(r.trend).toBe(0.4);
|
|
10
|
+
expect(r.why).toContain("strong");
|
|
11
|
+
});
|
|
12
|
+
it("clamps out-of-range values to [0,1]", () => {
|
|
13
|
+
const r = parseClipScoreResponse(JSON.stringify({ hook: 99, flow: -5, engagement: 0.5, trend: 1 }));
|
|
14
|
+
expect(r.hook).toBe(1);
|
|
15
|
+
expect(r.flow).toBe(0);
|
|
16
|
+
expect(r.engagement).toBe(0.5);
|
|
17
|
+
expect(r.trend).toBe(1);
|
|
18
|
+
});
|
|
19
|
+
it("falls back to 0 on missing fields", () => {
|
|
20
|
+
const r = parseClipScoreResponse("{}");
|
|
21
|
+
expect(r.hook).toBe(0);
|
|
22
|
+
expect(r.flow).toBe(0);
|
|
23
|
+
expect(r.engagement).toBe(0);
|
|
24
|
+
expect(r.trend).toBe(0);
|
|
25
|
+
});
|
|
26
|
+
it("survives prose wrapping the JSON", () => {
|
|
27
|
+
const r = parseClipScoreResponse('pre {"hook":0.5,"flow":0.5,"engagement":0.5,"trend":0.5,"why":"x"} post');
|
|
28
|
+
expect(r.hook).toBe(0.5);
|
|
29
|
+
});
|
|
30
|
+
it("throws on totally absent JSON", () => {
|
|
31
|
+
expect(() => parseClipScoreResponse("no json here")).toThrow(/no JSON/);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe("combineClipScore", () => {
|
|
35
|
+
it("produces 100 for max scores with default weights", () => {
|
|
36
|
+
const r = combineClipScore({ hook: 1, flow: 1, engagement: 1, trend: 1, why: "" }, 30);
|
|
37
|
+
expect(r.score).toBe(100);
|
|
38
|
+
expect(r.durationSec).toBe(30);
|
|
39
|
+
});
|
|
40
|
+
it("produces 0 for min scores", () => {
|
|
41
|
+
const r = combineClipScore({ hook: 0, flow: 0, engagement: 0, trend: 0, why: "" }, 30);
|
|
42
|
+
expect(r.score).toBe(0);
|
|
43
|
+
});
|
|
44
|
+
it("respects custom weights", () => {
|
|
45
|
+
// hook=1, others=0; with weights {hook:100, flow:0, engagement:0, trend:0}
|
|
46
|
+
// → 100. Default weights would put it at 30.
|
|
47
|
+
const r = combineClipScore({ hook: 1, flow: 0, engagement: 0, trend: 0, why: "" }, 30, {
|
|
48
|
+
hook: 100,
|
|
49
|
+
flow: 0,
|
|
50
|
+
engagement: 0,
|
|
51
|
+
trend: 0,
|
|
52
|
+
});
|
|
53
|
+
expect(r.score).toBe(100);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe("scoreClipInternal", () => {
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
vi.unstubAllGlobals();
|
|
59
|
+
delete process.env.OPENAI_API_KEY;
|
|
60
|
+
});
|
|
61
|
+
it("throws when no API key is configured", async () => {
|
|
62
|
+
delete process.env.OPENAI_API_KEY;
|
|
63
|
+
await expect(scoreClipInternal("hello world", 0, 10)).rejects.toThrow(/OPENAI_API_KEY/);
|
|
64
|
+
});
|
|
65
|
+
it("calls OpenAI chat completions with json_object response format and parses the result", async () => {
|
|
66
|
+
process.env.OPENAI_API_KEY = "test-key";
|
|
67
|
+
let captured;
|
|
68
|
+
vi.stubGlobal("fetch", vi.fn(async (url, init) => {
|
|
69
|
+
captured = { url, body: JSON.parse(init?.body ?? "{}") };
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
status: 200,
|
|
73
|
+
json: async () => ({
|
|
74
|
+
choices: [
|
|
75
|
+
{
|
|
76
|
+
message: {
|
|
77
|
+
content: JSON.stringify({
|
|
78
|
+
hook: 0.8,
|
|
79
|
+
flow: 0.6,
|
|
80
|
+
engagement: 0.7,
|
|
81
|
+
trend: 0.5,
|
|
82
|
+
why: "tight open, payoff lands",
|
|
83
|
+
}),
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
}),
|
|
88
|
+
text: async () => "",
|
|
89
|
+
};
|
|
90
|
+
}));
|
|
91
|
+
const r = await scoreClipInternal("a strong hook line", 5, 25);
|
|
92
|
+
expect(r.score).toBeGreaterThan(0);
|
|
93
|
+
expect(r.score).toBeLessThanOrEqual(100);
|
|
94
|
+
expect(r.hook).toBe(0.8);
|
|
95
|
+
expect(r.durationSec).toBe(20);
|
|
96
|
+
expect(captured?.url).toBe("https://api.openai.com/v1/chat/completions");
|
|
97
|
+
expect(captured?.body.response_format).toEqual({ type: "json_object" });
|
|
98
|
+
expect(captured?.body.temperature).toBe(0);
|
|
99
|
+
});
|
|
100
|
+
it("propagates HTTP errors with the OpenAI status", async () => {
|
|
101
|
+
process.env.OPENAI_API_KEY = "test-key";
|
|
102
|
+
vi.stubGlobal("fetch", vi.fn(async () => {
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
status: 429,
|
|
106
|
+
text: async () => "rate limited",
|
|
107
|
+
json: async () => ({}),
|
|
108
|
+
};
|
|
109
|
+
}));
|
|
110
|
+
await expect(scoreClipInternal("hi", 0, 10)).rejects.toThrow(/429/);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=clip-scoring.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clip-scoring.test.js","sourceRoot":"","sources":["../../src/core/clip-scoring.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emoji-injection pass for keyword captions.
|
|
3
|
+
*
|
|
4
|
+
* Submagic / CapCut have made the "💯 caption + 🔥 emoji" look the
|
|
5
|
+
* default vibe of viral short-form. Rather than ship a static emoji
|
|
6
|
+
* map, we ask the LLM (one call over ALL cues) for the single
|
|
7
|
+
* most-fitting emoji per cue (or empty when nothing clearly fits).
|
|
8
|
+
*
|
|
9
|
+
* Pure-ish: takes the OpenAI-style chat client as a dependency so the
|
|
10
|
+
* caller can swap in `fetch` directly OR mock it in tests.
|
|
11
|
+
*/
|
|
12
|
+
import type { AssCue } from "./ass.js";
|
|
13
|
+
export type EmojiDensity = "low" | "med" | "high";
|
|
14
|
+
export interface InjectEmojisOptions {
|
|
15
|
+
density?: EmojiDensity;
|
|
16
|
+
model?: string;
|
|
17
|
+
apiKey?: string;
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
/** Override fetch — for tests. Defaults to global fetch. */
|
|
20
|
+
fetchImpl?: typeof fetch;
|
|
21
|
+
}
|
|
22
|
+
export interface InjectEmojisResult {
|
|
23
|
+
cues: AssCue[];
|
|
24
|
+
/** Count of cues that received an emoji (excluding empty strings). */
|
|
25
|
+
injected: number;
|
|
26
|
+
/** When set, the LLM call failed and cues were returned unchanged. */
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Inject emojis into a list of cues via one LLM call. On failure
|
|
31
|
+
* (network down, malformed response, length mismatch) we return the
|
|
32
|
+
* cues UNCHANGED + populate `error` so the caller can surface it.
|
|
33
|
+
*
|
|
34
|
+
* For density="high" the emoji is rendered on its own line ABOVE the
|
|
35
|
+
* caption text (separated by ASS's `\\N` break) for the stacked CapCut
|
|
36
|
+
* look. For low/med it's appended inline.
|
|
37
|
+
*/
|
|
38
|
+
export declare function injectEmojis(cues: AssCue[], opts?: InjectEmojisOptions): Promise<InjectEmojisResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Tolerant parser. Accepts either {emojis: [...]} or a bare array, pads
|
|
41
|
+
* / truncates to `expected` length so length always lines up. Exported
|
|
42
|
+
* for testing.
|
|
43
|
+
*/
|
|
44
|
+
export declare function parseEmojiResponse(content: string, expected: number): string[];
|
|
45
|
+
//# sourceMappingURL=emoji-captions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emoji-captions.d.ts","sourceRoot":"","sources":["../../src/core/emoji-captions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGvC,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAElD,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAgBD;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,kBAAkB,CAAC,CAgE7B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAsB9E"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { resolveApiKey } from "./auth/api-keys.js";
|
|
2
|
+
const SYSTEM = `You are an emoji selector for short-form video captions. For each caption line, return the SINGLE most-fitting emoji or an empty string when nothing clearly fits.
|
|
3
|
+
|
|
4
|
+
Rules:
|
|
5
|
+
- Output JSON: {"emojis": [string, ...]} aligned 1-to-1 with the input order. Length MUST equal the input length.
|
|
6
|
+
- Each entry is a single emoji character (e.g. "🔥") or "". No multi-emoji entries, no text.
|
|
7
|
+
- Don't force emojis where they'd feel random — empty string is the right answer ~half the time.
|
|
8
|
+
- Match the density target the user specifies.`;
|
|
9
|
+
const DENSITY_TARGETS = {
|
|
10
|
+
low: "Roughly 1 in 4 cues should get an emoji. Be very selective — only when an emoji genuinely amplifies the line.",
|
|
11
|
+
med: "Roughly 1 in 2 cues should get an emoji. Pick the obvious wins; leave neutral lines empty.",
|
|
12
|
+
high: "Every cue should get an emoji where ANY emoji fits. Empty only for purely connective lines.",
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Inject emojis into a list of cues via one LLM call. On failure
|
|
16
|
+
* (network down, malformed response, length mismatch) we return the
|
|
17
|
+
* cues UNCHANGED + populate `error` so the caller can surface it.
|
|
18
|
+
*
|
|
19
|
+
* For density="high" the emoji is rendered on its own line ABOVE the
|
|
20
|
+
* caption text (separated by ASS's `\\N` break) for the stacked CapCut
|
|
21
|
+
* look. For low/med it's appended inline.
|
|
22
|
+
*/
|
|
23
|
+
export async function injectEmojis(cues, opts = {}) {
|
|
24
|
+
if (cues.length === 0)
|
|
25
|
+
return { cues, injected: 0 };
|
|
26
|
+
const density = opts.density ?? "med";
|
|
27
|
+
const model = opts.model ?? "gpt-4o-mini";
|
|
28
|
+
const apiKey = opts.apiKey ?? resolveApiKey("OPENAI_API_KEY", "openai");
|
|
29
|
+
if (!apiKey) {
|
|
30
|
+
return { cues, injected: 0, error: "OPENAI_API_KEY not set" };
|
|
31
|
+
}
|
|
32
|
+
const numbered = cues.map((c, i) => `${i + 1}. ${c.text}`).join("\n");
|
|
33
|
+
const userText = `Density target: ${DENSITY_TARGETS[density]}\n\n` +
|
|
34
|
+
`Return exactly ${cues.length} entries.\n\n` +
|
|
35
|
+
`CAPTIONS:\n${numbered}`;
|
|
36
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
37
|
+
let emojis;
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetchImpl("https://api.openai.com/v1/chat/completions", {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: {
|
|
42
|
+
Authorization: `Bearer ${apiKey}`,
|
|
43
|
+
"Content-Type": "application/json",
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify({
|
|
46
|
+
model,
|
|
47
|
+
messages: [
|
|
48
|
+
{ role: "system", content: SYSTEM },
|
|
49
|
+
{ role: "user", content: userText },
|
|
50
|
+
],
|
|
51
|
+
response_format: { type: "json_object" },
|
|
52
|
+
temperature: 0,
|
|
53
|
+
}),
|
|
54
|
+
signal: opts.signal,
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const text = await res.text().catch(() => "");
|
|
58
|
+
return {
|
|
59
|
+
cues,
|
|
60
|
+
injected: 0,
|
|
61
|
+
error: `OpenAI HTTP ${res.status}: ${text.slice(0, 160)}`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const data = (await res.json());
|
|
65
|
+
const content = data.choices?.[0]?.message?.content ?? "";
|
|
66
|
+
emojis = parseEmojiResponse(content, cues.length);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
return { cues, injected: 0, error: e.message };
|
|
70
|
+
}
|
|
71
|
+
if (emojis.length !== cues.length) {
|
|
72
|
+
return { cues, injected: 0, error: "emoji count mismatch" };
|
|
73
|
+
}
|
|
74
|
+
let injected = 0;
|
|
75
|
+
const out = cues.map((c, i) => {
|
|
76
|
+
const e = (emojis[i] ?? "").trim();
|
|
77
|
+
if (!e)
|
|
78
|
+
return c;
|
|
79
|
+
injected++;
|
|
80
|
+
const text = density === "high" ? `${e}\\N${c.text}` : `${c.text} ${e}`;
|
|
81
|
+
return { ...c, text };
|
|
82
|
+
});
|
|
83
|
+
return { cues: out, injected };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Tolerant parser. Accepts either {emojis: [...]} or a bare array, pads
|
|
87
|
+
* / truncates to `expected` length so length always lines up. Exported
|
|
88
|
+
* for testing.
|
|
89
|
+
*/
|
|
90
|
+
export function parseEmojiResponse(content, expected) {
|
|
91
|
+
let arr;
|
|
92
|
+
try {
|
|
93
|
+
const obj = JSON.parse(content);
|
|
94
|
+
if (Array.isArray(obj))
|
|
95
|
+
arr = obj;
|
|
96
|
+
else if (typeof obj === "object" && obj !== null) {
|
|
97
|
+
const v = obj.emojis;
|
|
98
|
+
arr = Array.isArray(v) ? v : [];
|
|
99
|
+
}
|
|
100
|
+
else
|
|
101
|
+
arr = [];
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
const m = content.match(/\[[\s\S]*\]/);
|
|
105
|
+
if (m) {
|
|
106
|
+
try {
|
|
107
|
+
arr = JSON.parse(m[0]);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
arr = [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else
|
|
114
|
+
arr = [];
|
|
115
|
+
}
|
|
116
|
+
const list = Array.isArray(arr) ? arr.map((v) => (typeof v === "string" ? v : "")) : [];
|
|
117
|
+
while (list.length < expected)
|
|
118
|
+
list.push("");
|
|
119
|
+
return list.slice(0, expected);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=emoji-captions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emoji-captions.js","sourceRoot":"","sources":["../../src/core/emoji-captions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Face-tracked reframe — pure planning + ffmpeg-filter synthesis.
|
|
3
|
+
*
|
|
4
|
+
* `analyzeReframe` shells out to the MediaPipe / PySceneDetect sidecar to get
|
|
5
|
+
* a per-shot smoothed face centre. `buildReframeFilter` is a pure function
|
|
6
|
+
* that compiles the plan into a single ffmpeg `crop=...` expression that
|
|
7
|
+
* follows the subject across shots. Splitting the heavy I/O from the
|
|
8
|
+
* filter math keeps the latter unit-testable without Python installed.
|
|
9
|
+
*/
|
|
10
|
+
export type Aspect = "9:16" | "1:1" | "4:5" | "16:9";
|
|
11
|
+
export interface AspectRatio {
|
|
12
|
+
/** Width side of the ratio (e.g. 9 for 9:16). */
|
|
13
|
+
w: number;
|
|
14
|
+
/** Height side of the ratio (e.g. 16 for 9:16). */
|
|
15
|
+
h: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function parseAspect(aspect: Aspect): AspectRatio;
|
|
18
|
+
export interface ReframeFrameSample {
|
|
19
|
+
atSec: number;
|
|
20
|
+
faceCx: number;
|
|
21
|
+
faceCy: number;
|
|
22
|
+
faceW: number;
|
|
23
|
+
faceH: number;
|
|
24
|
+
}
|
|
25
|
+
export type ShotMode = "face" | "motion" | "static";
|
|
26
|
+
export interface ReframeShot {
|
|
27
|
+
startSec: number;
|
|
28
|
+
endSec: number;
|
|
29
|
+
frames: ReframeFrameSample[];
|
|
30
|
+
/** Smoothed centre X (normalized 0-1). */
|
|
31
|
+
smoothedX: number;
|
|
32
|
+
/** Smoothed centre Y (normalized 0-1). */
|
|
33
|
+
smoothedY: number;
|
|
34
|
+
mode: ShotMode;
|
|
35
|
+
}
|
|
36
|
+
export interface ReframePlan {
|
|
37
|
+
shots: ReframeShot[];
|
|
38
|
+
totalSec: number;
|
|
39
|
+
fps: number;
|
|
40
|
+
sourceWidth: number;
|
|
41
|
+
sourceHeight: number;
|
|
42
|
+
}
|
|
43
|
+
export interface AnalyzeReframeOptions {
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
/** Frames per second to face-detect. Default 5. */
|
|
46
|
+
sampleFps?: number;
|
|
47
|
+
/** MediaPipe min_detection_confidence. Default 0.5. */
|
|
48
|
+
minDetectionConfidence?: number;
|
|
49
|
+
/** Smoothing window in seconds (informational; sidecar uses median). Default 0.5. */
|
|
50
|
+
smoothingWindowSec?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Spawn the face-reframe sidecar; return the parsed shot plan.
|
|
54
|
+
* Throws on sidecar failure with the structured error embedded.
|
|
55
|
+
*/
|
|
56
|
+
export declare function analyzeReframe(videoPath: string, opts?: AnalyzeReframeOptions): Promise<ReframePlan>;
|
|
57
|
+
export interface BuildReframeFilterResult {
|
|
58
|
+
/** ffmpeg `-vf` value (already includes both crop + scale). */
|
|
59
|
+
filter: string;
|
|
60
|
+
/** Output width (after crop, before scale-back). */
|
|
61
|
+
outWidth: number;
|
|
62
|
+
/** Output height (after crop, before scale-back). */
|
|
63
|
+
outHeight: number;
|
|
64
|
+
/** Per-shot crop X positions (after clamping into [0, srcW-outW]). */
|
|
65
|
+
shotXs: number[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Compute the crop dimensions for a target aspect when cropping FROM a
|
|
69
|
+
* source frame. The crop is sized to fit ENTIRELY inside the source — i.e.
|
|
70
|
+
* we letterbox neither dimension. The returned `outW` × `outH` matches the
|
|
71
|
+
* target aspect exactly.
|
|
72
|
+
*
|
|
73
|
+
* Pure / unit-testable.
|
|
74
|
+
*/
|
|
75
|
+
export declare function reframeCropSize(srcW: number, srcH: number, aspect: Aspect): {
|
|
76
|
+
outW: number;
|
|
77
|
+
outH: number;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Compile a per-shot smoothed-face plan into one ffmpeg `crop` expression
|
|
81
|
+
* that switches X position by timestamp. Each shot contributes one
|
|
82
|
+
* `if(between(t, a, b), x_i, ...)` tier; the outermost ELSE is a centre
|
|
83
|
+
* crop so the filter is a no-op outside any defined shot.
|
|
84
|
+
*
|
|
85
|
+
* The crop's Y is left static (we don't currently track vertical motion;
|
|
86
|
+
* faces tend to dominate the upper third on portrait crops anyway).
|
|
87
|
+
*
|
|
88
|
+
* Pure / unit-testable. No ffmpeg execution.
|
|
89
|
+
*/
|
|
90
|
+
export declare function buildReframeFilter(plan: ReframePlan, aspect: Aspect): BuildReframeFilterResult;
|
|
91
|
+
//# sourceMappingURL=face-reframe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"face-reframe.d.ts","sourceRoot":"","sources":["../../src/core/face-reframe.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,iDAAiD;IACjD,CAAC,EAAE,MAAM,CAAC;IACV,mDAAmD;IACnD,CAAC,EAAE,MAAM,CAAC;CACX;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAMvD;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEpD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,qFAAqF;IACrF,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,qBAA0B,GAC/B,OAAO,CAAC,WAAW,CAAC,CAiCtB;AAED,MAAM,WAAW,wBAAwB;IACvC,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA6BhC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,wBAAwB,CAsC9F"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { runPython } from "./python.js";
|
|
2
|
+
import { sidecarPath } from "./python/sidecar-path.js";
|
|
3
|
+
export function parseAspect(aspect) {
|
|
4
|
+
const [w, h] = aspect.split(":").map((n) => Number(n));
|
|
5
|
+
if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {
|
|
6
|
+
throw new Error(`invalid aspect: ${aspect}`);
|
|
7
|
+
}
|
|
8
|
+
return { w, h };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Spawn the face-reframe sidecar; return the parsed shot plan.
|
|
12
|
+
* Throws on sidecar failure with the structured error embedded.
|
|
13
|
+
*/
|
|
14
|
+
export async function analyzeReframe(videoPath, opts = {}) {
|
|
15
|
+
const script = sidecarPath("face_reframe.py");
|
|
16
|
+
const stdin = JSON.stringify({
|
|
17
|
+
videoPath,
|
|
18
|
+
sampleFps: opts.sampleFps,
|
|
19
|
+
minDetectionConfidence: opts.minDetectionConfidence,
|
|
20
|
+
smoothingWindowSec: opts.smoothingWindowSec,
|
|
21
|
+
});
|
|
22
|
+
const { code, stdout, stderr } = await runPython(script, [], {
|
|
23
|
+
signal: opts.signal,
|
|
24
|
+
stdin,
|
|
25
|
+
});
|
|
26
|
+
const trimmed = stdout.trim();
|
|
27
|
+
if (!trimmed) {
|
|
28
|
+
throw new Error(`face-reframe sidecar returned empty stdout (exit ${code}): ${tail(stderr)}`);
|
|
29
|
+
}
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = JSON.parse(trimmed);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new Error(`face-reframe sidecar returned malformed output: ${trimmed.slice(-200)} | stderr: ${tail(stderr)}`);
|
|
36
|
+
}
|
|
37
|
+
if (parsed && typeof parsed === "object" && "error" in parsed) {
|
|
38
|
+
const err = parsed;
|
|
39
|
+
throw new Error(err.error ?? "unknown face-reframe sidecar error");
|
|
40
|
+
}
|
|
41
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.shots)) {
|
|
42
|
+
throw new Error(`face-reframe sidecar returned unexpected shape: ${trimmed.slice(-200)}`);
|
|
43
|
+
}
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Compute the crop dimensions for a target aspect when cropping FROM a
|
|
48
|
+
* source frame. The crop is sized to fit ENTIRELY inside the source — i.e.
|
|
49
|
+
* we letterbox neither dimension. The returned `outW` × `outH` matches the
|
|
50
|
+
* target aspect exactly.
|
|
51
|
+
*
|
|
52
|
+
* Pure / unit-testable.
|
|
53
|
+
*/
|
|
54
|
+
export function reframeCropSize(srcW, srcH, aspect) {
|
|
55
|
+
if (srcW <= 0 || srcH <= 0) {
|
|
56
|
+
throw new Error("source dimensions must be positive");
|
|
57
|
+
}
|
|
58
|
+
const { w: aw, h: ah } = parseAspect(aspect);
|
|
59
|
+
// Two candidate crops:
|
|
60
|
+
// width-bound: outW = srcW, outH = srcW * ah / aw
|
|
61
|
+
// height-bound: outH = srcH, outW = srcH * aw / ah
|
|
62
|
+
// Pick whichever fits inside the source frame.
|
|
63
|
+
const heightFromW = (srcW * ah) / aw;
|
|
64
|
+
let outW;
|
|
65
|
+
let outH;
|
|
66
|
+
if (heightFromW <= srcH) {
|
|
67
|
+
// Width-bound (e.g. 16:9 source → 1:1 crop is height-bound; 16:9 source → 9:16 crop is height-bound;
|
|
68
|
+
// a square source → 9:16 crop is width-bound).
|
|
69
|
+
outW = srcW;
|
|
70
|
+
outH = heightFromW;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
outH = srcH;
|
|
74
|
+
outW = (srcH * aw) / ah;
|
|
75
|
+
}
|
|
76
|
+
// Round DOWN to even integers. ffmpeg's libx264 needs even crop dims;
|
|
77
|
+
// rounding down avoids exceeding the source.
|
|
78
|
+
outW = Math.floor(outW / 2) * 2;
|
|
79
|
+
outH = Math.floor(outH / 2) * 2;
|
|
80
|
+
if (outW <= 0 || outH <= 0) {
|
|
81
|
+
throw new Error("computed crop is degenerate");
|
|
82
|
+
}
|
|
83
|
+
return { outW, outH };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Compile a per-shot smoothed-face plan into one ffmpeg `crop` expression
|
|
87
|
+
* that switches X position by timestamp. Each shot contributes one
|
|
88
|
+
* `if(between(t, a, b), x_i, ...)` tier; the outermost ELSE is a centre
|
|
89
|
+
* crop so the filter is a no-op outside any defined shot.
|
|
90
|
+
*
|
|
91
|
+
* The crop's Y is left static (we don't currently track vertical motion;
|
|
92
|
+
* faces tend to dominate the upper third on portrait crops anyway).
|
|
93
|
+
*
|
|
94
|
+
* Pure / unit-testable. No ffmpeg execution.
|
|
95
|
+
*/
|
|
96
|
+
export function buildReframeFilter(plan, aspect) {
|
|
97
|
+
const { sourceWidth: srcW, sourceHeight: srcH } = plan;
|
|
98
|
+
const { outW, outH } = reframeCropSize(srcW, srcH, aspect);
|
|
99
|
+
// For each shot compute the desired top-left X from the smoothed face
|
|
100
|
+
// centre, then clamp into the legal range so the crop never escapes the
|
|
101
|
+
// frame. Y is centred — vertical face position varies less for talking
|
|
102
|
+
// heads, and exposing both axes invites jitter.
|
|
103
|
+
const yCentre = Math.floor((srcH - outH) / 2);
|
|
104
|
+
const shots = plan.shots
|
|
105
|
+
.filter((s) => s.endSec > s.startSec)
|
|
106
|
+
.sort((a, b) => a.startSec - b.startSec);
|
|
107
|
+
const shotXs = [];
|
|
108
|
+
// Build `xExpr` from the LAST shot backwards so each `if` wraps the next.
|
|
109
|
+
// Default (outside all shots): centre crop.
|
|
110
|
+
const centreX = Math.floor((srcW - outW) / 2);
|
|
111
|
+
let xExpr = String(centreX);
|
|
112
|
+
for (let i = shots.length - 1; i >= 0; i--) {
|
|
113
|
+
const s = shots[i];
|
|
114
|
+
const desiredCx = Math.round(s.smoothedX * srcW);
|
|
115
|
+
let x = desiredCx - Math.floor(outW / 2);
|
|
116
|
+
// Clamp into [0, srcW - outW].
|
|
117
|
+
if (x < 0)
|
|
118
|
+
x = 0;
|
|
119
|
+
const maxX = srcW - outW;
|
|
120
|
+
if (x > maxX)
|
|
121
|
+
x = maxX;
|
|
122
|
+
shotXs[i] = x;
|
|
123
|
+
const a = fmt(s.startSec);
|
|
124
|
+
const b = fmt(s.endSec);
|
|
125
|
+
xExpr = `if(between(t\\,${a}\\,${b})\\,${x}\\,${xExpr})`;
|
|
126
|
+
}
|
|
127
|
+
// crop=W:H:x:y — single filter, single pass. We pin Y to a constant
|
|
128
|
+
// (centre) for now; if we extend to vertical tracking later this becomes
|
|
129
|
+
// a parallel piecewise expression.
|
|
130
|
+
const cropFilter = `crop=${outW}:${outH}:x='${xExpr}':y=${yCentre}`;
|
|
131
|
+
return { filter: cropFilter, outWidth: outW, outHeight: outH, shotXs };
|
|
132
|
+
}
|
|
133
|
+
function fmt(n) {
|
|
134
|
+
if (!Number.isFinite(n))
|
|
135
|
+
return "0";
|
|
136
|
+
return Number(n.toFixed(4)).toString();
|
|
137
|
+
}
|
|
138
|
+
function tail(s) {
|
|
139
|
+
return s.split("\n").filter(Boolean).slice(-3).join(" | ").slice(-300);
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=face-reframe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"face-reframe.js","sourceRoot":"","sources":["../../src/core/face-reframe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"face-reframe.test.d.ts","sourceRoot":"","sources":["../../src/core/face-reframe.test.ts"],"names":[],"mappings":""}
|