import logging import os from enum import Enum LOG_FILE = 'textual.log' LOGGING_LEVEL = logging.DEBUG LOG_FORMAT = '[%(asctime)s] %(levelname)s in %(module)s: %(message)s' # Clear Log File os.remove(LOG_FILE) logging.basicConfig(filename=LOG_FILE, level=LOGGING_LEVEL, format=LOG_FORMAT) import SubSonic from textual import on, events from textual.app import App, ComposeResult from textual.widgets import Footer, Header, OptionList, DataTable, Tabs, Tab, TabbedContent, TabPane, ProgressBar, Label from textual.screen import Screen from textual.widgets.option_list import Option u = 'pseudo' p = 'telegram9dock-MICHEL' conn = SubSonic.SubpyConn(u, p, "https://music.0x01fe.net") class AlbumSongs(Screen): BINDINGS = [ ('escape', 'die()', "Back"), ('p', 'play()', 'Play Song'), ('q' 'queue()', 'Queue Song') ] selected_song_id: str | None = None songs = None def __init__(self, album_id: str): self.album_id = album_id super().__init__() def compose(self) -> ComposeResult: yield DataTable(id="albumSongs") yield Footer() def on_mount(self) -> None: self.songs = conn.getAlbumSongs(self.album_id) songsTable: DataTable = self.query_one(DataTable) songsTable.cursor_type = 'row' songsTable.add_columns("#", "Title", "Artist", "Duration", "Bitrate") for song in self.songs: songsTable.add_row(song.trackNumber, song.title, song.artist, song.duration, song.bitRate, key=song.id) @on(DataTable.RowHighlighted) def row_highlighted(self, event: DataTable.RowHighlighted) -> None: self.selected_song_id = event.row_key.value def action_play(self) -> None: app.play_song(self._find_selected_song()) def action_queue(self) -> None: app.queue_song(self._find_selected_song()) # Find the song that was selected def _find_selected_song(self) -> SubSonic.Song: for song in self.songs: if song.id == self.selected_song_id: return song # Pops and uninstalls self def action_die(self) -> None: app.pop_screen() app.uninstall_screen("albumSongs") class ArtistAlbums(Screen): BINDINGS = [ ('escape', 'die()', "Back") ] def __init__(self, artist_id: str): self.artist_id = artist_id super().__init__() def compose(self) -> ComposeResult: yield DataTable(id="Albums") yield Footer() def on_mount(self) -> None: albums = conn.getArtistAlbums(self.artist_id) albumTable: DataTable = self.query_one(DataTable) albumTable.cursor_type = 'row' albumTable.add_columns("Title", "Songs", "Plays", "Year", "Time") for album in albums: albumTable.add_row(album.title, album.songCount, album.playCount, album.year, album.duration, key=album.id) @on(DataTable.RowSelected) async def album_selected(self, event: DataTable.RowSelected) -> None: app.install_screen(AlbumSongs(event.row_key.value), "albumSongs") app.push_screen("albumSongs") # Pops and uninstalls self def action_die(self) -> None: app.pop_screen() app.uninstall_screen("artistAlbums") class Artists(Screen): BINDINGS = [ ('escape', 'app.pop_screen', "Back") ] def compose(self) -> ComposeResult: yield OptionList(id="Artists") yield Footer() def on_mount(self) -> None: artists = conn.getArtists() artistList = self.query_one(OptionList) for artist in artists: artistList.add_option(Option(artist.name, artist.id)) @on(OptionList.OptionSelected) def artist_selected(self, event: OptionList.OptionSelected) -> None: app.install_screen(ArtistAlbums(event.option_id), 'artistAlbums') app.push_screen('artistAlbums') class Albums(Screen): BINDINGS = [ ('escape', 'app.pop_screen', "Back") ] def compose(self) -> ComposeResult: yield DataTable() yield Footer() def on_mount(self) -> None: albums = conn.getAllAlbums() albumTable = self.query_one(DataTable) albumTable.cursor_type = 'row' albumTable.add_columns("Title", "Songs", "Plays", "Year", "Time") for album in albums: albumTable.add_row(album.title, album.songCount, album.playCount, album.year, album.duration, key=album.id) @on(DataTable.RowSelected) def album_selected(self, event: DataTable.RowSelected) -> None: app.install_screen(AlbumSongs(event.row_key.value), 'albumSongs') app.push_screen('albumSongs') class Playlists(Screen): BINDINGS = [ ('escape', 'app.pop_screen', "Back") ] playlists: list[SubSonic.Playlist] | None = None def compose(self) -> ComposeResult: yield Header(icon="") yield DataTable() yield Footer() def on_mount(self) -> None: app.sub_title = "Playlists" self.playlists = conn.getPlaylists() playlistTable = self.query_one(DataTable) playlistTable.cursor_type = "row" playlistTable.add_columns("Name", "Songs", "Time", "Owner") for playlist in self.playlists: playlistTable.add_row(playlist.name, playlist.songCount, playlist.duration, playlist.owner, key=playlist.id) class Favourites(Screen): BINDINGS = [ ('escape', 'app.pop_screen', "Back") ] songs: list[SubSonic.Song] | None = None def compose(self) -> ComposeResult: yield Header(icon="") yield DataTable() yield Footer() def on_mount(self) -> None: app.sub_title = "Favourites" self.songs = conn.getStarredSongs() songTable = self.query_one(DataTable) songTable.cursor_type = "row" songTable.add_columns("Title", "Album", "Artist", "Plays", "Duration", "Bitrate") for song in self.songs: songTable.add_row(song.title, song.album, song.artist, song.playCount, song.duration, song.bitRate, key=song.id) class TextualSubpyPlayer(App): BINDINGS = [ ("f1", "push_screen('artists')", "Artists"), ("f2", "push_screen('albums')", "Albums"), ("f3", "push_screen('favourites')", "Favourites"), ("f4", "push_screen('playlists')", "Playlists") ] currently_playing: SubSonic.Song | None = None song_queue: list[SubSonic.Song] = [] def compose(self) -> ComposeResult: yield Label() yield ProgressBar() yield Footer() def on_mount(self) -> None: logging.debug("mount called") self.title = "SubPy" self.install_screen(Artists(), name="artists") self.install_screen(Albums(), name="albums") self.install_screen(Favourites(), name="favourites") self.install_screen(Playlists(), name="playlists") def on_key(self, event: events.Key) -> None: logging.debug(f'Key Pressed {event.name}') def play_song(self, song: SubSonic.Song) -> None: self.currently_playing = song logging.debug(f"Playing song {song.title} by {song.artist}.") # Set callback loop for progress bar # self.progress_timer = self.set_interval(1 / 10, self.update_progress, pause=False) # def update_progress(self) -> None: # self.query_one(ProgressBar).advance(1) def queue_song(self, song: SubSonic.Song) -> None: logging.debug(f'Queue\'d song "{song.title}" by {song.artist}.') self.song_queue.append(song) if __name__ == '__main__': app = TextualSubpyPlayer() app.run()