feat(bassdrive): add the drum & bass relay and a shared wordmark font
This commit is contained in:
1 parent
84ec3b08f6
commit
50a9a735f7
14 files changed
+1166
-79
No files matched your search
@@ -8,9 +8,10 @@ own ANSI palette on the terminal's default background so it fits any theme
|
||||
(including transparent terminals).
|
||||
|
||||
The station catalogue is pluggable. Beefy Player ships with
|
||||
**[SomaFM](https://somafm.com)** (≈50 curated channels) and
|
||||
**[SomaFM](https://somafm.com)** (≈50 curated channels),
|
||||
**[tilderadio](https://tilderadio.org)** (the tildeverse's single community
|
||||
stream), and the UI never talks to a station catalogue directly: it asks a
|
||||
stream) and **[Bassdrive](https://bassdrive.com)** (worldwide drum & bass), and
|
||||
the UI never talks to a station catalogue directly: it asks a
|
||||
[provider](#providers) for stations, song history and stream URLs. Press `p` to
|
||||
switch catalogue without restarting. Adding another internet-radio catalogue is
|
||||
a self-contained module, not a rewrite.
|
||||
@@ -41,7 +42,9 @@ implemented here from public endpoints and open protocols.
|
||||
iTerm2 inline-image protocol. Images occupy exactly the ON AIR art cell box
|
||||
and fall back to the procedural ANSI mosaic for other terminals, failed
|
||||
loads, or blocked graphics passthrough. Images are cached in
|
||||
`~/.cache/beefy-player/artwork`.
|
||||
`~/.cache/beefy-player/artwork`, letterboxed rather than cropped, and an
|
||||
image that carries an alpha channel is composited onto black — a transparent
|
||||
PNG drawn over the mosaic reads as noise behind a station logo.
|
||||
- **Now playing** — artist, title, album, DJ, listeners, format/bitrate and
|
||||
"played N minutes ago", with procedural ANSI-shaded album art that changes
|
||||
per track. When the current track can be found on Bandcamp, a clickable
|
||||
@@ -57,9 +60,10 @@ implemented here from public endpoints and open protocols.
|
||||
- **Pluggable catalogues** — one `providers` module per radio network, with a
|
||||
`p` picker in the UI, `--provider` on the command line, and the choice
|
||||
remembered per catalogue (including the last station you tuned).
|
||||
- **A hero per catalogue** — the top-left art comes from the provider: SomaFM's
|
||||
block wordmark, or tilderadio's radio icon converted from its logo PNG to
|
||||
half-block ANSI by `tools/ansi_art.py`.
|
||||
- **A hero per catalogue** — the top-left art is the catalogue's own name in a
|
||||
shared half-block font: SomaFM in the spectrum gradient, tilderadio in the
|
||||
terminal's default colour, Bassdrive in its two blues. Every hero is three
|
||||
rows tall, so the header keeps one height.
|
||||
- **Schedule pane** — a single-stream catalogue gets a broadcast schedule
|
||||
instead of a list of one station: who is on air now, since/until when, and
|
||||
the upcoming shows grouped by day.
|
||||
@@ -193,35 +197,45 @@ layout and the guessed icecast mirrors all live in
|
||||
`beefy_player/providers/somafm/`. Nothing outside that package imports it; the
|
||||
app only ever holds a `RadioProvider`.
|
||||
|
||||
Two catalogues ship today:
|
||||
Three catalogues ship today:
|
||||
|
||||
| slug | catalogue | station list | history | streams |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `somafm` | [SomaFM](https://somafm.com) | `channels.json` (≈50) | `songs/<station>.json` | `.pls` → icecast mirrors |
|
||||
| `tilderadio` | [tilderadio](https://tilderadio.org) | `/api/now/` (1) | `now_playing` + `history[]` | AzuraCast mounts (mp3/ogg, 320k) |
|
||||
| `bassdrive` | [Bassdrive](https://bassdrive.com) | icecast status (1) | rolling `display-title` | three mounts + mirrors |
|
||||
|
||||
They are deliberately different shapes: one has fifty channels and PLS
|
||||
playlists, the other has a single stream whose whole catalogue arrives in the
|
||||
same response as its history. That is the seam working.
|
||||
They are deliberately different shapes: fifty channels with PLS playlists, a
|
||||
single AzuraCast stream that carries its own history, and an Icecast relay whose
|
||||
"now playing" is the *show* rather than the track. That is the seam working.
|
||||
|
||||
The tilderadio package also exercises the two optional capabilities: its left
|
||||
pane is the broadcast schedule (`/api/schedule/`, with the live DJ coming from
|
||||
`/api/now/`) rather than a list containing one station, and its header art is
|
||||
the station's own radio icon converted to half blocks. The icon is generated,
|
||||
not hand-drawn — regenerate it after the logo changes:
|
||||
The single-stream catalogues also use the optional capabilities: their left
|
||||
pane is the broadcast schedule rather than a list containing one station, and
|
||||
they opt out of Bandcamp lookups. Bassdrive is the extreme case — Icecast
|
||||
reports one title and keeps no history, so the provider maps the title to
|
||||
`Station.dj`/`presenter` and keeps a rolling record of the shows it has heard.
|
||||
|
||||
```bash
|
||||
uv run python tools/ansi_art.py \
|
||||
--url https://tilderadio.org/logos/tilderadio.png \
|
||||
--cols 22 --rows 7 \
|
||||
--module beefy_player/providers/tilderadio/hero.py
|
||||
### Header wordmarks
|
||||
|
||||
All three heroes come from one font, `beefy_player/providers/wordmark.py`:
|
||||
four-column capitals six pixels tall, emitted as three rows of half blocks
|
||||
(`▀`/`▄`/`█`) — the shape of the original SomaFM logo. A provider's `hero.py`
|
||||
is one line of data:
|
||||
|
||||
```python
|
||||
# beefy_player/providers/bassdrive/hero.py
|
||||
HERO = render("BASSDRIVE", ["bright_cyan"] * 4 + ["cyan"] * 5)
|
||||
```
|
||||
|
||||
`tools/ansi_art.py` crops the transparent margin, resizes to 22×14 pixels (two
|
||||
per text cell), quantises to the stock 16-colour ANSI palette and emits
|
||||
run-length encoded rich markup — so the art follows the terminal's palette
|
||||
instead of pinning RGB values. `--preview out.png` draws the result, which is
|
||||
far easier to judge than ▀ and ▄.
|
||||
`render()` takes one optional style per character and groups equal neighbours
|
||||
into markup spans; with no styles the wordmark inherits the terminal's default
|
||||
foreground (tilderadio). Add a glyph to `GLYPHS` when a catalogue needs a letter
|
||||
the font does not have yet — `▀█▀` is the I, one pixel thick on purpose.
|
||||
|
||||
Image logos are a different problem: `tools/ansi_art.py` converts a PNG into
|
||||
half-block art (`--key-white` for logos on white, `--preview out.png` to see the
|
||||
result). No shipped catalogue needs it today, but it is the tool to reach for
|
||||
when a station's identity is a picture rather than a word.
|
||||
|
||||
Adding a catalogue:
|
||||
|
||||
@@ -347,8 +361,10 @@ beefy_player/
|
||||
config.py XDG paths, settings, BEEFY_* env helpers
|
||||
providers/
|
||||
base.py RadioProvider interface + HTTP/playlist toolkit
|
||||
wordmark.py the half-block font every hero is drawn with
|
||||
somafm/ the SomaFM catalogue (API, hero, bundled fallback list)
|
||||
tilderadio/ the tilderadio catalogue (one station, hero, schedule)
|
||||
bassdrive/ Bassdrive (Icecast status, show schedule, rolling history)
|
||||
player.py mpv over a Unix-socket JSON IPC channel
|
||||
spectrum.py analyser (parec / Core Audio tap / ffmpeg / sim)
|
||||
macos_tap.py macOS output capture (ctypes + Core Audio)
|
||||
@@ -382,6 +398,14 @@ tools/
|
||||
the provider in place: the old stream is stopped, workers for the previous
|
||||
catalogue are cancelled, the hero height and left pane follow the new
|
||||
provider, and the station list (or schedule) reloads from cache first.
|
||||
- `beefy_player/providers/bassdrive/api.py` — Icecast's keyless
|
||||
`status-json.xsl` (mounts, bitrates, listeners, `display-title`) plus the
|
||||
homepage schedule grid, whose slots carry absolute `<local-time datetime>`
|
||||
values. A memoised fetch and a lock keep a cold start to one homepage
|
||||
download; the display-title's show is matched against the running slot for
|
||||
"live from <city>".
|
||||
- `beefy_player/providers/wordmark.py` — the half-block capitals behind every
|
||||
hero, with per-character style spans.
|
||||
- `beefy_player/widgets/schedule.py` — the left pane for single-stream
|
||||
catalogues: the live slot with its `since`/`until` window, then upcoming
|
||||
shows grouped by local day, with `… +N more` when the pane runs out.
|
||||
@@ -419,7 +443,8 @@ tools/
|
||||
~/.cache/beefy-player/channels-<id>.json last good list, other catalogues
|
||||
~/.cache/beefy-player/schedule-<id>.json last good broadcast schedule
|
||||
~/.cache/beefy-player/playlists.json resolved stream URLs
|
||||
~/.cache/beefy-player/songs/<id>.json song history snapshots
|
||||
~/.cache/beefy-player/songs/<id>.json SomaFM song history snapshots
|
||||
~/.cache/beefy-player/songs/<p>-<id>.json history for every other catalogue
|
||||
~/.cache/beefy-player/artwork/<sha1> cached album/channel artwork
|
||||
```
|
||||
|
||||
@@ -488,6 +513,8 @@ so favourites and volume survive the rename.
|
||||
- SomaFM is listener-supported: if you enjoy it, [donate](https://somafm.com/support/).
|
||||
- tilderadio is a volunteer-run community station: [drop by](https://tilderadio.org),
|
||||
or show up on the [schedule](https://tilderadio.org/schedule/) with a set.
|
||||
- Bassdrive is free and listener-supported too: [bassdrive.com](https://bassdrive.com)
|
||||
has the shows, archives and the shop that keeps the relays running.
|
||||
|
||||
## NixOS (optional aside)
|
||||
|
||||
|
||||
Reference in new issue
Block a user