Roadmap¶
What's open, organized by what it would feel like.
Tier 1 — listening experience wins¶
- Lyrics via LRCLIB — read embedded
\xa9lyr(MP4) /USLT(ID3); fetch from lrclib.net for missing ones (free, no API key, supports synced LRC). Cache per-track as<track>.lrcsidecar. Server'sgetLyrics/getLyricsBySongIdare wired but only return embedded text; remote-fetch + sidecar cache is still open. Symfonium + Amperfy display synced lyrics live. - Album cover art in the TUI — small thumbnail in the now-playing header. Tried once with
textual-image(Unicode halfblock fallback was too low-res); achafasubprocess approach would give crisp output but adds abrew install chafasystem dep. Deferred until the cost/benefit is clearer.
Tier 2 — bigger directions (~3-5 sessions)¶
- Web UI — small Vue/htmx frontend mounted at
/(replacing the JSON probe response). Same Subsonic backend; lets you play from any browser without installing an app. - Podcast support — Subsonic spec already defines
getPodcasts/getPodcastEpisode/createPodcastChannel. Add an RSS feed list, fetch episodes on schedule, store position. Symfonium has decent podcast UX out of the box. - iTunes / Apple Music import — read the local Apple Music database to import play counts, ratings, and playlists. One-shot migration tool.
Tier 3 — server hardening / scale¶
- Recorded-session integration tests — VCR-style HTTP fixtures from real Symfonium / Amperfy / Feishin sessions; replay against
TestClient. Catches "client X probes new endpoint" regressions. - systemd / launchd plist — for users wanting
serveto autostart on boot. - MQTT / webhook scrobble forwarding — push play events to Home Assistant / Last.fm / external systems.
- Bigger
getCoverArtcache — currently it's recomputed per request; an LRU keyed on(album_id, size)would help on slow disks. - TUI
/-bar filter on top of FTS5 —/search3is FTS5-backed (see Done); the TUI's incremental/filter is still in-memory casefold substring. Wiring it through the same in-memory FTS5 index would give ranked / prefix-matching results in the TUI.
Tier 4 — convert-pipeline polish¶
- Better folder-fallback for live-venue parens — currently
(Live in Madrid)is preserved (correctly), butSome Album (Live, 2003-04-15, Madrid)could be cleaned up while still preserving the live-ness signal. - AcoustID auto-enable — currently you have to pass
--acoustid-keyper run. Read it from~/.config/musickit/serve.tomlonce and apply automatically when an album has tagless tracks. - Album merge tool — when the same album exists with different tags as two folders, an interactive merge.
--dry-runwith rich diff — show exactly what tags would change, what files would move.- In-TUI rename when album/artist tags change — the tag editor patches tags in place but doesn't move the on-disk folder.
retag --renamedoes this from the CLI; the TUI editor could too.
Tier 5 — speculative¶
Things that would be interesting if anyone ever asked for them, but not pursued speculatively:
- BPM / key analysis (needs
librosa, big dep weight) - AI-generated playlists with audio-feature similarity (current
musickit playlist genis tag-based; an audio-feature pass would need fingerprinting /librosa) - Multi-user serve (right now: single-user)
- Sonos / Chromecast / DLNA output (AirPlay covers the Apple-ecosystem case)
- Cross-fade between tracks
- Listening rooms / sync-play across clients
- Voice control
Done¶
- Convert pipeline — covered in Convert
- Library audit + fix — covered in Library
- TUI: local + radio modes
- TUI: Subsonic-client mode (lazy-loaded)
- TUI: AirPlay output (CLI + in-TUI picker, persists across launches; pause + volume routed to the device)
- TUI: in-place tag editor —
eopens a track or album-wide editor that writes via mutagen + patches the in-memory model - TUI:
/incremental filter on the focused pane - TUI: single-click cursor / double-click play (Spotify-style); cursor snaps back to playing track on focus loss
- TUI: ReplayGain normalisation on local playback
- TUI: bordered-panel UI polish (boxed sections, now-playing card, warm accent for active playback)
- TUI: dynamic title-column width that re-flows on terminal resize (debounced)
- TUI: audio engine in a separate process — PyAV decoder + sounddevice callback live in their own interpreter, communicate via
multiprocessing.Queue+ shared memory. UI reflows / focus changes / GC pauses can no longer stall the audio callback into a buffer underrun. Replaces the prior GIL-contention mitigations (500ms / 1s PortAudio buffer, resize-debounce, focus-change short-circuit), now that the engine has its own GIL. - Subsonic-compatible serve (~30 endpoints)
- XML response format (Subsonic spec default) + POST + form-body credentials
- ffmpeg-on-the-fly transcoding (
format=mp3/maxBitRate) - OpenSubsonic extensions advertised:
formPost,transcodeOffset,multipleGenres,songLyrics - Multi-genre indexing —
byGenreandgetGenreshonourtrack.genres[], not just the legacy single-genre field - Per-track recording MBIDs (one MB
release/<mbid>?inc=recordingslookup at enrich time) - mDNS / Bonjour autodiscovery (server advertises, TUI auto-detects)
- Filesystem watcher with debounced auto-rescan
- Persistent SQLite library index —
<root>/.musickit/index.dbremoves the cold-start filesystem walk + tag read;library.load_or_scanhydrates from rows then runs a delta-validate pass to pick up filesystem changes (added / removed / tag-edited albums);musickit library index status|drop|rebuildfor management - Genre indexing (model + scan +
getGenres+byGenre) - cover-pick semi-automated workflow
- Folder-name edition-annotation strip
- Continuous-numbering across-discs detection
- Real-world tested against Amperfy / Symfonium / play:Sub / Feishin
- Push to GitHub + CI (
make check && make teston push/PR) - This documentation site (mkdocs + Material)
musickit playlist gen|list|show— tag-based auto-generated.m3u8mixes anchored to a seed track (similarity scorer over artist / genre / year / compilation flag, per-album + per-artist caps; output is standard extended M3U)- TUI
gkeybind — generate-and-play a 60-min mix from the highlighted or currently-playing track - TUI Mixes browser entry — saved
.m3u8files appear in the right pane; selecting one resolves paths against the live index and plays it as a virtual album with stale-path graceful degradation - Persistent stars / favourites — Subsonic clients' heart buttons are now real.
/star,/unstar,/getStarred,/getStarred2backed by<root>/.musickit/stars.toml(TOML, hand-editable, survives index rebuilds). - FTS5 search backend —
/search3and/search2use an in-memory SQLite FTS5 index built fresh on every cache reindex. Sub-ms ranked results with prefix matching (beymatchesBeyoncé), diacritic folding (unicode61 remove_diacritics 2), bm25 ordering, and multi-token AND across title + album_artist + year body text. Falls back to casefolded substring scan when SQLite was compiled without FTS5.