72 lines
4.6 KiB
Python
72 lines
4.6 KiB
Python
"""Deck controls the original Videodienst service lifecycle, never video generation."""
|
|
import json
|
|
from pathlib import Path
|
|
import threading
|
|
import time
|
|
|
|
class Video:
|
|
def __init__(self,scheduler,stop_owned,helper,root):
|
|
self.scheduler=scheduler;self.stop_owned=stop_owned;self.helper=helper;self.lock=threading.RLock();self.thread=None;self.state='idle';self.error=None;self.target_mode=None;self.switch_phase=None;self.switch_started_at=None
|
|
self.path=Path(root)/'active-service.json';self.selected=json.loads(self.path.read_text()).get('id') if self.path.exists() else None
|
|
self.services=[];self.service={};self.refresh()
|
|
def refresh(self):
|
|
try:self.services=self.helper.call('video-status')['services'];unknown=False
|
|
except (OSError,ValueError,KeyError):self.services=[];unknown=True
|
|
running=[s for s in self.services if s.get('running')]
|
|
if self.selected is None and len(running)==1:self.selected=running[0]['id']
|
|
if self.selected is None and len(self.services)==1:self.selected=self.services[0]['id']
|
|
self.service=next((s for s in self.services if s['id']==self.selected),{})
|
|
if self.state!='switching':
|
|
blocked=any(s.get('running') is not False for s in self.services) or (unknown and self.selected is not None)
|
|
with self.scheduler.cv:self.scheduler.gpu_mode='video' if blocked else 'llm';self.scheduler.cv.notify_all()
|
|
self.state='ready' if self.service.get('ready') else 'starting' if self.service.get('running') else 'failed' if blocked else 'idle'
|
|
def select(self,ident):
|
|
with self.lock:
|
|
self.refresh()
|
|
if self.state=='switching' or self.scheduler.gpu_mode!='llm':raise ValueError('Zuerst auf LLM wechseln und den laufenden Videodienst beenden.')
|
|
if not any(s['id']==ident for s in self.services):raise ValueError('Videodienst nicht eingerichtet.')
|
|
self.path.parent.mkdir(parents=True,exist_ok=True);temp=self.path.with_suffix('.tmp');temp.write_text(json.dumps({'id':ident}));temp.replace(self.path);self.selected=ident
|
|
return self.status()
|
|
def status(self):
|
|
with self.lock:
|
|
self.refresh()
|
|
return dict(mode=self.scheduler.gpu_mode,state=self.state,error=self.error,target_mode=self.target_mode,switch_phase=self.switch_phase,switch_started_at=self.switch_started_at,service=dict(self.service),services=list(self.services),selected=self.selected,memory_policy='Deck steuert nur den aktiven Videodienst. Modelle, Eingaben und Generierung werden über die Original-API des Dienstes verwaltet.')
|
|
def switch(self,mode):
|
|
if mode not in ('llm','video'):raise ValueError('Modus muss llm oder video sein.')
|
|
with self.lock:
|
|
if self.state=='switching':raise ValueError('Moduswechsel läuft bereits.')
|
|
self.refresh()
|
|
if not self.service:raise ValueError('Zuerst einen eingerichteten Videodienst unter Weitere Dienste aktivieren.')
|
|
with self.scheduler.cv:self.scheduler.gpu_mode='switching';self.scheduler.cv.notify_all()
|
|
self.state='switching';self.error=None;self.target_mode=mode;self.switch_started_at=time.time();self.switch_phase='Deck-GPU-Aufträge beenden' if mode=='video' else 'Videodienst beenden und GPU-Speicher freigeben'
|
|
self.thread=threading.Thread(target=self._switch,args=(mode,),daemon=True);self.thread.start()
|
|
return self.status()
|
|
def _switch(self,mode):
|
|
try:
|
|
if mode=='video':
|
|
self.stop_owned()
|
|
deadline=time.monotonic()+60
|
|
with self.scheduler.cv:
|
|
while self.scheduler.active or self.scheduler.transition:
|
|
if time.monotonic()>deadline:raise ValueError('Deck-Aufträge konnten noch nicht beendet werden.')
|
|
self.scheduler.cv.wait(.2)
|
|
self.scheduler.key=None
|
|
self.switch_phase='Freie GPUs prüfen und Videodienst-Dienst starten'
|
|
self.helper.call('video-start',self.selected);self.switch_phase='Auf Original-API des Dienstes warten'
|
|
deadline=time.monotonic()+240
|
|
while True:
|
|
service=next(s for s in self.helper.call('video-status')['services'] if s['id']==self.selected)
|
|
if service.get('ready'):break
|
|
if not service.get('running'):raise ValueError('Videodienst-Dienst wurde beendet. Installation prüfen.')
|
|
if time.monotonic()>deadline:raise ValueError('Videodienst läuft, seine API ist noch nicht bereit. Erneut prüfen oder auf LLM zurückschalten.')
|
|
time.sleep(2)
|
|
else:
|
|
for service in self.helper.call('video-status')['services']:
|
|
if service.get('running'):self.helper.call('video-stop',service['id'])
|
|
except Exception as exc:self.error=str(exc) if isinstance(exc,ValueError) else 'Videodienst-Systemhelfer nicht erreichbar.'
|
|
finally:
|
|
with self.lock:self.state='idle';self.refresh()
|
|
def close(self):
|
|
# Do not terminate an external service on a UI restart. Restore its gate at startup.
|
|
if self.thread and self.thread.is_alive():self.thread.join(5)
|