diff options
Diffstat (limited to 'tidal_dl_ng/gui/covers.py')
| -rw-r--r-- | tidal_dl_ng/gui/covers.py | 161 |
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 |
