@kenkaiiii/gg-editor 0.6.7 → 0.7.1
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/index.d.ts +18 -5
- package/dist/core/auth/index.d.ts.map +1 -1
- package/dist/core/auth/index.js +16 -4
- package/dist/core/auth/index.js.map +1 -1
- package/dist/core/auth/login.d.ts.map +1 -1
- package/dist/core/auth/login.js +4 -31
- 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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"multi-format.test.d.ts","sourceRoot":"","sources":["../../src/core/multi-format.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildRenderFilter, MULTI_FORMATS, multiFormatSpec, } from "./multi-format.js";
|
|
3
|
+
describe("multiFormatSpec", () => {
|
|
4
|
+
it("exposes seven canonical presets", () => {
|
|
5
|
+
expect(MULTI_FORMATS).toEqual([
|
|
6
|
+
"youtube-1080p",
|
|
7
|
+
"shorts-9x16",
|
|
8
|
+
"reels-9x16",
|
|
9
|
+
"tiktok-9x16",
|
|
10
|
+
"square-1x1",
|
|
11
|
+
"instagram-4x5",
|
|
12
|
+
"twitter-16x9",
|
|
13
|
+
]);
|
|
14
|
+
});
|
|
15
|
+
it("returns 1920x1080 for youtube-1080p (scale-pad)", () => {
|
|
16
|
+
expect(multiFormatSpec("youtube-1080p")).toEqual({
|
|
17
|
+
width: 1920,
|
|
18
|
+
height: 1080,
|
|
19
|
+
transform: "scale-pad",
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
it("returns 1080x1920 + centre-crop for the three vertical aliases", () => {
|
|
23
|
+
for (const f of ["shorts-9x16", "reels-9x16", "tiktok-9x16"]) {
|
|
24
|
+
expect(multiFormatSpec(f)).toEqual({
|
|
25
|
+
width: 1080,
|
|
26
|
+
height: 1920,
|
|
27
|
+
transform: "centre-crop",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
it("returns 1080x1080 for square-1x1, 1080x1350 for instagram-4x5, 1280x720 for twitter-16x9", () => {
|
|
32
|
+
expect(multiFormatSpec("square-1x1")).toMatchObject({ width: 1080, height: 1080 });
|
|
33
|
+
expect(multiFormatSpec("instagram-4x5")).toMatchObject({ width: 1080, height: 1350 });
|
|
34
|
+
expect(multiFormatSpec("twitter-16x9")).toMatchObject({
|
|
35
|
+
width: 1280,
|
|
36
|
+
height: 720,
|
|
37
|
+
transform: "scale-pad",
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it("throws on unknown preset", () => {
|
|
41
|
+
expect(() => multiFormatSpec("bogus")).toThrow(/unknown multi-format preset/);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe("buildRenderFilter — 1920x1080 source", () => {
|
|
45
|
+
it("youtube-1080p: emits scale+pad to 1920x1080", () => {
|
|
46
|
+
const r = buildRenderFilter(1920, 1080, "youtube-1080p");
|
|
47
|
+
expect(r.transform).toBe("scale-pad");
|
|
48
|
+
expect(r.targetW).toBe(1920);
|
|
49
|
+
expect(r.targetH).toBe(1080);
|
|
50
|
+
expect(r.vf).toBe("scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black");
|
|
51
|
+
});
|
|
52
|
+
it("shorts-9x16 from 1920x1080: centre-crops width to 607.5, x=656.25", () => {
|
|
53
|
+
const r = buildRenderFilter(1920, 1080, "shorts-9x16");
|
|
54
|
+
expect(r.transform).toBe("centre-crop");
|
|
55
|
+
expect(r.targetW).toBe(1080);
|
|
56
|
+
expect(r.targetH).toBe(1920);
|
|
57
|
+
expect(r.vf).toBe("crop=607.5:1080:656.25:0,scale=1080:1920");
|
|
58
|
+
});
|
|
59
|
+
it("square-1x1 from 1920x1080: crops width to 1080, x=420", () => {
|
|
60
|
+
const r = buildRenderFilter(1920, 1080, "square-1x1");
|
|
61
|
+
expect(r.transform).toBe("centre-crop");
|
|
62
|
+
// 1080 * 1 / 1 = 1080 ; (1920 - 1080)/2 = 420
|
|
63
|
+
expect(r.vf).toBe("crop=1080:1080:420:0,scale=1080:1080");
|
|
64
|
+
});
|
|
65
|
+
it("instagram-4x5 from 1920x1080: crops width to 864, x=528", () => {
|
|
66
|
+
const r = buildRenderFilter(1920, 1080, "instagram-4x5");
|
|
67
|
+
// 1080 * 4 / 5 = 864 ; (1920 - 864)/2 = 528
|
|
68
|
+
expect(r.transform).toBe("centre-crop");
|
|
69
|
+
expect(r.vf).toBe("crop=864:1080:528:0,scale=1080:1350");
|
|
70
|
+
});
|
|
71
|
+
it("twitter-16x9 from 1920x1080: scale+pad to 1280x720", () => {
|
|
72
|
+
const r = buildRenderFilter(1920, 1080, "twitter-16x9");
|
|
73
|
+
expect(r.transform).toBe("scale-pad");
|
|
74
|
+
expect(r.targetW).toBe(1280);
|
|
75
|
+
expect(r.targetH).toBe(720);
|
|
76
|
+
expect(r.vf).toBe("scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2:color=black");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe("buildRenderFilter — 3840x2160 source (4K UHD, also 16:9)", () => {
|
|
80
|
+
it("shorts-9x16: same crop math scaled — cropW=1215, x=1312.5", () => {
|
|
81
|
+
const r = buildRenderFilter(3840, 2160, "shorts-9x16");
|
|
82
|
+
// 2160 * 9 / 16 = 1215 ; (3840 - 1215) / 2 = 1312.5
|
|
83
|
+
expect(r.vf).toBe("crop=1215:2160:1312.5:0,scale=1080:1920");
|
|
84
|
+
});
|
|
85
|
+
it("youtube-1080p: scale-pad still emits the 1080p target", () => {
|
|
86
|
+
const r = buildRenderFilter(3840, 2160, "youtube-1080p");
|
|
87
|
+
expect(r.targetW).toBe(1920);
|
|
88
|
+
expect(r.targetH).toBe(1080);
|
|
89
|
+
expect(r.vf).toContain("scale=1920:1080:force_original_aspect_ratio=decrease");
|
|
90
|
+
});
|
|
91
|
+
it("instagram-4x5 from 4K: cropW=1728, x=1056", () => {
|
|
92
|
+
const r = buildRenderFilter(3840, 2160, "instagram-4x5");
|
|
93
|
+
// 2160 * 4 / 5 = 1728 ; (3840 - 1728) / 2 = 1056
|
|
94
|
+
expect(r.vf).toBe("crop=1728:2160:1056:0,scale=1080:1350");
|
|
95
|
+
});
|
|
96
|
+
it("square-1x1 from 4K: cropW=2160, x=840", () => {
|
|
97
|
+
const r = buildRenderFilter(3840, 2160, "square-1x1");
|
|
98
|
+
expect(r.vf).toBe("crop=2160:2160:840:0,scale=1080:1080");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe("buildRenderFilter — 1080x1920 portrait source", () => {
|
|
102
|
+
it("shorts-9x16: aspects match, no crop — just scale", () => {
|
|
103
|
+
const r = buildRenderFilter(1080, 1920, "shorts-9x16");
|
|
104
|
+
expect(r.transform).toBe("centre-crop");
|
|
105
|
+
expect(r.vf).toBe("scale=1080:1920");
|
|
106
|
+
});
|
|
107
|
+
it("youtube-1080p: scale+pad pillarboxes the portrait into 1920x1080", () => {
|
|
108
|
+
const r = buildRenderFilter(1080, 1920, "youtube-1080p");
|
|
109
|
+
expect(r.transform).toBe("scale-pad");
|
|
110
|
+
expect(r.vf).toBe("scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black");
|
|
111
|
+
});
|
|
112
|
+
it("square-1x1 from portrait: src is taller than 1:1, so crop HEIGHT", () => {
|
|
113
|
+
const r = buildRenderFilter(1080, 1920, "square-1x1");
|
|
114
|
+
// srcAR = 1080/1920 = 0.5625 < 1 (target). crop height: cropH = iw*Ht/Wt = 1080*1/1 = 1080 ; y = (1920-1080)/2 = 420
|
|
115
|
+
expect(r.vf).toBe("crop=1080:1080:0:420,scale=1080:1080");
|
|
116
|
+
});
|
|
117
|
+
it("instagram-4x5 from portrait: src AR (0.5625) < target AR (0.8) → crop height", () => {
|
|
118
|
+
const r = buildRenderFilter(1080, 1920, "instagram-4x5");
|
|
119
|
+
// cropH = iw * Ht/Wt = 1080 * 1350/1080 = 1350 ; y = (1920 - 1350)/2 = 285
|
|
120
|
+
expect(r.vf).toBe("crop=1080:1350:0:285,scale=1080:1350");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
describe("buildRenderFilter — scale-pad math for upscale source 1280x720", () => {
|
|
124
|
+
it("youtube-1080p target from 720p: scale up + pad to 1920x1080", () => {
|
|
125
|
+
const r = buildRenderFilter(1280, 720, "youtube-1080p");
|
|
126
|
+
expect(r.transform).toBe("scale-pad");
|
|
127
|
+
expect(r.targetW).toBe(1920);
|
|
128
|
+
expect(r.targetH).toBe(1080);
|
|
129
|
+
expect(r.vf).toBe("scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black");
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe("buildRenderFilter — faceTracked option", () => {
|
|
133
|
+
it("forces scale-pad for vertical presets when faceTracked=true", () => {
|
|
134
|
+
const r = buildRenderFilter(1920, 1080, "shorts-9x16", { faceTracked: true });
|
|
135
|
+
expect(r.transform).toBe("scale-pad");
|
|
136
|
+
expect(r.vf).toBe("scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black");
|
|
137
|
+
});
|
|
138
|
+
it("is a no-op for already-scale-pad presets", () => {
|
|
139
|
+
const a = buildRenderFilter(1920, 1080, "youtube-1080p", { faceTracked: true });
|
|
140
|
+
const b = buildRenderFilter(1920, 1080, "youtube-1080p", { faceTracked: false });
|
|
141
|
+
expect(a.vf).toBe(b.vf);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe("buildRenderFilter — input validation", () => {
|
|
145
|
+
it("rejects zero / negative / non-finite dimensions", () => {
|
|
146
|
+
expect(() => buildRenderFilter(0, 1080, "youtube-1080p")).toThrow(/invalid source dimensions/);
|
|
147
|
+
expect(() => buildRenderFilter(1920, -1, "youtube-1080p")).toThrow(/invalid source dimensions/);
|
|
148
|
+
expect(() => buildRenderFilter(NaN, 1080, "youtube-1080p")).toThrow(/invalid source dimensions/);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
//# sourceMappingURL=multi-format.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-format.test.js","sourceRoot":"","sources":["../../src/core/multi-format.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,eAAe,GAEhB,MAAM,mBAAmB,CAAC;AAE3B,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC;YAC5B,eAAe;YACf,aAAa;YACb,YAAY;YACZ,aAAa;YACb,YAAY;YACZ,eAAe;YACf,cAAc;SACf,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC;YAC/C,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,WAAW;SACvB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,KAAK,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,YAAY,EAAE,aAAa,CAAU,EAAE,CAAC;YACtE,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACjC,KAAK,EAAE,IAAI;gBACX,MAAM,EAAE,IAAI;gBACZ,SAAS,EAAE,aAAa;aACzB,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0FAA0F,EAAE,GAAG,EAAE;QAClG,MAAM,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACnF,MAAM,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACtF,MAAM,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC,CAAC,aAAa,CAAC;YACpD,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,GAAG;YACX,SAAS,EAAE,WAAW;SACvB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,OAAsB,CAAC,CAAC,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC;IAC/F,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sCAAsC,EAAE,GAAG,EAAE;IACpD,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;QACzD,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CACf,oGAAoG,CACrG,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;QACvD,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACxC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;QACtD,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACxC,8CAA8C;QAC9C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;QACzD,4CAA4C;QAC5C,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACxC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QACxD,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CACf,kGAAkG,CACnG,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,0DAA0D,EAAE,GAAG,EAAE;IACxE,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;QACvD,oDAAoD;QACpD,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;QACzD,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,sDAAsD,CAAC,CAAC;IACjF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;QACzD,iDAAiD;QACjD,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;QACtD,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,+CAA+C,EAAE,GAAG,EAAE;IAC7D,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;QACvD,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACxC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;QACzD,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CACf,oGAAoG,CACrG,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;QACtD,qHAAqH;QACrH,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8EAA8E,EAAE,GAAG,EAAE;QACtF,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;QACzD,2EAA2E;QAC3E,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gEAAgE,EAAE,GAAG,EAAE;IAC9E,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;QACxD,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CACf,oGAAoG,CACrG,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;IACtD,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9E,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CACf,oGAAoG,CACrG,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAChF,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;QACjF,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sCAAsC,EAAE,GAAG,EAAE;IACpD,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;QAC/F,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;QAChG,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,OAAO,CACjE,2BAA2B,CAC5B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Beat detection sidecar.
|
|
3
|
+
|
|
4
|
+
Reads JSON on stdin: {"audioPath": "...", "sr": <optional int>}
|
|
5
|
+
Writes JSON on stdout: {"tempo": float, "beats": [seconds...], "durationSec": float}
|
|
6
|
+
|
|
7
|
+
On error: prints {"error": "...", "trace": "..."} to stdout and exits 1.
|
|
8
|
+
|
|
9
|
+
Required deps: pip install librosa numpy soundfile
|
|
10
|
+
"""
|
|
11
|
+
import sys
|
|
12
|
+
import json
|
|
13
|
+
import traceback
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main():
|
|
17
|
+
try:
|
|
18
|
+
import librosa # noqa: F401 (heavy import — failure surfaces as ImportError below)
|
|
19
|
+
import numpy as np
|
|
20
|
+
except ImportError as e:
|
|
21
|
+
print(json.dumps({
|
|
22
|
+
"error": f"missing python dep: {e.name}; install librosa numpy soundfile",
|
|
23
|
+
}))
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
|
|
26
|
+
raw = sys.stdin.read() or "{}"
|
|
27
|
+
try:
|
|
28
|
+
args = json.loads(raw)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
print(json.dumps({"error": f"invalid stdin JSON: {e}"}))
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
audio_path = args.get("audioPath")
|
|
34
|
+
if not audio_path:
|
|
35
|
+
print(json.dumps({"error": "missing required arg: audioPath"}))
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
sr = args.get("sr") # may be None — librosa default
|
|
38
|
+
|
|
39
|
+
y, sr_used = librosa.load(audio_path, sr=sr, mono=True)
|
|
40
|
+
duration = float(librosa.get_duration(y=y, sr=sr_used))
|
|
41
|
+
tempo, beats = librosa.beat.beat_track(y=y, sr=sr_used, units="time")
|
|
42
|
+
# librosa>=0.10 returns numpy 1d array for tempo; collapse to scalar.
|
|
43
|
+
if hasattr(tempo, "__len__"):
|
|
44
|
+
tempo = float(np.atleast_1d(tempo)[0])
|
|
45
|
+
|
|
46
|
+
print(json.dumps({
|
|
47
|
+
"tempo": float(tempo),
|
|
48
|
+
"beats": [float(b) for b in beats],
|
|
49
|
+
"durationSec": duration,
|
|
50
|
+
}))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
try:
|
|
55
|
+
main()
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(json.dumps({
|
|
58
|
+
"error": str(e),
|
|
59
|
+
"trace": traceback.format_exc()[-500:],
|
|
60
|
+
}))
|
|
61
|
+
sys.exit(1)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Face-tracked reframe analysis sidecar.
|
|
3
|
+
|
|
4
|
+
Reads JSON on stdin:
|
|
5
|
+
{
|
|
6
|
+
"videoPath": "...",
|
|
7
|
+
"sampleFps": <float, default 5.0>,
|
|
8
|
+
"minDetectionConfidence": <float, default 0.5>,
|
|
9
|
+
"smoothingWindowSec": <float, default 0.5>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
Writes JSON on stdout:
|
|
13
|
+
{
|
|
14
|
+
"shots": [{startSec, endSec, frames: [...], smoothedX, smoothedY, mode}],
|
|
15
|
+
"totalSec": float,
|
|
16
|
+
"fps": float,
|
|
17
|
+
"sourceWidth": int,
|
|
18
|
+
"sourceHeight": int
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
On error: prints {"error": "...", "trace": "..."} to stdout and exits 1.
|
|
22
|
+
|
|
23
|
+
Pipeline:
|
|
24
|
+
1. PySceneDetect (ContentDetector) -> shot boundaries.
|
|
25
|
+
2. For each shot, sample at sampleFps and run MediaPipe face_detection
|
|
26
|
+
(model_selection=1, full-range).
|
|
27
|
+
3. Pick the largest detection per frame; collect normalized centre.
|
|
28
|
+
4. Smooth via median (robust to dropouts) per shot. Mark shot as
|
|
29
|
+
"static" when no detections were found.
|
|
30
|
+
|
|
31
|
+
Required deps: pip install opencv-python mediapipe scenedetect numpy
|
|
32
|
+
"""
|
|
33
|
+
import sys
|
|
34
|
+
import json
|
|
35
|
+
import traceback
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main():
|
|
39
|
+
try:
|
|
40
|
+
import cv2
|
|
41
|
+
import numpy as np
|
|
42
|
+
import mediapipe as mp
|
|
43
|
+
from scenedetect import detect, ContentDetector
|
|
44
|
+
except ImportError as e:
|
|
45
|
+
print(json.dumps({
|
|
46
|
+
"error": (
|
|
47
|
+
f"missing python dep: {e.name}; "
|
|
48
|
+
"install: pip install opencv-python mediapipe scenedetect numpy"
|
|
49
|
+
),
|
|
50
|
+
}))
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
raw = sys.stdin.read() or "{}"
|
|
54
|
+
try:
|
|
55
|
+
args = json.loads(raw)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(json.dumps({"error": f"invalid stdin JSON: {e}"}))
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
video = args.get("videoPath")
|
|
61
|
+
if not video:
|
|
62
|
+
print(json.dumps({"error": "missing required arg: videoPath"}))
|
|
63
|
+
sys.exit(1)
|
|
64
|
+
sample_fps = float(args.get("sampleFps", 5.0))
|
|
65
|
+
min_conf = float(args.get("minDetectionConfidence", 0.5))
|
|
66
|
+
# smooth_win currently informs window selection; we use a robust median
|
|
67
|
+
# over all in-shot samples which is tolerant to dropouts.
|
|
68
|
+
_smooth_win = float(args.get("smoothingWindowSec", 0.5)) # noqa: F841
|
|
69
|
+
|
|
70
|
+
cap = cv2.VideoCapture(video)
|
|
71
|
+
if not cap.isOpened():
|
|
72
|
+
print(json.dumps({"error": f"cannot open video: {video}"}))
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
src_fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
|
|
75
|
+
src_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
76
|
+
src_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
|
77
|
+
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
78
|
+
duration = total_frames / src_fps if src_fps > 0 else 0.0
|
|
79
|
+
|
|
80
|
+
# PySceneDetect — falls back to whole video if it errors.
|
|
81
|
+
try:
|
|
82
|
+
scenes = detect(video, ContentDetector(threshold=27))
|
|
83
|
+
shots = [(s[0].get_seconds(), s[1].get_seconds()) for s in scenes]
|
|
84
|
+
except Exception:
|
|
85
|
+
shots = []
|
|
86
|
+
if not shots:
|
|
87
|
+
shots = [(0.0, duration)]
|
|
88
|
+
|
|
89
|
+
detector = mp.solutions.face_detection.FaceDetection(
|
|
90
|
+
model_selection=1, min_detection_confidence=min_conf,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
out_shots = []
|
|
94
|
+
sample_step = max(1, int(round(src_fps / sample_fps))) if sample_fps > 0 else 1
|
|
95
|
+
for shot_start, shot_end in shots:
|
|
96
|
+
frames_data = []
|
|
97
|
+
f0 = int(shot_start * src_fps)
|
|
98
|
+
f1 = int(shot_end * src_fps)
|
|
99
|
+
for f in range(f0, f1, sample_step):
|
|
100
|
+
cap.set(cv2.CAP_PROP_POS_FRAMES, f)
|
|
101
|
+
ok, img = cap.read()
|
|
102
|
+
if not ok:
|
|
103
|
+
continue
|
|
104
|
+
t = f / src_fps if src_fps > 0 else 0.0
|
|
105
|
+
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
|
106
|
+
res = detector.process(rgb)
|
|
107
|
+
if res.detections:
|
|
108
|
+
# Pick the largest face (in normalized area).
|
|
109
|
+
best = max(
|
|
110
|
+
res.detections,
|
|
111
|
+
key=lambda d: (
|
|
112
|
+
d.location_data.relative_bounding_box.width
|
|
113
|
+
* d.location_data.relative_bounding_box.height
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
bb = best.location_data.relative_bounding_box
|
|
117
|
+
cx = bb.xmin + bb.width / 2
|
|
118
|
+
cy = bb.ymin + bb.height / 2
|
|
119
|
+
frames_data.append({
|
|
120
|
+
"atSec": t,
|
|
121
|
+
"faceCx": float(cx),
|
|
122
|
+
"faceCy": float(cy),
|
|
123
|
+
"faceW": float(bb.width),
|
|
124
|
+
"faceH": float(bb.height),
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
if frames_data:
|
|
128
|
+
xs = np.array([d["faceCx"] for d in frames_data])
|
|
129
|
+
ys = np.array([d["faceCy"] for d in frames_data])
|
|
130
|
+
mode = "face"
|
|
131
|
+
sx = float(np.median(xs))
|
|
132
|
+
sy = float(np.median(ys))
|
|
133
|
+
else:
|
|
134
|
+
mode = "static"
|
|
135
|
+
sx, sy = 0.5, 0.5
|
|
136
|
+
out_shots.append({
|
|
137
|
+
"startSec": float(shot_start),
|
|
138
|
+
"endSec": float(shot_end),
|
|
139
|
+
"frames": frames_data,
|
|
140
|
+
"smoothedX": sx,
|
|
141
|
+
"smoothedY": sy,
|
|
142
|
+
"mode": mode,
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
cap.release()
|
|
146
|
+
print(json.dumps({
|
|
147
|
+
"shots": out_shots,
|
|
148
|
+
"totalSec": float(duration),
|
|
149
|
+
"fps": float(src_fps),
|
|
150
|
+
"sourceWidth": src_w,
|
|
151
|
+
"sourceHeight": src_h,
|
|
152
|
+
}))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
if __name__ == "__main__":
|
|
156
|
+
try:
|
|
157
|
+
main()
|
|
158
|
+
except Exception as e:
|
|
159
|
+
print(json.dumps({
|
|
160
|
+
"error": str(e),
|
|
161
|
+
"trace": traceback.format_exc()[-500:],
|
|
162
|
+
}))
|
|
163
|
+
sys.exit(1)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the on-disk path of a Python sidecar shipped under `src/core/python/`.
|
|
3
|
+
*
|
|
4
|
+
* In `src/` (during tests / dev), `import.meta.url` points inside `src/core/python/`
|
|
5
|
+
* so the script sits next to this file. In `dist/` (after `tsc`), the build's
|
|
6
|
+
* post-step copies `src/core/python/` → `dist/core/python/`, so the same
|
|
7
|
+
* relative resolution works.
|
|
8
|
+
*
|
|
9
|
+
* If the script cannot be found we throw — better an explicit error than
|
|
10
|
+
* silently spawning Python with a non-existent path.
|
|
11
|
+
*/
|
|
12
|
+
export declare function sidecarPath(filename: string): string;
|
|
13
|
+
//# sourceMappingURL=sidecar-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidecar-path.d.ts","sourceRoot":"","sources":["../../../src/core/python/sidecar-path.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUpD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the on-disk path of a Python sidecar shipped under `src/core/python/`.
|
|
6
|
+
*
|
|
7
|
+
* In `src/` (during tests / dev), `import.meta.url` points inside `src/core/python/`
|
|
8
|
+
* so the script sits next to this file. In `dist/` (after `tsc`), the build's
|
|
9
|
+
* post-step copies `src/core/python/` → `dist/core/python/`, so the same
|
|
10
|
+
* relative resolution works.
|
|
11
|
+
*
|
|
12
|
+
* If the script cannot be found we throw — better an explicit error than
|
|
13
|
+
* silently spawning Python with a non-existent path.
|
|
14
|
+
*/
|
|
15
|
+
export function sidecarPath(filename) {
|
|
16
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const candidate = resolve(here, filename);
|
|
18
|
+
if (!existsSync(candidate)) {
|
|
19
|
+
throw new Error(`python sidecar '${filename}' not found at ${candidate} — ` +
|
|
20
|
+
"the build step that copies src/core/python/ → dist/core/python/ may have failed");
|
|
21
|
+
}
|
|
22
|
+
return candidate;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=sidecar-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidecar-path.js","sourceRoot":"","sources":["../../../src/core/python/sidecar-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC1C,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,mBAAmB,QAAQ,kBAAkB,SAAS,KAAK;YACzD,iFAAiF,CACpF,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Python helpers for one-shot sidecar invocations.
|
|
3
|
+
*
|
|
4
|
+
* The Resolve bridge (`hosts/resolve/bridge.ts`) is a *long-lived* Python
|
|
5
|
+
* subprocess we talk to via JSON-line protocol. Other tools (beat detection,
|
|
6
|
+
* face reframe, etc.) only need a one-shot invocation: spawn → write JSON to
|
|
7
|
+
* stdin → read JSON from stdout → exit. Both modes share interpreter probing,
|
|
8
|
+
* so `findPython()` lives here and is re-exported from bridge.ts for callers
|
|
9
|
+
* that already imported it from there.
|
|
10
|
+
*
|
|
11
|
+
* Cross-platform notes match the bridge's:
|
|
12
|
+
* - macOS/Linux ship `python3`. Windows often only has `python` or `py -3`.
|
|
13
|
+
* - Force UTF-8 I/O so JSON with non-ASCII names is safe on Windows.
|
|
14
|
+
* - Capture stderr separately for diagnostics (warnings shouldn't break parsing).
|
|
15
|
+
*/
|
|
16
|
+
export interface PythonCmd {
|
|
17
|
+
cmd: string;
|
|
18
|
+
args: string[];
|
|
19
|
+
/**
|
|
20
|
+
* `sys.prefix` of the interpreter (Windows-only convenience for setting
|
|
21
|
+
* PYTHONHOME when multiple Pythons sit on PATH). Other callers can ignore.
|
|
22
|
+
*/
|
|
23
|
+
prefix?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function findPython(): PythonCmd | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Test-only: clear the cached interpreter so subsequent calls re-probe.
|
|
28
|
+
* Production code never needs this — there's no path where Python
|
|
29
|
+
* disappears mid-process and we need to rediscover.
|
|
30
|
+
*/
|
|
31
|
+
export declare function __resetPythonCacheForTests(): void;
|
|
32
|
+
export interface RunPythonResult {
|
|
33
|
+
code: number;
|
|
34
|
+
stdout: string;
|
|
35
|
+
stderr: string;
|
|
36
|
+
}
|
|
37
|
+
export interface RunPythonOptions {
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
cwd?: string;
|
|
40
|
+
/** Extra env vars layered on top of process.env. */
|
|
41
|
+
env?: Record<string, string>;
|
|
42
|
+
/**
|
|
43
|
+
* JSON payload written to the script's stdin (one shot). Stringified by
|
|
44
|
+
* the caller in most cases — pass a string directly if you need raw bytes.
|
|
45
|
+
*/
|
|
46
|
+
stdin?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Run a Python script as a one-shot subprocess. Resolves with combined
|
|
50
|
+
* stdout/stderr/exit-code; never rejects on a non-zero exit (callers
|
|
51
|
+
* decide how to interpret it). Rejects only on spawn failure.
|
|
52
|
+
*
|
|
53
|
+
* Distinct from the Resolve bridge's long-lived spawn — this is for
|
|
54
|
+
* sidecars that produce one JSON blob and exit.
|
|
55
|
+
*/
|
|
56
|
+
export declare function runPython(scriptPath: string, scriptArgs: string[], opts?: RunPythonOptions): Promise<RunPythonResult>;
|
|
57
|
+
//# sourceMappingURL=python.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python.d.ts","sourceRoot":"","sources":["../../src/core/python.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAoBD,wBAAgB,UAAU,IAAI,SAAS,GAAG,SAAS,CA8BlD;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CACvB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAAE,EACpB,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,eAAe,CAAC,CAgD1B"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { wireChildAbort } from "./child-abort.js";
|
|
3
|
+
/**
|
|
4
|
+
* Probe for a working Python 3 interpreter. Order matters:
|
|
5
|
+
* 1. python3 — macOS/Linux standard
|
|
6
|
+
* 2. python — Windows default name; also macOS with pyenv
|
|
7
|
+
* 3. py -3 — Windows launcher (when only the launcher is on PATH)
|
|
8
|
+
*
|
|
9
|
+
* We verify each candidate prints a 3.x version before accepting it.
|
|
10
|
+
*
|
|
11
|
+
* The first successful probe is cached for the lifetime of the process.
|
|
12
|
+
* Python-backed tools call this on every invocation (preflight + the actual
|
|
13
|
+
* sidecar spawn inside `runPython`), so without a cache a single agent turn
|
|
14
|
+
* could spawn 4+ synchronous subprocesses just to re-confirm the same
|
|
15
|
+
* interpreter. Negative results are NOT cached — if Python wasn't on PATH
|
|
16
|
+
* when the process started but the user installed it mid-session, the next
|
|
17
|
+
* call will probe again and pick it up.
|
|
18
|
+
*/
|
|
19
|
+
let cached;
|
|
20
|
+
export function findPython() {
|
|
21
|
+
if (cached)
|
|
22
|
+
return cached;
|
|
23
|
+
const candidates = [
|
|
24
|
+
{ cmd: "python3", args: [] },
|
|
25
|
+
{ cmd: "python", args: [] },
|
|
26
|
+
{ cmd: "py", args: ["-3"] },
|
|
27
|
+
];
|
|
28
|
+
for (const c of candidates) {
|
|
29
|
+
const r = spawnSync(c.cmd, [...c.args, "--version"], {
|
|
30
|
+
encoding: "utf8",
|
|
31
|
+
windowsHide: true,
|
|
32
|
+
});
|
|
33
|
+
if (r.status === 0) {
|
|
34
|
+
const out = (r.stdout || r.stderr || "").trim();
|
|
35
|
+
if (/Python 3\./.test(out)) {
|
|
36
|
+
// Best-effort sys.prefix probe (used on Windows for PYTHONHOME).
|
|
37
|
+
const pr = spawnSync(c.cmd, [...c.args, "-c", "import sys;print(sys.prefix)"], {
|
|
38
|
+
encoding: "utf8",
|
|
39
|
+
windowsHide: true,
|
|
40
|
+
});
|
|
41
|
+
const prefix = pr.status === 0 && typeof pr.stdout === "string" ? pr.stdout.trim() : undefined;
|
|
42
|
+
cached = prefix ? { ...c, prefix } : c;
|
|
43
|
+
return cached;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Test-only: clear the cached interpreter so subsequent calls re-probe.
|
|
51
|
+
* Production code never needs this — there's no path where Python
|
|
52
|
+
* disappears mid-process and we need to rediscover.
|
|
53
|
+
*/
|
|
54
|
+
export function __resetPythonCacheForTests() {
|
|
55
|
+
cached = undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Run a Python script as a one-shot subprocess. Resolves with combined
|
|
59
|
+
* stdout/stderr/exit-code; never rejects on a non-zero exit (callers
|
|
60
|
+
* decide how to interpret it). Rejects only on spawn failure.
|
|
61
|
+
*
|
|
62
|
+
* Distinct from the Resolve bridge's long-lived spawn — this is for
|
|
63
|
+
* sidecars that produce one JSON blob and exit.
|
|
64
|
+
*/
|
|
65
|
+
export function runPython(scriptPath, scriptArgs, opts = {}) {
|
|
66
|
+
const py = findPython();
|
|
67
|
+
if (!py) {
|
|
68
|
+
return Promise.reject(new Error("No Python 3 interpreter found. Install Python 3 (python3 / python / 'py -3') and ensure it's on PATH."));
|
|
69
|
+
}
|
|
70
|
+
const env = { ...process.env, ...(opts.env ?? {}) };
|
|
71
|
+
// UTF-8 stdio everywhere — Windows' default codepage can mangle JSON.
|
|
72
|
+
env.PYTHONIOENCODING = env.PYTHONIOENCODING ?? "utf-8";
|
|
73
|
+
// Don't litter the script dir with .pyc.
|
|
74
|
+
env.PYTHONDONTWRITEBYTECODE = "1";
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const child = spawn(py.cmd, [...py.args, scriptPath, ...scriptArgs], {
|
|
77
|
+
env,
|
|
78
|
+
cwd: opts.cwd,
|
|
79
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
80
|
+
windowsHide: true,
|
|
81
|
+
});
|
|
82
|
+
let stdout = "";
|
|
83
|
+
let stderr = "";
|
|
84
|
+
child.stdout.setEncoding("utf8");
|
|
85
|
+
child.stderr.setEncoding("utf8");
|
|
86
|
+
child.stdout.on("data", (d) => (stdout += d));
|
|
87
|
+
child.stderr.on("data", (d) => (stderr += d));
|
|
88
|
+
// Robust abort: SIGTERM → SIGKILL after 1.5s, with synchronous fire on
|
|
89
|
+
// pre-aborted signals. CPU-bound Python sidecars (librosa beat detect,
|
|
90
|
+
// MediaPipe face reframe) can chew through SIGTERM if they're inside a
|
|
91
|
+
// numpy/cv2 inner loop — the SIGKILL escalation is non-negotiable.
|
|
92
|
+
const cleanup = wireChildAbort(opts.signal, child);
|
|
93
|
+
child.on("error", (e) => {
|
|
94
|
+
cleanup();
|
|
95
|
+
reject(e);
|
|
96
|
+
});
|
|
97
|
+
child.on("close", (code) => {
|
|
98
|
+
cleanup();
|
|
99
|
+
resolve({ code: code ?? 1, stdout, stderr });
|
|
100
|
+
});
|
|
101
|
+
if (opts.stdin !== undefined) {
|
|
102
|
+
child.stdin.write(opts.stdin);
|
|
103
|
+
}
|
|
104
|
+
child.stdin.end();
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=python.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python.js","sourceRoot":"","sources":["../../src/core/python.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python.test.d.ts","sourceRoot":"","sources":["../../src/core/python.test.ts"],"names":[],"mappings":""}
|