@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.
Files changed (446) hide show
  1. package/dist/cli.js +64 -1
  2. package/dist/cli.js.map +1 -1
  3. package/dist/core/audio-mix.d.ts.map +1 -1
  4. package/dist/core/audio-mix.js +8 -1
  5. package/dist/core/audio-mix.js.map +1 -1
  6. package/dist/core/audio-mix.test.js +1 -1
  7. package/dist/core/audio-mix.test.js.map +1 -1
  8. package/dist/core/auth/api-keys.d.ts +1 -1
  9. package/dist/core/auth/api-keys.d.ts.map +1 -1
  10. package/dist/core/auth/api-keys.js +2 -1
  11. package/dist/core/auth/api-keys.js.map +1 -1
  12. package/dist/core/auth/login.d.ts.map +1 -1
  13. package/dist/core/auth/login.js.map +1 -1
  14. package/dist/core/beats.d.ts +59 -0
  15. package/dist/core/beats.d.ts.map +1 -0
  16. package/dist/core/beats.js +122 -0
  17. package/dist/core/beats.js.map +1 -0
  18. package/dist/core/beats.test.d.ts +2 -0
  19. package/dist/core/beats.test.d.ts.map +1 -0
  20. package/dist/core/beats.test.js +86 -0
  21. package/dist/core/beats.test.js.map +1 -0
  22. package/dist/core/brand-kit.d.ts +80 -0
  23. package/dist/core/brand-kit.d.ts.map +1 -0
  24. package/dist/core/brand-kit.js +96 -0
  25. package/dist/core/brand-kit.js.map +1 -0
  26. package/dist/core/brand-kit.test.d.ts +2 -0
  27. package/dist/core/brand-kit.test.d.ts.map +1 -0
  28. package/dist/core/brand-kit.test.js +76 -0
  29. package/dist/core/brand-kit.test.js.map +1 -0
  30. package/dist/core/bundled-sfx.d.ts +64 -0
  31. package/dist/core/bundled-sfx.d.ts.map +1 -0
  32. package/dist/core/bundled-sfx.js +218 -0
  33. package/dist/core/bundled-sfx.js.map +1 -0
  34. package/dist/core/bundled-sfx.test.d.ts +2 -0
  35. package/dist/core/bundled-sfx.test.d.ts.map +1 -0
  36. package/dist/core/bundled-sfx.test.js +81 -0
  37. package/dist/core/bundled-sfx.test.js.map +1 -0
  38. package/dist/core/child-abort.d.ts +57 -0
  39. package/dist/core/child-abort.d.ts.map +1 -0
  40. package/dist/core/child-abort.js +95 -0
  41. package/dist/core/child-abort.js.map +1 -0
  42. package/dist/core/child-abort.test.d.ts +2 -0
  43. package/dist/core/child-abort.test.d.ts.map +1 -0
  44. package/dist/core/child-abort.test.js +88 -0
  45. package/dist/core/child-abort.test.js.map +1 -0
  46. package/dist/core/clip-scoring.d.ts +44 -0
  47. package/dist/core/clip-scoring.d.ts.map +1 -0
  48. package/dist/core/clip-scoring.js +165 -0
  49. package/dist/core/clip-scoring.js.map +1 -0
  50. package/dist/core/clip-scoring.test.d.ts +2 -0
  51. package/dist/core/clip-scoring.test.d.ts.map +1 -0
  52. package/dist/core/clip-scoring.test.js +113 -0
  53. package/dist/core/clip-scoring.test.js.map +1 -0
  54. package/dist/core/emoji-captions.d.ts +45 -0
  55. package/dist/core/emoji-captions.d.ts.map +1 -0
  56. package/dist/core/emoji-captions.js +121 -0
  57. package/dist/core/emoji-captions.js.map +1 -0
  58. package/dist/core/face-reframe.d.ts +91 -0
  59. package/dist/core/face-reframe.d.ts.map +1 -0
  60. package/dist/core/face-reframe.js +141 -0
  61. package/dist/core/face-reframe.js.map +1 -0
  62. package/dist/core/face-reframe.test.d.ts +2 -0
  63. package/dist/core/face-reframe.test.d.ts.map +1 -0
  64. package/dist/core/face-reframe.test.js +171 -0
  65. package/dist/core/face-reframe.test.js.map +1 -0
  66. package/dist/core/filler-words.d.ts +57 -9
  67. package/dist/core/filler-words.d.ts.map +1 -1
  68. package/dist/core/filler-words.js +61 -9
  69. package/dist/core/filler-words.js.map +1 -1
  70. package/dist/core/filler-words.test.js +91 -17
  71. package/dist/core/filler-words.test.js.map +1 -1
  72. package/dist/core/hook-rewrite.d.ts +48 -0
  73. package/dist/core/hook-rewrite.d.ts.map +1 -0
  74. package/dist/core/hook-rewrite.js +151 -0
  75. package/dist/core/hook-rewrite.js.map +1 -0
  76. package/dist/core/hook-rewrite.test.d.ts +2 -0
  77. package/dist/core/hook-rewrite.test.d.ts.map +1 -0
  78. package/dist/core/hook-rewrite.test.js +58 -0
  79. package/dist/core/hook-rewrite.test.js.map +1 -0
  80. package/dist/core/hosts/lazy.d.ts.map +1 -1
  81. package/dist/core/hosts/lazy.js +2 -0
  82. package/dist/core/hosts/lazy.js.map +1 -1
  83. package/dist/core/hosts/premiere/adapter.d.ts +1 -0
  84. package/dist/core/hosts/premiere/adapter.d.ts.map +1 -1
  85. package/dist/core/hosts/premiere/adapter.js.map +1 -1
  86. package/dist/core/hosts/premiere/bridge-source.d.ts.map +1 -1
  87. package/dist/core/hosts/premiere/bridge-source.js +6 -3
  88. package/dist/core/hosts/premiere/bridge-source.js.map +1 -1
  89. package/dist/core/hosts/resolve/adapter.d.ts +1 -0
  90. package/dist/core/hosts/resolve/adapter.d.ts.map +1 -1
  91. package/dist/core/hosts/resolve/adapter.js.map +1 -1
  92. package/dist/core/hosts/resolve/bridge-source.d.ts.map +1 -1
  93. package/dist/core/hosts/resolve/bridge-source.js +31 -4
  94. package/dist/core/hosts/resolve/bridge-source.js.map +1 -1
  95. package/dist/core/hosts/resolve/bridge.d.ts +2 -19
  96. package/dist/core/hosts/resolve/bridge.d.ts.map +1 -1
  97. package/dist/core/hosts/resolve/bridge.js +70 -41
  98. package/dist/core/hosts/resolve/bridge.js.map +1 -1
  99. package/dist/core/hosts/resolve/bridge.test.js +130 -0
  100. package/dist/core/hosts/resolve/bridge.test.js.map +1 -1
  101. package/dist/core/hosts/types.d.ts +6 -0
  102. package/dist/core/hosts/types.d.ts.map +1 -1
  103. package/dist/core/hosts/types.js.map +1 -1
  104. package/dist/core/logger.d.ts +32 -0
  105. package/dist/core/logger.d.ts.map +1 -0
  106. package/dist/core/logger.js +188 -0
  107. package/dist/core/logger.js.map +1 -0
  108. package/dist/core/loop-match.d.ts +57 -0
  109. package/dist/core/loop-match.d.ts.map +1 -0
  110. package/dist/core/loop-match.js +91 -0
  111. package/dist/core/loop-match.js.map +1 -0
  112. package/dist/core/media/ffmpeg.d.ts.map +1 -1
  113. package/dist/core/media/ffmpeg.js +14 -3
  114. package/dist/core/media/ffmpeg.js.map +1 -1
  115. package/dist/core/multi-format.d.ts +67 -0
  116. package/dist/core/multi-format.d.ts.map +1 -0
  117. package/dist/core/multi-format.js +127 -0
  118. package/dist/core/multi-format.js.map +1 -0
  119. package/dist/core/multi-format.test.d.ts +2 -0
  120. package/dist/core/multi-format.test.d.ts.map +1 -0
  121. package/dist/core/multi-format.test.js +151 -0
  122. package/dist/core/multi-format.test.js.map +1 -0
  123. package/dist/core/python/beats.py +61 -0
  124. package/dist/core/python/face_reframe.py +163 -0
  125. package/dist/core/python/sidecar-path.d.ts +13 -0
  126. package/dist/core/python/sidecar-path.d.ts.map +1 -0
  127. package/dist/core/python/sidecar-path.js +24 -0
  128. package/dist/core/python/sidecar-path.js.map +1 -0
  129. package/dist/core/python.d.ts +57 -0
  130. package/dist/core/python.d.ts.map +1 -0
  131. package/dist/core/python.js +107 -0
  132. package/dist/core/python.js.map +1 -0
  133. package/dist/core/python.test.d.ts +2 -0
  134. package/dist/core/python.test.d.ts.map +1 -0
  135. package/dist/core/python.test.js +129 -0
  136. package/dist/core/python.test.js.map +1 -0
  137. package/dist/core/retention-structure.d.ts +81 -0
  138. package/dist/core/retention-structure.d.ts.map +1 -0
  139. package/dist/core/retention-structure.js +206 -0
  140. package/dist/core/retention-structure.js.map +1 -0
  141. package/dist/core/retention-structure.test.d.ts +2 -0
  142. package/dist/core/retention-structure.test.d.ts.map +1 -0
  143. package/dist/core/retention-structure.test.js +88 -0
  144. package/dist/core/retention-structure.test.js.map +1 -0
  145. package/dist/core/review.d.ts +17 -0
  146. package/dist/core/review.d.ts.map +1 -1
  147. package/dist/core/review.js +20 -24
  148. package/dist/core/review.js.map +1 -1
  149. package/dist/core/safe-paths.d.ts +11 -0
  150. package/dist/core/safe-paths.d.ts.map +1 -1
  151. package/dist/core/safe-paths.js +26 -10
  152. package/dist/core/safe-paths.js.map +1 -1
  153. package/dist/core/safe-paths.test.js +16 -0
  154. package/dist/core/safe-paths.test.js.map +1 -1
  155. package/dist/core/skills-loader.d.ts +48 -2
  156. package/dist/core/skills-loader.d.ts.map +1 -1
  157. package/dist/core/skills-loader.js +97 -19
  158. package/dist/core/skills-loader.js.map +1 -1
  159. package/dist/core/skills-loader.test.js +63 -1
  160. package/dist/core/skills-loader.test.js.map +1 -1
  161. package/dist/core/srt.d.ts +42 -7
  162. package/dist/core/srt.d.ts.map +1 -1
  163. package/dist/core/srt.js +101 -32
  164. package/dist/core/srt.js.map +1 -1
  165. package/dist/core/srt.test.js +54 -1
  166. package/dist/core/srt.test.js.map +1 -1
  167. package/dist/core/thumbnail-compose.d.ts +58 -0
  168. package/dist/core/thumbnail-compose.d.ts.map +1 -0
  169. package/dist/core/thumbnail-compose.js +101 -0
  170. package/dist/core/thumbnail-compose.js.map +1 -0
  171. package/dist/core/thumbnail-promise.d.ts +46 -0
  172. package/dist/core/thumbnail-promise.d.ts.map +1 -0
  173. package/dist/core/thumbnail-promise.js +133 -0
  174. package/dist/core/thumbnail-promise.js.map +1 -0
  175. package/dist/core/thumbnail-promise.test.d.ts +2 -0
  176. package/dist/core/thumbnail-promise.test.d.ts.map +1 -0
  177. package/dist/core/thumbnail-promise.test.js +52 -0
  178. package/dist/core/thumbnail-promise.test.js.map +1 -0
  179. package/dist/core/viral-moments.d.ts +70 -0
  180. package/dist/core/viral-moments.d.ts.map +1 -0
  181. package/dist/core/viral-moments.js +192 -0
  182. package/dist/core/viral-moments.js.map +1 -0
  183. package/dist/core/viral-moments.test.d.ts +2 -0
  184. package/dist/core/viral-moments.test.d.ts.map +1 -0
  185. package/dist/core/viral-moments.test.js +153 -0
  186. package/dist/core/viral-moments.test.js.map +1 -0
  187. package/dist/core/whisper.d.ts +16 -0
  188. package/dist/core/whisper.d.ts.map +1 -1
  189. package/dist/core/whisper.js +72 -5
  190. package/dist/core/whisper.js.map +1 -1
  191. package/dist/core/whisper.test.js +111 -1
  192. package/dist/core/whisper.test.js.map +1 -1
  193. package/dist/core/youtube-metadata.d.ts +44 -0
  194. package/dist/core/youtube-metadata.d.ts.map +1 -0
  195. package/dist/core/youtube-metadata.js +168 -0
  196. package/dist/core/youtube-metadata.js.map +1 -0
  197. package/dist/core/youtube-metadata.test.d.ts +2 -0
  198. package/dist/core/youtube-metadata.test.d.ts.map +1 -0
  199. package/dist/core/youtube-metadata.test.js +132 -0
  200. package/dist/core/youtube-metadata.test.js.map +1 -0
  201. package/dist/prompt-commands.d.ts +24 -0
  202. package/dist/prompt-commands.d.ts.map +1 -0
  203. package/dist/prompt-commands.js +243 -0
  204. package/dist/prompt-commands.js.map +1 -0
  205. package/dist/prompt-commands.test.d.ts +2 -0
  206. package/dist/prompt-commands.test.d.ts.map +1 -0
  207. package/dist/prompt-commands.test.js +46 -0
  208. package/dist/prompt-commands.test.js.map +1 -0
  209. package/dist/skills.d.ts +6 -6
  210. package/dist/skills.d.ts.map +1 -1
  211. package/dist/skills.js +1426 -445
  212. package/dist/skills.js.map +1 -1
  213. package/dist/system-prompt.d.ts.map +1 -1
  214. package/dist/system-prompt.js +108 -0
  215. package/dist/system-prompt.js.map +1 -1
  216. package/dist/tools/add-fades.d.ts.map +1 -1
  217. package/dist/tools/add-fades.js +2 -1
  218. package/dist/tools/add-fades.js.map +1 -1
  219. package/dist/tools/add-sfx-at-cuts.d.ts.map +1 -1
  220. package/dist/tools/add-sfx-at-cuts.js +36 -11
  221. package/dist/tools/add-sfx-at-cuts.js.map +1 -1
  222. package/dist/tools/add-sfx-to-timeline.d.ts +34 -0
  223. package/dist/tools/add-sfx-to-timeline.d.ts.map +1 -0
  224. package/dist/tools/add-sfx-to-timeline.js +169 -0
  225. package/dist/tools/add-sfx-to-timeline.js.map +1 -0
  226. package/dist/tools/add-sfx-to-timeline.test.d.ts +2 -0
  227. package/dist/tools/add-sfx-to-timeline.test.d.ts.map +1 -0
  228. package/dist/tools/add-sfx-to-timeline.test.js +181 -0
  229. package/dist/tools/add-sfx-to-timeline.test.js.map +1 -0
  230. package/dist/tools/audit-first-frame.d.ts +36 -0
  231. package/dist/tools/audit-first-frame.d.ts.map +1 -0
  232. package/dist/tools/audit-first-frame.js +181 -0
  233. package/dist/tools/audit-first-frame.js.map +1 -0
  234. package/dist/tools/audit-retention-structure.d.ts +20 -0
  235. package/dist/tools/audit-retention-structure.d.ts.map +1 -0
  236. package/dist/tools/audit-retention-structure.js +95 -0
  237. package/dist/tools/audit-retention-structure.js.map +1 -0
  238. package/dist/tools/audit-retention-structure.test.d.ts +2 -0
  239. package/dist/tools/audit-retention-structure.test.d.ts.map +1 -0
  240. package/dist/tools/audit-retention-structure.test.js +93 -0
  241. package/dist/tools/audit-retention-structure.test.js.map +1 -0
  242. package/dist/tools/bleep-words.d.ts +59 -0
  243. package/dist/tools/bleep-words.d.ts.map +1 -0
  244. package/dist/tools/bleep-words.js +211 -0
  245. package/dist/tools/bleep-words.js.map +1 -0
  246. package/dist/tools/bleep-words.test.d.ts +2 -0
  247. package/dist/tools/bleep-words.test.d.ts.map +1 -0
  248. package/dist/tools/bleep-words.test.js +96 -0
  249. package/dist/tools/bleep-words.test.js.map +1 -0
  250. package/dist/tools/burn-subtitles.d.ts.map +1 -1
  251. package/dist/tools/burn-subtitles.js +10 -5
  252. package/dist/tools/burn-subtitles.js.map +1 -1
  253. package/dist/tools/clean-audio.d.ts.map +1 -1
  254. package/dist/tools/clean-audio.js +2 -1
  255. package/dist/tools/clean-audio.js.map +1 -1
  256. package/dist/tools/cluster-takes.js +2 -1
  257. package/dist/tools/cluster-takes.js.map +1 -1
  258. package/dist/tools/compose-thumbnail-variants.d.ts +70 -0
  259. package/dist/tools/compose-thumbnail-variants.d.ts.map +1 -0
  260. package/dist/tools/compose-thumbnail-variants.js +274 -0
  261. package/dist/tools/compose-thumbnail-variants.js.map +1 -0
  262. package/dist/tools/compose-thumbnail.d.ts +6 -13
  263. package/dist/tools/compose-thumbnail.d.ts.map +1 -1
  264. package/dist/tools/compose-thumbnail.js +44 -81
  265. package/dist/tools/compose-thumbnail.js.map +1 -1
  266. package/dist/tools/concat-videos.d.ts.map +1 -1
  267. package/dist/tools/concat-videos.js +12 -5
  268. package/dist/tools/concat-videos.js.map +1 -1
  269. package/dist/tools/concat-videos.test.d.ts +2 -0
  270. package/dist/tools/concat-videos.test.d.ts.map +1 -0
  271. package/dist/tools/concat-videos.test.js +103 -0
  272. package/dist/tools/concat-videos.test.js.map +1 -0
  273. package/dist/tools/crossfade-videos.d.ts.map +1 -1
  274. package/dist/tools/crossfade-videos.js +2 -1
  275. package/dist/tools/crossfade-videos.js.map +1 -1
  276. package/dist/tools/cut-filler-words.d.ts.map +1 -1
  277. package/dist/tools/cut-filler-words.js +24 -8
  278. package/dist/tools/cut-filler-words.js.map +1 -1
  279. package/dist/tools/detect-speaker-changes.js +2 -1
  280. package/dist/tools/detect-speaker-changes.js.map +1 -1
  281. package/dist/tools/extract-audio.d.ts.map +1 -1
  282. package/dist/tools/extract-audio.js +13 -7
  283. package/dist/tools/extract-audio.js.map +1 -1
  284. package/dist/tools/face-reframe.d.ts +30 -0
  285. package/dist/tools/face-reframe.d.ts.map +1 -0
  286. package/dist/tools/face-reframe.js +143 -0
  287. package/dist/tools/face-reframe.js.map +1 -0
  288. package/dist/tools/face-reframe.test.d.ts +2 -0
  289. package/dist/tools/face-reframe.test.d.ts.map +1 -0
  290. package/dist/tools/face-reframe.test.js +139 -0
  291. package/dist/tools/face-reframe.test.js.map +1 -0
  292. package/dist/tools/find-viral-moments.d.ts +23 -0
  293. package/dist/tools/find-viral-moments.d.ts.map +1 -0
  294. package/dist/tools/find-viral-moments.js +176 -0
  295. package/dist/tools/find-viral-moments.js.map +1 -0
  296. package/dist/tools/find-viral-moments.test.d.ts +2 -0
  297. package/dist/tools/find-viral-moments.test.d.ts.map +1 -0
  298. package/dist/tools/find-viral-moments.test.js +144 -0
  299. package/dist/tools/find-viral-moments.test.js.map +1 -0
  300. package/dist/tools/generate-gif.d.ts.map +1 -1
  301. package/dist/tools/generate-gif.js +47 -40
  302. package/dist/tools/generate-gif.js.map +1 -1
  303. package/dist/tools/generate-gif.test.d.ts +2 -0
  304. package/dist/tools/generate-gif.test.d.ts.map +1 -0
  305. package/dist/tools/generate-gif.test.js +115 -0
  306. package/dist/tools/generate-gif.test.js.map +1 -0
  307. package/dist/tools/generate-outro.d.ts +18 -0
  308. package/dist/tools/generate-outro.d.ts.map +1 -0
  309. package/dist/tools/generate-outro.js +175 -0
  310. package/dist/tools/generate-outro.js.map +1 -0
  311. package/dist/tools/generate-youtube-metadata.d.ts +23 -0
  312. package/dist/tools/generate-youtube-metadata.d.ts.map +1 -0
  313. package/dist/tools/generate-youtube-metadata.js +103 -0
  314. package/dist/tools/generate-youtube-metadata.js.map +1 -0
  315. package/dist/tools/generate-youtube-metadata.test.d.ts +2 -0
  316. package/dist/tools/generate-youtube-metadata.test.d.ts.map +1 -0
  317. package/dist/tools/generate-youtube-metadata.test.js +118 -0
  318. package/dist/tools/generate-youtube-metadata.test.js.map +1 -0
  319. package/dist/tools/index.d.ts +14 -0
  320. package/dist/tools/index.d.ts.map +1 -1
  321. package/dist/tools/index.js +130 -1
  322. package/dist/tools/index.js.map +1 -1
  323. package/dist/tools/index.test.js +27 -1
  324. package/dist/tools/index.test.js.map +1 -1
  325. package/dist/tools/ken-burns.d.ts.map +1 -1
  326. package/dist/tools/ken-burns.js +2 -1
  327. package/dist/tools/ken-burns.js.map +1 -1
  328. package/dist/tools/loop-match-short.d.ts +22 -0
  329. package/dist/tools/loop-match-short.d.ts.map +1 -0
  330. package/dist/tools/loop-match-short.js +107 -0
  331. package/dist/tools/loop-match-short.js.map +1 -0
  332. package/dist/tools/mix-audio.d.ts.map +1 -1
  333. package/dist/tools/mix-audio.js +2 -1
  334. package/dist/tools/mix-audio.js.map +1 -1
  335. package/dist/tools/normalize-loudness.d.ts.map +1 -1
  336. package/dist/tools/normalize-loudness.js +2 -1
  337. package/dist/tools/normalize-loudness.js.map +1 -1
  338. package/dist/tools/path-traversal.test.d.ts +15 -0
  339. package/dist/tools/path-traversal.test.d.ts.map +1 -0
  340. package/dist/tools/path-traversal.test.js +223 -0
  341. package/dist/tools/path-traversal.test.js.map +1 -0
  342. package/dist/tools/pick-best-takes.js +2 -1
  343. package/dist/tools/pick-best-takes.js.map +1 -1
  344. package/dist/tools/punch-in.d.ts.map +1 -1
  345. package/dist/tools/punch-in.js +2 -1
  346. package/dist/tools/punch-in.js.map +1 -1
  347. package/dist/tools/read-transcript.js +2 -1
  348. package/dist/tools/read-transcript.js.map +1 -1
  349. package/dist/tools/render-multi-format.d.ts +35 -0
  350. package/dist/tools/render-multi-format.d.ts.map +1 -0
  351. package/dist/tools/render-multi-format.js +206 -0
  352. package/dist/tools/render-multi-format.js.map +1 -0
  353. package/dist/tools/render-multi-format.test.d.ts +2 -0
  354. package/dist/tools/render-multi-format.test.d.ts.map +1 -0
  355. package/dist/tools/render-multi-format.test.js +312 -0
  356. package/dist/tools/render-multi-format.test.js.map +1 -0
  357. package/dist/tools/render.d.ts.map +1 -1
  358. package/dist/tools/render.js +2 -2
  359. package/dist/tools/render.js.map +1 -1
  360. package/dist/tools/rewrite-hook.d.ts +32 -0
  361. package/dist/tools/rewrite-hook.d.ts.map +1 -0
  362. package/dist/tools/rewrite-hook.js +65 -0
  363. package/dist/tools/rewrite-hook.js.map +1 -0
  364. package/dist/tools/score-clip.d.ts +30 -0
  365. package/dist/tools/score-clip.d.ts.map +1 -0
  366. package/dist/tools/score-clip.js +109 -0
  367. package/dist/tools/score-clip.js.map +1 -0
  368. package/dist/tools/score-clip.test.d.ts +2 -0
  369. package/dist/tools/score-clip.test.d.ts.map +1 -0
  370. package/dist/tools/score-clip.test.js +110 -0
  371. package/dist/tools/score-clip.test.js.map +1 -0
  372. package/dist/tools/search-tools.d.ts +34 -0
  373. package/dist/tools/search-tools.d.ts.map +1 -0
  374. package/dist/tools/search-tools.js +86 -0
  375. package/dist/tools/search-tools.js.map +1 -0
  376. package/dist/tools/search-tools.test.d.ts +2 -0
  377. package/dist/tools/search-tools.test.d.ts.map +1 -0
  378. package/dist/tools/search-tools.test.js +60 -0
  379. package/dist/tools/search-tools.test.js.map +1 -0
  380. package/dist/tools/snap-cuts-to-beats.d.ts +18 -0
  381. package/dist/tools/snap-cuts-to-beats.d.ts.map +1 -0
  382. package/dist/tools/snap-cuts-to-beats.js +110 -0
  383. package/dist/tools/snap-cuts-to-beats.js.map +1 -0
  384. package/dist/tools/snap-cuts-to-beats.test.d.ts +2 -0
  385. package/dist/tools/snap-cuts-to-beats.test.d.ts.map +1 -0
  386. package/dist/tools/snap-cuts-to-beats.test.js +99 -0
  387. package/dist/tools/snap-cuts-to-beats.test.js.map +1 -0
  388. package/dist/tools/speed-ramp.d.ts.map +1 -1
  389. package/dist/tools/speed-ramp.js +2 -1
  390. package/dist/tools/speed-ramp.js.map +1 -1
  391. package/dist/tools/stabilize-video.d.ts.map +1 -1
  392. package/dist/tools/stabilize-video.js +2 -1
  393. package/dist/tools/stabilize-video.js.map +1 -1
  394. package/dist/tools/suggest-broll.d.ts +34 -0
  395. package/dist/tools/suggest-broll.d.ts.map +1 -0
  396. package/dist/tools/suggest-broll.js +367 -0
  397. package/dist/tools/suggest-broll.js.map +1 -0
  398. package/dist/tools/suggest-broll.test.d.ts +2 -0
  399. package/dist/tools/suggest-broll.test.d.ts.map +1 -0
  400. package/dist/tools/suggest-broll.test.js +217 -0
  401. package/dist/tools/suggest-broll.test.js.map +1 -0
  402. package/dist/tools/text-based-cut.d.ts +33 -0
  403. package/dist/tools/text-based-cut.d.ts.map +1 -0
  404. package/dist/tools/text-based-cut.js +172 -0
  405. package/dist/tools/text-based-cut.js.map +1 -0
  406. package/dist/tools/text-based-cut.test.d.ts +2 -0
  407. package/dist/tools/text-based-cut.test.d.ts.map +1 -0
  408. package/dist/tools/text-based-cut.test.js +32 -0
  409. package/dist/tools/text-based-cut.test.js.map +1 -0
  410. package/dist/tools/transcribe.d.ts +1 -1
  411. package/dist/tools/transition-videos.d.ts +1 -1
  412. package/dist/tools/transition-videos.d.ts.map +1 -1
  413. package/dist/tools/transition-videos.js +2 -1
  414. package/dist/tools/transition-videos.js.map +1 -1
  415. package/dist/tools/trim-dead-air.d.ts +59 -0
  416. package/dist/tools/trim-dead-air.d.ts.map +1 -0
  417. package/dist/tools/trim-dead-air.js +215 -0
  418. package/dist/tools/trim-dead-air.js.map +1 -0
  419. package/dist/tools/trim-dead-air.test.d.ts +2 -0
  420. package/dist/tools/trim-dead-air.test.d.ts.map +1 -0
  421. package/dist/tools/trim-dead-air.test.js +75 -0
  422. package/dist/tools/trim-dead-air.test.js.map +1 -0
  423. package/dist/tools/verify-thumbnail-promise.d.ts +33 -0
  424. package/dist/tools/verify-thumbnail-promise.d.ts.map +1 -0
  425. package/dist/tools/verify-thumbnail-promise.js +112 -0
  426. package/dist/tools/verify-thumbnail-promise.js.map +1 -0
  427. package/dist/tools/verify-thumbnail-promise.test.d.ts +2 -0
  428. package/dist/tools/verify-thumbnail-promise.test.d.ts.map +1 -0
  429. package/dist/tools/verify-thumbnail-promise.test.js +38 -0
  430. package/dist/tools/verify-thumbnail-promise.test.js.map +1 -0
  431. package/dist/tools/write-keyword-captions.d.ts +7 -0
  432. package/dist/tools/write-keyword-captions.d.ts.map +1 -1
  433. package/dist/tools/write-keyword-captions.js +35 -4
  434. package/dist/tools/write-keyword-captions.js.map +1 -1
  435. package/dist/ui/App.d.ts.map +1 -1
  436. package/dist/ui/App.js +75 -11
  437. package/dist/ui/App.js.map +1 -1
  438. package/dist/ui/tool-formatters.d.ts +30 -0
  439. package/dist/ui/tool-formatters.d.ts.map +1 -0
  440. package/dist/ui/tool-formatters.js +461 -0
  441. package/dist/ui/tool-formatters.js.map +1 -0
  442. package/dist/ui/tool-formatters.test.d.ts +2 -0
  443. package/dist/ui/tool-formatters.test.d.ts.map +1 -0
  444. package/dist/ui/tool-formatters.test.js +143 -0
  445. package/dist/ui/tool-formatters.test.js.map +1 -0
  446. package/package.json +10 -9
@@ -0,0 +1,133 @@
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
+ import { readFileSync } from "node:fs";
18
+ import { resolveApiKey } from "./auth/api-keys.js";
19
+ const SYSTEM = `You verify that a YouTube thumbnail's visual promise is delivered in the OPENING N seconds of the video.
20
+
21
+ Process:
22
+ 1. Look at the THUMBNAIL FIRST. What concrete visual element is it promising? (e.g. "man underwater", "huge pile of money", "broken phone screen", "1000 mousetraps".) The promise is what would make a scroller click.
23
+ 2. Then look at the N sampled OPENING FRAMES (in order). They were sampled across the first windowSec seconds of the video.
24
+ 3. Decide: does the promised element appear in the opening? Is it CLEARLY building toward the promise? Or is the opening a logo slate / unrelated b-roll?
25
+
26
+ Output JSON with these EXACT keys:
27
+ {
28
+ "matches": <0..1>,
29
+ "thumbnailPromise": "<≤200 char>",
30
+ "openingShows": "<≤200 char>",
31
+ "missing": [<≤80 char>, ...], // 0-4 entries; promised elements absent in opening
32
+ "suggestion": "<≤200 char>"
33
+ }
34
+
35
+ Scoring rubric:
36
+ 1.0 = promised element is on-screen within the opening window.
37
+ 0.7 = element is clearly being built up to (visible in last sampled frame, or directly referenced).
38
+ 0.4 = related but indirect — a tease without the payoff.
39
+ 0.0 = no connection. Logo slate, unrelated b-roll, or completely different content.
40
+
41
+ Be honest. A thumbnail that promises X but shows X at minute 8 must score low even if the eventual payoff is good — retention is decided in seconds.`;
42
+ /**
43
+ * Run ONE vision call against the thumbnail + N opening frames.
44
+ */
45
+ export async function runThumbnailPromise(thumbnailPath, framePaths, opts) {
46
+ const apiKey = opts.apiKey ?? resolveApiKey("OPENAI_API_KEY", "openai");
47
+ if (!apiKey)
48
+ throw new Error("OPENAI_API_KEY required for verify_thumbnail_promise.");
49
+ const model = opts.model ?? "gpt-4o-mini";
50
+ const detail = opts.detail ?? "low";
51
+ const thumbB64 = readFileSync(thumbnailPath).toString("base64");
52
+ const thumbMime = thumbnailPath.toLowerCase().endsWith(".png") ? "image/png" : "image/jpeg";
53
+ const userContent = [
54
+ { type: "text", text: "THUMBNAIL:" },
55
+ {
56
+ type: "image_url",
57
+ image_url: { url: `data:${thumbMime};base64,${thumbB64}`, detail },
58
+ },
59
+ {
60
+ type: "text",
61
+ text: `OPENING FRAMES (${framePaths.length} samples across the first ${opts.windowSec}s):`,
62
+ },
63
+ ];
64
+ for (let i = 0; i < framePaths.length; i++) {
65
+ const p = framePaths[i];
66
+ const mime = p.toLowerCase().endsWith(".png") ? "image/png" : "image/jpeg";
67
+ const b64 = readFileSync(p).toString("base64");
68
+ userContent.push({ type: "text", text: `FRAME ${i + 1}/${framePaths.length}:` });
69
+ userContent.push({
70
+ type: "image_url",
71
+ image_url: { url: `data:${mime};base64,${b64}`, detail },
72
+ });
73
+ }
74
+ const body = {
75
+ model,
76
+ messages: [
77
+ { role: "system", content: SYSTEM },
78
+ { role: "user", content: userContent },
79
+ ],
80
+ response_format: { type: "json_object" },
81
+ max_tokens: 2000,
82
+ temperature: 0,
83
+ };
84
+ const res = await fetch("https://api.openai.com/v1/chat/completions", {
85
+ method: "POST",
86
+ headers: {
87
+ Authorization: `Bearer ${apiKey}`,
88
+ "Content-Type": "application/json",
89
+ },
90
+ body: JSON.stringify(body),
91
+ signal: opts.signal,
92
+ });
93
+ if (!res.ok) {
94
+ const text = await res.text().catch(() => "");
95
+ throw new Error(`OpenAI HTTP ${res.status}: ${text.slice(0, 200)}`);
96
+ }
97
+ const data = (await res.json());
98
+ const content = data.choices[0]?.message?.content;
99
+ if (!content)
100
+ throw new Error("verify_thumbnail_promise: empty model response");
101
+ return parsePromiseResponse(content);
102
+ }
103
+ /**
104
+ * Parse the model's JSON. Robust to missing keys; clamps `matches` to
105
+ * [0, 1] and caps strings. Pure — exported for testing.
106
+ */
107
+ export function parsePromiseResponse(content) {
108
+ const start = content.indexOf("{");
109
+ const end = content.lastIndexOf("}");
110
+ if (start < 0 || end < start) {
111
+ throw new Error("verify_thumbnail_promise: no JSON object in response");
112
+ }
113
+ const parsed = JSON.parse(content.slice(start, end + 1));
114
+ const missing = Array.isArray(parsed.missing)
115
+ ? parsed.missing
116
+ .map((v) => String(v ?? "").slice(0, 100))
117
+ .filter((v) => v.length > 0)
118
+ .slice(0, 4)
119
+ : [];
120
+ return {
121
+ matches: clamp01(Number(parsed.matches)),
122
+ thumbnailPromise: String(parsed.thumbnailPromise ?? "").slice(0, 240),
123
+ openingShows: String(parsed.openingShows ?? "").slice(0, 240),
124
+ missing,
125
+ suggestion: String(parsed.suggestion ?? "").slice(0, 240),
126
+ };
127
+ }
128
+ function clamp01(n) {
129
+ if (!Number.isFinite(n))
130
+ return 0;
131
+ return Math.max(0, Math.min(1, n));
132
+ }
133
+ //# sourceMappingURL=thumbnail-promise.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnail-promise.js","sourceRoot":"","sources":["../../src/core/thumbnail-promise.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=thumbnail-promise.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnail-promise.test.d.ts","sourceRoot":"","sources":["../../src/core/thumbnail-promise.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { parsePromiseResponse } from "./thumbnail-promise.js";
3
+ describe("parsePromiseResponse", () => {
4
+ it("parses a complete response", () => {
5
+ const content = JSON.stringify({
6
+ matches: 0.7,
7
+ thumbnailPromise: "man underwater",
8
+ openingShows: "man near pool",
9
+ missing: ["actually being submerged"],
10
+ suggestion: "cut to underwater shot at 0:02",
11
+ });
12
+ const r = parsePromiseResponse(content);
13
+ expect(r.matches).toBeCloseTo(0.7);
14
+ expect(r.thumbnailPromise).toBe("man underwater");
15
+ expect(r.missing).toEqual(["actually being submerged"]);
16
+ });
17
+ it("clamps matches to [0,1]", () => {
18
+ const r = parsePromiseResponse(JSON.stringify({
19
+ matches: 5,
20
+ thumbnailPromise: "",
21
+ openingShows: "",
22
+ missing: [],
23
+ suggestion: "",
24
+ }));
25
+ expect(r.matches).toBe(1);
26
+ const r2 = parsePromiseResponse(JSON.stringify({
27
+ matches: -0.5,
28
+ thumbnailPromise: "",
29
+ openingShows: "",
30
+ missing: [],
31
+ suggestion: "",
32
+ }));
33
+ expect(r2.matches).toBe(0);
34
+ });
35
+ it("caps missing at 4 entries", () => {
36
+ const r = parsePromiseResponse(JSON.stringify({
37
+ matches: 0.5,
38
+ missing: ["a", "b", "c", "d", "e", "f"],
39
+ }));
40
+ expect(r.missing).toHaveLength(4);
41
+ });
42
+ it("tolerates missing keys", () => {
43
+ const r = parsePromiseResponse(JSON.stringify({}));
44
+ expect(r.matches).toBe(0);
45
+ expect(r.thumbnailPromise).toBe("");
46
+ expect(r.missing).toEqual([]);
47
+ });
48
+ it("throws on non-JSON", () => {
49
+ expect(() => parsePromiseResponse("not json")).toThrow();
50
+ });
51
+ });
52
+ //# sourceMappingURL=thumbnail-promise.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnail-promise.test.js","sourceRoot":"","sources":["../../src/core/thumbnail-promise.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;YAC7B,OAAO,EAAE,GAAG;YACZ,gBAAgB,EAAE,gBAAgB;YAClC,YAAY,EAAE,eAAe;YAC7B,OAAO,EAAE,CAAC,0BAA0B,CAAC;YACrC,UAAU,EAAE,gCAAgC;SAC7C,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACxC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAClD,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,MAAM,CAAC,GAAG,oBAAoB,CAC5B,IAAI,CAAC,SAAS,CAAC;YACb,OAAO,EAAE,CAAC;YACV,gBAAgB,EAAE,EAAE;YACpB,YAAY,EAAE,EAAE;YAChB,OAAO,EAAE,EAAE;YACX,UAAU,EAAE,EAAE;SACf,CAAC,CACH,CAAC;QACF,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1B,MAAM,EAAE,GAAG,oBAAoB,CAC7B,IAAI,CAAC,SAAS,CAAC;YACb,OAAO,EAAE,CAAC,GAAG;YACb,gBAAgB,EAAE,EAAE;YACpB,YAAY,EAAE,EAAE;YAChB,OAAO,EAAE,EAAE;YACX,UAAU,EAAE,EAAE;SACf,CAAC,CACH,CAAC;QACF,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,CAAC,GAAG,oBAAoB,CAC5B,IAAI,CAAC,SAAS,CAAC;YACb,OAAO,EAAE,GAAG;YACZ,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;SACxC,CAAC,CACH,CAAC;QACF,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,CAAC,GAAG,oBAAoB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;QACnD,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1B,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC5B,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Long-form → multi-shorts orchestrator.
3
+ *
4
+ * The "1 hour podcast → 10 shorts" pipeline. Slides a window across a
5
+ * transcript, asks the LLM to nominate up to 3 candidate clips inside
6
+ * each window, then `score_clip`'s every nominee, dedups overlaps, and
7
+ * returns the top-N.
8
+ *
9
+ * Pure helpers + the LLM proposal step live here so the tool wrapper
10
+ * can stay thin. The ranking + dedup pass is plain TS — easy to unit
11
+ * test without the network.
12
+ */
13
+ import type { Transcript } from "./whisper.js";
14
+ export interface ViralCandidate {
15
+ startSec: number;
16
+ endSec: number;
17
+ hookLine: string;
18
+ suggestedTitle: string;
19
+ suggestedCaption: string;
20
+ why: string;
21
+ }
22
+ export interface ScoredCandidate extends ViralCandidate {
23
+ score: number;
24
+ hook: number;
25
+ flow: number;
26
+ engagement: number;
27
+ trend: number;
28
+ durationSec: number;
29
+ }
30
+ export interface WindowSpec {
31
+ startSec: number;
32
+ endSec: number;
33
+ /** Transcript text within this window, already extracted. */
34
+ text: string;
35
+ }
36
+ /**
37
+ * Slice the transcript into overlapping windows. Each window covers
38
+ * `windowSec` seconds with `overlapSec` spillover into the next so a
39
+ * clip straddling a boundary can still be proposed by at least one
40
+ * window. The dedup step later collapses duplicates.
41
+ *
42
+ * Pure — exported for unit testing.
43
+ */
44
+ export declare function buildSlidingWindows(t: Transcript, windowSec: number, overlapSec: number): WindowSpec[];
45
+ /**
46
+ * Dedup overlapping candidates: when two candidates overlap > 50% (of
47
+ * the smaller), keep the higher-scoring one. Stable / sort-agnostic —
48
+ * the function sorts internally before walking.
49
+ *
50
+ * Pure — exported for testing.
51
+ */
52
+ export declare function dedupCandidates(cands: ScoredCandidate[]): ScoredCandidate[];
53
+ /**
54
+ * Clamp candidate ranges to [0, totalSec] and to the requested duration
55
+ * range. Drops candidates that fall outside or are degenerate.
56
+ */
57
+ export declare function normalizeCandidates(cands: ViralCandidate[], totalSec: number, minSec: number, maxSec: number): ViralCandidate[];
58
+ export interface ProposeOptions {
59
+ apiKey?: string;
60
+ model?: string;
61
+ durationRange: [number, number];
62
+ signal?: AbortSignal;
63
+ }
64
+ export declare function proposeCandidates(window: WindowSpec, opts: ProposeOptions): Promise<ViralCandidate[]>;
65
+ /**
66
+ * Tolerant parser. Drops malformed entries silently (we'd rather skip a
67
+ * bad candidate than blow up the whole window).
68
+ */
69
+ export declare function parseProposalResponse(content: string): ViralCandidate[];
70
+ //# sourceMappingURL=viral-moments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viral-moments.d.ts","sourceRoot":"","sources":["../../src/core/viral-moments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,UAAU,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,UAAU,EAAE,CAiBd;AAWD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE,CAS3E;AAeD;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,cAAc,EAAE,EACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,cAAc,EAAE,CAYlB;AAiBD,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,cAAc,EAAE,CAAC,CAqC3B;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CA+BvE"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Long-form → multi-shorts orchestrator.
3
+ *
4
+ * The "1 hour podcast → 10 shorts" pipeline. Slides a window across a
5
+ * transcript, asks the LLM to nominate up to 3 candidate clips inside
6
+ * each window, then `score_clip`'s every nominee, dedups overlaps, and
7
+ * returns the top-N.
8
+ *
9
+ * Pure helpers + the LLM proposal step live here so the tool wrapper
10
+ * can stay thin. The ranking + dedup pass is plain TS — easy to unit
11
+ * test without the network.
12
+ */
13
+ import { resolveApiKey } from "./auth/api-keys.js";
14
+ /**
15
+ * Slice the transcript into overlapping windows. Each window covers
16
+ * `windowSec` seconds with `overlapSec` spillover into the next so a
17
+ * clip straddling a boundary can still be proposed by at least one
18
+ * window. The dedup step later collapses duplicates.
19
+ *
20
+ * Pure — exported for unit testing.
21
+ */
22
+ export function buildSlidingWindows(t, windowSec, overlapSec) {
23
+ if (windowSec <= 0)
24
+ throw new Error("windowSec must be > 0");
25
+ if (overlapSec < 0 || overlapSec >= windowSec) {
26
+ throw new Error("overlapSec must be in [0, windowSec)");
27
+ }
28
+ const total = t.durationSec;
29
+ if (!Number.isFinite(total) || total <= 0)
30
+ return [];
31
+ const step = windowSec - overlapSec;
32
+ const out = [];
33
+ for (let s = 0; s < total; s += step) {
34
+ const e = Math.min(total, s + windowSec);
35
+ const text = collectText(t, s, e);
36
+ if (text)
37
+ out.push({ startSec: s, endSec: e, text });
38
+ if (e >= total)
39
+ break;
40
+ }
41
+ return out;
42
+ }
43
+ function collectText(t, startSec, endSec) {
44
+ const parts = [];
45
+ for (const seg of t.segments) {
46
+ if (seg.end <= startSec || seg.start >= endSec)
47
+ continue;
48
+ parts.push(seg.text.trim());
49
+ }
50
+ return parts.join(" ").trim();
51
+ }
52
+ /**
53
+ * Dedup overlapping candidates: when two candidates overlap > 50% (of
54
+ * the smaller), keep the higher-scoring one. Stable / sort-agnostic —
55
+ * the function sorts internally before walking.
56
+ *
57
+ * Pure — exported for testing.
58
+ */
59
+ export function dedupCandidates(cands) {
60
+ // Sort by score desc; for each, drop subsequent ones that overlap > 50%.
61
+ const sorted = [...cands].sort((a, b) => b.score - a.score);
62
+ const kept = [];
63
+ for (const c of sorted) {
64
+ const conflict = kept.some((k) => overlapFraction(c, k) > 0.5);
65
+ if (!conflict)
66
+ kept.push(c);
67
+ }
68
+ return kept;
69
+ }
70
+ function overlapFraction(a, b) {
71
+ const lo = Math.max(a.startSec, b.startSec);
72
+ const hi = Math.min(a.endSec, b.endSec);
73
+ const overlap = Math.max(0, hi - lo);
74
+ if (overlap === 0)
75
+ return 0;
76
+ const minLen = Math.min(a.endSec - a.startSec, b.endSec - b.startSec);
77
+ if (minLen <= 0)
78
+ return 0;
79
+ return overlap / minLen;
80
+ }
81
+ /**
82
+ * Clamp candidate ranges to [0, totalSec] and to the requested duration
83
+ * range. Drops candidates that fall outside or are degenerate.
84
+ */
85
+ export function normalizeCandidates(cands, totalSec, minSec, maxSec) {
86
+ const out = [];
87
+ for (const c of cands) {
88
+ const s = Math.max(0, Math.min(totalSec, c.startSec));
89
+ let e = Math.max(0, Math.min(totalSec, c.endSec));
90
+ if (e <= s)
91
+ continue;
92
+ const dur = e - s;
93
+ if (dur < minSec)
94
+ continue;
95
+ if (dur > maxSec)
96
+ e = s + maxSec;
97
+ out.push({ ...c, startSec: s, endSec: e });
98
+ }
99
+ return out;
100
+ }
101
+ // ── LLM proposal step ───────────────────────────────────────
102
+ const PROPOSAL_SYSTEM = `You are a short-form-video selector. Find up to 3 distinct moments inside the given transcript window that would make great standalone Shorts / Reels / TikToks.
103
+
104
+ Each candidate must satisfy:
105
+ - duration is within the requested range (the user will tell you the seconds bounds)
106
+ - hookLine = the FIRST SENTENCE of the clip (verbatim from transcript)
107
+ - suggestedTitle ≤ 60 chars, hook-driven (a question, a claim, or a number)
108
+ - suggestedCaption ≤ 220 chars, social-ready (hook + payoff tease, ≤2 hashtags)
109
+ - why ≤ 140 chars, names what makes this moment work
110
+
111
+ Return JSON: {"candidates":[{"startSec":number,"endSec":number,"hookLine":string,"suggestedTitle":string,"suggestedCaption":string,"why":string}, ...]}
112
+
113
+ Return at most 3 candidates. If nothing in the window is strong enough, return {"candidates":[]}. Be ruthless — bad clips waste compute downstream.`;
114
+ export async function proposeCandidates(window, opts) {
115
+ const apiKey = opts.apiKey ?? resolveApiKey("OPENAI_API_KEY", "openai");
116
+ if (!apiKey)
117
+ throw new Error("OPENAI_API_KEY required for find_viral_moments.");
118
+ const model = opts.model ?? "gpt-4o-mini";
119
+ const [minSec, maxSec] = opts.durationRange;
120
+ const userText = `Window: ${window.startSec.toFixed(2)}s → ${window.endSec.toFixed(2)}s.\n` +
121
+ `Duration range: ${minSec}s..${maxSec}s.\n\n` +
122
+ `TRANSCRIPT:\n${window.text}`;
123
+ const body = {
124
+ model,
125
+ messages: [
126
+ { role: "system", content: PROPOSAL_SYSTEM },
127
+ { role: "user", content: userText },
128
+ ],
129
+ response_format: { type: "json_object" },
130
+ temperature: 0,
131
+ };
132
+ const res = await fetch("https://api.openai.com/v1/chat/completions", {
133
+ method: "POST",
134
+ headers: {
135
+ Authorization: `Bearer ${apiKey}`,
136
+ "Content-Type": "application/json",
137
+ },
138
+ body: JSON.stringify(body),
139
+ signal: opts.signal,
140
+ });
141
+ if (!res.ok) {
142
+ const text = await res.text().catch(() => "");
143
+ throw new Error(`OpenAI HTTP ${res.status}: ${text.slice(0, 200)}`);
144
+ }
145
+ const data = (await res.json());
146
+ const content = data.choices[0]?.message?.content ?? "";
147
+ return parseProposalResponse(content);
148
+ }
149
+ /**
150
+ * Tolerant parser. Drops malformed entries silently (we'd rather skip a
151
+ * bad candidate than blow up the whole window).
152
+ */
153
+ export function parseProposalResponse(content) {
154
+ const start = content.indexOf("{");
155
+ const end = content.lastIndexOf("}");
156
+ if (start < 0 || end < start)
157
+ return [];
158
+ let obj;
159
+ try {
160
+ obj = JSON.parse(content.slice(start, end + 1));
161
+ }
162
+ catch {
163
+ return [];
164
+ }
165
+ if (typeof obj !== "object" || obj === null)
166
+ return [];
167
+ const cands = obj.candidates;
168
+ if (!Array.isArray(cands))
169
+ return [];
170
+ const out = [];
171
+ for (const c of cands) {
172
+ if (typeof c !== "object" || c === null)
173
+ continue;
174
+ const r = c;
175
+ const startSec = Number(r.startSec);
176
+ const endSec = Number(r.endSec);
177
+ if (!Number.isFinite(startSec) || !Number.isFinite(endSec))
178
+ continue;
179
+ if (endSec <= startSec)
180
+ continue;
181
+ out.push({
182
+ startSec,
183
+ endSec,
184
+ hookLine: String(r.hookLine ?? "").slice(0, 240),
185
+ suggestedTitle: String(r.suggestedTitle ?? "").slice(0, 80),
186
+ suggestedCaption: String(r.suggestedCaption ?? "").slice(0, 280),
187
+ why: String(r.why ?? "").slice(0, 200),
188
+ });
189
+ }
190
+ return out.slice(0, 3);
191
+ }
192
+ //# sourceMappingURL=viral-moments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viral-moments.js","sourceRoot":"","sources":["../../src/core/viral-moments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=viral-moments.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viral-moments.test.d.ts","sourceRoot":"","sources":["../../src/core/viral-moments.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,153 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildSlidingWindows, dedupCandidates, normalizeCandidates, parseProposalResponse, } from "./viral-moments.js";
3
+ describe("buildSlidingWindows", () => {
4
+ it("emits non-overlapping windows when overlap=0", () => {
5
+ const t = {
6
+ language: "en",
7
+ durationSec: 100,
8
+ segments: [
9
+ { start: 0, end: 50, text: "first half" },
10
+ { start: 50, end: 100, text: "second half" },
11
+ ],
12
+ };
13
+ const windows = buildSlidingWindows(t, 50, 0);
14
+ expect(windows).toHaveLength(2);
15
+ expect(windows[0].startSec).toBe(0);
16
+ expect(windows[0].endSec).toBe(50);
17
+ expect(windows[1].startSec).toBe(50);
18
+ });
19
+ it("emits overlapping windows when overlap>0", () => {
20
+ const t = {
21
+ language: "en",
22
+ durationSec: 120,
23
+ segments: [{ start: 0, end: 120, text: "all" }],
24
+ };
25
+ const windows = buildSlidingWindows(t, 60, 30);
26
+ // step=30 → starts at 0, 30, 60, 90; last window clipped to 120
27
+ expect(windows.length).toBeGreaterThanOrEqual(3);
28
+ });
29
+ it("returns [] for zero-duration transcripts", () => {
30
+ expect(buildSlidingWindows({ language: "en", durationSec: 0, segments: [] }, 60, 0)).toEqual([]);
31
+ });
32
+ it("rejects invalid args", () => {
33
+ const t = { language: "en", durationSec: 100, segments: [] };
34
+ expect(() => buildSlidingWindows(t, 0, 0)).toThrow();
35
+ expect(() => buildSlidingWindows(t, 30, 30)).toThrow();
36
+ expect(() => buildSlidingWindows(t, 30, 40)).toThrow();
37
+ });
38
+ });
39
+ describe("dedupCandidates", () => {
40
+ const make = (startSec, endSec, score) => ({
41
+ startSec,
42
+ endSec,
43
+ durationSec: endSec - startSec,
44
+ score,
45
+ hook: 0,
46
+ flow: 0,
47
+ engagement: 0,
48
+ trend: 0,
49
+ hookLine: "",
50
+ suggestedTitle: "",
51
+ suggestedCaption: "",
52
+ why: "",
53
+ });
54
+ it("keeps the higher-scored of two heavy overlaps", () => {
55
+ const out = dedupCandidates([make(0, 30, 70), make(5, 32, 80)]);
56
+ expect(out).toHaveLength(1);
57
+ expect(out[0].score).toBe(80);
58
+ });
59
+ it("keeps non-overlapping candidates", () => {
60
+ const out = dedupCandidates([make(0, 30, 70), make(60, 90, 65)]);
61
+ expect(out).toHaveLength(2);
62
+ });
63
+ it("keeps both when overlap is <= 50%", () => {
64
+ // a: 0-30, b: 25-55, overlap=5, min length=30, fraction=5/30 ≈ 0.17 → keep both
65
+ const out = dedupCandidates([make(0, 30, 70), make(25, 55, 65)]);
66
+ expect(out).toHaveLength(2);
67
+ });
68
+ it("returns sorted desc by score", () => {
69
+ const out = dedupCandidates([make(0, 30, 70), make(60, 90, 90), make(120, 150, 50)]);
70
+ expect(out.map((c) => c.score)).toEqual([90, 70, 50]);
71
+ });
72
+ });
73
+ describe("normalizeCandidates", () => {
74
+ it("clamps to [0, total] and drops too-short", () => {
75
+ const out = normalizeCandidates([
76
+ {
77
+ startSec: -5,
78
+ endSec: 25,
79
+ hookLine: "",
80
+ suggestedTitle: "",
81
+ suggestedCaption: "",
82
+ why: "",
83
+ },
84
+ {
85
+ startSec: 50,
86
+ endSec: 51,
87
+ hookLine: "",
88
+ suggestedTitle: "",
89
+ suggestedCaption: "",
90
+ why: "",
91
+ },
92
+ ], 100, 20, 60);
93
+ expect(out).toHaveLength(1);
94
+ expect(out[0].startSec).toBe(0);
95
+ expect(out[0].endSec).toBe(25);
96
+ });
97
+ it("trims overlong clips to maxSec", () => {
98
+ const out = normalizeCandidates([
99
+ {
100
+ startSec: 10,
101
+ endSec: 200,
102
+ hookLine: "",
103
+ suggestedTitle: "",
104
+ suggestedCaption: "",
105
+ why: "",
106
+ },
107
+ ], 300, 20, 60);
108
+ expect(out[0].endSec).toBe(70);
109
+ });
110
+ });
111
+ describe("parseProposalResponse", () => {
112
+ it("parses well-formed proposals", () => {
113
+ const r = parseProposalResponse(JSON.stringify({
114
+ candidates: [
115
+ {
116
+ startSec: 10,
117
+ endSec: 40,
118
+ hookLine: "let me tell you something",
119
+ suggestedTitle: "Why X",
120
+ suggestedCaption: "Watch this",
121
+ why: "tight payoff",
122
+ },
123
+ ],
124
+ }));
125
+ expect(r).toHaveLength(1);
126
+ expect(r[0].startSec).toBe(10);
127
+ expect(r[0].suggestedTitle).toBe("Why X");
128
+ });
129
+ it("drops malformed entries silently", () => {
130
+ const r = parseProposalResponse(JSON.stringify({
131
+ candidates: [
132
+ { startSec: "bad", endSec: 30 },
133
+ { startSec: 10, endSec: 5 }, // end <= start
134
+ { startSec: 0, endSec: 10, hookLine: "ok" },
135
+ ],
136
+ }));
137
+ expect(r).toHaveLength(1);
138
+ expect(r[0].hookLine).toBe("ok");
139
+ });
140
+ it("returns [] on absent JSON", () => {
141
+ expect(parseProposalResponse("plain prose")).toEqual([]);
142
+ });
143
+ it("caps to 3 candidates", () => {
144
+ const r = parseProposalResponse(JSON.stringify({
145
+ candidates: Array.from({ length: 10 }, (_, i) => ({
146
+ startSec: i * 10,
147
+ endSec: i * 10 + 5,
148
+ })),
149
+ }));
150
+ expect(r).toHaveLength(3);
151
+ });
152
+ });
153
+ //# sourceMappingURL=viral-moments.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viral-moments.test.js","sourceRoot":"","sources":["../../src/core/viral-moments.test.ts"],"names":[],"mappings":"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"}