youtube-knowledge-mcp 2.0.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -33
- package/dist/brain-schemas.d.ts +502 -0
- package/dist/brain-schemas.d.ts.map +1 -0
- package/dist/brain-schemas.js +109 -0
- package/dist/brain-schemas.js.map +1 -0
- package/dist/http/app.d.ts +16 -0
- package/dist/http/app.d.ts.map +1 -0
- package/dist/http/app.js +176 -0
- package/dist/http/app.js.map +1 -0
- package/dist/http/auth.d.ts +38 -0
- package/dist/http/auth.d.ts.map +1 -0
- package/dist/http/auth.js +79 -0
- package/dist/http/auth.js.map +1 -0
- package/dist/http/config.d.ts +41 -0
- package/dist/http/config.d.ts.map +1 -0
- package/dist/http/config.js +65 -0
- package/dist/http/config.js.map +1 -0
- package/dist/http/rate-limiter.d.ts +24 -0
- package/dist/http/rate-limiter.d.ts.map +1 -0
- package/dist/http/rate-limiter.js +55 -0
- package/dist/http/rate-limiter.js.map +1 -0
- package/dist/http/sessions.d.ts +34 -0
- package/dist/http/sessions.d.ts.map +1 -0
- package/dist/http/sessions.js +62 -0
- package/dist/http/sessions.js.map +1 -0
- package/dist/http.d.ts +6 -48
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +35 -252
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -431
- package/dist/index.js.map +1 -1
- package/dist/prompts-brain.d.ts +4 -0
- package/dist/prompts-brain.d.ts.map +1 -0
- package/dist/prompts-brain.js +48 -0
- package/dist/prompts-brain.js.map +1 -0
- package/dist/prompts-research.d.ts +4 -0
- package/dist/prompts-research.d.ts.map +1 -0
- package/dist/prompts-research.js +74 -0
- package/dist/prompts-research.js.map +1 -0
- package/dist/prompts-shared.d.ts +13 -0
- package/dist/prompts-shared.d.ts.map +1 -0
- package/dist/prompts-shared.js +7 -0
- package/dist/prompts-shared.js.map +1 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +5 -58
- package/dist/prompts.js.map +1 -1
- package/dist/registry/brain-tools.d.ts +4 -0
- package/dist/registry/brain-tools.d.ts.map +1 -0
- package/dist/registry/brain-tools.js +104 -0
- package/dist/registry/brain-tools.js.map +1 -0
- package/dist/registry/channel-tools.d.ts +4 -0
- package/dist/registry/channel-tools.d.ts.map +1 -0
- package/dist/registry/channel-tools.js +53 -0
- package/dist/registry/channel-tools.js.map +1 -0
- package/dist/registry/health-tools.d.ts +4 -0
- package/dist/registry/health-tools.d.ts.map +1 -0
- package/dist/registry/health-tools.js +21 -0
- package/dist/registry/health-tools.js.map +1 -0
- package/dist/registry/index.d.ts +8 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +27 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/library-tools.d.ts +4 -0
- package/dist/registry/library-tools.d.ts.map +1 -0
- package/dist/registry/library-tools.js +116 -0
- package/dist/registry/library-tools.js.map +1 -0
- package/dist/registry/media-tools.d.ts +4 -0
- package/dist/registry/media-tools.d.ts.map +1 -0
- package/dist/registry/media-tools.js +100 -0
- package/dist/registry/media-tools.js.map +1 -0
- package/dist/registry/thumbnail-tools.d.ts +7 -0
- package/dist/registry/thumbnail-tools.d.ts.map +1 -0
- package/dist/registry/thumbnail-tools.js +72 -0
- package/dist/registry/thumbnail-tools.js.map +1 -0
- package/dist/registry/transcript-tools.d.ts +4 -0
- package/dist/registry/transcript-tools.d.ts.map +1 -0
- package/dist/registry/transcript-tools.js +68 -0
- package/dist/registry/transcript-tools.js.map +1 -0
- package/dist/registry/types.d.ts +41 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +17 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/registry/video-tools.d.ts +4 -0
- package/dist/registry/video-tools.d.ts.map +1 -0
- package/dist/registry/video-tools.js +101 -0
- package/dist/registry/video-tools.js.map +1 -0
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +75 -6
- package/dist/resources.js.map +1 -1
- package/dist/schemas.d.ts +74 -54
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +30 -4
- package/dist/schemas.js.map +1 -1
- package/dist/thumbnail-resource.d.ts +8 -0
- package/dist/thumbnail-resource.d.ts.map +1 -0
- package/dist/thumbnail-resource.js +38 -0
- package/dist/thumbnail-resource.js.map +1 -0
- package/dist/thumbnail-schemas.d.ts +512 -0
- package/dist/thumbnail-schemas.d.ts.map +1 -0
- package/dist/thumbnail-schemas.js +95 -0
- package/dist/thumbnail-schemas.js.map +1 -0
- package/dist/tools/ask-brain.d.ts +62 -0
- package/dist/tools/ask-brain.d.ts.map +1 -0
- package/dist/tools/ask-brain.js +70 -0
- package/dist/tools/ask-brain.js.map +1 -0
- package/dist/tools/batch.d.ts +10 -10
- package/dist/tools/batch.js +3 -3
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/brain-info.d.ts +180 -0
- package/dist/tools/brain-info.d.ts.map +1 -0
- package/dist/tools/brain-info.js +108 -0
- package/dist/tools/brain-info.js.map +1 -0
- package/dist/tools/build-brain.d.ts +120 -0
- package/dist/tools/build-brain.d.ts.map +1 -0
- package/dist/tools/build-brain.js +123 -0
- package/dist/tools/build-brain.js.map +1 -0
- package/dist/tools/check-health.d.ts +2 -0
- package/dist/tools/check-health.d.ts.map +1 -1
- package/dist/tools/check-health.js +9 -0
- package/dist/tools/check-health.js.map +1 -1
- package/dist/tools/clips.d.ts +13 -45
- package/dist/tools/clips.d.ts.map +1 -1
- package/dist/tools/clips.js +52 -128
- package/dist/tools/clips.js.map +1 -1
- package/dist/tools/fetch-channel-thumbnails.d.ts +179 -0
- package/dist/tools/fetch-channel-thumbnails.d.ts.map +1 -0
- package/dist/tools/fetch-channel-thumbnails.js +123 -0
- package/dist/tools/fetch-channel-thumbnails.js.map +1 -0
- package/dist/tools/fetch-videos.d.ts +11 -8
- package/dist/tools/fetch-videos.d.ts.map +1 -1
- package/dist/tools/fetch-videos.js +3 -0
- package/dist/tools/fetch-videos.js.map +1 -1
- package/dist/tools/frames-subtitles.d.ts +44 -0
- package/dist/tools/frames-subtitles.d.ts.map +1 -0
- package/dist/tools/frames-subtitles.js +104 -0
- package/dist/tools/frames-subtitles.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 +8 -1
- package/dist/tools/get-channel-info.js.map +1 -1
- package/dist/tools/get-chapters.d.ts +2 -2
- package/dist/tools/get-comments.d.ts +2 -2
- package/dist/tools/get-playlist-info.d.ts.map +1 -1
- package/dist/tools/get-playlist-info.js +1 -0
- package/dist/tools/get-playlist-info.js.map +1 -1
- package/dist/tools/get-thumbnail.d.ts +38 -0
- package/dist/tools/get-thumbnail.d.ts.map +1 -0
- package/dist/tools/get-thumbnail.js +152 -0
- package/dist/tools/get-thumbnail.js.map +1 -0
- package/dist/tools/get-transcript.d.ts +2 -2
- package/dist/tools/get-transcript.d.ts.map +1 -1
- package/dist/tools/get-transcript.js +2 -14
- package/dist/tools/get-transcript.js.map +1 -1
- package/dist/tools/library.d.ts +9 -7
- package/dist/tools/library.d.ts.map +1 -1
- package/dist/tools/library.js +23 -7
- package/dist/tools/library.js.map +1 -1
- package/dist/tools/list-channel-thumbnails.d.ts +235 -0
- package/dist/tools/list-channel-thumbnails.d.ts.map +1 -0
- package/dist/tools/list-channel-thumbnails.js +79 -0
- package/dist/tools/list-channel-thumbnails.js.map +1 -0
- package/dist/tools/list-library.d.ts +4 -4
- package/dist/tools/manage-brain.d.ts +39 -0
- package/dist/tools/manage-brain.d.ts.map +1 -0
- package/dist/tools/manage-brain.js +53 -0
- package/dist/tools/manage-brain.js.map +1 -0
- package/dist/tools/save-to-library.d.ts.map +1 -1
- package/dist/tools/save-to-library.js +5 -1
- package/dist/tools/save-to-library.js.map +1 -1
- package/dist/tools/search-channels.d.ts +10 -4
- package/dist/tools/search-channels.d.ts.map +1 -1
- package/dist/tools/search-transcript.d.ts +2 -2
- package/dist/tools/search-transcript.d.ts.map +1 -1
- package/dist/tools/search-transcript.js +6 -13
- package/dist/tools/search-transcript.js.map +1 -1
- package/dist/tools/search-videos.d.ts +11 -8
- package/dist/tools/search-videos.d.ts.map +1 -1
- package/dist/utils/batches.d.ts +15 -0
- package/dist/utils/batches.d.ts.map +1 -0
- package/dist/utils/batches.js +31 -0
- package/dist/utils/batches.js.map +1 -0
- package/dist/utils/brain-build.d.ts +40 -0
- package/dist/utils/brain-build.d.ts.map +1 -0
- package/dist/utils/brain-build.js +200 -0
- package/dist/utils/brain-build.js.map +1 -0
- package/dist/utils/brain-chunks.d.ts +33 -0
- package/dist/utils/brain-chunks.d.ts.map +1 -0
- package/dist/utils/brain-chunks.js +87 -0
- package/dist/utils/brain-chunks.js.map +1 -0
- package/dist/utils/brain-index.d.ts +27 -0
- package/dist/utils/brain-index.d.ts.map +1 -0
- package/dist/utils/brain-index.js +101 -0
- package/dist/utils/brain-index.js.map +1 -0
- package/dist/utils/brain-ingest.d.ts +57 -0
- package/dist/utils/brain-ingest.d.ts.map +1 -0
- package/dist/utils/brain-ingest.js +127 -0
- package/dist/utils/brain-ingest.js.map +1 -0
- package/dist/utils/brain-lock.d.ts +4 -0
- package/dist/utils/brain-lock.d.ts.map +1 -0
- package/dist/utils/brain-lock.js +17 -0
- package/dist/utils/brain-lock.js.map +1 -0
- package/dist/utils/brain-lookup.d.ts +11 -0
- package/dist/utils/brain-lookup.d.ts.map +1 -0
- package/dist/utils/brain-lookup.js +36 -0
- package/dist/utils/brain-lookup.js.map +1 -0
- package/dist/utils/brain-paths.d.ts +19 -0
- package/dist/utils/brain-paths.d.ts.map +1 -0
- package/dist/utils/brain-paths.js +34 -0
- package/dist/utils/brain-paths.js.map +1 -0
- package/dist/utils/brain-phrases.d.ts +31 -0
- package/dist/utils/brain-phrases.d.ts.map +1 -0
- package/dist/utils/brain-phrases.js +116 -0
- package/dist/utils/brain-phrases.js.map +1 -0
- package/dist/utils/brain-stats.d.ts +25 -0
- package/dist/utils/brain-stats.d.ts.map +1 -0
- package/dist/utils/brain-stats.js +64 -0
- package/dist/utils/brain-stats.js.map +1 -0
- package/dist/utils/brain-storage.d.ts +28 -0
- package/dist/utils/brain-storage.d.ts.map +1 -0
- package/dist/utils/brain-storage.js +87 -0
- package/dist/utils/brain-storage.js.map +1 -0
- package/dist/utils/caption-probe.d.ts +7 -0
- package/dist/utils/caption-probe.d.ts.map +1 -0
- package/dist/utils/caption-probe.js +36 -0
- package/dist/utils/caption-probe.js.map +1 -0
- package/dist/utils/channel-images.d.ts +16 -0
- package/dist/utils/channel-images.d.ts.map +1 -0
- package/dist/utils/channel-images.js +35 -0
- package/dist/utils/channel-images.js.map +1 -0
- package/dist/utils/channel-lookup.d.ts +5 -0
- package/dist/utils/channel-lookup.d.ts.map +1 -0
- package/dist/utils/channel-lookup.js +37 -0
- package/dist/utils/channel-lookup.js.map +1 -0
- package/dist/utils/chapters.d.ts +11 -0
- package/dist/utils/chapters.d.ts.map +1 -0
- package/dist/utils/chapters.js +24 -0
- package/dist/utils/chapters.js.map +1 -0
- package/dist/utils/clips.d.ts.map +1 -1
- package/dist/utils/clips.js +22 -30
- package/dist/utils/clips.js.map +1 -1
- package/dist/utils/env.d.ts +26 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +72 -0
- package/dist/utils/env.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 +20 -2
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/file-lock.d.ts +34 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +91 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/dist/utils/flat-listing.d.ts +89 -0
- package/dist/utils/flat-listing.d.ts.map +1 -0
- package/dist/utils/flat-listing.js +68 -0
- package/dist/utils/flat-listing.js.map +1 -0
- package/dist/utils/format.d.ts +1 -3
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +1 -5
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/guard.d.ts +21 -0
- package/dist/utils/guard.d.ts.map +1 -0
- package/dist/utils/guard.js +105 -0
- package/dist/utils/guard.js.map +1 -0
- package/dist/utils/image-dimensions.d.ts +19 -0
- package/dist/utils/image-dimensions.d.ts.map +1 -0
- package/dist/utils/image-dimensions.js +93 -0
- package/dist/utils/image-dimensions.js.map +1 -0
- package/dist/utils/image-fetch.d.ts +31 -0
- package/dist/utils/image-fetch.d.ts.map +1 -0
- package/dist/utils/image-fetch.js +141 -0
- package/dist/utils/image-fetch.js.map +1 -0
- package/dist/utils/json-file.d.ts +45 -0
- package/dist/utils/json-file.d.ts.map +1 -0
- package/dist/utils/json-file.js +60 -0
- package/dist/utils/json-file.js.map +1 -0
- package/dist/utils/paths.d.ts +19 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +27 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/pattern.d.ts +30 -0
- package/dist/utils/pattern.d.ts.map +1 -0
- package/dist/utils/pattern.js +130 -0
- package/dist/utils/pattern.js.map +1 -0
- package/dist/utils/search-index.d.ts +22 -7
- package/dist/utils/search-index.d.ts.map +1 -1
- package/dist/utils/search-index.js +26 -8
- package/dist/utils/search-index.js.map +1 -1
- package/dist/utils/storage.d.ts +14 -44
- package/dist/utils/storage.d.ts.map +1 -1
- package/dist/utils/storage.js +47 -84
- package/dist/utils/storage.js.map +1 -1
- package/dist/utils/subtitles.d.ts +7 -0
- package/dist/utils/subtitles.d.ts.map +1 -0
- package/dist/utils/subtitles.js +30 -0
- package/dist/utils/subtitles.js.map +1 -0
- package/dist/utils/thumbnail-entry.d.ts +19 -0
- package/dist/utils/thumbnail-entry.d.ts.map +1 -0
- package/dist/utils/thumbnail-entry.js +119 -0
- package/dist/utils/thumbnail-entry.js.map +1 -0
- package/dist/utils/thumbnail-fetch.d.ts +37 -0
- package/dist/utils/thumbnail-fetch.d.ts.map +1 -0
- package/dist/utils/thumbnail-fetch.js +89 -0
- package/dist/utils/thumbnail-fetch.js.map +1 -0
- package/dist/utils/thumbnail-ladder.d.ts +44 -0
- package/dist/utils/thumbnail-ladder.d.ts.map +1 -0
- package/dist/utils/thumbnail-ladder.js +68 -0
- package/dist/utils/thumbnail-ladder.js.map +1 -0
- package/dist/utils/thumbnail-listing.d.ts +16 -0
- package/dist/utils/thumbnail-listing.d.ts.map +1 -0
- package/dist/utils/thumbnail-listing.js +26 -0
- package/dist/utils/thumbnail-listing.js.map +1 -0
- package/dist/utils/thumbnail-paths.d.ts +20 -0
- package/dist/utils/thumbnail-paths.d.ts.map +1 -0
- package/dist/utils/thumbnail-paths.js +35 -0
- package/dist/utils/thumbnail-paths.js.map +1 -0
- package/dist/utils/thumbnail-store.d.ts +19 -0
- package/dist/utils/thumbnail-store.d.ts.map +1 -0
- package/dist/utils/thumbnail-store.js +125 -0
- package/dist/utils/thumbnail-store.js.map +1 -0
- package/dist/utils/transcript-cache.d.ts +20 -0
- package/dist/utils/transcript-cache.d.ts.map +1 -0
- package/dist/utils/transcript-cache.js +127 -0
- package/dist/utils/transcript-cache.js.map +1 -0
- package/dist/utils/transcript-search.d.ts +20 -0
- package/dist/utils/transcript-search.d.ts.map +1 -0
- package/dist/utils/transcript-search.js +35 -0
- package/dist/utils/transcript-search.js.map +1 -0
- package/dist/utils/transcript.d.ts +14 -34
- package/dist/utils/transcript.d.ts.map +1 -1
- package/dist/utils/transcript.js +5 -64
- package/dist/utils/transcript.js.map +1 -1
- package/dist/utils/validate-youtube.d.ts +35 -0
- package/dist/utils/validate-youtube.d.ts.map +1 -0
- package/dist/utils/validate-youtube.js +87 -0
- package/dist/utils/validate-youtube.js.map +1 -0
- package/dist/utils/validate.d.ts +11 -3
- package/dist/utils/validate.d.ts.map +1 -1
- package/dist/utils/validate.js +43 -18
- package/dist/utils/validate.js.map +1 -1
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +19 -0
- package/dist/utils/version.js.map +1 -0
- package/dist/utils/youtube-channel.d.ts +44 -0
- package/dist/utils/youtube-channel.d.ts.map +1 -0
- package/dist/utils/youtube-channel.js +61 -0
- package/dist/utils/youtube-channel.js.map +1 -0
- package/dist/utils/youtube-download.d.ts +27 -0
- package/dist/utils/youtube-download.d.ts.map +1 -0
- package/dist/utils/youtube-download.js +118 -0
- package/dist/utils/youtube-download.js.map +1 -0
- package/dist/utils/youtube-schemas.d.ts +394 -0
- package/dist/utils/youtube-schemas.d.ts.map +1 -0
- package/dist/utils/youtube-schemas.js +118 -0
- package/dist/utils/youtube-schemas.js.map +1 -0
- package/dist/utils/youtube-search.d.ts +14 -0
- package/dist/utils/youtube-search.d.ts.map +1 -0
- package/dist/utils/youtube-search.js +39 -0
- package/dist/utils/youtube-search.js.map +1 -0
- package/dist/utils/youtube-url.d.ts +25 -0
- package/dist/utils/youtube-url.d.ts.map +1 -0
- package/dist/utils/youtube-url.js +75 -0
- package/dist/utils/youtube-url.js.map +1 -0
- package/dist/utils/youtube-video.d.ts +47 -0
- package/dist/utils/youtube-video.d.ts.map +1 -0
- package/dist/utils/youtube-video.js +98 -0
- package/dist/utils/youtube-video.js.map +1 -0
- package/dist/utils/youtube.d.ts +13 -111
- package/dist/utils/youtube.d.ts.map +1 -1
- package/dist/utils/youtube.js +11 -480
- package/dist/utils/youtube.js.map +1 -1
- package/dist/utils/ytdlp-env.d.ts +33 -0
- package/dist/utils/ytdlp-env.d.ts.map +1 -0
- package/dist/utils/ytdlp-env.js +121 -0
- package/dist/utils/ytdlp-env.js.map +1 -0
- package/dist/utils/ytdlp-limiter.d.ts +24 -0
- package/dist/utils/ytdlp-limiter.d.ts.map +1 -0
- package/dist/utils/ytdlp-limiter.js +69 -0
- package/dist/utils/ytdlp-limiter.js.map +1 -0
- package/dist/utils/ytdlp-parse.d.ts +13 -0
- package/dist/utils/ytdlp-parse.d.ts.map +1 -0
- package/dist/utils/ytdlp-parse.js +48 -0
- package/dist/utils/ytdlp-parse.js.map +1 -0
- package/dist/utils/ytdlp.d.ts +13 -18
- package/dist/utils/ytdlp.d.ts.map +1 -1
- package/dist/utils/ytdlp.js +21 -110
- package/dist/utils/ytdlp.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { BrainChunk, BrainVideoState } from '../brain-schemas.js';
|
|
2
|
+
import { type VideoListItem } from './youtube.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reading one video into passages.
|
|
5
|
+
*
|
|
6
|
+
* Nothing here throws for anything to do with the video itself. A channel with
|
|
7
|
+
* a members-only upload, a live stream still processing, or an episode nobody
|
|
8
|
+
* captioned is still a channel worth having a brain for; which videos those
|
|
9
|
+
* were belongs in the manifest, not in an exception that loses the other two
|
|
10
|
+
* hundred.
|
|
11
|
+
*
|
|
12
|
+
* Metadata is read before the transcript, in that order deliberately: it is the
|
|
13
|
+
* cheaper request, it carries the date and length the filters need, and a video
|
|
14
|
+
* ruled out by them then costs one request instead of two.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* A transcript that chunks past this is not a transcript. Recording the video
|
|
18
|
+
* as failed keeps the brain buildable; letting it through does not.
|
|
19
|
+
*
|
|
20
|
+
* The brain-wide ceiling is what bounds memory, and it was chosen by measuring
|
|
21
|
+
* rather than by feel: a corpus of 100,000 passages is 81MB on disk, and the
|
|
22
|
+
* heaviest thing done to it — the phrase pass — peaked at 767MB of heap. Raising
|
|
23
|
+
* it means measuring again.
|
|
24
|
+
*/
|
|
25
|
+
export declare const MAX_CHUNKS_PER_VIDEO = 2000;
|
|
26
|
+
export declare const MAX_CHUNKS_PER_BRAIN = 100000;
|
|
27
|
+
/** Recorded against a video the size guards refused, alongside yt-dlp's own codes. */
|
|
28
|
+
export declare const TOO_LARGE = "TOO_LARGE";
|
|
29
|
+
export declare const BRAIN_FULL = "BRAIN_FULL";
|
|
30
|
+
export interface VideoFilters {
|
|
31
|
+
/** Ignore anything published before this date (YYYY-MM-DD). */
|
|
32
|
+
since?: string;
|
|
33
|
+
/** Ignore anything shorter, to leave out shorts and clips. */
|
|
34
|
+
minDurationSeconds: number;
|
|
35
|
+
}
|
|
36
|
+
export interface IngestOptions extends VideoFilters {
|
|
37
|
+
language: string;
|
|
38
|
+
}
|
|
39
|
+
export interface IngestResult {
|
|
40
|
+
state: BrainVideoState;
|
|
41
|
+
chunks: BrainChunk[];
|
|
42
|
+
}
|
|
43
|
+
export declare function ingestVideo(video: VideoListItem, chunksSoFar: number, options: IngestOptions): Promise<IngestResult>;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the filters rule this video out, or `undefined` when its recorded
|
|
46
|
+
* state cannot say.
|
|
47
|
+
*
|
|
48
|
+
* A build asks this of the manifest before deciding what to read, which is what
|
|
49
|
+
* lets a changed filter take effect without re-fetching anything: once a video
|
|
50
|
+
* has been looked at, its real date and length are on disk and the question is
|
|
51
|
+
* answerable from there. `undefined` is the honest answer for a video only ever
|
|
52
|
+
* seen in a flat listing — not a licence to assume it qualifies.
|
|
53
|
+
*/
|
|
54
|
+
export declare function isExcluded(state: BrainVideoState, filters: VideoFilters): boolean | undefined;
|
|
55
|
+
export declare function pendingState(video: VideoListItem): BrainVideoState;
|
|
56
|
+
export declare function excluded(state: BrainVideoState): BrainVideoState;
|
|
57
|
+
//# sourceMappingURL=brain-ingest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brain-ingest.d.ts","sourceRoot":"","sources":["../../src/utils/brain-ingest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAKvE,OAAO,EAAkC,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAElF;;;;;;;;;;;;GAYG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AACzC,eAAO,MAAM,oBAAoB,SAAU,CAAC;AAE5C,sFAAsF;AACtF,eAAO,MAAM,SAAS,cAAc,CAAC;AACrC,eAAO,MAAM,UAAU,eAAe,CAAC;AAEvC,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,wBAAsB,WAAW,CAC/B,KAAK,EAAE,aAAa,EACpB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAoDvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,GAAG,SAAS,CAe7F;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,eAAe,CAWlE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,eAAe,CAEhE"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { chunkTranscript } from './brain-chunks.js';
|
|
2
|
+
import { throwIfAborted } from './context.js';
|
|
3
|
+
import { asYouTubeError } from './errors.js';
|
|
4
|
+
import { countWords } from './transcript.js';
|
|
5
|
+
import { getTranscript, getVideoDetails } from './youtube.js';
|
|
6
|
+
/**
|
|
7
|
+
* Reading one video into passages.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here throws for anything to do with the video itself. A channel with
|
|
10
|
+
* a members-only upload, a live stream still processing, or an episode nobody
|
|
11
|
+
* captioned is still a channel worth having a brain for; which videos those
|
|
12
|
+
* were belongs in the manifest, not in an exception that loses the other two
|
|
13
|
+
* hundred.
|
|
14
|
+
*
|
|
15
|
+
* Metadata is read before the transcript, in that order deliberately: it is the
|
|
16
|
+
* cheaper request, it carries the date and length the filters need, and a video
|
|
17
|
+
* ruled out by them then costs one request instead of two.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* A transcript that chunks past this is not a transcript. Recording the video
|
|
21
|
+
* as failed keeps the brain buildable; letting it through does not.
|
|
22
|
+
*
|
|
23
|
+
* The brain-wide ceiling is what bounds memory, and it was chosen by measuring
|
|
24
|
+
* rather than by feel: a corpus of 100,000 passages is 81MB on disk, and the
|
|
25
|
+
* heaviest thing done to it — the phrase pass — peaked at 767MB of heap. Raising
|
|
26
|
+
* it means measuring again.
|
|
27
|
+
*/
|
|
28
|
+
export const MAX_CHUNKS_PER_VIDEO = 2000;
|
|
29
|
+
export const MAX_CHUNKS_PER_BRAIN = 100_000;
|
|
30
|
+
/** Recorded against a video the size guards refused, alongside yt-dlp's own codes. */
|
|
31
|
+
export const TOO_LARGE = 'TOO_LARGE';
|
|
32
|
+
export const BRAIN_FULL = 'BRAIN_FULL';
|
|
33
|
+
export async function ingestVideo(video, chunksSoFar, options) {
|
|
34
|
+
const listed = pendingState(video);
|
|
35
|
+
try {
|
|
36
|
+
// The listing is flat, which is what makes enumerating a thousand videos one
|
|
37
|
+
// request rather than a thousand — and flat entries carry no date. These are
|
|
38
|
+
// the real values, and the chapters come back in the same request.
|
|
39
|
+
const details = await getVideoDetails(video.id);
|
|
40
|
+
const known = {
|
|
41
|
+
...listed,
|
|
42
|
+
uploadDate: details.uploadDate,
|
|
43
|
+
durationSeconds: details.durationSeconds,
|
|
44
|
+
};
|
|
45
|
+
if (isExcluded(known, options) === true)
|
|
46
|
+
return { state: excluded(known), chunks: [] };
|
|
47
|
+
const transcript = await getTranscript(video.id, { language: options.language });
|
|
48
|
+
const chunks = chunkTranscript({
|
|
49
|
+
videoId: video.id,
|
|
50
|
+
title: video.title,
|
|
51
|
+
segments: transcript.segments,
|
|
52
|
+
chapters: details.chapters,
|
|
53
|
+
});
|
|
54
|
+
if (chunks.length > MAX_CHUNKS_PER_VIDEO)
|
|
55
|
+
return refused(known, TOO_LARGE);
|
|
56
|
+
if (chunksSoFar + chunks.length > MAX_CHUNKS_PER_BRAIN)
|
|
57
|
+
return refused(known, BRAIN_FULL);
|
|
58
|
+
return {
|
|
59
|
+
state: {
|
|
60
|
+
...known,
|
|
61
|
+
state: 'indexed',
|
|
62
|
+
chunkCount: chunks.length,
|
|
63
|
+
wordCount: chunks.reduce((total, chunk) => total + countWords(chunk.text), 0),
|
|
64
|
+
},
|
|
65
|
+
chunks,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
// Cancellation is the client's decision, not this video's problem. Asking
|
|
70
|
+
// the signal rather than classifying the error catches it however it was
|
|
71
|
+
// raised — an aborted spawn, a rejected read, or the check between videos.
|
|
72
|
+
throwIfAborted();
|
|
73
|
+
const failure = asYouTubeError(error);
|
|
74
|
+
return {
|
|
75
|
+
state: failure.code === 'NO_CAPTIONS'
|
|
76
|
+
? { ...listed, state: 'no-captions' }
|
|
77
|
+
: { ...listed, state: 'failed', error: failure.code },
|
|
78
|
+
chunks: [],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Whether the filters rule this video out, or `undefined` when its recorded
|
|
84
|
+
* state cannot say.
|
|
85
|
+
*
|
|
86
|
+
* A build asks this of the manifest before deciding what to read, which is what
|
|
87
|
+
* lets a changed filter take effect without re-fetching anything: once a video
|
|
88
|
+
* has been looked at, its real date and length are on disk and the question is
|
|
89
|
+
* answerable from there. `undefined` is the honest answer for a video only ever
|
|
90
|
+
* seen in a flat listing — not a licence to assume it qualifies.
|
|
91
|
+
*/
|
|
92
|
+
export function isExcluded(state, filters) {
|
|
93
|
+
const { since, minDurationSeconds } = filters;
|
|
94
|
+
let undecided = false;
|
|
95
|
+
if (minDurationSeconds > 0) {
|
|
96
|
+
if (state.durationSeconds <= 0)
|
|
97
|
+
undecided = true;
|
|
98
|
+
else if (state.durationSeconds < minDurationSeconds)
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
if (since !== undefined) {
|
|
102
|
+
if (state.uploadDate === '')
|
|
103
|
+
undecided = true;
|
|
104
|
+
else if (state.uploadDate < since)
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
return undecided ? undefined : false;
|
|
108
|
+
}
|
|
109
|
+
export function pendingState(video) {
|
|
110
|
+
return {
|
|
111
|
+
videoId: video.id,
|
|
112
|
+
title: video.title,
|
|
113
|
+
url: video.url,
|
|
114
|
+
uploadDate: video.uploadDate,
|
|
115
|
+
durationSeconds: video.duration,
|
|
116
|
+
state: 'pending',
|
|
117
|
+
chunkCount: 0,
|
|
118
|
+
wordCount: 0,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export function excluded(state) {
|
|
122
|
+
return { ...state, state: 'excluded', chunkCount: 0, wordCount: 0 };
|
|
123
|
+
}
|
|
124
|
+
function refused(state, reason) {
|
|
125
|
+
return { state: { ...state, state: 'failed', error: reason }, chunks: [] };
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=brain-ingest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brain-ingest.js","sourceRoot":"","sources":["../../src/utils/brain-ingest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brain-lock.d.ts","sourceRoot":"","sources":["../../src/utils/brain-lock.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,+EAA+E;AAC/E,wBAAsB,aAAa,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAK7F"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ensureBrainDir } from './brain-storage.js';
|
|
2
|
+
import { lockPath } from './brain-paths.js';
|
|
3
|
+
import { YouTubeError } from './errors.js';
|
|
4
|
+
import { withFileLock } from './file-lock.js';
|
|
5
|
+
export { LOCK_STALE_MS } from './file-lock.js';
|
|
6
|
+
/** Exclusive access to a brain while it is being built. See `withFileLock`. */
|
|
7
|
+
export async function withBuildLock(channelId, build) {
|
|
8
|
+
await ensureBrainDir(channelId);
|
|
9
|
+
return withFileLock(lockPath(channelId), build, (existing) => alreadyRunning(channelId, existing));
|
|
10
|
+
}
|
|
11
|
+
function alreadyRunning(channelId, existing) {
|
|
12
|
+
const owner = existing === undefined ? '' : `Started at ${existing.startedAt} by process ${existing.pid}. `;
|
|
13
|
+
return new YouTubeError('INVALID_INPUT', `A build for ${channelId} is already running.`, {
|
|
14
|
+
nextStep: `${owner}Wait for it to finish, then call build_brain again to continue where it stopped.`,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=brain-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brain-lock.js","sourceRoot":"","sources":["../../src/utils/brain-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAmB,MAAM,gBAAgB,CAAC;AAE/D,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,aAAa,CAAI,SAAiB,EAAE,KAAuB;IAC/E,MAAM,cAAc,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE,CAC3D,cAAc,CAAC,SAAS,EAAE,QAAQ,CAAC,CACpC,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,SAAiB,EAAE,QAAgC;IACzE,MAAM,KAAK,GACT,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,QAAQ,CAAC,SAAS,eAAe,QAAQ,CAAC,GAAG,IAAI,CAAC;IAEhG,OAAO,IAAI,YAAY,CAAC,eAAe,EAAE,eAAe,SAAS,sBAAsB,EAAE;QACvF,QAAQ,EAAE,GAAG,KAAK,kFAAkF;KACrG,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BrainManifest } from '../brain-schemas.js';
|
|
2
|
+
/**
|
|
3
|
+
* Find a brain from whatever the caller called the channel.
|
|
4
|
+
*
|
|
5
|
+
* Every brain tool except `build_brain` answers from disk, so none of them
|
|
6
|
+
* should need the network to work out which brain is meant. The matching
|
|
7
|
+
* itself is shared with every other channel-keyed store; what is specific here
|
|
8
|
+
* is what to say when there is no brain, or more than one.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveBrain(channel: string): Promise<BrainManifest>;
|
|
11
|
+
//# sourceMappingURL=brain-lookup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brain-lookup.d.ts","sourceRoot":"","sources":["../../src/utils/brain-lookup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAKzD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAU1E"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { listManifests } from './brain-storage.js';
|
|
2
|
+
import { findByChannel } from './channel-lookup.js';
|
|
3
|
+
import { YouTubeError } from './errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Find a brain from whatever the caller called the channel.
|
|
6
|
+
*
|
|
7
|
+
* Every brain tool except `build_brain` answers from disk, so none of them
|
|
8
|
+
* should need the network to work out which brain is meant. The matching
|
|
9
|
+
* itself is shared with every other channel-keyed store; what is specific here
|
|
10
|
+
* is what to say when there is no brain, or more than one.
|
|
11
|
+
*/
|
|
12
|
+
export async function resolveBrain(channel) {
|
|
13
|
+
const manifests = await listManifests();
|
|
14
|
+
const matches = findByChannel(manifests, channel);
|
|
15
|
+
const [first, second] = matches;
|
|
16
|
+
if (first === undefined)
|
|
17
|
+
throw noBrain(channel, manifests);
|
|
18
|
+
if (second !== undefined)
|
|
19
|
+
throw ambiguous(channel, matches);
|
|
20
|
+
return first;
|
|
21
|
+
}
|
|
22
|
+
function noBrain(channel, manifests) {
|
|
23
|
+
const built = manifests.map((manifest) => manifest.channel.handle || manifest.channel.name);
|
|
24
|
+
return new YouTubeError('NOT_FOUND', `No brain has been built for "${channel}".`, {
|
|
25
|
+
nextStep: built.length === 0
|
|
26
|
+
? 'Call build_brain with the channel URL or handle to build one.'
|
|
27
|
+
: `Call build_brain to build it. Brains that exist: ${built.join(', ')}.`,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function ambiguous(channel, matches) {
|
|
31
|
+
const names = matches.map((manifest) => manifest.channel.channelId);
|
|
32
|
+
return new YouTubeError('INVALID_INPUT', `"${channel}" matches more than one brain.`, {
|
|
33
|
+
nextStep: `Use the channel id instead: ${names.join(', ')}.`,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=brain-lookup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brain-lookup.js","sourceRoot":"","sources":["../../src/utils/brain-lookup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAe;IAChD,MAAM,SAAS,GAAG,MAAM,aAAa,EAAE,CAAC;IACxC,MAAM,OAAO,GAAG,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAElD,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;IAEhC,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC3D,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAE5D,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,OAAO,CAAC,OAAe,EAAE,SAA0B;IAC1D,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5F,OAAO,IAAI,YAAY,CAAC,WAAW,EAAE,gCAAgC,OAAO,IAAI,EAAE;QAChF,QAAQ,EACN,KAAK,CAAC,MAAM,KAAK,CAAC;YAChB,CAAC,CAAC,+DAA+D;YACjE,CAAC,CAAC,oDAAoD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;KAC9E,CAAC,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CAAC,OAAe,EAAE,OAAwB;IAC1D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEpE,OAAO,IAAI,YAAY,CAAC,eAAe,EAAE,IAAI,OAAO,gCAAgC,EAAE;QACpF,QAAQ,EAAE,+BAA+B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;KAC7D,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every file a brain is made of.
|
|
3
|
+
*
|
|
4
|
+
* Separate from the modules that read and write them so that the manifest layer
|
|
5
|
+
* and the index layer can each know where things live without importing each
|
|
6
|
+
* other.
|
|
7
|
+
*
|
|
8
|
+
* A brain is keyed on the channel id rather than the handle: handles get
|
|
9
|
+
* renamed, and a renamed handle would orphan the brain built under the old one.
|
|
10
|
+
* The id is also what reaches the filesystem, so it is validated here — once,
|
|
11
|
+
* at the only place that turns one into a path.
|
|
12
|
+
*/
|
|
13
|
+
export declare function brainsDir(): string;
|
|
14
|
+
export declare function brainDir(channelId: string): string;
|
|
15
|
+
export declare function manifestPath(channelId: string): string;
|
|
16
|
+
export declare function chunksPath(channelId: string): string;
|
|
17
|
+
export declare function profilePath(channelId: string): string;
|
|
18
|
+
export declare function lockPath(channelId: string): string;
|
|
19
|
+
//# sourceMappingURL=brain-paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brain-paths.d.ts","sourceRoot":"","sources":["../../src/utils/brain-paths.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AAEH,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAElD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { dataDir } from './paths.js';
|
|
3
|
+
import { assertChannelId } from './validate.js';
|
|
4
|
+
/**
|
|
5
|
+
* Every file a brain is made of.
|
|
6
|
+
*
|
|
7
|
+
* Separate from the modules that read and write them so that the manifest layer
|
|
8
|
+
* and the index layer can each know where things live without importing each
|
|
9
|
+
* other.
|
|
10
|
+
*
|
|
11
|
+
* A brain is keyed on the channel id rather than the handle: handles get
|
|
12
|
+
* renamed, and a renamed handle would orphan the brain built under the old one.
|
|
13
|
+
* The id is also what reaches the filesystem, so it is validated here — once,
|
|
14
|
+
* at the only place that turns one into a path.
|
|
15
|
+
*/
|
|
16
|
+
export function brainsDir() {
|
|
17
|
+
return dataDir('brains');
|
|
18
|
+
}
|
|
19
|
+
export function brainDir(channelId) {
|
|
20
|
+
return join(brainsDir(), assertChannelId(channelId));
|
|
21
|
+
}
|
|
22
|
+
export function manifestPath(channelId) {
|
|
23
|
+
return join(brainDir(channelId), 'manifest.json');
|
|
24
|
+
}
|
|
25
|
+
export function chunksPath(channelId) {
|
|
26
|
+
return join(brainDir(channelId), 'chunks.json');
|
|
27
|
+
}
|
|
28
|
+
export function profilePath(channelId) {
|
|
29
|
+
return join(brainDir(channelId), 'profile.md');
|
|
30
|
+
}
|
|
31
|
+
export function lockPath(channelId) {
|
|
32
|
+
return join(brainDir(channelId), 'build.lock');
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=brain-paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brain-paths.js","sourceRoot":"","sources":["../../src/utils/brain-paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD;;;;;;;;;;;GAWG;AAEH,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,SAAiB;IACxC,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,eAAe,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,SAAiB;IAC1C,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,SAAiB;IAC3C,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,YAAY,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,SAAiB;IACxC,OAAO,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,YAAY,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BrainChunk } from '../brain-schemas.js';
|
|
2
|
+
/**
|
|
3
|
+
* Phrases a creator repeats across videos.
|
|
4
|
+
*
|
|
5
|
+
* This is the one statistic here that is not a plain count, so it is worth
|
|
6
|
+
* saying what it does and does not claim. A phrase appearing in several
|
|
7
|
+
* different videos is repeated; that is a fact about the corpus. Whether it is
|
|
8
|
+
* a catchphrase, a verbal tic or a segment title is a judgement, and belongs to
|
|
9
|
+
* whoever reads the list.
|
|
10
|
+
*
|
|
11
|
+
* Counting every 3-to-6-word window at once would hold millions of keys in
|
|
12
|
+
* memory. Instead each length is counted only where the length below it
|
|
13
|
+
* survived — a phrase cannot recur more often than its own prefix does — so all
|
|
14
|
+
* but the first pass work on a small candidate set.
|
|
15
|
+
*
|
|
16
|
+
* It is still the most expensive thing done to a corpus: 12.6s over 100,000
|
|
17
|
+
* passages, which is why a build runs it once at the end rather than at every
|
|
18
|
+
* checkpoint.
|
|
19
|
+
*/
|
|
20
|
+
export declare const PHRASE_MIN_WORDS = 3;
|
|
21
|
+
export declare const PHRASE_MAX_WORDS = 6;
|
|
22
|
+
/** Repetition inside one video is a habit of that video, not of the channel. */
|
|
23
|
+
export declare const PHRASE_MIN_VIDEOS = 3;
|
|
24
|
+
export declare const PHRASE_LIMIT = 20;
|
|
25
|
+
export interface RecurringPhrase {
|
|
26
|
+
phrase: string;
|
|
27
|
+
videoCount: number;
|
|
28
|
+
occurrences: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function findRecurringPhrases(chunks: BrainChunk[]): RecurringPhrase[];
|
|
31
|
+
//# sourceMappingURL=brain-phrases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brain-phrases.d.ts","sourceRoot":"","sources":["../../src/utils/brain-phrases.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD;;;;;;;;;;;;;;;;;GAiBG;AAEH,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,YAAY,KAAK,CAAC;AAE/B,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAaD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,eAAe,EAAE,CAuB5E"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { isStopWord } from './search-index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Phrases a creator repeats across videos.
|
|
4
|
+
*
|
|
5
|
+
* This is the one statistic here that is not a plain count, so it is worth
|
|
6
|
+
* saying what it does and does not claim. A phrase appearing in several
|
|
7
|
+
* different videos is repeated; that is a fact about the corpus. Whether it is
|
|
8
|
+
* a catchphrase, a verbal tic or a segment title is a judgement, and belongs to
|
|
9
|
+
* whoever reads the list.
|
|
10
|
+
*
|
|
11
|
+
* Counting every 3-to-6-word window at once would hold millions of keys in
|
|
12
|
+
* memory. Instead each length is counted only where the length below it
|
|
13
|
+
* survived — a phrase cannot recur more often than its own prefix does — so all
|
|
14
|
+
* but the first pass work on a small candidate set.
|
|
15
|
+
*
|
|
16
|
+
* It is still the most expensive thing done to a corpus: 12.6s over 100,000
|
|
17
|
+
* passages, which is why a build runs it once at the end rather than at every
|
|
18
|
+
* checkpoint.
|
|
19
|
+
*/
|
|
20
|
+
export const PHRASE_MIN_WORDS = 3;
|
|
21
|
+
export const PHRASE_MAX_WORDS = 6;
|
|
22
|
+
/** Repetition inside one video is a habit of that video, not of the channel. */
|
|
23
|
+
export const PHRASE_MIN_VIDEOS = 3;
|
|
24
|
+
export const PHRASE_LIMIT = 20;
|
|
25
|
+
export function findRecurringPhrases(chunks) {
|
|
26
|
+
const corpus = wordsByVideo(chunks);
|
|
27
|
+
const kept = [];
|
|
28
|
+
let candidates = new Set();
|
|
29
|
+
for (let length = PHRASE_MIN_WORDS; length <= PHRASE_MAX_WORDS; length++) {
|
|
30
|
+
const tallies = countPhrases(corpus, length, length === PHRASE_MIN_WORDS ? undefined : candidates);
|
|
31
|
+
candidates = new Set();
|
|
32
|
+
for (const [phrase, tally] of tallies) {
|
|
33
|
+
if (tally.videoCount < PHRASE_MIN_VIDEOS)
|
|
34
|
+
continue;
|
|
35
|
+
candidates.add(phrase);
|
|
36
|
+
kept.push({ phrase, videoCount: tally.videoCount, occurrences: tally.occurrences });
|
|
37
|
+
}
|
|
38
|
+
if (candidates.size === 0)
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
return rank(kept);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The corpus as one word list per video, in a stable order.
|
|
45
|
+
*
|
|
46
|
+
* Grouping is what lets distinct-video counts be kept as a single field per
|
|
47
|
+
* phrase rather than a set of video ids per phrase, which for a large channel
|
|
48
|
+
* is the difference between tens of megabytes and hundreds.
|
|
49
|
+
*/
|
|
50
|
+
function wordsByVideo(chunks) {
|
|
51
|
+
const byVideo = new Map();
|
|
52
|
+
for (const chunk of chunks) {
|
|
53
|
+
const words = byVideo.get(chunk.videoId) ?? [];
|
|
54
|
+
words.push(...phraseWords(chunk.text));
|
|
55
|
+
byVideo.set(chunk.videoId, words);
|
|
56
|
+
}
|
|
57
|
+
return [...byVideo.values()];
|
|
58
|
+
}
|
|
59
|
+
/** Lowercased words, apostrophes kept because "don't" is one word. */
|
|
60
|
+
function phraseWords(text) {
|
|
61
|
+
return text
|
|
62
|
+
.toLowerCase()
|
|
63
|
+
.split(/[^\p{L}\p{N}']+/u)
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
function countPhrases(corpus, length, candidatePrefixes) {
|
|
67
|
+
const tallies = new Map();
|
|
68
|
+
for (const [videoIndex, words] of corpus.entries()) {
|
|
69
|
+
for (let start = 0; start + length <= words.length; start++) {
|
|
70
|
+
const window = words.slice(start, start + length);
|
|
71
|
+
// A longer phrase only recurs where its prefix already did. A phrase
|
|
72
|
+
// whose opening words are all filler is therefore never reached, which is
|
|
73
|
+
// the intended trade: it costs "and then i went on" to drop "and then i".
|
|
74
|
+
if (candidatePrefixes !== undefined) {
|
|
75
|
+
if (!candidatePrefixes.has(window.slice(0, length - 1).join(' ')))
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
else if (window.every(isStopWord)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const phrase = window.join(' ');
|
|
82
|
+
const tally = tallies.get(phrase);
|
|
83
|
+
if (tally === undefined) {
|
|
84
|
+
tallies.set(phrase, { occurrences: 1, videoCount: 1, lastVideoIndex: videoIndex });
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
tally.occurrences++;
|
|
88
|
+
if (tally.lastVideoIndex !== videoIndex) {
|
|
89
|
+
tally.videoCount++;
|
|
90
|
+
tally.lastVideoIndex = videoIndex;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return tallies;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Drop the truncations, then order by reach.
|
|
98
|
+
*
|
|
99
|
+
* A phrase contained in a longer kept phrase that is just as widespread says
|
|
100
|
+
* nothing the longer one does not — otherwise every long catchphrase drags in
|
|
101
|
+
* three truncations of itself. Subsumption has to consider the longest first,
|
|
102
|
+
* which is not the order worth reporting, so the two happen in separate passes.
|
|
103
|
+
*/
|
|
104
|
+
function rank(phrases) {
|
|
105
|
+
const longestFirst = [...phrases].sort((a, b) => b.phrase.split(' ').length - a.phrase.split(' ').length || b.videoCount - a.videoCount);
|
|
106
|
+
const distinct = [];
|
|
107
|
+
for (const candidate of longestFirst) {
|
|
108
|
+
const subsumed = distinct.some((existing) => existing.phrase.includes(candidate.phrase) && existing.videoCount >= candidate.videoCount);
|
|
109
|
+
if (!subsumed)
|
|
110
|
+
distinct.push(candidate);
|
|
111
|
+
}
|
|
112
|
+
return distinct
|
|
113
|
+
.sort((a, b) => b.videoCount - a.videoCount || b.occurrences - a.occurrences)
|
|
114
|
+
.slice(0, PHRASE_LIMIT);
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=brain-phrases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brain-phrases.js","sourceRoot":"","sources":["../../src/utils/brain-phrases.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BrainChunk, BrainStats, BrainVideoState } from '../brain-schemas.js';
|
|
2
|
+
/**
|
|
3
|
+
* What the server can say about a channel without inferring anything.
|
|
4
|
+
*
|
|
5
|
+
* Everything here is counted or measured from the corpus. Deliberately absent:
|
|
6
|
+
* named entities, because auto-generated captions arrive lowercase and
|
|
7
|
+
* unpunctuated so capitalisation heuristics produce noise; and distinctive
|
|
8
|
+
* vocabulary by TF-IDF, because there is no honest background corpus to compare
|
|
9
|
+
* a channel against and inventing one would produce confident nonsense.
|
|
10
|
+
*
|
|
11
|
+
* Anything the numbers cannot support belongs in the profile, which a model
|
|
12
|
+
* writes from passages it can cite.
|
|
13
|
+
*/
|
|
14
|
+
export interface StatsOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Whether to run the phrase pass, which reads every word of the corpus. It is
|
|
17
|
+
* measured in seconds at the sizes a brain is allowed to reach — 12.6s over
|
|
18
|
+
* 100,000 passages on the machine this was written on — so a build that
|
|
19
|
+
* checkpoints every ten videos must not pay for it fifty times over. Omitted
|
|
20
|
+
* rather than guessed at when it has not been run.
|
|
21
|
+
*/
|
|
22
|
+
phrases: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function computeStats(all: BrainVideoState[], chunks: BrainChunk[], options: StatsOptions): BrainStats;
|
|
25
|
+
//# sourceMappingURL=brain-stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brain-stats.d.ts","sourceRoot":"","sources":["../../src/utils/brain-stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGnF;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,eAAe,EAAE,EACtB,MAAM,EAAE,UAAU,EAAE,EACpB,OAAO,EAAE,YAAY,GACpB,UAAU,CAyBZ"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { findRecurringPhrases } from './brain-phrases.js';
|
|
2
|
+
export function computeStats(all, chunks, options) {
|
|
3
|
+
// A video the filters ruled out is not part of this brain, so it does not get
|
|
4
|
+
// to move the brain's numbers — its date must not stretch the upload range,
|
|
5
|
+
// and its length must not enter the speaking rate. It is reported as its own
|
|
6
|
+
// count instead, because "we chose not to read 40 videos" is worth knowing.
|
|
7
|
+
const videos = all.filter((video) => video.state !== 'excluded');
|
|
8
|
+
const uploadDates = videos
|
|
9
|
+
.map((video) => video.uploadDate)
|
|
10
|
+
.filter(Boolean)
|
|
11
|
+
.sort();
|
|
12
|
+
return {
|
|
13
|
+
videoCount: videos.length,
|
|
14
|
+
excludedCount: all.length - videos.length,
|
|
15
|
+
indexedCount: countState(videos, 'indexed'),
|
|
16
|
+
noCaptionsCount: countState(videos, 'no-captions'),
|
|
17
|
+
failedCount: countState(videos, 'failed'),
|
|
18
|
+
pendingCount: countState(videos, 'pending'),
|
|
19
|
+
chunkCount: chunks.length,
|
|
20
|
+
totalWords: videos.reduce((total, video) => total + video.wordCount, 0),
|
|
21
|
+
medianWordsPerMinute: medianWordsPerMinute(videos),
|
|
22
|
+
...firstAndLast(uploadDates),
|
|
23
|
+
uploadsPerMonth: uploadsPerMonth(uploadDates),
|
|
24
|
+
...(options.phrases ? { recurringPhrases: findRecurringPhrases(chunks) } : {}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function countState(videos, state) {
|
|
28
|
+
return videos.filter((video) => video.state === state).length;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The median rather than the mean: one mis-timed transcript, or a ten-hour
|
|
32
|
+
* ambience upload with forty words in it, would drag an average anywhere.
|
|
33
|
+
*/
|
|
34
|
+
function medianWordsPerMinute(videos) {
|
|
35
|
+
const rates = videos
|
|
36
|
+
.filter((video) => video.durationSeconds > 0 && video.wordCount > 0)
|
|
37
|
+
.map((video) => (video.wordCount / video.durationSeconds) * 60)
|
|
38
|
+
.sort((a, b) => a - b);
|
|
39
|
+
if (rates.length === 0)
|
|
40
|
+
return 0;
|
|
41
|
+
const middle = Math.floor(rates.length / 2);
|
|
42
|
+
const median = rates.length % 2 === 0
|
|
43
|
+
? ((rates[middle - 1] ?? 0) + (rates[middle] ?? 0)) / 2
|
|
44
|
+
: (rates[middle] ?? 0);
|
|
45
|
+
return Math.round(median);
|
|
46
|
+
}
|
|
47
|
+
function firstAndLast(sortedDates) {
|
|
48
|
+
const first = sortedDates[0];
|
|
49
|
+
const last = sortedDates[sortedDates.length - 1];
|
|
50
|
+
return {
|
|
51
|
+
...(first === undefined ? {} : { firstUpload: first }),
|
|
52
|
+
...(last === undefined ? {} : { lastUpload: last }),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Months with no uploads are absent rather than zero: this is a tally, not a series. */
|
|
56
|
+
function uploadsPerMonth(sortedDates) {
|
|
57
|
+
const months = new Map();
|
|
58
|
+
for (const date of sortedDates) {
|
|
59
|
+
const month = date.slice(0, 7);
|
|
60
|
+
months.set(month, (months.get(month) ?? 0) + 1);
|
|
61
|
+
}
|
|
62
|
+
return [...months.entries()].map(([month, videos]) => ({ month, videos }));
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=brain-stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brain-stats.js","sourceRoot":"","sources":["../../src/utils/brain-stats.ts"],"names":[],"mappings":"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"}
|