fixed playing stuff, added cover art, added pause button

This commit is contained in:
JISAUAY committed 2025-09-04 14:46:40 -05:00
1 parent 00d66f7258
commit 4c1f5bfe82
2 files changed
+84 -17

No files matched your search

+71 -17
View File
@@ -22,7 +22,7 @@ from rich.table import Table, Column
from textual import on, events, work from textual import on, events, work
from textual.reactive import reactive from textual.reactive import reactive
from textual.app import App, ComposeResult, RenderResult from textual.app import App, ComposeResult, RenderResult
from textual.widgets import Footer, Header, OptionList, DataTable, ProgressBar, Label from textual.widgets import Footer, Header, OptionList, DataTable, ProgressBar, Label, Static
from textual.widget import Widget from textual.widget import Widget
from textual.containers import Horizontal, Vertical, Container, VerticalScroll from textual.containers import Horizontal, Vertical, Container, VerticalScroll
from textual.screen import Screen from textual.screen import Screen
@@ -38,19 +38,24 @@ conn = SubSonic.SubpyConn(u, p, "https://music.0x01fe.net")
pygame.mixer.init() pygame.mixer.init()
class CoverArtPixels(Container):
class NowPlaying(Widget):
song = reactive[SubSonic.Song | None](None, layout=True)
cover = reactive[Image.Image | None](None) cover = reactive[Image.Image | None](None)
def render(self) -> RenderResult: def render(self) -> RenderResult:
if not self.song:
return "Nothing playing"
if self.cover: if self.cover:
return Pixels.from_image(self.cover) width, height = self.size
else: width -= 4
return f"Playing: {self.song.title} by {self.song.artist}" height -= 4
if width > height:
width = height
elif height > width:
height = width
return Pixels.from_image(self.cover, resize=(width, height))
return ''
@work(exclusive=True, thread=True) @work(exclusive=True, thread=True)
def get_cover_art(self, cover_art_id: str) -> None: def get_cover_art(self, cover_art_id: str) -> None:
@@ -62,23 +67,55 @@ class NowPlaying(Widget):
logging.error(f"Failed to get cover art: {e}") logging.error(f"Failed to get cover art: {e}")
self.cover = None self.cover = None
def watch_song(self, song: SubSonic.Song | None) -> None: class NowPlayingInfo(Container):
if song and song.albumId: song = reactive[SubSonic.Song | None](None, layout=True)
def render(self) -> RenderResult:
if self.song:
return f"Playing: {self.song.title} by {self.song.artist}"
else:
return "Nothing Playing"
class NowPlaying(Widget):
def compose(self) -> ComposeResult:
yield NowPlayingInfo()
yield CoverArtPixels(id="coverart")
def update_song(self, song: SubSonic.Song) -> None:
info = self.query_one(NowPlayingInfo)
info.song = song
if song.albumId:
cover_art = self.query_one(CoverArtPixels)
album = conn.getAlbumSongs(song.albumId) album = conn.getAlbumSongs(song.albumId)
if album: if album:
cover_id = album[0].coverArtId cover_id: str | None = album[0].coverArtId
if cover_id: if cover_id:
self.get_cover_art(cover_id) cover_art.get_cover_art(cover_id)
class MusicApp(App): class MusicApp(App):
CSS_PATH = 'app.tcss'
BINDINGS = [
# ("r", "rewind()", "⏪"),
("space", "play_pause_song()", "⏯")
# ("s", "skip()", "⏭")
]
currently_playing: bool = False
is_paused: bool = False
place: int = 0
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header() yield Header()
yield Horizontal( yield Horizontal(
VerticalScroll(OptionList(id="playlists"), id="sidebar"), VerticalScroll(OptionList(id="playlists"), id="sidebar"),
Vertical( Vertical(
DataTable(id="songs", cursor_type='row'),
NowPlaying(id="now-playing"), NowPlaying(id="now-playing"),
DataTable(id="songs"),
id="main-panel" id="main-panel"
) )
) )
@@ -109,6 +146,7 @@ class MusicApp(App):
table.clear() table.clear()
logging.debug([song.title for song in songs]) logging.debug([song.title for song in songs])
for song in songs: for song in songs:
# Doesn't work with duplicated songs
table.add_row(song.trackNumber, song.title, song.artist, song.album, key=song.id) table.add_row(song.trackNumber, song.title, song.artist, song.album, key=song.id)
except Exception as e: except Exception as e:
logging.error(f"Failed to get playlist songs: {e}") logging.error(f"Failed to get playlist songs: {e}")
@@ -119,19 +157,35 @@ class MusicApp(App):
if song_id: if song_id:
self.play_song(song_id) self.play_song(song_id)
@work(exclusive=True, thread=True) @work(exclusive=True)
def play_song(self, song_id: str) -> None: async def play_song(self, song_id: str) -> None:
try: try:
song = conn.getSong(song_id) song = conn.getSong(song_id)
now_playing = self.query_one(NowPlaying) now_playing = self.query_one(NowPlaying)
self.call_from_thread(setattr, now_playing, "song", song) # self.call_from_thread(NowPlaying.update_song, now_playing, song)
now_playing.update_song(song)
stream_data = conn.stream(song_id) stream_data = conn.stream(song_id)
pygame.mixer.music.load(io.BytesIO(stream_data.content)) pygame.mixer.music.load(io.BytesIO(stream_data.content))
pygame.mixer.music.play() pygame.mixer.music.play()
self.currently_playing = True
except Exception as e: except Exception as e:
logging.error(f"Failed to play song {song_id}: {e}") logging.error(f"Failed to play song {song_id}: {e}")
def action_play_pause_song(self) -> None:
if not self.currently_playing:
return None
if not self.is_paused:
self.notify("Playback paused")
self.place = pygame.mixer.music.get_pos()
pygame.mixer.music.pause()
self.is_paused = True
else:
self.notify("Resuming Playback")
pygame.mixer.music.unpause()
self.is_paused = False
if __name__ == "__main__": if __name__ == "__main__":
app = MusicApp() app = MusicApp()
app.run() app.run()
+13
View File
@@ -0,0 +1,13 @@
#songs {
height: 50%;
}
#now-playing {
height: 90%;
}
#coverart {
height: 100%;
width: 100%;
}