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

+43 -6
View File
@@ -3,9 +3,22 @@ import hashlib,json,os,shutil,sys,time
from pathlib import Path
from ltx_original_runtime import LTXOriginalRuntime
VERSION='0.47.0'
SOURCES=json.loads(Path(__file__).with_name('separator_sources.json').read_text())
def package_files(name):return [x['file'] for x in SOURCES[name]['files']]
def verify_package(directory,name):
for item in SOURCES[name]['files']:
path=Path(directory)/item['file']
if not path.is_file() or path.is_symlink():return False
digest=hashlib.sha256()
with path.open('rb') as stream:
for chunk in iter(lambda:stream.read(1024**2),b''):digest.update(chunk)
if digest.hexdigest()!=item['sha256']:return False
return True
MODELS={
'vocals_mel_band_roformer.ckpt':('MelBand RoFormer · Gesang / Instrumental','vocals_mel_band_roformer.yaml'),
'model_bs_roformer_ep_317_sdr_12.9755.ckpt':('BS-RoFormer · Gesang / Instrumental','model_bs_roformer_ep_317_sdr_12.9755.yaml')}
'model_bs_roformer_ep_317_sdr_12.9755.ckpt':('BS-RoFormer · Gesang / Instrumental','model_bs_roformer_ep_317_sdr_12.9755.yaml'),
'htdemucs_ft.yaml':('Demucs FT · Gesang / Schlagzeug / Bass / Rest','htdemucs_ft.yaml'),
'htdemucs_6s.yaml':('Demucs 6s · zusätzlich Gitarre / Piano','htdemucs_6s.yaml')}
class SeparatorRuntime(LTXOriginalRuntime):
name='Audio Separator'
def __init__(self,root):super().__init__(root,None)
@@ -19,8 +32,15 @@ class SeparatorRuntime(LTXOriginalRuntime):
return self.root/'missing-python',self.root/'missing-runtime'
def status(self):
python,base=self.paths()
return dict(installed=python.is_file() and (base/'ready').is_file(),version=VERSION,required_disk_gib=15,job=dict(self.job) if self.job else None,models=[dict(id=name,name=label,configuration=config,installed=self.model_ready(name)) for name,(label,config) in MODELS.items()])
def model_ready(self,name):return name in MODELS and (self.root/'models'/(name+'.ready')).is_file() and all((self.root/'models'/f).is_file() and (self.root/'models'/f).stat().st_size>0 for f in (name,MODELS[name][1]))
return dict(installed=python.is_file() and (base/'ready').is_file(),version=VERSION,required_disk_gib=15,job=dict(self.job) if self.job else None,models=[dict(id=name,name=label,configuration=config,source=SOURCES[name],installed=self.model_ready(name)) for name,(label,config) in MODELS.items()])
def model_ready(self,name):
if name not in MODELS:return False
directory=self.root/'models';marker=directory/(name+'.ready')
try:
recorded=json.loads(marker.read_text())
return recorded=={x['file']:x['sha256'] for x in SOURCES[name]['files']} and all((directory/f).is_file() and not (directory/f).is_symlink() and (directory/f).stat().st_size>0 for f in package_files(name))
except (OSError,ValueError):return False
def start(self):
if not shutil.which('ffmpeg'):raise ValueError('FFmpeg fehlt. Deck-Systemvoraussetzungen installieren.')
with self.lock:
@@ -41,9 +61,26 @@ class SeparatorRuntime(LTXOriginalRuntime):
try:
if model:
directory=base;directory.mkdir(mode=0o700)
self._command([self.paths()[0],'-c','from audio_separator.separator import Separator; import sys; Separator(model_file_dir=sys.argv[1],info_only=True).download_model_files(sys.argv[2])',directory,model])
filenames=(model,MODELS[model][1])
if any(not (directory/f).is_file() or (directory/f).stat().st_size==0 for f in filenames):raise ValueError('Modell oder YAML-Konfiguration fehlt.')
import urllib.request
for item in SOURCES[model]['files']:
self._phase('Modellpaket: '+item['file'])
succeeded=False
for url in item['urls']:
try:
digest=hashlib.sha256();size=0
with urllib.request.urlopen(url,timeout=60) as response,(directory/item['file']).open('wb') as out:
while chunk:=response.read(1024**2):
if self.cancel.is_set():raise InterruptedError()
size+=len(chunk)
if size>2*1024**3:raise ValueError('Quelldatei zu groß.')
digest.update(chunk);out.write(chunk)
if digest.hexdigest()!=item['sha256']:raise ValueError('Quelldatei stimmt nicht mit dem geprüften Rezept überein.')
succeeded=True;break
except InterruptedError:raise
except Exception:continue
if not succeeded:raise ValueError('Geprüfte Quelle nicht verfügbar: '+item['file'])
filenames=package_files(model)
if not verify_package(directory,model):raise ValueError('Modellpaket-Prüfsumme stimmt nicht.')
if self.cancel.is_set():raise InterruptedError()
destination=self.root/'models';destination.mkdir(exist_ok=True);hashes={}
for f in filenames: