commit a9c364f637e3df63fa66dfab46f48edb49c214cb
parent bfbdcbb85bdcf3fa2a1cfdce260456b3ed29b3a8
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Tue, 6 Oct 2026 10:17:57 -0400
offline index reads alternate tracks; one canHoldAltTracks
The PWA's per-channel offline index (searchIndex.worker) indexes the lines an
alternate track says that the transcript does not, under the track, and the
offline result row names it. buildIndex asks one canHoldAltTracks for both the
one-shot pass and the per-record read.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
5 files changed, 40 insertions(+), 14 deletions(-)
diff --git a/common/components/searchIndex.worker.ts b/common/components/searchIndex.worker.ts
@@ -51,7 +51,7 @@ function mkIndex(): Index {
}
type RawCue = { start?: number; text?: unknown };
-type RawEntry = { slug?: unknown; cues?: unknown };
+type RawEntry = { slug?: unknown; cues?: unknown; altTracks?: unknown };
async function build(reqId: number, slug: string): Promise<void> {
// Manifest → page count.
@@ -75,13 +75,29 @@ async function build(reqId: number, slug: string): Promise<void> {
for (const entry of entries) {
const eslug = typeof entry.slug === "string" ? entry.slug : slug;
const cues = Array.isArray(entry.cues) ? entry.cues : [];
+ const said = new Set<string>();
for (const c of cues as RawCue[]) {
const text = typeof c.text === "string" ? c.text : "";
if (!text) continue;
+ said.add(text);
idx.add(id, text);
meta[id] = { slug: eslug, start: c.start, text };
id++;
}
+ // The record's alternate English tracks (lib/captionTracks.ts): a line
+ // the transcript itself does not say is indexed too, under its track.
+ const alts = Array.isArray(entry.altTracks) ? entry.altTracks : [];
+ for (const alt of alts as { track?: unknown; cues?: unknown }[]) {
+ const track = typeof alt.track === "string" ? alt.track : "";
+ if (!track || !Array.isArray(alt.cues)) continue;
+ for (const c of alt.cues as RawCue[]) {
+ const text = typeof c.text === "string" ? c.text : "";
+ if (!text || said.has(text)) continue;
+ idx.add(id, text);
+ meta[id] = { slug: eslug, start: c.start, text, track };
+ id++;
+ }
+ }
}
post({ type: "progress", reqId, slug, done: p + 1, total: pageCount });
}
diff --git a/common/components/searchIndexWorkerProtocol.ts b/common/components/searchIndexWorkerProtocol.ts
@@ -2,7 +2,9 @@
// the FlexSearch web worker (searchIndex.worker). Types only — safe to import
// from both sides.
-export type IndexHit = { slug: string; start?: number; text: string };
+// `track`: a cue from one of the record's alternate English tracks
+// (lib/captionTracks.ts) — absent for the transcript's own.
+export type IndexHit = { slug: string; start?: number; text: string; track?: string };
// Main thread → worker.
export type WorkerRequest =
diff --git a/common/controller/buildIndex.ts b/common/controller/buildIndex.ts
@@ -247,6 +247,17 @@ function storedCuesEmpty(db: { getBinaryFast(key: IndexKey): Buffer | undefined
const ALT_TRACKS_VERSION = 1;
const ALT_TRACKS_KEY = "altTracks";
+// Whether a scanned record can hold an alternate track at all — no file read.
+function canHoldAltTracks(s: {
+ transcriptKind: IndexTranscript["kind"] | null;
+ englishVttCount: number;
+}): boolean {
+ return (
+ (s.transcriptKind === "vtt" && s.englishVttCount >= 2) ||
+ (s.transcriptKind === "whisper" && s.englishVttCount >= 1)
+ );
+}
+
export type CaptionTrackChannelReport = {
reread: number;
// Re-read records whose caption text is not what the index held.
@@ -911,10 +922,7 @@ export async function buildIndex({
);
let reread = 0;
for (const s of live) {
- const can =
- (s.transcriptKind === "vtt" && s.englishVttCount >= 2) ||
- (s.transcriptKind === "whisper" && s.englishVttCount >= 1);
- if (!can) continue;
+ if (!canHoldAltTracks(s)) continue;
const pk: PathKey = [s.channelSlug, s.videoDir];
if (!mtimes.get(pk)) continue;
reread++;
@@ -1069,12 +1077,10 @@ export async function buildIndex({
// The other English tracks, where their words differ from the
// primary's (lib/captionTracks.ts). Only a record that can hold one
// reads anything.
- const canHoldAlts =
- (s.transcriptKind === "vtt" && s.englishVttCount >= 2) ||
- (s.transcriptKind === "whisper" && s.englishVttCount >= 1);
- const trackFields: TrackFields = canHoldAlts
- ? await readTrackFields(videoFullDir, s.transcriptKind!, cueList)
- : {};
+ const trackFields: TrackFields =
+ canHoldAltTracks(s) && s.transcriptKind
+ ? await readTrackFields(videoFullDir, s.transcriptKind, cueList)
+ : {};
if (trackFields.altTracks) {
alts.put(indexKey, trackFields);
altTrackRecords++;
@@ -1273,7 +1279,7 @@ export async function buildIndex({
}
if (altTrackRecords > 0) {
- log(`Alternate tracks: ${altTrackRecords} re-read record(s) hold a track whose words differ from the primary's.`);
+ log(`Alternate tracks: ${altTrackRecords} record(s) read this build hold a track whose words differ from the primary's.`);
}
for (const { pathKey, indexKey } of removed) {
diff --git a/common/lib/captionTracks.test.ts b/common/lib/captionTracks.test.ts
@@ -79,7 +79,7 @@ test("recordTracks and cuesOfTrack: primary first; an unknown track is undefined
assert.equal(cuesOfTrack(rec, "en-orig")?.[0].text, "a");
assert.equal(cuesOfTrack(rec, "en")?.[0].text, "b");
assert.equal(cuesOfTrack(rec, "en-GB"), undefined);
- assert.deepEqual(recordTracks({ cues: [] }), []);
+ assert.deepEqual(recordTracks({}), []);
});
test("a search finds a word every track says once, in the primary, and an alternate's own word there", () => {
diff --git a/export/app/components/OfflineManager.tsx b/export/app/components/OfflineManager.tsx
@@ -15,6 +15,7 @@ import {
workerSupported,
type IndexHit,
} from "yt-dlp-transcript-common/components/searchIndexWorkerClient";
+import { inTrackLabel } from "yt-dlp-transcript-common/lib/captionTracks";
export type OfflineChannel = { slug: string; name: string };
@@ -336,6 +337,7 @@ function OfflineSearch({
{typeof hit.start === "number"
? ` · ${formatTime(hit.start)}`
: ""}
+ {hit.track ? ` · ${inTrackLabel(hit.track)}` : ""}
</span>
</li>
))}