Add exclusive LTX-2 video studio profile

This commit is contained in:
Mikei386
2026-09-13 21:33:57 +02:00
parent fce9900389
commit a78aed8e9e
12 changed files with 233 additions and 15 deletions
@@ -42,6 +42,8 @@ APPLIO_LABEL_KEY = "com.mike-ai.applio-worker"
APPLIO_WORKER = os.environ.get("APPLIO_WORKER", "").strip()
TRELLIS_LABEL_KEY = "com.mike-ai.trellis-worker"
TRELLIS_WORKER = os.environ.get("TRELLIS_WORKER", "").strip()
VIDEO_LABEL_KEY = "com.mike-ai.video-worker"
VIDEO_WORKER = os.environ.get("VIDEO_WORKER", "").strip()
LOCK = threading.Lock()
log = logging.getLogger("profile-controller")
@@ -187,6 +189,22 @@ def trellis_container() -> dict:
return matches[0]
def video_container() -> dict:
if not VIDEO_WORKER:
raise RuntimeError("video worker is not configured")
matches = [item for item in labelled_containers(VIDEO_LABEL_KEY)
if item.get("Labels", {}).get(VIDEO_LABEL_KEY) == VIDEO_WORKER]
if len(matches) != 1:
raise RuntimeError(
f"expected exactly one video worker {VIDEO_WORKER!r}, found {len(matches)}")
return matches[0]
def stop_video_if_configured() -> None:
if VIDEO_WORKER:
stop_container(video_container(), timeout=30)
def stop_music_if_configured() -> None:
if MUSIC_WORKER:
stop_container(music_container(), timeout=30)
@@ -290,6 +308,7 @@ def set_image_worker(running: bool, kind: str = IMAGE_WORKER) -> dict:
stop_separator_if_configured()
stop_voice_tools()
stop_trellis_if_configured()
stop_video_if_configured()
for other in image_containers():
if other["Id"] != item["Id"]:
stop_container(other, timeout=20)
@@ -319,6 +338,7 @@ def set_music_worker(running: bool) -> dict:
stop_voice_tools()
stop_yue2_if_configured()
stop_trellis_if_configured()
stop_video_if_configured()
start_container(item)
else:
stop_container(item, timeout=30)
@@ -340,6 +360,7 @@ def set_yue2_worker(running: bool) -> dict:
stop_separator_if_configured()
stop_voice_tools()
stop_trellis_if_configured()
stop_video_if_configured()
start_container(item)
else:
stop_container(item, timeout=30)
@@ -361,6 +382,7 @@ def set_separator_worker(running: bool) -> dict:
stop_yue2_if_configured()
stop_voice_tools()
stop_trellis_if_configured()
stop_video_if_configured()
start_container(item)
else:
stop_container(item, timeout=30)
@@ -383,6 +405,7 @@ def set_voice_worker(running: bool) -> dict:
stop_separator_if_configured()
stop_voice_tools("voice")
stop_trellis_if_configured()
stop_video_if_configured()
start_container(item)
else:
stop_container(item, timeout=30)
@@ -405,6 +428,7 @@ def set_voice_change_worker(running: bool) -> dict:
stop_separator_if_configured()
stop_voice_tools("voicechange")
stop_trellis_if_configured()
stop_video_if_configured()
start_container(item)
else:
stop_container(item, timeout=30)
@@ -427,6 +451,7 @@ def set_applio_worker(running: bool) -> dict:
stop_separator_if_configured()
stop_voice_tools("applio")
stop_trellis_if_configured()
stop_video_if_configured()
start_container(item)
else:
stop_container(item, timeout=30)
@@ -455,6 +480,28 @@ def set_trellis_worker(running: bool) -> dict:
"state": "running" if running else "stopped"}
def set_video_worker(running: bool) -> dict:
"""Start LTX-2 exclusively, or stop it before another mode is loaded."""
with LOCK:
item = video_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()
stop_yue2_if_configured()
stop_separator_if_configured()
stop_voice_tools()
stop_trellis_if_configured()
start_container(item)
else:
stop_container(item, timeout=30)
return {"video_worker": VIDEO_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"]
@@ -475,6 +522,7 @@ def activate(profile: str) -> dict:
stop_separator_if_configured()
stop_voice_tools()
stop_trellis_if_configured()
stop_video_if_configured()
start_container(tts_container())
items = containers()
missing = [name for name in ALLOWED if name not in items]
@@ -588,6 +636,13 @@ class Handler(BaseHTTPRequestHandler):
"unhealthy" if "(unhealthy)" in trellis_status else
"starting" if trellis.get("State") == "running" else
"stopped")
video = video_container() if VIDEO_WORKER else {}
video_status = video.get("Status", "")
video_health = ("disabled" if not VIDEO_WORKER else
"healthy" if "(healthy)" in video_status else
"unhealthy" if "(unhealthy)" in video_status else
"starting" if video.get("State") == "running" else
"stopped")
self.reply(200, {"active_profile": active_profile(items),
"music_worker": music.get("State", "disabled"),
"music_health": music_health,
@@ -603,6 +658,8 @@ class Handler(BaseHTTPRequestHandler):
"applio_health": applio_health,
"trellis_worker": trellis.get("State", "disabled"),
"trellis_health": trellis_health,
"video_worker": video.get("State", "disabled"),
"video_health": video_health,
"profiles": {name: items.get(name, {}).get(
"State", "missing") for name in ALLOWED}})
except Exception as exc:
@@ -669,6 +726,13 @@ class Handler(BaseHTTPRequestHandler):
log.exception("TRELLIS worker transition failed")
self.reply(503, {"error": str(exc)})
return
if self.path in {"/workers/video/start", "/workers/video/stop"}:
try:
self.reply(200, set_video_worker(self.path.endswith("/start")))
except Exception as exc:
log.exception("video worker transition failed")
self.reply(503, {"error": str(exc)})
return
worker_paths = {
"/workers/image/start": (IMAGE_WORKER, True),
"/workers/image/stop": (IMAGE_WORKER, False),