@kenkaiiii/gg-editor 0.7.0 → 0.7.2

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