mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-16 05:10:42 -07:00
- Added support for TTS providers in the backend, including endpoints for listing, starting, stopping, and downloading providers. - Enhanced the release workflow to build and upload TTS provider binaries for both Windows and Linux platforms. - Updated the architecture documentation to reflect the new provider system and its benefits for modularity and user experience. - Introduced a new `ProviderSettings` component in the frontend for managing provider configurations.
239 lines
5.8 KiB
Python
239 lines
5.8 KiB
Python
"""
|
|
Standalone TTS provider server for PyTorch CPU.
|
|
"""
|
|
|
|
import argparse
|
|
import asyncio
|
|
import base64
|
|
import io
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
import numpy as np
|
|
import soundfile as sf
|
|
from fastapi import FastAPI, File, Form, HTTPException, UploadFile
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
import uvicorn
|
|
|
|
# Add parent directory to path to import backend modules
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "backend"))
|
|
|
|
from backend.backends.pytorch_backend import PyTorchTTSBackend
|
|
|
|
|
|
app = FastAPI(title="Voicebox TTS Provider - PyTorch CPU")
|
|
|
|
# CORS middleware
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Global backend instance
|
|
_backend: Optional[PyTorchTTSBackend] = None
|
|
|
|
|
|
def get_backend() -> PyTorchTTSBackend:
|
|
"""Get or create backend instance."""
|
|
global _backend
|
|
if _backend is None:
|
|
_backend = PyTorchTTSBackend()
|
|
return _backend
|
|
|
|
|
|
@app.get("/tts/health")
|
|
async def health():
|
|
"""Health check endpoint."""
|
|
backend = get_backend()
|
|
backend_type = "pytorch-cpu"
|
|
|
|
model_size = None
|
|
if backend.is_loaded():
|
|
if hasattr(backend, '_current_model_size') and backend._current_model_size:
|
|
model_size = backend._current_model_size
|
|
|
|
device = backend.device if hasattr(backend, 'device') else "cpu"
|
|
|
|
return {
|
|
"status": "healthy",
|
|
"provider": backend_type,
|
|
"version": "1.0.0", # TODO: Get from version file
|
|
"model": model_size,
|
|
"device": device,
|
|
}
|
|
|
|
|
|
@app.get("/tts/status")
|
|
async def status():
|
|
"""Model status endpoint."""
|
|
backend = get_backend()
|
|
|
|
model_size = None
|
|
if backend.is_loaded():
|
|
if hasattr(backend, '_current_model_size') and backend._current_model_size:
|
|
model_size = backend._current_model_size
|
|
|
|
available_sizes = ["1.7B", "0.6B"]
|
|
|
|
gpu_available = False
|
|
vram_used_mb = None
|
|
|
|
try:
|
|
import torch
|
|
gpu_available = torch.cuda.is_available()
|
|
if gpu_available:
|
|
vram_used_mb = int(torch.cuda.memory_allocated() / 1024 / 1024)
|
|
except ImportError:
|
|
pass
|
|
|
|
return {
|
|
"model_loaded": backend.is_loaded(),
|
|
"model_size": model_size,
|
|
"available_sizes": available_sizes,
|
|
"gpu_available": gpu_available,
|
|
"vram_used_mb": vram_used_mb,
|
|
}
|
|
|
|
|
|
@app.post("/tts/generate")
|
|
async def generate(
|
|
text: str,
|
|
voice_prompt: dict,
|
|
language: str = "en",
|
|
seed: Optional[int] = None,
|
|
model_size: str = "1.7B",
|
|
):
|
|
"""
|
|
Generate speech from text.
|
|
|
|
Request body (JSON):
|
|
{
|
|
"text": "Hello world!",
|
|
"voice_prompt": {...},
|
|
"language": "en",
|
|
"seed": 12345,
|
|
"model_size": "1.7B"
|
|
}
|
|
"""
|
|
backend = get_backend()
|
|
|
|
# Load model if not loaded or different size
|
|
if not backend.is_loaded() or (
|
|
hasattr(backend, '_current_model_size') and
|
|
backend._current_model_size != model_size
|
|
):
|
|
await backend.load_model_async(model_size)
|
|
|
|
# Generate audio
|
|
audio, sample_rate = await backend.generate(
|
|
text=text,
|
|
voice_prompt=voice_prompt,
|
|
language=language,
|
|
seed=seed,
|
|
instruct=None, # TODO: Add instruct support
|
|
)
|
|
|
|
# Convert to base64
|
|
buffer = io.BytesIO()
|
|
sf.write(buffer, audio, sample_rate, format="WAV")
|
|
buffer.seek(0)
|
|
audio_bytes = buffer.read()
|
|
audio_b64 = base64.b64encode(audio_bytes).decode('utf-8')
|
|
|
|
# Calculate duration
|
|
duration = len(audio) / sample_rate
|
|
|
|
return {
|
|
"audio": audio_b64,
|
|
"sample_rate": sample_rate,
|
|
"duration": duration,
|
|
}
|
|
|
|
|
|
@app.post("/tts/create_voice_prompt")
|
|
async def create_voice_prompt(
|
|
audio: UploadFile = File(...),
|
|
reference_text: str = Form(...),
|
|
use_cache: bool = Form(True),
|
|
):
|
|
"""
|
|
Create voice prompt from reference audio.
|
|
|
|
Request (multipart/form-data):
|
|
- audio: Audio file
|
|
- reference_text: Transcript
|
|
- use_cache: Whether to use cached prompts (default: true)
|
|
"""
|
|
backend = get_backend()
|
|
|
|
# Save uploaded file temporarily
|
|
import tempfile
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_file:
|
|
tmp_path = tmp_file.name
|
|
content = await audio.read()
|
|
tmp_file.write(content)
|
|
|
|
try:
|
|
# Create voice prompt
|
|
voice_prompt, was_cached = await backend.create_voice_prompt(
|
|
audio_path=tmp_path,
|
|
reference_text=reference_text,
|
|
use_cache=use_cache,
|
|
)
|
|
|
|
return {
|
|
"voice_prompt": voice_prompt,
|
|
"was_cached": was_cached,
|
|
}
|
|
finally:
|
|
# Clean up temp file
|
|
Path(tmp_path).unlink(missing_ok=True)
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
parser = argparse.ArgumentParser(description="Voicebox TTS Provider - PyTorch CPU")
|
|
parser.add_argument(
|
|
"--port",
|
|
type=int,
|
|
default=0, # 0 means random port
|
|
help="Port to bind to",
|
|
)
|
|
parser.add_argument(
|
|
"--data-dir",
|
|
type=str,
|
|
default=None,
|
|
help="Data directory for models and cache",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
# Set data directory if provided
|
|
if args.data_dir:
|
|
from backend import config
|
|
config.set_data_dir(args.data_dir)
|
|
|
|
# Determine port
|
|
port = args.port
|
|
if port == 0:
|
|
import socket
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
s.bind(('', 0))
|
|
port = s.getsockname()[1]
|
|
|
|
print(f"Starting TTS Provider (PyTorch CPU) on port {port}")
|
|
|
|
uvicorn.run(
|
|
app,
|
|
host="127.0.0.1",
|
|
port=port,
|
|
log_level="info",
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|