aboutsummaryrefslogtreecommitdiffstats
path: root/deckit
diff options
context:
space:
mode:
Diffstat (limited to 'deckit')
-rw-r--r--deckit/module.py16
-rw-r--r--deckit/modules/obs.py113
-rw-r--r--deckit/server.py1
3 files changed, 130 insertions, 0 deletions
diff --git a/deckit/module.py b/deckit/module.py
index fd5997c..f1a7fa1 100644
--- a/deckit/module.py
+++ b/deckit/module.py
@@ -37,6 +37,22 @@ class Registry:
_get = get
+ def start_all(self) -> None:
+ for m in self._modules.values():
+ if m.start:
+ try:
+ m.start()
+ except Exception:
+ pass
+
+ def stop_all(self) -> None:
+ for m in self._modules.values():
+ if m.stop:
+ try:
+ m.stop()
+ except Exception:
+ pass
+
def state(self, name: str) -> dict:
return self._get(name).state()
diff --git a/deckit/modules/obs.py b/deckit/modules/obs.py
new file mode 100644
index 0000000..f4bf71a
--- /dev/null
+++ b/deckit/modules/obs.py
@@ -0,0 +1,113 @@
+"""OBS stream-deck: recording, scenes, audio channels, transitions and sources."""
+
+import threading
+
+from ..module import Module, ModuleError
+from ..obsclient import ObsClient
+
+AUDIO_KINDS = {
+ "pulse_input_capture", "pulse_output_capture", "alsa_input_capture", "alsa_output_capture",
+ "wasapi_input_capture", "wasapi_output_capture", "coreaudio_input_capture", "coreaudio_output_capture",
+ "sndio_input_capture", "sndio_output_capture", "jack_input_capture", "jack_output_capture",
+}
+
+
+def make(cfg: dict) -> Module:
+ o = cfg.get("obs", {})
+ client = cfg.get("client") or ObsClient(
+ host=o.get("host", "127.0.0.1"), port=o.get("port", 4455), password=o.get("password", ""))
+ lock = threading.Lock()
+ stop = threading.Event()
+
+ def req(kind, data=None):
+ with lock:
+ return client.request(kind, data)
+
+ def connect_loop():
+ while not stop.is_set():
+ if not getattr(client, "connected", False):
+ try:
+ client.connect()
+ except Exception:
+ pass
+ stop.wait(5)
+
+ def state():
+ if not getattr(client, "connected", False):
+ return {"connected": False}
+ try:
+ scenes = req("GetSceneList")
+ rec = req("GetRecordStatus")
+ stream = req("GetStreamStatus")
+ trans = req("GetCurrentSceneTransition")
+ kinds = req("GetTransitionKindList").get("transitionKinds", [])
+ current = scenes.get("currentProgramSceneName")
+ sources = []
+ if current:
+ for item in req("GetSceneItemList", {"sceneName": current}).get("sceneItems", []):
+ sources.append({"id": item["sceneItemId"], "name": item["sourceName"],
+ "on": item["sceneItemEnabled"]})
+ inputs = []
+ for inp in req("GetInputList").get("inputs", []):
+ if inp.get("inputKind") not in AUDIO_KINDS:
+ continue
+ try:
+ vol = req("GetInputVolume", {"inputName": inp["inputName"]})
+ muted = req("GetInputMute", {"inputName": inp["inputName"]})
+ except ModuleError:
+ continue
+ inputs.append({"name": inp["inputName"],
+ "volume": vol.get("inputVolumeMul", 0),
+ "muted": muted.get("inputMuted", False)})
+ except ModuleError:
+ return {"connected": False}
+ return {
+ "connected": True,
+ "scenes": [s["sceneName"] for s in scenes.get("scenes", [])],
+ "currentScene": current,
+ "recording": rec.get("outputActive", False),
+ "recordingTime": rec.get("outputDuration", 0) / 1000,
+ "streaming": stream.get("outputActive", False),
+ "transitions": kinds,
+ "transition": trans.get("transitionName"),
+ "sources": sources,
+ "inputs": inputs,
+ }
+
+ def scene(body):
+ name = body.get("scene")
+ if not isinstance(name, str) or not name:
+ raise ModuleError("scene required")
+ req("SetCurrentProgramScene", {"sceneName": name})
+
+ def record(body):
+ req("StartRecord" if body.get("on") else "StopRecord")
+
+ def stream(body):
+ req("StartStream" if body.get("on") else "StopStream")
+
+ def mute(body):
+ req("SetInputMute", {"inputName": str(body.get("input")), "inputMuted": bool(body.get("muted"))})
+
+ def volume(body):
+ req("SetInputVolume", {"inputName": str(body.get("input")),
+ "inputVolumeMul": float(body.get("level", 0))})
+
+ def transition(body):
+ req("SetCurrentSceneTransition", {"transitionName": str(body.get("name"))})
+
+ def trigger(body):
+ req("TriggerStudioModeTransition")
+
+ def source(body):
+ req("SetSceneItemEnabled", {"sceneName": str(body.get("scene")),
+ "sceneItemId": int(body.get("id")),
+ "sceneItemEnabled": bool(body.get("on"))})
+
+ return Module(
+ name="obs", title="OBS", state=state,
+ actions={"scene": scene, "record": record, "stream": stream, "mute": mute,
+ "volume": volume, "transition": transition, "trigger": trigger, "source": source},
+ start=lambda: threading.Thread(target=connect_loop, daemon=True).start(),
+ stop=lambda: (stop.set(), client.close()),
+ )
diff --git a/deckit/server.py b/deckit/server.py
index f51621e..bbd92cf 100644
--- a/deckit/server.py
+++ b/deckit/server.py
@@ -157,6 +157,7 @@ def serve():
TOKEN = load_token()
REGISTRY = build(CFG)
+ REGISTRY.start_all()
RUNDIR.mkdir(mode=0o700, parents=True, exist_ok=True)
RUNDIR.chmod(0o700)
SOCKET.unlink(missing_ok=True)