round from from Claude Opus 4.8
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user