From e2f4e699dce96ef452c9b222015233b0d1e6a520 Mon Sep 17 00:00:00 2001 From: William Bell <62452284+Ugric@users.noreply.github.com> Date: Thu, 11 Dec 2025 07:33:03 +0000 Subject: [PATCH] change album cover layout and add improve player syncing when connection is dropped --- app.py | 86 +++++++++++++++++++++---------------------------------- player.py | 36 ++++++++++++++++------- 2 files changed, 58 insertions(+), 64 deletions(-) diff --git a/app.py b/app.py index 43d836b..7d6faac 100644 --- a/app.py +++ b/app.py @@ -120,7 +120,7 @@ def load_album_assets(): def setup_3d_environment(render_width, render_height): camera = pr.Camera3D() - camera.position = pr.Vector3(0.0, 0.0, 4.0) # Moved back slightly to fit the new models + camera.position = pr.Vector3(0.0, -0.35, 4.0) # Moved back slightly to fit the new models camera.target = pr.Vector3(0.0, 0.0, 0.0) camera.up = pr.Vector3(0.0, 1.0, 0.0) camera.fovy = 45.0 @@ -141,65 +141,28 @@ def draw_3d_cover_flow(camera, model): # -------------------------------------------------------- # Draw model at (0,0,0) with 1.0 scale pr.rl_push_matrix() - pr.rl_translatef(0.0, 0.0, 1.5) # Spaced out slightly more - pr.rl_rotatef(0.0, 0.0, 1.0, 0.0) # Sharper angle + pr.rl_translatef(0.0, -0.0, 1.5) # Spaced out slightly more + pr.rl_rotatef(5.0, 1.0, 0.0, 0.0) # Sharper angle pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.WHITE) pr.rl_pop_matrix() - # -------------------------------------------------------- - # 3. PREVIOUS ALBUM (Far Far Left) - # -------------------------------------------------------- - pr.rl_push_matrix() - pr.rl_translatef(-3.5, 0.0, 0.0) # Spaced out slightly more - pr.rl_rotatef(90.0, 0.0, 1.0, 0.0) # Sharper angle - pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.LIGHTGRAY) # Slightly darkened - pr.rl_pop_matrix() - # -------------------------------------------------------- - # 3. PREVIOUS ALBUM (Far Left) - # -------------------------------------------------------- - pr.rl_push_matrix() - pr.rl_translatef(-2.5, 0.0, 0.0) # Spaced out slightly more - pr.rl_rotatef(90.0, 0.0, 1.0, 0.0) # Sharper angle - pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.LIGHTGRAY) # Slightly darkened - pr.rl_pop_matrix() - - # -------------------------------------------------------- - # 3. PREVIOUS ALBUM (Near Left) - # -------------------------------------------------------- - pr.rl_push_matrix() - pr.rl_translatef(-1.5, 0.0, 0.5) # Added slight Z offset for depth - pr.rl_rotatef(65.0, 0.0, 1.0, 0.0) - pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.WHITE) - pr.rl_pop_matrix() + for i in range(-5, 0): + pr.rl_push_matrix() + pr.rl_translatef(-1.5+0.15*i, 0.0, 0.5) # Added slight Z offset for depth + pr.rl_rotatef(50.0, 0.0, 1.0, 0.0) + pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.WHITE) + pr.rl_pop_matrix() - # -------------------------------------------------------- - # 4. NEXT ALBUM (Near Right) - # -------------------------------------------------------- - pr.rl_push_matrix() - pr.rl_translatef(1.5, 0.0, 0.5) - pr.rl_rotatef(-65.0, 0.0, 1.0, 0.0) - pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.WHITE) - pr.rl_pop_matrix() - # -------------------------------------------------------- - # 4. NEXT ALBUM (Far Right) - # -------------------------------------------------------- - pr.rl_push_matrix() - pr.rl_translatef(2.5, 0.0, 0.0) - pr.rl_rotatef(-90.0, 0.0, 1.0, 0.0) - pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.LIGHTGRAY) - pr.rl_pop_matrix() - # -------------------------------------------------------- - # 4. NEXT ALBUM (Far Far Right) - # -------------------------------------------------------- - pr.rl_push_matrix() - pr.rl_translatef(3.5, 0.0, 0.0) - pr.rl_rotatef(-90.0, 0.0, 1.0, 0.0) - pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.LIGHTGRAY) - pr.rl_pop_matrix() - + for i in range(1,6): + pr.rl_push_matrix() + pr.rl_translatef(1.5+0.15*i, 0.0, 0.5) + pr.rl_rotatef(-50.0, 0.0, 1.0, 0.0) + pr.draw_model(model, pr.Vector3(0.0, 0.0, 0.0), 1.0, pr.WHITE) + pr.rl_pop_matrix() + pr.end_mode_3d() # --- Main Setup and Loop --- @@ -213,8 +176,24 @@ pr.set_target_fps(TARGET_FPS) player = FFQueuePlayer() print("add queue") +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("dab6efb24bb2372794d2b4fb53a12376")["Id"], server["AccessToken"], server["UserId"])) +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("58822c0fc47ec63ba798ba4f04ea3cf3")["Id"], server["AccessToken"], server["UserId"])) +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("6382005f9dbae8d187d80a5cdca3e7a6")["Id"], server["AccessToken"], server["UserId"])) +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("a5d2453e07a4998ea20e957c44f90be6")["Id"], server["AccessToken"], server["UserId"])) +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("398d481a7b85287ad200578b5ab997b0")["Id"], server["AccessToken"], server["UserId"])) +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("f9f32ca67be7f83139cee3c66e1e4965")["Id"], server["AccessToken"], server["UserId"])) +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("2f651e103b1fd22ea2f202d6f3398b36")["Id"], server["AccessToken"], server["UserId"])) +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("164b95968ab1a725fff060fa8c351cc8")["Id"], server["AccessToken"], server["UserId"])) + player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("38a6c21561f54d284a6acad89a3ea8b0")["Id"], server["AccessToken"], server["UserId"])) player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("631aeddb0557fef65f49463abb20ad7f")["Id"], server["AccessToken"], server["UserId"])) + +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("3d611c8664c5b2072edbf46da2a76c89")["Id"], server["AccessToken"], server["UserId"])) +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("66559c40d5904944a3f97198d0297894")["Id"], server["AccessToken"], server["UserId"])) +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("84b75eeb5c8e862d002bae05d2671b1b")["Id"], server["AccessToken"], server["UserId"])) +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("7ef66992426093252696e1d8666a22e4")["Id"], server["AccessToken"], server["UserId"])) +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("f37982227942d3df031381e653ec5790")["Id"], server["AccessToken"], server["UserId"])) +player.add_to_queue(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("0e8fc5fcf119de0439f5a15a4f255c5c")["Id"], server["AccessToken"], server["UserId"])) player.add_to_queue('music/pink floyd/dark side of the moon/01 Speak to Me.flac')#(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("99067e877d91be1a66eb5a7ff2f4128f")["Id"], server["AccessToken"], server["UserId"])) player.add_to_queue('music/pink floyd/dark side of the moon/02 Breathe (In the Air).flac')#(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("916eda422f48efd8705f29e0600a3e60")["Id"], server["AccessToken"], server["UserId"])) player.add_to_queue('music/pink floyd/dark side of the moon/03 On the Run.flac')#(build_jellyfin_audio_url(server["address"], client.jellyfin.get_item("5e1067d59ed98979ad12a58548b27b83")["Id"], server["AccessToken"], server["UserId"])) @@ -226,7 +205,6 @@ player.add_to_queue('music/pink floyd/dark side of the moon/08 Any Colour You Li player.add_to_queue('music/pink floyd/dark side of the moon/09 Brain Damage.flac') player.add_to_queue('music/pink floyd/dark side of the moon/10 Eclipse.flac') print("add queue done") -player.play() # Initial setup render_rect = get_3d_render_area(state["screen_width"], state["screen_height"]) diff --git a/player.py b/player.py index 0171633..f6e9104 100644 --- a/player.py +++ b/player.py @@ -16,6 +16,7 @@ import threading import queue import sys import io +import fcntl os.makedirs("logs", exist_ok=True) @@ -59,9 +60,10 @@ class FFQueuePlayer: def _open_ffmpeg(self, url, seek=0): self.song+=1 - return subprocess.Popen( + proc = subprocess.Popen( [ "ffmpeg", + # "-re", "-ss", str(seek), "-i", url, "-f", "s16le", @@ -73,6 +75,13 @@ class FFQueuePlayer: stdout=subprocess.PIPE, stderr=open('logs/'+str(self.song)+".txt", "wb") ) + + # --- make stdout non-blocking --- + fd = proc.stdout.fileno() + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) + + return proc def seek(self, pos): with self.lock: @@ -173,18 +182,25 @@ class FFQueuePlayer: elif self.next_preload_state == 0: self.preload_next_threaded() else: - data = self.proc.stdout.read(needed) or b'' + try: + data = self.proc.stdout.read(needed) or b'' + except BlockingIOError: + pass self.position += len(data) / (self.samplerate * self.channels * 2) if self.position >= self.playback_info_to_duration(self.playback_info)-10: self.preload_next_threaded() if self.proc.poll() is not None and len(data)= self.playback_info_to_duration(self.playback_info)-0.1: + self._start_next() + if self.proc is not None and self.proc.poll() is None: + try: + new_data = self.proc.stdout.read(needed-len(data)) or b'' + except BlockingIOError: + new_data = b'' + self.position += len(new_data) / (self.samplerate * self.channels * 2) + data += new_data else: - print("bruh") + self.proc = self._open_ffmpeg(self.current_file, self.position) outdata[:len(data)] = data @@ -204,7 +220,7 @@ def build_jellyfin_audio_url( """ Build a Jellyfin audio stream URL using urllib.parse. """ - path = f"/Items/{item_id}/Download" + path = f"/Audio/{item_id}/universal" params = { "UserId": user_id, @@ -226,7 +242,7 @@ def build_jellyfin_audio_url( client = JellyfinClient() load_dotenv() -client.config.app('FinPod', '0.0.1', 'FinPod prototype', 'FinPod_prototype_1') +client.config.app('UgPod', '0.0.1', 'UgPod prototype', 'UgPod_prototype_1') client.config.data["auth.ssl"] = True client.auth.connect_to_address(os.getenv("host"))