youtube-knowledge-mcp 2.2.0 → 3.0.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 (407) hide show
  1. package/README.md +126 -36
  2. package/dist/brain-schemas.d.ts +10 -0
  3. package/dist/brain-schemas.d.ts.map +1 -1
  4. package/dist/harvest-schemas.d.ts +75 -0
  5. package/dist/harvest-schemas.d.ts.map +1 -0
  6. package/dist/harvest-schemas.js +111 -0
  7. package/dist/harvest-schemas.js.map +1 -0
  8. package/dist/registry/brain-tools.d.ts.map +1 -1
  9. package/dist/registry/brain-tools.js +16 -0
  10. package/dist/registry/brain-tools.js.map +1 -1
  11. package/dist/registry/channel-tools.d.ts.map +1 -1
  12. package/dist/registry/channel-tools.js +16 -0
  13. package/dist/registry/channel-tools.js.map +1 -1
  14. package/dist/registry/harvest-tools.d.ts +10 -0
  15. package/dist/registry/harvest-tools.d.ts.map +1 -0
  16. package/dist/registry/harvest-tools.js +109 -0
  17. package/dist/registry/harvest-tools.js.map +1 -0
  18. package/dist/registry/health-tools.d.ts.map +1 -1
  19. package/dist/registry/health-tools.js +18 -0
  20. package/dist/registry/health-tools.js.map +1 -1
  21. package/dist/registry/index.d.ts.map +1 -1
  22. package/dist/registry/index.js +6 -0
  23. package/dist/registry/index.js.map +1 -1
  24. package/dist/registry/media-tools.d.ts.map +1 -1
  25. package/dist/registry/media-tools.js +31 -0
  26. package/dist/registry/media-tools.js.map +1 -1
  27. package/dist/registry/store-tools.d.ts +9 -0
  28. package/dist/registry/store-tools.d.ts.map +1 -0
  29. package/dist/registry/store-tools.js +59 -0
  30. package/dist/registry/store-tools.js.map +1 -0
  31. package/dist/registry/transcript-tools.d.ts +4 -1
  32. package/dist/registry/transcript-tools.d.ts.map +1 -1
  33. package/dist/registry/transcript-tools.js +20 -1
  34. package/dist/registry/transcript-tools.js.map +1 -1
  35. package/dist/registry/video-tools.d.ts.map +1 -1
  36. package/dist/registry/video-tools.js +49 -1
  37. package/dist/registry/video-tools.js.map +1 -1
  38. package/dist/registry/vision-tools.d.ts +7 -0
  39. package/dist/registry/vision-tools.d.ts.map +1 -0
  40. package/dist/registry/vision-tools.js +55 -0
  41. package/dist/registry/vision-tools.js.map +1 -0
  42. package/dist/schemas.d.ts +245 -0
  43. package/dist/schemas.d.ts.map +1 -1
  44. package/dist/schemas.js +58 -0
  45. package/dist/schemas.js.map +1 -1
  46. package/dist/thumbnail-schemas.d.ts +38 -28
  47. package/dist/thumbnail-schemas.d.ts.map +1 -1
  48. package/dist/tools/ask-brain.js +1 -1
  49. package/dist/tools/ask-brain.js.map +1 -1
  50. package/dist/tools/ask-brains.d.ts +66 -0
  51. package/dist/tools/ask-brains.d.ts.map +1 -0
  52. package/dist/tools/ask-brains.js +111 -0
  53. package/dist/tools/ask-brains.js.map +1 -0
  54. package/dist/tools/brain-info.js +1 -1
  55. package/dist/tools/brain-info.js.map +1 -1
  56. package/dist/tools/check-health.d.ts +75 -6
  57. package/dist/tools/check-health.d.ts.map +1 -1
  58. package/dist/tools/check-health.js +114 -6
  59. package/dist/tools/check-health.js.map +1 -1
  60. package/dist/tools/check-new-uploads.d.ts +55 -0
  61. package/dist/tools/check-new-uploads.d.ts.map +1 -0
  62. package/dist/tools/check-new-uploads.js +74 -0
  63. package/dist/tools/check-new-uploads.js.map +1 -0
  64. package/dist/tools/clips.d.ts +15 -0
  65. package/dist/tools/clips.d.ts.map +1 -1
  66. package/dist/tools/clips.js +5 -5
  67. package/dist/tools/clips.js.map +1 -1
  68. package/dist/tools/detect-scenes.d.ts +38 -0
  69. package/dist/tools/detect-scenes.d.ts.map +1 -0
  70. package/dist/tools/detect-scenes.js +66 -0
  71. package/dist/tools/detect-scenes.js.map +1 -0
  72. package/dist/tools/export-data.d.ts +85 -0
  73. package/dist/tools/export-data.d.ts.map +1 -0
  74. package/dist/tools/export-data.js +99 -0
  75. package/dist/tools/export-data.js.map +1 -0
  76. package/dist/tools/fetch-channel-thumbnails.d.ts +8 -8
  77. package/dist/tools/fetch-videos.d.ts +15 -4
  78. package/dist/tools/fetch-videos.d.ts.map +1 -1
  79. package/dist/tools/fetch-videos.js +63 -22
  80. package/dist/tools/fetch-videos.js.map +1 -1
  81. package/dist/tools/frames-subtitles.d.ts.map +1 -1
  82. package/dist/tools/frames-subtitles.js +16 -8
  83. package/dist/tools/frames-subtitles.js.map +1 -1
  84. package/dist/tools/frames.d.ts +73 -0
  85. package/dist/tools/frames.d.ts.map +1 -0
  86. package/dist/tools/frames.js +136 -0
  87. package/dist/tools/frames.js.map +1 -0
  88. package/dist/tools/get-channel-info.d.ts +2 -0
  89. package/dist/tools/get-channel-info.d.ts.map +1 -1
  90. package/dist/tools/get-channel-info.js +4 -0
  91. package/dist/tools/get-channel-info.js.map +1 -1
  92. package/dist/tools/get-comments.d.ts +238 -6
  93. package/dist/tools/get-comments.d.ts.map +1 -1
  94. package/dist/tools/get-comments.js +85 -21
  95. package/dist/tools/get-comments.js.map +1 -1
  96. package/dist/tools/get-coverage.d.ts +122 -0
  97. package/dist/tools/get-coverage.d.ts.map +1 -0
  98. package/dist/tools/get-coverage.js +105 -0
  99. package/dist/tools/get-coverage.js.map +1 -0
  100. package/dist/tools/get-sponsor-segments.d.ts +49 -0
  101. package/dist/tools/get-sponsor-segments.d.ts.map +1 -0
  102. package/dist/tools/get-sponsor-segments.js +61 -0
  103. package/dist/tools/get-sponsor-segments.js.map +1 -0
  104. package/dist/tools/get-thumbnail.d.ts.map +1 -1
  105. package/dist/tools/get-thumbnail.js +2 -1
  106. package/dist/tools/get-thumbnail.js.map +1 -1
  107. package/dist/tools/get-transcript.d.ts +1 -0
  108. package/dist/tools/get-transcript.d.ts.map +1 -1
  109. package/dist/tools/get-transcript.js +11 -4
  110. package/dist/tools/get-transcript.js.map +1 -1
  111. package/dist/tools/get-video-details.d.ts +118 -0
  112. package/dist/tools/get-video-details.d.ts.map +1 -0
  113. package/dist/tools/get-video-details.js +147 -0
  114. package/dist/tools/get-video-details.js.map +1 -0
  115. package/dist/tools/gif-concat.d.ts +49 -0
  116. package/dist/tools/gif-concat.d.ts.map +1 -0
  117. package/dist/tools/gif-concat.js +96 -0
  118. package/dist/tools/gif-concat.js.map +1 -0
  119. package/dist/tools/harvest-channel.d.ts +92 -0
  120. package/dist/tools/harvest-channel.d.ts.map +1 -0
  121. package/dist/tools/harvest-channel.js +62 -0
  122. package/dist/tools/harvest-channel.js.map +1 -0
  123. package/dist/tools/harvest-comments.d.ts +81 -0
  124. package/dist/tools/harvest-comments.d.ts.map +1 -0
  125. package/dist/tools/harvest-comments.js +115 -0
  126. package/dist/tools/harvest-comments.js.map +1 -0
  127. package/dist/tools/harvest-details.d.ts +84 -0
  128. package/dist/tools/harvest-details.d.ts.map +1 -0
  129. package/dist/tools/harvest-details.js +71 -0
  130. package/dist/tools/harvest-details.js.map +1 -0
  131. package/dist/tools/library.js +1 -1
  132. package/dist/tools/library.js.map +1 -1
  133. package/dist/tools/list-captions.d.ts +17 -0
  134. package/dist/tools/list-captions.d.ts.map +1 -0
  135. package/dist/tools/list-captions.js +60 -0
  136. package/dist/tools/list-captions.js.map +1 -0
  137. package/dist/tools/list-channel-playlists.d.ts +30 -0
  138. package/dist/tools/list-channel-playlists.d.ts.map +1 -0
  139. package/dist/tools/list-channel-playlists.js +44 -0
  140. package/dist/tools/list-channel-playlists.js.map +1 -0
  141. package/dist/tools/list-channel-thumbnails.d.ts +10 -10
  142. package/dist/tools/list-channel-thumbnails.js +1 -1
  143. package/dist/tools/list-channel-thumbnails.js.map +1 -1
  144. package/dist/tools/list-library.js +1 -1
  145. package/dist/tools/list-library.js.map +1 -1
  146. package/dist/tools/prune-harvest.d.ts +59 -0
  147. package/dist/tools/prune-harvest.d.ts.map +1 -0
  148. package/dist/tools/prune-harvest.js +108 -0
  149. package/dist/tools/prune-harvest.js.map +1 -0
  150. package/dist/tools/query-comments.d.ts +125 -0
  151. package/dist/tools/query-comments.d.ts.map +1 -0
  152. package/dist/tools/query-comments.js +106 -0
  153. package/dist/tools/query-comments.js.map +1 -0
  154. package/dist/tools/query-videos.d.ts +118 -0
  155. package/dist/tools/query-videos.d.ts.map +1 -0
  156. package/dist/tools/query-videos.js +97 -0
  157. package/dist/tools/query-videos.js.map +1 -0
  158. package/dist/tools/repair-store.d.ts +34 -0
  159. package/dist/tools/repair-store.d.ts.map +1 -0
  160. package/dist/tools/repair-store.js +58 -0
  161. package/dist/tools/repair-store.js.map +1 -0
  162. package/dist/tools/search-channels.d.ts +6 -0
  163. package/dist/tools/search-channels.d.ts.map +1 -1
  164. package/dist/tools/search-channels.js +5 -1
  165. package/dist/tools/search-channels.js.map +1 -1
  166. package/dist/tools/search-videos.js +1 -1
  167. package/dist/tools/search-videos.js.map +1 -1
  168. package/dist/tools/transcribe-video.d.ts +28 -0
  169. package/dist/tools/transcribe-video.d.ts.map +1 -0
  170. package/dist/tools/transcribe-video.js +52 -0
  171. package/dist/tools/transcribe-video.js.map +1 -0
  172. package/dist/utils/brain-fusion.d.ts +24 -0
  173. package/dist/utils/brain-fusion.d.ts.map +1 -0
  174. package/dist/utils/brain-fusion.js +18 -0
  175. package/dist/utils/brain-fusion.js.map +1 -0
  176. package/dist/utils/channel-playlists.d.ts +21 -0
  177. package/dist/utils/channel-playlists.d.ts.map +1 -0
  178. package/dist/utils/channel-playlists.js +56 -0
  179. package/dist/utils/channel-playlists.js.map +1 -0
  180. package/dist/utils/clips.d.ts.map +1 -1
  181. package/dist/utils/clips.js +3 -17
  182. package/dist/utils/clips.js.map +1 -1
  183. package/dist/utils/comment-store.d.ts +71 -0
  184. package/dist/utils/comment-store.d.ts.map +1 -0
  185. package/dist/utils/comment-store.js +129 -0
  186. package/dist/utils/comment-store.js.map +1 -0
  187. package/dist/utils/comment-threads.d.ts +51 -0
  188. package/dist/utils/comment-threads.d.ts.map +1 -0
  189. package/dist/utils/comment-threads.js +76 -0
  190. package/dist/utils/comment-threads.js.map +1 -0
  191. package/dist/utils/concat.d.ts +19 -0
  192. package/dist/utils/concat.d.ts.map +1 -0
  193. package/dist/utils/concat.js +135 -0
  194. package/dist/utils/concat.js.map +1 -0
  195. package/dist/utils/contact-sheet.d.ts +28 -0
  196. package/dist/utils/contact-sheet.d.ts.map +1 -0
  197. package/dist/utils/contact-sheet.js +65 -0
  198. package/dist/utils/contact-sheet.js.map +1 -0
  199. package/dist/utils/coverage-text.d.ts +3 -0
  200. package/dist/utils/coverage-text.d.ts.map +1 -0
  201. package/dist/utils/coverage-text.js +62 -0
  202. package/dist/utils/coverage-text.js.map +1 -0
  203. package/dist/utils/coverage.d.ts +57 -0
  204. package/dist/utils/coverage.d.ts.map +1 -0
  205. package/dist/utils/coverage.js +142 -0
  206. package/dist/utils/coverage.js.map +1 -0
  207. package/dist/utils/detail-batch.d.ts +25 -0
  208. package/dist/utils/detail-batch.d.ts.map +1 -0
  209. package/dist/utils/detail-batch.js +58 -0
  210. package/dist/utils/detail-batch.js.map +1 -0
  211. package/dist/utils/errors.d.ts +1 -1
  212. package/dist/utils/errors.d.ts.map +1 -1
  213. package/dist/utils/errors.js +19 -1
  214. package/dist/utils/errors.js.map +1 -1
  215. package/dist/utils/export-data.d.ts +41 -0
  216. package/dist/utils/export-data.d.ts.map +1 -0
  217. package/dist/utils/export-data.js +114 -0
  218. package/dist/utils/export-data.js.map +1 -0
  219. package/dist/utils/export-format.d.ts +23 -0
  220. package/dist/utils/export-format.d.ts.map +1 -0
  221. package/dist/utils/export-format.js +59 -0
  222. package/dist/utils/export-format.js.map +1 -0
  223. package/dist/utils/export-load.d.ts +28 -0
  224. package/dist/utils/export-load.d.ts.map +1 -0
  225. package/dist/utils/export-load.js +70 -0
  226. package/dist/utils/export-load.js.map +1 -0
  227. package/dist/utils/export-rows.d.ts +35 -0
  228. package/dist/utils/export-rows.d.ts.map +1 -0
  229. package/dist/utils/export-rows.js +69 -0
  230. package/dist/utils/export-rows.js.map +1 -0
  231. package/dist/utils/ffmpeg.d.ts +26 -0
  232. package/dist/utils/ffmpeg.d.ts.map +1 -0
  233. package/dist/utils/ffmpeg.js +28 -0
  234. package/dist/utils/ffmpeg.js.map +1 -0
  235. package/dist/utils/format.d.ts +23 -1
  236. package/dist/utils/format.d.ts.map +1 -1
  237. package/dist/utils/format.js +5 -1
  238. package/dist/utils/format.js.map +1 -1
  239. package/dist/utils/frame-plan.d.ts +24 -0
  240. package/dist/utils/frame-plan.d.ts.map +1 -0
  241. package/dist/utils/frame-plan.js +58 -0
  242. package/dist/utils/frame-plan.js.map +1 -0
  243. package/dist/utils/frames.d.ts +32 -0
  244. package/dist/utils/frames.d.ts.map +1 -0
  245. package/dist/utils/frames.js +60 -0
  246. package/dist/utils/frames.js.map +1 -0
  247. package/dist/utils/gif.d.ts +31 -0
  248. package/dist/utils/gif.d.ts.map +1 -0
  249. package/dist/utils/gif.js +77 -0
  250. package/dist/utils/gif.js.map +1 -0
  251. package/dist/utils/harvest-catalog.d.ts +54 -0
  252. package/dist/utils/harvest-catalog.d.ts.map +1 -0
  253. package/dist/utils/harvest-catalog.js +173 -0
  254. package/dist/utils/harvest-catalog.js.map +1 -0
  255. package/dist/utils/harvest-details.d.ts +30 -0
  256. package/dist/utils/harvest-details.d.ts.map +1 -0
  257. package/dist/utils/harvest-details.js +98 -0
  258. package/dist/utils/harvest-details.js.map +1 -0
  259. package/dist/utils/harvest-lock.d.ts +3 -0
  260. package/dist/utils/harvest-lock.d.ts.map +1 -0
  261. package/dist/utils/harvest-lock.js +26 -0
  262. package/dist/utils/harvest-lock.js.map +1 -0
  263. package/dist/utils/harvest-receipts.d.ts +14 -0
  264. package/dist/utils/harvest-receipts.d.ts.map +1 -0
  265. package/dist/utils/harvest-receipts.js +110 -0
  266. package/dist/utils/harvest-receipts.js.map +1 -0
  267. package/dist/utils/listing-cursor.d.ts +31 -0
  268. package/dist/utils/listing-cursor.d.ts.map +1 -0
  269. package/dist/utils/listing-cursor.js +39 -0
  270. package/dist/utils/listing-cursor.js.map +1 -0
  271. package/dist/utils/local-transcription.d.ts +14 -0
  272. package/dist/utils/local-transcription.d.ts.map +1 -0
  273. package/dist/utils/local-transcription.js +85 -0
  274. package/dist/utils/local-transcription.js.map +1 -0
  275. package/dist/utils/moment.d.ts +24 -0
  276. package/dist/utils/moment.d.ts.map +1 -0
  277. package/dist/utils/moment.js +22 -0
  278. package/dist/utils/moment.js.map +1 -0
  279. package/dist/utils/most-replayed.d.ts +15 -0
  280. package/dist/utils/most-replayed.d.ts.map +1 -0
  281. package/dist/utils/most-replayed.js +25 -0
  282. package/dist/utils/most-replayed.js.map +1 -0
  283. package/dist/utils/pot-preflight.d.ts +53 -0
  284. package/dist/utils/pot-preflight.d.ts.map +1 -0
  285. package/dist/utils/pot-preflight.js +146 -0
  286. package/dist/utils/pot-preflight.js.map +1 -0
  287. package/dist/utils/scenes.d.ts +39 -0
  288. package/dist/utils/scenes.d.ts.map +1 -0
  289. package/dist/utils/scenes.js +83 -0
  290. package/dist/utils/scenes.js.map +1 -0
  291. package/dist/utils/seconds.d.ts +5 -0
  292. package/dist/utils/seconds.d.ts.map +1 -0
  293. package/dist/utils/seconds.js +7 -0
  294. package/dist/utils/seconds.js.map +1 -0
  295. package/dist/utils/sleep.d.ts +9 -0
  296. package/dist/utils/sleep.d.ts.map +1 -0
  297. package/dist/utils/sleep.js +25 -0
  298. package/dist/utils/sleep.js.map +1 -0
  299. package/dist/utils/sponsorblock.d.ts +24 -0
  300. package/dist/utils/sponsorblock.d.ts.map +1 -0
  301. package/dist/utils/sponsorblock.js +153 -0
  302. package/dist/utils/sponsorblock.js.map +1 -0
  303. package/dist/utils/store-health.d.ts +37 -0
  304. package/dist/utils/store-health.d.ts.map +1 -0
  305. package/dist/utils/store-health.js +92 -0
  306. package/dist/utils/store-health.js.map +1 -0
  307. package/dist/utils/store-migrations.d.ts +33 -0
  308. package/dist/utils/store-migrations.d.ts.map +1 -0
  309. package/dist/utils/store-migrations.js +41 -0
  310. package/dist/utils/store-migrations.js.map +1 -0
  311. package/dist/utils/store-paths.d.ts +33 -0
  312. package/dist/utils/store-paths.d.ts.map +1 -0
  313. package/dist/utils/store-paths.js +48 -0
  314. package/dist/utils/store-paths.js.map +1 -0
  315. package/dist/utils/store-rows.d.ts +22 -0
  316. package/dist/utils/store-rows.d.ts.map +1 -0
  317. package/dist/utils/store-rows.js +62 -0
  318. package/dist/utils/store-rows.js.map +1 -0
  319. package/dist/utils/store-schema.d.ts +16 -0
  320. package/dist/utils/store-schema.d.ts.map +1 -0
  321. package/dist/utils/store-schema.js +168 -0
  322. package/dist/utils/store-schema.js.map +1 -0
  323. package/dist/utils/store.d.ts +34 -0
  324. package/dist/utils/store.d.ts.map +1 -0
  325. package/dist/utils/store.js +157 -0
  326. package/dist/utils/store.js.map +1 -0
  327. package/dist/utils/transcript-cache.d.ts +5 -1
  328. package/dist/utils/transcript-cache.d.ts.map +1 -1
  329. package/dist/utils/transcript-cache.js +31 -50
  330. package/dist/utils/transcript-cache.js.map +1 -1
  331. package/dist/utils/transcript-files.d.ts +24 -0
  332. package/dist/utils/transcript-files.d.ts.map +1 -0
  333. package/dist/utils/transcript-files.js +61 -0
  334. package/dist/utils/transcript-files.js.map +1 -0
  335. package/dist/utils/transcript.d.ts +3 -0
  336. package/dist/utils/transcript.d.ts.map +1 -1
  337. package/dist/utils/transcript.js +2 -0
  338. package/dist/utils/transcript.js.map +1 -1
  339. package/dist/utils/upload-check.d.ts +38 -0
  340. package/dist/utils/upload-check.d.ts.map +1 -0
  341. package/dist/utils/upload-check.js +93 -0
  342. package/dist/utils/upload-check.js.map +1 -0
  343. package/dist/utils/validate.d.ts +2 -0
  344. package/dist/utils/validate.d.ts.map +1 -1
  345. package/dist/utils/validate.js +4 -0
  346. package/dist/utils/validate.js.map +1 -1
  347. package/dist/utils/video-detail-store.d.ts +47 -0
  348. package/dist/utils/video-detail-store.d.ts.map +1 -0
  349. package/dist/utils/video-detail-store.js +89 -0
  350. package/dist/utils/video-detail-store.js.map +1 -0
  351. package/dist/utils/video-stats-cache.d.ts +16 -0
  352. package/dist/utils/video-stats-cache.d.ts.map +1 -0
  353. package/dist/utils/video-stats-cache.js +81 -0
  354. package/dist/utils/video-stats-cache.js.map +1 -0
  355. package/dist/utils/video-stats-schema.d.ts +187 -0
  356. package/dist/utils/video-stats-schema.d.ts.map +1 -0
  357. package/dist/utils/video-stats-schema.js +50 -0
  358. package/dist/utils/video-stats-schema.js.map +1 -0
  359. package/dist/utils/video-stats-storage.d.ts +16 -0
  360. package/dist/utils/video-stats-storage.d.ts.map +1 -0
  361. package/dist/utils/video-stats-storage.js +37 -0
  362. package/dist/utils/video-stats-storage.js.map +1 -0
  363. package/dist/utils/video-stats.d.ts +82 -0
  364. package/dist/utils/video-stats.d.ts.map +1 -0
  365. package/dist/utils/video-stats.js +108 -0
  366. package/dist/utils/video-stats.js.map +1 -0
  367. package/dist/utils/video-store.d.ts +69 -0
  368. package/dist/utils/video-store.d.ts.map +1 -0
  369. package/dist/utils/video-store.js +77 -0
  370. package/dist/utils/video-store.js.map +1 -0
  371. package/dist/utils/video-stream.d.ts +21 -0
  372. package/dist/utils/video-stream.d.ts.map +1 -0
  373. package/dist/utils/video-stream.js +36 -0
  374. package/dist/utils/video-stream.js.map +1 -0
  375. package/dist/utils/whisper.d.ts +39 -0
  376. package/dist/utils/whisper.d.ts.map +1 -0
  377. package/dist/utils/whisper.js +137 -0
  378. package/dist/utils/whisper.js.map +1 -0
  379. package/dist/utils/youtube-channel.d.ts +13 -2
  380. package/dist/utils/youtube-channel.d.ts.map +1 -1
  381. package/dist/utils/youtube-channel.js +39 -3
  382. package/dist/utils/youtube-channel.js.map +1 -1
  383. package/dist/utils/youtube-schemas.d.ts +104 -0
  384. package/dist/utils/youtube-schemas.d.ts.map +1 -1
  385. package/dist/utils/youtube-schemas.js +32 -0
  386. package/dist/utils/youtube-schemas.js.map +1 -1
  387. package/dist/utils/youtube-video.d.ts +22 -1
  388. package/dist/utils/youtube-video.d.ts.map +1 -1
  389. package/dist/utils/youtube-video.js +40 -28
  390. package/dist/utils/youtube-video.js.map +1 -1
  391. package/dist/utils/ytdlp-limiter.d.ts +3 -0
  392. package/dist/utils/ytdlp-limiter.d.ts.map +1 -1
  393. package/dist/utils/ytdlp-limiter.js +19 -3
  394. package/dist/utils/ytdlp-limiter.js.map +1 -1
  395. package/dist/utils/ytdlp-pacer.d.ts +64 -0
  396. package/dist/utils/ytdlp-pacer.d.ts.map +1 -0
  397. package/dist/utils/ytdlp-pacer.js +165 -0
  398. package/dist/utils/ytdlp-pacer.js.map +1 -0
  399. package/dist/utils/ytdlp-stream.d.ts +32 -0
  400. package/dist/utils/ytdlp-stream.d.ts.map +1 -0
  401. package/dist/utils/ytdlp-stream.js +61 -0
  402. package/dist/utils/ytdlp-stream.js.map +1 -0
  403. package/dist/utils/ytdlp.d.ts +10 -0
  404. package/dist/utils/ytdlp.d.ts.map +1 -1
  405. package/dist/utils/ytdlp.js +36 -25
  406. package/dist/utils/ytdlp.js.map +1 -1
  407. package/package.json +10 -4
@@ -0,0 +1,69 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import { z } from 'zod';
3
+ /**
4
+ * Reading the catalogued videos back.
5
+ *
6
+ * `upload_date` is NULL, never '', for a video only ever seen in a flat
7
+ * listing — flat entries carry no publication date. A date filter therefore
8
+ * excludes those rows rather than silently treating them as ancient.
9
+ */
10
+ declare const videoRowSchema: z.ZodObject<{
11
+ video_id: z.ZodString;
12
+ channel_id: z.ZodNullable<z.ZodString>;
13
+ title: z.ZodString;
14
+ duration_s: z.ZodNullable<z.ZodNumber>;
15
+ upload_date: z.ZodNullable<z.ZodString>;
16
+ view_count: z.ZodNullable<z.ZodNumber>;
17
+ comment_count: z.ZodNullable<z.ZodNumber>;
18
+ live_status: z.ZodNullable<z.ZodString>;
19
+ thumbnail_url: z.ZodNullable<z.ZodString>;
20
+ tab: z.ZodNullable<z.ZodString>;
21
+ also_in_tabs: z.ZodString;
22
+ detail_at: z.ZodNullable<z.ZodNumber>;
23
+ stored_comments: z.ZodNumber;
24
+ }, "strip", z.ZodTypeAny, {
25
+ title: string;
26
+ upload_date: string | null;
27
+ view_count: number | null;
28
+ comment_count: number | null;
29
+ live_status: string | null;
30
+ channel_id: string | null;
31
+ tab: string | null;
32
+ video_id: string;
33
+ duration_s: number | null;
34
+ thumbnail_url: string | null;
35
+ also_in_tabs: string;
36
+ detail_at: number | null;
37
+ stored_comments: number;
38
+ }, {
39
+ title: string;
40
+ upload_date: string | null;
41
+ view_count: number | null;
42
+ comment_count: number | null;
43
+ live_status: string | null;
44
+ channel_id: string | null;
45
+ tab: string | null;
46
+ video_id: string;
47
+ duration_s: number | null;
48
+ thumbnail_url: string | null;
49
+ also_in_tabs: string;
50
+ detail_at: number | null;
51
+ stored_comments: number;
52
+ }>;
53
+ export type StoredVideo = z.infer<typeof videoRowSchema>;
54
+ export interface VideoQuery {
55
+ channelId?: string;
56
+ match?: string;
57
+ since?: string;
58
+ until?: string;
59
+ minDurationSeconds?: number;
60
+ detailedOnly?: boolean;
61
+ withComments?: boolean;
62
+ order?: 'newest' | 'oldest' | 'views' | 'comments' | 'catalog';
63
+ limit?: number;
64
+ offset?: number;
65
+ }
66
+ export declare function queryVideos(database: DatabaseSync, query: VideoQuery): StoredVideo[];
67
+ export declare function countVideos(database: DatabaseSync, query: VideoQuery): number;
68
+ export {};
69
+ //# sourceMappingURL=video-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-store.d.ts","sourceRoot":"","sources":["../../src/utils/video-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;GAMG;AAEH,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAclB,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEzD,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAiDD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,GAAG,WAAW,EAAE,CAYpF;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CAQ7E"}
@@ -0,0 +1,77 @@
1
+ import { z } from 'zod';
2
+ import { queryRows } from './store-rows.js';
3
+ /**
4
+ * Reading the catalogued videos back.
5
+ *
6
+ * `upload_date` is NULL, never '', for a video only ever seen in a flat
7
+ * listing — flat entries carry no publication date. A date filter therefore
8
+ * excludes those rows rather than silently treating them as ancient.
9
+ */
10
+ const videoRowSchema = z.object({
11
+ video_id: z.string(),
12
+ channel_id: z.string().nullable(),
13
+ title: z.string(),
14
+ duration_s: z.number().nullable(),
15
+ upload_date: z.string().nullable(),
16
+ view_count: z.number().nullable(),
17
+ comment_count: z.number().nullable(),
18
+ live_status: z.string().nullable(),
19
+ thumbnail_url: z.string().nullable(),
20
+ tab: z.string().nullable(),
21
+ also_in_tabs: z.string(),
22
+ detail_at: z.number().nullable(),
23
+ stored_comments: z.number(),
24
+ });
25
+ function buildWhere(query) {
26
+ const clauses = [];
27
+ const params = [];
28
+ if (query.channelId !== undefined) {
29
+ clauses.push('v.channel_id = ?');
30
+ params.push(query.channelId);
31
+ }
32
+ if (query.match !== undefined && query.match !== '') {
33
+ clauses.push('lower(v.title) LIKE ?');
34
+ params.push(`%${query.match.toLowerCase()}%`);
35
+ }
36
+ if (query.since !== undefined) {
37
+ clauses.push('v.upload_date IS NOT NULL AND v.upload_date >= ?');
38
+ params.push(query.since.replace(/-/g, ''));
39
+ }
40
+ if (query.until !== undefined) {
41
+ clauses.push('v.upload_date IS NOT NULL AND v.upload_date <= ?');
42
+ params.push(query.until.replace(/-/g, ''));
43
+ }
44
+ if (query.minDurationSeconds !== undefined && query.minDurationSeconds > 0) {
45
+ clauses.push('v.duration_s >= ?');
46
+ params.push(query.minDurationSeconds);
47
+ }
48
+ if (query.detailedOnly === true)
49
+ clauses.push('v.detail_at IS NOT NULL');
50
+ if (query.withComments === true) {
51
+ clauses.push('EXISTS (SELECT 1 FROM comment c WHERE c.video_id = v.video_id)');
52
+ }
53
+ return { sql: clauses.length === 0 ? '' : ` WHERE ${clauses.join(' AND ')}`, params };
54
+ }
55
+ const ORDER = {
56
+ // NULLS LAST so an un-dated video sorts as unknown rather than oldest.
57
+ newest: 'v.upload_date DESC NULLS LAST, v.video_id',
58
+ oldest: 'v.upload_date ASC NULLS LAST, v.video_id',
59
+ views: 'v.view_count DESC NULLS LAST, v.video_id',
60
+ comments: 'stored_comments DESC, v.video_id',
61
+ catalog: 'v.catalog_rank ASC NULLS LAST, v.video_id',
62
+ };
63
+ const SELECT = `
64
+ SELECT v.video_id, v.channel_id, v.title, v.duration_s, v.upload_date, v.view_count,
65
+ v.comment_count, v.live_status, v.thumbnail_url, v.tab, v.also_in_tabs, v.detail_at,
66
+ (SELECT COUNT(*) FROM comment c WHERE c.video_id = v.video_id) AS stored_comments
67
+ FROM video v`;
68
+ export function queryVideos(database, query) {
69
+ const where = buildWhere(query);
70
+ return queryRows(database.prepare(`${SELECT}${where.sql} ORDER BY ${ORDER[query.order ?? 'newest']} LIMIT ? OFFSET ?`), videoRowSchema, ...where.params, query.limit ?? 25, query.offset ?? 0);
71
+ }
72
+ export function countVideos(database, query) {
73
+ const where = buildWhere(query);
74
+ const rows = queryRows(database.prepare(`SELECT COUNT(*) AS n FROM video v${where.sql}`), z.object({ n: z.number() }), ...where.params);
75
+ return rows[0]?.n ?? 0;
76
+ }
77
+ //# sourceMappingURL=video-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-store.js","sourceRoot":"","sources":["../../src/utils/video-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Handing a video's stream to ffmpeg without downloading the file.
3
+ *
4
+ * Every tool that looks at pixels (a still, a contact sheet, scene cuts) needs the
5
+ * same two guarantees, so they live in one place: the media URL really is an https
6
+ * URL, and ffmpeg is told it may open nothing else.
7
+ */
8
+ /** Up to 1080p: enough to read small text in a still. */
9
+ export declare const DETAIL_STREAM_SELECTOR = "bestvideo[height<=1080]/best";
10
+ /** Up to 720p: plenty for frames shrunk to fit a model's context. */
11
+ export declare const PREVIEW_STREAM_SELECTOR = "bestvideo[height<=720]/best[height<=720]/best";
12
+ /** The lightest stream that still shows a cut: scene detection reads every frame. */
13
+ export declare const SCAN_STREAM_SELECTOR = "bestvideo[height<=360]/worst";
14
+ /**
15
+ * ffmpeg accepts file:, pipe:, concat: and more as inputs. The only thing yt-dlp
16
+ * should have resolved is an https media URL, and this allowlist makes ffmpeg refuse
17
+ * anything else even if it had. A per-input option, so it must precede `-i`.
18
+ */
19
+ export declare const FFMPEG_NETWORK_INPUT_ARGS: string[];
20
+ export declare function resolveStreamUrl(videoId: string, selector: string, label: string): Promise<string>;
21
+ //# sourceMappingURL=video-stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-stream.d.ts","sourceRoot":"","sources":["../../src/utils/video-stream.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AAEH,yDAAyD;AACzD,eAAO,MAAM,sBAAsB,iCAAiC,CAAC;AACrE,qEAAqE;AACrE,eAAO,MAAM,uBAAuB,kDAAkD,CAAC;AACvF,qFAAqF;AACrF,eAAO,MAAM,oBAAoB,iCAAiC,CAAC;AAEnE;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,UAAkD,CAAC;AAEzF,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CAiBjB"}
@@ -0,0 +1,36 @@
1
+ import { YouTubeError } from './errors.js';
2
+ import { runYtDlp } from './ytdlp.js';
3
+ /**
4
+ * Handing a video's stream to ffmpeg without downloading the file.
5
+ *
6
+ * Every tool that looks at pixels (a still, a contact sheet, scene cuts) needs the
7
+ * same two guarantees, so they live in one place: the media URL really is an https
8
+ * URL, and ffmpeg is told it may open nothing else.
9
+ */
10
+ /** Up to 1080p: enough to read small text in a still. */
11
+ export const DETAIL_STREAM_SELECTOR = 'bestvideo[height<=1080]/best';
12
+ /** Up to 720p: plenty for frames shrunk to fit a model's context. */
13
+ export const PREVIEW_STREAM_SELECTOR = 'bestvideo[height<=720]/best[height<=720]/best';
14
+ /** The lightest stream that still shows a cut: scene detection reads every frame. */
15
+ export const SCAN_STREAM_SELECTOR = 'bestvideo[height<=360]/worst';
16
+ /**
17
+ * ffmpeg accepts file:, pipe:, concat: and more as inputs. The only thing yt-dlp
18
+ * should have resolved is an https media URL, and this allowlist makes ffmpeg refuse
19
+ * anything else even if it had. A per-input option, so it must precede `-i`.
20
+ */
21
+ export const FFMPEG_NETWORK_INPUT_ARGS = ['-protocol_whitelist', 'https,tls,tcp,crypto'];
22
+ export async function resolveStreamUrl(videoId, selector, label) {
23
+ const mediaUrl = (await runYtDlp(['-f', selector, '--get-url', '--no-playlist'], {
24
+ label: `${label} (resolve)`,
25
+ target: `https://www.youtube.com/watch?v=${videoId}`,
26
+ }))
27
+ .trim()
28
+ .split('\n')[0];
29
+ if (!mediaUrl?.startsWith('https://')) {
30
+ throw new YouTubeError('YTDLP_FAILED', 'Could not resolve a video stream for this video.', {
31
+ nextStep: 'Check the video is playable, or call check_health.',
32
+ });
33
+ }
34
+ return mediaUrl;
35
+ }
36
+ //# sourceMappingURL=video-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-stream.js","sourceRoot":"","sources":["../../src/utils/video-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC;;;;;;GAMG;AAEH,yDAAyD;AACzD,MAAM,CAAC,MAAM,sBAAsB,GAAG,8BAA8B,CAAC;AACrE,qEAAqE;AACrE,MAAM,CAAC,MAAM,uBAAuB,GAAG,+CAA+C,CAAC;AACvF,qFAAqF;AACrF,MAAM,CAAC,MAAM,oBAAoB,GAAG,8BAA8B,CAAC;AAEnE;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,qBAAqB,EAAE,sBAAsB,CAAC,CAAC;AAEzF,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAe,EACf,QAAgB,EAChB,KAAa;IAEb,MAAM,QAAQ,GAAG,CACf,MAAM,QAAQ,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,CAAC,EAAE;QAC7D,KAAK,EAAE,GAAG,KAAK,YAAY;QAC3B,MAAM,EAAE,mCAAmC,OAAO,EAAE;KACrD,CAAC,CACH;SACE,IAAI,EAAE;SACN,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAElB,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,YAAY,CAAC,cAAc,EAAE,kDAAkD,EAAE;YACzF,QAAQ,EAAE,oDAAoD;SAC/D,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,39 @@
1
+ import type { TranscriptSegment } from './transcript.js';
2
+ /**
3
+ * Transcribing audio on this machine, with whisper.cpp.
4
+ *
5
+ * For a video with no captions this is the only way to get words out of it. It is
6
+ * strictly optional, like ffmpeg: nothing here runs unless a person asks for a local
7
+ * transcription, and no model is downloaded for them. The model is a file they point
8
+ * `YOUTUBE_MCP_WHISPER_MODEL` at, because which one to run is a trade between speed,
9
+ * disk and accuracy that this server cannot make for them.
10
+ */
11
+ /** The executable's name since whisper.cpp 1.7; earlier releases called it `main`. */
12
+ export declare const WHISPER_BINARY = "whisper-cli";
13
+ export interface WhisperStatus {
14
+ installed: boolean;
15
+ version?: string;
16
+ /** The path the person configured, whether or not a file is there. */
17
+ modelPath?: string;
18
+ modelFound: boolean;
19
+ }
20
+ export declare function probeWhisper(): Promise<WhisperStatus>;
21
+ /** The model to run, or the typed reason there is nothing to run. */
22
+ export declare function requireWhisper(operation: string): Promise<string>;
23
+ /** whisper.cpp's JSON as segments in seconds; the silence marker is dropped. */
24
+ export declare function parseWhisperJson(raw: string): TranscriptSegment[];
25
+ /**
26
+ * The language as whisper.cpp names it: the primary subtag, lower-case.
27
+ *
28
+ * Callers pass BCP-47 tags such as `pt-BR` or `zh-Hans`, and whisper.cpp knows only `pt`
29
+ * and `zh` and exits on anything else (measured against whisper.cpp 1.9.5).
30
+ */
31
+ export declare function whisperLanguage(tag: string): string;
32
+ /**
33
+ * Transcribe one audio file.
34
+ *
35
+ * The language is always explicit: left to auto-detect, a small model took English
36
+ * speech for French, and a transcript in the wrong language is worse than none.
37
+ */
38
+ export declare function runWhisper(audioPath: string, language: string, modelPath: string, outputBase: string): Promise<TranscriptSegment[]>;
39
+ //# sourceMappingURL=whisper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whisper.d.ts","sourceRoot":"","sources":["../../src/utils/whisper.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD;;;;;;;;GAQG;AAEH,sFAAsF;AACtF,eAAO,MAAM,cAAc,gBAAgB,CAAC;AAM5C,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;CACrB;AAuBD,wBAAsB,YAAY,IAAI,OAAO,CAAC,aAAa,CAAC,CAY3D;AAED,qEAAqE;AACrE,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkCvE;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA0BjE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA8B9B"}
@@ -0,0 +1,137 @@
1
+ import { execa } from 'execa';
2
+ import { readFile, rm, stat } from 'node:fs/promises';
3
+ import { z } from 'zod';
4
+ import { currentContext } from './context.js';
5
+ import { envString } from './env.js';
6
+ import { YouTubeError } from './errors.js';
7
+ import { MILLISECONDS_PER_SECOND } from './seconds.js';
8
+ /**
9
+ * Transcribing audio on this machine, with whisper.cpp.
10
+ *
11
+ * For a video with no captions this is the only way to get words out of it. It is
12
+ * strictly optional, like ffmpeg: nothing here runs unless a person asks for a local
13
+ * transcription, and no model is downloaded for them. The model is a file they point
14
+ * `YOUTUBE_MCP_WHISPER_MODEL` at, because which one to run is a trade between speed,
15
+ * disk and accuracy that this server cannot make for them.
16
+ */
17
+ /** The executable's name since whisper.cpp 1.7; earlier releases called it `main`. */
18
+ export const WHISPER_BINARY = 'whisper-cli';
19
+ const PROBE_TIMEOUT_MS = 5_000;
20
+ /** whisper.cpp writes this for a stretch with no speech; it is not part of what was said. */
21
+ const SILENCE_MARKER = '[BLANK_AUDIO]';
22
+ const whisperOutputSchema = z.object({
23
+ transcription: z.array(z.object({
24
+ offsets: z.object({ from: z.number(), to: z.number() }),
25
+ text: z.string(),
26
+ })),
27
+ });
28
+ function configuredModel() {
29
+ return envString('YOUTUBE_MCP_WHISPER_MODEL');
30
+ }
31
+ async function isFile(path) {
32
+ try {
33
+ return (await stat(path)).isFile();
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ export async function probeWhisper() {
40
+ const modelPath = configuredModel();
41
+ const modelFound = modelPath !== undefined && (await isFile(modelPath));
42
+ const model = modelPath === undefined ? {} : { modelPath };
43
+ try {
44
+ const { stdout } = await execa(WHISPER_BINARY, ['--version'], { timeout: PROBE_TIMEOUT_MS });
45
+ const version = /version:\s*(\S+)/.exec(stdout)?.[1] ?? 'unknown';
46
+ return { installed: true, version, ...model, modelFound };
47
+ }
48
+ catch {
49
+ return { installed: false, ...model, modelFound };
50
+ }
51
+ }
52
+ /** The model to run, or the typed reason there is nothing to run. */
53
+ export async function requireWhisper(operation) {
54
+ const status = await probeWhisper();
55
+ if (!status.installed) {
56
+ throw new YouTubeError('WHISPER_MISSING', `${operation} requires whisper.cpp, which is not installed.`, {
57
+ nextStep: 'Install whisper.cpp (macOS: `brew install whisper.cpp`; elsewhere build it from https://github.com/ggml-org/whisper.cpp) so that `whisper-cli` is on PATH, then call check_health.',
58
+ });
59
+ }
60
+ if (status.modelPath === undefined) {
61
+ throw new YouTubeError('WHISPER_MISSING', `${operation} needs a whisper model, and none is configured.`, {
62
+ nextStep: 'Download a ggml model file (for example ggml-base.bin from https://huggingface.co/ggerganov/whisper.cpp) and set YOUTUBE_MCP_WHISPER_MODEL to its path.',
63
+ });
64
+ }
65
+ if (!status.modelFound) {
66
+ throw new YouTubeError('WHISPER_MISSING', `The whisper model at ${status.modelPath} is not a readable file.`, { nextStep: 'Fix the path in YOUTUBE_MCP_WHISPER_MODEL, then call check_health.' });
67
+ }
68
+ return status.modelPath;
69
+ }
70
+ /** whisper.cpp's JSON as segments in seconds; the silence marker is dropped. */
71
+ export function parseWhisperJson(raw) {
72
+ let json;
73
+ try {
74
+ json = JSON.parse(raw);
75
+ }
76
+ catch (error) {
77
+ throw new YouTubeError('MALFORMED_RESPONSE', 'whisper.cpp wrote output that is not JSON.', {
78
+ cause: error,
79
+ });
80
+ }
81
+ const parsed = whisperOutputSchema.safeParse(json);
82
+ if (!parsed.success) {
83
+ throw new YouTubeError('MALFORMED_RESPONSE', 'whisper.cpp wrote JSON without a transcription.', { cause: parsed.error });
84
+ }
85
+ return parsed.data.transcription
86
+ .map(({ offsets, text }) => ({
87
+ start: offsets.from / MILLISECONDS_PER_SECOND,
88
+ end: offsets.to / MILLISECONDS_PER_SECOND,
89
+ text: text.trim(),
90
+ }))
91
+ .filter((segment) => segment.text !== '' && segment.text !== SILENCE_MARKER);
92
+ }
93
+ /**
94
+ * The language as whisper.cpp names it: the primary subtag, lower-case.
95
+ *
96
+ * Callers pass BCP-47 tags such as `pt-BR` or `zh-Hans`, and whisper.cpp knows only `pt`
97
+ * and `zh` and exits on anything else (measured against whisper.cpp 1.9.5).
98
+ */
99
+ export function whisperLanguage(tag) {
100
+ return (tag.split('-')[0] ?? tag).toLowerCase();
101
+ }
102
+ /**
103
+ * Transcribe one audio file.
104
+ *
105
+ * The language is always explicit: left to auto-detect, a small model took English
106
+ * speech for French, and a transcript in the wrong language is worse than none.
107
+ */
108
+ export async function runWhisper(audioPath, language, modelPath, outputBase) {
109
+ const outputPath = `${outputBase}.json`;
110
+ try {
111
+ await execa(WHISPER_BINARY, [
112
+ '-m',
113
+ modelPath,
114
+ '-f',
115
+ audioPath,
116
+ '-l',
117
+ whisperLanguage(language),
118
+ '-oj',
119
+ '-of',
120
+ outputBase,
121
+ '-np',
122
+ ], { cancelSignal: currentContext().signal });
123
+ return parseWhisperJson(await readFile(outputPath, 'utf-8'));
124
+ }
125
+ catch (error) {
126
+ if (error instanceof YouTubeError)
127
+ throw error;
128
+ throw new YouTubeError('YTDLP_FAILED', 'whisper.cpp could not transcribe this audio.', {
129
+ nextStep: 'Check that the model file is a valid ggml model, or call check_health.',
130
+ cause: error,
131
+ });
132
+ }
133
+ finally {
134
+ await rm(outputPath, { force: true });
135
+ }
136
+ }
137
+ //# sourceMappingURL=whisper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whisper.js","sourceRoot":"","sources":["../../src/utils/whisper.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,4 @@
1
1
  import { type ListedImage } from './flat-listing.js';
2
- /** Listings and metadata for channels and playlists. */
3
2
  export interface VideoListItem {
4
3
  id: string;
5
4
  title: string;
@@ -26,6 +25,10 @@ export interface ChannelInfo {
26
25
  avatarUrl?: string;
27
26
  /** The channel's uncropped banner, when yt-dlp listed one. */
28
27
  bannerUrl?: string;
28
+ /** Present only when the channel page says so. */
29
+ isVerified?: boolean;
30
+ /** The keywords the channel lists for itself; absent when it lists none. */
31
+ tags?: string[];
29
32
  }
30
33
  export interface PlaylistInfo {
31
34
  id: string;
@@ -38,7 +41,15 @@ export interface PlaylistInfo {
38
41
  url: string;
39
42
  description: string;
40
43
  }
41
- export declare function listVideos(urlOrChannel: string, limit?: number): Promise<VideoListItem[]>;
44
+ export declare function listVideos(urlOrChannel: string, limit?: number, start?: number): Promise<VideoListItem[]>;
45
+ /**
46
+ * How many videos a listing says it has, when it says anything.
47
+ *
48
+ * `playlist_count` is populated for playlists and routinely null for channel
49
+ * tabs, so this returns undefined rather than a guess. One extra cheap call —
50
+ * the same `--playlist-items 0` metadata read getPlaylistInfo already does.
51
+ */
52
+ export declare function playlistTotal(urlOrChannel: string): Promise<number | undefined>;
42
53
  export declare function getPlaylistInfo(playlistUrl: string): Promise<PlaylistInfo>;
43
54
  export declare function getChannelInfo(channel: string): Promise<ChannelInfo>;
44
55
  //# sourceMappingURL=youtube-channel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"youtube-channel.d.ts","sourceRoot":"","sources":["../../src/utils/youtube-channel.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAK3B,wDAAwD;AAExD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,UAAU,CAAC,EAAE,WAAW,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAkB3F;AAED,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAwBhF;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAwB1E"}
1
+ {"version":3,"file":"youtube-channel.d.ts","sourceRoot":"","sources":["../../src/utils/youtube-channel.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAS3B,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,UAAU,CAAC,EAAE,WAAW,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,UAAU,CAC9B,YAAY,EAAE,MAAM,EACpB,KAAK,SAAK,EACV,KAAK,SAAI,GACR,OAAO,CAAC,aAAa,EAAE,CAAC,CAsB1B;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAiBrF;AAED,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAwBhF;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA8B1E"}
@@ -1,17 +1,24 @@
1
+ import { z } from 'zod';
1
2
  import { formatYouTubeDate } from './format.js';
2
3
  import { TIMEOUTS, parseYtDlpJson, parseYtDlpJsonLines, runYtDlp } from './ytdlp.js';
3
4
  import { FLAT_PRINT_TEMPLATE, flatEntrySchema, toVideoListItem, } from './flat-listing.js';
4
5
  import { selectChannelImages } from './channel-images.js';
5
6
  import { channelMetaSchema, playlistMetaSchema } from './youtube-schemas.js';
6
7
  import { channelUrlFor, resolveListTarget } from './youtube-url.js';
7
- export async function listVideos(urlOrChannel, limit = 20) {
8
+ /** Listings and metadata for channels and playlists. */
9
+ const playlistCountSchema = z.object({ playlist_count: z.number().nullish() });
10
+ export async function listVideos(urlOrChannel, limit = 20, start = 1) {
8
11
  const stdout = await runYtDlp([
9
12
  '--skip-download',
10
13
  '--flat-playlist',
14
+ '--lazy-playlist',
11
15
  '--print',
12
16
  FLAT_PRINT_TEMPLATE,
13
- '--playlist-end',
14
- limit.toString(),
17
+ // `-I start:end` rather than --playlist-end so a later page can begin
18
+ // where the previous one stopped. YouTube still walks from the top to
19
+ // reach `start`, which is why the caller caches instead of paging deep.
20
+ '--playlist-items',
21
+ `${String(start)}:${String(start + limit - 1)}`,
15
22
  ], {
16
23
  label: 'fetch_videos',
17
24
  timeoutMs: TIMEOUTS.transcript,
@@ -19,6 +26,29 @@ export async function listVideos(urlOrChannel, limit = 20) {
19
26
  });
20
27
  return parseYtDlpJsonLines(stdout, flatEntrySchema).map(toVideoListItem);
21
28
  }
29
+ /**
30
+ * How many videos a listing says it has, when it says anything.
31
+ *
32
+ * `playlist_count` is populated for playlists and routinely null for channel
33
+ * tabs, so this returns undefined rather than a guess. One extra cheap call —
34
+ * the same `--playlist-items 0` metadata read getPlaylistInfo already does.
35
+ */
36
+ export async function playlistTotal(urlOrChannel) {
37
+ try {
38
+ const stdout = await runYtDlp(['--dump-single-json', '--flat-playlist', '--playlist-items', '0'], {
39
+ label: 'fetch_videos:count',
40
+ timeoutMs: TIMEOUTS.metadata,
41
+ target: resolveListTarget(urlOrChannel),
42
+ });
43
+ const parsed = parseYtDlpJson(stdout, playlistCountSchema, 'a playlist count');
44
+ return parsed.playlist_count ?? undefined;
45
+ }
46
+ catch {
47
+ // A total is a nicety; failing to get one must not fail the page. Its
48
+ // absence is already meaningful — "unknown", which is the truth here.
49
+ return undefined;
50
+ }
51
+ }
22
52
  export async function getPlaylistInfo(playlistUrl) {
23
53
  const target = resolveListTarget(playlistUrl);
24
54
  const stdout = await runYtDlp(['--dump-single-json', '--flat-playlist', '--playlist-items', '0'], {
@@ -53,6 +83,12 @@ export async function getChannelInfo(channel) {
53
83
  subscriberCount: data.channel_follower_count ?? 0,
54
84
  channelUrl: data.channel_url ?? channelUrl,
55
85
  description: data.description ?? '',
86
+ ...(data.channel_is_verified === null || data.channel_is_verified === undefined
87
+ ? {}
88
+ : { isVerified: data.channel_is_verified }),
89
+ ...(data.tags === null || data.tags === undefined || data.tags.length === 0
90
+ ? {}
91
+ : { tags: data.tags }),
56
92
  // The same listing carries the avatar and banner; they were discarded here
57
93
  // for two releases.
58
94
  ...selectChannelImages(data.thumbnails),
@@ -1 +1 @@
1
- {"version":3,"file":"youtube-channel.js","sourceRoot":"","sources":["../../src/utils/youtube-channel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"youtube-channel.js","sourceRoot":"","sources":["../../src/utils/youtube-channel.ts"],"names":[],"mappings":"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"}