[gnome-music/wip/mschraal/core-thumb-property: 1/9] corealbum: Add thumbnail property



commit 512f513aebae7fb0b4f5b7460b5399f3451a1be3
Author: Marinus Schraal <mschraal gnome org>
Date:   Sun Apr 5 23:30:59 2020 +0200

    corealbum: Add thumbnail property
    
    Use the ArtistArt machinery to retrieve and store art.

 data/ui/AlbumCover.ui                         |  2 +-
 data/ui/AlbumWidget.ui                        |  2 +-
 data/ui/ArtistAlbumWidget.ui                  |  2 +-
 gnomemusic/albumart.py                        | 62 +++++++++++++++++++++++++++
 gnomemusic/artcache.py                        | 41 +++++++++++++-----
 gnomemusic/corealbum.py                       | 27 ++++++++++++
 gnomemusic/coregrilo.py                       |  8 ++++
 gnomemusic/grilowrappers/grltrackerwrapper.py | 36 ++++++++++++++++
 gnomemusic/storeartistart.py                  | 39 ++++++++++-------
 gnomemusic/widgets/albumcover.py              |  6 +--
 gnomemusic/widgets/albumwidget.py             |  6 +--
 gnomemusic/widgets/artistalbumwidget.py       |  8 ++--
 gnomemusic/widgets/artstack.py                |  8 ++--
 13 files changed, 204 insertions(+), 43 deletions(-)
---
diff --git a/data/ui/AlbumCover.ui b/data/ui/AlbumCover.ui
index d825c1cc..7ac403c2 100644
--- a/data/ui/AlbumCover.ui
+++ b/data/ui/AlbumCover.ui
@@ -16,7 +16,7 @@
             <property name="can_focus">False</property>
             <property name="margin_bottom">4</property>
             <child>
-              <object class="CoverStack" id="_cover_stack">
+              <object class="ArtStack" id="_art_stack">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <property name="vexpand">True</property>
diff --git a/data/ui/AlbumWidget.ui b/data/ui/AlbumWidget.ui
index 9fdb87cf..e5848709 100644
--- a/data/ui/AlbumWidget.ui
+++ b/data/ui/AlbumWidget.ui
@@ -32,7 +32,7 @@
                 <property name="orientation">vertical</property>
                 <property name="spacing">18</property>
                 <child>
-                  <object class="CoverStack" id="_cover_stack">
+                  <object class="ArtStack" id="_art_stack">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
                     <property name="halign">center</property>
diff --git a/data/ui/ArtistAlbumWidget.ui b/data/ui/ArtistAlbumWidget.ui
index 7462799b..e25083ce 100644
--- a/data/ui/ArtistAlbumWidget.ui
+++ b/data/ui/ArtistAlbumWidget.ui
@@ -7,7 +7,7 @@
     <property name="margin_right">120</property>
     <property name="visible">True</property>
     <child>
-      <object class="CoverStack" id="_cover_stack">
+      <object class="ArtStack" id="_art_stack">
         <property name="visible">True</property>
        <property name="margin_top">20</property>
        <property name="margin_right">30</property>
diff --git a/gnomemusic/albumart.py b/gnomemusic/albumart.py
new file mode 100644
index 00000000..f0137c71
--- /dev/null
+++ b/gnomemusic/albumart.py
@@ -0,0 +1,62 @@
+# Copyright 2020 The GNOME Music developers
+#
+# GNOME Music is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNOME Music is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNOME Music; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The GNOME Music authors hereby grant permission for non-GPL compatible
+# GStreamer plugins to be used and distributed together with GStreamer
+# and GNOME Music.  This permission is above and beyond the permissions
+# granted by the GPL license by which GNOME Music is covered.  If you
+# modify this code, you may extend this exception to your version of the
+# code, but you are not obligated to do so.  If you do not wish to do so,
+# delete this exception statement from your version.
+import gi
+gi.require_version("MediaArt", "2.0")
+from gi.repository import GObject, MediaArt
+
+from gnomemusic.musiclogger import MusicLogger
+
+
+class AlbumArt(GObject.GObject):
+
+    _log = MusicLogger()
+
+    def __init__(self, application, corealbum):
+        """Initialize AlbumArt
+
+        :param Application application: The application object
+        :param CoreAlbum corealbum: The corealbum to use
+        """
+        super().__init__()
+
+        self._corealbum = corealbum
+        self._album = self._corealbum.props.title
+        self._artist = self._corealbum.props.artist
+
+        if self._in_cache():
+            return
+
+        application.props.coregrilo.get_album_art(self._corealbum)
+
+    def _in_cache(self):
+        success, thumb_file = MediaArt.get_file(
+            self._artist, self._album, "album")
+        if (not success
+                or not thumb_file.query_exists()):
+            self._corealbum.props.thumbnail = "loading"
+            return False
+
+        self._corealbum.props.thumbnail = thumb_file.get_path()
+
+        return True
diff --git a/gnomemusic/artcache.py b/gnomemusic/artcache.py
index 7b6a849e..8627b938 100644
--- a/gnomemusic/artcache.py
+++ b/gnomemusic/artcache.py
@@ -27,6 +27,8 @@ from math import pi
 import cairo
 from gi.repository import Gdk, GdkPixbuf, Gio, Gtk, GLib, GObject
 
+from gnomemusic.corealbum import CoreAlbum
+from gnomemusic.coreartist import CoreArtist
 from gnomemusic.musiclogger import MusicLogger
 
 
@@ -120,6 +122,7 @@ class DefaultIcon(GObject.GObject):
 
     class Type(Enum):
         ARTIST = "avatar-default-symbolic"
+        ARTIST_LOADING = "content-loading-symbolic"
         LOADING = "content-loading-symbolic"
         MUSIC = "folder-music-symbolic"
 
@@ -182,19 +185,32 @@ class ArtCache(GObject.GObject):
         self._size = size
         self._scale = scale
 
-        self._loading_icon = DefaultIcon().get(
-            DefaultIcon.Type.LOADING, self._size, self._scale,
-            round_shape=True)
-        self._default_icon = DefaultIcon().get(
-            DefaultIcon.Type.ARTIST, self._size, self._scale, round_shape=True)
+        self._coreobject = None
+        self._default_icon = None
+        self._loading_icon = None
 
-    def query(self, coreartist):
+    def query(self, coreobject):
         """Start the cache query
 
-        :param CoreArtist coreartist: The object to search art for
+        :param coreobject: The object to search art for
         """
-        thumbnail_uri = coreartist.props.thumbnail
-        if thumbnail_uri == "loading":
+        self._coreobject = coreobject
+
+        if isinstance(coreobject, CoreArtist):
+            self._loading_icon = DefaultIcon().get(
+                DefaultIcon.Type.ARTIST_LOADING, self._size, self._scale,
+                round_shape=True)
+            self._default_icon = DefaultIcon().get(
+                DefaultIcon.Type.ARTIST, self._size, self._scale,
+                round_shape=True)
+        elif isinstance(coreobject, CoreAlbum):
+            self._loading_icon = DefaultIcon().get(
+                DefaultIcon.Type.LOADING, self._size, self._scale)
+            self._default_icon = DefaultIcon().get(
+                DefaultIcon.Type.MUSIC, self._size, self._scale)
+
+        thumbnail_uri = coreobject.props.thumbnail
+        if thumbnail_uri in ["loading", "", None]:
             self.emit("result", self._loading_icon)
             return
         elif thumbnail_uri == "generic":
@@ -234,8 +250,11 @@ class ArtCache(GObject.GObject):
 
         surface = Gdk.cairo_surface_create_from_pixbuf(
             pixbuf, self._scale, None)
-        surface = _make_icon_frame(
-            surface, self._size, self._scale, round_shape=True)
+        if isinstance(self._coreobject, CoreArtist):
+            surface = _make_icon_frame(
+                surface, self._size, self._scale, round_shape=True)
+        elif isinstance(self._coreobject, CoreAlbum):
+            surface = _make_icon_frame(surface, self._size, self._scale)
 
         self.emit("result", surface)
 
diff --git a/gnomemusic/corealbum.py b/gnomemusic/corealbum.py
index 588001f7..906a9a0b 100644
--- a/gnomemusic/corealbum.py
+++ b/gnomemusic/corealbum.py
@@ -28,6 +28,8 @@ from gi.repository import Gfm, Gio, Grl, GObject
 
 import gnomemusic.utils as utils
 
+from gnomemusic.albumart import AlbumArt
+
 
 class CoreAlbum(GObject.GObject):
     """Exposes a Grl.Media with relevant data as properties
@@ -49,9 +51,12 @@ class CoreAlbum(GObject.GObject):
         """
         super().__init__()
 
+        self._application = application
         self._coregrilo = application.props.coregrilo
         self._model = None
         self._selected = False
+        self._thumbnail = None
+
         self.update(media)
 
     def update(self, media):
@@ -126,3 +131,25 @@ class CoreAlbum(GObject.GObject):
         # is requested, it will trigger the filled model update as
         # well.
         self.props.model.items_changed(0, 0, 0)
+
+    @GObject.Property(type=str, default=None)
+    def thumbnail(self):
+        """Album art thumbnail retrieval
+
+        :return: The album art location or "generic" or "loading"
+        :rtype: string
+        """
+        if self._thumbnail is None:
+            self._thumbnail = "loading"
+            self.notify("thumbnail")
+            AlbumArt(self._application, self)
+
+        return self._thumbnail
+
+    @thumbnail.setter
+    def thumbnail(self, value):
+        """Album art thumbnail setter
+
+        :param string value: path, "generic" or "loading"
+        """
+        self._thumbnail = value
diff --git a/gnomemusic/coregrilo.py b/gnomemusic/coregrilo.py
index fa977ab1..cf78bd58 100644
--- a/gnomemusic/coregrilo.py
+++ b/gnomemusic/coregrilo.py
@@ -204,6 +204,14 @@ class CoreGrilo(GObject.GObject):
             self._wrappers["grl-tracker-source"].get_album_art_for_item(
                 coresong, callback)
 
+    def get_album_art(self, corealbum):
+        """Retrieve album art for the given CoreAlbum
+
+        :param CoreAlbum corealbum: CoreAlbum to retrieve art for
+        """
+        if "grl-tracker-source" in self._wrappers:
+            self._wrappers["grl-tracker-source"].get_album_art(corealbum)
+
     def get_artist_art(self, coreartist):
         """Retrieve artist art for the given CoreArtist
 
diff --git a/gnomemusic/grilowrappers/grltrackerwrapper.py b/gnomemusic/grilowrappers/grltrackerwrapper.py
index 652e2269..8cb0952f 100644
--- a/gnomemusic/grilowrappers/grltrackerwrapper.py
+++ b/gnomemusic/grilowrappers/grltrackerwrapper.py
@@ -982,6 +982,42 @@ class GrlTrackerWrapper(GObject.GObject):
 
         return query
 
+    def get_album_art(self, corealbum):
+        """Retrieve album art for the given CoreAlbum
+
+        :param CoreAlbum corealbum: CoreAlbum to get art for
+        """
+        media = corealbum.props.media
+
+        def art_retrieved_cb(source, op_id, queried_media, remaining, error):
+            if error:
+                self._log.warning("Error: {}".format(error))
+                corealbum.props.thumbnail = "generic"
+                return
+
+            if (remaining == 0
+                    and queried_media is None):
+                corealbum.props.thumbnail = "generic"
+                return
+
+            thumbnail_uri = queried_media.get_thumbnail()
+            if thumbnail_uri is None:
+                corealbum.props.thumbnail = "generic"
+            else:
+                media.set_thumbnail(thumbnail_uri)
+                StoreArtistArt(corealbum)
+
+        album_id = media.get_id()
+        query = self._get_album_for_album_id(album_id)
+
+        full_options = Grl.OperationOptions()
+        full_options.set_resolution_flags(
+            Grl.ResolutionFlags.FULL | Grl.ResolutionFlags.IDLE_RELAY)
+
+        self._source.query(
+            query, self.METADATA_THUMBNAIL_KEYS, full_options,
+            art_retrieved_cb)
+
     def get_artist_art(self, coreartist):
         """Retrieve artist art for the given CoreArtist
 
diff --git a/gnomemusic/storeartistart.py b/gnomemusic/storeartistart.py
index 813355a2..24b396b0 100644
--- a/gnomemusic/storeartistart.py
+++ b/gnomemusic/storeartistart.py
@@ -27,25 +27,27 @@ gi.require_versions({"MediaArt": "2.0", "Soup": "2.4"})
 from gi.repository import Gio, GLib, GObject, MediaArt, Soup
 
 from gnomemusic.musiclogger import MusicLogger
-
+from gnomemusic.coreartist import CoreArtist
+from gnomemusic.corealbum import CoreAlbum
 
 class StoreArtistArt(GObject.Object):
     """Stores Art in the MediaArt cache.
     """
 
-    def __init__(self, coreartist):
+    def __init__(self, coreobject):
         """Initialize StoreArtistArt
 
-        :param CoreArtist coreartist: The core artist to store art for
+        :param coreobject: The CoreArtist or CoreSong to store art for
         """
-        self._coreartist = coreartist
+        self._coreobject = coreobject
+
         self._log = MusicLogger()
         self._soup_session = Soup.Session.new()
 
-        uri = coreartist.props.media.get_thumbnail()
+        uri = coreobject.props.media.get_thumbnail()
         if (uri is None
                 or uri == ""):
-            self._coreartist.props.thumbnail = "generic"
+            self._coreobject.props.thumbnail = "generic"
 
             return
 
@@ -66,7 +68,7 @@ class StoreArtistArt(GObject.Object):
             except GLib.Error as error:
                 self._log.warning(
                     "Error: {}, {}".format(error.domain, error.message))
-                self._coreartist.props.thumbnail = "generic"
+                self._coreobject.props.thumbnail = "generic"
 
                 return
 
@@ -86,7 +88,7 @@ class StoreArtistArt(GObject.Object):
         except GLib.Error as error:
             self._log.warning(
                 "Error: {}, {}".format(error.domain, error.message))
-            self._coreartist.props.thumbnail = "generic"
+            self._coreobject.props.thumbnail = "generic"
             return
 
         istream = Gio.MemoryInputStream.new_from_bytes(
@@ -117,14 +119,21 @@ class StoreArtistArt(GObject.Object):
         except GLib.Error as error:
             self._log.warning(
                 "Error: {}, {}".format(error.domain, error.message))
-            self._coreartist.props.thumbnail = "generic"
+            self._coreobject.props.thumbnail = "generic"
             return
 
-        success, cache_path = MediaArt.get_path(
-            self._coreartist.props.artist, None, "artist")
+        if isinstance(self._coreobject, CoreArtist):
+            success, cache_path = MediaArt.get_path(
+                self._coreobject.props.artist, None, "artist")
+        elif isinstance(self._coreobject, CoreAlbum):
+            success, cache_path = MediaArt.get_path(
+                self._coreobject.props.artist, self._coreobject.props.title,
+                "album")
+        else:
+            success = False
 
         if not success:
-            self._coreartist.props.thumbnail = "generic"
+            self._coreobject.props.thumbnail = "generic"
             return
 
         try:
@@ -133,11 +142,11 @@ class StoreArtistArt(GObject.Object):
         except GLib.Error as error:
             self._log.warning(
                 "Error: {}, {}".format(error.domain, error.message))
-            self._coreartist.props.thumbnail = "generic"
+            self._coreobject.props.thumbnail = "generic"
             return
 
-        self._coreartist.props.media.set_thumbnail(cache_path)
-        self._coreartist.props.thumbnail = cache_path
+        self._coreobject.props.media.set_thumbnail(cache_path)
+        self._coreobject.props.thumbnail = cache_path
 
         tmp_file.delete_async(
             GLib.PRIORITY_LOW, None, self._delete_callback, None)
diff --git a/gnomemusic/widgets/albumcover.py b/gnomemusic/widgets/albumcover.py
index 9bd31e6b..557b318b 100644
--- a/gnomemusic/widgets/albumcover.py
+++ b/gnomemusic/widgets/albumcover.py
@@ -40,7 +40,7 @@ class AlbumCover(Gtk.FlowBoxChild):
 
     __gtype_name__ = 'AlbumCover'
 
-    _cover_stack = Gtk.Template.Child()
+    _art_stack = Gtk.Template.Child()
     _check = Gtk.Template.Child()
     _title_label = Gtk.Template.Child()
     _artist_label = Gtk.Template.Child()
@@ -81,7 +81,7 @@ class AlbumCover(Gtk.FlowBoxChild):
 
         self.connect('query-tooltip', self._on_tooltip_query)
 
-        self._cover_stack.props.size = Art.Size.MEDIUM
+        self._art_stack.props.size = Art.Size.MEDIUM
 
         self.show()
 
@@ -95,7 +95,7 @@ class AlbumCover(Gtk.FlowBoxChild):
             return
 
         self._retrieved = True
-        self._cover_stack.update(self._corealbum)
+        self._art_stack.props.coreobject = self._corealbum
 
     @GObject.Property(type=CoreAlbum, flags=GObject.ParamFlags.READABLE)
     def corealbum(self):
diff --git a/gnomemusic/widgets/albumwidget.py b/gnomemusic/widgets/albumwidget.py
index 635b19cd..6bc81f5b 100644
--- a/gnomemusic/widgets/albumwidget.py
+++ b/gnomemusic/widgets/albumwidget.py
@@ -45,7 +45,7 @@ class AlbumWidget(Gtk.EventBox):
     _artist_label = Gtk.Template.Child()
     _composer_label = Gtk.Template.Child()
     _composer_info_label = Gtk.Template.Child()
-    _cover_stack = Gtk.Template.Child()
+    _art_stack = Gtk.Template.Child()
     _disc_list_box = Gtk.Template.Child()
     _released_info_label = Gtk.Template.Child()
     _running_info_label = Gtk.Template.Child()
@@ -68,7 +68,7 @@ class AlbumWidget(Gtk.EventBox):
         self._duration_signal_id = None
         self._model_signal_id = None
 
-        self._cover_stack.props.size = Art.Size.LARGE
+        self._art_stack.props.size = Art.Size.LARGE
         self._player = self._application.props.player
 
         self.bind_property(
@@ -89,7 +89,7 @@ class AlbumWidget(Gtk.EventBox):
 
         self._corealbum = corealbum
 
-        self._cover_stack.update(self._corealbum)
+        self._art_stack.props.coreobject = self._corealbum
 
         album_name = self._corealbum.props.title
         artist = self._corealbum.props.artist
diff --git a/gnomemusic/widgets/artistalbumwidget.py b/gnomemusic/widgets/artistalbumwidget.py
index 47f35f4f..b80be261 100644
--- a/gnomemusic/widgets/artistalbumwidget.py
+++ b/gnomemusic/widgets/artistalbumwidget.py
@@ -40,7 +40,7 @@ class ArtistAlbumWidget(Gtk.Box):
     __gtype_name__ = 'ArtistAlbumWidget'
 
     _album_box = Gtk.Template.Child()
-    _cover_stack = Gtk.Template.Child()
+    _art_stack = Gtk.Template.Child()
     _disc_list_box = Gtk.Template.Child()
     _title_year = Gtk.Template.Child()
 
@@ -66,8 +66,8 @@ class ArtistAlbumWidget(Gtk.Box):
 
         self._selection_mode = False
 
-        self._cover_stack.props.size = Art.Size.MEDIUM
-        self._cover_stack.update(corealbum)
+        self._art_stack.props.size = Art.Size.MEDIUM
+        self._art_stack.props.coreobject = corealbum
 
         self.bind_property(
             'selection-mode', self._disc_list_box, 'selection-mode',
@@ -83,7 +83,7 @@ class ArtistAlbumWidget(Gtk.Box):
             self._size_group.add_widget(self._album_box)
 
         if self._cover_size_group:
-            self._cover_size_group.add_widget(self._cover_stack)
+            self._cover_size_group.add_widget(self._art_stack)
 
         corealbum.props.model.connect_after(
             "items-changed", self._on_model_items_changed)
diff --git a/gnomemusic/widgets/artstack.py b/gnomemusic/widgets/artstack.py
index 5c1f405f..2fde30ba 100644
--- a/gnomemusic/widgets/artstack.py
+++ b/gnomemusic/widgets/artstack.py
@@ -1,4 +1,4 @@
-# Copyright 2019 The GNOME Music developers
+# Copyright 2020 The GNOME Music developers
 #
 # GNOME Music is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -112,9 +112,9 @@ class ArtStack(Gtk.Stack):
             self.props.visible_child_name = "B"
 
     def _on_destroy(self, widget):
-        # If the stacm is destroyed while the art is updated, an error
-        # can occur once the art is retrieved because the CoverStack
-        # does not have children anymore.
+        # If the stack is destroyed while the art is updated, an error
+        # can occur once the art is retrieved because the ArtStack does
+        # not have children anymore.
         self._disconnect_cache()
 
     def _disconnect_cache(self):


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