Files
soundboard-py/soundboard/config.py
T

73 lines
2.3 KiB
Python

"""Load/save the soundboard configuration as JSON.
Pure/headless: no audio or GUI imports, so it is fully unit-testable. Reads are
defensive — a missing or corrupt file yields a valid default config rather than crashing.
"""
from __future__ import annotations
import json
import os
from typing import Any
DEFAULT_CONFIG: dict[str, Any] = {
"output_device": None, # substring of the device name, e.g. "CABLE Input"
"volume": 80, # 0-100 (UI scale)
"buttons": [], # list of {"id", "label", "file_path"}
}
def default_config() -> dict[str, Any]:
"""A fresh deep copy of the default config."""
return json.loads(json.dumps(DEFAULT_CONFIG))
def _coerce(raw: Any) -> dict[str, Any]:
"""Merge a parsed object onto the defaults, dropping anything malformed."""
cfg = default_config()
if not isinstance(raw, dict):
return cfg
if isinstance(raw.get("output_device"), str):
cfg["output_device"] = raw["output_device"]
vol = raw.get("volume")
if isinstance(vol, (int, float)):
cfg["volume"] = int(max(0, min(100, vol)))
buttons = []
if isinstance(raw.get("buttons"), list):
for b in raw["buttons"]:
if not isinstance(b, dict):
continue
bid = b.get("id")
label = b.get("label")
path = b.get("file_path")
if isinstance(bid, str) and isinstance(label, str) and isinstance(path, str):
buttons.append({"id": bid, "label": label, "file_path": path})
cfg["buttons"] = buttons
return cfg
def load_config(path: str) -> dict[str, Any]:
"""Load config from ``path``; return a valid default if missing or corrupt."""
if not os.path.exists(path):
return default_config()
try:
with open(path, "r", encoding="utf-8") as fh:
raw = json.load(fh)
except (OSError, ValueError):
return default_config()
return _coerce(raw)
def save_config(path: str, config: dict[str, Any]) -> None:
"""Atomically write config to ``path`` (write temp + replace)."""
config = _coerce(config)
directory = os.path.dirname(os.path.abspath(path))
os.makedirs(directory, exist_ok=True)
tmp = path + ".tmp"
with open(tmp, "w", encoding="utf-8") as fh:
json.dump(config, fh, indent=2)
os.replace(tmp, path)