示例#1
0
 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)
示例#2
0
    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)
示例#3
0
 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()
示例#4
0
 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)
示例#5
0
 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
示例#6
0
 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)
示例#7
0
 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)
示例#8
0
    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()
示例#9
0
    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}")
示例#10
0
    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