def test_artwork_url(self): lib = SoundcloudLibrary(self.FakeClient()) lib.query_with_refresh("") s = listvalues(lib._contents)[0] self.failUnlessEqual( s("artwork_url"), "https://i1.sndcdn.com/artworks-000108682375-q4j7y6-t500x500.jpg")
def __invoke(self, librarian, event, *args): args = list(args) if args and args[0]: if isinstance(args[0], dict): args[0] = SongWrapper(args[0]) elif isinstance(args[0], (set, list)): args[0] = ListWrapper(args[0]) for plugin in listvalues(self.__plugins): method_name = 'plugin_on_' + event.replace('-', '_') handler = getattr(plugin, method_name, None) def overridden(obj, name): return name in type(obj).__dict__ if overridden(plugin, method_name): try: handler(*args) except Exception: print_e("Error during %s on %s" % (method_name, type(plugin))) errorhook() if event not in ["removed", "changed"] and args: from quodlibet import app songs = args[0] if not isinstance(songs, (set, list)): songs = [songs] songs = filter(None, songs) check_wrapper_changed(librarian, app.window, songs)
def _delayed_recalc(self): self._timeout = None tv = self.get_tree_view() assert tv is not None range_ = tv.get_visible_range() if not range_: return start, end = range_ start = start[0] end = end[0] # compute the cell width for all drawn cells in range +/- 3 for key, value in listitems(self._texts): if not (start - 3) <= key <= (end + 3): del self._texts[key] elif isinstance(value, string_types): self._texts[key] = self._cell_width(value) # resize if too small or way too big and above the minimum width = self.get_width() needed_width = max([self._get_min_width()] + listvalues(self._texts)) if width < needed_width: self._resize(needed_width) elif width - needed_width >= self._cell_width("0"): self._resize(needed_width)
def rebuild(self, paths, force=False, exclude=[], cofuncid=None): """Reload or remove songs if they have changed or been deleted. This generator rebuilds the library over the course of iteration. Any paths given will be scanned for new files, using the 'scan' method. Only items present in the library when the rebuild is started will be checked. If this function is copooled, set "cofuncid" to enable pause/stop buttons in the UI. """ print_d("Rebuilding, force is %s." % force, self) task = Task(_("Library"), _("Checking mount points")) if cofuncid: task.copool(cofuncid) for i, (point, items) in task.list(enumerate(self._masked.items())): if ismount(point): self._contents.update(items) del(self._masked[point]) self.emit('added', listvalues(items)) yield True task = Task(_("Library"), _("Scanning library")) if cofuncid: task.copool(cofuncid) changed, removed = set(), set() for i, (key, item) in task.list(enumerate(sorted(self.items()))): if key in self._contents and force or not item.valid(): self.reload(item, changed, removed) # These numbers are pretty empirical. We should yield more # often than we emit signals; that way the main loop stays # interactive and doesn't get bogged down in updates. if len(changed) > 100: self.emit('changed', changed) changed = set() if len(removed) > 100: self.emit('removed', removed) removed = set() if len(changed) > 5 or i % 100 == 0: yield True print_d("Removing %d, changing %d." % (len(removed), len(changed)), self) if removed: self.emit('removed', removed) if changed: self.emit('changed', changed) for value in self.scan(paths, exclude, cofuncid): yield value
def rebuild(self, paths, force=False, exclude=[], cofuncid=None): """Reload or remove songs if they have changed or been deleted. This generator rebuilds the library over the course of iteration. Any paths given will be scanned for new files, using the 'scan' method. Only items present in the library when the rebuild is started will be checked. If this function is copooled, set "cofuncid" to enable pause/stop buttons in the UI. """ print_d("Rebuilding, force is %s." % force, self) task = Task(_("Library"), _("Checking mount points")) if cofuncid: task.copool(cofuncid) for i, (point, items) in task.list(enumerate(self._masked.items())): if ismount(point): self._contents.update(items) del (self._masked[point]) self.emit('added', listvalues(items)) yield True task = Task(_("Library"), _("Scanning library")) if cofuncid: task.copool(cofuncid) changed, removed = set(), set() for i, (key, item) in task.list(enumerate(sorted(self.items()))): if key in self._contents and force or not item.valid(): self.reload(item, changed, removed) # These numbers are pretty empirical. We should yield more # often than we emit signals; that way the main loop stays # interactive and doesn't get bogged down in updates. if len(changed) > 100: self.emit('changed', changed) changed = set() if len(removed) > 100: self.emit('removed', removed) removed = set() if len(changed) > 5 or i % 100 == 0: yield True print_d("Removing %d, changing %d." % (len(removed), len(changed)), self) if removed: self.emit('removed', removed) if changed: self.emit('changed', changed) for value in self.scan(paths, exclude, cofuncid): yield value
def __get_albums(self, songs): albums = {} for song in songs: key = song.album_key if key not in albums: albums[key] = [] albums[key].append(song) albums = listvalues(albums) for album in albums: album.sort() return albums
def get_content(self): """Return visible and masked items""" items = listvalues(self) for masked in self._masked.values(): items.extend(masked.values()) # Item keys are often based on filenames, in which case # sorting takes advantage of the filesystem cache when we # reload/rescan the files. items.sort(key=lambda item: item.key) return items
def test_parse(self): lib = self.lib lib.query_with_refresh("dummy search") songs = listvalues(lib._contents) self.failUnlessEqual(len(songs), 1) s = songs[0] self.failUnlessEqual(s("artist"), "Kerstin Eden") self.failUnlessEqual(s("date"), "2015-03-02") self.failUnlessEqual(s("~#download_count"), 2062) self.failUnlessEqual(s("~#playback_count"), 23656) self.failUnlessEqual(s("~#favoritings_count"), 882) self.failUnlessEqual(s("~#rating"), 1.0) self.failUnlessEqual(s("~#playcount"), 4) assert int(s("~#bitrate")) == 319
def test_utils(self): model = CollectionTreeStore() model.set_albums([("~people", 0)], self.albums) a = listvalues(self.albums) a.sort(key=lambda x: x.key) path = model.get_path_for_album(a[0]) albums = model.get_albums_for_path(path) self.failUnless(a[0] in albums) albums = model.get_albums_for_iter(model.get_iter(path)) self.failUnless(a[0] in albums) x = model.get_album(model.get_iter_first()) self.failIf(x) x = model.get_album(model.get_iter(path)) self.failUnlessEqual(x, a[0]) for r in model: self.failUnless(model.get_markup(model.tags, r.iter)) x = list(model.iter_albums(None)) self.assertEqual(set(x), set(a))
def get_masked(self, mount_point): """List of items for a mount point""" return listvalues(self._masked.get(mount_point, {}))
def write(self): with translate_errors(): audio = self.Kind(self['~filename']) if audio.tags is None: audio.add_tags() tag = audio.tags # prefill TMCL with the ones we can't read mcl = tag.get("TMCL", mutagen.id3.TMCL(encoding=3, people=[])) mcl.people = [(r, n) for (r, n) in mcl.people if not self.__validate_name(r)] # delete all TXXX/COMM we can read except empty COMM for frame in ["COMM:", "TXXX:"]: for t in tag.getall(frame + "QuodLibet:"): if t.desc and self.__validate_name(t.desc): del tag[t.HashKey] for key in [ "UFID:http://musicbrainz.org", "TMCL", "POPM:%s" % const.EMAIL, "POPM:%s" % config.get("editing", "save_email") ]: if key in tag: del (tag[key]) for key, id3name in self.SDI.items(): tag.delall(id3name) if key not in self: continue enc = encoding_for(self[key]) Kind = mutagen.id3.Frames[id3name] text = self[key].split("\n") if id3name == "WOAR": for t in text: tag.add(Kind(url=t)) else: tag.add(Kind(encoding=enc, text=text)) dontwrite = ["genre", "comment", "musicbrainz_trackid", "lyrics"] \ + RG_KEYS + listvalues(self.TXXX_MAP) if "musicbrainz_trackid" in self.realkeys(): f = mutagen.id3.UFID( owner="http://musicbrainz.org", data=self["musicbrainz_trackid"].encode("utf-8")) tag.add(f) # Issue 439 - Only write valid ISO 639-2 codes to TLAN (else TXXX) tag.delall("TLAN") if "language" in self: langs = self["language"].split("\n") if all([lang in ISO_639_2 for lang in langs]): # Save value(s) to TLAN tag. Guaranteed to be ASCII here tag.add(mutagen.id3.TLAN(encoding=3, text=langs)) dontwrite.append("language") else: print_d("Not using invalid language code '%s' in TLAN" % self["language"]) # Filter out known keys, and ones set not to write [generically]. keys_to_write = filter(lambda k: not (k in self.SDI or k in dontwrite), self.realkeys()) for key in keys_to_write: enc = encoding_for(self[key]) if key.startswith("performer:"): mcl.people.append([key.split(":", 1)[1], self[key]]) continue f = mutagen.id3.TXXX(encoding=enc, text=self[key].split("\n"), desc=u"QuodLibet::%s" % key) tag.add(f) if mcl.people: tag.add(mcl) if "genre" in self: enc = encoding_for(self["genre"]) t = self["genre"].split("\n") tag.add(mutagen.id3.TCON(encoding=enc, text=t)) else: try: del (tag["TCON"]) except KeyError: pass tag.delall("COMM:") if "comment" in self: enc = encoding_for(self["comment"]) t = self["comment"].split("\n") tag.add( mutagen.id3.COMM(encoding=enc, text=t, desc=u"", lang="\x00\x00\x00")) tag.delall("USLT::\x00\x00\x00") if "lyrics" in self: enc = encoding_for(self["lyrics"]) # lyrics are single string, not array tag.add( mutagen.id3.USLT(encoding=enc, text=self["lyrics"], desc=u"", lang="\x00\x00\x00")) # Delete old foobar replaygain .. for frame in tag.getall("TXXX"): if frame.desc.lower() in RG_KEYS: del tag[frame.HashKey] # .. write new one for k in RG_KEYS: # Add new ones if k in self: value = self[k] tag.add( mutagen.id3.TXXX(encoding=encoding_for(value), text=value.split("\n"), desc=k)) # we shouldn't delete all, but we use unknown ones as fallback, so make # sure they don't come back after reloading for t in tag.getall("RVA2"): if t.channel == 1: del tag[t.HashKey] for k in ["track", "album"]: if ('replaygain_%s_gain' % k) in self: try: gain = float(self["replaygain_%s_gain" % k].split()[0]) except (ValueError, IndexError): gain = 0 try: peak = float(self["replaygain_%s_peak" % k]) except (ValueError, KeyError): peak = 0 # https://github.com/quodlibet/quodlibet/issues/1027 peak = max(min(1.9, peak), 0) gain = max(min(63.9, gain), -64) f = mutagen.id3.RVA2(desc=k, channel=1, gain=gain, peak=peak) tag.add(f) for key in self.TXXX_MAP: try: del (tag["TXXX:" + key]) except KeyError: pass for key in self.PAM_XXXT: if key in self.SDI: # we already write it back using non-TXXX frames continue if key in self: value = self[key] f = mutagen.id3.TXXX(encoding=encoding_for(value), text=value.split("\n"), desc=self.PAM_XXXT[key]) tag.add(f) if (config.getboolean("editing", "save_to_songs") and (self.has_rating or self.get("~#playcount", 0) != 0)): email = config.get("editing", "save_email").strip() email = email or const.EMAIL t = mutagen.id3.POPM(email=email, rating=int(255 * self("~#rating")), count=self.get("~#playcount", 0)) tag.add(t) with translate_errors(): audio.save() self.sanitize()
def write(self): with translate_errors(): audio = self.Kind(self['~filename']) if audio.tags is None: audio.add_tags() tag = audio.tags # prefill TMCL with the ones we can't read mcl = tag.get("TMCL", mutagen.id3.TMCL(encoding=3, people=[])) mcl.people = [(r, n) for (r, n) in mcl.people if not self.__validate_name(r)] # delete all TXXX/COMM we can read except empty COMM for frame in ["COMM:", "TXXX:"]: for t in tag.getall(frame + "QuodLibet:"): if t.desc and self.__validate_name(t.desc): del tag[t.HashKey] for key in ["UFID:http://musicbrainz.org", "TMCL", "POPM:%s" % const.EMAIL, "POPM:%s" % config.get("editing", "save_email")]: if key in tag: del(tag[key]) for key, id3name in self.SDI.items(): tag.delall(id3name) if key not in self: continue enc = encoding_for(self[key]) Kind = mutagen.id3.Frames[id3name] text = self[key].split("\n") if id3name == "WOAR": for t in text: tag.add(Kind(url=t)) else: tag.add(Kind(encoding=enc, text=text)) dontwrite = ["genre", "comment", "musicbrainz_trackid", "lyrics"] \ + RG_KEYS + listvalues(self.TXXX_MAP) if "musicbrainz_trackid" in self.realkeys(): f = mutagen.id3.UFID( owner="http://musicbrainz.org", data=self["musicbrainz_trackid"].encode("utf-8")) tag.add(f) # Issue 439 - Only write valid ISO 639-2 codes to TLAN (else TXXX) tag.delall("TLAN") if "language" in self: langs = self["language"].split("\n") if all([lang in ISO_639_2 for lang in langs]): # Save value(s) to TLAN tag. Guaranteed to be ASCII here tag.add(mutagen.id3.TLAN(encoding=3, text=langs)) dontwrite.append("language") else: print_d("Not using invalid language code '%s' in TLAN" % self["language"]) # Filter out known keys, and ones set not to write [generically]. keys_to_write = filter(lambda k: not (k in self.SDI or k in dontwrite), self.realkeys()) for key in keys_to_write: enc = encoding_for(self[key]) if key.startswith("performer:"): mcl.people.append([key.split(":", 1)[1], self[key]]) continue f = mutagen.id3.TXXX( encoding=enc, text=self[key].split("\n"), desc=u"QuodLibet::%s" % key) tag.add(f) if mcl.people: tag.add(mcl) if "genre" in self: enc = encoding_for(self["genre"]) t = self["genre"].split("\n") tag.add(mutagen.id3.TCON(encoding=enc, text=t)) else: try: del(tag["TCON"]) except KeyError: pass tag.delall("COMM:") if "comment" in self: enc = encoding_for(self["comment"]) t = self["comment"].split("\n") tag.add(mutagen.id3.COMM(encoding=enc, text=t, desc=u"", lang="\x00\x00\x00")) tag.delall("USLT::\x00\x00\x00") if "lyrics" in self: enc = encoding_for(self["lyrics"]) # lyrics are single string, not array tag.add(mutagen.id3.USLT(encoding=enc, text=self["lyrics"], desc=u"", lang="\x00\x00\x00")) # Delete old foobar replaygain .. for frame in tag.getall("TXXX"): if frame.desc.lower() in RG_KEYS: del tag[frame.HashKey] # .. write new one for k in RG_KEYS: # Add new ones if k in self: value = self[k] tag.add(mutagen.id3.TXXX(encoding=encoding_for(value), text=value.split("\n"), desc=k)) # we shouldn't delete all, but we use unknown ones as fallback, so make # sure they don't come back after reloading for t in tag.getall("RVA2"): if t.channel == 1: del tag[t.HashKey] for k in ["track", "album"]: if ('replaygain_%s_gain' % k) in self: try: gain = float(self["replaygain_%s_gain" % k].split()[0]) except ValueError: gain = 0 try: peak = float(self["replaygain_%s_peak" % k]) except (ValueError, KeyError): peak = 0 # https://github.com/quodlibet/quodlibet/issues/1027 peak = max(min(1.9, peak), 0) gain = max(min(63.9, gain), -64) f = mutagen.id3.RVA2(desc=k, channel=1, gain=gain, peak=peak) tag.add(f) for key in self.TXXX_MAP: try: del(tag["TXXX:" + key]) except KeyError: pass for key in self.PAM_XXXT: if key in self.SDI: # we already write it back using non-TXXX frames continue if key in self: value = self[key] f = mutagen.id3.TXXX(encoding=encoding_for(value), text=value.split("\n"), desc=self.PAM_XXXT[key]) tag.add(f) if (config.getboolean("editing", "save_to_songs") and (self.has_rating or self.get("~#playcount", 0) != 0)): email = config.get("editing", "save_email").strip() email = email or const.EMAIL t = mutagen.id3.POPM(email=email, rating=int(255 * self("~#rating")), count=self.get("~#playcount", 0)) tag.add(t) with translate_errors(): audio.save() self.sanitize()
def get_content(self): """All items including hidden ones for saving the library (see FileLibrary with masked items) """ return listvalues(self)