Files
subpy/screens-test.py
T

374 lines
12 KiB
Python

import logging
import os
import io
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
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
import pygame
from textual import on, events
from textual.app import App, ComposeResult
from textual.widgets import Footer, Header, OptionList, DataTable, ProgressBar, Label
from textual.containers import Horizontal, Vertical
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")
pygame.mixer.init()
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
selected_playlist_id: str = 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)
@on(DataTable.RowHighlighted)
def row_highlighted(self, event: DataTable.RowHighlighted) -> None:
self.selected_playlist_id = event.row_key.value
@on(DataTable.RowSelected)
def playlist_selected(self, event: DataTable.RowSelected) -> None:
app.install_screen(PlaylistSongs(self._find_selected_playlist()), "playlistSongs")
app.push_screen('playlistSongs')
def _find_selected_playlist(self) -> SubSonic.Playlist:
for playlist in self.playlists:
if playlist.id == self.selected_playlist_id:
return playlist
class PlaylistSongs(Screen):
BINDINGS = [
('escape', 'die()', "Back"),
('p', 'play()', 'Play Song'),
('q', 'queue()', 'Queue Song')
]
selected_song_id: str | None = None
def __init__(self, playlist: SubSonic.Playlist):
self.playlist = playlist
self.songs = conn.getPlaylist(playlist.id)
super().__init__()
def compose(self) -> ComposeResult:
self.sub_title = self.playlist.name
yield Header(icon="")
yield DataTable()
yield Footer()
def on_mount(self) -> None:
songTable: DataTable = 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)
# Pops and uninstalls self
def action_die(self) -> None:
app.pop_screen()
app.uninstall_screen("playlistSongs")
@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
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 = [
("space", "play_pause_song()", "⏯️"),
("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] = []
isPlaying: bool = False
place: int = 0
def compose(self) -> ComposeResult:
yield Header(icon="")
with Horizontal():
with Vertical(classes="column"):
yield Label(id="playing")
yield ProgressBar()
yield DataTable()
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")
queueTable: DataTable = self.query_one(DataTable)
queueTable.cursor_type = "row"
queueTable.add_columns("Title", "Album", "Artist", "Plays", "Duration", "Bitrate")
def on_key(self, event: events.Key) -> None:
# logging.debug(f'Key Pressed {event.name}')
pass
@on(events.ScreenSuspend)
def sus(self, event: events.ScreenSuspend) -> None:
logging.debug('sus')
def on_screen_suspend(self, event: events.ScreenSuspend) -> None:
logging.debug('sus3')
@on(events.ScreenResume)
def screen_resumed(self, event: events.ScreenResume) -> None:
logging.debug('Main screen resumed')
queueTable: DataTable = self.query_one(DataTable)
for row_key, row in queueTable.rows.items():
queueTable.remove_row(row_key)
for song in self.song_queue:
queueTable.add_row(song.title, song.album, song.artist, song.playCount, song.duration, song.bitRate)
def play_song(self, song: SubSonic.Song) -> None:
self.currently_playing = song
self.isPlaying = True
logging.debug(f"Playing song {song.title} by {song.artist}.")
self.notify(f"Playing \"{song.title}\" by {song.artist}.")
r = conn.stream(song.id)
audio_data = io.BytesIO(r.content)
pygame.mixer.music.load(audio_data)
pygame.mixer.music.play()
# playingLabel: Label = self.query_one(Label)
# playingLabel.
# 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)
def action_play_pause_song(self) -> None:
self.play_pause_song()
def play_pause_song(self) -> None:
if not self.currently_playing:
return None
if self.isPlaying:
self.place = pygame.mixer.music.get_pos()
pygame.mixer.music.pause()
self.isPlaying = False
else:
pygame.mixer.music.unpause()
self.isPlaying = True
if __name__ == '__main__':
app = TextualSubpyPlayer()
app.run()