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 iteritems(replace_order): tag = tag.replace("<%s>" % key, "<%s>" % value) for key, value in iteritems(TAG_TO_SORT): 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 init_defaults(): """Fills in the defaults, so they are guaranteed to be available""" _config.defaults.clear() for section, values in iteritems(INITIAL): _config.defaults.add_section(section) for key, value in iteritems(values): _config.defaults.set(section, key, value)
def _fill_stream(self, tags, librarian): # get a new remote file new_info = self.__info_buffer if not new_info: new_info = type(self.song)(self.song["~filename"]) new_info.multisong = False new_info.streamsong = True # copy from the old songs # we should probably listen to the library for self.song changes new_info.update(self.song) new_info.update(self.info) changed = False info_changed = False tags = TagListWrapper(tags, merge=True) tags = parse_gstreamer_taglist(tags) for key, value in iteritems(sanitize_tags(tags, stream=False)): if self.song.get(key) != value: changed = True self.song[key] = value for key, value in iteritems(sanitize_tags(tags, stream=True)): if new_info.get(key) != value: info_changed = True new_info[key] = value if info_changed: # in case the title changed, make self.info a new instance # and emit ended/started for the the old/new one if self.info.get("title") != new_info.get("title"): if self.info is not self.song: self.emit('song-ended', self.info, False) self.info = new_info self.__info_buffer = None self.emit('song-started', self.info) else: # in case title didn't changed, update the values of the # old instance if there is one and tell the library. if self.info is not self.song: self.info.update(new_info) librarian.changed([self.info]) else: # So we don't loose all tags before the first title # save it for later self.__info_buffer = new_info if changed: librarian.changed([self.song])
def __load_tags(self, tags, expected): m = OggVorbis(self.filename) for key, value in iteritems(tags): m.tags[key] = value m.save() song = OggFile(self.filename) for key, value in iteritems(expected): self.failUnlessEqual(song(key), value) if self.MAIN not in expected: self.failIf(self.MAIN in song) if self.SINGLE not in expected: self.failIf(self.SINGLE in song) if self.FALLBACK not in expected: self.failIf(self.FALLBACK in song)
def emit_properties_changed(self, interface, properties, path="/"): """Emits PropertiesChanged for the specified properties""" combos = {} for prop in properties: iface = self.get_interface(interface, prop) if iface is None: raise ValueError("Property %s not registered" % prop) combos.setdefault(iface, []).append(prop) for iface, props in iteritems(combos): values = {} inval = [] for prop in props: emit = self.__props[iface][prop]["emit"] if emit == "false": raise ValueError("Can't emit changed signal for %s" % prop) elif emit == "true": values[prop] = self.get_value(iface, prop, path) elif emit == "invalidates": inval.append(prop) if self.SUPPORTS_MULTIPLE_OBJECT_PATHS: self.PropertiesChanged(iface, values, inval, rel=path) else: self.PropertiesChanged(iface, values, inval)
def _text_to_song(self, text, song): assert isinstance(text, text_type) # parse tags = {} for line in text.splitlines(): if not line.strip() or line.startswith(u"#"): continue try: key, value = line.split(u"=", 1) except ValueError: continue tags.setdefault(key, []).append(value) # apply changes, sort to always have the same output for key in sorted(song.realkeys(), key=sortkey): new = tags.pop(key, []) old = song.list(key) for value in old: if value not in new: self.log("Remove %s=%s" % (key, value)) song.remove(key, value) for value in new: if value not in old: self.log("Add %s=%s" % (key, value)) song.add(key, value) for key, values in iteritems(tags): if not song.can_change(key): raise CommandError( "Can't change key '%(key-name)s'." % {"key-name": key}) for value in values: self.log("Add %s=%s" % (key, value)) song.add(key, value)
def enum(cls): """Class decorator for enum types:: @enum class SomeEnum(int): FOO = 0 BAR = 1 Result is an int subclass and all attributes are instances of it. """ type_ = cls.__bases__[0] d = dict(cls.__dict__) new_type = type(cls.__name__, (type_, ), d) new_type.__module__ = cls.__module__ map_ = {} for key, value in iteritems(d): if key.upper() == key and isinstance(value, type_): value_instance = new_type(value) setattr(new_type, key, value_instance) map_[value] = key def repr_(self): if self in map_: return "%s.%s" % (type(self).__name__, map_[self]) else: return "%s(%s)" % (type(self).__name__, self) setattr(new_type, "__repr__", repr_) return new_type
def __init__(self, parent, library): DBusIntrospectable.__init__(self) DBusPropertyFilter.__init__(self) MediaObject.__init__(self, parent) MediaContainer.__init__(self) bus = dbus.SessionBus() self.ref = dbus.service.BusName(BUS_NAME, bus) dbus.service.FallbackObject.__init__(self, bus, self.PATH) parent.register_child(self) self.__library = library.albums self.__library.load() self.__map = dict((id(v), v) for v in itervalues(self.__library)) self.__reverse = dict((v, k) for k, v in iteritems(self.__map)) signals = [ ("changed", self.__albums_changed), ("removed", self.__albums_removed), ("added", self.__albums_added), ] self.__sigs = map( lambda x: self.__library.connect(x[0], x[1]), signals) self.__dummy = DummyAlbumObject(self)
def __init__(self, library, users): DBusIntrospectable.__init__(self) DBusProperty.__init__(self) MediaObject.__init__(self, None) MediaItem.__init__(self, optional=SUPPORTED_SONG_PROPERTIES) bus = dbus.SessionBus() self.ref = dbus.service.BusName(BUS_NAME, bus) dbus.service.FallbackObject.__init__(self, bus, self.PATH) self.__library = library self.__map = dict((id(v), v) for v in itervalues(self.__library)) self.__reverse = dict((v, k) for k, v in iteritems(self.__map)) self.__song = DummySongObject(self) self.__users = users signals = [ ("changed", self.__songs_changed), ("removed", self.__songs_removed), ("added", self.__songs_added), ] self.__sigs = map( lambda x: self.__library.connect(x[0], x[1]), signals)
def test_handles_albums(self): for id_, plugin in iteritems(self.plugins): if isinstance(plugin, SongsMenuPlugin): ha = plugin.handles_albums self.failIf(hasattr(plugin, "plugin_single_album") and not ha) self.failIf(hasattr(plugin, "plugin_plugin_album") and not ha) self.failIf(hasattr(plugin, "plugin_albums") and not ha)
def test_main(self): args = {"reply_handler": self._reply, "error_handler": self._error} piface = "org.mpris.MediaPlayer2" app.window.hide() self.failIf(app.window.get_visible()) self._main_iface().Raise(**args) self.failIf(self._wait()) self.failUnless(app.window.get_visible()) app.window.hide() props = { "CanQuit": dbus.Boolean(True), "CanRaise": dbus.Boolean(True), "CanSetFullscreen": dbus.Boolean(False), "HasTrackList": dbus.Boolean(False), "Identity": dbus.String("Quod Libet"), "DesktopEntry": dbus.String("quodlibet"), "SupportedUriSchemes": dbus.Array(), } for key, value in iteritems(props): self._prop().Get(piface, key, **args) resp = self._wait()[0] self.failUnlessEqual(resp, value) self.failUnless(isinstance(resp, type(value))) self._prop().Get(piface, "SupportedMimeTypes", **args) self.failUnless("audio/vorbis" in self._wait()[0]) self._introspect_iface().Introspect(**args) assert self._wait()
def test_player(self): args = {"reply_handler": self._reply, "error_handler": self._error} piface = "org.mpris.MediaPlayer2.Player" props = { "PlaybackStatus": dbus.String("Stopped"), "LoopStatus": dbus.String("None"), "Rate": dbus.Double(1.0), "Shuffle": dbus.Boolean(False), "Volume": dbus.Double(1.0), "Position": dbus.Int64(0), "MinimumRate": dbus.Double(1.0), "MaximumRate": dbus.Double(1.0), "CanGoNext": dbus.Boolean(True), "CanGoPrevious": dbus.Boolean(True), "CanPlay": dbus.Boolean(True), "CanPause": dbus.Boolean(True), "CanSeek": dbus.Boolean(True), "CanControl": dbus.Boolean(True), } for key, value in iteritems(props): self._prop().Get(piface, key, **args) resp = self._wait(msg="for key '%s'" % key)[0] self.failUnlessEqual(resp, value) self.failUnless(isinstance(resp, type(value)))
def enum(cls): """Class decorator for enum types:: @enum class SomeEnum(int): FOO = 0 BAR = 1 Result is an int subclass and all attributes are instances of it. """ type_ = cls.__bases__[0] d = dict(cls.__dict__) new_type = type(cls.__name__, (type_,), d) new_type.__module__ = cls.__module__ map_ = {} for key, value in iteritems(d): if key.upper() == key: value_instance = new_type(value) setattr(new_type, key, value_instance) map_[value] = key def repr_(self): if self in map_: return "%s.%s" % (type(self).__name__, map_[self]) else: return "%s(%s)" % (type(self).__name__, self) setattr(new_type, "__repr__", repr_) return new_type
def __init__(self, parent, library): DBusIntrospectable.__init__(self) DBusPropertyFilter.__init__(self) MediaObject.__init__(self, parent) MediaContainer.__init__(self) bus = dbus.SessionBus() self.ref = dbus.service.BusName(BUS_NAME, bus) dbus.service.FallbackObject.__init__(self, bus, self.PATH) parent.register_child(self) self.__library = library.albums self.__library.load() self.__map = dict((id(v), v) for v in itervalues(self.__library)) self.__reverse = dict((v, k) for k, v in iteritems(self.__map)) signals = [ ("changed", self.__albums_changed), ("removed", self.__albums_removed), ("added", self.__albums_added), ] self.__sigs = map(lambda x: self.__library.connect(x[0], x[1]), signals) self.__dummy = DummyAlbumObject(self)
def __init__(self, library, users): DBusIntrospectable.__init__(self) DBusProperty.__init__(self) MediaObject.__init__(self, None) MediaItem.__init__(self, optional=SUPPORTED_SONG_PROPERTIES) bus = dbus.SessionBus() self.ref = dbus.service.BusName(BUS_NAME, bus) dbus.service.FallbackObject.__init__(self, bus, self.PATH) self.__library = library self.__map = dict((id(v), v) for v in itervalues(self.__library)) self.__reverse = dict((v, k) for k, v in iteritems(self.__map)) self.__song = DummySongObject(self) self.__users = users signals = [ ("changed", self.__songs_changed), ("removed", self.__songs_removed), ("added", self.__songs_added), ] self.__sigs = map(lambda x: self.__library.connect(x[0], x[1]), signals)
def test_main(self): args = {"reply_handler": self._reply, "error_handler": self._error} piface = "org.mpris.MediaPlayer2" app.window.hide() self.failIf(app.window.get_visible()) self._main_iface().Raise(**args) self.failIf(self._wait()) self.failUnless(app.window.get_visible()) app.window.hide() props = { "CanQuit": dbus.Boolean(True), "CanRaise": dbus.Boolean(True), "CanSetFullscreen": dbus.Boolean(False), "HasTrackList": dbus.Boolean(False), "Identity": dbus.String("Quod Libet"), "DesktopEntry": dbus.String("quodlibet"), "SupportedUriSchemes": dbus.Array(), } for key, value in iteritems(props): self._prop().Get(piface, key, **args) resp = self._wait()[0] self.failUnlessEqual(resp, value) self.failUnless(isinstance(resp, type(value))) self._prop().Get(piface, "SupportedMimeTypes", **args) self.failUnless("audio/vorbis" in self._wait()[0])
def plugin_album(self, songs): songs.sort(key=sort_key_for) chooser = filechooser(save=False, title=songs[0]('album')) box = Gtk.HBox() rename = Gtk.CheckButton("Rename Files") rename.set_active(False) box.pack_start(rename, True, True, 0) append = Gtk.CheckButton("Append Metadata") append.set_active(True) box.pack_start(append, True, True, 0) box.show_all() chooser.set_extra_widget(box) resp = chooser.run() append = append.get_active() rename = rename.get_active() fn = chooser.get_filename() chooser.destroy() if resp != Gtk.ResponseType.ACCEPT: return global lastfolder lastfolder = dirname(fn) metadata = [] names = [] index = 0 for line in open(fn, 'rU'): if index == len(metadata): names.append(line[:line.rfind('.')]) metadata.append({}) elif line == '\n': index = len(metadata) else: key, value = line[:-1].split('=', 1) value = value.decode('utf-8') try: metadata[index][key].append(value) except KeyError: metadata[index][key] = [value] if not (len(songs) == len(metadata) == len(names)): ErrorMessage(None, "Songs mismatch", "There are %(select)d songs selected, but %(meta)d " "songs in the file. Aborting." % dict(select=len(songs), meta=len(metadata))).run() return for song, meta, name in zip(songs, metadata, names): for key, values in iteritems(meta): if append and key in song: values = song.list(key) + values song[key] = '\n'.join(values) if rename: origname = song['~filename'] newname = name + origname[origname.rfind('.'):] app.library.rename(origname, newname)
def _get_role_map(tags): roles = {} for (name, tag) in iteritems(tags): if tag.role: roles[name] = tag.role if tag.has_sort: roles[name + "sort"] = tag.role return roles
def _get_standard_tags(tags, machine=False): stags = [] for name, tag in iteritems(tags): if tag.user and tag.machine == machine: stags.append(name) if tag.has_sort: stags.append("%ssort" % name) return stags
def plugin_album(self, songs): songs.sort(key=sort_key_for) chooser = filechooser(save=False, title=songs[0]('album')) box = Gtk.HBox() rename = Gtk.CheckButton("Rename Files") rename.set_active(False) box.pack_start(rename, True, True, 0) append = Gtk.CheckButton("Append Metadata") append.set_active(True) box.pack_start(append, True, True, 0) box.show_all() chooser.set_extra_widget(box) resp = chooser.run() append = append.get_active() rename = rename.get_active() fn = chooser.get_filename() chooser.destroy() if resp != Gtk.ResponseType.ACCEPT: return global lastfolder lastfolder = dirname(fn) metadata = [] names = [] index = 0 for line in open(fn, 'r', encoding="utf-8"): if index == len(metadata): names.append(line[:line.rfind('.')]) metadata.append({}) elif line == '\n': index = len(metadata) else: key, value = line[:-1].split('=', 1) try: metadata[index][key].append(value) except KeyError: metadata[index][key] = [value] if not (len(songs) == len(metadata) == len(names)): ErrorMessage(None, "Songs mismatch", "There are %(select)d songs selected, but %(meta)d " "songs in the file. Aborting." % dict(select=len(songs), meta=len(metadata))).run() return for song, meta, name in zip(songs, metadata, names): for key, values in iteritems(meta): if append and key in song: values = song.list(key) + values song[key] = '\n'.join(values) if rename: origname = song['~filename'] newname = name + origname[origname.rfind('.'):] app.library.rename(origname, newname)
def has_changed(self, dep_paths): if set(self.deps.keys()) != set(dep_paths): return True for path, old_mtime in iteritems(self.deps): if mtime(path) != old_mtime: return True return False
def headers(self, new_headers): for button, headers in iteritems(self.__headers): if headers == new_headers: button.set_active(True) button.emit("toggled") break else: self.__headers[self.__custom] = new_headers self.__custom.set_active(True)
def Introspect(self): parts = [] parts.append("<node>") for iface, intros in iteritems(self.__ispec): parts.append("<interface name=\"%s\">" % iface) parts.extend(intros) parts.append("</interface>") parts.append("</node>") return ("\n".join(parts)).encode("utf-8")
def test_items(self): items = [] for i in range(20): items.append(self.Fake(i)) items[-1].key = i + 100 self.library.add(items) expected = list(zip(range(100, 120), range(20))) self.failUnlessEqual(sorted(self.library.items()), expected) self.failUnlessEqual(sorted(iteritems(self.library)), expected)
def apply_to_song(meta, song): """Applies the tags to a AudioFile instance""" for key, value in iteritems(meta): if not value: song.remove(key) else: assert isinstance(value, text_type) song[key] = value
def __save_tags(self, tags, expected): #return song = OggFile(self.filename) for key, value in iteritems(tags): song[key] = value song.write() m = OggVorbis(self.filename) # test if all values ended up where we wanted for key, value in iteritems(expected): self.failUnless(key in m.tags) self.failUnlessEqual(m.tags[key], [value]) # test if not specified are not there if self.MAIN not in expected: self.failIf(self.MAIN in m.tags) if self.FALLBACK not in expected: self.failIf(self.FALLBACK in m.tags) if self.SINGLE not in expected: self.failIf(self.SINGLE in m.tags)
def test_handle_single(self): self.skipTest("Pops up windows and needs user input.. so disabled." "Still worth keeping whilst we don't have unit tests " "for all plugins.") # Ignored... for id_, plugin in iteritems(self.plugins): if self.h.plugin_handle(plugin): self.h.plugin_enable(plugin, None) self.h.handle(id_, self.lib, self.parent, SONGS) self.h.plugin_disable(plugin)
def build_song_data(release, track): """Returns a dict of tags to apply to a song. All the values are unicode. If the value is empty it means the tag should be deleted. """ meta = {} join = lambda l: "\n".join(l) # track/disc data meta["tracknumber"] = "%s/%d" % (track.tracknumber, track.track_count) if release.disc_count > 1: meta["discnumber"] = "%s/%d" % (track.discnumber, release.disc_count) else: meta["discnumber"] = "" meta["title"] = track.title meta["musicbrainz_releasetrackid"] = track.id meta["musicbrainz_trackid"] = u"" # we used to write those, so delete # disc data meta["discsubtitle"] = track.disctitle # release data meta["album"] = release.title meta["date"] = release.date meta["musicbrainz_albumid"] = release.id meta["labelid"] = release.labelid if not release.is_single_artist and not release.is_various_artists: artists = release.artists meta["albumartist"] = join([a.name for a in artists]) meta["albumartistsort"] = join([a.sort_name for a in artists]) meta["musicbrainz_albumartistid"] = join([a.id for a in artists]) else: meta["albumartist"] = "" meta["albumartistsort"] = "" meta["musicbrainz_albumartistid"] = "" meta["artist"] = join([a.name for a in track.artists]) meta["artistsort"] = join([a.sort_name for a in track.artists]) meta["musicbrainz_artistid"] = join([a.id for a in track.artists]) meta["musicbrainz_releasetrackid"] = track.id # clean up "redundant" data if meta["albumartist"] == meta["albumartistsort"]: meta["albumartistsort"] = "" if meta["artist"] == meta["artistsort"]: meta["artistsort"] = "" # finally, as musicbrainzngs returns str values if it's ascii, we force # everything to unicode now for key, value in iteritems(meta): meta[key] = text_type(value) return meta
def get_tracks(self, params): merged = { "q": "", "limit": self.PAGE_SIZE, "duration[from]": self.MIN_DURATION_SECS * 1000, } for k, v in iteritems(params): delim = " " if k == 'q' else "," merged[k] = delim.join(list(v)) print_d("Getting tracks: params=%s" % merged) self._get('/tracks', self._on_track_data, **merged)
def __init__(self, songs, real_keys_only=True): keys = {} first = {} all = {} total = len(songs) self.songs = songs self.is_file = True can_multi = True can_change = True for song in songs: self.is_file &= song.is_file if real_keys_only: iter_func = song.iterrealitems else: iter_func = song.items for comment, val in iter_func(): keys[comment] = keys.get(comment, 0) + 1 first.setdefault(comment, val) all[comment] = all.get(comment, True) and first[comment] == val song_can_multi = song.can_multiple_values() if song_can_multi is not True: if can_multi is True: can_multi = set(song_can_multi) else: can_multi.intersection_update(song_can_multi) song_can_change = song.can_change() if song_can_change is not True: if can_change is True: can_change = set(song_can_change) else: can_change.intersection_update(song_can_change) self._can_multi = can_multi self._can_change = can_change # collect comment representations for tag, count in iteritems(keys): first_value = first[tag] if not isinstance(first_value, string_types): first_value = text_type(first_value) shared = all[tag] complete = count == total if shared and complete: values = first_value.split("\n") else: values = [first_value] for v in values: self.setdefault(tag, []).append(Comment(v, count, total, shared))
def _get_sort_map(tags): """See TAG_TO_SORT""" tts = {} for name, tag in iteritems(tags): if tag.has_sort: if tag.user: tts[name] = "%ssort" % name if tag.internal: tts["~%s" % name] = "~%ssort" % name return tts
def __init__(self, songs, real_keys_only=True): keys = {} first = {} all = {} total = len(songs) self.songs = songs self.is_file = True can_multi = True can_change = True for song in songs: self.is_file &= song.is_file if real_keys_only: iter_func = song.iterrealitems else: iter_func = song.iteritems for comment, val in iter_func(): keys[comment] = keys.get(comment, 0) + 1 first.setdefault(comment, val) all[comment] = all.get(comment, True) and first[comment] == val song_can_multi = song.can_multiple_values() if song_can_multi is not True: if can_multi is True: can_multi = set(song_can_multi) else: can_multi.intersection_update(song_can_multi) song_can_change = song.can_change() if song_can_change is not True: if can_change is True: can_change = set(song_can_change) else: can_change.intersection_update(song_can_change) self._can_multi = can_multi self._can_change = can_change # collect comment representations for tag, count in iteritems(keys): first_value = first[tag] if not isinstance(first_value, string_types): first_value = text_type(first_value) shared = all[tag] complete = count == total if shared and complete: values = first_value.split("\n") else: values = [first_value] for v in values: self.setdefault(tag, []).append( Comment(v, count, total, shared))
def test_all(self): tested_any = False for id_, plugin in iteritems(self.plugins): plugin = plugin.cls if hasattr(plugin, "PLUGIN_INSTANCE"): plugin = plugin() if hasattr(plugin, "PluginPreferences"): tested_any = True plugin.PluginPreferences(Gtk.Window()) self.assertTrue(tested_any)
def next(self, playlist, current): super(PlaycountEqualizer, self).next(playlist, current) remaining = self.remaining(playlist) # Don't try to search through an empty / played playlist. if len(remaining) <= 0: return None # Set-up the search information. max_count = max([song('~#playcount') for song in remaining.values()]) weights = {i: max_count - song('~#playcount') for i, song in iteritems(remaining)} choice = int(max(1, math.ceil(sum(weights) * random.random()))) # Search for a track. for i, weight in iteritems(weights): choice -= weight if choice <= 0: return playlist.get_iter([i]) else: # This should only happen if all songs have equal play counts. return playlist.get_iter([random.choice(list(remaining.keys()))])
def next(self, playlist, current): super(PlaycountEqualizer, self).next(playlist, current) remaining = self.remaining(playlist) # Don't try to search through an empty / played playlist. if len(remaining) <= 0: return None # Set-up the search information. max_count = max([song('~#playcount') for song in remaining.values()]) weights = {i: max_count - song('~#playcount') for i, song in iteritems(remaining)} choice = int(max(1, math.ceil(sum(weights) * random.random()))) # Search for a track. for i, weight in iteritems(weights): choice -= weight if choice <= 0: return playlist.get_iter([i]) else: # This should only happen if all songs have equal play counts. return playlist.get_iter([random.choice(remaining.keys())])
def generate_re_mapping(_diacritic_for_letters): letter_to_variants = {} # combine combining characters with the ascii chars for dia, letters in iteritems(_diacritic_for_letters): for c in letters: unichar = unicodedata.normalize("NFKC", c + dia) letter_to_variants.setdefault(c, []).append(unichar) # create strings to replace ascii with for k, v in letter_to_variants.items(): letter_to_variants[k] = u"".join(sorted(v)) return letter_to_variants
def failures(self): """module name: list of error message text lines""" errors = {} for name, error in iteritems(self.__scanner.failures): exception = error.exception if isinstance(exception, PluginImportException): if not exception.should_show(): continue errors[name] = [exception.desc] else: errors[name] = error.traceback return errors
def get_images(self): try: tag = mutagen.apev2.APEv2(self['~filename']) except Exception: return [] images = [] for key, value in iteritems(tag): image = parse_cover(key, value) if image is not None: images.append(image) images.sort(key=lambda c: c.sort_key) return images