Commit Graph
61 Commits
Author SHA1 Message Date
beefcarver e6684ddbbd docs(readme): document makepkg install while AUR sign-ups are closed 2026-11-04 17:07:14 -05:00
beefcarver d06a94f388 build(aur): add generated .SRCINFO metadata 2026-11-04 17:05:04 -05:00
beefcarver 1f686ce7ba build(homebrew): add Homebrew tap formula and publishing notes 2026-11-04 17:04:52 -05:00
beefcarver cbcdfae8bf build(aur): add AUR packaging 2026-11-04 17:04:47 -05:00
beefcarver ffd67fc90d docs(readme): document Arch Linux AUR installation 2026-11-04 17:04:38 -05:00
beefcarver 941c744638 docs(readme): trim licence section to the licence reference 2026-11-04 16:22:54 -05:00
beefcarver 2753a7b6ba chore(release): bump version to 1.2.2 2026-11-04 16:20:38 -05:00
beefcarver dc6ed00abe docs(readme): document Nix installation, modules and the flake dev shell 2026-11-04 16:20:38 -05:00
beefcarver 12792b1265 build(nix): add flake with package, dev shell and NixOS/Home Manager modules 2026-11-04 16:20:38 -05:00
beefcarver 4346f75eac test: spawn fake mpv and parec helpers with sys.executable 2026-11-04 16:20:38 -05:00
beefcarver e0e1ad5df3 chore(release): bump version to 1.2.1 2026-11-04 15:49:28 -05:00
beefcarver ec2ca72efb fix(frame): start the corner fade at the brightest palette stop
A ladder is a hue walk, not a brightness walk, so painting the hot end
backwards put the dim colour at the corner.  Sort the ramp by stock
brightness and follow the pane height for the bottom-left glow.
2026-11-04 15:49:26 -05:00
beefcarver 81116282cb chore(release): bump version to 1.2.0 2026-11-04 15:11:54 -05:00
beefcarver 03297e8751 docs(readme): rewrite for public release 2026-11-04 15:11:49 -05:00
beefcarver 638cc9eddb feat(radiobrowser): add search, paging and favourite lookup 2026-11-04 15:11:42 -05:00
beefcarver f96923b5b7 chore(release): 1.1.0 2026-11-04 08:56:10 -05:00
beefcarver 7ba2d50503 fix(ui): keep the help and catalogue menus inside the screen 2026-11-04 08:56:04 -05:00
beefcarver 43ddd8666b feat(palette): sixteen ANSI colour ladders, cycled with c 2026-11-04 08:55:51 -05:00
beefcarver 253b462df1 chore(release): 1.0.0 2026-11-03 20:38:34 -05:00
beefcarver 5eb60d378c feat(catalogue): tune a single-station catalogue on switch 2026-11-03 20:31:18 -05:00
beefcarver 0219df454c feat(ui): draw pane frames, page keys and a scroll thumb 2026-11-03 20:25:02 -05:00
beefcarver 49f7efe0d2 feat(providers): add nine catalogues and the Radio Browser directory 2026-11-03 20:24:53 -05:00
beefcarver a3ad693132 fix(ui): keep the catalogue picker title and hint apart 2026-11-03 18:44:57 -05:00
beefcarver b3bd8c1e9f chore(tools): key out white backgrounds in the ANSI art converter 2026-11-03 18:44:52 -05:00
beefcarver e14a349c2d fix(artwork): letterbox images and back alpha art with black 2026-11-03 18:44:47 -05:00
beefcarver 50a9a735f7 feat(bassdrive): add the drum & bass relay and a shared wordmark font 2026-11-03 18:44:40 -05:00
beefcarver 84ec3b08f6 docs(agents): record the uv sync requirement for checked-out venvs 2026-11-03 17:51:32 -05:00
beefcarver ba114599d0 fix(stage): name the presenter instead of a lone artwork glyph 2026-11-03 17:51:29 -05:00
beefcarver b31ad3f6fb feat(ui): add catalogue picker, schedule pane and per-provider hero 2026-11-03 17:51:27 -05:00
beefcarver 058d336a2c feat(tilderadio): add the tildeverse community stream as a catalogue 2026-11-03 17:51:23 -05:00
beefcarver 58e9202e25 test(providers): cover the registry and the app's catalogue independence
Registry lookup and its fallback for a stale slug, SomaFM branding and guessed mirrors, provider round-trip in the config, legacy-config migration and BEEFY_*/SOMAFM_* precedence - plus an end-to-end boot against a fake second provider, which is what actually proves the UI no longer knows about SomaFM.
2026-11-03 16:10:39 -05:00
beefcarver c4cc91ee1a feat(providers): add a pluggable station-catalogue seam
The app now holds one RadioProvider and asks it for stations, song history and stream URLs instead of importing SomaFM's HTTP client directly.

- models.py: provider-neutral Song/Station plus the shared parsers every catalogue needs (Station.provider/.uid keep ids unique once there is more than one).
- providers/base.py: the RadioProvider interface and the toolkit it needs in practice - httpx client factory, .pls/.m3u resolution with an 8h disk cache, and a default stream_candidates() strategy.
- providers/somafm/: everything SomaFM-specific (channels.json, songs/<id>.json, PLS layout, guessed icecast mirrors) behind SomaFMProvider.
- providers/__init__.py: registry, get_provider() and --provider, remembered in the config file.

Widgets render branding, splash copy and the help credits from the active provider, so a second catalogue needs no UI changes; with SomaFM selected the screen is byte-identical to before.
2026-11-03 16:10:30 -05:00
beefcarver e30ca09627 refactor!: rename somafm-tui to beefy-player
The app is now a general internet-radio player rather than a SomaFM client, so the package, console script, MPRIS identity, mpv namespaces, macOS tap names, XDG directories and docs all move to beefy-player.

Environment variables are now BEEFY_* (the old SOMAFM_* spellings are still honoured) and an existing ~/.config/somafm-tui/config.json is migrated on first run, so favourites and volume survive the rename.

The visible interface is untouched: same layout, same logo, same keys.
2026-11-03 16:10:25 -05:00
beefcarver 4bed90b489 docs(visualizer): document the six visualiser modes 2026-11-01 09:58:59 -05:00
beefcarver a42678ef30 test(visualizer): cover the new modes and the full v cycle 2026-11-01 09:58:57 -05:00
beefcarver 6d188c00bc feat(visualizer): add waterfall, radial and braille spectrum modes 2026-11-01 09:58:53 -05:00
beefcarver b9d3d7e809 test(spectrum): cover monitor recovery across tuning 2026-11-01 09:45:47 -05:00
beefcarver ca45303386 fix(spectrum): reset analyser on station change and retry SYNC 2026-11-01 09:45:43 -05:00
beefcarver d2b4f1bf64 fix(ui): keep Bandcamp link across history refreshes 2026-11-01 08:58:46 -05:00
beefcarver 3ae4dc3dab feat(bandcamp): simulate typed autocomplete searches 2026-11-01 08:58:43 -05:00
beefcarver 48e480d58c feat(spectrum): capture macOS output with a Core Audio process tap
SYNC mode on macOS needed a virtual loopback driver (BlackHole, Loopback,
Soundflower) routed through a Multi-Output Device, and shelled out to
'ffmpeg -f avfoundation' to read it. macOS 14.2 lets a process tap the
system mix directly, so do that instead: CATapDescription ->
AudioHardwareCreateProcessTap -> private aggregate device -> IOProc,
straight through ctypes. No driver, no helper binary, no ffmpeg.

The tap hands out native-rate interleaved float32, so the analyser can
now retune its window and band edges when the capture rate differs from
44.1 kHz, and mono-downmixes the frames before the FFT.

A tap that was never granted System Audio Recording permission is
created happily but only ever delivers digital silence: treat that as a
failed capture instead of flat bars, explain it in the status bar and
fall back to the stream tap. A tap that renders nothing *yet* (playback
still starting, or mpv pointed at another device) is not a permanent
failure either - the stream tap covers for a minute, then the tap is
retried.

Drop the avfoundation device enumeration, the loopback-name heuristics
and the microphone safeguards' tests, and document the new permission
and its diagnostics instead.
2026-10-30 22:53:29 -04:00
beefcarver 7042707e84 docs(config): note that the mpris setting covers all media integration
The JSON key stays 'mpris' for backwards compatibility, but it now also
governs the macOS Now Playing session and the media keys.
2026-10-30 18:43:33 -04:00
beefcarver 2570d9b854 docs(readme): document macOS support, Now Playing and loopback capture
Split the install instructions per platform, add the macOS microphone
permission and BlackHole/Multi-Output recipe for a synced spectrum,
describe the Now Playing integration and its limits, and note that config
and cache stay in the XDG locations. The in-app help text is now
platform-aware too, and the package metadata lists macOS.
2026-10-30 18:43:26 -04:00
beefcarver 06386d3e1e fix(art): keep iTerm2 inline images on screen
iTerm2 images live in the cell grid, unlike Kitty placements: repainting
the mosaic behind them erases the picture. Re-emit the image whenever the
mosaic identity (station + track) changes, even if the artwork bytes are
the same.

Also send doNotMoveCursor=1 to iTerm2 itself so the terminal does not
scroll or jump the cursor out from under Textual's compositor; WezTerm
keeps the previous payload since it is pickier about parameters.
2026-10-30 18:43:22 -04:00
beefcarver 6eb0c6b6fe fix(artwork): ignore TMUX/ZELLIJ/STY values that mean 'not set'
Some shells and wrappers export ZELLIJ=0 or TMUX=0 outside a multiplexer.
A plain truthiness check treated that as 'inside tmux/zellij' and
downgraded iTerm2/Kitty to the ANSI mosaic, so real artwork silently
disappeared. Only meaningful values now count.
2026-10-30 18:43:16 -04:00
beefcarver 69b46b9035 feat(spectrum): capture macOS output with an avfoundation loopback
SYNC mode was Linux-only: it shelled out to parec. On macOS there is no
PulseAudio, so the analyser always fell back to the drifting stream tap.

Capture with 'ffmpeg -f avfoundation' from a loopback input instead
(BlackHole, Loopback, Soundflower, Multi-Output/Aggregate devices are
auto-detected; SOMAFM_SPECTRUM_DEVICE accepts a name or index). Plain
microphones are never auto-selected.

A loopback that nothing is routed into delivers endless digital silence,
so treat ~6 s of all-zero blocks as a dead monitor and fall back to the
stream tap rather than showing frozen bars. parec handling is unchanged.
2026-10-30 18:43:10 -04:00
beefcarver 9c722c5f61 feat(macos): drive the system Now Playing session through mpv
macOS has no D-Bus/MPRIS: the audio engine is what the OS media UI talks
to. mpv's Cocoa backend registers with MPRemoteCommandCenter and
MPNowPlayingInfoCenter, so it already publishes the live ICY title and
receives the media keys.

Add a platform-neutral media session (mediasession.py) so the app keeps a
single interface: MprisService on D-Bus platforms, NowPlayingSession on
macOS. Map mpv's NEXT/PREV/PLAY/PLAYONLY/PAUSEONLY/STOP keys onto
script-messages and read them back as client-message IPC events, so the
hardware media keys and Control Center drive the TUI (station changes,
play/pause/stop) instead of fighting it.

--no-mpris (now also --no-media) disables the media keys too. macOS-only
mpv options are gated on sys.platform so the Linux command line is
unchanged; mpv aborts on unknown flags.
2026-10-30 18:43:03 -04:00
beefcarver d83a9ef03d fix(mpris): keep the service importable without dbus-next
The stub dbus_property() returned a bare function, so the read/write
properties (@Volume.setter and friends) raised AttributeError at import
time. That made any host without dbus-next -- macOS, Windows, minimal
containers -- crash before the TUI could start, because app.py imports
this module unconditionally.

Replace the stub with a descriptor that supports .getter()/.setter(), and
give the service a short label for the status bar.
2026-10-30 18:42:50 -04:00
beefcarver 876057bf68 docs(readme): show demo gif without asciinema link 2026-10-30 15:27:26 +00:00
beefcarver 8393432f97 docs(readme): document Bandcamp track links 2026-10-30 15:12:37 +00:00