youtube-knowledge-mcp 3.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -41
- package/dist/brain-schemas.d.ts +10 -0
- package/dist/brain-schemas.d.ts.map +1 -1
- package/dist/harvest-schemas.d.ts +6 -6
- package/dist/harvest-schemas.d.ts.map +1 -1
- package/dist/harvest-schemas.js +5 -0
- package/dist/harvest-schemas.js.map +1 -1
- package/dist/registry/brain-tools.d.ts.map +1 -1
- package/dist/registry/brain-tools.js +16 -0
- package/dist/registry/brain-tools.js.map +1 -1
- package/dist/registry/channel-tools.d.ts.map +1 -1
- package/dist/registry/channel-tools.js +16 -0
- package/dist/registry/channel-tools.js.map +1 -1
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +4 -0
- package/dist/registry/index.js.map +1 -1
- package/dist/registry/media-tools.d.ts.map +1 -1
- package/dist/registry/media-tools.js +31 -0
- package/dist/registry/media-tools.js.map +1 -1
- package/dist/registry/store-tools.d.ts +9 -0
- package/dist/registry/store-tools.d.ts.map +1 -0
- package/dist/registry/store-tools.js +59 -0
- package/dist/registry/store-tools.js.map +1 -0
- package/dist/registry/transcript-tools.d.ts +4 -1
- package/dist/registry/transcript-tools.d.ts.map +1 -1
- package/dist/registry/transcript-tools.js +20 -1
- package/dist/registry/transcript-tools.js.map +1 -1
- package/dist/registry/video-tools.d.ts.map +1 -1
- package/dist/registry/video-tools.js +48 -0
- package/dist/registry/video-tools.js.map +1 -1
- package/dist/registry/vision-tools.d.ts +7 -0
- package/dist/registry/vision-tools.d.ts.map +1 -0
- package/dist/registry/vision-tools.js +55 -0
- package/dist/registry/vision-tools.js.map +1 -0
- package/dist/schemas.d.ts +6 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +2 -0
- package/dist/schemas.js.map +1 -1
- package/dist/thumbnail-schemas.d.ts +10 -0
- package/dist/thumbnail-schemas.d.ts.map +1 -1
- package/dist/tools/ask-brains.d.ts +66 -0
- package/dist/tools/ask-brains.d.ts.map +1 -0
- package/dist/tools/ask-brains.js +111 -0
- package/dist/tools/ask-brains.js.map +1 -0
- package/dist/tools/check-health.d.ts +16 -6
- package/dist/tools/check-health.d.ts.map +1 -1
- package/dist/tools/check-health.js +30 -1
- package/dist/tools/check-health.js.map +1 -1
- package/dist/tools/check-new-uploads.d.ts +55 -0
- package/dist/tools/check-new-uploads.d.ts.map +1 -0
- package/dist/tools/check-new-uploads.js +74 -0
- package/dist/tools/check-new-uploads.js.map +1 -0
- package/dist/tools/clips.d.ts +15 -0
- package/dist/tools/clips.d.ts.map +1 -1
- package/dist/tools/clips.js +5 -5
- package/dist/tools/clips.js.map +1 -1
- package/dist/tools/detect-scenes.d.ts +38 -0
- package/dist/tools/detect-scenes.d.ts.map +1 -0
- package/dist/tools/detect-scenes.js +66 -0
- package/dist/tools/detect-scenes.js.map +1 -0
- package/dist/tools/export-data.d.ts +85 -0
- package/dist/tools/export-data.d.ts.map +1 -0
- package/dist/tools/export-data.js +99 -0
- package/dist/tools/export-data.js.map +1 -0
- package/dist/tools/frames-subtitles.d.ts.map +1 -1
- package/dist/tools/frames-subtitles.js +16 -8
- package/dist/tools/frames-subtitles.js.map +1 -1
- package/dist/tools/frames.d.ts +73 -0
- package/dist/tools/frames.d.ts.map +1 -0
- package/dist/tools/frames.js +136 -0
- package/dist/tools/frames.js.map +1 -0
- package/dist/tools/get-channel-info.d.ts +2 -0
- package/dist/tools/get-channel-info.d.ts.map +1 -1
- package/dist/tools/get-channel-info.js +4 -0
- package/dist/tools/get-channel-info.js.map +1 -1
- package/dist/tools/get-comments.d.ts +5 -5
- package/dist/tools/get-coverage.d.ts +5 -5
- package/dist/tools/get-sponsor-segments.d.ts +49 -0
- package/dist/tools/get-sponsor-segments.d.ts.map +1 -0
- package/dist/tools/get-sponsor-segments.js +61 -0
- package/dist/tools/get-sponsor-segments.js.map +1 -0
- package/dist/tools/get-thumbnail.d.ts.map +1 -1
- package/dist/tools/get-thumbnail.js +2 -1
- package/dist/tools/get-thumbnail.js.map +1 -1
- package/dist/tools/get-transcript.d.ts +1 -0
- package/dist/tools/get-transcript.d.ts.map +1 -1
- package/dist/tools/get-transcript.js +11 -4
- package/dist/tools/get-transcript.js.map +1 -1
- package/dist/tools/get-video-details.d.ts +118 -0
- package/dist/tools/get-video-details.d.ts.map +1 -0
- package/dist/tools/get-video-details.js +147 -0
- package/dist/tools/get-video-details.js.map +1 -0
- package/dist/tools/gif-concat.d.ts +49 -0
- package/dist/tools/gif-concat.d.ts.map +1 -0
- package/dist/tools/gif-concat.js +96 -0
- package/dist/tools/gif-concat.js.map +1 -0
- package/dist/tools/harvest-channel.d.ts +5 -5
- package/dist/tools/harvest-comments.d.ts +5 -5
- package/dist/tools/harvest-details.d.ts +84 -0
- package/dist/tools/harvest-details.d.ts.map +1 -0
- package/dist/tools/harvest-details.js +71 -0
- package/dist/tools/harvest-details.js.map +1 -0
- package/dist/tools/list-captions.d.ts +17 -0
- package/dist/tools/list-captions.d.ts.map +1 -0
- package/dist/tools/list-captions.js +60 -0
- package/dist/tools/list-captions.js.map +1 -0
- package/dist/tools/list-channel-playlists.d.ts +30 -0
- package/dist/tools/list-channel-playlists.d.ts.map +1 -0
- package/dist/tools/list-channel-playlists.js +44 -0
- package/dist/tools/list-channel-playlists.js.map +1 -0
- package/dist/tools/query-comments.d.ts +5 -5
- package/dist/tools/query-videos.d.ts +5 -5
- package/dist/tools/search-channels.d.ts +6 -0
- package/dist/tools/search-channels.d.ts.map +1 -1
- package/dist/tools/transcribe-video.d.ts +28 -0
- package/dist/tools/transcribe-video.d.ts.map +1 -0
- package/dist/tools/transcribe-video.js +52 -0
- package/dist/tools/transcribe-video.js.map +1 -0
- package/dist/utils/brain-fusion.d.ts +24 -0
- package/dist/utils/brain-fusion.d.ts.map +1 -0
- package/dist/utils/brain-fusion.js +18 -0
- package/dist/utils/brain-fusion.js.map +1 -0
- package/dist/utils/channel-playlists.d.ts +21 -0
- package/dist/utils/channel-playlists.d.ts.map +1 -0
- package/dist/utils/channel-playlists.js +56 -0
- package/dist/utils/channel-playlists.js.map +1 -0
- package/dist/utils/clips.d.ts.map +1 -1
- package/dist/utils/clips.js +3 -17
- package/dist/utils/clips.js.map +1 -1
- package/dist/utils/concat.d.ts +19 -0
- package/dist/utils/concat.d.ts.map +1 -0
- package/dist/utils/concat.js +135 -0
- package/dist/utils/concat.js.map +1 -0
- package/dist/utils/contact-sheet.d.ts +28 -0
- package/dist/utils/contact-sheet.d.ts.map +1 -0
- package/dist/utils/contact-sheet.js +65 -0
- package/dist/utils/contact-sheet.js.map +1 -0
- package/dist/utils/detail-batch.d.ts +25 -0
- package/dist/utils/detail-batch.d.ts.map +1 -0
- package/dist/utils/detail-batch.js +58 -0
- package/dist/utils/detail-batch.js.map +1 -0
- package/dist/utils/errors.d.ts +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +10 -0
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/export-data.d.ts +41 -0
- package/dist/utils/export-data.d.ts.map +1 -0
- package/dist/utils/export-data.js +114 -0
- package/dist/utils/export-data.js.map +1 -0
- package/dist/utils/export-format.d.ts +23 -0
- package/dist/utils/export-format.d.ts.map +1 -0
- package/dist/utils/export-format.js +59 -0
- package/dist/utils/export-format.js.map +1 -0
- package/dist/utils/export-load.d.ts +28 -0
- package/dist/utils/export-load.d.ts.map +1 -0
- package/dist/utils/export-load.js +70 -0
- package/dist/utils/export-load.js.map +1 -0
- package/dist/utils/export-rows.d.ts +35 -0
- package/dist/utils/export-rows.d.ts.map +1 -0
- package/dist/utils/export-rows.js +69 -0
- package/dist/utils/export-rows.js.map +1 -0
- package/dist/utils/ffmpeg.d.ts +26 -0
- package/dist/utils/ffmpeg.d.ts.map +1 -0
- package/dist/utils/ffmpeg.js +28 -0
- package/dist/utils/ffmpeg.js.map +1 -0
- package/dist/utils/format.d.ts +6 -0
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +4 -0
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/frame-plan.d.ts +24 -0
- package/dist/utils/frame-plan.d.ts.map +1 -0
- package/dist/utils/frame-plan.js +58 -0
- package/dist/utils/frame-plan.js.map +1 -0
- package/dist/utils/frames.d.ts +32 -0
- package/dist/utils/frames.d.ts.map +1 -0
- package/dist/utils/frames.js +60 -0
- package/dist/utils/frames.js.map +1 -0
- package/dist/utils/gif.d.ts +31 -0
- package/dist/utils/gif.d.ts.map +1 -0
- package/dist/utils/gif.js +77 -0
- package/dist/utils/gif.js.map +1 -0
- package/dist/utils/harvest-catalog.d.ts +10 -0
- package/dist/utils/harvest-catalog.d.ts.map +1 -1
- package/dist/utils/harvest-catalog.js +14 -5
- package/dist/utils/harvest-catalog.js.map +1 -1
- package/dist/utils/harvest-details.d.ts +30 -0
- package/dist/utils/harvest-details.d.ts.map +1 -0
- package/dist/utils/harvest-details.js +98 -0
- package/dist/utils/harvest-details.js.map +1 -0
- package/dist/utils/local-transcription.d.ts +14 -0
- package/dist/utils/local-transcription.d.ts.map +1 -0
- package/dist/utils/local-transcription.js +85 -0
- package/dist/utils/local-transcription.js.map +1 -0
- package/dist/utils/moment.d.ts +24 -0
- package/dist/utils/moment.d.ts.map +1 -0
- package/dist/utils/moment.js +22 -0
- package/dist/utils/moment.js.map +1 -0
- package/dist/utils/most-replayed.d.ts +15 -0
- package/dist/utils/most-replayed.d.ts.map +1 -0
- package/dist/utils/most-replayed.js +25 -0
- package/dist/utils/most-replayed.js.map +1 -0
- package/dist/utils/scenes.d.ts +39 -0
- package/dist/utils/scenes.d.ts.map +1 -0
- package/dist/utils/scenes.js +83 -0
- package/dist/utils/scenes.js.map +1 -0
- package/dist/utils/seconds.d.ts +5 -0
- package/dist/utils/seconds.d.ts.map +1 -0
- package/dist/utils/seconds.js +7 -0
- package/dist/utils/seconds.js.map +1 -0
- package/dist/utils/sponsorblock.d.ts +24 -0
- package/dist/utils/sponsorblock.d.ts.map +1 -0
- package/dist/utils/sponsorblock.js +153 -0
- package/dist/utils/sponsorblock.js.map +1 -0
- package/dist/utils/transcript-cache.d.ts +5 -1
- package/dist/utils/transcript-cache.d.ts.map +1 -1
- package/dist/utils/transcript-cache.js +31 -50
- package/dist/utils/transcript-cache.js.map +1 -1
- package/dist/utils/transcript-files.d.ts +24 -0
- package/dist/utils/transcript-files.d.ts.map +1 -0
- package/dist/utils/transcript-files.js +61 -0
- package/dist/utils/transcript-files.js.map +1 -0
- package/dist/utils/transcript.d.ts +3 -0
- package/dist/utils/transcript.d.ts.map +1 -1
- package/dist/utils/transcript.js +2 -0
- package/dist/utils/transcript.js.map +1 -1
- package/dist/utils/upload-check.d.ts +38 -0
- package/dist/utils/upload-check.d.ts.map +1 -0
- package/dist/utils/upload-check.js +93 -0
- package/dist/utils/upload-check.js.map +1 -0
- package/dist/utils/validate.d.ts +2 -0
- package/dist/utils/validate.d.ts.map +1 -1
- package/dist/utils/validate.js +4 -0
- package/dist/utils/validate.js.map +1 -1
- package/dist/utils/video-detail-store.d.ts +47 -0
- package/dist/utils/video-detail-store.d.ts.map +1 -0
- package/dist/utils/video-detail-store.js +89 -0
- package/dist/utils/video-detail-store.js.map +1 -0
- package/dist/utils/video-stats-cache.js +1 -1
- package/dist/utils/video-stats-schema.d.ts +187 -0
- package/dist/utils/video-stats-schema.d.ts.map +1 -0
- package/dist/utils/video-stats-schema.js +50 -0
- package/dist/utils/video-stats-schema.js.map +1 -0
- package/dist/utils/video-stats-storage.d.ts +16 -0
- package/dist/utils/video-stats-storage.d.ts.map +1 -0
- package/dist/utils/video-stats-storage.js +37 -0
- package/dist/utils/video-stats-storage.js.map +1 -0
- package/dist/utils/video-stats.d.ts +23 -127
- package/dist/utils/video-stats.d.ts.map +1 -1
- package/dist/utils/video-stats.js +42 -102
- package/dist/utils/video-stats.js.map +1 -1
- package/dist/utils/video-stream.d.ts +21 -0
- package/dist/utils/video-stream.d.ts.map +1 -0
- package/dist/utils/video-stream.js +36 -0
- package/dist/utils/video-stream.js.map +1 -0
- package/dist/utils/whisper.d.ts +39 -0
- package/dist/utils/whisper.d.ts.map +1 -0
- package/dist/utils/whisper.js +137 -0
- package/dist/utils/whisper.js.map +1 -0
- package/dist/utils/youtube-channel.d.ts +4 -0
- package/dist/utils/youtube-channel.d.ts.map +1 -1
- package/dist/utils/youtube-channel.js +6 -0
- package/dist/utils/youtube-channel.js.map +1 -1
- package/dist/utils/youtube-schemas.d.ts +6 -0
- package/dist/utils/youtube-schemas.d.ts.map +1 -1
- package/dist/utils/youtube-schemas.js +2 -0
- package/dist/utils/youtube-schemas.js.map +1 -1
- package/package.json +9 -3
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const VIDEO_COLUMNS = [
|
|
3
|
+
{ header: 'videoId', value: (row) => row.videoId, verbatim: true },
|
|
4
|
+
{ header: 'channelId', value: (row) => row.channelId, verbatim: true },
|
|
5
|
+
{ header: 'title', value: (row) => row.title },
|
|
6
|
+
{ header: 'durationSeconds', value: (row) => row.durationSeconds },
|
|
7
|
+
{ header: 'uploadDate', value: (row) => row.uploadDate },
|
|
8
|
+
{ header: 'viewCount', value: (row) => row.viewCount },
|
|
9
|
+
{ header: 'commentCount', value: (row) => row.commentCount },
|
|
10
|
+
{ header: 'tab', value: (row) => row.tab },
|
|
11
|
+
{ header: 'alsoInTabs', value: (row) => row.alsoInTabs.join(' ') },
|
|
12
|
+
{ header: 'detailed', value: (row) => row.detailed },
|
|
13
|
+
{ header: 'storedComments', value: (row) => row.storedComments },
|
|
14
|
+
];
|
|
15
|
+
export const COMMENT_COLUMNS = [
|
|
16
|
+
{ header: 'commentId', value: (row) => row.commentId, verbatim: true },
|
|
17
|
+
{ header: 'videoId', value: (row) => row.videoId, verbatim: true },
|
|
18
|
+
{ header: 'parentId', value: (row) => row.parentId, verbatim: true },
|
|
19
|
+
{ header: 'author', value: (row) => row.author },
|
|
20
|
+
{ header: 'authorId', value: (row) => row.authorId, verbatim: true },
|
|
21
|
+
{ header: 'likeCount', value: (row) => row.likeCount },
|
|
22
|
+
{ header: 'isPinned', value: (row) => row.isPinned },
|
|
23
|
+
{ header: 'isUploader', value: (row) => row.isUploader },
|
|
24
|
+
{ header: 'isVerified', value: (row) => row.isVerified },
|
|
25
|
+
{ header: 'publishedAt', value: (row) => row.publishedAt },
|
|
26
|
+
{ header: 'text', value: (row) => row.text },
|
|
27
|
+
];
|
|
28
|
+
/** The tabs column is JSON written by SQLite; a damaged value reads as no extra tabs. */
|
|
29
|
+
function alsoInTabs(json) {
|
|
30
|
+
try {
|
|
31
|
+
return z.array(z.string()).catch([]).parse(JSON.parse(json));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function toVideoRow(row) {
|
|
38
|
+
return {
|
|
39
|
+
videoId: row.video_id,
|
|
40
|
+
...(row.channel_id === null ? {} : { channelId: row.channel_id }),
|
|
41
|
+
title: row.title,
|
|
42
|
+
...(row.duration_s === null ? {} : { durationSeconds: row.duration_s }),
|
|
43
|
+
...(row.upload_date === null ? {} : { uploadDate: row.upload_date }),
|
|
44
|
+
...(row.view_count === null ? {} : { viewCount: row.view_count }),
|
|
45
|
+
...(row.comment_count === null ? {} : { commentCount: row.comment_count }),
|
|
46
|
+
...(row.tab === null ? {} : { tab: row.tab }),
|
|
47
|
+
alsoInTabs: alsoInTabs(row.also_in_tabs),
|
|
48
|
+
detailed: row.detail_at !== null,
|
|
49
|
+
storedComments: row.stored_comments,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function toCommentRow(row) {
|
|
53
|
+
return {
|
|
54
|
+
commentId: row.comment_id,
|
|
55
|
+
videoId: row.video_id,
|
|
56
|
+
...(row.parent_id === null ? {} : { parentId: row.parent_id }),
|
|
57
|
+
author: row.author,
|
|
58
|
+
...(row.author_id === null ? {} : { authorId: row.author_id }),
|
|
59
|
+
likeCount: row.like_count,
|
|
60
|
+
isPinned: row.is_pinned,
|
|
61
|
+
isUploader: row.author_is_uploader,
|
|
62
|
+
isVerified: row.author_is_verified,
|
|
63
|
+
...(row.published_at === null
|
|
64
|
+
? {}
|
|
65
|
+
: { publishedAt: new Date(row.published_at * 1000).toISOString() }),
|
|
66
|
+
text: row.text,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=export-rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export-rows.js","sourceRoot":"","sources":["../../src/utils/export-rows.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place this server runs ffmpeg.
|
|
3
|
+
*
|
|
4
|
+
* Every tool that looks at or cuts a video ends in an ffmpeg process, and each needs
|
|
5
|
+
* the same three things: it must stop when the client cancels the request, it must stop
|
|
6
|
+
* after a time budget, and its failure must reach the caller as a typed, actionable error
|
|
7
|
+
* rather than a raw process error. Kept here, a tool cannot forget one of them.
|
|
8
|
+
*/
|
|
9
|
+
/** JPEG quality on ffmpeg's 2 (best) to 31 (worst) scale. */
|
|
10
|
+
export declare const FFMPEG_JPEG_QUALITY = "4";
|
|
11
|
+
export interface FfmpegRun {
|
|
12
|
+
/** What was being done, as the failure message: "ffmpeg could not make that GIF." */
|
|
13
|
+
failure: string;
|
|
14
|
+
/** What the caller should try next. */
|
|
15
|
+
nextStep: string;
|
|
16
|
+
/** Milliseconds before the process is killed. 0 disables the budget. */
|
|
17
|
+
timeoutMs: number;
|
|
18
|
+
/** Bytes written to ffmpeg's standard input. */
|
|
19
|
+
input?: Uint8Array;
|
|
20
|
+
}
|
|
21
|
+
export interface FfmpegOutput {
|
|
22
|
+
stdout: Buffer;
|
|
23
|
+
stderr: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function runFfmpeg(args: string[], run: FfmpegRun): Promise<FfmpegOutput>;
|
|
26
|
+
//# sourceMappingURL=ffmpeg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../src/utils/ffmpeg.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AAEH,6DAA6D;AAC7D,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC,MAAM,WAAW,SAAS;IACxB,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CAYrF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import { currentContext } from './context.js';
|
|
3
|
+
import { YouTubeError } from './errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* The one place this server runs ffmpeg.
|
|
6
|
+
*
|
|
7
|
+
* Every tool that looks at or cuts a video ends in an ffmpeg process, and each needs
|
|
8
|
+
* the same three things: it must stop when the client cancels the request, it must stop
|
|
9
|
+
* after a time budget, and its failure must reach the caller as a typed, actionable error
|
|
10
|
+
* rather than a raw process error. Kept here, a tool cannot forget one of them.
|
|
11
|
+
*/
|
|
12
|
+
/** JPEG quality on ffmpeg's 2 (best) to 31 (worst) scale. */
|
|
13
|
+
export const FFMPEG_JPEG_QUALITY = '4';
|
|
14
|
+
export async function runFfmpeg(args, run) {
|
|
15
|
+
try {
|
|
16
|
+
const result = await execa('ffmpeg', args, {
|
|
17
|
+
encoding: 'buffer',
|
|
18
|
+
timeout: run.timeoutMs,
|
|
19
|
+
cancelSignal: currentContext().signal,
|
|
20
|
+
...(run.input === undefined ? {} : { input: run.input }),
|
|
21
|
+
});
|
|
22
|
+
return { stdout: Buffer.from(result.stdout), stderr: Buffer.from(result.stderr).toString() };
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw new YouTubeError('YTDLP_FAILED', run.failure, { nextStep: run.nextStep, cause: error });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=ffmpeg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../../src/utils/ffmpeg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;GAOG;AAEH,6DAA6D;AAC7D,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAkBvC,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAc,EAAE,GAAc;IAC5D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE;YACzC,QAAQ,EAAE,QAAQ;YAClB,OAAO,EAAE,GAAG,CAAC,SAAS;YACtB,YAAY,EAAE,cAAc,EAAE,CAAC,MAAM;YACrC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC;SACzD,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;IAC/F,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,YAAY,CAAC,cAAc,EAAE,GAAG,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAChG,CAAC;AACH,CAAC"}
|
package/dist/utils/format.d.ts
CHANGED
|
@@ -44,6 +44,12 @@ export declare function fileResourceLink(filePath: string, name: string, mimeTyp
|
|
|
44
44
|
name: string;
|
|
45
45
|
mimeType?: string;
|
|
46
46
|
};
|
|
47
|
+
/** A picture the model can look at, as an MCP image block. */
|
|
48
|
+
export declare function imageBlock(bytes: Uint8Array, mimeType: string): {
|
|
49
|
+
type: 'image';
|
|
50
|
+
data: string;
|
|
51
|
+
mimeType: string;
|
|
52
|
+
};
|
|
47
53
|
/** A result that both reads well and points at the file it produced. */
|
|
48
54
|
export declare function fileResult(text: string, structured: Record<string, unknown>, file: {
|
|
49
55
|
path: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKjD;AAOD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,cAAc,CAK5F;AAED,wEAAwE;AACxE,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,SAAS;IACxB,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAU,EAAE,EAAE,SAAS,GAAG,QAAQ,CAS1E;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,GAChB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAOzE;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACtD,cAAc,CAQhB"}
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKjD;AAOD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,cAAc,CAK5F;AAED,wEAAwE;AACxE,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,SAAS;IACxB,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAU,EAAE,EAAE,SAAS,GAAG,QAAQ,CAS1E;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,GAChB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAOzE;AAED,8DAA8D;AAC9D,wBAAgB,UAAU,CACxB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAEnD;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACtD,cAAc,CAQhB"}
|
package/dist/utils/format.js
CHANGED
|
@@ -59,6 +59,10 @@ export function fileResourceLink(filePath, name, mimeType) {
|
|
|
59
59
|
...(mimeType === undefined ? {} : { mimeType }),
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
+
/** A picture the model can look at, as an MCP image block. */
|
|
63
|
+
export function imageBlock(bytes, mimeType) {
|
|
64
|
+
return { type: 'image', data: Buffer.from(bytes).toString('base64'), mimeType };
|
|
65
|
+
}
|
|
62
66
|
/** A result that both reads well and points at the file it produced. */
|
|
63
67
|
export function fileResult(text, structured, file) {
|
|
64
68
|
return {
|
package/dist/utils/format.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deciding which moments of a video to look at.
|
|
3
|
+
*
|
|
4
|
+
* Pure, so every rule about what is a valid request lives in one place and is
|
|
5
|
+
* testable without a video: the tools that take pictures only ever receive a
|
|
6
|
+
* sorted list of timestamps that are known to exist.
|
|
7
|
+
*/
|
|
8
|
+
export interface FramePlanRequest {
|
|
9
|
+
/** Exact moments, in seconds. Takes precedence over `count`. */
|
|
10
|
+
timestamps?: number[];
|
|
11
|
+
/** How many frames to spread evenly across the window. */
|
|
12
|
+
count?: number;
|
|
13
|
+
/** Window start in seconds. Default: 0. */
|
|
14
|
+
start?: number;
|
|
15
|
+
/** Window end in seconds. Default: the end of the video. */
|
|
16
|
+
end?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The sorted timestamps to capture.
|
|
20
|
+
*
|
|
21
|
+
* `duration` of 0 means unknown, in which case nothing can be bounded by it.
|
|
22
|
+
*/
|
|
23
|
+
export declare function planTimestamps(request: FramePlanRequest, duration: number, max: number): number[];
|
|
24
|
+
//# sourceMappingURL=frame-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-plan.d.ts","sourceRoot":"","sources":["../../src/utils/frame-plan.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAmDD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAcjG"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { YouTubeError } from './errors.js';
|
|
2
|
+
import { roundToMillisecond } from './seconds.js';
|
|
3
|
+
function pastTheEnd(seconds, duration) {
|
|
4
|
+
return new YouTubeError('INVALID_INPUT', `${Math.round(seconds)}s is past the end of this ${duration}s video.`, { nextStep: 'Pick a moment inside the video.' });
|
|
5
|
+
}
|
|
6
|
+
function tooMany(max) {
|
|
7
|
+
return new YouTubeError('INVALID_INPUT', `Ask for at most ${max} frames per call.`, {
|
|
8
|
+
nextStep: 'Narrow the window, or make several calls.',
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function explicitTimestamps(timestamps, duration, max) {
|
|
12
|
+
const unique = [...new Set(timestamps)].sort((a, b) => a - b);
|
|
13
|
+
if (unique.length > max)
|
|
14
|
+
throw tooMany(max);
|
|
15
|
+
const beyond = unique.find((seconds) => duration > 0 && seconds >= duration);
|
|
16
|
+
if (beyond !== undefined)
|
|
17
|
+
throw pastTheEnd(beyond, duration);
|
|
18
|
+
return unique;
|
|
19
|
+
}
|
|
20
|
+
function spacedTimestamps(request, duration, max) {
|
|
21
|
+
const { count, start = 0 } = request;
|
|
22
|
+
if (count === undefined || count < 1) {
|
|
23
|
+
throw new YouTubeError('INVALID_INPUT', 'Pass at least 1 for count.');
|
|
24
|
+
}
|
|
25
|
+
if (count > max)
|
|
26
|
+
throw tooMany(max);
|
|
27
|
+
if (duration > 0 && start >= duration)
|
|
28
|
+
throw pastTheEnd(start, duration);
|
|
29
|
+
const requestedEnd = request.end ?? duration;
|
|
30
|
+
if (requestedEnd <= 0) {
|
|
31
|
+
throw new YouTubeError('INVALID_INPUT', 'Pass end when the video duration is unknown.');
|
|
32
|
+
}
|
|
33
|
+
if (requestedEnd <= start) {
|
|
34
|
+
throw new YouTubeError('INVALID_INPUT', 'end must be later than start.');
|
|
35
|
+
}
|
|
36
|
+
const end = duration > 0 ? Math.min(requestedEnd, duration) : requestedEnd;
|
|
37
|
+
const slice = (end - start) / count;
|
|
38
|
+
// Midpoints: the first frame of a video is often black and the last may not exist.
|
|
39
|
+
return Array.from({ length: count }, (_, index) => roundToMillisecond(start + (index + 0.5) * slice));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The sorted timestamps to capture.
|
|
43
|
+
*
|
|
44
|
+
* `duration` of 0 means unknown, in which case nothing can be bounded by it.
|
|
45
|
+
*/
|
|
46
|
+
export function planTimestamps(request, duration, max) {
|
|
47
|
+
const { timestamps, count } = request;
|
|
48
|
+
if (timestamps !== undefined && timestamps.length > 0) {
|
|
49
|
+
return explicitTimestamps(timestamps, duration, max);
|
|
50
|
+
}
|
|
51
|
+
if (count === undefined) {
|
|
52
|
+
throw new YouTubeError('INVALID_INPUT', 'Provide timestamps or a count.', {
|
|
53
|
+
nextStep: 'Pass timestamps for exact moments, or count to spread frames across the video.',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return spacedTimestamps(request, duration, max);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=frame-plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-plan.js","sourceRoot":"","sources":["../../src/utils/frame-plan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAqBlD,SAAS,UAAU,CAAC,OAAe,EAAE,QAAgB;IACnD,OAAO,IAAI,YAAY,CACrB,eAAe,EACf,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,6BAA6B,QAAQ,UAAU,EACrE,EAAE,QAAQ,EAAE,iCAAiC,EAAE,CAChD,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,GAAW;IAC1B,OAAO,IAAI,YAAY,CAAC,eAAe,EAAE,mBAAmB,GAAG,mBAAmB,EAAE;QAClF,QAAQ,EAAE,2CAA2C;KACtD,CAAC,CAAC;AACL,CAAC;AAED,SAAS,kBAAkB,CAAC,UAAoB,EAAE,QAAgB,EAAE,GAAW;IAC7E,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9D,IAAI,MAAM,CAAC,MAAM,GAAG,GAAG;QAAE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IAE5C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,GAAG,CAAC,IAAI,OAAO,IAAI,QAAQ,CAAC,CAAC;IAC7E,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAE7D,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAyB,EAAE,QAAgB,EAAE,GAAW;IAChF,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC;IACrC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,YAAY,CAAC,eAAe,EAAE,4BAA4B,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,KAAK,GAAG,GAAG;QAAE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,QAAQ,GAAG,CAAC,IAAI,KAAK,IAAI,QAAQ;QAAE,MAAM,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAEzE,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,IAAI,QAAQ,CAAC;IAC7C,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,YAAY,CAAC,eAAe,EAAE,8CAA8C,CAAC,CAAC;IAC1F,CAAC;IACD,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,MAAM,KAAK,GAAG,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC;IAEpC,mFAAmF;IACnF,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAChD,kBAAkB,CAAC,KAAK,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC,CAClD,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,OAAyB,EAAE,QAAgB,EAAE,GAAW;IACrF,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAEtC,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtD,OAAO,kBAAkB,CAAC,UAAU,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,YAAY,CAAC,eAAe,EAAE,gCAAgC,EAAE;YACxE,QAAQ,EAAE,gFAAgF;SAC3F,CAAC,CAAC;IACL,CAAC;IAED,OAAO,gBAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type FramePlanRequest } from './frame-plan.js';
|
|
2
|
+
/**
|
|
3
|
+
* Looking at a video: stills captured into memory, never onto disk.
|
|
4
|
+
*
|
|
5
|
+
* A picture returned inline is something the model can read, which is what makes
|
|
6
|
+
* "what is on screen at 3:20" answerable without a human in the loop. Nothing is
|
|
7
|
+
* written, so the same code serves a contact sheet and a handful of stills.
|
|
8
|
+
*/
|
|
9
|
+
/** Each frame is an inline image, so a call is bounded by what a reply can carry. */
|
|
10
|
+
export declare const MAX_FRAMES_PER_CALL = 12;
|
|
11
|
+
/** Wide enough to read slide text, small enough to be about 100 KB a frame. */
|
|
12
|
+
export declare const DEFAULT_FRAME_WIDTH = 768;
|
|
13
|
+
export interface CapturedFrame {
|
|
14
|
+
timestamp: number;
|
|
15
|
+
bytes: Buffer;
|
|
16
|
+
}
|
|
17
|
+
export interface FrameBatch {
|
|
18
|
+
videoId: string;
|
|
19
|
+
title: string;
|
|
20
|
+
duration: number;
|
|
21
|
+
frames: CapturedFrame[];
|
|
22
|
+
}
|
|
23
|
+
export interface CaptureOptions {
|
|
24
|
+
/** Frames are shrunk to this width, never enlarged. */
|
|
25
|
+
width: number;
|
|
26
|
+
maxFrames: number;
|
|
27
|
+
/** The tool asking, for the ffmpeg preflight message and yt-dlp logs. */
|
|
28
|
+
label: string;
|
|
29
|
+
}
|
|
30
|
+
/** Capture the frames a request describes, in timestamp order. */
|
|
31
|
+
export declare function captureFrames(video: string, request: FramePlanRequest, options: CaptureOptions): Promise<FrameBatch>;
|
|
32
|
+
//# sourceMappingURL=frames.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frames.d.ts","sourceRoot":"","sources":["../../src/utils/frames.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAUxE;;;;;;GAMG;AAEH,qFAAqF;AACrF,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAKvC,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;CACf;AAqCD,kEAAkE;AAClE,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,UAAU,CAAC,CAerB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { inBatches } from './batches.js';
|
|
2
|
+
import { YouTubeError } from './errors.js';
|
|
3
|
+
import { FFMPEG_JPEG_QUALITY, runFfmpeg } from './ffmpeg.js';
|
|
4
|
+
import { planTimestamps } from './frame-plan.js';
|
|
5
|
+
import { requireFfmpeg } from './preflight.js';
|
|
6
|
+
import { FFMPEG_NETWORK_INPUT_ARGS, PREVIEW_STREAM_SELECTOR, resolveStreamUrl, } from './video-stream.js';
|
|
7
|
+
import { TIMEOUTS } from './ytdlp.js';
|
|
8
|
+
import { getVideoInfo } from './youtube.js';
|
|
9
|
+
/**
|
|
10
|
+
* Looking at a video: stills captured into memory, never onto disk.
|
|
11
|
+
*
|
|
12
|
+
* A picture returned inline is something the model can read, which is what makes
|
|
13
|
+
* "what is on screen at 3:20" answerable without a human in the loop. Nothing is
|
|
14
|
+
* written, so the same code serves a contact sheet and a handful of stills.
|
|
15
|
+
*/
|
|
16
|
+
/** Each frame is an inline image, so a call is bounded by what a reply can carry. */
|
|
17
|
+
export const MAX_FRAMES_PER_CALL = 12;
|
|
18
|
+
/** Wide enough to read slide text, small enough to be about 100 KB a frame. */
|
|
19
|
+
export const DEFAULT_FRAME_WIDTH = 768;
|
|
20
|
+
/** Concurrent ffmpeg processes. Each is one ranged request against the same stream. */
|
|
21
|
+
const CAPTURE_CONCURRENCY = 4;
|
|
22
|
+
async function captureOne(mediaUrl, timestamp, width) {
|
|
23
|
+
const failure = `ffmpeg could not capture the frame at ${String(timestamp)}s.`;
|
|
24
|
+
const nextStep = 'Try a different timestamp, or check that ffmpeg works with network input.';
|
|
25
|
+
const { stdout } = await runFfmpeg([
|
|
26
|
+
...FFMPEG_NETWORK_INPUT_ARGS,
|
|
27
|
+
// -ss before -i seeks without decoding everything up to that point.
|
|
28
|
+
'-ss',
|
|
29
|
+
String(timestamp),
|
|
30
|
+
'-i',
|
|
31
|
+
mediaUrl,
|
|
32
|
+
'-frames:v',
|
|
33
|
+
'1',
|
|
34
|
+
'-vf',
|
|
35
|
+
`scale=min(${String(width)}\\,iw):-2`,
|
|
36
|
+
'-q:v',
|
|
37
|
+
FFMPEG_JPEG_QUALITY,
|
|
38
|
+
'-f',
|
|
39
|
+
'image2pipe',
|
|
40
|
+
'-c:v',
|
|
41
|
+
'mjpeg',
|
|
42
|
+
'pipe:1',
|
|
43
|
+
], { failure, nextStep, timeoutMs: TIMEOUTS.transcript });
|
|
44
|
+
if (stdout.byteLength === 0)
|
|
45
|
+
throw new YouTubeError('YTDLP_FAILED', failure, { nextStep });
|
|
46
|
+
return { timestamp, bytes: stdout };
|
|
47
|
+
}
|
|
48
|
+
/** Capture the frames a request describes, in timestamp order. */
|
|
49
|
+
export async function captureFrames(video, request, options) {
|
|
50
|
+
const { width, maxFrames, label } = options;
|
|
51
|
+
// Cheap local checks first: a missing ffmpeg or an impossible request should
|
|
52
|
+
// fail before a single network round trip.
|
|
53
|
+
await requireFfmpeg(label);
|
|
54
|
+
const info = await getVideoInfo(video);
|
|
55
|
+
const timestamps = planTimestamps(request, info.duration, maxFrames);
|
|
56
|
+
const mediaUrl = await resolveStreamUrl(info.id, PREVIEW_STREAM_SELECTOR, label);
|
|
57
|
+
const frames = await inBatches(timestamps, CAPTURE_CONCURRENCY, (timestamp) => captureOne(mediaUrl, timestamp, width));
|
|
58
|
+
return { videoId: info.id, title: info.title, duration: info.duration, frames };
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=frames.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frames.js","sourceRoot":"","sources":["../../src/utils/frames.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAyB,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;;;GAMG;AAEH,qFAAqF;AACrF,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AACtC,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEvC,uFAAuF;AACvF,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAsB9B,KAAK,UAAU,UAAU,CACvB,QAAgB,EAChB,SAAiB,EACjB,KAAa;IAEb,MAAM,OAAO,GAAG,yCAAyC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC;IAC/E,MAAM,QAAQ,GAAG,2EAA2E,CAAC;IAE7F,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAChC;QACE,GAAG,yBAAyB;QAC5B,oEAAoE;QACpE,KAAK;QACL,MAAM,CAAC,SAAS,CAAC;QACjB,IAAI;QACJ,QAAQ;QACR,WAAW;QACX,GAAG;QACH,KAAK;QACL,aAAa,MAAM,CAAC,KAAK,CAAC,WAAW;QACrC,MAAM;QACN,mBAAmB;QACnB,IAAI;QACJ,YAAY;QACZ,MAAM;QACN,OAAO;QACP,QAAQ;KACT,EACD,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,UAAU,EAAE,CACtD,CAAC;IAEF,IAAI,MAAM,CAAC,UAAU,KAAK,CAAC;QAAE,MAAM,IAAI,YAAY,CAAC,cAAc,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC3F,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AACtC,CAAC;AAED,kEAAkE;AAClE,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAa,EACb,OAAyB,EACzB,OAAuB;IAEvB,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAE5C,6EAA6E;IAC7E,2CAA2C;IAC3C,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;IAC3B,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAErE,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,EAAE,EAAE,uBAAuB,EAAE,KAAK,CAAC,CAAC;IACjF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,UAAU,EAAE,mBAAmB,EAAE,CAAC,SAAS,EAAE,EAAE,CAC5E,UAAU,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,CACvC,CAAC;IAEF,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;AAClF,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A short animated GIF from a moment of a video.
|
|
3
|
+
*
|
|
4
|
+
* A GIF stores every frame whole, so its size grows with length, width and frame
|
|
5
|
+
* rate far faster than a video's does. The length is therefore capped: past it the
|
|
6
|
+
* file is one nobody can share, and a clip is the right tool.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MAX_GIF_SECONDS = 15;
|
|
9
|
+
export declare const DEFAULT_GIF_WIDTH = 480;
|
|
10
|
+
export declare const DEFAULT_GIF_FPS = 12;
|
|
11
|
+
export interface GifResult {
|
|
12
|
+
videoId: string;
|
|
13
|
+
title: string;
|
|
14
|
+
filePath: string;
|
|
15
|
+
start: number;
|
|
16
|
+
end: number;
|
|
17
|
+
duration: number;
|
|
18
|
+
width: number;
|
|
19
|
+
fps: number;
|
|
20
|
+
bytes: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function extractGif(video: string, rangeOptions: {
|
|
23
|
+
start?: number;
|
|
24
|
+
end?: number;
|
|
25
|
+
chapter?: string;
|
|
26
|
+
}, options: {
|
|
27
|
+
width: number;
|
|
28
|
+
fps: number;
|
|
29
|
+
outputDir?: string;
|
|
30
|
+
}): Promise<GifResult>;
|
|
31
|
+
//# sourceMappingURL=gif.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gif.d.ts","sourceRoot":"","sources":["../../src/utils/gif.ts"],"names":[],"mappings":"AAeA;;;;;;GAMG;AAEH,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,eAAe,KAAK,CAAC;AAIlC,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,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,EAChE,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1D,OAAO,CAAC,SAAS,CAAC,CAoEpB"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { mkdir, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { resolveRange, safeStem } from './clips.js';
|
|
4
|
+
import { YouTubeError } from './errors.js';
|
|
5
|
+
import { runFfmpeg } from './ffmpeg.js';
|
|
6
|
+
import { dataDir } from './paths.js';
|
|
7
|
+
import { requireFfmpeg } from './preflight.js';
|
|
8
|
+
import { resolveOutputDir } from './validate.js';
|
|
9
|
+
import { FFMPEG_NETWORK_INPUT_ARGS, PREVIEW_STREAM_SELECTOR, resolveStreamUrl, } from './video-stream.js';
|
|
10
|
+
import { TIMEOUTS } from './ytdlp.js';
|
|
11
|
+
/**
|
|
12
|
+
* A short animated GIF from a moment of a video.
|
|
13
|
+
*
|
|
14
|
+
* A GIF stores every frame whole, so its size grows with length, width and frame
|
|
15
|
+
* rate far faster than a video's does. The length is therefore capped: past it the
|
|
16
|
+
* file is one nobody can share, and a clip is the right tool.
|
|
17
|
+
*/
|
|
18
|
+
export const MAX_GIF_SECONDS = 15;
|
|
19
|
+
export const DEFAULT_GIF_WIDTH = 480;
|
|
20
|
+
export const DEFAULT_GIF_FPS = 12;
|
|
21
|
+
const GIFS_DIR = dataDir('gifs');
|
|
22
|
+
export async function extractGif(video, rangeOptions, options) {
|
|
23
|
+
const { width, fps } = options;
|
|
24
|
+
// Cheap local checks first: a missing ffmpeg, an unwritable destination or an
|
|
25
|
+
// over-long range should fail now, not after a network round trip.
|
|
26
|
+
await requireFfmpeg('GIF extraction');
|
|
27
|
+
const targetDir = resolveOutputDir(options.outputDir, GIFS_DIR);
|
|
28
|
+
const { range, title, videoId } = await resolveRange(video, rangeOptions);
|
|
29
|
+
const duration = range.end - range.start;
|
|
30
|
+
if (duration > MAX_GIF_SECONDS) {
|
|
31
|
+
throw new YouTubeError('INVALID_INPUT', `A GIF can be at most ${String(MAX_GIF_SECONDS)} seconds, and this range is ${String(Math.round(duration))}.`, { nextStep: 'Narrow the range, or use extract_clip for a longer excerpt.' });
|
|
32
|
+
}
|
|
33
|
+
await mkdir(targetDir, { recursive: true });
|
|
34
|
+
const mediaUrl = await resolveStreamUrl(videoId, PREVIEW_STREAM_SELECTOR, 'extract_gif');
|
|
35
|
+
const filePath = join(targetDir, `${safeStem(title, videoId)} [${String(Math.round(range.start))}-${String(Math.round(range.end))}].gif`);
|
|
36
|
+
try {
|
|
37
|
+
await runFfmpeg([
|
|
38
|
+
...FFMPEG_NETWORK_INPUT_ARGS,
|
|
39
|
+
// Both before -i: seek there, then read only the range.
|
|
40
|
+
'-ss',
|
|
41
|
+
String(range.start),
|
|
42
|
+
'-t',
|
|
43
|
+
String(duration),
|
|
44
|
+
'-i',
|
|
45
|
+
mediaUrl,
|
|
46
|
+
'-vf',
|
|
47
|
+
// The palette is built from this clip's own frames, which is what keeps the
|
|
48
|
+
// colours faithful within a GIF's 256.
|
|
49
|
+
`fps=${String(fps)},scale=${String(width)}:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse`,
|
|
50
|
+
'-loop',
|
|
51
|
+
'0',
|
|
52
|
+
'-y',
|
|
53
|
+
filePath,
|
|
54
|
+
], {
|
|
55
|
+
failure: 'ffmpeg could not make that GIF.',
|
|
56
|
+
nextStep: 'Try a shorter range, or check that ffmpeg works with network input.',
|
|
57
|
+
timeoutMs: TIMEOUTS.transcript,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
// ffmpeg writes as it goes, so a failed or cancelled run leaves half a GIF behind.
|
|
62
|
+
await rm(filePath, { force: true });
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
videoId,
|
|
67
|
+
title,
|
|
68
|
+
filePath,
|
|
69
|
+
start: range.start,
|
|
70
|
+
end: range.end,
|
|
71
|
+
duration,
|
|
72
|
+
width,
|
|
73
|
+
fps,
|
|
74
|
+
bytes: (await stat(filePath)).size,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=gif.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gif.js","sourceRoot":"","sources":["../../src/utils/gif.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,14 @@ import type { Coverage } from '../harvest-schemas.js';
|
|
|
15
15
|
export declare const HARVEST_TABS: readonly ["videos", "shorts", "streams", "releases", "podcasts"];
|
|
16
16
|
export type HarvestTab = (typeof HARVEST_TABS)[number];
|
|
17
17
|
export declare const MAX_VIDEOS_PER_CATALOG = 10000;
|
|
18
|
+
/**
|
|
19
|
+
* Whether a listing failed only because the channel has no such tab.
|
|
20
|
+
*
|
|
21
|
+
* Recognised by the error's code, which the classifier assigns from yt-dlp's own words.
|
|
22
|
+
* It used to look for those words in the message, which never carries them, so no
|
|
23
|
+
* channel lacking a tab was ever catalogued as complete.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isAbsentTab(error: unknown): boolean;
|
|
18
26
|
export interface TabResult {
|
|
19
27
|
tab: HarvestTab;
|
|
20
28
|
listed: number;
|
|
@@ -31,6 +39,8 @@ export interface CatalogResult {
|
|
|
31
39
|
videosAdded: number;
|
|
32
40
|
coverage: Coverage;
|
|
33
41
|
}
|
|
42
|
+
/** A channel tab as yt-dlp's flat listing, one JSON object per line, newest first. */
|
|
43
|
+
export declare function listTab(channelUrl: string, tab: HarvestTab, limit: number, label?: string): Promise<string>;
|
|
34
44
|
/**
|
|
35
45
|
* Walks every tab of a channel into the store.
|
|
36
46
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harvest-catalog.d.ts","sourceRoot":"","sources":["../../src/utils/harvest-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAahD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,YAAY,kEAAmE,CAAC;AAC7F,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,eAAO,MAAM,sBAAsB,QAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"harvest-catalog.d.ts","sourceRoot":"","sources":["../../src/utils/harvest-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAahD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,YAAY,kEAAmE,CAAC;AAC7F,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEnD;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,UAAU,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAsBD,sFAAsF;AACtF,wBAAsB,OAAO,CAC3B,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,MAAM,EACb,KAAK,SAA2B,GAC/B,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACnC,OAAO,CAAC,aAAa,CAAC,CAQxB"}
|
|
@@ -24,8 +24,16 @@ import { YouTubeError } from './errors.js';
|
|
|
24
24
|
*/
|
|
25
25
|
export const HARVEST_TABS = ['videos', 'shorts', 'streams', 'releases', 'podcasts'];
|
|
26
26
|
export const MAX_VIDEOS_PER_CATALOG = 10_000;
|
|
27
|
-
/**
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Whether a listing failed only because the channel has no such tab.
|
|
29
|
+
*
|
|
30
|
+
* Recognised by the error's code, which the classifier assigns from yt-dlp's own words.
|
|
31
|
+
* It used to look for those words in the message, which never carries them, so no
|
|
32
|
+
* channel lacking a tab was ever catalogued as complete.
|
|
33
|
+
*/
|
|
34
|
+
export function isAbsentTab(error) {
|
|
35
|
+
return error instanceof YouTubeError && error.code === 'NOT_FOUND';
|
|
36
|
+
}
|
|
29
37
|
const UPSERT_VIDEO = `
|
|
30
38
|
INSERT INTO video (
|
|
31
39
|
video_id, channel_id, title, duration_s, upload_date, view_count,
|
|
@@ -45,7 +53,8 @@ ON CONFLICT(video_id) DO UPDATE SET
|
|
|
45
53
|
WHEN instr(video.also_in_tabs, excluded.tab) > 0 THEN video.also_in_tabs
|
|
46
54
|
ELSE json_insert(video.also_in_tabs, '$[#]', excluded.tab)
|
|
47
55
|
END`;
|
|
48
|
-
|
|
56
|
+
/** A channel tab as yt-dlp's flat listing, one JSON object per line, newest first. */
|
|
57
|
+
export async function listTab(channelUrl, tab, limit, label = `harvest_channel:${tab}`) {
|
|
49
58
|
return runYtDlp([
|
|
50
59
|
'--skip-download',
|
|
51
60
|
'--flat-playlist',
|
|
@@ -55,7 +64,7 @@ async function listTab(channelUrl, tab, limit) {
|
|
|
55
64
|
'--playlist-end',
|
|
56
65
|
String(limit),
|
|
57
66
|
], {
|
|
58
|
-
label
|
|
67
|
+
label,
|
|
59
68
|
timeoutMs: TIMEOUTS.transcript,
|
|
60
69
|
target: resolveListTarget(`${channelUrl}/${tab}`),
|
|
61
70
|
});
|
|
@@ -101,7 +110,7 @@ async function walkTabs(database, info, maxVideos) {
|
|
|
101
110
|
}
|
|
102
111
|
catch (error) {
|
|
103
112
|
const message = error instanceof YouTubeError ? error.message : String(error);
|
|
104
|
-
const absent = error
|
|
113
|
+
const absent = isAbsentTab(error);
|
|
105
114
|
tabs.push({
|
|
106
115
|
tab,
|
|
107
116
|
listed: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harvest-catalog.js","sourceRoot":"","sources":["../../src/utils/harvest-catalog.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,CAAU,CAAC;AAG7F,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE7C
|
|
1
|
+
{"version":3,"file":"harvest-catalog.js","sourceRoot":"","sources":["../../src/utils/harvest-catalog.ts"],"names":[],"mappings":"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"}
|