feat(stage): show Bandcamp link in ON AIR
This commit is contained in:
1 parent
8e1c0a64eb
commit
66729c7542
4 files changed
+81
-3
No files matched your search
@@ -56,6 +56,7 @@ class Song:
|
|||||||
artist: str = ""
|
artist: str = ""
|
||||||
album: str = ""
|
album: str = ""
|
||||||
art_url: str = ""
|
art_url: str = ""
|
||||||
|
bandcamp_url: str = ""
|
||||||
played_at: datetime | None = None
|
played_at: datetime | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
+46
-1
@@ -17,7 +17,7 @@ from textual.css.query import NoMatches
|
|||||||
from textual.theme import Theme
|
from textual.theme import Theme
|
||||||
from textual.widgets import Input
|
from textual.widgets import Input
|
||||||
|
|
||||||
from . import api, artwork, config
|
from . import api, artwork, bandcamp, config
|
||||||
from .api import Station
|
from .api import Station
|
||||||
from .fallback import fallback_channels
|
from .fallback import fallback_channels
|
||||||
from .mpris import MprisService
|
from .mpris import MprisService
|
||||||
@@ -133,6 +133,7 @@ class SomaFMApp(App[None]):
|
|||||||
self.artwork_url = ""
|
self.artwork_url = ""
|
||||||
self.artwork_bytes: bytes | None = None
|
self.artwork_bytes: bytes | None = None
|
||||||
self._art_track_key = ""
|
self._art_track_key = ""
|
||||||
|
self._bandcamp_key = ""
|
||||||
self._http: httpx.AsyncClient | None = None
|
self._http: httpx.AsyncClient | None = None
|
||||||
self._closed = False
|
self._closed = False
|
||||||
self._tick_timer = None
|
self._tick_timer = None
|
||||||
@@ -415,8 +416,51 @@ class SomaFMApp(App[None]):
|
|||||||
)
|
)
|
||||||
self.mpris.set_status(self._mpris_status())
|
self.mpris.set_status(self._mpris_status())
|
||||||
self.mpris.set_volume(self.volume / 100.0)
|
self.mpris.set_volume(self.volume / 100.0)
|
||||||
|
self._update_bandcamp()
|
||||||
self._update_artwork()
|
self._update_artwork()
|
||||||
|
|
||||||
|
def _update_bandcamp(self) -> None:
|
||||||
|
"""Find a Bandcamp track link once per now-playing track."""
|
||||||
|
station = self.current
|
||||||
|
if station is None or self.offline or self._http is None:
|
||||||
|
return
|
||||||
|
song = station.now_playing
|
||||||
|
if song is None or not (song.artist.strip() or song.title.strip()):
|
||||||
|
return
|
||||||
|
|
||||||
|
track_key = f"{station.id}|{song.key()}"
|
||||||
|
if track_key == self._bandcamp_key:
|
||||||
|
return
|
||||||
|
self._bandcamp_key = track_key
|
||||||
|
self._fetch_bandcamp(
|
||||||
|
station.id, song.key(), song.artist, song.album, song.title
|
||||||
|
)
|
||||||
|
|
||||||
|
@work(exclusive=True, group="bandcamp", exit_on_error=False)
|
||||||
|
async def _fetch_bandcamp(
|
||||||
|
self,
|
||||||
|
station_id: str,
|
||||||
|
song_key: str,
|
||||||
|
artist: str,
|
||||||
|
album: str,
|
||||||
|
title: str,
|
||||||
|
) -> None:
|
||||||
|
if self._http is None or self.offline:
|
||||||
|
return
|
||||||
|
url = await bandcamp.find_track(self._http, artist, album, title)
|
||||||
|
if not url:
|
||||||
|
return
|
||||||
|
|
||||||
|
station = next((s for s in self.stations if s.id == station_id), None)
|
||||||
|
if station is None or self.current is None or self.current.id != station_id:
|
||||||
|
return
|
||||||
|
song = station.now_playing
|
||||||
|
if song is None or song.key() != song_key:
|
||||||
|
return
|
||||||
|
song.bandcamp_url = url
|
||||||
|
with contextlib.suppress(NoMatches):
|
||||||
|
self.query_one(StagePanel).refresh_stage()
|
||||||
|
|
||||||
def _refresh_art(self) -> None:
|
def _refresh_art(self) -> None:
|
||||||
with contextlib.suppress(NoMatches):
|
with contextlib.suppress(NoMatches):
|
||||||
self.query_one(StagePanel).refresh_art()
|
self.query_one(StagePanel).refresh_art()
|
||||||
@@ -758,6 +802,7 @@ class SomaFMApp(App[None]):
|
|||||||
|
|
||||||
def action_refresh(self) -> None:
|
def action_refresh(self) -> None:
|
||||||
self._last_history_fetch = 0.0
|
self._last_history_fetch = 0.0
|
||||||
|
self._bandcamp_key = ""
|
||||||
self._set_notice("refreshing…", 2)
|
self._set_notice("refreshing…", 2)
|
||||||
if self.offline:
|
if self.offline:
|
||||||
self._set_notice("offline mode — nothing to refresh", 3)
|
self._set_notice("offline mode — nothing to refresh", 3)
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ class StageInfo(Widget):
|
|||||||
|
|
||||||
notice = Text(truncate(app.notice, width), style="italic yellow")
|
notice = Text(truncate(app.notice, width), style="italic yellow")
|
||||||
|
|
||||||
return [
|
details = [
|
||||||
heading,
|
heading,
|
||||||
description,
|
description,
|
||||||
Text(""),
|
Text(""),
|
||||||
@@ -170,8 +170,16 @@ class StageInfo(Widget):
|
|||||||
title,
|
title,
|
||||||
album,
|
album,
|
||||||
meta,
|
meta,
|
||||||
notice,
|
|
||||||
]
|
]
|
||||||
|
if song is not None and song.bandcamp_url:
|
||||||
|
link = Text(no_wrap=True)
|
||||||
|
link.append(
|
||||||
|
"Bandcamp ↗",
|
||||||
|
style=f"bold cyan link {song.bandcamp_url}",
|
||||||
|
)
|
||||||
|
details.append(link)
|
||||||
|
details.append(notice)
|
||||||
|
return details
|
||||||
|
|
||||||
|
|
||||||
class HistoryPane(Widget):
|
class HistoryPane(Widget):
|
||||||
|
|||||||
@@ -133,3 +133,27 @@ async def test_narrow_layout() -> None:
|
|||||||
text = rendered_text(app)
|
text = rendered_text(app)
|
||||||
assert "STATIONS" in text
|
assert "STATIONS" in text
|
||||||
assert "ON AIR" in text
|
assert "ON AIR" in text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_stage_shows_bandcamp_link_only_when_present() -> None:
|
||||||
|
app = SomaFMApp(autoplay=False, offline=True, audio=False)
|
||||||
|
async with app.run_test(size=(110, 40)) as pilot:
|
||||||
|
await pilot.pause()
|
||||||
|
station = app.current
|
||||||
|
assert station is not None
|
||||||
|
song = Song(
|
||||||
|
title="Rauschebart",
|
||||||
|
artist="Jens Buchert",
|
||||||
|
album="Gravity",
|
||||||
|
bandcamp_url="https://jensbuchert.bandcamp.com/track/rauschebart",
|
||||||
|
)
|
||||||
|
station.now_playing = song
|
||||||
|
app.query_one(StagePanel).refresh_stage()
|
||||||
|
await pilot.pause()
|
||||||
|
assert "Bandcamp" in rendered_text(app)
|
||||||
|
|
||||||
|
song.bandcamp_url = ""
|
||||||
|
app.query_one(StagePanel).refresh_stage()
|
||||||
|
await pilot.pause()
|
||||||
|
assert "Bandcamp" not in rendered_text(app)
|
||||||
Reference in new issue
Block a user