- Rust 75.8%
- TypeScript 13.7%
- Shell 5.4%
- Svelte 1.8%
- Python 1.1%
- Other 2.2%
The owner's real-device matrix (docs/compat.md) showed WebM playing on every platform, so ADR-0003 stands, and turned up player issues: - a media error minutes into a WebM (network cut between tracks with the screen off) flagged the device as unable to play WebM for good; the fallback now applies only to a source that never produced audio, and transient errors are retried with backoff at the live position. Pause, restart, next at the end of the list and a list change cancel a pending retry; every resume path reloads an errored element. The flag's storage key changed so devices flagged by the old rule recover on their own; - iOS lock screen: absolute artwork URLs (512 and 128 px) so covers show; seekbackward/seekforward are not registered on Apple engines, which otherwise replace previous/next with 10-second skips; - the volume slider is hidden where the engine ignores volume (iOS); - the catalogue request retries with backoff and offers a Retry button; the service worker waits 8 s instead of 3 s before falling back; - `scoutify status` names every open issue and every quarantined or failed job with its error; encoder stderr is sanitised at capture and every printed string again at print time; the client accepts only root-relative media and artwork URLs. Docs record the pass, the open mobile items (Android screen-off across a track change, iOS 26 silent resume, no next track while locked) and the corrected §13 claim about play() from the ended handler on iOS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|---|---|---|
| .cargo | ||
| .forgejo/workflows | ||
| assets | ||
| crates | ||
| deploy | ||
| docs | ||
| frontend | ||
| scripts | ||
| .editorconfig | ||
| .flake8 | ||
| .gitattributes | ||
| .gitignore | ||
| .isort.cfg | ||
| .nvmrc | ||
| .pylintrc | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| clippy.toml | ||
| deny.toml | ||
| Justfile | ||
| LICENSE | ||
| openapi.json | ||
| README.md | ||
| rust-toolchain.toml | ||

Scoutify
Self-hosted music streaming: one static Rust binary on your Linux server, one installable web app for desktop and mobile.
Drop a <name>.wav + <name>.mp3 pair into the library directory and, within a
minute, the track is playable in the app with its title, artist, year, genre,
cover and lyrics taken from the MP3's ID3v2 tag. The WAV master is encoded once
into streaming renditions (Opus in WebM, FLAC) and offered for download as WAV,
FLAC, Opus or the original MP3.
Features
- Home / catalogue — every track, searchable and sortable, with cover art.
- Player — persistent playback with lock-screen controls, seeking, quality selection (data saver / high / lossless), loudness-aware volume.
- Metadata on demand — cover, artist, year, genre and lyrics are each toggleable; lyrics are hidden by default and highlighted in sync when timing data exists (ID3 SYLT, LRC, sidecar files, or an owner-made sync).
- Profiles without registration — an optional profile is identified by a random access key; no e-mail, no password. Save the key, log in on any device, keep and share playlists.
- Downloads — WAV (uncompressed), FLAC (lossless), Opus (small, tagged, with cover) and the original MP3, all resumable.
How it is built
| Part | Choice |
|---|---|
| Backend | Rust 1.96, axum, tokio, sqlx (SQLite, WAL), lofty (ID3v2), blake3 |
| Frontend | SvelteKit 2 / Svelte 5 static SPA, TypeScript 6, vite-plugin-pwa |
| Media | ffmpeg/ffprobe 6.1 (libopus), flac 1.4 — invoked as child processes; opus-tools optional |
| Serving | one static musl binary embedding the SPA, behind nginx for TLS |
| Storage | /srv/music (your masters, read-only) and /var/lib/scoutify (database + content-addressed objects) |
The full decision record, including the audio format matrix, the access-key identity design, the data model and the HTTP API, is in docs/ARCHITECTURE.md. The phased plan with exit criteria is in docs/ROADMAP.md.
Status
Pre-alpha. Phase 1 (vertical slice) is code complete: a WAV + MP3 pair copied
into the library is scanned, tagged, encoded to Opus/WebM and playable in the
web app within a minute; the catalogue and media APIs, the player bar and the
probe/status/retry commands exist; the full quality gate and the
Playwright suite pass. Still open before Phase 2: the real-device matrix in
docs/compat.md and a corpus report over the owner's library
(see docs/ROADMAP.md).
Development
just verify-toolchain # rustup, musl-tools, Node 24 (nvm), pnpm, ffmpeg (libopus)/flac/sqlite3
just build && just run # backend on http://127.0.0.1:8080 serving testdata/library
just dev # frontend with hot reload on http://localhost:5173
just test && just e2e # unit/integration tests, then Playwright against the real binary
just qa # the full gate (scripts/qa.sh): what CI and the pre-commit rule run
just lists every recipe. Configuration is environment variables only; the
defaults just run uses are in the Justfile, the full list in
deploy/scoutify.env.example.
Requirements
- Server: Linux with systemd (Rocky/Alma/RHEL 9 or Debian 12/Ubuntu 24.04 are
documented in docs/runbook.md),
ffmpegwithlibopus,flac,sqlite3,zstd, nginx for TLS;opus-toolsoptional. - Development: Rust 1.96 with the
x86_64-unknown-linux-musltarget andmusl-tools, Node 24 LTS (nvm), pnpm 10,just, the same media tools as the server.
Contributing
Read CLAUDE.md for the quality gate: every commit must pass
scripts/qa.sh (zero warnings from cargo clippy with pedantic and nursery
lints, eslint --max-warnings=0, svelte-check, shellcheck) plus a code
review and a security audit, and must update CHANGELOG.md.
Licence
MIT, see LICENSE.