summaryrefslogtreecommitdiff
path: root/tidal_dl_ng/gui/covers.py
diff options
context:
space:
mode:
authorWarry <FunWarry@users.noreply.github.com>2025-12-04 12:03:31 +0100
committerWarry <FunWarry@users.noreply.github.com>2025-12-04 12:03:31 +0100
commit75868b32d07aafe46b69649d2da0dc2c833f112d (patch)
tree970a50f4cbf141af0e5e0856e087e347f3b5be48 /tidal_dl_ng/gui/covers.py
parent428d34056be16ed9d28597253b333dfbd7f21a47 (diff)
downloadtidal-dl-ng-75868b32d07aafe46b69649d2da0dc2c833f112d.tar.gz
refactor: Restructure GUI components into separate modules for improved maintainability
Diffstat (limited to 'tidal_dl_ng/gui/covers.py')
-rw-r--r--tidal_dl_ng/gui/covers.py161
1 files changed, 161 insertions, 0 deletions
diff --git a/tidal_dl_ng/gui/covers.py b/tidal_dl_ng/gui/covers.py
new file mode 100644
index 0000000..858ab11
--- /dev/null
+++ b/tidal_dl_ng/gui/covers.py
@@ -0,0 +1,161 @@
+"""Cover management for GUI - Handles cover loading, caching and display."""
+
+from contextlib import suppress
+
+from PySide6 import QtGui
+
+from tidal_dl_ng.cache import CoverPixmapCache
+from tidal_dl_ng.download import Download
+from tidal_dl_ng.helper.path import resource_path
+from tidal_dl_ng.logger import logger_gui
+from tidal_dl_ng.worker import Worker
+
+
+class CoverManager:
+ """Manages cover art loading, caching and display operations."""
+
+ def __init__(self, parent_window, threadpool, info_tab_widget):
+ """Initialize the cover manager.
+
+ Args:
+ parent_window: Main window instance
+ threadpool: QThreadPool for async operations
+ info_tab_widget: InfoTabWidget instance for display
+ """
+ self.parent = parent_window
+ self.threadpool = threadpool
+ self.info_tab = info_tab_widget
+ self.cache = CoverPixmapCache()
+ self.cover_url_current = ""
+
+ def load_cover(self, media, use_cache_check: bool = True):
+ """Load and display cover for media item.
+
+ Args:
+ media: Media object (Track, Album, etc.)
+ use_cache_check: If True, check cache before loading
+ """
+ if use_cache_check:
+ # Try cache first for instant display
+ cover_url = self._get_cover_url(media)
+ if cover_url:
+ if cover_url == self.cover_url_current:
+ return # Already displayed
+
+ cached_pixmap = self.cache.get(cover_url)
+ if cached_pixmap:
+ self._display_cover(cached_pixmap, cover_url)
+ return
+
+ # Load asynchronously
+ worker = Worker(self._load_cover_async, media)
+ self.threadpool.start(worker)
+
+ def _load_cover_async(self, media):
+ """Load cover in background thread."""
+ # Emit spinner on tab widget instead of InfoTabWidget (which is QObject, not QWidget)
+ tab_widget = self.info_tab.tab_widget if hasattr(self.info_tab, "tab_widget") else None
+ if tab_widget:
+ self.parent.s_spinner_start.emit(tab_widget)
+
+ try:
+ cover_url = self._get_cover_url(media)
+
+ if cover_url and self.cover_url_current != cover_url:
+ # Check cache again (thread-safe)
+ cached_pixmap = self.cache.get(cover_url)
+ if cached_pixmap:
+ self._display_cover(cached_pixmap, cover_url)
+ else:
+ # Download and cache
+ data_cover = Download.cover_data(cover_url)
+ pixmap = QtGui.QPixmap()
+ pixmap.loadFromData(data_cover)
+ self.cache.set(cover_url, pixmap)
+ self._display_cover(pixmap, cover_url)
+ elif not cover_url:
+ self._display_default_cover()
+ except Exception as e:
+ logger_gui.warning(f"Failed to load cover: {e}")
+ self._display_default_cover()
+ finally:
+ self.parent.s_spinner_stop.emit()
+
+ def _get_cover_url(self, media) -> str | None:
+ """Extract cover URL from media object."""
+ with suppress(Exception):
+ if hasattr(media, "album") and media.album:
+ return media.album.image()
+ if hasattr(media, "image") and callable(getattr(media, "image", None)):
+ return media.image()
+ return None
+
+ def _display_cover(self, pixmap: QtGui.QPixmap, url: str):
+ """Display a pixmap on the info tab."""
+ self.info_tab.set_cover_pixmap(pixmap)
+ self.info_tab.cover_url_current = url
+ self.cover_url_current = url
+
+ def _display_default_cover(self):
+ """Display default cover image."""
+ path_image = resource_path("tidal_dl_ng/ui/default_album_image.png")
+ pixmap = QtGui.QPixmap(path_image)
+ self.info_tab.set_cover_pixmap(pixmap)
+ self.info_tab.cover_url_current = ""
+ self.cover_url_current = ""
+
+ def preload_covers_for_playlist(self, items: list) -> None:
+ """Preload cover pixmaps for a list of tracks in background.
+
+ Args:
+ items: List of Track/Video objects to preload covers for.
+ """
+
+ def worker() -> None:
+ # Extract unique cover URLs
+ cover_urls = set()
+ for item in items[:50]: # Limit to first 50
+ with suppress(Exception):
+ url = self._get_cover_url(item)
+ if url:
+ cover_urls.add(url)
+
+ # Preload each unique cover
+ for cover_url in cover_urls:
+ if self.cache.get(cover_url):
+ continue # Already cached
+
+ with suppress(Exception):
+ data_cover = Download.cover_data(cover_url)
+ pixmap = QtGui.QPixmap()
+ pixmap.loadFromData(data_cover)
+ self.cache.set(cover_url, pixmap)
+ logger_gui.debug(f"Preloaded cover: {cover_url[:50]}...")
+
+ worker_obj = Worker(worker)
+ self.threadpool.start(worker_obj)
+
+ def _queue_cover_fetch(self, media):
+ """Queue a cover fetch operation for a media item."""
+ with suppress(Exception):
+ # previously try/except/pass
+ self.threadpool.start(Worker(self.load_cover, media))
+
+ def _fetch_cover_pixmap(self, media, use_cache_check: bool):
+ """Fetch cover pixmap for media, with optional cache check."""
+ with suppress(Exception):
+ # previously try/except/continue inside loop
+ pixmap = self.cache.get(self._get_cover_url(media))
+ if pixmap:
+ return pixmap
+
+ # If not in cache, download cover
+ cover_url = self._get_cover_url(media)
+ if cover_url:
+ data_cover = Download.cover_data(cover_url)
+ pixmap = QtGui.QPixmap()
+ pixmap.loadFromData(data_cover)
+ self.cache.set(cover_url, pixmap)
+ return pixmap
+
+ return None