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.
This commit is contained in:
1 parent
06386d3e1e
commit
2570d9b854
3 files changed
+153
-42
No files matched your search
@@ -3,13 +3,17 @@
|
|||||||

|

|
||||||
|
|
||||||
A flashy terminal radio for **[SomaFM](https://somafm.com)** — a live spectrum
|
A flashy terminal radio for **[SomaFM](https://somafm.com)** — a live spectrum
|
||||||
analyser, song history, and full desktop media integration over MPRIS, drawn
|
analyser, song history, and full desktop media integration, drawn with your
|
||||||
with your terminal's own ANSI palette on the terminal's default background so
|
terminal's own ANSI palette on the terminal's default background so it fits
|
||||||
it fits any theme (including transparent terminals).
|
any theme (including transparent terminals).
|
||||||
|
|
||||||
|
It runs the same way on **Linux** (PulseAudio/PipeWire) and **macOS**
|
||||||
|
(CoreAudio, iTerm2/Kitty/WezTerm): MPRIS on the desktop bus where that exists,
|
||||||
|
macOS Now Playing (Control Center and the media keys) where it does not.
|
||||||
|
|
||||||
No existing SomaFM client is used: the station list, playlists, song history,
|
No existing SomaFM client is used: the station list, playlists, song history,
|
||||||
audio control, spectrum visualiser and MPRIS service are all implemented here from
|
audio control, spectrum visualiser and media-session integration are all
|
||||||
the public SomaFM endpoints and open protocols.
|
implemented here from the public SomaFM endpoints and open protocols.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -17,15 +21,17 @@ the public SomaFM endpoints and open protocols.
|
|||||||
gradient taken from your terminal's ANSI palette and a symmetric mirrored
|
gradient taken from your terminal's ANSI palette and a symmetric mirrored
|
||||||
spectrum; or a full-height oscilloscope. Three modes (`v`).
|
spectrum; or a full-height oscilloscope. Three modes (`v`).
|
||||||
- **Synced spectrum, not a guess** — by default the analyser records your
|
- **Synced spectrum, not a guess** — by default the analyser records your
|
||||||
actual PulseAudio/PipeWire output monitor with `parec`, so the bars follow
|
actual audio output, so the bars follow what you hear (`SYNC`): the
|
||||||
what you hear (`SYNC`). Without a monitor it falls back to a muted second
|
PulseAudio/PipeWire monitor via `parec` on Linux, an `ffmpeg`/avfoundation
|
||||||
`ffmpeg` decode of the stream (`FFT`, can drift), then to a synthesized
|
loopback input (BlackHole, Loopback, Soundflower, …) on macOS. Without an
|
||||||
display (`SIM`).
|
output capture it falls back to a muted second `ffmpeg` decode of the stream
|
||||||
- **Real artwork where supported** — Kitty uses the kitty graphics protocol;
|
(`FFT`, can drift), then to a synthesized display (`SIM`).
|
||||||
iTerm2 and WezTerm use their native iTerm2 inline-image protocol. Images
|
- **Real artwork on iTerm2, Kitty and WezTerm** — Kitty uses the kitty
|
||||||
occupy exactly the ON AIR art cell box and fall back to the procedural ANSI
|
graphics protocol; iTerm2 (including on macOS) and WezTerm use their native
|
||||||
mosaic for other terminals, failed loads, or blocked graphics passthrough.
|
iTerm2 inline-image protocol. Images occupy exactly the ON AIR art cell box
|
||||||
Images are cached in `~/.cache/somafm-tui/artwork`.
|
and fall back to the procedural ANSI mosaic for other terminals, failed
|
||||||
|
loads, or blocked graphics passthrough. Images are cached in
|
||||||
|
`~/.cache/somafm-tui/artwork`.
|
||||||
- **Now playing** — artist, title, album, DJ, listeners, format/bitrate and
|
- **Now playing** — artist, title, album, DJ, listeners, format/bitrate and
|
||||||
"played N minutes ago", with procedural ANSI-shaded album art that changes
|
"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
|
per track. When the current track can be found on Bandcamp, a clickable
|
||||||
@@ -34,10 +40,13 @@ the public SomaFM endpoints and open protocols.
|
|||||||
- **Every station** — the whole `channels.json` roster (≈50 channels), live
|
- **Every station** — the whole `channels.json` roster (≈50 channels), live
|
||||||
listener counts, genre search.
|
listener counts, genre search.
|
||||||
- **Favourites** that float to the top, remembered between runs.
|
- **Favourites** that float to the top, remembered between runs.
|
||||||
- **MPRIS** — an `org.mpris.MediaPlayer2.somafmtui.instance<pid>` player exposes
|
- **Desktop media integration** — on Linux an
|
||||||
|
`org.mpris.MediaPlayer2.somafmtui.instance<pid>` player exposes
|
||||||
`PlaybackStatus`, `Metadata` (title/artist/album/artUrl), `Volume`, and
|
`PlaybackStatus`, `Metadata` (title/artist/album/artUrl), `Volume`, and
|
||||||
`Play`/`Pause`/`PlayPause`/`Stop`/`Next`/`Previous`. Desktop media widgets and
|
`Play`/`Pause`/`PlayPause`/`Stop`/`Next`/`Previous`, so GNOME/KDE widgets,
|
||||||
media keys pick it up automatically.
|
lock screens, `playerctl` and the media keys pick it up. On macOS the audio
|
||||||
|
engine (mpv) publishes to the system **Now Playing** session, so Control
|
||||||
|
Center, the menu bar and the keyboard media keys drive the app the same way.
|
||||||
- **Theme-aware** — every accent is emitted as native ANSI (not RGB), and the
|
- **Theme-aware** — every accent is emitted as native ANSI (not RGB), and the
|
||||||
background is the terminal default (`SGR 49`), so transparent terminals stay
|
background is the terminal default (`SGR 49`), so transparent terminals stay
|
||||||
transparent and the TUI adopts your shell/terminal colour scheme.
|
transparent and the TUI adopts your shell/terminal colour scheme.
|
||||||
@@ -48,17 +57,32 @@ the public SomaFM endpoints and open protocols.
|
|||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
`somafm-tui` is a normal Python application: there is nothing to compile and
|
`somafm-tui` is a normal Python application: there is nothing to compile and
|
||||||
no distro-specific packaging. It runs on any Linux distribution with Python
|
no distro-specific packaging. It runs on any Linux distribution or macOS with
|
||||||
**3.10+** and a few command-line audio tools on `$PATH`.
|
Python **3.10+** and a few command-line audio tools on `$PATH`.
|
||||||
|
|
||||||
| tool | why |
|
| tool | why |
|
||||||
| ---- | --- |
|
| ---- | --- |
|
||||||
| `python3` | runtime (3.10 or newer; no exact version pin) |
|
| `python3` | runtime (3.10 or newer; no exact version pin) |
|
||||||
| [`uv`](https://docs.astral.sh/uv/) | creates the venv and installs every Python dependency from `uv.lock` |
|
| [`uv`](https://docs.astral.sh/uv/) | creates the venv and installs every Python dependency from `uv.lock` |
|
||||||
| `mpv` | playback and JSON IPC control |
|
| `mpv` | playback and JSON IPC control |
|
||||||
| `ffmpeg` | muted stream decode for the fallback analyser |
|
| `ffmpeg` | muted stream decode for the fallback analyser, and the macOS output capture |
|
||||||
| `parec` / `pactl` | synced monitor capture for the spectrum (`pulseaudio-utils`; works with PipeWire) |
|
| `parec` / `pactl` *(Linux)* | synced monitor capture for the spectrum (`pulseaudio-utils`; works with PipeWire) |
|
||||||
| D-Bus session bus | MPRIS desktop media integration (`playerctl` is handy for testing) |
|
| a loopback audio device *(macOS, optional)* | synced output capture for the spectrum: BlackHole, Loopback, Soundflower, … |
|
||||||
|
| D-Bus session bus *(Linux)* | MPRIS desktop media integration (`playerctl` is handy for testing) |
|
||||||
|
|
||||||
|
On macOS the media integration needs nothing extra: mpv is what the system's
|
||||||
|
Now Playing session (Control Center, media keys) talks to.
|
||||||
|
|
||||||
|
Install `uv` once (skip if it is already there):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
`brew install uv`, `pipx install uv`, or a distro package also work. If your
|
||||||
|
system Python is older than 3.10, let uv manage one: `uv python install 3.12`.
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
Install these with your package manager. Names are usually:
|
Install these with your package manager. Names are usually:
|
||||||
|
|
||||||
@@ -80,14 +104,39 @@ For other distributions, install the equivalents. On a headless machine you
|
|||||||
can skip the audio/D-Bus tools and use `--no-audio` for a UI-only demo, though
|
can skip the audio/D-Bus tools and use `--no-audio` for a UI-only demo, though
|
||||||
the synced spectrum needs `parec`/`pactl`.
|
the synced spectrum needs `parec`/`pactl`.
|
||||||
|
|
||||||
Install `uv` once with the official installer:
|
### macOS
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
brew install mpv ffmpeg uv # ffplay ships with ffmpeg
|
||||||
```
|
```
|
||||||
|
|
||||||
`pipx install uv`, `brew install uv`, or a distro package also work. If your
|
That is enough to run: playback, artwork in iTerm2/Kitty/WezTerm, the muted
|
||||||
distro's Python is older than 3.10, let uv manage one: `uv python install 3.12`.
|
`ffmpeg` spectrum tap and the macOS Now Playing integration all work out of the
|
||||||
|
box. The first capture attempt makes macOS ask for **microphone** access for
|
||||||
|
whatever terminal you use — that permission is what lets `ffmpeg` read the
|
||||||
|
loopback device.
|
||||||
|
|
||||||
|
For a *synced* spectrum (`SYNC` instead of `FFT`) macOS needs an output
|
||||||
|
loopback, because CoreAudio does not let an ordinary process record the
|
||||||
|
speakers directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install blackhole-2ch # or Loopback / Soundflower
|
||||||
|
```
|
||||||
|
|
||||||
|
Then either
|
||||||
|
|
||||||
|
1. create a **Multi-Output Device** in *Audio MIDI Setup* containing both your
|
||||||
|
speakers/headphones and *BlackHole 2ch*, and select it as the system output,
|
||||||
|
or
|
||||||
|
2. set *BlackHole 2ch* as the output and monitor the audio elsewhere.
|
||||||
|
|
||||||
|
The app auto-detects loopback-style inputs (BlackHole, Loopback, Soundflower,
|
||||||
|
“Multi-Output”, “Aggregate”) and never grabs a plain microphone by accident.
|
||||||
|
Pick one explicitly with `SOMAFM_SPECTRUM_DEVICE` (name or avfoundation index,
|
||||||
|
see `ffmpeg -f avfoundation -list_devices true -i ""`). If the loopback turns
|
||||||
|
out to be silent (nothing routed into it), the analyser notices after a few
|
||||||
|
seconds and falls back to `FFT` instead of showing dead bars.
|
||||||
|
|
||||||
Terminal image rendering uses [Pillow](https://python-pillow.org/) and is
|
Terminal image rendering uses [Pillow](https://python-pillow.org/) and is
|
||||||
implemented directly (Kitty TGP + iTerm2 OSC 1337) — no terminal probing.
|
implemented directly (Kitty TGP + iTerm2 OSC 1337) — no terminal probing.
|
||||||
@@ -126,7 +175,7 @@ uv run somafm-tui --play # start the last station immediately
|
|||||||
uv run somafm-tui --station defcon # pick a station
|
uv run somafm-tui --station defcon # pick a station
|
||||||
uv run somafm-tui --volume 55
|
uv run somafm-tui --volume 55
|
||||||
uv run somafm-tui --offline # no network; cached/bundled stations
|
uv run somafm-tui --offline # no network; cached/bundled stations
|
||||||
uv run somafm-tui --no-mpris # don't announce over D-Bus
|
uv run somafm-tui --no-mpris # no MPRIS / Now Playing, no media keys
|
||||||
uv run somafm-tui --list # print station ids
|
uv run somafm-tui --list # print station ids
|
||||||
uv run somafm-tui --no-audio # UI demo without mpv/ffmpeg
|
uv run somafm-tui --no-audio # UI demo without mpv/ffmpeg
|
||||||
uv run somafm-tui --spectrum-source monitor # force synced output capture
|
uv run somafm-tui --spectrum-source monitor # force synced output capture
|
||||||
@@ -158,9 +207,9 @@ The UI wants roughly **84×24**; it also works smaller, just less pretty.
|
|||||||
| `?` | help |
|
| `?` | help |
|
||||||
| `q` | quit |
|
| `q` | quit |
|
||||||
|
|
||||||
## Desktop media widgets (MPRIS)
|
## Desktop media widgets (MPRIS / Now Playing)
|
||||||
|
|
||||||
While playing, the app exports
|
**Linux** — while playing, the app exports
|
||||||
`org.mpris.MediaPlayer2.somafmtui.instance<pid>` on the **session bus**. Check it:
|
`org.mpris.MediaPlayer2.somafmtui.instance<pid>` on the **session bus**. Check it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -173,6 +222,19 @@ playerctl play-pause
|
|||||||
channel logo. If there is no session bus (SSH without `dbus-run-session`,
|
channel logo. If there is no session bus (SSH without `dbus-run-session`,
|
||||||
containers, …) MPRIS is skipped and the status bar shows `mpris off`.
|
containers, …) MPRIS is skipped and the status bar shows `mpris off`.
|
||||||
|
|
||||||
|
**macOS** — mpv owns the system Now Playing session, so Control Center, the
|
||||||
|
menu-bar widget and the keyboard media keys (▶︎/⏸, ⏭, ⏮) all target this app
|
||||||
|
while it is playing. mpv publishes the live stream title (the same
|
||||||
|
“Artist - Title” that SomaFM sends with the audio), and the media keys are
|
||||||
|
routed back into the TUI, so `⏭`/`⏮` really change station and the panel, the
|
||||||
|
spectrum and the station list stay in sync. The status bar shows
|
||||||
|
`nowplaying on`. There is no D-Bus and no extra dependency involved, and
|
||||||
|
`--no-mpris` (also spelled `--no-media`) turns the media keys off again.
|
||||||
|
|
||||||
|
> macOS shows one Now Playing app at a time. If Music/Safari/another mpv is
|
||||||
|
> playing, the media keys go there instead — quit it or pause it to hand the
|
||||||
|
> keys back.
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
- `somafm_tui/api.py` — async `httpx` client for:
|
- `somafm_tui/api.py` — async `httpx` client for:
|
||||||
@@ -183,14 +245,19 @@ containers, …) MPRIS is skipped and the status bar shows `mpris off`.
|
|||||||
offline.
|
offline.
|
||||||
- `somafm_tui/player.py` — mpv over a Unix-socket JSON IPC channel. Observes
|
- `somafm_tui/player.py` — mpv over a Unix-socket JSON IPC channel. Observes
|
||||||
`pause`/`volume`, handles mirror failover on `end-file`, auto-restarts a dead
|
`pause`/`volume`, handles mirror failover on `end-file`, auto-restarts a dead
|
||||||
engine, and uses `SIGSTOP`/`SIGCONT` when falling back to `ffplay`.
|
engine, and uses `SIGSTOP`/`SIGCONT` when falling back to `ffplay`. On macOS
|
||||||
|
it also asks mpv to publish Now Playing and maps the media keys onto
|
||||||
|
`script-message`s that come back over the same IPC socket.
|
||||||
- `somafm_tui/spectrum.py` — 44.1 kHz mono s16le analyser. Prefers capturing
|
- `somafm_tui/spectrum.py` — 44.1 kHz mono s16le analyser. Prefers capturing
|
||||||
the real sink monitor with `parec` (SYNC); otherwise a muted low-latency
|
the real output: `parec` (PulseAudio/PipeWire monitor) on Linux, or
|
||||||
`ffmpeg` decode of the stream (FFT). Hann-windowed 2048-point FFT, log band
|
`ffmpeg -f avfoundation` on a loopback input on macOS (SYNC). Otherwise a
|
||||||
grouping, auto-gain, fast attack/decay smoothing, peak hold and waveform
|
muted low-latency `ffmpeg` decode of the stream (FFT). Hann-windowed
|
||||||
downsampling.
|
2048-point FFT, log band grouping, auto-gain, fast attack/decay smoothing,
|
||||||
|
peak hold and waveform downsampling.
|
||||||
|
- `somafm_tui/mediasession.py` — picks the desktop media backend (MPRIS on
|
||||||
|
Linux, Now Playing on macOS) so the app has one platform-neutral interface.
|
||||||
- `somafm_tui/mpris.py` — `dbus-next` MPRIS service in its own thread with a
|
- `somafm_tui/mpris.py` — `dbus-next` MPRIS service in its own thread with a
|
||||||
thread-safe command queue back into the UI.
|
thread-safe command queue back into the UI (only loaded on D-Bus platforms).
|
||||||
- `somafm_tui/bandcamp.py` — searches Bandcamp's public autocomplete API for
|
- `somafm_tui/bandcamp.py` — searches Bandcamp's public autocomplete API for
|
||||||
the now-playing track (artist + track first, then artist + album, then the
|
the now-playing track (artist + track first, then artist + album, then the
|
||||||
album page) and returns a track URL when there is a match.
|
album page) and returns a track URL when there is a match.
|
||||||
@@ -208,14 +275,21 @@ containers, …) MPRIS is skipped and the status bar shows `mpris off`.
|
|||||||
~/.cache/somafm-tui/artwork/<sha1> cached album/channel artwork
|
~/.cache/somafm-tui/artwork/<sha1> cached album/channel artwork
|
||||||
```
|
```
|
||||||
|
|
||||||
|
These stay in the XDG locations on every platform (macOS included), so the
|
||||||
|
same dotfiles/config work everywhere; `XDG_CONFIG_HOME`, `XDG_CACHE_HOME` and
|
||||||
|
`XDG_RUNTIME_DIR` are honoured when set.
|
||||||
|
|
||||||
## Notes / troubleshooting
|
## Notes / troubleshooting
|
||||||
|
|
||||||
- **Artwork shows the mosaic?** Real images are auto-enabled for Kitty,
|
- **Artwork shows the mosaic?** Real images are auto-enabled for Kitty,
|
||||||
iTerm2 and WezTerm. Force a protocol with `SOMAFM_IMAGES=tgp` (Kitty/TGP) or
|
iTerm2 and WezTerm. Force a protocol with `SOMAFM_IMAGES=tgp` (Kitty/TGP) or
|
||||||
`SOMAFM_IMAGES=iterm2` (iTerm2/WezTerm); `SOMAFM_IMAGES=0` forces the ANSI
|
`SOMAFM_IMAGES=iterm2` (iTerm2/WezTerm); `SOMAFM_IMAGES=0` forces the ANSI
|
||||||
mosaic. Kitty inside tmux is attempted through tmux passthrough (enable
|
mosaic. iTerm2 on macOS is detected from `TERM_PROGRAM`/`LC_TERMINAL`, and
|
||||||
`set -g allow-passthrough on`); iTerm2/WezTerm inside tmux default to the
|
empty or `0` values in `TMUX`/`ZELLIJ`/`STY` (exported by some wrappers) are
|
||||||
mosaic. Images are cached under `~/.cache/somafm-tui/artwork`.
|
ignored so they cannot downgrade it. Kitty inside tmux is attempted through
|
||||||
|
tmux passthrough (enable `set -g allow-passthrough on`); iTerm2/WezTerm
|
||||||
|
inside tmux default to the mosaic. Images are cached under
|
||||||
|
`~/.cache/somafm-tui/artwork`.
|
||||||
- **Clock shows UTC?** The clock and song history use the system zone from
|
- **Clock shows UTC?** The clock and song history use the system zone from
|
||||||
`/etc/localtime`; a non-UTC `TZ` overrides it. A container or dev shell can
|
`/etc/localtime`; a non-UTC `TZ` overrides it. A container or dev shell can
|
||||||
inherit a build-time `TZ=UTC`, which the app deliberately ignores; bind the
|
inherit a build-time `TZ=UTC`, which the app deliberately ignores; bind the
|
||||||
@@ -224,10 +298,11 @@ containers, …) MPRIS is skipped and the status bar shows `mpris off`.
|
|||||||
- **MPRIS says off/error?** No session bus. Run from your desktop session
|
- **MPRIS says off/error?** No session bus. Run from your desktop session
|
||||||
(the shell profile picks up `$XDG_RUNTIME_DIR/bus`). `dbus-run-session -- uv
|
(the shell profile picks up `$XDG_RUNTIME_DIR/bus`). `dbus-run-session -- uv
|
||||||
run somafm-tui` starts a private bus for testing, but desktop widgets won't
|
run somafm-tui` starts a private bus for testing, but desktop widgets won't
|
||||||
see it.
|
see it. macOS does not use MPRIS at all — see “Now Playing” above.
|
||||||
- **No audio?** `somafm-tui` needs `mpv` (preferred) or `ffplay`. Without
|
- **No audio?** `somafm-tui` needs `mpv` (preferred) or `ffplay`. Without
|
||||||
either, the UI still runs and tells you. Extra mpv args can be injected with
|
either, the UI still runs and tells you. Extra mpv args can be injected with
|
||||||
`SOMAFM_MPV_ARGS` (e.g. `SOMAFM_MPV_ARGS='--audio-device=...'`).
|
`SOMAFM_MPV_ARGS` (e.g. `SOMAFM_MPV_ARGS='--audio-device=...'`, which is the
|
||||||
|
macOS way to choose an output device).
|
||||||
- **Spectrum says `FFT` instead of `SYNC`?** `parec`/`pactl` are unavailable,
|
- **Spectrum says `FFT` instead of `SYNC`?** `parec`/`pactl` are unavailable,
|
||||||
the monitor device failed, or you forced the stream tap. The bars are still
|
the monitor device failed, or you forced the stream tap. The bars are still
|
||||||
real audio, but the second stream can drift relative to playback. Use
|
real audio, but the second stream can drift relative to playback. Use
|
||||||
@@ -240,6 +315,20 @@ containers, …) MPRIS is skipped and the status bar shows `mpris off`.
|
|||||||
- **`SYNC` but the bars are flat?** mpv may be playing to a different sink (or
|
- **`SYNC` but the bars are flat?** mpv may be playing to a different sink (or
|
||||||
directly to ALSA). Point `SOMAFM_SPECTRUM_DEVICE` at that sink's monitor, or
|
directly to ALSA). Point `SOMAFM_SPECTRUM_DEVICE` at that sink's monitor, or
|
||||||
use `--spectrum-source stream`.
|
use `--spectrum-source stream`.
|
||||||
|
- **macOS: spectrum stays `FFT`?** Install a loopback (BlackHole/Loopback) and
|
||||||
|
route your speakers through a Multi-Output Device, then check
|
||||||
|
`ffmpeg -f avfoundation -list_devices true -i ""` lists it. The first capture
|
||||||
|
triggers a macOS microphone-permission prompt; grant it to your terminal
|
||||||
|
(System Settings → Privacy & Security → Microphone), otherwise ffmpeg cannot
|
||||||
|
open the device and the analyser stays on `FFT`.
|
||||||
|
- **macOS: media keys do nothing?** Another app currently owns the Now Playing
|
||||||
|
session, or the app was started with `--no-mpris`, or the audio backend fell
|
||||||
|
back to `ffplay` (mpv is required for Now Playing). The status bar shows
|
||||||
|
`nowplaying on` when the integration is armed.
|
||||||
|
- **macOS: Control Center shows only a title?** mpv publishes the stream's live
|
||||||
|
“Artist - Title” as the Now Playing title; artist/album fields and artwork are
|
||||||
|
not filled in by macOS for plain audio streams. The TUI itself always shows
|
||||||
|
the full metadata and album art.
|
||||||
- **Bandwidth** is unchanged in SYNC mode (the monitor capture creates no
|
- **Bandwidth** is unchanged in SYNC mode (the monitor capture creates no
|
||||||
second stream). The FFT fallback opens one extra stream; if you are on a
|
second stream). The FFT fallback opens one extra stream; if you are on a
|
||||||
metered link, use `--spectrum-source monitor` (or `--no-audio` for a UI-only
|
metered link, use `--spectrum-source monitor` (or `--no-audio` for a UI-only
|
||||||
|
|||||||
+4
-1
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "somafm-tui"
|
name = "somafm-tui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "A flashy terminal radio for SomaFM with a live spectrum analyser, MPRIS support and song history"
|
description = "A flashy terminal radio for SomaFM with a live spectrum analyser, MPRIS / macOS Now Playing support and song history"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "WTFPL"
|
license = "WTFPL"
|
||||||
license-files = ["LICENSE"]
|
license-files = ["LICENSE"]
|
||||||
@@ -17,6 +17,9 @@ classifiers = [
|
|||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: 3.13",
|
"Programming Language :: Python :: 3.13",
|
||||||
"Topic :: Multimedia :: Sound/Audio",
|
"Topic :: Multimedia :: Sound/Audio",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"Operating System :: MacOS",
|
||||||
|
"Environment :: MacOS X",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"textual>=8.0,<9",
|
"textual>=8.0,<9",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
from rich.console import Group
|
from rich.console import Group
|
||||||
@@ -31,11 +32,14 @@ HELP_ROWS: tuple[tuple[str, str], ...] = (
|
|||||||
("q", "quit"),
|
("q", "quit"),
|
||||||
)
|
)
|
||||||
|
|
||||||
ABOUT = (
|
ABOUT_COMMON = (
|
||||||
(
|
(
|
||||||
"Stations and song history come from somafm.com "
|
"Stations and song history come from somafm.com "
|
||||||
"(channels.json + songs/<station>.json)."
|
"(channels.json + songs/<station>.json)."
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
ABOUT_LINUX = (
|
||||||
(
|
(
|
||||||
"The spectrum captures your actual PulseAudio/PipeWire output with "
|
"The spectrum captures your actual PulseAudio/PipeWire output with "
|
||||||
"parec (SYNC); otherwise it falls back to a muted ffmpeg stream tap "
|
"parec (SYNC); otherwise it falls back to a muted ffmpeg stream tap "
|
||||||
@@ -47,6 +51,21 @@ ABOUT = (
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ABOUT_MACOS = (
|
||||||
|
(
|
||||||
|
"The spectrum follows your audio output (SYNC) when a loopback device "
|
||||||
|
"such as BlackHole is available for ffmpeg/avfoundation; otherwise it "
|
||||||
|
"falls back to a muted ffmpeg stream tap (FFT, can drift) or a "
|
||||||
|
"synthesized display (SIM)."
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"mpv publishes what is playing to the macOS Now Playing session, so "
|
||||||
|
"Control Center and the keyboard media keys control this app."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
ABOUT = ABOUT_COMMON + (ABOUT_MACOS if sys.platform == "darwin" else ABOUT_LINUX)
|
||||||
|
|
||||||
|
|
||||||
class HelpScreen(ModalScreen[None]):
|
class HelpScreen(ModalScreen[None]):
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
|
|||||||
Reference in new issue
Block a user