Add BS-RoFormer vocal separation mode

This commit is contained in:
Mikei386
2026-09-08 19:23:29 +02:00
parent a4e894fe70
commit 0069b61dbb
15 changed files with 421 additions and 52 deletions
@@ -30,6 +30,8 @@ TTS_LABEL_KEY = "com.mike-ai.tts-worker"
TTS_WORKER = os.environ.get("TTS_WORKER", "qwen3")
MUSIC_LABEL_KEY = "com.mike-ai.music-worker"
MUSIC_WORKER = os.environ.get("MUSIC_WORKER", "").strip()
SEPARATOR_LABEL_KEY = "com.mike-ai.stem-separator"
SEPARATOR_WORKER = os.environ.get("SEPARATOR_WORKER", "").strip()
LOCK = threading.Lock()
log = logging.getLogger("profile-controller")
@@ -109,11 +111,27 @@ def music_container() -> dict:
return matches[0]
def separator_container() -> dict:
if not SEPARATOR_WORKER:
raise RuntimeError("stem separator is not configured")
matches = [item for item in labelled_containers(SEPARATOR_LABEL_KEY)
if item.get("Labels", {}).get(SEPARATOR_LABEL_KEY) == SEPARATOR_WORKER]
if len(matches) != 1:
raise RuntimeError(
f"expected exactly one stem separator {SEPARATOR_WORKER!r}, found {len(matches)}")
return matches[0]
def stop_music_if_configured() -> None:
if MUSIC_WORKER:
stop_container(music_container(), timeout=30)
def stop_separator_if_configured() -> None:
if SEPARATOR_WORKER:
stop_container(separator_container(), timeout=30)
def stop_container(item: dict, timeout: int = 120) -> None:
if item.get("State") != "running":
return
@@ -152,6 +170,7 @@ def set_image_worker(running: bool, kind: str = IMAGE_WORKER) -> dict:
# Qwen3-TTS. The gateway retains Piper as a fallback meanwhile.
stop_container(tts_container(), timeout=30)
stop_music_if_configured()
stop_separator_if_configured()
for other in image_containers():
if other["Id"] != item["Id"]:
stop_container(other, timeout=20)
@@ -177,6 +196,7 @@ def set_music_worker(running: bool) -> dict:
for worker in image_containers():
stop_container(worker, timeout=20)
stop_container(tts_container(), timeout=30)
stop_separator_if_configured()
start_container(item)
else:
stop_container(item, timeout=30)
@@ -184,6 +204,24 @@ def set_music_worker(running: bool) -> dict:
"state": "running" if running else "stopped"}
def set_separator_worker(running: bool) -> dict:
"""Start vocal separation exclusively, or stop it before LLM restoration."""
with LOCK:
item = separator_container()
if running:
for profile_item in containers().values():
stop_container(profile_item)
for worker in image_containers():
stop_container(worker, timeout=20)
stop_container(tts_container(), timeout=30)
stop_music_if_configured()
start_container(item)
else:
stop_container(item, timeout=30)
return {"separator_worker": SEPARATOR_WORKER,
"state": "running" if running else "stopped"}
def active_profile(items: dict[str, dict] | None = None) -> str | None:
items = items or containers()
active = [name for name, item in items.items() if item.get("State") == "running"]
@@ -200,6 +238,7 @@ def activate(profile: str) -> dict:
for worker in image_containers():
stop_container(worker)
stop_music_if_configured()
stop_separator_if_configured()
start_container(tts_container())
items = containers()
missing = [name for name in ALLOWED if name not in items]
@@ -271,9 +310,18 @@ class Handler(BaseHTTPRequestHandler):
"unhealthy" if "(unhealthy)" in music_status else
"starting" if music.get("State") == "running" else
"stopped")
separator = separator_container() if SEPARATOR_WORKER else {}
separator_status = separator.get("Status", "")
separator_health = ("disabled" if not SEPARATOR_WORKER else
"healthy" if "(healthy)" in separator_status else
"unhealthy" if "(unhealthy)" in separator_status else
"starting" if separator.get("State") == "running" else
"stopped")
self.reply(200, {"active_profile": active_profile(items),
"music_worker": music.get("State", "disabled"),
"music_health": music_health,
"separator_worker": separator.get("State", "disabled"),
"separator_health": separator_health,
"profiles": {name: items.get(name, {}).get(
"State", "missing") for name in ALLOWED}})
except Exception as exc:
@@ -298,6 +346,13 @@ class Handler(BaseHTTPRequestHandler):
log.exception("music worker transition failed")
self.reply(503, {"error": str(exc)})
return
if self.path in {"/workers/separator/start", "/workers/separator/stop"}:
try:
self.reply(200, set_separator_worker(self.path.endswith("/start")))
except Exception as exc:
log.exception("stem separator transition failed")
self.reply(503, {"error": str(exc)})
return
worker_paths = {
"/workers/image/start": (IMAGE_WORKER, True),
"/workers/image/stop": (IMAGE_WORKER, False),