def setUp(self): # Testing locally is VERY dangerous without this... self.assertTrue(_TEMP_DIR in PLAYLISTS or os.name == "nt", msg="Failing, don't want to delete %s" % PLAYLISTS) try: shutil.rmtree(PLAYLISTS) except OSError: pass mkdir(PLAYLISTS) self.lib = quodlibet.browsers.playlists.library = SongLibrary() self.lib.librarian = SongLibrarian() all_songs = SONGS + [self.ANOTHER_SONG] for af in all_songs: af.sanitize() self.lib.add(all_songs) self.big = pl = Playlist.new(PLAYLISTS, "Big", self.lib) pl.extend(SONGS) pl.write() self.small = pl = Playlist.new(PLAYLISTS, "Small", self.lib) pl.extend([self.ANOTHER_SONG]) pl.write() PlaylistsBrowser.init(self.lib) self.bar = PlaylistsBrowser(self.lib) self.bar.connect('songs-selected', self._expected) self.bar._select_playlist(self.bar.playlists()[0]) self.expected = None
def test_listlike(s): pl = Playlist(s.temp, "playlist") pl.extend(NUMERIC_SONGS) s.failUnlessEqual(NUMERIC_SONGS[0], pl[0]) s.failUnlessEqual(NUMERIC_SONGS[1:2], pl[1:2]) s.failUnless(NUMERIC_SONGS[1] in pl) pl.delete()
def setUp(self): try: shutil.rmtree(PLAYLISTS) except OSError: pass mkdir(PLAYLISTS) self.lib = quodlibet.browsers.playlists.library = SongLibrary() self.lib.librarian = SongLibrarian() all_songs = SONGS + [self.ANOTHER_SONG] for af in all_songs: af.sanitize() self.lib.add(all_songs) pl = Playlist.new(PLAYLISTS, "Big", self.lib) pl.extend(SONGS) pl.write() pl = Playlist.new(PLAYLISTS, "Small", self.lib) pl.extend([self.ANOTHER_SONG]) pl.write() PlaylistsBrowser.init(self.lib) self.bar = PlaylistsBrowser(self.lib) self.bar.connect('songs-selected', self._expected) self.bar._select_playlist(self.bar.playlists()[0]) self.expected = None
def test_duplicates(self): pl = Playlist(self.temp, "playlist") pl.extend(self.TWO_SONGS) pl.extend(self.TWO_SONGS) self.failUnlessEqual(len(pl), 4) self.failUnless(pl.has_duplicates, ("Playlist has un-detected duplicates: %s " % "\n".join([str(s) for s in pl._list])))
def test_make_dup(self): p1 = Playlist.new(self.temp, "Does not exist") p2 = Playlist.new(self.temp, "Does not exist") self.failUnlessEqual(p1.name, "Does not exist") self.failUnless(p2.name.startswith("Does not exist")) self.failIfEqual(p1.name, p2.name) p1.delete() p2.delete()
def test_playlists_tag(self): # Arguably belongs in _audio songs = NUMERIC_SONGS pl_name = "playlist 123!" pl = Playlist(self.temp, pl_name) pl.extend(songs) for song in songs: self.assertEquals(pl_name, song("~playlists")) pl.delete()
def test_playlists_featuring(s): with s.wrap("playlist") as pl: pl.extend(NUMERIC_SONGS) playlists = Playlist.playlists_featuring(NUMERIC_SONGS[0]) s.failUnlessEqual(set(playlists), {pl}) # Now add a second one, check that instance tracking works with s.wrap("playlist2") as pl2: pl2.append(NUMERIC_SONGS[0]) playlists = Playlist.playlists_featuring(NUMERIC_SONGS[0]) s.failUnlessEqual(set(playlists), {pl, pl2})
def test_internal_tags(s): pl = Playlist(s.temp, "playlist") pl.extend(s.TWO_SONGS) s.failIfEqual(pl.comma("~long-length"), "") s.failIfEqual(pl.comma("~tracks"), "") s.failIfEqual(pl.comma("~discs"), "") s.failUnlessEqual(pl.comma("~foo"), "") s.failUnlessEqual(pl.comma(""), "") s.failUnlessEqual(pl.comma("~"), "") s.failUnlessEqual(pl.get("~#"), "")
def setup(): Playlist._remove_all() for i in xrange(NUM_PLAYLISTS): pls.append(Playlist(PLAYLISTS, "List %d" % (i+1))) for i in xrange(NUM_SONGS): a = ARTISTS[randint(0,2)] t = "Song %d" % i data = {"title": t, "artist":a, "~#tracknumber": i % 20, "~filename": "%s.mp3" % t, "~#filesize":randint(1000000,100000000)} song = Fakesong(data) library.append(song) if not (i % SONGS_TO_PLAYLIST_SIZE_RATIO): song["~included"] = "yes" for j in range(PLAYLISTS_PER_PLAYLISTED_SONG): pls[(i+j) % NUM_PLAYLISTS].append(song)
def __parse_playlist(name, plfilename, files, library): playlist = Playlist.new(PLAYLISTS, name, library=library) songs = [] win = WaitLoadWindow(None, len(files), _("Importing playlist.\n\n%(current)d/%(total)d songs added.")) win.show() for i, filename in enumerate(files): try: uri = URI(filename) except ValueError: if os.name == "nt": filename = filename.decode("utf-8", "replace") # Plain filename. filename = os.path.realpath(os.path.join(os.path.dirname(plfilename), filename)) if library and filename in library: songs.append(library[filename]) else: songs.append(formats.MusicFile(filename)) else: if uri.scheme == "file": # URI-encoded local filename. filename = os.path.realpath(os.path.join(os.path.dirname(plfilename), uri.filename)) if library and filename in library: songs.append(library[filename]) else: songs.append(formats.MusicFile(filename)) else: # Who knows! Hand it off to GStreamer. songs.append(formats.remote.RemoteFile(uri)) if win.step(): break win.destroy() playlist.extend(filter(None, songs)) return playlist
def test_playlist_plugin(self): pl = Playlist("foo", library=app.librarian) pl.extend([AudioFile({"~filename": "/dev/null"})]) self.called_pl = None self.called_songs = None def proxy(songs, playlist=None): self.called_pl = playlist self.called_songs = songs plugin = self.plugin() plugin._handle_songs = proxy # Test that as a Playlist plugin it delegates correctly plugin.plugin_playlist(pl) self.failUnless(self.called_songs) self.assertEqual(self.called_pl, pl) self.assertEqual(self.called_songs, pl.songs)
def test_updating_aggregates_remove_songs(s): pl = Playlist(s.temp, "playlist") pl.extend(NUMERIC_SONGS) s.failUnless(pl.get("~#length")) pl.remove_songs(NUMERIC_SONGS) s.failIf(pl.get("~#length"))
def _on_new_playlist_activate(self, item, songs): parent = get_menu_item_top_parent(item) title = Playlist.suggested_name_for(songs) title = GetPlaylistName(qltk.get_top_parent(parent)).run(title) if title is None: return playlist = FileBackedPlaylist.new(PLAYLISTS, title) playlist.extend(songs) PlaylistsBrowser.changed(playlist)
def _on_new_playlist_activate(self, item, songs): parent = get_menu_item_top_parent(item) title = Playlist.suggested_name_for(songs) title = self._get_new_name(parent, title) if title is None: return playlist = FileBackedPlaylist.new(PLAYLISTS, title, library=self.librarian) playlist.extend(songs) self._emit_new(playlist)
def setUp(self): quodlibet.config.init() self.lib = quodlibet.browsers.search.library = FileLibrary() quodlibet.browsers.search.library.librarian = SongLibrarian() for af in self.SONGS: af.sanitize() self.lib.add(self.SONGS) self._dir = mkdtemp() self.pl = Playlist.new(self._dir, "Foobar", self.lib) self.pl.extend(self.SONGS)
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 = filter(None, map(library.get, 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 = Playlist.fromsongs(PLAYLISTS, songs, library) GLib.idle_add(self.__select_playlist, playlist) else: playlist = model[path][0] playlist.extend(songs) PlaylistsBrowser.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) 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 or os.path.basename(uri) or _("New Playlist") uri = uri.encode('utf-8') try: sock = urllib.urlopen(uri) f = NamedTemporaryFile() f.write(sock.read()) f.flush() if uri.lower().endswith('.pls'): playlist = parse_pls(f.name, library=library) elif uri.lower().endswith('.m3u'): playlist = parse_m3u(f.name, library=library) else: raise IOError library.add_filename(playlist) if name: playlist.rename(name) PlaylistsBrowser.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 " "and PLS formats.")).run()
def test_updating_aggregates_extend(s): pl = Playlist(s.temp, "playlist") pl.extend(NUMERIC_SONGS) old_length = pl.get("~#length") old_size = pl.get("~#filesize") # Double the playlist pl.extend(NUMERIC_SONGS) new_length = pl.get("~#length") new_size = pl.get("~#filesize") s.failUnless(new_length > old_length, msg="Ooops, %d <= %d" % (new_length, old_length)) s.failUnless(new_size > old_size, msg="Ooops, %d <= %d" % (new_size, old_size))
def test_write(self): pl = Playlist(self.temp, "playlist") pl.extend(NUMERIC_SONGS) pl.extend([fsnative(u"xf0xf0")]) pl.write() with open(pl.filename, "rb") as h: self.assertEqual(len(h.read().splitlines()), len(NUMERIC_SONGS) + 1)
def test_updating_aggregates_append(s): pl = Playlist(s.temp, "playlist") pl.extend(NUMERIC_SONGS) old_rating = pl.get("~#rating") pl.append(AMAZING_SONG) new_rating = pl.get("~#filesize") s.failUnless(new_rating > old_rating)
def reordered(self, songs): model, iter = self.__view.get_selection().get_selected() playlist = None if iter: playlist = model[iter][0] playlist[:] = songs elif songs: playlist = Playlist.fromsongs(PLAYLISTS, songs) GLib.idle_add(self.__select_playlist, playlist) if playlist: PlaylistsBrowser.changed(playlist, refresh=False)
def test_playlists_tag(self): # Arguably belongs in _audio songs = NUMERIC_SONGS Playlist._remove_all() Playlist._clear_global_cache() pl_name="playlist 123!" pl = Playlist(self.temp, pl_name) pl.extend(songs) for song in songs: self.assertEquals(pl_name, song("~playlists"))
def init(klass, library): model = klass.__lists.get_model() for playlist in os.listdir(PLAYLISTS): try: playlist = Playlist(PLAYLISTS, Playlist.unquote(playlist), library=library) model.append(row=[playlist]) except EnvironmentError: pass library.connect('removed', klass.__removed) library.connect('added', klass.__added) library.connect('changed', klass.__changed)
def test_index(s): pl = Playlist(s.temp, "playlist") songs = s.TWO_SONGS pl.extend(songs) # Just a sanity check... s.failUnlessEqual(1, songs.index(songs[1])) # And now the happy paths.. s.failUnlessEqual(0, pl.index(songs[0])) s.failUnlessEqual(1, pl.index(songs[1])) # ValueError is what we want here try: pl.index(Fakesong({})) s.fail() except ValueError: pass pl.delete()
def test_read(self): pl = Playlist(self.temp, "playlist") pl.extend(NUMERIC_SONGS) pl.write() lib = FileLibrary("foobar") lib.add(NUMERIC_SONGS) pl = Playlist(self.temp, "playlist", lib) self.assertEqual(len(pl), len(NUMERIC_SONGS))
def __add_to_playlist(playlist, songs, parent): if playlist is None: if len(songs) == 1: title = songs[0].comma("title") else: title = ngettext( "%(title)s and %(count)d more", "%(title)s and %(count)d more", len(songs) - 1) % ( {'title': songs[0].comma("title"), 'count': len(songs) - 1}) title = GetPlaylistName(qltk.get_top_parent(parent)).run(title) if title is None: return playlist = Playlist.new(PLAYLISTS, title) playlist.extend(songs) Playlists.changed(playlist)
def _on_new_playlist_activate(self, item, songs): parent = get_menu_item_top_parent(item) if len(songs) == 1: title = songs[0].comma("title") else: title = ngettext( "%(title)s and %(count)d more", "%(title)s and %(count)d more", len(songs) - 1) % { 'title': songs[0].comma("title"), 'count': len(songs) - 1 } title = GetPlaylistName(qltk.get_top_parent(parent)).run(title) if title is None: return playlist = Playlist.new(PLAYLISTS, title) playlist.extend(songs) PlaylistsBrowser.changed(playlist)
def test_rename_working(self): p1 = Playlist.new(self.temp, "Foobar") p1.rename("Foo Quuxly") self.failUnlessEqual(p1.name, "Foo Quuxly") p1.delete()
def test_rename_nothing(self): p1 = Playlist.new(self.temp, "Foobar") self.failUnlessRaises(ValueError, p1.rename, "") p1.delete()
def test_numeric_ops(s): songs = NUMERIC_SONGS pl = Playlist(s.temp, "playlist") pl.extend(songs) s.failUnlessEqual(pl.get("~#length"), 12) s.failUnlessEqual(pl.get("~#length:sum"), 12) s.failUnlessEqual(pl.get("~#length:max"), 7) s.failUnlessEqual(pl.get("~#length:min"), 1) s.failUnlessEqual(pl.get("~#length:avg"), 4) s.failUnlessEqual(pl.get("~#length:foo"), 0) s.failUnlessEqual(pl.get("~#rating:avg"), avg([0.1, 0.3, 0.5])) s.failUnlessEqual(pl.get("~#filesize"), 303) s.failUnlessEqual(pl.get("~#added"), 7) s.failUnlessEqual(pl.get("~#lastplayed"), 88) s.failUnlessEqual(pl.get("~#bitrate"), 200) s.failUnlessEqual(pl.get("~#year"), 33) s.failUnlessEqual(pl.get("~#rating"), 0.3) s.failUnlessEqual(pl.get("~#originalyear"), 2002) pl.delete()
import os import shutil from gi.repository import Gtk from quodlibet.browsers import Browser from quodlibet.library import SongLibrary from quodlibet.plugins.playlist import PlaylistPlugin, PlaylistPluginHandler from quodlibet.util.collection import Playlist from tests import TestCase, mkstemp, mkdtemp from quodlibet.plugins import PluginManager, Plugin from tests.helper import capture_output MAX_PLAYLISTS = 50 TEST_PLAYLIST = Playlist("foo") def generate_playlists(n): return [Playlist("Playlist %d" % x) for x in range(n)] class TPlaylistPlugins(TestCase): class MockBrowser(Browser): def __init__(self): super(TPlaylistPlugins.MockBrowser, self).__init__() self.activated = False def activate(self): self.activated = True
def test_duplicates_single_item(self): pl = Playlist(self.temp, "playlist") pl.append(self.TWO_SONGS[0]) self.failIf(pl.has_duplicates) pl.append(self.TWO_SONGS[0]) self.failUnless(pl.has_duplicates)
def test_playlists_featuring(s): pl = Playlist(s.temp, "playlist") pl.extend(NUMERIC_SONGS) playlists = Playlist.playlists_featuring(NUMERIC_SONGS[0]) s.failUnlessEqual(set(playlists), set([pl])) # Now add a second one, check that instance tracking works pl2 = Playlist(s.temp, "playlist2") pl2.append(NUMERIC_SONGS[0]) playlists = Playlist.playlists_featuring(NUMERIC_SONGS[0]) s.failUnlessEqual(set(playlists), set([pl, pl2])) pl.delete() pl2.delete()
def generate_playlists(n): return [Playlist(DIR, "Playlist %d" % x) for x in range(n)]
def test_internal_tags(s): pl = Playlist(s.temp, "playlist") pl.extend(s.TWO_SONGS) s.failIfEqual(pl.comma("~long-length"), "") s.failIfEqual(pl.comma("~tracks"), "") s.failIfEqual(pl.comma("~discs"), "") s.failUnlessEqual(pl.comma("~foo"), "") s.failUnlessEqual(pl.comma(""), "") s.failUnlessEqual(pl.comma("~"), "") s.failUnlessEqual(pl.get("~#"), "") pl.delete()
def pl(self, name, lib=None) -> Playlist: return Playlist(name, lib)
def test_make(self): p1 = Playlist.new(self.temp, "Does not exist") self.failUnlessEqual(0, len(p1)) self.failUnlessEqual(p1.name, "Does not exist") p1.delete()
def test_equality(s): pl = Playlist(s.temp, "playlist") pl2 = Playlist(s.temp, "playlist") pl3 = Playlist(s.temp2, "playlist") s.failUnlessEqual(pl, pl2) # Debatable s.failUnlessEqual(pl, pl3) pl4 = Playlist(s.temp, "foobar") s.failIfEqual(pl, pl4) pl.delete() pl2.delete() pl3.delete() pl4.delete()
def __new_playlist(self, activator): playlist = Playlist.new(PLAYLISTS) self.__lists.get_model().append(row=[playlist]) self.__select_playlist(playlist)
def test_masked_handling(self): if os.name == "nt": # FIXME: masking isn't properly implemented on Windows return # playlists can contain songs and paths for masked handling.. lib = FileLibrary("foobar") pl = Playlist(self.temp, "playlist", lib) song = Fakesong({"date": "2038", "~filename": fsnative(u"/fake")}) song.sanitize() lib.add([song]) # mask and update lib.mask("/") pl.append(song) pl.remove_songs([song]) self.failUnless("/fake" in pl) pl.extend(self.TWO_SONGS) # check if collections can handle the mix self.failUnlessEqual(pl("date"), "2038") # unmask and update lib.unmask("/") pl.add_songs(["/fake"], lib) self.failUnless(song in pl) pl.delete() lib.destroy()
def _create_temp_playlist_with(cls, name, songs): pl = Playlist.new(PLAYLISTS, name, cls.lib) pl.extend(songs) pl.write()
import os import shutil from gi.repository import Gtk from quodlibet.browsers import Browser from quodlibet.library import SongLibrary from quodlibet.plugins.playlist import PlaylistPlugin, PlaylistPluginHandler from quodlibet.util.collection import Playlist from tests import TestCase, mkstemp, mkdtemp from quodlibet.plugins import PluginManager, Plugin from tests.helper import capture_output MAX_PLAYLISTS = 50 DIR = mkdtemp() TEST_PLAYLIST = Playlist(DIR, "foo") def generate_playlists(n): return [Playlist(DIR, "Playlist %d" % x) for x in range(n)] class TPlaylistPlugins(TestCase): class MockBrowser(Browser): def __init__(self): super(TPlaylistPlugins.MockBrowser, self).__init__() self.activated = False def activate(self): self.activated = True
def test_rename_dup(self): p1 = Playlist.new(self.temp, "Foobar") p2 = Playlist.new(self.temp, "Crazy") self.failUnlessRaises(ValueError, p2.rename, "Foobar") p1.delete() p2.delete()
def __call__(self, key, default=u"", connector=" - ", joiner=', '): """Return the value(s) for a key, synthesizing if necessary. Multiple values for a key are delimited by newlines. A default value may be given (like `dict.get`); the default default is an empty unicode string (even if the tag is numeric). If a tied tag ('a~b') is requested, the `connector` keyword argument may be used to specify what it is tied with. In case the tied tag contains numeric and file path tags, the result will still be a unicode string. The `joiner` keyword specifies how multiple *values* will be joined within that tied tag output, e.g. ~people~title = "Kanye West, Jay Z - New Day" For details on tied tags, see the documentation for `util.tagsplit`. """ if key[:1] == "~": key = key[1:] if "~" in key: real_key = "~" + key values = [] sub_tags = util.tagsplit(real_key) # If it's genuinely a tied tag (not ~~people etc), we want # to delimit the multi-values separately from the tying j = joiner if len(sub_tags) > 1 else "\n" for t in sub_tags: vs = [decode_value(real_key, v) for v in (self.list(t))] v = j.join(vs) if v: values.append(v) return connector.join(values) or default elif key == "#track": try: return int(self["tracknumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "#disc": try: return int(self["discnumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "length": length = self.get("~#length") if length is None: return default else: return util.format_time_display(length) elif key == "#rating": return dict.get(self, "~" + key, config.RATINGS.default) elif key == "rating": return util.format_rating(self("~#rating")) elif key == "people": return "\n".join(self.list_unique(PEOPLE)) or default elif key == "people:real": # Issue 1034: Allow removal of V.A. if others exist. unique = self.list_unique(PEOPLE) # Order is important, for (unlikely case): multiple removals for val in VARIOUS_ARTISTS_VALUES: if len(unique) > 1 and val in unique: unique.remove(val) return "\n".join(unique) or default elif key == "people:roles": return (self._role_call("performer", PEOPLE) or default) elif key == "peoplesort": return ("\n".join(self.list_unique(PEOPLE_SORT)) or self("~people", default, connector)) elif key == "peoplesort:roles": # Ignores non-sort tags if there are any sort tags (e.g. just # returns "B" for {artist=A, performersort=B}). # TODO: figure out the "correct" behavior for mixed sort tags return (self._role_call("performersort", PEOPLE_SORT) or self("~peoplesort", default, connector)) elif key in ("performers", "performer"): return self._prefixvalue("performer") or default elif key in ("performerssort", "performersort"): return (self._prefixvalue("performersort") or self("~" + key[-4:], default, connector)) elif key in ("performers:roles", "performer:roles"): return (self._role_call("performer") or default) elif key in ("performerssort:roles", "performersort:roles"): return (self._role_call("performersort") or self( "~" + key.replace("sort", ""), default, connector)) elif key == "basename": return os.path.basename(self["~filename"]) or self["~filename"] elif key == "dirname": return os.path.dirname(self["~filename"]) or self["~filename"] elif key == "uri": try: return self["~uri"] except KeyError: return fsn2uri(self["~filename"]) elif key == "format": return self.get("~format", str(self.format)) elif key == "codec": codec = self.get("~codec") if codec is None: return self("~format") return codec elif key == "encoding": parts = filter(None, [self.get("~encoding"), self.get("encodedby")]) encoding = u"\n".join(parts) return encoding or default elif key == "language": codes = self.list("language") if not codes: return default return u"\n".join(iso639.translate(c) or c for c in codes) elif key == "bitrate": return util.format_bitrate(self("~#bitrate")) elif key == "#date": date = self.get("date") if date is None: return default return util.date_key(date) elif key == "year": return self.get("date", default)[:4] elif key == "#year": try: return int(self.get("date", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "originalyear": return self.get("originaldate", default)[:4] elif key == "#originalyear": try: return int(self.get("originaldate", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "#tracks": try: return int(self["tracknumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "#discs": try: return int(self["discnumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "lyrics": # First, try the embedded lyrics. try: return self["lyrics"] except KeyError: pass try: return self["unsyncedlyrics"] except KeyError: pass # If there are no embedded lyrics, try to read them from # the external file. try: with open(self.lyric_filename, "rb") as fileobj: print_d("Reading lyrics from %s" % self.lyric_filename) text = fileobj.read().decode("utf-8", "replace") # try to skip binary files if "\0" in text: return default return text except EnvironmentError: return default elif key == "filesize": return util.format_size(self("~#filesize", 0)) elif key == "playlists": # See Issue 876 # Avoid circular references from formats/__init__.py from quodlibet.util.collection import Playlist playlists = Playlist.playlists_featuring(self) return "\n".join(s.name for s in playlists) or default elif key.startswith("#replaygain_"): try: val = self.get(key[1:], default) return round(float(val.split(" ")[0]), 2) except (ValueError, TypeError, AttributeError): return default elif key[:1] == "#": key = "~" + key if key in self: return self[key] elif key in NUMERIC_ZERO_DEFAULT: return 0 else: try: val = self[key[2:]] except KeyError: return default try: return int(val) except ValueError: try: return float(val) except ValueError: return default else: return dict.get(self, "~" + key, default) elif key == "title": title = dict.get(self, "title") if title is None: basename = self("~basename") return "%s [%s]" % (decode_value("~basename", basename), _("Unknown")) else: return title elif key in SORT_TO_TAG: try: return self[key] except KeyError: key = SORT_TO_TAG[key] return dict.get(self, key, default)
def test_remove_no_lib(self): pl = Playlist.new(self._dir, "Foobar") pl.extend(self.SONGS) self.assertTrue(len(pl)) pl.remove_songs(self.SONGS, False) self.assertFalse(len(pl))