Beefy Player

beefy-player demo

A flashy terminal player for internet radio — a live spectrum analyser, song history and album art, with desktop media integration on Linux and macOS. It draws with your terminal's own ANSI palette on the terminal's default background, so it fits any theme, including transparent terminals.

Thirteen station catalogues ship today, from SomaFM's curated channels to the 62,000-station Radio Browser directory, loaded as you scroll and searched live as you type. Press p to switch catalogue without restarting.

Features

  • Live spectrum analyser — 72 log-spaced bands with peak caps, a palette gradient and a mirrored spectrum. Six modes (v): mirrored bars, plain bars, oscilloscope, scrolling waterfall, radial spectrum and a high-resolution braille oscilloscope.
  • Synced to what you hear — the analyser listens to your actual audio output by default, so the bars follow the music. If that is unavailable it falls back to decoding the stream, then to a simulated display.
  • Now playing and history — artist, title, album, DJ, listeners, bitrate and "played N minutes ago", plus the full recent history. A clickable Bandcamp ↗ link appears when the current track can be found there.
  • Real album art — native images in Kitty, iTerm2 and WezTerm; an ANSI-shaded mosaic in every other terminal.
  • Thirteen station catalogues — SomaFM (≈50 channels), Radio Paradise, tilderadio, Bassdrive, dublab, Rinse FM, Refuge Worldwide, LYL Radio, The Lot Radio, Cashmere Radio, Datafruits, aNONradio and the Radio Browser directory (loaded as you scroll in popularity order, with live search across all 62,000).
  • Favourites, search and schedules — favourites float to the top and are remembered between runs, including directory stations outside the loaded page; search by title, genre or description, with live server-side search across the Radio Browser directory; single-stream stations show their broadcast schedule instead of a one-item list.
  • Desktop media integration — MPRIS on Linux (GNOME/KDE widgets, lock screens, playerctl and the media keys) and Now Playing on macOS (Control Center and the media keys).
  • Sixteen palettes, theme-aware — every colour is native ANSI against the terminal's default background, so transparent terminals stay transparent and the TUI follows your colour scheme. Press c to cycle palettes.
  • Graceful offline — cached stations and schedules, a bundled fallback list, automatic mirror failover and ffplay when mpv is missing.

Installation

Nix

The flake builds the package, runs the test suite and wraps everything the app calls at runtime (mpv, ffmpeg, and PulseAudio's parec/pactl — PipeWire speaks the same protocol):

nix run git+https://tildeforge.dev/beefcarver/beefy-player              # try it
nix profile install git+https://tildeforge.dev/beefcarver/beefy-player  # install it

In a NixOS configuration:

{
  inputs.beefy-player.url = "git+https://tildeforge.dev/beefcarver/beefy-player";

  outputs = { nixpkgs, beefy-player, ... }: {
    nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
      modules = [
        beefy-player.nixosModules.default
        {
          programs.beefy-player.enable = true;
        }
      ];
    };
  };
}

Or, for a single user with Home Manager:

{
  inputs.beefy-player.url = "git+https://tildeforge.dev/beefcarver/beefy-player";

  outputs = { home-manager, beefy-player, ... }: {
    homeConfigurations.me = home-manager.lib.homeManagerConfiguration {
      modules = [
        beefy-player.homeModules.default
        {
          programs.beefy-player.enable = true;
        }
      ];
    };
  };
}

Both modules add one option, programs.beefy-player.package, so a pinned or overridden build can be substituted for the flake's own. The raw package (packages.${system}.default) and an overlay (overlays.default) are exported too. From a checkout, nix develop gives you uv, ruff and the audio tools for the usual uv sync / uv run pytest workflow; nix build builds and tests the package.

Homebrew

A tap lives at tildeforge.dev/beefcarver/homebrew. Homebrew's one-argument brew tap assumes GitHub, so point it at the tap URL and trust the formula explicitly:

brew tap beefcarver/homebrew https://tildeforge.dev/beefcarver/homebrew.git
brew trust --formula beefcarver/homebrew/beefy-player
brew install beefcarver/homebrew/beefy-player

brew update refreshes the tap and brew upgrade picks up new versions like any other formula. The formula builds from source, with numpy, pillow, mpv and ffmpeg pulled in as Homebrew dependencies.

No release is tagged yet, so the formula still carries a placeholder checksum and cannot install from a tarball. Until the first v* tag is published, use brew install --HEAD beefcarver/homebrew/beefy-player.

The tap is a git submodule at contrib/homebrew, so the formula and its maintenance notes live in the tap repository itself; after cloning this one, run git submodule update --init to check it out.

Arch Linux

The contrib/aur/PKGBUILD here is AUR-ready, but the package is not published in the AUR: the AUR has closed new sign-ups, so there is no account to push it to. Build and install it from a checkout instead:

git clone https://tildeforge.dev/beefcarver/beefy-player
cd beefy-player/contrib/aur
makepkg -si        # needs base-devel; builds, runs the test suite, installs

Once the AUR accepts new accounts again, the package can be published and paru -S beefy-player / yay -S beefy-player will work; until then makepkg is the only route. The contrib/aur/PKGBUILD is tracked and kept in sync with every release either way.

The Python side is pulled in automatically. For audio, the output-synced spectrum and image passthrough through tmux, install the optional tools too:

sudo pacman -S mpv ffmpeg libpulse tmux

Standalone binary (no Nix, no packaging step)

uv can build the package and drop the executable in ~/.local/bin — this is the pip install --user-style install, without touching a system or project environment:

uv tool install git+https://tildeforge.dev/beefcarver/beefy-player
# or, from a checkout:
uv tool install .

That creates its own isolated environment under ~/.local/share/uv/tools/ and symlinks beefy-player into ~/.local/bin (uv's default, which is also where pip install --user writes on Linux). If uv warns that the directory is not on your PATH:

uv tool update-shell        # or add ~/.local/bin to PATH by hand

Then run beefy-player from anywhere. uv tool upgrade beefy-player re-installs the current source; uv tool uninstall beefy-player removes it. pipx install git+https://tildeforge.dev/beefcarver/beefy-player is the pipx equivalent and lands in the same place.

Only the Python side is installed this way, so mpv / ffmpeg (and parec for the synced spectrum) still come from your package manager; without them the app runs but has no audio.

On macOS, note that ~/.local/bin is uv's and pipx's choice, not Python's: a pip install --user against Homebrew's Python uses ~/Library/Python/3.x/bin instead.

From source

uv sync installs into a project virtualenv, so the executable is .venv/bin/beefy-player and is not on your PATH:

uv sync
uv run beefy-player     # or: .venv/bin/beefy-player

Usage

beefy-player

The UI wants roughly 84×24; it also works smaller, just less pretty.

flag what it does
--provider <slug> pick a station catalogue
--station <id> pick a station (see --list)
--play tune the last station right away
--list print station ids for the current catalogue
--volume <0-100> set the initial volume
--offline no network; use cached and bundled stations
--no-audio UI demo without mpv / ffmpeg
--no-mpris no MPRIS / Now Playing, no media keys
--spectrum-source monitor|stream force synced output capture or the stream tap

Environment variables are prefixed BEEFY_: BEEFY_IMAGES (tgp, iterm2 or 0), BEEFY_MPV_ARGS and BEEFY_SPECTRUM_DEVICE.

Keys

key action
↑ / ↓, j / k move through stations
pgup / pgdn page through stations
enter tune in to the selected station
space play / pause
s stop
] / [ next / previous station (tunes immediately)
+ / - volume
m mute
f favourite (favourites sort to the top)
/ filter by title, genre, description
esc close the search box; press again to clear the filter
v visualiser: mirror → bars → wave → waterfall → radial → braille
c colour palette (16 of them, remembered)
r refresh stations, schedule and song history
p switch station catalogue
g / G top / bottom
? help
q quit

Station catalogues

catalogue slug stations
SomaFM somafm ≈50 curated channels
Radio Paradise radioparadise 5 mixes
tilderadio tilderadio 1 + schedule
Bassdrive bassdrive 1 + schedule
dublab dublab 1 + schedule
Rinse FM rinse 3
Refuge Worldwide refuge 1
LYL Radio lyl 1
The Lot Radio lot 1
Cashmere Radio cashmere 1
Datafruits datafruits 1
aNONradio anonradio 1
Radio Browser radiobrowser 62k · paged + live search

Pick one with p in the UI, --provider <slug> on the command line, or by setting "provider" in the config file.

Desktop media integration

Linux — while playing, the app exposes an org.mpris.MediaPlayer2.beefyplayer.instance<pid> player on the session bus, so GNOME/KDE widgets, lock screens, playerctl and the media keys pick it up:

playerctl -l
playerctl metadata --format '{{artist}} — {{title}} ({{album}})'
playerctl play-pause

If there is no session bus (SSH without dbus-run-session, containers, …) MPRIS is skipped and the status bar shows mpris off.

macOS — mpv publishes to the system Now Playing session, so Control Center, the menu bar and the media keys drive the app while it plays. The status bar shows nowplaying on. --no-mpris (also spelled --no-media) turns the media keys off.

macOS shows one Now Playing app at a time. If Music, Safari or another mpv is playing, the media keys go there instead.

Configuration

Settings live in ~/.config/beefy-player/config.json (favourites, volume, provider and the last station tuned in each catalogue). Cached stations, schedules and artwork live in ~/.cache/beefy-player/.

These stay in the usual XDG locations on every platform, macOS included; XDG_CONFIG_HOME and XDG_CACHE_HOME are honoured when set.

Troubleshooting

  • Artwork shows the mosaic? Real images are auto-enabled for Kitty, iTerm2 and WezTerm. Force a protocol with BEEFY_IMAGES=tgp (Kitty) or BEEFY_IMAGES=iterm2 (iTerm2/WezTerm); BEEFY_IMAGES=0 forces the mosaic. --diagnose-images prints what was detected.
  • No audio? mpv is preferred, ffplay works as a fallback. Without either, the UI still runs and tells you. Extra mpv arguments can be injected with BEEFY_MPV_ARGS, e.g. BEEFY_MPV_ARGS='--audio-device=...'.
  • Spectrum says FFT instead of SYNC? Output capture is unavailable or failed, so the analyser is decoding the stream instead; the bars are still real audio but can drift. Force sync with --spectrum-source monitor, or pick a specific monitor with BEEFY_SPECTRUM_DEVICE on Linux. SIM means the bars are synthesized.
  • macOS: spectrum stays FFT? Grant your terminal System Audio Recording permission under System Settings → Privacy & Security → System Audio Recording, then restart the app. Also check that mpv is playing to the default output device, since that is what the analyser records.
  • macOS: media keys do nothing? Another app owns the Now Playing session, the app was started with --no-mpris, or audio fell back to ffplay (mpv is required for Now Playing).
  • MPRIS says off? There is no session bus. Run from your desktop session, or use dbus-run-session for a private bus. macOS does not use MPRIS.

Development

uv sync                         # create the environment
git submodule update --init     # contrib/homebrew, the Homebrew tap
uv run pytest                   # test suite
uvx ruff check .                # lint

Each catalogue is a self-contained provider module under beefy_player/providers/; base.py documents the interface. Every catalogue's header is a wordmark from the shared half-block font, and the header paints it with the active palette gradient (hero_gradient, on by default) so all thirteen follow the terminal's colours. tools/ansi_art.py converts a PNG logo into half-block ANSI art that carries colours of its own; a provider using that art must set hero_gradient = False, because the header would otherwise repaint it with the gradient.

uvx ruff check . is the enforced lint. The tree is not ruff format-clean (54 files would change), so do not reformat it wholesale.

tools/bump_packaging.py <version> keeps the AUR package and the Homebrew tap formula in step with a release — it rewrites PKGBUILD, regenerates .SRCINFO from it (no makepkg needed), rewrites the formula, and --commits both. --check reports drift without writing.

Pushes run the test suite (.forgejo/workflows/tests.yml); a v* tag runs release.yml, which checks that the tag, pyproject.toml and beefy_player.__version__ agree and then publishes the release notes. It does not touch packaging, so contrib/aur/PKGBUILD and the tap's formula are bumped by hand for each release.

On NixOS, nix develop (or the older shell.nix) provides the same uv sync / uv run workflow.

Licence

WTFPL — Do What The Fuck You Want To Public License, Version 2. See LICENSE.

S
Description
A flashy terminal player for internet radio
Readme WTFPL
1.9 MiB
0 Stars 1 Watchers 0 Forks
v1.2.5
Latest
2026-11-15 14:22:47 +00:00
Languages
Python 98.3%
Nix 1.4%
Shell 0.3%