youtube-knowledge-mcp 2.0.2 → 2.2.0

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 (390) hide show
  1. package/README.md +166 -33
  2. package/dist/brain-schemas.d.ts +502 -0
  3. package/dist/brain-schemas.d.ts.map +1 -0
  4. package/dist/brain-schemas.js +109 -0
  5. package/dist/brain-schemas.js.map +1 -0
  6. package/dist/http/app.d.ts +16 -0
  7. package/dist/http/app.d.ts.map +1 -0
  8. package/dist/http/app.js +176 -0
  9. package/dist/http/app.js.map +1 -0
  10. package/dist/http/auth.d.ts +38 -0
  11. package/dist/http/auth.d.ts.map +1 -0
  12. package/dist/http/auth.js +79 -0
  13. package/dist/http/auth.js.map +1 -0
  14. package/dist/http/config.d.ts +41 -0
  15. package/dist/http/config.d.ts.map +1 -0
  16. package/dist/http/config.js +65 -0
  17. package/dist/http/config.js.map +1 -0
  18. package/dist/http/rate-limiter.d.ts +24 -0
  19. package/dist/http/rate-limiter.d.ts.map +1 -0
  20. package/dist/http/rate-limiter.js +55 -0
  21. package/dist/http/rate-limiter.js.map +1 -0
  22. package/dist/http/sessions.d.ts +34 -0
  23. package/dist/http/sessions.d.ts.map +1 -0
  24. package/dist/http/sessions.js +62 -0
  25. package/dist/http/sessions.js.map +1 -0
  26. package/dist/http.d.ts +6 -48
  27. package/dist/http.d.ts.map +1 -1
  28. package/dist/http.js +35 -252
  29. package/dist/http.js.map +1 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +11 -431
  32. package/dist/index.js.map +1 -1
  33. package/dist/prompts-brain.d.ts +4 -0
  34. package/dist/prompts-brain.d.ts.map +1 -0
  35. package/dist/prompts-brain.js +48 -0
  36. package/dist/prompts-brain.js.map +1 -0
  37. package/dist/prompts-research.d.ts +4 -0
  38. package/dist/prompts-research.d.ts.map +1 -0
  39. package/dist/prompts-research.js +74 -0
  40. package/dist/prompts-research.js.map +1 -0
  41. package/dist/prompts-shared.d.ts +13 -0
  42. package/dist/prompts-shared.d.ts.map +1 -0
  43. package/dist/prompts-shared.js +7 -0
  44. package/dist/prompts-shared.js.map +1 -0
  45. package/dist/prompts.d.ts.map +1 -1
  46. package/dist/prompts.js +5 -58
  47. package/dist/prompts.js.map +1 -1
  48. package/dist/registry/brain-tools.d.ts +4 -0
  49. package/dist/registry/brain-tools.d.ts.map +1 -0
  50. package/dist/registry/brain-tools.js +104 -0
  51. package/dist/registry/brain-tools.js.map +1 -0
  52. package/dist/registry/channel-tools.d.ts +4 -0
  53. package/dist/registry/channel-tools.d.ts.map +1 -0
  54. package/dist/registry/channel-tools.js +53 -0
  55. package/dist/registry/channel-tools.js.map +1 -0
  56. package/dist/registry/health-tools.d.ts +4 -0
  57. package/dist/registry/health-tools.d.ts.map +1 -0
  58. package/dist/registry/health-tools.js +21 -0
  59. package/dist/registry/health-tools.js.map +1 -0
  60. package/dist/registry/index.d.ts +8 -0
  61. package/dist/registry/index.d.ts.map +1 -0
  62. package/dist/registry/index.js +27 -0
  63. package/dist/registry/index.js.map +1 -0
  64. package/dist/registry/library-tools.d.ts +4 -0
  65. package/dist/registry/library-tools.d.ts.map +1 -0
  66. package/dist/registry/library-tools.js +116 -0
  67. package/dist/registry/library-tools.js.map +1 -0
  68. package/dist/registry/media-tools.d.ts +4 -0
  69. package/dist/registry/media-tools.d.ts.map +1 -0
  70. package/dist/registry/media-tools.js +100 -0
  71. package/dist/registry/media-tools.js.map +1 -0
  72. package/dist/registry/thumbnail-tools.d.ts +7 -0
  73. package/dist/registry/thumbnail-tools.d.ts.map +1 -0
  74. package/dist/registry/thumbnail-tools.js +72 -0
  75. package/dist/registry/thumbnail-tools.js.map +1 -0
  76. package/dist/registry/transcript-tools.d.ts +4 -0
  77. package/dist/registry/transcript-tools.d.ts.map +1 -0
  78. package/dist/registry/transcript-tools.js +68 -0
  79. package/dist/registry/transcript-tools.js.map +1 -0
  80. package/dist/registry/types.d.ts +41 -0
  81. package/dist/registry/types.d.ts.map +1 -0
  82. package/dist/registry/types.js +17 -0
  83. package/dist/registry/types.js.map +1 -0
  84. package/dist/registry/video-tools.d.ts +4 -0
  85. package/dist/registry/video-tools.d.ts.map +1 -0
  86. package/dist/registry/video-tools.js +101 -0
  87. package/dist/registry/video-tools.js.map +1 -0
  88. package/dist/resources.d.ts.map +1 -1
  89. package/dist/resources.js +75 -6
  90. package/dist/resources.js.map +1 -1
  91. package/dist/schemas.d.ts +74 -54
  92. package/dist/schemas.d.ts.map +1 -1
  93. package/dist/schemas.js +30 -4
  94. package/dist/schemas.js.map +1 -1
  95. package/dist/thumbnail-resource.d.ts +8 -0
  96. package/dist/thumbnail-resource.d.ts.map +1 -0
  97. package/dist/thumbnail-resource.js +38 -0
  98. package/dist/thumbnail-resource.js.map +1 -0
  99. package/dist/thumbnail-schemas.d.ts +512 -0
  100. package/dist/thumbnail-schemas.d.ts.map +1 -0
  101. package/dist/thumbnail-schemas.js +95 -0
  102. package/dist/thumbnail-schemas.js.map +1 -0
  103. package/dist/tools/ask-brain.d.ts +62 -0
  104. package/dist/tools/ask-brain.d.ts.map +1 -0
  105. package/dist/tools/ask-brain.js +70 -0
  106. package/dist/tools/ask-brain.js.map +1 -0
  107. package/dist/tools/batch.d.ts +10 -10
  108. package/dist/tools/batch.js +3 -3
  109. package/dist/tools/batch.js.map +1 -1
  110. package/dist/tools/brain-info.d.ts +180 -0
  111. package/dist/tools/brain-info.d.ts.map +1 -0
  112. package/dist/tools/brain-info.js +108 -0
  113. package/dist/tools/brain-info.js.map +1 -0
  114. package/dist/tools/build-brain.d.ts +120 -0
  115. package/dist/tools/build-brain.d.ts.map +1 -0
  116. package/dist/tools/build-brain.js +123 -0
  117. package/dist/tools/build-brain.js.map +1 -0
  118. package/dist/tools/check-health.d.ts +2 -0
  119. package/dist/tools/check-health.d.ts.map +1 -1
  120. package/dist/tools/check-health.js +9 -0
  121. package/dist/tools/check-health.js.map +1 -1
  122. package/dist/tools/clips.d.ts +13 -45
  123. package/dist/tools/clips.d.ts.map +1 -1
  124. package/dist/tools/clips.js +52 -128
  125. package/dist/tools/clips.js.map +1 -1
  126. package/dist/tools/fetch-channel-thumbnails.d.ts +179 -0
  127. package/dist/tools/fetch-channel-thumbnails.d.ts.map +1 -0
  128. package/dist/tools/fetch-channel-thumbnails.js +123 -0
  129. package/dist/tools/fetch-channel-thumbnails.js.map +1 -0
  130. package/dist/tools/fetch-videos.d.ts +11 -8
  131. package/dist/tools/fetch-videos.d.ts.map +1 -1
  132. package/dist/tools/fetch-videos.js +3 -0
  133. package/dist/tools/fetch-videos.js.map +1 -1
  134. package/dist/tools/frames-subtitles.d.ts +44 -0
  135. package/dist/tools/frames-subtitles.d.ts.map +1 -0
  136. package/dist/tools/frames-subtitles.js +104 -0
  137. package/dist/tools/frames-subtitles.js.map +1 -0
  138. package/dist/tools/get-channel-info.d.ts +2 -0
  139. package/dist/tools/get-channel-info.d.ts.map +1 -1
  140. package/dist/tools/get-channel-info.js +8 -1
  141. package/dist/tools/get-channel-info.js.map +1 -1
  142. package/dist/tools/get-chapters.d.ts +2 -2
  143. package/dist/tools/get-comments.d.ts +2 -2
  144. package/dist/tools/get-playlist-info.d.ts.map +1 -1
  145. package/dist/tools/get-playlist-info.js +1 -0
  146. package/dist/tools/get-playlist-info.js.map +1 -1
  147. package/dist/tools/get-thumbnail.d.ts +38 -0
  148. package/dist/tools/get-thumbnail.d.ts.map +1 -0
  149. package/dist/tools/get-thumbnail.js +152 -0
  150. package/dist/tools/get-thumbnail.js.map +1 -0
  151. package/dist/tools/get-transcript.d.ts +2 -2
  152. package/dist/tools/get-transcript.d.ts.map +1 -1
  153. package/dist/tools/get-transcript.js +2 -14
  154. package/dist/tools/get-transcript.js.map +1 -1
  155. package/dist/tools/library.d.ts +9 -7
  156. package/dist/tools/library.d.ts.map +1 -1
  157. package/dist/tools/library.js +23 -7
  158. package/dist/tools/library.js.map +1 -1
  159. package/dist/tools/list-channel-thumbnails.d.ts +235 -0
  160. package/dist/tools/list-channel-thumbnails.d.ts.map +1 -0
  161. package/dist/tools/list-channel-thumbnails.js +79 -0
  162. package/dist/tools/list-channel-thumbnails.js.map +1 -0
  163. package/dist/tools/list-library.d.ts +4 -4
  164. package/dist/tools/manage-brain.d.ts +39 -0
  165. package/dist/tools/manage-brain.d.ts.map +1 -0
  166. package/dist/tools/manage-brain.js +53 -0
  167. package/dist/tools/manage-brain.js.map +1 -0
  168. package/dist/tools/save-to-library.d.ts.map +1 -1
  169. package/dist/tools/save-to-library.js +5 -1
  170. package/dist/tools/save-to-library.js.map +1 -1
  171. package/dist/tools/search-channels.d.ts +10 -4
  172. package/dist/tools/search-channels.d.ts.map +1 -1
  173. package/dist/tools/search-transcript.d.ts +2 -2
  174. package/dist/tools/search-transcript.d.ts.map +1 -1
  175. package/dist/tools/search-transcript.js +6 -13
  176. package/dist/tools/search-transcript.js.map +1 -1
  177. package/dist/tools/search-videos.d.ts +11 -8
  178. package/dist/tools/search-videos.d.ts.map +1 -1
  179. package/dist/utils/batches.d.ts +15 -0
  180. package/dist/utils/batches.d.ts.map +1 -0
  181. package/dist/utils/batches.js +31 -0
  182. package/dist/utils/batches.js.map +1 -0
  183. package/dist/utils/brain-build.d.ts +40 -0
  184. package/dist/utils/brain-build.d.ts.map +1 -0
  185. package/dist/utils/brain-build.js +200 -0
  186. package/dist/utils/brain-build.js.map +1 -0
  187. package/dist/utils/brain-chunks.d.ts +33 -0
  188. package/dist/utils/brain-chunks.d.ts.map +1 -0
  189. package/dist/utils/brain-chunks.js +87 -0
  190. package/dist/utils/brain-chunks.js.map +1 -0
  191. package/dist/utils/brain-index.d.ts +27 -0
  192. package/dist/utils/brain-index.d.ts.map +1 -0
  193. package/dist/utils/brain-index.js +101 -0
  194. package/dist/utils/brain-index.js.map +1 -0
  195. package/dist/utils/brain-ingest.d.ts +57 -0
  196. package/dist/utils/brain-ingest.d.ts.map +1 -0
  197. package/dist/utils/brain-ingest.js +127 -0
  198. package/dist/utils/brain-ingest.js.map +1 -0
  199. package/dist/utils/brain-lock.d.ts +4 -0
  200. package/dist/utils/brain-lock.d.ts.map +1 -0
  201. package/dist/utils/brain-lock.js +17 -0
  202. package/dist/utils/brain-lock.js.map +1 -0
  203. package/dist/utils/brain-lookup.d.ts +11 -0
  204. package/dist/utils/brain-lookup.d.ts.map +1 -0
  205. package/dist/utils/brain-lookup.js +36 -0
  206. package/dist/utils/brain-lookup.js.map +1 -0
  207. package/dist/utils/brain-paths.d.ts +19 -0
  208. package/dist/utils/brain-paths.d.ts.map +1 -0
  209. package/dist/utils/brain-paths.js +34 -0
  210. package/dist/utils/brain-paths.js.map +1 -0
  211. package/dist/utils/brain-phrases.d.ts +31 -0
  212. package/dist/utils/brain-phrases.d.ts.map +1 -0
  213. package/dist/utils/brain-phrases.js +116 -0
  214. package/dist/utils/brain-phrases.js.map +1 -0
  215. package/dist/utils/brain-stats.d.ts +25 -0
  216. package/dist/utils/brain-stats.d.ts.map +1 -0
  217. package/dist/utils/brain-stats.js +64 -0
  218. package/dist/utils/brain-stats.js.map +1 -0
  219. package/dist/utils/brain-storage.d.ts +28 -0
  220. package/dist/utils/brain-storage.d.ts.map +1 -0
  221. package/dist/utils/brain-storage.js +87 -0
  222. package/dist/utils/brain-storage.js.map +1 -0
  223. package/dist/utils/caption-probe.d.ts +7 -0
  224. package/dist/utils/caption-probe.d.ts.map +1 -0
  225. package/dist/utils/caption-probe.js +36 -0
  226. package/dist/utils/caption-probe.js.map +1 -0
  227. package/dist/utils/channel-images.d.ts +16 -0
  228. package/dist/utils/channel-images.d.ts.map +1 -0
  229. package/dist/utils/channel-images.js +35 -0
  230. package/dist/utils/channel-images.js.map +1 -0
  231. package/dist/utils/channel-lookup.d.ts +5 -0
  232. package/dist/utils/channel-lookup.d.ts.map +1 -0
  233. package/dist/utils/channel-lookup.js +37 -0
  234. package/dist/utils/channel-lookup.js.map +1 -0
  235. package/dist/utils/chapters.d.ts +11 -0
  236. package/dist/utils/chapters.d.ts.map +1 -0
  237. package/dist/utils/chapters.js +24 -0
  238. package/dist/utils/chapters.js.map +1 -0
  239. package/dist/utils/clips.d.ts.map +1 -1
  240. package/dist/utils/clips.js +22 -30
  241. package/dist/utils/clips.js.map +1 -1
  242. package/dist/utils/env.d.ts +26 -0
  243. package/dist/utils/env.d.ts.map +1 -0
  244. package/dist/utils/env.js +72 -0
  245. package/dist/utils/env.js.map +1 -0
  246. package/dist/utils/errors.d.ts +1 -1
  247. package/dist/utils/errors.d.ts.map +1 -1
  248. package/dist/utils/errors.js +20 -2
  249. package/dist/utils/errors.js.map +1 -1
  250. package/dist/utils/file-lock.d.ts +34 -0
  251. package/dist/utils/file-lock.d.ts.map +1 -0
  252. package/dist/utils/file-lock.js +91 -0
  253. package/dist/utils/file-lock.js.map +1 -0
  254. package/dist/utils/flat-listing.d.ts +89 -0
  255. package/dist/utils/flat-listing.d.ts.map +1 -0
  256. package/dist/utils/flat-listing.js +68 -0
  257. package/dist/utils/flat-listing.js.map +1 -0
  258. package/dist/utils/format.d.ts +1 -3
  259. package/dist/utils/format.d.ts.map +1 -1
  260. package/dist/utils/format.js +1 -5
  261. package/dist/utils/format.js.map +1 -1
  262. package/dist/utils/guard.d.ts +21 -0
  263. package/dist/utils/guard.d.ts.map +1 -0
  264. package/dist/utils/guard.js +105 -0
  265. package/dist/utils/guard.js.map +1 -0
  266. package/dist/utils/image-dimensions.d.ts +19 -0
  267. package/dist/utils/image-dimensions.d.ts.map +1 -0
  268. package/dist/utils/image-dimensions.js +93 -0
  269. package/dist/utils/image-dimensions.js.map +1 -0
  270. package/dist/utils/image-fetch.d.ts +31 -0
  271. package/dist/utils/image-fetch.d.ts.map +1 -0
  272. package/dist/utils/image-fetch.js +141 -0
  273. package/dist/utils/image-fetch.js.map +1 -0
  274. package/dist/utils/json-file.d.ts +45 -0
  275. package/dist/utils/json-file.d.ts.map +1 -0
  276. package/dist/utils/json-file.js +60 -0
  277. package/dist/utils/json-file.js.map +1 -0
  278. package/dist/utils/paths.d.ts +19 -0
  279. package/dist/utils/paths.d.ts.map +1 -0
  280. package/dist/utils/paths.js +27 -0
  281. package/dist/utils/paths.js.map +1 -0
  282. package/dist/utils/pattern.d.ts +30 -0
  283. package/dist/utils/pattern.d.ts.map +1 -0
  284. package/dist/utils/pattern.js +130 -0
  285. package/dist/utils/pattern.js.map +1 -0
  286. package/dist/utils/search-index.d.ts +22 -7
  287. package/dist/utils/search-index.d.ts.map +1 -1
  288. package/dist/utils/search-index.js +26 -8
  289. package/dist/utils/search-index.js.map +1 -1
  290. package/dist/utils/storage.d.ts +14 -44
  291. package/dist/utils/storage.d.ts.map +1 -1
  292. package/dist/utils/storage.js +47 -84
  293. package/dist/utils/storage.js.map +1 -1
  294. package/dist/utils/subtitles.d.ts +7 -0
  295. package/dist/utils/subtitles.d.ts.map +1 -0
  296. package/dist/utils/subtitles.js +30 -0
  297. package/dist/utils/subtitles.js.map +1 -0
  298. package/dist/utils/thumbnail-entry.d.ts +19 -0
  299. package/dist/utils/thumbnail-entry.d.ts.map +1 -0
  300. package/dist/utils/thumbnail-entry.js +119 -0
  301. package/dist/utils/thumbnail-entry.js.map +1 -0
  302. package/dist/utils/thumbnail-fetch.d.ts +37 -0
  303. package/dist/utils/thumbnail-fetch.d.ts.map +1 -0
  304. package/dist/utils/thumbnail-fetch.js +89 -0
  305. package/dist/utils/thumbnail-fetch.js.map +1 -0
  306. package/dist/utils/thumbnail-ladder.d.ts +44 -0
  307. package/dist/utils/thumbnail-ladder.d.ts.map +1 -0
  308. package/dist/utils/thumbnail-ladder.js +68 -0
  309. package/dist/utils/thumbnail-ladder.js.map +1 -0
  310. package/dist/utils/thumbnail-listing.d.ts +16 -0
  311. package/dist/utils/thumbnail-listing.d.ts.map +1 -0
  312. package/dist/utils/thumbnail-listing.js +26 -0
  313. package/dist/utils/thumbnail-listing.js.map +1 -0
  314. package/dist/utils/thumbnail-paths.d.ts +20 -0
  315. package/dist/utils/thumbnail-paths.d.ts.map +1 -0
  316. package/dist/utils/thumbnail-paths.js +35 -0
  317. package/dist/utils/thumbnail-paths.js.map +1 -0
  318. package/dist/utils/thumbnail-store.d.ts +19 -0
  319. package/dist/utils/thumbnail-store.d.ts.map +1 -0
  320. package/dist/utils/thumbnail-store.js +125 -0
  321. package/dist/utils/thumbnail-store.js.map +1 -0
  322. package/dist/utils/transcript-cache.d.ts +20 -0
  323. package/dist/utils/transcript-cache.d.ts.map +1 -0
  324. package/dist/utils/transcript-cache.js +127 -0
  325. package/dist/utils/transcript-cache.js.map +1 -0
  326. package/dist/utils/transcript-search.d.ts +20 -0
  327. package/dist/utils/transcript-search.d.ts.map +1 -0
  328. package/dist/utils/transcript-search.js +35 -0
  329. package/dist/utils/transcript-search.js.map +1 -0
  330. package/dist/utils/transcript.d.ts +14 -34
  331. package/dist/utils/transcript.d.ts.map +1 -1
  332. package/dist/utils/transcript.js +5 -64
  333. package/dist/utils/transcript.js.map +1 -1
  334. package/dist/utils/validate-youtube.d.ts +35 -0
  335. package/dist/utils/validate-youtube.d.ts.map +1 -0
  336. package/dist/utils/validate-youtube.js +87 -0
  337. package/dist/utils/validate-youtube.js.map +1 -0
  338. package/dist/utils/validate.d.ts +11 -3
  339. package/dist/utils/validate.d.ts.map +1 -1
  340. package/dist/utils/validate.js +43 -18
  341. package/dist/utils/validate.js.map +1 -1
  342. package/dist/utils/version.d.ts +2 -0
  343. package/dist/utils/version.d.ts.map +1 -0
  344. package/dist/utils/version.js +19 -0
  345. package/dist/utils/version.js.map +1 -0
  346. package/dist/utils/youtube-channel.d.ts +44 -0
  347. package/dist/utils/youtube-channel.d.ts.map +1 -0
  348. package/dist/utils/youtube-channel.js +61 -0
  349. package/dist/utils/youtube-channel.js.map +1 -0
  350. package/dist/utils/youtube-download.d.ts +27 -0
  351. package/dist/utils/youtube-download.d.ts.map +1 -0
  352. package/dist/utils/youtube-download.js +118 -0
  353. package/dist/utils/youtube-download.js.map +1 -0
  354. package/dist/utils/youtube-schemas.d.ts +394 -0
  355. package/dist/utils/youtube-schemas.d.ts.map +1 -0
  356. package/dist/utils/youtube-schemas.js +118 -0
  357. package/dist/utils/youtube-schemas.js.map +1 -0
  358. package/dist/utils/youtube-search.d.ts +14 -0
  359. package/dist/utils/youtube-search.d.ts.map +1 -0
  360. package/dist/utils/youtube-search.js +39 -0
  361. package/dist/utils/youtube-search.js.map +1 -0
  362. package/dist/utils/youtube-url.d.ts +25 -0
  363. package/dist/utils/youtube-url.d.ts.map +1 -0
  364. package/dist/utils/youtube-url.js +75 -0
  365. package/dist/utils/youtube-url.js.map +1 -0
  366. package/dist/utils/youtube-video.d.ts +47 -0
  367. package/dist/utils/youtube-video.d.ts.map +1 -0
  368. package/dist/utils/youtube-video.js +98 -0
  369. package/dist/utils/youtube-video.js.map +1 -0
  370. package/dist/utils/youtube.d.ts +13 -111
  371. package/dist/utils/youtube.d.ts.map +1 -1
  372. package/dist/utils/youtube.js +11 -480
  373. package/dist/utils/youtube.js.map +1 -1
  374. package/dist/utils/ytdlp-env.d.ts +33 -0
  375. package/dist/utils/ytdlp-env.d.ts.map +1 -0
  376. package/dist/utils/ytdlp-env.js +121 -0
  377. package/dist/utils/ytdlp-env.js.map +1 -0
  378. package/dist/utils/ytdlp-limiter.d.ts +24 -0
  379. package/dist/utils/ytdlp-limiter.d.ts.map +1 -0
  380. package/dist/utils/ytdlp-limiter.js +69 -0
  381. package/dist/utils/ytdlp-limiter.js.map +1 -0
  382. package/dist/utils/ytdlp-parse.d.ts +13 -0
  383. package/dist/utils/ytdlp-parse.d.ts.map +1 -0
  384. package/dist/utils/ytdlp-parse.js +48 -0
  385. package/dist/utils/ytdlp-parse.js.map +1 -0
  386. package/dist/utils/ytdlp.d.ts +13 -18
  387. package/dist/utils/ytdlp.d.ts.map +1 -1
  388. package/dist/utils/ytdlp.js +21 -110
  389. package/dist/utils/ytdlp.js.map +1 -1
  390. package/package.json +2 -1
@@ -0,0 +1,119 @@
1
+ import { rm } from 'node:fs/promises';
2
+ import { imageFormatSchema, } from '../thumbnail-schemas.js';
3
+ import { throwIfAborted } from './context.js';
4
+ import { asYouTubeError } from './errors.js';
5
+ import { largestThumbnail } from './flat-listing.js';
6
+ import { MAX_IMAGE_BYTES } from './image-fetch.js';
7
+ import { writeFileAtomic } from './json-file.js';
8
+ import { climb, videoRungs } from './thumbnail-ladder.js';
9
+ import { channelImagePath, relativeFile, videoThumbnailPath } from './thumbnail-paths.js';
10
+ import { channelImageFile, entryPath, isIntact } from './thumbnail-store.js';
11
+ const ALL_FORMATS = imageFormatSchema.options;
12
+ /**
13
+ * Per-video entries, reconciled against the files actually on disk.
14
+ *
15
+ * A record that says `saved` whose file is missing or the wrong size goes back
16
+ * to `pending`; a video in today's listing that has no record gets one; and a
17
+ * record for a video not listed today is kept, since its file is still a
18
+ * thumbnail and the tab may simply not have been requested this time.
19
+ */
20
+ export async function reconciled(channelId, existing, listings) {
21
+ const entries = new Map();
22
+ for (const [videoId, recorded] of Object.entries(existing?.videos ?? {})) {
23
+ const intact = await isIntact(entryPath(channelId, recorded), recorded.bytes);
24
+ entries.set(videoId, intact ? recorded : { ...recorded, state: 'pending' });
25
+ }
26
+ for (const { tab, videos } of listings) {
27
+ for (const video of videos) {
28
+ if (!entries.has(video.id))
29
+ entries.set(video.id, pendingEntry(tab, video));
30
+ }
31
+ }
32
+ return entries;
33
+ }
34
+ function pendingEntry(tab, video) {
35
+ const listed = largestThumbnail(video.thumbnails ?? []);
36
+ const portrait = listed?.width !== null &&
37
+ listed?.width !== undefined &&
38
+ listed.height !== null &&
39
+ listed.height !== undefined &&
40
+ listed.height > listed.width;
41
+ return {
42
+ videoId: video.id,
43
+ title: video.title,
44
+ url: video.url,
45
+ tab,
46
+ isShort: tab === 'shorts' || portrait,
47
+ durationSeconds: video.duration,
48
+ ...(video.liveStatus === undefined ? {} : { liveStatus: video.liveStatus }),
49
+ ...(listed === undefined ? {} : { listedUrl: listed.url }),
50
+ ...(listed?.width === null || listed?.width === undefined ? {} : { listedWidth: listed.width }),
51
+ ...(listed?.height === null || listed?.height === undefined
52
+ ? {}
53
+ : { listedHeight: listed.height }),
54
+ state: 'pending',
55
+ };
56
+ }
57
+ export async function fetchOne(channelId, entry, quality) {
58
+ const listed = entry.listedUrl === undefined
59
+ ? undefined
60
+ : {
61
+ url: entry.listedUrl,
62
+ ...(entry.listedWidth === undefined ? {} : { width: entry.listedWidth }),
63
+ ...(entry.listedHeight === undefined ? {} : { height: entry.listedHeight }),
64
+ };
65
+ try {
66
+ const climbed = await climb(videoRungs(entry.videoId, listed, entry.isShort, quality), MAX_IMAGE_BYTES);
67
+ const path = videoThumbnailPath(channelId, entry.tab, entry.videoId, climbed.probe.format);
68
+ await replaceFile(path, climbed, (format) => videoThumbnailPath(channelId, entry.tab, entry.videoId, format));
69
+ return { ...withoutSaved(entry), state: 'saved', ...saved(channelId, path, climbed) };
70
+ }
71
+ catch (error) {
72
+ throwIfAborted();
73
+ return { ...withoutSaved(entry), state: 'failed', error: asYouTubeError(error).code };
74
+ }
75
+ }
76
+ export async function channelImage(channelId, kind, url, recorded) {
77
+ if (url === undefined)
78
+ return recorded;
79
+ if (recorded !== undefined &&
80
+ (await isIntact(channelImageFile(channelId, recorded), recorded.bytes))) {
81
+ return recorded;
82
+ }
83
+ try {
84
+ const climbed = await climb([{ variant: 'uncropped', url }], MAX_IMAGE_BYTES);
85
+ const path = channelImagePath(channelId, kind, climbed.probe.format);
86
+ await replaceFile(path, climbed, (format) => channelImagePath(channelId, kind, format));
87
+ return { kind, state: 'saved', ...saved(channelId, path, climbed) };
88
+ }
89
+ catch (error) {
90
+ throwIfAborted();
91
+ return { kind, state: 'failed', error: asYouTubeError(error).code };
92
+ }
93
+ }
94
+ /** Write the image, and remove an earlier file of the same name in another format. */
95
+ async function replaceFile(path, climbed, pathFor) {
96
+ await writeFileAtomic(path, climbed.bytes);
97
+ for (const format of ALL_FORMATS) {
98
+ if (format !== climbed.probe.format)
99
+ await rm(pathFor(format), { force: true });
100
+ }
101
+ }
102
+ function saved(channelId, path, climbed) {
103
+ return {
104
+ file: relativeFile(channelId, path),
105
+ sourceUrl: climbed.rung.url,
106
+ variant: climbed.rung.variant,
107
+ width: climbed.probe.width,
108
+ height: climbed.probe.height,
109
+ bytes: climbed.bytes.byteLength,
110
+ format: climbed.probe.format,
111
+ fetchedAt: new Date().toISOString(),
112
+ };
113
+ }
114
+ /** The entry with every field of a previous save removed, so a failure cannot keep stale sizes. */
115
+ function withoutSaved(entry) {
116
+ const { file: _file, sourceUrl: _sourceUrl, variant: _variant, width: _width, height: _height, bytes: _bytes, format: _format, fetchedAt: _fetchedAt, error: _error, ...rest } = entry;
117
+ return rest;
118
+ }
119
+ //# sourceMappingURL=thumbnail-entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnail-entry.js","sourceRoot":"","sources":["../../src/utils/thumbnail-entry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { ThumbnailManifest, ThumbnailQuality, ThumbnailTab } from '../thumbnail-schemas.js';
2
+ import { type TabListing } from './thumbnail-entry.js';
3
+ export type { TabListing } from './thumbnail-entry.js';
4
+ import type { ChannelInfo } from './youtube.js';
5
+ /**
6
+ * Fetching a channel's thumbnails, resumably.
7
+ *
8
+ * Shaped like a brain build, and for the same reason: several hundred images
9
+ * is several hundred requests, so the job gets interrupted — cancelled by the
10
+ * client, throttled by YouTube, or killed with the editor. Per-image state
11
+ * rather than one flag, checkpoints rather than one write at the end, a
12
+ * failed image costing only itself, and a second call that continues.
13
+ */
14
+ export declare const CHECKPOINT_EVERY_ITEMS = 20;
15
+ export declare const RATE_LIMIT_TOLERANCE = 3;
16
+ export interface FetchThumbnailsOptions {
17
+ channel: ChannelInfo;
18
+ listings: TabListing[];
19
+ existing: ThumbnailManifest | undefined;
20
+ quality: ThumbnailQuality;
21
+ }
22
+ export interface FetchThumbnailsResult {
23
+ manifest: ThumbnailManifest;
24
+ considered: number;
25
+ fetched: number;
26
+ skipped: number;
27
+ failed: number;
28
+ failures: {
29
+ videoId: string;
30
+ tab: ThumbnailTab;
31
+ error: string;
32
+ }[];
33
+ stoppedEarly: boolean;
34
+ stopReason?: string;
35
+ }
36
+ export declare function fetchChannelThumbnails(options: FetchThumbnailsOptions): Promise<FetchThumbnailsResult>;
37
+ //# sourceMappingURL=thumbnail-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnail-fetch.d.ts","sourceRoot":"","sources":["../../src/utils/thumbnail-fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGjG,OAAO,EAAsC,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAE3F,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAOvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;;GAQG;AAEH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AACzC,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,QAAQ,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACxC,OAAO,EAAE,gBAAgB,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,YAAY,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAClE,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAiFhC"}
@@ -0,0 +1,89 @@
1
+ import { inBatches } from './batches.js';
2
+ import { reportProgress, throwIfAborted } from './context.js';
3
+ import { channelImage, fetchOne, reconciled } from './thumbnail-entry.js';
4
+ import { THUMBNAIL_MANIFEST_VERSION, computeThumbnailStats, writeThumbnailManifest, } from './thumbnail-store.js';
5
+ import { concurrencyState } from './ytdlp.js';
6
+ /**
7
+ * Fetching a channel's thumbnails, resumably.
8
+ *
9
+ * Shaped like a brain build, and for the same reason: several hundred images
10
+ * is several hundred requests, so the job gets interrupted — cancelled by the
11
+ * client, throttled by YouTube, or killed with the editor. Per-image state
12
+ * rather than one flag, checkpoints rather than one write at the end, a
13
+ * failed image costing only itself, and a second call that continues.
14
+ */
15
+ export const CHECKPOINT_EVERY_ITEMS = 20;
16
+ export const RATE_LIMIT_TOLERANCE = 3;
17
+ export async function fetchChannelThumbnails(options) {
18
+ const { channel, listings, existing, quality } = options;
19
+ const { channelId } = channel;
20
+ const createdAt = existing?.createdAt ?? new Date().toISOString();
21
+ const tabs = [...new Set([...(existing?.tabs ?? []), ...listings.map((l) => l.tab)])];
22
+ const entries = await reconciled(channelId, existing, listings);
23
+ const images = { avatar: existing?.avatar, banner: existing?.banner };
24
+ const save = async () => {
25
+ const manifest = {
26
+ version: THUMBNAIL_MANIFEST_VERSION,
27
+ channel,
28
+ tabs,
29
+ quality,
30
+ createdAt,
31
+ updatedAt: new Date().toISOString(),
32
+ ...(images.avatar === undefined ? {} : { avatar: images.avatar }),
33
+ ...(images.banner === undefined ? {} : { banner: images.banner }),
34
+ videos: Object.fromEntries(entries),
35
+ stats: computeThumbnailStats([...entries.values()]),
36
+ };
37
+ await writeThumbnailManifest(manifest);
38
+ return manifest;
39
+ };
40
+ images.avatar = await channelImage(channelId, 'avatar', channel.avatarUrl, images.avatar);
41
+ images.banner = await channelImage(channelId, 'banner', channel.bannerUrl, images.banner);
42
+ const outstanding = [...entries.values()].filter((e) => e.state !== 'saved');
43
+ const failures = [];
44
+ let attempted = 0;
45
+ let fetched = 0;
46
+ let consecutiveThrottles = 0;
47
+ let stopReason;
48
+ try {
49
+ await inBatches(outstanding, concurrencyState().limit, async (entry) => {
50
+ if (stopReason !== undefined)
51
+ return;
52
+ throwIfAborted();
53
+ const updated = await fetchOne(channelId, entry, quality);
54
+ entries.set(entry.videoId, updated);
55
+ attempted++;
56
+ if (updated.state === 'saved')
57
+ fetched++;
58
+ else
59
+ failures.push({
60
+ videoId: entry.videoId,
61
+ tab: entry.tab,
62
+ error: updated.error ?? 'FETCH_FAILED',
63
+ });
64
+ consecutiveThrottles = updated.error === 'RATE_LIMITED' ? consecutiveThrottles + 1 : 0;
65
+ if (consecutiveThrottles >= RATE_LIMIT_TOLERANCE)
66
+ stopReason = 'YouTube is rate limiting image downloads.';
67
+ reportProgress(attempted, outstanding.length, `Fetched ${attempted} of ${outstanding.length}`);
68
+ if (attempted % CHECKPOINT_EVERY_ITEMS === 0)
69
+ await save();
70
+ });
71
+ }
72
+ catch (error) {
73
+ // A cancelled job keeps what it fetched since the last checkpoint.
74
+ await save();
75
+ throw error;
76
+ }
77
+ const manifest = await save();
78
+ return {
79
+ manifest,
80
+ considered: entries.size,
81
+ fetched,
82
+ skipped: entries.size - outstanding.length,
83
+ failed: failures.length,
84
+ failures,
85
+ stoppedEarly: stopReason !== undefined,
86
+ ...(stopReason === undefined ? {} : { stopReason }),
87
+ };
88
+ }
89
+ //# sourceMappingURL=thumbnail-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnail-fetch.js","sourceRoot":"","sources":["../../src/utils/thumbnail-fetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { type ImageProbe } from './image-dimensions.js';
2
+ import type { ListedImage } from './flat-listing.js';
3
+ import type { ThumbnailQuality } from '../thumbnail-schemas.js';
4
+ /**
5
+ * Getting the best thumbnail YouTube will serve for one video.
6
+ *
7
+ * A flat channel listing offers each video's thumbnail at up to 720 pixels
8
+ * wide (verified against yt-dlp 2026.07.04). YouTube also serves a larger
9
+ * `maxresdefault.jpg` for many videos — the probed video returned one at
10
+ * 1280 wide — but not, as far as this code knows, for every video, and a
11
+ * missing one is not guaranteed to be a 404. So the ladder tries it and then
12
+ * checks what came back: an image is accepted only if it decodes and is wider
13
+ * than the listed one. Anything else falls through to the next rung. No
14
+ * behaviour of YouTube's is assumed that a rung could not verify for itself.
15
+ */
16
+ export interface Rung {
17
+ variant: string;
18
+ url: string;
19
+ /** Accept the image only if it decodes wider than this. */
20
+ mustExceedWidth?: number;
21
+ }
22
+ export interface Climbed {
23
+ bytes: Buffer;
24
+ probe: ImageProbe;
25
+ rung: Rung;
26
+ }
27
+ /**
28
+ * The rungs for one video, best first.
29
+ *
30
+ * Shorts are listed with portrait thumbnails; whether `maxresdefault` exists
31
+ * for a short, or is portrait when it does, is not verified, so a short uses
32
+ * its listed image and the small landscape fallback only.
33
+ */
34
+ export declare function videoRungs(videoId: string, listed: ListedImage | undefined, isShort: boolean, quality: ThumbnailQuality): Rung[];
35
+ /**
36
+ * Fetch rungs in order and return the first acceptable image.
37
+ *
38
+ * A rung is accepted when it downloads, decodes as an image this server reads,
39
+ * and — where the rung demands it — is wider than the listed image. A 404 or
40
+ * an unacceptable image moves to the next rung; rate limiting, a timeout or a
41
+ * cancelled request stops the climb immediately.
42
+ */
43
+ export declare function climb(rungs: Rung[], maxBytes: number): Promise<Climbed>;
44
+ //# sourceMappingURL=thumbnail-ladder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnail-ladder.d.ts","sourceRoot":"","sources":["../../src/utils/thumbnail-ladder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,IAAI;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,IAAI,CAAC;CACZ;AAQD;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,gBAAgB,GACxB,IAAI,EAAE,CAaR;AAKD;;;;;;;GAOG;AACH,wBAAsB,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgC7E"}
@@ -0,0 +1,68 @@
1
+ import { probeImage } from './image-dimensions.js';
2
+ import { fetchImage } from './image-fetch.js';
3
+ import { YouTubeError } from './errors.js';
4
+ const named = (videoId, variant, mustExceedWidth) => ({
5
+ variant,
6
+ url: `https://i.ytimg.com/vi/${videoId}/${variant}.jpg`,
7
+ ...(mustExceedWidth === undefined ? {} : { mustExceedWidth }),
8
+ });
9
+ /**
10
+ * The rungs for one video, best first.
11
+ *
12
+ * Shorts are listed with portrait thumbnails; whether `maxresdefault` exists
13
+ * for a short, or is portrait when it does, is not verified, so a short uses
14
+ * its listed image and the small landscape fallback only.
15
+ */
16
+ export function videoRungs(videoId, listed, isShort, quality) {
17
+ const listedRung = listed === undefined ? [] : [{ variant: 'listed', url: listed.url }];
18
+ const fallback = named(videoId, 'hqdefault');
19
+ if (isShort)
20
+ return [...listedRung, fallback];
21
+ if (quality === 'listed')
22
+ return [...listedRung, fallback];
23
+ const best = named(videoId, 'maxresdefault', listed?.width ?? 200);
24
+ // With no listing to beat, the smaller named sizes are worth trying too.
25
+ return listed === undefined
26
+ ? [best, named(videoId, 'sddefault'), fallback]
27
+ : [best, ...listedRung, fallback];
28
+ }
29
+ /** The reasons to stop climbing: they are about the client, not the rung. */
30
+ const ABORTING = new Set(['RATE_LIMITED', 'TIMEOUT', 'CANCELLED']);
31
+ /**
32
+ * Fetch rungs in order and return the first acceptable image.
33
+ *
34
+ * A rung is accepted when it downloads, decodes as an image this server reads,
35
+ * and — where the rung demands it — is wider than the listed image. A 404 or
36
+ * an unacceptable image moves to the next rung; rate limiting, a timeout or a
37
+ * cancelled request stops the climb immediately.
38
+ */
39
+ export async function climb(rungs, maxBytes) {
40
+ let last;
41
+ for (const rung of rungs) {
42
+ try {
43
+ const { bytes } = await fetchImage(rung.url, { maxBytes });
44
+ const probe = probeImage(bytes);
45
+ if (probe === undefined) {
46
+ last = new YouTubeError('FETCH_FAILED', 'The response was not an image this server can read.');
47
+ continue;
48
+ }
49
+ if (rung.mustExceedWidth !== undefined && probe.width <= rung.mustExceedWidth) {
50
+ last = new YouTubeError('NOT_FOUND', 'No larger image is available at that address.');
51
+ continue;
52
+ }
53
+ return { bytes, probe, rung };
54
+ }
55
+ catch (error) {
56
+ if (!(error instanceof YouTubeError))
57
+ throw error;
58
+ if (ABORTING.has(error.code))
59
+ throw error;
60
+ last = error;
61
+ }
62
+ }
63
+ throw (last ??
64
+ new YouTubeError('NOT_FOUND', 'No thumbnail address was available for this video.', {
65
+ nextStep: 'The listing carried no thumbnail; try again later.',
66
+ }));
67
+ }
68
+ //# sourceMappingURL=thumbnail-ladder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnail-ladder.js","sourceRoot":"","sources":["../../src/utils/thumbnail-ladder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { ThumbnailTab } from '../thumbnail-schemas.js';
2
+ import type { TabListing } from './thumbnail-entry.js';
3
+ /**
4
+ * List each tab. The uploads tab must list — every channel has one, so a
5
+ * failure there is worth reporting. Whether yt-dlp lists an empty shorts or
6
+ * streams tab or refuses it is not verified, so those failures are recorded
7
+ * per tab and the run continues.
8
+ */
9
+ export declare function listTabs(channelUrl: string, tabs: ThumbnailTab[], maxVideos: number): Promise<{
10
+ listings: TabListing[];
11
+ tabErrors: {
12
+ tab: ThumbnailTab;
13
+ error: string;
14
+ }[];
15
+ }>;
16
+ //# sourceMappingURL=thumbnail-listing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnail-listing.d.ts","sourceRoot":"","sources":["../../src/utils/thumbnail-listing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvD;;;;;GAKG;AACH,wBAAsB,QAAQ,CAC5B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,YAAY,EAAE,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;IAAC,SAAS,EAAE;QAAE,GAAG,EAAE,YAAY,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAAC,CAkBxF"}
@@ -0,0 +1,26 @@
1
+ import { asYouTubeError } from './errors.js';
2
+ import { listVideos } from './youtube.js';
3
+ /**
4
+ * List each tab. The uploads tab must list — every channel has one, so a
5
+ * failure there is worth reporting. Whether yt-dlp lists an empty shorts or
6
+ * streams tab or refuses it is not verified, so those failures are recorded
7
+ * per tab and the run continues.
8
+ */
9
+ export async function listTabs(channelUrl, tabs, maxVideos) {
10
+ const base = channelUrl.replace(/\/+$/, '');
11
+ const listings = [];
12
+ const tabErrors = [];
13
+ for (const tab of tabs) {
14
+ try {
15
+ const videos = (await listVideos(`${base}/${tab}`, maxVideos)).slice(0, maxVideos);
16
+ listings.push({ tab, videos });
17
+ }
18
+ catch (error) {
19
+ if (tab === 'videos')
20
+ throw error;
21
+ tabErrors.push({ tab, error: asYouTubeError(error).code });
22
+ }
23
+ }
24
+ return { listings, tabErrors };
25
+ }
26
+ //# sourceMappingURL=thumbnail-listing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnail-listing.js","sourceRoot":"","sources":["../../src/utils/thumbnail-listing.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAsB,MAAM,cAAc,CAAC;AAE9D;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,UAAkB,EAClB,IAAoB,EACpB,SAAiB;IAEjB,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAiB,EAAE,CAAC;IAClC,MAAM,SAAS,GAA2C,EAAE,CAAC;IAE7D,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,GAAoB,CAAC,MAAM,UAAU,CAAC,GAAG,IAAI,IAAI,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,CACnF,CAAC,EACD,SAAS,CACV,CAAC;YACF,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,GAAG,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAC;YAClC,SAAS,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACjC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { ImageFormat } from './image-dimensions.js';
2
+ import type { ThumbnailTab } from '../thumbnail-schemas.js';
3
+ /**
4
+ * Every file a channel's thumbnails are made of.
5
+ *
6
+ * A sibling of the brains rather than inside them: deleting a brain removes
7
+ * its whole directory, and a channel's images should outlive its corpus. Keyed
8
+ * on the channel id, validated once here; a filename is the video id, also
9
+ * validated here; and the tab and format are closed enums. Nothing a caller or
10
+ * a hand-edited manifest supplies reaches a path unchecked.
11
+ */
12
+ export declare function thumbnailsDir(): string;
13
+ export declare function thumbnailDir(channelId: string): string;
14
+ export declare function thumbnailManifestPath(channelId: string): string;
15
+ export declare function thumbnailLockPath(channelId: string): string;
16
+ export declare function channelImagePath(channelId: string, kind: 'avatar' | 'banner', format: ImageFormat): string;
17
+ export declare function videoThumbnailPath(channelId: string, tab: ThumbnailTab, videoId: string, format: ImageFormat): string;
18
+ /** The informational `file` field: where a file sits inside the channel directory. */
19
+ export declare function relativeFile(channelId: string, absolute: string): string;
20
+ //# sourceMappingURL=thumbnail-paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnail-paths.d.ts","sourceRoot":"","sources":["../../src/utils/thumbnail-paths.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D;;;;;;;;GAQG;AAEH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,QAAQ,GAAG,QAAQ,EACzB,MAAM,EAAE,WAAW,GAClB,MAAM,CAER;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,GAClB,MAAM,CAER;AAED,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAExE"}
@@ -0,0 +1,35 @@
1
+ import { join, relative } from 'node:path';
2
+ import { dataDir } from './paths.js';
3
+ import { assertChannelId, assertVideoId } from './validate.js';
4
+ /**
5
+ * Every file a channel's thumbnails are made of.
6
+ *
7
+ * A sibling of the brains rather than inside them: deleting a brain removes
8
+ * its whole directory, and a channel's images should outlive its corpus. Keyed
9
+ * on the channel id, validated once here; a filename is the video id, also
10
+ * validated here; and the tab and format are closed enums. Nothing a caller or
11
+ * a hand-edited manifest supplies reaches a path unchecked.
12
+ */
13
+ export function thumbnailsDir() {
14
+ return dataDir('thumbnails');
15
+ }
16
+ export function thumbnailDir(channelId) {
17
+ return join(thumbnailsDir(), assertChannelId(channelId));
18
+ }
19
+ export function thumbnailManifestPath(channelId) {
20
+ return join(thumbnailDir(channelId), 'manifest.json');
21
+ }
22
+ export function thumbnailLockPath(channelId) {
23
+ return join(thumbnailDir(channelId), 'fetch.lock');
24
+ }
25
+ export function channelImagePath(channelId, kind, format) {
26
+ return join(thumbnailDir(channelId), 'channel', `${kind}.${format}`);
27
+ }
28
+ export function videoThumbnailPath(channelId, tab, videoId, format) {
29
+ return join(thumbnailDir(channelId), tab, `${assertVideoId(videoId)}.${format}`);
30
+ }
31
+ /** The informational `file` field: where a file sits inside the channel directory. */
32
+ export function relativeFile(channelId, absolute) {
33
+ return relative(thumbnailDir(channelId), absolute);
34
+ }
35
+ //# sourceMappingURL=thumbnail-paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnail-paths.js","sourceRoot":"","sources":["../../src/utils/thumbnail-paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG/D;;;;;;;;GAQG;AAEH,MAAM,UAAU,aAAa;IAC3B,OAAO,OAAO,CAAC,YAAY,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,SAAiB;IACrD,OAAO,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,eAAe,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,OAAO,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,YAAY,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,SAAiB,EACjB,IAAyB,EACzB,MAAmB;IAEnB,OAAO,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,IAAI,MAAM,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,SAAiB,EACjB,GAAiB,EACjB,OAAe,EACf,MAAmB;IAEnB,OAAO,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,GAAG,aAAa,CAAC,OAAO,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC;AACnF,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,SAAiB,EAAE,QAAgB;IAC9D,OAAO,QAAQ,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;AACrD,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { type ChannelImage, type ThumbnailEntry, type ThumbnailManifest, type ThumbnailStats, type ThumbnailTab } from '../thumbnail-schemas.js';
2
+ /** The manifest of a channel's thumbnails: reading, writing, finding and forgetting it. */
3
+ export declare const THUMBNAIL_MANIFEST_VERSION = 1;
4
+ export declare function ensureThumbnailDirs(channelId: string, tabs: ThumbnailTab[]): Promise<string>;
5
+ export declare function readThumbnailManifest(channelId: string): Promise<ThumbnailManifest | undefined>;
6
+ export declare function requireThumbnailManifest(channelId: string): Promise<ThumbnailManifest>;
7
+ export declare function writeThumbnailManifest(manifest: ThumbnailManifest): Promise<void>;
8
+ /** Every thumbnail set on disk, newest first. Unreadable directories are skipped. */
9
+ export declare function listThumbnailManifests(): Promise<ThumbnailManifest[]>;
10
+ /** The thumbnail set for a channel named any way the caller likes, from disk alone. */
11
+ export declare function resolveThumbnails(channel: string): Promise<ThumbnailManifest>;
12
+ export declare function deleteThumbnails(channelId: string): Promise<boolean>;
13
+ /** Where a saved entry's file is, recomputed from validated parts; never read from the manifest. */
14
+ export declare function entryPath(channelId: string, entry: ThumbnailEntry): string | undefined;
15
+ export declare function channelImageFile(channelId: string, image: ChannelImage): string | undefined;
16
+ /** True when the file a saved record describes is on disk at the recorded size. */
17
+ export declare function isIntact(path: string | undefined, bytes: number | undefined): Promise<boolean>;
18
+ export declare function computeThumbnailStats(entries: ThumbnailEntry[]): ThumbnailStats;
19
+ //# sourceMappingURL=thumbnail-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnail-store.d.ts","sourceRoot":"","sources":["../../src/utils/thumbnail-store.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,YAAY,EAClB,MAAM,yBAAyB,CAAC;AAajC,2FAA2F;AAE3F,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,YAAY,EAAE,GACnB,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAExC;AAED,wBAAsB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAY5F;AAED,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvF;AAED,qFAAqF;AACrF,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAa3E;AAED,uFAAuF;AACvF,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAmBnF;AAED,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAK1E;AAED,oGAAoG;AACpG,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAGtF;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAG3F;AAED,mFAAmF;AACnF,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,cAAc,CA2B/E"}