Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@ jobs:

- uses: mlugg/setup-zig@v2
with:
version: 0.16.0

- name: install libmpv
run: sudo apt-get update && sudo apt-get install -y libmpv-dev
version: 0.17.0

- name: unit tests
run: |
zig build test -Dmpv-prefix=
zig build test -Doptimize=ReleaseSafe -Dmpv-prefix=
zig build test
zig build test -Doptimize=ReleaseSafe

# Type-checks freebsd/linux/macos in Debug and ReleaseSafe without
# linking. The FreeBSD release leg has broken twice on translate-c; this
# catches that on every push instead of at tag time inside a VM.
- name: cross-target check
run: zig build check

- name: libmpv runtime load
run: |
sudo apt-get update && sudo apt-get install -y libmpv2
zig build
./zig-out/bin/hum doctor | tee doctor.txt
grep -E '^ libmpv +libmpv\.so' doctor.txt
122 changes: 20 additions & 102 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,121 +8,39 @@ permissions:
contents: write

jobs:
build-linux:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- { name: linux-arm64, os: ubuntu-24.04-arm }
- { name: linux-x86_64, os: ubuntu-24.04 }
steps:
- uses: actions/checkout@v5

- uses: mlugg/setup-zig@v2
with:
version: 0.16.0

- name: install libmpv
run: sudo apt-get update && sudo apt-get install -y libmpv-dev

- name: build
run: |
zig build test -Dmpv-prefix=
zig build -Doptimize=ReleaseSafe -Dmpv-prefix=

- name: package
run: tar -czf "hum-${{ matrix.name }}.tar.gz" -C zig-out/bin hum

- uses: softprops/action-gh-release@v3
with:
files: hum-${{ matrix.name }}.tar.gz

# Single universal (arm64 + x86_64) binary, cross-built on one Apple-silicon
# runner and lipo'd — avoids the scarce macos-13 Intel runner entirely.
# Apple-silicon only. The universal build died when Homebrew stopped
# shipping x86_64 bottles for mpv (Tier 3 under Rosetta): the x86_64 slice
# needs an x86_64 libmpv to link against, and building mpv from source in CI
# means building ffmpeg too. Intel Macs build from source via the tap.
build-macos:
name: macos-arm64
build:
name: all targets
runs-on: macos-14
timeout-minutes: 30
steps:
- uses: actions/checkout@v5

- uses: mlugg/setup-zig@v2
with:
version: 0.16.0
version: 0.17.0

- name: install libmpv
run: brew install mpv
- name: test
run: zig build test

- name: build
run: |
MPV="$(brew --prefix mpv)"
zig build test -Dmpv-prefix="$MPV"
zig build -Doptimize=ReleaseSafe -Dtarget=aarch64-macos -Dmpv-prefix="$MPV"
file zig-out/bin/hum
set -e
for t in aarch64-macos x86_64-macos x86_64-linux-gnu aarch64-linux-gnu x86_64-freebsd; do
zig build -Doptimize=ReleaseSafe -Dtarget=$t --prefix out/$t
done
mkdir -p out/macos-universal/bin
lipo -create -output out/macos-universal/bin/hum out/aarch64-macos/bin/hum out/x86_64-macos/bin/hum
file out/*/bin/hum

- name: package
run: tar -czf hum-macos-arm64.tar.gz -C zig-out/bin hum

- uses: softprops/action-gh-release@v3
with:
files: hum-macos-arm64.tar.gz

# Everything here runs inside an emulated FreeBSD VM, so every step is
# 5-10x its native cost. The 30-minute cap was being hit; the work was
# a 55MB in-VM toolchain download plus two full compiles.
build-freebsd:
name: freebsd-x86_64
runs-on: ubuntu-latest
timeout-minutes: 60
continue-on-error: true
steps:
- uses: actions/checkout@v5

# Pull the toolchain on the fast host (and cache it between releases)
# instead of over the VM's emulated network. The VM sees the workspace.
- name: cache zig tarball
id: zigcache
uses: actions/cache@v4
with:
path: zig-freebsd.tar.xz
key: zig-freebsd-0.16.0

- name: fetch zig (host)
if: steps.zigcache.outputs.cache-hit != 'true'
run: curl -fsSL -o zig-freebsd.tar.xz "https://ziglang.org/download/0.16.0/zig-x86_64-freebsd-0.16.0.tar.xz"

- uses: vmactions/freebsd-vm@v1
with:
usesh: true
prepare: |
set -e
# mpv drags in ffmpeg/X11/vulkan; we only ever link libmpv, but the
# port is how FreeBSD ships it. Keep the set otherwise minimal.
pkg install -y mpv pkgconf curl
run: |
set -e
# Unpacked here rather than in `prepare`: this block is the one that
# is guaranteed to run inside the synced workspace, where the
# host-fetched tarball actually is.
mkdir -p /opt/zig
tar -xJf zig-freebsd.tar.xz -C /opt/zig --strip-components=1
ln -sf /opt/zig/zig /usr/local/bin/zig
zig version
# Tests already run on the linux and macos legs of this same
# workflow. Repeating them here bought a second full compile in the
# slowest environment we have, for no extra coverage.
zig build -Doptimize=ReleaseSafe -Dmpv-prefix=/usr/local

- name: package
run: tar -czf hum-freebsd-x86_64.tar.gz -C zig-out/bin hum
run: |
set -e
pack() { tar -czf "hum-$1.tar.gz" -C "out/$2/bin" hum; }
pack macos-universal macos-universal
pack linux-x86_64 x86_64-linux-gnu
pack linux-arm64 aarch64-linux-gnu
pack freebsd-x86_64 x86_64-freebsd

- uses: softprops/action-gh-release@v3
with:
files: hum-freebsd-x86_64.tar.gz
files: hum-*.tar.gz
2 changes: 1 addition & 1 deletion .zigversion
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.16.0
0.17.0
76 changes: 57 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,33 @@ your local music library, streamed rss/urls, and yt music, one zig binary that r
<img src="https://img.shields.io/badge/license-MIT-000?style=flat-square&labelColor=500" alt="MIT"/>
</p>

zig 0.16, single binary
- `libmpv` for audio; plays local files, streams, and youtube
zig 0.17, single binary
- `libmpv` for audio, loaded at runtime; plays local files, streams, and youtube
- `hum` now has it's own tag reader (id3, vorbis/flac, ogg/opus, mp4) | no taglib, no ffprobe
- shells out to `curl` and `yt-dlp` **for youtube obviously**; local playback needs neither
- astats lavfi filter for visualizer via `ffmpeg`
- astats lavfi filter for visualizer (ffmpeg inside mpv)
- outbound calls only: never listens on a socket

## version
<b>v0.1.7</b> dropping out the `ytcli` name; it's a media player now and youtube is only one source
<b>v0.1.8</b> visualizers, mouse, and installs anywhere mpv does
- **libmpv loads at runtime** instead of being linked: one binary per platform, and intel macs get a release again (universal macOS binary)
- no mpv? `hum` still starts, searches and browses, and tells you to install mpv instead of failing to launch
- **mp3 durations**: local mp3s without a `TLEN` tag showed `00:00`; length now comes from the Xing/Info header, or bytes ÷ bitrate for CBR. existing library rows refresh on the next scan
- **linux media keys / status bars** (MPRIS): install `mpv-mpris` and hum shows up in waybar, polybar, gnome, kde
- now-playing widgets (macOS Now Playing, MPRIS) show the track title instead of a stream URL
- `hum doctor` reports where libmpv was found and flags a stale `yt-dlp`; the TUI says so too when a youtube track fails because of it
- **visualizer view** (`Ctrl+V`): the visualizer takes over the main box while now playing stays below, and the queue sits beside it on wide terminals. five built in: bars, cube, orbit, rings, plasma. `v`/`V` cycles them. they follow your theme and react to loudness, left/right balance and brightness (treble). resize all you want; it lays out fresh every frame
- **your own visualizers**: drop a `.vis` text file (ascii frames + a few `key = value` lines) in `~/.config/hum/visualizers/` and it joins the cycle. see [`examples/visualizers/pulse.vis`](examples/visualizers/pulse.vis)
- **mouse**: wheel scrolls lists, click selects, click again plays, click the progress bar to seek, click `both`/`youtube`/`library` and `all`/`songs`/… to switch scope and filter. in the visualizer: left/right click cycles, the queue panel plays on click and reorders by drag. `mouse=off` in the config turns it off (hold `shift`, or `option` in macOS Terminal, to select text while it's on)
- **paste**: pasting into search goes in as text (bracketed paste), so a pasted newline or escape code can't fire keys
- **zig 0.17**: builds with current zig (and homebrew's), no more C header imports anywhere
- `brew install zblauser/tap/hum` installs the prebuilt binary instead of compiling: intel macs no longer build zig (and LLVM) from source first

<details>
<summary>previous</summary><br>

<b>v0.1.7</b><br>
dropping out the `ytcli` name; it's a media player now and youtube is only one source
- **renamed**: the binary is `hum`. your existing history, playlists and config keep working; it does read the old `ytcli/` config/data dirs when the new `hum/` ones aren't present
- **local files**: `hum ~/Music/album/01.flac` plays a file; `http(s)://` URL streams
- **local library**: `hum library dir ~/Music`, then `Ctrl+L` in the TUI browses artists → albums → tracks
Expand All @@ -30,9 +48,6 @@ zig 0.16, single binary
- fix: search rows showed a duration where the artist belongs (top-result rows come from a card shelf that carries the artist in its header)
- dropped: cookie/SAPISIDHASH personalization. not worth the credential surface

<details>
<summary>previous</summary><br>

<b>v0.1.6</b><br>
- fix crash on non-UTF-8 autocomplete (the suggest endpoint answers in latin-1 without `ie`/`oe=utf-8`)
- remote text is scrubbed of control bytes before it reaches your terminal or your history/playlist files
Expand Down Expand Up @@ -84,24 +99,28 @@ zig 0.16, single binary
+ handful of color themes
</details>

## build/install
## install
```sh
zig build # → zig-out/bin/hum
zig build install --prefix ~/.local # → ~/.local/bin/hum
brew install zblauser/tap/hum # macOS / linuxbrew: prebuilt binary + mpv + yt-dlp, nothing to compile
```
or grab a binary from [releases](../../releases): macOS (universal), Linux (arm64/x86_64), FreeBSD. windows: run under WSL (no native build just yet).

## dependencies

```sh
brew install mpv ffmpeg # macOS (mpv pulls in yt-dlp)
apt install libmpv-dev yt-dlp ffmpeg # Debian/Ubuntu
brew install mpv # macOS (mpv pulls in yt-dlp)
apt install libmpv2 yt-dlp # Debian/Ubuntu
apt install mpv-mpris # optional: media keys + status bar widgets on linux
```
<br>

> **[ ! ]** currently requires `mpv`, `ffmpeg` and `yt-dlp` particularly on PATH (try and keep your `yt-dlp` updated, youtube breaks old versions)
> **[ ! ]** `libmpv` plays everything; `yt-dlp` is only for youtube (try and keep it updated, youtube breaks old versions). `hum doctor` checks both

## releases

macOS (universal: arm64 + x86_64), Linux (arm64/x86_64), and FreeBSD binaries are attached to each [release](../../releases). windows: run under WSL (no native build just yet).
## build
```sh
zig build # → zig-out/bin/hum (needs only zig 0.17, not mpv)
zig build install --prefix ~/.local # → ~/.local/bin/hum
```

## run
```sh
Expand Down Expand Up @@ -171,6 +190,17 @@ made an effort to use commands that felt fairly intuitive
| | `m` | Mute |
| | `Ctrl+R` | Cycle repeat mode (off/track/queue) |
| | `Ctrl+Y` | Cycle theme |
| | `Ctrl+V` | Visualizer view (`esc` or `Ctrl+V` again to leave) |
| **Visualizer (`Ctrl+V`)** | `v` / `V` | Next / previous visualizer (remembered) |
| | `↑` / `↓` | Volume up / down |
| | `←` / `→` | Seek backward / forward 10 seconds |
| **Mouse** | wheel | Move the selection (volume in the visualizer) |
| | click | Select a row; click it again to play or open it |
| | click the progress bar | Jump to that point in the track |
| | left / right click the visualizer | Next / previous visualizer |
| | click a queue-panel track | Play it |
| | drag a track onto another | Move it there (queue panel and `Ctrl+Q`) |
| | click a scope or filter label | Switch to it |

</details>

Expand All @@ -179,7 +209,8 @@ made an effort to use commands that felt fairly intuitive
- `$XDG_DATA_HOME/hum/log` - timestamped failures (search/album/stream) with the underlying error and any `curl`/`yt-dlp` stderr. check here first when something says `(see log)`.
- `$XDG_DATA_HOME/hum/playlists` - saved playlists: `[name]` header, then one `target⇥title⇥artist⇥kind⇥source⇥duration` line per track (`target` is a video id, or a path for local files). tab-separated, editable by hand. files written by older versions still load.
- `$XDG_CACHE_HOME/hum/library` - the local index. rebuildable. delete it and `hum library scan`. rows are reused when a file's mtime and size are unchanged, so a rescan only re-reads what actually changed.
- `$XDG_CONFIG_HOME/hum/config` - `key=value` settings (theme, volume, `music_dir`).
- `$XDG_CONFIG_HOME/hum/config` - `key=value` settings (theme, volume, `music_dir`, `visualizer`, `mouse=off`, `splash=off`).
- `$XDG_CONFIG_HOME/hum/visualizers/*.vis` - your own visualizers. header lines `name`, `fps` (1-30), `speed` (`fixed`/`level`), `color` (`level`/`dim`/`accent`/`strong`), then frames separated by `---` lines. up to 32 files, 64KB each; a file that doesn't parse is skipped and noted in the log
- history is just newline delimited text - `grep`/`cat` it, or seed it so the TUI autocompletes your favorites from the first keystroke:
```sh
printf '%s\n' "elephant gym" "autechre" "john zorn" >> ~/.local/share/hum/history
Expand All @@ -192,8 +223,15 @@ grep -i jazz ~/.local/share/hum/history
> [ ! ] requests written to `/tmp/hum_body*` (mkstemp, 0600, unlinked after) per call + audio streamed/buffered via mpv; not written to disk.

## visualizer
simple spectrum bars via `astats` lavfi filter<br>
dB to linear/modulated per bar | *not a true per-band FFT*
<p align="center">
<img src="assets/visualizers.gif" alt="hum's visualizer view cycling bars, cube, orbit, rings, plasma and a custom flipbook" width="82%"/>
</p>

full screen the visualizer using `Ctrl+V` while something plays. `v`/`V` cycles bars, cube, orbit, rings, plasma and any `.vis` files you've added; left/right click on the visualizer does the same. the queue on the right is clickable (play) and draggable (reorder)<br>

mpv's `astats` lavfi filter reports loudness, per-channel level and the zero-crossing rate (a stand-in for brightness/treble) every 50ms; the visualizers are driven by those<br>

*not a true per-band FFT*: the bars are modulated by loudness, not split by frequency

## contribution
please feel free to contribute, not a guarantee it will be merged, but a guarantee a human will look at it<br>
Expand Down
Binary file modified assets/hum.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/visualizers.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading