Tab jump keys, reverse-tab, title filter, and first/last/page movement make a 160-item library usable. Review/Stuck/Library badge counts, a movie detail pane, scrollable Calendar/Health, history and candidate colors, and non-blocking add-search fix the daily friction. Status messages expire so key hints come back; review selection no longer walks off the end of the list. |
||
|---|---|---|
| .forgejo/workflows | ||
| .idea | ||
| breadarr-shared | ||
| breadarr-tui | ||
| breadarrd | ||
| ci | ||
| packaging/systemd | ||
| .gitattributes | ||
| .gitignore | ||
| AGENTS.md | ||
| bakery.toml | ||
| Cargo.lock | ||
| Cargo.toml | ||
| config.example.toml | ||
| CONTRIBUTING.md | ||
| LICENSE | ||
| README.md | ||
breadarr
A single-daemon Rust replacement for the Sonarr + Radarr + Prowlarr stack — one process, one database, and a small, opinionated set of behaviors instead of a fully general, endlessly-pluggable indexer/automation platform. If Sonarr/Radarr/Prowlarr's flexibility is more than you need and you're fine with a small hardcoded set of sources and a terminal UI in exchange for a much lighter footprint, this is built for you.
breadarrd— the daemon. Watches sources, matches releases to your library, scores and grabs candidates, imports completed downloads, probes them for real ground-truth quality, and refreshes Jellyfin. Runs as asystemd --userservice.breadarr-tui— a terminal client (ratatui) that talks tobreadarrd's local HTTP API. No web UI, on purpose.
Distribution
breadarr is a homelab product. It is distributed through bakery (bakery install breadarr) and is not baked into the BOS ISO. It is not a GTK/desktop-shell app, has no bos-settings panel, and does not subscribe to or emit bread events. bread's KNOWN_APPS list reserves the "arr" app id for a possible future integration; this repo does not emit on that id.
Why this exists
Sonarr + Radarr + Prowlarr is three separate services, three databases, three web UIs, and a lot of setup surface for one workflow: watch for releases, pick the best one, download it, file it correctly, tell Jellyfin. breadarr collapses that into a single daemon, with a few specific problems solved directly rather than configured around:
- Wrong default audio track — a common pattern on some trackers is "Italian track 1, English track 2." breadarr detects this after download and remuxes the English track to default via
mkvmerge, instead of scoring the release down or grabbing something worse. The same fix can be swept across the existing library after the fact (remux-backlog), not just applied to new grabs. - Anime numbering — absolute episode numbers get resolved to season/episode via a bundled AniDB↔TVDB↔TMDB mapping, and anime is allowed to fall back to Japanese-only audio when no English release exists (every other content gate requires English audio).
- Fuzzy title matching — release titles are matched to your monitored library via a local ONNX embedding model (
all-MiniLM-L6-v2, CPU-only, no GPU dependency), not exact-string matching. Low-confidence matches land in a review queue instead of silently grabbing the wrong show. A token-overlap sanity gate also blocks the model's one known failure mode (see Known limitations) from reaching the review queue at all, not just from auto-matching. - Library normalization — a library-scan mode matches existing folders to TVDB/TMDB and renames them down to a clean
Title (Year)form, stripping release-group tags, quality markers, and season/episode cruft that shouldn't be in a folder name. - Season packs — a batch release (a whole season, a multi-episode range) is grabbed and split correctly: each file inside is matched to its own tracked episode by re-parsing its own filename, then imported individually, with quality compared per-episode against whatever's already owned.
- Media intelligence — every library file gets
ffprobed on import (and, incrementally, in the background for the existing library), so breadarr knows its actual resolution/codec/audio/subtitle makeup — not just what the release title claimed — and can flag files that don't match up. - Quality upgrades keep happening after import — a separate, slow-cadence background cycle periodically re-checks already-owned, monitored episodes/movies against what's currently searchable, and re-grabs when a candidate beats the current file's score by more than a configurable margin (repacks/propers always supersede regardless of margin). The existing hardlink-swap-on-import logic handles the actual file replacement, so this is purely a new trigger, on its own interval and budget (
upgrade_*in[sources]) so it never competes with missing-content search for request budget. Its limited per-cycle budget is spent onmedia_file_probe-flagged files first (verified under-quality, missing subtitles, non-English default audio, or a probe/decode failure) before it's spent uniformly across everything else owned. - Manual release picker — for the cases auto-grab and the review queue don't handle well, press
con a selected show/episode/movie in the TUI to see the same scored (or gate-rejected, with the reason) candidate list the automatic pipeline would have seen, and grab one by hand.
Sources
- nyaa.si (RSS) — anime TV, polled continuously. Full-auto, no request budget concerns (it's a plain RSS feed).
- apibay.org (a community JSON API mirror of The Pirate Bay's search) — the primary search-driven source for general TV and movies. Unlike 1337x this is a genuine machine-readable API, needs no HTML scraping, and its search actually ranks by relevance rather than pure seeder count, which matters a lot for titles made of common words.
- 1337x (scraped HTML, via community mirrors) — secondary search-driven source, tried after TPB. 1337x's main domain is Cloudflare-protected and has a ban history, so requests are round-robined across mirrors with automatic cooldown/backoff on failures, jittered between searches, and rate-limit responses are honored explicitly.
- nyaa.si search mode — anime movies specifically route here instead of TPB/1337x, since nyaa is the safe, official-RSS-interface target and gives materially better results for anime content.
All three search-driven sources share one per-cycle request budget (default: 5 searches per 30-minute cycle, TPB tried first, then 1337x, then nyaa search) — kept conservative since 1337x has a ban history and the goal is steady backlog clearing, not maximum throughput. A whole cycle failing outright backs off the next cycle's interval (1h → 2h → 4h, capped), on top of each source's own per-mirror cooldowns.
Setup
Install via bakery (bakery install breadarr) on a homelab host, or build from this repo. breadarr is not on the BOS ISO.
- Copy
config.example.tomlto~/.config/breadarr/breadarrd.tomland fill in:- qBittorrent WebUI URL/credentials
- Jellyfin URL + API key
- TVDB API key (thetvdb.com, "Fan/Personal" tier)
- TMDB API Read Access Token (themoviedb.org, v4 auth, not the shorter v3 key)
- Optionally, a Gotify-shaped
notifications.webhook_urlfor push notifications, anddaemon.api_tokeniflisten_addrwill ever be bound to more than loopback.
- Install
mkvtoolnix-cliandffmpegon the host (breadarr runs natively, not in Docker). - Install the systemd user unit (
packaging/systemd/breadarrd.service) to~/.config/systemd/user/, then:loginctl enable-linger $USER # so it runs without an active login session systemctl --user daemon-reload systemctl --user enable --now breadarrd - Point
breadarr-tuiat the same config (it readsdaemon.listen_addrto find the API) and run it.
Using the TUI
Tab / Shift+Tab cycle Library / History / Review / Add / Stuck / Calendar / Health / Profiles; 1–8 jump straight to a tab. j/k or arrows move, g/G jump to first/last, PgUp/PgDn page, Enter opens detail or runs a search, Esc backs out. ? is the full key list.
- Review —
aapproves andrrejects a pending low-confidence title match. The tab title badges the pending count. Check this periodically, especially early on. - Library —
/incrementally filters the list by title;f/ocycle kind-filter and sort. With an item's detail open:striggers an immediate search-now pass for that item's backlog;njumps to the next missing monitored episode;m/e/Stoggle monitored on the show/episode/season respectively;x(confirm with a secondx) removes the item from tracking without touching files on disk;d(confirm with a secondd) deletes a bad imported file from disk and clears its tracking, freeing the episode/movie to be re-grabbed on the next cycle — the redownload path for a file that turned out to be wrong or broken;cfetches the manual release picker for the selected episode/movie,Entergrabs the highlighted candidate,Esccancels. Movies (no episode list) show a summary pane with the same keys. - Stuck — surfaces grabs that look stalled (no download progress advancing, or missing from qBittorrent) before the daemon's own auto-fail timers would catch them.
Enterjumps to that show. - Calendar — upcoming/recently-aired episodes in a roughly week-either-side window.
Enteropens the matching episode. - Health — daemon cycle outcomes plus the library-health report (see below), scrollable.
- Profiles — quality-profile weight axes; open a profile and edit a weight in place.
Operational notes
curl http://127.0.0.1:7879/healthis liveness-only (is the process up). Cycle outcomes live at/health/detail(requiresAuthorization: Bearer <token>whendaemon.api_tokenis set).curl http://127.0.0.1:7879/library/health(or the TUI's Health tab) reports corrupt files, under-1080p files, missing-English-audio files, non-English-default-audio files, missing-subtitle files, duplicate-file groups, and library-wide summary stats (codec breakdown, resolution distribution, subtitle coverage %) — all derived fromffprobedata, not release-title claims.- The API is plaintext HTTP — there is no TLS. Binding
listen_addroff loopback requires a non-emptydaemon.api_token. - If
daemon.api_tokenis set, every route except/healthrequiresAuthorization: Bearer <token>. - Library normalization is a manual, explicit action (not run automatically against your files):
breadarrd debug-scan-tv <root-dir>/breadarrd debug-scan-movies <root-dir>. breadarrd remux-backlogsweeps the whole library for files with a non-English default audio track and applies the same track-promotion fix used automatically on fresh imports — a one-time (or occasional) pass against files that predate the fix, or were imported before breadarr started tracking them.breadarrd probe-librarybackfillsffprobedata for the whole library in one run (the running daemon does this incrementally, a bounded batch per hour, so it doesn't stall the grab/import/search cycles — this command is for getting it all done immediately instead).breadarrd verify-libraryruns the expensive full-decode corruption check (ffmpeg -xerror, actually decoding every frame) against every file whose cheap header probe succeeded but hasn't been decode-verified yet. This is opt-in and can take minutes per file, so — unlikeprobe-library— it's never run automatically by any ticker; run it by hand (or on a cron) whenever you want a real, not-just-header-parseable confirmation the library is intact.- Other
debug-*subcommands are diagnostics for exercising one piece of the pipeline directly — runbreadarrd <name>with no args to see its usage. Currently:debug-qbit-add,debug-qbit-list,debug-jellyfin-refresh,debug-tvdb-add,debug-tvdb-search,debug-anime-map-refresh,debug-match-title,debug-grab-cycle,debug-import-cycle,debug-1337x-search,debug-scan-tv,debug-scan-movies,debug-search-show(a manually-triggered, unthrottled search pass over one already-tracked title's whole backlog),debug-reconcile-report(dry-run of the disk-reconciliation pass — safe to run against a freshly-restored or otherwise suspect database before trusting the hourly ticker with it unattended). breadarrd transcode-librarybackfills AV1 transcode over existing library files (requires[transcode] enabled = true).breadarrd retranscode-oversizedre-encodes already-AV1 files that landed larger than the current ceiling.breadarrd relink-orphaned-filesreattaches episode files on disk that tracking lost.- The embedding model (~90MB) downloads automatically on first run.
- The database is backed up (with its WAL/SHM sidecars) to
<db-dir>/backups/on every daemon startup, keeping the 5 most recent copies — there's no migration framework, so this stands in for the pre-upgrade backup Sonarr/Radarr do on every schema change. - An hourly background pass reconciles tracked
episode_filepaths against what's actually on disk: a file renamed or transcoded in place (e.g. Tdarr converting codec/container) gets its path repaired rather than being wrongly treated as deleted; a file genuinely gone gets cleared from tracking so it becomes searchable again. A circuit breaker refuses to touch anything if an anomalous fraction of the library looks missing at once (the classic false signal of an offline mount), rather than mass-clearing tracking for files that are actually still there.
Known limitations
- No subtitle generation yet — a Whisper-based reimplementation of an existing external tool is planned (see Roadmap), with the schema (
episode_file.subtitle_status) already reserved for it. - No Sonarr/Radarr API compatibility shim yet, so tools expecting that API (e.g. Overseerr/Seerr) can't integrate directly — still a future idea (see Roadmap).
- Quality-profile weights are loaded and editable (Profiles tab).
min_seedersand the group denylist are still hardcoded defaults, not user-configurable. - The title-matching embedding model (all-MiniLM-L6-v2) doesn't actually discriminate between unrelated romanized-Japanese titles — it clusters any two romaji strings as "similar foreign text" regardless of content (verified live: several unrelated anime auto-matched at >0.85 confidence against a handful of "attractor" shows with zero real relation). A token-overlap gate (
MIN_TOKEN_OVERLAPinmatcher/mod.rs) blocks this from both auto-matching and reaching the review queue, but the underlying model limitation is a workaround, not a fix. - The search loop's per-cycle budget is intentionally conservative; raising it trades faster backlog clearing for more request volume against 1337x specifically, which has a ban history. The upgrade-search loop shares the same underlying sources and the same conservatism applies.
Roadmap
Everything above is shipped and running. This section is the honest, disciplined version of "what would this become if taken all the way" — grounded in subsystems that already exist, not a wishlist. Nothing here contradicts the project's two foundational design choices (a small hardcoded set of sources, not a general indexer-plugin architecture; a TUI, not a web UI) — anything that would require reconsidering either is flagged as such.
Medium-term (closes the loop on data already being collected)
- Tdarr hand-off.
media_file_probealready hasvideo_codec,container_bitrate,video_bitrate, andhdrper file — everything needed to generate a "these files are still H.264/high-bitrate and are good AV1-transcode candidates" list without re-scanning the filesystem. Whether that's a report the user acts on manually or a direct trigger into the existing Tdarr pipeline is a judgment call for whenever this is built — but the data side of this is already sitting in the database, unused. - Smart-library auto-upgrade daemon. The combination of the quality-upgrade loop above, corruption detection, and duplicate-group detection (
library/health'sduplicate_groups) is most of what's needed for a low-touch "keep the library clean" background process: re-grab flagged files, quarantine/remove confirmed-corrupt ones, resolve duplicate groups down to the single best-scoring file. This is a composition of pieces above, not a new subsystem — the risk is entirely in getting the "don't touch a file a human hasn't implicitly signed off on" judgment calls right, which argues for notification-first (as the webhook infrastructure already supports) before any auto-delete behavior. - Tracker/release-group reliability scoring.
torrent_fetchis a permanent, unopinionated log of every torrent ever grabbed, independent of what happened to it afterward — cross-referencing it againstrelease.status(importedvsfailed) andmedia_file_probe's post-import quality flags would let a release-group or per-tracker track record feed back intoscoring/score.rsas a real weighted axis, instead of only the staticgroup_allowlist/group_denylistlists that exist today. This is genuinely unexploited surface area — the raw data already exists and nothing reads it. - Mining the raw ffprobe JSON.
media_file_probe.raw_ffprobe_jsonis kept verbatim specifically so nothing has to be re-probed when a new use is found for a field that doesn't have its own column yet (chapter markers, encoder tags, less-common stream metadata). The near-term backlog above doesn't need it; whatever comes after does, and it's already there.
Longer-term (larger, still-plausible extensions)
- Learning from review-queue decisions. Every approve/reject in the review queue is already a labeled example of "was this match actually correct." Logging outcomes (not just acting on them) and periodically comparing auto-tuned per-library confidence thresholds against the fixed
AUTO_MATCH_CONFIDENCE/MIN_TOKEN_OVERLAPconstants inmatcher/mod.rsis a plausible way to let the matcher get measurably better over time for this library's actual title vocabulary — anime is the library segment most exposed to the token-overlap workaround today, so it's also the segment most likely to benefit first. This is a genuine judgment call (a wrong auto-tune silently degrades match quality with no review-queue visibility into it happening), not a slam dunk — worth prototyping as an offline analysis of logged decisions before it's ever allowed to write back to live thresholds. - Seerr/Overseerr compatibility shim, then real request fulfillment. A Sonarr/Radarr v3-API-compatible shim is the integration path Seerr's client code expects. Once that shim exists, the natural next step isn't just protocol compatibility — it's tracking fulfillment end-to-end (a request maps to a
media_item, which maps torelease/event_historyrows that already record exactly when and how it was grabbed and imported), giving a requester real status instead of Seerr's own best-effort polling. - Whisper subtitle generation. Fully speced already: reimplement the existing external Python/Whisper tool's exact behavior — "Full Subtitles" (everything transcribed) and "Foreign Parts" (segments where a translate-pass diverges from the transcribe-pass, via text-similarity diff at a 0.80 threshold) SRT tracks, muxed with
mkvmerge— but gated and on-demand (triggered post-import only when a file lacks subtitles and has non-English/fallback audio), unlike the external tool's full-library batch sweep.episode_file.subtitle_statusis already wired through the schema for this. The open technical questions are a Rust equivalent of Python'sdifflib.SequenceMatcherfor the similarity diff, and which Whisper-in-Rust GPU backend story actually works well across the range of consumer GPU hardware this runs on in practice (CUDA/Metal/Vulkan-centric crates likewhisper-rshave no strong story for e.g. Intel Arc or other OpenVINO-friendly hardware).
Deliberately not on this list
A general indexer-plugin system (Prowlarr-style) and a web UI were both explicit, considered rejections early in this project, not gaps — a hardcoded, well-understood set of sources and a terminal-only client are the point, not a limitation to eventually fix. Nothing above should be read as walking either decision back; if a future need ever seriously challenges one of them, that deserves its own explicit reconsideration, not a quiet reversal buried in a roadmap item.
License
MIT — see LICENSE.