youtube-knowledge-mcp 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/README.md +87 -41
  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 +6 -6
  5. package/dist/harvest-schemas.d.ts.map +1 -1
  6. package/dist/harvest-schemas.js +5 -0
  7. package/dist/harvest-schemas.js.map +1 -1
  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/index.d.ts.map +1 -1
  15. package/dist/registry/index.js +4 -0
  16. package/dist/registry/index.js.map +1 -1
  17. package/dist/registry/media-tools.d.ts.map +1 -1
  18. package/dist/registry/media-tools.js +31 -0
  19. package/dist/registry/media-tools.js.map +1 -1
  20. package/dist/registry/store-tools.d.ts +9 -0
  21. package/dist/registry/store-tools.d.ts.map +1 -0
  22. package/dist/registry/store-tools.js +59 -0
  23. package/dist/registry/store-tools.js.map +1 -0
  24. package/dist/registry/transcript-tools.d.ts +4 -1
  25. package/dist/registry/transcript-tools.d.ts.map +1 -1
  26. package/dist/registry/transcript-tools.js +20 -1
  27. package/dist/registry/transcript-tools.js.map +1 -1
  28. package/dist/registry/video-tools.d.ts.map +1 -1
  29. package/dist/registry/video-tools.js +48 -0
  30. package/dist/registry/video-tools.js.map +1 -1
  31. package/dist/registry/vision-tools.d.ts +7 -0
  32. package/dist/registry/vision-tools.d.ts.map +1 -0
  33. package/dist/registry/vision-tools.js +55 -0
  34. package/dist/registry/vision-tools.js.map +1 -0
  35. package/dist/schemas.d.ts +6 -0
  36. package/dist/schemas.d.ts.map +1 -1
  37. package/dist/schemas.js +2 -0
  38. package/dist/schemas.js.map +1 -1
  39. package/dist/thumbnail-schemas.d.ts +10 -0
  40. package/dist/thumbnail-schemas.d.ts.map +1 -1
  41. package/dist/tools/ask-brains.d.ts +66 -0
  42. package/dist/tools/ask-brains.d.ts.map +1 -0
  43. package/dist/tools/ask-brains.js +111 -0
  44. package/dist/tools/ask-brains.js.map +1 -0
  45. package/dist/tools/check-health.d.ts +16 -6
  46. package/dist/tools/check-health.d.ts.map +1 -1
  47. package/dist/tools/check-health.js +30 -1
  48. package/dist/tools/check-health.js.map +1 -1
  49. package/dist/tools/check-new-uploads.d.ts +55 -0
  50. package/dist/tools/check-new-uploads.d.ts.map +1 -0
  51. package/dist/tools/check-new-uploads.js +74 -0
  52. package/dist/tools/check-new-uploads.js.map +1 -0
  53. package/dist/tools/clips.d.ts +15 -0
  54. package/dist/tools/clips.d.ts.map +1 -1
  55. package/dist/tools/clips.js +5 -5
  56. package/dist/tools/clips.js.map +1 -1
  57. package/dist/tools/detect-scenes.d.ts +38 -0
  58. package/dist/tools/detect-scenes.d.ts.map +1 -0
  59. package/dist/tools/detect-scenes.js +66 -0
  60. package/dist/tools/detect-scenes.js.map +1 -0
  61. package/dist/tools/export-data.d.ts +85 -0
  62. package/dist/tools/export-data.d.ts.map +1 -0
  63. package/dist/tools/export-data.js +99 -0
  64. package/dist/tools/export-data.js.map +1 -0
  65. package/dist/tools/frames-subtitles.d.ts.map +1 -1
  66. package/dist/tools/frames-subtitles.js +16 -8
  67. package/dist/tools/frames-subtitles.js.map +1 -1
  68. package/dist/tools/frames.d.ts +73 -0
  69. package/dist/tools/frames.d.ts.map +1 -0
  70. package/dist/tools/frames.js +136 -0
  71. package/dist/tools/frames.js.map +1 -0
  72. package/dist/tools/get-channel-info.d.ts +2 -0
  73. package/dist/tools/get-channel-info.d.ts.map +1 -1
  74. package/dist/tools/get-channel-info.js +4 -0
  75. package/dist/tools/get-channel-info.js.map +1 -1
  76. package/dist/tools/get-comments.d.ts +5 -5
  77. package/dist/tools/get-coverage.d.ts +5 -5
  78. package/dist/tools/get-sponsor-segments.d.ts +49 -0
  79. package/dist/tools/get-sponsor-segments.d.ts.map +1 -0
  80. package/dist/tools/get-sponsor-segments.js +61 -0
  81. package/dist/tools/get-sponsor-segments.js.map +1 -0
  82. package/dist/tools/get-thumbnail.d.ts.map +1 -1
  83. package/dist/tools/get-thumbnail.js +2 -1
  84. package/dist/tools/get-thumbnail.js.map +1 -1
  85. package/dist/tools/get-transcript.d.ts +1 -0
  86. package/dist/tools/get-transcript.d.ts.map +1 -1
  87. package/dist/tools/get-transcript.js +11 -4
  88. package/dist/tools/get-transcript.js.map +1 -1
  89. package/dist/tools/get-video-details.d.ts +118 -0
  90. package/dist/tools/get-video-details.d.ts.map +1 -0
  91. package/dist/tools/get-video-details.js +147 -0
  92. package/dist/tools/get-video-details.js.map +1 -0
  93. package/dist/tools/gif-concat.d.ts +49 -0
  94. package/dist/tools/gif-concat.d.ts.map +1 -0
  95. package/dist/tools/gif-concat.js +96 -0
  96. package/dist/tools/gif-concat.js.map +1 -0
  97. package/dist/tools/harvest-channel.d.ts +5 -5
  98. package/dist/tools/harvest-comments.d.ts +5 -5
  99. package/dist/tools/harvest-details.d.ts +84 -0
  100. package/dist/tools/harvest-details.d.ts.map +1 -0
  101. package/dist/tools/harvest-details.js +71 -0
  102. package/dist/tools/harvest-details.js.map +1 -0
  103. package/dist/tools/list-captions.d.ts +17 -0
  104. package/dist/tools/list-captions.d.ts.map +1 -0
  105. package/dist/tools/list-captions.js +60 -0
  106. package/dist/tools/list-captions.js.map +1 -0
  107. package/dist/tools/list-channel-playlists.d.ts +30 -0
  108. package/dist/tools/list-channel-playlists.d.ts.map +1 -0
  109. package/dist/tools/list-channel-playlists.js +44 -0
  110. package/dist/tools/list-channel-playlists.js.map +1 -0
  111. package/dist/tools/query-comments.d.ts +5 -5
  112. package/dist/tools/query-videos.d.ts +5 -5
  113. package/dist/tools/search-channels.d.ts +6 -0
  114. package/dist/tools/search-channels.d.ts.map +1 -1
  115. package/dist/tools/transcribe-video.d.ts +28 -0
  116. package/dist/tools/transcribe-video.d.ts.map +1 -0
  117. package/dist/tools/transcribe-video.js +52 -0
  118. package/dist/tools/transcribe-video.js.map +1 -0
  119. package/dist/utils/brain-fusion.d.ts +24 -0
  120. package/dist/utils/brain-fusion.d.ts.map +1 -0
  121. package/dist/utils/brain-fusion.js +18 -0
  122. package/dist/utils/brain-fusion.js.map +1 -0
  123. package/dist/utils/channel-playlists.d.ts +21 -0
  124. package/dist/utils/channel-playlists.d.ts.map +1 -0
  125. package/dist/utils/channel-playlists.js +56 -0
  126. package/dist/utils/channel-playlists.js.map +1 -0
  127. package/dist/utils/clips.d.ts.map +1 -1
  128. package/dist/utils/clips.js +3 -17
  129. package/dist/utils/clips.js.map +1 -1
  130. package/dist/utils/concat.d.ts +19 -0
  131. package/dist/utils/concat.d.ts.map +1 -0
  132. package/dist/utils/concat.js +135 -0
  133. package/dist/utils/concat.js.map +1 -0
  134. package/dist/utils/contact-sheet.d.ts +28 -0
  135. package/dist/utils/contact-sheet.d.ts.map +1 -0
  136. package/dist/utils/contact-sheet.js +65 -0
  137. package/dist/utils/contact-sheet.js.map +1 -0
  138. package/dist/utils/detail-batch.d.ts +25 -0
  139. package/dist/utils/detail-batch.d.ts.map +1 -0
  140. package/dist/utils/detail-batch.js +58 -0
  141. package/dist/utils/detail-batch.js.map +1 -0
  142. package/dist/utils/errors.d.ts +1 -1
  143. package/dist/utils/errors.d.ts.map +1 -1
  144. package/dist/utils/errors.js +10 -0
  145. package/dist/utils/errors.js.map +1 -1
  146. package/dist/utils/export-data.d.ts +41 -0
  147. package/dist/utils/export-data.d.ts.map +1 -0
  148. package/dist/utils/export-data.js +114 -0
  149. package/dist/utils/export-data.js.map +1 -0
  150. package/dist/utils/export-format.d.ts +23 -0
  151. package/dist/utils/export-format.d.ts.map +1 -0
  152. package/dist/utils/export-format.js +59 -0
  153. package/dist/utils/export-format.js.map +1 -0
  154. package/dist/utils/export-load.d.ts +28 -0
  155. package/dist/utils/export-load.d.ts.map +1 -0
  156. package/dist/utils/export-load.js +70 -0
  157. package/dist/utils/export-load.js.map +1 -0
  158. package/dist/utils/export-rows.d.ts +35 -0
  159. package/dist/utils/export-rows.d.ts.map +1 -0
  160. package/dist/utils/export-rows.js +69 -0
  161. package/dist/utils/export-rows.js.map +1 -0
  162. package/dist/utils/ffmpeg.d.ts +26 -0
  163. package/dist/utils/ffmpeg.d.ts.map +1 -0
  164. package/dist/utils/ffmpeg.js +28 -0
  165. package/dist/utils/ffmpeg.js.map +1 -0
  166. package/dist/utils/format.d.ts +6 -0
  167. package/dist/utils/format.d.ts.map +1 -1
  168. package/dist/utils/format.js +4 -0
  169. package/dist/utils/format.js.map +1 -1
  170. package/dist/utils/frame-plan.d.ts +24 -0
  171. package/dist/utils/frame-plan.d.ts.map +1 -0
  172. package/dist/utils/frame-plan.js +58 -0
  173. package/dist/utils/frame-plan.js.map +1 -0
  174. package/dist/utils/frames.d.ts +32 -0
  175. package/dist/utils/frames.d.ts.map +1 -0
  176. package/dist/utils/frames.js +60 -0
  177. package/dist/utils/frames.js.map +1 -0
  178. package/dist/utils/gif.d.ts +31 -0
  179. package/dist/utils/gif.d.ts.map +1 -0
  180. package/dist/utils/gif.js +77 -0
  181. package/dist/utils/gif.js.map +1 -0
  182. package/dist/utils/harvest-catalog.d.ts +10 -0
  183. package/dist/utils/harvest-catalog.d.ts.map +1 -1
  184. package/dist/utils/harvest-catalog.js +14 -5
  185. package/dist/utils/harvest-catalog.js.map +1 -1
  186. package/dist/utils/harvest-details.d.ts +30 -0
  187. package/dist/utils/harvest-details.d.ts.map +1 -0
  188. package/dist/utils/harvest-details.js +98 -0
  189. package/dist/utils/harvest-details.js.map +1 -0
  190. package/dist/utils/local-transcription.d.ts +14 -0
  191. package/dist/utils/local-transcription.d.ts.map +1 -0
  192. package/dist/utils/local-transcription.js +85 -0
  193. package/dist/utils/local-transcription.js.map +1 -0
  194. package/dist/utils/moment.d.ts +24 -0
  195. package/dist/utils/moment.d.ts.map +1 -0
  196. package/dist/utils/moment.js +22 -0
  197. package/dist/utils/moment.js.map +1 -0
  198. package/dist/utils/most-replayed.d.ts +15 -0
  199. package/dist/utils/most-replayed.d.ts.map +1 -0
  200. package/dist/utils/most-replayed.js +25 -0
  201. package/dist/utils/most-replayed.js.map +1 -0
  202. package/dist/utils/scenes.d.ts +39 -0
  203. package/dist/utils/scenes.d.ts.map +1 -0
  204. package/dist/utils/scenes.js +83 -0
  205. package/dist/utils/scenes.js.map +1 -0
  206. package/dist/utils/seconds.d.ts +5 -0
  207. package/dist/utils/seconds.d.ts.map +1 -0
  208. package/dist/utils/seconds.js +7 -0
  209. package/dist/utils/seconds.js.map +1 -0
  210. package/dist/utils/sponsorblock.d.ts +24 -0
  211. package/dist/utils/sponsorblock.d.ts.map +1 -0
  212. package/dist/utils/sponsorblock.js +153 -0
  213. package/dist/utils/sponsorblock.js.map +1 -0
  214. package/dist/utils/transcript-cache.d.ts +5 -1
  215. package/dist/utils/transcript-cache.d.ts.map +1 -1
  216. package/dist/utils/transcript-cache.js +31 -50
  217. package/dist/utils/transcript-cache.js.map +1 -1
  218. package/dist/utils/transcript-files.d.ts +24 -0
  219. package/dist/utils/transcript-files.d.ts.map +1 -0
  220. package/dist/utils/transcript-files.js +61 -0
  221. package/dist/utils/transcript-files.js.map +1 -0
  222. package/dist/utils/transcript.d.ts +3 -0
  223. package/dist/utils/transcript.d.ts.map +1 -1
  224. package/dist/utils/transcript.js +2 -0
  225. package/dist/utils/transcript.js.map +1 -1
  226. package/dist/utils/upload-check.d.ts +38 -0
  227. package/dist/utils/upload-check.d.ts.map +1 -0
  228. package/dist/utils/upload-check.js +93 -0
  229. package/dist/utils/upload-check.js.map +1 -0
  230. package/dist/utils/validate.d.ts +2 -0
  231. package/dist/utils/validate.d.ts.map +1 -1
  232. package/dist/utils/validate.js +4 -0
  233. package/dist/utils/validate.js.map +1 -1
  234. package/dist/utils/video-detail-store.d.ts +47 -0
  235. package/dist/utils/video-detail-store.d.ts.map +1 -0
  236. package/dist/utils/video-detail-store.js +89 -0
  237. package/dist/utils/video-detail-store.js.map +1 -0
  238. package/dist/utils/video-stats-cache.js +1 -1
  239. package/dist/utils/video-stats-schema.d.ts +187 -0
  240. package/dist/utils/video-stats-schema.d.ts.map +1 -0
  241. package/dist/utils/video-stats-schema.js +50 -0
  242. package/dist/utils/video-stats-schema.js.map +1 -0
  243. package/dist/utils/video-stats-storage.d.ts +16 -0
  244. package/dist/utils/video-stats-storage.d.ts.map +1 -0
  245. package/dist/utils/video-stats-storage.js +37 -0
  246. package/dist/utils/video-stats-storage.js.map +1 -0
  247. package/dist/utils/video-stats.d.ts +23 -127
  248. package/dist/utils/video-stats.d.ts.map +1 -1
  249. package/dist/utils/video-stats.js +42 -102
  250. package/dist/utils/video-stats.js.map +1 -1
  251. package/dist/utils/video-stream.d.ts +21 -0
  252. package/dist/utils/video-stream.d.ts.map +1 -0
  253. package/dist/utils/video-stream.js +36 -0
  254. package/dist/utils/video-stream.js.map +1 -0
  255. package/dist/utils/whisper.d.ts +39 -0
  256. package/dist/utils/whisper.d.ts.map +1 -0
  257. package/dist/utils/whisper.js +137 -0
  258. package/dist/utils/whisper.js.map +1 -0
  259. package/dist/utils/youtube-channel.d.ts +4 -0
  260. package/dist/utils/youtube-channel.d.ts.map +1 -1
  261. package/dist/utils/youtube-channel.js +6 -0
  262. package/dist/utils/youtube-channel.js.map +1 -1
  263. package/dist/utils/youtube-schemas.d.ts +6 -0
  264. package/dist/utils/youtube-schemas.d.ts.map +1 -1
  265. package/dist/utils/youtube-schemas.js +2 -0
  266. package/dist/utils/youtube-schemas.js.map +1 -1
  267. package/package.json +9 -3
@@ -0,0 +1,30 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { Coverage } from '../harvest-schemas.js';
3
+ import { type DetailFailure } from './detail-batch.js';
4
+ import { type YouTubeErrorCode } from './errors.js';
5
+ /**
6
+ * Reading every catalogued video of a channel, and keeping what each said.
7
+ *
8
+ * Resumable by construction: a video is read once, its row says so, and the next run
9
+ * takes the next unread one. What came back before a refusal is kept, because every
10
+ * batch commits on its own.
11
+ */
12
+ export declare const DEFAULT_DETAIL_BATCH = 50;
13
+ export declare const MAX_DETAIL_BATCH = 500;
14
+ export interface DetailsResult {
15
+ channelId: string;
16
+ name: string;
17
+ attempted: number;
18
+ stored: number;
19
+ failures: DetailFailure[];
20
+ /** Videos still unread and still readable. */
21
+ remaining: number;
22
+ /** The error that made the run stop early, when one did. */
23
+ stoppedBecause?: YouTubeErrorCode;
24
+ coverage: Coverage;
25
+ }
26
+ export declare function harvestDetails(database: DatabaseSync, channel: string, options: {
27
+ maxVideos: number;
28
+ retryUnreadable?: boolean;
29
+ }): Promise<DetailsResult>;
30
+ //# sourceMappingURL=harvest-details.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harvest-details.d.ts","sourceRoot":"","sources":["../../src/utils/harvest-details.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAAyC,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC9F,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAQlE;;;;;;GAMG;AAEH,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAQpC,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,wBAAsB,cAAc,CAClC,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GACxD,OAAO,CAAC,aAAa,CAAC,CAmFxB"}
@@ -0,0 +1,98 @@
1
+ import { reportProgress, throwIfAborted } from './context.js';
2
+ import { coverageOf } from './coverage.js';
3
+ import { STOPPING, persist, read, stoppingCode } from './detail-batch.js';
4
+ import { YouTubeError } from './errors.js';
5
+ import { withChannelHarvestLock } from './harvest-lock.js';
6
+ import { readReceipt, saveReceipt } from './harvest-receipts.js';
7
+ import { inTransaction } from './store.js';
8
+ import { catalogCounts, pendingVideoIds, unreadableCount } from './video-detail-store.js';
9
+ import { getChannelInfo } from './youtube-channel.js';
10
+ import { concurrencyState } from './ytdlp.js';
11
+ /**
12
+ * Reading every catalogued video of a channel, and keeping what each said.
13
+ *
14
+ * Resumable by construction: a video is read once, its row says so, and the next run
15
+ * takes the next unread one. What came back before a refusal is kept, because every
16
+ * batch commits on its own.
17
+ */
18
+ export const DEFAULT_DETAIL_BATCH = 50;
19
+ export const MAX_DETAIL_BATCH = 500;
20
+ /** Concurrent reads. Each is a full yt-dlp extraction, the call YouTube throttles hardest. */
21
+ const READ_CONCURRENCY = 4;
22
+ function plural(count, noun) {
23
+ return `${String(count)} ${noun}${count === 1 ? '' : 's'}`;
24
+ }
25
+ export async function harvestDetails(database, channel, options) {
26
+ const info = await getChannelInfo(channel);
27
+ if (catalogCounts(database, info.channelId).videos === 0) {
28
+ throw new YouTubeError('NOT_FOUND', `${info.name} has not been catalogued yet.`, {
29
+ nextStep: 'Call harvest_channel first, so there is a catalogue of videos to read.',
30
+ });
31
+ }
32
+ return withChannelHarvestLock(info.channelId, async () => {
33
+ const startedAt = Date.now();
34
+ const { channelId } = info;
35
+ const queue = pendingVideoIds(database, channelId, options.maxVideos, {
36
+ includeUnreadable: options.retryUnreadable === true,
37
+ });
38
+ const width = Math.max(1, Math.min(concurrencyState().limit, READ_CONCURRENCY));
39
+ const failures = [];
40
+ let stored = 0;
41
+ let attempted = 0;
42
+ let stoppedBecause;
43
+ for (let start = 0; start < queue.length && stoppedBecause === undefined; start += width) {
44
+ throwIfAborted();
45
+ reportProgress(start, queue.length, 'harvest_details');
46
+ const batch = await Promise.all(queue.slice(start, start + width).map(read));
47
+ attempted += batch.length;
48
+ const saved = persist(database, batch);
49
+ stored += saved.stored;
50
+ failures.push(...saved.failures);
51
+ stoppedBecause = stoppingCode(batch);
52
+ }
53
+ const counts = catalogCounts(database, channelId);
54
+ const remaining = pendingVideoIds(database, channelId, counts.videos).length;
55
+ const unreadable = unreadableCount(database, channelId);
56
+ const capBound = attempted >= options.maxVideos && remaining > 0;
57
+ const catalog = readReceipt(database, 'channel-catalog', channelId);
58
+ const stopReason = stoppedBecause === undefined ? undefined : STOPPING[stoppedBecause];
59
+ const coverage = coverageOf({
60
+ scope: 'video-details',
61
+ targetId: channelId,
62
+ have: counts.detailed,
63
+ source: 'yt-dlp -j per catalogued video',
64
+ // The catalogue's size is a fair total only if the catalogue itself is whole.
65
+ ...(catalog?.complete === true
66
+ ? { expected: { value: counts.videos, source: 'store:catalog' } }
67
+ : {}),
68
+ ...(capBound ? { limitApplied: options.maxVideos } : {}),
69
+ ...(stopReason !== undefined
70
+ ? { reason: stopReason }
71
+ : remaining === 0 && (failures.length > 0 || unreadable > 0)
72
+ ? { reason: 'PARTIAL_ERROR' }
73
+ : {}),
74
+ ...(remaining > 0 || stoppedBecause !== undefined
75
+ ? { resumeToken: `details:${channelId}` }
76
+ : {}),
77
+ ...(unreadable > 0
78
+ ? {
79
+ note: `${plural(unreadable, 'video')} cannot be read (private, members-only, age-gated, removed or failing), so this stays incomplete.`,
80
+ }
81
+ : {}),
82
+ });
83
+ inTransaction(database, () => {
84
+ saveReceipt(database, coverage, { startedAt });
85
+ });
86
+ return {
87
+ channelId,
88
+ name: info.name,
89
+ attempted,
90
+ stored,
91
+ failures,
92
+ remaining,
93
+ ...(stoppedBecause === undefined ? {} : { stoppedBecause }),
94
+ coverage,
95
+ };
96
+ });
97
+ }
98
+ //# sourceMappingURL=harvest-details.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harvest-details.js","sourceRoot":"","sources":["../../src/utils/harvest-details.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { type TranscriptResult } from './transcript-cache.js';
2
+ /**
3
+ * Turning a video's audio into a transcript when YouTube has no captions for it.
4
+ *
5
+ * Slow and CPU-heavy by nature, so it only ever runs when asked for, and what it
6
+ * produces is kept: a transcription is deterministic and takes minutes to redo.
7
+ */
8
+ export interface LocalTranscriptionOptions {
9
+ language?: string;
10
+ /** Transcribe again even if a kept transcription exists. */
11
+ refresh?: boolean;
12
+ }
13
+ export declare function transcribeLocally(urlOrId: string, options: LocalTranscriptionOptions): Promise<TranscriptResult>;
14
+ //# sourceMappingURL=local-transcription.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-transcription.d.ts","sourceRoot":"","sources":["../../src/utils/local-transcription.ts"],"names":[],"mappings":"AAYA,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAO/E;;;;;GAKG;AAEH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAcD,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,gBAAgB,CAAC,CAmE3B"}
@@ -0,0 +1,85 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdtemp, rm } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { YouTubeError } from './errors.js';
5
+ import { ensurePrivateDir } from './paths.js';
6
+ import { requireFfmpeg } from './preflight.js';
7
+ import { TRANSCRIPT_DIR, asrPath, readTranscriptFile, writeTranscriptFile, } from './transcript-files.js';
8
+ import { resultFromEntry } from './transcript-cache.js';
9
+ import { TRANSCRIPT_CACHE_VERSION, segmentsToText } from './transcript.js';
10
+ import { assertLanguageTag } from './validate.js';
11
+ import { requireWhisper, runWhisper } from './whisper.js';
12
+ import { extractVideoId, watchUrl } from './youtube-url.js';
13
+ import { TIMEOUTS, runYtDlp } from './ytdlp.js';
14
+ /** What yt-dlp is told to leave behind: whisper reads 16 kHz mono, so make it that size. */
15
+ const AUDIO_ARGS = [
16
+ '-f',
17
+ 'bestaudio',
18
+ '-x',
19
+ '--audio-format',
20
+ 'wav',
21
+ '--postprocessor-args',
22
+ 'ExtractAudio:-ar 16000 -ac 1',
23
+ '--no-playlist',
24
+ ];
25
+ export async function transcribeLocally(urlOrId, options) {
26
+ const language = options.language ?? 'en';
27
+ const videoId = extractVideoId(urlOrId);
28
+ assertLanguageTag(language);
29
+ if (options.refresh !== true) {
30
+ const kept = await readTranscriptFile(asrPath(videoId, language), { expires: false });
31
+ if (kept)
32
+ return resultFromEntry(kept, videoId, 'local-asr');
33
+ }
34
+ // Cheap local checks first: a missing tool should fail now, not after the audio
35
+ // has been downloaded.
36
+ await requireFfmpeg('Local transcription');
37
+ const modelPath = await requireWhisper('Local transcription');
38
+ const tempDir = join(TRANSCRIPT_DIR, 'temp');
39
+ await ensurePrivateDir(tempDir);
40
+ // A folder of its own for this call: yt-dlp leaves the original download and `.part`
41
+ // files beside the wav it makes, and two calls for one video must not share, then
42
+ // delete, each other's audio. The whole folder goes when this call does.
43
+ const workDir = await mkdtemp(join(tempDir, `${videoId}-`));
44
+ const audioBase = join(workDir, 'audio');
45
+ const audioPath = `${audioBase}.wav`;
46
+ try {
47
+ await runYtDlp([...AUDIO_ARGS, '-o', `${audioBase}.%(ext)s`], {
48
+ label: 'transcribe_video',
49
+ timeoutMs: TIMEOUTS.download,
50
+ retry: false,
51
+ target: watchUrl(videoId),
52
+ });
53
+ if (!existsSync(audioPath)) {
54
+ throw new YouTubeError('YTDLP_FAILED', 'yt-dlp did not produce the audio to transcribe.', {
55
+ nextStep: 'Check the video is playable, or call check_health.',
56
+ });
57
+ }
58
+ const segments = await runWhisper(audioPath, language, modelPath, join(workDir, 'whisper'));
59
+ if (segments.length === 0) {
60
+ throw new YouTubeError('NOT_FOUND', 'No speech was found in this video’s audio.', {
61
+ nextStep: 'The audio may be music or silence, or the model may not know this language. Try a larger model or another language.',
62
+ });
63
+ }
64
+ const entry = {
65
+ version: TRANSCRIPT_CACHE_VERSION,
66
+ videoId,
67
+ language,
68
+ fetchedAt: new Date().toISOString(),
69
+ segments,
70
+ };
71
+ await writeTranscriptFile(asrPath(videoId, language), entry);
72
+ return {
73
+ transcript: segmentsToText(segments),
74
+ segments,
75
+ language,
76
+ videoId,
77
+ cached: false,
78
+ source: 'local-asr',
79
+ };
80
+ }
81
+ finally {
82
+ await rm(workDir, { recursive: true, force: true });
83
+ }
84
+ }
85
+ //# sourceMappingURL=local-transcription.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-transcription.js","sourceRoot":"","sources":["../../src/utils/local-transcription.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * A point in a video, in every form a caller needs it.
4
+ *
5
+ * Frames, contact-sheet cells and scene cuts all answer "when", and each used to
6
+ * need the same three spellings of it: seconds to compute with, a clock reading to
7
+ * show a person, and a link that opens the video there.
8
+ */
9
+ export declare const videoMomentSchema: z.ZodObject<{
10
+ timestampSeconds: z.ZodNumber;
11
+ timestamp: z.ZodString;
12
+ url: z.ZodString;
13
+ }, "strip", z.ZodTypeAny, {
14
+ url: string;
15
+ timestamp: string;
16
+ timestampSeconds: number;
17
+ }, {
18
+ url: string;
19
+ timestamp: string;
20
+ timestampSeconds: number;
21
+ }>;
22
+ export type VideoMoment = z.infer<typeof videoMomentSchema>;
23
+ export declare function videoMoment(videoId: string, seconds: number): VideoMoment;
24
+ //# sourceMappingURL=moment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moment.d.ts","sourceRoot":"","sources":["../../src/utils/moment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;GAMG;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;EAI5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,CAMzE"}
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ import { deepLink, formatTimestamp } from './transcript.js';
3
+ /**
4
+ * A point in a video, in every form a caller needs it.
5
+ *
6
+ * Frames, contact-sheet cells and scene cuts all answer "when", and each used to
7
+ * need the same three spellings of it: seconds to compute with, a clock reading to
8
+ * show a person, and a link that opens the video there.
9
+ */
10
+ export const videoMomentSchema = z.object({
11
+ timestampSeconds: z.number(),
12
+ timestamp: z.string().describe('Clock reading, H:MM:SS or M:SS'),
13
+ url: z.string().describe('Link that opens the video at this moment'),
14
+ });
15
+ export function videoMoment(videoId, seconds) {
16
+ return {
17
+ timestampSeconds: seconds,
18
+ timestamp: formatTimestamp(seconds),
19
+ url: deepLink(videoId, seconds),
20
+ };
21
+ }
22
+ //# sourceMappingURL=moment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moment.js","sourceRoot":"","sources":["../../src/utils/moment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAE5D;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE;IAC5B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gCAAgC,CAAC;IAChE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0CAA0C,CAAC;CACrE,CAAC,CAAC;AAIH,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,OAAe;IAC1D,OAAO;QACL,gBAAgB,EAAE,OAAO;QACzB,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC;QACnC,GAAG,EAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;KAChC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The moments of a video people replay most.
3
+ *
4
+ * YouTube's heatmap is a hundred or so equal slices, each scored from 0 to 1. A
5
+ * spike spans several adjacent slices, so taking the highest-scoring slices would
6
+ * return one spike over and over; instead each moment reserves the slices around it.
7
+ */
8
+ export interface HeatSegment {
9
+ startSeconds: number;
10
+ endSeconds: number;
11
+ value: number;
12
+ }
13
+ /** Up to `count` distinct moments, strongest first. */
14
+ export declare function mostReplayed(heatmap: HeatSegment[], count: number): HeatSegment[];
15
+ //# sourceMappingURL=most-replayed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"most-replayed.d.ts","sourceRoot":"","sources":["../../src/utils/most-replayed.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAUD,uDAAuD;AACvD,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,EAAE,CASjF"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The moments of a video people replay most.
3
+ *
4
+ * YouTube's heatmap is a hundred or so equal slices, each scored from 0 to 1. A
5
+ * spike spans several adjacent slices, so taking the highest-scoring slices would
6
+ * return one spike over and over; instead each moment reserves the slices around it.
7
+ */
8
+ /** A moment keeps this many slice-widths clear on either side of it. */
9
+ const SPACING_IN_SEGMENTS = 3;
10
+ function isClearOf(segment, chosen) {
11
+ const reach = SPACING_IN_SEGMENTS * (segment.endSeconds - segment.startSeconds);
12
+ return chosen.every((peak) => Math.abs(segment.startSeconds - peak.startSeconds) >= reach);
13
+ }
14
+ /** Up to `count` distinct moments, strongest first. */
15
+ export function mostReplayed(heatmap, count) {
16
+ const chosen = [];
17
+ for (const segment of [...heatmap].sort((a, b) => b.value - a.value)) {
18
+ if (chosen.length >= count)
19
+ break;
20
+ if (isClearOf(segment, chosen))
21
+ chosen.push(segment);
22
+ }
23
+ return chosen;
24
+ }
25
+ //# sourceMappingURL=most-replayed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"most-replayed.js","sourceRoot":"","sources":["../../src/utils/most-replayed.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,wEAAwE;AACxE,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B,SAAS,SAAS,CAAC,OAAoB,EAAE,MAAqB;IAC5D,MAAM,KAAK,GAAG,mBAAmB,GAAG,CAAC,OAAO,CAAC,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAChF,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,CAAC;AAC7F,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,YAAY,CAAC,OAAsB,EAAE,KAAa;IAChE,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,KAAK,MAAM,OAAO,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACrE,IAAI,MAAM,CAAC,MAAM,IAAI,KAAK;YAAE,MAAM;QAClC,IAAI,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Finding where a video cuts.
3
+ *
4
+ * ffmpeg scores every frame by how different it is from the one before; frames
5
+ * scoring above the threshold are cuts. Detection has to decode every frame of the
6
+ * window, so it reads the lightest stream and only a bounded window at a time.
7
+ */
8
+ /** A scan decodes every frame; this keeps one call to a few minutes of work. */
9
+ export declare const MAX_SCENE_WINDOW_SECONDS = 900;
10
+ export declare const MAX_SCENE_CUTS = 200;
11
+ export declare const DEFAULT_SCENE_THRESHOLD = 0.4;
12
+ export interface ScanWindowRequest {
13
+ start?: number;
14
+ end?: number;
15
+ }
16
+ export interface SceneScanRequest extends ScanWindowRequest {
17
+ /** 0 to 1: how different a frame must be from the last to count as a cut. */
18
+ threshold: number;
19
+ }
20
+ export interface SceneScan {
21
+ videoId: string;
22
+ title: string;
23
+ duration: number;
24
+ start: number;
25
+ end: number;
26
+ threshold: number;
27
+ /** Absolute video time of each cut, ascending. */
28
+ cuts: number[];
29
+ /** True when more cuts were found than are returned. */
30
+ truncated: boolean;
31
+ }
32
+ /** Frames ffmpeg selected, as the times its showinfo filter logged them at. */
33
+ export declare function parseSceneCuts(stderr: string, offset: number): number[];
34
+ export declare function planScanWindow(request: ScanWindowRequest, duration: number): {
35
+ start: number;
36
+ end: number;
37
+ };
38
+ export declare function detectScenes(video: string, request: SceneScanRequest): Promise<SceneScan>;
39
+ //# sourceMappingURL=scenes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenes.d.ts","sourceRoot":"","sources":["../../src/utils/scenes.ts"],"names":[],"mappings":"AAWA;;;;;;GAMG;AAEH,gFAAgF;AAChF,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,cAAc,MAAM,CAAC;AAClC,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAG3C,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,wDAAwD;IACxD,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAUvE;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,iBAAiB,EAC1B,QAAQ,EAAE,MAAM,GACf;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CA0BhC;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CA2C/F"}
@@ -0,0 +1,83 @@
1
+ import { YouTubeError } from './errors.js';
2
+ import { runFfmpeg } from './ffmpeg.js';
3
+ import { roundToMillisecond } from './seconds.js';
4
+ import { requireFfmpeg } from './preflight.js';
5
+ import { FFMPEG_NETWORK_INPUT_ARGS, resolveStreamUrl, SCAN_STREAM_SELECTOR, } from './video-stream.js';
6
+ import { getVideoInfo } from './youtube.js';
7
+ /**
8
+ * Finding where a video cuts.
9
+ *
10
+ * ffmpeg scores every frame by how different it is from the one before; frames
11
+ * scoring above the threshold are cuts. Detection has to decode every frame of the
12
+ * window, so it reads the lightest stream and only a bounded window at a time.
13
+ */
14
+ /** A scan decodes every frame; this keeps one call to a few minutes of work. */
15
+ export const MAX_SCENE_WINDOW_SECONDS = 900;
16
+ export const MAX_SCENE_CUTS = 200;
17
+ export const DEFAULT_SCENE_THRESHOLD = 0.4;
18
+ const SCAN_TIMEOUT_MS = 300_000;
19
+ /** Frames ffmpeg selected, as the times its showinfo filter logged them at. */
20
+ export function parseSceneCuts(stderr, offset) {
21
+ const times = stderr
22
+ .split('\n')
23
+ .filter((line) => line.includes('Parsed_showinfo'))
24
+ .flatMap((line) => {
25
+ const match = /pts_time:(\d+(?:\.\d+)?)/.exec(line);
26
+ return match?.[1] === undefined ? [] : [roundToMillisecond(offset + Number(match[1]))];
27
+ });
28
+ return [...new Set(times)].sort((a, b) => a - b);
29
+ }
30
+ export function planScanWindow(request, duration) {
31
+ const { start = 0 } = request;
32
+ if (duration > 0 && start >= duration) {
33
+ throw new YouTubeError('INVALID_INPUT', `${Math.round(start)}s is past the end of this ${duration}s video.`, { nextStep: 'Pick a start time inside the video.' });
34
+ }
35
+ const requestedEnd = request.end ?? start + MAX_SCENE_WINDOW_SECONDS;
36
+ if (requestedEnd <= start) {
37
+ throw new YouTubeError('INVALID_INPUT', 'end must be later than start.');
38
+ }
39
+ const end = duration > 0 ? Math.min(requestedEnd, duration) : requestedEnd;
40
+ if (end - start > MAX_SCENE_WINDOW_SECONDS) {
41
+ throw new YouTubeError('INVALID_INPUT', `Scene detection reads at most ${MAX_SCENE_WINDOW_SECONDS}s per call.`, { nextStep: 'Scan the video in windows, moving start forward each time.' });
42
+ }
43
+ return { start, end };
44
+ }
45
+ export async function detectScenes(video, request) {
46
+ await requireFfmpeg('Scene detection');
47
+ const info = await getVideoInfo(video);
48
+ const { start, end } = planScanWindow(request, info.duration);
49
+ const { threshold } = request;
50
+ const mediaUrl = await resolveStreamUrl(info.id, SCAN_STREAM_SELECTOR, 'Scene detection');
51
+ const { stderr } = await runFfmpeg([
52
+ ...FFMPEG_NETWORK_INPUT_ARGS,
53
+ // Both before -i: seek there, then read only the window.
54
+ '-ss',
55
+ String(start),
56
+ '-t',
57
+ String(end - start),
58
+ '-i',
59
+ mediaUrl,
60
+ '-an',
61
+ '-vf',
62
+ `select=gt(scene\\,${String(threshold)}),showinfo`,
63
+ '-f',
64
+ 'null',
65
+ '-',
66
+ ], {
67
+ failure: 'ffmpeg could not scan this video for scene cuts.',
68
+ nextStep: 'Try a shorter window, or check that ffmpeg works with network input.',
69
+ timeoutMs: SCAN_TIMEOUT_MS,
70
+ });
71
+ const cuts = parseSceneCuts(stderr, start);
72
+ return {
73
+ videoId: info.id,
74
+ title: info.title,
75
+ duration: info.duration,
76
+ start,
77
+ end,
78
+ threshold,
79
+ cuts: cuts.slice(0, MAX_SCENE_CUTS),
80
+ truncated: cuts.length > MAX_SCENE_CUTS,
81
+ };
82
+ }
83
+ //# sourceMappingURL=scenes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenes.js","sourceRoot":"","sources":["../../src/utils/scenes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EACL,yBAAyB,EACzB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;;;GAMG;AAEH,gFAAgF;AAChF,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAC5C,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,CAAC;AAClC,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAC3C,MAAM,eAAe,GAAG,OAAO,CAAC;AAyBhC,+EAA+E;AAC/E,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,MAAc;IAC3D,MAAM,KAAK,GAAG,MAAM;SACjB,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC;SAClD,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAChB,MAAM,KAAK,GAAG,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpD,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;IAEL,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,OAA0B,EAC1B,QAAgB;IAEhB,MAAM,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC;IAE9B,IAAI,QAAQ,GAAG,CAAC,IAAI,KAAK,IAAI,QAAQ,EAAE,CAAC;QACtC,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,6BAA6B,QAAQ,UAAU,EACnE,EAAE,QAAQ,EAAE,qCAAqC,EAAE,CACpD,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,IAAI,KAAK,GAAG,wBAAwB,CAAC;IACrE,IAAI,YAAY,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,IAAI,YAAY,CAAC,eAAe,EAAE,+BAA+B,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,GAAG,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;IAC3E,IAAI,GAAG,GAAG,KAAK,GAAG,wBAAwB,EAAE,CAAC;QAC3C,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,iCAAiC,wBAAwB,aAAa,EACtE,EAAE,QAAQ,EAAE,4DAA4D,EAAE,CAC3E,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAa,EAAE,OAAyB;IACzE,MAAM,aAAa,CAAC,iBAAiB,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9D,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAE9B,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,EAAE,EAAE,oBAAoB,EAAE,iBAAiB,CAAC,CAAC;IAE1F,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAChC;QACE,GAAG,yBAAyB;QAC5B,yDAAyD;QACzD,KAAK;QACL,MAAM,CAAC,KAAK,CAAC;QACb,IAAI;QACJ,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC;QACnB,IAAI;QACJ,QAAQ;QACR,KAAK;QACL,KAAK;QACL,qBAAqB,MAAM,CAAC,SAAS,CAAC,YAAY;QAClD,IAAI;QACJ,MAAM;QACN,GAAG;KACJ,EACD;QACE,OAAO,EAAE,kDAAkD;QAC3D,QAAQ,EAAE,sEAAsE;QAChF,SAAS,EAAE,eAAe;KAC3B,CACF,CAAC;IAEF,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC3C,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,EAAE;QAChB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,KAAK;QACL,GAAG;QACH,SAAS;QACT,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC;QACnC,SAAS,EAAE,IAAI,CAAC,MAAM,GAAG,cAAc;KACxC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Time in the units the tools speak (seconds) and the units ffmpeg and whisper.cpp write (milliseconds). */
2
+ export declare const MILLISECONDS_PER_SECOND = 1000;
3
+ /** Seconds rounded to the nearest millisecond, which is all the precision any source here has. */
4
+ export declare function roundToMillisecond(seconds: number): number;
5
+ //# sourceMappingURL=seconds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seconds.d.ts","sourceRoot":"","sources":["../../src/utils/seconds.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAE7G,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,kGAAkG;AAClG,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE1D"}
@@ -0,0 +1,7 @@
1
+ /** Time in the units the tools speak (seconds) and the units ffmpeg and whisper.cpp write (milliseconds). */
2
+ export const MILLISECONDS_PER_SECOND = 1000;
3
+ /** Seconds rounded to the nearest millisecond, which is all the precision any source here has. */
4
+ export function roundToMillisecond(seconds) {
5
+ return Math.round(seconds * MILLISECONDS_PER_SECOND) / MILLISECONDS_PER_SECOND;
6
+ }
7
+ //# sourceMappingURL=seconds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seconds.js","sourceRoot":"","sources":["../../src/utils/seconds.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAE7G,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C,kGAAkG;AAClG,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,uBAAuB,CAAC,GAAG,uBAAuB,CAAC;AACjF,CAAC"}
@@ -0,0 +1,24 @@
1
+ /** SponsorBlock's documented categories that mark something a viewer may want to skip. */
2
+ export declare const SPONSOR_CATEGORIES: readonly ["sponsor", "selfpromo", "interaction", "intro", "outro", "preview", "music_offtopic", "filler"];
3
+ export type SponsorCategory = (typeof SPONSOR_CATEGORIES)[number];
4
+ export interface SponsorSegment {
5
+ category: string;
6
+ startSeconds: number;
7
+ endSeconds: number;
8
+ durationSeconds: number;
9
+ /** Community votes: higher is more agreed upon. */
10
+ votes: number;
11
+ }
12
+ /**
13
+ * The length covered by the segments, with overlaps counted once.
14
+ *
15
+ * SponsorBlock categories overlap (a sponsor read inside a self-promotion) and several
16
+ * submissions can mark the same range, so a plain sum of durations is more than the
17
+ * video can hold.
18
+ */
19
+ export declare function coveredSeconds(segments: readonly SponsorSegment[]): number;
20
+ export declare function hashPrefix(videoId: string): string;
21
+ export declare function fetchSponsorSegments(videoId: string, categories?: readonly string[], options?: {
22
+ timeoutMs?: number;
23
+ }): Promise<SponsorSegment[]>;
24
+ //# sourceMappingURL=sponsorblock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sponsorblock.d.ts","sourceRoot":"","sources":["../../src/utils/sponsorblock.ts"],"names":[],"mappings":"AAuBA,0FAA0F;AAC1F,eAAO,MAAM,kBAAkB,2GASrB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;CACf;AAeD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAa1E;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD;AA8DD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,SAAS,MAAM,EAAuB,EAClD,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACnC,OAAO,CAAC,cAAc,EAAE,CAAC,CA6C3B"}