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,153 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { z } from 'zod';
3
+ import { currentContext } from './context.js';
4
+ import { YouTubeError } from './errors.js';
5
+ import { assertVideoId } from './validate.js';
6
+ /**
7
+ * The stretches of a video the community has marked as sponsor reads, intros, outros
8
+ * and the like, from SponsorBlock (https://sponsor.ajay.app), the database the whole
9
+ * ecosystem uses.
10
+ *
11
+ * Its API is queried by the first four hex digits of the video id's SHA-256, never by
12
+ * the id, so the service cannot tell which of the several videos sharing a prefix was
13
+ * asked about. That is the privacy design SponsorBlock documents, and the only way this
14
+ * module asks.
15
+ */
16
+ const API = 'https://sponsor.ajay.app/api/skipSegments';
17
+ const HASH_PREFIX_LENGTH = 4;
18
+ const DEFAULT_TIMEOUT_MS = 15_000;
19
+ /** A hash prefix answers with every video sharing it; far more than this is not plausible. */
20
+ const MAX_RESPONSE_CHARS = 8_000_000;
21
+ /** SponsorBlock's documented categories that mark something a viewer may want to skip. */
22
+ export const SPONSOR_CATEGORIES = [
23
+ 'sponsor',
24
+ 'selfpromo',
25
+ 'interaction',
26
+ 'intro',
27
+ 'outro',
28
+ 'preview',
29
+ 'music_offtopic',
30
+ 'filler',
31
+ ];
32
+ const responseSchema = z.array(z.object({
33
+ videoID: z.string(),
34
+ segments: z.array(z.object({
35
+ category: z.string(),
36
+ segment: z.tuple([z.number(), z.number()]),
37
+ votes: z.number().optional(),
38
+ })),
39
+ }));
40
+ /**
41
+ * The length covered by the segments, with overlaps counted once.
42
+ *
43
+ * SponsorBlock categories overlap (a sponsor read inside a self-promotion) and several
44
+ * submissions can mark the same range, so a plain sum of durations is more than the
45
+ * video can hold.
46
+ */
47
+ export function coveredSeconds(segments) {
48
+ let covered = 0;
49
+ let reachedTo = Number.NEGATIVE_INFINITY;
50
+ for (const { startSeconds, endSeconds } of [...segments].sort((a, b) => a.startSeconds - b.startSeconds)) {
51
+ const from = Math.max(startSeconds, reachedTo);
52
+ if (endSeconds > from)
53
+ covered += endSeconds - from;
54
+ reachedTo = Math.max(reachedTo, endSeconds);
55
+ }
56
+ return covered;
57
+ }
58
+ export function hashPrefix(videoId) {
59
+ return createHash('sha256').update(videoId).digest('hex').slice(0, HASH_PREFIX_LENGTH);
60
+ }
61
+ function failure(message, cause) {
62
+ return new YouTubeError('FETCH_FAILED', message, {
63
+ nextStep: 'SponsorBlock is a community service and may be down. Try again later.',
64
+ retryable: true,
65
+ cause,
66
+ });
67
+ }
68
+ /**
69
+ * The body as text, read as it arrives and abandoned once it passes the cap, so a
70
+ * misbehaving service cannot make this process hold an unbounded response in memory.
71
+ */
72
+ async function readBounded(response) {
73
+ if (response.body === null)
74
+ return '';
75
+ const reader = response.body.getReader();
76
+ const decoder = new TextDecoder();
77
+ let text = '';
78
+ for (;;) {
79
+ const { done, value } = await reader.read();
80
+ if (done)
81
+ return text + decoder.decode();
82
+ text += decoder.decode(value, { stream: true });
83
+ if (text.length > MAX_RESPONSE_CHARS) {
84
+ await reader.cancel();
85
+ throw failure('SponsorBlock answered with too much data.');
86
+ }
87
+ }
88
+ }
89
+ async function request(url, timeoutMs) {
90
+ const timeout = AbortSignal.timeout(timeoutMs);
91
+ const { signal: cancelled } = currentContext();
92
+ const signal = cancelled === undefined ? timeout : AbortSignal.any([cancelled, timeout]);
93
+ try {
94
+ // The host is fixed, so a redirect is never legitimate: refusing it keeps this
95
+ // request on the one origin it was written for.
96
+ const response = await fetch(url, {
97
+ signal,
98
+ redirect: 'error',
99
+ headers: { Accept: 'application/json' },
100
+ });
101
+ return { status: response.status, text: await readBounded(response) };
102
+ }
103
+ catch (error) {
104
+ if (error instanceof YouTubeError)
105
+ throw error;
106
+ if (error instanceof DOMException && error.name === 'TimeoutError') {
107
+ throw new YouTubeError('TIMEOUT', 'SponsorBlock did not answer in time.', {
108
+ nextStep: 'Try again later.',
109
+ retryable: true,
110
+ cause: error,
111
+ });
112
+ }
113
+ if (cancelled?.aborted === true) {
114
+ throw new YouTubeError('CANCELLED', 'The request was cancelled.');
115
+ }
116
+ throw failure('Could not reach SponsorBlock.', error);
117
+ }
118
+ }
119
+ export async function fetchSponsorSegments(videoId, categories = SPONSOR_CATEGORIES, options = {}) {
120
+ assertVideoId(videoId);
121
+ const url = new URL(`${API}/${hashPrefix(videoId)}`);
122
+ url.searchParams.set('categories', JSON.stringify(categories));
123
+ const { status, text } = await request(url, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
124
+ // 404 is how the service says no video sharing the prefix has any segment.
125
+ if (status === 404)
126
+ return [];
127
+ if (status < 200 || status >= 300) {
128
+ throw failure(`SponsorBlock answered with status ${String(status)}.`);
129
+ }
130
+ let json;
131
+ try {
132
+ json = JSON.parse(text);
133
+ }
134
+ catch (error) {
135
+ throw new YouTubeError('MALFORMED_RESPONSE', 'SponsorBlock answered with something that is not JSON.', { cause: error });
136
+ }
137
+ const parsed = responseSchema.safeParse(json);
138
+ if (!parsed.success) {
139
+ throw new YouTubeError('MALFORMED_RESPONSE', 'SponsorBlock answered in a shape this server does not know.', { cause: parsed.error });
140
+ }
141
+ return parsed.data
142
+ .filter((entry) => entry.videoID === videoId)
143
+ .flatMap((entry) => entry.segments)
144
+ .map(({ category, segment: [start, end], votes }) => ({
145
+ category,
146
+ startSeconds: start,
147
+ endSeconds: end,
148
+ durationSeconds: end - start,
149
+ votes: votes ?? 0,
150
+ }))
151
+ .sort((a, b) => a.startSeconds - b.startSeconds);
152
+ }
153
+ //# sourceMappingURL=sponsorblock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sponsorblock.js","sourceRoot":"","sources":["../../src/utils/sponsorblock.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { type TranscriptSegment } from './transcript.js';
1
+ import { type CachedTranscript, type TranscriptSegment, type TranscriptSource } from './transcript.js';
2
+ /** Fetching a transcript from YouTube and keeping it on disk. */
2
3
  export interface TranscriptResult {
3
4
  /** The whole transcript as plain text, unchanged from previous releases. */
4
5
  transcript: string;
@@ -8,6 +9,8 @@ export interface TranscriptResult {
8
9
  videoId: string;
9
10
  /** True when served from the local cache rather than refetched. */
10
11
  cached: boolean;
12
+ /** Where the words came from: YouTube's captions, or a local transcription of the audio. */
13
+ source: TranscriptSource;
11
14
  }
12
15
  export interface GetTranscriptOptions {
13
16
  language?: string;
@@ -16,5 +19,6 @@ export interface GetTranscriptOptions {
16
19
  }
17
20
  /** True when a usable transcript is already on disk, in any language. */
18
21
  export declare function hasCachedTranscript(videoId: string): boolean;
22
+ export declare function resultFromEntry(entry: CachedTranscript, videoId: string, source: TranscriptSource): TranscriptResult;
19
23
  export declare function getTranscript(urlOrId: string, options?: GetTranscriptOptions): Promise<TranscriptResult>;
20
24
  //# sourceMappingURL=transcript-cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transcript-cache.d.ts","sourceRoot":"","sources":["../../src/utils/transcript-cache.ts"],"names":[],"mappings":"AAUA,OAAO,EAML,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;AAOzB,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAqCD,yEAAyE;AACzE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAK5D;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,gBAAgB,CAAC,CAqF3B"}
1
+ {"version":3,"file":"transcript-cache.d.ts","sourceRoot":"","sources":["../../src/utils/transcript-cache.ts"],"names":[],"mappings":"AAeA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC;AAGzB,iEAAiE;AAEjE,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,MAAM,EAAE,OAAO,CAAC;IAChB,4FAA4F;IAC5F,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,yEAAyE;AACzE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAK5D;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,gBAAgB,GACvB,gBAAgB,CASlB;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,gBAAgB,CAAC,CAuF3B"}
@@ -5,67 +5,40 @@ import { YouTubeError } from './errors.js';
5
5
  import { TIMEOUTS, runYtDlp } from './ytdlp.js';
6
6
  import { noCaptionsError } from './caption-probe.js';
7
7
  import { assertLanguageTag } from './validate.js';
8
- import { envInt } from './env.js';
9
- import { dataDir, ensurePrivateDir } from './paths.js';
10
- import { writeJsonAtomic } from './json-file.js';
11
- import { TRANSCRIPT_CACHE_VERSION, cachedTranscriptSchema, parseVtt, segmentsToText, } from './transcript.js';
8
+ import { ensurePrivateDir } from './paths.js';
9
+ import { TRANSCRIPT_DIR, asrPath, captionsPath, readTranscriptFile, writeTranscriptFile, } from './transcript-files.js';
10
+ import { TRANSCRIPT_CACHE_VERSION, parseVtt, segmentsToText, } from './transcript.js';
12
11
  import { extractVideoId, watchUrl } from './youtube-url.js';
13
- /** Fetching a transcript from YouTube and keeping it on disk. */
14
- const CACHE_DIR = dataDir('transcripts');
15
- /** Captions rarely change, but a cache with no expiry is a cache that goes wrong. */
16
- const TRANSCRIPT_TTL_MS = envInt('YOUTUBE_MCP_TRANSCRIPT_TTL_MS', 30 * 86_400_000, { min: 0 });
17
- function cachePath(videoId, language) {
18
- return join(CACHE_DIR, `${videoId}.${language}.json`);
19
- }
20
- async function readCachedTranscript(videoId, language) {
21
- const path = cachePath(videoId, language);
22
- if (!existsSync(path))
23
- return undefined;
24
- try {
25
- const parsed = cachedTranscriptSchema.safeParse(JSON.parse(await readFile(path, 'utf-8')));
26
- // Anything unreadable — an older layout, a truncated write, a hand-edit —
27
- // is treated as absent and refetched, rather than half-trusted.
28
- if (!parsed.success)
29
- return undefined;
30
- const cached = parsed.data;
31
- // A cache written by an older version holds untimed text; regenerate it
32
- // rather than reading it back as if it had timings.
33
- if (cached.version !== TRANSCRIPT_CACHE_VERSION)
34
- return undefined;
35
- const fetchedAt = Date.parse(cached.fetchedAt);
36
- if (Number.isFinite(fetchedAt) && Date.now() - fetchedAt > TRANSCRIPT_TTL_MS)
37
- return undefined;
38
- return cached;
39
- }
40
- catch {
41
- return undefined;
42
- }
43
- }
44
12
  /** True when a usable transcript is already on disk, in any language. */
45
13
  export function hasCachedTranscript(videoId) {
46
- if (!existsSync(CACHE_DIR))
14
+ if (!existsSync(TRANSCRIPT_DIR))
47
15
  return false;
48
- return readdirSync(CACHE_DIR).some((name) => name.startsWith(`${videoId}.`) && name.endsWith('.json'));
16
+ return readdirSync(TRANSCRIPT_DIR).some((name) => name.startsWith(`${videoId}.`) && name.endsWith('.json'));
17
+ }
18
+ export function resultFromEntry(entry, videoId, source) {
19
+ return {
20
+ transcript: segmentsToText(entry.segments),
21
+ segments: entry.segments,
22
+ language: entry.language,
23
+ videoId,
24
+ cached: true,
25
+ source,
26
+ };
49
27
  }
50
28
  export async function getTranscript(urlOrId, options = {}) {
51
29
  const preferredLang = options.language ?? 'en';
52
30
  const videoId = extractVideoId(urlOrId);
53
31
  assertLanguageTag(preferredLang);
54
32
  const url = watchUrl(videoId);
55
- await ensurePrivateDir(CACHE_DIR);
33
+ await ensurePrivateDir(TRANSCRIPT_DIR);
56
34
  if (!options.refresh) {
57
- const cached = await readCachedTranscript(videoId, preferredLang);
58
- if (cached) {
59
- return {
60
- transcript: segmentsToText(cached.segments),
61
- segments: cached.segments,
62
- language: cached.language,
63
- videoId,
64
- cached: true,
65
- };
66
- }
35
+ const cached = await readTranscriptFile(captionsPath(videoId, preferredLang), {
36
+ expires: true,
37
+ });
38
+ if (cached)
39
+ return resultFromEntry(cached, videoId, 'captions');
67
40
  }
68
- const tempDir = join(CACHE_DIR, 'temp');
41
+ const tempDir = join(TRANSCRIPT_DIR, 'temp');
69
42
  await ensurePrivateDir(tempDir);
70
43
  const outputTemplate = join(tempDir, videoId);
71
44
  try {
@@ -103,7 +76,7 @@ export async function getTranscript(urlOrId, options = {}) {
103
76
  fetchedAt: new Date().toISOString(),
104
77
  segments,
105
78
  };
106
- await writeJsonAtomic(cachePath(videoId, preferredLang), entry, { pretty: false });
79
+ await writeTranscriptFile(captionsPath(videoId, preferredLang), entry);
107
80
  await unlink(found.file).catch(() => undefined);
108
81
  return {
109
82
  transcript: segmentsToText(segments),
@@ -111,9 +84,17 @@ export async function getTranscript(urlOrId, options = {}) {
111
84
  language: found.language,
112
85
  videoId,
113
86
  cached: false,
87
+ source: 'captions',
114
88
  };
115
89
  }
116
90
  catch (error) {
91
+ if (error instanceof YouTubeError && error.code === 'NO_CAPTIONS') {
92
+ // A transcription made locally stands in only when the video has no captions
93
+ // at all, so real captions are never displaced by a machine's guess.
94
+ const heard = await readTranscriptFile(asrPath(videoId, preferredLang), { expires: false });
95
+ if (heard)
96
+ return resultFromEntry(heard, videoId, 'local-asr');
97
+ }
117
98
  // Already typed and actionable — pass it through rather than flattening it
118
99
  // into a generic string, which is what the old wrapper did to every failure.
119
100
  if (error instanceof YouTubeError)
@@ -1 +1 @@
1
- {"version":3,"file":"transcript-cache.js","sourceRoot":"","sources":["../../src/utils/transcript-cache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transcript-cache.js","sourceRoot":"","sources":["../../src/utils/transcript-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { type CachedTranscript } from './transcript.js';
2
+ /**
3
+ * Transcripts on disk: where they live and how they are read back.
4
+ *
5
+ * Captions and local transcriptions sit side by side but never overwrite each other:
6
+ * `<id>.<lang>.json` holds what YouTube's captions said, `<id>.<lang>.asr.json` what
7
+ * whisper heard. Real captions are always preferred, so a machine transcription can
8
+ * never replace the better source.
9
+ */
10
+ export declare const TRANSCRIPT_DIR: string;
11
+ export declare function captionsPath(videoId: string, language: string): string;
12
+ export declare function asrPath(videoId: string, language: string): string;
13
+ /**
14
+ * A transcript read back from `path`, or undefined when there is nothing usable.
15
+ *
16
+ * Anything unreadable — an older layout, a truncated write, a hand-edit — counts as
17
+ * absent rather than half-trusted. Captions expire; a local transcription does not,
18
+ * because it is deterministic and costs minutes to redo.
19
+ */
20
+ export declare function readTranscriptFile(path: string, options: {
21
+ expires: boolean;
22
+ }): Promise<CachedTranscript | undefined>;
23
+ export declare function writeTranscriptFile(path: string, entry: CachedTranscript): Promise<void>;
24
+ //# sourceMappingURL=transcript-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-files.d.ts","sourceRoot":"","sources":["../../src/utils/transcript-files.ts"],"names":[],"mappings":"AAMA,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;GAOG;AAEH,eAAO,MAAM,cAAc,QAAyB,CAAC;AAWrD,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,GAC5B,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAwBvC;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9F"}
@@ -0,0 +1,61 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { envInt } from './env.js';
5
+ import { writeJsonAtomic } from './json-file.js';
6
+ import { dataDir } from './paths.js';
7
+ import { TRANSCRIPT_CACHE_VERSION, cachedTranscriptSchema, } from './transcript.js';
8
+ /**
9
+ * Transcripts on disk: where they live and how they are read back.
10
+ *
11
+ * Captions and local transcriptions sit side by side but never overwrite each other:
12
+ * `<id>.<lang>.json` holds what YouTube's captions said, `<id>.<lang>.asr.json` what
13
+ * whisper heard. Real captions are always preferred, so a machine transcription can
14
+ * never replace the better source.
15
+ */
16
+ export const TRANSCRIPT_DIR = dataDir('transcripts');
17
+ /** Captions rarely change, but a cache with no expiry is a cache that goes wrong. */
18
+ const MS_PER_DAY = 86_400_000;
19
+ const DEFAULT_TRANSCRIPT_TTL_DAYS = 30;
20
+ const TRANSCRIPT_TTL_MS = envInt('YOUTUBE_MCP_TRANSCRIPT_TTL_MS', DEFAULT_TRANSCRIPT_TTL_DAYS * MS_PER_DAY, { min: 0 });
21
+ export function captionsPath(videoId, language) {
22
+ return join(TRANSCRIPT_DIR, `${videoId}.${language}.json`);
23
+ }
24
+ export function asrPath(videoId, language) {
25
+ return join(TRANSCRIPT_DIR, `${videoId}.${language}.asr.json`);
26
+ }
27
+ /**
28
+ * A transcript read back from `path`, or undefined when there is nothing usable.
29
+ *
30
+ * Anything unreadable — an older layout, a truncated write, a hand-edit — counts as
31
+ * absent rather than half-trusted. Captions expire; a local transcription does not,
32
+ * because it is deterministic and costs minutes to redo.
33
+ */
34
+ export async function readTranscriptFile(path, options) {
35
+ if (!existsSync(path))
36
+ return undefined;
37
+ try {
38
+ const parsed = cachedTranscriptSchema.safeParse(JSON.parse(await readFile(path, 'utf-8')));
39
+ if (!parsed.success)
40
+ return undefined;
41
+ const cached = parsed.data;
42
+ // A cache written by an older version holds untimed text; regenerate it
43
+ // rather than reading it back as if it had timings.
44
+ if (cached.version !== TRANSCRIPT_CACHE_VERSION)
45
+ return undefined;
46
+ if (options.expires) {
47
+ const fetchedAt = Date.parse(cached.fetchedAt);
48
+ if (Number.isFinite(fetchedAt) && Date.now() - fetchedAt > TRANSCRIPT_TTL_MS) {
49
+ return undefined;
50
+ }
51
+ }
52
+ return cached;
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ }
58
+ export async function writeTranscriptFile(path, entry) {
59
+ await writeJsonAtomic(path, entry, { pretty: false });
60
+ }
61
+ //# sourceMappingURL=transcript-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-files.js","sourceRoot":"","sources":["../../src/utils/transcript-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EACL,wBAAwB,EACxB,sBAAsB,GAEvB,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;GAOG;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;AAErD,qFAAqF;AACrF,MAAM,UAAU,GAAG,UAAU,CAAC;AAC9B,MAAM,2BAA2B,GAAG,EAAE,CAAC;AACvC,MAAM,iBAAiB,GAAG,MAAM,CAC9B,+BAA+B,EAC/B,2BAA2B,GAAG,UAAU,EACxC,EAAE,GAAG,EAAE,CAAC,EAAE,CACX,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,QAAgB;IAC5D,OAAO,IAAI,CAAC,cAAc,EAAE,GAAG,OAAO,IAAI,QAAQ,OAAO,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAe,EAAE,QAAgB;IACvD,OAAO,IAAI,CAAC,cAAc,EAAE,GAAG,OAAO,IAAI,QAAQ,WAAW,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAY,EACZ,OAA6B;IAE7B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAExC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,sBAAsB,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAC3F,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC;QAEtC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC;QAE3B,wEAAwE;QACxE,oDAAoD;QACpD,IAAI,MAAM,CAAC,OAAO,KAAK,wBAAwB;YAAE,OAAO,SAAS,CAAC;QAElE,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC/C,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,iBAAiB,EAAE,CAAC;gBAC7E,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAY,EAAE,KAAuB;IAC7E,MAAM,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;AACxD,CAAC"}
@@ -23,6 +23,9 @@ export declare const transcriptSegmentSchema: z.ZodObject<{
23
23
  start: number;
24
24
  }>;
25
25
  export type TranscriptSegment = z.infer<typeof transcriptSegmentSchema>;
26
+ /** Where a transcript's words came from. A machine's guess must never pass as captions. */
27
+ export declare const TRANSCRIPT_SOURCES: readonly ["captions", "local-asr"];
28
+ export type TranscriptSource = (typeof TRANSCRIPT_SOURCES)[number];
26
29
  /** Bumped whenever the cache representation changes, so old files are refetched. */
27
30
  export declare const TRANSCRIPT_CACHE_VERSION = 2;
28
31
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../src/utils/transcript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;GAQG;AAEH,eAAO,MAAM,uBAAuB;IAClC,2CAA2C;;;;;;;;;;;;EAI3C,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB;;;;;;QApBjC,2CAA2C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0B3C,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,wCAAwC;AACxC,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAS5D;AA0BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA4BhE;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAMpE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAI3E;AAED,iDAAiD;AACjD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,KAAK,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9C,iBAAiB,EAAE,CAOrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAcxF;AAED,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../src/utils/transcript.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;GAQG;AAEH,eAAO,MAAM,uBAAuB;IAClC,2CAA2C;;;;;;;;;;;;EAI3C,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,2FAA2F;AAC3F,eAAO,MAAM,kBAAkB,oCAAqC,CAAC;AACrE,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB;;;;;;QAxBjC,2CAA2C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8B3C,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,wCAAwC;AACxC,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAS5D;AA0BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA4BhE;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAMpE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAI3E;AAED,iDAAiD;AACjD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,KAAK,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9C,iBAAiB,EAAE,CAOrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAcxF;AAED,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC"}
@@ -15,6 +15,8 @@ export const transcriptSegmentSchema = z.object({
15
15
  end: z.number(),
16
16
  text: z.string(),
17
17
  });
18
+ /** Where a transcript's words came from. A machine's guess must never pass as captions. */
19
+ export const TRANSCRIPT_SOURCES = ['captions', 'local-asr'];
18
20
  /** Bumped whenever the cache representation changes, so old files are refetched. */
19
21
  export const TRANSCRIPT_CACHE_VERSION = 2;
20
22
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"transcript.js","sourceRoot":"","sources":["../../src/utils/transcript.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transcript.js","sourceRoot":"","sources":["../../src/utils/transcript.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import { type HarvestTab } from './harvest-catalog.js';
3
+ /**
4
+ * What a channel has published since its catalogue was made.
5
+ *
6
+ * Looks at the newest few videos of each tab and names the ones the store has never
7
+ * seen. It reads and compares; it does not add, so what is new can be inspected
8
+ * before deciding to catalogue it. A channel is not watched: this runs when asked.
9
+ */
10
+ export declare const DEFAULT_LOOKBACK = 30;
11
+ export declare const MAX_LOOKBACK = 200;
12
+ export interface CheckedTab {
13
+ tab: HarvestTab;
14
+ listed: number;
15
+ error?: string;
16
+ }
17
+ export interface NewUpload {
18
+ videoId: string;
19
+ title: string;
20
+ tab: HarvestTab;
21
+ url: string;
22
+ durationSeconds?: number;
23
+ }
24
+ export interface UploadCheck {
25
+ channelId: string;
26
+ name: string;
27
+ handle: string;
28
+ lookback: number;
29
+ checked: CheckedTab[];
30
+ newVideos: NewUpload[];
31
+ /** When the catalogue was last written, so "nothing new" can be read against its age. */
32
+ catalogHarvestedAt?: string;
33
+ catalogComplete: boolean;
34
+ }
35
+ export declare function checkNewUploads(database: DatabaseSync, channel: string, options: {
36
+ lookback: number;
37
+ }): Promise<UploadCheck>;
38
+ //# sourceMappingURL=upload-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-check.d.ts","sourceRoot":"","sources":["../../src/utils/upload-check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,OAAO,EAAsC,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAQ3F;;;;;;GAMG;AAEH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,YAAY,MAAM,CAAC;AAEhC,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,UAAU,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,UAAU,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,yFAAyF;IACzF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;CAC1B;AAkCD,wBAAsB,eAAe,CACnC,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC5B,OAAO,CAAC,WAAW,CAAC,CAwDtB"}
@@ -0,0 +1,93 @@
1
+ import { z } from 'zod';
2
+ import { throwIfAborted } from './context.js';
3
+ import { YouTubeError } from './errors.js';
4
+ import { flatEntrySchema, toVideoListItem } from './flat-listing.js';
5
+ import { HARVEST_TABS, isAbsentTab, listTab } from './harvest-catalog.js';
6
+ import { readReceipt } from './harvest-receipts.js';
7
+ import { queryRows } from './store-rows.js';
8
+ import { catalogCounts } from './video-detail-store.js';
9
+ import { getChannelInfo } from './youtube-channel.js';
10
+ import { watchUrl } from './youtube-url.js';
11
+ import { parseYtDlpJsonLines } from './ytdlp-parse.js';
12
+ /**
13
+ * What a channel has published since its catalogue was made.
14
+ *
15
+ * Looks at the newest few videos of each tab and names the ones the store has never
16
+ * seen. It reads and compares; it does not add, so what is new can be inspected
17
+ * before deciding to catalogue it. A channel is not watched: this runs when asked.
18
+ */
19
+ export const DEFAULT_LOOKBACK = 30;
20
+ export const MAX_LOOKBACK = 200;
21
+ async function listRecent(channelUrl, tab, lookback) {
22
+ try {
23
+ const stdout = await listTab(channelUrl, tab, lookback, `check_new_uploads:${tab}`);
24
+ return { tab, entries: parseYtDlpJsonLines(stdout, flatEntrySchema).map(toVideoListItem) };
25
+ }
26
+ catch (error) {
27
+ if (isAbsentTab(error))
28
+ return { tab, entries: [] };
29
+ const failure = error instanceof YouTubeError
30
+ ? error
31
+ : new YouTubeError('YTDLP_FAILED', String(error), { cause: error });
32
+ return { tab, entries: [], error: failure };
33
+ }
34
+ }
35
+ function knownIds(database, ids) {
36
+ if (ids.length === 0)
37
+ return new Set();
38
+ const placeholders = ids.map(() => '?').join(', ');
39
+ return new Set(queryRows(database.prepare(`SELECT video_id FROM video WHERE video_id IN (${placeholders})`), z.object({ video_id: z.string() }), ...ids).map((row) => row.video_id));
40
+ }
41
+ export async function checkNewUploads(database, channel, options) {
42
+ const { lookback } = options;
43
+ const info = await getChannelInfo(channel);
44
+ if (catalogCounts(database, info.channelId).videos === 0) {
45
+ throw new YouTubeError('NOT_FOUND', `${info.name} has not been catalogued yet.`, {
46
+ nextStep: 'Call harvest_channel first: there is nothing to compare new uploads against.',
47
+ });
48
+ }
49
+ const listed = [];
50
+ for (const tab of HARVEST_TABS) {
51
+ throwIfAborted();
52
+ listed.push(await listRecent(info.channelUrl, tab, lookback));
53
+ }
54
+ const failed = listed.flatMap((tab) => (tab.error === undefined ? [] : [tab.error]));
55
+ const [firstFailure] = failed;
56
+ if (firstFailure !== undefined && failed.length === listed.length)
57
+ throw firstFailure;
58
+ const known = knownIds(database, [
59
+ ...new Set(listed.flatMap((tab) => tab.entries.map((entry) => entry.id))),
60
+ ]);
61
+ const seen = new Set();
62
+ const newVideos = [];
63
+ for (const { tab, entries } of listed) {
64
+ for (const entry of entries) {
65
+ if (known.has(entry.id) || seen.has(entry.id))
66
+ continue;
67
+ seen.add(entry.id);
68
+ newVideos.push({
69
+ videoId: entry.id,
70
+ title: entry.title,
71
+ tab,
72
+ url: watchUrl(entry.id),
73
+ ...(entry.duration > 0 ? { durationSeconds: entry.duration } : {}),
74
+ });
75
+ }
76
+ }
77
+ const receipt = readReceipt(database, 'channel-catalog', info.channelId);
78
+ return {
79
+ channelId: info.channelId,
80
+ name: info.name,
81
+ handle: info.handle,
82
+ lookback,
83
+ checked: listed.map(({ tab, entries, error }) => ({
84
+ tab,
85
+ listed: entries.length,
86
+ ...(error === undefined ? {} : { error: error.message }),
87
+ })),
88
+ newVideos,
89
+ ...(receipt === undefined ? {} : { catalogHarvestedAt: receipt.harvestedAt }),
90
+ catalogComplete: receipt?.complete === true,
91
+ };
92
+ }
93
+ //# sourceMappingURL=upload-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-check.js","sourceRoot":"","sources":["../../src/utils/upload-check.ts"],"names":[],"mappings":"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"}