Skip to main content

Cinematlas

Ask a question. Get the second in the video that answers it.

pip install "cinematlas[whisper]"
cinematlas doctor
cinematlas ingest "www.b.com/keynote.mp4"
cinematlas search "when do they announce pricing?"
 1. vid_3f2a…#12 @    7:11  Pricing starts at ten dollars a seat.
    https://www.b.com/keynote.mp4#t=431

The finding: fuse in the embedding, not in the ranking

Video search has two kinds of question. "How many medals has his beer won?" is about what was said. "The one with the girl on hay bales" is about what was shown.

The standard design indexes speech and pictures separately, retrieves from each, and merges the ranked lists. That fails, because the lists disagree on every question that's about only one of the two, and merging averages the disagreement away. Cinematlas embeds each scene's keyframe and its transcript into one vector, so there's nothing to reconcile.

Mean Hit@1 First corpus Held-out corpus
merged rankings (rank fusion, tuned weights, reranked) 0.65 0.21
one joint image+speech vector 0.83 0.62
questions where exactly one wins (joint vs merged) 14 vs 3, p = 0.013 40 vs 7, p < 0.001

It holds on video we never tuned on. The held-out corpus is a different domain (a silent station tour, astronaut Q&A, science demos; 386 scenes, no burned-in captions), with 80 questions written by an agent that saw only the videos, never the code or results.

It isn't reading subtitles. On the first corpus, where every frame has burned-in captions, cropping them made keyframes alone worse on speech (0.53 → 0.40) but left the joint vector intact (0.73 → 0.77).

So the default ranks with that one vector. search() finds scenes with the joint vector and uses a sentence reranker only to pick the exact second. The router we built to rescue merged rankings ties it on both corpora (p = 1.0 and p = 0.69) at about twice the latency, so it's now opt-in. The two do differ: the default is better on questions about what was shown, routing leans ahead on what was said. If your users mostly ask about speech, pass routing="adaptive".

Full results, both corpora, caption ablation and limits · the story: fuse in the embedding, not in the ranking.


Quickstart

export MONGODB_URI="mongodb+srv://…"     # MDB_URI also works
export VOYAGE_API_KEY="pa-…"
from cinematlas import Cinematlas

engine = Cinematlas()
engine.ensure_indexes()                                  # once; idempotent

engine.ingest("https://www.youtube.com/watch?v=5NhYvbMdbBU")
engine.ingest("lecture.mov")                             # or a URL, bytes, file object, web upload

results = engine.search("how loud is a sonic boom?")     # the scene, down to the second
results.top.link                                         # 'https://…#t=34'
results.top.text                                         # 'Sonic booms are about 110 decibels.'
results.top.explain()                                    # rank per source, relevance, score

engine.search_scene_vector("a baby's hand holding a finger")   # joint vector only: the scene, ~80 ms

Results are plain dicts underneath (json.dumps works). Cinematlas doesn't pick an LLM for you; results.to_context() gives you numbered, citable excerpts to pass to one.


Examples

Four runnable scripts in examples/. They need only MONGODB_URI and VOYAGE_API_KEY in .env. The first two search a demo corpus of six NASA interviews; the last two index your own video.

uv run python examples/search.py "a little girl standing on hay bales"
uv run python examples/search.py --adaptive "what did he say about his first flight?"
uv run python examples/ask.py "What first got these people interested in aviation?"
uv run python examples/index_and_search.py lecture.mp4 "when is the exam?"
Example What it does
search.py The scene and the second for any question, with why each hit ranked. --adaptive shows routing reading a question as said or shown
ask.py A cited answer from a local LLM (Ollama, no API key), each citation a link to the exact second
index_and_search.py Index any URL, YouTube link or file with live progress, then search it
api.py A FastAPI service: POST /videos to upload, GET /search for deep links

How it works

 ingest(video)
   ├─ scenes ── PySceneDetect cuts, ≤30 s each
   ├─ said ──── faster-whisper → timestamped sentences, aligned to scenes
   ├─ shown ─── the middle keyframe of each scene
   └─ Voyage ── one joint keyframe+transcript vector per scene (plus keyframe-only and transcript vectors)
                → one MongoDB Atlas document per scene

 search(question)
   ├─ joint-vector search over scenes                                        one query, ranks scenes
   └─ $rerank over those scenes' sentences                                   picks the second

 search(question, routing="adaptive")
   ├─ $rankFusion over scene, keyframe, transcript and BM25 retrieval        one query
   └─ weights set per question by the reranker's confidence                  said vs shown
Need Call
The scene and the exact second (default) search(q)
Mostly questions about speech search(q, routing="adaptive")
The scene, fastest search_scene_vector(q)
Speech only search(q, sources=("transcript", "text"))
Your own blend search(q, weights={"scene": 2, "transcript": 1, "rerank": 1})
One source search_transcript · search_text · search_visual_vector · search_scene_vector

All of them accept video_id=. Every hit carries moment ({start, end, text}), moment_link (YouTube ?t=431s, files #t=431), ranks, relevance and the scene's fields.

ingest() accepts a URL (YouTube or any file link, scheme optional), a path, bytes, a file object, or a FastAPI UploadFile / Flask FileStorage. Remote URLs are treated as untrusted: private addresses are refused, downloads are capped, and signed-URL credentials are stripped before storage. Re-ingesting a video replaces it without a gap.


CLI

cinematlas doctor                        # checks the deployment and prints the exact fix for each problem
cinematlas setup [--update]              # create indexes; --update upgrades them in place
cinematlas ingest <url|path|->           # progress on stderr, JSON on stdout
cinematlas search "<question>" [-k 5] [--by hybrid|transcript|visual|text] [--format table|json|context]

Global options: --uri, --db, --collection, --transcript-mode, -v.

Atlas features used

$rankFusion (8.0+) for one-query hybrid retrieval, $rerank (8.3+) for in-database sentence reranking, Automated Embedding for transcripts, Atlas Search for BM25, scalar quantization and BSON float32 vectors. Each has an equivalent fallback, and cinematlas doctor tells you which path is in use.

Development

uv sync
uv run pytest -m "not integration and not media"    # unit, offline (~9 s)
uv run pytest -m media                               # real ffmpeg / Whisper on a committed NASA fixture
uv run pytest -m integration                         # live Atlas + Docker Atlas Local (reads .env)
uv run python bench/ingest.py                        # the benchmark corpora, once:
uv run python bench/ingest.py --no-captions          #   caption ablation
uv run python bench/ingest.py --station              #   held-out corpus
uv run python bench/run.py                           # both corpora, caption ablation, paired tests

MIT license. Test and benchmark media: NASA, public domain.

Release files for cinematlas 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cinematlas 0.5.0
File Size Uploaded
cinematlas-0.5.0.tar.gz 933.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cinematlas 0.5.0
File Interpreter ABI Platform
cinematlas-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 978.4 kB

Release files / cinematlas-0.5.0.tar.gz

Download URL cinematlas-0.5.0.tar.gz
Size 933.7 kB
Tags Source
SHA-256 checksum
How to use checksums
6cb0b90af2f2f18c8e340d981c0e95fe416ed9782054d5b16b4a624c17cdf2e0
BLAKE2b-256 checksum
How to use checksums
cd6ae78e42e9ea97247c05d2c2786b04fac76ae3154cf67202bd03b18656f00a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / cinematlas-0.5.0-py3-none-any.whl

Download URL cinematlas-0.5.0-py3-none-any.whl
Size 44.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
28026f583d931d284e5f2cb0b79c4f67aed92531a89910e2c750e9fcb659add6
BLAKE2b-256 checksum
How to use checksums
d86183697bd902271de8f3d7057c3cdbe00eaa713e511ef88a6a92edcb2e5f09
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.13.0

2 release files

0.12.0

2 release files

0.11.0

2 release files

0.10.1

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.1

2 release files

This release

0.5.0 This release

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page