1# Copyright 2020 The GNOME Music developers 2# 3# GNOME Music is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2 of the License, or 6# (at your option) any later version. 7# 8# GNOME Music is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License along 14# with GNOME Music; if not, write to the Free Software Foundation, Inc., 15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16# 17# The GNOME Music authors hereby grant permission for non-GPL compatible 18# GStreamer plugins to be used and distributed together with GStreamer 19# and GNOME Music. This permission is above and beyond the permissions 20# granted by the GPL license by which GNOME Music is covered. If you 21# modify this code, you may extend this exception to your version of the 22# code, but you are not obligated to do so. If you do not wish to do so, 23# delete this exception statement from your version. 24 25import gi 26gi.require_versions({"MediaArt": "2.0", "Soup": "2.4"}) 27from gi.repository import Gio, GLib, GObject, MediaArt, Soup, GdkPixbuf 28 29from gnomemusic.musiclogger import MusicLogger 30from gnomemusic.coreartist import CoreArtist 31from gnomemusic.corealbum import CoreAlbum 32from gnomemusic.coresong import CoreSong 33 34 35class StoreArt(GObject.Object): 36 """Stores Art in the MediaArt cache. 37 """ 38 39 __gsignals__ = { 40 "finished": (GObject.SignalFlags.RUN_FIRST, None, ()) 41 } 42 43 def __init__(self): 44 """Initialize StoreArtistArt 45 46 :param coreobject: The CoreArtist or CoreAlbum to store art for 47 :param string uri: The art uri 48 """ 49 super().__init__() 50 51 self._coreobject = None 52 53 self._file = None 54 self._log = MusicLogger() 55 self._soup_session = Soup.Session.new() 56 57 def start(self, coreobject, uri): 58 self._coreobject = coreobject 59 60 if (uri is None 61 or uri == ""): 62 self._coreobject.props.thumbnail = "generic" 63 self.emit("finished") 64 return 65 66 if isinstance(self._coreobject, CoreArtist): 67 success, self._file = MediaArt.get_file( 68 self._coreobject.props.artist, None, "artist") 69 elif isinstance(self._coreobject, CoreAlbum): 70 success, self._file = MediaArt.get_file( 71 self._coreobject.props.artist, self._coreobject.props.title, 72 "album") 73 elif isinstance(self._coreobject, CoreSong): 74 success, self._file = MediaArt.get_file( 75 self._coreobject.props.artist, self._coreobject.props.album, 76 "album") 77 else: 78 success = False 79 80 if not success: 81 self._coreobject.props.thumbnail = "generic" 82 self.emit("finished") 83 return 84 85 cache_dir = GLib.build_filenamev( 86 [GLib.get_user_cache_dir(), "media-art"]) 87 cache_dir_file = Gio.File.new_for_path(cache_dir) 88 cache_dir_file.query_info_async( 89 Gio.FILE_ATTRIBUTE_ACCESS_CAN_READ, Gio.FileQueryInfoFlags.NONE, 90 GLib.PRIORITY_LOW, None, self._cache_dir_info_read, uri) 91 92 def _cache_dir_info_read(self, cache_dir_file, res, uri): 93 try: 94 cache_dir_file.query_info_finish(res) 95 except GLib.Error: 96 # directory does not exist yet 97 try: 98 cache_dir_file.make_directory(None) 99 except GLib.Error as error: 100 self._log.warning( 101 "Error: {}, {}".format(error.domain, error.message)) 102 self._coreobject.props.thumbnail = "generic" 103 self.emit("finished") 104 return 105 106 msg = Soup.Message.new("GET", uri) 107 self._soup_session.queue_message(msg, self._read_callback, None) 108 109 def _read_callback(self, src, result, data): 110 if result.props.status_code != 200: 111 self._log.debug( 112 "Failed to get remote art: {}".format( 113 result.props.reason_phrase)) 114 self.emit("finished") 115 return 116 117 istream = Gio.MemoryInputStream.new_from_bytes( 118 result.props.response_body_data) 119 GdkPixbuf.Pixbuf.new_from_stream_async( 120 istream, None, self._pixbuf_from_stream_finished) 121 122 def _pixbuf_from_stream_finished( 123 self, stream: Gio.MemoryInputStream, 124 result: Gio.AsyncResult) -> None: 125 try: 126 pixbuf = GdkPixbuf.Pixbuf.new_from_stream_finish(result) 127 except GLib.Error as error: 128 self._log.warning(f"Error: {error.domain}, {error.message}") 129 self.emit("finished") 130 else: 131 self._file.create_async( 132 Gio.FileCreateFlags.NONE, GLib.PRIORITY_LOW, None, 133 self._output_stream_created, pixbuf) 134 finally: 135 stream.close_async(GLib.PRIORITY_LOW, None, self._stream_closed) 136 137 def _output_stream_created( 138 self, stream: Gio.FileOutputStream, result: Gio.AsyncResult, 139 pixbuf: GdkPixbuf.Pixbuf) -> None: 140 try: 141 output_stream = stream.create_finish(result) 142 except GLib.Error as error: 143 # File already exists. 144 self._log.info(f"Error: {error.domain}, {error.message}") 145 else: 146 pixbuf.save_to_streamv_async( 147 output_stream, "jpeg", None, None, None, 148 self._output_stream_saved, output_stream) 149 150 def _output_stream_saved( 151 self, pixbuf: GdkPixbuf.Pixbuf, result: Gio.AsyncResult, 152 output_stream: Gio.FileOutputStream) -> None: 153 try: 154 pixbuf.save_to_stream_finish(result) 155 except GLib.Error as error: 156 self._log.warning(f"Error: {error.domain}, {error.message}") 157 else: 158 self.emit("finished") 159 finally: 160 output_stream.close_async( 161 GLib.PRIORITY_LOW, None, self._stream_closed) 162 163 def _stream_closed( 164 self, stream: Gio.OutputStream, result: Gio.AsyncResult) -> None: 165 try: 166 stream.close_finish(result) 167 except GLib.Error as error: 168 self._log.warning(f"Error: {error.domain}, {error.message}") 169