Add audio separation web app and verified recovery sources

This commit is contained in:
Mikei386 committed 2026-10-01 16:19:33 +02:00
1 parent b5ee7c1e01
commit 433e6709cf
25 files changed
+381 -46

No files matched your search

+10 -8
View File
@@ -8,17 +8,19 @@ class SeparatorTests:
self.root=Path(root);self.runtime=runtime;self.scheduler=scheduler;self.lock=threading.RLock();self.job=None;self.process=None;self.cancel=threading.Event();self.thread=None
def status(self):
with self.lock:return dict(job=dict(self.job) if self.job else None)
def start(self,model,audio):
def start(self,model,audio,target=None,full=False):
targets={'vocals':'model_bs_roformer_ep_317_sdr_12.9755.ckpt','drums':'htdemucs_ft.yaml','bass':'htdemucs_ft.yaml','guitar':'htdemucs_6s.yaml','piano':'htdemucs_6s.yaml','other':'htdemucs_6s.yaml'}
if target is not None and targets.get(target)!=model:raise ValueError('Zielspur und Modellpaket passen nicht zusammen.')
if not isinstance(model,str) or model not in MODELS or not self.runtime.status()['installed'] or not self.runtime.model_ready(model):raise ValueError('Laufzeit und Modellpaket zuerst unter Audio Separator einrichten.')
if not isinstance(audio,bytes) or not 44<=len(audio)<=8*1024**2:raise ValueError('WAV bis 8 MiB erforderlich.')
if not isinstance(audio,bytes) or not 44<=len(audio)<=(256 if full else 8)*1024**2:raise ValueError('WAV überschreitet die erlaubte Upload-Größe.')
try:
with wave.open(io.BytesIO(audio)) as wav:
if wav.getnchannels() not in (1,2) or wav.getsampwidth()!=2 or not 8000<=wav.getframerate()<=48000 or not 0<wav.getnframes()/wav.getframerate()<=30:raise ValueError('PCM-WAV, 16 Bit, Mono/Stereo, maximal 30 Sekunden erforderlich.')
if wav.getnchannels() not in (1,2) or wav.getsampwidth()!=2 or not 8000<=wav.getframerate()<=48000 or not 0<wav.getnframes()/wav.getframerate()<=(1200 if full else 30):raise ValueError('PCM-WAV, 16 Bit, Mono/Stereo, maximal 30 Sekunden erforderlich.')
if len(wav.readframes(wav.getnframes()))!=wav.getnframes()*wav.getnchannels()*2:raise ValueError('WAV-Datei ist unvollständig.')
except (wave.Error,EOFError):raise ValueError('Ungültige WAV-Datei.') from None
with self.lock:
if self.job and self.job['state']=='running':raise ValueError('Trenntest läuft bereits.')
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 GPU-Freigabe',started_at=time.time(),files=[])
self.target=target;self.full=full;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 GPU-Freigabe',started_at=time.time(),files=[])
self.thread=threading.Thread(target=self._run,args=(model,audio),daemon=True);self.thread.start();return dict(self.job)
def _run(self,model,audio):
release=None;directory=self.root/self.job['id'];state='failed';phase='Trennung fehlgeschlagen.'
@@ -30,12 +32,12 @@ class SeparatorTests:
with self.lock:
if self.cancel.is_set():raise InterruptedError()
self.job.update(phase='Modell laden und Audio trennen',gpu=gpu['name'])
self.process=subprocess.Popen([str(self.runtime.paths()[0]),str(Path(__file__).with_name('separator_worker.py')),str(self.runtime.root/'models'),model,str(directory/'input.wav'),str(directory)],stdout=subprocess.DEVNULL,stderr=diagnostics,start_new_session=True,env=dict(os.environ,CUDA_VISIBLE_DEVICES=gpu['uuid'],OMP_NUM_THREADS='6'))
self.process=subprocess.Popen([str(self.runtime.paths()[0]),str(Path(__file__).with_name('separator_worker.py')),str(self.runtime.root/'models'),model,str(directory/'input.wav'),str(directory),getattr(self,'target',None) or ''],stdout=subprocess.DEVNULL,stderr=diagnostics,start_new_session=True,env=dict(os.environ,CUDA_VISIBLE_DEVICES=gpu['uuid'],OMP_NUM_THREADS='6'))
# Drain diagnostics into a bounded temporary file rather than retaining user data.
deadline=time.monotonic()+600
deadline=time.monotonic()+(7200 if getattr(self,'full',False) else 600)
while self.process.poll() is None:
if self.cancel.wait(.2):raise InterruptedError()
if time.monotonic()>deadline:raise ValueError('Trenntest überschreitet zehn Minuten.')
if time.monotonic()>deadline:raise ValueError('Trennauftrag überschreitet das Zeitlimit.')
with self.lock:self.job['elapsed_seconds']=round(time.time()-self.job['started_at'])
diagnostics.close();error=(directory/'worker.tmp').read_bytes()[-65536:].decode(errors='replace')
if self.process.returncode:raise ValueError('GPU-Speicher reicht nicht (OOM).' if 'out of memory' in error.lower() else 'Audio Separator hat den Test abgebrochen. Modellpaket und Laufzeit prüfen.')
@@ -67,5 +69,5 @@ class SeparatorTests:
if type(index) is not int or not 0<=index<len(names) or Path(names[index]).name!=names[index]:raise ValueError('Ungültige Spur.')
path=directory/names[index]
if path.is_symlink() or path.resolve().parent!=directory.resolve():raise ValueError('Ungültiger Ergebnispfad.')
if path.stat().st_size>32*1024**2:raise ValueError('Spur zu groß.')
if path.stat().st_size>512*1024**2:raise ValueError('Spur zu groß.')
return path.read_bytes()