feat(tui): implement SomaFM terminal radio player

This commit is contained in:
beefcarver committed 2026-10-30 04:17:53 +00:00
1 parent 941e45a20f
commit 237de2efe9
21 files changed
+4937

No files matched your search

+74
View File
@@ -0,0 +1,74 @@
"""Full-screen keyboard help."""
from __future__ import annotations
import textwrap
from rich.console import Group
from rich.table import Table
from rich.text import Text
from textual.app import ComposeResult
from textual.screen import ModalScreen
from textual.widgets import Static
from .common import SPECTRUM_STOPS, gradient
HELP_ROWS: tuple[tuple[str, str], ...] = (
("↑ / ↓ j / k", "move through the station list"),
("enter", "tune in to the selected station"),
("space", "play / pause the current station"),
("s", "stop playback"),
("] / [", "next / previous station (tunes immediately)"),
("+ / -", "volume up / down"),
("m", "mute / unmute"),
("f", "favourite — favourites float to the top"),
("/", "filter stations by title, genre or description"),
("esc", "clear the filter / close overlays"),
("v", "cycle visualiser: mirrored bars → bars → wave"),
("r", "refresh station list and song history"),
("g / G", "jump to the top / bottom of the list"),
("? ", "this help"),
("q", "quit"),
)
ABOUT = (
(
"Stations and song history come from somafm.com "
"(channels.json + songs/<station>.json)."
),
(
"The spectrum captures your actual PulseAudio/PipeWire output with "
"parec (SYNC); otherwise it falls back to a muted ffmpeg stream tap "
"(FFT, can drift) or a synthesized display (SIM)."
),
(
"The desktop picks us up over MPRIS as a "
"somafmtui media player — try `playerctl -l` / `playerctl metadata`."
),
)
class HelpScreen(ModalScreen[None]):
def compose(self) -> ComposeResult:
table = Table.grid(padding=(0, 2), expand=False)
table.add_column(style="bold cyan", no_wrap=True, justify="right")
table.add_column(style="")
for key, description in HELP_ROWS:
table.add_row(key, description)
header = Text()
header.append("◤ ", style=f"bold {gradient(SPECTRUM_STOPS, 0.9)}")
header.append("SOMAFM TUI", style=f"bold {gradient(SPECTRUM_STOPS, 0.55)}")
header.append(" keyboard & credits", style="dim")
footer = Text()
for paragraph in ABOUT:
for line in textwrap.wrap(paragraph, width=70):
footer.append(line + "\n", style="dim")
body = Group(header, Text(""), table, Text(""), footer)
yield Static(body, id="help")
def on_key(self, event) -> None:
event.stop()
self.dismiss(None)