{"id":49143682,"url":"https://github.com/i-ial9000/shrinkerr","last_synced_at":"2026-05-31T19:01:03.969Z","repository":{"id":352741880,"uuid":"1216413067","full_name":"I-IAL9000/shrinkerr","owner":"I-IAL9000","description":"Self-hosted media library transcoder with hardware encoding, VMAF quality assurance, live monitoring, and a safety net.","archived":false,"fork":false,"pushed_at":"2026-05-21T00:12:09.000Z","size":11992,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-21T07:19:22.934Z","etag":null,"topics":["docker","ffmpeg","ffmpeg-gui","gpu","hevc","hevc-encoder","libx265","media-server","nvenc","nvidia","nzbget","plex","plex-media-server","radarr","renamer","self-hosted","sonarr","sonarrr","transcoding","vmaf"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/I-IAL9000.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"docs/security.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-20T22:03:33.000Z","updated_at":"2026-05-21T00:12:13.000Z","dependencies_parsed_at":"2026-05-13T19:04:04.380Z","dependency_job_id":null,"html_url":"https://github.com/I-IAL9000/shrinkerr","commit_stats":null,"previous_names":["i-ial9000/shrinkerr"],"tags_count":193,"template":false,"template_full_name":null,"purl":"pkg:github/I-IAL9000/shrinkerr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/I-IAL9000%2Fshrinkerr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/I-IAL9000%2Fshrinkerr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/I-IAL9000%2Fshrinkerr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/I-IAL9000%2Fshrinkerr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/I-IAL9000","download_url":"https://codeload.github.com/I-IAL9000/shrinkerr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/I-IAL9000%2Fshrinkerr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33744447,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","ffmpeg","ffmpeg-gui","gpu","hevc","hevc-encoder","libx265","media-server","nvenc","nvidia","nzbget","plex","plex-media-server","radarr","renamer","self-hosted","sonarr","sonarrr","transcoding","vmaf"],"created_at":"2026-04-22T02:04:58.582Z","updated_at":"2026-05-31T19:01:03.921Z","avatar_url":"https://github.com/I-IAL9000.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\".github/assets/logo.svg\" alt=\"Shrinkerr\" width=\"360\"\u003e\n\n**Save space, bandwidth \u0026 money while retaining quality.**\n\n[![Build \u0026 publish images](https://github.com/I-IAL9000/shrinkerr/actions/workflows/build-images.yml/badge.svg)](https://github.com/I-IAL9000/shrinkerr/actions/workflows/build-images.yml)\n![Version](https://img.shields.io/badge/version-0.3.0--beta-blue)\n![Docker: multi-arch](https://img.shields.io/badge/docker-multi--arch-0db7ed?logo=docker)\n[![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-green)](LICENSE)\n\n\u003c/div\u003e\n\n---\n\nShrinkerr scans your media library, identifies files that are worth re-encoding, queues them up, and runs `ffmpeg` in the background — replacing each file with a smaller x265 copy only when the quality check passes. It's designed for home media servers (Plex, Jellyfin, Emby) where you want to reclaim drive space without hand-rolling ffmpeg scripts.\n\nTypical result on a mixed TV + movies library: **50–65% smaller files** with no visible quality loss, fully automated, with originals optionally retained in a backup folder for easy rollback.\n\n\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\".github/assets/hero.webp\" alt=\"Shrinkerr UI tour\" width=\"960\"\u003e\n\n\u003c/div\u003e\n\n\u003e 📚 **[Full documentation in `/docs`](docs/README.md)** — installation\n\u003e scenarios, encoding guide, remote-worker setup, rules \u0026 automation,\n\u003e best practices, troubleshooting, FAQ.\n\n## Features\n\n**Encoding**\n- x264 (or any other source codec) → x265 (HEVC) conversion, NVENC hardware or libx265 CPU\n- **Intel QSV and Intel/AMD VAAPI** — *experimental* hardware encoders for non-NVIDIA GPUs (see [Intel/AMD GPU support](#intelamd-gpu-support-experimental))\n- Per-resolution CQ/CRF overrides (4K, 1080p, 720p, SD)\n- Encoding rules — by directory / source / resolution / file size / codec, by Plex label / collection / genre / library, or by Sonarr / Radarr tag\n- Optional [VMAF](https://github.com/Netflix/vmaf) quality check with a configurable minimum score — encodes that score below the threshold are discarded and the original is kept\n- **Dry run** — fire off a 30-second test encode before committing to a full job and get its VMAF score back, so you can validate your settings on a single clip without burning 20 minutes on a bad preset\n- Automatic fallback from NVENC → libx265 when the requested encoder isn't available on a node\n- Multiple parallel jobs per host, capped by a `parallel_jobs` setting\n- Built-in conversion guide + quick-action presets for common scenarios\n\n**Library management**\n- Recursive scanner with ffprobe-based codec / bitrate analysis\n- TMDB metadata + native-language lookup so the audio/subtitle cleanup rules know which language is the original\n- Audio track cleanup — keep only the languages you want, drop commentary / descriptive tracks\n- Subtitle track cleanup — same as audio, plus detection of forced / SDH / CC variants\n- External subtitle detection — sidecar `.srt` / `.ass` / `.sub` files show up next to embedded ones and can be merged into the output during conversion\n- Health check — probes files for corruption before (and optionally after) encoding\n- File-level ignore list with one-click restore\n- Extensive filter system + advanced search — match on any attribute (codec, resolution, size, bitrate, audio languages, native language, Plex metadata, …)\n- Watch folders — new files appear in the scanner view automatically\n- Poster grid view with TMDB artwork for visual browsing\n\n**Automation**\n- Queue with drag-and-drop reordering, bulk apply, priority levels, and scheduling\n- Watch folders — auto-queue newly-added files in real time\n- NZBGet / SABnzbd post-processing scripts — auto-queue freshly-downloaded releases\n- Sonarr / Radarr integration — trigger replacement searches, upgrade searches, and missing-episode searches without leaving the UI; library refresh on completion\n- Plex / Jellyfin integration — label / collection / genre / library-based rules, watch-status sync, library refresh on completion, trash cleanup\n- Scheduling — only encode during off-peak hours, pause around Plex / Jellyfin prime-time\n- Post-conversion hook scripts with rich env-var context (job details, space saved, [VMAF](https://github.com/Netflix/vmaf) score, etc.)\n- Batch rename with Plex-friendly patterns, with optional auto-rename after conversion\n- File-size / bitrate threshold filters on the conversion queue (skip tiny files, skip already-low-bitrate files)\n- Custom ffmpeg flags per job or per rule\n- Notifications via Discord / Telegram / email / webhook\n\n**Distributed workers**\n- Offload encoding to remote hosts (second machine, gaming PC, ARM box)\n- Capability-aware job routing (NVENC jobs go to NVENC hosts, CPU jobs anywhere)\n- Per-node pause / affinity / schedule / concurrency limits\n- Path-mapping support for workers that see the library at a different path\n- Circuit breaker auto-pauses a node after repeated failures\n\n**Monitoring \u0026 statistics**\n- Live dashboard — active jobs, queue depth, total saved, projected savings\n- System monitor — GPU (utilization, VRAM, temp, NVENC/NVDEC load), CPU, RAM, disk I/O, network, Plex stream count\n- 90-day trend charts — cumulative savings, daily encodes, avg FPS per job\n- Library breakdown — codecs, resolutions, source types, native languages, audio track languages\n- [VMAF](https://github.com/Netflix/vmaf) score distribution + per-job quality breakdown\n- Activity log — every scan, encode, ignore, arr action, Plex sync — with timestamps\n\n**Safety net**\n- Keep originals for N days in a per-directory `.shrinkerr_backup` folder, or centralize to one path\n- **Undo conversion** — restore any recently-encoded file to its original with one click\n- Automatic detection of \"no savings\" encodes → keep the original, mark the file as ignored\n- Output verification — ffmpeg must successfully encode AND the output must be non-empty before the original is touched\n- Automatic retry with directory-scan fallback for transient NFS/SMB filesystem glitches\n\n**Quality of life**\n- Dark + light theme\n- Queue/dashboard updates via WebSocket — no page refresh needed\n- Bulk-edit selected jobs (encoder, preset, CQ, priority, audio codec)\n- Settings export/import (JSON) for easy migration\n- Full backup/restore (includes DB + media-dir config + encoding rules)\n- Keyboard shortcuts for navigation and queue start/pause\n- Tab-visibility-aware polling — pauses background fetches when the tab isn't visible\n\n---\n\n## Requirements\n\n- **Docker 20.10+** with **Docker Compose V2** (the built-in `docker compose` subcommand, not legacy `docker-compose`)\n- **Media library** on a filesystem Docker can bind-mount with read + write access\n- **Port 6680** free on the host (or another port of your choosing)\n- **For NVENC (optional, 5–10× faster than CPU)**:\n  - NVIDIA GPU (Pascal / GTX 10xx or newer)\n  - NVIDIA driver 525.60.13+ (for `:nvenc`) or 570+ (for `:edge-nvenc`)\n  - **Linux**: [NVIDIA Container Toolkit](https://github.com/NVIDIA/nvidia-container-toolkit)\n  - **Windows**: Docker Desktop in WSL2 mode (Windows 10 21H2+ / Windows 11) with a recent NVIDIA Windows driver — nothing else to install\n  - **macOS**: not supported — Apple Silicon has no NVIDIA path; use the portable `:latest` image\n\n---\n\n## Image variants\n\nFour tags are published to two registries — pick whichever you prefer:\n\n- [`ghcr.io/i-ial9000/shrinkerr`](https://github.com/I-IAL9000/shrinkerr/pkgs/container/shrinkerr) — GitHub Container Registry (no rate limits on anonymous pulls)\n- [`pal9000/shrinkerr`](https://hub.docker.com/r/pal9000/shrinkerr) — Docker Hub (mirror; familiar default, anonymous pulls are rate-limited to 100 / 6h / IP)\n\nBoth registries publish the same tags simultaneously:\n\n| Tag | Platforms | Encoding | When to use |\n|---|---|---|---|\n| `:latest` | linux/amd64 + linux/arm64 | libx265 (CPU only) | **Default.** Works on Mac, Windows, Linux, Raspberry Pi, ARM cloud. Pick this unless you specifically want GPU. |\n| `:edge` | linux/amd64 + linux/arm64 | libx265 (CPU only) | Same as `:latest` but with bleeding-edge ffmpeg master build. |\n| `:nvenc` | linux/amd64 | NVENC + libx265 | NVIDIA GPU host on Linux or Windows+WSL2. ffmpeg n7.1, needs driver 525.60.13+. |\n| `:edge-nvenc` | linux/amd64 | NVENC + libx265 | NVIDIA GPU host running a very recent driver. ffmpeg master, needs driver 570+. |\n\n**All variants share the same database schema and settings format** — you can switch between them with a single `image:` line change and a `docker compose pull \u0026\u0026 docker compose up -d`. The app's runtime capability detection handles the encoder difference transparently.\n\nThe compose snippets below use `ghcr.io/...`; substitute `pal9000/shrinkerr:\u003ctag\u003e` if you prefer Docker Hub.\n\n---\n\n## Quick start\n\n### Option A — Portable (works on any host, CPU encoding)\n\nThis is the recommended starting point, even on a GPU host, because it lets you validate the setup before layering on NVENC.\n\n```yaml\n# docker-compose.yml\nservices:\n  shrinkerr:\n    image: ghcr.io/i-ial9000/shrinkerr:latest\n    container_name: shrinkerr\n    ports:\n      - \"6680:6680\"\n    volumes:\n      - ./data:/app/data                 # Shrinkerr's SQLite DB + logs + history\n      - /srv/media:/media                # YOUR media library (read + write)\n    restart: unless-stopped\n```\n\n```bash\ndocker compose up -d\n```\n\nOpen \u003chttp://localhost:6680\u003e. On first launch, go to **Settings → System → Authentication** and set a username and password before exposing the port beyond localhost.\n\n### Option B — With NVENC (Linux + NVIDIA GPU)\n\n```yaml\nservices:\n  shrinkerr:\n    image: ghcr.io/i-ial9000/shrinkerr:nvenc\n    container_name: shrinkerr\n    ports:\n      - \"6680:6680\"\n    volumes:\n      - ./data:/app/data\n      - /srv/media:/media\n    environment:\n      - NVIDIA_VISIBLE_DEVICES=all\n    restart: unless-stopped\n    runtime: nvidia                      # Linux with NVIDIA Container Toolkit\n    deploy:\n      resources:\n        reservations:\n          devices:\n            - driver: nvidia\n              count: all\n              capabilities: [gpu, compute, video, utility]\n              # gpu      — claim a GPU device (meta capability)\n              # compute  — CUDA libs (some downstream libs need it)\n              # video    — NVENC + NVDEC hardware encode/decode\n              # utility  — nvidia-smi + NVML (drives the GPU\n              #            utilization / temp / VRAM panels in\n              #            the Monitor page)\n              # Drop `utility` and the encode still works but the\n              # Monitor page can't show GPU stats — see the\n              # troubleshooting entry below.\n```\n\n**Windows (Docker Desktop WSL2)**: same file, but **remove** the `runtime: nvidia` line. Docker Desktop doesn't register a `nvidia` runtime by name; the `deploy:` block is the cross-platform way.\n\nA heavily-commented production template is at [`docker-compose.portainer.yml`](docker-compose.portainer.yml) — copy it, adjust the paths, and paste into Portainer.\n\n---\n\n## First-time setup\n\nAfter `docker compose up -d` the setup wizard walks you through four steps:\n\n1. **Add media directories** — Settings → Directories. Point at `/media/TV`, `/media/Movies`, etc. (whatever paths exist inside the container mount).\n2. **Scan your library** — Scanner → select paths → Scan. Shrinkerr probes each file with ffprobe and classifies it.\n3. **Connect Plex (optional)** — Settings → Connections → Plex. Enables label-based rules and automatic library refresh.\n4. **Start converting** — Queue → Start. Watches run 1 job at a time by default; the Nodes page lets you bump this to match your hardware.\n\nYou can skip 1 and 3 and go straight to manual encoding via Scanner → Add selected to queue.\n\n## Intel/AMD GPU support (experimental)\n\n\u003e ⚠️ **Experimental** — confirmed end-to-end on one tester's modern\n\u003e Intel iGPU (Ubuntu 24.04, ~145 fps QSV / ~190 fps VAAPI on 1080p\n\u003e HEVC) as of v0.3.92, but we don't have data from a wide enough\n\u003e hardware spread (older Coffee Lake iGPUs, AMD GPUs, Arc /\n\u003e Battlemage) to call them stable. **If you try them, please\n\u003e [open an issue](https://github.com/I-IAL9000/shrinkerr/issues) with\n\u003e a quick \"works on Intel UHD 630, Debian 12, 50 GB → 22 GB\" or \"fails\n\u003e with X\" — that's how we expand the supported matrix.**\n\nBoth Docker images (`:latest` CPU and `:nvenc`) ship with the VA-API\nruntime as of v0.3.67. To activate the encoders, the host needs to\npass through `/dev/dri` and add the container user to the render\ngroup:\n\n```yaml\nservices:\n  shrinkerr:\n    devices:\n      - /dev/dri:/dev/dri\n    group_add:\n      - video\n      # numeric GID of the host group that owns /dev/dri/renderD128.\n      # Find with: stat -c '%g' /dev/dri/renderD128\n      - \"110\"\n```\n\nAfter `docker compose down \u0026\u0026 up -d`, Settings → Encoding → Default\nEncoder will surface QSV / VAAPI options if the host hardware supports\nthem. Click *Re-detect* if your existing tab opened before passthrough\nwas active.\n\n| Encoder | Hardware | When to use |\n|---|---|---|\n| **QSV** (`hevc_qsv`) | Intel iGPUs (Gen8+ / 2014+), Intel Arc / Battlemage | Best on Intel — better quality controls than VAAPI on the same hardware |\n| **VAAPI** (`hevc_vaapi`) | Intel iGPUs (any) AND AMD GPUs (Polaris / Vega / RDNA) | The only hardware path for AMD; works on Intel too |\n| **NVENC** | NVIDIA GPUs (Maxwell+) | Use the `:nvenc` image variant; doesn't go through `/dev/dri` |\n\nVerify with `docker exec shrinkerr vainfo` — should list HEVC encode\nprofiles (e.g. `VAProfileHEVCMain`). If it errors with `vaInitialize\nfailed`, the render node likely belongs to a different vendor's\ndriver (e.g. nvidia-drm); see the troubleshooting section in\n`docs/installation.md`.\n\n---\n\n## Workflows\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eEncode a library\u003c/b\u003e\u003c/summary\u003e\n\n1. **Scanner** → pick directories → **Scan**. Takes 5-30 minutes per 10k files depending on disk speed.\n2. Filter by codec (show x264 only), by resolution (1080p+), by size, or by any attribute via the search bar / advanced filter panel.\n3. Select a subset → **Add selected to queue**. An estimate modal shows expected space saved + estimated time.\n4. **Queue** → **Start**. Shrinkerr encodes in the background and updates the UI live via WebSocket.\n5. Files are replaced in place. Originals go to `.shrinkerr_backup` (or the trash, or permanently deleted — configurable).\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eAudio/subtitle cleanup\u003c/b\u003e\u003c/summary\u003e\n\nMany Blu-ray rips ship with 5+ audio tracks (commentary, descriptive audio, foreign-language dubs) and 20+ subtitle tracks. Shrinkerr can strip the ones you don't want without re-encoding video.\n\n1. **Settings → Audio / Subtitles** — set your \"always keep\" languages (e.g. `eng, isl`) and optionally ignore unknown-language tracks.\n2. Scanner flags files with removable tracks.\n3. Add to queue → Shrinkerr runs an ffmpeg stream-copy (fast, no quality loss) with the unwanted tracks dropped.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eSonarr/Radarr — request a replacement\u003c/b\u003e\u003c/summary\u003e\n\nFor a corrupt or low-quality file already on disk:\n\n1. Queue → Completed/Failed tab → click the file → **Request replacement**.\n2. Shrinkerr blocklists the current release in Sonarr/Radarr, deletes the file from disk, and triggers a fresh search.\n3. Or use **Search for upgrades** / **Search for missing** as bulk actions across a directory.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eNZBGet / SABnzbd integration\u003c/b\u003e\u003c/summary\u003e\n\nAuto-queue freshly-downloaded releases so they start encoding the moment Sonarr/Radarr hands them off.\n\n1. Settings → Automation → **Download NZBGet script** (or SABnzbd).\n2. Drop the downloaded script into your downloader's scripts folder.\n3. Your server URL and API key are baked in — no further config.\n\nFiles with nzb-assigned categories / tags you configure in the Shrinkerr UI will be queued automatically after the download completes.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eDistributed encoding — add a worker node\u003c/b\u003e\u003c/summary\u003e\n\nOffload encoding to a second machine (gaming PC, idle NUC, ARM server).\n\n1. On the main server: Settings → Nodes → **Create worker API key**.\n2. On the worker host, deploy the same image in worker mode:\n\n   ```yaml\n   services:\n     shrinkerr-worker:\n       image: ghcr.io/i-ial9000/shrinkerr:latest    # or :nvenc for a GPU worker\n       environment:\n         - SHRINKERR_MODE=worker\n         - SERVER_URL=http://\u003cmain-host-ip\u003e:6680\n         - API_KEY=\u003cpaste-the-key-here\u003e\n         - WORKER_NAME=gaming-pc\n         - CAPABILITIES=nvenc,libx265               # advertise what this box can do\n         # If the worker sees the library at a different path than the server:\n         # - PATH_MAPPINGS=[[\"/media\",\"/mnt/nas\"]]\n       volumes:\n         - /mnt/nas:/media                          # must match PATH_MAPPINGS or the server's path\n       restart: unless-stopped\n   ```\n\n3. `docker compose up -d`. The worker registers itself and appears on the Nodes page within 30 seconds.\n\nNVENC workers need the same GPU passthrough config as the main server (`runtime: nvidia` + `deploy:` devices on Linux).\n\n\u003c/details\u003e\n\n---\n\n## Configuration\n\n### Environment variables\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `SHRINKERR_DB_PATH` | `/app/data/shrinkerr.db` | SQLite database file path |\n| `SHRINKERR_MEDIA_ROOT` | `/media` | Container-side root of the media library |\n| `SHRINKERR_MODE` | `server` | Set to `worker` to run as a remote worker node |\n| `NVIDIA_VISIBLE_DEVICES` | unset | Set to `all` on NVENC variants to enable GPU passthrough |\n\n**Worker-only** (when `SHRINKERR_MODE=worker`):\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `SERVER_URL` | — | Main server URL (e.g. `http://192.168.1.10:6680`) |\n| `API_KEY` | — | Worker API key created in Settings → Nodes |\n| `WORKER_NAME` | hostname | Display name on the Nodes page |\n| `CAPABILITIES` | auto-detected | Override detection: `nvenc,libx265` or just `libx265` |\n| `PATH_MAPPINGS` | `[]` | JSON list: `[[\"server_path\", \"worker_path\"], …]` |\n| `POLL_INTERVAL` | `5` | Seconds between job polls |\n| `HEARTBEAT_INTERVAL` | `30` | Seconds between keepalive pings |\n| `METRICS_INTERVAL` | `5` | Seconds between CPU/GPU metric reports |\n\nLegacy `SQUEEZARR_*` equivalents are honored as a fallback — the app was originally called Squeezarr and this back-compat avoids breaking existing deployments.\n\n### Settings\n\nAlmost everything else is configured via **Settings** in the UI (encoding presets, always-keep languages, Plex/Sonarr/Radarr credentials, scheduling, notifications, backup retention, etc.). The full settings blob is exportable/importable as JSON for migration.\n\n---\n\n## Monitoring\n\n- **Dashboard** (`/`) — live status, queue depth, total saved, 90-day trends\n- **Monitor** (`/monitor`) — real-time CPU/GPU/RAM/disk gauges, Plex stream count, per-worker-node gauges, Shrinkerr workload summary\n- **Activity** (`/activity`) — timeline of every event (scan, encode, arr action, Plex sync, file-event, etc.)\n- **Logs** (`/logs`) — tail of the container stdout/stderr for debugging\n\n---\n\n## Upgrading\n\nFor a compose deployment pinned to `:latest` / `:nvenc`:\n\n```bash\ndocker compose pull\ndocker compose up -d\n```\n\nThe database migrates itself on startup — never overwrites user data, only adds new columns. If upgrading from the pre-rename era (when the app was called Squeezarr), the `squeezarr.db` file is auto-renamed to `shrinkerr.db` on first start along with its WAL sidecar files.\n\nFor deterministic upgrades in production, pin to a version tag (e.g. `shrinkerr:v0.3.0-nvenc`) instead of a floating `:latest`.\n\n---\n\n## Troubleshooting\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eMonitor says \"No NVIDIA GPU detected\" on a host that has one\u003c/b\u003e\u003c/summary\u003e\n\nHost side — run these on the host, not inside the container:\n\n```bash\nnvidia-smi                                  # does the OS see the GPU?\ndocker info | grep -i runtime               # is the `nvidia` runtime registered?\ndocker run --rm --gpus all \\\n  nvidia/cuda:12.3.1-runtime-ubuntu22.04 nvidia-smi   # end-to-end test\n```\n\nIf `nvidia-smi` fails on the host, reinstall the NVIDIA driver.\n\nIf the runtime isn't registered with Docker:\n\n```bash\nsudo nvidia-ctk runtime configure --runtime=docker\nsudo systemctl restart docker\n```\n\nIf everything on the host looks good but Shrinkerr still doesn't see it, your compose is missing either `runtime: nvidia` (Linux) or the `deploy.resources.reservations.devices` block (both platforms). See the [Quick start — Option B](#option-b--with-nvenc-linux--nvidia-gpu) above for the full working config.\n\nNote: `runtime: nvidia` works on Linux but is rejected by Docker Desktop on Windows. Use only the `deploy:` block on Windows.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eNVENC works but Monitor shows no GPU stats (utilization / temp / VRAM)\u003c/b\u003e\u003c/summary\u003e\n\nThe Encoding Capability card says NVENC is available, but the GPU panel above it doesn't render. NVENC and `nvidia-smi` are gated by *different* capabilities in the NVIDIA Container Toolkit:\n\n- `video` → enables NVENC / NVDEC (encodes work)\n- `utility` → enables `nvidia-smi` and NVML (the panels)\n\nTwo YAML keys can carry capabilities and they take *different* values:\n\n**1. `device_requests.capabilities`** (under `deploy.resources.reservations.devices`):\n\n```yaml\ncapabilities: [gpu, compute, video, utility]\n```\n\n`gpu` is valid here (meta-capability for \"claim a GPU\"). Add `utility` if you want the Monitor panels.\n\n**2. `NVIDIA_DRIVER_CAPABILITIES` env var** (under `environment:`):\n\n```yaml\nenvironment:\n  - NVIDIA_DRIVER_CAPABILITIES=compute,video,utility\n```\n\n`gpu` is **NOT** valid here — the toolkit silently ignores unrecognized values. Valid: `compute`, `video`, `utility`, `graphics`, `display`, `compat32`, `all`.\n\nA compose with `NVIDIA_DRIVER_CAPABILITIES=gpu,video,utility` (the env var, mistaken for the device_requests vocabulary) ends up with effectively just `video,utility` parsed — and on some toolkit versions an invalid entry rejects the whole list, leaving the toolkit's defaults. Easiest fix: replace the env var value with `all`, or use the device_requests block instead.\n\nTo diagnose:\n\n```bash\ndocker exec shrinkerr env | grep NVIDIA   # see the env var\ndocker exec shrinkerr which nvidia-smi    # is the binary even mounted?\ndocker exec shrinkerr nvidia-smi          # does it run?\n```\n\nIf `nvidia-smi` is missing → the toolkit didn't inject it (capabilities or runtime issue). If it exists but errors → host driver upgrade since container start; `docker compose down \u0026\u0026 up -d` re-injects against the new driver.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eNVENC advertised but encodes fail with \"driver too old\"\u003c/b\u003e\u003c/summary\u003e\n\n- `:nvenc` ships ffmpeg n7.1 → requires NVIDIA driver **525.60.13+**\n- `:edge-nvenc` ships ffmpeg master → requires NVIDIA driver **570.00+**\n\nThe Monitor page shows your current driver alongside the requirement. Either upgrade the driver or switch to the image variant that matches.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e\"Output file missing or empty after conversion\"\u003c/b\u003e\u003c/summary\u003e\n\nUsually a transient filesystem glitch on NFS/SMB. The app retries a few times and scans the directory for a late-landed temp file before failing; when it does fail, the source file is never touched so you can safely retry.\n\nCheck the logs for the full diagnostic block — it prints the expected temp path, ffmpeg exit code, and whether the source is intact.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eQueue tab eats CPU in the browser\u003c/b\u003e\u003c/summary\u003e\n\nFixed in v0.3.0 (virtualized list + throttled WebSocket progress). If you're still seeing it on an older version, the Monitor page's Shrinkerr workload card is a good workaround — it shows the same running/pending/fps numbers without rendering individual queue rows.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eI locked myself out of the UI — wrong password / forgot username after enabling Authentication\u003c/b\u003e\u003c/summary\u003e\n\nYou don't need to nuke the database. The auth toggle, username, and password hash all live in the `settings` table — flip them directly via the container's Python (the `sqlite3` CLI isn't installed in the image, but Python's stdlib `sqlite3` module is):\n\n```bash\n# 1. Disable auth entirely (fastest unlock — log back in, fix the credentials, re-enable)\ndocker exec shrinkerr python3 -c \"\nimport sqlite3\nsqlite3.connect('/app/data/shrinkerr.db').execute(\n    \\\"UPDATE settings SET value='false' WHERE key='auth_enabled'\\\"\n).connection.commit()\"\n\n# 2. Or just clear the password hash (auth stays on, but blank password works)\ndocker exec shrinkerr python3 -c \"\nimport sqlite3\nsqlite3.connect('/app/data/shrinkerr.db').execute(\n    \\\"UPDATE settings SET value='' WHERE key='auth_password_hash'\\\"\n).connection.commit()\"\n\n# Either way, restart so the auth cache invalidates immediately\ndocker compose restart\n```\n\nAdjust `/app/data/shrinkerr.db` if you mounted the data volume elsewhere — match the path inside the container.\n\nAfter unlocking, set fresh credentials in **Settings → System → Authentication** and re-enable the toggle.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eHow do I reset everything and start over?\u003c/b\u003e\u003c/summary\u003e\n\n```bash\ndocker compose down\nrm -rf ./data               # or wherever your /app/data volume mounts from\ndocker compose up -d\n```\n\nYour media files are untouched. Only the Shrinkerr DB + settings get wiped.\n\n\u003c/details\u003e\n\n---\n\n## Development\n\nShrinkerr is a FastAPI + React app. Running locally without Docker:\n\n```bash\n# Backend — Python 3.11\npython -m venv .venv \u0026\u0026 source .venv/bin/activate\npip install -r requirements.txt\npython -m backend.main            # starts on :6680\n\n# Frontend — Node 20\ncd frontend\nnpm ci\nnpm run dev                       # starts on :5173 with /api proxy to :6680\n```\n\nYou need `ffmpeg` and `ffprobe` on your `$PATH`. BtbN's static builds are recommended — grab the `n7.1` release tag for your platform.\n\nBuild both Docker images locally:\n\n```bash\n./scripts/build-images.sh                  # all four variants (needs buildx + QEMU for multi-arch)\n./scripts/build-images.sh latest nvenc     # subset\n```\n\n### Project structure\n\n```\nbackend/\n  main.py               FastAPI app, lifespan, WS broadcast\n  database.py           SQLite schema + migrations\n  config.py             Pydantic settings\n  queue.py              Job queue + worker loop + post-conversion hooks\n  converter.py          ffmpeg orchestration, VMAF, backup rotation\n  audio.py              Audio/subtitle remux path\n  scanner.py            Media directory walker + ffprobe classifier\n  nodes.py              Distributed worker registration + metrics\n  worker_mode.py        Worker-mode entry point (SHRINKERR_MODE=worker)\n  plex.py               Plex API client\n  arr.py                Sonarr/Radarr integration\n  health_check.py       File corruption detection\n  routes/               HTTP endpoints (one file per domain)\nfrontend/src/\n  pages/                One per top-level route (Dashboard, Scanner, Queue, …)\n  components/           Shared widgets (JobCard, ProgressBar, FolderBrowser, …)\n  api.ts                Typed API client + WebSocket hook\n  types.ts              Shared TypeScript types\n```\n\n### Tests\n\n```bash\npytest backend/tests/\n```\n\nMost coverage is in the converter, scanner, and health-check paths — the places where an uncaught bug could corrupt user data.\n\n---\n\n## Architecture\n\n- **Backend**: Python 3.11, FastAPI, SQLite (WAL mode) via aiosqlite, APScheduler for periodic jobs\n- **Frontend**: React 19, TypeScript, Vite, Recharts for stats\n- **Realtime**: single WebSocket broadcast channel for job progress + scan progress + node updates\n- **Encoding**: subprocess-spawned `ffmpeg` per job, with progress parsed from stderr\n- **Storage**: everything in `/app/data/shrinkerr.db` — scans, jobs, rules, settings, backups. Bind-mount this directory for persistence.\n\n---\n\n## License\n\nShrinkerr is released under the [Apache License 2.0](LICENSE). TL;DR: you can use it, modify it, redistribute it, and build commercial products on top of it — just keep the license notice, state your changes, and don't sue contributors over patent claims. See [LICENSE](LICENSE) for the full text.\n\n---\n\n## Acknowledgements\n\n- [BtbN/FFmpeg-Builds](https://github.com/BtbN/FFmpeg-Builds) — static ffmpeg builds used by the Docker images\n- [NVIDIA Container Toolkit](https://github.com/NVIDIA/nvidia-container-toolkit) — GPU passthrough to containers\n- [VMAF](https://github.com/Netflix/vmaf) — perceptual quality measurement\n- [Sonarr](https://sonarr.tv/) / [Radarr](https://radarr.video/) / [Plex](https://www.plex.tv/) / [NZBGet](https://nzbget.com/) / [SABnzbd](https://sabnzbd.org/) — the ecosystem this slots into\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fi-ial9000%2Fshrinkerr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fi-ial9000%2Fshrinkerr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fi-ial9000%2Fshrinkerr/lists"}