A terminal music player for YouTube Music, built for keyboard-driven workflows.
Status: Beta. Playback, search, library, history, lyrics, radio, likes, MPRIS, theming, and custom keymaps are functional.
Maintenance: This is a hobby project, developed in bursts. Once it does what I need, it may go quiet for stretches — issues and PRs are still welcome, and reviews are best-effort.
Home view, synthwave theme. All images are generated from a built-in demo
dataset (screenshots/generate.sh) — no real account data.
ytmusic-tui brings your YouTube Music library to the terminal — playlists, search, recommendations, and queue management with vim-style keybindings. Inspired by spotify_player, designed for tiling WM setups.
- Multi-category search — songs, albums, artists, and playlists in a 4-pane
grid, with
#songs:-style category filters - Home view with personalized recommendations
- Playlist browsing — drill into playlists, queue from any track
- 3-pane library — Playlists, Albums, and Artists tabs (Tab to cycle)
- Album and Artist views — dedicated detail pages
- Queue management with shuffle and repeat
- Radio — start a YouTube Music radio from any track (
R) - Likes — like/unlike tracks without leaving the terminal (
f) - Recently played — your listening history as a page (
H) - Lyrics page (
Lkey) - MPRIS2 integration — playerctl / waybar / KDE Connect control and metadata
- Navigation history — Esc goes back through visited pages
- Action popup (
.key) — context actions for the selected track/playlist/album - Filter bar (
/key) — live-filter the current list without leaving the view - Custom keymaps via
~/.config/ytmusic-tui/keymap.toml, including two-key sequences (g sfor search page) - Responsive layout — adapts orientation based on terminal size
- mpv-based audio backend (plays YouTube URLs directly via ytdl-hook)
- Vim-style keybindings (spotify_player-compatible defaults, fully remappable)
- TOML configuration (
~/.config/ytmusic-tui/config.toml) - Theme system with four built-in palettes: synthwave, nord, gruvbox, catppuccin
- Theme switcher (
Tkey) — change themes on the fly - Player bar with progress, volume, and now-playing info
![]() |
![]() |
| Search — songs, albums, artists, playlists | Library — playlists, albums, artists |
![]() |
![]() |
| Queue with current-track marker | Action popup (.) — context actions |
The player bar (now playing, progress, shuffle/repeat, volume) is visible at the bottom of every view.
- libmpv2 — the shared library
libmpv.so.2must be on the system library path.- Arch Linux:
sudo pacman -S mpv(includeslibmpv.so.2) - Debian/Ubuntu:
sudo apt install libmpv2
- Arch Linux:
- yt-dlp — mpv's ytdl-hook uses it to resolve YouTube stream URLs. Keep it up to date: YouTube rotates its JavaScript challenge periodically, and an outdated yt-dlp silently fails to play anything (keep at least version 2026.6.9 or newer).
# Install system dependencies (Arch Linux)
sudo pacman -S mpv yt-dlp
# Install from crates.io
cargo install ytmusic-tuiOr build from source:
git clone https://github.com/WakaTaira/ytmusic-tui.git
cd ytmusic-tui
cargo build --release
cp target/release/ytmusic-tui ~/.local/bin/Authentication is cookie-based — ytmusic-tui reads cookies from your browser
and writes them to ~/.config/ytmusic-tui/browser.json.
Easiest: auto-extract from your signed-in browser
ytmusic-tui auth --from-browser firefoxSupported browsers (via rookie):
firefox, librewolf, chrome, chromium, brave, edge, vivaldi,
opera, opera_gx, arc, safari (macOS only).
You must already be signed in to music.youtube.com
in that browser. The command validates the extracted session against the live
API before writing browser.json, so a failed extraction never clobbers
an existing file.
Zen Browser: rookie 0.5.6 does not detect Zen's cookie store yet — Zen keeps cookies at
~/.config/zen/<profile>/cookies.sqliterather than Firefox's path. Use the manual paste fallback below until issue #25 ships a Zen-aware loader.
Fallback: paste request headers
ytmusic-tui auth- Open music.youtube.com and sign in.
- Open DevTools → Network → reload the page → pick any
browserequest. - Right-click the request → Copy → Copy as cURL.
- Paste the headers when prompted.
The command parses the headers (case-insensitive, CRLF-safe), validates that
SAPISID is present, and writes ~/.config/ytmusic-tui/browser.json at
mode 0600.
Cookies expire after a while. If your library suddenly shows up empty, re-run either command above.
OAuth? Broken upstream (ytmusicapi issue #813). Browser cookie auth is the only working method right now.
Legacy: the previous
ytmusicapi browserPython flow still works (thebrowser.jsonformat is identical), but is no longer required — you don't need Python in the install path anymore.
ytmusic-tuiIf a browser.json is missing or invalid, the player still starts and shows a
session warning on the status line. Use the auth commands above to create one,
then restart.
| Key | Action |
|---|---|
j / k (or ↓ / ↑) |
Navigate rows down / up |
Enter |
Play / Select |
Space |
Play / Pause |
n / p |
Next / Previous |
> / < |
Seek +5 s / -5 s |
^ |
Seek to start |
_ |
Mute toggle |
b |
Cycle audio quality (low/normal/high) |
f |
Like / unlike current track |
R |
Start radio from current track |
H |
Recently played (history) |
/ |
Filter current list |
. |
Action popup (context menu) |
T |
Theme switcher |
Tab |
Cycle panes (search / library) |
g |
Home |
l |
Library |
q |
Queue view |
L |
Lyrics |
a |
Go to current track's artist |
A |
Go to current track's album |
s |
Shuffle toggle |
r |
Repeat toggle |
+ / - |
Volume up / down |
d |
Remove from queue |
Esc |
Back (navigation history) |
Q |
Quit |
All keybindings can be remapped via keymap.toml (see Configuration).
Some actions ship without a default key but can be assigned in keymap.toml:
-
search_page— jump to the search view and focus its input box. The spotify_player default for this is the two-key sequenceg s, which this binary supports natively (unlike the Python version). It ships withg spre-wired; you can rebind it to a single key if you prefer:[keybinds] search_page = "ctrl+s"
- Typo'd action names are silently ignored (forward compatibility: new action names added in future versions will not break existing keymap files).
- Typo'd key strings (unparseable key syntax) are surfaced as a one-line warning on the status line once at startup, then silently dropped. Fix the typo and restart to clear the warning.
# ~/.config/ytmusic-tui/config.toml
[auth]
browser_auth_path = "~/.config/ytmusic-tui/browser.json"
[player]
volume = 80
audio_quality = "high" # low / normal / high
[ui]
theme = "synthwave" # synthwave / nord / gruvbox / catppuccinOverride any keybinding by creating or editing the user keymap file:
# Copy the shipped defaults as a starting point
cp config/default_keymap.toml ~/.config/ytmusic-tui/keymap.toml
# (The config/ directory is at the repo root alongside the Cargo workspace)# ~/.config/ytmusic-tui/keymap.toml
# List only the bindings you want to change.
[keybinds]
toggle_pause = "space"
next_track = "n"
previous_track = "p"
search_page = "g s" # two-key sequence; supported natively
# Key names follow the Python/Textual convention (see default_keymap.toml for all actions)The config format is identical to the Python version's keymap.toml, so
existing files work unchanged.
| Theme | Description |
|---|---|
| synthwave | Magenta/cyan/purple on dark (default) |
| nord | Blue/teal accent on dark gray |
| gruvbox | Orange/yellow on dark brown |
| catppuccin | Lavender/pink on dark |
![]() |
![]() |
| synthwave | nord |
![]() |
![]() |
| gruvbox | catppuccin |
Screenshots are regenerated with ./screenshots/generate.sh (requires
vhs; on Arch: sudo pacman -S vhs ttyd).
The script drives the binary in a built-in demo mode (YTMUSIC_TUI_DEMO=1)
with a fictional catalog, so output is deterministic and contains no account
data.
The player registers itself on the session D-Bus as
org.mpris.MediaPlayer2.ytmusic-tui. Commands via playerctl just work:
playerctl --player ytmusic-tui play-pause
playerctl --player ytmusic-tui next
playerctl --player ytmusic-tui metadatawaybar position counter freezes. This is a waybar client-side behavior, not
a player bug. The MPRIS spec says compliant players must not spam the Position
property via PropertiesChanged signals — ytmusic-tui follows the spec.
waybar's mpris module renders event-driven by default and so does not
auto-refresh position. Fix this by adding "interval": 1 to the mpris module
in your waybar config:
.
├── ytmusic-api/ # Library crate: InnerTube transport + auth + domain models
│ src/
│ ├── lib.rs # Public API re-exports (InnerTubeClient, BrowserAuth, Track, ...)
│ ├── auth.rs # Browser-header auth + SAPISIDHASH generation
│ ├── client.rs # InnerTubeClient: HTTP requests, session canary
│ ├── classify.rs # classify_api_error: error taxonomy (auth / not-found / network / ...)
│ ├── endpoints/ # One file per API surface (home, search, album, artist, ...)
│ ├── models.rs # Domain types: Track, AlbumInfo, ArtistInfo, PlaylistInfo, ...
│ └── parse.rs / nav.rs / context.rs # InnerTube response navigation helpers
│
└── ytmusic-tui/ # Binary + lib crate: UI, player, queue, keymap, config
src/
├── main.rs # Binary entry: config loading, terminal setup, render/input loop
├── lib.rs # Crate module root
├── app/mod.rs # AppCommand/AppEvent channels, spawn_runtime (tokio + forwarder threads)
├── player.rs # libmpv-rs wrapper: play, seek, volume, mute, PlayerEvent stream
├── queue.rs # QueueManager: shuffle, repeat, track list
├── config.rs # TOML config loading, theme definitions, keymap loading
├── keymap.rs # Keymap dispatcher: key→Action, two-key sequence support
├── navigation.rs # NavigationManager: page-stack history (Esc goes back)
├── layout.rs # Responsive orientation detection (aspect ratio 2.3 threshold)
├── formatting.rs # Duration formatting helpers
├── mpris/ # MPRIS2 server (mpris-server 0.10 on the tokio runtime)
└── views/ # One module per page + popup + filter bar (ratatui widgets)
The binary runs two extra threads beyond the main render thread:
- Runtime thread — a
std::threadhosting atokio::Runtime. Owns theInnerTubeClient,QueueManager,Player, and (M6) the MPRIS server. All async API calls and playback commands land here via atokio::sync::mpscunbounded channel. - Forwarder thread — a
std::threadthat blocks on mpv'sPlayerEventreceiver and re-publishes each event as anAppEventonto the UI'sstd::sync::mpscchannel. A second sink will be added for MPRIS metadata updates without changing the forwarder's shape (YAGNI until a second consumer exists).
The main thread's ratatui render loop is fully synchronous and never blocks.
See CONTRIBUTING.md for development setup and guidelines.








