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,129 @@
1
+ import { z } from 'zod';
2
+ import { queryRows, sqliteBoolean } from './store-rows.js';
3
+ /**
4
+ * Comments on disk.
5
+ *
6
+ * Upserted on the comment id, never INSERT OR REPLACE: replace deletes and
7
+ * reinserts, which changes the rowid the external-content FTS index is keyed
8
+ * on. Upsert keeps it and fires the update trigger, so a re-harvest costs
9
+ * network and never data.
10
+ */
11
+ export const COMMENT_COMMIT_BATCH = 2_000;
12
+ const UPSERT = `
13
+ INSERT INTO comment (
14
+ comment_id, video_id, parent_id, text, like_count, author, author_id, author_url,
15
+ author_thumbnail, author_is_uploader, author_is_verified, is_pinned, is_favorited,
16
+ time_text, published_at, harvested_at
17
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
18
+ ON CONFLICT(comment_id) DO UPDATE SET
19
+ text = excluded.text,
20
+ like_count = excluded.like_count,
21
+ is_pinned = excluded.is_pinned,
22
+ is_favorited = excluded.is_favorited,
23
+ harvested_at = excluded.harvested_at`;
24
+ const commentRowSchema = z.object({
25
+ comment_id: z.string(),
26
+ video_id: z.string(),
27
+ parent_id: z.string().nullable(),
28
+ text: z.string(),
29
+ like_count: z.number(),
30
+ author: z.string(),
31
+ author_id: z.string().nullable(),
32
+ author_is_uploader: sqliteBoolean,
33
+ author_is_verified: sqliteBoolean,
34
+ is_pinned: sqliteBoolean,
35
+ is_favorited: sqliteBoolean,
36
+ published_at: z.number().nullable(),
37
+ });
38
+ function bind(comment, videoId, now) {
39
+ return [
40
+ comment.id,
41
+ videoId,
42
+ comment.parentId,
43
+ comment.text,
44
+ comment.likeCount,
45
+ comment.author,
46
+ comment.authorId ?? null,
47
+ comment.authorUrl ?? null,
48
+ comment.authorThumbnailUrl ?? null,
49
+ Number(comment.authorIsUploader),
50
+ Number(comment.authorIsVerified),
51
+ Number(comment.isPinned),
52
+ Number(comment.isFavorited),
53
+ comment.timeText ?? null,
54
+ comment.timestamp ?? null,
55
+ now,
56
+ ];
57
+ }
58
+ /** Writes threads and their replies. The caller owns the transaction. */
59
+ export function saveThreads(database, videoId, threads, now = Date.now()) {
60
+ const statement = database.prepare(UPSERT);
61
+ let written = 0;
62
+ for (const thread of threads) {
63
+ statement.run(...bind(thread.comment, videoId, now));
64
+ written += 1;
65
+ for (const reply of thread.replies) {
66
+ statement.run(...bind(reply, videoId, now));
67
+ written += 1;
68
+ }
69
+ }
70
+ return written;
71
+ }
72
+ function buildWhere(query) {
73
+ const clauses = [];
74
+ const params = [];
75
+ if (query.videoId !== undefined) {
76
+ clauses.push('c.video_id = ?');
77
+ params.push(query.videoId);
78
+ }
79
+ if (query.channelId !== undefined) {
80
+ clauses.push('c.video_id IN (SELECT video_id FROM video WHERE channel_id = ?)');
81
+ params.push(query.channelId);
82
+ }
83
+ if (query.authorId !== undefined) {
84
+ clauses.push('(c.author_id = ? OR c.author = ?)');
85
+ params.push(query.authorId, query.authorId);
86
+ }
87
+ if (query.threadOf !== undefined) {
88
+ clauses.push('(c.comment_id = ? OR c.parent_id = ?)');
89
+ params.push(query.threadOf, query.threadOf);
90
+ }
91
+ if (query.topLevelOnly === true)
92
+ clauses.push('c.parent_id IS NULL');
93
+ if (query.minLikes !== undefined && query.minLikes > 0) {
94
+ clauses.push('c.like_count >= ?');
95
+ params.push(query.minLikes);
96
+ }
97
+ if (query.match !== undefined && query.match !== '') {
98
+ // Bound, never concatenated: `match` is caller text and FTS5 has its own
99
+ // expression syntax that must not reach the SQL parser.
100
+ clauses.push('c.rowid IN (SELECT rowid FROM comment_fts WHERE comment_fts MATCH ?)');
101
+ params.push(query.match);
102
+ }
103
+ return { sql: clauses.length === 0 ? '' : ` WHERE ${clauses.join(' AND ')}`, params };
104
+ }
105
+ const ORDER = {
106
+ likes: 'c.like_count DESC, c.comment_id',
107
+ newest: 'c.published_at DESC, c.comment_id',
108
+ oldest: 'c.published_at ASC, c.comment_id',
109
+ // Threads read in reply order; relevance needs the FTS rank, which only
110
+ // exists when a match was given, so it falls back to likes.
111
+ relevance: 'c.like_count DESC, c.comment_id',
112
+ };
113
+ export function queryComments(database, query) {
114
+ const where = buildWhere(query);
115
+ const order = query.threadOf === undefined
116
+ ? ORDER[query.order ?? 'likes']
117
+ : 'c.parent_id NULLS FIRST, c.published_at';
118
+ return queryRows(database.prepare(`SELECT c.comment_id, c.video_id, c.parent_id, c.text, c.like_count, c.author,
119
+ c.author_id, c.author_is_uploader, c.author_is_verified, c.is_pinned,
120
+ c.is_favorited, c.published_at
121
+ FROM comment c${where.sql}
122
+ ORDER BY ${order} LIMIT ? OFFSET ?`), commentRowSchema, ...where.params, query.limit ?? 25, query.offset ?? 0);
123
+ }
124
+ export function countComments(database, query) {
125
+ const where = buildWhere(query);
126
+ const rows = queryRows(database.prepare(`SELECT COUNT(*) AS n FROM comment c${where.sql}`), z.object({ n: z.number() }), ...where.params);
127
+ return rows[0]?.n ?? 0;
128
+ }
129
+ //# sourceMappingURL=comment-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-store.js","sourceRoot":"","sources":["../../src/utils/comment-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { z } from 'zod';
2
+ import type { commentRowSchema } from './youtube-schemas.js';
3
+ /**
4
+ * Rebuilding threads from yt-dlp's flat comment array.
5
+ *
6
+ * yt-dlp returns replies alongside their parents and marks each with a
7
+ * `parent` of `root` or the parent's id. The previous implementation filtered
8
+ * the replies away *after* paying to fetch them, which is the most expensive
9
+ * possible way to have no replies.
10
+ */
11
+ export type CommentRow = z.infer<typeof commentRowSchema>;
12
+ export interface ThreadedComment {
13
+ id: string;
14
+ parentId: string | null;
15
+ author: string;
16
+ authorId?: string;
17
+ authorUrl?: string;
18
+ authorThumbnailUrl?: string;
19
+ authorIsUploader: boolean;
20
+ authorIsVerified: boolean;
21
+ text: string;
22
+ likeCount: number;
23
+ isPinned: boolean;
24
+ isFavorited: boolean;
25
+ timestamp?: number;
26
+ publishedAt?: string;
27
+ timeText?: string;
28
+ }
29
+ export interface CommentThread {
30
+ comment: ThreadedComment;
31
+ replies: ThreadedComment[];
32
+ /** Replies present in THIS extraction, not YouTube's own figure. */
33
+ replyCount: number;
34
+ }
35
+ export interface ThreadedComments {
36
+ threads: CommentThread[];
37
+ rootCount: number;
38
+ replyCount: number;
39
+ /** Replies whose parent was cut by a cap. Kept, and counted, never dropped. */
40
+ orphanCount: number;
41
+ }
42
+ /**
43
+ * Groups rows into threads, in yt-dlp's own order.
44
+ *
45
+ * A reply whose parent is missing — because `max_parents` truncated the set —
46
+ * is promoted to a root rather than discarded, keeping its `parentId` so the
47
+ * gap stays visible. Silently dropping data is the bug this whole release is
48
+ * about.
49
+ */
50
+ export declare function toThreads(rows: CommentRow[]): ThreadedComments;
51
+ //# sourceMappingURL=comment-threads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-threads.d.ts","sourceRoot":"","sources":["../../src/utils/comment-threads.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D;;;;;;;GAOG;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAC;CACrB;AAiCD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,gBAAgB,CA0C9D"}
@@ -0,0 +1,76 @@
1
+ function toComment(row, index) {
2
+ const parent = row.parent ?? 'root';
3
+ const timestamp = row.timestamp ?? undefined;
4
+ // yt-dlp writes null for an absent field; normalised to undefined once here
5
+ // so every spread below is a plain `=== undefined` check.
6
+ const authorId = row.author_id ?? undefined;
7
+ const authorUrl = row.author_url ?? undefined;
8
+ const authorThumbnail = row.author_thumbnail ?? undefined;
9
+ const timeText = row._time_text ?? undefined;
10
+ return {
11
+ // yt-dlp always supplies an id; the fallback keeps a malformed row
12
+ // addressable rather than silently merging it with another.
13
+ id: row.id ?? `unknown-${String(index)}`,
14
+ parentId: parent === 'root' ? null : parent,
15
+ author: row.author ?? 'Unknown',
16
+ ...(authorId === undefined ? {} : { authorId }),
17
+ ...(authorUrl === undefined ? {} : { authorUrl }),
18
+ ...(authorThumbnail === undefined ? {} : { authorThumbnailUrl: authorThumbnail }),
19
+ authorIsUploader: row.author_is_uploader ?? false,
20
+ authorIsVerified: row.author_is_verified ?? false,
21
+ text: row.text ?? '',
22
+ likeCount: row.like_count ?? 0,
23
+ isPinned: row.is_pinned ?? false,
24
+ isFavorited: row.is_favorited ?? false,
25
+ ...(timestamp === undefined ? {} : { timestamp }),
26
+ ...(timestamp === undefined ? {} : { publishedAt: new Date(timestamp * 1000).toISOString() }),
27
+ ...(timeText === undefined ? {} : { timeText }),
28
+ };
29
+ }
30
+ /**
31
+ * Groups rows into threads, in yt-dlp's own order.
32
+ *
33
+ * A reply whose parent is missing — because `max_parents` truncated the set —
34
+ * is promoted to a root rather than discarded, keeping its `parentId` so the
35
+ * gap stays visible. Silently dropping data is the bug this whole release is
36
+ * about.
37
+ */
38
+ export function toThreads(rows) {
39
+ const comments = rows.map(toComment);
40
+ const threads = [];
41
+ const threadById = new Map();
42
+ let replyCount = 0;
43
+ let orphanCount = 0;
44
+ for (const comment of comments) {
45
+ if (comment.parentId === null) {
46
+ const thread = { comment, replies: [], replyCount: 0 };
47
+ threads.push(thread);
48
+ threadById.set(comment.id, thread);
49
+ }
50
+ }
51
+ for (const comment of comments) {
52
+ if (comment.parentId === null)
53
+ continue;
54
+ const thread = threadById.get(comment.parentId);
55
+ if (thread === undefined) {
56
+ // The parent was cut by a cap. Keep the reply as its own thread so the
57
+ // text survives, and count it so the caller knows threads are partial.
58
+ orphanCount += 1;
59
+ threads.push({ comment, replies: [], replyCount: 0 });
60
+ continue;
61
+ }
62
+ thread.replies.push(comment);
63
+ thread.replyCount += 1;
64
+ replyCount += 1;
65
+ }
66
+ for (const thread of threads) {
67
+ thread.replies.sort((left, right) => (left.timestamp ?? 0) - (right.timestamp ?? 0));
68
+ }
69
+ return {
70
+ threads,
71
+ rootCount: threads.length - orphanCount,
72
+ replyCount,
73
+ orphanCount,
74
+ };
75
+ }
76
+ //# sourceMappingURL=comment-threads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-threads.js","sourceRoot":"","sources":["../../src/utils/comment-threads.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Joining clips into one video, in the order given.
3
+ *
4
+ * The inputs are files already on this machine, typically what extract_clip wrote.
5
+ * They are re-encoded rather than copied, because clips cut at different moments or
6
+ * from different sources rarely share the exact stream parameters a copy needs.
7
+ */
8
+ export declare const MIN_CONCAT_FILES = 2;
9
+ export declare const MAX_CONCAT_FILES = 20;
10
+ export interface ConcatResult {
11
+ filePath: string;
12
+ clips: number;
13
+ bytes: number;
14
+ }
15
+ export declare function concatClips(inputs: string[], options: {
16
+ name?: string;
17
+ outputDir?: string;
18
+ }): Promise<ConcatResult>;
19
+ //# sourceMappingURL=concat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concat.d.ts","sourceRoot":"","sources":["../../src/utils/concat.ts"],"names":[],"mappings":"AAWA;;;;;;GAMG;AAEH,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAKnC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AA8DD,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,OAAO,CAAC,YAAY,CAAC,CAwEvB"}
@@ -0,0 +1,135 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, mkdtemp, rm, stat, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { safeStem } from './clips.js';
5
+ import { YouTubeError } from './errors.js';
6
+ import { runFfmpeg } from './ffmpeg.js';
7
+ import { dataDir } from './paths.js';
8
+ import { requireFfmpeg } from './preflight.js';
9
+ import { assertInsideHome, resolveOutputDir } from './validate.js';
10
+ import { TIMEOUTS } from './ytdlp.js';
11
+ /**
12
+ * Joining clips into one video, in the order given.
13
+ *
14
+ * The inputs are files already on this machine, typically what extract_clip wrote.
15
+ * They are re-encoded rather than copied, because clips cut at different moments or
16
+ * from different sources rarely share the exact stream parameters a copy needs.
17
+ */
18
+ export const MIN_CONCAT_FILES = 2;
19
+ export const MAX_CONCAT_FILES = 20;
20
+ const MONTAGES_DIR = dataDir('montages');
21
+ const NAME_HASH_LENGTH = 8;
22
+ /** A path as one entry of ffmpeg's concat list: single-quoted, with any quote escaped. */
23
+ function listEntry(path) {
24
+ return `file '${path.replace(/'/g, `'\\''`)}'`;
25
+ }
26
+ async function exists(path) {
27
+ try {
28
+ await stat(path);
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ /**
36
+ * Where the result may go.
37
+ *
38
+ * Never onto one of the clips being joined: ffmpeg would read and write the same file
39
+ * and destroy it. And never over a file the caller did not make: a name the caller
40
+ * chose can collide with anything in the folder, so an existing file is refused. A name
41
+ * this server derived from the inputs is its own, so repeating the call rewrites it.
42
+ */
43
+ async function assertSafeOutput(filePath, inputs, chosenName) {
44
+ if (inputs.includes(filePath)) {
45
+ throw new YouTubeError('INVALID_INPUT', `The result would overwrite ${filePath}, a clip being joined.`, {
46
+ nextStep: 'Choose a different name, or another outputDir.',
47
+ });
48
+ }
49
+ if (chosenName && (await exists(filePath))) {
50
+ throw new YouTubeError('INVALID_INPUT', `${filePath} already exists.`, {
51
+ nextStep: 'Choose a different name, or remove the existing file first.',
52
+ });
53
+ }
54
+ }
55
+ async function assertReadableFile(path) {
56
+ let isFile;
57
+ try {
58
+ isFile = (await stat(path)).isFile();
59
+ }
60
+ catch (error) {
61
+ throw new YouTubeError('NOT_FOUND', `There is no file at ${path}.`, {
62
+ nextStep: 'Pass the paths extract_clip returned, in the order you want them.',
63
+ cause: error,
64
+ });
65
+ }
66
+ if (!isFile) {
67
+ throw new YouTubeError('INVALID_INPUT', `${path} is not a file.`, {
68
+ nextStep: 'Pass paths to video files, not folders.',
69
+ });
70
+ }
71
+ }
72
+ export async function concatClips(inputs, options) {
73
+ if (inputs.length < MIN_CONCAT_FILES || inputs.length > MAX_CONCAT_FILES) {
74
+ throw new YouTubeError('INVALID_INPUT', `Join between ${String(MIN_CONCAT_FILES)} and ${String(MAX_CONCAT_FILES)} clips at a time.`, { nextStep: 'Join in stages: concatenate groups, then concatenate the results.' });
75
+ }
76
+ await requireFfmpeg('Joining clips');
77
+ const targetDir = resolveOutputDir(options.outputDir, MONTAGES_DIR);
78
+ const paths = inputs.map((input) => assertInsideHome(input, 'clip'));
79
+ for (const path of paths)
80
+ await assertReadableFile(path);
81
+ const hash = createHash('sha256')
82
+ .update(paths.join('\n'))
83
+ .digest('hex')
84
+ .slice(0, NAME_HASH_LENGTH);
85
+ const stem = options.name === undefined ? `montage-${hash}` : safeStem(options.name, `montage-${hash}`);
86
+ const filePath = join(targetDir, `${stem}.mp4`);
87
+ await assertSafeOutput(filePath, paths, options.name !== undefined);
88
+ const resultExisted = await exists(filePath);
89
+ await mkdir(targetDir, { recursive: true });
90
+ // One folder per call: two calls joining the same clips must not share, and then
91
+ // delete, each other's list.
92
+ const workDir = await mkdtemp(join(targetDir, '.concat-'));
93
+ const listPath = join(workDir, 'list.txt');
94
+ await writeFile(listPath, `${paths.map(listEntry).join('\n')}\n`, 'utf-8');
95
+ try {
96
+ await runFfmpeg([
97
+ // The list is the only thing ffmpeg reads, and it names files only.
98
+ '-protocol_whitelist',
99
+ 'file',
100
+ '-f',
101
+ 'concat',
102
+ // Paths in the list are absolute and already checked above.
103
+ '-safe',
104
+ '0',
105
+ '-i',
106
+ listPath,
107
+ '-c:v',
108
+ 'libx264',
109
+ '-c:a',
110
+ 'aac',
111
+ '-movflags',
112
+ '+faststart',
113
+ '-y',
114
+ filePath,
115
+ ], {
116
+ failure: 'ffmpeg could not join those clips.',
117
+ nextStep: 'Check that every file is a playable video, or call check_health.',
118
+ // Joining re-encodes, so it takes as long as the clips do; the client's
119
+ // cancellation is the lever, as for a download.
120
+ timeoutMs: TIMEOUTS.download,
121
+ });
122
+ }
123
+ catch (error) {
124
+ // Only a file this call began is removed: before it, nothing existed at that path
125
+ // unless the name was derived, and a derived name is this server's own.
126
+ if (!resultExisted)
127
+ await rm(filePath, { force: true });
128
+ throw error;
129
+ }
130
+ finally {
131
+ await rm(workDir, { recursive: true, force: true });
132
+ }
133
+ return { filePath, clips: paths.length, bytes: (await stat(filePath)).size };
134
+ }
135
+ //# sourceMappingURL=concat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concat.js","sourceRoot":"","sources":["../../src/utils/concat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAClC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAEnC,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AACzC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAQ3B,0FAA0F;AAC1F,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AACjD,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,IAAY;IAChC,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,gBAAgB,CAC7B,QAAgB,EAChB,MAAgB,EAChB,UAAmB;IAEnB,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,8BAA8B,QAAQ,wBAAwB,EAC9D;YACE,QAAQ,EAAE,gDAAgD;SAC3D,CACF,CAAC;IACJ,CAAC;IACD,IAAI,UAAU,IAAI,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,YAAY,CAAC,eAAe,EAAE,GAAG,QAAQ,kBAAkB,EAAE;YACrE,QAAQ,EAAE,6DAA6D;SACxE,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,IAAY;IAC5C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,YAAY,CAAC,WAAW,EAAE,uBAAuB,IAAI,GAAG,EAAE;YAClE,QAAQ,EAAE,mEAAmE;YAC7E,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,YAAY,CAAC,eAAe,EAAE,GAAG,IAAI,iBAAiB,EAAE;YAChE,QAAQ,EAAE,yCAAyC;SACpD,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAgB,EAChB,OAA8C;IAE9C,IAAI,MAAM,CAAC,MAAM,GAAG,gBAAgB,IAAI,MAAM,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QACzE,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,gBAAgB,MAAM,CAAC,gBAAgB,CAAC,QAAQ,MAAM,CAAC,gBAAgB,CAAC,mBAAmB,EAC3F,EAAE,QAAQ,EAAE,mEAAmE,EAAE,CAClF,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,CAAC,eAAe,CAAC,CAAC;IACrC,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAEpE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IACrE,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,MAAM,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAEzD,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC;SAC9B,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;SACxB,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;IAC9B,MAAM,IAAI,GACR,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,IAAI,EAAE,CAAC,CAAC;IAC7F,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC;IAChD,MAAM,gBAAgB,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;IACpE,MAAM,aAAa,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;IAE7C,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,iFAAiF;IACjF,6BAA6B;IAC7B,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAC3C,MAAM,SAAS,CAAC,QAAQ,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAE3E,IAAI,CAAC;QACH,MAAM,SAAS,CACb;YACE,oEAAoE;YACpE,qBAAqB;YACrB,MAAM;YACN,IAAI;YACJ,QAAQ;YACR,4DAA4D;YAC5D,OAAO;YACP,GAAG;YACH,IAAI;YACJ,QAAQ;YACR,MAAM;YACN,SAAS;YACT,MAAM;YACN,KAAK;YACL,WAAW;YACX,YAAY;YACZ,IAAI;YACJ,QAAQ;SACT,EACD;YACE,OAAO,EAAE,oCAAoC;YAC7C,QAAQ,EAAE,kEAAkE;YAC5E,wEAAwE;YACxE,gDAAgD;YAChD,SAAS,EAAE,QAAQ,CAAC,QAAQ;SAC7B,CACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,kFAAkF;QAClF,wEAAwE;QACxE,IAAI,CAAC,aAAa;YAAE,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC/E,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { FramePlanRequest } from './frame-plan.js';
2
+ /**
3
+ * A whole video's worth of looking in one picture.
4
+ *
5
+ * A model reading a video does not need every frame; it needs to see the shape of
6
+ * it. Tiling a couple of dozen small stills into one image is a single inline
7
+ * block instead of two dozen, and the order of the cells is the order of the
8
+ * timestamps returned beside it, so no text has to be burned into the pixels.
9
+ */
10
+ /** Each cell's width; four columns come to about 1300px, sharp but still a single block. */
11
+ export declare const SHEET_TILE_WIDTH = 320;
12
+ export declare const MAX_SHEET_FRAMES = 24;
13
+ export declare const DEFAULT_SHEET_COLUMNS = 4;
14
+ export interface ContactSheet {
15
+ bytes: Buffer;
16
+ columns: number;
17
+ rows: number;
18
+ }
19
+ export interface VideoContactSheet extends ContactSheet {
20
+ videoId: string;
21
+ title: string;
22
+ duration: number;
23
+ /** One per cell, left to right then top to bottom. */
24
+ timestamps: number[];
25
+ }
26
+ export declare function buildContactSheet(frames: Buffer[], columns: number): Promise<ContactSheet>;
27
+ export declare function captureContactSheet(video: string, request: FramePlanRequest, columns: number): Promise<VideoContactSheet>;
28
+ //# sourceMappingURL=contact-sheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contact-sheet.d.ts","sourceRoot":"","sources":["../../src/utils/contact-sheet.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAIxD;;;;;;;GAOG;AAEH,4FAA4F;AAC5F,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAIvC,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAkB,SAAQ,YAAY;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAqChG;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,iBAAiB,CAAC,CAkB5B"}
@@ -0,0 +1,65 @@
1
+ import { YouTubeError } from './errors.js';
2
+ import { FFMPEG_JPEG_QUALITY, runFfmpeg } from './ffmpeg.js';
3
+ import { captureFrames } from './frames.js';
4
+ import { TIMEOUTS } from './ytdlp.js';
5
+ /**
6
+ * A whole video's worth of looking in one picture.
7
+ *
8
+ * A model reading a video does not need every frame; it needs to see the shape of
9
+ * it. Tiling a couple of dozen small stills into one image is a single inline
10
+ * block instead of two dozen, and the order of the cells is the order of the
11
+ * timestamps returned beside it, so no text has to be burned into the pixels.
12
+ */
13
+ /** Each cell's width; four columns come to about 1300px, sharp but still a single block. */
14
+ export const SHEET_TILE_WIDTH = 320;
15
+ export const MAX_SHEET_FRAMES = 24;
16
+ export const DEFAULT_SHEET_COLUMNS = 4;
17
+ const TILE_GAP = 4;
18
+ export async function buildContactSheet(frames, columns) {
19
+ if (frames.length === 0) {
20
+ throw new YouTubeError('INVALID_INPUT', 'A contact sheet needs at least one frame.');
21
+ }
22
+ // A grid wider than the frames it holds is only empty margin.
23
+ const width = Math.max(1, Math.min(columns, frames.length));
24
+ const rows = Math.ceil(frames.length / width);
25
+ const failure = 'ffmpeg could not assemble the contact sheet.';
26
+ const nextStep = 'Try fewer frames, or check that ffmpeg is working.';
27
+ const { stdout } = await runFfmpeg([
28
+ '-f',
29
+ 'image2pipe',
30
+ '-c:v',
31
+ 'mjpeg',
32
+ '-i',
33
+ 'pipe:0',
34
+ '-vf',
35
+ `tile=${String(width)}x${String(rows)}:margin=${String(TILE_GAP)}:padding=${String(TILE_GAP)}:color=black`,
36
+ '-frames:v',
37
+ '1',
38
+ '-q:v',
39
+ FFMPEG_JPEG_QUALITY,
40
+ '-f',
41
+ 'image2pipe',
42
+ '-c:v',
43
+ 'mjpeg',
44
+ 'pipe:1',
45
+ ], { failure, nextStep, timeoutMs: TIMEOUTS.transcript, input: Buffer.concat(frames) });
46
+ if (stdout.byteLength === 0)
47
+ throw new YouTubeError('YTDLP_FAILED', failure, { nextStep });
48
+ return { bytes: stdout, columns: width, rows };
49
+ }
50
+ export async function captureContactSheet(video, request, columns) {
51
+ const batch = await captureFrames(video, request, {
52
+ width: SHEET_TILE_WIDTH,
53
+ maxFrames: MAX_SHEET_FRAMES,
54
+ label: 'Contact sheet',
55
+ });
56
+ const sheet = await buildContactSheet(batch.frames.map((frame) => frame.bytes), columns);
57
+ return {
58
+ ...sheet,
59
+ videoId: batch.videoId,
60
+ title: batch.title,
61
+ duration: batch.duration,
62
+ timestamps: batch.frames.map((frame) => frame.timestamp),
63
+ };
64
+ }
65
+ //# sourceMappingURL=contact-sheet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contact-sheet.js","sourceRoot":"","sources":["../../src/utils/contact-sheet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC;;;;;;;GAOG;AAEH,4FAA4F;AAC5F,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AACpC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEvC,MAAM,QAAQ,GAAG,CAAC,CAAC;AAgBnB,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAgB,EAAE,OAAe;IACvE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,YAAY,CAAC,eAAe,EAAE,2CAA2C,CAAC,CAAC;IACvF,CAAC;IAED,8DAA8D;IAC9D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAG,8CAA8C,CAAC;IAC/D,MAAM,QAAQ,GAAG,oDAAoD,CAAC;IAEtE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAChC;QACE,IAAI;QACJ,YAAY;QACZ,MAAM;QACN,OAAO;QACP,IAAI;QACJ,QAAQ;QACR,KAAK;QACL,QAAQ,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,MAAM,CAAC,QAAQ,CAAC,YAAY,MAAM,CAAC,QAAQ,CAAC,cAAc;QAC1G,WAAW;QACX,GAAG;QACH,MAAM;QACN,mBAAmB;QACnB,IAAI;QACJ,YAAY;QACZ,MAAM;QACN,OAAO;QACP,QAAQ;KACT,EACD,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CACpF,CAAC;IAEF,IAAI,MAAM,CAAC,UAAU,KAAK,CAAC;QAAE,MAAM,IAAI,YAAY,CAAC,cAAc,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC3F,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAa,EACb,OAAyB,EACzB,OAAe;IAEf,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE;QAChD,KAAK,EAAE,gBAAgB;QACvB,SAAS,EAAE,gBAAgB;QAC3B,KAAK,EAAE,eAAe;KACvB,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,MAAM,iBAAiB,CACnC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EACxC,OAAO,CACR,CAAC;IAEF,OAAO;QACL,GAAG,KAAK;QACR,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC;KACzD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Coverage } from '../harvest-schemas.js';
2
+ export declare function renderCoverage(coverage: Coverage): string;
3
+ //# sourceMappingURL=coverage-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-text.d.ts","sourceRoot":"","sources":["../../src/utils/coverage-text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AA8CtD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CA6BzD"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The human- and model-readable form of a receipt.
3
+ *
4
+ * `structuredContent` carries the machine-readable truth, but a model reads
5
+ * the text block, and that is where an overclaim actually happens. So every
6
+ * extraction tool prepends this, from one function, rather than each writing
7
+ * its own wording and drifting.
8
+ *
9
+ * The incomplete form says three things on purpose: the numbers, why it
10
+ * stopped, and an explicit instruction not to describe it as everything. The
11
+ * last one reads as belt-and-braces next to the `complete: false` field, and
12
+ * it is: the field is for programs, the sentence is for the reader.
13
+ */
14
+ const UNITS = {
15
+ 'channel-catalog': 'videos',
16
+ 'video-details': 'videos',
17
+ 'video-comments': 'comments',
18
+ };
19
+ const WHY = {
20
+ COMPLETE: 'complete',
21
+ CAP_REACHED: 'stopped by the requested cap',
22
+ BUDGET_SPENT: 'stopped when the time budget ran out',
23
+ RATE_LIMITED: 'stopped because YouTube throttled this client',
24
+ CANCELLED: 'cancelled by the client',
25
+ TIMEOUT: 'timed out',
26
+ SOURCE_REFUSED: 'YouTube refused this content to the current session',
27
+ COMMENTS_DISABLED: 'comments are turned off for this video',
28
+ SOURCE_SILENT: 'YouTube states no total, so exhaustion cannot be proven',
29
+ PARTIAL_ERROR: 'some items failed',
30
+ NOT_ATTEMPTED: 'not attempted yet',
31
+ };
32
+ function count(value) {
33
+ return value.toLocaleString('en-US');
34
+ }
35
+ function ratio(coverage) {
36
+ if (coverage.expected === undefined || coverage.expected === 0)
37
+ return '';
38
+ const percent = (coverage.have / coverage.expected) * 100;
39
+ return ` (${percent < 0.1 && percent > 0 ? '<0.1' : percent.toFixed(1)}%)`;
40
+ }
41
+ export function renderCoverage(coverage) {
42
+ const unit = UNITS[coverage.scope];
43
+ const of = coverage.expected === undefined
44
+ ? `${count(coverage.have)} ${unit}`
45
+ : `${count(coverage.have)} of ${coverage.expectedSource === 'youtube:comment_count' ? 'about ' : ''}${count(coverage.expected)} ${unit}${ratio(coverage)}`;
46
+ const lines = [`Coverage: ${of} for ${coverage.targetId}.`];
47
+ if (coverage.complete) {
48
+ lines.push(`COMPLETE — ${WHY[coverage.reason]}, harvested ${coverage.harvestedAt}.`);
49
+ if (coverage.staleAfter !== undefined) {
50
+ lines.push(`Re-check after ${coverage.staleAfter} (this can change upstream).`);
51
+ }
52
+ return lines.join('\n');
53
+ }
54
+ const bias = coverage.sortApplied === 'top' && coverage.limitApplied !== undefined
55
+ ? ', sorted by "top" (a biased prefix, not a random sample)'
56
+ : '';
57
+ lines.push(`INCOMPLETE — ${WHY[coverage.reason]}${bias}.`, `Do not describe this as the full ${unit === 'comments' ? 'comment history' : 'video list'}.`);
58
+ if (coverage.note !== undefined)
59
+ lines.push(coverage.note);
60
+ return lines.join('\n');
61
+ }
62
+ //# sourceMappingURL=coverage-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-text.js","sourceRoot":"","sources":["../../src/utils/coverage-text.ts"],"names":[],"mappings":"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"}