Files
Athena-Deck/video.py
T

72 lines
4.7 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 im Bereich Video 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.tts_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 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 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)