def __tag(self, node, scope, qscope, tags, queries, text_formatter): text = [] if isinstance(node, TextNode): if text_formatter: text.append("a(_format(%r))" % node.text) else: text.append("a(%r)" % node.text) elif isinstance(node, ConditionNode): var = self.__get_query(text, scope, qscope, node.expr, queries) ic = self.__tag(node.ifcase, dict(scope), dict(qscope), tags, queries, text_formatter) ec = self.__tag(node.elsecase, dict(scope), dict(qscope), tags, queries, text_formatter) if not ic and not ec: text.pop(-1) elif ic: text.append("if %s:" % var) text.extend(ic) if ec: text.append("else:") text.extend(ec) else: text.append("if not %s:" % var) text.extend(ec) elif isinstance(node, TagNode): tags.extend(util.tagsplit(node.tag)) var = self.__get_value(text, scope, node.tag) text.append("a(%s)" % var) elif isinstance(node, PatternNode): for child in node.children: for line in self.__tag(child, scope, qscope, tags, queries, text_formatter): text.append(" " + line) return text
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 Menu(self, header, browser, library): songs = self.get_selected_songs() if not songs: return can_filter = browser.can_filter menu = browser.Menu(songs, self, library) def Filter(t): # Translators: The substituted string is the name of the # selected column (a translated tag name). b = qltk.MenuItem( _("_Filter on %s") % util.tag(t, True), gtk.STOCK_INDEX) b.connect_object('activate', self.__filter_on, t, songs, browser) return b header = util.tagsplit(header)[0] if can_filter("artist") or can_filter("album") or can_filter(header): menu.preseparate() if can_filter("artist"): menu.prepend(Filter("artist")) if can_filter("album"): menu.prepend(Filter("album")) if (header not in ["artist", "album"] and can_filter(header)): menu.prepend(Filter(header)) ratings = RatingsMenuItem(songs, library) menu.preseparate() menu.prepend(ratings) menu.show_all() return menu
def __tag(self, node, scope, qscope, tags, queries): text = [] if isinstance(node, TextNode): text.append('a(%r)' % node.text) elif isinstance(node, ConditionNode): var = self.__get_query(text, scope, qscope, node.expr, queries) ic = self.__tag(node.ifcase, dict(scope), dict(qscope), tags, queries) ec = self.__tag(node.elsecase, dict(scope), dict(qscope), tags, queries) if not ic and not ec: text.pop(-1) elif ic: text.append('if %s:' % var) text.extend(ic) if ec: text.append('else:') text.extend(ec) else: text.append('if not %s:' % var) text.extend(ec) elif isinstance(node, TagNode): tags.extend(util.tagsplit(node.tag)) var = self.__get_value(text, scope, node.tag) text.append('a(%s)' % var) elif isinstance(node, PatternNode): for child in node.children: for line in self.__tag(child, scope, qscope, tags, queries): text.append(" " + line) return text
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 "<" in tag: for key, value in replace_order.iteritems(): tag = tag.replace("<%s>" % key, "<%s>" % value) for key, value in TAG_TO_SORT.iteritems(): tag = tag.replace("<%s>" % key, "<{1}|<{1}>|<{0}>>".format(key, 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 Menu(self, header, browser, library): songs = self.get_selected_songs() if not songs: return can_filter = browser.can_filter menu = browser.Menu(songs, self, library) def Filter(t): # Translators: The substituted string is the name of the # selected column (a translated tag name). b = qltk.MenuItem( _("_Filter on %s") % util.tag(t, True), Gtk.STOCK_INDEX) b.connect_object('activate', self.__filter_on, t, songs, browser) return b header = util.tagsplit(header)[0] if can_filter("artist") or can_filter("album") or can_filter(header): menu.preseparate() if can_filter("artist"): menu.prepend(Filter("artist")) if can_filter("album"): menu.prepend(Filter("album")) if (header not in ["artist", "album"] and can_filter(header)): menu.prepend(Filter(header)) ratings = RatingsMenuItem(songs, library) menu.preseparate() menu.prepend(ratings) menu.show_all() return menu
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
def __init__(self, row_pattern): parts = [p.replace(r"\:", ":") for p in (re.split(r"(?<!\\):", row_pattern))] is_numeric = lambda s: s[:2] == "~#" and "~" not in s[2:] is_pattern = lambda s: '<' in s f_round = lambda s: (isinstance(s, float) and "%.2f" % s) or s def is_date(s): return s in TIME_TAGS disp = parts[1] if len( parts) >= 2 else r"[i][span alpha='40%']<~#tracks>[/span][/i]" cat = parts[0] if is_pattern(cat): title = util.pattern(cat, esc=True, markup=True) try: pc = XMLFromPattern(cat) except ValueError: pc = XMLFromPattern("") tags = pc.tags format = pc.format_list has_markup = True else: title = util.tag(cat) tags = util.tagsplit(cat) has_markup = False if is_date(cat): def format(song: AudioFile) -> List[Tuple[Text, Text]]: fmt = config.gettext("settings", "datecolumn_timestamp_format") date_str = format_date(song(cat), fmt) return [(date_str, date_str)] elif is_numeric(cat): def format(song: AudioFile) -> List[Tuple[Text, Text]]: v = str(f_round(song(cat))) return [(v, v)] else: def format(song: AudioFile) -> List[Tuple[Text, Text]]: return song.list_separate(cat) if is_pattern(disp): try: pd = XMLFromPattern(disp) except ValueError: pd = XMLFromPattern("") format_display = pd.format else: if is_numeric(disp): format_display = lambda coll: str(f_round(coll(disp))) else: format_display = lambda coll: util.escape(coll.comma(disp)) self.title = title self.tags = set(tags) self.format = format self.format_display = format_display self.has_markup = has_markup
def __cols_changed(self, view, browser): for header in view.get_columns(): tag = header.header_name for t in util.tagsplit(tag): if t in browser.headers: header.set_visible(True) break else: header.set_visible(False)
def header_tag_split(header): """Split a pattern or a tied tag into separate tags""" if "<" in header: try: return list(Pattern(header).tags) except ValueError: return [] else: return util.tagsplit(header)
def get(self, key, default="", connector=" - "): if key[:1] == "~" and '~' in key[1:]: return connector.join(map(self.get, util.tagsplit(key))) elif key[:1] == "~" and key[-4:-3] == ":": func = key[-3:] key = key[:-4] return "%s<%s>" % (util.tag(key), func) elif key in self: return self[key] return util.tag(key)
def list_separate(self, key): """For tied tags return the list union of the display,sort values otherwise just do list_sort """ if key[:1] == "~" and "~" in key[1:]: # tied tag vals = [self.list_sort(tag) for tag in util.tagsplit(key)] r = [j for i in vals for j in i] return r else: return self.list_sort(key)
def __hide_headers(self, activator=None): for column in self.songlist.get_columns(): if self.browser.headers is None: column.set_visible(True) else: for tag in util.tagsplit(column.header_name): if tag in self.browser.headers: column.set_visible(True) break else: column.set_visible(False)
def __init__(self, row_pattern): parts = re.split(r"(?<!\\):", row_pattern) parts = list(map(lambda p: p.replace(r"\:", ":"), parts)) is_numeric = lambda s: s[:2] == "~#" and "~" not in s[2:] is_pattern = lambda s: '<' in s f_round = lambda s: (isinstance(s, float) and "%.2f" % s) or s disp = (len(parts) >= 2 and parts[1]) or r"[i](<~#tracks>)[/i]" cat = parts[0] if is_pattern(cat): title = util.pattern(cat, esc=True, markup=True) try: pc = XMLFromPattern(cat) except ValueError: pc = XMLFromPattern("") tags = pc.tags format = pc.format_list has_markup = True else: title = util.tag(cat) tags = util.tagsplit(cat) has_markup = False if is_numeric(cat): def format(song): v = text_type(f_round(song(cat))) return [(v, v)] else: format = lambda song: song.list_separate(cat) if is_pattern(disp): try: pd = XMLFromPattern(disp) except ValueError: pd = XMLFromPattern("") format_display = pd.format else: if is_numeric(disp): format_display = lambda coll: text_type(f_round(coll(disp))) else: format_display = lambda coll: util.escape(coll.comma(disp)) self.title = title self.tags = set(tags) self.format = format self.format_display = format_display self.has_markup = has_markup
def list_separate(self, key, connector=" - "): """Similar to list, but will return a list of all combinations for tied tags instead of one comma separated string""" if key[:1] == "~" and "~" in key[1:]: vals = \ filter(None, map(lambda x: isinstance(x, basestring) and x or str(x), map(lambda x: (isinstance(x, float) and "%.2f" % x) or x, (self(tag) for tag in util.tagsplit(key))))) vals = (val.split("\n") for val in vals) r = [[]] for x in vals: r = [i + [y] for y in x for i in r] return map(connector.join, r) else: return self.list(key)
def get(self, key, default=u"", connector=u" - "): if not self.songs: return default if key[:1] == "~" and "~" in key[1:]: if not isinstance(default, basestring): return default keys = util.tagsplit(key) v = map(self.__get_cached_value, keys) def default_funct(x): if x is None: return default return x v = map(default_funct , v) v = map(lambda x: (isinstance(x, float) and "%.2f" % x) or x, v) v = map(lambda x: isinstance(x, basestring) and x or str(x), v) return connector.join(filter(None, v)) or default else: value = self.__get_cached_value(key) if value is None: return default return value
def list_separate(self, key, connector=" - "): """Similar to list, but will return a list of all combinations for tied tags instead of one comma separated string. In case of tied tags the result will be unicode, otherwise it returns the same as list() """ if key[:1] == "~" and "~" in key[1:]: vals = [] for v in map(self.__call__, util.tagsplit(key)): v = decode_value(key, v) if v: vals.append(v.split("\n")) r = [[]] for x in vals: r = [i + [y] for y in x for i in r] return map(connector.join, r) else: return self.list(key)
def set_all_column_headers(cls, headers): config.set("settings", "headers", " ".join(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 get(self, key, default=u"", connector=u" - "): if not self.songs: return default if key[:1] == "~" and "~" in key[1:]: if not isinstance(default, basestring): return default keys = util.tagsplit(key) v = map(self.__get_cached_value, keys) def default_funct(x): if x is None: return default return x v = map(default_funct, v) v = map(lambda x: (isinstance(x, float) and "%.2f" % x) or x, v) v = map(lambda x: isinstance(x, basestring) and x or str(x), v) return connector.join(filter(None, v)) or default else: value = self.__get_cached_value(key) if value is None: return default return value
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 __tag(self, node, scope, tags): text = [] if isinstance(node, TextNode): text.append('a("%s")' % self.__escape(node.text)) elif isinstance(node, ConditionNode): tag = self.__put_tag(text, scope, node.tag) ic = self.__pattern(node.ifcase, dict(scope), tags) ec = self.__pattern(node.elsecase, dict(scope), tags) if not ic and not ec: text.pop(-1) elif ic: text.append('if %s:' % scope[tag]) text.extend(ic) if ec: text.append('else:') text.extend(ec) else: text.append('if not %s:' % scope[tag]) text.extend(ec) elif isinstance(node, TagNode): tags.update(util.tagsplit(node.tag)) tag = self.__put_tag(text, scope, node.tag) text.append('a(%s)' % scope[tag]) return text
def __tag(self, node, scope, tags): text = [] if isinstance(node, TextNode): text.append('a("%s")' % self.__escape(node.text)) elif isinstance(node, ConditionNode): tag = self.__put_tag(text, scope, node.tag) ic = self.__pattern(node.ifcase, dict(scope), tags) ec = self.__pattern(node.elsecase, dict(scope), tags) if not ic and not ec: text.pop(-1) elif ic: text.append('if %s:' % scope[tag]) text.extend(ic) if ec: text.append('else:') text.extend(ec) else: text.append('if not %s:' % scope[tag]) text.extend(ec) elif isinstance(node, TagNode): tags.extend(util.tagsplit(node.tag)) tag = self.__put_tag(text, scope, node.tag) text.append('a(%s)' % scope[tag]) return text
def test_two_prefix(self): self.failUnlessEqual(util.tagsplit("~foo~bar"), ["foo", "bar"])
def test_synth_tag(self): self.failUnlessEqual(util.tagsplit("~foo"), ["~foo"])
def test_single_tag(self): self.failUnlessEqual(util.tagsplit("foo"), ["foo"])
def test_synth(self): self.failUnlessEqual(util.tagsplit("~foo~~bar"), ["foo", "~bar"])
def __call__(self, key, default: Any = 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": encoding = "\n".join( part for part in [self.get("~encoding"), self.get("encodedby")] if part) 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. lyric_filename = self.lyric_filename if not lyric_filename: return default try: with open(lyric_filename, "rb") as fileobj: print_d(f"Reading lyrics from {lyric_filename!r}") text = fileobj.read().decode("utf-8", "replace") # try to skip binary files if "\0" in text: return default return text except (EnvironmentError, UnicodeDecodeError): return default elif key == "filesize": return util.format_size(self("~#filesize", 0)) elif key == "playlists": # TODO: avoid static dependency here... somehow from quodlibet import app lib = app.library if not lib: return "" playlists = lib.playlists.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: # build a title with missing_title_template option unknown_track_template = _( config.gettext("browsers", "missing_title_template")) from quodlibet.pattern import Pattern try: pattern = Pattern(unknown_track_template) except ValueError: title = decode_value("~basename", self("~basename")) else: title = pattern % self 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_two_synth_start(self): self.failUnlessEqual(util.tagsplit("~~people~album"), ["~people", "album"])
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", text_type(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[key] except KeyError: pass # If there are no embedded lyrics, try to read them from # the external file. try: fileobj = open(self.lyric_filename, "rU") except EnvironmentError: return default else: return fileobj.read().decode("utf-8", "replace") 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_two_numeric(self): self.failUnlessEqual(util.tagsplit("~#foo~~#bar"), ["~#foo", "~#bar"])
def __call__(self, key, default=u"", connector=" - "): """Return a key, synthesizing it if necessary. 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. For details on tied tags, see the documentation for util.tagsplit. """ if key[:1] == "~": key = key[1:] if "~" in key: real_key = "~" + key values = [] for v in map(self.__call__, util.tagsplit(real_key)): v = decode_value(real_key, v) 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 text_type(fsn2uri(self["~filename"])) elif key == "format": return self.get("~format", 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[key] except KeyError: pass # If there are no embedded lyrics, try to read them from # the external file. try: fileobj = open(self.lyric_filename, "rU") except EnvironmentError: return default else: return fileobj.read().decode("utf-8", "replace") 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 __call__(self, key, default=u"", connector=" - "): """Return a key, synthesizing it if necessary. 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. For details on tied tags, see the documentation for util.tagsplit.""" if key[:1] == "~": key = key[1:] if "~" in key: # FIXME: decode ~filename etc. if not isinstance(default, basestring): return default return connector.join( filter( None, map( lambda x: isinstance(x, basestring) and x or str(x ), map( lambda x: (isinstance(x, float) and "%.2f" % x) or x, map(self.__call__, util.tagsplit("~" + key)))))) 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 URI.frompath(self["~filename"]) elif key == "format": return self.get("~format", self.format) 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": try: fileobj = file(self.lyric_filename, "rU") except EnvironmentError: return default else: return fileobj.read().decode("utf-8", "replace") 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 INTERN_NUM_DEFAULT: return dict.get(self, key, 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") basename = basename.decode(const.FSCODING, "replace") return "%s [%s]" % (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_two_synth_start(self): self.failUnlessEqual( util.tagsplit("~~people~album"), ["~people", "album"])
def __call__(self, key, default=u"", connector=" - "): """Return a key, synthesizing it if necessary. 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. For details on tied tags, see the documentation for util.tagsplit. """ if key[:1] == "~": key = key[1:] if "~" in key: real_key = "~" + key values = [] for v in map(self.__call__, util.tagsplit(real_key)): v = decode_value(real_key, v) 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 URI.frompath(self["~filename"]) elif key == "format": return self.get("~format", self.format) 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": try: fileobj = file(self.lyric_filename, "rU") except EnvironmentError: return default else: return fileobj.read().decode("utf-8", "replace") 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 INTERN_NUM_DEFAULT: return dict.get(self, key, 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 __call__(self, key, default=u"", connector=" - "): """Return a key, synthesizing it if necessary. 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. For details on tied tags, see the documentation for util.tagsplit.""" if key[:1] == "~": key = key[1:] if "~" in key: # FIXME: decode ~filename etc. if not isinstance(default, basestring): return default return connector.join( filter(None, map(lambda x: isinstance(x, basestring) and x or str(x), map(lambda x: (isinstance(x, float) and "%.2f" % x) or x, map(self.__call__, util.tagsplit("~" + key)))))) 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(length) elif key == "#rating": return dict.get(self, "~" + key, const.DEFAULT_RATING) elif key == "rating": return util.format_rating(self("~#rating")) elif key == "people": join = "\n".join people = filter(None, map(self.__call__, PEOPLE)) if not people: return default people = join(people).split("\n") index = people.index return join([person for (i, person) in enumerate(people) if index(person) == i]) elif key == "peoplesort": join = "\n".join people = filter(None, map(self.__call__, PEOPLE_SORT)) people = join(people).split("\n") index = people.index return (join([person for (i, person) in enumerate(people) if index(person) == i]) or self("~people", default, connector)) elif key == "performers" or key == "performer": performers = {} for key in self.keys(): if key.startswith("performer:"): role = key.split(":", 1)[1] for value in self.list(key): try: performers[str(value)] except: performers[str(value)] = [] performers[str(value)].append(util.title(role)) values = [] if len(performers) > 0: for performer in performers: roles = '' i = 0 for role in performers[performer]: if i > 0: roles += ', ' roles += role i += 1 values.append("%s (%s)" % (performer, roles)) values.extend(self.list("performer")) if not values: return default return "\n".join(values) elif key == "performerssort" or key == "performersort": values = [] for key in self.keys(): if key.startswith("performersort:"): role = key.split(":", 1)[1] for value in self.list(key): values.append("%s (%s)" % (value, role)) values.extend(self.list("performersort")) return ("\n".join(values) or self("~performers", 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 URI.frompath(self["~filename"]) elif key == "format": return self.get("~format", self.format) 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": try: fileobj = file(self.lyric_filename, "rU") except EnvironmentError: return default else: return fileobj.read().decode("utf-8", "replace") elif key == "playlists": # See Issue 876 # Avoid circular references from formats/__init__.py from quodlibet.util.collection import Playlist try: start = time.time() playlists = Playlist.playlists_featuring(self) import random if not random.randint(0, 1000): print_d("A sample song('~playlists') call: took %d μs " % (1E6 * (time.time() - start))) return "\n".join([s.name for s in playlists]) except KeyError: return 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: self[key] elif key in INTERN_NUM_DEFAULT: return dict.get(self, key, 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") basename = basename.decode(const.FSCODING, "replace") return "%s [%s]" % (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)