Index transcripts into library search (FTS), both UIs

The 🔍 library search now matches spoken words, not just titles /
channels / descriptions.

- database.rs: add a `transcript` column to the `video_search` FTS5 index.
  Fresh DBs get it directly; existing ones are migrated (FTS5 has no ADD
  COLUMN, so the table is recreated and search_meta cleared to force a
  one-time reindex). `sync_search_index` reads each video's first subtitle
  (mtime-gated like the description), flattens it via the shared `vtt`
  parser (`transcript_text`, capped at 256 KB), and indexes it. Search
  snippets now use column -1 so the excerpt comes from whichever column
  matched (description or transcript).
- library.rs: `build_search_entries` passes the first subtitle path.
- Both UIs: search copy updated to mention transcripts.

Tests: a unit test (a word only in the .vtt is found), a migration test
(seed an old 5-column index + stale meta, open, confirm the recreated
index indexes transcript text), and the search integration test now seeds
a subtitle and asserts a spoken-only word hits. Also bumped the test
server's startup budget so the ffmpeg-heavy dedup test can't starve a
sibling server into a flaky timeout. 120 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Luna 2026-06-07 06:55:00 -07:00
parent 4136a9468b
commit e840d55332
5 changed files with 132 additions and 18 deletions

View file

@ -702,6 +702,7 @@ pub fn build_search_entries(lib: &[Channel]) -> Vec<crate::database::SearchEntry
channel: ch.name.clone(),
title: v.title.clone(),
description_path: v.description_path.clone(),
subtitle_path: v.subtitles.first().map(|s| s.path.clone()),
});
}
}