- commit
- b322e3b98473d4eeeef5ad75f35f52bd575321cf
- parent
- dc24e1a79d94d6cf1b1c90b2e6bcef9f5c619c7b
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2021-06-17 21:15
use mpv IPC see https://mpv.io/manual/master/#using-mpv-from-other-programs-or-scripts
Diffstat
| M | cplay.py | 112 | +++++++++++++++++++++++++++++++++++++++---------------------- |
1 files changed, 71 insertions, 41 deletions
diff --git a/cplay.py b/cplay.py
@@ -1,10 +1,12 @@ 1 1 import curses 2 2 import functools -1 3 import json 3 4 import os 4 5 import random 5 6 import re 6 7 import selectors 7 8 import signal -1 9 import socket 8 10 import subprocess 9 11 import sys 10 12 import termios @@ -86,6 +88,16 @@ def resize(*args): 86 88 os.write(app.resize_out, b'.') 87 89 88 90 -1 91 def get_socket(path): -1 92 while True: -1 93 try: -1 94 sock = socket.socket(family=socket.AF_UNIX) -1 95 sock.connect(path) -1 96 return sock -1 97 except (FileNotFoundError, ConnectionRefusedError): -1 98 time.sleep(0.1) -1 99 -1 100 89 101 @functools.lru_cache() 90 102 def relpath(path): 91 103 if path.startswith(filelist.path): @@ -123,26 +135,56 @@ def listdir(path): 123 135 124 136 125 137 class Player:126 -1 re_progress = re.compile(br'AV?: (\d+):(\d+):(\d+) / (\d+):(\d+):(\d+)')127 -1128 138 def __init__(self):129 -1 self._proc = None130 139 self.path = None 131 140 self.position = 0 132 141 self.length = 0 133 142 self._seek_step = 0 134 143 self._seek_timeout = None -1 144 self.is_playing = False -1 145 self._playing = 0 -1 146 self._buffer = b'' 135 147136 -1 self.stdin_r, self.stdin_w = os.pipe()137 -1 self.stdout_r, self.stdout_w = os.pipe()138 -1 self.stderr_r, self.stderr_w = os.pipe()139 -1140 -1 def parse_progress(self, fd):141 -1 match = self.re_progress.search(os.read(fd, 512))142 -1 if match and not self._seek_step:143 -1 ph, pm, ps, lh, lm, ls = map(int, match.groups())144 -1 self.position = ph * 3600 + pm * 60 + ps145 -1 self.length = lh * 3600 + lm * 60 + ls-1 148 self.socket_path = '/tmp/mpv-cplay-%i.sock' % os.getpid() -1 149 self._proc = subprocess.Popen( -1 150 [ -1 151 'mpv', -1 152 '--input-ipc-server=%s' % self.socket_path, -1 153 '--idle', -1 154 '--audio-display=no', -1 155 '--replaygain=track', -1 156 ], -1 157 stdin=subprocess.DEVNULL, -1 158 stdout=subprocess.DEVNULL, -1 159 stderr=subprocess.DEVNULL, -1 160 ) -1 161 self.socket = get_socket(self.socket_path) -1 162 -1 163 self._ipc('observe_property', 1, 'time-pos') -1 164 self._ipc('observe_property', 2, 'duration') -1 165 -1 166 def _ipc(self, cmd, *args): -1 167 data = json.dumps({'command': [cmd, *args]}) -1 168 msg = data.encode('utf-8') + b'\n' -1 169 self.socket.send(msg) -1 170 -1 171 def handle_ipc(self, data): -1 172 if data.get('event') == 'property-change' and data['id'] == 1: -1 173 if data['data'] is not None and not self._seek_step: -1 174 self.position = data['data'] -1 175 elif data.get('event') == 'property-change' and data['id'] == 2: -1 176 if data['data'] is not None: -1 177 self.length = data['data'] -1 178 elif data.get('event') == 'end-file': -1 179 self._playing -= 1 -1 180 -1 181 def parse_progress(self): -1 182 self._buffer += self.socket.recv(1024) -1 183 msgs = self._buffer.split(b'\n') -1 184 self._buffer = msgs.pop() -1 185 for msg in msgs: -1 186 data = json.loads(msg.decode('utf-8')) -1 187 self.handle_ipc(data) 146 188 147 189 def get_progress(self): 148 190 if self.length == 0: @@ -150,28 +192,16 @@ class Player: 150 192 return self.position / self.length 151 193 152 194 def stop(self):153 -1 if self._proc:154 -1 self._proc.terminate()155 -1 self._proc = None-1 195 self.is_playing = False -1 196 self._ipc('stop') 156 197 157 198 def _play(self):158 -1 self.stop()159 199 if not self.path: -1 200 self.is_playing = False 160 201 return161 -1162 -1 self._proc = subprocess.Popen(163 -1 [164 -1 'mpv',165 -1 '--audio-display=no',166 -1 '--start=%i' % self.position,167 -1 '--volume=100',168 -1 '--replaygain=track',169 -1 self.path,170 -1 ],171 -1 stdout=self.stdout_w,172 -1 stderr=self.stderr_w,173 -1 stdin=self.stdin_r,174 -1 )-1 202 self.is_playing = True -1 203 self._playing += 1 -1 204 self._ipc('loadfile', self.path, 'replace', 'start=%i' % self.position) 175 205 176 206 def play(self, path): 177 207 self.path = path @@ -181,7 +211,7 @@ class Player: 181 211 self._play() 182 212 183 213 def toggle(self):184 -1 if self._proc:-1 214 if self.is_playing: 185 215 self.stop() 186 216 elif self.path: 187 217 self._play() @@ -205,12 +235,12 @@ class Player: 205 235 self._play() 206 236 207 237 @property208 -1 def is_playing(self):209 -1 return self._proc is not None210 -1211 -1 @property212 238 def is_finished(self):213 -1 return self._proc is not None and self._proc.poll() is not None-1 239 return self.is_playing and self._playing == 0 -1 240 -1 241 def cleanup(self): -1 242 self._proc.terminate() -1 243 os.remove(self.socket_path) 214 244 215 245 216 246 class Input: @@ -730,7 +760,7 @@ class Application: 730 760 with selectors.DefaultSelector() as sel: 731 761 sel.register(sys.stdin, selectors.EVENT_READ) 732 762 sel.register(self.resize_in, selectors.EVENT_READ)733 -1 sel.register(player.stderr_r, selectors.EVENT_READ)-1 763 sel.register(player.socket, selectors.EVENT_READ) 734 764 735 765 while True: 736 766 player.finish_seek() @@ -743,8 +773,8 @@ class Application: 743 773 self.render(force=True) 744 774 elif key.fileobj is sys.stdin: 745 775 self.process_key(screen.get_wch())746 -1 elif key.fileobj is player.stderr_r:747 -1 player.parse_progress(player.stderr_r)-1 776 elif key.fileobj is player.socket: -1 777 player.parse_progress() 748 778 749 779 if player.is_finished: 750 780 player.play(playlist.next()) @@ -774,7 +804,7 @@ def main(): 774 804 with enable_ctrl_keys(): 775 805 app.run() 776 806 finally:777 -1 player.stop()-1 807 player.cleanup() 778 808 curses.endwin() 779 809 780 810