diff options
author | pacien | 2017-09-07 10:01:59 +0200 |
---|---|---|
committer | pacien | 2017-09-07 10:01:59 +0200 |
commit | d284fb7a137d69f2a846a2d7e08cd4aaa5233ef8 (patch) | |
tree | 0e652a50daaaa659447f92091e97528d88a468e3 /umpv | |
parent | 26dc11c6c5f6ba6584bd273f5cd93ae527a0a383 (diff) | |
download | desktop-utilities-d284fb7a137d69f2a846a2d7e08cd4aaa5233ef8.tar.gz |
Add unique mpv instance management script
Diffstat (limited to 'umpv')
-rwxr-xr-x | umpv | 97 |
1 files changed, 97 insertions, 0 deletions
@@ -0,0 +1,97 @@ | |||
1 | #!/usr/bin/env python | ||
2 | |||
3 | """ | ||
4 | This script emulates "unique application" functionality on Linux. When starting | ||
5 | playback with this script, it will try to reuse an already running instance of | ||
6 | mpv (but only if that was started with umpv). Other mpv instances (not started | ||
7 | by umpv) are ignored, and the script doesn't know about them. | ||
8 | |||
9 | This only takes filenames as arguments. Custom options can't be used; the script | ||
10 | interprets them as filenames. If mpv is already running, the files passed to | ||
11 | umpv are appended to mpv's internal playlist. If a file does not exist or is | ||
12 | otherwise not playable, mpv will skip the playlist entry when attempting to | ||
13 | play it (from the GUI perspective, it's silently ignored). | ||
14 | |||
15 | If mpv isn't running yet, this script will start mpv and let it control the | ||
16 | current terminal. It will not write output to stdout/stderr, because this | ||
17 | will typically just fill ~/.xsession-errors with garbage. | ||
18 | |||
19 | mpv will terminate if there are no more files to play, and running the umpv | ||
20 | script after that will start a new mpv instance. | ||
21 | |||
22 | Note that you can control the mpv instance by writing to the command fifo: | ||
23 | |||
24 | echo "cycle fullscreen" > ~/.umpv_fifo | ||
25 | |||
26 | Note: 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 | |||
33 | import sys | ||
34 | import os | ||
35 | import errno | ||
36 | import subprocess | ||
37 | import fcntl | ||
38 | import stat | ||
39 | import string | ||
40 | |||
41 | files = sys.argv[1:] | ||
42 | |||
43 | # this is the same method mpv uses to decide this | ||
44 | def 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 | ||
54 | def make_abs(filename): | ||
55 | if not is_url(filename): | ||
56 | return os.path.abspath(filename) | ||
57 | return filename | ||
58 | files = [make_abs(f) for f in files] | ||
59 | |||
60 | FIFO = os.path.join(os.getenv("HOME"), ".umpv_fifo") | ||
61 | |||
62 | fifo_fd = -1 | ||
63 | try: | ||
64 | fifo_fd = os.open(FIFO, os.O_NONBLOCK | os.O_WRONLY) | ||
65 | except 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 | |||
73 | if 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") | ||
82 | else: | ||
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) | ||