def add_artwork_to_cache(self, name, surface, prefix): """ Add artwork to cache @param name as str @param surface as cairo.Surface @param prefix as str @thread safe """ try: encoded = md5(name.encode("utf-8")).hexdigest() width = surface.get_width() height = surface.get_height() cache_path_jpg = "%s/@%s@%s_%s_%s.jpg" % (CACHE_PATH, prefix, encoded, width, height) pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0, width, height) pixbuf.savev( cache_path_jpg, "jpeg", ["quality"], [str(App().settings.get_value("cover-quality").get_int32())]) except Exception as e: Logger.error("Art::add_artwork_to_cache(): %s" % e)
def __get_duckduck_name(self, string): """ Get wikipedia duck duck name for string @param string as str @return str """ name = None try: uri = "https://api.duckduckgo.com/?q=%s&format=json&pretty=1"\ % string (status, data) = App().task_helper.load_uri_content_sync(uri) if status: import json decode = json.loads(data.decode("utf-8")) uri = decode["AbstractURL"] if uri: name = uri.split("/")[-1] except Exception as e: Logger.error("Wikipedia::__get_duckduck_name(): %s", e) return name
def __on_request_send_async(self, source, result, callback, cancellable, uri, *args): """ Get stream and start reading from it @param source as Soup.Session @param result as Gio.AsyncResult @param cancellable as Gio.Cancellable @param callback as a function @param uri as str """ try: stream = source.send_finish(result) # We use a bytearray here as seems that bytes += is really slow stream.read_bytes_async(4096, GLib.PRIORITY_LOW, cancellable, self.__on_read_bytes_async, bytearray(0), cancellable, callback, uri, *args) except Exception as e: Logger.error("TaskHelper::__on_soup_msg_finished(): %s" % e) callback(uri, False, b"", *args)
def remove_disc(self, disc, album_id): """ Remove disc for album_id @param disc as Disc @param album_id as int """ try: removed = [] for album in self._albums: if album.id == album_id: for track in list(album.tracks): if track.id in disc.track_ids: empty = album.remove_track(track) if empty: removed.append(album) for album in removed: self._albums.remove(album) self.emit("playlist-changed") except Exception as e: Logger.error("Player::remove_disc(): %s" % e)
def load_tracks(self, album, cancellable): """ Load tracks for album @param album as Album @param cancellable as Gio.Cancellable """ try: mbid = album.uri.replace("mb:", "") tracks = self.get_tracks_payload(mbid, cancellable) # We want to share the same item as lp_album_id may change album_item = album.collection_item for track in tracks: lollypop_payload = self.lollypop_track_payload(track) self.save_track_payload_to_db(lollypop_payload, album_item, album.storage_type, False, cancellable) except Exception as e: Logger.error("MusicBrainzSearch::load_tracks(): %s", e)
def _get_lastfm_album_artwork(self, artist, album): """ Get album artwork from lastfm @param artist as string @param album as string @return data as bytes @tread safe """ image = None if App().lastfm is not None: try: helper = TaskHelper() last_album = App().lastfm.get_album(artist, album) uri = last_album.get_cover_image(4) if uri is not None: (status, image) = helper.load_uri_content_sync(uri, None) except Exception as e: Logger.error("Downloader::_get_album_art_lastfm: %s [%s/%s]" % (e, artist, album)) return image
def __on_uri_content(self, uri, status, data): """ Save artwork to cache and set artist artwork @param uri as str @param status as bool @param data as bytes """ try: if not status: return self.__cover_data = data scale_factor = self.get_scale_factor() App().art.add_artist_artwork(self.__artist_name, data, StorageType.EPHEMERAL) App().art_helper.set_artist_artwork( self.__artist_name, ArtSize.SMALL, ArtSize.SMALL, scale_factor, ArtBehaviour.CROP | ArtBehaviour.CACHE | ArtBehaviour.ROUNDED, self.__on_artist_artwork) except Exception as e: Logger.error("ArtistRow::__on_uri_content(): %s", e)
def __on_current_changed(self, player): if App().player.current_track.id is None: self.__lollypop_id = 0 else: self.__lollypop_id = App().player.current_track.id # We only need to recalculate a new trackId at song changes. self.__track_id = self.__get_media_id(self.__lollypop_id) self.__rating = None self.__update_metadata() properties = { "Metadata": GLib.Variant("a{sv}", self.__metadata), "CanPlay": GLib.Variant("b", True), "CanPause": GLib.Variant("b", True), "CanGoNext": GLib.Variant("b", True), "CanGoPrevious": GLib.Variant("b", True) } try: self.PropertiesChanged(self.__MPRIS_PLAYER_IFACE, properties, []) except Exception as e: Logger.error("MPRIS::__on_current_changed(): %s" % e)
def set_prev(self): """ Set previous track """ if self.current_track.id == Type.RADIOS: return try: prev_track = ShufflePlayer.prev(self) # Look at user playlist then if prev_track.id is None: prev_track = PlaylistPlayer.prev(self) # Get a linear track then if prev_track.id is None: prev_track = LinearPlayer.prev(self) self._prev_track = prev_track self.emit("prev-changed") except Exception as e: Logger.error("Player::set_prev(): %s" % e)
def _get_lastfm_album_artwork_uri(self, artist, album, cancellable=None): """ Get album artwork uri from lastfm @param artist as str @param album as str @param cancellable as Gio.Cancellable @return uri as str @tread safe """ if not get_network_available("LASTFM"): return None if App().lastfm is not None: try: last_album = App().lastfm.get_album(artist, album) uri = last_album.get_cover_image(4) return uri except Exception as e: Logger.error("ArtDownloader::_get_album_art_lastfm_uri: %s" % e) return None
def populate(self): """ Populate view """ try: ArtworkSearchWidget.populate(self) # First load local files uris = App().art.get_album_artworks(self.__album) # Direct load, not using loopback because not many items for uri in uris: child = ArtworkSearchChild(_("Local")) child.show() f = Gio.File.new_for_uri(uri) (status, content, tag) = f.load_contents() if status: status = child.populate(content) if status: self._flowbox.add(child) except Exception as e: Logger.error("AlbumArtworkSearchWidget::populate(): %s", e)
def clean_album_cache(self, album, width=-1, height=-1): """ Remove cover from cache for album id @param album as Album @param width as int @param height as int """ try: from pathlib import Path if width == -1 or height == -1: for p in Path(CACHE_PATH).glob("%s*.jpg" % album.lp_album_id): p.unlink() else: filename = "%s/%s_%s_%s.jpg" % (CACHE_PATH, album.lp_album_id, width, height) f = Gio.File.new_for_path(filename) if f.query_exists(): f.delete() except Exception as e: Logger.error("AlbumArt::clean_album_cache(): %s" % e)
def __populate_loved_tracks(self): """ Populate loved tracks playlist """ if not self.available: return try: user = self.get_user(self.__login) for loved in user.get_loved_tracks(limit=None): artist = str(loved.track.artist) title = str(loved.track.title) track_id = App().tracks.search_track(artist, title) if track_id is None: Logger.warning( "LastFM::__populate_loved_tracks(): %s, %s" % (artist, title)) else: Track(track_id).set_loved(1) except Exception as e: Logger.error("LastFM::__populate_loved_tracks: %s" % e)
def uncache_radio_artwork(self, name): """ Remove radio artwork from cache @param name as string """ cache_name = self.__get_radio_cache_name(name) try: f = Gio.File.new_for_path(self._CACHE_PATH) infos = f.enumerate_children( "standard::name", Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, None) for info in infos: f = infos.get_child(info) basename = f.get_basename() if re.search(r"%s_.*\.png" % re.escape(cache_name), basename): f.delete() except Exception as e: Logger.error("RadioArt::clean_radio_cache(): %s, %s" % (e, cache_name))
def search_similar_artists(self, artist, cancellable): """ Search similar artists @param artist as str @param cancellable as Gio.Cancellable """ try: found = False artist_item = self.get_artist(artist) for similar_item in artist_item.get_similar(): if cancellable.is_cancelled(): raise Exception("cancelled") found = True artist_name = similar_item.item.name cover_uri = similar_item.item.get_cover_image() GLib.idle_add(self.emit, "new-artist", artist_name, cover_uri) except Exception as e: Logger.error("LastFM::search_similar_artists(): %s", e) if not found: GLib.idle_add(self.emit, "new-artist", None, None)
def _get_spotify_album_artwork_uri(self, artist, album, cancellable=None): """ Get album artwork uri from spotify @param artist as str @param album as str @param cancellable as Gio.Cancellable @return uri as str @tread safe """ if not get_network_available("SPOTIFY"): return None artists_spotify_ids = [] try: token = self.__get_spotify_token(cancellable) artist_formated = GLib.uri_escape_string(artist, None, True).replace(" ", "+") uri = "https://api.spotify.com/v1/search?q=%s" % artist_formated +\ "&type=artist" token = "Bearer %s" % token helper = TaskHelper() helper.add_header("Authorization", token) (status, data) = helper.load_uri_content_sync(uri, cancellable) if status: decode = json.loads(data.decode("utf-8")) for item in decode["artists"]["items"]: artists_spotify_ids.append(item["id"]) for artist_spotify_id in artists_spotify_ids: uri = "https://api.spotify.com/v1/artists/" +\ "%s/albums" % artist_spotify_id (status, data) = helper.load_uri_content_sync(uri, cancellable) if status: decode = json.loads(data.decode("utf-8")) uri = None for item in decode["items"]: if noaccents(item["name"].lower()) ==\ noaccents(album.lower()): return item["images"][0]["url"] except Exception as e: Logger.error("ArtDownloader::_get_album_art_spotify_uri: %s" % e) return None
def populate(self): """ Setup an initial widget based on current request """ sql = App().playlists.get_smart_sql(self.__playlist_id) if sql is None: return try: if sql.find(" UNION ") != -1: operand = "OR" else: operand = "AND" self.__operand_combobox.set_active_id(operand) except Exception as e: self.__operand_combobox.set_active(0) Logger.warning("SmartPlaylistView::populate: %s", e) # Setup rows for line in sql.split("((")[1:]: widget = SmartPlaylistRow(self.__size_group) try: widget.set(line.split("))")[0]) except Exception as e: Logger.error("SmartPlaylistView::populate: %s", e) widget.show() self.__listbox.add(widget) try: split_limit = sql.split("LIMIT") limit = int(split_limit[1].split(" ")[1]) self.__limit_spin.set_value(limit) except Exception as e: Logger.warning("SmartPlaylistView::populate: %s", e) try: split_order = sql.split("ORDER BY") split_spaces = split_order[1].split(" ") orderby = split_spaces[1] if split_spaces[2] in ["ASC", "DESC"]: orderby += " %s" % split_spaces[2] self.__select_combobox.set_active_id(orderby) except Exception as e: self.__select_combobox.set_active(0) Logger.warning("SmartPlaylistView::populate: %s", e)
def get_album_cache_path(self, album, width, height): """ get artwork cache path for album_id @param album as Album @param width as int @param height as int @return cover path as string or None if no cover """ try: cache_filepath = "%s/%s_%s_%s.%s" % (CACHE_PATH, album.lp_album_id, width, height, self._ext) f = Gio.File.new_for_path(cache_filepath) if f.query_exists(): return cache_filepath else: self.get_album_artwork(album, width, height, 1) if f.query_exists(): return cache_filepath except Exception as e: Logger.error("Art::get_album_cache_path(): %s" % e) return None
def __edit_tag(self, action, variant): """ Run tag editor @param Gio.SimpleAction @param GLib.Variant """ path = GLib.filename_from_uri(self._object.uri)[0] if GLib.find_program_in_path("flatpak-spawn") is not None: argv = ["flatpak-spawn", "--host", App().art.tag_editor, path] else: argv = [App().art.tag_editor, path] try: (pid, stdin, stdout, stderr) = GLib.spawn_async( argv, flags=GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.STDOUT_TO_DEV_NULL, standard_input=False, standard_output=False, standard_error=False ) except Exception as e: Logger.error("MenuPopover::__edit_tag(): %s" % e)
def __on_edit_button_clicked(self, button): """ Run tags editor @param button as Gtk.Button """ path = GLib.filename_from_uri(self.__object.uri)[0] if GLib.find_program_in_path("flatpak-spawn") is not None: argv = ["flatpak-spawn", "--host", App().art.tag_editor, path] else: argv = [App().art.tag_editor, path] try: (pid, stdin, stdout, stderr) = GLib.spawn_async(argv, flags=GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.STDOUT_TO_DEV_NULL, standard_input=False, standard_output=False, standard_error=False) except Exception as e: Logger.error("ContextWidget::__on_edit_button_clicked(): %s" % e) self.hide()
def remove_oldest(path, timestamp): """ Remove oldest files at path @param path as str @param timestamp as int """ SCAN_QUERY_INFO = "%s" % FILE_ATTRIBUTE_TIME_ACCESS try: d = Gio.File.new_for_path(path) infos = d.enumerate_children(SCAN_QUERY_INFO, Gio.FileQueryInfoFlags.NONE, None) for info in infos: f = infos.get_child(info) if info.get_file_type() == Gio.FileType.REGULAR: atime = int( info.get_attribute_as_string(FILE_ATTRIBUTE_TIME_ACCESS)) # File not used since one year if time() - atime > timestamp: f.delete(None) except Exception as e: Logger.error("remove_oldest(): %s", e)
def __on_load_google_content(self, uri, loaded, content): """ Extract uris from content @param uri as str @param loaded as bool @param content as bytes """ try: if not loaded: self.emit("uri-artwork-found", []) return decode = json.loads(content.decode("utf-8")) results = [] for item in decode["items"]: if item["link"] is not None: results.append((item["link"], "Google")) self.emit("uri-artwork-found", results) except Exception as e: self.emit("uri-artwork-found", []) Logger.error("ArtDownloader::__on_load_google_content(): %s: %s" % (e, content))
def __playing_now(self, artist, album, title, duration, mb_track_id): """ Now playing track @param artist as str @param title as str @param album as str @param duration as int @thread safe """ try: self.update_now_playing(artist=artist, album=album, title=title, duration=duration, mbid=mb_track_id) Logger.debug("LastFM::__playing_now(): %s, %s, %s, %s, %s" % (artist, album, title, duration, mb_track_id)) except WSError: pass except Exception as e: Logger.error("LastFM::__playing_now(): %s" % e)
def get_album_payload(self, album, artist, cancellable): """ Get album payload for mbid @param album as str @param artist as str @param cancellable as Gio.Cancellable @return {}/None """ artist = GLib.uri_escape_string(artist, None, True) album = GLib.uri_escape_string(album, None, True) try: uri = "http://ws.audioscrobbler.com/2.0/" uri += "?method=album.getInfo" uri += "&album=%s&artist=%s&api_key=%s&format=json" % ( album, artist, LASTFM_API_KEY) (status, data) = App().task_helper.load_uri_content_sync(uri, None) if status: return json.loads(data.decode("utf-8"))["album"] except: Logger.error("LastFMWebHelper::get_album_payload(): %s", uri) return None
def install(self): """ Install missing plugins """ if not GstPbutils.install_plugins_supported(): return try: context = GstPbutils.InstallPluginsContext.new() try: context.set_desktop_id("org.gnome.Lollypop.desktop") except: pass # Not supported by Ubuntu VIVID details = [] for message in self._messages: detail = \ GstPbutils.missing_plugin_message_get_installer_detail( message) details.append(detail) GstPbutils.install_plugins_async(details, context, self.__null) except Exception as e: Logger.error("Codecs::__init__(): %s" % e)
def __get_cover_art_uri(self, mbid, cancellable): """ Get cover art URI for mbid @param mbid as str @param cancellable as Gio.Cancellable @return str/None """ try: uri = "http://coverartarchive.org/release/%s" % mbid (status, data) = App().task_helper.load_uri_content_sync(uri, None) if status: decode = json.loads(data.decode("utf-8")) for image in decode["images"]: if not image["front"]: continue return image["image"] except Exception as e: Logger.error(e) Logger.error( "SaveWebHelper::__get_cover_art_uri(): %s", data) return None
def skip_album(self): """ Skip current album """ try: # In party or shuffle, just update next track if self.is_party or\ App().settings.get_enum("shuffle") == Shuffle.TRACKS: self.set_next() # We send this signal to update next popover self.emit("queue-changed") elif self._current_track.id is not None: index = self.album_ids.index( App().player._current_playback_track.album.id) if index + 1 >= len(self._albums): next_album = self._albums[0] else: next_album = self._albums[index + 1] self.load(next_album.tracks[0]) except Exception as e: Logger.error("Player::skip_album(): %s" % e)
def execute(self, request): """ Execute SQL request (only smart one) @param request as str @return list """ try: with SqlCursor(App().db) as sql: result = sql.execute(request) # Special case for OR request if request.find("ORDER BY random()") == -1 and\ request.find("UNION") != -1: ids = [] for (id, other) in list(result): ids.append(id) return ids else: return list(itertools.chain(*result)) except Exception as e: Logger.error("Database::execute(): %s -> %s", e, request) return []
def pop(self, index=-1): """ Pop last view from history @param index as int @return View """ try: if not self.__items: return None (view, _class, args, sidebar_id, selection_ids, position) = self.__items.pop(index) # View is offloaded, create a new one if view is None: view = self.__get_view_from_class(_class, args) view.set_sidebar_id(sidebar_id) view.set_selection_ids(selection_ids) view.set_populated_scrolled_position(position) return view except Exception as e: Logger.error("AdaptiveHistory::pop(): %s" % e) self.__items = []
def get_token(self, service): """ Get token for service @param service as str """ try: secret = Secret.Service.get_sync(Secret.ServiceFlags.NONE) SecretSchema = {"service": Secret.SchemaAttributeType.STRING} SecretAttributes = {"service": service} schema = Secret.Schema.new("org.gnome.Lollypop", Secret.SchemaFlags.NONE, SecretSchema) items = secret.search_sync(schema, SecretAttributes, Secret.SearchFlags.ALL, None) if items: items[0].load_secret_sync(None) value = items[0].get_secret() if value is not None: return value.get_text() except Exception as e: Logger.error("PasswordsHelper::get_sync(): %s" % e) return None