From b61b58ce2c1371d6f904f108fa7feed3abfbe5c7 Mon Sep 17 00:00:00 2001 From: Robert Günzler Date: Wed, 8 Apr 2026 13:18:14 +0900 Subject: * MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated-commit-by: dots Signed-off-by: Robert Günzler --- bin/play-mpv | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100755 bin/play-mpv (limited to 'bin/play-mpv') diff --git a/bin/play-mpv b/bin/play-mpv new file mode 100755 index 0000000..21f20c6 --- /dev/null +++ b/bin/play-mpv @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 + +""" +This script emulates "unique application" functionality. When starting +playback with this script, it will try to reuse an already running instance of +mpv (but only if that was started with umpv). Other mpv instances (not started +by umpv) are ignored, and the script doesn't know about them. + +This only takes filenames as arguments. Custom options can't be used; the script +interprets them as filenames. If mpv is already running, the files passed to +umpv are appended to mpv's internal playlist. If a file does not exist or is +otherwise not playable, mpv will skip the playlist entry when attempting to +play it (from the GUI perspective, it's silently ignored). + +If mpv isn't running yet, this script will start mpv and let it control the +current terminal. It will not write output to stdout/stderr, because this +will typically just fill ~/.xsession-errors with garbage. + +mpv will terminate if there are no more files to play, and running the umpv +script after that will start a new mpv instance. + +Note: you can supply custom mpv path and options with the MPV environment + variable. The environment variable will be split on whitespace, and the + first item is used as path to mpv binary and the rest is passed as options + _if_ the script starts mpv. If mpv is not started by the script (i.e. mpv + is already running), this will be ignored. +""" + +import os +import shlex +import socket +import string +import subprocess +import sys +from collections.abc import Iterable +from typing import BinaryIO + +from itertools import tee + +def notify(files: Iterable[str]) -> None: + for f in files: + subprocess.Popen([ + 'fyi', + 'play-mpv', + f, + ], start_new_session=True) + +def is_url(filename: str) -> bool: + parts = filename.split("://", 1) + if len(parts) < 2: + return False + # protocol prefix has no special characters => it's an URL + allowed_symbols = string.ascii_letters + string.digits + "_" + prefix = parts[0] + return all(c in allowed_symbols for c in prefix) + +def get_socket_path() -> str: + base_dir = ( + os.getenv("UMPV_SOCKET_DIR") or + os.getenv("XDG_RUNTIME_DIR") or + os.getenv("HOME") or + os.getenv("TMPDIR") + ) + + if not base_dir: + raise Exception("Could not determine a base directory for the socket. " + "Ensure that one of the following environment variables is set: " + "UMPV_SOCKET_DIR, XDG_RUNTIME_DIR, HOME or TMPDIR.") + + return os.path.join(base_dir, ".umpv") + +def send_files_to_mpv(conn: socket.socket | BinaryIO, files: Iterable[str]) -> None: + notify(tee(files)[0]) + + try: + send = conn.send if isinstance(conn, socket.socket) else conn.write + for f in files: + f = f.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + send(f'raw loadfile "{f}" append-play\n'.encode()) + + except Exception: + print("mpv is terminating or the connection was lost.", file=sys.stderr) + sys.exit(1) + +def start_mpv(files: Iterable[str], socket_path: str) -> None: + [files, files_copy] = tee(files, 2) + notify(files_copy) + + mpv = "mpv" + mpv_command = shlex.split(os.getenv("MPV", mpv)) + mpv_command.extend([ + "--profile=builtin-pseudo-gui", + f"--input-ipc-server={socket_path}", + "--", + ]) + mpv_command.extend(files) + print(mpv_command) + # subprocess.Popen(mpv_command, start_new_session=True) + +def main() -> None: + files = (os.path.abspath(f) if not is_url(f) else f for f in sys.argv[1:]) + socket_path = get_socket_path() + + try: + with socket.socket(socket.AF_UNIX) as sock: + sock.connect(socket_path) + send_files_to_mpv(sock, files) + except (FileNotFoundError, ConnectionRefusedError): + start_mpv(files, socket_path) + +if __name__ == "__main__": + main() -- cgit 1.4.1