Add version-bound LTX 2.5 video component recipe

This commit is contained in:
Mikei386
2026-09-29 12:13:54 +02:00
parent 7f38286f72
commit 7910307cfb
6 changed files with 84 additions and 17 deletions
+27 -6
View File
@@ -51,6 +51,20 @@ def image_recipe(model):
if model['repo']==FLUX_REPO and model['file']=='Flux.2 Klein-9B_fp16_nsfw.safetensors':return FLUX_COMPONENTS
return None
LTX_REPO='Lightricks/LTX-2.5'
LTX_FILE='diffusion_models/ltx-2.5-22b-distilled-transformer-bf16.safetensors'
LTX_COMPONENTS={
'text_encoder':{'label':'Gemma 4 12B · Textencoder mit LTX-Projektionen','files':['text_encoders/gemma4-12b-with-proj-ltx-2.5-bf16.safetensors']},
'video_vae':{'label':'Video-VAE · BF16','files':['vae/ltx-2.5-video-vae-bf16.safetensors']},
'audio_vae':{'label':'Audio-VAE und Vocoder · BF16','files':['vae/ltx-2.5-audio-vae-bf16.safetensors']},
'spatial_upsampler':{'label':'Spatial-Upsampler · zweistufige Distilled-Pipeline','files':['latent_upscale_models/ltx-2.5-latent-spatial-upscaler-x2-bf16-1.0.safetensors']}
}
def video_recipe(model):
return LTX_COMPONENTS if model.get('kind')=='video' and model.get('repo')==LTX_REPO and model.get('file')==LTX_FILE else None
def component_recipe(model):
return video_recipe(model) or image_recipe(model)
class Profiles:
def __init__(self,path,catalog):
self.path=Path(path);self.catalog=catalog;self.lock=threading.RLock()
@@ -73,6 +87,10 @@ class Profiles:
except ValueError as exc:p['model']=None;p['blockers']=[str(exc)]
if p.get('model') and image_recipe(p['model']) and self.image_runtime_ready():
p['blockers']=[b for b in p['blockers'] if not b.startswith('Eine eigene Bildlaufzeit')]
if p.get('model') and video_recipe(p['model']):
for role,info in video_recipe(p['model']).items():
try:self._component(p['model'],role,p.get('components',{}).get(role))
except ValueError:p['blockers'].append(info['label']+' fehlt oder ist noch nicht zugeordnet.')
if p['kind']=='audio' and self.tts_blockers and p.get('model'):p['blockers']=self.tts_blockers(p)
if p['kind']=='stt' and self.stt_blockers and p.get('model'):p['blockers']=self.stt_blockers(p)
if p['kind']=='chat' and self.chat_blockers and p.get('model'):p['blockers']=self.chat_blockers(p)
@@ -120,15 +138,16 @@ class Profiles:
return {'deleted':old['id']}
def _component(self,model,role,ident):
recipe=image_recipe(model)
recipe=component_recipe(model)
if not recipe or role not in recipe:raise ValueError('Für dieses Modell ist kein geprüftes Komponentenrezept hinterlegt.')
item=self.catalog.entry(ident)
if item['repo']!=recipe[role].get('repo',model['repo']) or item['kind']!='image' or item['file'] not in recipe[role]['files']:raise ValueError('Die Datei gehört nicht zu dieser Komponente des Modells.')
if item['repo']!=recipe[role].get('repo',model['repo']) or item['kind']!=model['kind'] or item['file'] not in recipe[role]['files']:raise ValueError('Die Datei gehört nicht zu dieser Komponente des Modells.')
if video_recipe(model) and item['revision']!=model['revision']:raise ValueError('LTX-Komponente muss dieselbe Quellversion wie das Hauptmodell verwenden.')
return item
def components(self,model_id):
model=self.catalog.entry(model_id)
if not image_recipe(model) or model['kind']!='image':return dict(supported=False,requirements=[],message='Für dieses Modell ist noch kein Komponentenrezept hinterlegt. Keine automatische Zuordnung.')
recipe=image_recipe(model);source=self.catalog.files(next(iter(recipe.values())).get('repo',model['repo']));entries=self.catalog.status()['entries'];requirements=[]
if not component_recipe(model):return dict(supported=False,requirements=[],message='Für dieses Modell ist noch kein Komponentenrezept hinterlegt. Keine automatische Zuordnung.')
recipe=component_recipe(model);video=bool(video_recipe(model));source=self.catalog.files(model['repo'],model['revision']) if video else self.catalog.files(next(iter(recipe.values())).get('repo',model['repo']));entries=self.catalog.status()['entries'];requirements=[]
for role,info in recipe.items():
available=[]
for item in entries:
@@ -137,14 +156,16 @@ class Profiles:
except ValueError:pass
candidates=[dict(f,repo=source.get('repo',info.get('repo',model['repo'])),revision=source['revision'],gated=source['gated'],auth_configured=source.get('auth_configured',False)) for name in info['files'] for f in source['files'] if f['name']==name]
requirements.append(dict(role=role,label=info['label'],available=available,candidates=candidates))
if video:return dict(supported=True,requirements=requirements,source='https://huggingface.co/'+model['repo']+'/blob/'+model['revision']+'/README.md',license=source['license'],runtime='LTX-Pipelines · Distilled BF16 · noch nicht an Deck angebunden',runtime_installed=False,message='Geprüftes Dateirezept nach Herstellerdokumentation für die zweistufige Distilled-Pipeline mit fester Bildanzahl. Alle Komponenten stammen aus derselben Version wie dein Transformer. Kein beliebiger Gemma-Encoder; keine Comfy-INT8-Dateien. Optionaler Duration-Head, Temporal-Upsampling und DFR sind nicht Teil dieses Rezepts. Vollständige Dateien bedeuten noch keine ausführbare Video-Laufzeit oder Speicherzusage.')
return dict(supported=True,requirements=requirements,source=source['url'],license=source['license'],runtime='ComfyUI + ComfyUI-GGUF',runtime_installed=self.image_runtime_ready(),
message='Fehlende Dateien herunterladen, danach Bibliothek aktualisieren und zuordnen. Für FLUX.2 Klein empfehlen sich zum Einstieg 4 Schritte und Guidance 1. Die Bildgewichte werden bei Bedarf zwischen RAM und RTX 5080 verschoben.' if model['repo']==FLUX_REPO else 'Zusatzdateien laut Modellkarte für ComfyUI. Kompatibilität und Speicherbedarf müssen mit der eingerichteten Laufzeit noch praktisch geprüft werden.')
def assign(self,data):
if set(data)!={'id','revision','components'} or not isinstance(data['components'],dict) or set(data['components'])-set(QWEN_COMPONENTS):raise ValueError('Ungültige Komponentenzuordnung.')
if set(data)!={'id','revision','components'} or not isinstance(data['components'],dict):raise ValueError('Ungültige Komponentenzuordnung.')
with self.lock:
old=next((p for p in self.rows if p['id']==data['id']),None)
if not old or old['kind']!='image' or old['revision']!=data['revision']:raise ValueError('Profil geändert oder nicht gefunden. Ansicht neu laden.')
if not old or old['kind'] not in ('image','video') or old['revision']!=data['revision']:raise ValueError('Profil geändert oder nicht gefunden. Ansicht neu laden.')
model=self.catalog.entry(old['model_id'])
if set(data['components'])-set(component_recipe(model) or {}):raise ValueError('Unbekannte Komponente für dieses Modell.')
components={k:v for k,v in data['components'].items() if v}
for role,ident in components.items():self._component(model,role,ident)
updated=dict(old,components=components,revision=old['revision']+1,updated_at=time.time())