---
title: "ClipFinder: Natural-Language Video Moment Search in One app.py"
date: "2026-09-09"
author: "Pierre Brunelle"
tags:
  - Video Search
  - CLIP
  - Frame Iterator
  - Multimodal AI
  - TableModel
  - Pixeltable Cloud
  - Vector Search
description: "Upload MP4s, search “person typing on a laptop,” get the top moments with a preview frame. Pixeltable is the frames, CLIP index, and incremental insert — not FFmpeg plus Pinecone."
url: "https://www.pixeltable.com/blog/clipfinder-semantic-video-moment-search"
---

# ClipFinder: Natural-Language Video Moment Search in One app.py

**Summary:** ClipFinder is the product a video-tool founder actually wants: drop raw MP4s, search a visual moment in English, land on a timestamp and a preview. You do not write an FFmpeg worker, a frame table, and a vector database. You declare `Videos` and a `Frames` view. CLIP is an index on the frame. Tomorrow’s insert is searchable without a rebuild. `pip install pixeltable`.

 
## The Product

 

 - Ingest an MP4 with `video_id`, title, category

 - Sample the file every second without an FFmpeg script

 - Text query → top 5 moments with a preview frame

 - A new upload tomorrow is indexed; the old library is not re-embedded

 

 
Image-to-video search is the sibling path: [find a video from an image](/blog/find-video-from-image-pixelsearch). This post is text → moment. The 20-minute kitchen-sink pipeline is [video intelligence](/blog/video-intelligence-pipeline-tutorial). Here the receipt is one query.

 
## The Stitch You Delete

 
Object store for bytes. A job that dumps JPEGs. CLIP in a GPU box. Pinecone for the vectors. A cron so yesterday’s clip is in the index. That is the [Frankenstein stack](/blog/deconstructing-ai-frankenstein-stack). Pixeltable is the catalog those joints were approximating.

 
## The Receipt

 
```python
import pixeltable as pxt
from pixeltable.functions.huggingface import clip
from pixeltable.functions.video import frame_iterator
from pixeltable.serving import FastAPIRouter

TableModel = pxt.model_base()
clip_embed = clip.using(model_id='openai/clip-vit-base-patch32')

class Videos(TableModel, name='videos'):
 video_id: pxt.String
 title: pxt.String
 category: pxt.String
 video: pxt.Video

class Frames(
 TableModel,
 name='frames',
 base=Videos,
 iterator=frame_iterator(video=Videos.video, fps=1),
):
 __indexes__ = [pxt.EmbeddingIndex(frame, image_embed=clip_embed)]

@pxt.query
def find_video_moments(query: str, top_k: int = 5):
 sim = Frames.frame.similarity(string=query)
 return (
 Frames.order_by(sim, asc=False)
 .limit(top_k)
 .select(Videos.video_id, Videos.title, Frames.frame)
 )

api = FastAPIRouter(name='clipfinder')
api.add_insert_route(Videos, path='/videos', inputs=[Videos.video, Videos.video_id, Videos.title, Videos.category])
api.add_query_route(path='/search', query=find_video_moments)
```

 
`frame` is the iterator column. One CLIP embedder on that column serves the text query. Insert a row; the view and the index follow. Same file locally and on Cloud:

 
```bash
pxt init
pxt schema update app.py clipfinder
pxt service update app.py clipfinder
pxt db update pxt://org:db
pxt schema update app.py pxt://org:db
pxt service update app.py pxt://org:db
```

 
## What This Is Not

 
Not a hosted consumer search engine — that is [PixelSearch](/pixelsearch). Not per-moment audio. Not Twelve Labs. Poster-only search is the Photos post; this one is every sampled frame.

 
## People Also Ask

 
**Do I need Pinecone?** No. The index lives on the view.

 
**What fps?** `1` is the default for interviews and B-roll. Lower it for long archives.

 
**How do I go to Cloud?** Same `app.py`. `PIXELTABLE_API_KEY`, `pxt://org:db`, then `pxt db update` → `pxt schema update` → `pxt service update`.

 
**Is this an AI automation workflow?** Yes. Insert an MP4; frames and CLIP run as columns. The category is [AI automation workflow](/blog/ai-automation-workflow).

 
## Search the Moment, Not the Filename

 
Declare the table. Apply the file. Insert the MP4. Call `find_video_moments`. Keep the product. Delete the frame farm.

 
Episode chaptering and a highlight cut on the parent row is [ReelForge](/blog/reelforge-podcast-chaptering-viral-clips). Slide-grounded lecture Q&A is [LectureSync](/blog/lecturesync-slide-lecture-qa).

 

 - **[Pixeltable on GitHub](https://github.com/pixeltable/pixeltable)**: `pip install pixeltable`

 - **[Get started](/get-started.md)**

 - **[CallSense](/blog/callsense-sales-call-intelligence)** — sales-call auditor

 - **[DocuVision](/blog/docuvision-pdf-chart-qa)** — PDF Q&A

 - **[SnapCatalog](/blog/snapcatalog-product-visual-search)** — shop-the-look

 - **[SafeStream](/blog/safestream-ugc-video-moderation)** — UGC gate

 - **[ClaimBot](/blog/claimbot-multimodal-fnol-triage)** — claims triage

 - **[Pixeltable documentation](https://docs.pixeltable.com)**