def populate(self, items): """ Populate items @param items """ if self.__obj is not None: new_items = [] for item in items: if not App().playlists.get_smart(item): new_items.append(item) items = new_items else: items = [i[0] for i in ShownPlaylists.get()] + items FlowBoxView.populate(self, items)
def add_genre(self, track_id, genre_id): """ Add genre to track @param track_id as int @param genre_id as int @warning: commit needed """ with SqlCursor(App().db, True) as sql: genres = self.get_genre_ids(track_id) if genre_id not in genres: sql.execute("INSERT INTO\ track_genres (track_id, genre_id)\ VALUES (?, ?)", (track_id, genre_id))
def search_track(self, artist, title): """ Get track id for artist and title @param artist as string @param title as string @return track id as int """ artist = noaccents(artist.lower()) track_ids = self.get_ids_for_name(title) for track_id in track_ids: album_id = App().tracks.get_album_id(track_id) artist_ids = set(App().albums.get_artist_ids(album_id)) &\ set(App().tracks.get_artist_ids(track_id)) for artist_id in artist_ids: db_artist = noaccents( App().artists.get_name(artist_id).lower()) if artist.find(db_artist) != -1 or\ db_artist.find(artist) != -1: return track_id artists = ", ".join(App().tracks.get_artists(track_id)).lower() if noaccents(artists) == artist: return track_id return None
def get_mtime(self, track_id): """ Get modification time @param track_id as int @return modification time as int """ with SqlCursor(App().db) as sql: request = "SELECT mtime FROM tracks\ WHERE tracks.rowid=?" result = sql.execute(request, (track_id,)) v = result.fetchone() if v is not None: return v[0] return 0
def get_loved(self, track_id): """ Get track loved status @param track_id as int @return loved as int """ with SqlCursor(App().db) as sql: result = sql.execute("SELECT loved FROM tracks WHERE\ rowid=?", (track_id,)) v = result.fetchone() if v is not None: return v[0] return 0
def get_randoms(self): """ Return random tracks @return array of track ids as int """ if self.__cached_randoms: return self.__cached_randoms with SqlCursor(App().db) as sql: result = sql.execute("SELECT tracks.rowid\ FROM tracks WHERE mtime != 0\ ORDER BY random() LIMIT 100") tracks = list(itertools.chain(*result)) self.__cached_randoms = list(tracks) return tracks
def get_album_name(self, track_id): """ Get album name for track id @param track_id as int @return album name as str """ with SqlCursor(App().db) as sql: result = sql.execute("SELECT albums.name from albums,tracks\ WHERE tracks.rowid=? AND\ tracks.album_id=albums.rowid", (track_id,)) v = result.fetchone() if v is not None: return v[0] return _("Unknown")
def __search_tracks(self, search, storage_type, cancellable): """ Get tracks for search items @param search as str @param storage_type as StorageType @param cancellable as Gio.Cancellable @return [int] """ tracks = [] track_ids = [] split = self.__split_string(search) for search_str in [search] + split: tracks += App().tracks.search_performed(search_str, storage_type) tracks += App().tracks.search(search_str, storage_type) if cancellable.is_cancelled(): break for (track_id, track_name) in tracks: valid = True no_accents = noaccents(track_name) if not no_accents.startswith(search): for word in split: if word not in no_accents: valid = False break # Start with same word, adding to result else: track_ids.append(track_id) # All words are valid, adding to result if valid: track_ids.append(track_id) # Detect an artist match, adding to result for artist in App().tracks.get_artists(track_id): no_accents = noaccents(artist) for word in split: if word in no_accents: track_ids.append(track_id) return track_ids
def get_all(self, genre_ids=[]): """ Get all available artists @param genre_ids as [int] @return [int, str, str] """ if App().settings.get_value("show-artist-sort"): select = "artists.rowid, artists.sortname, artists.sortname" else: select = "artists.rowid, artists.name, artists.sortname" with SqlCursor(App().db) as sql: result = [] if not genre_ids or genre_ids[0] == Type.ALL: # Only artist that really have an album result = sql.execute( "SELECT DISTINCT %s FROM artists, track_artists, tracks\ WHERE artists.rowid=track_artists.artist_id\ AND tracks.rowid=track_artists.track_id\ AND tracks.mtime!=0\ ORDER BY artists.sortname\ COLLATE NOCASE COLLATE LOCALIZED" % select) else: genres = tuple(genre_ids) request = "SELECT DISTINCT %s\ FROM artists, tracks, track_genres, track_artists\ WHERE artists.rowid=track_artists.artist_id\ AND tracks.rowid=track_artists.track_id\ AND tracks.mtime!=0\ AND track_genres.track_id=tracks.rowid AND (" for genre_id in genre_ids: request += "track_genres.genre_id=? OR " request += "1=0) ORDER BY artists.sortname\ COLLATE NOCASE COLLATE LOCALIZED" result = sql.execute(request % select, genres) return [(row[0], row[1], row[2]) for row in result]
def __init__(self, widget, rowid, mask): """ Init menu @param widget as Gtk.Widget @param rowid as int @param mask as SelectionListMask """ Popover.__init__(self) self.__widget = widget self.__rowid = rowid self.__mask = mask menu = Gio.Menu() self.bind_model(menu, None) # Startup menu if rowid in [Type.POPULARS, Type.RADIOS, Type.LOVED, Type.ALL, Type.RECENTS, Type.YEARS, Type.RANDOMS, Type.NEVER, Type.PLAYLISTS, Type.ARTISTS] and\ not App().settings.get_value("save-state"): startup_menu = Gio.Menu() if self.__mask & SelectionListMask.LIST_TWO: exists = rowid in App().settings.get_value("startup-two-ids") else: exists = rowid in App().settings.get_value("startup-one-ids") action = Gio.SimpleAction.new_stateful( "default_selection_id", None, GLib.Variant.new_boolean(exists)) App().add_action(action) action.connect("change-state", self.__on_default_change_state, rowid) item = Gio.MenuItem.new(_("Default on startup"), "app.default_selection_id") startup_menu.append_item(item) menu.insert_section(0, _("Startup"), startup_menu) # Shown menu shown_menu = Gio.Menu() if mask & SelectionListMask.PLAYLISTS: lists = ShownPlaylists.get(True) wanted = App().settings.get_value("shown-playlists") else: lists = ShownLists.get(mask, True) wanted = App().settings.get_value("shown-album-lists") for item in lists: exists = item[0] in wanted encoded = sha256(item[1].encode("utf-8")).hexdigest() action = Gio.SimpleAction.new_stateful( encoded, None, GLib.Variant.new_boolean(exists)) action.connect("change-state", self.__on_shown_change_state, item[0]) App().add_action(action) shown_menu.append(item[1], "app.%s" % encoded) # Translators: shown => items menu.insert_section(1, _("Shown"), shown_menu)
def __init__(self): """ Init box """ Gtk.Box.__init__(self) # Prevent updating progress while seeking self.__seeking = False # Update pogress position self.__timeout_id = None self.__time_label = Gtk.Label.new() self.__time_label.show() self.__progress = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, None) self.__progress.show() self.__progress.set_hexpand(True) self.__progress.set_draw_value(False) self.__progress.connect("change-value", self.__on_change_value) self.__multi_press = Gtk.GestureMultiPress.new(self.__progress) self.__multi_press.set_propagation_phase(Gtk.PropagationPhase.TARGET) self.__multi_press.connect("pressed", self.__on_multi_pressed) self.__multi_press.connect("released", self.__on_multi_released) self.__multi_press.set_button(1) self.__event_controller = Gtk.EventControllerScroll.new( self.__progress, Gtk.EventControllerScrollFlags.BOTH_AXES) self.__event_controller.set_propagation_phase( Gtk.PropagationPhase.TARGET) self.__event_controller.connect("scroll", self.__on_scroll) self.__total_time_label = Gtk.Label.new() self.__total_time_label.show() self.set_spacing(MARGIN_SMALL) self.pack_start(self.__time_label, False, False, 0) self.pack_start(self.__progress, False, True, 0) self.pack_start(self.__total_time_label, False, False, 0) self.connect("destroy", self.__on_destroy) return [(App().player, "current-changed", "_on_current_changed"), (App().player, "status-changed", "_on_status_changed"), (App().player, "duration-changed", "_on_duration_changed"), (App().player, "seeked", "_on_seeked")]
def __on_shown_change_state(self, action, variant, rowid): """ Set action value @param action as Gio.SimpleAction @param variant as GLib.Variant @param rowid as int """ action.set_state(variant) if self.__mask & SelectionListMask.PLAYLISTS: option = "shown-playlists" else: option = "shown-album-lists" wanted = list(App().settings.get_value(option)) if variant: wanted.append(rowid) else: wanted.remove(rowid) App().settings.set_value(option, GLib.Variant("ai", wanted)) if self.__mask & SelectionListMask.PLAYLISTS: items = ShownPlaylists.get(True) else: items = ShownLists.get(self.__mask, True) if variant: for item in items: if item[0] == rowid: self.__widget.add_value(item) break else: self.__widget.remove_value(rowid) if self.__mask & SelectionListMask.LIST_ONE: ids = list(App().settings.get_value("startup-one-ids")) if rowid in ids: ids.remove(rowid) App().settings.set_value("startup-one-ids", GLib.Variant("ai", ids)) App().settings.set_value("startup-two-ids", GLib.Variant("ai", []))
def __set_popularity(self, pop): """ Set popularity as kid3 is installed @param pop as int """ try: if App().art.kid3_available: if pop == 0: value = 0 elif pop == 1: value = 1 elif pop == 2: value = 64 elif pop == 3: value = 128 elif pop == 4: value = 196 else: value = 255 path = GLib.filename_from_uri(self.__object.uri)[0] if GLib.find_program_in_path("flatpak-spawn") is not None: argv = [ "flatpak-spawn", "--host", "kid3-cli", "-c", "set POPM %s" % value, path ] else: argv = ["kid3-cli", "-c", "set POPM %s" % value, path] if App().scanner.inotify is not None: App().scanner.inotify.disable() (pid, stdin, stdout, stderr) = GLib.spawn_async( argv, flags=GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.STDOUT_TO_DEV_NULL) # Force mtime update to not run a collection update App().tracks.set_mtime(self.__object.id, int(time()) + 10) except Exception as e: Logger.error("RatingWidget::__on_can_set_popularity(): %s" % e)
def _on_button_release_event(self, widget, event): """ Set album popularity @param widget as Gtk.EventBox @param event as Gdk.Event """ user_rating = True rate = self.__object.rate if rate < 1: rate = self.__object.get_popularity() user_rating = False max_star = self.__star_from_rate(rate) event_star = widget.get_children()[0] if event_star in self._stars: position = self._stars.index(event_star) else: position = -1 pop = position + 1 if event.button != 1: self.__object.set_popularity(pop) self.__object.set_rate(0) self._on_leave_notify_event(None, None) elif pop == 0 or pop == max_star: if user_rating: self.__object.set_rate(0) else: self.__object.set_popularity(0) self._on_leave_notify_event(None, None) else: self.__object.set_rate(pop) # Save to tags if needed if App().settings.get_value("save-to-tags") and\ isinstance(self.__object, Track) and\ self.__object.id >= 0: App().task_helper.run(self.__set_popularity, pop) return True
def __setup_content(self): """ Setup window content """ self.__container = Container() self.set_stack(self.container.stack) self.__container.show() self.__vgrid = Gtk.Grid() self.__vgrid.set_orientation(Gtk.Orientation.VERTICAL) self.__vgrid.show() self.__toolbar = Toolbar(self) self.__toolbar.show() if App().settings.get_value("disable-csd") or is_unity(): self.__vgrid.add(self.__toolbar) else: self.set_titlebar(self.__toolbar) self.__toolbar.set_show_close_button( not App().settings.get_value("disable-csd")) self.__vgrid.add(self.__container) self.add(self.__vgrid) self.drag_dest_set(Gtk.DestDefaults.DROP | Gtk.DestDefaults.MOTION, [], Gdk.DragAction.MOVE) self.drag_dest_add_uri_targets() self.connect("drag-data-received", self.__on_drag_data_received)
def __init__(self, view_type): """ Init widget @param view_type as ViewType """ if App().settings.get_value("force-single-column"): view_type &= ~ViewType.TWO_COLUMNS self._view_type = view_type self._width = None self.__discs = [] self._responsive_widget = None self._orientation = None self.__populated = False self.__allocation_timeout_id = None self.__cancellable = Gio.Cancellable()
def __init__(self): """ Init player """ BinPlayer.__init__(self) QueuePlayer.__init__(self) LinearPlayer.__init__(self) ShufflePlayer.__init__(self) PlaylistPlayer.__init__(self) RadioPlayer.__init__(self) SimilarsPlayer.__init__(self) self.__stop_after_track_id = None self.update_crossfading() App().settings.connect("changed::repeat", self.__on_repeat_changed) self._albums_backup = []
def __init__(self, album, genre_ids, artist_ids): """ Init Album widget @param album as Album @param genre_ids as [int] @param artist_ids as [int] """ self._artwork = None self._album = album self._genre_ids = genre_ids self._artist_ids = artist_ids self.__filtered = False self.connect("destroy", self.__on_destroy) self._scan_signal = App().scanner.connect("album-updated", self._on_album_updated)
def get(self, current_search, cancellable, callback): """ Get track for name @param current_search as str @param cancellable as Gio.Cancellable @param callback as callback """ search_items = [] for item in current_search.lower().split(): if item not in search_items: search_items.append(item) App().task_helper.run(self.__get, search_items, cancellable, callback=callback)
def __on_remove_action_activate(self, action, variant): """ Remove playlist @param Gio.SimpleAction @param GLib.Variant """ def remove_playlist(): App().playlists.remove(self.__playlist_id) from lollypop.app_notification import AppNotification notification = AppNotification(_("Remove this playlist?"), [_("Confirm")], [remove_playlist]) notification.show() App().window.container.add_overlay(notification) notification.set_reveal_child(True)
def set_smart_sql(self, playlist_id, request): """ Set playlist SQL smart request @param playlist_id as int @param request as str """ name = self.get_name(playlist_id) # Clear cache App().art.remove_artwork_from_cache("playlist_" + name, "ROUNDED") with SqlCursor(self, True) as sql: sql.execute("UPDATE playlists\ SET smart_sql=?\ WHERE rowid=?", (request, playlist_id)) emit_signal(self, "playlists-updated", playlist_id)
def __get_playlists_random(self): """ Return a track from current playlist @return Track """ for track in sorted(self._playlist_tracks, key=lambda *args: random.random()): # Ignore current track, not an issue if playing one track # in shuffle because LinearPlayer will handle next() if track != App().player.current_track and ( track.album not in self.__already_played_tracks.keys() or track not in self.__already_played_tracks[track.album]): return track self._next_context = NextContext.STOP return Track()
def _on_activate(self, flowbox, child): """ Save artwork @param flowbox as Gtk.FlowBox @param child as ArtworkSearchChild """ try: if isinstance(child, ArtworkSearchChild): self._close_popover() App().art.save_album_artwork(child.bytes, self.__album) self._streams = {} else: ArtworkSearchWidget._on_activate(self, flowbox, child) except Exception as e: Logger.error("AlbumArtworkSearchWidget::_on_activate(): %s", e)
def _on_primary_press_gesture(self, x, y, event): """ Show covers popover @param x as int @param y as int @param event as Gdk.Event """ if self.__view_type & ViewType.ALBUM: from lollypop.widgets_menu import MenuBuilder from lollypop.menu_artwork import AlbumArtworkMenu menu = Gio.Menu() if App().window.folded: from lollypop.menu_header import AlbumMenuHeader menu.append_item(AlbumMenuHeader(self.__album)) menu_widget = MenuBuilder(menu, False) menu_widget.show() menu_ext = AlbumArtworkMenu(self.__album, self.__view_type, False) menu_ext.connect("hidden", self.__close_artwork_menu) menu_ext.show() menu_widget.add_widget(menu_ext, False) self.__artwork_popup = popup_widget(menu_widget, self, None, None, None) else: App().window.container.show_view([Type.ALBUM], self.__album)
def __on_dir_changed(self, monitor, changed_file, other_file, event): """ Stop collection scanner if running Delayed update by default @param monitor as Gio.FileMonitor @param changed_file as Gio.File/None @param other_file as Gio.File/None @param event as Gio.FileMonitorEvent """ update = False # Stop collection scanner and wait if App().scanner.is_locked(): App().scanner.stop() GLib.timeout_add(self.__TIMEOUT, self.__on_dir_changed, monitor, changed_file, other_file, event) # Run update delayed else: uri = changed_file.get_uri() d = Gio.File.new_for_uri(uri) if d.query_exists(): # If a directory, monitor it if changed_file.query_file_type( Gio.FileQueryInfoFlags.NONE, None) == Gio.FileType.DIRECTORY: self.add_monitor(uri) # If not an audio file, exit elif is_audio(changed_file): update = True else: update = True if update: if self.__timeout is not None: GLib.source_remove(self.__timeout) self.__timeout = None self.__timeout = GLib.timeout_add(self.__TIMEOUT, self.__run_collection_update)
def format_artist_name(name): """ Return formated artist name @param name as str """ if not App().settings.get_value("smart-artist-sort"): return name # Handle language ordering # Translators: Add here words that shoud be ignored for artist sort order # Translators: Add The the too for special in _("The the").split(): if name.startswith(special + " "): strlen = len(special) + 1 name = name[strlen:] + ", " + special return name
def __on_list_two_selected(self, selection_list): """ Update view based on selected object @param selection_list as SelectionList """ Logger.debug("Container::__on_list_two_selected()") self._stack.destroy_non_visible_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 __on_remove_track(self, row): """ Remove row's track @param row as PlaylistRow """ if row.track.id != self.__last_drag_id: App().player.remove_from_queue(row.track.id) if row.previous_row is None: row.next_row.set_previous_row(None) elif row.next_row is None: row.previous_row.set_next_row(None) else: row.next_row.set_previous_row(row.previous_row) row.previous_row.set_next_row(row.next_row) self.__last_drag_id = None
def skip_album(self): """ Skip current album """ # In party or shuffle, just update next track if self.is_party or\ App().settings.get_enum("shuffle") == Shuffle.TRACKS: self.set_next() # We send this signal to update next popover self.emit("queue-changed") elif self._current_track.id is not None: self.pause() self.load(self._current_track.album.tracks[-1]) self.set_next() self.next()
def remove(self, playlist_id): """ Remove playlist @param playlist_id as int """ name = self.get_name(playlist_id) with SqlCursor(self, True) as sql: sql.execute("DELETE FROM playlists\ WHERE rowid=?", (playlist_id,)) sql.execute("DELETE FROM tracks\ WHERE playlist_id=?", (playlist_id,)) emit_signal(self, "playlists-removed", playlist_id) App().art.remove_artwork_from_cache("playlist_" + name, "ROUNDED")