Reference architecture

Video search reference architecture

Video search ranks a moment in a library, not a filename. Sample stills, embed them in a shared text-and-image space, and return the parent clip with a presentation timestamp. A hosted video API is a different product: JSON over HTTP, no table you own.

Updated · Matching docs cookbook

Video search is not "send the file to a chat model." That can describe one clip. It does not rank a library, and it does not get cheaper as the library grows. Search retrieves a timestamped still, and optionally a transcript line, then returns the parent clip.

This page names the slots. The Python how-tos are How do I index a video library and search it by text, How do I extract keyframes from video, and How do I build video RAG. Compare Pixeltable vs Twelve Labs and Pixeltable vs Mixpeek when a managed video API is the product.

Video search: a clip becomes timestamped stills, then a ranked moment

Video search: a clip becomes timestamped stills, then a ranked moment1. Ingest: Video file (upload or URL). 2. Store: Video column (or S3 + path table). 3. Sample: frame_iterator (fps, keyframes, or count), Optional speech (extract_audio + Whisper). 4. Index: CLIP on frames (or Twelve Labs / Mixpeek), Transcript index (or pgvector). 5. Serve: Search HTTP (FastAPIRouter or their API)1. IngestVideo fileupload or URL2. StoreVideo columnor S3 + path table3. Sampleframe_iteratorfps, keyframes, or countOptional speechextract_audio + Whisper4. IndexCLIP on framesor Twelve Labs / MixpeekTranscript indexor pgvector5. ServeSearch HTTPFastAPIRouter or their API

Role of each component

Clip store
A typed Video column, or an object store plus a path table. Later frame extraction needs a media type, not a spreadsheet of paths.
Frame sampler
One row per still. keyframes_only emits I-frames. fps=1 samples once a second. num_frames emits evenly spaced stills. Set only one of those three. Each row keeps frame_attrs with a presentation timestamp.
Optional speech path
extract_audio plus Whisper (or a hosted STT API on the same column). Useful when the question is about what was said rather than what was shown.
Multimodal index
CLIP on the frame column so a sentence retrieves a still. A separate vector database is optional. Twelve Labs Marengo is a hosted video embedding you can also call as a column.
Search HTTP
Frame search returns title, frame_attrs, and score. Transcript search returns the clip, not a timestamp inside it. FastAPIRouter exposes insert and both searches. A managed API returns JSON and no table.

Honest alternatives per slot

Keep the named engine when it is already the contract. Pixeltable fills the slot when you want that job on the same table as the files.

Named alternatives for Video search reference architecture
SlotRolePixeltableNamed alternatives
Object storeBytes for the clipsA Video column; media under PIXELTABLE_HOME, or a URL you insert
  • Amazon S3. The bucket is already the archive
  • Cloudflare R2. S3-compatible storage with no egress fee on that vendor
  • MinIO. An S3 API inside your network
Frame extractionStills with timestampsframe_iterator on a view; frame_attrs["time"] is the presentation timestamp
  • ffmpeg in a worker. You already dump JPEGs and must keep that job
  • PySceneDetect. You want shot boundaries rather than a fixed fps
Video embedding / search APIRank a moment from a sentence or an imageCLIP EmbeddingIndex on the frame column; optional Whisper on the clip
  • Twelve Labs. You want hosted Marengo / Pegasus and no GPU of your own
  • Mixpeek. A managed multimodal retrieval API is the product
  • VideoDB. A hosted video database API, not a table you own
  • Qdrant or pgvector. You already embed frames elsewhere and only need ANN
Speech-to-textTranscript for spoken-word searchextract_audio plus whisper.transcribe, or openai.transcriptions on the same column
  • Deepgram, AssemblyAI, Gladia. A hosted STT API is the model choice; Pixeltable is not STT
  • WhisperX. You need speakers and word-level timestamps; that is a separate cookbook
ServingInsert a clip and searchFastAPIRouter beside the tables
  • Their HTTP API. Twelve Labs or Mixpeek is the serving contract
  • Hand-written FastAPI. You already join S3, a vector DB, and a worker

Where Pixeltable fits

Pixeltable is the unified multimodal backend for this architecture: a Video column, a frame_iterator view, CLIP EmbeddingIndex, optional Whisper, and FastAPIRouter insert plus search in one file. Keep S3 when the bucket is the archive. Keep Twelve Labs or Mixpeek when a managed video API is the product. Keep Qdrant or pgvector when you already embed frames in another job. Pixeltable Cloud hosts the same app file.

import pixeltable as pxt
from pixeltable.functions.huggingface import clip, sentence_transformer
from pixeltable.functions.video import extract_audio, frame_iterator
from pixeltable.functions.whisper import transcribe
from pixeltable.serving import FastAPIRouter
TableModel = pxt.model_base()
clip_embed = clip.using(model_id='openai/clip-vit-base-patch32')
text_embed = sentence_transformer.using(model_id='all-MiniLM-L6-v2')
class Videos(TableModel, name='videos'):
title: pxt.String
video: pxt.Video
audio = extract_audio(video)
transcript = transcribe(audio, model='base').text.astype(pxt.String)
__indexes__ = [
pxt.EmbeddingIndex(transcript, string_embed=text_embed),
]
class Frames(
TableModel,
name='frames',
base=Videos,
iterator=frame_iterator(video=Videos.video, fps=1),
):
__indexes__ = [
pxt.EmbeddingIndex(frame, embedding=clip_embed),
]
@pxt.query
def search_video(query: str, limit: int = 5):
sim = Frames.frame.similarity(string=query)
return (
Frames.order_by(sim, asc=False)
.limit(limit)
.select(title=Videos.title, frame_attrs=Frames.frame_attrs, score=sim)
)
@pxt.query
def search_speech(query: str, limit: int = 5):
sim = Videos.transcript.similarity(string=query)
return (
Videos.order_by(sim, asc=False)
.limit(limit)
.select(Videos.title, Videos.video, score=sim)
)
api = FastAPIRouter(name='video_lib')
api.add_insert_route(
Videos,
path='/videos',
inputs=[Videos.title],
uploadfile_inputs=[Videos.video],
)
api.add_query_route(path='/search', query=search_video)
api.add_query_route(path='/search-speech', query=search_speech)
# pxt init
# pxt schema update app.py video_lib -f
# pxt service update app.py video_lib -f

To run a sketch, install pixeltable[serve] and the packages its model functions import (such as sentence-transformers, openai, or openai-whisper), export the provider key a model call reads (such as OPENAI_API_KEY), save the file as app.py, and run the commands in its last lines. The tables start empty: insert a row, or POST to an insert route, then select the computed columns. Get started covers the CLI.

Questions

When should I use Twelve Labs instead?

When you want hosted Marengo and Pegasus, no GPU of your own, and you are fine calling their API. Pixeltable can also call those models as columns when you want the table and the timestamps in a file you own. Pixeltable vs Twelve Labs is that split.

Do I index every encoded frame?

Almost never. I-frames, a fixed fps, or a fixed count are the documented sampling contracts. Every encoded frame explodes row counts and embedding cost. Only one of fps, num_frames, or keyframes_only can be set.

Where is the timestamp?

frame_attrs["time"] is the presentation timestamp in seconds (pts times the stream time base). frame_attrs["index"] is the stream index. Seconds from the start of the clip was the retired pos_msec value.