def prev(self): """ Prev track base on.current_track context @return track as Track """ # If no album available, repeat current track if not self._albums: return self._current_track track = Track() if self._current_track.album.id in self._context.genre_ids and \ self._albums: genre_ids = self._context.genre_ids[self._current_track.album.id] artist_ids = self._context.artist_ids[self._current_track.album.id] album = Album(self._current_track.album.id, genre_ids, artist_ids) if self._current_track.id in album.track_ids: new_track_position = album.track_ids.index( self._current_track.id) - 1 # Previous album if new_track_position < 0: try: pos = self._albums.index(album.id) if pos - 1 < 0: # we are on last album, go to first pos = len(self._albums) - 1 else: pos -= 1 except: pos = 0 # Happens if current album has been removed genre_ids = self._context.genre_ids[self._albums[pos]] track = Album(self._albums[pos], genre_ids, artist_ids).tracks[-1] # Previous track else: track = album.tracks[new_track_position] return track
def _on_add_clicked(self, widget): """ Add artist albums """ try: albums = Lp().albums.get_ids(self._artist_ids, self._genre_ids) if self.__add_button.get_image().get_icon_name( )[0] == 'list-add-symbolic': for album_id in albums: album = Album(album_id) # If playing and no albums, play it if not Lp().player.has_album(album): if Lp().player.is_playing() and\ not Lp().player.get_albums(): Lp().player.play_album(album) else: Lp().player.add_album(album) else: for album_id in albums: album = Album(album_id) if Lp().player.has_album(album): Lp().player.remove_album(album) self.__set_add_icon() except: pass # Artist not available anymore for this context
def _add_rows(self, results): """ Add a rows recursively @param results as array of SearchObject """ if results: result = results.pop(0) if not self._exists(result): search_row = SearchRow() if result.is_track: obj = Track(result.id) album_id = obj.album_id else: obj = Album(result.id) album_id = obj.id search_row.set_id(result.id, result.is_track) search_row.set_artist_ids(result.artist_ids) search_row.set_cover(Lp().art.get_album_artwork( Album(album_id), ArtSize.MEDIUM * self.get_scale_factor())) if result.is_track: self._view.prepend(search_row) else: self._view.add(search_row) if self._stop_thread: self._in_thread = False self._stop_thread = False else: GLib.idle_add(self._add_rows, results) else: self._in_thread = False self._stop_thread = False
def prev(self): """ Prev track base on.current_track context @return track as Track """ # If no album available, repeat current track if not self._albums: return self.current_track track = Track() if self._albums is not None: album = Album(self.current_track.album.id, self.context.genre_id) if self.current_track.id in album.tracks_ids: new_track_position = album.tracks_ids.index( self.current_track.id) - 1 # Previous album if new_track_position < 0: pos = self._albums.index(album.id) if pos - 1 < 0: # we are on last album, go to first pos = len(self._albums) - 1 else: pos -= 1 track = Album(self._albums[pos], self.context.genre_id).tracks[-1] # Previous track else: track = album.tracks[new_track_position] return track
def __play_new_tracks(self, uris): """ Play new tracks @param uri as [str] """ # First get tracks tracks = [] for uri in uris: track_id = App().tracks.get_id_by_uri(uri) tracks.append(Track(track_id)) # Then get album ids album_ids = {} for track in tracks: if track.album.id in album_ids.keys(): album_ids[track.album.id].append(track) else: album_ids[track.album.id] = [track] # Create albums with tracks play = True for album_id in album_ids.keys(): album = Album(album_id) album.set_tracks(album_ids[album_id]) if play: App().player.play_album(album) else: App().player.add_album(album)
def next(self): """ Next track based on.current_track context @return track as Track """ # If no album available, repeat current track if not self._albums: return self.current_track track = Track() if self._albums is not None: album = Album(self.current_track.album.id, self.context.genre_id) if self.current_track.id in album.tracks_ids: new_track_position = album.tracks_ids.index( self.current_track.id) + 1 # next album if new_track_position >= len(album.tracks) or\ self.context.next == NextContext.START_NEW_ALBUM: if self.context.next == NextContext.START_NEW_ALBUM: self.context.next = NextContext.NONE pos = self._albums.index(album.id) # we are on last album, go to first if pos + 1 >= len(self._albums): pos = 0 else: pos += 1 track = Album(self._albums[pos], self.context.genre_id).tracks[0] # next track else: track = album.tracks[new_track_position] return track
def _on_play_clicked(self, widget): """ Play artist albums """ try: if Lp().player.is_party: Lp().player.set_party(False) album_ids = Lp().albums.get_ids(self._artist_ids, self._genre_ids) if album_ids: track = None if Lp().settings.get_enum("shuffle") == Shuffle.TRACKS: album_id = choice(album_ids) album_tracks = Album(album_id).tracks if album_tracks: track = choice(album_tracks) else: album_id = choice(album_ids) album_tracks = Album(album_id).tracks if album_tracks: track = album_tracks[0] if track is not None: Lp().player.load(track) Lp().player.set_albums(track.id, self._artist_ids, self._genre_ids) self.__set_add_icon() except: pass # Artist not available anymore for this context
def __init(self): """ Init row """ artists = [] if self.__item.is_track: obj = Track(self.__item.id) album_id = obj.album_id else: obj = Album(self.__item.id) album_id = obj.id if self.__item.id is None: if self.__item.is_track: self.__name.set_text("♫ " + self.__item.name) else: self.__name.set_text(self.__item.name) artists = self.__item.artists surface = Lp().art.get_default_icon('emblem-music-symbolic', ArtSize.MEDIUM, self.get_scale_factor()) else: if self.__item.is_track: self.__name.set_text("♫ " + Track(self.__item.id).name) else: self.__name.set_text(Album(self.__item.id).name) for artist_id in self.__item.artist_ids: artists.append(Lp().artists.get_name(artist_id)) surface = Lp().art.get_album_artwork(Album(album_id), ArtSize.MEDIUM, self.get_scale_factor()) self.__cover.set_from_surface(surface) del surface self.__artist.set_text(", ".join(artists))
def __on_disc_button_press_event(self, button, event, disc): """ Add disc to playback @param button as Gtk.Button @param event as Gdk.ButtonEvent @param disc as Disc """ album = Album(disc.album.id) album.set_tracks(disc.tracks) App().player.play_album(album)
def __get(self, search_items, cancellable): """ Get track for name @param search_items as [str] @param cancellable as Gio.Cancellable @return items as [(int, Album, bool)] """ album_ids = self.__search_albums(search_items, cancellable) track_ids = self.__search_tracks(search_items, cancellable) artist_ids = self.__search_artists(search_items, cancellable) # Get tracks/albums for artists for artist_id in set(artist_ids): if cancellable.is_cancelled(): return [] for album_id in App().albums.get_ids([artist_id], []): if album_id not in album_ids: album_ids.append(album_id) for track_id in App().tracks.get_ids_by_artist(artist_id): if track_id not in track_ids: track_ids.append(track_id) albums = [] # Create albums for tracks album_tracks = {} for track_id in track_ids: track = Track(track_id) if track.album.id in album_ids: continue score = self.__calculate_score(track.name, search_items) for artist in track.artists: score += self.__calculate_score(artist, search_items) # Get a new album album = track.album if album.id in album_tracks.keys(): (album, tracks, score) = album_tracks[album.id] tracks.append(track) album_tracks[track.album.id] = (album, tracks, score) else: album_tracks[track.album.id] = (album, [track], score) # Create albums for album results for album_id in album_ids: album = Album(album_id) score = self.__calculate_score(album.name, search_items) for artist in album.artists: score += self.__calculate_score(artist, search_items) albums.append((score, album, False)) # Merge albums from track results for key in album_tracks.keys(): (album, tracks, score) = album_tracks[key] album.set_tracks(tracks) albums.append((score, album, True)) albums.sort(key=lambda tup: tup[0], reverse=True) return albums
def _add_rows(self, results): """ Add a rows recursively @param results as array of SearchObject """ if results: result = results.pop(0) if not self._exists(result): search_row = SearchRow(self._parent) if result.count != -1: result.title += " (%s)" % result.count search_row.set_text(result.artist, result.title) search_row.set_cover( Lp().art.get_album_artwork( Album(result.album_id), ArtSize.MEDIUM*self.get_scale_factor())) search_row.id = result.id search_row.is_track = result.is_track self._view.add(search_row) if self._stop_thread: self._in_thread = False self._stop_thread = False else: GLib.idle_add(self._add_rows, results) else: self._in_thread = False self._stop_thread = False
def __init__(self, object): """ Init edit menu @param object as Album/Track """ # Ignore genre_ids/artist_ids if isinstance(object, Album): obj = Album(object.id) else: obj = Track(object.id) BaseMenu.__init__(self, obj) if self._object.is_web: self.__set_remove_action() else: # Check portal for tag editor can_launch = False try: bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) proxy = Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None, 'org.gnome.Lollypop.Portal', '/org/gnome/LollypopPortal', 'org.gnome.Lollypop.Portal', None) can_launch = proxy.call_sync('CanLaunchTagEditor', None, Gio.DBusCallFlags.NO_AUTO_START, 500, None)[0] except Exception as e: print("EditMenu::__init__():", e) if can_launch: self.__set_edit_actions()
def load(): items = [] is_compilation = artist_ids and artist_ids[0] == Type.COMPILATIONS if genre_ids and genre_ids[0] == Type.ALL: if is_compilation or\ App().settings.get_value( "show-compilations-in-album-view"): items = App().albums.get_compilation_ids([]) if not is_compilation: items += App().albums.get_ids([], []) elif genre_ids and genre_ids[0] == Type.POPULARS: items = App().albums.get_rated() count = 100 - len(items) for album in App().albums.get_populars(count): if album not in items: items.append(album) elif genre_ids and genre_ids[0] == Type.LOVED: items = App().albums.get_loved_albums() elif genre_ids and genre_ids[0] == Type.RECENTS: items = App().albums.get_recents() elif genre_ids and genre_ids[0] == Type.NEVER: items = App().albums.get_never_listened_to() elif genre_ids and genre_ids[0] == Type.RANDOMS: items = App().albums.get_randoms() else: if is_compilation or\ App().settings.get_value( "show-compilations-in-album-view"): items = App().albums.get_compilation_ids(genre_ids) if not is_compilation: items += App().albums.get_ids([], genre_ids) return [ Album(album_id, genre_ids, artist_ids) for album_id in items ]
def __save_album_thread(self, item, persistent): """ Save item into collection as album @param item as SearchItem @param persistent as DbPersistent """ nb_items = len(item.subitems) # Should not happen but happen :-/ if nb_items == 0: return start = 0 album_artist = item.subitems[0].artists[0] album_id = None for track_item in item.subitems: (album_id, track_id) = self.__save_track(track_item, persistent, album_artist) if track_id is None: continue # Download cover if start == 0: t = Thread(target=self.__save_cover, args=(item, album_id)) t.daemon = True t.start() start += 1 GLib.idle_add(Lp().window.progress.set_fraction, start / nb_items, self) GLib.idle_add(Lp().window.progress.set_fraction, 1.0, self) # Play if needed if album_id is not None and persistent == DbPersistent.NONE: Lp().player.clear_albums() album = Album(album_id) GLib.idle_add(Lp().player.load, album.tracks[0]) GLib.idle_add(Lp().player.add_album, album) if Lp().settings.get_value('artist-artwork'): Lp().art.cache_artists_info()
def _on_tracks_populated(self, disc_number): """ Emit populated signal @param disc_number as int """ if TracksView.get_populated(self): from lollypop.view_albums_box import AlbumsBoxView for artist_id in self.__artist_ids: if artist_id == Type.COMPILATIONS: album_ids = App().albums.get_compilation_ids( self.__genre_ids) else: album_ids = App().albums.get_ids([artist_id], []) if self._album.id in album_ids: album_ids.remove(self._album.id) if not album_ids: continue artist = GLib.markup_escape_text( App().artists.get_name(artist_id)) label = Gtk.Label.new() label.set_markup('''<span size="large" alpha="40000" weight="bold">%s %s</span>''' % (_("Others albums from"), artist)) label.set_property("halign", Gtk.Align.START) label.set_margin_top(40) label.show() self.__grid.add(label) self.__others_box = AlbumsBoxView([], [artist_id], ViewType.SMALL) self.__others_box.show() self.__grid.add(self.__others_box) self.__others_box.populate([Album(id) for id in album_ids]) else: TracksView.populate(self)
def _on_add_clicked(self, widget): """ Add artist albums """ try: if App().settings.get_value("show-performers"): album_ids = App().tracks.get_album_ids(self._artist_ids, self._genre_ids) else: album_ids = App().albums.get_ids(self._artist_ids, self._genre_ids) icon_name = self._add_button.get_image().get_icon_name()[0] add = icon_name == "list-add-symbolic" for album_id in album_ids: if add and album_id not in App().player.album_ids: App().player.add_album( Album(album_id, self._genre_ids, self._artist_ids)) elif not add and album_id in App().player.album_ids: App().player.remove_album_by_id(album_id) if len(App().player.album_ids) == 0: App().player.stop() elif App().player.current_track.album.id\ not in App().player.album_ids: App().player.skip_album() self._update_icon(not add) except Exception as e: Logger.error("ArtistView::_on_add_clicked: %s" % e)
def __use_album_artwork(self, width, height): """ Set artwork with album artwork @param width as int @param height as int """ # Select an album if self.__album_id is None: if self.__album_ids is None: if App().settings.get_value("show-performers"): self.__album_ids = App().tracks.get_album_ids( [self.__artist_id], []) else: self.__album_ids = App().albums.get_ids( [self.__artist_id], []) if self.__album_ids: self.__album_id = self.__album_ids.pop(0) # Get artwork if self.__album_id is not None: album = Album(self.__album_id) App().art_helper.set_album_artwork( album, # +100 to prevent resize lag width + 100, height, self.__artwork.get_scale_factor(), ArtBehaviour.BLUR_HARD | ArtBehaviour.DARKER, self.__on_album_artwork)
def __on_save_artwork_tags(self, source, result, album_id): """ Save image to tags @param source as GObject.Object @param result as Gio.AsyncResult @param album_id as int """ try: can_set_cover = source.call_finish(result)[0] except: can_set_cover = False if can_set_cover: dbus_helper = DBusHelper() for uri in Lp().albums.get_track_uris(album_id, [], []): path = GLib.filename_from_uri(uri)[0] dbus_helper.call( "SetCover", GLib.Variant("(ss)", (path, "%s/lollypop_cover_tags.jpg" % self._CACHE_PATH)), None, None) self.clean_album_cache(Album(album_id)) # FIXME Should be better to send all covers at once and listen # to as signal but it works like this GLib.timeout_add(2000, self.album_artwork_update, album_id) else: # Lollypop-portal or kid3-cli removed? Lp().settings.set_value("save-to-tags", GLib.Variant("b", False))
def __cache_albums_art(self): """ Cache albums artwork (from queue) @thread safe """ self.__in_albums_download = True try: while self.__albums_queue: album_id = self.__albums_queue.pop() album = App().albums.get_name(album_id) artist_ids = App().albums.get_artist_ids(album_id) is_compilation = artist_ids and\ artist_ids[0] == Type.COMPILATIONS if is_compilation: artist = "" else: artist = ", ".join(App().albums.get_artists(album_id)) for (api, a_helper, helper, b_helper) in self._WEBSERVICES: if helper is None: continue method = getattr(self, helper) uri = method(artist, album) if uri is not None: (status, data) = App().task_helper.load_uri_content_sync( uri, None) if status: self.__albums_history.append(album_id) App().art.save_album_artwork(data, Album(album_id)) break except Exception as e: Logger.error("ArtDownloader::__cache_albums_art: %s" % e) self.__albums_history.append(album_id) self.__in_albums_download = False
def set_object_id(self, object_id): """ Store current object id and object @param object id as int """ Row.set_object_id(self, object_id) self._object = Album(self._object_id)
def _set_artwork(self): """ Set artist artwork """ if self._artwork is None: return def set_icon_name(): icon_name = get_icon_name(self._data) or "avatar-default-symbolic" self._artwork.set_from_icon_name(icon_name, Gtk.IconSize.DIALOG) self.emit("populated") self._artwork.get_style_context().add_class("artwork-icon-large") if self._data < 0: set_icon_name() elif App().settings.get_value("artist-artwork"): App().art_helper.set_artist_artwork( self.name, self._art_size, self._art_size, self._artwork.get_scale_factor(), self.__on_artist_artwork) else: album_ids = App().albums.get_ids([self._data], []) if album_ids: shuffle(album_ids) App().art_helper.set_album_artwork( Album(album_ids[0]), self._art_size, self._art_size, self._artwork.get_scale_factor(), self.__on_artist_artwork, ArtHelperEffect.ROUNDED) else: set_icon_name()
def del_tracks(self, track_ids): """ Delete tracks from db @param track_ids as [int] """ all_album_ids = [] all_artist_ids = [] all_genre_ids = [] for track_id in track_ids: album_id = App().tracks.get_album_id(track_id) art_file = App().art.get_album_cache_name(Album(album_id)) genre_ids = App().tracks.get_genre_ids(track_id) album_artist_ids = App().albums.get_artist_ids(album_id) artist_ids = App().tracks.get_artist_ids(track_id) uri = App().tracks.get_uri(track_id) App().playlists.remove_uri_from_all(uri) App().tracks.remove(track_id) App().tracks.clean(track_id) all_album_ids.append(album_id) all_artist_ids += album_artist_ids + artist_ids all_genre_ids += genre_ids for album_id in list(set(all_album_ids)): if App().albums.clean(album_id): App().art.clean_store(art_file) for artist_id in list(set(all_artist_ids)): App().artists.clean(artist_id) for genre_id in list(set(all_genre_ids)): App().genres.clean(genre_id)
def __get_random(self): """ Return a random track and make sure it has never been played """ for album_id in sorted(self._albums, key=lambda *args: random.random()): # We need to check this as in party mode, some items do not # have a valid genre (Populars, ...) if album_id in self._context.genre_ids.keys(): genre_ids = self._context.genre_ids[album_id] else: genre_ids = [] tracks = Album(album_id, genre_ids).track_ids for track in sorted(tracks, key=lambda *args: random.random()): if album_id not in self.__already_played_tracks.keys() or\ track not in self.__already_played_tracks[album_id]: return track # No new tracks for this album, remove it # If albums not in shuffle history, it's not present # in db anymore (update since shuffle set) if album_id in self.__already_played_tracks.keys(): self.__already_played_tracks.pop(album_id) self.__already_played_albums.append(album_id) self._albums.remove(album_id) self._next_context = NextContext.STOP return None
def _on_album_activated(self, flowbox, child): """ Show Context view for activated album @param flowbox as Gtk.Flowbox @param child as Gtk.FlowboxChild """ album_widget = child.get_child() if self._press_rect is None: if self._context_album_id == album_widget.get_id(): self._context_album_id = None self._context.hide() self._context_widget.destroy() self._context_widget = None else: if Lp().settings.get_value('auto-play'): album = Album(album_widget.get_id()) track = Track(album.tracks_ids[0]) Lp().player.load(track) Lp().player.set_albums(track.id, None, self._genre_id) else: self._context_album_id = album_widget.get_id() self._populate_context(self._context_album_id) self._context.show() else: if self._context_album_id is not None: self._context_album_id = None self._context.hide() self._context_widget.destroy() self._context_widget = None popover = AlbumPopoverWidget(album_widget.get_id(), self._genre_id) popover.set_relative_to(album_widget) popover.set_pointing_to(self._press_rect) self._context_widget = popover.get_widget() popover.connect('destroy', self._on_popover_destroyed) popover.show()
def do_render(self, ctx, widget, background_area, cell_area, flags): if self.album == Type.NONE: return surface = Lp().art.get_album_artwork(Album(self.album), ArtSize.MEDIUM, widget.get_scale_factor()) width = surface.get_width() height = surface.get_height() # If cover smaller than wanted size, translate translate_x = cell_area.x translate_y = cell_area.y wanted = ArtSize.MEDIUM * widget.get_scale_factor() if width < wanted: translate_x += (wanted - width) / 2 if height < wanted: translate_y += (wanted - height) / 2 ctx.translate(translate_x, translate_y) ctx.new_sub_path() radius = 2 degrees = pi / 180 ctx.arc(width + 2 - radius, radius, radius - 0.5, -90 * degrees, 0 * degrees) ctx.arc(width + 2 - radius, height + 2 - radius, radius - 0.5, 0 * degrees, 90 * degrees) ctx.arc(radius, height + 2 - radius, radius - 0.5, 90 * degrees, 180 * degrees) ctx.arc(radius, radius, radius - 0.5, 180 * degrees, 270 * degrees) ctx.close_path() ctx.set_line_width(1) ctx.fill() ctx.set_source_surface(surface, 1, 1) del surface ctx.paint()
def _download_albums_art(self): """ Download albums artwork (from queue) @thread safe """ self._in_albums_download = True sql = Lp().db.get_cursor() while self._albums_queue: album_id = self._albums_queue.pop() album = Lp().albums.get_name(album_id) artist = Lp().albums.get_artist_name(album_id) pixbuf = self._get_album_art_spotify(artist, album) if pixbuf is None: pixbuf = self._get_album_art_itunes(artist, album) if pixbuf is None: pixbuf = self._get_album_art_lastfm(artist, album) if pixbuf is None: continue try: Lp().art.save_album_artwork(pixbuf, album_id) Lp().art.clean_album_cache(Album(album_id)) GLib.idle_add(Lp().art.album_artwork_update, album_id) except Exception as e: print("ArtDownloader::_download_albums_art: %s" % e) self._in_albums_download = False sql.close()
def __remove_album(self, action, variant): """ Remove album @param SimpleAction @param GVariant """ album = Album(self._object_id) artist_ids = [] for track_id in album.track_ids: artist_ids += Lp().tracks.get_artist_ids(track_id) Lp().tracks.remove(track_id) Lp().tracks.clean(track_id) artist_ids += album.artist_ids genre_ids = Lp().albums.get_genre_ids(album.id) Lp().albums.clean(album.id) for artist_id in list(set(artist_ids)): ret = Lp().artists.clean(artist_id) if ret: GLib.idle_add(Lp().scanner.emit, 'artist-updated', artist_id, album.id, False) for genre_id in genre_ids: ret = Lp().genres.clean(genre_id) if ret: GLib.idle_add(Lp().scanner.emit, 'genre-updated', genre_id, False) with SqlCursor(Lp().db) as sql: sql.commit() GLib.idle_add(Lp().scanner.emit, 'album-updated', self._object_id)
def __create_album(self, album_id, cover_uri, cancellable): """ Create album and download cover @param cancellable as Gio.Cancellable """ if not cancellable.is_cancelled(): GLib.idle_add(self.emit, "new-album", Album(album_id), cover_uri)
def del_tracks(self, track_ids): """ Delete tracks from db @param track_ids as [int] """ with SqlCursor(self) as sql: all_album_ids = [] all_artist_ids = [] all_genre_ids = [] for track_id in track_ids: album_id = Lp().tracks.get_album_id(track_id) art_file = Lp().art.get_album_cache_name(Album(album_id)) genre_ids = Lp().tracks.get_genre_ids(track_id) album_artist_ids = Lp().albums.get_artist_ids(album_id) artist_ids = Lp().tracks.get_artist_ids(track_id) uri = Lp().tracks.get_uri(track_id) Lp().playlists.remove(uri) Lp().tracks.remove(track_id) Lp().tracks.clean(track_id) all_album_ids.append(album_id) all_artist_ids += album_artist_ids + artist_ids all_genre_ids += genre_ids for album_id in list(set(all_album_ids)): if Lp().albums.clean(album_id): Lp().art.clean_store(art_file) for artist_id in list(set(all_artist_ids)): Lp().artists.clean(artist_id) for genre_id in list(set(all_genre_ids)): Lp().genres.clean(genre_id) sql.commit()
def __init__(self, artist_ids, genre_ids): """ Init ArtistView @param artist_id as int (Current if None) @param genre_id as int """ View.__init__(self) self._genre_ids = genre_ids self._artist_ids = artist_ids ArtistViewCommon.__init__(self) self._jump_button.hide() self.__overlay = Gtk.Overlay() self.__overlay.show() self.__overlay.add_overlay(self._banner) album_ids = App().albums.get_ids(artist_ids, genre_ids) self.__album_box = AlbumsBoxView( genre_ids, artist_ids, ViewType.MEDIUM | ViewType.SCROLLED | ViewType.NOT_ADAPTIVE) height = self._banner.default_height // 3 self._banner.set_height(height) self.__album_box.set_margin_top(height) self.__album_box.populate([Album(id) for id in album_ids]) self.__album_box.show() self.__overlay.add_overlay(self.__album_box) self.add(self.__overlay)