track_info.py
1 from textual.containers import Container 2 from textual.app import ComposeResult 3 from textual.widgets import Static 4 5 from linamp.messages import PlayerStateUpdate 6 7 8 class TrackInfo(Container): 9 """Displays the currently playing track between controls and playlist. 10 11 Priority: ICY stream title (artist - track) > media title > station name. 12 """ 13 14 DEFAULT_CSS = """ 15 TrackInfo { 16 height: 4; 17 border: round #585b70; 18 padding: 0 1; 19 } 20 TrackInfo .track-label { 21 color: #585b70; 22 height: 1; 23 } 24 TrackInfo .track-title { 25 color: #f9e2af; 26 text-style: bold; 27 height: 1; 28 } 29 """ 30 31 def compose(self) -> ComposeResult: 32 yield Static("NOW PLAYING", classes="track-label", id="track-label") 33 yield Static("", classes="track-title", id="track-title") 34 35 def _best_title(self, event: PlayerStateUpdate) -> str: 36 """Pick the best available title for the current track.""" 37 # ICY title is the gold standard — real-time artist/track from the stream 38 if event.icy_title: 39 return event.icy_title 40 # media_title is mpv's synthesized title (may come from ytdl or stream) 41 # Only use it if it differs from the station name (otherwise it's redundant) 42 if event.media_title and event.station and event.media_title != event.station.name: 43 return event.media_title 44 # Fall back to station name 45 if event.station: 46 return event.station.name 47 return "" 48 49 def on_player_state_update(self, event: PlayerStateUpdate) -> None: 50 label = self.query_one("#track-label", Static) 51 title = self.query_one("#track-title", Static) 52 53 if event.is_playing: 54 label.update("NOW PLAYING") 55 title.update(self._best_title(event)) 56 elif event.is_paused: 57 label.update("PAUSED") 58 title.update(self._best_title(event)) 59 else: 60 label.update("NOW PLAYING") 61 title.update("")