def search(self, search, cancellable): """ Get albums related to search We need a thread because we are going to populate DB @param search as str @param cancellable as Gio.Cancellable """ try: while self.wait_for_token(): if cancellable.is_cancelled(): raise Exception("cancelled") sleep(1) token = "Bearer %s" % self.__token helper = TaskHelper() helper.add_header("Authorization", token) uri = "https://api.spotify.com/v1/search?" uri += "q=%s&type=album,track" % search (status, data) = helper.load_uri_content_sync(uri, cancellable) if status: decode = json.loads(data.decode("utf-8")) album_ids = [] self.__create_albums_from_album_payload( decode["albums"]["items"], album_ids, cancellable) self.__create_albums_from_tracks_payload( decode["tracks"]["items"], album_ids, cancellable) except Exception as e: Logger.warning("SpotifyHelper::search(): %s", e) # Do not emit search-finished on cancel if str(e) == "cancelled": return GLib.idle_add(self.emit, "search-finished")
def __connect(self, full_sync=False): """ Connect service @param full_sync as bool @thread safe """ if self.__goa is not None or (self.__password != "" and self.__login != ""): self.__is_auth = True else: self.__is_auth = False try: self.session_key = "" self.__check_for_proxy() if self.__goa is not None: self.session_key = self.__goa.call_get_access_token_sync( None)[0] else: skg = SessionKeyGenerator(self) self.session_key = skg.get_session_key(username=self.__login, password_hash=md5( self.__password)) if full_sync: helper = TaskHelper() helper.run(self.__populate_loved_tracks) except Exception as e: debug("LastFM::__connect(): %s" % e) self.__is_auth = False
def _get_itunes_album_artwork(self, artist, album): """ Get album artwork from itunes @param artist as string @param album as string @return image as bytes @tread safe """ image = None try: album_formated = GLib.uri_escape_string(album, None, True).replace(" ", "+") uri = "https://itunes.apple.com/search" +\ "?entity=album&term=%s" % album_formated helper = TaskHelper() (status, data) = helper.load_uri_content_sync(uri, None) if status: decode = json.loads(data.decode("utf-8")) for item in decode["results"]: if item["artistName"].lower() == artist.lower(): uri = item["artworkUrl60"].replace("60x60", "512x512") (status, image) = helper.load_uri_content_sync(uri, None) break except Exception as e: Logger.error("Downloader::_get_album_art_itunes: %s [%s/%s]" % (e, artist, album)) return image
def populate(self): """ Populate view """ image = Gtk.Image.new_from_icon_name("edit-clear-all-symbolic", Gtk.IconSize.DIALOG) image.set_property("valign", Gtk.Align.CENTER) image.set_property("halign", Gtk.Align.CENTER) context = image.get_style_context() context.add_class("cover-frame") padding = context.get_padding(Gtk.StateFlags.NORMAL) border = context.get_border(Gtk.StateFlags.NORMAL) image.set_size_request( ArtSize.BIG + padding.left + padding.right + border.left + border.right, ArtSize.BIG + padding.top + padding.bottom + border.top + border.bottom) image.show() self.__view.add(image) # First load local files if self.__album is not None: uris = App().art.get_album_artworks(self.__album) for uri in uris: try: f = Gio.File.new_for_uri(uri) (status, content, tag) = f.load_contents() self.__add_pixbuf(uri, status, content, print) except Exception as e: Logger.error("ArtworkSearch::populate(): %s" % e) # Then google uri = App().art.get_google_search_uri(self.__get_current_search()) helper = TaskHelper() helper.load_uri_content(uri, self.__cancellable, self.__on_google_content_loaded)
def __populate(self, uris): """ Add uris to view @param uris as [str] """ if self.__cancellable.is_cancelled(): return helper = TaskHelper() # Fallback to link extraction if uris is None and WEBKIT2: if self.__web_search is None: self.__back_button.show() self.__web_search = ArtworkSearchWebView(self.__spinner) self.__web_search.connect("populated", self.__on_web_search_populated) self.__web_search.show() self.__entry.hide() self.__stack.add_named(self.__web_search, "web") self.__stack.set_visible_child_name("web") self.__web_search.search(self.__get_current_search()) # Populate the view elif uris: uri = uris.pop(0) helper.load_uri_content(uri, self.__cancellable, self.__add_pixbuf, self.__populate, uris) # Nothing to load, stop else: self.__spinner.stop()
def _get_deezer_album_artwork(self, artist, album): """ Get album artwork from deezer @param artist as string @param album as string @return image as bytes @tread safe """ image = None try: album_formated = GLib.uri_escape_string(album, None, True) uri = "https://api.deezer.com/search/album/?" +\ "q=%s&output=json" % album_formated helper = TaskHelper() (status, data) = helper.load_uri_content_sync(uri, None) if status: decode = json.loads(data.decode("utf-8")) uri = None for item in decode["data"]: if item["artist"]["name"].lower() == artist.lower(): uri = item["cover_xl"] break if uri is not None: (status, image) = helper.load_uri_content_sync(uri, None) except Exception as e: Logger.error("Downloader::__get_deezer_album_artwork: %s" % e) return image
def __download_genius_lyrics(self): """ Download lyrics from genius """ self.__downloads_running += 1 # Update lyrics if self.__current_track.id == Type.RADIOS: split = App().player.current_track.name.split(" - ") if len(split) < 2: return artist = split[0] title = split[1] else: if self.__current_track.artists: artist = self.__current_track.artists[0] elif self.__current_track.album_artists: artist = self.__current_track.album_artists[0] else: artist = "" title = self.__current_track.name string = escape("%s %s" % (artist, title)) uri = "https://genius.com/%s-lyrics" % string.replace(" ", "-") helper = TaskHelper() helper.load_uri_content(uri, self.__cancellable, self.__on_lyrics_downloaded, "song_body-lyrics", "")
def get_similar_artists(self, artist_id, cancellable): """ Get similar artists @param artist_id as int @param cancellable as Gio.Cancellable @return artists as [str] """ artists = [] try: while self.wait_for_token(): if cancellable.is_cancelled(): raise Exception("cancelled") sleep(1) token = "Bearer %s" % self.__token helper = TaskHelper() helper.add_header("Authorization", token) uri = "https://api.spotify.com/v1/artists/%s/related-artists" %\ artist_id (status, data) = helper.load_uri_content_sync(uri, cancellable) if status: decode = json.loads(data.decode("utf-8")) for item in decode["artists"]: artists.append(item["name"]) except Exception as e: Logger.error("SpotifyHelper::get_similar_artists(): %s", e) return artists
def populate(self): """ Populate view """ image = Gtk.Image() surface = Lp().art.get_default_icon("edit-clear-all-symbolic", ArtSize.BIG, self.get_scale_factor()) image.set_from_surface(surface) image.set_property("valign", Gtk.Align.CENTER) image.set_property("halign", Gtk.Align.CENTER) image.get_style_context().add_class("cover-frame") image.show() self._view.add(image) # First load local files if self.__album is not None: uris = Lp().art.get_album_artworks(self.__album) for uri in uris: try: f = Gio.File.new_for_uri(uri) (status, content, tag) = f.load_contents() self.__add_pixbuf(uri, status, content, print) except Exception as e: print("ArtworkSearch::populate()", e) # Then google uri = Lp().art.get_google_search_uri(self.__get_current_search()) helper = TaskHelper() helper.load_uri_content(uri, self.__cancellable, self.__on_google_content_loaded)
def _get_spotify_artist_artwork_uri(self, artist, cancellable=None): """ Return spotify artist information @param artist as str @param cancellable as Gio.Cancellable @return uri as str @tread safe """ if not get_network_available("SPOTIFY"): return None try: 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" % self.__get_spotify_token(cancellable) helper = TaskHelper() helper.add_header("Authorization", token) (status, data) = helper.load_uri_content_sync(uri, cancellable) if status: uri = None decode = json.loads(data.decode("utf-8")) for item in decode["artists"]["items"]: if noaccents(item["name"].lower()) ==\ noaccents(artist.lower()): uri = item["images"][0]["url"] return uri except Exception as e: Logger.debug( "ArtDownloader::_get_spotify_artist_artwork_uri(): %s" % e) return None
def _on_new_btn_clicked(self, button): """ Create a new playlist based on search @param button as Gtk.Button """ helper = TaskHelper() helper.run(self.__new_playlist)
def _on_delete_confirm(self, button): """ Delete tracks after confirmation @param button as Gtk.Button """ selection = self.__view.get_selection() selected = selection.get_selected_rows()[1] rows = [] for item in selected: rows.append(Gtk.TreeRowReference.new(self.__model, item)) tracks = [] for row in rows: iterator = self.__model.get_iter(row.get_path()) track = Track(self.__model.get_value(iterator, 3)) tracks.append(track) if self.__playlist_id == Type.LOVED and Lp().lastfm is not None: if track.album.artist_id == Type.COMPILATIONS: artist_name = ", ".join(track.artists) else: artist_name = ", ".join(track.album.artists) helper = TaskHelper() helper.run(Lp().lastfm.unlove, artist_name, track.name) self.__model.remove(iterator) Lp().playlists.remove_tracks(self.__playlist_id, tracks) self.__infobar.hide() self.__unselectall()
def populate(self): """ populate view if needed """ if len(self.__model) == 0: helper = TaskHelper() helper.run(self.__append_tracks, callback=(self.__append_track,))
def __on_track_moved(self, widget, dst, src, up): """ Move track from src to row Recalculate track position @param widget as TracksWidget @param dst as int @param src as int @param up as bool """ def update_playlist(): # Save playlist in db only if one playlist visible if len(self.__playlist_ids) == 1 and self.__playlist_ids[0] >= 0: Lp().playlists.clear(self.__playlist_ids[0], False) tracks = [] for track_id in self.__tracks_left + self.__tracks_right: tracks.append(Track(track_id)) Lp().playlists.add_tracks(self.__playlist_ids[0], tracks, False) if not (set(self.__playlist_ids) - set(Lp().player.get_user_playlist_ids())): Lp().player.update_user_playlist(self.__tracks_left + self.__tracks_right) (src_widget, dst_widget, src_index, dst_index) = \ self.__move_track(dst, src, up) self.__update_tracks() self.__update_position() self.__update_headers() self.__tracks_widget_left.update_indexes(1) self.__tracks_widget_right.update_indexes(len(self.__tracks_left) + 1) helper = TaskHelper() helper.run(update_playlist)
def __download_wikia_lyrics(self): """ Downloas lyrics from wikia """ self.__downloads_running += 1 # Update lyrics if self.__current_track.id == Type.RADIOS: split = self.__current_track.name.split(" - ") if len(split) < 2: return artist = GLib.uri_escape_string(split[0], None, False) title = GLib.uri_escape_string(split[1], None, False) else: if self.__current_track.artists: artist = GLib.uri_escape_string( self.__current_track.artists[0], None, False) elif self.__current_track.album_artists: artist = self.__current_track.album_artists[0] else: artist = "" title = GLib.uri_escape_string(self.__current_track.name, None, False) uri = "https://lyrics.wikia.com/wiki/%s:%s" % (artist, title) helper = TaskHelper() helper.load_uri_content(uri, self.__cancellable, self.__on_lyrics_downloaded, "lyricbox", "\n")
def search_similar_artists(self, spotify_id, cancellable): """ Search similar artists @param spotify_id as str @param cancellable as Gio.Cancellable """ try: while self.wait_for_token(): if cancellable.is_cancelled(): raise Exception("cancelled") sleep(1) found = False token = "Bearer %s" % self.__token helper = TaskHelper() helper.add_header("Authorization", token) uri = "https://api.spotify.com/v1/artists/%s/related-artists" % \ spotify_id (status, data) = helper.load_uri_content_sync(uri, cancellable) if status: decode = json.loads(data.decode("utf-8")) for item in decode["artists"]: if cancellable.is_cancelled(): raise Exception("cancelled") found = True artist_name = item["name"] cover_uri = item["images"][1]["url"] GLib.idle_add(self.emit, "new-artist", artist_name, cover_uri) except Exception as e: Logger.error("SpotifyHelper::search_similar_artists(): %s", e) if not found: GLib.idle_add(self.emit, "new-artist", None, None)
def __remove_from_playlist(self, action, variant, playlist_id): """ Del from playlist @param SimpleAction @param GVariant @param object id as int @param is album as bool @param playlist id as int """ def remove(playlist_id): tracks = [] if isinstance(self._object, Album): track_ids = Lp().albums.get_track_ids(self._object.id, self._object.genre_ids, self._object.artist_ids) for track_id in track_ids: tracks.append(Track(track_id)) else: tracks = [Track(self._object.id)] Lp().playlists.remove_tracks(playlist_id, tracks) if playlist_id in Lp().player.get_user_playlist_ids(): Lp().player.update_user_playlist( Lp().playlists.get_track_ids(playlist_id)) helper = TaskHelper() helper.run(remove, playlist_id)
def init(self): """ Init main application """ self.settings = Settings.new() # Mount enclosing volume as soon as possible uris = self.settings.get_music_uris() try: for uri in uris: if uri.startswith("file:/"): continue f = Gio.File.new_for_uri(uri) f.mount_enclosing_volume(Gio.MountMountFlags.NONE, None, None, None) except Exception as e: Logger.error("Application::init(): %s" % e) cssProviderFile = Gio.File.new_for_uri( "resource:///org/gnome/Lollypop/application.css") cssProvider = Gtk.CssProvider() cssProvider.load_from_file(cssProviderFile) screen = Gdk.Screen.get_default() styleContext = Gtk.StyleContext() styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER) self.db = Database() self.cache = CacheDatabase() self.playlists = Playlists() self.albums = AlbumsDatabase(self.db) self.artists = ArtistsDatabase(self.db) self.genres = GenresDatabase(self.db) self.tracks = TracksDatabase(self.db) self.player = Player() self.inhibitor = Inhibitor() self.scanner = CollectionScanner() self.notify = NotificationManager() self.task_helper = TaskHelper() self.art_helper = ArtHelper() self.art = Art() self.art.update_art_size() self.ws_director = DirectorWebService() self.ws_director.start() if not self.settings.get_value("disable-mpris"): from lollypop.mpris import MPRIS MPRIS(self) settings = Gtk.Settings.get_default() self.__gtk_dark = settings.get_property( "gtk-application-prefer-dark-theme") if not self.__gtk_dark: dark = self.settings.get_value("dark-ui") settings.set_property("gtk-application-prefer-dark-theme", dark) ApplicationActions.__init__(self) monitor = Gio.NetworkMonitor.get_default() if monitor.get_network_available() and\ not monitor.get_network_metered() and\ self.settings.get_value("recent-youtube-dl"): self.task_helper.run(install_youtube_dl)
def cache_artists_info(self): """ Cache info for all artists """ if self.__cache_artists_running: return self.__cache_artists_running = True helper = TaskHelper() helper.run(self.__cache_artists_info)
def get(self, search_items, cancellable, callback): """ Get track for name @param search_items as [str] @param cancellable as Gio.Cancellable @param callback as callback """ helper = TaskHelper() helper.run(self.__get, search_items, cancellable, callback=callback)
def copy_uri_to_cache(self, uri, name, size): """ Copy uri to cache at size @param uri as str @param name as str @param size as int @thread safe """ helper = TaskHelper() helper.load_uri_content(uri, None, self.__on_uri_content, name, size)
def __download_images(self): """ Download and set image for TuneItem @thread safe """ if self.__covers_to_download and not self.__cancellable.is_cancelled(): (item, image) = self.__covers_to_download.pop(0) helper = TaskHelper() helper.load_uri_content(item.LOGO, self.__cancellable, self.__on_image_downloaded, image)
def populate(self): """ Populate view with tracks from playlist """ Lp().player.set_radios(self.__radios_manager.get()) if Lp().player.current_track.id == Type.RADIOS: Lp().player.set_next() # We force next update Lp().player.set_prev() # We force prev update helper = TaskHelper() helper.run(self.__get_radios, callback=(self.__on_get_radios, ))
def __update_db(self, action=None, param=None): """ Search for new music @param action as Gio.SimpleAction @param param as GLib.Variant """ if self.window: helper = TaskHelper() helper.run(self.art.clean_all_cache) self.window.update_db()
def cache_album_art(self, album_id): """ Download album artwork @param album id as int """ if album_id in self.__albums_history: return if get_network_available(): self.__albums_queue.append(album_id) if not self.__in_albums_download: helper = TaskHelper() helper.run(self.__cache_albums_art)
def __populate(self, uris): """ Add uris to view @param uris as [str] """ if uris: uri = uris.pop(0) helper = TaskHelper() helper.load_uri_content(uri, self.__cancellable, self.__add_pixbuf, self.__populate, uris) elif len(self.__view.get_children()) == 0: self.__stack.set_visible_child_name("notfound")
def now_playing(self, artist, album, title, duration): """ Now playing track @param artist as str @param title as str @param album as str @param duration as int """ if get_network_available() and\ self.__is_auth and Secret is not None: helper = TaskHelper() helper.run(self.__now_playing, artist, album, title, duration)
def do_scrobble(self, artist, album, title, timestamp): """ Scrobble track @param artist as str @param title as str @param album as str @param timestamp as int @param duration as int """ if get_network_available() and\ self.__is_auth and Secret is not None: helper = TaskHelper() helper.run(self.__scrobble, artist, album, title, timestamp)
def save_album_artwork(self, data, album_id): """ Save data for album id @param data as bytes @param album id as int """ try: album = Album(album_id) arturi = None save_to_tags = Lp().settings.get_value("save-to-tags") uri_count = Lp().albums.get_uri_count(album.uri) filename = self.get_album_cache_name(album) + ".jpg" if save_to_tags: helper = TaskHelper() helper.run(self.__save_artwork_tags, data, album) store_path = self._STORE_PATH + "/" + filename if album.uri == "" or is_readonly(album.uri): arturi = GLib.filename_to_uri(store_path) # Many albums with same path, suffix with artist_album name elif uri_count > 1: arturi = album.uri + "/" + filename favorite_uri = album.uri + "/" + self.__favorite favorite = Gio.File.new_for_uri(favorite_uri) if favorite.query_exists(): favorite.trash() else: arturi = album.uri + "/" + self.__favorite # Save cover to uri dst = Gio.File.new_for_uri(arturi) if not save_to_tags or dst.query_exists(): bytes = GLib.Bytes(data) stream = Gio.MemoryInputStream.new_from_bytes(bytes) bytes.unref() pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale( stream, ArtSize.MONSTER, ArtSize.MONSTER, True, None) stream.close() pixbuf.savev(store_path, "jpeg", ["quality"], [str(Lp().settings.get_value( "cover-quality").get_int32())]) dst = Gio.File.new_for_uri(arturi) src = Gio.File.new_for_path(store_path) src.move(dst, Gio.FileCopyFlags.OVERWRITE, None, None) self.clean_album_cache(album) GLib.idle_add(self.album_artwork_update, album.id) except Exception as e: print("Art::save_album_artwork(): %s" % e)
def charts(self, cancellable, language="global"): """ Get albums related to search We need a thread because we are going to populate DB @param cancellable as Gio.Cancellable @param language as str """ from csv import reader try: while self.wait_for_token(): if cancellable.is_cancelled(): raise Exception("cancelled") sleep(1) token = "Bearer %s" % self.__token helper = TaskHelper() helper.add_header("Authorization", token) uri = self.__CHARTS % language spotify_ids = [] (status, data) = helper.load_uri_content_sync(uri, cancellable) if status: decode = data.decode("utf-8") for line in decode.split("\n"): try: for row in reader([line]): if not row: continue url = row[4] if url == "URL": continue spotify_id = url.split("/")[-1] if spotify_id: spotify_ids.append(spotify_id) except Exception as e: Logger.warning("SpotifyHelper::charts(): %s", e) album_ids = [] for spotify_id in spotify_ids: if cancellable.is_cancelled(): raise Exception("cancelled") payload = self.__get_track_payload(helper, spotify_id, cancellable) self.__create_albums_from_tracks_payload( [payload], album_ids, cancellable) except Exception as e: Logger.warning("SpotifyHelper::charts(): %s", e) # Do not emit search-finished on cancel if str(e) == "cancelled": return GLib.idle_add(self.emit, "search-finished")