2 unstable releases
| 0.41.1 | Apr 13, 2026 |
|---|---|
| 0.0.0 | Apr 11, 2026 |
#160 in GUI
455KB
9K
SLoC
This is the
mainbranch — active development (v0.55.0+, SQLite-backed).For the v0.42.0 legacy line (JSONL + TOML persistence), check out the
legacybranch. Both branches share crate names, binary names, and the~/.config/avatarr/config file — run only one version at a time, and back up~/.local/share/avatarr/before cross-version experiments.
🎬 Avatarr
A single-binary Rust media manager. Search torrents, download via qBittorrent, organize into a Plex-compatible library — GUI and CLI in one portable executable.
Working toward a leaner, unified replacement for the Sonarr + Radarr stack: TV and movies in one binary, one database, three UI surfaces (Slint desktop GUI for power users, React WebUI for homelab/Docker, avatarr CLI for headless). The rewrite arc runs from m40 (rename, shipped) through m110 (user docs). See ROADMAP.md for the friendly status mirror across all 68 milestones, or docs/plans/2026-04-30-avatarr-roadmap-post-m53b-redraft.md for the strategic spec with full reasoning + decision log. The earlier 2026-04-21 + 2026-04-11 roadmaps are preserved for historical context.
Design reference: visual, interaction, and information design for the unified rebuild lives at docs/design/DESIGN.md (v1 handoff). The bundled HTML mockups under docs/design/hifi/ are pixel reference — implementation recreates them in Slint rather than lifting code. Start with docs/design/README.md for onboarding order.
Renamed from
plex-pipeline-rsin v0.40.0 (April 2026). The project migrated to fresh repos at https://codeberg.org/alan090/avatarr and https://github.com/alan09086/avatarr. The oldplex-pipeline-rsrepos have been archived. See CHANGELOG.md for the migration entry.
🧭 Status
Today (v0.63.0):
- Working torrent search across TPB, YTS, EZTV, and RuTracker with score-based ranking
- qBittorrent monitor that auto-organizes completed downloads into Plex folder structures
- Movies + TV (seasons, episodes, ranges, complete-series packs)
- Explicit Movie/TV mode selector in the Search tab — no more guessing what the parser will infer
- TV Season Picker dialog when a bare TV title is searched — TMDB resolves the canonical title, the picker offers S01–S10 or a Complete Pack shortcut
avatarr search --type {movie,tv}is strict;--completeand--season Nprovide direct-dispatch shortcuts for TV- Smart per-episode quality upgrades (size + resolution comparison)
- IMDB/TMDB metadata resolution with year-aware caching
- clap-based CLI (Slint desktop GUI rebuilds at m71)
- Cross-platform: Linux, Windows, macOS
- SQLite catalog foundation (m43): hardened sqlx pool, 49-table unified schema from Sonarr v4 + Radarr v6, automatic migrations at startup, fatal-error exit with actionable messages on pool-open failure
- Series / Season / Episode domain models (m44): typed Rust structs mirror the schema column-for-column; enums with
Unknown(i32)fallthrough round-trip unknown DB values without erroring;SeriesRepository/SeasonRepository/EpisodeRepositoryas#[async_trait]+ object-safe surfaces;Episode::has_file()+is_missing(now)computed helpers. Migration 0002 triggers keep the legacySeries.SeasonsJSON column coherent with theSeasonsTABLE by construction inside the triggering transaction. - Movie / MovieMetadata / MovieFile domain models (m45): Movies side of the port — 18-field
Moviewith schema-mirrored denormalisation, 29-field content-sideMovieMetadatabound viaMovieMetadataIdFK, andMovieFilewithhas_media_info()helper.MovieStatusType(Deleted=-1,Tba=0,Announced=1,InCinemas=2,Released=3),MovieMonitorType,AddMovieMethodenums withUnknown(i32)fallthrough.MovieRepository/MovieMetadataRepository/MovieFileRepositoryas#[async_trait]+ object-safe surfaces with sqlx-backed impls. Migration 0003 triggers keepMovies.HasFile/Movies.MovieFileIdin sync with theMovieFilestable on every INSERT/UPDATE/DELETE. - Repository layer polish + path newtypes + remote-path resolver (m46):
IBasicRepository<T, NewT>supertrait bundlesfind_by_id/insert/update/delete_by_idacross Series/Episode/Movie/MovieFile — each entity repo now inherits the shared CRUD surface instead of redeclaring it (Season + MovieMetadata stay onupsert, so they opt out). 6 strongly-typed path newtypes (SeriesPath,MoviePath,MovieFilePath,RelativePath,LocalPath,RemotePath) replace rawString/PathBufon the TV + Movie domain structs.RemotePathMapping+SqliteRemotePathMappingRepository+RemotePathResolvertranslate between remote and local path namespaces using longest-prefix-match withPath::components()normalization and a TTL +AtomicU64version-counter invalidated cache (4 error variants,..escape-path rejection). No new migration —RemotePathMappingstable was already declared in0001_initial.sql. - MoveLogs SQLite migration + dual-writer (m47): filesystem-move audit trail migrates from JSONL-only to a SQLite + JSONL dual-writer over a 9-release window (v0.47.x → v0.55.x). Typed
MoveLog/NewMoveLog/FileMovedomain models withMoveLogStatus/MoveLogCategoryenums carrying#[serde(other)] Unknownfor forward-compat.MoveLogWritertrait abstraction withDualWriter(JSONL first — rollback-safe, then SQLite — warn-on-error) andSpyWriterfor tests. Startup sentinel probe (transaction-rollbackINSERT) catches broken write paths at boot (CLI exit code 3). Periodic parity check every 100 writes logserror!on drift beyond ±2 rows. Migration0004_move_logs.sqlships theMoveLogstable + 3 indexes; SHA-256 pinned. JSONL writer removal is scheduled for v0.56.0. - RootFolder domain + repository (m48):
RootFolder(12 fields) andNewRootFolder(11 fields) domain types mapping theRootFolderstable.RootFolderRepositorytrait extendsIBasicRepositorywithfind_by_path(UNIQUE path, byte-equal) andfind_all(insertion-order).SqliteRootFolderRepositorywith privateRootFolderRow+Fromboundary pattern. No new migration — table was declared in0001_initial.sql. - Parser baseline measurement (m49): Sonarr v4.0.17.2952 parser fixture corpus (1,416 cases, 12 categories) vendored at
tests/sonarr-fixtures/.crates/parser-baseline/harness benchmarks hunch v2.0.0 and the existing bespoke parser side-by-side. Verdict: path C — port Sonarr's QualityParser.cs + LanguageParser.cs (hunch 30.3% / bespoke 0.9% weighted, both below the 60% fork threshold). Report atdocs/research/hunch-baseline.md. m50 reframes as Sonarr parser port. - Sonarr parser port (m50):
crates/parser/workspace member with full GPL-3.0 port of Sonarr v4.0.17.2952'sQualityParser.cs(15 regexes, 21Qualityvariants via Resolution + QualitySource cascade) andLanguageParser.cs(6 regexes, 47Languagevariants). avatarr-parser scores 94.5% quality / 100.0% language on the fixture corpus, well above the m51 ≥70% gate. 4-way bake-off incrates/parser-baseline/(Sonarr ground-truth + hunch + bespoke + avatarr-parser). Library only at m50; m51 wires into release matching.scripts/build-appimage.shrestored. Report atdocs/research/m50-port-baseline.md. - Episode regex cascade + release-group pipeline (m52-m53b): 89 Sonarr
ReportTitleRegex[]cascade entries ported — 42 standard (m52), 9 daily, 34 absolute (m53). Post-cascade release-group pipeline hand-rolls Sonarr'sParseReleaseGroup(anime[SubGroup], exception lists, trailing-GROUP). Fixture pass rates: single 171/171, multi 69/69, season 49/49, daily 42/42, absolute 155/155 (100%), release_group 112/112. m53b closed the absolute fixture gap via mask-and-retry lookahead emulation, batch-range fallback, dual-capture range routing, trailing-year reattachment, and post-handler tail enrichment.extract.pyfixture extractor fixed for paren-balancing and comment-skipping. - Series add + TVDB resolver (m61):
avatarr series searchqueries TheTVDB for series by name;avatarr series add --tvdb-id N --path /tv/showfetches metadata and atomically inserts Series + Seasons + Episodes in a single transaction. 14-variant monitor strategy (--monitor all|none|future|first-season|…), root-folder default resolution,--jsonoutput for both commands. TVDB v4 API client with bearer-token caching and automatic 401 retry. - Movie add + TMDB resolver (m62):
avatarr movie searchqueries TMDB for movies by title (optional--yearfilter);avatarr movie add --tmdb-id N --path /movies/dirfetches TMDB metadata and atomically inserts MovieMetadata + Movie in a single transaction. Configurable--monitored,--minimum-availability(announced/in-cinemas/released), root-folder quality profile resolution,--jsonoutput for both commands. Dedicated TMDB v3 API client with query-param auth. - Monitor / unmonitor state machine (m63): runtime
MonitorServiceplus a CLI surface that toggles theMonitoredflag on Series, Season, Episode, and Movie rows.avatarr series monitor/unmonitor,avatarr series apply-monitor --strategy <STRATEGY>(cascades through every season + episode using the same 14-variant strategy as m61's add-time picker, now includingMonitorSpecials/UnmonitorSpecials),avatarr series set-monitor-new-items --mode all|none,avatarr series show/list,avatarr movie monitor/unmonitor,avatarr movie show/list,avatarr season monitor/unmonitor, andavatarr episode monitor/unmonitor(range syntax:1-5,1,3,5,1-3,7,10-12). Reposet_monitoredreturnsResult<usize, _>with aMonitored != ?guard to skip no-op writes;--dry-runon apply-monitor + episode commands distinguishes targeted rows from rows actually changing. - Missing detection + bulk-add (m64):
avatarr series missingandavatarr movie missingsurface monitored items without files.avatarr series bulk-add --file <path>andavatarr movie bulk-add --file <path>ingest JSON arrays for batch library population, with--dry-runlocal-DB validation and--jsonoutput. Final milestone of the Library Management cluster (m61-m64). - 1,394 tests,
cargo clippy --workspace --all-targets -- -D warningsclean - Auto-migration from
~/.config/plex-pipeline/→~/.config/avatarr/on first launch (idempotent, EXDEV-safe)
Heading toward m110 (68 milestones, ~22-24 months solo part-time):
Weekly milestone cadence (each ships v0.N.0), grouped into thematic clusters per the 2026-04-30 redraft:
| Cluster | Milestones | Theme |
|---|---|---|
| Foundation | m43 → m48 ✅ | sqlx infra, domain models, repository layer, JSONL→SQLite migration, RootFolder |
| Parser quality | m49 → m53b ✅ | Sonarr fixture corpus + parser port + 100% fixture pass rate |
| Cleanup + parser arc | m54 → m57 | Legacy GUI deletion + binary migration to crates/cli, parser anime + year mismatch, parser wire-in, crates.io publish |
| Decision engine | m58 → m60 | Quality profiles + cutoff/upgrade + Accept/Reject/Upgrade engine |
| Library management | m61 → m64 | Series/Movie add workflows, monitor state machine, missing detection |
| Scheduler | m65 → m66 | tokio-cron-scheduler + RSS polling + missing-search |
| Downloads + import | m67 → m70 | IDownloadClient trait, tracked downloads, hardlink import, indexer abstraction, avatarr smoke end-to-end self-test |
| Slint desktop GUI rebuild | m71 → m85 | New crates/gui from DESIGN.md — tokens + 4-accent theme cycle + 22 components + 10 screens + cutover + soak |
| API v3 (Axum) | m86 → m93 | Load-bearing for both WebUI and Bazarr/Prowlarr; X-Api-Key auth, full endpoint coverage, OpenAPI spec |
| WebUI (React + TS) | m94 → m103 | Parallel implementation sharing tokens.toml with Slint, 10 screens mirroring DESIGN.md, Docker compose ship |
| Migration importer | m104 → m105 | avatarr-cli import {sonarr,radarr} with dry-run + idempotency |
| Notifications + polish | m106 → m108 | Discord/Telegram/webhooks + health checks + multi-arch Docker + .deb + AppImage + signed releases |
| Contributor + user docs | m109 → m110 | CONTRIBUTING.md + dev-setup + plan template formalization (m109); user docs (m110) |
Pre-scheduled 1.0 release removed — Avatarr's 1.0 tag happens organically if and when earned, not as a roadmap checkpoint. Shipped-milestone signals (m93 = Bazarr/Prowlarr work, m103 = WebUI ships, m104-m105 = Sonarr/Radarr import works) communicate readiness more precisely than a version number ceremony.
Distribution discipline — AppImage, Docker multi-arch, .deb, and signed releases all ship at m108. There is no per-commit AppImage rebuild during the m54-m107 development arc; cargo run / cargo test are the dev iteration loop.
Versioning: milestone N ships as v0.N.0; v0.N.1+ is strictly for post-ship bug fixes.
✨ Features (today)
- 🔍 Multi-source torrent search: TPB, YTS, EZTV, RuTracker (fallback chain — RuTracker disabled by default, requires account)
- 🌟 Quality preference: Select "Any" to prefer 1080p while accepting all qualities, or choose a specific quality (720p, 1080p, 2160p) to only show results matching that quality
- 📊 Smart scoring: Ranks results by title match, year, quality, seeders, file size
- 📺 TV support: Season packs, episode fallback, auto-discovery (
S01+), season ranges (S01-S05), complete series packs - 📽️ UHD routing: 2160p/4K movies automatically routed to UHD Movies library
- ⬆️ Smart quality upgrade: Incoming files compared by resolution and size — upgrades lower quality, fills missing episodes, replaces zero-byte files
- 👀 Torrent monitor: Polls qBittorrent, auto-organizes completed downloads into Plex folder structure
- 🏷️ IMDB/TMDB metadata: Resolves missing years and canonical titles via API lookup (year-aware caching, acronym-aware matching)
- ❓ Bare title resolution: Titles without a year or season tag are automatically resolved via TMDB — movies and TV shows correctly identified and categorized
- 💾 Persistent move logs: JSONL history at
~/.config/avatarr/move_log.jsonl— survives restarts, easy to grep - 🎁 Extras collection: Extras/bonus folders in TV packs automatically moved to Show/Featurettes/
- 🎥 Movie extraction: Movie files found in TV packs processed as regular movies
- 🌐 Cross-platform: Linux, Windows, macOS
- ⌨️ CLI-first: Full-featured command-line interface (Slint desktop GUI rebuilds at m71)
- 🚀 First-run setup: Auto-generates config template on first launch
- 📄 Batch mode: Process list files with movies, TV seasons, season ranges, and discover mode
list.txt ──> Search ──> Score & Rank ──> Download ──> Monitor ──> Organize ──> Plex
(TPB/YTS/EZTV/RuTracker) (auto-pick) (qBittorrent) (rename/move)
📦 Install
From source
Prerequisites:
- Rust toolchain (1.75+)
- A C compiler (gcc/clang/MSVC) — required by some dependencies
🐧 Linux
# Debian/Ubuntu
sudo apt install build-essential pkg-config libssl-dev
# Arch/CachyOS
sudo pacman -S base-devel openssl
git clone https://codeberg.org/alan090/avatarr.git
cd avatarr
cargo build --release
# Binary: target/release/avatarr
🪟 Windows
# Requires Visual Studio Build Tools with "Desktop development with C++"
# Install Rust: https://rustup.rs/ (use default MSVC toolchain)
git clone https://codeberg.org/alan090/avatarr.git
cd avatarr
cargo build --release
# Binary: target\release\avatarr.exe
🍎 macOS
xcode-select --install
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
git clone https://codeberg.org/alan090/avatarr.git
cd avatarr
cargo build --release
# Binary: target/release/avatarr
Note: macOS builds are unsigned. Right-click and choose Open to bypass Gatekeeper on first launch.
Migrating from plex-pipeline-rs
If you previously ran plex-pipeline-rs, the v0.40.0 binary auto-migrates your ~/.config/plex-pipeline/ directory to ~/.config/avatarr/ on first launch. No manual action required. The migration is idempotent (it only fires when the new directory does not yet exist) and falls back to a recursive copy on EXDEV if your config and home directories live on different filesystems. See the v0.40.0 changelog entry for the full migration notes, including how to roll back.
🕹️ Usage
# Search
avatarr search "Big Adventure 1985" # Single movie
avatarr search "Sunset Valley S01" # TV season
avatarr search "Echo Company S05E14" # Single episode
avatarr search "Echo Company" # Complete series
avatarr search "Night Runner" # Bare title (TMDB resolves)
avatarr search --file list.txt --auto # Batch from file
avatarr search --file list.txt --auto --dry-run # Preview only
avatarr search "Night Runner 2003" --quality 1080p # Quality filter
# Monitor
avatarr monitor # Continuous (polls every 30s)
avatarr monitor --once # Process completed and exit
avatarr monitor --dry-run # Preview moves
avatarr monitor --test-resolve # Test metadata resolver
# Movie library
avatarr movie search "The Matrix" # Search TMDB
avatarr movie search "The Matrix" --year 1999 # Filter by year
avatarr movie search "inception" --json # JSON output
avatarr movie add --tmdb-id 603 --path /media/Movies/Matrix --monitored
avatarr movie add --tmdb-id 603 --path /media/Movies/Matrix --minimum-availability in-cinemas
avatarr movie list # List all movies with monitor flag
avatarr movie show --tmdb-id 603 # Show one movie row
avatarr movie monitor --tmdb-id 603 # Mark monitored
avatarr movie unmonitor --tmdb-id 603 # Mark unmonitored
# Series monitor state machine
avatarr series list # List all series
avatarr series show --tvdb-id 12345 # Show series + per-season subtable
avatarr series monitor --tvdb-id 12345 # Toggle master Monitored on
avatarr series unmonitor --tvdb-id 12345 # Toggle master Monitored off
avatarr series apply-monitor --tvdb-id 12345 --strategy pilot # Cascade strategy through seasons + episodes
avatarr series apply-monitor --tvdb-id 12345 --strategy missing --dry-run
avatarr series set-monitor-new-items --tvdb-id 12345 --mode all # Auto-monitor newly imported items
# Season + episode monitor toggles
avatarr season monitor --tvdb-id 12345 --season 1
avatarr season unmonitor --tvdb-id 12345 --season 1
avatarr episode monitor --tvdb-id 12345 --season 1 --range 1-5
avatarr episode monitor --tvdb-id 12345 --season 1 --range 1,3,5,7-9
avatarr episode unmonitor --tvdb-id 12345 --season 1 --range 1-3,7,10-12 --dry-run
# Quality profiles
avatarr quality list # List quality definitions
avatarr quality list --json # JSON output
avatarr profile list # List profiles
avatarr profile show Any # Show profile detail + items
avatarr profile show Any --json # JSON output
avatarr profile create --clone Any --name "HD Only" # Clone a profile
avatarr profile set "HD Only" --cutoff Bluray-1080p # Edit profile settings
avatarr profile items add "HD Only" "HDTV-2160p" # Add quality to profile
avatarr profile items remove "HD Only" "SDTV" # Remove quality
avatarr profile items reorder "HD Only" "Bluray-1080p" --position 0
avatarr profile delete "HD Only" --force # Delete profile
# Debug
avatarr --test-parse # Test torrent name parsers
📄 Input file format
# Movies — title with year
Big Adventure (1985)
Night Runner 2003
# TV — show name with S## tag
Sunset Valley S01
Echo Company S01-S03
The Drift S01+
# TV — individual episode
Sunset Valley S05E14
# TV — complete series pack (no season specified)
The Drift
# Bare title — resolved via TMDB (movie or TV detected automatically)
Big Adventure
⚙️ Configuration
Config file: ~/.config/avatarr/config.toml (auto-generated on first run)
[qbittorrent]
url = "http://localhost:8080"
username = ""
password = ""
[paths]
staging_dir = "/tmp/staging"
movie_dir = "/media/Movies"
uhd_movie_dir = "/media/UHD Movies"
tv_dir = "/media/TV Shows"
[sources.tpb]
enabled = true
api_base = "https://apibay.org"
request_delay_ms = 2000
min_seeders = 1
[sources.yts]
enabled = true
[sources.eztv]
enabled = true
[sources.rutracker]
enabled = false
[monitor]
poll_interval_secs = 30
[tmdb]
api_key = ""
🏷️ Metadata resolution
When a torrent name lacks the year (common for TV shows), the monitor resolves the canonical title and year automatically. This ensures Plex-standard folder naming:
Movie Name (Year)/Movie Name (Year).ext
Show Name (Year)/Season N/Show Name (Year) - S01E01.ext
Resolution chain (first match wins):
- 🎞️ IMDB Suggestion API — free, no key needed
- 🎥 TMDB Search API — year-aware filtering (
primary_release_yearfor movies,first_air_date_yearfor TV); addapi_keyunder[tmdb]in config - 📝 Torrent name — if the year is already present
- 📁 Existing library folder — parsed name matched against disk, with automatic folder rename when the resolver provides a corrected canonical title
Cache keys include title, media type, and year — same-title entries from different years are resolved independently.
💾 Database
Avatarr uses SQLite via sqlx. The database file lives at:
- Linux:
~/.local/share/avatarr/avatarr.db - macOS:
~/Library/Application Support/avatarr/avatarr.db - Windows:
%LOCALAPPDATA%\avatarr\avatarr.db(non-roaming — avoids profile-sync thrash on growing SQLite files)
Override with the AVATARR_DATA_DIR environment variable for isolated dev/test installs:
AVATARR_DATA_DIR=/tmp/avatarr-dev avatarr
Schema lives in crates/core/migrations/*.sql. The pool opens with PRAGMA foreign_keys=ON, WAL journal mode, and busy_timeout=5s; migrations run automatically at CLI/GUI startup with exponential-backoff retry on transient busy errors. Failure to open is fatal with a per-variant error message (stderr for CLI; stderr + ~/.cache/avatarr/startup-error.log for GUI) and exit code 2.
Regenerating sqlx offline query cache (contributors)
After modifying a sqlx::query! macro:
cargo install sqlx-cli --no-default-features --features native-tls,sqlite
export DATABASE_URL="sqlite:///tmp/avatarr-prepare.db"
cargo sqlx database create
cargo sqlx migrate run --source crates/core/migrations
cargo sqlx prepare --workspace
Commit the resulting .sqlx/query-*.json files. CI runs with SQLX_OFFLINE=true — no DATABASE_URL required at build time.
🔧 Troubleshooting
| Issue | Solution |
|---|---|
| qBittorrent connection failed | Check URL in Settings (default: http://localhost:8080), ensure Web UI is enabled |
| No results for specific quality | Switch to "Any" — specific quality does hard filtering |
| Config not found | Run the app once to auto-generate (it lives at ~/.config/avatarr/config.toml) |
| Permission denied moving files | Check that configured paths exist and are writable |
First launch left both ~/.config/plex-pipeline/ and ~/.config/avatarr/ |
Should never happen — the migration is idempotent and uses fs::rename. If it does, the new dir wins; you can manually rm -rf ~/.config/plex-pipeline once you've confirmed the new dir is intact |
🏗️ Architecture
Cargo workspace with four crates:
| Crate | Purpose |
|---|---|
avatarr-core |
Library: parsers, search engine, scoring, monitor, metadata, config, move log |
avatarr-cli |
CLI binary (avatarr) via clap |
avatarr-parser |
Sonarr-ported parser library (episode/quality/language/release-group) |
avatarr-parser-baseline |
Fixture test harness for parser accuracy measurement |
Tech stack today (v0.63.0): Rust 2021, Tokio async runtime, reqwest for HTTP, regex for parsers, clap for CLI, TOML for config, sqlx 0.8 against SQLite for the catalog and move log, wiremock for API client integration tests. The schema (crates/core/migrations/, 4 migrations, 49 tables) is stable since m43; domain models, repository layer, TVDB/TMDB API clients, and the per-entity monitor state machine are complete through m63. See the roadmap for the full migration plan.
🤝 Heritage and attribution
Avatarr will port code from Sonarr and Radarr starting in m44 (parser port phase 1). Both upstream projects are GPL-3.0; Avatarr is GPL-3.0-or-later, so the port is legally clean. Each ported file gets a header comment crediting the source commit, and THANKS.md is the canonical attribution list (currently a placeholder until m44 lands).
This project is also a Rust rewrite of the original Python plex-pipeline (archived). The Rust port is ~484× faster startup, ~30× faster parsing, ~5.6× less memory, and ~7.7× smaller binary than the PyQt6 original.
📜 Changelog
See CHANGELOG.md for the full release history. The most recent release is v0.63.0 (m63 monitor / unmonitor state machine).
⚖️ License
GPL-3.0-or-later — see LICENSE. This is required for the m44 parser port to be legally clean against Sonarr/Radarr's GPL-3.0; the license is locked and will not change.
Dependencies
~30–58MB
~834K SLoC