____ _____ _ _
| _ \ | ____| | | / \
| | | || _| _ | | / _ \
| |_| || |___ | |_| | / ___ \
|____/ |_____| \___/ /_/ \_\
__ __ _ _
\ \ / / | | | |
\ \ / / | | | |
\ V / | |_| |
\_/ \___/
D É J À V U
live -> freeze -> loop
Frigate dejavu allows you to swap live go2rtc camera feeds with convincing quality video loops or frozen frames by rewriting the go2rtc streams in Frigate’s configuration through its REST API on demand. After an automatic restart, recordings, detection, birdseye, Home Assistant cards, and WebRTC viewers all keep working. Turning privacy off restores the original stream sources and restarts Frigate again.
Dejavu privacy engages in two stages. Stage 1 prepares a freeze frame for every profiled stream, rewrites the go2rtc sources to point at those clips, and goes on through one coordinated Frigate restart—privacy is immediate, guaranteed, and permanent, and a stream is never left live. Stage 2 (loop mode only) then runs in the background while already private: it searches each camera's recent recordings for quiet or sparsely active scenes using Frigate activity, lighting, IR mode, drift, and loop-seam similarity, copies the winner while silencing audio (by default), atomically swaps each loop over its freeze clip at the same path, and swaps them in through a second coordinated Frigate restart. A stream whose loop is not ready in time stays on its freeze frame permanently.
If you want more raw video, set mode: loop with the opt-in restream source to capture live video directly, bypassing all recordings-based safety and ranking guards.
“It’s so convincing that it’s unreal.”
Déjà Vu is never real, and that is the point: it is a convincing false reality, built to be undetectable rather than true. Don't forget to turn privacy mode off!
Start with the tracked examples. Real configuration, Compose files, state, and clips are gitignored.
cp config.example.yaml config.yaml
cp compose.example.yaml compose.yaml
mkdir -p data
# Edit every value marked EDIT, then validate and start.
docker compose config
docker compose up -dCommon controls:
docker exec frigate-dejavu dejavu status
docker exec frigate-dejavu dejavu on --dry-run
docker exec frigate-dejavu dejavu on
docker exec frigate-dejavu dejavu on --profile indoor
docker exec frigate-dejavu dejavu on --mode freeze
docker exec frigate-dejavu dejavu offThe CLI blocks and prints progress. The REST API starts the same CLI job in the
background and returns immediately; poll /api/dejavu/status for progress.
Examples consistently use frigate for the Frigate service and
frigate-dejavu for this container and its client-facing hostname.
The tracked examples are a sanitized version of a tuned architecture, not a toy configuration:
cameras -------> go2rtc ┬-> Frigate -> recordings / detection / Birdseye
replacement clips -^ └-> WebRTC / Home Assistant
Déjà Vu -> Frigate REST API: validate config, save sources, restart, verify
- Every Frigate camera consumes a local
go2rtcrestream. - Déjà Vu shares Frigate's existing config mount for clips.
- Frigate's recordings mount is read-only; its export API covers missing or unreadable segments.
- Déjà Vu reaches Frigate and embedded
go2rtcthrough the configured endpoints. - Restarts go through Frigate's API. No Docker socket is mounted.
Different addresses, mounts, and camera brands are expected. Changing the topology or restart model requires revalidation; see SPEC §2 for the verified production versions and constraints.
The example exposes the API on the Docker host at http://127.0.0.1:8898 and
also joins Frigate's external Docker network, where other containers can use
http://frigate-dejavu:8898.
# Engage the default profile; returns 202.
curl -X POST http://127.0.0.1:8898/api/dejavu/on
# Engage a named profile; mode, cameras, capture_seconds, and source are optional.
curl -X POST http://127.0.0.1:8898/api/dejavu/on \
-H 'Content-Type: application/json' \
-d '{"profile": "indoor"}'
# Restore live sources; returns 202, or 409 while another transition is active.
curl -X POST http://127.0.0.1:8898/api/dejavu/off
# Current state, per-stream phases, drift, and Frigate health.
curl http://127.0.0.1:8898/api/dejavu/status
# Available profiles and their effective settings.
curl http://127.0.0.1:8898/api/dejavu/profiles
# Liveness; never requires authentication.
curl http://127.0.0.1:8898/healthzAn empty on request uses the default profile. "cameras": [] explicitly means
all Frigate cameras. Malformed JSON, unknown fields, blank names, and invalid
types return 400.
API responses: 202 accepted · 400 invalid · 401 bad or missing token ·
409 busy or debounced (retry_after included).
CLI exits: 0 success · 1 failure · 2 busy or debounced · 3 invalid.
API authentication is optional on loopback or a trusted network. When
api.bearer_token resolves from DEJAVU_API_TOKEN, every /api/* request must
send Authorization: Bearer <token>. /healthz remains public.
Do not expose an unauthenticated API beyond the Docker host without an authenticating reverse proxy.
Frigate's internal :5000 needs no credentials and is the default. To use the
authenticated :8971 port instead, frigate.api_auth supports both of
Frigate's auth models — either way the resolved identity needs the admin
role, since engaging privacy saves Frigate's configuration and restarts it:
- Native auth (Frigate's default): set
userandpassword. Déjà Vu logs in through/api/loginand re-authenticates on a401. - Proxy auth (
auth.enabled: falsebehind an authenticating reverse proxy): setheadersto the static header map the proxy contract expects — typicallyX-Proxy-Secretmatching Frigate'sproxy.auth_secret, plus the user/group headers named inproxy.header_map. Group headers must map toadminvia Frigate'sproxy.header_map.role_map.
That port is HTTPS, and Frigate serves a self-signed certificate there by
default, reissued whenever the container is recreated. Point
frigate.tls_verify at a CA bundle or set it false, or every request fails
the TLS handshake before credentials are ever considered.
There is no static-token option, because Frigate has no long-lived API key: its
bearer tokens are /api/login JWTs that expire after auth.session_length
(24 hours by default, raisable but never unlimited), and Frigate refreshes the
session cookie only — never an Authorization header. A configured token would
quietly stop working.
Copy config.example.yaml to config.yaml. Most deployments
only need to change the Frigate endpoints, volume paths, and profile camera names.
| Mode | Source | What it does | Fallback |
|---|---|---|---|
loop |
recordings |
Freeze on via a restart, then a background loop upgrade lands via a second restart | Stay on freeze |
loop |
restream |
Captures a live loop directly; bypasses recordings-based guards | Abort activation |
freeze |
recordings |
Builds one freeze clip | Live → recorded → cached → black |
freeze |
restream |
Builds one freeze clip without searching recordings | Live → cached → black |
recordings is the production default. Loop assembly reads the /recordings
mount directly when available; without one (or when a candidate's segment files
are missing) it falls back to Frigate's export API — waiting first for the
export worker to actually answer, since it lags minutes behind the engage
restart. That shared, one-time readiness wait and all subsequent assembly consume
the same loop budget. If neither source yields a loop in time, streams simply
stay on their freeze frames. A profile without mode defaults safely to freeze.
capture.seconds and max_loop_seconds set the target and maximum loop lengths
(300 and 1200 seconds by default).
The defaults favor believable loops without turning tuning into a science project:
search_hours: 3bounds the recent-past lookback (about one 3-hour lighting period).match_ir_moderejects day/night mismatches.max_brightness_deltacompares a candidate with the camera now.max_brightness_driftrejects dawn, dusk, and lighting ramps inside a loop.- Equivalent candidates are ranked by first/last-frame similarity to reduce the visible seam.
sync_tolerance_minuteskeeps overlapping cameras near the same source time.recordings.audio: silenceremoves repeating audio.
recordings.dilute is the practical fallback for busy cameras. It permits a long
window with sparse tracked activity, bounded by max_activity_fraction, while
block_labels must include person. These are Frigate metadata guarantees, not
independent video recognition.
streams.includeandstreams.excludeare glob rails applied after camera names resolve togo2rtcstreams.profiles.<name>.camerascontains Frigate camera names;[]means all cameras.stream:<name>targets ago2rtcstream directly.- Profiles may override
mode,capture_seconds, andsource. - Request and CLI options override the selected profile.
There is no configurable on_failure or fallback: a selected stream is never
quietly left live. Unresolvable streams or a failed engage ladder abort the whole
activation before Frigate's configuration changes. Partial privacy is never
reported as on.
String values may contain {DEJAVU_NAME} placeholders. Only names beginning with
DEJAVU_ are expanded, and every referenced variable must exist. An explicitly
empty value is valid for optional credentials.
dejavu on --dry-run shows the replacement plan and metadata-derived candidate
windows without touching Frigate. Lighting, drift, IR, and seam guards run during
real capture, so the final winner may differ.
Use docker logs frigate-dejavu first when a camera behaves unexpectedly. Every
job ends with a per-stream result table. Set DEJAVU_LOG_LEVEL=DEBUG for individual
candidate decisions.
State is persisted in data/state/state.json and shared by the CLI and API:
off -> capturing -> applying -> on -> restoring -> off
- Stage-1 freeze capture finishes before any configuration change (the loop upgrade runs later, in stage 2).
- Frigate validates the proposed configuration before saving it.
- After restart, Déjà Vu verifies through
go2rtcthat every replacement landed. - A failed activation rolls back automatically while Frigate remains reachable.
- Turning off during capture cancels workers, removes partial clips, and leaves Frigate untouched.
- State, clips, and backups survive a Déjà Vu restart while privacy is active.
Pristine backups live in data/state/backup.*.yaml. Normal restore is surgical:
it restores only the source lists Déjà Vu replaced, preserving unrelated edits.
Conflicting edits to an active replacement are saved as drift copies.
dejavu force-restore writes the pristine backup verbatim when surgical recovery
is not enough.
The first activation installs the reserved
go2rtc.ffmpeg.frigate_dejavu_loop template. It remains installed but unused after
normal restore, avoiding needless config churn. A conflicting value is refused,
never overwritten.
Ready-to-use examples live in homeassistant/. They add:
- REST commands for the example profiles;
- a status sensor polled every 20 seconds; and
select.frigate_privacywithOff,Perimeter,Indoor, andAll Cameras.
The selector follows changes made through Home Assistant, the API, or the CLI.
Profiles are mutually exclusive, so select Off before changing active profiles.
- Recordings and detections created during privacy contain the replacement feed.
- Camera-burned timestamps retain the freeze or loop's source time. Fixing that would require transcoding.
- Lighting is matched when privacy engages. Re-toggle after a major dawn/dusk transition during a long session.
- Engaging in
loopmode restarts Frigate twice a short time apart: restart #1 turns privacy on with freeze frames, then the background loop upgrade lands via restart #2 (up tocapture.loop_assembly_budget_secondslater — a second, delayed, brief recording/detection gap after the appliance already reportedon).freezemode and switch-off each restart Frigate once. Every restart letsgo2rtcand Frigate consumers rebuild in the correct order. - Offline cameras freeze on their ladder rung (live frame → recorded frame → cached frame → black, visible in status and logs) and can still upgrade to a loop of their own recent recordings when any exist in the search window — the frozen frame itself serves as the lighting reference.
- The first restore may reformat a few folded lines in Frigate's YAML without changing their values, comments, quoting, or secret placeholders.
- The clip directory must be the same storage mounted as
/clipsin Déjà Vu and/config/dejavu-clipsin Frigate.