Ytcli - youtube music TUI via innertube

ytcli


first off, thank you for taking the time to check out the project. posting because i genuinely thought people would find it useful and/or neat. it’s literally a free TUI ytmusic player.

ytcli repo: github

ytcli

what the heck does it do and why?

ytcli searches, browses, and plays from ytmusic right from your terminal; with the use of the innertube api key. requires no login. type, watch autocomplete fill in, hit enter, it streams to a “now-playing” footer with a little spectrum visualizer. single binary.

something interesting i learned while putting this thing together; there’s no public “search music” api, this isn’t the interesting part. however, the youtubei/v1 endpoints that the web app utilizes for devices has actually been pretty heavily reverse engineered to the point it was capable of being implemented as the WEB_REMIX client. this key can actually be found in every youtube music page. i thought that was neat. anyway, the filters for searching (songs, videos, albums, artists) are just ripped base64 protobuf tokens.

.songs => “EgWKAQIIAWoKEAkQBRAKEAMQBA%3D%3D”
.albums => “EgWKAQIYAWoKEAkQChAFEAMQBA%3D%3D”

obviously the innertube json is fucking insanely massive, heavily nested, and pretty undocumented (not publicly by google anyway). google can also alter it whenever they please. instead of taking on the task of modeling the scheme as zig structs, the extractor just walks the std.json.Value recursively and when it hits a musicResponsiveListItemRenderer tries to pull a track from it. if it fails you get ?Track. google can also restructure their surrounding envelope all they want; though having the renderer key allows search to theoretically keep working.

as for the “why?” uh.. because i wanted one idk. i’m also fairly new to zig, come from C and was looking for somewhat of an introductory project. i also spend a lot of time in the terminal and i spend a lot of time with a browser widow pulled up on ytmusic, i figured why not shell out processes and run this thing from a terminal as well, less overhead and it’s neat.

so it probably has dependencies right?

so, build.zig.zon has an empty dependency set. libmpv is linked C, and yt-dlp and curl are subprocesses.

  • for now; as it seems std.http in 0.16 is still moving, libcurl linking was more than required and http is a curl subprocess atm.
  • everything takes explicit allocation (arena=per request/gpa=long term)
  • the temp writes’ request bodies that travel through curl --data-binary @file are written via mkstemp (using a random name, mode 0600) and are unlinked after use, as opposed to a predictable /tmp/ytcli_body path.

fun fact; the visualizer is total lie. it mimics a spectrum analyzer but there’s no FFT to be found. mpv runs astats lavfi filter on the audio graph. it reads the overall rms level via the api as a string: af-metadata/vis/lavfi.astats.Overall.RMS_level. dB is converted to linear; each of the 28 bars is modulated off the single envelope value. basically cheap spectrum using scalar.

supported zig versions

zig 0.16

ai / llm usage disclosure

claude code (sonnet 4.6) was used carefully and sparingly for drafting and refactoring implementation at my direction. it did also help serve as a sounding board while i sorted out the innertube implementation, architecture, design decisions; as i’m trying to better learn zig. every implemented line was read and tested by myself before it shipped. this program was created by a human for humans to serve a very particular purpose.

4 Likes

I get an album load failed on every thing I tried, and it is not clear what step went wrong, whether yt-dlp is not working or anything else. I tried searching on Youtube for the same material (guessing what ytcli found), and copy-pasting the url after yt-dlp worked fine.

I suggest either adding the option to write a log file under /.local/share/ytcli, and also to extend the history file format with a time stamp for each command as well a tag that indicates failure or success in retrieval, etc.

After exiting with ^C, I have to do a tput reset to get my cursor back.

brew install mvp already pulls in yt-dlp so there is no need to specify that explicitly.

This was on a macOS M1 machine.

1 Like

seriously appreciate the feedback. i haven’t tested much on anything besides an intel mac. feel free to open an issue but i will definitely take a look at this regardless.

so obviously not being able to replicate it on an intel mac, i am working on a log file that should at least output the error to a log if you would be so kind as to try again when i push again here shortly. it will still read album load failed, though referencing the log. this log lives at ~/.local/share/ytcli/log.

it reads:

2026-06-02 19:43:35 yt-dlp exit 1: ERROR: [generic] '' is not a valid URL

the format is timestamp, what failed, and why (real error text from curl, or yt-dlp stderr). what’s listed above will be an example of what you should see.

also you’re totally right about mpv already pulling yt-dlp. updated the readme as well.

I can retest but, did not see any changes yet in the repo. I’ll have less time starting the weekend, so don’t interpret that as disinterest.

I ran this on my server ( Ubuntu 22.04 ) and it downloaded and started to play (but since there is no audio connected, I could not hear). But I had difficulty exiting during play (^C ?, I did not see howw to stop playing and quit), so I had to close the ssh connection to stop the playing (could probably have logged into via another terminal tab).

1 Like