Files
UmbraAudio/umbraaudio/voice_maintenance.py
2026-10-18 19:01:04 -07:00

62 lines
1.9 KiB
Python

# Copyright 2026 Umbra Tools (umbratools.com)
# SPDX-License-Identifier: Apache-2.0
"""Web-UI maintenance banner state for long-running offline phases.
During the LLM speaker re-attribution run the Mac's Qwen3-TTS models must
stay unloaded (16 GB RAM), so voice re-generation is temporarily offline.
The run script records that state here, the web routes surface it through
a template global, and this module's pid-liveness check makes a crashed
run self-heal the banner away instead of lingering until a human notices.
"""
from __future__ import annotations
import contextlib
import json
import os
import time
from umbraaudio.config import PROJECT_BASE
MAINTENANCE_FILE = PROJECT_BASE / ".umbra" / "voice_maintenance.json"
def set_voice_maintenance(reason: str) -> None:
"""Record that voice re-generation is temporarily unavailable."""
payload = {
"pid": os.getpid(),
"started_at": int(time.time()),
"reason": reason,
}
MAINTENANCE_FILE.parent.mkdir(parents=True, exist_ok=True)
tmp = MAINTENANCE_FILE.with_suffix(".json.tmp")
tmp.write_text(json.dumps(payload, indent=2))
tmp.replace(MAINTENANCE_FILE)
def clear_voice_maintenance() -> None:
"""Remove the maintenance marker so the banner disappears."""
with contextlib.suppress(FileNotFoundError):
MAINTENANCE_FILE.unlink()
def active_voice_maintenance() -> dict | None:
"""Return the marker dict while a live process owns it, else None.
The pid check means a run that died without clearing the file stops
showing the banner as soon as its process is gone.
"""
try:
with open(MAINTENANCE_FILE) as f:
data = json.load(f)
except (FileNotFoundError, json.JSONDecodeError, OSError):
return None
pid = data.get("pid")
if not isinstance(pid, int) or pid <= 0:
return None
try:
os.kill(pid, 0)
except (ProcessLookupError, PermissionError):
return None
return data