def test_uri2fsn(self): if os.name != "nt": path = uri2fsn("file:///home/piman/cr%21azy") self.assertTrue(is_fsnative(path)) self.assertEqual(path, fsnative(u"/home/piman/cr!azy")) else: path = uri2fsn("file:///C:/foo") self.assertTrue(is_fsnative(path)) self.assertEqual(path, fsnative(u"C:\\foo"))
def test_uri2fsn(self): if os.name != "nt": path = uri2fsn("file:///home/piman/cr%21azy") self.assertTrue(isinstance(path, fsnative)) self.assertEqual(path, fsnative(u"/home/piman/cr!azy")) else: path = uri2fsn("file:///C:/foo") self.assertTrue(isinstance(path, fsnative)) self.assertEqual(path, fsnative(u"C:\\foo"))
def test_uri_roundtrip(): if os.name == "nt": for path in [ u"C:\\foo-\u1234", u"C:\\bla\\quux ha", u"\\\\\u1234\\foo\\\u1423", u"\\\\foo;\\f" ]: path = fsnative(path) assert uri2fsn(fsn2uri(path)) == path assert isinstance(uri2fsn(fsn2uri(path)), fsnative) else: path = path2fsn(b"/foo-\xe1\x88\xb4") assert uri2fsn(fsn2uri(fsnative(u"/foo"))) == "/foo" assert uri2fsn(fsn2uri(path)) == path assert isinstance(uri2fsn(fsn2uri(path)), fsnative)
def __create_playlist(name, source_dir, files, library): playlist = FileBackedPlaylist.new(PLAYLISTS, name, library=library) print_d("Created playlist %s" % playlist) songs = [] win = WaitLoadWindow( None, len(files), _("Importing playlist.\n\n%(current)d/%(total)d songs added.")) win.show() for i, filename in enumerate(files): if not uri_is_valid(filename): # Plain filename. songs.append(_af_for(filename, library, source_dir)) else: try: filename = uri2fsn(filename) except ValueError: # Who knows! Hand it off to GStreamer. songs.append(formats.remote.RemoteFile(filename)) else: # URI-encoded local filename. songs.append(_af_for(filename, library, source_dir)) if win.step(): break win.destroy() playlist.extend(list(filter(None, songs))) return playlist
def __parse_playlist(name, plfilename, files, library): playlist = FileBackedPlaylist.new(PLAYLISTS, name, library=library) songs = [] win = WaitLoadWindow( None, len(files), _("Importing playlist.\n\n%(current)d/%(total)d songs added.")) win.show() for i, filename in enumerate(files): if not uri_is_valid(filename): # Plain filename. filename = os.path.realpath(os.path.join( os.path.dirname(plfilename), filename)) if library and filename in library: songs.append(library[filename]) else: songs.append(formats.MusicFile(filename)) else: try: filename = uri2fsn(filename) except ValueError: # Who knows! Hand it off to GStreamer. songs.append(formats.remote.RemoteFile(filename)) else: # URI-encoded local filename. filename = os.path.realpath(os.path.join( os.path.dirname(plfilename), filename)) if library and filename in library: songs.append(library[filename]) else: songs.append(formats.MusicFile(filename)) if win.step(): break win.destroy() playlist.extend(filter(None, songs)) return playlist
def __parse_playlist(name, plfilename, files, library): playlist = FileBackedPlaylist.new(PLAYLISTS, name, library=library) songs = [] win = WaitLoadWindow( None, len(files), _("Importing playlist.\n\n%(current)d/%(total)d songs added.")) win.show() for i, filename in enumerate(files): if not uri_is_valid(filename): # Plain filename. filename = os.path.realpath( os.path.join(os.path.dirname(plfilename), filename)) if library and filename in library: songs.append(library[filename]) else: songs.append(formats.MusicFile(filename)) else: try: filename = uri2fsn(filename) except ValueError: # Who knows! Hand it off to GStreamer. songs.append(formats.remote.RemoteFile(filename)) else: # URI-encoded local filename. filename = os.path.realpath( os.path.join(os.path.dirname(plfilename), filename)) if library and filename in library: songs.append(library[filename]) else: songs.append(formats.MusicFile(filename)) if win.step(): break win.destroy() playlist.extend(filter(None, songs)) return playlist
def test_any_pathnames(path): fsn = path2fsn(path) abspath = os.path.abspath(fsn) if os.path.isabs(abspath): if is_wine: # FIXME: fails on native Windows assert uri2fsn(fsn2uri(abspath)) == abspath
def uri2gsturi(uri): """Takes a correct uri and returns a gstreamer-compatible uri""" if not is_windows(): return uri try: # gstreamer requires extra slashes for network shares return GLib.filename_to_uri(uri2fsn(uri)) except (GLib.Error, ValueError): return uri
def read(self, db): """Iterate through the database and import data for songs found in the library """ # use the Row class for extracting rows db.row_factory = sqlite3.Row # iterate over all songs in the database # throws sqlite3.OperationalError if CoreTracks is not found for row in db.execute("SELECT * FROM CoreTracks"): try: filename = uri2fsn(row["Uri"]) except ValueError: continue song = self._library.get(normalize_path(filename)) if not song: continue has_changed = False # rating is stored as integer from 0 to 5 b_rating = row["Rating"] / 5.0 if b_rating != song("~#rating"): song["~#rating"] = b_rating has_changed = True # play count is stored as integer from 0 if row["PlayCount"] != song("~#playcount"): # summing play counts would break on multiple imports song["~#playcount"] = row["PlayCount"] has_changed = True # skip count is stored as integer from 0 if row["SkipCount"] != song("~#skipcount"): song["~#skipcount"] = row["SkipCount"] has_changed = True # timestamp is stored as integer or None if row["LastPlayedStamp"] is not None: value = row["LastPlayedStamp"] # keep timestamp if it is newer than what we had if value > song("~#lastplayed", 0): song["~#lastplayed"] = value has_changed = True if row["DateAddedStamp"] is not None: value = row["DateAddedStamp"] # keep timestamp if it is older than what we had if value < song("~#added", 0): song["~#added"] = value has_changed = True if has_changed: self._changed_songs.append(song)
def test_roundtrip(self): if os.name == "nt": paths = [u"C:\\öäü.txt"] else: paths = [u"/öäü.txt", u"/a/foo/bar", u"/a/b/foo/bar"] for source in paths: path = uri2fsn(fsn2uri(fsnative(source))) self.assertTrue(isinstance(path, fsnative)) self.assertEqual(path, fsnative(source))
def test_roundtrip(self): if os.name == "nt": paths = [u"C:\\öäü.txt"] else: paths = [u"/öäü.txt", u"//foo/bar", u"///foo/bar"] for source in paths: path = uri2fsn(fsn2uri(fsnative(source))) self.assertTrue(is_fsnative(path)) self.assertEqual(path, fsnative(source))
def endElement(self, name): self._tag = None if name == "entry" and self._current is not None: current = self._current self._current = None if len(current) > 1: uri = current.pop("location", "") try: filename = uri2fsn(uri) except ValueError: return self._process_song(normalize_path(filename), current)
def __drag_data_received(self, widget, drag_ctx, x, y, data, info, time): if info == 42: uris = data.get_uris() if uris: try: filename = uri2fsn(uris[0]) except ValueError: pass else: self.go_to(filename) Gtk.drag_finish(drag_ctx, True, False, time) return Gtk.drag_finish(drag_ctx, False, False, time)
def _populate_from_file(self): library = self.songs_lib try: tree = ET.parse(self.path) # TODO: validate some top-level tag data root = tree.getroot() if root.tag == "playlist": ns_mapping = None print_w(f"Using legacy namespace for import of {self.path}") elif root.tag == "{" + XSPF_NS + "}playlist": # Try correct format first ns_mapping = {"": XSPF_NS} else: raise ValueError(f"Unknown playlist root of {root.tag}") node = root.find("title", namespaces=ns_mapping) if node is None: print_w(f"No <title> found in {self.path} " f"(Got nodes: {[el.tag for el in root.iter()]})") elif self.name != node.text: print_w(f"Playlist was named {node.text!r} in XML " f"instead of {self.name!r} at {self.path!r}") for node in tree.iterfind(".//track", namespaces=ns_mapping): location = node.findtext("location", namespaces=ns_mapping).strip() path = location.replace("\n", "").replace("\r", "") try: # TODO: process relative URIs too? path = uri2fsn(path) except ValueError: pass if path in library: self._list.append(library[path]) elif library and library.masked(path): self._list.append(path) else: # TODO: handle missing playlist items (#3105, #729, #3131) node_dump = ET.tostring(node, method="xml").decode("utf-8") print_w("Couldn't find %r in playlist at %r. " "Perhaps its metadata will help: %r" % (path, self.path, node_dump)) self._list.append(path) library.mask(path) except (ET.ParseError, ValueError) as e: print_w("Couldn't load %r (%s)" % (self.path, e))
def test_write(self): with self.wrap("playlist") as pl: pl.extend(NUMERIC_SONGS) some_path = fsnative(os.path.join(self.temp, "xf0xf0")) pl.extend([some_path]) pl.write() assert exists(pl.path), "File doesn't exist" root = ElementTree().parse(pl.path) assert root.tag == "{http://xspf.org/ns/0/}playlist" tracks = root.findall(".//track", namespaces={"": XSPF_NS}) assert len(tracks) == len(NUMERIC_SONGS) + 1, f"Hmm found {tracks}" # Should now write compliant local URLs last_location = tracks[-1].find("location", namespaces={ "": XSPF_NS }).text assert uri2fsn(last_location) == some_path
def _enqueue_files(app, value): """Enqueues comma-separated filenames or song names. Commas in filenames should be backslash-escaped""" library = app.library window = app.window songs = [] for param in split_escape(value, ","): try: song_path = uri2fsn(param) except ValueError: song_path = param if song_path in library: songs.append(library[song_path]) elif os.path.isfile(song_path): songs.append(library.add_filename(os.path.realpath(value))) if songs: window.playlist.enqueue(songs)
def show_uri(label, uri): """Shows a uri. The uri can be anything handled by GIO or a quodlibet specific one. Currently handled quodlibet uris: - quodlibet:///prefs/plugins/<plugin id> Args: label (str) uri (str) the uri to show Returns: True on success, False on error """ parsed = urlparse(uri) if parsed.scheme == "quodlibet": if parsed.netloc != "": print_w("Unknown QuodLibet URL format (%s)" % uri) return False else: return __show_quodlibet_uri(parsed) elif parsed.scheme == "file" and (is_windows() or is_osx()): # Gio on non-Linux can't handle file URIs for some reason, # fall back to our own implementation for now from quodlibet.qltk.showfiles import show_files try: filepath = uri2fsn(uri) except ValueError: return False else: return show_files(filepath, []) else: # Gtk.show_uri_on_window exists since 3.22 try: if hasattr(Gtk, "show_uri_on_window"): from quodlibet.qltk import get_top_parent return Gtk.show_uri_on_window(get_top_parent(label), uri, 0) else: return Gtk.show_uri(None, uri, 0) except GLib.Error: return False
def __drag_data_received(self, widget, ctx, x, y, sel, tid, etime): assert tid == DND_URI_LIST uris = sel.get_uris() dirs = [] error = False for uri in uris: try: filename = uri2fsn(uri) except ValueError: filename = None if filename is not None: loc = os.path.normpath(filename) if os.path.isdir(loc): dirs.append(loc) else: loc = os.path.realpath(loc) if loc not in self.__library: self.__library.add_filename(loc) elif app.player.can_play_uri(uri): if uri not in self.__library: self.__library.add([RemoteFile(uri)]) else: error = True break Gtk.drag_finish(ctx, not error, False, etime) if error: ErrorMessage( self, _("Unable to add songs"), _("%s uses an unsupported protocol.") % util.bold(uri)).run() else: if dirs: copool.add(self.__library.scan, dirs, cofuncid="library", funcid="library")
def __create_playlist(name, source_dir, files, songs_lib, pl_lib): songs = [] win = WaitLoadWindow( None, len(files), _("Importing playlist.\n\n%(current)d/%(total)d songs added.")) win.show() for i, filename in enumerate(files): song = None if not uri_is_valid(filename): # Plain filename. song = _af_for(filename, songs_lib, source_dir) else: try: filename = uri2fsn(filename) except ValueError: # Who knows! Hand it off to GStreamer. song = formats.remote.RemoteFile(filename) else: # URI-encoded local filename. song = _af_for(filename, songs_lib, source_dir) # Only add existing (not None) files to the playlist. # Otherwise multiple errors are thrown when the files are accessed # to update the displayed track infos. if song is not None: songs.append(song) elif (os.path.exists(filename) or os.path.exists(os.path.join(source_dir, filename))): print_w("Can't add file to playlist:" f" Unsupported file format. '{filename}'") else: print_w( f"Can't add file to playlist: File not found. '{filename}'") if win.step(): break win.destroy() return pl_lib.create_from_songs(songs)
def __create_playlist(name, source_dir, files, songs_lib, pl_lib): songs = [] win = WaitLoadWindow( None, len(files), _("Importing playlist.\n\n%(current)d/%(total)d songs added.")) win.show() for i, filename in enumerate(files): if not uri_is_valid(filename): # Plain filename. songs.append(_af_for(filename, songs_lib, source_dir)) else: try: filename = uri2fsn(filename) except ValueError: # Who knows! Hand it off to GStreamer. songs.append(formats.remote.RemoteFile(filename)) else: # URI-encoded local filename. songs.append(_af_for(filename, songs_lib, source_dir)) if win.step(): break win.destroy() return pl_lib.create_from_songs(songs)
def __drag_data_received(self, widget, ctx, x, y, sel, tid, etime): assert tid == DND_URI_LIST uris = sel.get_uris() dirs = [] error = False for uri in uris: try: filename = uri2fsn(uri) except ValueError: filename = None if filename is not None: loc = os.path.normpath(filename) if os.path.isdir(loc): dirs.append(loc) else: loc = os.path.realpath(loc) if loc not in self.__library: self.__library.add_filename(loc) elif app.player.can_play_uri(uri): if uri not in self.__library: self.__library.add([RemoteFile(uri)]) else: error = True break Gtk.drag_finish(ctx, not error, False, etime) if error: ErrorMessage( self, _("Unable to add songs"), _("%s uses an unsupported protocol.") % util.bold(uri)).run() else: if dirs: copool.add( self.__library.scan, dirs, cofuncid="library", funcid="library")
def to_filename(s): try: return uri2fsn(s) except ValueError: return None
def test_uri2fsn(): if os.name != "nt": with pytest.raises(ValueError): assert uri2fsn(u"file:///%00") with pytest.raises(ValueError): assert uri2fsn("file:///%00") assert uri2fsn("file:///foo") == fsnative(u"/foo") assert uri2fsn(u"file:///foo") == fsnative(u"/foo") assert isinstance(uri2fsn("file:///foo"), fsnative) assert isinstance(uri2fsn(u"file:///foo"), fsnative) assert \ uri2fsn("file:///foo-%E1%88%B4") == path2fsn(b"/foo-\xe1\x88\xb4") assert uri2fsn("file:NOPE") == fsnative(u"/NOPE") assert uri2fsn("file:/NOPE") == fsnative(u"/NOPE") with pytest.raises(ValueError): assert uri2fsn("file://NOPE") assert uri2fsn("file:///bla:[email protected]") == \ fsnative(u"/bla:[email protected]") assert uri2fsn("file:///bla?x#b") == fsnative(u"/bla?x#b") else: assert uri2fsn("file:NOPE") == "\\NOPE" assert uri2fsn("file:/NOPE") == "\\NOPE" with pytest.raises(ValueError): assert uri2fsn(u"file:///C:/%00") with pytest.raises(ValueError): assert uri2fsn("file:///C:/%00") assert uri2fsn("file:///C:/foo") == fsnative(u"C:\\foo") assert uri2fsn(u"file:///C:/foo") == fsnative(u"C:\\foo") assert isinstance(uri2fsn("file:///C:/foo"), fsnative) assert isinstance(uri2fsn(u"file:///C:/foo"), fsnative) assert uri2fsn(u"file:///C:/foo-\u1234") == fsnative(u"C:\\foo-\u1234") assert \ uri2fsn("file:///C:/foo-%E1%88%B4") == fsnative(u"C:\\foo-\u1234") assert uri2fsn(u"file://UNC/foo/bar") == u"\\\\UNC\\foo\\bar" assert uri2fsn(u"file://\u1234/\u4321") == u"\\\\\u1234\\\u4321" with pytest.raises(TypeError): uri2fsn(object()) with pytest.raises(ValueError): uri2fsn("http://www.foo.bar") if os.name == "nt": with pytest.raises(ValueError): uri2fsn(u"\u1234") if PY3: with pytest.raises(TypeError): uri2fsn(b"file:///foo")
def read(self, plist): for track in plist['Tracks'].values(): try: filename = uri2fsn(track['Location']).replace('\\', '', 2) except ValueError: continue song = self._lib.get(normalize_path(filename)) if not song: continue has_changed = False try: track['Rating Computed'] except KeyError: try: iRating = track['Rating'] / 100.0 if song("~#rating") == 0 or song("~#rating") == None: song["~#rating"] = iRating has_changed = True elif iRating != song["~#rating"]: avgr = (iRating + song("~#rating")) / 2.0 song["~#rating"] = avgr has_changed = True except KeyError: pass try: pc = track['Play Count'] try: pc = song["~#playcount"] + pc song["~#playcount"] = pc has_changed = True except KeyError: song["~#playcount"] = pc has_changed = True except KeyError: pass try: sc = track['Skip Count'] try: sc = song["~#skipcount"] + sc song["~#skipcount"] = sc has_changed = True except KeyError: song["~#skipcount"] = sc has_changed = True except KeyError: pass try: song["~#lastplayed"] except KeyError: try: song["~#lastplayed"] = track['Play Date'] has_changed = True except KeyError: pass try: add_date = int(track['Date Added'].timestamp()) if song["~#added"] > add_date: song['~#added'] = add_date has_changed = True except KeyError: pass if has_changed: self._changed_songs.append(song)
def process_arguments(argv): from quodlibet.util.path import uri_is_valid from quodlibet import util from quodlibet import const actions = [] controls = [ "next", "previous", "play", "pause", "play-pause", "stop", "hide-window", "show-window", "toggle-window", "focus", "quit", "unfilter", "refresh", "force-previous" ] controls_opt = [ "seek", "repeat", "query", "volume", "filter", "set-rating", "set-browser", "open-browser", "shuffle", "song-list", "queue", "stop-after", "random", "repeat-type", "shuffle-type" ] options = util.OptionParser("Quod Libet", const.VERSION, _("a music library and player"), _("[option]")) options.add("print-playing", help=_("Print the playing song and exit")) options.add("start-playing", help=_("Begin playing immediately")) options.add("start-hidden", help=_("Don't show any windows on start")) for opt, help in [ ("next", _("Jump to next song")), ("previous", _("Jump to previous song or restart if near the beginning")), ("force-previous", _("Jump to previous song")), ("play", _("Start playback")), ("pause", _("Pause playback")), ("play-pause", _("Toggle play/pause mode")), ("stop", _("Stop playback")), ("volume-up", _("Turn up volume")), ("volume-down", _("Turn down volume")), ("status", _("Print player status")), ("hide-window", _("Hide main window")), ("show-window", _("Show main window")), ("toggle-window", _("Toggle main window visibility")), ("focus", _("Focus the running player")), ("unfilter", _("Remove active browser filters")), ("refresh", _("Refresh and rescan library")), ("list-browsers", _("List available browsers")), ("print-playlist", _("Print the current playlist")), ("print-queue", _("Print the contents of the queue")), ("print-query-text", _("Print the active text query")), ("no-plugins", _("Start without plugins")), ("run", _("Start Quod Libet if it isn't running")), ("quit", _("Exit Quod Libet")), ]: options.add(opt, help=help) for opt, help, arg in [ ("seek", _("Seek within the playing song"), _("[+|-][HH:]MM:SS")), ("shuffle", _("Set or toggle shuffle mode"), "0|1|t"), ("shuffle-type", _("Set shuffle mode type"), "random|weighted|off"), ("repeat", _("Turn repeat off, on, or toggle it"), "0|1|t"), ("repeat-type", _("Set repeat mode type"), "current|all|one|off"), ("volume", _("Set the volume"), "(+|-|)0..100"), ("query", _("Search your audio library"), _("query")), ("play-file", _("Play a file"), C_("command", "filename")), ("set-rating", _("Rate the playing song"), "0.0..1.0"), ("set-browser", _("Set the current browser"), "BrowserName"), ("stop-after", _("Stop after the playing song"), "0|1|t"), ("open-browser", _("Open a new browser"), "BrowserName"), ("queue", _("Show or hide the queue"), "on|off|t"), ("song-list", _("Show or hide the main song list (deprecated)"), "on|off|t"), ("random", _("Filter on a random value"), C_("command", "tag")), ("filter", _("Filter on a tag value"), _("tag=value")), ("enqueue", _("Enqueue a file or query"), "%s|%s" % (C_("command", "filename"), _("query"))), ("enqueue-files", _("Enqueue comma-separated files"), "%s[,%s..]" % (_("filename"), _("filename"))), ("print-query", _("Print filenames of results of query to stdout"), _("query")), ("unqueue", _("Unqueue a file or query"), "%s|%s" % (C_("command", "filename"), _("query"))), ]: options.add(opt, help=help, arg=arg) options.add("sm-config-prefix", arg="dummy") options.add("sm-client-id", arg="prefix") options.add("screen", arg="dummy") def is_vol(str): if len(str) == 1 and str[0] in '+-': return True return is_float(str) def is_time(str): if str[0] not in "+-0123456789": return False elif str[0] in "+-": str = str[1:] parts = str.split(":") if len(parts) > 3: return False else: return not (False in [p.isdigit() for p in parts]) def is_float(str): try: float(str) except ValueError: return False else: return True validators = { "shuffle": ["0", "1", "t", "on", "off", "toggle"].__contains__, "shuffle-type": ["random", "weighted", "off", "0"].__contains__, "repeat": ["0", "1", "t", "on", "off", "toggle"].__contains__, "repeat-type": ["current", "all", "one", "off", "0"].__contains__, "volume": is_vol, "seek": is_time, "set-rating": is_float, "stop-after": ["0", "1", "t"].__contains__, } cmds_todo = [] def queue(*args): cmds_todo.append(args) # XXX: to make startup work in case the desktop file isn't passed # a file path/uri if argv[-1] == "--play-file": argv = argv[:-1] opts, args = options.parse(argv[1:]) for command, arg in opts.items(): if command in controls: queue(command) elif command in controls_opt: if command in validators and not validators[command](arg): print_e(_("Invalid argument for '%s'.") % command) print_e(_("Try %s --help.") % fsn2text(argv[0])) exit_(True, notify_startup=True) else: queue(command, arg) elif command == "status": queue("status") elif command == "print-playlist": queue("dump-playlist") elif command == "print-queue": queue("dump-queue") elif command == "list-browsers": queue("dump-browsers") elif command == "volume-up": queue("volume +") elif command == "volume-down": queue("volume -") elif command == "enqueue" or command == "unqueue": try: filename = uri2fsn(arg) except ValueError: filename = arg queue(command, filename) elif command == "enqueue-files": queue(command, arg) elif command == "play-file": if uri_is_valid(arg) and arg.startswith("quodlibet://"): # TODO: allow handling of URIs without --play-file queue("uri-received", arg) else: try: filename = uri2fsn(arg) except ValueError: filename = arg filename = os.path.abspath(util.path.expanduser(arg)) queue("play-file", filename) elif command == "print-playing": try: queue("print-playing", args[0]) except IndexError: queue("print-playing") elif command == "print-query": queue(command, arg) elif command == "print-query-text": queue(command) elif command == "start-playing": actions.append(command) elif command == "start-hidden": actions.append(command) elif command == "no-plugins": actions.append(command) elif command == "run": actions.append(command) if cmds_todo: for cmd in cmds_todo: control(*cmd, **{"ignore_error": "run" in actions}) else: # this will exit if it succeeds control('focus', ignore_error=True) return actions, cmds_todo
def process_arguments(argv): from quodlibet.util.path import uri_is_valid from quodlibet import util from quodlibet import const actions = [] controls = ["next", "previous", "play", "pause", "play-pause", "stop", "hide-window", "show-window", "toggle-window", "focus", "quit", "unfilter", "refresh", "force-previous"] controls_opt = ["seek", "repeat", "query", "volume", "filter", "set-rating", "set-browser", "open-browser", "shuffle", "song-list", "queue", "stop-after"] options = util.OptionParser( "Quod Libet", const.VERSION, _("a music library and player"), _("[option]")) options.add("print-playing", help=_("Print the playing song and exit")) options.add("start-playing", help=_("Begin playing immediately")) for opt, help in [ ("next", _("Jump to next song")), ("previous", _("Jump to previous song or restart if near the beginning")), ("force-previous", _("Jump to previous song")), ("play", _("Start playback")), ("pause", _("Pause playback")), ("play-pause", _("Toggle play/pause mode")), ("stop", _("Stop playback")), ("volume-up", _("Turn up volume")), ("volume-down", _("Turn down volume")), ("status", _("Print player status")), ("hide-window", _("Hide main window")), ("show-window", _("Show main window")), ("toggle-window", _("Toggle main window visibility")), ("focus", _("Focus the running player")), ("unfilter", _("Remove active browser filters")), ("refresh", _("Refresh and rescan library")), ("list-browsers", _("List available browsers")), ("print-playlist", _("Print the current playlist")), ("print-queue", _("Print the contents of the queue")), ("print-query-text", _("Print the active text query")), ("no-plugins", _("Start without plugins")), ("run", _("Start Quod Libet if it isn't running")), ("quit", _("Exit Quod Libet")), ]: options.add(opt, help=help) for opt, help, arg in [ ("seek", _("Seek within the playing song"), _("[+|-][HH:]MM:SS")), ("shuffle", _("Set or toggle shuffle mode"), "[0|1|t"), ("repeat", _("Turn repeat off, on, or toggle it"), "0|1|t"), ("volume", _("Set the volume"), "(+|-|)0..100"), ("query", _("Search your audio library"), _("query")), ("play-file", _("Play a file"), C_("command", "filename")), ("set-rating", _("Rate the playing song"), "0.0..1.0"), ("set-browser", _("Set the current browser"), "BrowserName"), ("stop-after", _("Stop after the playing song"), "0|1|t"), ("open-browser", _("Open a new browser"), "BrowserName"), ("queue", _("Show or hide the queue"), "on|off|t"), ("song-list", _("Show or hide the main song list (deprecated)"), "on|off|t"), ("random", _("Filter on a random value"), C_("command", "tag")), ("filter", _("Filter on a tag value"), _("tag=value")), ("enqueue", _("Enqueue a file or query"), "%s|%s" % ( C_("command", "filename"), _("query"))), ("enqueue-files", _("Enqueue comma-separated files"), "%s[,%s..]" % ( _("filename"), _("filename"))), ("print-query", _("Print filenames of results of query to stdout"), _("query")), ("unqueue", _("Unqueue a file or query"), "%s|%s" % ( C_("command", "filename"), _("query"))), ]: options.add(opt, help=help, arg=arg) options.add("sm-config-prefix", arg="dummy") options.add("sm-client-id", arg="prefix") options.add("screen", arg="dummy") def is_vol(str): if str[0] in '+-': if len(str) == 1: return True str = str[1:] return str.isdigit() def is_time(str): if str[0] not in "+-0123456789": return False elif str[0] in "+-": str = str[1:] parts = str.split(":") if len(parts) > 3: return False else: return not (False in [p.isdigit() for p in parts]) def is_float(str): try: float(str) except ValueError: return False else: return True validators = { "shuffle": ["0", "1", "t", "on", "off", "toggle"].__contains__, "repeat": ["0", "1", "t", "on", "off", "toggle"].__contains__, "volume": is_vol, "seek": is_time, "set-rating": is_float, "stop-after": ["0", "1", "t"].__contains__, } cmds_todo = [] def queue(*args): cmds_todo.append(args) # XXX: to make startup work in case the desktop file isn't passed # a file path/uri if argv[-1] == "--play-file": argv = argv[:-1] opts, args = options.parse(argv[1:]) for command, arg in opts.items(): if command in controls: queue(command) elif command in controls_opt: if command in validators and not validators[command](arg): print_e(_("Invalid argument for '%s'.") % command) print_e(_("Try %s --help.") % fsn2text(argv[0])) exit_(True, notify_startup=True) else: queue(command, arg) elif command == "status": queue("status") elif command == "print-playlist": queue("dump-playlist") elif command == "print-queue": queue("dump-queue") elif command == "list-browsers": queue("dump-browsers") elif command == "volume-up": queue("volume +") elif command == "volume-down": queue("volume -") elif command == "enqueue" or command == "unqueue": try: filename = uri2fsn(arg) except ValueError: filename = arg queue(command, filename) elif command == "enqueue-files": queue(command, arg) elif command == "play-file": if uri_is_valid(arg) and arg.startswith("quodlibet://"): # TODO: allow handling of URIs without --play-file queue("uri-received", arg) else: try: filename = uri2fsn(arg) except ValueError: filename = arg filename = os.path.abspath(util.path.expanduser(arg)) queue("play-file", filename) elif command == "print-playing": try: queue("print-playing", args[0]) except IndexError: queue("print-playing") elif command == "print-query": queue(command, arg) elif command == "print-query-text": queue(command) elif command == "start-playing": actions.append(command) elif command == "no-plugins": actions.append(command) elif command == "run": actions.append(command) if cmds_todo: for cmd in cmds_todo: control(*cmd, **{"ignore_error": "run" in actions}) else: # this will exit if it succeeds control('focus', ignore_error=True) return actions, cmds_todo