def set_next(self): """ Play next track """ if self._current_track.id is None: return if self._current_track.id == self.__stop_after_track_id: self._next_track = Track() return try: next_track = QueuePlayer.next(self) if next_track.id is None: # Diverge current track to restore playback from queue diverge_current_track = None if self._queue_current_track is not None: diverge_current_track = self._current_track self._current_track = self._queue_current_track if App().settings.get_value("shuffle") or self.is_party: next_track = ShufflePlayer.next(self) else: next_track = LinearPlayer.next(self) # Restore current track if diverge_current_track is not None: self._current_track = diverge_current_track self._queue_current_track = None self._next_track = next_track emit_signal(self, "next-changed") except Exception as e: Logger.error("Player::set_next(): %s" % e)
def clear_albums(self): """ Clear all albums """ self._albums = [] emit_signal(self, "playback-setted", []) self.update_next_prev()
def show_menu(self, widget): """ Show menu widget @param widget as Gtk.Widget """ def on_hidden(widget, hide, view): if hide: self._stack.set_transition_type( Gtk.StackTransitionType.SLIDE_UP) self.go_back() self._stack.set_transition_type( Gtk.StackTransitionType.CROSSFADE) App().enable_special_shortcuts(True) if App().lookup_action("reload").get_state(): self.reload_view() if self.can_go_back: emit_signal(self, "can-go-back-changed", True) from lollypop.view_menu import MenuView view = MenuView(widget) view.show() widget.connect("hidden", on_hidden, view) self._stack.add(view) self._stack.set_transition_type(Gtk.StackTransitionType.SLIDE_DOWN) self._stack.set_visible_child(view) self._stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) emit_signal(self, "can-go-back-changed", False) App().enable_special_shortcuts(False)
def get(self, search, storage_type, cancellable): """ Get albums for search We need a thread because we are going to populate DB @param search as str @param storage_type as StorageType @param cancellable as Gio.Cancellable """ if not get_network_available("MUSICBRAINZ"): emit_signal(self, "finished") return try: uri = "http://musicbrainz.org/ws/2/release/?fmt=json&query=%s" %\ search (status, data) = App().task_helper.load_uri_content_sync( uri, cancellable) if status: decode = json.loads(data.decode("utf-8")) for release in decode["releases"]: payload = self.lollypop_album_payload(release) self.save_album_payload_to_db(payload, storage_type, True, cancellable) except Exception as e: Logger.warning("MusicBrainzSearch::get(): %s", e) if not cancellable.is_cancelled(): emit_signal(self, "finished")
def __on_load_startpage_content(self, uri, loaded, content): """ Extract uris from content @param uri as str @param loaded as bool @param content as bytes """ import re def search_in_data(lines, found_uris=[]): if lines: line = lines.pop(0) # Do not call findall if nothing to find if line.find("oiu=") != -1: res = re.findall(r'.*oiu=([^&]*).*', line) for data in res: uri = GLib.uri_unescape_string(data, "") if uri in found_uris or uri is None: continue found_uris.append(uri) GLib.idle_add(search_in_data, lines, found_uris) else: results = [(uri, "Startpage") for uri in found_uris] emit_signal(self, "uri-artwork-found", results) try: if not loaded: emit_signal(self, "uri-artwork-found", None) return lines = content.decode("utf-8").splitlines() search_in_data(lines) except Exception as e: emit_signal(self, "uri-artwork-found", None) Logger.error("DownloaderArt::__on_load_startpage_content(): %s" % e)
def load_similars(self, artist_ids, storage_type, cancellable): """ Load similar artists for artist ids @param artist_ids as int @param storage_type as StorageType @param cancellable as Gio.Cancellable """ names = [App().artists.get_name(artist_id) for artist_id in artist_ids] spotify_ids = self.get_similar_artist_ids(names, cancellable) track_ids = [] for spotify_id in spotify_ids: spotify_ids = self.get_artist_top_tracks(spotify_id, cancellable) # We want some randomizing so keep tracks for later usage spotify_id = choice(spotify_ids) track_ids += spotify_ids payload = self.get_track_payload(spotify_id, cancellable) lollypop_payload = self.lollypop_album_payload(payload["album"]) item = self.save_album_payload_to_db(lollypop_payload, storage_type, True, cancellable) lollypop_payload = self.lollypop_track_payload(payload) self.save_track_payload_to_db(lollypop_payload, item, storage_type, True, cancellable) shuffle(track_ids) for spotify_id in track_ids: payload = self.get_track_payload(spotify_id, cancellable) lollypop_payload = self.lollypop_album_payload(payload["album"]) item = self.save_album_payload_to_db(lollypop_payload, storage_type, True, cancellable) lollypop_payload = self.lollypop_track_payload(payload) self.save_track_payload_to_db(lollypop_payload, item, storage_type, True, cancellable) emit_signal(self, "finished")
def __on_uri_content_loaded(self, helper, uri): """ Emit loaded signal with content @param helper as BaseWebHelper @param uri as str """ emit_signal(self, "loaded", uri)
def populate(self): """ Populate tracks lazy """ self._init() if self.__discs_to_load: disc = self.__discs_to_load.pop(0) disc_number = disc.number tracks = disc.tracks items = [] if self.view_type & ViewType.SINGLE_COLUMN: items.append((self._tracks_widget_left[0], tracks)) else: mid_tracks = int(0.5 + len(tracks) / 2) items.append((self._tracks_widget_left[disc_number], tracks[:mid_tracks])) items.append((self._tracks_widget_right[disc_number], tracks[mid_tracks:])) self.__load_disc(items, disc_number) else: self.__populated = True emit_signal(self, "populated") if not self.children: label = Gtk.Label.new(_("All tracks skipped")) label.show() self._responsive_widget.insert_row(0) self._responsive_widget.attach(label, 0, 0, 2, 1)
def set_party(self, party): """ Set party mode on if party is True Play a new random track if not already playing @param party as bool """ def start_party(*ignore): if self._albums: # Start a new song if not playing if self._current_track.id is None: track = self.__get_tracks_random() self.load(track) elif not self.is_playing: self.play() emit_signal(self, "loading-changed", False, Track()) if party == self._is_party: return self._is_party = party if party: App().task_helper.run(self.set_party_ids, callback=(start_party, )) else: # We want current album to continue playback self._albums = [self._current_track.album] emit_signal(self, "playback-setted", []) emit_signal(self, "playback-added", self._current_track.album) if self._current_track.id is not None: self.set_next() self.set_prev()
def _on_activated(self, widget, track): """ Handle playback if album or pass signal @param widget as TracksWidget @param track as Track """ if self.view_type & (ViewType.ALBUM | ViewType.ARTIST): tracks = [] for child in self.children: if child.track.loved != -1 or track.id == child.track.id: tracks.append(child.track) child.set_state_flags(Gtk.StateFlags.NORMAL, True) # Do not update album list if in party or album already available playback_track = App().player.track_in_playback(track) if playback_track is not None: App().player.load(playback_track) elif not App().player.is_party: album = Album(track.album.id, [], []) album.set_tracks(tracks) if not App().settings.get_value("append-albums"): App().player.clear_albums() App().player.add_album(album) App().player.load(album.get_track(track.id)) else: App().player.load(track) else: emit_signal(self, "activated", track)
def load_similars(self, artist_ids, storage_type, cancellable): """ Load similar artists for artist ids @param artist_ids as int @param storage_type as StorageType @param cancellable as Gio.Cancellable """ for artist_id in artist_ids: artist_name = App().artists.get_name(artist_id) deezer_id = self.get_artist_id(artist_name, cancellable) try: uri = "https://api.deezer.com/artist/%s/radio" % deezer_id (status, data) = App().task_helper.load_uri_content_sync( uri, cancellable) if status: decode = json.loads(data.decode("utf-8")) for payload in decode["data"]: track_payload = self.get_track_payload( payload["id"], cancellable) album_payload = self.get_album_payload( payload["album"]["id"], cancellable) lollypop_payload = self.lollypop_album_payload( album_payload) item = self.save_album_payload_to_db( lollypop_payload, storage_type, True, cancellable) lollypop_payload = self.lollypop_track_payload( track_payload) self.save_track_payload_to_db(lollypop_payload, item, storage_type, True, cancellable) except Exception as e: Logger.error("DeezerSimilars::load_similars(): %s", e) emit_signal(self, "finished")
def set_artwork(self): """ set_artwork widget """ if self.__artwork is None: return if self.__rowid == Type.SEPARATOR: pass elif self.__mask & SelectionListMask.ARTISTS and\ self.__rowid >= 0 and\ App().settings.get_value("artist-artwork"): App().art_helper.set_artist_artwork( self.__name, ArtSize.SMALL, ArtSize.SMALL, self.get_scale_factor(), ArtBehaviour.ROUNDED | ArtBehaviour.CROP_SQUARE | ArtBehaviour.CACHE, self.__on_artist_artwork) self.__artwork.show() elif self.__rowid < 0: icon_name = get_icon_name(self.__rowid) self.__artwork.set_from_icon_name(icon_name, Gtk.IconSize.INVALID) self.__artwork.set_pixel_size(20) self.__artwork.show() emit_signal(self, "populated") else: self.__artwork.hide() emit_signal(self, "populated")
def go_home(self): """ Go back to first page """ self.__widget.set_visible_child(self.sidebar) self._stack.clear() emit_signal(self, "can-go-back-changed", False)
def get(self, search, storage_type, cancellable): """ Get albums for search We need a thread because we are going to populate DB @param search as str @param storage_type as StorageType @param cancellable as Gio.Cancellable """ if not get_network_available("DEEZER"): emit_signal(self, "finished") return try: uri = "https://api.deezer.com/search/album?q=%s" %\ search (status, data) = App().task_helper.load_uri_content_sync( uri, cancellable) if status: decode = json.loads(data.decode("utf-8")) for albums in decode["data"]: payload = self.lollypop_album_payload(albums) self.save_album_payload_to_db(payload, storage_type, True, cancellable) except Exception as e: Logger.warning("DeezerSearch::get(): %s", e) if not cancellable.is_cancelled(): emit_signal(self, "finished")
def __on_closed(self, popover): """ Destroy self """ emit_signal(self, "hidden", True) if self.__auto_destroy: GLib.idle_add(self.destroy)
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 _on_album_artwork(self, surface): """ Set album artwork @param surface as str """ emit_signal(self, "populated") CoverWidgetBase._on_album_artwork(self, surface)
def __play_radio_common(self): """ Emit signal and reset cancellable """ emit_signal(self, "loading-changed", True, Track()) self.__radio_cancellable.cancel() self.__radio_cancellable = Gio.Cancellable()
def album_artwork_update(self, album_id): """ Announce album cover update @param album_id as int """ if album_id is not None: emit_signal(self, "album-artwork-changed", album_id)
def save_album_payload_to_db(self, payload, storage_type, notify, cancellable): """ Save album to DB @param payload as {} @param storage_type as StorageType @param notify as bool @param cancellable as Gio.Cancellable @return CollectionItem/None """ lp_album_id = get_lollypop_album_id(payload["name"], payload["artists"]) album_id = App().albums.get_id_for_lp_album_id(lp_album_id) if album_id >= 0: album = Album(album_id) if notify: emit_signal(self, "match-album", album_id, storage_type) return album.collection_item item = self.__save_album(payload, storage_type) album = Album(item.album_id) if notify: self.save_artwork(album, payload["artwork-uri"], cancellable) emit_signal(self, "match-album", album.id, storage_type) return item
def populate(self): """ Populate widget """ if self.__rowid == Type.SEPARATOR: separator = Gtk.Separator.new(Gtk.Orientation.HORIZONTAL) separator.show() self.add(separator) self.set_sensitive(False) emit_signal(self, "populated") else: self.__grid = Gtk.Grid() self.__grid.set_column_spacing(7) self.__grid.show() self.__artwork = Gtk.Image.new() self.__grid.add(self.__artwork) self.__label = Gtk.Label.new() self.__label.set_markup(GLib.markup_escape_text(self.__name)) self.__label.set_property("has-tooltip", True) self.__label.connect("query-tooltip", on_query_tooltip) self.__label.set_xalign(0) self.__grid.add(self.__label) if self.__mask & SelectionListMask.ARTISTS: self.__grid.set_margin_end(20) self.add(self.__grid) self.set_artwork() self.set_mask()
def _on_primary_press_gesture(self, x, y, event): """ Activate current row @param x as int @param y as int @param event as Gdk.Event """ row = self.get_row_at_y(y) if row is None: return if event.state & Gdk.ModifierType.CONTROL_MASK and\ self.__view_type & ViewType.DND: self.set_selection_mode(Gtk.SelectionMode.MULTIPLE) elif event.state & Gdk.ModifierType.SHIFT_MASK: if self.__view_type & ViewType.DND: self.set_selection_mode(Gtk.SelectionMode.MULTIPLE) do_shift_selection(self, row) elif App().player.is_in_queue(row.track.id): App().player.remove_from_queue(row.track.id, True) else: App().player.append_to_queue(row.track.id, True) elif event.state & Gdk.ModifierType.MOD1_MASK: self.set_selection_mode(Gtk.SelectionMode.NONE) App().player.clear_albums() App().player.load(row.track) else: self.set_selection_mode(Gtk.SelectionMode.NONE) emit_signal(self, "activated", row.track)
def __on_finished(self, search): """ Emit finished signals if all search are finished @param search as Search provider """ self.__search_count -= 1 emit_signal(self, "finished", self.__search_count == 0)
def set_rate(self, rate): """ Set rate @param rate as int between -1 and 5 """ self.db.set_rate(self.id, rate) self.reset("rate") emit_signal(App().player, "rate-changed", self.id, rate)
def __remove_from_queue(self, action, variant): """ Delete track id from queue @param Gio.SimpleAction @param GLib.Variant """ App().player.remove_from_queue(self.__track.id, False) emit_signal(App().player, "queue-changed")
def __append_to_queue(self, action, variant): """ Append track to queue @param Gio.SimpleAction @param GLib.Variant """ App().player.append_to_queue(self.__track.id, False) emit_signal(App().player, "queue-changed")
def __on_scroll(self, event_controller, x, y): """ Pass scroll @param event_controller as Gtk.EventControllerScroll @param x as int @param y as int """ emit_signal(self, "scroll", x, y)
def __on_volume_changed(self, playbin, sink): """ Emit volume-changed signal @param playbin as Gst.Bin @param sink as Gst.Sink """ App().settings.set_value("volume-rate", GLib.Variant("d", self.volume)) emit_signal(self, "volume-changed")
def __on_clear_button_clicked(self, button): """ Clear albums @param button as Gtk.Button """ self.__view.clear() self.__view.populate() emit_signal(App().player, "status-changed")
def __on_tracks_view_track_removed(self, view, row): """ Remove row @param view as TracksView @param row as TrackRow """ row.destroy() emit_signal(self, "track-removed", row.track)