-
Notifications
You must be signed in to change notification settings - Fork 1
/
mpv_np.py
173 lines (129 loc) · 4.92 KB
/
mpv_np.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# requires Python 3
from abc import ABCMeta, abstractmethod
import json
import os.path
import socket
import sys
import hexchat
__module_name__ = "mpv now playing"
__module_version__ = "1.0.0"
__module_description__ = "Announces info of the currently loaded 'file' in mpv"
# # Configuration # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Paths to mpv's IPC socket or named pipe.
# Set the same path in your mpv.conf `input-ipc-server` setting
# or adjust these values.
WIN_PIPE_PATH = R"\\.\pipe\mpvsocket"
UNIX_PIPE_PATH = "/tmp/mpv-socket" # variables are expanded
# The command that is being executed.
# Supports mpv's property expansion:
# https://mpv.io/manual/stable/#property-expansion
CMD_FMT = R'me is playing: ${media-title} [${time-pos}${!duration==0: / ${duration}}]'
# # The Script # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# If asynchronous IO was to be added,
# the Win32API would need to be used on Windows.
# Details:
# - https://msdn.microsoft.com/en-us/library/windows/desktop/aa365683%28v=vs.85%29.aspx
# Examples:
# - https://msdn.microsoft.com/en-us/library/windows/desktop/aa365690%28v=vs.85%29.aspx
# - https://msdn.microsoft.com/en-us/library/windows/desktop/aa365592%28v=vs.85%29.aspx
# - https://github.com/mpv-player/mpv/blob/master/input/ipc-win.c
class MpvIpcClient(metaclass=ABCMeta):
"""Work with an open MPC instance via its JSON IPC.
In a blocking way.
Supports sending IPC commands,
input commands (input.conf style)
and arbitrary read/write_line calls.
Classmethod `for_platform`
will resolve to one of WinMpvIpcClient or UnixMpvIpcClient,
depending on the current platform.
"""
def __init__(self, path):
self.path = path
self._connect()
@classmethod
def for_platform(cls, platform=sys.platform, path=None):
if platform == 'win32':
return WinMpvIpcClient(path or WIN_PIPE_PATH)
else:
return UnixMpvIpcClient(path or UNIX_PIPE_PATH)
@abstractmethod
def _connect(self):
pass
@abstractmethod
def _write_line(self):
pass
@abstractmethod
def _read_line(self):
pass
@abstractmethod
def close(self):
pass
def command(self, command, *params):
data = json.dumps({"command": [command] + list(params)})
self._write_line(data)
while 1:
# read until a result line is found (containing "error" key)
result_line = self._read_line()
result = json.loads(result_line)
if 'error' in result:
break
if result['error'] != "success":
raise RuntimeError("mpv returned an error", result['error'])
return result['data']
def input_command(self, cmd):
"""Send an input command."""
self._write_line(cmd)
def __enter__(self):
return self
def __exit__(self, *_):
self.close()
class WinMpvIpcClient(MpvIpcClient):
def _connect(self):
self._f = open(self.path, "w+t", newline='', encoding='utf-8')
def _write_line(self, line):
self._f.write(line.strip())
self._f.write("\n")
self._f.flush()
def _read_line(self):
return self._f.readline()
def close(self):
self._f.close()
class UnixMpvIpcClient(MpvIpcClient):
buffer = b""
def _connect(self):
self._sock = socket.socket(socket.AF_UNIX)
self.expanded_path = os.path.expanduser(os.path.expandvars(self.path))
self._sock.connect(self.expanded_path)
def _write_line(self, line):
self._sock.sendall(line.strip().encode('utf-8'))
self._sock.send(b"\n")
def _read_line(self):
while 1:
if b"\n" in self.buffer:
line, _, self.buffer = self.buffer.partition(b"\n")
return line.decode('utf-8')
self.buffer += self._sock.recv(4096)
def close(self):
self._sock.close()
###############################################################################
def mpv_np(caller, callee, helper):
try:
with MpvIpcClient.for_platform() as mpv:
command = mpv.command('expand-text', CMD_FMT) # since mpv 0.25.0
if command:
hexchat.command(command)
else:
print("unable to expand property string; result was", command)
except OSError:
# import traceback; traceback.print_exc()
print("mpv IPC not running or bad configuration (see /help mpv)")
return hexchat.EAT_ALL
if __name__ == '__main__':
help_str = (
"Usage: /mpv\n"
"Setup: set `input-ipc-server={path}` in your mpv.conf file "
"(or adjust the path in the script source)."
.format(path=WIN_PIPE_PATH if sys.platform == 'win32' else UNIX_PIPE_PATH)
)
hexchat.hook_command("mpv", mpv_np, help=help_str)
print(__module_name__, __module_version__, "loaded")