def add(self, uris): """ Add uris to collection """ GLib.idle_add(App().window.container.progress.pulse, True) walk_uris = list(uris) while walk_uris: uri = walk_uris.pop(0) if not uri: continue try: f = Gio.File.new_for_uri(uri) file_type = f.query_file_type(Gio.FileQueryInfoFlags.NONE, None) if file_type == Gio.FileType.DIRECTORY: infos = f.enumerate_children( "standard::name,standard::type,standard::is-hidden", Gio.FileQueryInfoFlags.NONE, None) for info in infos: f = infos.get_child(info) child_uri = f.get_uri() if info.get_is_hidden(): continue elif info.get_file_type() == Gio.FileType.DIRECTORY: walk_uris.append(child_uri) else: if is_audio(f): self.__add_file(f) elif is_audio(f): self.__add_file(f) else: Logger.info("Not an audio file %s" % uri) except Exception as e: Logger.error("CollectionImporter::add(): %s" % e) GLib.idle_add(App().window.container.progress.pulse, False)
def load(self, base_uri): """ Loads the metadata db from the MTP device @param base_uri as str """ self.__base_uri = base_uri self.__db_uri = self.__base_uri + "/lollypop-sync.db" Logger.debug("MtpSyncDb::__load_db()") try: dbfile = Gio.File.new_for_uri(self.__db_uri) (status, jsonraw, tags) = dbfile.load_contents(None) if status: jsondb = json.loads(jsonraw.decode("utf-8")) if "encoder" in jsondb: self.__encoder = jsondb["encoder"] if "normalize" in jsondb: self.__normalize = jsondb["normalize"] if "version" in jsondb and jsondb["version"] == 1: for m in jsondb["tracks_metadata"]: self.__metadata[m["uri"]] = m["metadata"] else: Logger.info("MtpSyncDb::__load_db():" " unknown sync db version") except Exception as e: Logger.error("MtpSyncDb::load(): %s" % e)
def __cache_artist_info(self, artist): """ Cache artist information @param artist as str """ content = None try: if get_network_available("WIKIPEDIA"): wikipedia = Wikipedia() content = wikipedia.get_content(artist) else: for (api, a_helper, ar_helper, helper) in self._WEBSERVICES: if helper is None: continue try: method = getattr(self, helper) content = method(artist) if content is not None: break except Exception as e: Logger.error( "InfoDownloader::__cache_artists_artwork(): %s" % e) self.save_artist_information(artist, content) except Exception as e: Logger.info("InfoDownloader::__cache_artist_info(): %s" % e) GLib.idle_add(self.emit, "artist-info-changed", artist)
def __populate_db(self): """ Populate DB in a background task """ try: Logger.info("Collection download started") self.__is_running = True self.__cancellable = Gio.Cancellable() storage_types = [] mask = App().settings.get_value("suggestions-mask").get_int32() # Check if storage type needs to be updated # Check if albums newer than a week are enough timestamp = time() - 604800 for storage_type in self.__STORAGE_TYPES: if not mask & storage_type: continue newer_albums = App().albums.get_newer_for_storage_type( storage_type, timestamp) if len(newer_albums) < self.MIN_ITEMS_PER_STORAGE_TYPE: storage_types.append(storage_type) # Update needed storage types if storage_types: for storage_type in storage_types: if self.__cancellable.is_cancelled(): raise Exception("cancelled") self.__METHODS[storage_type](self, self.__cancellable) self.clean_old_albums(storage_types) App().artists.update_featuring() except Exception as e: Logger.warning("CollectionWebService::__populate_db(): %s", e) self.__is_running = False Logger.info("Collection download finished")
def search_similar_albums(self, cancellable): """ Add similar albums to DB @param cancellable as Gio.Cancellable """ Logger.info("Get similar albums from Spotify") from lollypop.similars_spotify import SpotifySimilars similars = SpotifySimilars() try: storage_type = get_default_storage_type() artists = App().artists.get_randoms( self.MAX_ITEMS_PER_STORAGE_TYPE, storage_type) artist_names = [name for (aid, name, sortname) in artists] similar_ids = similars.get_similar_artist_ids( artist_names, cancellable) # Add albums shuffle(similar_ids) for similar_id in similar_ids[:self.MAX_ITEMS_PER_STORAGE_TYPE]: albums_payload = self.__get_artist_albums_payload( similar_id, cancellable) shuffle(albums_payload) for album in albums_payload: if cancellable.is_cancelled(): raise Exception("Cancelled") lollypop_payload = SpotifyWebHelper.lollypop_album_payload( self, album) self.save_album_payload_to_db(lollypop_payload, StorageType.SPOTIFY_SIMILARS, True, cancellable) break except Exception as e: Logger.warning("SpotifyWebService::search_similar_albums(): %s", e)
def restore_state(self): """ Restore player state """ try: if App().settings.get_value("save-state"): current_track_id = load(open(LOLLYPOP_DATA_PATH + "/track_id.bin", "rb")) self.set_queue(load(open(LOLLYPOP_DATA_PATH + "/queue.bin", "rb"))) albums = load(open(LOLLYPOP_DATA_PATH + "/Albums.bin", "rb")) playlist_ids = load(open(LOLLYPOP_DATA_PATH + "/playlist_ids.bin", "rb")) (is_playing, was_party) = load(open(LOLLYPOP_DATA_PATH + "/player.bin", "rb")) if playlist_ids and playlist_ids[0] == Type.RADIOS: radios = Radios() track = Track() name = radios.get_name(current_track_id) url = radios.get_url(name) track.set_radio(name, url) self.load(track, is_playing) elif App().tracks.get_uri(current_track_id) != "": if albums: if was_party: App().lookup_action("party").change_state( GLib.Variant("b", True)) else: self._albums = load(open( LOLLYPOP_DATA_PATH + "/Albums.bin", "rb")) # Load track from player albums current_track = Track(current_track_id) index = self.album_ids.index(current_track.album.id) for track in self._albums[index].tracks: if track.id == current_track_id: self._load_track(track) break else: for playlist_id in playlist_ids: tracks = App().playlists.get_tracks(playlist_id) App().player.populate_playlist_by_tracks( tracks, playlist_ids) for track in tracks: if track.id == current_track_id: self._load_track(track) break if is_playing: self.play() else: self.pause() position = load(open(LOLLYPOP_DATA_PATH + "/position.bin", "rb")) self.seek(position / Gst.SECOND) else: Logger.info("Player::restore_state(): track missing") self.emit("playlist-changed") except Exception as e: Logger.error("Player::restore_state(): %s" % e)
def search_charts(self, cancellable): """ Add charts to DB @param cancellable as Gio.Cancellable """ Logger.info("Get charts with Deezer") try: album_ids = [] uri = "https://api.deezer.com/chart/0/albums?limit=30" (status, data) = App().task_helper.load_uri_content_sync( uri, cancellable) if status: decode = json.loads(data.decode("utf-8")) for album in decode["data"]: album_ids.append(album["id"]) for album_id in album_ids: if cancellable.is_cancelled(): raise Exception("Cancelled") payload = DeezerWebHelper.get_album_payload( self, album_id, cancellable) if payload is None: continue lollypop_payload = DeezerWebHelper.lollypop_album_payload( self, payload) self.save_album_payload_to_db(lollypop_payload, StorageType.DEEZER_CHARTS, True, cancellable) except Exception as e: Logger.warning( "DeezerCollectionWebService::search_charts(): %s", e)
def update(self, scan_type, uris=[]): """ Update database @param scan_type as ScanType @param uris as [str] """ self.__disable_compilations = not App().settings.get_value( "show-compilations") App().lookup_action("update_db").set_enabled(False) # Stop previous scan if self.is_locked() and scan_type != ScanType.EXTERNAL: self.stop() GLib.timeout_add(250, self.update, scan_type, uris) elif App().ws_director.collection_ws is not None and\ not App().ws_director.collection_ws.stop(): GLib.timeout_add(250, self.update, scan_type, uris) else: if scan_type == ScanType.FULL: uris = App().settings.get_music_uris() if not uris: return # Register to progressbar if scan_type != ScanType.EXTERNAL: App().window.container.progress.add(self) App().window.container.progress.set_fraction(0, self) Logger.info("Scan started") # Launch scan in a separate thread self.__thread = App().task_helper.run(self.__scan, scan_type, uris)
def load(self): """ Load track URI """ uri = self.__load_from_cache() if uri is None: self.__load_uri_with_helper() else: Logger.info("%s loaded from cache", uri) self.__load_uri_content_with_helper(uri, None)
def _create_cache(self): """ Create cache dir """ d = Gio.File.new_for_path(self._CACHE_PATH) if not d.query_exists(): try: d.make_directory_with_parents() except: Logger.info("Can't create %s" % self._CACHE_PATH)
def __init__(self): """ Init album art """ try: d = Gio.File.new_for_path(self._INFO_PATH) if not d.query_exists(): d.make_directory_with_parents() except: Logger.info("Can't create %s" % self._INFO_PATH)
def create_dir(path): """ Create dir @param path as str """ d = Gio.File.new_for_path(path) if not d.query_exists(): try: d.make_directory_with_parents() except: Logger.info("Can't create %s" % path)
def wrapper(*args, **kwargs): start_time = time.perf_counter() ret = f(*args, **kwargs) elapsed_time = time.perf_counter() - start_time Logger.info( "%s::%s: execution time %d:%f" % (f.__module__, f.__name__, elapsed_time / 60, elapsed_time % 60)) return ret
def start(self): """ Start web service (load save queue) """ try: self.__cancellable = Gio.Cancellable() self.__queue = load( open(LOLLYPOP_DATA_PATH + "/%s_queue.bin" % self.__name, "rb")) except Exception as e: Logger.info("ListenBrainzWebService::__init__(): %s", e) self.__queue = []
def __on_spotify_similar_artists(self, artists): """ Add one album @param artists as [str] """ similar_artist_ids = self.__get_artist_ids(artists) if similar_artist_ids: Logger.info("Found similar artists via Spotify:") if self.albums: self.__add_a_new_album(similar_artist_ids) else: self.__add_a_new_track(similar_artist_ids)
def __init__(self): """ Init ListenBrainz object """ GObject.GObject.__init__(self) try: self.__uri = "https://api.listenbrainz.org/1/submit-listens" self.__name = "listenbrainz" self.__queue = [] self.start() except Exception as e: Logger.info("LastFM::__init__(): %s", e)
def get_uri_content(self, uri, cancellable): """ Get content uri @param uri as str @param cancellable as Gio.Cancellable @return content uri as str/None """ Logger.info("Loading %s with Invidious %s", uri, self.__server) youtube_id = uri.replace("https://www.youtube.com/watch?v=", "") video = self.__VIDEO % youtube_id uri = "%s/%s" % (self.__server, video) App().task_helper.load_uri_content(uri, cancellable, self.__on_uri_content)
def stop(self): """ Stop current tasks and save queue to disk @return bool """ self.__cancellable.cancel() try: with open(LOLLYPOP_DATA_PATH + "/%s_queue.bin" % self.__name, "wb") as f: dump(list(self.__queue), f) except Exception as e: Logger.info("ListenBrainzWebService::stop: %s", e) return True
def __init__(self): """ Init ListenBrainz object """ GObject.GObject.__init__(self) try: self.__queue_id = None self.__queue = load( open(LOLLYPOP_DATA_PATH + "/listenbrainz_queue.bin", "rb")) except Exception as e: Logger.info("LastFM::__init__(): %s", e) self.__queue = [] self.__next_request_time = 0
def _on_bus_error(self, bus, message): """ Try a codec install and update current track @param bus as Gst.Bus @param message as Gst.Message """ Logger.info("Player::_on_bus_error(): %s" % message.parse_error()[1]) App().window.container.pulse(False) if self.__codecs.is_missing_codec(message): self.__codecs.install() App().scanner.stop() elif App().notify is not None: App().notify.send(message.parse_error()[0].message) self.stop()
def __on_get_local_similar_artists(self, artists): """ Add one album from artists to player @param artists as [] """ if self.__next_cancellable.is_cancelled(): return similar_artist_ids = self.__get_artist_ids(artists) album = None if similar_artist_ids: album = self.__get_album_from_artists(similar_artist_ids) if album is not None: Logger.info("Found a similar album") self.add_album(album)
def get_music_uris(self): """ Return music uris @return [str] """ uris = self.get_value("music-uris") if not uris: filename = GLib.get_user_special_dir( GLib.UserDirectory.DIRECTORY_MUSIC) if filename: uris = [GLib.filename_to_uri(filename)] else: Logger.info("You need to add a music uri" " to org.gnome.Lollypop in dconf") return list(uris)
def init(): """ Init store """ try: d = Gio.File.new_for_path(InformationStore._INFO_PATH) if not d.query_exists(): d.make_directory_with_parents() except: Logger.info("Can't create %s" % InformationStore._INFO_PATH) try: d = Gio.File.new_for_path(InformationStore._CACHE_PATH) if not d.query_exists(): d.make_directory_with_parents() except: Logger.info("Can't create %s" % InformationStore._CACHE_PATH)
def __get_bio_content(self): """ Get bio content and call callback @param content as str """ content = None try: wikipedia = Wikipedia(self.__cancellable) content = wikipedia.get_content(self.__artist_name) except Exception as e: Logger.info("InformationPopover::__get_bio_content(): %s" % e) try: if content is None and App().lastfm is not None: content = App().lastfm.get_artist_bio(self.__artist_name) except Exception as e: Logger.info("InformationPopover::__get_bio_content(): %s" % e) return content
def _on_bus_error(self, bus, message): """ Try a codec install and update current track @param bus as Gst.Bus @param message as Gst.Message """ self.emit("loading-changed", False) Logger.info("Player::_on_bus_error(): %s" % message.parse_error()[1]) if self.current_track.id is not None and self.current_track.id >= 0: if self.__codecs.is_missing_codec(message): self.__codecs.install() App().scanner.stop() self.stop() elif App().notify is not None: (error, parsed) = message.parse_error() App().notify.send(parsed) self.stop()
def get_similar_artists(self, artist_names, cancellable): """ Get similar artists @param artist_ids as [int] @param cancellable as Gio.Cancellable @return [(str, None)] """ artist_ids = [] for artist_name in artist_names: artist_ids.append(App().artists.get_id(artist_name)[0]) genre_ids = App().artists.get_genre_ids(artist_ids, StorageType.COLLECTION) artists = App().artists.get(genre_ids, StorageType.COLLECTION) shuffle(artists) result = [(name, None) for (artist_id, name, sortname) in artists] if result: Logger.info("Found similar artists with LocalSimilars") return result
def __finish(self, items): """ Notify from main thread when scan finished @param items as [CollectionItem] """ track_ids = [item.track_id for item in items] self.__thread = None Logger.info("Scan finished") App().lookup_action("update_db").set_enabled(True) App().window.container.progress.set_fraction(1.0, self) self.stop() emit_signal(self, "scan-finished", track_ids) # Update max count value App().albums.update_max_count() # Update featuring App().artists.update_featuring() if App().ws_director.collection_ws is not None: App().ws_director.collection_ws.start()
def __on_lastfm_similar_artists(self, artists): """ Add one album or run a Spotify search if none @param artists as [str] """ similar_artist_ids = self.__get_artist_ids(artists) if not similar_artist_ids: if self.current_track.artist_ids: artist_id = self.current_track.artist_ids[0] artist_name = App().artists.get_name(artist_id) App().spotify.get_artist_id(artist_name, self.__on_get_spotify_artist_id) else: Logger.info("Found a similar artist via Last.fm") if self.albums: self.__add_a_new_album(similar_artist_ids) else: self.__add_a_new_track(similar_artist_ids)
def __handle_listenbrainz(self, acl): """ Start/stop ListenBrainz based on acl @param acl as int """ start = acl & NetworkAccessACL["MUSICBRAINZ"] if start and self.__listenbrainz_ws is None: from lollypop.ws_listenbrainz import ListenBrainzWebService self.__listenbrainz_ws = ListenBrainzWebService() self.__listenbrainz_ws.start() App().settings.bind("listenbrainz-user-token", self.__listenbrainz_ws, "user_token", 0) Logger.info("ListenBrainz web service started") elif not start and self.__listenbrainz_ws is not None: App().settings.unbind(self.__listenbrainz_ws, "listenbrainz-user-token") self.__listenbrainz_ws = None Logger.info("ListenBrainz web service stopping")
def get_similar_artists(self, artist_names, cancellable): """ Search similar artists @param artist_names as [str] @param cancellable as Gio.Cancellable @return [(str, str)] as [(artist_name, cover_uri)] """ result = [] for artist_name in artist_names: try: for similar in self.__get_similar_artists(artist_name): if cancellable.is_cancelled(): raise Exception("cancelled") result.append((similar, None)) except Exception as e: Logger.error("LastFMSimilars::get_similar_artists(): %s", e) if result: Logger.info("Found similar artists with LastFMSimilars") return result