A Spotify client that runs entirely in your terminal. Album art, a real-time cava audio visualizer, full-app color theming, synced lyrics, and playback control — in iTerm2, kitty, or Windows Terminal, without leaving your shell or focusing the Spotify window. Turn your terminal into a disco.
Yes — everything above is a terminal emulator rendering text.
pipx install discoterminalPick a palette and the whole app rethemes — visualizer, borders, buttons, labels:
Eight visualizer styles (area, bars, mirror, peaks, outline, dots, led, rain):
Synced lyrics follow the song, Spotify-style; the card flips between now-playing, artist info, and lyrics:
Built with Textual. Playback control picks the best backend for your platform automatically:
| Platform | Local control | Visualizer audio |
|---|---|---|
| macOS | shpotify (AppleScript) | BlackHole loopback (setup script included) |
| Linux | playerctl (MPRIS) | PulseAudio/PipeWire monitor — works out of the box |
| Windows (experimental) | Spotify Web API | cava's native WASAPI loopback |
The Spotify Web API (OAuth PKCE) powers your library, search, devices, queue,
and is the universal control fallback. Override backend selection with
{"backend": "shpotify" | "playerctl" | "webapi"} in config.
- 🎨 Now playing card with album art (sixel/TGP/halfcell), live progress bar, and click-to-seek
- 📊 Audio visualizer — embedded cava, 8 render styles (area, bars, mirror, peaks, outline, dots, led, rain), 14 color palettes
- 🌈 Whole-app theming — pick a palette and every border, button, and label recolors to match
- 🔀 Card flip — artist info (genres, followers, popularity, top tracks) with artist photo
- 📻 Your playlists in a collapsible sidebar, fetched live from your account
- 🔍 Search anything — tracks, albums, playlists, artists — pick from a results modal
- 🎤 Lyrics via lrclib.net (no API key)
- ♥ Like/unlike the current track, ⏭ queue viewer, 📱 device switcher
- 🕺 Rave mode — beat-detected palette strobes across the whole app, or a fullscreen VJ takeover (flash rate capped at 4/sec)
- ⌨️ Everything keyboard-driven; playback via Web API so the Spotify app never steals focus
macOS:
brew install shpotify cava # local control + visualizer
pipx install discoterminal # or: pip install discoterminal
discoterminalLinux:
sudo apt install playerctl cava # or your distro's equivalent
pipx install discoterminal
discoterminalWindows (experimental — testers wanted):
pip install discoterminal # control goes through the Web API
discoterminal # cava optional; needs a sixel-capable terminalRequires Python 3.11+ and the Spotify desktop app. The Web API features (playlists, search, queue, lyrics card, artist info) need a Premium account — a Feb 2026 Spotify policy requires Premium for Web-API dev-mode apps. Local playback control via shpotify/playerctl works without Premium.
-
Go to the Spotify developer dashboard and create an app.
- If the Web API checkbox is greyed out on the create form (a known dashboard bug): create the app without it, then Edit the app and add Web API there — it won't be greyed out on edit.
-
Add redirect URI:
http://127.0.0.1:8888/callback -
Copy the Client ID into
~/.config/discoterminal/config.json:{ "client_id": "your-client-id-here" }(or
export SPOTIPY_CLIENT_ID=...) -
First launch opens a browser to log in once; the token is cached after. No client secret needed — discoterminal uses the PKCE flow.
Linux and Windows: nothing to do — cava captures system audio natively (PulseAudio/PipeWire monitor, WASAPI loopback).
macOS has no built-in way for apps to hear system output, so cava needs a loopback device:
brew install blackhole-2ch
sudo killall coreaudiod # load the driver
swift scripts/setup-audio.swift # create + activate a Multi-Output deviceThe script builds a "Disco Terminal Multi-Out" device (your speakers + BlackHole) via CoreAudio and switches the system output to it. discoterminal auto-detects BlackHole and points cava at it. Skip all this and the visualizer simply shows a hint instead.
Note: with a Multi-Output device active, macOS volume keys are disabled (aggregate-device limitation). Use discoterminal's
+/-— they control Spotify's own volume.
Turn it off / undo:
swift scripts/setup-audio.swift off # back to speakers (device kept)
swift scripts/setup-audio.swift remove # back to speakers + delete the deviceRe-running without arguments turns it back on. Or just pick any output in System Settings → Sound.
Automatic mode: once the Multi-Out device exists, discoterminal switches to it
on launch and restores your previous output on quit — you only run the
setup script once, ever. Opt out with {"auto_multiout": false} in
config.json.
| Key | Action |
|---|---|
space |
Play / pause |
n / p
|
Next / previous track |
+ / -
|
Volume up / down |
l |
♥ Like / unlike current track |
i |
Flip card: song ↔ artist info |
y |
Lyrics |
u |
Queue viewer |
d |
Device switcher |
/ |
Search (Enter → results picker) |
b |
Toggle playlist sidebar |
v |
Toggle visualizer |
shift+V |
Visualizer style picker |
c |
Color palette / theme picker |
| click progress bar | Seek |
shift+R |
🕺 Rave mode: beat-strobing palettes → fullscreen VJ takeover |
q |
Quit |
Startup arguments: discoterminal next, discoterminal <playlist name>, discoterminal play artist NF — opens the TUI and runs the action.
~/.config/discoterminal/config.json:
| Key | Values | Default |
|---|---|---|
client_id |
Spotify app client ID | — |
visualizer_style |
area bars mirror peaks outline dots led rain
|
area |
visualizer_colors |
aurora synthwave matrix fire ocean mono sunset vaporwave rainbow ice lava candy gold cyberpunk
|
aurora |
art_renderer |
auto sixel tgp halfcell unicode
|
auto |
auto_multiout |
true / false — switch to Multi-Out on launch, restore on quit (macOS) |
true |
backend |
shpotify playerctl webapi — override auto-selection |
auto |
pip install -e ".[dev]"
pytest
ruff check src tests
mypy srcTests run headless with mocked Spotify backends — no account or audio setup needed. CI covers Linux, macOS, and Windows.
MIT













