Self-hosted codec audit and re-encode tool for homelabs. Point it at the same movie and TV folders Plex, Jellyfin, or Emby already use, rank files by predicted HEVC savings, and manually queue overnight ffmpeg jobs.
Website: reclaim.reecerose.com
Single container: Go API + embedded web UI + ffmpeg/ffprobe. No database server, Redis, or sidecar services.
Reclaim is for large libraries with mixed codecs where you want a safe, manual-first way to find the biggest space wins.
| Does | Does not |
|---|---|
| Scans mounted library folders directly | Integrate with Sonarr, Radarr, Plex, Jellyfin, or Emby APIs |
| Ranks candidates by estimated savings (learns from your completed encodes) | Auto-encode your whole library |
| Per-job encode time estimates on the Queue page (learns from completed jobs) | Use GPU/NVENC hardware encoding (CPU libx265 only) |
| Library view for every scanned file with eligibility reasons | Pause for active streams (time window only) |
| Helps spot bloated rips better re-downloaded than re-encoded | |
| Optional TMDB posters and metadata for movies and TV | |
| Replaces files in-place after verification | |
| Runs encodes in a configurable overnight window (or force individual jobs) |
Full deployment guide: docs/DOCKER.md.
# Edit media paths and TZ in docker-compose.yml first
docker compose up --build -dOpen http://<nas-ip>:8080, create your login, and let the first scan run.
Media mounts must be read-write because Reclaim replaces files in-place after verification. The included Compose file uses a named DB volume; NAS users may prefer a host appdata path as shown in docs/DOCKER.md.
Set PUID and PGID in docker-compose.yml to match the user that owns your media library, or encodes fail with Permission denied when writing back to /movies and /tv. See docs/DOCKER.md for Unraid and other NAS layouts.
Three-stage Dockerfile: Next.js static export → Go binary (frontend embedded) → Alpine 3.21 with pinned ffmpeg.
docker build -t ghcr.io/reecerose/reclaim:latest .If you run outside Docker, you need ffmpeg and ffprobe on PATH:
export MOVIES_PATH=/mnt/movies
export TV_PATH=/mnt/tv
export DB_PATH=/var/lib/reclaim/reclaim.db
./reclaim| Variable | Required | Default | Description |
|---|---|---|---|
MOVIES_PATH |
yes | — | Absolute path to the movies root (rw) |
TV_PATH |
yes | — | Absolute path to the TV root (rw) |
DB_PATH |
yes | — | SQLite database file path |
ENCODE_WINDOW_START |
no | 00:00 |
Start of the encode window (HH:MM, 24h local time) |
ENCODE_WINDOW_END |
no | 06:00 |
End of the encode window |
SCAN_INTERVAL |
no | 24h |
How often a diff-based rescan runs (Go duration string) |
PROBE_CONCURRENCY |
no | 4 |
Max parallel ffprobe calls during a scan |
SCAN_ANCHOR |
no | 00:00 |
Daily scan anchor time (HH:MM, local) |
MISSING_RETENTION |
no | 0 |
How long files that vanished from disk are kept as missing rows before their records are deleted after a scan (Go duration string; 0 = keep forever) |
TIMEZONE |
no | TZ, else UTC |
IANA timezone the encode window and scan anchor are read in — set this so they fire on your clock. Logs and the process clock stay UTC. Also settable at runtime under Settings › Encoding |
TMDB_API_KEY |
no | — | TMDB API key for movie/TV poster, backdrop, and metadata fetching |
PUID |
no | 1000 |
Docker only — uid that owns your media library (see docker-entrypoint.sh) |
PGID |
no | 1000 |
Docker only — gid that owns your media library |
DISABLE_AUTH |
no | false |
Bypass login entirely — trusted LAN use only |
RESET_AUTH |
no | false |
Clear stored credentials on boot, re-triggering first-run setup |
See .env.example for a copy-paste template.
On first boot, open http://<host>:8080 and create a username/password. The password is bcrypt-hashed in SQLite; login uses a signed HTTP-only cookie.
DISABLE_AUTH=true— skips login; trusted LAN only.RESET_AUTH=true— clears credentials on boot; remove after resetting.
For HTTPS reverse proxies, forward X-Forwarded-Proto: https so cookies get the Secure flag.
-
Scan — walks
MOVIES_PATHandTV_PATH, probes video files withffprobe, and records codec, resolution, bitrate, size, mtime, and fingerprint. Later scans skip unchanged files and detect renames. -
Rank — files are sorted by predicted HEVC savings. After enough completed jobs for a codec, estimates switch from seed values to your observed results. Per-file codec, bitrate, and resolution help you spot rips that are better re-downloaded than re-encoded.
-
Browse — the Library view shows every scanned file, including already-HEVC, missing, and probe-failed items, each with a
candidate_stateexplaining eligibility. -
Queue — select files, pick a profile, and confirm before jobs are created. Queued jobs wait for the encode window unless you Force them to run immediately. The Queue page shows per-job encode time estimates (seed-based at first, then learned from your completed jobs) and live remaining time on the running job.
-
Encode — queued jobs run inside the encode window unless forced. Reclaim writes a
.reclaim-tmpfile, then:- Verifies the output (duration ±1 s, stream counts, resolution match)
- On pass: atomically swaps original →
.reclaim-backup, temp → original, deletes backup - On fail: marks the job failed, keeps the temp for inspection, leaves the original untouched
-
Recover — on boot, temp files are cleaned up, interrupted backups are restored, and stuck jobs are marked failed. Job and scan events are logged to a persistent audit trail (bell icon in the UI).
-
Notify — when new re-encode candidates arrive (anything indexed that isn't already HEVC), Reclaim tells you. Arrivals are collected until the library has been quiet for a configurable delay (default 15 minutes), then sent as one notification per show or movie — a whole season import pings you once, and a second show arriving at the same time gets its own message instead of being mixed in. Notifications land on the bell icon, and optionally on a webhook — Discord, Slack, ntfy, or generic JSON — configured under Settings › Notifications. The very first scan on a new install is treated as your library baseline and stays quiet.
CPU x265 is slow by design. Rough expectations:
| Preset | Typical speed | 1-hour HD file |
|---|---|---|
medium |
~0.5–1× realtime | 1–2 hours |
fast |
~2–3× realtime | 20–30 min |
ultrafast |
~8–10× realtime | 6–8 min |
A 20 000-file library at medium can take months of overnight windows. Reclaim is meant to chip away safely, not batch-convert everything at once.
Per-job encode time estimates on the Queue page learn from your completed jobs after a few runs on each profile; until then they use conservative preset-based guesses.
A running job is never interrupted when the window closes — it finishes and no new job is pulled.
The Docker image includes pinned Alpine ffmpeg/ffprobe. Rebuild to bump ffmpeg deliberately.
fsnotify is unreliable over NFS/SMB. Reclaim falls back to scheduled SCAN_INTERVAL rescans for remote shares.
On Linux, large libraries (20 000+ directories) can hit the default inotify watch limit (/proc/sys/fs/inotify/max_user_watches). Increase it if the startup log reports watch failures:
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -pSQLite WAL sidecars (reclaim.db-wal, reclaim.db-shm) next to the DB are normal. Do not delete them while Reclaim is running.
Reclaim never deletes the original file before verifying the encode. The replace sequence is:
original → original.reclaim-backup (rename, atomic)
tmp → original (rename, atomic)
delete original.reclaim-backup
Both renames happen in the same directory and are recovered on next boot if interrupted. Avoid filesystems that do not support atomic rename.
| Doc | Audience |
|---|---|
docs/DOCKER.md |
Homelab deployment (Unraid, Synology, Compose, docker run) |
docs/RELEASES.md |
Pulling versioned images from GHCR |
docs/API.md |
REST + WebSocket reference for scripting and integrations |
docs/ENCODE-TIME-PLAN.md |
Encode time estimation design (rate model, learning buckets, API fields) |