summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xumpv97
1 files changed, 97 insertions, 0 deletions
diff --git a/umpv b/umpv
new file mode 100755
index 0000000..0080b44
--- /dev/null
+++ b/umpv
@@ -0,0 +1,97 @@
1#!/usr/bin/env python
2
3"""
4This script emulates "unique application" functionality on Linux. When starting
5playback with this script, it will try to reuse an already running instance of
6mpv (but only if that was started with umpv). Other mpv instances (not started
7by umpv) are ignored, and the script doesn't know about them.
8
9This only takes filenames as arguments. Custom options can't be used; the script
10interprets them as filenames. If mpv is already running, the files passed to
11umpv are appended to mpv's internal playlist. If a file does not exist or is
12otherwise not playable, mpv will skip the playlist entry when attempting to
13play it (from the GUI perspective, it's silently ignored).
14
15If mpv isn't running yet, this script will start mpv and let it control the
16current terminal. It will not write output to stdout/stderr, because this
17will typically just fill ~/.xsession-errors with garbage.
18
19mpv will terminate if there are no more files to play, and running the umpv
20script after that will start a new mpv instance.
21
22Note that you can control the mpv instance by writing to the command fifo:
23
24 echo "cycle fullscreen" > ~/.umpv_fifo
25
26Note: you can supply custom mpv path and options with the MPV environment
27 variable. The environment variable will be split on whitespace, and the
28 first item is used as path to mpv binary and the rest is passed as options
29 _if_ the script starts mpv. If mpv is not started by the script (i.e. mpv
30 is already running), this will be ignored.
31"""
32
33import sys
34import os
35import errno
36import subprocess
37import fcntl
38import stat
39import string
40
41files = sys.argv[1:]
42
43# this is the same method mpv uses to decide this
44def is_url(filename):
45 parts = filename.split("://", 1)
46 if len(parts) < 2:
47 return False
48 # protocol prefix has no special characters => it's an URL
49 allowed_symbols = string.ascii_letters + string.digits + '_'
50 prefix = parts[0]
51 return all(map(lambda c: c in allowed_symbols, prefix))
52
53# make them absolute; also makes them safe against interpretation as options
54def make_abs(filename):
55 if not is_url(filename):
56 return os.path.abspath(filename)
57 return filename
58files = [make_abs(f) for f in files]
59
60FIFO = os.path.join(os.getenv("HOME"), ".umpv_fifo")
61
62fifo_fd = -1
63try:
64 fifo_fd = os.open(FIFO, os.O_NONBLOCK | os.O_WRONLY)
65except OSError as e:
66 if e.errno == errno.ENXIO:
67 pass # pipe has no writer
68 elif e.errno == errno.ENOENT:
69 pass # doesn't exist
70 else:
71 raise e
72
73if fifo_fd >= 0:
74 # Unhandled race condition: what if mpv is terminating right now?
75 fcntl.fcntl(fifo_fd, fcntl.F_SETFL, 0) # set blocking mode
76 fifo = os.fdopen(fifo_fd, "w")
77 for f in files:
78 # escape: \ \n "
79 f = f.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
80 f = "\"" + f + "\""
81 fifo.write("raw loadfile " + f + " append\n")
82else:
83 # Recreate pipe if it doesn't already exist.
84 # Also makes sure it's safe, and no other user can create a bogus pipe
85 # that breaks security.
86 try:
87 os.unlink(FIFO)
88 except OSError as e:
89 pass
90 os.mkfifo(FIFO, 0o600)
91
92 opts = (os.getenv("MPV") or "mpv").split()
93 opts.extend(["--no-terminal", "--force-window", "--input-file=" + FIFO,
94 "--"])
95 opts.extend(files)
96
97 subprocess.check_call(opts)