From 251f9c238dd467312a1be6158c820b69b11e357d Mon Sep 17 00:00:00 2001 From: "thomas.kopp" Date: Thu, 2 Apr 2026 01:46:19 +0200 Subject: [PATCH] fix: restore PipeWire combined source automatically on startup Save mic/monitor device names to pipewire-modules.json alongside module IDs. On startup, recreate transkriptor-combined if not already loaded. --- api/router.py | 2 +- main.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/api/router.py b/api/router.py index 6f8bcfb..7846245 100644 --- a/api/router.py +++ b/api/router.py @@ -275,7 +275,7 @@ async def create_combined_source(body: dict, user: dict = Depends(current_user)) ) state_path.parent.mkdir(parents=True, exist_ok=True) ids = [int(sink_id), int(mic_id), int(mon_id)] - state_path.write_text(json.dumps({"ids": ids})) + state_path.write_text(json.dumps({"ids": ids, "mic": mic, "monitor": monitor})) return {"device": "transkriptor-combined.monitor", "module_ids": ids} diff --git a/main.py b/main.py index acd4f8b..7568225 100644 --- a/main.py +++ b/main.py @@ -49,6 +49,45 @@ async def settingsjs(): return FileResponse(str(FRONTEND_DIR / "settings.js")) +# ── PipeWire combined source restore ────────────────────────────────────────── + +def _restore_pipewire_combined(): + """Recreate transkriptor-combined.monitor on startup if it was previously configured.""" + import json, subprocess, logging + state_path = Path(os.path.expanduser("~/.config/tueit-transcriber/pipewire-modules.json")) + if not state_path.exists(): + return + try: + data = json.loads(state_path.read_text()) + mic = data.get("mic") + monitor = data.get("monitor") + if not mic or not monitor: + return + sources = subprocess.check_output( + ["pactl", "list", "sources", "short"], stderr=subprocess.DEVNULL, timeout=5 + ).decode() + if "transkriptor-combined.monitor" in sources: + return # already loaded + sink_id = subprocess.check_output([ + "pactl", "load-module", "module-null-sink", + "sink_name=transkriptor-combined", + "sink_properties=device.description=Transkriptor Combined", + ], timeout=5).decode().strip() + mic_id = subprocess.check_output([ + "pactl", "load-module", "module-loopback", + f"source={mic}", "sink=transkriptor-combined", + ], timeout=5).decode().strip() + mon_id = subprocess.check_output([ + "pactl", "load-module", "module-loopback", + f"source={monitor}", "sink=transkriptor-combined", + ], timeout=5).decode().strip() + ids = [int(sink_id), int(mic_id), int(mon_id)] + state_path.write_text(json.dumps({"ids": ids, "mic": mic, "monitor": monitor})) + logging.getLogger(__name__).info("Restored PipeWire combined source (ids: %s)", ids) + except Exception as e: + logging.getLogger(__name__).warning("Could not restore PipeWire combined source: %s", e) + + # ── PID file ─────────────────────────────────────────────────────────────────── def write_pid(pid_path: str): @@ -145,6 +184,7 @@ if __name__ == "__main__": pid_path = cfg.get("pid_file", os.path.expanduser("~/.local/run/tueit-transcriber.pid")) write_pid(pid_path) + _restore_pipewire_combined() signal.signal(signal.SIGUSR1, _sigusr1_handler) uvicorn_cfg = uvicorn.Config(app, host=host, port=port, log_level="debug")