def step(self, **values): """Advance the counter by one. Arguments are applied to the originally-supplied text as a format string. This function doesn't return if the dialog is paused (though the GTK main loop will still run), and returns True if stop was pressed. """ if self.count: self.current += 1 self._progress.set_fraction( max(0, min(1, self.current / float(self.count)))) else: self._progress.pulse() values.setdefault("total", format_int_locale(self.count)) values.setdefault("current", format_int_locale(self.current)) if self.count: t = (time.time() - self._start_time) / self.current remaining = math.ceil((self.count - self.current) * t) values.setdefault("remaining", format_time_display(remaining)) self._label.set_markup(self._text % values) while not self.quit and (self.paused or Gtk.events_pending()): Gtk.main_iteration() return self.quit
def _cdf(self, column, cell, model, iter_, user_data): value = model.get_value(iter_).get("~#length", 0) if not self._needs_update(value): return text = util.format_time_display(value) cell.set_property('text', text) self._recalc_width(model.get_path(iter_), text)
def set_time(self, time_): """Set the time in seconds""" self._last_time = time_ if self._disabled: return self.set_text(util.format_time_display(time_))
def step(self, **values): """Advance the counter by one. Arguments are applied to the originally-supplied text as a format string. This function doesn't return if the dialog is paused (though the GTK main loop will still run), and returns True if stop was pressed. """ if self.count: self.current += 1 self._progress.set_fraction( max(0, min(1, self.current / float(self.count)))) else: self._progress.pulse() values.setdefault("total", self.count) values.setdefault("current", self.current) if self.count: t = (time.time() - self._start_time) / self.current remaining = math.ceil((self.count - self.current) * t) values.setdefault("remaining", util.format_time_display(remaining)) self._label.set_markup(self._text % values) while not self.quit and (self.paused or Gtk.events_pending()): Gtk.main_iteration() return self.quit
class Preferences(qltk.UniqueWindow, EditDisplayPatternMixin): _DEFAULT_PATTERN = DEFAULT_PATTERN_TEXT _PREVIEW_ITEM = FakeDisplayItem({ "date": "2010-10-31", "~length": util.format_time_display(6319), "~long-length": util.format_time_long(6319), "~tracks": numeric_phrase("%d track", "%d tracks", 5), "~discs": numeric_phrase("%d disc", "%d discs", 2), "~#rating": 0.75, "album": _("An Example Album"), "~people": _SOME_PEOPLE + "..."}) def __init__(self, browser): if self.is_not_unique(): return super(Preferences, self).__init__() self.set_border_width(12) self.set_title(_("Album List Preferences")) self.set_default_size(420, 380) self.set_transient_for(qltk.get_top_parent(browser)) # Do this config-driven setup at instance-time self._PREVIEW_ITEM["~rating"] = format_rating(0.75) box = Gtk.VBox(spacing=6) vbox = Gtk.VBox(spacing=6) cb = ConfigCheckButton( _("Show album _covers"), "browsers", "album_covers") cb.set_active(config.getboolean("browsers", "album_covers")) cb.connect('toggled', lambda s: browser.toggle_covers()) vbox.pack_start(cb, False, True, 0) cb = ConfigCheckButton( _("Inline _search includes people"), "browsers", "album_substrings") cb.set_active(config.getboolean("browsers", "album_substrings")) vbox.pack_start(cb, False, True, 0) f = qltk.Frame(_("Options"), child=vbox) box.pack_start(f, False, True, 12) display_frame = self.edit_display_pane(browser, _("Album Display")) box.pack_start(display_frame, True, True, 0) main_box = Gtk.VBox(spacing=12) close = Button(_("_Close"), Icons.WINDOW_CLOSE) close.connect('clicked', lambda *x: self.destroy()) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(close, True, True, 0) main_box.pack_start(box, True, True, 0) self.use_header_bar() if not self.has_close_button(): main_box.pack_start(b, False, True, 0) self.add(main_box) close.grab_focus() self.show_all()
class Preferences(qltk.UniqueWindow, EditDisplayPatternMixin): _A_SIZE = 127 * 1024 * 1024 _SOME_PEOPLE = "\n".join( tag(t) for t in ["artist", "performer", "composer", "arranger"]) _DEFAULT_PATTERN = DEFAULT_PATTERN_TEXT _PREVIEW_ITEM = FakeDisplayItem({ "date": "2015-11-31", "~length": format_time_display(6319), "~long-length": format_time_long(6319), "~tracks": numeric_phrase("%d track", "%d tracks", 27), "~#filesize": _A_SIZE, "~filesize": format_size(_A_SIZE), "~#rating": 0.75, "~name": _("Example Playlist"), "~people": _SOME_PEOPLE + "..." }) def __init__(self, browser): if self.is_not_unique(): return super(Preferences, self).__init__() self.set_border_width(12) self.set_title(_("Playlist Browser Preferences")) self.set_default_size(420, 240) self.set_transient_for(qltk.get_top_parent(browser)) box = Gtk.VBox(spacing=6) edit_frame = self.edit_display_pane(browser, _("Playlist display")) box.pack_start(edit_frame, False, True, 12) main_box = Gtk.VBox(spacing=12) close = Button(_("_Close"), Icons.WINDOW_CLOSE) close.connect('clicked', lambda *x: self.destroy()) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(close, True, True, 0) main_box.pack_start(box, True, True, 0) self.use_header_bar() if not self.has_close_button(): main_box.pack_start(b, False, True, 0) self.add(main_box) close.grab_focus() self.show_all()
def __update_count(self, model, path, lab): if len(model) == 0: text = "" else: time = sum([row[0].get("~#length", 0) for row in model]) text = ngettext("%(count)d song (%(time)s)", "%(count)d songs (%(time)s)", len(model)) % { "count": len(model), "time": util.format_time_display(time)} lab.set_text(text)
def _resize_labels(self, song): """Resize the labels to make sure there is enough space to display the length of the songs. This prevents the waveform from changing size when the position changes from 9:59 to 10:00 for example.""" length = util.format_time_display(song("~#length")) # Get the width needed to display the length of the song (the text # displayed in the labels will always be shorter than that) layout = self._remaining_label.get_layout() layout.set_text(length, -1) width, height = layout.get_pixel_size() # Set it as the minimum width of the labels to prevent them from # changing width self._remaining_label.set_size_request(width, -1) self._elapsed_label.set_size_request(width, -1)
class Preferences(qltk.UniqueWindow, EditDisplayPatternMixin): _DEFAULT_PATTERN = DEFAULT_PATTERN_TEXT _PREVIEW_ITEM = FakeDisplayItem({ "date": "2010-10-31", "~length": util.format_time_display(6319), "~long-length": util.format_time_long(6319), "~tracks": numeric_phrase("%d track", "%d tracks", 5), "~discs": numeric_phrase("%d disc", "%d discs", 2), "~#rating": 0.75, "album": _("An Example Album"), "~people": _SOME_PEOPLE + "..." }) def __init__(self, browser): if self.is_not_unique(): return super().__init__() self.set_border_width(12) self.set_title(_("Cover Grid Preferences")) self.set_default_size(420, 380) self.set_transient_for(qltk.get_top_parent(browser)) # Do this config-driven setup at instance-time self._PREVIEW_ITEM["~rating"] = format_rating(0.75) self.mag_lock = False box = Gtk.VBox(spacing=6) vbox = Gtk.VBox(spacing=6) cb = ConfigCheckButton(_("Show album _text"), "browsers", "album_text") cb.set_active(config.getboolean("browsers", "album_text")) cb.connect('toggled', lambda s: browser.toggle_text()) vbox.pack_start(cb, False, True, 0) cb2 = ConfigCheckButton(_("Show \"All Albums\" Item"), "browsers", "covergrid_all") cb2.set_active(config.getboolean("browsers", "covergrid_all", True)) def refilter(s): mod = browser.view.get_model() if mod: mod.refilter() cb2.connect('toggled', refilter) vbox.pack_start(cb2, False, True, 0) cb3 = ConfigCheckButton(_("Wide Mode"), "browsers", "covergrid_wide") cb3.set_active(config.getboolean("browsers", "covergrid_wide", False)) cb3.connect('toggled', lambda s: browser.toggle_wide()) vbox.pack_start(cb3, False, True, 0) # Redraws the covers only when the user releases the slider def mag_button_press(*_): self.mag_lock = True def mag_button_release(mag, _): self.mag_lock = False mag_changed(mag) def mag_changed(mag): if self.mag_lock: return newmag = mag.get_value() oldmag = config.getfloat("browsers", "covergrid_magnification", 3.) if newmag == oldmag: print_d("Covergrid magnification haven't changed: {0}".format( newmag)) return print_d('Covergrid magnification update from {0} to {1}'.format( oldmag, newmag)) config.set("browsers", "covergrid_magnification", mag.get_value()) browser.update_mag() mag_scale = Gtk.HScale(adjustment=Gtk.Adjustment.new( config.getfloat("browsers", "covergrid_magnification", 3), 0., 10., .5, .5, 0)) mag_scale.set_tooltip_text(_("Cover Magnification")) l = Gtk.Label(label=_("Cover Magnification")) mag_scale.set_value_pos(Gtk.PositionType.RIGHT) mag_scale.connect('button-press-event', mag_button_press) mag_scale.connect('button-release-event', mag_button_release) mag_scale.connect('value-changed', mag_changed) vbox.pack_start(l, False, True, 0) vbox.pack_start(mag_scale, False, True, 0) f = qltk.Frame(_("Options"), child=vbox) box.pack_start(f, False, True, 12) display_frame = self.edit_display_pane(browser, _("Album Display")) box.pack_start(display_frame, True, True, 0) main_box = Gtk.VBox(spacing=12) close = Button(_("_Close"), Icons.WINDOW_CLOSE) close.connect('clicked', lambda *x: self.destroy()) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(close, True, True, 0) main_box.pack_start(box, True, True, 0) self.use_header_bar() if not self.has_close_button(): main_box.pack_start(b, False, True, 0) self.add(main_box) close.grab_focus() self.show_all()
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 _apply_value(self, model, iter_, cell, value): text = util.format_time_display(value) cell.set_property('text', text) self._recalc_width(model.get_path(iter_), text)
def _get_min_width(self): # 1:22:22, allows entire albums as files (< 75mins) return self._cell_width(util.format_time_display(60 * 82 + 22))
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 __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 set_time(self, time_): """Set the time in seconds""" self.set_text(util.format_time_display(time_))
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)
class DuplicatesTreeModel(Gtk.TreeStore): """A tree store to model duplicated song information""" # Define columns to display (and how, in lieu of using qltk.browsers) def i(x): return x TAG_MAP = [ ("artist", i), ("title", i), ("album", i), ("~#length", lambda s: util.format_time_display(int(s))), ("~#filesize", lambda s: util.format_size(int(s))), ("~#bitrate", i), ("~filename", i)] # Now make a dict. This seems clunky. tag_functions = {} for t, f in TAG_MAP: tag_functions[t] = f @classmethod def group_value(cls, group, tag): """Gets a formatted aggregated value/dummy for a set of tag values""" try: vals = [] for comment in group[tag]: vals.append(comment.get_markup()) group_val = "\n".join(vals) except KeyError: return "" else: try: group_val = cls.tag_functions[tag](group_val) except (ValueError, TypeError): pass return group_val.replace("\n", ", ") def find_row(self, song): """Returns the row in the model from song, or None""" for parent in self: for row in parent.iterchildren(): if row[0] == song: self.__iter = row.iter self.sourced = True return row return None def add_to_existing_group(self, key, song): """Tries to add a song to an existing group. Returns None if not able """ for parent in self: if key == parent[0]: print_d("Found group", self) return self.append(parent.iter, self.__make_row(song)) # TODO: update group return None @classmethod def __make_row(cls, song): """Construct GTK row for a song, with all columns""" return [song] + [util.escape(str(f(song.comma(tag)))) for (tag, f) in cls.TAG_MAP] def add_group(self, key, songs): """Adds a new group, returning the row created""" group = AudioFileGroup(songs, real_keys_only=False) # Add the group first. parent = self.append(None, [key] + [self.group_value(group, tag) for tag, f in self.TAG_MAP]) for s in songs: self.append(parent, self.__make_row(s)) def go_to(self, song, explicit=False): self.__iter = None if isinstance(song, Gtk.TreeIter): self.__iter = song self.sourced = True elif not self.find_row(song): print_d("Failed to find song") return self.__iter def remove(self, itr): if self.__iter and self[itr].path == self[self.__iter].path: self.__iter = None super(DuplicatesTreeModel, self).remove(itr) def get(self): return [row[0] for row in self] @property def get_current(self): if self.__iter is None: return None elif self.is_empty(): return None else: return self[self.__iter][0] @property def get_current_path(self): if self.__iter is None: return None elif self.is_empty(): return None else: return self[self.__iter].path @property def get_current_iter(self): if self.__iter is None: return None elif self.is_empty(): return None else: return self.__iter def is_empty(self): return not len(self) def __init__(self): super(DuplicatesTreeModel, self).__init__( object, str, str, str, str, str, str, str)
class Preferences(qltk.UniqueWindow): _EXAMPLE_ALBUM = FakeAlbum({ "date": "2010-10-31", "~length": util.format_time_display(6319), "~long-length": util.format_time_long(6319), "~tracks": ngettext("%d track", "%d tracks", 5) % 5, "~discs": ngettext("%d disc", "%d discs", 2) % 2, "~#rating": 0.75, "album": _("An Example Album"), "~people": _SOME_PEOPLE + "..." }) def __init__(self, browser): if self.is_not_unique(): return super(Preferences, self).__init__() self.set_border_width(12) self.set_title(_("Album List Preferences") + " - Quod Libet") self.set_default_size(420, 380) self.set_transient_for(qltk.get_top_parent(browser)) # Do this config-driven setup at instance-time self._EXAMPLE_ALBUM["~rating"] = format_rating(0.75) box = Gtk.VBox(spacing=6) vbox = Gtk.VBox(spacing=6) cb = ConfigCheckButton(_("Show album _covers"), "browsers", "album_covers") cb.set_active(config.getboolean("browsers", "album_covers")) cb.connect('toggled', lambda s: browser.toggle_covers()) vbox.pack_start(cb, False, True, 0) cb = ConfigCheckButton(_("Inline _search includes people"), "browsers", "album_substrings") cb.set_active(config.getboolean("browsers", "album_substrings")) vbox.pack_start(cb, False, True, 0) f = qltk.Frame(_("Options"), child=vbox) box.pack_start(f, False, True, 12) vbox = Gtk.VBox(spacing=6) label = Gtk.Label() label.set_alignment(0.0, 0.5) label.set_padding(6, 6) eb = Gtk.EventBox() eb.get_style_context().add_class("entry") eb.add(label) edit = PatternEditBox(PATTERN) edit.text = browser._pattern_text edit.apply.connect('clicked', self.__set_pattern, edit, browser) connect_obj(edit.buffer, 'changed', self.__preview_pattern, edit, label) vbox.pack_start(eb, False, True, 3) vbox.pack_start(edit, True, True, 0) self.__preview_pattern(edit, label) f = qltk.Frame(_("Album Display"), child=vbox) box.pack_start(f, True, True, 0) main_box = Gtk.VBox(spacing=12) close = Gtk.Button(stock=Gtk.STOCK_CLOSE) close.connect('clicked', lambda *x: self.destroy()) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(close, True, True, 0) main_box.pack_start(box, True, True, 0) self.use_header_bar() if not self.has_close_button(): main_box.pack_start(b, False, True, 0) self.add(main_box) close.grab_focus() self.show_all() def __set_pattern(self, apply, edit, browser): browser.refresh_pattern(edit.text) def __preview_pattern(self, edit, label): try: text = XMLFromMarkupPattern(edit.text) % self._EXAMPLE_ALBUM except: text = _("Invalid pattern") edit.apply.set_sensitive(False) try: Pango.parse_markup(text, -1, u"\u0000") except GLib.GError: text = _("Invalid pattern") edit.apply.set_sensitive(False) else: edit.apply.set_sensitive(True) label.set_markup(text)
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)