mirror of https://github.com/mpv-player/mpv
123 lines
4.1 KiB
Python
Executable File
123 lines
4.1 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
"""
|
|
This script emulates "unique application" functionality on Linux. 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 that you can control the mpv instance by writing to the command fifo:
|
|
|
|
echo "cycle fullscreen" > /tmp/umpv-fifo-*
|
|
|
|
Note: you can supply custom options with the UMPV_OPTIONS environment variable.
|
|
The environment variable will be split on whitespace, and each item is
|
|
passed as option to mpv _if_ the script starts mpv. If mpv is not started
|
|
by the script (i.e. mpv is already running), the options will be ignored.
|
|
|
|
Warning:
|
|
|
|
The script attempts to make sure the FIFO is safely created (i.e. not world-
|
|
writable), and checks that it's really a FIFO. This is important for security,
|
|
because the FIFO allows anyone with write access to run arbitrary commands
|
|
in mpv's context using the "run" input command. If you are worried about
|
|
security, you should verify that the code handles these concerns correctly.
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import errno
|
|
import subprocess
|
|
import fcntl
|
|
import stat
|
|
import string
|
|
|
|
if len(sys.argv) < 1:
|
|
sys.exit(1)
|
|
files = sys.argv[1:]
|
|
|
|
# this is about the same method mpv uses to decide this
|
|
def is_url(filename):
|
|
parts = filename.split(":", 1)
|
|
if len(parts) < 2:
|
|
return False
|
|
# protocol prefix has no special characters => it's an URL
|
|
prefix = parts[0]
|
|
for c in prefix:
|
|
if string.ascii_letters.find(c) < 0:
|
|
return False
|
|
return True
|
|
|
|
# make them absolute; also makes them safe against interpretation as options
|
|
def make_abs(filename):
|
|
if not is_url(filename):
|
|
return os.path.abspath(filename)
|
|
return filename
|
|
files = [make_abs(f) for f in files]
|
|
|
|
opts_env = os.getenv("UMPV_OPTIONS")
|
|
opts_env = opts_env.split() if opts_env else []
|
|
|
|
FIFO = "/tmp/umpv-fifo-" + os.getenv("USER")
|
|
|
|
fifo_fd = -1
|
|
try:
|
|
fifo_fd = os.open(FIFO, os.O_NONBLOCK | os.O_WRONLY)
|
|
except OSError as e:
|
|
if e.errno == errno.ENXIO:
|
|
pass # pipe has no writer
|
|
elif e.errno == errno.ENOENT:
|
|
pass # doesn't exist
|
|
else:
|
|
raise e
|
|
|
|
if fifo_fd >= 0:
|
|
st = os.fstat(fifo_fd)
|
|
if (((st.st_mode & (stat.S_IRWXG | stat.S_IRWXO)) != 0) or
|
|
(not stat.S_ISFIFO(st.st_mode)) or
|
|
(st.st_uid != os.getuid())):
|
|
print("error: command FIFO is not a FIFO or has bogus permissions")
|
|
sys.exit(1)
|
|
|
|
if fifo_fd >= 0:
|
|
# Unhandled race condition: what if mpv is terminating right now?
|
|
fcntl.fcntl(fifo_fd, fcntl.F_SETFL, 0) # set blocking mode
|
|
fifo = os.fdopen(fifo_fd, "w")
|
|
for f in files:
|
|
# escape: \ \n "
|
|
f = f.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
|
|
f = "\"" + f + "\""
|
|
fifo.write("raw loadfile " + f + " append\n")
|
|
else:
|
|
# Recreate pipe if it doesn't already exist.
|
|
# Also makes sure it's safe, and no other user can create a bogus pipe
|
|
# that breaks security.
|
|
try:
|
|
os.unlink(FIFO)
|
|
except OSError as e:
|
|
if e.errno == errno.ENOENT:
|
|
pass
|
|
else:
|
|
raise e
|
|
os.mkfifo(FIFO, int("0600", 8))
|
|
|
|
opts = ["mpv", "--no-terminal", "--force-window", "--input-file=" + FIFO]
|
|
opts.extend(opts_env)
|
|
opts.append("--")
|
|
opts.extend(files)
|
|
|
|
subprocess.check_call(opts)
|