def from_dump(self, text): """Parses the text created with to_dump and adds the found tags. Args: text (bytes) """ for line in text.split(b"\n"): if not line: continue parts = line.split(b"=") key = decode(parts[0]) val = b"=".join(parts[1:]) if key == "~format": pass elif key in FILESYSTEM_TAGS: self.add(key, bytes2fsn(val, "utf-8")) elif key.startswith("~#"): try: self.add(key, int(val)) except ValueError: try: self.add(key, float(val)) except ValueError: pass else: self.add(key, decode(val))
def decode_key(key): """str if ascii, otherwise decode using utf-8""" if PY3: return decode(key) try: key.decode("ascii") except ValueError: return decode(key) return key
def parse_taglist(data): """Parses a dump file like list of tags and returns a list of IRFiles uri=http://... tag=value1 tag2=value tag=value2 uri=http://... ... """ stations = [] station = None for l in data.split(b"\n"): if not l: continue key = l.split(b"=")[0] value = l.split(b"=", 1)[1] key = decode(key) value = decode(value) if key == "uri": if station: stations.append(station) station = IRFile(value) continue san = list(sanitize_tags({key: value}, stream=True).items()) if not san: continue key, value = san[0] if key == "~listenerpeak": key = "~#listenerpeak" value = int(value) if not station: continue if isinstance(value, text_type): if value not in station.list(key): station.add(key, value) else: station[key] = value if station: stations.append(station) return stations
def decode_key(key): """str if ascii, otherwise decode using utf-8""" try: key.decode("ascii") except ValueError: return decode(key) return key
def from_dump(self, text): """Parses the text created with to_dump and adds the found tags.""" def decode_key(key): """str if ascii, otherwise decode using utf-8""" try: key.decode("ascii") except ValueError: return decode(key) return key for line in text.split("\n"): if not line: continue parts = line.split("=") key = parts[0] val = "=".join(parts[1:]) if key == "~format": pass elif key.startswith("~#"): try: self.add(key, int(val)) except ValueError: try: self.add(key, float(val)) except ValueError: pass else: self.add(decode_key(key), decode(val))
def __init__(self, songs): keys = {} first = {} all = {} total = len(songs) self.songs = songs for song in songs: self.is_file &= song.is_file self.multiple_values &= song.multiple_values for comment, val in song.iteritems(): keys[comment] = keys.get(comment, 0) + 1 first.setdefault(comment, val) all[comment] = all.get(comment, True) and first[comment] == val # collect comment representations for comment, count in keys.iteritems(): if count < total: if all[comment]: value = self.PartialSharedComment(first[comment]) else: value = self.PartialUnsharedComment(first[comment]) else: decoded = first[comment] if isinstance(decoded, str): decoded = decode(decoded) if all[comment]: value = self.SharedComment(decoded) else: value = self.UnsharedComment(decoded) value.have = count value.total = total value.missing = total - count self[comment] = value
def __init__(self, filename): with translate_errors(): audio = MP4(filename) self["~codec"] = audio.info.codec_description self["~#length"] = audio.info.length self["~#bitrate"] = int(audio.info.bitrate / 1000) if audio.info.channels: self["~#channels"] = audio.info.channels self["~#samplerate"] = audio.info.sample_rate self["~#bitdepth"] = audio.info.bits_per_sample for key, values in audio.items(): if key in self.__tupletranslate: if values: name = self.__tupletranslate[key] cur, total = values[0] if total: self[name] = u"%d/%d" % (cur, total) else: self[name] = text_type(cur) elif key in self.__translate: name = self.__translate[key] if key == "tmpo": self[name] = u"\n".join(map(text_type, values)) elif key.startswith("----"): self[name] = "\n".join( map(lambda v: decode(v).strip("\x00"), values)) else: self[name] = "\n".join(values) elif key == "covr": self.has_images = True self.sanitize(filename)
def __init__(self, filename): with translate_errors(): audio = MP4(filename) self["~codec"] = audio.info.codec_description self["~#length"] = audio.info.length self["~#bitrate"] = int(audio.info.bitrate / 1000) if audio.info.channels: self["~#channels"] = audio.info.channels self["~#samplerate"] = audio.info.sample_rate self["~#bitdepth"] = audio.info.bits_per_sample for key, values in audio.items(): if key in self.__tupletranslate: if values: name = self.__tupletranslate[key] cur, total = values[0] if total: self[name] = u"%d/%d" % (cur, total) else: self[name] = str(cur) elif key in self.__translate: name = self.__translate[key] if key == "tmpo": self[name] = u"\n".join(map(str, values)) elif key.startswith("----"): self[name] = "\n".join( map(lambda v: decode(v).strip("\x00"), values)) else: self[name] = "\n".join(values) elif key == "covr": self.has_images = True self.sanitize(filename)
def __init__(self, filename): audio = MP4(filename) self["~format"] = "%s %s" % ( self.format, getattr(audio.info, "codec_description", "AAC")) self["~#length"] = audio.info.length self["~#bitrate"] = int(audio.info.bitrate / 1000) for key, values in audio.items(): if key in self.__tupletranslate: name = self.__tupletranslate[key] cur, total = values[0] if total: self[name] = u"%d/%d" % (cur, total) else: self[name] = unicode(cur) elif key in self.__translate: name = self.__translate[key] if key == "tmpo": self[name] = "\n".join(map(unicode, values)) elif key.startswith("----"): self[name] = "\n".join( map(lambda v: decode(v).strip("\x00"), values)) else: self[name] = "\n".join(values) elif key == "covr": self.has_images = True self.sanitize(filename)
def __init__(self, filename): audio = MP4(filename) self["~codec"] = getattr(audio.info, "codec_description", u"AAC") self["~#length"] = audio.info.length self["~#bitrate"] = int(audio.info.bitrate / 1000) for key, values in audio.items(): if key in self.__tupletranslate: name = self.__tupletranslate[key] cur, total = values[0] if total: self[name] = u"%d/%d" % (cur, total) else: self[name] = unicode(cur) elif key in self.__translate: name = self.__translate[key] if key == "tmpo": self[name] = "\n".join(map(unicode, values)) elif key.startswith("----"): self[name] = "\n".join( map(lambda v: decode(v).strip("\x00"), values)) else: self[name] = "\n".join(values) elif key == "covr": self.has_images = True self.sanitize(filename)
def __init__(self, track): super(IPodSong, self).__init__() self.sanitize(gpod.itdb_filename_on_ipod(track)) # String keys for key in [ 'artist', 'album', 'title', 'genre', 'grouping', 'composer', 'albumartist' ]: # albumartist since libgpod-0.4.2 value = getattr(track, key, None) if value: self[key] = decode(value) # Sort keys (since libgpod-0.5.0) for key in ['artist', 'album', 'albumartist']: value = getattr(track, 'sort_' + key, None) if value: self[key + 'sort'] = decode(value) # Numeric keys for key in ['bitrate', 'playcount']: value = getattr(track, key) if value: self['~#' + key] = value try: self["date"] = unicode(track.year) except AttributeError: pass if track.cds: self["discnumber"] = u"%d/%d" % (track.cd_nr, track.cds) elif track.cd_nr: self["discnumber"] = u"%d" % track.cd_nr if track.tracks: self['tracknumber'] = u"%d/%d" % (track.track_nr, track.tracks) elif track.track_nr: self['tracknumber'] = u"%d" % track.track_nr for key, value in { '~#rating': min(1.0, track.rating / 100.0), '~#length': track.tracklen / 1000.0, }.items(): if value != 0: self[key] = value self['~format'] = u"iPod: %s" % track.filetype
def parse_gstreamer_taglist(tags): """Takes a GStreamer taglist and returns a dict containing only numeric and unicode values and str keys.""" merged = {} for key in tags.keys(): value = tags[key] # extended-comment sometimes containes a single vorbiscomment or # a list of them ["key=value", "key=value"] if key == "extended-comment": if not isinstance(value, list): value = [value] for val in value: if not isinstance(val, unicode): continue split = val.split("=", 1) sub_key = decode(split[0]) val = split[-1] if sub_key in merged: sub_val = merged[sub_key] if not isinstance(sub_val, unicode): continue if val not in sub_val.split("\n"): merged[sub_key] += "\n" + val else: merged[sub_key] = val elif isinstance(value, Gst.DateTime): value = value.to_iso8601_string() merged[key] = value else: if isinstance(value, (int, long, float)): merged[key] = value continue if isinstance(value, str): value = decode(value) if not isinstance(value, unicode): value = unicode(value) if key in merged: merged[key] += "\n" + value else: merged[key] = value return merged
def fsdecode(s, note=True): """Decoding a string according to the filesystem encoding. note specifies whether a note should be appended if decoding failed.""" if isinstance(s, unicode): return s elif note: return decode(s, fscoding) else: return s.decode(fscoding, 'replace')
def __init__(self, track): super(IPodSong, self).__init__() self.sanitize(gpod.itdb_filename_on_ipod(track)) # String keys for key in ['artist', 'album', 'title', 'genre', 'grouping', 'composer', 'albumartist']: # albumartist since libgpod-0.4.2 value = getattr(track, key, None) if value: self[key] = decode(value) # Sort keys (since libgpod-0.5.0) for key in ['artist', 'album', 'albumartist']: value = getattr(track, 'sort_' + key, None) if value: self[key + 'sort'] = decode(value) # Numeric keys for key in ['bitrate', 'playcount']: value = getattr(track, key) if value: self['~#' + key] = value try: self["date"] = unicode(track.year) except AttributeError: pass if track.cds: self["discnumber"] = u"%d/%d" % (track.cd_nr, track.cds) elif track.cd_nr: self["discnumber"] = u"%d" % track.cd_nr if track.tracks: self['tracknumber'] = u"%d/%d" % (track.track_nr, track.tracks) elif track.track_nr: self['tracknumber'] = u"%d" % track.track_nr for key, value in { '~#rating': min(1.0, track.rating / 100.0), '~#length': track.tracklen / 1000.0, }.items(): if value != 0: self[key] = value self['~format'] = u"iPod: %s" % track.filetype
def list_input_plugins(self): """ Returns: List[str] """ plugins = [] for plugin in xine_list_input_plugins(self._xine): if not plugin: break plugins.append(decode(plugin)) return plugins
def fsdecode(s, note=True): """Takes a native path and returns unicode for displaying it. Can not fail and can't be reversed. """ if isinstance(s, unicode): return s elif note: return decode(s, _FSCODING) else: return s.decode(_FSCODING, 'replace')
def __error(self, player, song, error, librarian): newstr = u"%s: %s\n\n" % ( decode(time.asctime(), const.ENCODING), error) self.__errors_in_a_row += 1 if self.__errors_in_a_row > MAX_ERRORS: self.__errors_in_a_row = 0 ErrorMessage(None, _("Too Many Errors"), _("Stopping playback because there were %d errors " "in a row.") % MAX_ERRORS).run() player.go_to(None) player.paused = True song["~errors"] = newstr + song.get("~errors", "")
def __init__(self, track): super(IPodSong, self).__init__() self.sanitize(gpod.itdb_filename_on_ipod(track)) # String keys for key in ["artist", "album", "title", "genre", "grouping", "composer", "albumartist"]: # albumartist since libgpod-0.4.2 value = getattr(track, key, None) if value: self[key] = decode(value) # Sort keys (since libgpod-0.5.0) for key in ["artist", "album", "albumartist"]: value = getattr(track, "sort_" + key, None) if value: self[key + "sort"] = decode(value) # Numeric keys for key in ["bitrate", "playcount"]: value = getattr(track, key) if value: self["~#" + key] = value try: self["date"] = unicode(track.year) except AttributeError: pass if track.cds: self["discnumber"] = u"%d/%d" % (track.cd_nr, track.cds) elif track.cd_nr: self["discnumber"] = u"%d" % track.cd_nr if track.tracks: self["tracknumber"] = u"%d/%d" % (track.track_nr, track.tracks) elif track.track_nr: self["tracknumber"] = u"%d" % track.track_nr for key, value in {"~#rating": min(1.0, track.rating / 100.0), "~#length": track.tracklen / 1000.0}.items(): if value != 0: self[key] = value self["~format"] = u"iPod: %s" % track.filetype
def __init__(self, driver, librarian): """May raise PlayerError""" super().__init__() self.name = "xine" self.version_info = "xine-lib: " + decode(xine_get_version_string()) self._volume = 1.0 self._handle = XineHandle() self._supports_gapless = xine_check_version(1, 1, 1) == 1 self._event_queue = None self._new_stream(driver) self._librarian = librarian self._destroyed = False
def __init__(self, driver, librarian): """May raise PlayerError""" super(XinePlaylistPlayer, self).__init__() self.name = "xine" self.version_info = "xine-lib: " + decode(xine_get_version_string()) self._volume = 1.0 self._handle = XineHandle() self._supports_gapless = xine_check_version(1, 1, 1) == 1 self._event_queue = None self._new_stream(driver) self._librarian = librarian self._destroyed = False
def __init__(self, driver, librarian): """May raise PlayerError""" super(XinePlaylistPlayer, self).__init__() self.name = "xine" self.version_info = "xine-lib: " + decode(xine_get_version_string()) self._volume = 1.0 self._handle = XineHandle() self._supports_gapless = xine_check_version(1, 1, 1) == 1 self._event_queue = None if not isinstance(driver, bytes): driver = driver.encode("utf-8") self._new_stream(driver) self._librarian = librarian self._destroyed = False
def parse_taglist(data): """Parses a dump file like list of tags and returns a list of IRFiles uri=http://... tag=value1 tag2=value tag=value2 uri=http://... ... """ stations = [] station = None for l in data.split("\n"): key = l.split("=")[0] value = l.split("=", 1)[1] if key == "uri": if station: stations.append(station) station = IRFile(value) continue value = decode(value) san = sanitize_tags({key: value}, stream=True).items() if not san: continue key, value = san[0] if key == "~listenerpeak": key = "~#listenerpeak" value = int(value) if isinstance(value, str): value = value.decode("utf-8") if value not in station.list(key): station.add(key, value) else: station[key] = value if station: stations.append(station) return stations
def _config(section, option, label, tooltip, getter): def on_changed(entry, *args): config.set(section, option, entry.get_text()) entry = UndoEntry() entry.set_tooltip_text(tooltip) entry.set_text(decode(config.get(section, option))) entry.connect("changed", on_changed) def on_reverted(*args): config.reset(section, option) entry.set_text(decode(config.get(section, option))) revert = Gtk.Button() revert.add(Gtk.Image.new_from_icon_name(Icons.DOCUMENT_REVERT, Gtk.IconSize.BUTTON)) revert.connect("clicked", on_reverted) return (Gtk.Label(label=label), entry, revert)
def _config(section, option, label, tooltip, getter): def on_changed(entry, *args): config.set(section, option, entry.get_text()) entry = UndoEntry() entry.set_tooltip_text(tooltip) entry.set_text(decode(config.get(section, option))) entry.connect("changed", on_changed) def on_reverted(*args): config.reset(section, option) entry.set_text(decode(config.get(section, option))) revert = Gtk.Button() revert.add(Gtk.Image.new_from_icon_name( Icons.DOCUMENT_REVERT, Gtk.IconSize.BUTTON)) revert.connect("clicked", on_reverted) return (Gtk.Label(label=label), entry, revert)
def from_dump(self, text): """Parses the text created with to_dump and adds the found tags. Args: text (bytes) """ def decode_key(key): """str if ascii, otherwise decode using utf-8""" if PY3: return decode(key) try: key.decode("ascii") except ValueError: return decode(key) return key for line in text.split(b"\n"): if not line: continue parts = line.split(b"=") key = decode_key(parts[0]) val = b"=".join(parts[1:]) if key == "~format": pass elif key in FILESYSTEM_TAGS: self.add(key, bytes2fsn(val, "utf-8")) elif key.startswith("~#"): try: self.add(key, int(val)) except ValueError: try: self.add(key, float(val)) except ValueError: pass else: self.add(key, decode(val))
def test_invalid(self): self.failUnlessEqual(decode("fo\xde"), u'fo\ufffd [Invalid Encoding]')
def test_invalid(self): self.failUnlessEqual( decode("fo\xde"), u'fo\ufffd [Invalid Encoding]')
def test_empty(self): self.failUnlessEqual(decode(""), "")
def test_safe(self): self.failUnlessEqual(decode("foo!"), "foo!")
def main(argv): import quodlibet quodlibet.init_cli() try: # we want basic commands not to import gtk (doubles process time) assert "gi.repository.Gtk" not in sys.modules sys.modules["gi.repository.Gtk"] = None startup_actions, cmds_todo = process_arguments(argv) finally: sys.modules.pop("gi.repository.Gtk", None) quodlibet.init() from quodlibet import app from quodlibet.qltk import add_signal_watch, Icons add_signal_watch(app.quit) import quodlibet.player import quodlibet.library from quodlibet import config from quodlibet import browsers from quodlibet import util from quodlibet.util.string import decode app.name = "Quod Libet" app.id = "quodlibet" quodlibet.set_application_info(Icons.QUODLIBET, app.id, app.name) config.init(os.path.join(quodlibet.get_user_dir(), "config")) library_path = os.path.join(quodlibet.get_user_dir(), "songs") print_d("Initializing main library (%s)" % ( quodlibet.util.path.unexpand(library_path))) library = quodlibet.library.init(library_path) app.library = library # this assumes that nullbe will always succeed from quodlibet.player import PlayerError wanted_backend = os.environ.get( "QUODLIBET_BACKEND", config.get("player", "backend")) backend_traceback = None for backend in [wanted_backend, "nullbe"]: try: player = quodlibet.player.init_player(backend, app.librarian) except PlayerError: backend_traceback = decode(traceback.format_exc()) else: break app.player = player os.environ["PULSE_PROP_media.role"] = "music" os.environ["PULSE_PROP_application.icon_name"] = "quodlibet" browsers.init() from quodlibet.qltk.songlist import SongList, get_columns from quodlibet.util.collection import Album try: cover_size = config.getint("browsers", "cover_size") except config.Error: pass else: if cover_size > 0: Album.COVER_SIZE = cover_size headers = get_columns() SongList.set_all_column_headers(headers) for opt in config.options("header_maps"): val = config.get("header_maps", opt) util.tags.add(opt, val) in_all = ("~filename ~uri ~#lastplayed ~#rating ~#playcount ~#skipcount " "~#added ~#bitrate ~current ~#laststarted ~basename " "~dirname").split() for Kind in browsers.browsers: if Kind.headers is not None: Kind.headers.extend(in_all) Kind.init(library) pm = quodlibet.init_plugins("no-plugins" in startup_actions) if hasattr(player, "init_plugins"): player.init_plugins() from quodlibet.qltk import unity unity.init("quodlibet.desktop", player) from quodlibet.qltk.songsmenu import SongsMenu SongsMenu.init_plugins() from quodlibet.util.cover import CoverManager app.cover_manager = CoverManager() app.cover_manager.init_plugins() from quodlibet.plugins.playlist import PLAYLIST_HANDLER PLAYLIST_HANDLER.init_plugins() from gi.repository import GLib def exec_commands(*args): for cmd in cmds_todo: try: resp = cmd_registry.run(app, *cmd) except CommandError: pass else: if resp is not None: print_(resp, end="") from quodlibet.qltk.quodlibetwindow import QuodLibetWindow, PlayerOptions # Call exec_commands after the window is restored, but make sure # it's after the mainloop has started so everything is set up. app.window = window = QuodLibetWindow( library, player, restore_cb=lambda: GLib.idle_add(exec_commands, priority=GLib.PRIORITY_HIGH)) app.player_options = PlayerOptions(window) from quodlibet.qltk.debugwindow import MinExceptionDialog from quodlibet.qltk.window import on_first_map if backend_traceback is not None: def show_backend_error(window): d = MinExceptionDialog(window, _("Audio Backend Failed to Load"), _("Loading the audio backend '%(name)s' failed. " "Audio playback will be disabled.") % {"name": wanted_backend}, backend_traceback) d.run() # so we show the main window first on_first_map(app.window, show_backend_error, app.window) from quodlibet.plugins.events import EventPluginHandler pm.register_handler(EventPluginHandler(library.librarian, player)) from quodlibet.mmkeys import MMKeysHandler from quodlibet.remote import Remote, RemoteError from quodlibet.commands import registry as cmd_registry, CommandError from quodlibet.qltk.tracker import SongTracker, FSInterface try: from quodlibet.qltk.dbus_ import DBusHandler except ImportError: DBusHandler = lambda player, library: None mmkeys_handler = MMKeysHandler(app.name, window, player) if "QUODLIBET_NO_MMKEYS" not in os.environ: mmkeys_handler.start() current_path = os.path.join(quodlibet.get_user_dir(), "current") fsiface = FSInterface(current_path, player) remote = Remote(app, cmd_registry) try: remote.start() except RemoteError: exit_(1, True) DBusHandler(player, library) tracker = SongTracker(library.librarian, player, window.playlist) from quodlibet.qltk import session session.init("quodlibet") quodlibet.enable_periodic_save(save_library=True) if "start-playing" in startup_actions: player.paused = False # restore browser windows from quodlibet.qltk.browser import LibraryBrowser GLib.idle_add(LibraryBrowser.restore, library, player, priority=GLib.PRIORITY_HIGH) def before_quit(): print_d("Saving active browser state") try: app.browser.save() except NotImplementedError: pass print_d("Shutting down player device %r." % player.version_info) player.destroy() quodlibet.main(window, before_quit=before_quit) app.player_options.destroy() quodlibet.finish_first_session(app.id) mmkeys_handler.quit() remote.stop() fsiface.destroy() tracker.destroy() quodlibet.library.save() config.save() print_d("Finished shutdown.")
def on_reverted(*args): config.reset(section, option) entry.set_text(decode(config.get(section, option)))
def getter(section, option): return decode(config.get(section, option))
def __save_files(self, save, revert, model, library): updated = {} deleted = {} added = {} renamed = {} for row in model: if row[EDITED] and not (row[DELETED] or row[RENAMED]): if row[ORIGVALUE] is not None: updated.setdefault(row[TAG], []) updated[row[TAG]].append( (decode(row[VALUE]), decode(row[ORIGVALUE]))) else: added.setdefault(row[TAG], []) added[row[TAG]].append(decode(row[VALUE])) if row[EDITED] and row[DELETED]: if row[ORIGVALUE] is not None: deleted.setdefault(row[TAG], []) deleted[row[TAG]].append(decode(row[ORIGVALUE])) if row[EDITED] and row[RENAMED] and not row[DELETED]: renamed.setdefault(row[TAG], []) renamed[row[TAG]].append( (decode(row[ORIGTAG]), decode(row[VALUE]), decode(row[ORIGVALUE]))) was_changed = set() songs = self.__songinfo.songs win = WritingWindow(self, len(songs)) win.show() for song in songs: if not song.valid() and not qltk.ConfirmAction( self, _("Tag may not be accurate"), _("<b>%s</b> changed while the program was running. " "Saving without refreshing your library may " "overwrite other changes to the song.\n\n" "Save this song anyway?") % util.escape(util.fsdecode(song("~basename")))).run(): break changed = False for key, values in updated.iteritems(): for (new_value, old_value) in values: new_value = util.unescape(new_value) if song.can_change(key): if old_value is None: song.add(key, new_value) else: song.change(key, old_value, new_value) changed = True for key, values in added.iteritems(): for value in values: value = util.unescape(value) if song.can_change(key): song.add(key, value) changed = True for key, values in deleted.iteritems(): for value in values: value = util.unescape(value) if song.can_change(key) and key in song: song.remove(key, value) changed = True save_rename = [] for new_tag, values in renamed.iteritems(): for old_tag, new_value, old_value in values: old_tag = util.unescape(old_tag) old_value = util.unescape(old_value) new_value = util.unescape(new_value) if (song.can_change(new_tag) and song.can_change(old_tag) and old_tag in song): if not is_special(new_value): song.remove(old_tag, old_value) save_rename.append((new_tag, new_value)) elif is_missing(new_value): value = strip_missing(old_value) song.remove(old_tag, old_value) save_rename.append((new_tag, new_value)) else: save_rename.append((new_tag, song[old_tag])) song.remove(old_tag, None) changed = True for tag, value in save_rename: song.add(tag, value) if changed: try: song.write() except: util.print_exc() qltk.ErrorMessage( self, _("Unable to save song"), _("Saving <b>%s</b> failed. The file " "may be read-only, corrupted, or you " "do not have permission to edit it.") % (util.escape(fsdecode(song('~basename'))))).run() library.reload(song, changed=was_changed) break was_changed.add(song) if win.step(): break win.destroy() library.changed(was_changed) for b in [save, revert]: b.set_sensitive(False)
def main(argv): import quodlibet quodlibet.init_cli() try: # we want basic commands not to import gtk (doubles process time) assert "gi.repository.Gtk" not in sys.modules sys.modules["gi.repository.Gtk"] = None startup_actions, cmds_todo = process_arguments(argv) finally: sys.modules.pop("gi.repository.Gtk", None) quodlibet.init() from quodlibet import app from quodlibet.qltk import add_signal_watch, Icons add_signal_watch(app.quit) import quodlibet.player import quodlibet.library from quodlibet import config from quodlibet import browsers from quodlibet import util from quodlibet.util.string import decode app.name = "Quod Libet" app.id = "quodlibet" quodlibet.set_application_info(Icons.QUODLIBET, app.id, app.name) config.init(os.path.join(quodlibet.get_user_dir(), "config")) library_path = os.path.join(quodlibet.get_user_dir(), "songs") print_d("Initializing main library (%s)" % (quodlibet.util.path.unexpand(library_path))) library = quodlibet.library.init(library_path) app.library = library # this assumes that nullbe will always succeed from quodlibet.player import PlayerError wanted_backend = os.environ.get("QUODLIBET_BACKEND", config.get("player", "backend")) backend_traceback = None for backend in [wanted_backend, "nullbe"]: try: player = quodlibet.player.init_player(backend, app.librarian) except PlayerError: backend_traceback = decode(traceback.format_exc()) else: break app.player = player os.environ["PULSE_PROP_media.role"] = "music" os.environ["PULSE_PROP_application.icon_name"] = "quodlibet" browsers.init() from quodlibet.qltk.songlist import SongList, get_columns from quodlibet.util.collection import Album try: cover_size = config.getint("browsers", "cover_size") except config.Error: pass else: if cover_size > 0: Album.COVER_SIZE = cover_size headers = get_columns() SongList.set_all_column_headers(headers) for opt in config.options("header_maps"): val = config.get("header_maps", opt) util.tags.add(opt, val) in_all = ("~filename ~uri ~#lastplayed ~#rating ~#playcount ~#skipcount " "~#added ~#bitrate ~current ~#laststarted ~basename " "~dirname").split() for Kind in browsers.browsers: if Kind.headers is not None: Kind.headers.extend(in_all) Kind.init(library) pm = quodlibet.init_plugins("no-plugins" in startup_actions) if hasattr(player, "init_plugins"): player.init_plugins() from quodlibet.qltk import unity unity.init("quodlibet.desktop", player) from quodlibet.qltk.songsmenu import SongsMenu SongsMenu.init_plugins() from quodlibet.util.cover import CoverManager app.cover_manager = CoverManager() app.cover_manager.init_plugins() from quodlibet.plugins.playlist import PLAYLIST_HANDLER PLAYLIST_HANDLER.init_plugins() from quodlibet.plugins.query import QUERY_HANDLER QUERY_HANDLER.init_plugins() from gi.repository import GLib def exec_commands(*args): for cmd in cmds_todo: try: resp = cmd_registry.run(app, *cmd) except CommandError: pass else: if resp is not None: print_(resp, end="") from quodlibet.qltk.quodlibetwindow import QuodLibetWindow, PlayerOptions # Call exec_commands after the window is restored, but make sure # it's after the mainloop has started so everything is set up. app.window = window = QuodLibetWindow( library, player, restore_cb=lambda: GLib.idle_add(exec_commands, priority=GLib.PRIORITY_HIGH)) app.player_options = PlayerOptions(window) from quodlibet.qltk.debugwindow import MinExceptionDialog from quodlibet.qltk.window import on_first_map if backend_traceback is not None: def show_backend_error(window): d = MinExceptionDialog( window, _("Audio Backend Failed to Load"), _("Loading the audio backend '%(name)s' failed. " "Audio playback will be disabled.") % {"name": wanted_backend}, backend_traceback) d.run() # so we show the main window first on_first_map(app.window, show_backend_error, app.window) from quodlibet.plugins.events import EventPluginHandler pm.register_handler(EventPluginHandler(library.librarian, player)) from quodlibet.mmkeys import MMKeysHandler from quodlibet.remote import Remote, RemoteError from quodlibet.commands import registry as cmd_registry, CommandError from quodlibet.qltk.tracker import SongTracker, FSInterface try: from quodlibet.qltk.dbus_ import DBusHandler except ImportError: DBusHandler = lambda player, library: None mmkeys_handler = MMKeysHandler(app) mmkeys_handler.start() current_path = os.path.join(quodlibet.get_user_dir(), "current") fsiface = FSInterface(current_path, player) remote = Remote(app, cmd_registry) try: remote.start() except RemoteError: exit_(1, True) DBusHandler(player, library) tracker = SongTracker(library.librarian, player, window.playlist) from quodlibet.qltk import session session.init("quodlibet") quodlibet.enable_periodic_save(save_library=True) if "start-playing" in startup_actions: player.paused = False # restore browser windows from quodlibet.qltk.browser import LibraryBrowser GLib.idle_add(LibraryBrowser.restore, library, player, priority=GLib.PRIORITY_HIGH) def before_quit(): print_d("Saving active browser state") try: app.browser.save() except NotImplementedError: pass print_d("Shutting down player device %r." % player.version_info) player.destroy() quodlibet.main(window, before_quit=before_quit) app.player_options.destroy() quodlibet.finish_first_session(app.id) mmkeys_handler.quit() remote.stop() fsiface.destroy() tracker.destroy() quodlibet.library.save() config.save() print_d("Finished shutdown.")