def create(self, name_base: Optional[str] = None) -> Playlist: if name_base: return XSPFBackedPlaylist.new(self.pl_dir, name_base, songs_lib=self._library, pl_lib=self) return XSPFBackedPlaylist.new(self.pl_dir, songs_lib=self._library, pl_lib=self)
def __new_playlist(self, activator, library): playlist = XSPFBackedPlaylist.new(PLAYLISTS, library=library) self.model.append(row=[playlist]) self._select_playlist(playlist, scroll=True) model, iter = self.__selected_playlists() path = model.get_path(iter) GLib.idle_add(self._start_rename, path)
def __drag_data_received(self, view, ctx, x, y, sel, tid, etime, library): # TreeModelSort doesn't support GtkTreeDragDestDrop. view.emit_stop_by_name('drag-data-received') model = view.get_model() if tid == DND_QL: filenames = qltk.selection_get_filenames(sel) songs = list(filter(None, [library.get(f) for f in filenames])) if not songs: Gtk.drag_finish(ctx, False, False, etime) return try: path, pos = view.get_dest_row_at_pos(x, y) except TypeError: playlist = XSPFBackedPlaylist.from_songs(PLAYLISTS, songs, library) GLib.idle_add(self._select_playlist, playlist) else: playlist = model[path][0] playlist.extend(songs) self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) # Cause a refresh to the dragged-to playlist if it is selected # so that the dragged (duplicate) track(s) appears if playlist is self.__get_name_of_current_selected_playlist(): model, plist_iter = self.__selected_playlists() songlist = qltk.get_top_parent(self).songlist self.activate(resort=not songlist.is_sorted()) else: if tid == DND_URI_LIST: uri = sel.get_uris()[0] name = os.path.basename(uri) elif tid == DND_MOZ_URL: data = sel.get_data() uri, name = data.decode('utf16', 'replace').split('\n') else: Gtk.drag_finish(ctx, False, False, etime) return name = _name_for(name or os.path.basename(uri)) try: sock = urlopen(uri) if uri.lower().endswith('.pls'): playlist = parse_pls(sock, name, library=library) elif (uri.lower().endswith('.m3u') or uri.lower().endswith('.m3u8')): playlist = parse_m3u(sock, name, library=library) else: raise IOError library.add(playlist.songs) self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) except IOError: Gtk.drag_finish(ctx, False, False, etime) qltk.ErrorMessage( qltk.get_top_parent(self), _("Unable to import playlist"), _("Quod Libet can only import playlists in the M3U/M3U8 " "and PLS formats.")).run()
def create_from_songs(self, songs: Iterable[AudioFile], title=None) -> Playlist: """Creates a playlist visible to this library""" return XSPFBackedPlaylist.from_songs(self.pl_dir, songs, title=title, songs_lib=self._library, pl_lib=self)
def test_load_legacy_format_to_xspf(self): playlist_fn = "old" songs_lib = FileLibrary() songs_lib.add(NUMERIC_SONGS) old_pl = FileBackedPlaylist(self.temp, playlist_fn) old_pl.extend(NUMERIC_SONGS) pl = XSPFBackedPlaylist.from_playlist(old_pl, songs_lib=songs_lib, pl_lib=None) expected_filenames = {s("~filename") for s in NUMERIC_SONGS} assert {s("~filename") for s in pl.songs} == expected_filenames
def reordered(self, songs): model, iter = self.__selected_playlists() playlist = None if iter: playlist = model[iter][0] playlist[:] = songs elif songs: playlist = XSPFBackedPlaylist.from_songs(PLAYLISTS, songs, self.library) GLib.idle_add(self._select_playlist, playlist) if playlist: self.changed(playlist, refresh=False)
def test_v1_load_non_compliant_xspf(self): """See #3983""" songs_lib = FileLibrary() test_filename = ("/music/Funk & Disco/" "Average White Band - Pickin' Up The Pieces/" "Average White Band - Your Love Is a Miracle.flac") songs_lib.add([AudioFile({"~filename": test_filename})]) playlist_fn = "non-compliant.xspf" path = str(Path(__file__).parent / "data") pl = XSPFBackedPlaylist(path, playlist_fn, songs_lib=songs_lib, pl_lib=None) assert {s("~filename") for s in pl.songs}, set(test_filename)
def init(klass, library): klass.library = library model = klass.__lists.get_model() for playlist in os.listdir(PLAYLISTS): if os.path.isdir(os.path.join(PLAYLISTS, playlist)): continue try: playlist = XSPFBackedPlaylist(PLAYLISTS, playlist, library) model.append(row=[playlist]) except TypeError: legacy = FileBackedPlaylist(PLAYLISTS, playlist, library) print_w("Converting \"%s\" to XSPF format" % playlist) playlist = XSPFBackedPlaylist.from_playlist(legacy, library) model.append(row=[playlist]) except EnvironmentError: print_w("Invalid Playlist '%s'" % playlist) klass._ids = [ library.connect('removed', klass.__removed), library.connect('added', klass.__added), library.connect('changed', klass.__changed), ] klass.load_pattern()
def _read_playlists(self, library) -> None: print_d( f"Reading playlist directory {self.pl_dir} (library: {library})") try: fns = os.listdir(self.pl_dir) except FileNotFoundError as e: print_w( f"No playlist dir found in {self.pl_dir!r}, creating. ({e})") os.mkdir(self.pl_dir) fns = [] # Populate this library by relying on existing signal passing. # Weird, but allows keeping the logic in one place for fn in fns: full_path = os.path.join(self.pl_dir, fn) if os.path.isdir(full_path): continue if HIDDEN_RE.match(fsn2text(fn)): print_d(f"Ignoring hidden file {fn!r}") continue try: XSPFBackedPlaylist(self.pl_dir, fn, songs_lib=library, pl_lib=self) except TypeError as e: # Don't add to library - it's temporary legacy = FileBackedPlaylist(self.pl_dir, fn, songs_lib=library, pl_lib=None) print_w(f"Converting {fn!r} to XSPF format ({e})") XSPFBackedPlaylist.from_playlist(legacy, songs_lib=library, pl_lib=self) except EnvironmentError: print_w(f"Invalid Playlist {fn!r}")
def setUp(self): self.success = False # Testing locally is VERY dangerous without this... self.assertTrue(_TEMP_DIR in _DEFAULT_PLAYLIST_DIR or os.name == "nt", msg="Failing, don't want to delete %s" % _DEFAULT_PLAYLIST_DIR) try: shutil.rmtree(_DEFAULT_PLAYLIST_DIR) except OSError: pass mkdir(_DEFAULT_PLAYLIST_DIR) init_fake_app() self.lib = quodlibet.browsers.playlists.library = SongFileLibrary() self.lib.librarian = SongLibrarian() for af in self.ALL_SONGS: af.sanitize() self.lib.add(self.ALL_SONGS) self.big = pl = FileBackedPlaylist.new(_DEFAULT_PLAYLIST_DIR, "Big", self.lib, self.lib.playlists) pl.extend(SONGS) pl.write() self.small = pl = XSPFBackedPlaylist.new(_DEFAULT_PLAYLIST_DIR, "Small", self.lib, self.lib.playlists) pl.extend([self.ANOTHER_SONG]) pl.write() PlaylistsBrowser.init(self.lib) self.bar = PlaylistsBrowser(self.lib, self.MockConfirmerAccepting) self.bar.connect('songs-selected', self._expected) self.bar._select_playlist(self.bar.playlists()[0]) self.expected = None # Uses the declining confirmer. self.bar_decline = PlaylistsBrowser(self.lib, self.MockConfirmerDeclining) self.bar_decline.connect('songs-selected', self._expected_decline) self.bar_decline._select_playlist(self.bar_decline.playlists()[0]) # Note that _do() uses self.expected, but _do() is not called by the # testcase for declining the prompt. Tests fail with a shared expected. self.expected_decline = None