From 08cd9dbe6593219820fb93e78e7136d74f867629 Mon Sep 17 00:00:00 2001 From: Olli Date: Sat, 10 Jan 2026 15:26:49 +0100 Subject: [PATCH 1/5] =?UTF-8?q?Set=20pixel=20size=20for=20=E2=80=9Cstandal?= =?UTF-8?q?one=E2=80=9D=20images=20(close=20#109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/coverpanel.py | 1 + src/librarypanel.py | 1 + src/playlistpanel.py | 1 + 3 files changed, 3 insertions(+) diff --git a/src/coverpanel.py b/src/coverpanel.py index d9c5d31..312d4da 100644 --- a/src/coverpanel.py +++ b/src/coverpanel.py @@ -251,4 +251,5 @@ class CoverPanel(Gtk.Overlay): return self.cover_image.set_from_pixbuf( pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER)) + self.cover_image.set_pixel_size(min(width, height)) self.cover_image.show() diff --git a/src/librarypanel.py b/src/librarypanel.py index e2ae1fb..fd56f9f 100644 --- a/src/librarypanel.py +++ b/src/librarypanel.py @@ -456,6 +456,7 @@ class LibraryPanel(Adw.Bin): # Pixelpuffer auf Oberfläche zeichnen self.standalone_image.set_from_pixbuf( pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER)) + self.standalone_image.set_pixel_size(min(width, height)) self.standalone_image.show() def _get_default_image(self): diff --git a/src/playlistpanel.py b/src/playlistpanel.py index 34abfee..a9b1d8a 100644 --- a/src/playlistpanel.py +++ b/src/playlistpanel.py @@ -263,6 +263,7 @@ class PlaylistPanel(Adw.Bin): # Pixelpuffer auf Oberfläche zeichnen self.standalone_image.set_from_pixbuf( pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER)) + self.standalone_image.set_pixel_size(min(width, height)) self.standalone_image.show() def _get_default_image(self): From 9311f9974a1610c310fd10b9b7e925c1714596c7 Mon Sep 17 00:00:00 2001 From: Olli Date: Sat, 10 Jan 2026 15:42:24 +0100 Subject: [PATCH 2/5] Do not try to convert default icon to GDK pixbuf (close #110) --- src/coverpanel.py | 1 + src/librarypanel.py | 27 +++++++++++++++------------ src/playlistpanel.py | 26 +++++++++++++++----------- src/utils.py | 7 ++++--- 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/coverpanel.py b/src/coverpanel.py index 312d4da..b6f3c67 100644 --- a/src/coverpanel.py +++ b/src/coverpanel.py @@ -236,6 +236,7 @@ class CoverPanel(Gtk.Overlay): pixbuf = self._cover_pixbuf # Check pixelbuffer if pixbuf is None: + self.cover_default.set_pixel_size(min(size_width, size_height)/2) return # Skalierungswert für Breite und Höhe ermitteln diff --git a/src/librarypanel.py b/src/librarypanel.py index fd56f9f..83cafe2 100644 --- a/src/librarypanel.py +++ b/src/librarypanel.py @@ -293,15 +293,13 @@ class LibraryPanel(Adw.Bin): def set_albumart(self, album, data): if album in self._selected_albums: + self._standalone_pixbuf = None if data: # Load image and draw it try: self._standalone_pixbuf = Utils.load_pixbuf(data) except Exception: self._logger.exception("Failed to set albumart") - self._standalone_pixbuf = self._get_default_image() - else: - self._standalone_pixbuf = self._get_default_image() # Show image GObject.idle_add(self._show_image) @@ -335,6 +333,8 @@ class LibraryPanel(Adw.Bin): self._grid_pixbufs.clear() for album_id in albums.keys(): album = albums[album_id] + grid_item = GridItem(album) + pixbuf = None try: pixbuf = Utils.load_thumbnail(cache, self._client, album, size) @@ -344,14 +344,13 @@ class LibraryPanel(Adw.Bin): except Exception as e: self._logger.exception("Failed to load albumart", e) if pixbuf is None: - pixbuf = self._icon_theme.lookup_icon( - Utils.STOCK_ICON_DEFAULT, None, self._item_size, - self._item_size, Gtk.TextDirection.LTR, - Gtk.IconLookupFlags.FORCE_SYMBOLIC) - if pixbuf is not None: + icon = self._get_default_icon(self._item_size, self._item_size) + grid_item.set_icon(icon) + else: self._grid_pixbufs[album.get_id()] = pixbuf - GObject.idle_add(self._library_grid_model.append, - GridItem(album, pixbuf)) + grid_item.set_cover(pixbuf) + + GObject.idle_add(self._library_grid_model.append, grid_item) i += 1 GObject.idle_add(self.progress_bar.set_fraction, i / n) @@ -440,6 +439,9 @@ class LibraryPanel(Adw.Bin): pixbuf = self._standalone_pixbuf # Check pixelbuffer if pixbuf is None: + icon = self._get_default_icon(size_width, size_height) + self.standalone_image.set_from_paintable(icon) + self.standalone_image.set_pixel_size(min(size_width, size_height)/2) return # Skalierungswert für Breite und Höhe ermitteln @@ -459,9 +461,10 @@ class LibraryPanel(Adw.Bin): self.standalone_image.set_pixel_size(min(width, height)) self.standalone_image.show() - def _get_default_image(self): + def _get_default_icon(self, width, height): return self._icon_theme.lookup_icon(Utils.STOCK_ICON_DEFAULT, None, - 512, 512, Gtk.TextDirection.LTR, + width, height, + Gtk.TextDirection.LTR, Gtk.IconLookupFlags.FORCE_SYMBOLIC) def _get_selected_albums(self): diff --git a/src/playlistpanel.py b/src/playlistpanel.py index a9b1d8a..3c18e9c 100644 --- a/src/playlistpanel.py +++ b/src/playlistpanel.py @@ -171,15 +171,13 @@ class PlaylistPanel(Adw.Bin): def set_albumart(self, album, data): if album in self._selected_albums: + self._standalone_pixbuf = None if data: # Load image and draw it try: self._standalone_pixbuf = Utils.load_pixbuf(data) except Exception: self._logger.exception("Failed to set albumart") - self._cover_pixbuf = self._get_default_image() - else: - self._cover_pixbuf = self._get_default_image() # Show image GObject.idle_add(self._show_image) @@ -197,6 +195,8 @@ class PlaylistPanel(Adw.Bin): cache = client.MCGCache(host, size) for album in playlist: + grid_item = GridItem(album) + pixbuf = None # Load albumart thumbnail try: @@ -207,12 +207,12 @@ class PlaylistPanel(Adw.Bin): except Exception: self._logger.exception("Failed to load albumart") if pixbuf is None: - pixbuf = self._icon_theme.lookup_icon( - Utils.STOCK_ICON_DEFAULT, None, self._item_size, - self._item_size, Gtk.TextDirection.LTR, - Gtk.IconLookupFlags.FORCE_SYMBOLIC) - if pixbuf is not None: - self._playlist_grid_model.append(GridItem(album, pixbuf)) + icon = self._get_default_icon(self._item_size, self._item_size) + grid_item.set_icon(icon) + else: + grid_item.set_cover(pixbuf) + + GObject.idle_add(self._playlist_grid_model.append, grid_item) if self._playlist_stop.is_set(): self._playlist_lock.release() @@ -247,6 +247,9 @@ class PlaylistPanel(Adw.Bin): pixbuf = self._standalone_pixbuf # Check pixelbuffer if pixbuf is None: + icon = self._get_default_icon(size_width, size_height) + self.standalone_image.set_from_paintable(icon) + self.standalone_image.set_pixel_size(min(size_width, size_height)/2) return # Skalierungswert für Breite und Höhe ermitteln @@ -266,9 +269,10 @@ class PlaylistPanel(Adw.Bin): self.standalone_image.set_pixel_size(min(width, height)) self.standalone_image.show() - def _get_default_image(self): + def _get_default_icon(self, width, height): return self._icon_theme.lookup_icon(Utils.STOCK_ICON_DEFAULT, None, - 512, 512, Gtk.TextDirection.LTR, + width, height, + Gtk.TextDirection.LTR, Gtk.IconLookupFlags.FORCE_SYMBOLIC) def _get_selected_albums(self): diff --git a/src/utils.py b/src/utils.py index ed8a04f..89b33af 100644 --- a/src/utils.py +++ b/src/utils.py @@ -86,11 +86,9 @@ class GridItem(GObject.GObject): tooltip = GObject.Property(type=str, default=None) cover = GObject.Property(type=Gdk.Paintable, default=None) - def __init__(self, album, cover): + def __init__(self, album): super().__init__() self._album = album - if cover: - self.cover = Gdk.Texture.new_for_pixbuf(cover) self.tooltip = GObject.markup_escape_text("\n".join([ album.get_title(), ', '.join(album.get_dates()), Utils.create_artists_label(album), @@ -103,6 +101,9 @@ class GridItem(GObject.GObject): def set_cover(self, cover): self.cover = Gdk.Texture.new_for_pixbuf(cover) + def set_icon(self, icon): + self.cover = icon + class SearchFilter(Gtk.Filter): From 0a109bc886a0b69824c26ea84358576fbc872d85 Mon Sep 17 00:00:00 2001 From: Olli Date: Sat, 10 Jan 2026 16:04:43 +0100 Subject: [PATCH 3/5] Fix handling and logging of thumbnail save failures --- src/librarypanel.py | 4 ++-- src/utils.py | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/librarypanel.py b/src/librarypanel.py index 83cafe2..22a8b43 100644 --- a/src/librarypanel.py +++ b/src/librarypanel.py @@ -341,8 +341,8 @@ class LibraryPanel(Adw.Bin): except client.CommandException: # Exception is handled by client pass - except Exception as e: - self._logger.exception("Failed to load albumart", e) + except Exception: + self._logger.exception("Failed to load albumart") if pixbuf is None: icon = self._get_default_icon(self._item_size, self._item_size) grid_item.set_icon(icon) diff --git a/src/utils.py b/src/utils.py index 89b33af..4b58fb9 100644 --- a/src/utils.py +++ b/src/utils.py @@ -3,6 +3,7 @@ import gi import hashlib import locale +import logging import os gi.require_version('Gtk', '4.0') @@ -37,7 +38,12 @@ class Utils: if pixbuf is not None: pixbuf = pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.HYPER) - pixbuf.savev(cache_url, 'jpeg', [], []) + try: + pixbuf.savev(cache_url, 'jpeg', [], []) + except Exception as e: + logger = logging.getLogger(__name__) + logger.warning("Failed to save thumbnail for album\"%s\": " + "%s", album.get_title(), e) return pixbuf @staticmethod From 9b29f7b274d8553ade36313b6ff4ed271c35b9b3 Mon Sep 17 00:00:00 2001 From: Olli Date: Sat, 10 Jan 2026 16:22:03 +0100 Subject: [PATCH 4/5] Preserve aspect ratio of album covers in grid views (close #111) --- src/librarypanel.py | 12 +++--------- src/playlistpanel.py | 12 +++--------- src/utils.py | 19 ++++++++++++++++++- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/librarypanel.py b/src/librarypanel.py index 22a8b43..02d8c08 100644 --- a/src/librarypanel.py +++ b/src/librarypanel.py @@ -444,15 +444,9 @@ class LibraryPanel(Adw.Bin): self.standalone_image.set_pixel_size(min(size_width, size_height)/2) return - # Skalierungswert für Breite und Höhe ermitteln - ratio_w = float(size_width) / float(pixbuf.get_width()) - ratio_h = float(size_height) / float(pixbuf.get_height()) - # Kleineren beider Skalierungswerte nehmen, nicht Hochskalieren - ratio = min(ratio_w, ratio_h) - ratio = min(ratio, 1) - # Neue Breite und Höhe berechnen - width = int(math.floor(pixbuf.get_width() * ratio)) - height = int(math.floor(pixbuf.get_height() * ratio)) + (width, height) = Utils.calculate_size(pixbuf.get_width(), + pixbuf.get_height(), size_width, + size_height) if width <= 0 or height <= 0: return # Pixelpuffer auf Oberfläche zeichnen diff --git a/src/playlistpanel.py b/src/playlistpanel.py index 3c18e9c..9f59e93 100644 --- a/src/playlistpanel.py +++ b/src/playlistpanel.py @@ -252,15 +252,9 @@ class PlaylistPanel(Adw.Bin): self.standalone_image.set_pixel_size(min(size_width, size_height)/2) return - # Skalierungswert für Breite und Höhe ermitteln - ratio_w = float(size_width) / float(pixbuf.get_width()) - ratio_h = float(size_height) / float(pixbuf.get_height()) - # Kleineren beider Skalierungswerte nehmen, nicht Hochskalieren - ratio = min(ratio_w, ratio_h) - ratio = min(ratio, 1) - # Neue Breite und Höhe berechnen - width = int(math.floor(pixbuf.get_width() * ratio)) - height = int(math.floor(pixbuf.get_height() * ratio)) + (width, height) = Utils.calculate_size(pixbuf.get_width(), + pixbuf.get_height(), size_width, + size_height) if width <= 0 or height <= 0: return # Pixelpuffer auf Oberfläche zeichnen diff --git a/src/utils.py b/src/utils.py index 4b58fb9..544e415 100644 --- a/src/utils.py +++ b/src/utils.py @@ -2,6 +2,7 @@ import gi import hashlib +import math import locale import logging import os @@ -36,7 +37,10 @@ class Utils: if albumart: pixbuf = Utils.load_pixbuf(albumart) if pixbuf is not None: - pixbuf = pixbuf.scale_simple(size, size, + (width, height) = Utils.calculate_size(pixbuf.get_width(), + pixbuf.get_height(), + size, size) + pixbuf = pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER) try: pixbuf.savev(cache_url, 'jpeg', [], []) @@ -78,6 +82,19 @@ class Utils: m.update(value.encode('utf-8')) return m.hexdigest() + @staticmethod + def calculate_size(src_width, src_height, dest_width, dest_height): + ratio_w = float(dest_width) / float(src_width) + ratio_h = float(dest_height) / float(src_height) + ratio = min(min(ratio_w, ratio_h), 1) + if ratio == 1: + return (src_width, src_height) + + width = int(math.floor(src_width * ratio)) + height = int(math.floor(src_height * ratio)) + + return (width, height) + class SortOrder: ARTIST = 0 From 7d474598e393e2fb4486c1e70b6b1c57cb507321 Mon Sep 17 00:00:00 2001 From: Olli Date: Sat, 10 Jan 2026 16:34:13 +0100 Subject: [PATCH 5/5] Center non-square album covers on grid views (close #112) --- data/ui/library-panel.ui | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/ui/library-panel.ui b/data/ui/library-panel.ui index b5512e3..b835e3f 100644 --- a/data/ui/library-panel.ui +++ b/data/ui/library-panel.ui @@ -221,10 +221,14 @@ vertical + true + true contain false + true + true GtkListItem