diff --git a/src/window/controlcenter/expanded_player.py b/src/window/controlcenter/expanded_player.py index 761cccf..47f370e 100644 --- a/src/window/controlcenter/expanded_player.py +++ b/src/window/controlcenter/expanded_player.py @@ -1,10 +1,7 @@ import weakref from fabric.utils import ( - GLib, bulk_connect, - get_relative_path, - os, ) from fabric.widgets.box import Box from fabric.widgets.button import Button @@ -15,20 +12,13 @@ from fabric.widgets.scale import Scale from fabric.widgets.stack import Stack from fabric.widgets.wayland import WaylandWindow as Window -import shared.data as data from services.mpris import PlayerManager, PlayerService from utils.utils import svg_file - -CACHE_DIR = f"{data.CACHE_DIR}/media" -_shared_mpris_manager = None -_artwork_cache = {} - - -def get_shared_mpris_manager(): - global _shared_mpris_manager - if _shared_mpris_manager is None: - _shared_mpris_manager = PlayerManager() - return _shared_mpris_manager +from window.controlcenter.player import ( + apply_player_art, + get_shared_mpris_manager, + PLAYER_FALLBACK_ART, +) class EmbeddedExpandedPlayer(Box): @@ -97,8 +87,8 @@ class PlayerBoxStack(Box): super().destroy() def _create_no_media_box(self): - fallback_cover_path = get_relative_path("../../assets/icons/music.svg") - album_cover = Box(name="macos-album-image-no") + fallback_cover_path = PLAYER_FALLBACK_ART + album_cover = Box(style_classes="album-image-c") album_cover.set_style(f"background-image:url('{fallback_cover_path}')") track_title = Label( @@ -284,7 +274,7 @@ class PlayerBox(Box): super().__init__(h_align="center", name="player-box", h_expand=True, **kwargs) self.player = player self.player_stack = player_stack - self.fallback_cover_path = get_relative_path("../../assets/icons/music.svg") + self.fallback_cover_path = PLAYER_FALLBACK_ART self.cover_path = self.fallback_cover_path self.exit = False self._user_seeking = False @@ -292,7 +282,7 @@ class PlayerBox(Box): self._property_bindings = [] self._seekbar_signal_ids = [] - self.album_cover = Box(name="macos-album-image") + self.album_cover = Box(style_classes="album-image-c") self.album_cover.set_style( f"background-image:url('{self.fallback_cover_path}')" ) @@ -505,11 +495,9 @@ class PlayerBox(Box): self.seek_bar.connect("realize", self._on_seek_bar_realized) - initial_art = self.player.get_artwork() - if initial_art: - self.album_cover.set_style(f"background-image:url('{initial_art}')") - else: - self.set_image() + result = apply_player_art(self.album_cover, self.player) + if result: + self.cover_path = result def update_buttons(self, player_buttons, show_buttons): self.stack_buttons_box.children = [] @@ -612,56 +600,18 @@ class PlayerBox(Box): self.player_stack.on_player_playback_changed(self, status) def _on_artwork_change(self, service, local_path, *_): - if not self.exit and local_path: - GLib.idle_add( - lambda: ( - self.album_cover.set_style(f"background-image:url('{local_path}')") - if os.path.isfile(local_path) - else None - ) - ) + if self.exit: + return + result = apply_player_art(self.album_cover, self.player, service, local_path) + if result: + self.cover_path = result def set_image(self, service=None, path=None, *_): if self.exit or self.player is None: return - if path: - self.cover_path = path - self.album_cover.set_style(f"background-image:url('{self.cover_path}')") - return - url = self.player.arturl - if url: - new_cover_path = ( - ( - CACHE_DIR - + "/" - + GLib.compute_checksum_for_string(GLib.ChecksumType.SHA1, url, -1) - ) - if "file://" != url[0:7] - else url[7:] - ) - if new_cover_path != self.cover_path: - self.cover_path = new_cover_path - if os.path.exists(self.cover_path): - self.album_cover.set_style( - f"background-image:url('{self.cover_path}')" - ) - return - from fabric.utils import Gio - - Gio.File.new_for_uri(uri=url).copy_async( - Gio.File.new_for_path(self.cover_path), - Gio.FileCopyFlags.OVERWRITE, - GLib.PRIORITY_DEFAULT, - None, - None, - lambda src, res, *_: ( - self.album_cover.set_style( - f"background-image:url('{self.cover_path}')" - ) - if not self.exit and os.path.isfile(self.cover_path) - else None - ), - ) + result = apply_player_art(self.album_cover, self.player, service, path) + if result: + self.cover_path = result def _on_track_position(self, service, pos: float, dur: float): if self.exit or self._user_seeking: diff --git a/src/window/controlcenter/player.py b/src/window/controlcenter/player.py index 6be8d8d..39325bd 100644 --- a/src/window/controlcenter/player.py +++ b/src/window/controlcenter/player.py @@ -1,4 +1,4 @@ -from fabric.utils import Gio, GLib, bulk_connect, get_relative_path, logger, os +from fabric.utils import bulk_connect, get_relative_path, logger, os from fabric.widgets.box import Box from fabric.widgets.button import Button from fabric.widgets.centerbox import CenterBox @@ -12,7 +12,7 @@ from services.mpris import PlayerManager, PlayerService from utils.utils import svg_file CACHE_DIR = f"{data.CACHE_DIR}/media" -MEDIA_CACHE = CACHE_DIR +PLAYER_FALLBACK_ART = get_relative_path("../../assets/icons/music.svg") if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR) @@ -36,6 +36,31 @@ def cleanup_shared_mpris_manager(): _shared_mpris_manager = None +def apply_player_art(album_cover_widget, player, service=None, path=None): + """ + Single source of truth for applying album art to any player widget. + + Call patterns: + - On init / metadata change: apply_player_art(widget, player) + - From artwork-change signal: apply_player_art(widget, player, service_obj, local_path) + + Returns the applied path, or None if no art is available yet (backend will + emit artwork-change when the async download completes). + """ + # artwork-change signal emits (service_obj, local_path_str) + if path and isinstance(path, str) and os.path.exists(path): + album_cover_widget.set_style(f"background-image:url('{path}')") + return path + # Check if backend already has it cached from a previous download + if player: + cached = player.get_artwork() + if cached and os.path.exists(cached): + album_cover_widget.set_style(f"background-image:url('{cached}')") + return cached + # Nothing yet — backend will emit artwork-change when download finishes + return None + + class PlayerBoxStack(Box): def __init__( self, mpris_manager: PlayerManager = None, control_center=None, **kwargs @@ -275,7 +300,7 @@ class PlayerBox(Box): self.player = player self.player_stack = player_stack self.control_center = control_center - self.cover_path = get_relative_path("../../assets/icons/music.svg") + self.cover_path = PLAYER_FALLBACK_ART self.exit = False self._signal_connections = [] @@ -394,15 +419,15 @@ class PlayerBox(Box): "play": self._on_playback_change, "pause": self._on_playback_change, "meta-change": self._on_metadata, + "artwork-change": self.set_image, }, ) for handler_id in connections: self._signal_connections.append((self.player, handler_id)) - initial_art = self.player.get_artwork() - if initial_art: - self.cover_path = initial_art - self.album_cover.set_style(f"background-image:url('{self.cover_path}')") + result = apply_player_art(self.album_cover, self.player) + if result: + self.cover_path = result def _on_outer_box_clicked(self, *_): if self.control_center and hasattr(self.control_center, "open_expanded_player"): @@ -442,42 +467,9 @@ class PlayerBox(Box): def set_image(self, service=None, path=None, *_): if self.exit or self.player is None: return - if path: - self.cover_path = path - self.album_cover.set_style(f"background-image:url('{self.cover_path}')") - return - url = self.player.arturl - if url: - new_cover_path = ( - ( - MEDIA_CACHE - + "/" - + GLib.compute_checksum_for_string(GLib.ChecksumType.SHA1, url, -1) - ) - if "file://" != url[0:7] - else url[7:] - ) - if new_cover_path != self.cover_path: - self.cover_path = new_cover_path - if os.path.exists(self.cover_path): - self.album_cover.set_style( - f"background-image:url('{self.cover_path}')" - ) - return - Gio.File.new_for_uri(uri=url).copy_async( - Gio.File.new_for_path(self.cover_path), - Gio.FileCopyFlags.OVERWRITE, - GLib.PRIORITY_DEFAULT, - None, - None, - lambda src, res, *_: ( - self.album_cover.set_style( - f"background-image:url('{self.cover_path}')" - ) - if not self.exit and os.path.isfile(self.cover_path) - else None - ), - ) + result = apply_player_art(self.album_cover, self.player, service, path) + if result: + self.cover_path = result def suspend(self): self.exit = True