@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
package/dist/core/srt.js
CHANGED
|
@@ -14,15 +14,35 @@
|
|
|
14
14
|
* CRLF line endings are NOT required by the spec — most parsers accept LF
|
|
15
15
|
* (we emit LF for simplicity and cross-platform sanity).
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* ── Precision policy ─────────────────────────────────────────
|
|
18
|
+
* SRT's wire precision is exactly 1ms. We canonicalise every timestamp to a
|
|
19
|
+
* non-negative INTEGER millisecond at the boundary of this module:
|
|
20
|
+
*
|
|
21
|
+
* secondsToMs(s) = Math.max(0, Math.round(s * 1000))
|
|
22
|
+
*
|
|
23
|
+
* All downstream math (cue ordering, gap extension, end>start validation,
|
|
24
|
+
* grouping) runs on those ms-ints. This eliminates two whole classes of
|
|
25
|
+
* float-arithmetic bugs:
|
|
26
|
+
*
|
|
27
|
+
* 1. Cues whose float `end > start` but whose ms-rounded values collide
|
|
28
|
+
* (e.g. start=0.1001, end=0.1004 → both round to 100). The float check
|
|
29
|
+
* would pass, then we'd emit `00:00:00,100 --> 00:00:00,100` — invalid.
|
|
30
|
+
* 2. Drift when extending a cue's end up to the next cue's start: float
|
|
31
|
+
* assignment can carry a 1e-15 epsilon that flips a Math.round at the
|
|
32
|
+
* ms boundary. Integer assignment is exact.
|
|
33
|
+
*
|
|
34
|
+
* Callers pass seconds (the natural unit for whisper / OpenAI output); the
|
|
35
|
+
* conversion happens exactly once per timestamp, here. If you already have
|
|
36
|
+
* ms-ints upstream, use `formatSrtTimeMs` directly to skip the round trip.
|
|
19
37
|
*/
|
|
20
38
|
/**
|
|
21
39
|
* Word-level SRT — one cue per word. Useful for vertical-format burned-in
|
|
22
40
|
* captions where each word pops as it's spoken.
|
|
23
41
|
*
|
|
24
42
|
* Behaviour:
|
|
25
|
-
* - Skips empty / zero-duration words.
|
|
43
|
+
* - Skips empty / zero-duration words (post-rounding, so 0.1001s/0.1004s
|
|
44
|
+
* pairs that collapse to the same ms are dropped instead of producing
|
|
45
|
+
* invalid same-stamp cues).
|
|
26
46
|
* - Optional `groupSize` clusters N consecutive words into one cue (e.g. 2-3
|
|
27
47
|
* words at a time for a more readable rhythm). Default 1.
|
|
28
48
|
* - Optional `gapSec` extends a cue's end up to the next cue's start so the
|
|
@@ -30,61 +50,110 @@
|
|
|
30
50
|
*/
|
|
31
51
|
export function buildWordLevelSrt(words, opts = {}) {
|
|
32
52
|
const groupSize = Math.max(1, Math.floor(opts.groupSize ?? 1));
|
|
33
|
-
const filled =
|
|
53
|
+
const filled = [];
|
|
54
|
+
for (const w of words) {
|
|
55
|
+
const text = (w.text ?? "").trim();
|
|
56
|
+
if (!text)
|
|
57
|
+
continue;
|
|
58
|
+
const startMs = secondsToMs(w.start);
|
|
59
|
+
const endMs = secondsToMs(w.end);
|
|
60
|
+
if (endMs <= startMs)
|
|
61
|
+
continue;
|
|
62
|
+
filled.push({ startMs, endMs, text });
|
|
63
|
+
}
|
|
34
64
|
const cues = [];
|
|
35
65
|
for (let i = 0; i < filled.length; i += groupSize) {
|
|
36
66
|
const group = filled.slice(i, i + groupSize);
|
|
37
67
|
cues.push({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
text: group.map((g) => g.text
|
|
68
|
+
startMs: group[0].startMs,
|
|
69
|
+
endMs: group[group.length - 1].endMs,
|
|
70
|
+
text: group.map((g) => g.text).join(" "),
|
|
41
71
|
});
|
|
42
72
|
}
|
|
43
73
|
if (opts.gapSec !== undefined && opts.gapSec >= 0) {
|
|
74
|
+
const gapMs = secondsToMs(opts.gapSec);
|
|
44
75
|
for (let i = 0; i < cues.length - 1; i += 1) {
|
|
45
|
-
const gap = cues[i + 1].
|
|
46
|
-
if (gap > 0 && gap <=
|
|
47
|
-
|
|
76
|
+
const gap = cues[i + 1].startMs - cues[i].endMs;
|
|
77
|
+
if (gap > 0 && gap <= gapMs) {
|
|
78
|
+
// Integer assignment — no float epsilon to lose.
|
|
79
|
+
cues[i].endMs = cues[i + 1].startMs;
|
|
48
80
|
}
|
|
49
81
|
}
|
|
50
82
|
}
|
|
51
|
-
return
|
|
83
|
+
return buildFromIntCues(cues);
|
|
52
84
|
}
|
|
53
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* Build an SRT string from cues. Cues are emitted in input order. Throws on
|
|
87
|
+
* cues that collapse to zero or negative duration AFTER ms-rounding —
|
|
88
|
+
* upstream code is expected to handle that ahead of time.
|
|
89
|
+
*/
|
|
54
90
|
export function buildSrt(cues) {
|
|
91
|
+
const intCues = cues.map((c) => ({
|
|
92
|
+
startMs: secondsToMs(c.start),
|
|
93
|
+
endMs: secondsToMs(c.end),
|
|
94
|
+
text: (c.text ?? "").trim(),
|
|
95
|
+
}));
|
|
96
|
+
// Validate each non-empty cue; matches the previous public contract of
|
|
97
|
+
// throwing when end <= start, but now in ms-int space so the error message
|
|
98
|
+
// reflects what would actually have been emitted.
|
|
99
|
+
let ordinal = 0;
|
|
100
|
+
for (const c of intCues) {
|
|
101
|
+
if (!c.text)
|
|
102
|
+
continue;
|
|
103
|
+
ordinal += 1;
|
|
104
|
+
if (c.endMs <= c.startMs) {
|
|
105
|
+
throw new Error(`cue ${ordinal}: end (${msToSrtTime(c.endMs)}) must be > start (${msToSrtTime(c.startMs)})`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return buildFromIntCues(intCues);
|
|
109
|
+
}
|
|
110
|
+
/** Internal: skip empty-text cues, format the rest. */
|
|
111
|
+
function buildFromIntCues(cues) {
|
|
55
112
|
const out = [];
|
|
56
113
|
let n = 0;
|
|
57
114
|
for (const cue of cues) {
|
|
58
|
-
|
|
59
|
-
if (!text)
|
|
115
|
+
if (!cue.text)
|
|
60
116
|
continue;
|
|
61
|
-
if (cue.
|
|
62
|
-
|
|
63
|
-
}
|
|
117
|
+
if (cue.endMs <= cue.startMs)
|
|
118
|
+
continue; // Defensive — public callers also validate.
|
|
64
119
|
n += 1;
|
|
65
120
|
out.push(String(n));
|
|
66
|
-
out.push(`${
|
|
67
|
-
out.push(text);
|
|
121
|
+
out.push(`${msToSrtTime(cue.startMs)} --> ${msToSrtTime(cue.endMs)}`);
|
|
122
|
+
out.push(cue.text);
|
|
68
123
|
out.push("");
|
|
69
124
|
}
|
|
70
125
|
return out.join("\n");
|
|
71
126
|
}
|
|
72
|
-
/** Format seconds → "HH:MM:SS,mmm" (SRT timestamp). */
|
|
127
|
+
/** Format seconds → "HH:MM:SS,mmm" (SRT timestamp). Single rounding step. */
|
|
73
128
|
export function formatSrtTime(seconds) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
129
|
+
return msToSrtTime(secondsToMs(seconds));
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Format ms-int → "HH:MM:SS,mmm". Use this when you already have ms-int
|
|
133
|
+
* timing (e.g. straight off whisper.cpp's `offsets.from`) to avoid an
|
|
134
|
+
* unnecessary round-trip through float seconds.
|
|
135
|
+
*/
|
|
136
|
+
export function msToSrtTime(ms) {
|
|
137
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
138
|
+
ms = 0;
|
|
139
|
+
ms = Math.round(ms); // Defensive — callers should pass integers.
|
|
140
|
+
const milli = ms % 1000;
|
|
141
|
+
const totalSec = Math.floor(ms / 1000);
|
|
79
142
|
const ss = totalSec % 60;
|
|
80
143
|
const mm = Math.floor(totalSec / 60) % 60;
|
|
81
144
|
const hh = Math.floor(totalSec / 3600);
|
|
82
|
-
return (String(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
145
|
+
return pad2(hh) + ":" + pad2(mm) + ":" + pad2(ss) + "," + String(milli).padStart(3, "0");
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Convert float seconds → integer milliseconds with the same rounding rule
|
|
149
|
+
* used everywhere in this module. Negative inputs and NaN clamp to 0.
|
|
150
|
+
*/
|
|
151
|
+
export function secondsToMs(seconds) {
|
|
152
|
+
if (!Number.isFinite(seconds) || seconds < 0)
|
|
153
|
+
return 0;
|
|
154
|
+
return Math.round(seconds * 1000);
|
|
155
|
+
}
|
|
156
|
+
function pad2(n) {
|
|
157
|
+
return n < 10 ? "0" + n : String(n);
|
|
89
158
|
}
|
|
90
159
|
//# sourceMappingURL=srt.js.map
|
package/dist/core/srt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"srt.js","sourceRoot":"","sources":["../../src/core/srt.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"srt.js","sourceRoot":"","sources":["../../src/core/srt.ts"],"names":[],"mappings":"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"}
|
package/dist/core/srt.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { buildSrt, buildWordLevelSrt, formatSrtTime } from "./srt.js";
|
|
2
|
+
import { buildSrt, buildWordLevelSrt, formatSrtTime, msToSrtTime, secondsToMs } from "./srt.js";
|
|
3
3
|
describe("formatSrtTime", () => {
|
|
4
4
|
it("formats zero", () => {
|
|
5
5
|
expect(formatSrtTime(0)).toBe("00:00:00,000");
|
|
@@ -13,6 +13,36 @@ describe("formatSrtTime", () => {
|
|
|
13
13
|
it("clamps negatives to zero", () => {
|
|
14
14
|
expect(formatSrtTime(-3)).toBe("00:00:00,000");
|
|
15
15
|
});
|
|
16
|
+
it("clamps NaN / Infinity to zero", () => {
|
|
17
|
+
expect(formatSrtTime(Number.NaN)).toBe("00:00:00,000");
|
|
18
|
+
expect(formatSrtTime(Number.POSITIVE_INFINITY)).toBe("00:00:00,000");
|
|
19
|
+
});
|
|
20
|
+
it("rounds half away from zero at the ms boundary", () => {
|
|
21
|
+
// 1.2345s → 1234.5ms → round to 1235 (Math.round rule).
|
|
22
|
+
expect(formatSrtTime(1.2345)).toBe("00:00:01,235");
|
|
23
|
+
});
|
|
24
|
+
it("survives float multiplication artifacts", () => {
|
|
25
|
+
// 0.1 + 0.2 = 0.30000000000000004 in IEEE-754; must still emit 300ms.
|
|
26
|
+
expect(formatSrtTime(0.1 + 0.2)).toBe("00:00:00,300");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe("secondsToMs / msToSrtTime", () => {
|
|
30
|
+
it("secondsToMs rounds half-away-from-zero", () => {
|
|
31
|
+
expect(secondsToMs(0)).toBe(0);
|
|
32
|
+
expect(secondsToMs(0.0005)).toBe(1);
|
|
33
|
+
expect(secondsToMs(0.0014)).toBe(1);
|
|
34
|
+
expect(secondsToMs(35.123)).toBe(35123);
|
|
35
|
+
});
|
|
36
|
+
it("secondsToMs clamps negatives, NaN, Infinity to 0", () => {
|
|
37
|
+
expect(secondsToMs(-1)).toBe(0);
|
|
38
|
+
expect(secondsToMs(Number.NaN)).toBe(0);
|
|
39
|
+
expect(secondsToMs(Number.POSITIVE_INFINITY)).toBe(0);
|
|
40
|
+
});
|
|
41
|
+
it("msToSrtTime accepts pre-converted integer ms (no float round-trip)", () => {
|
|
42
|
+
expect(msToSrtTime(0)).toBe("00:00:00,000");
|
|
43
|
+
expect(msToSrtTime(35123)).toBe("00:00:35,123");
|
|
44
|
+
expect(msToSrtTime(3661234)).toBe("01:01:01,234");
|
|
45
|
+
});
|
|
16
46
|
});
|
|
17
47
|
describe("buildWordLevelSrt", () => {
|
|
18
48
|
it("emits one cue per word by default", () => {
|
|
@@ -61,6 +91,22 @@ describe("buildWordLevelSrt", () => {
|
|
|
61
91
|
expect(out).toContain("2\n00:00:00,300 --> 00:00:00,600\nb");
|
|
62
92
|
expect(out).not.toContain("3\n");
|
|
63
93
|
});
|
|
94
|
+
it("drops words whose ms-rounded duration collapses to zero", () => {
|
|
95
|
+
const out = buildWordLevelSrt([
|
|
96
|
+
{ start: 0, end: 0.3, text: "keep" },
|
|
97
|
+
// start=0.3001, end=0.3004 — both round to 300ms. Drop silently rather
|
|
98
|
+
// than emit invalid `00:00:00,300 --> 00:00:00,300`.
|
|
99
|
+
{ start: 0.3001, end: 0.3004, text: "drop" },
|
|
100
|
+
{ start: 0.4, end: 0.6, text: "keep2" },
|
|
101
|
+
]);
|
|
102
|
+
expect(out).toContain("keep");
|
|
103
|
+
expect(out).toContain("keep2");
|
|
104
|
+
expect(out).not.toContain("drop");
|
|
105
|
+
// Numbering must be contiguous (1, 2 — not 1, 3).
|
|
106
|
+
expect(out).toContain("1\n");
|
|
107
|
+
expect(out).toContain("2\n");
|
|
108
|
+
expect(out).not.toContain("3\n");
|
|
109
|
+
});
|
|
64
110
|
});
|
|
65
111
|
describe("buildSrt", () => {
|
|
66
112
|
it("emits 1-indexed cues with HH:MM:SS,mmm timestamps", () => {
|
|
@@ -99,6 +145,13 @@ describe("buildSrt", () => {
|
|
|
99
145
|
expect(() => buildSrt([{ start: 5, end: 5, text: "x" }])).toThrow(/end/);
|
|
100
146
|
expect(() => buildSrt([{ start: 5, end: 4, text: "x" }])).toThrow(/end/);
|
|
101
147
|
});
|
|
148
|
+
it("rejects cues whose float end>start collapses to the same ms-int", () => {
|
|
149
|
+
// start=0.1001, end=0.1004 — both round to 100ms. Float check would
|
|
150
|
+
// pass, but the SRT line `00:00:00,100 --> 00:00:00,100` is invalid
|
|
151
|
+
// (zero displayed duration). Must throw, matching the post-rounding
|
|
152
|
+
// wire reality.
|
|
153
|
+
expect(() => buildSrt([{ start: 0.1001, end: 0.1004, text: "x" }])).toThrow(/end/);
|
|
154
|
+
});
|
|
102
155
|
it("returns empty string for no usable cues", () => {
|
|
103
156
|
expect(buildSrt([])).toBe("");
|
|
104
157
|
expect(buildSrt([{ start: 0, end: 1, text: "" }])).toBe("");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"srt.test.js","sourceRoot":"","sources":["../../src/core/srt.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"srt.test.js","sourceRoot":"","sources":["../../src/core/srt.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helper for composing a single thumbnail frame: pull a frame at
|
|
3
|
+
* `atSec` and burn a headline on top with ffmpeg drawtext.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from compose-thumbnail.ts so `compose_thumbnail_variants`
|
|
6
|
+
* can call the same logic three times without duplicating the drawtext
|
|
7
|
+
* filter construction.
|
|
8
|
+
*/
|
|
9
|
+
export interface ComposeThumbnailOpts {
|
|
10
|
+
/** Source video path (already absolute or pre-resolved). */
|
|
11
|
+
input: string;
|
|
12
|
+
/** Output .jpg/.png absolute path. */
|
|
13
|
+
output: string;
|
|
14
|
+
/** Frame timestamp. */
|
|
15
|
+
atSec: number;
|
|
16
|
+
/** Headline text overlay (raw — escaping is handled internally). */
|
|
17
|
+
text: string;
|
|
18
|
+
/** Path to a TTF font; falls back to common system locations. */
|
|
19
|
+
fontFile?: string;
|
|
20
|
+
fontSize?: number;
|
|
21
|
+
fontColor?: string;
|
|
22
|
+
/** Empty string disables outline. Default 'black'. */
|
|
23
|
+
outlineColor?: string;
|
|
24
|
+
position?: "top" | "center" | "bottom";
|
|
25
|
+
/** Optional output width (height auto). */
|
|
26
|
+
width?: number;
|
|
27
|
+
signal?: AbortSignal;
|
|
28
|
+
}
|
|
29
|
+
export interface ComposeThumbnailResult {
|
|
30
|
+
ok: true;
|
|
31
|
+
path: string;
|
|
32
|
+
fontFile: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Burn a headline onto a single frame from the source video and write
|
|
36
|
+
* it to disk. Returns the resolved font path so callers can surface it
|
|
37
|
+
* in their output.
|
|
38
|
+
*
|
|
39
|
+
* Throws on:
|
|
40
|
+
* - no system font found (and none provided)
|
|
41
|
+
* - ffmpeg drawtext exit ≠ 0
|
|
42
|
+
*/
|
|
43
|
+
export declare function composeThumbnailFrame(opts: ComposeThumbnailOpts, cwd?: string): Promise<ComposeThumbnailResult>;
|
|
44
|
+
export declare function pickFont(): string | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Escape a string for ffmpeg drawtext's `text='...'` parameter.
|
|
47
|
+
*
|
|
48
|
+
* Rules (verified against real-world implementations):
|
|
49
|
+
* 1. `\\` first → `\\\\`
|
|
50
|
+
* 2. `:` → `\\:`
|
|
51
|
+
* 3. `'` → `\\'`
|
|
52
|
+
* 4. `%` → `\\%` (because `%{...}` is used for runtime expansions)
|
|
53
|
+
* 5. newlines → literal `\\n` (drawtext renders as a line break)
|
|
54
|
+
*
|
|
55
|
+
* Backslash MUST come first so subsequent escapes don't double-escape.
|
|
56
|
+
*/
|
|
57
|
+
export declare function escapeDrawtextValue(text: string): string;
|
|
58
|
+
//# sourceMappingURL=thumbnail-compose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thumbnail-compose.d.ts","sourceRoot":"","sources":["../../src/core/thumbnail-compose.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAUD,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,oBAAoB,EAC1B,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,sBAAsB,CAAC,CA8CjC;AAED,wBAAgB,QAAQ,IAAI,MAAM,GAAG,SAAS,CAS7C;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOxD"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helper for composing a single thumbnail frame: pull a frame at
|
|
3
|
+
* `atSec` and burn a headline on top with ffmpeg drawtext.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from compose-thumbnail.ts so `compose_thumbnail_variants`
|
|
6
|
+
* can call the same logic three times without duplicating the drawtext
|
|
7
|
+
* filter construction.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
10
|
+
import { dirname, resolve as resolvePath } from "node:path";
|
|
11
|
+
import { runFfmpeg } from "./media/ffmpeg.js";
|
|
12
|
+
const SYSTEM_FONT_GUESSES = [
|
|
13
|
+
"/System/Library/Fonts/Helvetica.ttc",
|
|
14
|
+
"/System/Library/Fonts/Supplemental/Arial.ttf",
|
|
15
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
|
16
|
+
"/usr/share/fonts/TTF/DejaVuSans-Bold.ttf",
|
|
17
|
+
"C:\\Windows\\Fonts\\Arial.ttf",
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* Burn a headline onto a single frame from the source video and write
|
|
21
|
+
* it to disk. Returns the resolved font path so callers can surface it
|
|
22
|
+
* in their output.
|
|
23
|
+
*
|
|
24
|
+
* Throws on:
|
|
25
|
+
* - no system font found (and none provided)
|
|
26
|
+
* - ffmpeg drawtext exit ≠ 0
|
|
27
|
+
*/
|
|
28
|
+
export async function composeThumbnailFrame(opts, cwd = process.cwd()) {
|
|
29
|
+
const fontFile = opts.fontFile ? resolvePath(cwd, opts.fontFile) : pickFont();
|
|
30
|
+
if (!fontFile) {
|
|
31
|
+
throw new Error("no system font found — pass fontFile=<path-to-.ttf> (~/Library/Fonts on macOS, /usr/share/fonts on Linux)");
|
|
32
|
+
}
|
|
33
|
+
const fontSize = opts.fontSize ?? 96;
|
|
34
|
+
const fontColor = opts.fontColor ?? "white";
|
|
35
|
+
const outlineColor = opts.outlineColor === "" ? "" : (opts.outlineColor ?? "black");
|
|
36
|
+
const pos = opts.position ?? "bottom";
|
|
37
|
+
const yExpr = pos === "top" ? "h*0.08" : pos === "center" ? "(h-text_h)/2" : "h-text_h-h*0.08";
|
|
38
|
+
const safeText = escapeDrawtextValue(opts.text);
|
|
39
|
+
const drawText = `drawtext=fontfile=${escapeFilterPath(fontFile)}:` +
|
|
40
|
+
`text='${safeText}':` +
|
|
41
|
+
`fontsize=${fontSize}:` +
|
|
42
|
+
`fontcolor=${fontColor}:` +
|
|
43
|
+
`x=(w-text_w)/2:` +
|
|
44
|
+
`y=${yExpr}` +
|
|
45
|
+
(outlineColor ? `:bordercolor=${outlineColor}:borderw=4` : "");
|
|
46
|
+
const vf = opts.width ? `scale=${opts.width}:-1,${drawText}` : drawText;
|
|
47
|
+
mkdirSync(dirname(opts.output), { recursive: true });
|
|
48
|
+
const r = await runFfmpeg([
|
|
49
|
+
"-ss",
|
|
50
|
+
String(opts.atSec),
|
|
51
|
+
"-i",
|
|
52
|
+
opts.input,
|
|
53
|
+
"-frames:v",
|
|
54
|
+
"1",
|
|
55
|
+
"-vf",
|
|
56
|
+
vf,
|
|
57
|
+
"-q:v",
|
|
58
|
+
"2",
|
|
59
|
+
opts.output,
|
|
60
|
+
], { signal: opts.signal });
|
|
61
|
+
if (r.code !== 0) {
|
|
62
|
+
throw new Error(`ffmpeg drawtext exited ${r.code} — verify atSec is in range and ffmpeg has --enable-libfreetype`);
|
|
63
|
+
}
|
|
64
|
+
return { ok: true, path: opts.output, fontFile };
|
|
65
|
+
}
|
|
66
|
+
export function pickFont() {
|
|
67
|
+
for (const p of SYSTEM_FONT_GUESSES) {
|
|
68
|
+
try {
|
|
69
|
+
if (existsSync(p))
|
|
70
|
+
return p;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* ignore */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
function escapeFilterPath(p) {
|
|
79
|
+
return p.replace(/\\/g, "\\\\").replace(/:/g, "\\:").replace(/'/g, "\\'");
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Escape a string for ffmpeg drawtext's `text='...'` parameter.
|
|
83
|
+
*
|
|
84
|
+
* Rules (verified against real-world implementations):
|
|
85
|
+
* 1. `\\` first → `\\\\`
|
|
86
|
+
* 2. `:` → `\\:`
|
|
87
|
+
* 3. `'` → `\\'`
|
|
88
|
+
* 4. `%` → `\\%` (because `%{...}` is used for runtime expansions)
|
|
89
|
+
* 5. newlines → literal `\\n` (drawtext renders as a line break)
|
|
90
|
+
*
|
|
91
|
+
* Backslash MUST come first so subsequent escapes don't double-escape.
|
|
92
|
+
*/
|
|
93
|
+
export function escapeDrawtextValue(text) {
|
|
94
|
+
return text
|
|
95
|
+
.replace(/\\/g, "\\\\")
|
|
96
|
+
.replace(/:/g, "\\:")
|
|
97
|
+
.replace(/'/g, "\\'")
|
|
98
|
+
.replace(/%/g, "\\%")
|
|
99
|
+
.replace(/\r\n?|\n/g, "\\n");
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=thumbnail-compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thumbnail-compose.js","sourceRoot":"","sources":["../../src/core/thumbnail-compose.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thumbnail-promise verifier.
|
|
3
|
+
*
|
|
4
|
+
* The thumbnail makes a visual promise (a man underwater, a huge pile
|
|
5
|
+
* of money, a broken phone). Per the MrBeast manual: "match the
|
|
6
|
+
* clickbait expectations and front-load." If the promised element
|
|
7
|
+
* doesn't show up until minute 8, retention craters before the payoff.
|
|
8
|
+
*
|
|
9
|
+
* This module asks the vision model to:
|
|
10
|
+
* 1. Look at the thumbnail FIRST and identify the visual promise.
|
|
11
|
+
* 2. Look at N sampled frames from the opening window.
|
|
12
|
+
* 3. Score how well those frames deliver on (or build up to) the
|
|
13
|
+
* promise.
|
|
14
|
+
*
|
|
15
|
+
* One LLM call, structured JSON out. Pure parser exported for tests.
|
|
16
|
+
*/
|
|
17
|
+
export interface ThumbnailPromiseResult {
|
|
18
|
+
/** 0..1 — how well the opening delivers on the thumbnail's visual promise. */
|
|
19
|
+
matches: number;
|
|
20
|
+
/** ≤200 char description of what the thumbnail is promising. */
|
|
21
|
+
thumbnailPromise: string;
|
|
22
|
+
/** ≤200 char description of what the opening frames actually show. */
|
|
23
|
+
openingShows: string;
|
|
24
|
+
/** Up to 4 specific elements promised by the thumbnail but missing from the opening. */
|
|
25
|
+
missing: string[];
|
|
26
|
+
/** ≤200 char actionable suggestion. */
|
|
27
|
+
suggestion: string;
|
|
28
|
+
}
|
|
29
|
+
export interface VerifyOptions {
|
|
30
|
+
apiKey?: string;
|
|
31
|
+
model?: string;
|
|
32
|
+
detail?: "low" | "high";
|
|
33
|
+
/** Window length used to sample frames; surfaced in the prompt for context. */
|
|
34
|
+
windowSec: number;
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Run ONE vision call against the thumbnail + N opening frames.
|
|
39
|
+
*/
|
|
40
|
+
export declare function runThumbnailPromise(thumbnailPath: string, framePaths: string[], opts: VerifyOptions): Promise<ThumbnailPromiseResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Parse the model's JSON. Robust to missing keys; clamps `matches` to
|
|
43
|
+
* [0, 1] and caps strings. Pure — exported for testing.
|
|
44
|
+
*/
|
|
45
|
+
export declare function parsePromiseResponse(content: string): ThumbnailPromiseResult;
|
|
46
|
+
//# sourceMappingURL=thumbnail-promise.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thumbnail-promise.d.ts","sourceRoot":"","sources":["../../src/core/thumbnail-promise.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,MAAM,WAAW,sBAAsB;IACrC,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;CACpB;AA0BD,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAAE,EACpB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,sBAAsB,CAAC,CA+DjC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,sBAAsB,CAoB5E"}
|