Add UMPV_SOCKET_DIR for socket base dir selection and fallback to TMPDIR as last resort.
Unified · Split
| @@ -26,62 +26,67 @@ Note: you can supply custom mpv path and options with the MPV environment · 25 unmodified lines | |||
| 26 | is already running), this will be ignored. | 26 | is already running), this will be ignored. |
| 27 | """ | 27 | """ |
| 28 | 28 | ||
| 29 | import sys | ||
| 30 | import os | 29 | import os |
| 30 | import shlex | ||
| 31 | import socket | 31 | import socket |
| 32 | import errno | ||
| 33 | import subprocess | ||
| 34 | import string | 32 | import string |
| 35 | import shlex | 33 | import subprocess |
| 34 | import sys | ||
| 35 | from collections.abc import Iterable | ||
| 36 | 36 | ||
| 37 | files = sys.argv[1:] | ||
| 38 | 37 | ||
| 39 | # this is the same method mpv uses to decide this | 38 | def is_url(filename: str) -> bool: |
| 40 | def is_url(filename): | ||
| 41 | parts = filename.split("://", 1) | 39 | parts = filename.split("://", 1) |
| 42 | if len(parts) < 2: | 40 | if len(parts) < 2: |
| 43 | return False | 41 | return False |
| 44 | # protocol prefix has no special characters => it's an URL | 42 | # protocol prefix has no special characters => it's an URL |
| 45 | allowed_symbols = string.ascii_letters + string.digits + '_' | 43 | allowed_symbols = string.ascii_letters + string.digits + "_" |
| 46 | prefix = parts[0] | 44 | prefix = parts[0] |
| 47 | return all(map(lambda c: c in allowed_symbols, prefix)) | 45 | return all(c in allowed_symbols for c in prefix) |
| 48 | 46 | ||
| 49 | # make them absolute; also makes them safe against interpretation as options | 47 | def get_socket_path() -> str: |
| 50 | def make_abs(filename): | 48 | base_dir = ( |
| 51 | if not is_url(filename): | 49 | os.getenv("UMPV_SOCKET_DIR") or |
| 52 | return os.path.abspath(filename) | 50 | os.getenv("XDG_RUNTIME_DIR") or |
| 53 | return filename | 51 | os.getenv("HOME") or |
| 54 | files = (make_abs(f) for f in files) | 52 | os.getenv("TMPDIR") |
| 55 | 53 | ) | |
| 56 | SOCK = os.path.join(os.getenv("XDG_RUNTIME_DIR", os.getenv("HOME")), ".umpv_socket") | 54 | |
| 57 | 55 | if not base_dir: | |
| 58 | sock = None | 56 | raise Exception("Could not determine a base directory for the socket. " |
| 59 | try: | 57 | "Ensure that one of the following environment variables is set: " |
| 60 | sock = socket.socket(socket.AF_UNIX) | 58 | "UMPV_SOCKET_DIR, XDG_RUNTIME_DIR, HOME or TMPDIR.") |
| 61 | sock.connect(SOCK) | 59 | |
| 62 | except socket.error as e: | 60 | return os.path.join(base_dir, ".umpv_socket") |
| 63 | if e.errno == errno.ECONNREFUSED: | 61 | |
| 64 | sock = None | 62 | def send_files_to_mpv(sock: socket.socket, files: Iterable[str]) -> None: |
| 65 | pass # abandoned socket | ||
| 66 | elif e.errno == errno.ENOENT: | ||
| 67 | sock = None | ||
| 68 | pass # doesn't exist | ||
| 69 | else: | ||
| 70 | raise e | ||
| 71 | |||
| 72 | if sock: | ||
| 73 | # Unhandled race condition: what if mpv is terminating right now? | 63 | # Unhandled race condition: what if mpv is terminating right now? |
| 74 | for f in files: | 64 | for f in files: |
| 75 | # escape: \ \n " | 65 | # escape: \ \n " |
| 76 | f = f.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n") | 66 | f = f.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") |
| 77 | f = "\"" + f + "\"" | 67 | sock.send(f'raw loadfile "{f}" append\n'.encode()) |
| 78 | sock.send(("raw loadfile " + f + " append\n").encode("utf-8")) | 68 | |
| 79 | else: | 69 | def start_mpv(files: Iterable[str], socket_path: str) -> None: |
| 80 | # Let mpv recreate socket if it doesn't already exist. | 70 | mpv_command = shlex.split(os.getenv("MPV", "mpv")) |
| 81 | 71 | mpv_command.extend([ | |
| 82 | opts = shlex.split(os.getenv("MPV") or "mpv") | 72 | "--no-terminal", |
| 83 | opts.extend(["--no-terminal", "--force-window", "--input-ipc-server=" + SOCK, | 73 | "--force-window", |
| 84 | "--"]) | 74 | f"--input-ipc-server={socket_path}", |
| 85 | opts.extend(files) | 75 | "--", |
| 86 | 76 | ]) | |
| 87 | subprocess.check_call(opts) | 77 | mpv_command.extend(files) |
| 78 | subprocess.check_call(mpv_command) | ||
| 79 | |||
| 80 | def main() -> None: | ||
| 81 | files = (os.path.abspath(f) if not is_url(f) else f for f in sys.argv[1:]) | ||
| 82 | socket_path = get_socket_path() | ||
| 83 | |||
| 84 | try: | ||
| 85 | with socket.socket(socket.AF_UNIX) as sock: | ||
| 86 | sock.connect(socket_path) | ||
| 87 | send_files_to_mpv(sock, files) | ||
| 88 | except (FileNotFoundError, ConnectionRefusedError): | ||
| 89 | start_mpv(files, socket_path) | ||
| 90 | |||
| 91 | if __name__ == "__main__": | ||
| 92 | main() | ||