A video investigator for one computer · open source · MIT
Retrace makes one record for each person or vehicle for each visit. It keeps the index on your disk. You ask a question in plain language. You get clips that play, with the path drawn on the frame. No data leaves the machine.
Tracklet 21 from the sample video. The path, the box, the crop, and the readout are the data that Retrace stored. Drag to retrace the 15.5 s visit.
Source: one indexed video and one live frame
Six screens of the search console. Five come from OpenCV's public sample vtest.avi, 80 s of a courtyard with about 10 people. One comes from a street camera on YouTube Live. The screens are not staged and not cropped.






Source: README, "What runs on your machine"
This is the design of the cloud camera vendors on one laptop, with open weights. One Rust binary, one SQLite file, one HTML page. Each stage below is a component that you can name in the process list.
Decodes a file, an RTSP camera, an HLS stream, SRT, or a YouTube URL to 640 px frames at 4 fps. A second child records a live source into 10 s segments.
Finds the persons and the vehicles in each frame. Open weights, Apache-2.0.
Connects the detections into one tracklet for each visit: the path, the dwell, the entry and exit edge, the direction, and the 3 best crops. It predicts with constant velocity and confirms a track after 2 hits.
Embeds the best crops into one vector. The same model embeds your question. With EmbeddingGemma 2, text, images, and 10 s audio windows share one space.
Stores the tracklets, the vectors, the optional captions, the rules, the alerts, and the events in data/index.db. The crops are JPEG files next to it.
Retrace writes a track with a dwell of 8 s or more as provisional and checks it again after each 10 s. A rule "dwell 45 s or more" fires at 45 s, while the person is still in the frame.
Your question is rewritten, embedded, matched with an exact cosine search over the filtered candidates, fused with BM25 on the captions, and cut with a threshold. The answer is a list of clips.
Four frames in each second are sufficient to know who came, where they went, and how long they stayed. Between two frames, a person who walks moves almost one box width. On the sample video, the tracker made 35 visits where ByteTrack's defaults made 136 fragments.
The cut is max(top − 0.06, μ + σ), with at least 5 results. On the sample video, "a bicycle" returns 1 result and "a person in a red jacket" returns 5 of 30 candidates.
With EmbeddingGemma 2 in the binary, the same question in English, Spanish, or Telugu returns the same two people in red. "a person in a red jacket", "una persona con chaqueta roja", "ఎర్ర జాకెట్ వేసుకున్న వ్యక్తి": each puts them in the first 3 results. Retrace does not translate.
Measured 2026-10-13 and 2026-10-14 · Apple M4 Max · CPU
The numbers come from scripts/e2e.sh and retrace eval on the sample video, with SigLIP 2 B/16 at 256 px. The golden set is in the repository. We checked each hit against the crop by eye. You can run the same commands.
| What | Result | Note |
|---|---|---|
| Analysis of 79 s of video | 33 s2.4× real time | 318 frames at 4 fps, 3,121 detections, 35 tracklets |
| Search, end to end | 14 msp50 · p95 15 ms | embed the question, exact KNN, threshold, JSON. A Linux container with 2 cores: 50 to 65 ms |
| Recall at 5, seven queries | 0.95mAP 0.95 | the one miss is a white van that the detector classed as a car |
| EmbeddingGemma 2 in the process | 2 min 15 sindex · model load included | then 9 to 21 ms for each search; the binary is 57 MB |
| Bytes sent to a cloud | 0 | the model weights download one time; after that, no network is necessary |
$ retrace --embedder siglip2-b16-256 eval --golden eval/golden.vtest.json --k 5 a person in a red jacket recall@5=1.00 AP=1.00 top=[21, 30, 6, 26, 31] a white van recall@5=0.67 AP=0.67 top=[5, 16] two people walking together recall@5=1.00 AP=1.00 top=[7, 31, 28, 26, 23] a bicycle recall@5=1.00 AP=1.00 top=[35] person in a white hooded jacket recall@5=1.00 AP=1.00 top=[11, 6, 21, 31, 14] a man in a blue jacket recall@5=1.00 AP=1.00 top=[31, 10, 24, 28, 6] person in dark clothes recall@5=1.00 AP=0.96 top=[17, 25, 3, 18, 8] queries=7 recall@5=0.952 mAP=0.946 p50=14.3ms p95=15.3ms embedder=siglip2-b16-256
Tracklet ids are the same for each run on one machine. A different ffmpeg or onnxruntime build can find one track more or less, which moves the ids. The end-to-end script then prints the eval without the recall check.
macOS on Apple Silicon or Linux · Rust 1.88 or later · ffmpeg
The first build compiles the onnxruntime bindings and takes some minutes. The first run downloads the open weights one time. After that, Retrace does not use the network.
$ brew install ffmpeg rust $ git clone https://github.com/humancto/retrace && cd retrace $ cargo build --release $ scripts/e2e.sh # sample video → index → search → clip → eval
# your video $ ./target/release/retrace --embedder siglip2-b16-256 \ ingest --source ~/Movies/driveway.mp4 --cam driveway $ ./target/release/retrace --embedder siglip2-b16-256 serve $ open http://127.0.0.1:8080/
# a live camera, while the server runs $ ./target/release/retrace --embedder siglip2-b16-256 serve \ --source door=rtsp://user:pass@192.168.1.40:554/stream # read SECURITY.md before you bind to another address
No account, no telemetry, no update check. The only outbound calls are the one-time weight downloads, yt-dlp for YouTube URLs, and the sources that you give. Use --models-dir on a computer with no network.
DEIM / D-FINE, SigLIP 2, and EmbeddingGemma 2 are Apache-2.0. CLIP is MIT. Retrace is MIT. One flag changes the detector or the embedder.
Search, tracklets, clips, thumbnails, rules, alerts, events, and a live SSE feed are JSON over localhost. The UI is one HTML file. It uses only the functions that the API exposes.
Source: README, "Known limits" and CHANGELOG 0.4.0
A tool for evidence must say where it fails. These are the limits that we know on 2026-10-14.