Hum - TUI music player (local, rss, and youtube music)

hum


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 a free TUI player with the ability to play a wide range of local files, rss feeds, and youtube music.

hum repo: github

what the heck does it do and why?

hum not only searches, browses, and plays from ytmusic right from your terminal, it plays your local music and rss feeds as well. utilizing the innertube api key for yt, it requires no login. type, watch autocomplete fill in, hit enter, it streams to a “now-playing” footer with a little spectrum visualizer. search by library, youtube, or both. create playlists with local, yt, or both types of tracks. 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.

other interesting zig specific bits:

  • arena lifetimes continue to bite me. library rows are copied slices out of the index arena (not bytes), i.e. rescanning freed the strings that the results list was still pointing at and the scan progress callback drew them
  • that tag reader is structurally a hostile input parser. every tag length is a number, an out of range slice is foobar. to prevent self destruction, all reads are passed through a bounds check, the mp4 atom walker requires forward progress and caps the nesting depth, else it’ll hang the player
  • neither std.crypto.random or arc4random_buf are portable across all release targets. consequentially, shuffle seeds from ASLR + tick counter (works for the playlist but i feel like this could be better structured)

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 go to a mkstemp file (random name, 0600) and passed as curl --data-binary @file and are unlinked after use, as opposed to a predictable /tmp/hum_body path.

fun fact; the visualizer is a 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 5) 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.

10 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

sorry to bug you i realize you’re probably just as busy as the next person here, but if you happen to have time to try and re-run and see if it spits an error out for you in the log i added would be greatly appreciated. again no rush, whenever you’re free, i’ve been using it without error and been unable to reproduce it.

Sorry, I am a bit slow to get up to speed after holiday.

This seems to work, fine now on my Macbook, playing selected music.

The only thing I find a bit counter intuitiv is when I select another song and press l to play it, the previous song keeps playing (probably until data is retrieved from YT), before switching. The delay is long enough for me to have doubted that I pressed l at all and retried. There seems to be no visual feedback apart from the playing display freezing (my focus was on the results part of the window about 120 terminal lines higher, so that “freeze” is easy to miss). Not sure if possible, but I think I would like to experiment with immediately stopping the audio and the playing window telling me that YT is being connected to play the selected song.

I no longer have to do a tput reset after ^C to get the cursor back.

Quitting the program on Linux (connected via ssh) now worked and no longer required closing the terminal.

1 Like

this is exactly the kind of feedback i like. i have also noticed the delay on this and will def give a visual feedback of some sort, if not better orchestrating the function to immediately stop the audio.

Works great on arch linux. Dependencies were installed with:

pacman -S ffmpeg yt-dlp mpv

Building in debug did not work because of this issue. Building with -Doptimize=ReleaseSafe worked fine.

1 Like

interesting, so zig issue. great to hear though, glad it’s working well for you. please let me know if you hit anything error wise or think of something you’d like to see. working on trying to get at least some of my work to codeberg but if you feel like contributing can always submit a pr on gh.

1 Like

stopping in on this thread to let you know ytcli now has playlists; a fun little feature to help keep you organized.

1 Like

ytcli is now hum (and it’s a full fledged music player)

same project, renamed, with abilities that extend beyond youtube music. it now plays your local music and rss feeds. have been putting a lot of work into this and i hope you like it.

changes

  • local library: point hum at your music dirs, nothing on your disk moves, tree is built from tags not folders
  • tag reader: ID3v2.3/2.4, ogg/opus, mp4 currently, flac
  • podcasts: subscriptions to rss/atom, play inline
  • playlists can mix local, yt, and feed

the only linked c library is still libmpv

if you’re upgrading, your existing history, playlists, and config still work without being touched

3 Likes