Video Search Backend for CMU Database Course
Builds a semantic video search backend that extracts transcripts, generates local embeddings, and stores them in LanceDB for query-time frame extraction.
What this file does
Builds a semantic video search backend that extracts transcripts, generates local embeddings, and stores them in LanceDB for query-time frame extraction.
When to use it
- Building a searchable video archive from a YouTube playlist
- Replacing expensive cloud embedding APIs with local CPU models
- Implementing lazy blob loading for on-demand video frame extraction
- Creating an agent-friendly search engine that returns structured results with timestamps
Assumes this stack
Video Search Backend for CMU Database Course
Context
Build a "chat with your video" search backend for the CMU Database Course YouTube playlist. The system extracts transcripts, generates text embeddings, and stores everything in LanceDB for semantic search. Video frames are extracted on-demand at query time using Lance's blob API.
Playlist: https://www.youtube.com/playlist?list=PLSE8ODhjZXjZEVnVTtgDWw6P3wA_gDwj4
Architecture
User Query: "explain B+ tree insertion"
│
▼
┌───────────────────────┐
│ Embed query (local) │
│ bge-base-en-v1.5 │
└───────────────────────┘
│
▼
┌───────────────────────┐
│ Search transcripts │
│ (LanceDB vector) │
└───────────────────────┘
│
▼
Top-5 transcript chunks with timestamps
│
┌───────────┴───────────┐
▼ ▼
┌───────────────┐ ┌───────────────────────┐
│ Agent reasons │ │ Frontend: extract │
│ over text │ │ frames at timestamps │
│ chunks │ │ via Lance Blob API │
└───────────────┘ └───────────────────────┘
Key insight: For lecture videos (mostly talking heads), the transcript contains the semantic content. Video frames are extracted on-demand at query time - no frame embeddings needed.
Storage Schema
# videos table - metadata + blob for video
class VideoRecord(LanceModel):
video_id: str
title: str
duration_seconds: float
youtube_url: str
video_blob: bytes # Blob-encoded video for lazy loading via take_blobs()
# transcripts table - text embeddings for search
class TranscriptChunk(LanceModel):
chunk_id: str # {video_id}_{start_ms}
video_id: str
video_title: str
start_seconds: float
end_seconds: float
text: str # For FTS
vector: Vector(768) # bge-base-en-v1.5 embedding
Components
1. Embedding Model (Local, CPU)
- Model:
BAAI/bge-base-en-v1.5 - Dimensions: 768
- Runs locally on EC2 CPU - no API calls needed
- Library:
sentence-transformers
2. Transcript Chunking
- Chunk duration: 30 seconds
- Why 30s: Balances context (enough to understand a concept) with granularity (specific enough for search)
3. Video Streaming (Lazy Blob Loading)
Using Lance's blob API (reads directly from LanceDB Enterprise's S3 bucket):
# At serving time, stream video bytes on demand via HTTP Range requests
ds = lance.dataset("s3://lancedb-bucket/database/videos.lance")
blobs = ds.take_blobs("video_blob", indices=[row_idx])
blob_file = blobs[0]
blob_file.seek(start) # Seek to requested byte offset
data = blob_file.read(length) # Read only the requested range from S3
Row index mappings and blob sizes are cached (immutable after ingest), but BlobFile handles are created fresh per read to avoid stale seek/read state with S3-backed blobs.
What We Eliminated
| Before | After |
|---|---|
| Frame extraction at ingest time | On-demand at query time |
| 20,000+ frame embeddings | 0 frame embeddings |
| HF Inference Endpoint ($50-100) | Local CPU embeddings ($0) |
| frames table | Not needed |
| Image embedding model | Not needed |
Cost reduction: ~$100 → ~$0 for embeddings
Dependencies
dependencies = [
"lancedb>=0.15.0",
"pylance>=0.15.0", # For blob API
"boto3>=1.34.0",
"yt-dlp>=2024.1.0",
"av>=12.0.0", # On-demand frame extraction
"pillow>=10.0.0",
"sentence-transformers>=2.5.0", # Local embeddings
"youtube-transcript-api>=0.6.0",
"pydantic>=2.5.0",
"pydantic-settings>=2.1.0",
"pyyaml>=6.0.0",
"typer[all]>=0.9.0",
]
Implementation Steps
Phase 1: Project Setup
- ✅ Create
pyproject.tomlwith dependencies - ✅ Create
config/settings.yaml - ✅ Implement
config.py
Phase 2: Storage Layer
- ✅ Implement
storage/lancedb_client.py - ✅ Implement
storage/blob_utils.py(on-demand frame extraction)
Phase 3: Pipelines
- ✅ Implement
pipelines/download.py - ✅ Implement
pipelines/transcripts.py - ✅ Implement
models/embeddings.py(local sentence-transformers) - ✅ Implement
pipelines/ingest.py
Phase 4: Search & CLI
- ✅ Implement
search/engine.py - ✅ Implement
cli/main.py
Verification
-
Local test:
uv run scripts/test_local.py --video-url "https://youtube.com/watch?v=..." --test full -
Search test:
uv run scripts/search.py "B+ tree" --local -
Frame extraction test:
uv run scripts/extract_frames.py VIDEO_ID "10.5,30.0,60.0"
Future: Agent Integration
The search engine returns structured results for agent consumption:
results = engine.search("explain B+ tree insertion", limit=5)
# Returns: [SearchResult(video_id, start_seconds, end_seconds, text, score), ...]
# Agent uses text for reasoning
# Frontend extracts frames at timestamps for display
What's inside
Architecture diagram, storage schema, 4 component descriptions, dependency list, 4-phase implementation plan, verification commands
Change this for your project
- Replace playlist URL
https://www.youtube.com/playlist?list=PLSE8ODhjZXjZEVnVTtgDWw6P3wA_gDwj4with your own - Replace
s3://lancedb-bucket/database/videos.lancewith your LanceDB dataset path - Replace
BAAI/bge-base-en-v1.5with your preferred embedding model
Where it goes
Reference documentation for a retrieval pipeline. Keep with the ingestion or retrieval code it describes.
Worth borrowing
- On-demand frame extraction at query time instead of precomputing frame embeddings
- 30-second transcript chunking balances context and search granularity
- Caching row index mappings and blob sizes while creating fresh BlobFile handles per read
Related Documents
SUMMARY
Proposes three on-prem AI architectures, modular, hybrid, and fully local RAG, with hardware specs and vendor lists.
Retrieval & Prompts
Explains how CharMemory's extraction prompt and Vector Storage settings determine memory retrieval quality in SillyTavern.
App Review Support Guide — Switch2Go
Explains an AAC app's accessibility permissions, hardware needs, and reviewer walkthrough to pass App Store review.
RFC-BLite: High-Performance Embedded Document Database for .NET
Specifies an embedded document database for.NET with zero-allocation I/O, C-BSON format, and ACID transactions.