Commit 47aa0765 authored by Marinus Schraal's avatar Marinus Schraal

albumartcache: Rewrite

The former artworkcache was a monolithic method filled with callbacks,
hard to debug and comprehend. It also left quite a bit for the caller to
take care of.

The current design is that Music has an Art object that is specific to
one cairo.Surface or Gtk.Image (ArtImage) as requested. The Art object
takes care of retrieving the correct image and emits a signal
(cairo.Surface) or updates the Gtk.Image when done. This leads to less
art related code in the views and widgets.

The lookup process itself is now clearly divided into several steps:
1. (Cache) libmediaart cache lookup
2. (EmbeddedArt) local lookup
    1. tags (gstreamer)
    2. coverart in the directory (libmediaart)
3. (RemoteArt) remote lookup through Grilo coverart providers

Using a cairo.Surface in the Gtk.TreeView pixbuf renderer also allows
for HiDPI art in SearchView.

For simplicity and cleanliness, all art related calls have been removed
from BaseView as they were unused and there is no plan to bring it back
to BaseView.

Closes: #65
parent 099d3aad
This diff is collapsed.
......@@ -46,7 +46,7 @@ from gi.repository import Gtk, GLib, Gio, GObject, Gst, GstAudio, GstPbutils
from gettext import gettext as _, ngettext
from gnomemusic import log
from gnomemusic.albumartcache import AlbumArtCache, DefaultIcon, ArtSize
from gnomemusic.albumartcache import Art, ArtImage
from gnomemusic.grilo import grilo
from gnomemusic.playlists import Playlists
from gnomemusic.scrobbler import LastFmScrobbler
......@@ -109,11 +109,6 @@ class Player(GObject.GObject):
self.playlistField = None
self.currentTrack = None
self.currentTrackUri = None
scale = parent_window.get_scale_factor()
self.cache = AlbumArtCache(scale)
self._loading_icon_surface = DefaultIcon(scale).get(
DefaultIcon.Type.loading,
ArtSize.XSMALL)
self._missingPluginMessages = []
Gst.init(None)
......@@ -584,8 +579,8 @@ class Player(GObject.GObject):
artist = utils.get_artist_name(media)
self.artistLabel.set_label(artist)
self.coverImg.set_from_surface(self._loading_icon_surface)
self.cache.lookup(media, ArtSize.XSMALL, self._on_cache_lookup, None)
art = ArtImage(Art.Size.XSMALL, media)
art.image = self._image
title = utils.get_media_title(media)
self.titleLabel.set_label(title)
......@@ -639,7 +634,7 @@ class Player(GObject.GObject):
@log
def _on_cache_lookup(self, surface, data=None):
self.coverImg.set_from_surface(surface)
# FIXME: Need this for mpris
self.emit('thumbnail-updated')
@log
......@@ -780,9 +775,7 @@ class Player(GObject.GObject):
self.songTotalTimeLabel = self._ui.get_object('duration')
self.titleLabel = self._ui.get_object('title')
self.artistLabel = self._ui.get_object('artist')
self.coverImg = self._ui.get_object('cover')
self.coverImg.set_property("width-request", ArtSize.XSMALL.width)
self.coverImg.set_property("height-request", ArtSize.XSMALL.height)
self._image = self._ui.get_object('cover')
self.duration = self._ui.get_object('duration')
self.repeatBtnImage = self._ui.get_object('playlistRepeat')
......
......@@ -26,7 +26,7 @@ from gettext import gettext as _
from gi.repository import GLib, GObject, Gtk, Gdk
from gnomemusic import log
from gnomemusic.albumartcache import ArtSize
from gnomemusic.albumartcache import Art, ArtImage
from gnomemusic.grilo import grilo
from gnomemusic.toolbar import ToolbarState
from gnomemusic.views.baseview import BaseView
......@@ -167,13 +167,6 @@ class AlbumsView(BaseView):
child.title.set_label(title)
child.subtitle.set_label(artist)
child.image.set_from_surface(self._loading_icon_surface)
# In the case of off-sized icons (eg. provided in the soundfile)
# keep the size request equal to all other icons to get proper
# alignment with GtkFlowBox.
child.image.set_property("width-request", ArtSize.MEDIUM.width)
child.image.set_property("height-request", ArtSize.MEDIUM.height)
child.events.add_events(Gdk.EventMask.TOUCH_MASK)
child.events.connect('button-release-event',
......@@ -190,7 +183,8 @@ class AlbumsView(BaseView):
child.add(builder.get_object('main_box'))
child.show()
self._cache.lookup(item, ArtSize.MEDIUM, self._on_lookup_ready, child)
art = ArtImage(Art.Size.MEDIUM, item)
art.image = child.image
return child
......@@ -201,9 +195,6 @@ class AlbumsView(BaseView):
if self.selection_mode:
child.check.set_active(True)
def _on_lookup_ready(self, icon, child):
child.image.set_from_surface(icon)
@log
def _on_child_toggled(self, check, pspec, child):
if (check.get_active()
......
......@@ -23,10 +23,9 @@
# delete this exception statement from your version.
from gettext import gettext as _, ngettext
from gi.repository import Gd, Gdk, GdkPixbuf, GObject, Gtk
from gi.repository import Gd, GdkPixbuf, GObject, Gtk
from gnomemusic import log
from gnomemusic.albumartcache import AlbumArtCache, DefaultIcon, ArtSize
from gnomemusic.grilo import grilo
from gnomemusic.widgets.starhandlerwidget import StarHandlerWidget
import gnomemusic.utils as utils
......@@ -107,11 +106,6 @@ class BaseView(Gtk.Stack):
self.show_all()
self._view.hide()
scale = self.get_scale_factor()
self._cache = AlbumArtCache(scale)
self._loading_icon_surface = DefaultIcon(scale).get(
DefaultIcon.Type.loading, ArtSize.MEDIUM)
self._init = False
grilo.connect('ready', self._on_grilo_ready)
self._header_bar.connect('selection-mode-changed',
......@@ -211,6 +205,10 @@ class BaseView(Gtk.Stack):
def populate(self):
pass
@log
def _retrieval_finished(self, klass):
self.model[klass.iter][4] = klass.pixbuf
@log
def _add_item(self, source, param, item, remaining=0, data=None):
if not item:
......@@ -224,30 +222,17 @@ class BaseView(Gtk.Stack):
title = utils.get_media_title(item)
itr = self.model.append(None)
loading_icon = Gdk.pixbuf_get_from_surface(
self._loadin_icon_surface, 0, 0,
self._loading_icon_surface.get_width(),
self._loading_icon_surface.get_height())
self.model[itr][0, 1, 2, 3, 4, 5, 7, 9] = [
self.model[itr][0, 1, 2, 3, 5, 7, 9] = [
str(item.get_id()),
'',
title,
artist,
loading_icon,
item,
0,
False
]
@log
def _on_lookup_ready(self, surface, itr):
if surface:
pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0,
surface.get_width(),
surface.get_height())
self.model[itr][4] = pixbuf
@log
def _add_list_renderers(self):
pass
......
......@@ -25,7 +25,7 @@
from gettext import gettext as _
from gnomemusic import log
from gnomemusic.albumartcache import ArtSize
from gnomemusic.albumartcache import Art
from gnomemusic.views.emptyview import EmptyView
......@@ -43,7 +43,7 @@ class InitialStateView(EmptyView):
icon.set_margin_bottom(32)
icon.set_opacity(1)
icon.set_from_resource('/org/gnome/Music/initial-state.png')
icon.set_size_request(ArtSize.LARGE.width, ArtSize.LARGE.height)
icon.set_size_request(Art.Size.LARGE.width, Art.Size.LARGE.height)
# Update label
label = self.builder.get_object('label')
......
......@@ -23,9 +23,9 @@
# delete this exception statement from your version.
from gettext import gettext as _
from gi.repository import Gd, Gdk, GdkPixbuf, GObject, Grl, Gtk, Pango
from gi.repository import Gd, GdkPixbuf, GObject, Grl, Gtk, Pango
from gnomemusic.albumartcache import DefaultIcon, ArtSize
from gnomemusic.albumartcache import Art
from gnomemusic.grilo import grilo
from gnomemusic import log
from gnomemusic.player import DiscoveryStatus
......@@ -54,13 +54,6 @@ class SearchView(BaseView):
def __init__(self, window, player):
super().__init__('search', None, window, Gd.MainViewType.LIST)
scale = self.get_scale_factor()
loading_icon_surface = DefaultIcon(scale).get(
DefaultIcon.Type.loading, ArtSize.SMALL)
self._loading_icon = Gdk.pixbuf_get_from_surface(
loading_icon_surface, 0, 0, loading_icon_surface.get_width(),
loading_icon_surface.get_height())
self._add_list_renderers()
self.player = player
self._head_iters = [None, None, None, None]
......@@ -194,6 +187,13 @@ class SearchView(BaseView):
self._albums[key].songs.append(item)
self._add_item(source, None, item, 0, [self.model, 'song'])
@log
def _retrieval_finished(self, klass, model, _iter):
if not model[_iter][13]:
return
model[_iter][13] = klass.surface
@log
def _add_item(self, source, param, item, remaining=0, data=None):
if data is None:
......@@ -238,17 +238,12 @@ class SearchView(BaseView):
except:
pass
# FIXME: HiDPI icon lookups return a surface that can't be
# scaled by GdkPixbuf, so it results in a * scale factor sized
# icon for the search view.
_iter = None
if category == 'album':
_iter = self.model.insert_with_values(
self._head_iters[group], -1, [0, 2, 3, 4, 5, 9, 11],
[str(item.get_id()), title, artist, self._loading_icon, item,
2, category])
self._cache.lookup(
item, ArtSize.SMALL, self._on_lookup_ready, _iter)
self._head_iters[group], -1, [0, 2, 3, 5, 9, 11],
[str(item.get_id()), title, artist, item, 2,
category])
elif category == 'song':
# FIXME: source specific hack
if source.get_id() != 'grl-tracker-source':
......@@ -256,26 +251,30 @@ class SearchView(BaseView):
else:
fav = item.get_favourite()
_iter = self.model.insert_with_values(
self._head_iters[group], -1, [0, 2, 3, 4, 5, 9, 11],
[str(item.get_id()), title, artist, self._loading_icon, item,
fav, category])
self._cache.lookup(
item, ArtSize.SMALL, self._on_lookup_ready, _iter)
self._head_iters[group], -1, [0, 2, 3, 5, 9, 11],
[str(item.get_id()), title, artist, item, fav,
category])
else:
if not artist.casefold() in self._artists:
_iter = self.model.insert_with_values(
self._head_iters[group], -1, [0, 2, 4, 5, 9, 11],
[str(item.get_id()), artist, self._loading_icon, item, 2,
self._head_iters[group], -1, [0, 2, 5, 9, 11],
[str(item.get_id()), artist, item, 2,
category])
self._cache.lookup(
item, ArtSize.SMALL, self._on_lookup_ready, _iter)
self._artists[artist.casefold()] = {
'iter': _iter,
'albums': []
}
self._artists[artist.casefold()]['albums'].append(item)
# FIXME: Figure out why iter can be None here, seems illogical.
if _iter is not None:
scale = self._view.get_scale_factor()
art = Art(Art.Size.SMALL, item, scale)
self.model[_iter][13] = art.surface
art.connect(
'finished', self._retrieval_finished, self.model, _iter)
art.lookup()
if self.model.iter_n_children(self._head_iters[group]) == 1:
path = self.model.get_path(self._head_iters[group])
path = self._filter_model.convert_child_path_to_path(path)
......@@ -295,13 +294,31 @@ class SearchView(BaseView):
title_renderer, self._on_list_widget_title_render, None)
cols[0].add_attribute(title_renderer, 'text', 2)
self._star_handler.add_star_renderers(list_widget, cols[0])
# Add our own surface renderer, instead of the one provided by
# Gd. This avoids us having to set the model to a cairo.Surface
# which is currently not a working solution in pygobject.
# https://gitlab.gnome.org/GNOME/pygobject/issues/155
pixbuf_renderer = Gtk.CellRendererPixbuf(
xalign=0.5, yalign=0.5, xpad=12, ypad=2)
list_widget.add_renderer(
pixbuf_renderer, self._on_list_widget_pixbuf_renderer, None)
cols[0].add_attribute(pixbuf_renderer, 'surface', 13)
self._star_handler.add_star_renderers(list_widget, cols[0])
cells = cols[0].get_cells()
cols[0].reorder(cells[0], -1)
cols[0].reorder(cells[4], 1)
cols[0].set_cell_data_func(
cells[0], self._on_list_widget_selection_render, None)
@log
def _on_list_widget_pixbuf_renderer(self, col, cell, model, _iter, data):
if not model[_iter][13]:
return
cell.set_property("surface", model[_iter][13])
@log
def _on_list_widget_selection_render(self, col, cell, model, _iter, data):
if (self._view.get_selection_mode()
and model.iter_parent(_iter) is not None):
......@@ -309,11 +326,13 @@ class SearchView(BaseView):
else:
cell.set_visible(False)
@log
def _on_list_widget_title_render(self, col, cell, model, _iter, data):
cells = col.get_cells()
cells[0].set_visible(model.iter_parent(_iter) is not None)
cells[0].set_visible(False)
cells[1].set_visible(model.iter_parent(_iter) is not None)
cells[2].set_visible(model.iter_parent(_iter) is None)
cells[2].set_visible(model.iter_parent(_iter) is not None)
cells[3].set_visible(model.iter_parent(_iter) is None)
@log
def populate(self):
......@@ -456,7 +475,7 @@ class SearchView(BaseView):
GObject.TYPE_STRING,
GObject.TYPE_STRING, # item title or header text
GObject.TYPE_STRING, # artist for albums and songs
GdkPixbuf.Pixbuf, # album art
GdkPixbuf.Pixbuf, # Gd placeholder album art
GObject.TYPE_OBJECT, # item
GObject.TYPE_BOOLEAN,
GObject.TYPE_INT,
......@@ -464,7 +483,8 @@ class SearchView(BaseView):
GObject.TYPE_INT,
GObject.TYPE_BOOLEAN,
GObject.TYPE_STRING, # type
GObject.TYPE_INT
GObject.TYPE_INT,
object # album art surface
)
self._filter_model = self.model.filter_new(None)
......
......@@ -26,7 +26,7 @@ from gettext import gettext as _, ngettext
from gi.repository import GdkPixbuf, GLib, GObject, Gtk
from gnomemusic import log
from gnomemusic.albumartcache import AlbumArtCache, DefaultIcon, ArtSize
from gnomemusic.albumartcache import Art, ArtImage
from gnomemusic.grilo import grilo
from gnomemusic.widgets.disclistboxwidget import DiscBox, DiscListBox
import gnomemusic.utils as utils
......@@ -55,12 +55,6 @@ class AlbumWidget(Gtk.EventBox):
self._songs = []
scale = self.get_scale_factor()
self._cache = AlbumArtCache(scale)
self._loading_icon_surface = DefaultIcon(scale).get(
DefaultIcon.Type.loading,
ArtSize.LARGE)
self._player = player
self._iter_to_clean = None
......@@ -143,10 +137,9 @@ class AlbumWidget(Gtk.EventBox):
self.selection_toolbar = selection_toolbar
self._header_bar = header_bar
self._album = album
self._builder.get_object('cover').set_from_surface(
self._loading_icon_surface)
self._cache.lookup(item, ArtSize.LARGE, self._on_lookup, None)
self._duration = 0
art = ArtImage(Art.Size.LARGE, item)
art.image = self._builder.get_object('cover')
GLib.idle_add(grilo.populate_album_songs, item, self.add_item)
header_bar._select_button.connect(
......@@ -289,16 +282,6 @@ class AlbumWidget(Gtk.EventBox):
self.show_all()
@log
def _on_lookup(self, surface, data=None):
"""Albumart retrieved callback.
:param surface: The Cairo surface retrieved
:param path: The filesystem location the pixbuf
:param data: User data
"""
self._builder.get_object('cover').set_from_surface(surface)
@log
def _update_model(self, player, playlist, current_iter):
"""Player changed callback.
......
......@@ -22,10 +22,10 @@
# code, but you are not obligated to do so. If you do not wish to do so,
# delete this exception statement from your version.
from gi.repository import GLib, GObject, Gtk
from gi.repository import GObject, Gtk
from gnomemusic import log
from gnomemusic.albumartcache import AlbumArtCache, DefaultIcon, ArtSize
from gnomemusic.albumartcache import Art, ArtImage
from gnomemusic.grilo import grilo
from gnomemusic.widgets.disclistboxwidget import DiscBox
import gnomemusic.utils as utils
......@@ -48,11 +48,6 @@ class ArtistAlbumWidget(Gtk.Box):
self._size_group = size_group
self._cover_size_group = cover_size_group
scale = self.get_scale_factor()
self._cache = AlbumArtCache(scale)
self._loading_icon_surface = DefaultIcon(scale).get(
DefaultIcon.Type.loading,
ArtSize.MEDIUM)
self._media = media
self._player = player
......@@ -72,7 +67,8 @@ class ArtistAlbumWidget(Gtk.Box):
ui.add_from_resource('/org/gnome/Music/ArtistAlbumWidget.ui')
self.cover = ui.get_object('cover')
self.cover.set_from_surface(self._loading_icon_surface)
art = ArtImage(Art.Size.MEDIUM, self._media)
art.image = self.cover
self._disc_listbox = ui.get_object('disclistbox')
self._disc_listbox.set_selection_mode_allowed(
......@@ -93,7 +89,6 @@ class ArtistAlbumWidget(Gtk.Box):
self.pack_start(ui.get_object('ArtistAlbumWidget'), True, True, 0)
GLib.idle_add(self._update_album_art)
grilo.populate_album_songs(self._media, self._add_item)
def create_disc_box(self, disc_nr, disc_songs):
......@@ -159,15 +154,6 @@ class ArtistAlbumWidget(Gtk.Box):
if remaining == 0:
self.emit("songs-loaded")
@log
def _update_album_art(self):
self._cache.lookup(self._media, ArtSize.MEDIUM, self._get_album_cover,
None)
@log
def _get_album_cover(self, surface, data=None):
self.cover.set_from_surface(surface)
@log
def _song_activated(self, widget, song_widget):
if (not song_widget.can_be_played
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment