RETRACE

A video investigator for one computer · open source · MIT

Ask your cameras what happened.Then retrace their steps.

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.

14 mssearch, p50
0.95recall at 5
0bytes to a cloud
A courtyard from a fixed camera. A teal line shows the path of one person across the frame from right to left.
CAM 01 · COURTYARDID 21 · PERSON
+0.0 s

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

The evidence

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.

Search results for 'a person in a red jacket': five cards, the two red jackets ranked first.
SearchRetrace converts the question into an exact nearest-neighbour search with a filter and a threshold for each query. The two people in red clothes come first. The timeline shows the tracklet density for each camera, with the hits marked.Query: a person in a red jacket · 5 of 30 candidates
A tracklet opened: the clip plays, the path is drawn over the context frame.
Retrace the stepsOpen a result. The clip starts 4 s before the visit. Retrace draws the path on the frame: a green point at the entry, a red point at the exit. The panel shows the dwell, the edges, the speed, and the 3 best crops. The actions are: pin to the case, more like this, download the clip, copy the link.Detail panel · tracklet 21
More like this: nine crops similar to the opened tracklet.
More like thisRetrace compares the crops and finds the other visits of the same person. This includes the fragments that the tracker split. It helps when a person comes back 1 hour later.Crop similarity · same index
A time range selected on the timeline; the results are limited to that range.
TimelineDrag on a camera lane to select a time range. The search then runs only in that range. The lane shows the density of tracklets and the hits. Click a bar to open the tracklet at that time.One lane for each camera
An analysed frame from a YouTube Live street camera, with boxes, ids, dwell and trails.
Live viewAn analysed frame from the EarthCam New Orleans street camera on YouTube Live, 2026-10-14: 2 confirmed persons with their dwell and trails. Retrace draws what the index sees. When a stream sends no frame for 30 s, Retrace restarts the source.Live · 2026-10-14 00:22 local time
A standing rule added with its backtest: three matches on the indexed footage.
Rules and the caseA rule is a question that Retrace asks again for each new tracklet. When you add a rule, the backtest counts the matches in the indexed video. The case panel keeps the results that you pin and exports them as JSON.Rule: a person in a red jacket, score ≥ 0.13 · 3 matches

Source: README, "What runs on your machine"

The method

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.

  1. ffmpegone child for each source

    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.

  2. DEIM-D-FINE Sdetector · ONNX · 40 MB

    Finds the persons and the vehicles in each frame. Open weights, Apache-2.0.

  3. trackersrc/track.rs · in the process

    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.

  4. SigLIP 2 B/16or CLIP, or EmbeddingGemma 2

    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.

  5. SQLitesqlite-vec · FTS5 · one file

    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.

  6. rulesrun while the track is open

    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.

  7. 127.0.0.1:8080axum · JSON API · ui/index.html

    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.

A low frame rate, on purpose

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.

A threshold for each query

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.

Words in your language

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

Measured, not estimated

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.

WhatResultNote
Analysis of 79 s of video33 s2.4× real time318 frames at 4 fps, 3,121 detections, 35 tracklets
Search, end to end14 msp50 · p95 15 msembed the question, exact KNN, threshold, JSON. A Linux container with 2 cores: 50 to 65 ms
Recall at 5, seven queries0.95mAP 0.95the one miss is a white van that the detector classed as a car
EmbeddingGemma 2 in the process2 min 15 sindex · model load includedthen 9 to 21 ms for each search; the binary is 57 MB
Bytes sent to a cloud0the 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

Run it

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

Nothing leaves the machine

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.

Open weights, open code

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.

Each function is an API call

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

What it does not do yet

A tool for evidence must say where it fails. These are the limits that we know on 2026-10-14.