the queue now actually works
This commit is contained in:
1 parent
2d97c921af
commit
c1c640f837
5 files changed
+94
-25
No files matched your search
@@ -1 +1,2 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
|
art/*
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
textual==1.0.0
|
textual==1.0.0
|
||||||
pygame==2.6.0
|
pygame==2.6.0
|
||||||
|
Pillow==10.0.0
|
||||||
|
rich-pixels==3.0.1
|
||||||
+3
-1
@@ -4,7 +4,9 @@ from PIL import Image
|
|||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
|
size = 70
|
||||||
|
|
||||||
with Image.open("cover.png") as image:
|
with Image.open("cover.png") as image:
|
||||||
pixels = Pixels.from_image(image, resize=(60, 60))
|
pixels = Pixels.from_image(image, resize=(size, size))
|
||||||
|
|
||||||
console.print(pixels)
|
console.print(pixels)
|
||||||
+77
-15
@@ -16,12 +16,15 @@ import SubSonic
|
|||||||
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
|
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
|
||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
from textual import on, events
|
from textual import on, events, work
|
||||||
from textual.app import App, ComposeResult
|
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
|
||||||
from textual.containers import Horizontal, Vertical
|
from textual.containers import Horizontal, Vertical, Container
|
||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.widgets.option_list import Option
|
from textual.widgets.option_list import Option
|
||||||
|
from textual.timer import Timer
|
||||||
|
|
||||||
|
from rich_pixels import Pixels
|
||||||
|
|
||||||
u = 'pseudo'
|
u = 'pseudo'
|
||||||
p = 'telegram9dock-MICHEL'
|
p = 'telegram9dock-MICHEL'
|
||||||
@@ -241,7 +244,13 @@ class PlaylistSongs(Screen):
|
|||||||
return i
|
return i
|
||||||
|
|
||||||
class Favourites(Screen):
|
class Favourites(Screen):
|
||||||
|
BINDINGS = [
|
||||||
|
('p', 'play()', 'Play Song'),
|
||||||
|
('q', 'queue()', 'Queue Song')
|
||||||
|
]
|
||||||
|
|
||||||
songs: list[SubSonic.Song] | None = None
|
songs: list[SubSonic.Song] | None = None
|
||||||
|
selected_song_id: str | None = None
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Header(icon="")
|
yield Header(icon="")
|
||||||
@@ -259,13 +268,52 @@ class Favourites(Screen):
|
|||||||
for song in self.songs:
|
for song in self.songs:
|
||||||
songTable.add_row(song.title, song.album, song.artist, song.playCount, song.duration, song.bitRate, key=song.id)
|
songTable.add_row(song.title, song.album, song.artist, song.playCount, 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:
|
||||||
|
song_index = self._find_selected_song()
|
||||||
|
app.play_song(self.songs[song_index])
|
||||||
|
app.song_queue = self.songs[song_index + 1:]
|
||||||
|
|
||||||
|
def action_queue(self) -> None:
|
||||||
|
app.queue_song(self._find_selected_song())
|
||||||
|
|
||||||
|
# Returns index of selected song
|
||||||
|
def _find_selected_song(self) -> int:
|
||||||
|
for i, song in enumerate(self.songs):
|
||||||
|
if song.id == self.selected_song_id:
|
||||||
|
return i
|
||||||
|
|
||||||
|
# TODO : Maybe should just take bytes instead of saving it as a file
|
||||||
|
class CoverArtPixels(Container):
|
||||||
|
|
||||||
|
def __init__(self, cover_art_id: str):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.cover_art_id = cover_art_id
|
||||||
|
conn.getCoverArt(cover_art_id, path=f'art/{cover_art_id}.png')
|
||||||
|
|
||||||
|
def render(self) -> RenderResult:
|
||||||
|
|
||||||
|
# Make the size square
|
||||||
|
width, height = self.size
|
||||||
|
|
||||||
|
if width > height:
|
||||||
|
width = height
|
||||||
|
elif height > width:
|
||||||
|
height = width
|
||||||
|
|
||||||
|
return Pixels().from_image_path(f'art/{self.cover_art_id}.png', resize=(width, height))
|
||||||
|
|
||||||
class MainView(Screen):
|
class MainView(Screen):
|
||||||
CSS_PATH = 'main.tcss'
|
CSS_PATH = 'main.tcss'
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Header(icon="")
|
yield Header(icon="")
|
||||||
with Horizontal():
|
with Horizontal():
|
||||||
with Vertical(classes="info"):
|
with Vertical(classes="info", id='info'):
|
||||||
yield Label("Playing")
|
yield Label("Playing")
|
||||||
yield DataTable(id="currently_playing")
|
yield DataTable(id="currently_playing")
|
||||||
yield ProgressBar()
|
yield ProgressBar()
|
||||||
@@ -284,12 +332,8 @@ class MainView(Screen):
|
|||||||
currentlyPlaying.cursor_type = "row"
|
currentlyPlaying.cursor_type = "row"
|
||||||
currentlyPlaying.add_columns("", "")
|
currentlyPlaying.add_columns("", "")
|
||||||
|
|
||||||
@on(events.ScreenSuspend)
|
|
||||||
def sus(self, event: events.ScreenSuspend) -> None:
|
|
||||||
logging.debug('sus')
|
|
||||||
|
|
||||||
@on(events.ScreenResume)
|
@on(events.ScreenResume)
|
||||||
def screen_resumed(self, event: events.ScreenResume) -> None:
|
async def screen_resumed(self, event: events.ScreenResume) -> None:
|
||||||
app.sub_title = "Overview"
|
app.sub_title = "Overview"
|
||||||
|
|
||||||
# Update Queue Table
|
# Update Queue Table
|
||||||
@@ -315,6 +359,8 @@ class MainView(Screen):
|
|||||||
currentlyPlaying.add_row("Track Number", song.trackNumber)
|
currentlyPlaying.add_row("Track Number", song.trackNumber)
|
||||||
currentlyPlaying.add_row("Bit Rate", song.bitRate)
|
currentlyPlaying.add_row("Bit Rate", song.bitRate)
|
||||||
|
|
||||||
|
await self.query_one('#info').mount(CoverArtPixels(app.currently_playing.id))
|
||||||
|
|
||||||
def _clear_table(self, table_id: str) -> None:
|
def _clear_table(self, table_id: str) -> None:
|
||||||
table: DataTable = self.query_one(table_id)
|
table: DataTable = self.query_one(table_id)
|
||||||
|
|
||||||
@@ -347,9 +393,10 @@ class TextualSubpyPlayer(App):
|
|||||||
currently_playing: SubSonic.Song | None = None
|
currently_playing: SubSonic.Song | None = None
|
||||||
song_queue: list[SubSonic.Song] = []
|
song_queue: list[SubSonic.Song] = []
|
||||||
user_queue: list[SubSonic.Song] = []
|
user_queue: list[SubSonic.Song] = []
|
||||||
isPlaying: bool = False
|
is_paused: bool = False
|
||||||
place: int = 0
|
place: int = 0
|
||||||
|
|
||||||
|
check_playing_timer: Timer | None = None
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
self.title = "SubPy"
|
self.title = "SubPy"
|
||||||
@@ -359,9 +406,10 @@ class TextualSubpyPlayer(App):
|
|||||||
# logging.debug(f'Key Pressed {event.name}')
|
# logging.debug(f'Key Pressed {event.name}')
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def play_song(self, song: SubSonic.Song) -> None:
|
@work(exclusive=True)
|
||||||
|
async def play_song(self, song: SubSonic.Song) -> None:
|
||||||
self.currently_playing = song
|
self.currently_playing = song
|
||||||
self.isPlaying = True
|
self.is_paused = False
|
||||||
logging.debug(f"Playing song {song.title} by {song.artist}.")
|
logging.debug(f"Playing song {song.title} by {song.artist}.")
|
||||||
|
|
||||||
self.notify(f"Playing \"{song.title}\" by {song.artist}.")
|
self.notify(f"Playing \"{song.title}\" by {song.artist}.")
|
||||||
@@ -372,17 +420,31 @@ class TextualSubpyPlayer(App):
|
|||||||
pygame.mixer.music.load(audio_data)
|
pygame.mixer.music.load(audio_data)
|
||||||
pygame.mixer.music.play()
|
pygame.mixer.music.play()
|
||||||
|
|
||||||
|
self.check_playing_timer = self.set_interval(1 / 5, self.check_done_playing, pause=False)
|
||||||
|
|
||||||
# playingLabel: Label = self.query_one(Label)
|
# playingLabel: Label = self.query_one(Label)
|
||||||
# playingLabel.
|
# playingLabel.
|
||||||
|
|
||||||
# Set callback loop for progress bar
|
# Set callback loop for progress bar
|
||||||
# self.progress_timer = self.set_interval(1 / 10, self.update_progress, pause=False)
|
# self.progress_timer = self.set_interval(1 / 10, self.update_progress, pause=False)
|
||||||
|
|
||||||
|
def check_done_playing(self) -> None:
|
||||||
|
if not pygame.mixer.music.get_busy() and not self.is_paused:
|
||||||
|
queue = self.user_queue
|
||||||
|
queue.extend(self.song_queue)
|
||||||
|
|
||||||
|
next_song = queue.pop(0)
|
||||||
|
|
||||||
|
self.play_song(next_song)
|
||||||
|
|
||||||
|
self.check_playing_timer.stop()
|
||||||
|
|
||||||
# def update_progress(self) -> None:
|
# def update_progress(self) -> None:
|
||||||
# self.query_one(ProgressBar).advance(1)
|
# self.query_one(ProgressBar).advance(1)
|
||||||
|
|
||||||
def queue_song(self, song: SubSonic.Song) -> None:
|
def queue_song(self, song: SubSonic.Song) -> None:
|
||||||
logging.debug(f'Queue\'d song "{song.title}" by {song.artist}.')
|
logging.debug(f'Queue\'d song "{song.title}" by {song.artist}.')
|
||||||
|
self.notify(f'Queue\'d song "{song.title}" by {song.artist}.')
|
||||||
self.user_queue.append(song)
|
self.user_queue.append(song)
|
||||||
|
|
||||||
def action_play_pause_song(self) -> None:
|
def action_play_pause_song(self) -> None:
|
||||||
@@ -392,13 +454,13 @@ class TextualSubpyPlayer(App):
|
|||||||
if not self.currently_playing:
|
if not self.currently_playing:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if self.isPlaying:
|
if not self.is_paused:
|
||||||
self.place = pygame.mixer.music.get_pos()
|
self.place = pygame.mixer.music.get_pos()
|
||||||
pygame.mixer.music.pause()
|
pygame.mixer.music.pause()
|
||||||
self.isPlaying = False
|
self.is_paused = True
|
||||||
else:
|
else:
|
||||||
pygame.mixer.music.unpause()
|
pygame.mixer.music.unpause()
|
||||||
self.isPlaying = True
|
self.is_paused = False
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = TextualSubpyPlayer()
|
app = TextualSubpyPlayer()
|
||||||
|
|||||||
+11
-9
@@ -1,9 +1,11 @@
|
|||||||
[2025-01-24 14:38:51,125] DEBUG in selector_events: Using selector: EpollSelector
|
[2025-01-24 17:00:13,155] DEBUG in selector_events: Using selector: EpollSelector
|
||||||
[2025-01-24 14:38:51,902] DEBUG in screens-test: sus
|
[2025-01-24 17:00:15,224] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
[2025-01-24 14:38:51,930] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
[2025-01-24 17:00:15,621] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getStarred?u=pseudo&t=df47a647004f9967d54a717354d758bd&s=OEYQY6IP&v=1.16.1&c=testing&f=json HTTP/1.1" 200 None
|
||||||
[2025-01-24 14:38:52,091] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getPlaylists?u=pseudo&t=3b3e07bd0a56711c9db81acfb74b5560&s=2Q2MV419&v=1.16.1&c=testing&f=json HTTP/1.1" 200 705
|
[2025-01-24 17:00:18,614] DEBUG in screens-test: Playing song rap music by Lil Darkie.
|
||||||
[2025-01-24 14:38:52,947] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
[2025-01-24 17:00:18,615] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
[2025-01-24 14:38:53,121] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getPlaylist?u=pseudo&t=174305d05353398cf608a65562db736f&s=GQLIDGEJ&v=1.16.1&c=testing&f=json&id=e6e8ff27-2a60-4e33-8aa1-09586191166a HTTP/1.1" 200 None
|
[2025-01-24 17:00:18,843] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/stream?u=pseudo&t=17ea6e0ce1111a102699f10662381805&s=UUJ177KV&v=1.16.1&c=testing&id=62196e9d0d531adff738bf47349cbfc6 HTTP/1.1" 200 8464191
|
||||||
[2025-01-24 14:38:54,045] DEBUG in screens-test: Playing song Theia by King Gizzard & The Lizard Wizard.
|
[2025-01-24 17:00:20,392] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
[2025-01-24 14:38:54,046] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
[2025-01-24 17:00:20,628] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getCoverArt?u=pseudo&t=36a4b74c419b83821131781619811886&s=I4SC818D&v=1.16.1&c=testing&id=62196e9d0d531adff738bf47349cbfc6 HTTP/1.1" 200 None
|
||||||
[2025-01-24 14:38:54,249] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/stream?u=pseudo&t=61c596042c3be872619f0f39ddcc645d&s=LAPQRCLD&v=1.16.1&c=testing&id=9e28af41e8bfcf423a5a958cd7aca5cc HTTP/1.1" 200 8314893
|
[2025-01-24 17:03:36,276] DEBUG in screens-test: Playing song Pegasus Pro Ultra Fusion by Dan Terminus.
|
||||||
|
[2025-01-24 17:03:36,278] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-01-24 17:03:36,763] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/stream?u=pseudo&t=3b4bd01e32919d4f7868d0f89e702b93&s=94XX1BQN&v=1.16.1&c=testing&id=49f56133cf2f7bc6462be7620113ee79 HTTP/1.1" 200 None
|
||||||
Reference in new issue
Block a user