media/mpd/ SimplePythonWebApi
This is a work in progress.
#!/usr/bin/env python3
# Python 3 server example
from http.server import BaseHTTPRequestHandler, HTTPServer
from subprocess import run, PIPE
import time
import sys
import re
import json
import os
from datetime import datetime
from threading import Thread
import urllib.parse
hostName = ""
serverPort = 4097
def play_pause():
Thread(target = lambda: run(["mp","p"])).start()
def mpc_vol(v):
Thread(target = lambda: run(["mpc","vol",str(v)])).start()
class Mpd:
def __init__(self,bp,rp):
self.bp = bp
self.rp = rp
self.port = bp+rp
self.status = None
self.running = False
self.should_terminate = False
def start(self):
self.thread = Thread(target=self.task)
self.thread.start()
def join(self):
return self.thread.join()
def terminate(self):
self.should_terminate = True
def task(self):
while not self.should_terminate:
self.update_status()
time.sleep(1)
def parse_opts(self,a):
# volume: 60% repeat: on random: on single: on consume: off
b = re.split(r"\W{3,}",a) # the
for x in b:
k,v = x.split(": ")
self.status[k] = v
def update_status(self):
m = run(["mpc","-p",str(self.port),"status"],stdout=PIPE,stderr=PIPE)
if m.returncode == 0:
self.running = True
self.status = {}
s = m.stdout.decode()
l = s.splitlines()
self.error = None
if len(l) == 3:
# paused/playing
# Odysseus - Blackout - Synthwave but it gets dark
# [playing] #1/3 45:21/46:52 (96%)
# volume: 70% repeat: on random: off single: on consume: off
self.track_name = l[0]
if m := re.match(r"3409f7071db2f596293544d20d49410debfb07bf64c5e69a051dd465a13b8fc26b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b\s+#(\d+)/(\d+)\s+(\d+):(\d+)/(\d+):(\d+)\s+3409f7071db2f596293544d20d49410debfb07bf64c5e69a051dd465a13b8fc2d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",l[1]):
s,tn,tt,mc,sc,mt,st,pc = m.groups()
mc, sc, mt, st, pc = map(int,(mc, sc, mt, st, pc))
if self.status is None:
self.status = {}
self.status["play_state"] = s
self.status["track_index"] = tn
self.status["track_total"] = tt
self.status["current_position"] = (mc,sc)
self.status["track_duration"] = (mt,st)
self.status["percentage"] = pc
else:
self.error = "Fail to parse line 2"
self.parse_opts(l[2])
elif len(l) == 1:
# stopped
# volume: n/a repeat: on random: on single: on consume: off
self.parse_opts(l[0])
else:
self.error = "mpc status parse error"
else:
self.error = m.stderr.decode()
if not "Connection refused" in self.error:
print(f"Error: {self.error}")
self.running = False
self.status = None
def do_cmd(self,cmd):
env = os.environ.copy()
env["MPD_PORT"] = str(self.port)
m = run(["mp"]+cmd,stdout=PIPE,env=env)
return (m.returncode,m.stdout.decode())
class MpdMgr:
def __init__(self,np=20,bp=6600):
self.mpds = { i : Mpd(bp,i) for i in range(np) }
for mpd in self.mpds.values():
mpd.start()
def get_running(self):
return { i : mpd.running for i, mpd in self.mpds.items() }
def get_statuses(self):
return { i : mpd.status for i, mpd in self.mpds.items() if mpd.running }
def get_status(self,i):
if i in self.mpds:
return self.mpd[i].status
return None
def has(self,k):
return k in self.mpds
def __getitem__(self,k):
if k in self.mpds:
return self.mpds[k]
else:
return None
class MyServer(BaseHTTPRequestHandler):
def homepage(self):
d = { "running": mpdmgr.get_running(), "status": mpdmgr.get_statuses(), "when": datetime.now().strftime("%c:") }
self.send_as_json(d)
def send_as_json(self,d,response_code=200):
j = json.dumps(d)
self.send_json(j,response_code)
def send_json(self,j,response_code=200):
self.send_response(response_code)
self.send_header("Content-type", "application/json")
self.end_headers()
self.wfile.write(bytes(j, "utf-8"))
def do_GET(self):
if self.path == "/favicon.ico":
self.send_response(404)
self.end_headers()
self.wfile.write(b"No favicon")
if self.path.strip("/") == "quit":
if self.path.strip("/") == "":
return self.homepage()
print(datetime.now().strftime("%c:"))
print(f"Request path: {self.path}")
p = [urllib.parse.unquote(x) for x in self.path.strip("/").split("/")]
# /10 -- get status of mpd 10
# /10/play -- send command "play" to mpd 10
# /10/volume/50 -- mp volume 50
if p[0].isnumeric():
i = int(p[0])
print(f"Index {i=}")
print(f"{p=}")
if not mpdmgr.has(i):
print(f"Not in mpdmgr")
self.send_response(404)
self.end_headers()
self.wfile.write(b"404")
return
mpd = mpdmgr[i]
if len(p) == 1:
print(f"Get status {i=}")
sys.stdout.flush()
d = { "running": mpd.running, "status": mpd.status, "when": datetime.now().strftime("%c:") }
self.send_as_json(d)
else:
print(f"Command {p=}")
sys.stdout.flush()
cmd = p[1:]
rc,o = mpd.do_cmd(p[1:])
d = { "returncode": rc, "output": rc, "when": datetime.now().strftime("%c:") }
self.send_as_json(d)
else:
self.send_respose(400)
self.end_headers()
self.wfile.write(b"400")
return
if __name__ == "__main__":
mpdmgr = MpdMgr(np=20)
webServer = HTTPServer((hostName, serverPort), MyServer)
print("Server started http://%s:%s" % (hostName, serverPort))
try:
webServer.serve_forever()
except KeyboardInterrupt:
pass
webServer.server_close()
print("Server stopped.")