No description
  • Rust 75.8%
  • TypeScript 13.7%
  • Shell 5.4%
  • Svelte 1.8%
  • Python 1.1%
  • Other 2.2%
Find a file
Max Fiedler b4c451c1d0
All checks were successful
CI / rust gate (push) Successful in 8m5s
CI / frontend gate (push) Successful in 33s
CI / deploy and docs gate (push) Successful in 6s
CI / end-to-end (push) Successful in 1m24s
Act on the first device pass: fallback, retries, lock screen, status
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>
2026-09-12 00:01:53 +02:00
.cargo Bootstrap Phase 0: workspace, server, PWA shell, gate, deploy, docs 2026-09-09 23:39:16 +02:00
.forgejo/workflows Encode the Opus/WebM stream with ffmpeg libopus; make opusenc optional 2026-09-11 08:56:10 +02:00
assets Bootstrap Phase 0: workspace, server, PWA shell, gate, deploy, docs 2026-09-09 23:39:16 +02:00
crates Act on the first device pass: fallback, retries, lock screen, status 2026-09-12 00:01:53 +02:00
deploy Encode the Opus/WebM stream with ffmpeg libopus; make opusenc optional 2026-09-11 08:56:10 +02:00
docs Act on the first device pass: fallback, retries, lock screen, status 2026-09-12 00:01:53 +02:00
frontend Act on the first device pass: fallback, retries, lock screen, status 2026-09-12 00:01:53 +02:00
scripts Encode the Opus/WebM stream with ffmpeg libopus; make opusenc optional 2026-09-11 08:56:10 +02:00
.editorconfig Bootstrap Phase 0: workspace, server, PWA shell, gate, deploy, docs 2026-09-09 23:39:16 +02:00
.flake8 Implement Phase 1: a dropped WAV+MP3 pair plays end-to-end 2026-09-10 11:21:33 +02:00
.gitattributes Bootstrap Phase 0: workspace, server, PWA shell, gate, deploy, docs 2026-09-09 23:39:16 +02:00
.gitignore Implement Phase 1: a dropped WAV+MP3 pair plays end-to-end 2026-09-10 11:21:33 +02:00
.isort.cfg Implement Phase 1: a dropped WAV+MP3 pair plays end-to-end 2026-09-10 11:21:33 +02:00
.nvmrc Bootstrap Phase 0: workspace, server, PWA shell, gate, deploy, docs 2026-09-09 23:39:16 +02:00
.pylintrc Implement Phase 1: a dropped WAV+MP3 pair plays end-to-end 2026-09-10 11:21:33 +02:00
Cargo.lock Implement Phase 1: a dropped WAV+MP3 pair plays end-to-end 2026-09-10 11:21:33 +02:00
Cargo.toml Bootstrap Phase 0: workspace, server, PWA shell, gate, deploy, docs 2026-09-09 23:39:16 +02:00
CHANGELOG.md Act on the first device pass: fallback, retries, lock screen, status 2026-09-12 00:01:53 +02:00
CLAUDE.md Encode the Opus/WebM stream with ffmpeg libopus; make opusenc optional 2026-09-11 08:56:10 +02:00
clippy.toml Bootstrap Phase 0: workspace, server, PWA shell, gate, deploy, docs 2026-09-09 23:39:16 +02:00
deny.toml Bootstrap Phase 0: workspace, server, PWA shell, gate, deploy, docs 2026-09-09 23:39:16 +02:00
Justfile Implement Phase 1: a dropped WAV+MP3 pair plays end-to-end 2026-09-10 11:21:33 +02:00
LICENSE Bootstrap Phase 0: workspace, server, PWA shell, gate, deploy, docs 2026-09-09 23:39:16 +02:00
openapi.json Encode the Opus/WebM stream with ffmpeg libopus; make opusenc optional 2026-09-11 08:56:10 +02:00
README.md Encode the Opus/WebM stream with ffmpeg libopus; make opusenc optional 2026-09-11 08:56:10 +02:00
rust-toolchain.toml Bootstrap Phase 0: workspace, server, PWA shell, gate, deploy, docs 2026-09-09 23:39:16 +02:00

Scoutify

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), ffmpeg with libopus, flac, sqlite3, zstd, nginx for TLS; opus-tools optional.
  • Development: Rust 1.96 with the x86_64-unknown-linux-musl target and musl-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.