@kenkaiiii/gg-editor 0.7.0 → 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/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,81 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { BUNDLED_SFX, bundledSfxDescriptionList, ensureBundledSfx, getSfxCacheDir, listBundledSfxNames, resolveSfx, } from "./bundled-sfx.js";
|
|
3
|
+
// We don't want any of these tests touching real ffmpeg or the user's home
|
|
4
|
+
// directory. Most assertions are pure-function. The synthesis tests mock
|
|
5
|
+
// runFfmpeg via vi.mock at module scope.
|
|
6
|
+
vi.mock("./media/ffmpeg.js", async () => {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
8
|
+
const real = await vi.importActual("./media/ffmpeg.js");
|
|
9
|
+
return {
|
|
10
|
+
...real,
|
|
11
|
+
runFfmpeg: vi.fn(async () => ({ code: 0, stdout: "", stderr: "" })),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
describe("BUNDLED_SFX registry", () => {
|
|
15
|
+
it("ships at least 8 named effects covering the creator vocabulary", () => {
|
|
16
|
+
const names = listBundledSfxNames();
|
|
17
|
+
for (const required of ["pop", "click", "whoosh", "swoosh", "riser", "thump", "ding"]) {
|
|
18
|
+
expect(names).toContain(required);
|
|
19
|
+
}
|
|
20
|
+
expect(names.length).toBeGreaterThanOrEqual(8);
|
|
21
|
+
});
|
|
22
|
+
it("every recipe has stereo + non-empty args + no embedded output path", () => {
|
|
23
|
+
for (const [_name, recipe] of Object.entries(BUNDLED_SFX)) {
|
|
24
|
+
expect(recipe.description.length).toBeGreaterThan(10);
|
|
25
|
+
expect(recipe.args).toContain("-ac");
|
|
26
|
+
const acIdx = recipe.args.indexOf("-ac");
|
|
27
|
+
expect(recipe.args[acIdx + 1]).toBe("2");
|
|
28
|
+
// Output path is appended at synthesis time, not embedded in recipes.
|
|
29
|
+
const lastArg = recipe.args[recipe.args.length - 1];
|
|
30
|
+
expect(lastArg.endsWith(".wav")).toBe(false);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
it("bundledSfxDescriptionList includes every name + description", () => {
|
|
34
|
+
const list = bundledSfxDescriptionList();
|
|
35
|
+
for (const name of listBundledSfxNames()) {
|
|
36
|
+
expect(list).toContain(name);
|
|
37
|
+
}
|
|
38
|
+
expect(list).toContain("(");
|
|
39
|
+
expect(list).toContain(")");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe("getSfxCacheDir", () => {
|
|
43
|
+
it("returns a path under the user's home", () => {
|
|
44
|
+
const dir = getSfxCacheDir();
|
|
45
|
+
expect(dir).toContain(".gg");
|
|
46
|
+
expect(dir).toContain("sfx-cache");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe("resolveSfx", () => {
|
|
50
|
+
it("treats unambiguous bundled names as bundled", async () => {
|
|
51
|
+
// Force ffmpeg mock to claim success without touching disk.
|
|
52
|
+
const r = await resolveSfx("whoosh", "/tmp/x").catch((e) => ({ error: e.message }));
|
|
53
|
+
if ("error" in r) {
|
|
54
|
+
// Expected if cache mkdir somehow fails on the test runner — skip.
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
expect(r.bundled).toBe(true);
|
|
58
|
+
expect(r.name).toBe("whoosh");
|
|
59
|
+
expect(r.path).toMatch(/whoosh\.wav$/);
|
|
60
|
+
});
|
|
61
|
+
it("treats anything containing a path separator as a file path", async () => {
|
|
62
|
+
const r = await resolveSfx("./assets/myfx.wav", "/home/user");
|
|
63
|
+
expect(r.bundled).toBe(false);
|
|
64
|
+
expect(r.path).toBe("/home/user/assets/myfx.wav");
|
|
65
|
+
expect(r.name).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
it("treats anything with a dot (extension) as a file path", async () => {
|
|
68
|
+
const r = await resolveSfx("custom.mp3", "/home/user");
|
|
69
|
+
expect(r.bundled).toBe(false);
|
|
70
|
+
expect(r.path).toBe("/home/user/custom.mp3");
|
|
71
|
+
});
|
|
72
|
+
it("rejects unknown bare names with the bundled list in the message", async () => {
|
|
73
|
+
await expect(resolveSfx("notarealsfx", "/tmp")).rejects.toThrow(/Bundled:.*whoosh/);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe("ensureBundledSfx", () => {
|
|
77
|
+
it("rejects unknown names", async () => {
|
|
78
|
+
await expect(ensureBundledSfx("notarealsfx")).rejects.toThrow(/unknown bundled SFX/);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=bundled-sfx.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundled-sfx.test.js","sourceRoot":"","sources":["../../src/core/bundled-sfx.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared abort wiring for spawned child processes.
|
|
3
|
+
*
|
|
4
|
+
* Every long-running tool in gg-editor — ffmpeg, whisper.cpp, whisperx,
|
|
5
|
+
* Python sidecars (librosa beats, MediaPipe face reframe), Resolve bridge —
|
|
6
|
+
* is wrapped in a Promise that resolves on `child.on("close")`. Without
|
|
7
|
+
* careful abort handling those promises can hang for the duration of the
|
|
8
|
+
* child's actual exit even after the user pressed ESC.
|
|
9
|
+
*
|
|
10
|
+
* The naive pattern `signal.addEventListener("abort", () => child.kill())`
|
|
11
|
+
* has three real failure modes:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Pre-abort race** — if `signal.aborted` is already true at attach
|
|
14
|
+
* time, the listener never fires (Node only dispatches `abort` once,
|
|
15
|
+
* at the moment the controller flips). The child runs to completion.
|
|
16
|
+
*
|
|
17
|
+
* 2. **SIGTERM ignored** — CPU-bound inference (whisper.cpp processing a
|
|
18
|
+
* 30-min audio chunk, ffmpeg encoding a long video) can ignore
|
|
19
|
+
* SIGTERM until it finishes the current loop iteration. Users press
|
|
20
|
+
* ESC and watch nothing happen for many seconds.
|
|
21
|
+
*
|
|
22
|
+
* 3. **Promise waits for close** — even if the kill works, the outer
|
|
23
|
+
* Promise resolves on `child.on("close")`, which fires AFTER the
|
|
24
|
+
* child finishes dying. The agent loop sees the abort late.
|
|
25
|
+
*
|
|
26
|
+
* `wireChildAbort` fixes all three:
|
|
27
|
+
*
|
|
28
|
+
* - Synchronous pre-aborted check → kills + rejects immediately.
|
|
29
|
+
* - SIGTERM, then SIGKILL after a configurable grace period (default 1.5s).
|
|
30
|
+
* - Caller decides whether to reject early (most callers pass `onAbort`
|
|
31
|
+
* that rejects with an `AbortError`); resolves can still happen
|
|
32
|
+
* naturally on close if the caller prefers a "code: SIGTERM" outcome.
|
|
33
|
+
* - Returns a cleanup function that removes the listener and clears the
|
|
34
|
+
* SIGKILL timer. Caller must invoke once the promise settles.
|
|
35
|
+
*/
|
|
36
|
+
import type { ChildProcess } from "node:child_process";
|
|
37
|
+
/**
|
|
38
|
+
* Standard AbortError. Mirrors what `fetch` raises when its signal fires —
|
|
39
|
+
* `name === "AbortError"` is the cross-platform contract for cancellation.
|
|
40
|
+
*/
|
|
41
|
+
export declare function abortError(message?: string): Error;
|
|
42
|
+
export interface WireChildAbortOptions {
|
|
43
|
+
/** Optional. Called on abort BEFORE the SIGKILL grace period. Use to reject the wrapping Promise immediately. */
|
|
44
|
+
onAbort?: () => void;
|
|
45
|
+
/** Grace period before escalating to SIGKILL. Default 1500 ms. */
|
|
46
|
+
killAfterMs?: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Wire an AbortSignal to a child process. See file header for design rationale.
|
|
50
|
+
*
|
|
51
|
+
* Returns a cleanup function the caller MUST invoke once the promise settles
|
|
52
|
+
* (in both `child.on("close")` and `child.on("error")`). Without cleanup the
|
|
53
|
+
* abort listener stays on the signal and the SIGKILL timer keeps a process
|
|
54
|
+
* reference alive.
|
|
55
|
+
*/
|
|
56
|
+
export declare function wireChildAbort(signal: AbortSignal | undefined, child: ChildProcess, opts?: WireChildAbortOptions): () => void;
|
|
57
|
+
//# sourceMappingURL=child-abort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-abort.d.ts","sourceRoot":"","sources":["../../src/core/child-abort.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,SAAY,GAAG,KAAK,CAOrD;AAED,MAAM,WAAW,qBAAqB;IACpC,iHAAiH;IACjH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,KAAK,EAAE,YAAY,EACnB,IAAI,GAAE,qBAA0B,GAC/B,MAAM,IAAI,CAoCZ"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared abort wiring for spawned child processes.
|
|
3
|
+
*
|
|
4
|
+
* Every long-running tool in gg-editor — ffmpeg, whisper.cpp, whisperx,
|
|
5
|
+
* Python sidecars (librosa beats, MediaPipe face reframe), Resolve bridge —
|
|
6
|
+
* is wrapped in a Promise that resolves on `child.on("close")`. Without
|
|
7
|
+
* careful abort handling those promises can hang for the duration of the
|
|
8
|
+
* child's actual exit even after the user pressed ESC.
|
|
9
|
+
*
|
|
10
|
+
* The naive pattern `signal.addEventListener("abort", () => child.kill())`
|
|
11
|
+
* has three real failure modes:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Pre-abort race** — if `signal.aborted` is already true at attach
|
|
14
|
+
* time, the listener never fires (Node only dispatches `abort` once,
|
|
15
|
+
* at the moment the controller flips). The child runs to completion.
|
|
16
|
+
*
|
|
17
|
+
* 2. **SIGTERM ignored** — CPU-bound inference (whisper.cpp processing a
|
|
18
|
+
* 30-min audio chunk, ffmpeg encoding a long video) can ignore
|
|
19
|
+
* SIGTERM until it finishes the current loop iteration. Users press
|
|
20
|
+
* ESC and watch nothing happen for many seconds.
|
|
21
|
+
*
|
|
22
|
+
* 3. **Promise waits for close** — even if the kill works, the outer
|
|
23
|
+
* Promise resolves on `child.on("close")`, which fires AFTER the
|
|
24
|
+
* child finishes dying. The agent loop sees the abort late.
|
|
25
|
+
*
|
|
26
|
+
* `wireChildAbort` fixes all three:
|
|
27
|
+
*
|
|
28
|
+
* - Synchronous pre-aborted check → kills + rejects immediately.
|
|
29
|
+
* - SIGTERM, then SIGKILL after a configurable grace period (default 1.5s).
|
|
30
|
+
* - Caller decides whether to reject early (most callers pass `onAbort`
|
|
31
|
+
* that rejects with an `AbortError`); resolves can still happen
|
|
32
|
+
* naturally on close if the caller prefers a "code: SIGTERM" outcome.
|
|
33
|
+
* - Returns a cleanup function that removes the listener and clears the
|
|
34
|
+
* SIGKILL timer. Caller must invoke once the promise settles.
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* Standard AbortError. Mirrors what `fetch` raises when its signal fires —
|
|
38
|
+
* `name === "AbortError"` is the cross-platform contract for cancellation.
|
|
39
|
+
*/
|
|
40
|
+
export function abortError(message = "aborted") {
|
|
41
|
+
if (typeof DOMException !== "undefined") {
|
|
42
|
+
return new DOMException(message, "AbortError");
|
|
43
|
+
}
|
|
44
|
+
const e = new Error(message);
|
|
45
|
+
e.name = "AbortError";
|
|
46
|
+
return e;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Wire an AbortSignal to a child process. See file header for design rationale.
|
|
50
|
+
*
|
|
51
|
+
* Returns a cleanup function the caller MUST invoke once the promise settles
|
|
52
|
+
* (in both `child.on("close")` and `child.on("error")`). Without cleanup the
|
|
53
|
+
* abort listener stays on the signal and the SIGKILL timer keeps a process
|
|
54
|
+
* reference alive.
|
|
55
|
+
*/
|
|
56
|
+
export function wireChildAbort(signal, child, opts = {}) {
|
|
57
|
+
if (!signal)
|
|
58
|
+
return () => { };
|
|
59
|
+
const killAfterMs = opts.killAfterMs ?? 1500;
|
|
60
|
+
let killTimer;
|
|
61
|
+
let fired = false;
|
|
62
|
+
const onAbort = () => {
|
|
63
|
+
if (fired)
|
|
64
|
+
return;
|
|
65
|
+
fired = true;
|
|
66
|
+
try {
|
|
67
|
+
child.kill("SIGTERM");
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// child may already be dead — fine.
|
|
71
|
+
}
|
|
72
|
+
killTimer = setTimeout(() => {
|
|
73
|
+
try {
|
|
74
|
+
child.kill("SIGKILL");
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// ditto
|
|
78
|
+
}
|
|
79
|
+
}, killAfterMs);
|
|
80
|
+
opts.onAbort?.();
|
|
81
|
+
};
|
|
82
|
+
if (signal.aborted) {
|
|
83
|
+
// Pre-aborted — fire synchronously so caller's onAbort runs in the same tick.
|
|
84
|
+
onAbort();
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
88
|
+
}
|
|
89
|
+
return () => {
|
|
90
|
+
signal.removeEventListener("abort", onAbort);
|
|
91
|
+
if (killTimer)
|
|
92
|
+
clearTimeout(killTimer);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=child-abort.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-abort.js","sourceRoot":"","sources":["../../src/core/child-abort.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,OAAO,GAAG,SAAS;IAC5C,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE,CAAC;QACxC,OAAO,IAAI,YAAY,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACjD,CAAC;IACD,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC,CAAC,IAAI,GAAG,YAAY,CAAC;IACtB,OAAO,CAAC,CAAC;AACX,CAAC;AASD;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,MAA+B,EAC/B,KAAmB,EACnB,OAA8B,EAAE;IAEhC,IAAI,CAAC,MAAM;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IAE7B,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC;IAC7C,IAAI,SAAqC,CAAC;IAC1C,IAAI,KAAK,GAAG,KAAK,CAAC;IAElB,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,IAAI,KAAK;YAAE,OAAO;QAClB,KAAK,GAAG,IAAI,CAAC;QACb,IAAI,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,oCAAoC;QACtC,CAAC;QACD,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAC1B,IAAI,CAAC;gBACH,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACxB,CAAC;YAAC,MAAM,CAAC;gBACP,QAAQ;YACV,CAAC;QACH,CAAC,EAAE,WAAW,CAAC,CAAC;QAChB,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;IACnB,CAAC,CAAC;IAEF,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,8EAA8E;QAC9E,OAAO,EAAE,CAAC;IACZ,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,GAAG,EAAE;QACV,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7C,IAAI,SAAS;YAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-abort.test.d.ts","sourceRoot":"","sources":["../../src/core/child-abort.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { abortError, wireChildAbort } from "./child-abort.js";
|
|
4
|
+
describe("abortError", () => {
|
|
5
|
+
it("returns an error with name 'AbortError'", () => {
|
|
6
|
+
const e = abortError();
|
|
7
|
+
expect(e.name).toBe("AbortError");
|
|
8
|
+
});
|
|
9
|
+
it("accepts a custom message", () => {
|
|
10
|
+
expect(abortError("custom").message).toContain("custom");
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* Use a child that sleeps long enough that SIGTERM-then-SIGKILL is observable
|
|
15
|
+
* but short enough that test runs stay fast. Node spawned with `-e` running a
|
|
16
|
+
* setTimeout works on every platform CI uses.
|
|
17
|
+
*/
|
|
18
|
+
function spawnSleeper(seconds) {
|
|
19
|
+
return spawn(process.execPath, ["-e", `setTimeout(() => {}, ${seconds * 1000})`], {
|
|
20
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
describe("wireChildAbort", () => {
|
|
24
|
+
it("is a no-op when signal is undefined", () => {
|
|
25
|
+
const child = spawnSleeper(0.1);
|
|
26
|
+
const cleanup = wireChildAbort(undefined, child);
|
|
27
|
+
expect(typeof cleanup).toBe("function");
|
|
28
|
+
cleanup(); // shouldn't throw
|
|
29
|
+
child.kill();
|
|
30
|
+
});
|
|
31
|
+
it("kills the child with SIGTERM when the signal aborts", async () => {
|
|
32
|
+
const ac = new AbortController();
|
|
33
|
+
const child = spawnSleeper(10);
|
|
34
|
+
const killSpy = vi.spyOn(child, "kill");
|
|
35
|
+
const cleanup = wireChildAbort(ac.signal, child);
|
|
36
|
+
ac.abort();
|
|
37
|
+
expect(killSpy).toHaveBeenCalledWith("SIGTERM");
|
|
38
|
+
// Wait for child to actually exit so the test runner doesn't leak.
|
|
39
|
+
await new Promise((resolve) => child.on("close", () => resolve()));
|
|
40
|
+
cleanup();
|
|
41
|
+
});
|
|
42
|
+
it("fires onAbort exactly once even on repeated aborts", () => {
|
|
43
|
+
const ac = new AbortController();
|
|
44
|
+
const child = spawnSleeper(10);
|
|
45
|
+
const onAbort = vi.fn();
|
|
46
|
+
wireChildAbort(ac.signal, child, { onAbort });
|
|
47
|
+
ac.abort();
|
|
48
|
+
ac.abort(); // duplicate signal — listener attached `once`
|
|
49
|
+
expect(onAbort).toHaveBeenCalledTimes(1);
|
|
50
|
+
child.kill("SIGKILL");
|
|
51
|
+
});
|
|
52
|
+
it("fires synchronously when the signal is already aborted at attach time", () => {
|
|
53
|
+
const ac = new AbortController();
|
|
54
|
+
ac.abort(); // pre-abort
|
|
55
|
+
const child = spawnSleeper(10);
|
|
56
|
+
const killSpy = vi.spyOn(child, "kill");
|
|
57
|
+
const onAbort = vi.fn();
|
|
58
|
+
wireChildAbort(ac.signal, child, { onAbort });
|
|
59
|
+
expect(killSpy).toHaveBeenCalledWith("SIGTERM");
|
|
60
|
+
expect(onAbort).toHaveBeenCalledTimes(1);
|
|
61
|
+
child.kill("SIGKILL");
|
|
62
|
+
});
|
|
63
|
+
it("cleanup removes the listener (no further onAbort after cleanup)", () => {
|
|
64
|
+
const ac = new AbortController();
|
|
65
|
+
const child = spawnSleeper(10);
|
|
66
|
+
const onAbort = vi.fn();
|
|
67
|
+
const cleanup = wireChildAbort(ac.signal, child, { onAbort });
|
|
68
|
+
cleanup();
|
|
69
|
+
ac.abort();
|
|
70
|
+
expect(onAbort).not.toHaveBeenCalled();
|
|
71
|
+
child.kill("SIGKILL");
|
|
72
|
+
});
|
|
73
|
+
it("escalates to SIGKILL after the grace period when SIGTERM is ignored", async () => {
|
|
74
|
+
vi.useFakeTimers();
|
|
75
|
+
const ac = new AbortController();
|
|
76
|
+
const child = spawnSleeper(10);
|
|
77
|
+
const killSpy = vi.spyOn(child, "kill");
|
|
78
|
+
wireChildAbort(ac.signal, child, { killAfterMs: 50 });
|
|
79
|
+
ac.abort();
|
|
80
|
+
expect(killSpy).toHaveBeenLastCalledWith("SIGTERM");
|
|
81
|
+
vi.advanceTimersByTime(60);
|
|
82
|
+
expect(killSpy).toHaveBeenLastCalledWith("SIGKILL");
|
|
83
|
+
vi.useRealTimers();
|
|
84
|
+
child.kill("SIGKILL");
|
|
85
|
+
await new Promise((resolve) => child.on("close", () => resolve()));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=child-abort.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-abort.test.js","sourceRoot":"","sources":["../../src/core/child-abort.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface ClipScoreResponse {
|
|
2
|
+
hook: number;
|
|
3
|
+
flow: number;
|
|
4
|
+
engagement: number;
|
|
5
|
+
trend: number;
|
|
6
|
+
why: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ClipScore extends ClipScoreResponse {
|
|
9
|
+
score: number;
|
|
10
|
+
durationSec: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ClipScoreWeights {
|
|
13
|
+
hook?: number;
|
|
14
|
+
flow?: number;
|
|
15
|
+
engagement?: number;
|
|
16
|
+
trend?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Robust parser — tolerates prose around the JSON, missing keys, and
|
|
20
|
+
* out-of-range numbers. Mirrors `parseHookVisionResponse`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseClipScoreResponse(content: string): ClipScoreResponse;
|
|
23
|
+
/**
|
|
24
|
+
* Combine the four 0-1 dimensions into a single 0-100 score, using the
|
|
25
|
+
* default (or caller-supplied) weights.
|
|
26
|
+
*/
|
|
27
|
+
export declare function combineClipScore(vision: ClipScoreResponse, durationSec: number, weights?: ClipScoreWeights): ClipScore;
|
|
28
|
+
export interface ScoreClipOptions {
|
|
29
|
+
apiKey?: string;
|
|
30
|
+
model?: string;
|
|
31
|
+
detail?: "low" | "high";
|
|
32
|
+
weights?: ClipScoreWeights;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Run the LLM scoring pass. Frames are optional — transcript-only is
|
|
37
|
+
* the cheap default; pass frames when visual energy matters.
|
|
38
|
+
*
|
|
39
|
+
* Pure-ish: the only side effects are the OpenAI fetch and reading
|
|
40
|
+
* frame files off disk. Used directly by both the tool wrapper and
|
|
41
|
+
* `find_viral_moments`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function scoreClipInternal(transcriptText: string, startSec: number, endSec: number, framePaths?: string[], opts?: ScoreClipOptions): Promise<ClipScore>;
|
|
44
|
+
//# sourceMappingURL=clip-scoring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clip-scoring.d.ts","sourceRoot":"","sources":["../../src/core/clip-scoring.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,SAAU,SAAQ,iBAAiB;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyCD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAczE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,gBAAqB,GAC7B,SAAS,CAkBX;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,UAAU,GAAE,MAAM,EAAO,EACzB,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,SAAS,CAAC,CAuDpB"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clip scoring — does this arbitrary window earn the watch?
|
|
3
|
+
*
|
|
4
|
+
* `analyze_hook` only scores the first 3 seconds (the algorithmic
|
|
5
|
+
* checkpoint). Once you're past the hook, the next questions are:
|
|
6
|
+
*
|
|
7
|
+
* - Hook — does the OPENER (first ~5s) earn the watch?
|
|
8
|
+
* - Flow — does the middle stay engaging without dragging?
|
|
9
|
+
* - Engagement — emotional intensity, curiosity gap, payoff.
|
|
10
|
+
* - Trend — is the topic something currently shareable?
|
|
11
|
+
*
|
|
12
|
+
* One LLM call: transcript window in (and optional sample frames),
|
|
13
|
+
* structured 0-1 scores out, weighted total 0-100. Pure-ish — the
|
|
14
|
+
* scoring rubric, prompt template, parser and weighting all live here so
|
|
15
|
+
* `find_viral_moments` can reuse the engine without going through the
|
|
16
|
+
* tool wrapper.
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { resolveApiKey } from "./auth/api-keys.js";
|
|
20
|
+
const DEFAULT_WEIGHTS = {
|
|
21
|
+
hook: 30,
|
|
22
|
+
flow: 25,
|
|
23
|
+
engagement: 30,
|
|
24
|
+
trend: 15,
|
|
25
|
+
};
|
|
26
|
+
const CLIP_SYSTEM = `You are a short-form video selector. Your job is to score a CLIP (transcript ± sample frames) on its potential to go viral as a standalone Short / Reel / TikTok.
|
|
27
|
+
|
|
28
|
+
Output a JSON object with these EXACT keys (no extras, no prose):
|
|
29
|
+
{
|
|
30
|
+
"hook": <0..1>,
|
|
31
|
+
"flow": <0..1>,
|
|
32
|
+
"engagement": <0..1>,
|
|
33
|
+
"trend": <0..1>,
|
|
34
|
+
"why": "<≤160 char rationale: name the strongest + weakest dimension>"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Scoring rubric:
|
|
38
|
+
|
|
39
|
+
hook — Does the OPENING line earn the watch?
|
|
40
|
+
1.0 = strong scroll-stop opener (question, claim, shocking number, named subject);
|
|
41
|
+
0.5 = neutral lead; 0.0 = throat-clear / "so basically..." / dead air.
|
|
42
|
+
|
|
43
|
+
flow — Does the clip stay engaging across its full duration?
|
|
44
|
+
1.0 = every beat earns its time, no dragging, payoff lands;
|
|
45
|
+
0.5 = some slack but recovers; 0.0 = meanders / repeats / loses thread.
|
|
46
|
+
|
|
47
|
+
engagement — Emotional intensity, curiosity gap, surprise, payoff.
|
|
48
|
+
1.0 = strong emotional beat (laugh / shock / tension / aha);
|
|
49
|
+
0.5 = mildly interesting; 0.0 = flat informational.
|
|
50
|
+
|
|
51
|
+
trend — Topic currency. Does it intersect with what's currently
|
|
52
|
+
trending / evergreen-shareable on short-form?
|
|
53
|
+
1.0 = on-trend (AI, money, dating, fitness, productivity, gaming meta, current events);
|
|
54
|
+
0.5 = niche but proven shareable; 0.0 = obscure / dated / business-jargon.
|
|
55
|
+
|
|
56
|
+
Be honest. A technically-clean but flat clip should score 0.3-0.4, not 0.6+.`;
|
|
57
|
+
/**
|
|
58
|
+
* Robust parser — tolerates prose around the JSON, missing keys, and
|
|
59
|
+
* out-of-range numbers. Mirrors `parseHookVisionResponse`.
|
|
60
|
+
*/
|
|
61
|
+
export function parseClipScoreResponse(content) {
|
|
62
|
+
const start = content.indexOf("{");
|
|
63
|
+
const end = content.lastIndexOf("}");
|
|
64
|
+
if (start < 0 || end < start) {
|
|
65
|
+
throw new Error("clip-scoring: no JSON object in response");
|
|
66
|
+
}
|
|
67
|
+
const parsed = JSON.parse(content.slice(start, end + 1));
|
|
68
|
+
return {
|
|
69
|
+
hook: clamp01(num(parsed.hook, 0)),
|
|
70
|
+
flow: clamp01(num(parsed.flow, 0)),
|
|
71
|
+
engagement: clamp01(num(parsed.engagement, 0)),
|
|
72
|
+
trend: clamp01(num(parsed.trend, 0)),
|
|
73
|
+
why: String(parsed.why ?? "").slice(0, 240),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Combine the four 0-1 dimensions into a single 0-100 score, using the
|
|
78
|
+
* default (or caller-supplied) weights.
|
|
79
|
+
*/
|
|
80
|
+
export function combineClipScore(vision, durationSec, weights = {}) {
|
|
81
|
+
const w = { ...DEFAULT_WEIGHTS, ...weights };
|
|
82
|
+
const wSum = w.hook + w.flow + w.engagement + w.trend;
|
|
83
|
+
const raw = w.hook * vision.hook +
|
|
84
|
+
w.flow * vision.flow +
|
|
85
|
+
w.engagement * vision.engagement +
|
|
86
|
+
w.trend * vision.trend;
|
|
87
|
+
const score = Math.round((raw / wSum) * 100);
|
|
88
|
+
return {
|
|
89
|
+
score,
|
|
90
|
+
hook: vision.hook,
|
|
91
|
+
flow: vision.flow,
|
|
92
|
+
engagement: vision.engagement,
|
|
93
|
+
trend: vision.trend,
|
|
94
|
+
why: vision.why,
|
|
95
|
+
durationSec,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Run the LLM scoring pass. Frames are optional — transcript-only is
|
|
100
|
+
* the cheap default; pass frames when visual energy matters.
|
|
101
|
+
*
|
|
102
|
+
* Pure-ish: the only side effects are the OpenAI fetch and reading
|
|
103
|
+
* frame files off disk. Used directly by both the tool wrapper and
|
|
104
|
+
* `find_viral_moments`.
|
|
105
|
+
*/
|
|
106
|
+
export async function scoreClipInternal(transcriptText, startSec, endSec, framePaths = [], opts = {}) {
|
|
107
|
+
const apiKey = opts.apiKey ?? resolveApiKey("OPENAI_API_KEY", "openai");
|
|
108
|
+
if (!apiKey)
|
|
109
|
+
throw new Error("OPENAI_API_KEY required for score_clip.");
|
|
110
|
+
const durationSec = Math.max(0, endSec - startSec);
|
|
111
|
+
const detail = opts.detail ?? "low";
|
|
112
|
+
const model = opts.model ?? "gpt-4o-mini";
|
|
113
|
+
const userContent = [
|
|
114
|
+
{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: `CLIP WINDOW: ${startSec.toFixed(2)}s → ${endSec.toFixed(2)}s ` +
|
|
117
|
+
`(${durationSec.toFixed(2)}s).\n\nTRANSCRIPT:\n${transcriptText.trim() || "(empty)"}`,
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
for (const p of framePaths) {
|
|
121
|
+
const mime = p.toLowerCase().endsWith(".png") ? "image/png" : "image/jpeg";
|
|
122
|
+
const b64 = readFileSync(p).toString("base64");
|
|
123
|
+
userContent.push({
|
|
124
|
+
type: "image_url",
|
|
125
|
+
image_url: { url: `data:${mime};base64,${b64}`, detail },
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const body = {
|
|
129
|
+
model,
|
|
130
|
+
messages: [
|
|
131
|
+
{ role: "system", content: CLIP_SYSTEM },
|
|
132
|
+
{ role: "user", content: userContent },
|
|
133
|
+
],
|
|
134
|
+
response_format: { type: "json_object" },
|
|
135
|
+
temperature: 0,
|
|
136
|
+
};
|
|
137
|
+
const res = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
138
|
+
method: "POST",
|
|
139
|
+
headers: {
|
|
140
|
+
Authorization: `Bearer ${apiKey}`,
|
|
141
|
+
"Content-Type": "application/json",
|
|
142
|
+
},
|
|
143
|
+
body: JSON.stringify(body),
|
|
144
|
+
signal: opts.signal,
|
|
145
|
+
});
|
|
146
|
+
if (!res.ok) {
|
|
147
|
+
const text = await res.text().catch(() => "");
|
|
148
|
+
throw new Error(`OpenAI HTTP ${res.status}: ${text.slice(0, 200)}`);
|
|
149
|
+
}
|
|
150
|
+
const data = (await res.json());
|
|
151
|
+
const content = data.choices[0]?.message?.content;
|
|
152
|
+
if (!content)
|
|
153
|
+
throw new Error("score_clip: empty model response");
|
|
154
|
+
const parsed = parseClipScoreResponse(content);
|
|
155
|
+
return combineClipScore(parsed, durationSec, opts.weights);
|
|
156
|
+
}
|
|
157
|
+
// ── Helpers ─────────────────────────────────────────────────
|
|
158
|
+
function num(v, fb) {
|
|
159
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
160
|
+
return Number.isFinite(n) ? n : fb;
|
|
161
|
+
}
|
|
162
|
+
function clamp01(n) {
|
|
163
|
+
return Math.max(0, Math.min(1, n));
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=clip-scoring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clip-scoring.js","sourceRoot":"","sources":["../../src/core/clip-scoring.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clip-scoring.test.d.ts","sourceRoot":"","sources":["../../src/core/clip-scoring.test.ts"],"names":[],"mappings":""}
|