A fast, animated terminal player for Navidrome and any Subsonic server.
Real cover art in the terminal · playback through mpv · offline downloads · karaoke lyrics · endless radio · a control API for scripts and AI agents · five live-previewed themes — and everything moves.
- Why NaviTui
- Screenshots
- Features
- Install
- First run
- Keybindings
- Configuration
- Automation & the control API
- How it works
- Troubleshooting
- Development
- The suite
- License
- Acknowledgements
If you self-host your music on Navidrome (or any Subsonic-compatible server) and you live in a terminal, your options are usually a bare-bones TUI or a full desktop GUI. NaviTui aims for the sweet spot: a player that's as quick and keyboard-driven as a TUI should be, but as rich and good-looking as a GUI — real album art, fluid animation, offline downloads, and an automation surface none of the others have.
It is built for one workflow above all: just play my songs. No album grid to click through, no "now browse this artist's discography" ceremony. One sidebar of ways to list tracks, one big track list, press enter. Albums and artists still exist — they live inside search, where you go looking for them on purpose.
What sets it apart
- Cover art as actual pixels — kitty graphics / sixel where your terminal supports it, gracefully degrading to truecolor half-cells and then unicode.
- Offline-first, done properly — pin a track, a playlist, or your whole library to disk; an offline mode that skips the network entirely; and an offline mutation queue so the stars and scrobbles you make on a plane sync when you land.
- A control API + MCP server — drive a running player from a shell script, a global hotkey, or an AI agent. Nothing else in this space ships that.
- It feels alive — a shimmering wordmark, a visualizer that reacts to real playback loudness, a sub-cell progress bar that breathes — all from a single 8fps heartbeat that repaints only a handful of cells, so it stays cheap.
Cross-platform: playback works on Linux, macOS, and Windows (anywhere
libmpv runs). Native now-playing rides the OS integration on each — MPRIS2
on Linux, MPNowPlayingInfoCenter / Control Center on macOS, and the System
Media Transport Controls on Windows — so track, art, and progress show up
wherever your OS puts them. (Metadata display is solid across all three;
media-key delivery on macOS/Windows is new and still wants on-target testing.)
Search — everything, grouped, instantly
void theme — OLED black, art glowing
Synced, scrolling lyrics
- One sidebar: all tracks, recently added, recently played, most
played, starred, shuffle everything — your playlists right under
it, plus genres (
y), podcasts, and internet radio. - Albums and artists live inside search (
/), where they belong; jump from any track to its album (e) or artist (E). - Type-to-filter (
\) narrows the current list as you type — no network round-trip, instant. - Multi-select (
v) — tag rows, then queue, playlist-add, star, or download the whole selection at once.
- A queue that shows what's up next — played tracks dim above the line;
scroll up for history. Add (
a), play-next (A), remove (x), clear (X), reorder (ctrl+↑/↓). - Shuffle that reshuffles the up-next and starts a fresh view on a random track; repeat off / all / one.
- Sleep timer (
<) and playback speed (>) — great for podcasts and audiobooks; crossfade with next-track prefetch for seamless transitions. - The queue — including your position inside the current song — survives a restart.
- Download a track (
d), a whole view or playlist (D), or your entire library (ctrl+d). Pinned tracks get a ✓ and play locally forever. - Offline mode (
O) plays only what's downloaded and skips the network. - Stars / ratings / scrobbles made offline are queued and flushed on reconnect.
- A streaming bitrate cap (
Q) for tight connections — downloads always keep the original file.
- Timed, scrolling, highlighted lyrics (
L) that ride the beat — and the same view in zen mode (z): a big centred cover-and-lyrics splash.
- Endless radio — start a station from any track or artist (
i), or let the queue autoplay similar tracks forever when it drains (I). - Bookmarks (
w/W) to save and jump back to a spot in long tracks. - Listening stats (
ctrl+w) — a local mini-wrapped: top tracks & artists, this-week counts, an activity sparkline, streaks.
- Native now-playing on every platform: MPRIS2 (Linux — media keys, waybar,
playerctl), Control Center / lock screen (macOS), System Media Transport
Controls (Windows). Install
navitui[macos]ornavitui[windows]for those. - Desktop notifications on track change (cover as the icon) with prev / play-pause / next action buttons.
- Optional Discord rich presence with a live progress bar.
- Optional ListenBrainz scrobbling, alongside your server's own scrobble.
- A local control API over a unix socket, so other tools can drive a running
player. It powers:
navitui-remote— a scriptable CLI (navitui-remote status,next,play "<query>", …), perfect for global hotkeys.navitui-mcp— an MCP server so Claude and other agents can control playback, search, download, and read what's now playing.
- Jukebox mode (
J) — drive the server's own audio output, for a headless box wired to real speakers.
- Create, add (
p), remove (P), reorder (shift+↑/↓), rename (ctrl+r), delete (ctrl+x) — or save the current queue as a playlist (ctrl+s).
- The wordmark shimmers, the visualizer reacts to real playback loudness, the progress bar has 1/8-cell resolution and breathes, long titles marquee, panels fade in — all from one 8fps heartbeat.
- Album-art theming tints the UI toward the current cover's colors.
- Five themes, live-previewed (
tcycles,Tpicks) — includingclear(your terminal's transparency shows through) andsystem(your terminal's own ANSI palette). - Cache-first — every pane renders instantly from disk, then refreshes silently in the background (auto every 3 minutes).
- Scrobbles, stars & ratings — star (
f), rate 1–5 on the number row. - Command palette (
ctrl+p) — every action, fuzzy-searchable. - Export the now-playing card as an SVG (
C); copy a share link (S). - Vim repeat counts (
3jmoves down three) and full mouse support — click the progress bar to seek, drag the panel dividers, click shuffle/repeat.
You need libmpv for playback (everything else ships with the package):
# arch
sudo pacman -S mpv
# debian / ubuntu
sudo apt install libmpv2
# fedora
sudo dnf install mpv-libs
# macos
brew install mpv
# windows — put libmpv-2.dll on your PATH: https://mpv.io/installation/Then install NaviTui. uv is the easy path:
uv tool install "navitui[integrations] @ git+https://github.com/Gheat1/NaviTui"
navituior with pip:
pip install "navitui[integrations] @ git+https://github.com/Gheat1/NaviTui"Optional extras
| extra | pulls in | for |
|---|---|---|
integrations |
dbus-fast, pypresence |
MPRIS2 media keys (Linux), Discord presence |
macos |
pyobjc-framework-MediaPlayer, pyobjc-framework-Cocoa |
Control Center now-playing (macOS) |
windows |
winrt-runtime, winrt-Windows.* |
System Media Transport Controls (Windows) |
mcp |
mcp |
the navitui-mcp agent server |
Combine them: navitui[integrations,mcp]. Drop the extras entirely for the bare
player — every integration degrades to a clean no-op when its dependency is
absent, so nothing breaks.
On first launch NaviTui asks for your server URL, username, and password,
validates them live, and stores a salted token (never the password itself)
in ~/.config/navitui/, chmod 600. It also drops a commented
player.toml next to it for you to tweak later.
Works with Navidrome and any Subsonic / OpenSubsonic-compatible server (Gonic, Airsonic, …). No server of your own yet? Try it against the public demo:
server: https://demo.navidrome.org
username: demo
password: demo
Press ? in-app for a live cheatsheet, or ctrl+p to fuzzy-search every action
by name. Every key here is remappable — see Configuration.
Lists are vim-navigable (j / k / g / G) and accept repeat counts
(5j).
Playback & transport
| key | action | key | action |
|---|---|---|---|
space |
play / pause | s |
shuffle |
n / b |
next / previous | r |
repeat (off → all → one) |
← / → |
seek 5s | m |
mute |
shift+←/→ |
seek 30s | > |
playback speed |
- / + |
volume down / up | < |
sleep timer |
Queue
| key | action | key | action |
|---|---|---|---|
a |
add to queue | ctrl+↑ / ctrl+↓ |
move track up / down |
A |
play next | X |
clear queue |
x |
remove track | ctrl+s |
save queue as playlist |
Library & navigation
| key | action | key | action |
|---|---|---|---|
/ |
search everything | e / E |
go to album / artist |
\ |
filter current list | y |
browse by genre |
v |
multi-select mode | R |
refresh from server |
h / l |
previous / next panel | j/k/g/G |
move in lists |
Playlists
| key | action | key | action |
|---|---|---|---|
p |
add to a playlist | shift+↑/↓ |
reorder in playlist |
P |
remove from playlist | ctrl+r |
rename playlist |
ctrl+s |
save queue as playlist | ctrl+x |
delete playlist |
Offline & quality
| key | action | key | action |
|---|---|---|---|
d |
download track | O |
offline mode |
D |
download view / playlist | Q |
cycle streaming quality |
ctrl+d |
download whole library | J |
jukebox (server audio) mode |
Discovery & now-playing
| key | action | key | action |
|---|---|---|---|
i |
start radio from here | L |
lyrics |
I |
endless-autoplay toggle | z |
zen splash |
w / W |
set / list bookmarks | ctrl+w |
listening stats |
f |
star / unstar | 1–5 |
rate (same digit clears) |
S |
copy share link | C |
export now-playing card (SVG) |
App
| key | action | key | action |
|---|---|---|---|
t / T |
cycle / pick theme | N |
toggle notifications |
ctrl+p |
command palette | ? |
help |
R |
refresh | q |
quit |
NaviTui writes a fully-commented ~/.config/navitui/player.toml on first run.
Edit it and restart to apply. Everything is optional and safe to delete —
missing keys fall back to defaults, and a malformed file is ignored rather than
fatal.
# ── playback ──────────────────────────────────────────────
replaygain = "album" # "album" | "track" | "no"
gapless = "weak" # "yes" | "weak" (gapless when formats match) | "no"
crossfade = 0.0 # seconds of soft fade on track change (0 = off)
max_bitrate = 0 # streaming cap in kbps (0 = original / unlimited)
stream_format = "" # transcode target: "mp3" | "opus" | "raw" | "" original
# ── desktop & scrobbling ─────────────────────────────────
notifications = true # desktop notification on track change
art_theming = true # tint the UI toward the cover's colors
discord_rich_presence = false
discord_app_id = "" # from discord.com/developers/applications
listenbrainz_token = "" # from listenbrainz.org/profile
# ── control & server audio ───────────────────────────────
remote_control = true # local control API (unix socket) for the CLI & MCP
remote_token = "" # optional shared secret (required on the TCP fallback)
jukebox = false # play on the SERVER's audio output, not this machine
# ── remap any key ────────────────────────────────────────
[keybinds]
# action_id = "key" — comma-separate aliases, e.g. "plus,equals_sign"
next_track = "ctrl+n"
download = "d"
# …every action listed by `?` can be rebound herereplaygain/gapless/crossfadeare handed straight to mpv, so they behave exactly as mpv's own options do.max_bitrate/stream_formatapply only to network streams — offline downloads always keep the original file. Cycle presets at runtime withQ.remote_controlexposes the socket the CLI and MCP server talk to; it is localhost / socket only and never leaves your machine. Setremote_tokento require a shared secret.
A running NaviTui exposes a small local control API over a unix socket, so you can drive it from anywhere on the same machine.
From the shell (great for global hotkeys):
navitui-remote status # what's playing, as JSON
navitui-remote play-pause # toggle
navitui-remote next # skip
navitui-remote play "daft punk" # search + play the top hit
navitui-remote volume 80 # set volumeFrom an AI agent — point any MCP client at navitui-mcp:
The agent can then search your library, queue and download tracks, control playback, and report what's now playing. The transport is the same local socket — nothing is exposed off your machine.
Three principles, in order:
- Fast — cache-first everywhere. Each pane renders the last-known state from disk in ~50ms, then a background worker fetches fresh rows and swaps them in silently. Mutations update the cache immediately, so what you see is always what you did — even if you quit before the refresh lands.
- Alive — one shared 8fps heartbeat drives every animation (logo shimmer, visualizer, progress pulse, marquee, spinners). Each tick repaints only a few cells, so constant motion costs almost nothing and never fights your CPU.
- Pretty — rounded borders, a disciplined color palette, and nerd-font icons, all from ricekit, the design system NaviTui shares with the rest of its suite.
Under the hood: Textual for the UI,
python-mpv (libmpv) for playback,
textual-image for terminal-native
cover art, and an async httpx Subsonic client.
Auth is the salted-token scheme — the password is turned into a per-session
token and never written to disk.
"libmpv not found" / no sound. NaviTui still runs (you can browse, search,
queue) but can't play audio. Install mpv for your OS — see Install.
On Windows, libmpv-2.dll must be on your PATH.
Cover art shows as blocks instead of a real image. Your terminal (or a
multiplexer like tmux) isn't advertising a graphics protocol, so NaviTui fell
back to half-cells. Force a protocol with the NAVITUI_ART env var:
NAVITUI_ART=auto # detect the best available (default)
NAVITUI_ART=tgp # force the kitty graphics protocol
NAVITUI_ART=sixel # force sixel
NAVITUI_ART=halfcell # truecolor half-blocks (works nearly everywhere)
NAVITUI_ART=unicode # last-resort unicode
NAVITUI_ART=off # a tasteful placeholder, no imagekitty, WezTerm, Ghostty, and Konsole all render real images; inside tmux you may
need NAVITUI_ART=tgp.
Media keys do nothing. On Linux they ride on MPRIS2 — install the
integrations extra (dbus-fast) and be on a session bus; playerctl status
should report NaviTui while it's playing. On macOS install navitui[macos]
(now-playing appears in Control Center); on Windows install navitui[windows]
(the SMTC overlay). Metadata shows reliably on all three — if the hardware media
keys still don't reach NaviTui on macOS/Windows, that path is new and being
hardened, so file an issue with your terminal and OS version.
Nothing loads / it says it's offline. NaviTui shows your last cached library
when it can't reach the server, so you can still play downloaded tracks. Check
the server URL in ~/.config/navitui/, or press R to retry.
A key is bound to something I don't want. Rebind it under [keybinds] in
player.toml and restart — see Configuration.
git clone https://github.com/Gheat1/NaviTui
cd NaviTui
python -m venv .venv && . .venv/bin/activate
pip install -e ".[integrations,mcp]"
navituiTests run headless against a mocked client, or read-only against the public demo
server. Isolate state with HOME=$(mktemp -d) and pass ao="null" so mpv needs
no audio device. README screenshots are generated, never captured from a real
library — tools/shots.sh drives the real app in kitty; tools/screenshots.py
is the headless fallback. See CLAUDE.md for the architecture map
and the hard-won sharp-edges table.
Issues and PRs welcome. NaviTui is GPL-3.0-or-later, so contributions and forks stay open (see License).
- ricekit — the Textual design system NaviTui is built on
- ltui — a fast, beautiful TUI for Linear, where ricekit came from
GPL-3.0-or-later — made by @Gheat1.
Releases up to 0.3.0 were MIT; from 0.4.0 NaviTui is GPL, so derivatives stay open source and keep their notices. (Copies of the earlier MIT releases remain MIT — that can't be retracted — but everything current is copyleft.)
Built on the shoulders of Textual, mpv, Navidrome and the Subsonic API, textual-image, and httpx — with cover-art protocols courtesy of the kitty and sixel graphics standards. Thank you to everyone who self-hosts their music and keeps the ecosystem alive.
If NaviTui makes your library feel like yours again, drop it a ⭐.