Gemeinsame C++-/CUDA-Laufzeit für unterstützte Audio-Modellfamilien. Passendes GGUF-Paket und ein Deck-Worker sind zusätzlich erforderlich.
Herunterladen und bereitmachen
Eigene Build-Ausgabe
YuE2 und Ladypoly WebUI
Modellfamilie erkannt. Für Generierung fehlen noch der Deck-Musikworker und die Verbindung zur Ladypoly-Oberfläche. Dieser Build lädt keine Gewichte, startet kein Modell und gibt kein Profil am Endpunkt frei.
Gemeinsame C++-/CUDA-Laufzeit für unterstützte Audio-Modellfamilien. Passendes GGUF-Paket und ein Deck-Worker sind zusätzlich erforderlich.
Herunterladen und bereitmachen
Eigene Build-Ausgabe
YuE2 und Ladypoly WebUI
Modellfamilie erkannt. Für Generierung das Musikprofil und seine Komponenten einrichten, dann Musik → Testen verwenden. Die Ladypoly-Oberfläche ist separat. Dieser Build lädt keine Gewichte, startet kein Modell und gibt kein Profil am Endpunkt frei.
${s.installed?'Installiert · Worker-Anbindung noch offen':'Noch nicht installiert'}
Version ${e(s.version)} · Commit ${e(s.revision)}
Mindestens ${e(s.required_disk_gib)} GiB freier Speicher. Eigener Build mit sechs Jobs, CUDA-Ziele werden aus den vorhandenen GPUs ermittelt. Bestehende Laufzeiten werden nicht verändert.
${s.job?`
${e(s.job.state)} · ${e(s.job.phase)}
${running?'':''}`:''}`;if(s.job){const log=await api('/log');if(root.isConnected)root.querySelector('#ac-log').textContent=log.text;}}catch(x){if(root.isConnected)root.querySelector('#ac-message').textContent=x.message;}if(root.isConnected){clearTimeout(timer);timer=setTimeout(poll,3000);}}
+ async function poll(){if(!root.isConnected)return;try{const s=await api();if(!root.isConnected)return;const running=s.job?.state==='running';root.querySelector('#ac-install').disabled=s.installed||running;root.querySelector('#ac-cancel').disabled=!running;root.querySelector('#ac-status').innerHTML=`
${s.installed?'Installiert · YuE2-Musikworker verfügbar':'Noch nicht installiert'}
Version ${e(s.version)} · Commit ${e(s.revision)}
Mindestens ${e(s.required_disk_gib)} GiB freier Speicher. Eigener Build mit sechs Jobs, CUDA-Ziele werden aus den vorhandenen GPUs ermittelt. Bestehende Laufzeiten werden nicht verändert.
${s.job?`
${e(s.job.state)} · ${e(s.job.phase)}
${running?'':''}`:''}`;if(s.job){const log=await api('/log');if(root.isConnected)root.querySelector('#ac-log').textContent=log.text;}}catch(x){if(root.isConnected)root.querySelector('#ac-message').textContent=x.message;}if(root.isConnected){clearTimeout(timer);timer=setTimeout(poll,3000);}}
for(const [id,path] of [['ac-install','/install'],['ac-cancel','/cancel']])root.querySelector('#'+id).onclick=async()=>{root.querySelector('#'+id).disabled=true;try{await api(path,{});root.querySelector('#ac-message').textContent='Aktion angenommen.';}catch(x){root.querySelector('#ac-message').textContent=x.message;}poll();};poll();}
return {render};
})();
diff --git a/audio_cpp_runtime.py b/audio_cpp_runtime.py
index 8a5501a..d1f4e54 100644
--- a/audio_cpp_runtime.py
+++ b/audio_cpp_runtime.py
@@ -27,7 +27,7 @@ class AudioCppRuntime(LTXOriginalRuntime):
return self.root/'missing-cli',self.root/'missing-source'
def status(self):
cli,source=self.paths()
- return dict(installed=cli.is_file() and (source/'model_specs/yue2.json').is_file(),revision=REVISION,version=VERSION,job=dict(self.job) if self.job else None,required_disk_gib=6,adapter_ready=False,origin='0xShug0/audio.cpp',message='Gemeinsame Laufzeit. Ein Build startet kein Modell; YuE2-Worker und Ladypoly-Anbindung sind noch nicht eingerichtet.')
+ return dict(installed=cli.is_file() and (source/'model_specs/yue2.json').is_file(),revision=REVISION,version=VERSION,job=dict(self.job) if self.job else None,required_disk_gib=6,adapter_ready=True,origin='0xShug0/audio.cpp',message='Gemeinsame Laufzeit. Ein Build startet kein Modell; Der YuE2-Musikworker ist angebunden; Profil und Komponenten werden benötigt. Andere Familien brauchen eigene geprüfte Anbindungen.')
def start(self):
if sys.platform!='linux':raise ValueError('audio.cpp wird auf dem Debian-Server gebaut.')
missing=[name for name in ('git','cmake','g++','nvcc','nvidia-smi') if not shutil.which(name)]
diff --git a/backup.py b/backup.py
index 9c80d5f..2cf6057 100644
--- a/backup.py
+++ b/backup.py
@@ -202,7 +202,7 @@ class Backup:
if len(set(data['services']))!=len(data['services']) or any(name not in services for name in data['services']):raise ValueError('Unbekannter Zusatzdienst.')
if any(not services[name].get('registry') and not services[name].get('build_recipe') for name in data['services']):raise ValueError('Gewählter Dienst hat keine Wiederherstellungsquelle.')
s=self.server
- if s.catalog.pending or s.catalog.job and s.catalog.job['state']=='downloading' or s.runtime.busy or any((getattr(o,'job',None) or {}).get('state')=='running' for o in (s.image_runtime,s.tts_runtime,s.ltx_original_runtime,getattr(s,'audio_cpp_runtime',None),s.prompt_enhancer,s.auto_tests,s.chat_tests,s.image_tests,s.tts_tests,s.stt)):raise ValueError('Zuerst laufende Downloads, Builds und Tests abschließen oder abbrechen.')
+ if s.catalog.pending or s.catalog.job and s.catalog.job['state']=='downloading' or s.runtime.busy or any((getattr(o,'job',None) or {}).get('state')=='running' for o in (s.image_runtime,s.tts_runtime,s.ltx_original_runtime,getattr(s,'audio_cpp_runtime',None),getattr(s,'music',None),s.prompt_enhancer,s.auto_tests,s.chat_tests,s.image_tests,s.tts_tests,s.stt)):raise ValueError('Zuerst laufende Downloads, Builds und Tests abschließen oder abbrechen.')
self.cancel.clear();self.job=dict(id=secrets.token_hex(16),state='running',phase='Wiederherstellung vorbereiten',completed=0,total=len(doc['models'])+len(data['services'])+5,items=[],started_at=time.time(),theme=doc.get('theme','deck'))
self._save();threading.Thread(target=self._restore,args=(copy.deepcopy(doc),data['services'],data['restore_credentials']),daemon=True).start();self.plan=None
return self.status()
@@ -370,6 +370,7 @@ class Backup:
self.cancel.set()
if self.busy():
self.server.catalog.stop();self.server.runtime.stop();self.server.image_runtime.stop();self.server.tts_runtime.stop();self.server.ltx_original_runtime.stop();
+ if getattr(self.server,'music',None):self.server.music.stop()
if getattr(self.server,'audio_cpp_runtime',None):self.server.audio_cpp_runtime.stop()
self.server.prompt_enhancer.stop()
return {'cancellation_requested':True}
diff --git a/deploy/Dockerfile b/deploy/Dockerfile
index 83b7483..16182ab 100644
--- a/deploy/Dockerfile
+++ b/deploy/Dockerfile
@@ -16,8 +16,8 @@ WORKDIR /app
COPY deploy/image-requirements.lock /app/deploy/image-requirements.lock
COPY deploy/tts-requirements.lock /app/deploy/tts-requirements.lock
COPY deploy/ltx-original-requirements.lock /app/deploy/ltx-original-requirements.lock
-COPY audio_cpp_runtime.py ltx_original_runtime.py ltx_original_entry.py video_original.py backup.py backup_codec.py audio_policy.py prompt_enhancer.py prompt_enhancer_worker.py api_compat.py stt.py execution_setup.py tts_runtime.py tts_test.py tts_worker.py auto_test.py chat_test.py endpoint.py inference.py docker_support.py image_encoder_node.py image_runtime.py image_test.py image_upload.py profiles.py capacity.py server.py runtime.py video.py video_proxy.py video_comfy.py video_comfy_node.py video_comfy_proxy.py catalog.py hub_auth.py auth.py collect_hardware.py dashboard_data.py dashboard_history.py /app/
-COPY audio-cpp-ui.js ltx-original-ui.js backup-ui.js video-ui.js stt-ui.js tts-ui.js auto-test-ui.js chat-test-ui.js endpoint-ui.js docker-ui.js image-test-ui.js profiles-ui.js dashboard-ui.js index.html app.js studio.js runtime-ui.js catalog-ui.js style.css dashboard.css themes.css login.html login.js access-ui.js network-ui.js /app/
+COPY music.py audio_cpp_runtime.py ltx_original_runtime.py ltx_original_entry.py video_original.py backup.py backup_codec.py audio_policy.py prompt_enhancer.py prompt_enhancer_worker.py api_compat.py stt.py execution_setup.py tts_runtime.py tts_test.py tts_worker.py auto_test.py chat_test.py endpoint.py inference.py docker_support.py image_encoder_node.py image_runtime.py image_test.py image_upload.py profiles.py capacity.py server.py runtime.py video.py video_proxy.py video_comfy.py video_comfy_node.py video_comfy_proxy.py catalog.py hub_auth.py auth.py collect_hardware.py dashboard_data.py dashboard_history.py /app/
+COPY music-ui.js audio-cpp-ui.js ltx-original-ui.js backup-ui.js video-ui.js stt-ui.js tts-ui.js auto-test-ui.js chat-test-ui.js endpoint-ui.js docker-ui.js image-test-ui.js profiles-ui.js dashboard-ui.js index.html app.js studio.js runtime-ui.js catalog-ui.js style.css dashboard.css themes.css login.html login.js access-ui.js network-ui.js /app/
COPY deploy/docker_backup.py /app/deploy/docker_backup.py
COPY network/__init__.py network/client.py network/config.py network/rpc.py /app/network/
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/tmp \
diff --git a/deploy/Dockerfile.app-update b/deploy/Dockerfile.app-update
index 4822bb5..d0edf06 100644
--- a/deploy/Dockerfile.app-update
+++ b/deploy/Dockerfile.app-update
@@ -6,8 +6,8 @@ WORKDIR /app
COPY deploy/image-requirements.lock /app/deploy/image-requirements.lock
COPY deploy/tts-requirements.lock /app/deploy/tts-requirements.lock
COPY deploy/ltx-original-requirements.lock /app/deploy/ltx-original-requirements.lock
-COPY audio_cpp_runtime.py ltx_original_runtime.py ltx_original_entry.py video_original.py backup.py backup_codec.py audio_policy.py prompt_enhancer.py prompt_enhancer_worker.py api_compat.py stt.py execution_setup.py tts_runtime.py tts_test.py tts_worker.py auto_test.py chat_test.py endpoint.py inference.py docker_support.py image_encoder_node.py image_runtime.py image_test.py image_upload.py profiles.py capacity.py server.py runtime.py video.py video_proxy.py video_comfy.py video_comfy_node.py video_comfy_proxy.py catalog.py hub_auth.py auth.py collect_hardware.py dashboard_data.py dashboard_history.py /app/
-COPY audio-cpp-ui.js ltx-original-ui.js backup-ui.js video-ui.js stt-ui.js tts-ui.js auto-test-ui.js chat-test-ui.js endpoint-ui.js docker-ui.js image-test-ui.js profiles-ui.js dashboard-ui.js index.html app.js studio.js runtime-ui.js catalog-ui.js style.css dashboard.css themes.css login.html login.js access-ui.js network-ui.js /app/
+COPY music.py audio_cpp_runtime.py ltx_original_runtime.py ltx_original_entry.py video_original.py backup.py backup_codec.py audio_policy.py prompt_enhancer.py prompt_enhancer_worker.py api_compat.py stt.py execution_setup.py tts_runtime.py tts_test.py tts_worker.py auto_test.py chat_test.py endpoint.py inference.py docker_support.py image_encoder_node.py image_runtime.py image_test.py image_upload.py profiles.py capacity.py server.py runtime.py video.py video_proxy.py video_comfy.py video_comfy_node.py video_comfy_proxy.py catalog.py hub_auth.py auth.py collect_hardware.py dashboard_data.py dashboard_history.py /app/
+COPY music-ui.js audio-cpp-ui.js ltx-original-ui.js backup-ui.js video-ui.js stt-ui.js tts-ui.js auto-test-ui.js chat-test-ui.js endpoint-ui.js docker-ui.js image-test-ui.js profiles-ui.js dashboard-ui.js index.html app.js studio.js runtime-ui.js catalog-ui.js style.css dashboard.css themes.css login.html login.js access-ui.js network-ui.js /app/
COPY deploy/docker_backup.py /app/deploy/docker_backup.py
COPY network/__init__.py network/client.py network/config.py network/rpc.py /app/network/
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 HOME=/tmp \
diff --git a/deploy/install.py b/deploy/install.py
index 7fad487..776f044 100644
--- a/deploy/install.py
+++ b/deploy/install.py
@@ -14,7 +14,7 @@ import urllib.request
ROOT = Path(__file__).resolve().parent.parent
LABEL = 'de.casaderoll.athena-deck.standalone'
-FILES = ['audio_cpp_runtime.py','audio-cpp-ui.js','ltx_original_runtime.py','ltx_original_entry.py','video_original.py','ltx-original-ui.js','deploy/ltx-original-requirements.lock','backup.py','backup_codec.py','backup-ui.js','deploy/docker_backup.py','deploy/swarm-ui/Dockerfile','deploy/swarm-ui/patch-source.py','deploy/swarm-ui/proxy.py','deploy/swarm-ui/entrypoint.py','deploy/Dockerfile.app-update','video_comfy.py','video_comfy_node.py','video_comfy_proxy.py','prompt_enhancer.py','prompt_enhancer_worker.py','image_upload.py','audio_policy.py','deploy/tts-requirements.lock','stt.py','stt-ui.js','api_compat.py','execution_setup.py','tts_runtime.py','tts_test.py','tts_worker.py','tts-ui.js','auto_test.py','auto-test-ui.js','chat_test.py','chat-test-ui.js','endpoint.py','inference.py','endpoint-ui.js','docker_support.py','docker-ui.js','deploy/docker_helper.py','deploy/setup_docker_helper.py','image_encoder_node.py','image_runtime.py','image_test.py','image-test-ui.js','profiles.py','profiles-ui.js','capacity.py','runtime.py','runtime-ui.js','video.py','video_proxy.py','video-ui.js','catalog.py','hub_auth.py','catalog-ui.js','server.py','auth.py','collect_hardware.py','dashboard_data.py','dashboard_history.py','dashboard-ui.js','dashboard.css','themes.css','deploy/import_dashboard_history.py','index.html','app.js','studio.js','style.css','login.html','login.js','access-ui.js','network-ui.js','network/__init__.py','network/client.py','network/config.py','network/rpc.py','deploy/Dockerfile','deploy/image-requirements.lock']
+FILES = ['music.py','music-ui.js','audio_cpp_runtime.py','audio-cpp-ui.js','ltx_original_runtime.py','ltx_original_entry.py','video_original.py','ltx-original-ui.js','deploy/ltx-original-requirements.lock','backup.py','backup_codec.py','backup-ui.js','deploy/docker_backup.py','deploy/swarm-ui/Dockerfile','deploy/swarm-ui/patch-source.py','deploy/swarm-ui/proxy.py','deploy/swarm-ui/entrypoint.py','deploy/Dockerfile.app-update','video_comfy.py','video_comfy_node.py','video_comfy_proxy.py','prompt_enhancer.py','prompt_enhancer_worker.py','image_upload.py','audio_policy.py','deploy/tts-requirements.lock','stt.py','stt-ui.js','api_compat.py','execution_setup.py','tts_runtime.py','tts_test.py','tts_worker.py','tts-ui.js','auto_test.py','auto-test-ui.js','chat_test.py','chat-test-ui.js','endpoint.py','inference.py','endpoint-ui.js','docker_support.py','docker-ui.js','deploy/docker_helper.py','deploy/setup_docker_helper.py','image_encoder_node.py','image_runtime.py','image_test.py','image-test-ui.js','profiles.py','profiles-ui.js','capacity.py','runtime.py','runtime-ui.js','video.py','video_proxy.py','video-ui.js','catalog.py','hub_auth.py','catalog-ui.js','server.py','auth.py','collect_hardware.py','dashboard_data.py','dashboard_history.py','dashboard-ui.js','dashboard.css','themes.css','deploy/import_dashboard_history.py','index.html','app.js','studio.js','style.css','login.html','login.js','access-ui.js','network-ui.js','network/__init__.py','network/client.py','network/config.py','network/rpc.py','deploy/Dockerfile','deploy/image-requirements.lock']
def run(*args, check=True, interactive=False):
@@ -204,7 +204,7 @@ def update(base, rollback=False, force=False, api_ports=None, reuse_runtime=Fals
backup_name=config['name']+'-previous'
if inspect(backup_name):raise RuntimeError('Rückfall-Containername belegt. Keine Änderung ausgeführt.')
stamp=str(time.time_ns())
- shutil.copytree(base/'state',base/'backups'/stamp,ignore=shutil.ignore_patterns('audio-cpp-runtime','ltx-original-runtime','original-work','tts-runtime','tts-tests','models','runtime','runtime-verification','image-tests','image-verification','image-verification-*','image-runtime','video-runtime','video-verification','video-verification-*','comfy-work','router-verification-*'))
+ shutil.copytree(base/'state',base/'backups'/stamp,ignore=shutil.ignore_patterns('music-jobs','audio-cpp-runtime','ltx-original-runtime','original-work','tts-runtime','tts-tests','models','runtime','runtime-verification','image-tests','image-verification','image-verification-*','image-runtime','video-runtime','video-verification','video-verification-*','comfy-work','router-verification-*'))
was_running=previous['State']['Running']
if was_running:run('docker','stop','--time','15',config['name'])
run('docker','rename',config['name'],backup_name)
diff --git a/docs/MUSIC_API.md b/docs/MUSIC_API.md
new file mode 100644
index 0000000..b9d6019
--- /dev/null
+++ b/docs/MUSIC_API.md
@@ -0,0 +1,25 @@
+# YuE2 über audio.cpp
+
+Deck führt die offizielle YuE2-GGUF-Familie über die installierte audio.cpp-CLI aus. Keine neue Laufzeitinstallation oder Docker-Container nötig. BF16, Q8_0 und Q4_0-Hauptdateien aus audio-cpp/Yue2-3B-GGUF werden mit zugeordnetem VAE und vier Sidecars unterstützt.
+
+## Bedienung
+
+Musik → Profile → Einrichtung & Komponenten: fehlende Dateien herunterladen und zuordnen. Danach kann das Profil am API-Endpunkt freigegeben werden. Unter Musik → Testen ein ausführbares Profil wählen, Musikstil und Songtext eingeben und Musik erzeugen. Status, Laufzeit, Abbruch, Fehler und WAV-Ergebnis sind sichtbar. Englische Texte werden empfohlen. Standardtest: cot=off, 400 semantische Tokens, 8 NAR-Schritte, Seed 1234. Tokenlimit ist keine garantierte Sekundenlänge. Bis 9000 Tokens und 32 NAR-Schritte sind einstellbar.
+
+Deck reserviert exklusiv seinen GPU-Auftrag, entlädt sein LLM und verdrängt eigenes TTS. Fremde Prozesse werden nicht beendet. Die freie RTX 5080 wird verwendet; keine automatische CPU-Auslagerung. Video-Modus sperrt Musik. Nach Abschluss/Fehler/Abbruch endet der CLI-Prozess und gibt GPU-Speicher frei. Prompts und vorübergehende Worker-Ausgabe werden nicht dauerhaft gespeichert; WAV-Ergebnisse bleiben in music-jobs. Installation und Profilrezept sind Bestandteil der vorhandenen Wiederherstellung; Ergebnis-Audio nicht.
+
+## API
+
+Dies ist eine Deck-Musik-Erweiterung, kein standardisierter OpenAI-Musikendpunkt. Gleicher API-Port und Bearer-Token wie Chat, getrennte Modellliste:
+
+- GET /v1/audio/music/models: nur aktivierte ausführbare Musikprofile.
+- POST /v1/audio/music: JSON mit model (Profilname), lyrics, style und optional seed, max_tokens, steps; gibt audio/wav zurück. Authentifiziert und GPU-koordiniert. Maximales Worker-Zeitlimit 30 Minuten.
+- GET /v1/models bleibt ausschließlich für Chatmodelle.
+
+Interne GUI-API mit Deck-Anmeldung/CSRF: GET /api/v1/music; POST /api/v1/music/start mit profile_id, lyrics, style und optionalen Testparametern; POST /api/v1/music/cancel mit {}; GET /api/v1/music/audio?id=JOB_ID.
+
+Die Ladypoly-WebUI ist hiermit noch nicht verbunden. Der Deck-Testbereich und die Musik-API funktionieren unabhängig davon. Weitere Musikfamilien oder LoRA/ABC-Funktionen sind noch nicht angebunden.
+
+## Verifikation auf Athena
+
+2026-09-30: YuE2 BF16 mit zugeordnetem F16-VAE und Sidecars erzeugte bei 200 semantischen Tokens / 2 NAR-Schritten eine gültige stereo WAV von 8 Sekunden in etwa 18 Sekunden auf der RTX 5080. Danach Prozess beendet und GPU-Speicher freigegeben. Dies prüft Ausführung und Dateiformat, nicht musikalische Qualität oder Stabilität langer Aufträge.
diff --git a/docs/RUNTIME_DISCOVERY.md b/docs/RUNTIME_DISCOVERY.md
index 4d338d3..d4e1cb5 100644
--- a/docs/RUNTIME_DISCOVERY.md
+++ b/docs/RUNTIME_DISCOVERY.md
@@ -12,13 +12,13 @@ Eine vorhandene Laufzeit wird wiederverwendet. Ein fehlender Worker oder Workflo
**Einstellungen → Laufzeiten → audio.cpp** bietet einen isolierten CUDA-Build aus `0xShug0/audio.cpp`, Version v0.8.1, Commit `f2b4937306daa25f5c78520f3c626ed31495a37a`. Voraussetzung: Debian/Linux, Git, CMake, g++, nvcc, nvidia-smi und mindestens 6 GiB frei. GPU-Architekturen werden gelesen; gebaut wird mit sechs Jobs. Keine Host-Pakete, Treiber oder bestehenden Laufzeiten werden verändert. Fehlende Voraussetzungen werden gemeldet.
-Installation liegt im Deck-Zustandsverzeichnis unter `audio-cpp-runtime`. Status, Abbruch und eigene Build-Ausgabe sind in der GUI verfügbar. Der CLI-Start wird ohne Modell geprüft. Dieser Build lädt **keine Gewichte**. Der ausführbare Deck-Musikworker und die Ladypoly-WebUI-Anbindung fehlen noch; sie sind separat umzusetzen. Auch andere audio.cpp-Familien brauchen geprüfte modellspezifische Anbindungen.
+Installation liegt im Deck-Zustandsverzeichnis unter `audio-cpp-runtime`. Status, Abbruch und eigene Build-Ausgabe sind in der GUI verfügbar. Der CLI-Start wird ohne Modell geprüft. Dieser Build lädt **keine Gewichte**. Der ausführbare YuE2-Deck-Musikworker ist angebunden; Bedienung und API sind in MUSIC_API.md dokumentiert. Die Ladypoly-WebUI-Anbindung ist separat. Auch andere audio.cpp-Familien brauchen geprüfte modellspezifische Anbindungen.
Interne Admin-API: `GET /api/v1/audio-cpp-runtime`, `GET /api/v1/audio-cpp-runtime/log`, `POST /api/v1/audio-cpp-runtime/install` und `/cancel` (JSON `{}`, vorhandene Sitzung und CSRF-Header). Backup/Restore enthält das gepinnte Laufzeitrezept und baut es bei Bedarf neu; keine Binärdateien im Backup.
## YuE2-Komponenten in der GUI
-Unter **Musik → Profile → Komponenten** bietet Deck für das offizielle YuE2-GGUF-Hauptmodell fünf Rollen: VAE (F16 empfohlen oder F32), Modellkonfiguration, Generierungskonfiguration, Qwen-Tokenizer und VAE-Konfiguration. „Alle fehlenden Dateien herunterladen“ reiht je eine Datei pro Rolle in die Download-Warteschlange ein. Danach „Bibliothek aktualisieren“ und „Zuordnung auf Athena speichern“. Bereits vorhandene Dateien werden wiederverwendet. Quelle und Zuweisung müssen dieselbe Hub-Revision wie das Hauptmodell haben. Diese Zusatzdateien sind keine eigenständigen Modelle. Der fehlende Musikworker bleibt auch nach vollständiger Zuweisung als Blocker sichtbar.
+Unter **Musik → Profile → Komponenten** bietet Deck für das offizielle YuE2-GGUF-Hauptmodell fünf Rollen: VAE (F16 empfohlen oder F32), Modellkonfiguration, Generierungskonfiguration, Qwen-Tokenizer und VAE-Konfiguration. „Alle fehlenden Dateien herunterladen“ reiht je eine Datei pro Rolle in die Download-Warteschlange ein. Danach „Bibliothek aktualisieren“ und „Zuordnung auf Athena speichern“. Bereits vorhandene Dateien werden wiederverwendet. Quelle und Zuweisung müssen dieselbe Hub-Revision wie das Hauptmodell haben. Diese Zusatzdateien sind keine eigenständigen Modelle. Die Ausführungsprüfung bleibt auch nach vollständiger Zuweisung als Blocker sichtbar.
## Gemeinsame Einrichtung in allen Kategorien
diff --git a/endpoint-ui.js b/endpoint-ui.js
index 3863829..73eedb4 100644
--- a/endpoint-ui.js
+++ b/endpoint-ui.js
@@ -4,7 +4,7 @@ window.EndpointUI=(()=>{
let pending=false,sequence=0;
async function api(path='',data){const r=await fetch('/api/v1/endpoint'+path,data!==undefined?{method:'POST',headers:{'Content-Type':'application/json','X-Athena-Deck':'1'},body:JSON.stringify(data)}:{});const v=await r.json();if(!r.ok)throw Error(v.error||'Endpunkt nicht erreichbar');return v;}
function put(el,html){if(el._deckHtml!==html){el.innerHTML=html;el._deckHtml=html;}}
- function summary(s){return `
Aktivierte Profile werden angeboten. Erst eine Anfrage lädt das Modell. Wechsel warten auf laufende Antworten; Bildaufträge entladen vorher das eigene Sprachmodell.
${[['llm','Sprachmodelle'],['image','Bildgenerierung'],['tts','Text → Sprache (TTS)'],['stt','Sprache → Text (STT)']].map(([key,label])=>{const c=s.counts[key];return `
${label}
${c.supported?`${c.enabled} Profile aktiviert · ${c.available} ausführbar`:'Noch keine eigene Laufzeit eingerichtet'}
${c.loaded?'AKTIV IM SPEICHER':c.available?'BEI ANFRAGE LADEN':'NICHT AKTIV'}`}).join('')}
Aktivierte Profile werden angeboten. Erst eine Anfrage lädt das Modell. Wechsel warten auf laufende Antworten; Bildaufträge entladen vorher das eigene Sprachmodell.
${[['llm','Sprachmodelle'],['image','Bildgenerierung'],['tts','Text → Sprache (TTS)'],['stt','Sprache → Text (STT)'],['music','Musik']].map(([key,label])=>{const c=s.counts[key];return `
${label}
${c.supported?`${c.enabled} Profile aktiviert · ${c.available} ausführbar`:'Noch keine eigene Laufzeit eingerichtet'}
${c.loaded?'AKTIV IM SPEICHER':c.available?'BEI ANFRAGE LADEN':'NICHT AKTIV'}`}).join('')}
`;}
async function render(settings=false){
const view=document.querySelector('#view');let root=view.querySelector('#endpoint-page');
if(!root||root.dataset.settings!==String(settings)){
diff --git a/endpoint.py b/endpoint.py
index 89e2701..7183525 100644
--- a/endpoint.py
+++ b/endpoint.py
@@ -39,7 +39,7 @@ def chat_upstream_error(status, raw):
class Endpoint:
def __init__(self,root,profiles,worker,scheduler,images,credentials,management_port):
- self.root=Path(root);self.profiles=profiles;self.worker=worker;self.scheduler=scheduler;self.images=images;self.tts=None;self.stt=None;self.video=None;self.credentials=credentials
+ self.root=Path(root);self.profiles=profiles;self.worker=worker;self.scheduler=scheduler;self.images=images;self.tts=None;self.stt=None;self.music=None;self.video=None;self.credentials=credentials
self.management_port=management_port;self.lock=threading.RLock();self.http=None;self.thread=None;self.state='stopped';self.error=None;self.inflight=0
self.allowed_ports=[int(p) for p in os.environ.get('DECK_API_PORTS','').split(',') if p]
self.config=dict(port=self.allowed_ports[0] if self.allowed_ports else 8120,enabled_profiles=[],autostart=False)
@@ -58,9 +58,9 @@ class Endpoint:
return [dict(p,enabled=p['id'] in enabled) for p in rows if p['kind']!='video']
def status(self):
rows=self.rows();worker=self.worker.status();job=self.images.status()['job'];counts={}
- for key,kind in [('llm','chat'),('image','image'),('tts','audio'),('stt','stt')]:
+ for key,kind in [('llm','chat'),('image','image'),('tts','audio'),('stt','stt'),('music','music')]:
subset=[p for p in rows if p['kind']==kind]
- counts[key]=dict(enabled=sum(p['enabled'] for p in subset),available=sum(p['enabled'] and p['runnable'] for p in subset),loaded=bool(worker['state']=='ready' and kind=='chat') if kind=='chat' else bool(self.tts and self.tts.status().get('loaded')) if kind=='audio' else bool(self.stt and self.stt.status().get('loaded')) if kind=='stt' else bool(kind=='image' and job and job['state']=='running'),supported=kind in ('chat','image') or (kind=='audio' and self.tts is not None) or (kind=='stt' and self.stt is not None))
+ counts[key]=dict(enabled=sum(p['enabled'] for p in subset),available=sum(p['enabled'] and p['runnable'] for p in subset),loaded=bool(worker['state']=='ready' and kind=='chat') if kind=='chat' else bool(self.tts and self.tts.status().get('loaded')) if kind=='audio' else bool(self.stt and self.stt.status().get('loaded')) if kind=='stt' else bool(self.music and self.music.status().get('loaded')) if kind=='music' else bool(kind=='image' and job and job['state']=='running'),supported=kind in ('chat','image') or (kind=='audio' and self.tts is not None) or (kind=='stt' and self.stt is not None) or (kind=='music' and self.music is not None))
scheduler_status=self.scheduler.status()
with self.lock:return dict(video=self.video.status() if self.video else None,state=self.state,reachable=bool(self.thread and self.thread.is_alive() and self.state=='running'),port=self.config['port'],bind=os.environ.get('DECK_API_BIND','127.0.0.1'),base_url=f"http://127.0.0.1:{self.config['port']}/v1",allowed_ports=self.allowed_ports,error=self.error,counts=counts,worker=worker,scheduler=scheduler_status,profiles=[dict(id=p['id'],name=p['name'],kind=p['kind'],enabled=p['enabled'],runnable=p['runnable'],blockers=p['blockers']) for p in rows],active_requests=self.inflight)
def configure(self,data):
@@ -127,6 +127,7 @@ class Endpoint:
# Process shutdown does not change the user's autostart preference.
with self.lock:self.state='stopping';http=self.http
if http:http.shutdown();http.server_close()
+ if self.music:self.music.stop()
if self.tts:self.tts.stop()
if self.stt:self.stt.stop()
self.images.stop();self.worker.stop()
@@ -203,7 +204,7 @@ class APIHandler(BaseHTTPRequestHandler):
try:ep.video.relay(self) if callable(getattr(type(ep.video),'relay',None)) else relay(self,video['selected'])
except ValueError as exc:raise APIError(str(exc)) from None
return
- model_routes={'/v1/models':'chat','/v1/images/models':'image','/v1/audio/speech/models':'audio','/v1/audio/transcriptions/models':'stt'}
+ model_routes={'/v1/models':'chat','/v1/images/models':'image','/v1/audio/speech/models':'audio','/v1/audio/transcriptions/models':'stt','/v1/audio/music/models':'music'}
if self.command=='GET' and self.path in model_routes:return self.send(ep.model_list(model_routes[self.path]))
if self.command=='GET' and self.path=='/health':return self.send({'status':'ok','service':'athena-deck-api'})
if self.video_route(ep):return
@@ -219,7 +220,7 @@ class APIHandler(BaseHTTPRequestHandler):
fields['n']=int(fields.get('n','1'))
return self.image(ep,fields,images)
except ValueError as exc:raise APIError(str(exc)) from None
- if self.path not in ('/v1/chat/completions','/v1/images/generations','/v1/audio/speech'):raise APIError('Route nicht implementiert.',404)
+ if self.path not in ('/v1/chat/completions','/v1/images/generations','/v1/audio/speech','/v1/audio/music'):raise APIError('Route nicht implementiert.',404)
if self.headers.get('Transfer-Encoding'):raise APIError('Chunked Upload wird nicht unterstützt.')
try:length=int(self.headers.get('Content-Length','0'))
except ValueError:raise APIError('Ungültige Content-Length.') from None
@@ -228,6 +229,7 @@ class APIHandler(BaseHTTPRequestHandler):
except (ValueError,UnicodeError):raise APIError('Ungültiges JSON.') from None
if not isinstance(data,dict):raise APIError('JSON-Objekt erforderlich.')
if self.path=='/v1/chat/completions':return self.chat(ep,data)
+ if self.path=='/v1/audio/music':return self.music_generation(ep,data)
if self.path=='/v1/audio/speech':return self.speech(ep,data)
return self.image(ep,data)
except (APIError,InferenceError) as exc:
@@ -272,6 +274,20 @@ class APIHandler(BaseHTTPRequestHandler):
except (ValueError,OSError):time.sleep(.25);continue
self.sent=True;self.send_response(200);self.send_header('Content-Type','audio/wav');self.send_header('Content-Length',str(len(body)));self.send_header('Cache-Control','no-store');self.send_header('Connection','close');self.end_headers();self.wfile.write(body);return
raise APIError('TTS-Zeitlimit überschritten.',504)
+ def music_generation(self,ep,data):
+ if not ep.music:raise APIError('Musikworker nicht eingerichtet.',501)
+ if set(data)-{'model','lyrics','style','seed','max_tokens','steps'}:raise APIError('Nicht unterstützte Musikfelder.')
+ profile=ep.find_profile(data.get('model'),'music')
+ try:job=ep.music.start(profile['id'],data.get('lyrics'),data.get('style'),seed=data.get('seed',1234),max_tokens=data.get('max_tokens',400),steps=data.get('steps',8),wait=True)
+ except ValueError as exc:raise APIError(str(exc)) from None
+ end=time.monotonic()+1850
+ while time.monotonic()Athena DeckATHENA CONTROL SURFACEv0.7 · Router
+Athena DeckATHENA CONTROL SURFACEv0.7 · Router
diff --git a/music-ui.js b/music-ui.js
new file mode 100644
index 0000000..4ef76f1
--- /dev/null
+++ b/music-ui.js
@@ -0,0 +1,10 @@
+window.MusicUI=(()=>{
+ const e=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
+ async function api(path,data){const r=await fetch('/api/v1/'+path,data===undefined?{}:{method:'POST',headers:{'Content-Type':'application/json','X-Athena-Deck':'1'},body:JSON.stringify(data)});const x=await r.json();if(!r.ok)throw Error(x.error||'Musikanfrage fehlgeschlagen');return x;}
+ function html(){return '
Musik ausprobieren
YuE2 über audio.cpp. Englische Songtexte werden empfohlen. Ein kurzer Test beginnt mit höchstens 400 semantischen Tokens; dies ist keine feste Dauer in Sekunden.
RTX 5080 bevorzugt. Deck-Aufträge werden koordiniert. Nach der Anfrage wird das Modell entladen. Text und Stil werden nicht dauerhaft gespeichert.
';}
+ function bind(){const root=document.querySelector('#music-test'),el=id=>root.querySelector('#'+id);let ready=false,busy=false;
+ async function poll(){if(!root.isConnected)return;try{const s=await api('music');if(!root.isConnected)return;const j=s.job,running=j?.state==='running';el('music-state').textContent=(j?.phase||'Profil wählen und Musikstil sowie Songtext eingeben.')+(running?' · '+Math.round((Date.now()/1000)-j.started_at)+' s':'');el('music-progress').innerHTML=running?'':'';el('music-start').disabled=busy||running||!ready;el('music-cancel').disabled=!running;if(j?.state==='complete')el('music-result').innerHTML=`
`;if(j?.state==='failed')el('music-error').textContent=j.phase;}catch(x){if(root.isConnected)el('music-error').textContent=x.message;}if(root.isConnected)setTimeout(poll,1500);}
+ api('profiles').then(s=>{if(!root.isConnected)return;const profiles=s.profiles.filter(p=>p.kind==='music'&&p.runnable);ready=profiles.length>0;el('music-profile').innerHTML=profiles.map(p=>``).join('')||'';}).catch(x=>el('music-error').textContent=x.message);
+ el('music-form').onsubmit=async event=>{event.preventDefault();busy=true;el('music-start').disabled=true;el('music-error').textContent='';el('music-result').replaceChildren();try{await api('music/start',{profile_id:el('music-profile').value,lyrics:el('music-lyrics').value,style:el('music-style').value,seed:Number(el('music-seed').value),max_tokens:Number(el('music-tokens').value),steps:Number(el('music-steps').value)});}catch(x){el('music-error').textContent=x.message;}finally{busy=false;}};el('music-cancel').onclick=()=>api('music/cancel',{}).catch(x=>el('music-error').textContent=x.message);poll();}
+ return {html,bind};
+})();
diff --git a/music.py b/music.py
new file mode 100644
index 0000000..371f3e1
--- /dev/null
+++ b/music.py
@@ -0,0 +1,85 @@
+"""Owned audio.cpp music jobs, coordinated GPU lease, no persistent prompts."""
+import os,signal,subprocess,threading,time,uuid,wave
+from pathlib import Path
+from profiles import music_recipe
+from image_test import probe
+class Music:
+ def __init__(self,root,profiles,catalog,runtime,scheduler):
+ self.root=Path(root);self.profiles=profiles;self.catalog=catalog;self.runtime=runtime;self.scheduler=scheduler;self.lock=threading.RLock();self.job=None;self.process=None;self.cancel=threading.Event()
+ def blockers(self,p):
+ if not music_recipe(p['model']):return ['Für diese Musik-Modellfamilie fehlt noch eine geprüfte audio.cpp-Anbindung.']
+ result=[] if self.runtime.status()['installed'] else ['audio.cpp zuerst unter Laufzeiten installieren.']
+ for role,info in music_recipe(p['model']).items():
+ try:self.profiles._component(p['model'],role,p.get('components',{}).get(role))
+ except ValueError:result.append(info['label']+' fehlt oder ist nicht zugeordnet.')
+ return result
+ def status(self):
+ with self.lock:return dict(job=dict(self.job) if self.job else None,loaded=bool(self.process and self.process.poll() is None),runtime_installed=self.runtime.status()['installed'])
+ def start(self,profile_id,lyrics,style,seed=1234,max_tokens=400,steps=8,wait=False):
+ if not isinstance(lyrics,str) or not 1<=len(lyrics.strip())<=8000:raise ValueError('Songtext mit 1–8000 Zeichen eingeben.')
+ if not isinstance(style,str) or not 1<=len(style.strip())<=2000:raise ValueError('Musikstil mit 1–2000 Zeichen eingeben.')
+ for value,lo,hi in ((seed,0,2**31-1),(max_tokens,32,9000),(steps,1,32)):
+ if type(value) is not int or not lo<=value<=hi:raise ValueError('Ungültige Musikparameter.')
+ with self.lock:
+ if self.job and self.job['state']=='running':raise ValueError('Musikauftrag läuft bereits.')
+ p=next((p for p in self.profiles.status()['profiles'] if p['id']==profile_id and p['kind']=='music'),None)
+ if not p or not p['runnable']:raise ValueError('Musikprofil noch nicht vollständig eingerichtet.')
+ self.cancel.clear();self.root.mkdir(parents=True,exist_ok=True,mode=0o700)
+ self.job=dict(id=uuid.uuid4().hex,state='running',phase='Wartet auf Deck-GPU-Freigabe',profile_id=profile_id,started_at=time.time(),max_tokens=max_tokens)
+ threading.Thread(target=self._run,args=(p,lyrics,style,seed,max_tokens,steps,wait),daemon=True).start();return dict(self.job)
+ def _run(self,p,lyrics,style,seed,max_tokens,steps,wait):
+ release=None;state='failed';phase='Musikgenerierung fehlgeschlagen.';directory=self.root/self.job['id'];log=directory/'worker.tmp'
+ try:
+ release=self.scheduler.image_reservation(wait=wait)
+ if self.cancel.is_set():raise InterruptedError()
+ required=int(p['model']['size']/1024**2)+1800
+ gpu=next((g for g in probe() if 'RTX 5080' in g['name'] and not g['processes'] and g['free_mib']>required),None)
+ if not gpu:raise ValueError('RTX 5080 nicht ausreichend frei. Fremde GPU-Prozesse werden nicht beendet.')
+ directory.mkdir(mode=0o700);bundle=directory/'package';bundle.mkdir();(bundle/'sidecars').mkdir()
+ def link(entry,name):
+ source=self.catalog.root/entry['id']/('model'+Path(entry['file']).suffix)
+ (bundle/name).symlink_to(source.resolve())
+ link(p['model'],p['model']['file'])
+ for role in music_recipe(p['model']):
+ entry=self.profiles._component(p['model'],role,p['components'][role]);link(entry,entry['file'])
+ vae=self.catalog.entry(p['components']['vae'])['file'];cli=self.runtime.paths()[0]
+ args=[str(cli),'--task','gen','--family','yue2','--model',str(bundle),'--backend','cuda','--device','0','--threads','6','--session-option','yue2.model_gguf='+p['model']['file'],'--session-option','yue2.vae_gguf='+vae,'--lyrics',lyrics,'--request-option','style='+style,'--request-option','cot=off','--request-option','semantic_min_tokens=0','--request-option','semantic_max_tokens='+str(max_tokens),'--request-option','num_inference_steps='+str(steps),'--seed',str(seed),'--out',str(directory/'result.wav'),'--log']
+ with self.lock:self.job.update(phase='YuE2 wird auf der RTX 5080 geladen · anschließend Musik erzeugen',gpu=gpu['name'])
+ with log.open('w') as out:
+ with self.lock:
+ if self.cancel.is_set():raise InterruptedError()
+ self.process=subprocess.Popen(args,stdout=out,stderr=subprocess.STDOUT,start_new_session=True,env=dict(os.environ,CUDA_VISIBLE_DEVICES=gpu['uuid'],OMP_NUM_THREADS='6'))
+ deadline=time.monotonic()+1800
+ while self.process.poll() is None:
+ if self.cancel.wait(.5):raise InterruptedError()
+ if time.monotonic()>deadline:raise ValueError('Zeitlimit von 30 Minuten erreicht.')
+ with self.lock:self.job['elapsed_seconds']=round(time.time()-self.job['started_at'])
+ if self.process.returncode:
+ output=log.read_text(errors='replace').lower()
+ if 'out of memory' in output or 'cuda error 2' in output:raise ValueError('GPU-Speicher reicht nicht (Out of Memory). Kleinere Quantisierung oder kürzeren Test wählen.')
+ raise ValueError('audio.cpp hat die Generierung abgebrochen. Modellpaket oder Laufzeit prüfen.')
+ with wave.open(str(directory/'result.wav'),'rb') as wav:
+ if wav.getnframes()<1 or wav.getnchannels() not in (1,2):raise ValueError('Ungültige WAV-Ausgabe.')
+ duration=wav.getnframes()/wav.getframerate()
+ (directory/'complete').touch();state='complete';phase='Musik fertig · Modell entladen'
+ with self.lock:self.job['duration_seconds']=round(duration,2)
+ except InterruptedError:state='cancelled';phase='Musikgenerierung abgebrochen · Modell entladen'
+ except Exception as exc:phase=str(exc) if isinstance(exc,ValueError) else 'Musikgenerierung fehlgeschlagen. Modellpaket und Laufzeit prüfen.'
+ finally:
+ process=self.process
+ if process and process.poll() is None:
+ try:os.killpg(process.pid,signal.SIGTERM);process.wait(5)
+ except subprocess.TimeoutExpired:os.killpg(process.pid,signal.SIGKILL);process.wait()
+ except ProcessLookupError:pass
+ log.unlink(missing_ok=True)
+ with self.lock:self.process=None;self.job.update(state=state,phase=phase,finished_at=time.time())
+ if release:release()
+ def stop(self):
+ self.cancel.set();return {'cancellation_requested':True}
+ def audio(self,ident):
+ if not isinstance(ident,str) or len(ident)!=32 or any(c not in '0123456789abcdef' for c in ident):raise ValueError('Ungültige Auftrag-ID.')
+ directory=self.root/ident
+ if not (directory/'complete').is_file():raise ValueError('Musik noch nicht fertig.')
+ wav=directory/'result.wav'
+ if wav.stat().st_size>256*1024**2:raise ValueError('Audioausgabe zu groß.')
+ return wav.read_bytes()
diff --git a/profiles.py b/profiles.py
index b844170..504e327 100644
--- a/profiles.py
+++ b/profiles.py
@@ -144,6 +144,7 @@ class Profiles:
except ValueError:p['blockers'].append(info['label']+' fehlt oder ist noch nicht zugeordnet.')
if p['kind']=='video' and self.video_blockers and p.get('model'):p['blockers']=self.video_blockers(p)
if p['kind']=='audio' and self.tts_blockers and p.get('model'):p['blockers']=self.tts_blockers(p)
+ if p['kind']=='music' and getattr(self,'music_blockers',None) and p.get('model'):p['blockers']=self.music_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)
if p['kind']=='image' and (p['parameters']['width']>1024 or p['parameters']['height']>1024):p['blockers'].append('Die Bildlaufzeit unterstützt derzeit maximal 1024 × 1024 Pixel.')
@@ -241,7 +242,7 @@ 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 music_recipe(model):return dict(supported=True,requirements=requirements,source='https://huggingface.co/'+model['repo']+'/blob/'+model['revision']+'/README.md',license=source['license'],runtime='audio.cpp',runtime_installed=bool(getattr(self,'music_runtime_ready',lambda:False)()),message='Passendes YuE2-GGUF-Paket: je eine VAE-Datei und die vier Zusatzdateien derselben Quellversion. F16-VAE ist die erste Downloadempfehlung. Vorhandene Dateien wiederverwenden, fehlende herunterladen und danach zuordnen. Der Deck-Musikworker und die Ladypoly-Anbindung fehlen weiterhin; vollständige Komponenten starten kein Modell.')
+ if music_recipe(model):return dict(supported=True,requirements=requirements,source='https://huggingface.co/'+model['repo']+'/blob/'+model['revision']+'/README.md',license=source['license'],runtime='audio.cpp',runtime_installed=bool(getattr(self,'music_runtime_ready',lambda:False)()),message='Passendes YuE2-GGUF-Paket: je eine VAE-Datei und die vier Zusatzdateien derselben Quellversion. F16-VAE ist die erste Downloadempfehlung. Vorhandene Dateien wiederverwenden, fehlende herunterladen und danach zuordnen. Die YuE2-Musikanbindung verwendet diese Dateien über audio.cpp. Speichern startet kein Modell; anschließend Musik → Testen verwenden.')
if video:return dict(supported=True,requirements=requirements,source='https://huggingface.co/'+model['repo']+'/blob/'+model['revision']+'/README.md',license=source['license'],runtime='ComfyUI · gemeinsame Bild- und Videolaufzeit',runtime_installed=bool(getattr(self,'video_runtime_ready',lambda: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. Vorhandene ComfyUI-Laufzeit wiederverwenden; Videomodell im Bereich Video aktivieren. Vollständige Dateien sind keine Speicherzusage.')
if not image_recipe(model):return dict(supported=True,requirements=requirements,source=source['url'],license=source['license'],runtime='audio.cpp · Paketdefinition',runtime_installed=bool(getattr(self,'music_runtime_ready',lambda:False)()),message='Zusatzdateien aus der installierten Laufzeit-Paketdefinition. Weitere modellabhängige Pakete können erforderlich sein; dies ist keine vollständige Worker-Zusage.')
return dict(supported=True,requirements=requirements,source=source['url'],license=source['license'],runtime='ComfyUI + ComfyUI-GGUF',runtime_installed=self.image_runtime_ready(),
diff --git a/server.py b/server.py
index 2835270..9c7dc7d 100644
--- a/server.py
+++ b/server.py
@@ -112,12 +112,16 @@ class Server(ThreadingHTTPServer):
self.chat_tests=ChatTests(self.profiles,self.worker,self.scheduler)
self.auto_tests=AutoTests(self.catalog.root.parent/'auto-tests.json',self.profiles,self.worker,self.scheduler)
def stop_gpu_work():
- self.auto_tests.stop();self.chat_tests.stop();self.image_tests.stop();self.prompt_enhancer.stop_preview();self.tts_tests.stop();self.worker.stop()
+ self.music.stop();self.auto_tests.stop();self.chat_tests.stop();self.image_tests.stop();self.prompt_enhancer.stop_preview();self.tts_tests.stop();self.worker.stop()
from video_original import VideoRuntimes
from ltx_original_runtime import LTXOriginalRuntime
from audio_cpp_runtime import AudioCppRuntime
self.audio_cpp_runtime=AudioCppRuntime(self.catalog.root.parent/'audio-cpp-runtime')
self.profiles.music_runtime_ready=lambda:self.audio_cpp_runtime.status()['installed']
+ from music import Music
+ self.music=Music(self.catalog.root.parent/'music-jobs',self.profiles,self.catalog,self.audio_cpp_runtime,self.scheduler)
+ self.profiles.music_blockers=self.music.blockers
+ self.endpoint.music=self.music
from profiles import audio_package_recipe
self.profiles.runtime_recipe=lambda model:audio_package_recipe(model,self.audio_cpp_runtime.paths()[1])
self.ltx_original_runtime=LTXOriginalRuntime(self.catalog.root.parent/'ltx-original-runtime',self.image_runtime)
@@ -283,7 +287,7 @@ class Handler(BaseHTTPRequestHandler):
return self.headers.get('Host') in allowed
def execution_setup(self, model, profile=None):
- installed={'stt':bool(self.server.runtime.status().get('active')),'chat':bool(self.server.runtime.status().get('active')),'image':self.server.image_runtime.status().get('installed',False),'audio':self.server.tts_runtime.status().get('installed',False),'video':self.server.video.status()['runtime']['installed'],'audio_cpp':self.server.audio_cpp_runtime.status()['installed']}
+ installed={'stt':bool(self.server.runtime.status().get('active')),'chat':bool(self.server.runtime.status().get('active')),'image':self.server.image_runtime.status().get('installed',False),'audio':self.server.tts_runtime.status().get('installed',False),'video':self.server.video.status()['runtime']['installed'],'audio_cpp':self.server.audio_cpp_runtime.status()['installed'],'music_worker':True}
return execution_assess(model,installed,profile)
def do_GET(self):
@@ -297,7 +301,7 @@ class Handler(BaseHTTPRequestHandler):
return self.respond({'error':'Anmeldung erforderlich.'},401)
if not self.authenticated() and self.path == '/':
return self.respond((ROOT/'login.html').read_bytes(), mime='text/html; charset=utf-8')
- routes = {'/audio-cpp-ui.js':('audio-cpp-ui.js','text/javascript'),'/ltx-original-ui.js':('ltx-original-ui.js','text/javascript'),'/backup-ui.js':('backup-ui.js','text/javascript'),'/dashboard-ui.js':('dashboard-ui.js','text/javascript'),'/dashboard.css':('dashboard.css','text/css'),'/themes.css':('themes.css','text/css'),'/video-ui.js':('video-ui.js','text/javascript'),'/stt-ui.js':('stt-ui.js','text/javascript'),'/tts-ui.js':('tts-ui.js','text/javascript'),'/auto-test-ui.js':('auto-test-ui.js','text/javascript'),'/chat-test-ui.js':('chat-test-ui.js','text/javascript'),'/endpoint-ui.js':('endpoint-ui.js','text/javascript'),'/docker-ui.js': ('docker-ui.js','text/javascript'), '/': ('index.html', 'text/html; charset=utf-8'), '/app.js': ('app.js', 'text/javascript'), '/style.css': ('style.css', 'text/css'), '/network-ui.js': ('network-ui.js', 'text/javascript'), '/access-ui.js': ('access-ui.js', 'text/javascript'), '/studio.js': ('studio.js', 'text/javascript'), '/catalog-ui.js': ('catalog-ui.js','text/javascript'), '/runtime-ui.js': ('runtime-ui.js','text/javascript'), '/profiles-ui.js': ('profiles-ui.js','text/javascript'), '/image-test-ui.js': ('image-test-ui.js','text/javascript')}
+ routes = {'/music-ui.js':('music-ui.js','text/javascript'),'/audio-cpp-ui.js':('audio-cpp-ui.js','text/javascript'),'/ltx-original-ui.js':('ltx-original-ui.js','text/javascript'),'/backup-ui.js':('backup-ui.js','text/javascript'),'/dashboard-ui.js':('dashboard-ui.js','text/javascript'),'/dashboard.css':('dashboard.css','text/css'),'/themes.css':('themes.css','text/css'),'/video-ui.js':('video-ui.js','text/javascript'),'/stt-ui.js':('stt-ui.js','text/javascript'),'/tts-ui.js':('tts-ui.js','text/javascript'),'/auto-test-ui.js':('auto-test-ui.js','text/javascript'),'/chat-test-ui.js':('chat-test-ui.js','text/javascript'),'/endpoint-ui.js':('endpoint-ui.js','text/javascript'),'/docker-ui.js': ('docker-ui.js','text/javascript'), '/': ('index.html', 'text/html; charset=utf-8'), '/app.js': ('app.js', 'text/javascript'), '/style.css': ('style.css', 'text/css'), '/network-ui.js': ('network-ui.js', 'text/javascript'), '/access-ui.js': ('access-ui.js', 'text/javascript'), '/studio.js': ('studio.js', 'text/javascript'), '/catalog-ui.js': ('catalog-ui.js','text/javascript'), '/runtime-ui.js': ('runtime-ui.js','text/javascript'), '/profiles-ui.js': ('profiles-ui.js','text/javascript'), '/image-test-ui.js': ('image-test-ui.js','text/javascript')}
if self.path in routes:
name, mime = routes[self.path]
return self.respond((ROOT/name).read_bytes(), mime=mime)
@@ -316,6 +320,10 @@ class Handler(BaseHTTPRequestHandler):
if urlsplit(self.path).path == '/api/v1/tts/audio':
try:return self.respond(self.server.tts_tests.audio(parse_qs(urlsplit(self.path).query).get('id',[''])[0]),mime='audio/wav')
except (OSError,ValueError):return self.respond({'error':'Audio nicht verfügbar.'},404)
+ if self.path == '/api/v1/music':return self.respond(self.server.music.status())
+ if urlsplit(self.path).path == '/api/v1/music/audio':
+ try:return self.respond(self.server.music.audio(parse_qs(urlsplit(self.path).query).get('id',[''])[0]),mime='audio/wav')
+ except (OSError,ValueError):return self.respond({'error':'Musik nicht verfügbar.'},404)
if self.path == '/api/v1/audio-cpp-runtime':return self.respond(self.server.audio_cpp_runtime.status())
if self.path == '/api/v1/audio-cpp-runtime/log':return self.respond(self.server.audio_cpp_runtime.log())
if self.path == '/api/v1/ltx-original-runtime':return self.respond(self.server.ltx_original_runtime.status())
@@ -536,6 +544,13 @@ class Handler(BaseHTTPRequestHandler):
return self.respond(self.server.stt.setup() if self.path.endswith('/setup') else self.server.stt.stop())
except ValueError as exc:return self.respond({'error':str(exc)},400)
except Exception:return self.respond({'error':'STT-Aktion fehlgeschlagen.'},503)
+ if self.path in ('/api/v1/music/start','/api/v1/music/cancel'):
+ try:
+ data=self.read_json()
+ if self.path.endswith('/cancel') and not data:return self.respond(self.server.music.stop())
+ if set(data)-{'profile_id','lyrics','style','seed','max_tokens','steps'} or not {'profile_id','lyrics','style'}<=set(data):raise ValueError('Ungültige Musikfelder.')
+ return self.respond(self.server.music.start(**data))
+ except ValueError as exc:return self.respond({'error':str(exc)},400)
if self.path in ('/api/v1/tts/install','/api/v1/tts/install-cancel','/api/v1/tts/assign','/api/v1/tts/start','/api/v1/tts/cancel'):
try:
data=self.read_json();t=self.server.tts_tests
@@ -677,7 +692,7 @@ def main():
server.tts_runtime.stop()
server.image_runtime.stop()
server.ltx_original_runtime.stop()
- server.audio_cpp_runtime.stop()
+ server.music.stop();server.audio_cpp_runtime.stop()
server.prompt_enhancer.stop()
server.image_tests.stop()
server.runtime.stop()
diff --git a/studio.js b/studio.js
index a6f6b37..fb5fb5f 100644
--- a/studio.js
+++ b/studio.js
@@ -8,7 +8,7 @@ const Studio=(()=>{
if(!force&&document.querySelector('#studio')?.dataset.page===page)return;
if(category!==page){category=page;section='discover';}
if(modelId)section='profiles';
- const body=page==='stt-runtime'?STTUI.html(true):page==='tts-runtime'?TTSUI.html(true):['docker-runtime','services'].includes(page)?DockerUI.html(page==='services'):page==='runtimes'?runtimeOverview():page==='image-runtime'?imageRuntime():page==='runtime'?RuntimeUI.html():`
ATHENA / MODELLVERWALTUNG
${labels[page]}
${audioDescriptions[page]||'Modelle entdecken, herunterladen und mit gespeicherten Profilen konfigurieren.'}
Entdecken und Downloads sind verfügbar. Für diese Audio-Bereiche ist noch keine ausführbare Laufzeit in Deck angebunden. Gespeicherte Profile sind vorbereitend.
Entdecken und Downloads sind verfügbar. Für diese Audio-Bereiche ist noch keine ausführbare Laufzeit in Deck angebunden. Gespeicherte Profile sind vorbereitend.