went back to working on the first verison, fixed queue updating on main screen + weird footer bug
This commit is contained in:
1 parent
4c1f5bfe82
commit
a07ee3bd52
6 files changed
+85
-60
No files matched your search
@@ -157,12 +157,13 @@ class MusicApp(App):
|
|||||||
if song_id:
|
if song_id:
|
||||||
self.play_song(song_id)
|
self.play_song(song_id)
|
||||||
|
|
||||||
|
# Needs to be in the main thread so pause / play work
|
||||||
|
# TODO @0x01fe put stream https call into thread?
|
||||||
@work(exclusive=True)
|
@work(exclusive=True)
|
||||||
async 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(NowPlaying.update_song, now_playing, song)
|
|
||||||
now_playing.update_song(song)
|
now_playing.update_song(song)
|
||||||
|
|
||||||
stream_data = conn.stream(song_id)
|
stream_data = conn.stream(song_id)
|
||||||
|
|||||||
+31
-3
@@ -1,3 +1,31 @@
|
|||||||
[2025-09-03 13:34:47,213] DEBUG in proactor_events: Using proactor: IocpProactor
|
[2025-09-04 14:49:01,708] DEBUG in proactor_events: Using proactor: IocpProactor
|
||||||
[2025-09-03 13:34:55,201] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
[2025-09-04 14:49:04,142] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
[2025-09-03 13:34:55,423] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getArtists?u=pseudo&t=e920f5780d30ae16dc5e7f65087de8e4&s=LHHshoee&v=1.16.1&c=testing&f=json HTTP/1.1" 200 None
|
[2025-09-04 14:49:04,379] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getArtists?u=pseudo&t=2b8f5742d39c788122e9175e065483e0&s=e4mfyt4T&v=1.16.1&c=testing&f=json HTTP/1.1" 200 None
|
||||||
|
[2025-09-04 14:49:05,321] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-09-04 14:49:05,494] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getAlbumList?u=pseudo&t=def2a86e92d32523f99a0747c6741047&s=BJ3TK656&v=1.16.1&c=testing&f=json&type=alphabeticalByName&size=50&offset=0 HTTP/1.1" 200 None
|
||||||
|
[2025-09-04 14:49:05,499] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-09-04 14:49:05,651] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getAlbumList?u=pseudo&t=0d05edbddb083b938bdf2e0543c8e44e&s=q39xsQJN&v=1.16.1&c=testing&f=json&type=alphabeticalByName&size=50&offset=50 HTTP/1.1" 200 None
|
||||||
|
[2025-09-04 14:49:05,654] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-09-04 14:49:05,822] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getAlbumList?u=pseudo&t=0296cab8954310b2b6d1af418fab6744&s=1qbZbaat&v=1.16.1&c=testing&f=json&type=alphabeticalByName&size=50&offset=100 HTTP/1.1" 200 None
|
||||||
|
[2025-09-04 14:49:05,828] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-09-04 14:49:05,978] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getAlbumList?u=pseudo&t=3b405a5c09c5259c8b2db318be52a577&s=MV94hD8j&v=1.16.1&c=testing&f=json&type=alphabeticalByName&size=50&offset=150 HTTP/1.1" 200 None
|
||||||
|
[2025-09-04 14:49:05,981] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-09-04 14:49:06,134] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getAlbumList?u=pseudo&t=c7b992e3538ed9e92f158b825eb6c3c8&s=VTK0roQ0&v=1.16.1&c=testing&f=json&type=alphabeticalByName&size=50&offset=200 HTTP/1.1" 200 None
|
||||||
|
[2025-09-04 14:49:06,137] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-09-04 14:49:06,353] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getAlbumList?u=pseudo&t=64fe71d647a2d62db5be7b84ccbae798&s=dEoTBmsf&v=1.16.1&c=testing&f=json&type=alphabeticalByName&size=50&offset=250 HTTP/1.1" 200 None
|
||||||
|
[2025-09-04 14:49:06,356] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-09-04 14:49:06,501] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getAlbumList?u=pseudo&t=0665f656905f113e49c2c96ac73fb164&s=Zw9rzvYM&v=1.16.1&c=testing&f=json&type=alphabeticalByName&size=50&offset=300 HTTP/1.1" 200 None
|
||||||
|
[2025-09-04 14:49:06,503] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-09-04 14:49:06,645] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getAlbumList?u=pseudo&t=a96115e891c5315e1ae6118407b6504e&s=9o6Jmnpf&v=1.16.1&c=testing&f=json&type=alphabeticalByName&size=50&offset=350 HTTP/1.1" 200 145
|
||||||
|
[2025-09-04 14:49:08,083] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-09-04 14:49:08,408] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getStarred?u=pseudo&t=f7f7bc6d80eb625439aab5a8567dfc79&s=eHkyJQuw&v=1.16.1&c=testing&f=json HTTP/1.1" 200 None
|
||||||
|
[2025-09-04 14:49:12,809] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-09-04 14:49:13,026] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getPlaylists?u=pseudo&t=509133849b592141690226ff28d1ca76&s=UMJx5VbS&v=1.16.1&c=testing&f=json HTTP/1.1" 200 1348
|
||||||
|
[2025-09-04 14:49:14,583] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-09-04 14:49:14,818] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/getPlaylist?u=pseudo&t=01048946d8acff208aeea45da130e0d6&s=aQgAJaQL&v=1.16.1&c=testing&f=json&id=e6e8ff27-2a60-4e33-8aa1-09586191166a HTTP/1.1" 200 None
|
||||||
|
[2025-09-04 14:49:16,421] DEBUG in screens-test: Playing song I'm in Your Mind by King Gizzard & the Lizard Wizard.
|
||||||
|
[2025-09-04 14:49:16,423] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-09-04 14:49:16,619] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/stream?u=pseudo&t=03ab421b3afbad3b4439c0274989b6b1&s=PbHKnSOb&v=1.16.1&c=testing&f=json&id=2dcd70c3954594c7ea60eb5afe38a476 HTTP/1.1" 200 3418948
|
||||||
|
[2025-09-04 14:49:34,597] DEBUG in screens-test: Playing song I'm Not in Your Mind by King Gizzard & the Lizard Wizard.
|
||||||
|
[2025-09-04 14:49:34,598] DEBUG in connectionpool: Starting new HTTPS connection (1): music.0x01fe.net:443
|
||||||
|
[2025-09-04 14:49:34,913] DEBUG in connectionpool: https://music.0x01fe.net:443 "GET /rest/stream?u=pseudo&t=8303d34b33fd11d14072307afcdfd724&s=WNJQhtAu&v=1.16.1&c=testing&f=json&id=4f3b5f7c7c146695db01ccfb36bdcb45 HTTP/1.1" 200 None
|
||||||
File renamed without changes.
@@ -7,7 +7,8 @@ LOGGING_LEVEL = logging.DEBUG
|
|||||||
LOG_FORMAT = '[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
|
LOG_FORMAT = '[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
|
||||||
|
|
||||||
# Clear Log File
|
# Clear Log File
|
||||||
os.remove(LOG_FILE)
|
if os.path.exists(LOG_FILE):
|
||||||
|
os.remove(LOG_FILE)
|
||||||
|
|
||||||
logging.basicConfig(filename=LOG_FILE, level=LOGGING_LEVEL, format=LOG_FORMAT)
|
logging.basicConfig(filename=LOG_FILE, level=LOGGING_LEVEL, format=LOG_FORMAT)
|
||||||
|
|
||||||
@@ -31,8 +32,7 @@ from textual.timer import Timer
|
|||||||
from rich_pixels import Pixels
|
from rich_pixels import Pixels
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
u = 'pseudo'
|
u, p = [s.strip() for s in open('auth', 'r').read().split(',')]
|
||||||
p = 'telegram9dock-MICHEL'
|
|
||||||
|
|
||||||
conn = SubSonic.SubpyConn(u, p, "https://music.0x01fe.net")
|
conn = SubSonic.SubpyConn(u, p, "https://music.0x01fe.net")
|
||||||
|
|
||||||
@@ -74,6 +74,7 @@ class AlbumSongs(Screen):
|
|||||||
song_index = self._find_selected_song()
|
song_index = self._find_selected_song()
|
||||||
app.play_song(self.songs[song_index])
|
app.play_song(self.songs[song_index])
|
||||||
app.song_queue = self.songs[song_index + 1:]
|
app.song_queue = self.songs[song_index + 1:]
|
||||||
|
app.update_queue_table()
|
||||||
|
|
||||||
def action_queue(self) -> None:
|
def action_queue(self) -> None:
|
||||||
app.queue_song(self._find_selected_song())
|
app.queue_song(self._find_selected_song())
|
||||||
@@ -238,6 +239,7 @@ class PlaylistSongs(Screen):
|
|||||||
song_index = self._find_selected_song()
|
song_index = self._find_selected_song()
|
||||||
app.play_song(self.songs[song_index])
|
app.play_song(self.songs[song_index])
|
||||||
app.song_queue = self.songs[song_index + 1:]
|
app.song_queue = self.songs[song_index + 1:]
|
||||||
|
app.update_queue_table()
|
||||||
|
|
||||||
def action_queue(self) -> None:
|
def action_queue(self) -> None:
|
||||||
app.queue_song(self._find_selected_song())
|
app.queue_song(self._find_selected_song())
|
||||||
@@ -281,6 +283,7 @@ class Favourites(Screen):
|
|||||||
song_index = self._find_selected_song()
|
song_index = self._find_selected_song()
|
||||||
app.play_song(self.songs[song_index])
|
app.play_song(self.songs[song_index])
|
||||||
app.song_queue = self.songs[song_index + 1:]
|
app.song_queue = self.songs[song_index + 1:]
|
||||||
|
app.update_queue_table()
|
||||||
|
|
||||||
def action_queue(self) -> None:
|
def action_queue(self) -> None:
|
||||||
app.queue_song(self._find_selected_song())
|
app.queue_song(self._find_selected_song())
|
||||||
@@ -291,28 +294,35 @@ class Favourites(Screen):
|
|||||||
if song.id == self.selected_song_id:
|
if song.id == self.selected_song_id:
|
||||||
return i
|
return i
|
||||||
|
|
||||||
|
# TODO fix coverart
|
||||||
class CoverArtPixels(Container):
|
class CoverArtPixels(Container):
|
||||||
|
|
||||||
cover_art_id: str | None = None
|
cover = reactive[Image.Image | None](None)
|
||||||
|
|
||||||
def set_cover_art(self, cover_art_id: str) -> None:
|
|
||||||
self.cover_art_id = cover_art_id
|
|
||||||
|
|
||||||
def render(self) -> RenderResult:
|
def render(self) -> RenderResult:
|
||||||
|
if self.cover:
|
||||||
if not self.cover_art_id:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# Make the size square
|
|
||||||
width, height = self.size
|
width, height = self.size
|
||||||
|
width -= 4
|
||||||
|
height -= 4
|
||||||
|
|
||||||
if width > height:
|
if width > height:
|
||||||
width = height
|
width = height
|
||||||
elif height > width:
|
elif height > width:
|
||||||
height = width
|
height = width
|
||||||
|
|
||||||
image = Image.open(io.BytesIO(conn.getCoverArtRaw(self.cover_art_id)))
|
return Pixels.from_image(self.cover, resize=(width, height))
|
||||||
return Pixels().from_image(image, resize=(width, height))
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@work(exclusive=True, thread=True)
|
||||||
|
def get_cover_art(self, cover_art_id: str) -> None:
|
||||||
|
try:
|
||||||
|
cover_art_data = conn.getCoverArtRaw(cover_art_id)
|
||||||
|
image = Image.open(io.BytesIO(cover_art_data))
|
||||||
|
self.cover = image
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to get cover art: {e}")
|
||||||
|
self.cover = None
|
||||||
|
|
||||||
class QueueTable(Widget):
|
class QueueTable(Widget):
|
||||||
|
|
||||||
@@ -367,68 +377,39 @@ class MainView(Screen):
|
|||||||
yield Label("Playing")
|
yield Label("Playing")
|
||||||
yield PlayingInfo()
|
yield PlayingInfo()
|
||||||
yield ProgressBar()
|
yield ProgressBar()
|
||||||
yield CoverArtPixels()
|
yield CoverArtPixels(id="coverart")
|
||||||
with Vertical(classes="column"):
|
with Vertical(classes="column"):
|
||||||
yield Label("Queue")
|
yield Label("Queue")
|
||||||
yield DataTable(id="queue")
|
yield DataTable(id="queue", cursor_type='row')
|
||||||
# yield VerticalScroll(QueueTable())
|
yield Footer()
|
||||||
# yield Footer()
|
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
app.sub_title = "Overview"
|
app.sub_title = "Overview"
|
||||||
queueTable: DataTable = self.query_one("#queue")
|
queueTable: DataTable = self.query_one(DataTable)
|
||||||
queueTable.cursor_type = "row"
|
|
||||||
queueTable.add_columns("Title", "Album", "Artist", "Plays", "Duration", "Bitrate")
|
queueTable.add_columns("Title", "Album", "Artist", "Plays", "Duration", "Bitrate")
|
||||||
|
|
||||||
# currentlyPlaying: DataTable = self.query_one('#currently_playing')
|
|
||||||
# currentlyPlaying.cursor_type = "row"
|
|
||||||
# currentlyPlaying.add_columns("", "")
|
|
||||||
|
|
||||||
@on(events.ScreenResume)
|
@on(events.ScreenResume)
|
||||||
def screen_resumed(self, event: events.ScreenResume) -> None:
|
def screen_resumed(self, event: events.ScreenResume) -> None:
|
||||||
app.sub_title = "Overview"
|
app.sub_title = "Overview"
|
||||||
|
self.update_queue()
|
||||||
|
|
||||||
# Update Queue Table
|
def update_queue(self) -> None:
|
||||||
queueTable: DataTable = self.query_one('#queue')
|
table = self.query_one(DataTable)
|
||||||
self._clear_table('#queue')
|
table.clear()
|
||||||
|
|
||||||
queue: list[SubSonic.Song] = []
|
queue: list[SubSonic.Song] = []
|
||||||
queue.extend(app.user_queue)
|
queue.extend(app.user_queue)
|
||||||
queue.extend(app.song_queue)
|
queue.extend(app.song_queue)
|
||||||
|
|
||||||
for song in queue:
|
for song in queue:
|
||||||
queueTable.add_row(song.title, song.album, song.artist, song.playCount, song.duration, song.bitRate)
|
table.add_row(song.title, song.album, song.artist, song.playCount, song.duration, song.bitRate)
|
||||||
|
|
||||||
# Update Currently Playing Table
|
|
||||||
# self._clear_table('#currently_playing')
|
|
||||||
if app.currently_playing:
|
|
||||||
# song = app.currently_playing
|
|
||||||
# currentlyPlaying: DataTable = self.query_one('#currently_playing')
|
|
||||||
# currentlyPlaying.add_row("Title", song.title)
|
|
||||||
# currentlyPlaying.add_row("Artist", song.artist)
|
|
||||||
# currentlyPlaying.add_row("Album", song.album)
|
|
||||||
# currentlyPlaying.add_row("Plays", song.playCount if song.playCount else 0)
|
|
||||||
# currentlyPlaying.add_row("Duration", song.duration)
|
|
||||||
# currentlyPlaying.add_row("Track Number", song.trackNumber)
|
|
||||||
# currentlyPlaying.add_row("Bit Rate", song.bitRate)
|
|
||||||
|
|
||||||
self.query_one(CoverArtPixels).set_cover_art(app.currently_playing.id)
|
|
||||||
|
|
||||||
def _clear_table(self, table_id: str) -> None:
|
|
||||||
table: DataTable = self.query_one(table_id)
|
|
||||||
|
|
||||||
row_keys = []
|
|
||||||
for row_key, row in table.rows.items():
|
|
||||||
row_keys.append(row_key)
|
|
||||||
|
|
||||||
for row_key in row_keys:
|
|
||||||
table.remove_row(row_key)
|
|
||||||
|
|
||||||
|
|
||||||
class TextualSubpyPlayer(App):
|
class TextualSubpyPlayer(App):
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
("r", "rewind()", "⏪"),
|
("r", "rewind()", "⏪"),
|
||||||
("space", "play_pause_song()", "⏯️"),
|
("space", "play_pause_song()", "⏯"),
|
||||||
("s", "skip()", "⏭️"),
|
("s", "skip()", "⏭"),
|
||||||
("f1", "switch_mode('main')", "Overview"),
|
("f1", "switch_mode('main')", "Overview"),
|
||||||
("f2", "switch_mode('artists')", "Artists"),
|
("f2", "switch_mode('artists')", "Artists"),
|
||||||
("f3", "switch_mode('albums')", "Albums"),
|
("f3", "switch_mode('albums')", "Albums"),
|
||||||
@@ -477,6 +458,8 @@ class TextualSubpyPlayer(App):
|
|||||||
|
|
||||||
self.check_playing_timer = self.set_interval(1 / 5, self.check_done_playing, pause=False)
|
self.check_playing_timer = self.set_interval(1 / 5, self.check_done_playing, pause=False)
|
||||||
|
|
||||||
|
self.update_queue_table()
|
||||||
|
|
||||||
# playingLabel: Label = self.query_one(Label)
|
# playingLabel: Label = self.query_one(Label)
|
||||||
# playingLabel.
|
# playingLabel.
|
||||||
|
|
||||||
@@ -520,7 +503,7 @@ class TextualSubpyPlayer(App):
|
|||||||
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.notify(f'Queue\'d song "{song.title}" by {song.artist}.')
|
||||||
self.user_queue.append(song)
|
self.update_user_queue(song)
|
||||||
|
|
||||||
def action_play_pause_song(self) -> None:
|
def action_play_pause_song(self) -> None:
|
||||||
self.play_pause_song()
|
self.play_pause_song()
|
||||||
@@ -539,6 +522,19 @@ class TextualSubpyPlayer(App):
|
|||||||
pygame.mixer.music.unpause()
|
pygame.mixer.music.unpause()
|
||||||
self.is_paused = False
|
self.is_paused = False
|
||||||
|
|
||||||
|
def update_queue_table(self) -> None:
|
||||||
|
if isinstance(self.screen, MainView):
|
||||||
|
logging.debug("Updating queue through app")
|
||||||
|
self.screen.update_queue()
|
||||||
|
|
||||||
|
def update_user_queue(self, song: SubSonic.Song) -> None:
|
||||||
|
self.user_queue.append(song)
|
||||||
|
self.update_queue_table()
|
||||||
|
|
||||||
|
def update_song_queue(self, song: SubSonic.Song) -> None:
|
||||||
|
self.song_queue.append(song)
|
||||||
|
self.update_queue_table()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = TextualSubpyPlayer()
|
app = TextualSubpyPlayer()
|
||||||
app.run()
|
app.run()
|
||||||
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user