[gnome-music/wip/mschraal/coverart: 1/4] albumartcache: cleanup of art lookup
- From: Marinus Schraal <mschraal src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-music/wip/mschraal/coverart: 1/4] albumartcache: cleanup of art lookup
- Date: Tue, 27 Sep 2016 22:08:15 +0000 (UTC)
commit fbd67732e6e476d117daa3ed62e1b570bb492cc8
Author: Marinus Schraal <mschraal src gnome org>
Date: Fri Aug 26 16:41:50 2016 +0200
albumartcache: cleanup of art lookup
Reflow & cleanup the albumart retrieval, so it is not as arcane as it
looked before.
gnomemusic/albumartcache.py | 288 +++++++++++++++++++++++--------------------
gnomemusic/grilo.py | 6 +-
gnomemusic/player.py | 4 +-
gnomemusic/view.py | 19 ++--
gnomemusic/widgets.py | 11 +-
5 files changed, 174 insertions(+), 154 deletions(-)
---
diff --git a/gnomemusic/albumartcache.py b/gnomemusic/albumartcache.py
index 89430df..1aba258 100644
--- a/gnomemusic/albumartcache.py
+++ b/gnomemusic/albumartcache.py
@@ -40,6 +40,7 @@ from gi.repository import Gdk, GdkPixbuf, Gio, GLib, GObject, Gtk, MediaArt
from gnomemusic import log
from gnomemusic.grilo import grilo
+import gnomemusic.utils as utils
logger = logging.getLogger(__name__)
@@ -146,17 +147,16 @@ class DefaultIcon(GObject.GObject):
class AlbumArtCache(GObject.GObject):
- instance = None
+ """Album art retrieval class
+
+ On basis of a given media item looks up album art locally and if
+ not found remotely.
+ """
+ _instance = None
blacklist = {}
def __repr__(self):
- return '<AlbumArt>'
-
- @classmethod
- def get_default(cls):
- if not cls.instance:
- cls.instance = AlbumArtCache()
- return cls.instance
+ return '<AlbumArtCache>'
@staticmethod
def get_media_title(media, escaped=False):
@@ -186,157 +186,181 @@ class AlbumArtCache(GObject.GObject):
@log
def __init__(self):
GObject.GObject.__init__(self)
- try:
- self.cacheDir = os.path.join(GLib.get_user_cache_dir(), 'media-art')
- if not os.path.exists(self.cacheDir):
- Gio.file_new_for_path(self.cacheDir).make_directory(None)
- except Exception as e:
- logger.warn("Error: %s", e)
- self.default_icon = DefaultIcon()
+ self.cache_dir = os.path.join(GLib.get_user_cache_dir(), 'media-art')
+ if not os.path.exists(self.cache_dir):
+ try:
+ Gio.file_new_for_path(self.cache_dir).make_directory(None)
+ except Exception as err:
+ logger.warn("Error: %s, %s", err.__class__, err)
+ return
@log
- def lookup(self, item, width, height, callback, itr, artist, album, first=True):
- if artist in self.blacklist and album in self.blacklist[artist]:
- self.finish(item, None, None, callback, itr, width, height)
- return
-
- try:
- [success, thumb_file] = MediaArt.get_file(artist, album, "album")
+ def lookup(self, item, width, height, callback, itr):
+ """Find art for the given item
+
+ :param item: Grilo media item
+ :param int width: Width of the icon to return
+ :param int height: Height of the icon to return
+ :param callback: Callback function when retrieved
+ :param itr: Iter to return with callback
+ """
+ self._lookup_local(item, callback, itr, width, height)
- if success == False:
- self.finish(item, None, None, callback, itr, width, height)
+ @log
+ def _lookup_local(self, item, callback, itr, width, height):
+ """Checks if there is already a local art file, if not calls
+ the remote lookup function"""
+ album = self.get_media_title(item)
+ artist = utils.get_artist_name(item)
+
+ def stream_open(thumb_file, result, arguments):
+ try:
+ stream = thumb_file.read_finish(result)
+ except Exception as err:
+ logger.warn("Error: %s, %s", err.__class__, err)
+ do_callback(None)
return
- if not thumb_file.query_exists():
- if first:
- self.cached_thumb_not_found(item, width, height, thumb_file.get_path(), callback, itr,
artist, album)
- else:
- self.finish(item, None, None, callback, itr, width, height)
- return
+ GdkPixbuf.Pixbuf.new_from_stream_async(stream, None,
+ pixbuf_loaded, None)
- stream = thumb_file.read_async(GLib.PRIORITY_LOW, None, self.stream_open,
- [item, width, height, thumb_file, callback, itr, artist, album])
- except Exception as e:
- logger.warn("Error: %s, %s", e.__class__, e)
+ def pixbuf_loaded(stream, result, data):
+ try:
+ pixbuf = GdkPixbuf.Pixbuf.new_from_stream_finish(result)
+ except Exception as err:
+ logger.warn("Error: %s, %s", err.__class__, err)
+ do_callback(None)
+ return
- @log
- def stream_open(self, thumb_file, result, arguments):
- (item, width, height, thumb_file, callback, itr, artist, album) = arguments
+ do_callback(pixbuf)
+ return
- try:
- width = width or -1
- height = height or -1
- stream = thumb_file.read_finish (result)
- GdkPixbuf.Pixbuf.new_from_stream_at_scale_async(stream, width, height, True, None,
self.pixbuf_loaded,
- [item, width, height, thumb_file, callback, itr,
artist, album])
- except Exception as e:
- logger.warn("Error: %s, %s", e.__class__, e)
- self.finish(item, None, None, callback, itr, width, height)
+ def do_callback(pixbuf):
+ if not pixbuf:
+ pixbuf = DefaultIcon().get(width, height,
+ DefaultIcon.Type.music)
+ else:
+ pixbuf = pixbuf.scale_simple(width, height,
+ GdkPixbuf.InterpType.HYPER)
+ pixbuf = _make_icon_frame(pixbuf)
- @log
- def pixbuf_loaded(self, stream, result, arguments):
- (item, width, height, thumb_file, callback, itr, artist, album) = arguments
+ GLib.idle_add(callback, pixbuf, None, itr)
+ return
- try:
- pixbuf = GdkPixbuf.Pixbuf.new_from_stream_finish (result)
- self.finish(item, _make_icon_frame(pixbuf), thumb_file.get_path(), callback, itr, width, height,
artist, album)
- except Exception as e:
- logger.warn("Error: %s, %s", e.__class__, e)
- self.finish(item, None, None, callback, itr, width, height)
+ [success, thumb_file] = MediaArt.get_file(artist, album, "album")
- @log
- def finish(self, item, pixbuf, path, callback, itr, width=-1, height=-1, artist=None, album=None):
- if (pixbuf is None and artist is not None):
- # Blacklist artist-album combination
- if artist not in self.blacklist:
- self.blacklist[artist] = []
- self.blacklist[artist].append(album)
+ if (success
+ and thumb_file.query_exists()):
+ thumb_file.read_async(GLib.PRIORITY_LOW,
+ None,
+ stream_open,
+ None)
+ return
- if pixbuf is None:
- pixbuf = self.default_icon.get(width, height, DefaultIcon.Type.music)
+ stripped_album = MediaArt.strip_invalid_entities(album)
+ if (artist in self.blacklist
+ and stripped_album in self.blacklist[artist]):
+ do_callback(None)
+ return
- try:
- if path:
- item.set_thumbnail(GLib.filename_to_uri(path, None))
- GLib.idle_add(callback, pixbuf, path, itr)
- except Exception as e:
- logger.warn("Error: %s", e)
+ self._lookup_remote(item, callback, itr, width, height)
@log
- def cached_thumb_not_found(self, item, width, height, path, callback, itr, artist, album):
- try:
- uri = item.get_thumbnail()
- if uri is None:
- grilo.get_album_art_for_item(item, self.album_art_for_item_callback,
- (item, width, height, path, callback, itr, artist, album))
- return
-
- self.download_thumb(item, width, height, path, callback, itr, artist, album, uri)
- except Exception as e:
- logger.warn("Error: %s", e)
- self.finish(item, None, None, callback, itr, width, height, artist, album)
+ def _lookup_remote(self, item, callback, itr, width, height):
+ """Lookup remote art
- @log
- def album_art_for_item_callback(self, source, param, item, count, data, error):
- old_item, width, height, path, callback, itr, artist, album = data
- try:
- if item is None:
+ Lookup remote art through Grilo and if found copy locally
+ """
+ album = self.get_media_title(item)
+ artist = utils.get_artist_name(item)
+
+ @log
+ def delete_cb(src, result, data):
+ try:
+ src.delete_finish(result)
+ except Exception as err:
+ logger.warn("Error: %s, %s", err.__class__, err)
+
+ @log
+ def splice_cb(src, result, data):
+ tmp_file, iostream = data
+
+ try:
+ src.splice_finish(result)
+ except Exception as err:
+ logger.warn("Error: %s, %s", err.__class__, err)
+ art_retrieved(False)
return
- uri = item.get_thumbnail()
- if uri is None:
- logger.warn("can't find artwork for album '%s' by %s", album, artist)
- self.finish(item, None, None, callback, itr, width, height, artist, album)
+ success, cache_path = MediaArt.get_path(artist, album, "album")
+ try:
+ # FIXME: I/O blocking
+ MediaArt.file_to_jpeg(tmp_file.get_path(), cache_path)
+ except Exception as err:
+ logger.warn("Error: %s, %s", err.__class__, err)
+ art_retrieved(False)
return
- self.download_thumb(item, width, height, path, callback, itr, artist, album, uri)
- except Exception as e:
- logger.warn("Error: %s", e)
- self.finish(item, None, None, callback, itr, width, height, artist, album)
- @log
- def download_thumb(self, item, width, height, thumb_file, callback, itr, artist, album, uri):
- src = Gio.File.new_for_uri(uri)
- src.read_async(GLib.PRIORITY_LOW, None, self.open_remote_thumb,
- [item, width, height, thumb_file, callback, itr, artist, album])
+ art_retrieved(True)
- @log
- def open_remote_thumb(self, src, result, arguments):
- (item, width, height, thumb_file, callback, itr, artist, album) = arguments
- dest = Gio.File.new_for_path(thumb_file)
+ tmp_file.delete_async(GLib.PRIORITY_LOW,
+ None,
+ delete_cb,
+ None)
- try:
- istream = src.read_finish(result)
- dest.replace_async(None, False, Gio.FileCreateFlags.REPLACE_DESTINATION,
- GLib.PRIORITY_LOW, None, self.open_local_thumb,
- [item, width, height, thumb_file, callback, itr, artist, album, istream])
- except Exception as e:
- logger.warn("Error: %s", e)
- self.finish(item, None, None, callback, itr, width, height, artist, album)
+ @log
+ def async_read_cb(src, result, data):
+ try:
+ istream = src.read_finish(result)
+ except Exception as err:
+ logger.warn("Error: %s, %s", err.__class__, err)
+ art_retrieved(False)
+ return
- @log
- def open_local_thumb(self, dest, result, arguments):
- (item, width, height, thumb_file, callback, itr, artist, album, istream) = arguments
+ try:
+ [tmp_file, iostream] = Gio.File.new_tmp()
+ except Exception as err:
+ logger.warn("Error: %s, %s", err.__class__, err)
+ art_retrieved(False)
+ return
- try:
- ostream = dest.replace_finish(result)
+ ostream = iostream.get_output_stream()
+ # FIXME: Passing the iostream here, otherwise it gets
+ # closed. PyGI specific issue?
ostream.splice_async(istream,
Gio.OutputStreamSpliceFlags.CLOSE_SOURCE |
Gio.OutputStreamSpliceFlags.CLOSE_TARGET,
- GLib.PRIORITY_LOW, None,
- self.copy_finished,
- [item, width, height, thumb_file, callback, itr, artist, album])
- except Exception as e:
- logger.warn("Error: %s", e)
- self.finish(item, None, None, callback, itr, width, height, artist, album)
+ GLib.PRIORITY_LOW,
+ None,
+ splice_cb,
+ [tmp_file, iostream])
+
+ @log
+ def album_art_for_item_cb(source, param, item, count, error):
+ if error:
+ logger.warn("Grilo error %s", error)
+ art_retrieved(False)
+ return
- @log
- def copy_finished(self, ostream, result, arguments):
- (item, width, height, thumb_file, callback, itr, artist, album) = arguments
+ thumb_uri = item.get_thumbnail()
- try:
- ostream.splice_finish(result)
- self.lookup(item, width, height, callback, itr, artist, album, False)
- except Exception as e:
- logger.warn("Error: %s", e)
- self.finish(item, None, None, callback, itr, width, height, artist, album)
+ if thumb_uri is None:
+ art_retrieved(False)
+ return
+
+ src = Gio.File.new_for_uri(thumb_uri)
+ src.read_async(GLib.PRIORITY_LOW, None, async_read_cb, None)
+
+ @log
+ def art_retrieved(result):
+ if not result:
+ if artist not in self.blacklist:
+ self.blacklist[artist] = []
+
+ album_stripped = MediaArt.strip_invalid_entities(album)
+ self.blacklist[artist].append(album_stripped)
+
+ self.lookup(item, width, height, callback, itr)
+
+ grilo.get_album_art_for_item(item, album_art_for_item_cb)
diff --git a/gnomemusic/grilo.py b/gnomemusic/grilo.py
index 120466c..74cd4f7 100644
--- a/gnomemusic/grilo.py
+++ b/gnomemusic/grilo.py
@@ -305,10 +305,9 @@ class Grilo(GObject.GObject):
_multiple_search_callback, data)
@log
- def get_album_art_for_item(self, item, callback, data=None):
+ def get_album_art_for_item(self, item, callback):
item_id = item.get_id()
- query = None
if item.is_audio():
query = Query.get_album_for_song_id(item_id)
else:
@@ -317,7 +316,8 @@ class Grilo(GObject.GObject):
options = self.full_options.copy()
options.set_count(1)
- self.search_source.query(query, self.METADATA_THUMBNAIL_KEYS, options, callback, data)
+ self.search_source.query(query, self.METADATA_THUMBNAIL_KEYS, options,
+ callback)
@log
def get_playlist_with_id(self, playlist_id, callback):
diff --git a/gnomemusic/player.py b/gnomemusic/player.py
index cb30ea9..fff3787 100644
--- a/gnomemusic/player.py
+++ b/gnomemusic/player.py
@@ -110,7 +110,7 @@ class Player(GObject.GObject):
self.currentTrack = None
self.currentTrackUri = None
self._lastState = Gst.State.PAUSED
- self.cache = AlbumArtCache.get_default()
+ self.cache = AlbumArtCache()
self._no_artwork_icon = DefaultIcon().get(ART_SIZE,
ART_SIZE,
DefaultIcon.Type.music)
@@ -610,7 +610,7 @@ class Player(GObject.GObject):
self.coverImg.set_from_pixbuf(self._no_artwork_icon)
self.cache.lookup(
- media, ART_SIZE, ART_SIZE, self._on_cache_lookup, None, artist, album)
+ media, ART_SIZE, ART_SIZE, self._on_cache_lookup, None)
self._currentTitle = AlbumArtCache.get_media_title(media)
self.titleLabel.set_label(self._currentTitle)
diff --git a/gnomemusic/view.py b/gnomemusic/view.py
index d83aa41..e511d0c 100644
--- a/gnomemusic/view.py
+++ b/gnomemusic/view.py
@@ -129,7 +129,7 @@ class ViewContainer(Gtk.Stack):
self.show_all()
self.view.hide()
self._items = []
- self.cache = AlbumArtCache.get_default()
+ self.cache = AlbumArtCache()
self._loading_icon = DefaultIcon().get(self._iconWidth,
self._iconHeight,
DefaultIcon.Type.loading)
@@ -242,7 +242,6 @@ class ViewContainer(Gtk.Stack):
self._offset += 1
artist = utils.get_artist_name(item)
title = AlbumArtCache.get_media_title(item)
- # item.set_title(title)
_iter = self.model.append(None)
self.model.set(_iter,
@@ -250,8 +249,8 @@ class ViewContainer(Gtk.Stack):
[str(item.get_id()), '', title,
artist, self._loading_icon, item,
0, False])
- self.cache.lookup(item, self._iconWidth, self._iconHeight, self._on_lookup_ready,
- _iter, artist, title)
+ self.cache.lookup(item, self._iconWidth, self._iconHeight,
+ self._on_lookup_ready, _iter)
@log
def _on_lookup_ready(self, icon, path, _iter):
@@ -518,8 +517,8 @@ class Albums(ViewContainer):
child.add(builder.get_object('main_box'))
child.show()
- self.cache.lookup(item, self._iconWidth, self._iconHeight, self._on_lookup_ready,
- child, artist, title)
+ self.cache.lookup(item, self._iconWidth, self._iconHeight,
+ self._on_lookup_ready, child)
return child
@@ -1703,8 +1702,8 @@ class Search(ViewContainer):
[0, 2, 3, 4, 5, 9, 11],
[str(item.get_id()), title, artist,
self._loading_icon, item, 2, category])
- self.cache.lookup(item, self._iconWidth, self._iconHeight, self._on_lookup_ready,
- _iter, artist, title)
+ self.cache.lookup(item, self._iconWidth, self._iconHeight,
+ self._on_lookup_ready, _iter)
elif category == 'song':
_iter = self.model.insert_with_values(
self.head_iters[group], -1,
@@ -1718,8 +1717,8 @@ class Search(ViewContainer):
[0, 2, 4, 5, 9, 11],
[str(item.get_id()), artist,
self._loading_icon, item, 2, category])
- self.cache.lookup(item, self._iconWidth, self._iconHeight, self._on_lookup_ready,
- _iter, artist, title)
+ self.cache.lookup(item, self._iconWidth, self._iconHeight,
+ self._on_lookup_ready, _iter)
self._artists[artist.casefold()] = {'iter': _iter, 'albums': []}
self._artists[artist.casefold()]['albums'].append(item)
diff --git a/gnomemusic/widgets.py b/gnomemusic/widgets.py
index e4edf4b..5700892 100644
--- a/gnomemusic/widgets.py
+++ b/gnomemusic/widgets.py
@@ -45,7 +45,6 @@ import gnomemusic.utils as utils
logger = logging.getLogger(__name__)
-ALBUM_ART_CACHE = AlbumArtCache.get_default()
NOW_PLAYING_ICON_NAME = 'media-playback-start-symbolic'
ERROR_ICON_NAME = 'dialog-error-symbolic'
@@ -138,6 +137,7 @@ class AlbumWidget(Gtk.EventBox):
:param parent_view: The view this widget is part of
"""
Gtk.EventBox.__init__(self)
+ self._cache = AlbumArtCache()
self._player = player
self._iter_to_clean = None
@@ -290,10 +290,8 @@ class AlbumWidget(Gtk.EventBox):
self.selection_toolbar = selection_toolbar
self._header_bar = header_bar
self._album = album
- real_artist = utils.get_artist_name(item)
self._ui.get_object('cover').set_from_pixbuf(self._loading_icon)
- ALBUM_ART_CACHE.lookup(item, 256, 256, self._on_look_up, None,
- real_artist, album)
+ self._cache.lookup(item, 256, 256, self._on_look_up, None)
self._duration = 0
self._create_model()
GLib.idle_add(grilo.populate_album_songs, item, self.add_item)
@@ -635,6 +633,7 @@ class ArtistAlbumWidget(Gtk.Box):
@log
def __init__(self, artist, album, player, model, header_bar, selectionModeAllowed):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
+ self._cache = AlbumArtCache()
self.player = player
self.album = album
self.artist = artist
@@ -714,9 +713,7 @@ class ArtistAlbumWidget(Gtk.Box):
@log
def _update_album_art(self):
- artist = utils.get_artist_name(self.album)
- ALBUM_ART_CACHE.lookup(self.album, 128, 128, self._get_album_cover,
- None, artist, self.album.get_title())
+ self._cache.lookup(self.album, 128, 128, self._get_album_cover, None)
@log
def _get_album_cover(self, pixbuf, path, data=None):
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]