def __on_list_two_activated(self, listbox, row): """ Update view based on selected object @param listbox as Gtk.ListBox @param row as Gtk.ListBoxRow """ Logger.debug("Container::__on_list_two_activated()") self._stack.destroy_children() if not App().window.is_adaptive: App().window.emit("show-can-go-back", False) App().window.emit("can-go-back-changed", False) genre_ids = self._list_one.selected_ids selected_ids = self._list_two.selected_ids if not selected_ids or not genre_ids: return if genre_ids[0] == Type.PLAYLISTS: view = self._get_view_playlists(selected_ids) elif genre_ids[0] == Type.YEARS: view = self._get_view_albums_years(selected_ids) elif selected_ids[0] == Type.COMPILATIONS: view = self._get_view_albums(genre_ids, selected_ids) else: view = self._get_view_artists(genre_ids, selected_ids) self._stack.add(view) self._stack.set_visible_child(view)
def __playing_now(self, track): """ Now playing track @param track as Track """ try: payload = self.__get_payload(track) post_data = { "listen_type": "playing_now", "payload": payload } body = json.dumps(post_data).encode("utf-8") msg = Soup.Message.new("POST", self.__uri) msg.set_request("application/json", Soup.MemoryUse.STATIC, body) msg.request_headers.append("Accept-Charset", "utf-8") msg.request_headers.append("Authorization", "Token %s" % self.user_token) data = App().task_helper.send_message_sync(msg, self.__cancellable) if data is not None: Logger.debug("%s: %s", self.__uri, data) except Exception as e: Logger.error("ListenBrainzWebService::__playing_now(): %s" % e)
def _load_track(self, track, init_volume=True): """ Load track @param track as Track @param init volume as bool @return False if track not loaded """ if init_volume: self._plugins.volume.props.volume = 1.0 Logger.debug("BinPlayer::_load_track(): %s" % track.uri) try: self.__cancellable.cancel() self.__cancellable = Gio.Cancellable() self._current_track = track # We check track is URI track, if yes, do a load from Web # Will not work if we add another music provider one day track_uri = App().tracks.get_uri(track.id) if track.is_web and track.uri == track_uri: self.emit("loading-changed", True) App().task_helper.run(self._load_from_web, track) return False else: self._playbin.set_property("uri", track.uri) except Exception as e: # Gstreamer error Logger.error("BinPlayer::_load_track(): %s" % e) return False return True
def __listen(self, track, timestamp): """ Scrobble track @param track as Track @param timestamp as int """ tracks = self.__queue + [(track, timestamp)] self.__queue = [] try: for (track, timestamp) in tracks: payload = self.__get_payload(track) payload[0]["listened_at"] = timestamp post_data = { "listen_type": "single", "payload": payload } body = json.dumps(post_data).encode("utf-8") msg = Soup.Message.new("POST", self.__uri) msg.set_request("application/json", Soup.MemoryUse.STATIC, body) msg.request_headers.append("Accept-Charset", "utf-8") msg.request_headers.append("Authorization", "Token %s" % self.user_token) msg.request_headers.append("Content-Type", "application/json") data = App().task_helper.send_message_sync(msg, self.__cancellable) if data is not None: Logger.debug("%s: %s", self.__uri, data) except Exception as e: Logger.error("ListenBrainzWebService::__listen(): %s" % e)
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 __save_in_db(self, storage_type): """ Save current tags into DB @param storage_type as StorageType @return [CollectionItem] """ items = [] notify_index = 0 previous_album_id = None for uri in list(self.__tags.keys()): # Handle a stop request if self.__thread is None: raise Exception("cancelled") Logger.debug("Adding file: %s" % uri) tags = self.__tags[uri] item = self.__add2db(uri, *tags, storage_type) items.append(item) self.__progress_count += 1 self.__update_progress(self.__progress_count, self.__progress_total, 0.001) if previous_album_id != item.album_id: self.__notify_ui(items[notify_index:]) notify_index = len(items) previous_album_id = item.album_id del self.__tags[uri] # Handle a stop request if self.__thread is None: raise Exception("cancelled") self.__notify_ui(items) return items
def __playing_now(self, track): """ Now playing track @param track as Track """ try: token = App().ws_director.token_ws.get_token( self.__name, self.__cancellable) if token is None: return args = self.__get_args_for_method("track.updateNowPlaying") args.append(("artist", track.artists[0])) args.append(("track", track.name)) args.append(("album", track.album.name)) if track.mbid and track.mbid.find(":") == -1: args.append(("mbid", track.mbid)) args.append(("duration", str(track.duration // 1000))) args.append(("sk", token)) api_sig = self.__get_sig_for_args(args) args.append(("api_sig", api_sig)) post_data = {} for (name, value) in args: post_data[name] = value msg = Soup.form_request_new_from_hash("POST", self.__uri, post_data) msg.request_headers.append("Accept-Charset", "utf-8") data = App().task_helper.send_message_sync(msg, self.__cancellable) if data is not None: Logger.debug("%s: %s -> %s", self.__uri, data, post_data) except Exception as e: Logger.error("LastFMWebService::__playing_now(): %s" % e)
def _on_bus_message_tag(self, bus, message): """ Read tags from stream @param bus as Gst.Bus @param message as Gst.Message """ # Some radio streams send message tag every seconds! changed = False if self._current_track.id >= 0 or self._current_track.duration > 0.0: return Logger.debug("Player::__on_bus_message_tag(): %s" % self._current_track.uri) reader = TagReader() tags = message.parse_tag() title = reader.get_title(tags, "") if title != "" and self._current_track.name != title: self._current_track.name = title changed = True if self._current_track.name == "": self._current_track.name = self._current_track.uri changed = True artists = reader.get_artists(tags) if artists != "" and self._current_track.artists != artists: self._current_track.artists = artists.split(",") changed = True if not self._current_track.artists: self._current_track.artists = self._current_track.album_artists changed = True if changed: self.emit("current-changed")
def __love(self, artist, title, status): """ Love track @param artist as string @param title as string @param status as bool """ try: token = App().ws_director.token_ws.get_token( self.__name, self.__cancellable) if token is None: return if status: args = self.__get_args_for_method("track.love") else: args = self.__get_args_for_method("track.unlove") args.append(("artist", artist)) args.append(("track", title)) args.append(("sk", token)) api_sig = self.__get_sig_for_args(args) args.append(("api_sig", api_sig)) post_data = {} for (name, value) in args: post_data[name] = value msg = Soup.form_request_new_from_hash("POST", self.__uri, post_data) msg.request_headers.append("Accept-Charset", "utf-8") data = App().task_helper.send_message_sync(msg, self.__cancellable) if data is not None: Logger.debug("%s: %s", self.__uri, data) except Exception as e: Logger.error("LastFMWebService::__love(): %s" % e)
def save_album( self, item, ): """ Add album to DB @param item as CollectionItem """ Logger.debug("CollectionScanner::save_album(): " "Add album artists %s" % item.album_artists) (item.new_album_artist_ids, item.album_artist_ids) = self.add_artists(item.album_artists, item.aa_sortnames, item.mb_album_artist_id) # We handle artists already created by any previous save_track() for artist_id in item.album_artist_ids: if artist_id in self.__pending_new_artist_ids: item.new_album_artist_ids.append(artist_id) self.__pending_new_artist_ids.remove(artist_id) item.lp_album_id = get_lollypop_album_id(item.album_name, item.album_artists) Logger.debug("CollectionScanner::save_track(): Add album: " "%s, %s" % (item.album_name, item.album_artist_ids)) (item.new_album, item.album_id) = self.add_album( item.album_name, item.mb_album_id, item.lp_album_id, item.album_artist_ids, item.uri, item.album_loved, item.album_pop, item.album_rate, item.album_synced, item.album_mtime, item.storage_type) if item.year is not None: App().albums.set_year(item.album_id, item.year) App().albums.set_timestamp(item.album_id, item.timestamp)
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 __request(self, listen_type, payload, retry=0): """ Submit payload to service @param listen_type as str @param payload as [] @param retry as int (internal) """ self.__wait_for_ratelimit() Logger.debug("ListenBrainz %s: %r" % (listen_type, payload)) data = {"listen_type": listen_type, "payload": payload} body = json.dumps(data).encode("utf-8") session = Soup.Session.new() uri = "https://%s%s" % (HOST_NAME, PATH_SUBMIT) msg = Soup.Message.new("POST", uri) msg.set_request("application/json", Soup.MemoryUse.STATIC, body) msg.request_headers.append("Authorization", "Token %s" % self.user_token) try: status = session.send_message(msg) response_headers = msg.get_property("response-headers") self.__handle_ratelimit(response_headers) # Too Many Requests if status == 429 and retry < 5: self.__request(listen_type, payload, retry + 1) except Exception as e: print("ListenBrainz::__submit():", e)
def _get_deezer_artist_artwork_uri(self, artist, cancellable=None): """ Return deezer artist information @param artist as str @param cancellable as Gio.Cancellable @return uri as str @tread safe """ if not get_network_available("DEEZER"): return None try: artist_formated = GLib.uri_escape_string(artist, None, True).replace(" ", "+") uri = "https://api.deezer.com/search/artist/?" +\ "q=%s&output=json&index=0&limit=1&" % artist_formated (status, data) = App().task_helper.load_uri_content_sync(uri, cancellable) if status: uri = None decode = json.loads(data.decode("utf-8")) uri = decode["data"][0]["picture_xl"] return uri except Exception as e: Logger.debug( "ArtDownloader::_get_deezer_artist_artwork_uri(): %s" % e) return None
def save(self): """ Saves the metadata db to the MTP device """ try: Logger.debug("MtpSyncDb::__save()") jsondb = json.dumps({ "version": 1, "encoder": self.__encoder, "normalize": self.__normalize, "tracks_metadata": [{ "uri": x, "metadata": y } for x, y in sorted(self.__metadata.items())] }) dbfile = Gio.File.new_for_uri(self.__db_uri) (tmpfile, stream) = Gio.File.new_tmp() stream.get_output_stream().write_all(jsondb.encode("utf-8")) tmpfile.copy(dbfile, Gio.FileCopyFlags.OVERWRITE, None, None) stream.close() except Exception as e: Logger.error("MtpSyncDb::__save(): %s", e)
def __now_playing(self, artist, album, title, duration, mb_track_id, first=True): """ Now playing track @param artist as str @param title as str @param album as str @param duration as int @param first is internal @thread safe """ if App().settings.get_value("disable-scrobbling"): return try: self.update_now_playing(artist=artist, album=album, title=title, duration=duration, mbid=mb_track_id) Logger.debug("LastFM::__now_playing(): %s, %s, %s, %s, %s" % (artist, album, title, duration, mb_track_id)) except WSError: pass except Exception as e: Logger.error("LastFM::__now_playing(): %s" % e) # now playing sometimes fails if first: self.__connect() self.__now_playing(artist, album, title, duration, mb_track_id, False)
def __scan_to_handle(self, uri): """ Check if file has to be handle by scanner @param f as Gio.File @return bool """ try: file_type = get_file_type(uri) # Get file type using Gio (slower) if file_type == FileType.UNKNOWN: f = Gio.File.new_for_uri(uri) info = f.query_info(FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, Gio.FileQueryInfoFlags.NONE) if is_pls(info): file_type = FileType.PLS elif is_audio(info): file_type = FileType.AUDIO if file_type == FileType.PLS: Logger.debug("Importing playlist %s" % uri) if App().settings.get_value("import-playlists"): App().playlists.import_tracks(uri) elif file_type == FileType.AUDIO: Logger.debug("Importing audio %s" % uri) return True except Exception as e: Logger.error("CollectionScanner::__scan_to_handle(): %s" % e) return False
def __lazy_loading(self): """ Load the view in a lazy way """ widget = None if self.__priority_queue: widget = self.__priority_queue.pop(0) self.__lazy_queue.remove(widget) elif self.__lazy_queue: widget = self.__lazy_queue.pop(0) if widget is not None: widget.connect("populated", self._on_populated) widget.populate() else: self.__loading_state = LoadingState.FINISHED emit_signal(self, "populated") # Apply filtering if App().window.container.type_ahead.get_reveal_child(): text = App().window.container.type_ahead.entry.get_text() if text: self.search_for_child(text) else: GLib.idle_add( App().window.container.type_ahead.entry.grab_focus) Logger.debug("LazyLoadingView::lazy_loading(): %s", time() - self.__start_time)
def get_compilation(self, tags): """ Return True if album is a compilation @param tags as Gst.TagList @return bool """ if tags is None: return False size = tags.get_tag_size("private-id3v2-frame") for i in range(0, size): (exists, sample) = tags.get_sample_index("private-id3v2-frame", i) if not exists: continue (exists, m) = sample.get_buffer().map(Gst.MapFlags.READ) if not exists: continue frame = FrameTextTag(m.data) if frame.key == "TCMP": string = frame.string if not string: Logger.debug(tags.to_string()) return string and string[-1] == "1" size = tags.get_tag_size("extended-comment") for i in range(0, size): (exists, sample) = tags.get_string_index("extended-comment", i) if not exists or not sample.startswith("COMPILATION="): continue return sample[12] return False
def __connect(self, full_sync=False): """ Connect service @param full_sync as bool @thread safe """ if not get_network_available("LASTFM"): return try: self.session_key = "" if self.is_goa: auth = self.__goa.oauth2_based self.api_key = auth.props.client_id self.api_secret = auth.props.client_secret self.session_key = auth.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: App().task_helper.run(self.__populate_loved_tracks) track = App().player.current_track self.playing_now(track) except Exception as e: Logger.debug("LastFM::__connect(): %s" % e)
def __wait_for_ratelimit(self): """ Sleep to respect service X-RateLimit """ now = time.time() if self.__next_request_time > now: delay = self.__next_request_time - now Logger.debug("ListenBrainz rate limit applies, delay %d" % delay) time.sleep(delay)
def decodeUnicode(bites, encoding): codec = id3EncodingToString(encoding) Logger.debug("Unicode encoding: %s" % codec) if (codec.startswith("utf_16") and len(bites) % 2 != 0 and bites[-1:] == b"\x00"): # Catch and fix bad utf16 data, it is everywhere. Logger.warning("Fixing utf16 data with extra zero bytes") bites = bites[:-1] return bites.decode(codec).rstrip("\x00")
def __on_account_added(self, client, proxy): """ Update proxy and emit account-switched @param client as Goa.Client @param proxy as Goa.Object """ Logger.debug("GOA account added") if self._proxy is None and self.__account_matches_provider(proxy): self._proxy = proxy emit_signal(self, "account-switched")
def __on_account_changed(self, client, proxy): """ Reset current account settings @param client as Goa.Client @param proxy as Goa.Object """ Logger.debug("GOA account changed") if self._proxy == proxy: self._account = None self._oauth2_based = None
def __on_account_removed(self, client, proxy): """ Try finding a new account and emit account-switched @param client as Goa.Client @param proxy as Goa.Object """ Logger.debug("GOA account removed") if self._proxy == proxy: self.__find_account() emit_signal(self, "account-switched")
def __account_matches_provider(self, proxy): """ True if current account match proxy account provider @param proxy as Goa.Object @return bool """ account = proxy.get_account() Logger.debug("GOA __account_matches_provider: %s = %s ?" % (account.props.provider_name, self._provider_name)) return account.props.provider_name == self._provider_name
def wrapper(*args, **kwargs): start_time = time.perf_counter() ret = f(*args, **kwargs) elapsed_time = time.perf_counter() - start_time Logger.debug( "%s::%s: execution time %d:%f" % (f.__module__, f.__name__, elapsed_time / 60, elapsed_time % 60)) return ret
def __on_get_secret(self, source, result): """ Store secret proxy @param source as GObject.Object @param result as Gio.AsyncResult """ try: self.__secret = Secret.Service.get_finish(result) except Exception as e: self.__secret = -1 Logger.debug("PasswordsHelper::__on_get_secret(): %s" % e)
def _on_bus_eos(self, bus, message): """ On end of stream, stop playback go next otherwise """ Logger.debug("Player::__on_bus_eos(): %s" % self._current_track.uri) if self._playbin.get_bus() == bus: self._next_context = NextContext.NONE if self._next_track.id is not None: self._load_track(self._next_track) self.emit("current-changed")
def __on_current_changed(self, player): """ Update toolbar @param player as Player """ Logger.debug("Toolbar::_on_current_changed()") self.__toolbar_playback.on_current_changed(player) self.__toolbar_info.on_current_changed(player) if App().player.current_track.id is None: self.__toolbar_title.hide() elif not App().window.miniplayer: self.__toolbar_title.show() self.__toolbar_title.on_current_changed(player)
def __handle_ratelimit(self, response): """ Set rate limit from response @param response as Soup.MessageHeaders """ remaining = response.get("X-RateLimit-Remaining") reset_in = response.get("X-RateLimit-Reset-In") if remaining is None or reset_in is None: return Logger.debug("ListenBrainz X-RateLimit-Remaining: %s" % remaining) Logger.debug("ListenBrainz X-RateLimit-Reset-In: %s" % reset_in) if (int(remaining) == 0): self.__next_request_time = time.time() + int(reset_in)