diff --git a/app.py b/app.py index 7dfb5b0..d5d6798 100644 --- a/app.py +++ b/app.py @@ -23,6 +23,7 @@ CACHE = config.getboolean('STREAMING', 'Cache') BITRATE = config.get('STREAMING', 'Bitrate') BITRATE = re.findall(r'[0-9]+', BITRATE)[0] BITRATE = int(BITRATE) if BITRATE else BITRATE +IMAGE_MODE = config.getboolean('GENERAL', 'GUI') os.makedirs(".cache/art/", exist_ok=True) os.makedirs(".cache/audio/", exist_ok=True) @@ -170,13 +171,32 @@ class AlbumPanel(Widget, can_focus=True, can_focus_children=True): label = self.query_one(Label) label.update(self.album.album) -class ArtistAlbums2(Screen): + @on(events.Click) + def clicked(self, event: events.Click) -> None: + app.install_screen(AlbumSongs(self.album.id), 'albumSongs') + app.push_screen('albumSongs') + + def clear(self) -> None: + self.album = None + + coverArt = self.query_one(CoverArtPixels) + coverArt.cover = None + + label = self.query_one(Label) + label.update('') + +class ArtistAlbumsGUI(Screen): BINDINGS = [ - ('escape', 'die()', "Back") + ('escape', 'die()', "Back"), + Binding('a', 'page_backward()', 'Prev. Page'), + Binding('d', 'page_forward()', 'Next Page') ] artist: SubSonic.Artist albums: list[SubSonic.Album] + page: int = 1 + pages: int = 1 + albums_displayed: int def __init__(self, artist: SubSonic.Artist): self.artist = artist @@ -201,27 +221,82 @@ class ArtistAlbums2(Screen): yield AlbumPanel(classes="albumPanel") yield AlbumPanel(classes="albumPanel") yield AlbumPanel(classes="albumPanel") + yield Label(id='page-counter') yield Footer() def on_mount(self) -> None: self.albums = conn.getArtistAlbums(self.artist.id) + album_panels = self.query(AlbumPanel) artist_header = self.query_one(Label) artist_header.update(self.artist.name) - albums_displayed = 0 - album_panels = self.query(AlbumPanel) - for album_panel in album_panels: - album_panel.set_album(self.albums[albums_displayed]) - albums_displayed += 1 + self.pages = math.ceil(len(self.albums) / len(album_panels)) + self.update_pager() - if albums_displayed >= len(self.albums) - 1: + self.albums_displayed = 0 + for album_panel in album_panels: + album_panel.set_album(self.albums[self.albums_displayed]) + self.albums_displayed += 1 + + if self.albums_displayed >= len(self.albums): return + def update_pager(self) -> None: + pager = self.query_one('#page-counter', Label) + pager.update(f'{self.page} ... {self.pages}') + + def _clear_panels(self) -> None: + album_panels = self.query(AlbumPanel) + + for panel in album_panels: + panel.clear() + def action_die(self) -> None: app.pop_screen() app.uninstall_screen("artistAlbums") + def action_page_forward(self) -> None: + album_panels = self.query(AlbumPanel) + + if self.page + 1 > self.pages: + return + + self.page += 1 + self.update_pager() + self._clear_panels() + + for album_panel in album_panels: + if self.albums_displayed >= len(self.albums): + return + + album_panel.set_album(self.albums[self.albums_displayed]) + self.albums_displayed += 1 + + def action_page_backward(self) -> None: + album_panels = self.query(AlbumPanel) + + if self.page - 1 <= 0: + return + + # count how many panels actually are displaying an album + real_displayed = 0 + for album_panel in album_panels: + if album_panel.album: + real_displayed += 1 + + self.page -= 1 + self.update_pager() + self._clear_panels() + + self.albums_displayed -= len(album_panels) + real_displayed + for album_panel in album_panels: + if self.albums_displayed >= len(self.albums): + return + + album_panel.set_album(self.albums[self.albums_displayed]) + self.albums_displayed += 1 + class Artists(Screen): artists: list[SubSonic.Artist] @@ -322,7 +397,7 @@ class ArtistArt(Static): class ArtistPanel(Widget, can_focus=True, can_focus_children=True): - artist: SubSonic.Artist + artist: SubSonic.Artist | None def compose(self) -> ComposeResult: yield ArtistArt(classes="artistArt") @@ -339,20 +414,33 @@ class ArtistPanel(Widget, can_focus=True, can_focus_children=True): @on(events.Click) def clicked(self, event: events.Click) -> None: + if not self.artist: + return + logging.debug(f'ArtistPanel {self.artist.name} was clicked.') - app.install_screen(ArtistAlbums2(self.artist), 'artistAlbums') + app.install_screen(ArtistAlbumsGUI(self.artist), 'artistAlbums') app.push_screen('artistAlbums') -class Artists2(Screen): + def clear(self) -> None: + self.artist = None + + art: ArtistArt = self.query_one(ArtistArt) + art.art = None + + label = self.query_one(Label) + label.update('') + +class ArtistsGUI(Screen): BINDINGS = [ - Binding('d', "page_forward()", "Next Page"), - Binding('a', "page_backward()", "Prev. Page") + Binding('a', "page_backward()", "Prev. Page"), + Binding('d', "page_forward()", "Next Page") ] artists: list[SubSonic.Artist] artists_displayed: int = 0 page: int = 1 + pages: int = 1 def compose(self) -> ComposeResult: yield Header(icon='') @@ -362,6 +450,7 @@ class Artists2(Screen): yield Label(Text("F3 ", style="bold orange").append_text(Text("Albums", style="white")), classes="headerb") yield Label(Text("F4 ", style="bold orange").append_text(Text("Favourites", style="white")), classes="headerb") yield Label(Text("F5 ", style="bold orange").append_text(Text("Playlists", style="white")), classes="headerb") + yield Input(placeholder="Search", id="artistSearchBar") with Horizontal(): yield ArtistPanel(classes="artistPanel") yield ArtistPanel(classes="artistPanel") @@ -384,14 +473,15 @@ class Artists2(Screen): yield Footer() def on_mount(self) -> None: - app.sub_title = 'Artists2' + app.sub_title = 'Artists' self.artists = conn.getArtists() + artist_panels = self.query(ArtistPanel) self.artists_displayed = 0 + self.pages = math.ceil(len(self.artists)/len(artist_panels)) self.update_pager() - artist_panels = self.query(ArtistPanel) for artist_panel in artist_panels: if self.artists_displayed >= len(self.artists) - 1: return @@ -401,13 +491,17 @@ class Artists2(Screen): def update_pager(self) -> None: pager = self.query_one("#page-counter", Label) - pager.update(f"{self.page} ... {math.ceil(len(self.artists)/15)}") + pager.update(f"{self.page} ... {self.pages}") def action_page_forward(self) -> None: artist_panels = self.query(ArtistPanel) + if self.page + 1 > self.pages: + return + self.page += 1 self.update_pager() + self._clear_panels() for artist_panel in artist_panels: if self.artists_displayed >= len(self.artists) - 1: @@ -419,14 +513,52 @@ class Artists2(Screen): def action_page_backward(self) -> None: artist_panels = self.query(ArtistPanel) + if self.page - 1 <= 0: + return + self.page -= 1 self.update_pager() + self._clear_panels() - self.artists_displayed -= 30 + self.artists_displayed -= len(artist_panels) * 2 for artist_panel in artist_panels: artist_panel.set_artist(self.artists[self.artists_displayed]) self.artists_displayed += 1 + def _clear_panels(self) -> None: + artist_panels = self.query(ArtistPanel) + + for artist_panel in artist_panels: + artist_panel.clear() + + @on(Input.Submitted) + def search_submit(self, event: Input.Submitted) -> None: + search_value = event.value.lower() + logging.debug(search_value) + + artist_panels = self.query(ArtistPanel) + + self.artists_displayed = 0 + + filtered_artists = [] + for artist in self.artists: + if search_value in artist.name.lower(): + filtered_artists.append(artist) + + self.page = 1 + self.pages = math.ceil(len(filtered_artists) / len(artist_panels)) + self.update_pager() + self._clear_panels() + + for artist_panel in artist_panels: + if self.artists_displayed >= len(filtered_artists): + return + + artist_panel.set_artist(filtered_artists[self.artists_displayed]) + self.artists_displayed += 1 + + + class Albums(Screen): albums: list[SubSonic.Album] @@ -846,13 +978,14 @@ class TextualSubpyPlayer(App): MODES = { "main" : MainView, - "artists" : Artists2, + "artists" : ArtistsGUI, "albums" : Albums, "favourites" : Favourites, "playlists" : Playlists } currently_playing: SubSonic.Song | None = reactive(SubSonic.Song()) + is_playing: bool = False song_queue: list[SubSonic.Song] = reactive([]) user_queue: list[SubSonic.Song] = reactive([]) play_history: list[SubSonic.Song] = [] @@ -869,6 +1002,7 @@ class TextualSubpyPlayer(App): async def play_song(self, song: SubSonic.Song) -> None: self.currently_playing = song self.is_paused = False + self.is_playing = True logging.debug(f"Playing song {song.title} by {song.artist}.") self.notify(f"Playing \"{song.title}\" by {song.artist}.") @@ -920,6 +1054,8 @@ class TextualSubpyPlayer(App): if not pygame.mixer.music.get_busy() and not self.is_paused and self.currently_playing: self.play_history.append(self.currently_playing) + self.is_played = False + next_song: SubSonic.Song | None = None if self.user_queue: next_song = self.user_queue.pop(0) @@ -983,7 +1119,7 @@ class TextualSubpyPlayer(App): pygame.mixer.music.set_volume(min(pygame.mixer.music.get_volume() + 0.05, 1.0)) def play_pause_song(self) -> None: - if not self.currently_playing: + if not self.currently_playing or not self.is_playing: return None if not self.is_paused: @@ -1019,7 +1155,8 @@ class TextualSubpyPlayer(App): async def action_star_c_song(self) -> None: if self.currently_playing: - asyncio.create_task(asyncio.to_thread(conn.star, self.currently_playing.id)) + await asyncio.create_task(asyncio.to_thread(conn.star, self.currently_playing.id)) + self.notify(f'Favourite song "{self.currently_playing.title}".') def on_key(self, event: events.Key) -> None: logging.debug(event.key) diff --git a/config.ini b/config.ini index ba20e20..9720842 100644 --- a/config.ini +++ b/config.ini @@ -1,3 +1,6 @@ [STREAMING] Cache = true Bitrate = 320kbps + +[GENERAL] +GUI = true