ytm
YouTube Music in the terminal: search, queue, radio and lyrics, with mpv doing the playing.
Overview
YouTube Music has no desktop client that is not a browser. ytm is a small Python CLI and a Textual TUI over three tools that already do the hard parts: ytmusicapi for the catalogue, yt-dlp for stream resolution and mpv for audio.
mpv is the only long-running process. ytm starts it once, idle, with a JSON IPC socket, and every command after that is a stateless message to it. Close the terminal and the music keeps playing. A Lua script inside mpv keeps the queue fed with the station for whatever is playing, so it never runs dry.
Quickstart
pipx install ytm # or: uv tool install ytm / pip install ytm
ytm auth # cookies from a logged-in browser, see Authentication
ytm play "daft punk" # search, play the first hit, radio follows
ytm # the TUI
To hack on it instead:
git clone https://github.com/MaheshBhushan/yt-music-cli.git && cd yt-music-cli
python3 -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'
[!IMPORTANT]
mpvmust be on yourPATH; pip cannot install it.pacman -S mpv,apt install mpv,brew install mpv, or the installers at https://mpv.io. Node is optional but recommended: yt-dlp uses it to solve YouTube's JavaScript challenges.
Usage
The TUI is ytm with no arguments. Type a query and press Enter to play the first result. Every key is listed in the bar at the bottom, and everything is clickable: results, queue rows, playlists, the progress bar, the shortcuts.
| Key | Action |
|---|---|
/ or s |
Focus search |
Enter |
Play the selected result, queue entry or playlist |
q |
Enqueue the selected result |
space |
Play / pause |
n p |
Next / previous |
← → |
Seek 5 s |
+ - |
Volume |
a |
Add the selected song to a playlist: a, pick the list with ↑ ↓, a or Enter |
l |
Focus playlists |
Tab |
Cycle panes |
e |
Exit, music keeps playing |
x |
Exit and stop mpv |
One-shot commands talk to the same mpv. Add --json to any of them for machine-readable output.
ytm search "song name" -n 10 # results are numbered
ytm play 3 # a number from the last search, an 11-char video id, or a query
ytm add 4 # enqueue
ytm radio # replace the queue with a station for the current track
ytm status | queue | lyrics | like
ytm pause | resume | toggle | next | prev | stop
ytm seek -10 | seek --to 90 | volume 60 | clear | shuffle
ytm quit # stop mpv entirely
The queue never holds a track twice: playing something already queued jumps to it, and radio skips what is there.
Authentication
Search works signed out, but library, playlists, likes and lyrics need your account. Credentials live in ~/.config/ytm/auth.json (mode 0600) and are validated with a live call before being kept.
ytm auth # cookies from Chrome, Chromium, Edge, Brave, Vivaldi, Opera or Firefox
ytm auth --from-browser firefox # pick one
ytm auth --manual # paste request headers copied from DevTools
ytm auth --oauth # device-code flow, for SSH and headless boxes
Browser cookies expire after a few weeks; re-run ytm auth when the app says so. OAuth needs your own Google Cloud client (YouTube removed the shared one in 2024): create an OAuth client of type TVs and Limited Input devices and pass --client-id/--client-secret, or set YTM_OAUTH_CLIENT_ID/YTM_OAUTH_CLIENT_SECRET.
[!NOTE] Streams resolve anonymously by default. With account cookies, YouTube hands out URLs that require an account-bound proof-of-origin token and then answers 403. Anonymous resolution plays the same catalogue. Set
behaviour.authenticated_streams = trueonly if you need private or age-gated tracks.
Configuration
~/.config/ytm/config.toml. A missing file means these defaults; a partial file overrides only what it names; a bad value is warned about and ignored.
[audio]
volume = 70
device = "auto" # an mpv --audio-device name
[behaviour]
autoplay_radio = true # keep the queue fed with radio
confirm_remote_delete = true
authenticated_streams = false # see the note above
[ui]
theme = "dark" # or "light"
art = "blocks" # blocks | kitty | sixel | auto | ascii | off
[pot]
enabled = true # proof-of-origin tokens via bgutil-ytdlp-pot-provider
base_url = "http://127.0.0.1:4416"
[keys]
toggle = "space"
next = "n"
prev = "p"
search = "/"
quit = "e"
art = "blocks" draws the cover with coloured half-cell glyphs and works in every terminal, tmux included. kitty and sixel use the terminal's pixel protocol; Sixel is known to freeze the pane in Konsole, which is why it is opt-in.
The proof-of-origin token provider is a yt-dlp plugin installed with ytm. It asks an HTTP service for tokens when YouTube demands one; run docker run -d --name bgutil-provider -p 4416:4416 brainicism/bgutil-ytdlp-pot-provider if you want it, or set enabled = false. Playback works without it for most accounts.
More
- Offline cache.
ytm cache add <video_id>downloads a track into~/.cache/ytm/tracks/;cache rmandcache listmanage it. 2 GB cap, least-recently-played evicted first. - Local playlists live in
~/.local/state/ytm/playlists.jsonand show up next to your YouTube Music playlists in the TUI. - Media keys.
ytmhas no MPRIS of its own; install the mpv-mpris plugin and mpv announces itself to your desktop. - yt-dlp goes stale. YouTube changes things; yt-dlp follows within days.
pipx upgrade ytm(orpip install -U yt-dlpin the same environment) fixes most sudden resolution failures. - Windows is supported in design (named-pipe IPC, no D-Bus) but has not been tested.
- Logs. mpv writes to
~/.local/state/ytm/mpv.log.
Repository structure
ytm/
cli.py commands and the mpv launch configuration
player.py Player: mpv over JSON IPC
music.py ytmusicapi wrappers, Track
state.py remembered searches and track metadata
auth.py browser cookies, DevTools headers, OAuth
cache.py offline downloads
mpv/autoplay.lua radio autoplay inside mpv
tui/ Textual app, panes, backend over Player
tests/ pytest; no network and no mpv needed
pip install -e '.[dev]' && pytest -q
License
MIT, see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ytm-0.2.0.tar.gz.
File metadata
- Download URL: ytm-0.2.0.tar.gz
- Upload date:
- Size: 78.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43d130d5d0344d47ab60511ba2134e23eae861a8f9fc55d00e67a30de66d8569
|
|
| MD5 |
8e7409ba562f0833d17cff222a35716b
|
|
| BLAKE2b-256 |
06af0b644919b6aaea4ad9893f06cd02b331b8a8cef378b50ee204c1bd3e5874
|
Provenance
The following attestation bundles were made for ytm-0.2.0.tar.gz:
Publisher:
publish.yml on MaheshBhushan/yt-music-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ytm-0.2.0.tar.gz -
Subject digest:
43d130d5d0344d47ab60511ba2134e23eae861a8f9fc55d00e67a30de66d8569 - Sigstore transparency entry: 2702102795
- Sigstore integration time:
-
Permalink:
MaheshBhushan/yt-music-cli@ce6fa5db0f5bad1722119a019c988c5a19ec48d4 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/MaheshBhushan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ce6fa5db0f5bad1722119a019c988c5a19ec48d4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ytm-0.2.0-py3-none-any.whl.
File metadata
- Download URL: ytm-0.2.0-py3-none-any.whl
- Upload date:
- Size: 55.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8be85ee912e02dba96022930d82130efc97c35425f3c728e265710e6d249bcec
|
|
| MD5 |
0757705a8de84f02d5879e1f2fe03df6
|
|
| BLAKE2b-256 |
6f3e5a412e6bfbf6f7c7b989d47a81175a5e85e31fabf16ca56daba181b47d56
|
Provenance
The following attestation bundles were made for ytm-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on MaheshBhushan/yt-music-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ytm-0.2.0-py3-none-any.whl -
Subject digest:
8be85ee912e02dba96022930d82130efc97c35425f3c728e265710e6d249bcec - Sigstore transparency entry: 2702103201
- Sigstore integration time:
-
Permalink:
MaheshBhushan/yt-music-cli@ce6fa5db0f5bad1722119a019c988c5a19ec48d4 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/MaheshBhushan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ce6fa5db0f5bad1722119a019c988c5a19ec48d4 -
Trigger Event:
push
-
Statement type: