"""Old Athena separation UI, remote Deck processing; no CUDA or models here.""" import io,json,os,secrets,subprocess,tempfile,time,urllib.request,urllib.error,zipfile from pathlib import Path from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer from email import policy from email.parser import BytesParser BASE='http://127.0.0.1:8108' CONFIG=Path('/data/deck-connection.json') 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'} def request(path,body=None,ctype='application/json',token=None): if token is None:token=json.loads(CONFIG.read_text())['token'] if CONFIG.exists() else '' if not token:raise ValueError('Zuerst die Deck-Verbindung einrichten.') req=urllib.request.Request(BASE+path,data=body,headers={'Authorization':'Bearer '+token,'Content-Type':ctype}) try: with urllib.request.urlopen(req,timeout=90) as response:return response.read() except urllib.error.HTTPError as exc: try:message=json.loads(exc.read()).get('error','Deck-Anfrage abgelehnt') except Exception:message='Deck-Anfrage abgelehnt' raise ValueError(message if isinstance(message,str) else message.get('message','Deck-Anfrage abgelehnt')) from None class Handler(BaseHTTPRequestHandler): def log_message(self,*args):pass def send(self,body,status=200,mime='application/json'): if isinstance(body,dict):body=json.dumps(body).encode() self.send_response(status);self.send_header('Content-Type',mime);self.send_header('Content-Length',str(len(body)));self.send_header('Cache-Control','no-store');self.send_header('X-Content-Type-Options','nosniff');self.send_header('Content-Security-Policy',"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; frame-ancestors 'none'");self.end_headers();self.wfile.write(body) def do_GET(self): if self.path=='/health':return self.send({'status':'ok','connected':CONFIG.exists()}) if self.path=='/':return self.send(Path('/app/index.html').read_bytes(),mime='text/html; charset=utf-8') if self.path=='/api/status': try:return self.send(request('/api/v1/separator-runtime')) except ValueError as exc:return self.send({'detail':str(exc)},400) return self.send({'detail':'Nicht gefunden'},404) def do_POST(self): origin=self.headers.get('Origin');host=self.headers.get('Host','') if self.headers.get('X-Athena-UI')!='1' or origin and origin!='http://'+host:return self.send({'detail':'Same-origin erforderlich'},403) try: length=int(self.headers.get('Content-Length','0')) if self.headers.get('Transfer-Encoding') or not 04096:raise ValueError('Verbindungsdaten zu groß.') value=json.loads(raw);token=value.get('token','') if not isinstance(token,str) or not 1<=len(token)<=256:raise ValueError('Ungültiger Token.') request('/api/v1/separator-runtime',token=token) CONFIG.parent.mkdir(exist_ok=True);tmp=CONFIG.with_suffix('.tmp');tmp.write_text(json.dumps({'token':token}));tmp.chmod(0o600);tmp.replace(CONFIG) return self.send({'ok':True}) if self.path!='/v1/separate':return self.send({'detail':'Nicht gefunden'},404) ctype=self.headers.get('Content-Type','') if not ctype.startswith('multipart/form-data;'):raise ValueError('Audiodatei fehlt.') msg=BytesParser(policy=policy.default).parsebytes(b'Content-Type: '+ctype.encode()+b'\r\n\r\n'+raw) fields={} if not msg.is_multipart() or msg.defects:raise ValueError('Ungültiger Upload.') for part in msg.iter_parts(): name=part.get_param('name',header='content-disposition') if name not in ('file','target') or name in fields:raise ValueError('Ungültiges Upload-Feld.') fields[name]=part.get_payload(decode=True) target=fields.get('target',b'').decode() if target not in TARGETS:raise ValueError('Zielspur noch nicht angebunden.') with tempfile.TemporaryDirectory() as work: source=Path(work)/'input';source.write_bytes(fields['file']);wav=Path(work)/'input.wav' subprocess.run(['ffmpeg','-nostdin','-hide_banner','-loglevel','error','-y','-i',str(source),'-t','1201','-ac','2','-ar','44100','-c:a','pcm_s16le',str(wav)],check=True,timeout=120,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) data=wav.read_bytes();boundary=secrets.token_hex(24) body=b'' for name,value in [('model',TARGETS[target]),('target',target)]:body+=f'--{boundary}\r\nContent-Disposition: form-data; name="{name}"\r\n\r\n{value}\r\n'.encode() body+=f'--{boundary}\r\nContent-Disposition: form-data; name="file"; filename="input.wav"\r\nContent-Type: audio/wav\r\n\r\n'.encode()+data+f'\r\n--{boundary}--\r\n'.encode() job=json.loads(request('/api/v1/separator-jobs/start',body,'multipart/form-data; boundary='+boundary));ident=job['id'];deadline=time.monotonic()+7250 while time.monotonic()