[gnome-music] refactor albumart



commit 3f23e2ef79137c7f53305eba6ceb1afe507266db
Author: Vadim Rutkovsky <vrutkovs redhat com>
Date:   Wed Apr 16 14:36:19 2014 +0200

    refactor albumart

 gnomemusic/albumArtCache.py |  271 ++++++++++++-------------------------------
 gnomemusic/grilo.py         |   16 +--
 gnomemusic/notification.py  |    3 +-
 gnomemusic/player.py        |   22 +++-
 gnomemusic/view.py          |   24 +----
 gnomemusic/widgets.py       |    4 +-
 6 files changed, 101 insertions(+), 239 deletions(-)
---
diff --git a/gnomemusic/albumArtCache.py b/gnomemusic/albumArtCache.py
index f447dc1..a73c390 100644
--- a/gnomemusic/albumArtCache.py
+++ b/gnomemusic/albumArtCache.py
@@ -28,211 +28,60 @@
 # delete this exception statement from your version.
 
 
-from gi.repository import Gtk, GdkPixbuf, Gio, GLib, Grl, Gdk, MediaArt
+from gi.repository import Gtk, GdkPixbuf, Gio, GLib, Gdk, MediaArt
 from gettext import gettext as _
 import cairo
 from math import pi
-import threading
 import os
+from threading import Thread
+from queue import Queue, Empty
 from gnomemusic import log
 import logging
 logger = logging.getLogger(__name__)
-frame_lock = threading.Lock()
 
 
 @log
 def _make_icon_frame(pixbuf, path=None):
-
     border = 1.5
+    degrees = pi / 180
+    radius = 3
+
     w = pixbuf.get_width()
     h = pixbuf.get_height()
-    pixbuf = pixbuf.scale_simple(w - border * 2,
-                                 h - border * 2,
-                                 0)
-
-    result = _draw_rounded_path(0, 0, w, h, 3)
-
-    pixbuf.copy_area(border, border,
-                     w - border * 4,
-                     h - border * 4,
-                     result,
-                     border * 2, border * 2)
-    return pixbuf
-
-
- log
-def _draw_rounded_path(x, y, width, height, radius):
-    degrees = pi / 180
+    new_pixbuf = pixbuf.scale_simple(w - border * 2,
+                                     h - border * 2,
+                                     0)
 
-    global frame_lock
-    frame_lock.acquire()
-    #if key not in frame_cache:
-    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
     ctx = cairo.Context(surface)
     ctx.new_sub_path()
-    ctx.arc(x + width - radius, y + radius, radius - 0.5,
-            -90 * degrees, 0 * degrees)
-    ctx.arc(x + width - radius, y + height - radius, radius - 0.5,
-            0 * degrees, 90 * degrees)
-    ctx.arc(x + radius, y + height - radius, radius - 0.5,
-            90 * degrees, 180 * degrees)
-    ctx.arc(x + radius, y + radius, radius - 0.5, 180 * degrees,
-            270 * degrees)
+    ctx.arc(w - radius, radius, radius - 0.5, -90 * degrees, 0 * degrees)
+    ctx.arc(w - radius, h - radius, radius - 0.5, 0 * degrees, 90 * degrees)
+    ctx.arc(radius, h - radius, radius - 0.5, 90 * degrees, 180 * degrees)
+    ctx.arc(radius,  radius, radius - 0.5, 180 * degrees, 270 * degrees)
     ctx.close_path()
     ctx.set_line_width(0.6)
     ctx.set_source_rgb(0.2, 0.2, 0.2)
     ctx.stroke_preserve()
     ctx.set_source_rgb(1, 1, 1)
     ctx.fill()
-    res = Gdk.pixbuf_get_from_surface(surface, 0, 0, width, height)
-    frame_lock.release()
-    return res
-
-
-class LookupRequest:
-
-    @log
-    def __init__(self, item, width, height, callback, data=None):
-        self.item = item
-        self.width = width or -1
-        self.height = height or -1
-        self.callback = callback
-        self.data = data
-        self.artist = item.get_string(Grl.METADATA_KEY_ARTIST) or item.get_string(Grl.METADATA_KEY_AUTHOR) 
or ''
-        self.album = item.get_string(Grl.METADATA_KEY_ALBUM) or ''
-        self.path = MediaArt.get_path(self.artist, self.album, "album", None)[0]
-        self.started = False
-
-    @log
-    def start(self):
-        self.started = True
-        f = Gio.File.new_for_path(self.path)
-        f.read_async(GLib.PRIORITY_DEFAULT, None, self._on_read_ready, None)
-
-    @log
-    def finish(self, pixbuf):
-        self.callback(pixbuf, self.path, self.data)
-
-    @log
-    def _on_read_ready(self, obj, res, data=None):
-        try:
-            stream = obj.read_finish(res)
-
-            GdkPixbuf.Pixbuf.new_from_stream_async(stream, None, self._on_pixbuf_ready, None)
-            return
-
-        except Exception as error:
-            if AlbumArtCache.get_default().logLookupErrors:
-                print('ERROR:', error)
-
-        self._on_load_fail()
-
-    @log
-    def _on_pixbuf_ready(self, source, res, data=None):
-        try:
-            pixbuf = GdkPixbuf.Pixbuf.new_from_stream_finish(res)
-            if self.width < 0 and self.height < 0:
-                self.finish(pixbuf)
-                return
-
-            width = pixbuf.get_width()
-            height = pixbuf.get_height()
-            if width >= self.width or height >= self.height:
-                if width > height and self.width < 0:
-                    self.height *= (height / width)
-                elif height > width and self.height < 0:
-                    self.width *= (width / height)
-                scale = max(width / self.width, height / self.height)
-                pixbuf = pixbuf.scale_simple(width / scale, height / scale, 2)
-            pixbuf = _make_icon_frame(pixbuf)
-            self.finish(pixbuf)
-            return
-        except Exception as error:
-            if AlbumArtCache.get_default().logLookupErrors:
-                print('ERROR:', error)
-
-        self._on_load_fail()
-
-    @log
-    def _on_load_fail(self):
-        options = Grl.OperationOptions()
-        options.set_flags(Grl.ResolutionFlags.FULL |
-                          Grl.ResolutionFlags.IDLE_RELAY)
-
-        uri = self.item.get_thumbnail()
-        if uri is None:
-            self.finish(None)
-            return
+    border_pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0, w, h)
 
-        AlbumArtCache.get_default().get_from_uri(
-            uri, self.artist, self.album, self.width, self.height,
-            self.callback, self.data
-        )
-
-
-class GetUriRequest:
-
-    @log
-    def __init__(self, uri, artist, album, callback, data=None):
-        self.uri = uri
-        self.artist = artist
-        self.album = album
-        self.callback = callback
-        self.data = data
-        self.callbacks = []
-        self.path = ''
-        self.path = MediaArt.get_path(artist, album, "album", None)[0]
-        self.stream = None
-        self.started = False
-
-    @log
-    def start(self):
-        self.started = True
-        f = Gio.File.new_for_uri(self.uri)
-        f.read_async(300, None, self._on_read_ready, None)
-
-    @log
-    def _on_read_ready(self, outstream, res, user_data=None):
-        try:
-            self.stream = outstream.read_finish(res)
-            newFile = Gio.File.new_for_path(self.path)
-            newFile.replace_async(None, False,
-                                  Gio.FileCreateFlags.REPLACE_DESTINATION,
-                                  300, None, self._on_replace_ready, None)
-
-        except Exception as e:
-            print(e)
-
-    @log
-    def _on_replace_ready(self, new_file, res, user_data=None):
-        outstream = new_file.replace_finish(res)
-        outstream.splice_async(self.stream,
-                               Gio.IOStreamSpliceFlags.NONE,
-                               300, None, self._on_splice_ready, None)
-
-    @log
-    def _on_splice_ready(self, outstream, res, user_data=None):
-        for values in self.callbacks:
-            width, height, callback, data = values
-            try:
-                pixbuf =\
-                    GdkPixbuf.Pixbuf.new_from_file_at_scale(
-                        self.path, height, width, True)
-                callback(pixbuf, self.path, data)
-            except Exception as e:
-                print('Failed to load image: %s' % e.message)
-                callback(None, None, data)
-        self.callback(self, self.data)
+    new_pixbuf.copy_area(border, border,
+                         w - border * 4,
+                         h - border * 4,
+                         border_pixbuf,
+                         border * 2, border * 2)
+    return new_pixbuf
 
 
 class AlbumArtCache:
     instance = None
+    num_worker_threads = 5
 
     @classmethod
     def get_default(self):
-        if self.instance:
-            return self.instance
-        else:
+        if not self.instance:
             self.instance = AlbumArtCache()
         return self.instance
 
@@ -263,14 +112,17 @@ class AlbumArtCache:
 
     @log
     def __init__(self):
-        self.logLookupErrors = False
-        self.requested_uris = {}
-        self.cacheDir = os.path.join(GLib.get_user_cache_dir(), 'media-art')
-
         try:
+            self.cacheDir = os.path.join(GLib.get_user_cache_dir(), 'media-art')
             Gio.file_new_for_path(self.cacheDir).make_directory(None)
         except:
-            pass
+            logger.warn("Cannot create media-art dir")
+
+        self.q = Queue()
+        for i in range(self.num_worker_threads):
+            t = Thread(target=self.lookup_worker)
+            t.daemon = True
+            t.start()
 
     @log
     def get_default_icon(self, width, height):
@@ -293,28 +145,55 @@ class AlbumArtCache:
                        icon.get_height() * 3 / 2,
                        1, 1,
                        GdkPixbuf.InterpType.NEAREST, 0xff)
-        return result
+        return _make_icon_frame(result)
 
     @log
-    def lookup(self, item, width, height, callback, data=None):
-        request = LookupRequest(item, width, height, callback, data)
-        request.start()
+    def lookup(self, item, width, height, callback, itr, artist, album):
+        self.q.put((item, width, height, callback, itr, artist, album))
 
     @log
-    def get_from_uri(self, uri, artist, album, width, height, callback, data=None):
-        if not uri:
-            return
+    def lookup_worker(self):
+        while True:
+            try:
+                (item, width, height, callback, itr, artist, album) = self.q.get_nowait()
+                width = width or -1
+                height = height or -1
+                path = MediaArt.get_path(artist, album, "album", None)[0]
+                if not os.path.exists(path):
+                    self.cached_thumb_not_found(item, album, artist, path, callback, itr)
+                self.read_cached_pixbuf(path, width, height, callback, itr)
+            except Empty:
+                pass
+            except Exception as e:
+                logger.warn("Error: %s" % e)
 
-        if uri not in self.requested_uris:
-            request = GetUriRequest(uri, artist, album, self._on_get_uri_request_finish, data)
-            self.requested_uris[uri] = request
-        else:
-            request = self.requested_uris[uri]
+    @log
+    def read_cached_pixbuf(self, path, width, height, callback, itr):
+        try:
+            pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(path, width, height, True)
+            self.finish(_make_icon_frame(pixbuf), path, callback, itr)
+        except Exception as e:
+            logger.debug("Error: %s" % e)
 
-        request.callbacks.append([width, height, callback, data])
-        if not request.started:
-            request.start()
+    @log
+    def finish(self, pixbuf, path, callback, itr):
+        try:
+            callback(pixbuf, path, itr)
+        except Exception as e:
+            logger.warn("Error: %s" % e)
+        self.q.task_done()
 
     @log
-    def _on_get_uri_request_finish(self, request, data=None):
-        del self.requested_uris[request.uri]
+    def cached_thumb_not_found(self, item, album, artist, path, callback, itr):
+        try:
+            uri = item.get_thumbnail()
+            if uri is None:
+                logger.warn("can't find URL for album '%s' by %s" % (album, artist))
+                self.finish(None, path, callback, itr)
+                return
+
+            src = Gio.File.new_for_uri(uri)
+            dest = Gio.File.new_for_path(path)
+            src.copy(dest, Gio.FileCopyFlags.OVERWRITE)
+        except Exception as e:
+            logger.warn("Error: %s" % e)
diff --git a/gnomemusic/grilo.py b/gnomemusic/grilo.py
index 4e59326..4deaf75 100644
--- a/gnomemusic/grilo.py
+++ b/gnomemusic/grilo.py
@@ -43,12 +43,8 @@ class Grilo(GObject.GObject):
         Grl.METADATA_KEY_ID, Grl.METADATA_KEY_TITLE,
         Grl.METADATA_KEY_ARTIST, Grl.METADATA_KEY_ALBUM,
         Grl.METADATA_KEY_DURATION,
-        Grl.METADATA_KEY_CREATION_DATE]
-
-    METADATA_THUMBNAIL_KEYS = [
-        Grl.METADATA_KEY_ID,
-        Grl.METADATA_KEY_THUMBNAIL,
-    ]
+        Grl.METADATA_KEY_CREATION_DATE,
+        Grl.METADATA_KEY_THUMBNAIL]
 
     CHANGED_MEDIA_MAX_ITEMS = 500
     CHANGED_MEDIA_SIGNAL_TIMEOUT = 2000
@@ -61,7 +57,7 @@ class Grilo(GObject.GObject):
         if not (GLib.file_test(self.playlist_path, GLib.FileTest.IS_DIR)):
             GLib.mkdir_with_parents(self.playlist_path, int("0755", 8))
         self.options = Grl.OperationOptions()
-        self.options.set_flags(Grl.ResolutionFlags.FULL |
+        self.options.set_flags(Grl.ResolutionFlags.FAST_ONLY |
                                Grl.ResolutionFlags.IDLE_RELAY)
 
         self.sources = {}
@@ -180,12 +176,6 @@ class Grilo(GObject.GObject):
                           options, self._search_callback, source)
 
     @log
-    def get_album_art_for_album_id(self, album_id, _callback):
-        options = self.options.copy()
-        query = Query.get_album_for_id(album_id)
-        self.tracker.query(query, self.METADATA_THUMBNAIL_KEYS, options, _callback, None)
-
-    @log
     def get_media_from_uri(self, uri, callback):
         options = self.options.copy()
         query = Query.get_song_with_url(uri)
diff --git a/gnomemusic/notification.py b/gnomemusic/notification.py
index 49e3ac7..7535439 100644
--- a/gnomemusic/notification.py
+++ b/gnomemusic/notification.py
@@ -94,7 +94,8 @@ class NotificationManager:
                                                              '<i>' + album + '</i>'),
                                       'gnome-music')
 
-            self._albumArtCache.lookup(item, IMAGE_SIZE, IMAGE_SIZE, self._album_art_loaded)
+            self._albumArtCache.lookup(
+                item, IMAGE_SIZE, IMAGE_SIZE, self._album_art_loaded, None, artist, album)
 
     @log
     def _album_art_loaded(self, image, path, data):
diff --git a/gnomemusic/player.py b/gnomemusic/player.py
index 1ff807d..ee43438 100644
--- a/gnomemusic/player.py
+++ b/gnomemusic/player.py
@@ -376,17 +376,25 @@ class Player(GObject.GObject):
         self.playBtn.set_sensitive(True)
         self._sync_prev_next()
 
-        self.coverImg.set_from_pixbuf(self._symbolicIcon)
-        self.cache.lookup(media, ART_SIZE, ART_SIZE, self._on_cache_lookup)
-
-        self.titleLabel.set_label(AlbumArtCache.get_media_title(media))
-
+        artist = _("Unknown Artist")
         try:
+            assert media.get_artist() is not None
             artist = media.get_artist()
-            assert artist is not None
+        finally:
             self.artistLabel.set_label(artist)
+
+        album = _("Unknown Album")
+        try:
+            assert media.get_album() is not None
+            album = media.get_album()
         except:
-            self.artistLabel.set_label(_("Unknown Artist"))
+            pass
+
+        self.coverImg.set_from_pixbuf(self._symbolicIcon)
+        self.cache.lookup(
+            media, ART_SIZE, ART_SIZE, self._on_cache_lookup, None, artist, album)
+
+        self.titleLabel.set_label(AlbumArtCache.get_media_title(media))
 
         url = media.get_url()
         if url != self.player.get_value('current-uri', 0):
diff --git a/gnomemusic/view.py b/gnomemusic/view.py
index 621e563..bee691b 100644
--- a/gnomemusic/view.py
+++ b/gnomemusic/view.py
@@ -284,28 +284,11 @@ class ViewContainer(Gtk.Stack):
                             [str(item.get_id()), '', title,
                              artist, self._symbolicIcon, item,
                              0, icon_name, False, icon_name == self.errorIconName])
-            self._update_album_art(item, _iter)
-
-        GLib.idle_add(add_new_item)
-
-    @log
-    def _insert_album_art(self, item, cb_item, itr, x=False):
-        if item and cb_item and not item.get_thumbnail():
-            if cb_item.get_thumbnail():
-                item.set_thumbnail(cb_item.get_thumbnail())
             albumArtCache.get_default().lookup(
-                item,
-                self._iconWidth,
-                self._iconHeight,
-                self._on_lookup_ready, itr)
+                item, self._iconWidth, self._iconHeight, self._on_lookup_ready,
+                _iter, artist, title)
 
-    @log
-    def _update_album_art(self, item, itr):
-        grilo.get_album_art_for_album_id(
-            item.get_id(),
-            lambda source, count, cb_item, x, y, z:
-            self._insert_album_art(item, cb_item, itr, True)
-        )
+        GLib.idle_add(add_new_item)
 
     @log
     def _on_lookup_ready(self, icon, path, _iter):
@@ -313,7 +296,6 @@ class ViewContainer(Gtk.Stack):
             self._model.set_value(
                 _iter, 4,
                 icon)
-            self.view.queue_draw()
 
     @log
     def _add_list_renderers(self):
diff --git a/gnomemusic/widgets.py b/gnomemusic/widgets.py
index ddc0209..71a72ea 100644
--- a/gnomemusic/widgets.py
+++ b/gnomemusic/widgets.py
@@ -632,7 +632,9 @@ class ArtistAlbumWidget(Gtk.HBox):
 
     @log
     def _update_album_art(self):
-        ALBUM_ART_CACHE.lookup(self.album, 128, 128, self._get_album_cover)
+        ALBUM_ART_CACHE.lookup(
+            self.album, 128, 128, self._get_album_cover, None,
+            self.artist, self.album.get_title())
 
     @log
     def _get_album_cover(self, pixbuf, path, data=None):


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]