class DummySongObject(MediaItem, MediaObject, DBusPropertyFilter, DBusIntrospectable): """ A dummy song object that is not exported on the bus, but supports the usual interfaces. You need to assign a real song before using it, and have to pass a path prefix. The path of the song is /org/gnome/UPnP/MediaServer2/Song/<PREFIX>/SongID This lets us reconstruct the original parent path: /org/gnome/UPnP/MediaServer2/<PREFIX> atm. a prefix can look like "Albums/123456" """ SUPPORTS_MULTIPLE_OBJECT_PATHS = False __pattern = Pattern("<discnumber|<discnumber>.><tracknumber>. <title>") def __init__(self, parent): DBusIntrospectable.__init__(self) DBusPropertyFilter.__init__(self) MediaObject.__init__(self, parent) MediaItem.__init__(self, optional=SUPPORTED_SONG_PROPERTIES) def set_song(self, song, prefix): self.__song = song self.__prefix = prefix def get_property(self, interface, name): if interface == MediaObject.IFACE: if name == "Parent": return BASE_PATH + "/" + self.__prefix elif name == "Type": return "music" elif name == "Path": path = SongObject.PATH path += "/" + self.__prefix + "/" + str(id(self.__song)) return path elif name == "DisplayName": return unival(self.__song.comma("title")) elif interface == MediaItem.IFACE: if name == "URLs": return [self.__song("~uri")] elif name == "MIMEType": mimes = self.__song.mimes return mimes and mimes[0] elif name == "Size": return self.__song("~#filesize") elif name == "Artist": return unival(self.__song.comma("artist")) elif name == "Album": return unival(self.__song.comma("album")) elif name == "Date": return unival(self.__song.comma("date")) elif name == "Genre": return unival(self.__song.comma("genre")) elif name == "Duration": return self.__song("~#length") elif name == "TrackNumber": return self.__song("~#track", 0)
def __save(self, *data): """Save the cover and spawn the program to edit it if selected""" filename = self.name_combo.get_active_text() # Allow support for filename patterns pattern = Pattern(filename) filename = fsencode(pattern.format(self.song)) file_path = os.path.join(self.dirname, filename) msg = (_('The file <b>%s</b> already exists.\n\nOverwrite?') % util.escape(filename)) if (os.path.exists(file_path) and not qltk.ConfirmAction(None, _('File exists'), msg).run()): return try: f = open(file_path, 'wb') f.write(self.current_data) f.close() except IOError: qltk.ErrorMessage(None, _('Saving failed'), _('Unable to save "%s".') % file_path).run() else: if self.open_check.get_active(): try: util.spawn([self.cmd.get_text(), file_path]) except: pass app.window.emit("artwork-changed", [self.song]) self.main_win.destroy()
def __changed_entry(self, entry, cbs, label): text = entry.get_text() if text[0:1] == "<" and text[-1:] == ">": parts = text[1:-1].split("~") for cb in cbs: if parts and parts[0] == cb.tag: parts.pop(0) if parts: for cb in cbs: cb.set_inconsistent(True) else: parts = text[1:-1].split("~") for cb in cbs: cb.set_inconsistent(False) cb.set_active(cb.tag in parts) else: for cb in cbs: cb.set_inconsistent(True) if app.player.info is None: text = _("Not playing") else: text = Pattern(entry.get_text()) % app.player.info label.set_text(text) label.get_parent().set_tooltip_text(text) config.set("plugins", "icon_tooltip", entry.get_text())
def pattern(pat, cap=True, esc=False): """Return a 'natural' version of the pattern string for human-readable bits. Assumes all tags in the pattern are present.""" from quodlibet.parse import Pattern, XMLFromPattern class Fakesong(dict): cap = False def comma(self, key): return " - ".join(self.list(key)) def list(self, key): return [tag(k, self.cap) for k in tagsplit(key)] list_seperate = list __call__ = comma fakesong = Fakesong({'filename': tag('filename', cap)}) fakesong.cap = cap try: p = (esc and XMLFromPattern(pat)) or Pattern(pat) except ValueError: return _("Invalid pattern") return p.format(fakesong)
def __init__(self, *args, **kwargs): super(PatternColumn, self).__init__(*args, **kwargs) try: self._pattern = Pattern(self.header_name) except ValueError: self._pattern = None
def __get_sort_tag(self, tag): replace_order = { "~#track": "", "~#disc": "", "~length": "~#length" } if tag == "~title~version": tag = "title" elif tag == "~album~discsubtitle": tag = "album" if tag.startswith("<"): for key, value in replace_order.iteritems(): tag = tag.replace("<%s>" % key, "<%s>" % value) tag = Pattern(tag).format else: tags = util.tagsplit(tag) sort_tags = [] for tag in tags: tag = replace_order.get(tag, tag) tag = TAG_TO_SORT.get(tag, tag) if tag not in sort_tags: sort_tags.append(tag) if len(sort_tags) > 1: tag = "~" + "~".join(sort_tags) return tag
def get_sort_tag(tag): """Returns a tag that can be used for sorting for the given column tag. Returns '' if the default sort key should be used. """ replace_order = {"~#track": "", "~#disc": "", "~length": "~#length"} if tag == "~title~version": tag = "title" elif tag == "~album~discsubtitle": tag = "album" if tag.startswith("<"): for key, value in replace_order.iteritems(): tag = tag.replace("<%s>" % key, "<%s>" % value) tag = Pattern(tag).format else: tags = util.tagsplit(tag) sort_tags = [] for tag in tags: tag = replace_order.get(tag, tag) tag = TAG_TO_SORT.get(tag, tag) if tag not in sort_tags: sort_tags.append(tag) if len(sort_tags) > 1: tag = "~" + "~".join(sort_tags) return tag
class DummyAlbumObject(MediaContainer, MediaObject, DBusPropertyFilter, DBusIntrospectable): SUPPORTS_MULTIPLE_OBJECT_PATHS = False __pattern = Pattern("<albumartist|<~albumartist~album>|<~artist~album>>") def __init__(self, parent): DBusIntrospectable.__init__(self) DBusPropertyFilter.__init__(self) MediaObject.__init__(self, parent) MediaContainer.__init__(self) self.__song = DummySongObject(self) def get_dummy(self, song): self.__song.set_song(song, "Albums/" + str(id(self.__album))) return self.__song def set_album(self, album): self.__album = album self.PATH = self.parent.PATH + "/" + str(id(album)) def get_property(self, interface, name): if interface == MediaContainer.IFACE: if name == "ChildCount" or name == "ItemCount": return len(self.__album.songs) elif name == "ContainerCount": return 0 elif name == "Searchable": return False elif interface == MediaObject.IFACE: if name == "Parent": return self.parent.PATH elif name == "Type": return "container" elif name == "Path": return self.PATH elif name == "DisplayName": return unival(self.__pattern % self.__album) def list_containers(self, offset, max_, filter_): return [] def list_items(self, offset, max_, filter_): songs = sorted(self.__album.songs, key=lambda s: s.sort_key) dummy = self.get_dummy(None) props = dummy.get_properties_for_filter(MediaItem.IFACE, filter_) end = (max_ and offset + max_) or None result = [] for song in songs[offset:end]: result.append(self.get_dummy(song).get_values(props)) return result list_children = list_items
def test_escape_slash(s): fpat = FileFromPattern('<~filename>') pat = Pattern('<~filename>') wpat = FileFromPattern(r'\\<artist>\\ "<title>') s.assertEquals(fpat.format(s.a), "_path_to_a.mp3") s.assertEquals(pat.format(s.a), "/path/to/a.mp3") if os.name != "nt": s.assertEquals(wpat.format(s.a), "\\Artist\\ \"Title5.mp3") else: # FIXME.. pass
def plugin_on_song_started(self, song): if not self._icon: return if song: try: pattern = Pattern(config.get("plugins", "icon_tooltip")) except (ValueError, config.Error): pattern = self.__pattern tooltip = pattern % song else: tooltip = _("Not playing") self._icon.set_tooltip_markup(util.escape(tooltip))
def print_playing(fstring="<artist~album~tracknumber~title>"): import quodlibet from quodlibet.formats._audio import AudioFile from quodlibet.parse import Pattern from quodlibet import const try: text = open(const.CURRENT, "rb").read() song = AudioFile() song.from_dump(text) print_(Pattern(fstring).format(song)) quodlibet.exit() except (OSError, IOError): print_(_("No song is currently playing.")) quodlibet.exit(True)
def test_tied(s): pat = Pattern('<genre>') s.failUnlessEqual(pat.format_list(s.c), set(['/', '/'])) pat = Pattern('<performer>') s.failUnlessEqual(pat.format_list(s.d), set(['a', 'b'])) pat = Pattern('<performer><performer>') s.failUnlessEqual(set(pat.format_list(s.d)), set(['aa', 'ab', 'ba', 'bb'])) pat = Pattern('<~performer~artist>') s.failUnlessEqual(pat.format_list(s.d), set(['a - foo', 'b - foo', 'a - bar', 'b - bar'])) pat = Pattern('<performer~artist>') s.failUnlessEqual(pat.format_list(s.d), set(['a - foo', 'b - foo', 'a - bar', 'b - bar'])) pat = Pattern('<artist|<artist>.|<performer>>') s.failUnlessEqual(pat.format_list(s.d), set(['foo.', 'bar.'])) pat = Pattern('<artist|<artist|<artist>.|<performer>>>') s.failUnlessEqual(pat.format_list(s.d), set(['foo.', 'bar.']))
def set_all_column_headers(cls, headers): config.set_columns(headers) try: headers.remove("~current") except ValueError: pass cls.headers = headers for listview in cls.instances(): listview.set_column_headers(headers) star = list(Query.STAR) for header in headers: if "<" in header: try: tags = Pattern(header).tags except ValueError: continue else: tags = util.tagsplit(header) for tag in tags: if not tag.startswith("~#") and tag not in star: star.append(tag) SongList.star = star
def test_conditional_unknown(s): pat = Pattern('<album|foo|bar>') s.assertEquals(pat.format(s.a), 'bar')
def test_conditional_genre(s): pat = Pattern('<genre|<genre>|music>') s.assertEquals(pat.format(s.a), 'music') s.assertEquals(pat.format(s.b), 'music') s.assertEquals(pat.format(s.c), '/, /')
def test_conditional_other_other(s): # FIXME: was <tracknumber|a|b|c>.. but we can't put <>| in the format # string since it would break the XML pattern formater. s.assertEqual(Pattern('<tracknumber|a|b|c>').format(s.a), "")
def test_conditional_other_number_dot_title(s): pat = Pattern('<tracknumber|<tracknumber>|00>. <title>') s.assertEquals(pat.format(s.a), '5/6. Title5') s.assertEquals(pat.format(s.b), '6. Title6') s.assertEquals(pat.format(s.c), '00. test/subdir')
def test_missing_value(self): pat = Pattern('<genre> - <artist>') self.assertEqual(pat.format_list(self.a), set([" - Artist"])) pat = Pattern('') self.assertEqual(pat.format_list(self.a), set([""]))
def test_same(s): pat = Pattern('<~basename> <title>') s.failUnlessEqual(pat.format_list(s.a), set([pat.format(s.a)])) pat = Pattern('/a<genre|/<genre>>/<title>') s.failUnlessEqual(pat.format_list(s.a), set([pat.format(s.a)]))
def test_generated(s): pat = Pattern('<~basename>') s.assertEquals(pat.format(s.a), os.path.basename(s.a["~filename"]))
def test_number_dot_title(s): pat = Pattern('<tracknumber>. <title>') s.assertEquals(pat.format(s.a), '5/6. Title5') s.assertEquals(pat.format(s.b), '6. Title6') s.assertEquals(pat.format(s.c), '. test/subdir')
def test_unicode_with_int(s): song = s.AudioFile({"tracknumber": "5/6", "title": "\xe3\x81\x99\xe3\x81\xbf\xe3\x82\x8c".decode('utf-8')}) pat = Pattern('<~#track>. <title>') s.assertEquals(pat.format(song), "5. \xe3\x81\x99\xe3\x81\xbf\xe3\x82\x8c".decode('utf-8'))
def test_number_dot_genre(s): pat = Pattern('<tracknumber>. <genre>') s.assertEquals(pat.format(s.a), '5/6. ') s.assertEquals(pat.format(s.b), '6. ') s.assertEquals(pat.format(s.c), '. /, /')
def test_conditional_notfile(s): pat = Pattern('<tracknumber|<tracknumber>|00>') s.assertEquals(pat.format(s.a), '5/6') s.assertEquals(pat.format(s.b), '6') s.assertEquals(pat.format(s.c), '00')
def test_conditional_subdir(s): pat = Pattern('/a<genre|/<genre>>/<title>') s.assertEquals(pat.format(s.a), '/a/Title5') s.assertEquals(pat.format(s.b), '/a/Title6') s.assertEquals(pat.format(s.c), '/a//, //test/subdir')
def test_same2(s): fpat = FileFromPattern('<~filename>') pat = Pattern('<~filename>') s.assertEquals(fpat.format_list(s.a), set([fpat.format(s.a)])) s.assertEquals(pat.format_list(s.a), set([pat.format(s.a)]))
def test_recnumber_dot_title(s): pat = Pattern('\<<tracknumber>\>. <title>') s.assertEquals(pat.format(s.a), '<5/6>. Title5') s.assertEquals(pat.format(s.b), '<6>. Title6') s.assertEquals(pat.format(s.c), '<>. test/subdir')
def test_empty(self): self.failUnlessEqual(Pattern("").tags, set([]))
def test_generated_and_not_generated(s): pat = Pattern('<~basename> <title>') res = pat.format(s.a) s.assertEquals( res, os.path.basename(s.a["~filename"]) + " " + s.a["title"])
def test_both(self): pat = "<foo|<~bar~fuu> - <fa>|<bar>>" self.failUnlessEqual(Pattern(pat).tags, set(["bar", "fuu", "fa"]))