| import torch |
| import numpy as np |
| from fastapi import FastAPI, HTTPException, Request |
| from fastapi.responses import Response |
| import soundfile as sf |
| import io |
| import json |
| import os |
|
|
| app = FastAPI(title="AniTTS Vocoder Server") |
|
|
| |
| codec = None |
|
|
| |
| try: |
| from miocodec import MioCodecModel |
| codec = MioCodecModel.from_pretrained("Aratako/MioCodec-25Hz-24kHz") |
| codec = codec.eval() |
| print("✅ MioCodec loaded (method 1)") |
| except Exception as e: |
| print(f"⚠️ Method 1 failed: {e}") |
|
|
| |
| if codec is None: |
| try: |
| |
| import sys |
| sys.path.append(os.getcwd()) |
| from codec import CodecV6 |
| codec = CodecV6(device="cpu") |
| codec.model.eval() |
| print("✅ CodecV6 loaded (method 2)") |
| except Exception as e: |
| print(f"⚠️ Method 2 failed: {e}") |
|
|
| |
| if codec is None: |
| print("⚠️ Using dummy vocoder - will return beep only") |
| codec = "dummy" |
|
|
| @app.post("/vocoder") |
| async def vocoder_endpoint(request: Request): |
| global codec |
| |
| |
| try: |
| body = await request.body() |
| body_str = body.decode('utf-8') |
| |
| |
| tokens_str = None |
| embedding_str = None |
| |
| for part in body_str.split('&'): |
| if '=' in part: |
| key, val = part.split('=', 1) |
| if key == 'tokens': |
| tokens_str = val |
| elif key == 'embedding': |
| embedding_str = val |
| |
| if tokens_str is None or embedding_str is None: |
| raise HTTPException(status_code=400, detail="Missing tokens or embedding") |
| |
| |
| import urllib.parse |
| tokens_str = urllib.parse.unquote(tokens_str) |
| embedding_str = urllib.parse.unquote(embedding_str) |
| |
| tokens = json.loads(tokens_str) |
| speaker_emb = json.loads(embedding_str) |
| |
| except Exception as e: |
| print(f"Parse error: {e}") |
| raise HTTPException(status_code=400, detail=f"Invalid request: {e}") |
| |
| |
| if codec == "dummy" or codec is None: |
| print(f"⚠️ Dummy vocoder for {len(tokens)} tokens") |
| duration = 0.3 |
| sample_rate = 24000 |
| t = np.linspace(0, duration, int(sample_rate * duration)) |
| beep = 0.3 * np.sin(2 * np.pi * 440 * t) |
| beep = beep * np.hanning(len(beep)) |
| buffer = io.BytesIO() |
| sf.write(buffer, beep, sample_rate, format='wav') |
| buffer.seek(0) |
| return Response(content=buffer.read(), media_type="audio/wav") |
| |
| |
| try: |
| tokens_tensor = torch.tensor(tokens, dtype=torch.long) |
| speaker_emb_tensor = torch.tensor(speaker_emb, dtype=torch.float32) |
| |
| print(f"Processing {len(tokens)} tokens...") |
| |
| with torch.no_grad(): |
| waveform = codec.decode( |
| global_embedding=speaker_emb_tensor, |
| content_token_indices=tokens_tensor |
| ) |
| |
| if torch.is_tensor(waveform): |
| waveform = waveform.cpu().numpy() |
| |
| if waveform.ndim > 1: |
| waveform = waveform.squeeze() |
| |
| |
| max_val = np.abs(waveform).max() |
| if max_val > 0: |
| waveform = waveform / max_val * 0.95 |
| |
| buffer = io.BytesIO() |
| sf.write(buffer, waveform, 24000, format='wav') |
| buffer.seek(0) |
| |
| return Response(content=buffer.read(), media_type="audio/wav") |
| |
| except Exception as e: |
| print(f"Decode error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
| @app.get("/health") |
| async def health(): |
| return {"status": "ok", "codec_available": codec is not None} |
|
|
| if __name__ == "__main__": |
| import uvicorn |
| uvicorn.run(app, host="0.0.0.0", port=7860) |