Make Qwen3-TTS the default engine, add CPU-only docker support

- Switch default TTS engine from Piper to Qwen3-TTS (1.7B params)
- Upgrade to Python 3.12
- Add docker-compose.cpu.yml for CPU-only deployments
- Improve GPU configuration with NVIDIA environment variables
- Comment out optional engines (Piper, XTTS, Silero, Kokoro) in requirements
- Update Makefile with local/local-cpu targets and venv support
- Simplify voice_to_speaker.default.yaml for Qwen3-TTS voices
- Update docs/MODELS.md with Qwen3-TTS documentation
- Add git commit guidelines to CLAUDE.md
This commit is contained in:
russell@unturf.com 2026-01-26 10:40:29 -05:00
parent 4a019cf897
commit b315659be6
9 changed files with 572 additions and 951 deletions

View file

@ -79,7 +79,13 @@ owned_by = "UncloseAI"
**Never view or log secrets** - source them and use them. **Never view or log secrets** - source them and use them.
### 4. Documentation Requirements ### 4. Git Commit Guidelines
- **Never add AI attribution** - Do not use `Co-Authored-By: Claude` or similar in commit messages
- Write clear, concise commit messages describing what changed and why
- Use imperative mood ("Add feature" not "Added feature")
### 5. Documentation Requirements
When adding features, update ALL relevant docs: When adding features, update ALL relevant docs:
- `Makefile` help text - `Makefile` help text

View file

@ -1,4 +1,4 @@
FROM python:3.11-slim FROM python:3.12-slim
RUN --mount=type=cache,target=/root/.cache/pip pip install -U pip RUN --mount=type=cache,target=/root/.cache/pip pip install -U pip

145
Makefile
View file

@ -14,41 +14,48 @@ REMOTE_USER ?= $(USER)
REMOTE_PATH ?= ~/uncloseai-speech REMOTE_PATH ?= ~/uncloseai-speech
CONTAINER_NAME ?= uncloseai-speech-server-1 CONTAINER_NAME ?= uncloseai-speech-server-1
.PHONY: help deploy sync restart logs test clean stop start voices voices-piper voices-xtts voices-kokoro test-kokoro voices-silero test-silero voices-chatterbox test-chatterbox push-all hydrate load-test .PHONY: help deploy sync restart logs test clean stop start voices voices-qwen voices-piper voices-xtts voices-kokoro test-kokoro voices-silero test-silero voices-chatterbox test-chatterbox push-all hydrate load-test test-qwen venv venv-run local local-cpu
help: help:
@echo "🦝 Raccoon TTS Mission - Development Commands" @echo "🦝 Raccoon TTS Mission - Development Commands"
@echo "" @echo ""
@echo "Deployment:" @echo "Quick Start (Docker with GPU):"
@echo " make local - Build and run locally with GPU support"
@echo " make local-cpu - Build and run locally (CPU only, slower)"
@echo " make test - Test Qwen3-TTS endpoint"
@echo " make logs - Tail container logs"
@echo ""
@echo "Quick Start (No Docker):"
@echo " make venv - Create Python virtual environment"
@echo " make venv-run - Run server in virtual environment"
@echo ""
@echo "Remote Deployment:"
@echo " make deploy - Full deploy: sync files, restart container" @echo " make deploy - Full deploy: sync files, restart container"
@echo " make sync - Sync local files to remote server" @echo " make sync - Sync local files to remote server"
@echo " make restart - Restart the Docker container" @echo " make restart - Restart the Docker container"
@echo "" @echo ""
@echo "Development:"
@echo " make logs - Tail container logs"
@echo " make test - Test TTS endpoint (Piper)"
@echo " make test-xtts - Test XTTS HD endpoint"
@echo " make voices - Download all voices (Piper + XTTS)"
@echo " make voices-piper - Download Piper voices only"
@echo " make voices-xtts - Download XTTS voices and samples"
@echo " make voices-kokoro - Download Kokoro models"
@echo " make test-kokoro - Test Kokoro fast TTS"
@echo " make voices-silero - Download Silero models (en, ru, de, es, fr)"
@echo " make test-silero - Test Silero TTS endpoint"
@echo " make voices-chatterbox - Download Chatterbox models"
@echo " make test-chatterbox - Test Chatterbox TTS with emotion control"
@echo ""
@echo "Testing:" @echo "Testing:"
@echo " make hydrate - Hydrate all models by testing ALL 227 voices" @echo " make test - Test TTS endpoint (Qwen3-TTS)"
@echo " make load-test - Load test with concurrent random voice requests" @echo " make test-qwen - Test Qwen3-TTS voice cloning"
@echo " make hydrate - Test all configured voices"
@echo " make load-test - Concurrent load test"
@echo "" @echo ""
@echo "Container:" @echo "Other Engines (disabled by default):"
@echo " make test-xtts - Test XTTS HD endpoint"
@echo " make test-kokoro - Test Kokoro fast TTS"
@echo " make test-silero - Test Silero TTS endpoint"
@echo " make voices-piper - Download Piper voices"
@echo " make voices-xtts - Download XTTS voices"
@echo " make voices-kokoro - Download Kokoro models"
@echo " make voices-silero - Download Silero models"
@echo ""
@echo "Container Management:"
@echo " make start - Start Docker container" @echo " make start - Start Docker container"
@echo " make stop - Stop Docker container" @echo " make stop - Stop Docker container"
@echo " make clean - Stop and remove container" @echo " make clean - Stop and remove container"
@echo "" @echo ""
@echo "Git:" @echo "Git:"
@echo " make push-all - Push to all git remotes (origin + github)" @echo " make push-all - Push to all git remotes"
sync: sync:
@echo "📦 Syncing files to $(REMOTE_HOST)..." @echo "📦 Syncing files to $(REMOTE_HOST)..."
@ -61,6 +68,78 @@ sync:
deploy: sync restart deploy: sync restart
@echo "✅ Deployment complete!" @echo "✅ Deployment complete!"
# ============================================================================
# Local Development (Docker)
# ============================================================================
local:
@echo "🐳 Building and running with GPU support..."
@[ -f speech.env ] || cp sample.env speech.env
docker compose up -d --build
@echo ""
@echo "✅ Container started! Qwen3-TTS model will download on first request (~3.4GB)"
@echo " Test with: make test"
@echo " View logs: make logs"
local-cpu:
@echo "🐳 Building and running (CPU only - slower inference)..."
@[ -f speech.env ] || cp sample.env speech.env
docker compose -f docker-compose.cpu.yml up -d --build
@echo ""
@echo "✅ Container started (CPU mode)!"
@echo " Note: Qwen3-TTS is ~10x slower on CPU"
@echo " Test with: make test"
# ============================================================================
# Local Development (Python venv - no Docker)
# ============================================================================
VENV_DIR := .venv
PYTHON := python3
venv:
@echo "🐍 Creating Python virtual environment..."
@if [ ! -d "$(VENV_DIR)" ]; then \
$(PYTHON) -m venv $(VENV_DIR); \
echo "✅ Virtual environment created at $(VENV_DIR)"; \
else \
echo " Virtual environment already exists at $(VENV_DIR)"; \
fi
@echo ""
@echo "📦 Installing dependencies..."
$(VENV_DIR)/bin/pip install --upgrade pip
$(VENV_DIR)/bin/pip install -r requirements.txt
@echo ""
@echo "✅ Setup complete!"
@echo ""
@echo "To activate manually:"
@echo " source $(VENV_DIR)/bin/activate"
@echo ""
@echo "To run the server:"
@echo " make venv-run"
@echo ""
@echo "Or run directly:"
@echo " $(VENV_DIR)/bin/python speech.py"
venv-run:
@echo "🚀 Starting uncloseai-speech server..."
@if [ ! -d "$(VENV_DIR)" ]; then \
echo "❌ Virtual environment not found. Run 'make venv' first."; \
exit 1; \
fi
@[ -d "config" ] || mkdir -p config
@[ -d "voices" ] || mkdir -p voices
@echo ""
@echo "Server starting on http://localhost:8000"
@echo "Qwen3-TTS model will download on first request (~3.4GB)"
@echo ""
$(VENV_DIR)/bin/python speech.py
venv-clean:
@echo "🧹 Removing virtual environment..."
rm -rf $(VENV_DIR)
@echo "✅ Virtual environment removed"
restart: restart:
@echo "🔄 Rebuilding and restarting container on $(REMOTE_HOST)..." @echo "🔄 Rebuilding and restarting container on $(REMOTE_HOST)..."
ssh $(REMOTE_USER)@$(REMOTE_HOST) "cd $(REMOTE_PATH) && docker compose up -d --build" ssh $(REMOTE_USER)@$(REMOTE_HOST) "cd $(REMOTE_PATH) && docker compose up -d --build"
@ -81,17 +160,29 @@ logs:
@echo "📋 Tailing logs from $(REMOTE_HOST)..." @echo "📋 Tailing logs from $(REMOTE_HOST)..."
ssh $(REMOTE_USER)@$(REMOTE_HOST) "docker logs -f $(CONTAINER_NAME)" ssh $(REMOTE_USER)@$(REMOTE_HOST) "docker logs -f $(CONTAINER_NAME)"
test: test: test-qwen
@echo "🧪 Testing TTS endpoint..."
test-qwen:
@echo "🧪 Testing Qwen3-TTS endpoint (default model)..."
curl -X POST http://$(REMOTE_HOST):8000/v1/audio/speech \ curl -X POST http://$(REMOTE_HOST):8000/v1/audio/speech \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"model":"tts-1","voice":"alloy","input":"Raccoon mission TTS test"}' \ -d '{"model":"tts-1-qwen","voice":"alloy","input":"Raccoon mission TTS test with Qwen three"}' \
-o /tmp/raccoon_test.mp3 -o /tmp/qwen_test.mp3
@echo "✅ Test complete! Playing audio..." @echo "✅ Test complete! Playing audio..."
@firefox /tmp/raccoon_test.mp3 || mpv /tmp/raccoon_test.mp3 || echo "Install firefox or mpv to play audio" @firefox /tmp/qwen_test.mp3 || mpv /tmp/qwen_test.mp3 || echo "Install firefox or mpv to play audio"
voices: voices-piper voices-xtts voices-silero voices: voices-qwen
@echo "✅ All voices downloaded (Piper, XTTS, Silero)!" @echo "✅ Qwen3-TTS ready (model downloads automatically on first use)"
voices-qwen:
@echo "🎤 Qwen3-TTS models download automatically on first use"
@echo " Model: Qwen/Qwen3-TTS-12Hz-1.7B-Base (~3.4GB)"
@echo " The model will be cached in /app/voices/hub/"
@echo ""
@echo "To pre-download, run: make test-qwen"
voices-all: voices-qwen voices-piper voices-xtts voices-silero
@echo "✅ All voices downloaded (Qwen, Piper, XTTS, Silero)!"
voices-piper: voices-piper:
@echo "🎤 Downloading all Piper voices..." @echo "🎤 Downloading all Piper voices..."

18
docker-compose.cpu.yml Normal file
View file

@ -0,0 +1,18 @@
# CPU-only configuration (no GPU)
# Use this if you don't have an NVIDIA GPU
# Note: Qwen3-TTS is ~10x slower on CPU
services:
server:
build:
dockerfile: Dockerfile
image: uncloseai-speech:local
env_file: speech.env
ports:
- "8000:8000"
volumes:
- ./voices:/app/voices
- ./config:/app/config
environment:
- CUDA_VISIBLE_DEVICES= # Disable CUDA
restart: unless-stopped

View file

@ -1,3 +1,7 @@
# GPU configuration (NVIDIA CUDA)
# Requires: nvidia-container-toolkit installed on host
# Install: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html
services: services:
server: server:
build: build:
@ -9,13 +13,16 @@ services:
volumes: volumes:
- ./voices:/app/voices - ./voices:/app/voices
- ./config:/app/config - ./config:/app/config
# To install as a service environment:
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
restart: unless-stopped restart: unless-stopped
deploy: deploy:
resources: resources:
reservations: reservations:
devices: devices:
- driver: nvidia - driver: nvidia
#device_ids: ['0', '1'] # Select a gpu, or # Uncomment to select specific GPU(s):
# device_ids: ['0']
count: all count: all
capabilities: [gpu] capabilities: [gpu]

View file

@ -2,6 +2,22 @@
**Raccoon Mission:** Rescue abandoned open-source TTS models and integrate them into uncloseai-speech **Raccoon Mission:** Rescue abandoned open-source TTS models and integrate them into uncloseai-speech
## Default Model: Qwen3-TTS
**🎯 Qwen3-TTS is now the default and only enabled model.**
All other models (Piper, XTTS, Silero, Kokoro) are disabled by default. To enable them, uncomment their sections in `voice_to_speaker.yaml`.
### Quick Start
```bash
# Test Qwen3-TTS (default)
make test
# The model downloads automatically on first use (~3.4GB)
```
---
## Documentation Index ## Documentation Index
### Comprehensive Research ### Comprehensive Research
@ -33,7 +49,83 @@ Each model has detailed documentation covering technical specs, integration stat
## Currently Integrated ## Currently Integrated
### 1. Piper TTS ✅ ### 0. Qwen3-TTS ✅ (DEFAULT)
**Status:** INTEGRATED as tts-1-qwen (DEFAULT MODEL)
**Project:** Qwen/Qwen3-TTS (Alibaba, actively maintained)
**License:** Apache 2.0
**Model:** Qwen3-TTS-12Hz-1.7B-Base
**Why Default:**
- State-of-the-art quality with 1.7B parameters
- Extremely low latency (97ms first packet)
- Voice cloning from 3-second samples
- 10 languages: Chinese, English, Japanese, Korean, German, French, Russian, Portuguese, Spanish, Italian
- Apache 2.0 license (commercial-friendly)
- Actively maintained by Alibaba
**Features:**
- Universal end-to-end architecture (no cascading errors)
- 12Hz acoustic tokenizer for efficient compression
- Dual-track streaming/non-streaming generation
- High-fidelity speech reconstruction
- Natural language instruction control
- Supports both GPU and CPU inference
**Model Specs:**
- Parameters: 1.7B
- Sample Rate: ~24kHz
- Input: Text + Reference Audio (3+ seconds)
- Languages: 10 (zh, en, ja, ko, de, fr, ru, pt, es, it)
- Size: ~3.4GB
**Model Source:**
- HuggingFace: `Qwen/Qwen3-TTS-12Hz-1.7B-Base`
- Auto-downloaded on first use via huggingface-hub
- Cached in `/app/voices/hub/`
**Integration:**
- Used for `tts-1-qwen` model (default)
- Voice cloning with reference audio + transcript
- Pre-configured with Qwen's demo voice
**Example Config:**
```yaml
tts-1-qwen:
alloy:
ref_audio: https://example.com/reference.wav
ref_text: "The exact text spoken in the reference audio"
language: English
```
**Custom Voice Setup:**
1. Record 3+ seconds of clear speech
2. Transcribe the audio exactly
3. Add to `voice_to_speaker.yaml`:
```yaml
tts-1-qwen:
my_voice:
ref_audio: voices/my_voice_sample.wav
ref_text: "Hello, this is my voice sample for cloning."
language: English
```
**Makefile Targets:**
```bash
make test # Test Qwen3-TTS (default)
make test-qwen # Test Qwen3-TTS explicitly
```
**Hardware Requirements:**
- GPU: NVIDIA with 8GB+ VRAM (recommended)
- CPU: Works but slower (~10x)
- FlashAttention 2 recommended for lower memory
**Raccoon Priority:** ⭐⭐⭐⭐⭐ (State-of-the-art, actively maintained, Apache 2.0)
---
### 1. Piper TTS (disabled by default) ✅
> 📖 **See [detailed documentation](models/piper-tts.md)** for comprehensive technical specs and integration guide > 📖 **See [detailed documentation](models/piper-tts.md)** for comprehensive technical specs and integration guide
@ -505,8 +597,8 @@ The following models have detailed documentation but are not yet integrated or p
--- ---
**Last Updated:** 2025-11-09 **Last Updated:** 2026-01-26
**Raccoon Status:** 🦝 4 models rescued! Silero and Kokoro TTS integrated successfully **Raccoon Status:** 🦝 5 models rescued! Qwen3-TTS is now the default model
**Integration Status:** ✅ Piper (55 voices), XTTS (8 voices), Silero (148 voices), Kokoro (34 voices) | 🎯 Next: Chatterbox, StyleTTS2 **Integration Status:** ✅ Qwen3-TTS (default, unlimited voices via cloning) | Disabled: Piper (55), XTTS (8), Silero (148), Kokoro (34)
**API Endpoints:** tts-1, tts-1-hd, tts-1-silero, tts-1-kokoro | /v1/models for discovery **API Endpoints:** tts-1-qwen (default) | Others available: tts-1, tts-1-hd, tts-1-silero, tts-1-kokoro
**Documentation Status:** 📚 10 models fully documented, 1 comprehensive research overview **Documentation Status:** 📚 11 models fully documented, 1 comprehensive research overview

View file

@ -1,28 +1,31 @@
fastapi fastapi
uvicorn uvicorn
loguru loguru
# Qwen3-TTS - state-of-the-art TTS with voice cloning (Apache 2.0)
# 1.7B params, 10 languages, 97ms latency, 12Hz tokenizer
qwen-tts>=0.1.0
# OHF-Voice fork doesn't have installable Python package yet # OHF-Voice fork doesn't have installable Python package yet
# Stick with PyPI piper-tts but use absolute paths in config # Stick with PyPI piper-tts but use absolute paths in config
piper-tts>=1.2.0 # piper-tts>=1.2.0
# 🦝 RACCOON TODO: Create our own PyPI package from OHF-Voice fork # 🦝 RACCOON TODO: Create our own PyPI package from OHF-Voice fork
# git+https://github.com/OHF-Voice/piper1-gpl.git@v1.3.0#subdirectory=src/python_run # git+https://github.com/OHF-Voice/piper1-gpl.git@v1.3.0#subdirectory=src/python_run
coqui-tts[languages] # coqui-tts[languages]
# Silero TTS - actively maintained, small efficient models # Silero TTS - actively maintained, small efficient models
# Note: Silero models are loaded via torch.hub, no package install needed # Note: Silero models are loaded via torch.hub, no package install needed
# Models: ~50-100MB each, CPU-friendly, real-time capable # Models: ~50-100MB each, CPU-friendly, real-time capable
omegaconf # Required by Silero TTS # omegaconf # Required by Silero TTS
# Chatterbox - emotion control, 23 languages (Resemble AI) # Chatterbox - emotion control, 23 languages (Resemble AI)
# Install from git since no PyPI package exists yet # Install from git since no PyPI package exists yet
# 🦝 RACCOON NOTE: Disabled due to dependency conflict with Coqui TTS # 🦝 RACCOON NOTE: Disabled due to dependency conflict with Coqui TTS
# gradio 5.44.1 requires typer<1.0 and >=0.12, but spacy 3.6.x requires typer<0.10.0 # gradio 5.44.1 requires typer<1.0 and >=0.12, but spacy 3.6.x requires typer<0.10.0
# TODO: Test Chatterbox in isolated environment or wait for dependency updates # TODO: Test Chatterbox in isolated environment or wait for dependency updates
# git+https://github.com/resemble-ai/chatterbox.git # git+https://github.com/resemble-ai/chatterbox.git
langdetect # langdetect
pyyaml pyyaml
# Kokoro TTS - fast decoder-only architecture # Kokoro TTS - fast decoder-only architecture
# Lightweight decoder-only TTS, 82M params, 24kHz output # Lightweight decoder-only TTS, 82M params, 24kHz output
kokoro>=0.9.2 # kokoro>=0.9.2
soundfile # Required by Kokoro for audio output soundfile # Required by Qwen3-TTS and Kokoro for audio output
transformers>=4.35.0 transformers>=4.35.0
# Hugging Face Hub for model downloads # Hugging Face Hub for model downloads
huggingface-hub[cli] huggingface-hub[cli]

267
speech.py
View file

@ -38,6 +38,20 @@ except ImportError:
split_sentence = None split_sentence = None
detect = None detect = None
# Try to import Qwen3-TTS dependencies
try:
import torch
from qwen_tts import Qwen3TTSModel
QWEN_TTS_AVAILABLE = True
except ImportError:
QWEN_TTS_AVAILABLE = False
if torch is None:
try:
import torch
except ImportError:
torch = None
Qwen3TTSModel = None
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def lifespan(app): async def lifespan(app):
# Startup: Initialize voice caches in each worker process # Startup: Initialize voice caches in each worker process
@ -89,6 +103,10 @@ async def lifespan(app):
model_info["engine"] = "kokoro" model_info["engine"] = "kokoro"
model_info["description"] = "Lightweight decoder-only TTS (82M params)" model_info["description"] = "Lightweight decoder-only TTS (82M params)"
model_info["sample_rate"] = 24000 model_info["sample_rate"] = 24000
elif model_id == 'tts-1-qwen':
model_info["engine"] = "qwen3-tts"
model_info["description"] = "State-of-the-art TTS with voice cloning (1.7B params, 10 languages)"
model_info["sample_rate"] = 24000
models_data.append(model_info) models_data.append(model_info)
@ -125,6 +143,8 @@ silero_model = None
silero_speakers = {} silero_speakers = {}
kokoro_pipeline = None kokoro_pipeline = None
kokoro_lang = None kokoro_lang = None
qwen_model = None
qwen_voice_prompts = {} # Cache for voice clone prompts
# Default args for worker processes (will be overridden in __main__) # Default args for worker processes (will be overridden in __main__)
class DefaultArgs: class DefaultArgs:
@ -159,6 +179,7 @@ voices_cache = None
# Semaphores to limit concurrent model loading (prevent thread pool exhaustion) # Semaphores to limit concurrent model loading (prevent thread pool exhaustion)
silero_load_semaphore = asyncio.Semaphore(1) # Only one Silero model load at a time silero_load_semaphore = asyncio.Semaphore(1) # Only one Silero model load at a time
kokoro_load_semaphore = asyncio.Semaphore(1) # Only one Kokoro model load at a time kokoro_load_semaphore = asyncio.Semaphore(1) # Only one Kokoro model load at a time
qwen_load_semaphore = asyncio.Semaphore(1) # Only one Qwen model load at a time
def unload_model(): def unload_model():
import torch, gc import torch, gc
@ -350,6 +371,126 @@ class kokoro_wrapper():
logger.error(f"Kokoro TTS generation failed: {e}") logger.error(f"Kokoro TTS generation failed: {e}")
raise raise
class qwen3_wrapper():
"""Wrapper for Qwen3-TTS model
Qwen3-TTS is a state-of-the-art TTS with voice cloning:
- 1.7B parameters, 12Hz tokenizer
- 10 languages: zh, en, ja, ko, de, fr, ru, pt, es, it
- 97ms first-packet latency
- 3-second rapid voice cloning
Output: Variable sample rate (typically 24kHz)
"""
def __init__(self, model_name='Qwen/Qwen3-TTS-12Hz-1.7B-Base', device='cuda', dtype=None):
self.model_name = model_name
self.device = device
self.sample_rate = None # Set after first generation
self.voice_prompts = {} # Cache for reusable voice clone prompts
logger.info(f"Loading Qwen3-TTS model '{model_name}' on device '{device}'")
try:
import torch
from qwen_tts import Qwen3TTSModel
# Determine dtype
if dtype is None:
if device == 'cuda' and torch.cuda.is_available():
dtype = torch.bfloat16
else:
dtype = torch.float32
# Try to use flash attention if available
try:
self.model = Qwen3TTSModel.from_pretrained(
model_name,
device_map=device,
dtype=dtype,
attn_implementation="flash_attention_2",
)
logger.info(f"Loaded Qwen3-TTS with FlashAttention 2")
except Exception as fa_error:
logger.warning(f"FlashAttention 2 not available ({fa_error}), using default attention")
self.model = Qwen3TTSModel.from_pretrained(
model_name,
device_map=device,
dtype=dtype,
)
logger.info(f"Successfully loaded Qwen3-TTS model on {device}")
except Exception as e:
logger.error(f"Failed to load Qwen3-TTS model: {e}")
raise
def create_voice_prompt(self, ref_audio, ref_text, x_vector_only_mode=False):
"""Create a reusable voice clone prompt from reference audio/text.
Args:
ref_audio: Path to reference audio file, URL, or (numpy_array, sample_rate) tuple
ref_text: Text spoken in the reference audio
x_vector_only_mode: If True, use only speaker embedding (faster but lower quality)
Returns:
Voice clone prompt items for reuse
"""
logger.info(f"Creating voice clone prompt from ref_audio={ref_audio}, ref_text={ref_text[:50]}...")
return self.model.create_voice_clone_prompt(
ref_audio=ref_audio,
ref_text=ref_text,
x_vector_only_mode=x_vector_only_mode,
)
def tts(self, text, language='English', ref_audio=None, ref_text=None, voice_prompt=None):
"""Generate speech from text using voice cloning.
Args:
text: Text to synthesize (string or list of strings)
language: Language of the text
ref_audio: Path/URL to reference audio (if voice_prompt not provided)
ref_text: Text in reference audio (if voice_prompt not provided)
voice_prompt: Pre-computed voice clone prompt (for efficiency)
Returns:
Audio data as bytes (float32 PCM)
"""
import numpy as np
logger.info(f"Qwen3-TTS generating: text length={len(text)}, language={language}")
try:
if voice_prompt is not None:
# Use pre-computed voice prompt
wavs, sr = self.model.generate_voice_clone(
text=text,
language=language,
voice_clone_prompt=voice_prompt,
)
elif ref_audio is not None and ref_text is not None:
# Generate with inline reference
wavs, sr = self.model.generate_voice_clone(
text=text,
language=language,
ref_audio=ref_audio,
ref_text=ref_text,
)
else:
raise ValueError("Either voice_prompt or (ref_audio + ref_text) must be provided")
self.sample_rate = sr
logger.info(f"Qwen3-TTS generated {len(wavs)} audio segment(s) at {sr}Hz")
# Concatenate all wav segments and convert to bytes
if len(wavs) > 1:
full_audio = np.concatenate(wavs)
else:
full_audio = wavs[0]
return full_audio.astype(np.float32).tobytes()
except Exception as e:
logger.error(f"Qwen3-TTS generation failed: {e}")
raise
def default_exists(filename: str): def default_exists(filename: str):
if not os.path.exists(filename): if not os.path.exists(filename):
fpath, ext = os.path.splitext(filename) fpath, ext = os.path.splitext(filename)
@ -462,33 +603,41 @@ def build_ffmpeg_args(response_format, input_format, sample_rate):
async def list_models(): async def list_models():
"""List all available TTS models (OpenAI-compatible format)""" """List all available TTS models (OpenAI-compatible format)"""
# Return minimal OpenAI-compatible model list (no extra fields) # Return minimal OpenAI-compatible model list (no extra fields)
# Only tts-1-qwen is enabled by default
return { return {
"object": "list", "object": "list",
"data": [ "data": [
{ {
"id": "tts-1", "id": "tts-1-qwen",
"object": "model",
"created": 1700000000,
"owned_by": "uncloseai"
},
{
"id": "tts-1-hd",
"object": "model",
"created": 1700000000,
"owned_by": "uncloseai"
},
{
"id": "tts-1-silero",
"object": "model",
"created": 1700000000,
"owned_by": "uncloseai"
},
{
"id": "tts-1-kokoro",
"object": "model", "object": "model",
"created": 1700000000, "created": 1700000000,
"owned_by": "uncloseai" "owned_by": "uncloseai"
} }
# Other models disabled by default:
# {
# "id": "tts-1",
# "object": "model",
# "created": 1700000000,
# "owned_by": "uncloseai"
# },
# {
# "id": "tts-1-hd",
# "object": "model",
# "created": 1700000000,
# "owned_by": "uncloseai"
# },
# {
# "id": "tts-1-silero",
# "object": "model",
# "created": 1700000000,
# "owned_by": "uncloseai"
# },
# {
# "id": "tts-1-kokoro",
# "object": "model",
# "created": 1700000000,
# "owned_by": "uncloseai"
# }
] ]
} }
@ -542,6 +691,10 @@ async def list_voices():
model_info["engine"] = "kokoro" model_info["engine"] = "kokoro"
model_info["description"] = "Lightweight decoder-only TTS (82M params)" model_info["description"] = "Lightweight decoder-only TTS (82M params)"
model_info["sample_rate"] = 24000 model_info["sample_rate"] = 24000
elif model_id == 'tts-1-qwen':
model_info["engine"] = "qwen3-tts"
model_info["description"] = "State-of-the-art TTS with voice cloning (1.7B params, 10 languages)"
model_info["sample_rate"] = 24000
models_data.append(model_info) models_data.append(model_info)
@ -597,6 +750,8 @@ async def generate_speech(request: GenerateSpeechRequest):
media_type = "audio/pcm;rate=48000" media_type = "audio/pcm;rate=48000"
elif model == 'tts-1-kokoro': # kokoro elif model == 'tts-1-kokoro': # kokoro
media_type = "audio/pcm;rate=24000" media_type = "audio/pcm;rate=24000"
elif model == 'tts-1-qwen': # qwen3-tts
media_type = "audio/pcm;rate=24000"
else: else:
raise BadRequestError(f"Invalid response_format: '{response_format}'", param='response_format') raise BadRequestError(f"Invalid response_format: '{response_format}'", param='response_format')
@ -912,9 +1067,70 @@ async def generate_speech(request: GenerateSpeechRequest):
out_writer_worker = threading.Thread(target=out_writer, daemon=True) out_writer_worker = threading.Thread(target=out_writer, daemon=True)
out_writer_worker.start() out_writer_worker.start()
return StreamingResponse(content=ffmpeg_proc.stdout, media_type=media_type)
# Use Qwen3-TTS for tts-1-qwen
elif model == 'tts-1-qwen':
global qwen_model, qwen_voice_prompts
if not QWEN_TTS_AVAILABLE:
raise ServiceUnavailableError("Qwen3-TTS is not available. Install with: pip install qwen-tts")
voice_map = map_voice_to_speaker(voice, 'tts-1-qwen')
ref_audio = voice_map.get('ref_audio')
ref_text = voice_map.get('ref_text')
language = voice_map.get('language', 'English')
# Load Qwen model if not already loaded
if qwen_model is None:
async with qwen_load_semaphore:
if qwen_model is None:
device = args.xtts_device if args.xtts_device != 'none' else 'cpu'
logger.info(f"Loading Qwen3-TTS model on device '{device}'")
qwen_model = await asyncio.to_thread(
qwen3_wrapper,
model_name='Qwen/Qwen3-TTS-12Hz-1.7B-Base',
device=device
)
# Create or retrieve cached voice prompt
voice_prompt = None
if ref_audio and ref_text:
cache_key = f"{voice}_{ref_audio}"
if cache_key not in qwen_voice_prompts:
logger.info(f"Creating voice prompt for '{voice}'")
qwen_voice_prompts[cache_key] = await asyncio.to_thread(
qwen_model.create_voice_prompt,
ref_audio=ref_audio,
ref_text=ref_text
)
voice_prompt = qwen_voice_prompts[cache_key]
else:
raise BadRequestError(f"Voice '{voice}' requires ref_audio and ref_text configuration", param='voice')
# Generate audio
audio_data = await asyncio.to_thread(
qwen_model.tts,
text=input_text,
language=language,
voice_prompt=voice_prompt
)
# Qwen outputs float32 PCM at ~24kHz (sample rate from model)
sample_rate = str(qwen_model.sample_rate or 24000)
ffmpeg_args = build_ffmpeg_args(response_format, input_format="f32le", sample_rate=sample_rate)
# Apply speed adjustment if needed
if speed != 1.0:
ffmpeg_args.extend(["-af", f"atempo={speed}"])
ffmpeg_args.extend(["-"])
ffmpeg_proc = subprocess.Popen(ffmpeg_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
ffmpeg_proc.stdin.write(audio_data)
ffmpeg_proc.stdin.close()
return StreamingResponse(content=ffmpeg_proc.stdout, media_type=media_type) return StreamingResponse(content=ffmpeg_proc.stdout, media_type=media_type)
else: else:
raise BadRequestError("No such model, must be tts-1, tts-1-hd, tts-1-silero, or tts-1-kokoro.", param='model') raise BadRequestError("No such model, must be tts-1-qwen (default), tts-1, tts-1-hd, tts-1-silero, or tts-1-kokoro.", param='model')
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
@ -941,10 +1157,13 @@ if __name__ == "__main__":
elif args.preload: elif args.preload:
xtts = xtts_wrapper(args.preload, device=args.xtts_device, unload_timer=args.unload_timer) xtts = xtts_wrapper(args.preload, device=args.xtts_device, unload_timer=args.unload_timer)
app.register_model('tts-1') # Register only Qwen by default (other models disabled)
app.register_model('tts-1-hd') app.register_model('tts-1-qwen')
app.register_model('tts-1-silero') # To enable other models, uncomment below:
app.register_model('tts-1-kokoro') # app.register_model('tts-1')
# app.register_model('tts-1-hd')
# app.register_model('tts-1-silero')
# app.register_model('tts-1-kokoro')
# Use multiple workers for true concurrency (each worker = separate process with own GIL) # Use multiple workers for true concurrency (each worker = separate process with own GIL)
# This prevents thread pool exhaustion and allows concurrent model loading # This prevents thread pool exhaustion and allows concurrent model loading

View file

@ -1,892 +1,77 @@
tts-1: # uncloseai-speech Voice Configuration
# OpenAI-compatible voice aliases (backward compatibility) # Only tts-1-qwen is enabled by default
alloy:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 79 # 64, 79, 80, 101, 130
echo:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 134 # 52, 102, 134
fable:
model: /app/voices/en/en_GB/northern_english_male/medium/en_GB-northern_english_male-medium.onnx
speaker: # default speaker
onyx:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 159 # 55, 90, 132, 136, 137, 159
nova:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 107 # 57, 61, 107, 150, 162
shimmer:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 163
# English US voices - all available Piper models tts-1-qwen:
# libritts_r has 904 speakers (multi-speaker model) # OpenAI-compatible voice aliases
en_us_libritts_r_0: # Each voice requires ref_audio (reference audio) and ref_text (transcript of the audio)
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx # Language: Chinese, English, Japanese, Korean, German, French, Russian, Portuguese, Spanish, Italian
speaker: 0
en_us_libritts_r_52:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 52
en_us_libritts_r_55:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 55
en_us_libritts_r_57:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 57
en_us_libritts_r_61:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 61
en_us_libritts_r_64:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 64
en_us_libritts_r_79:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 79
en_us_libritts_r_80:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 80
en_us_libritts_r_90:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 90
en_us_libritts_r_101:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 101
en_us_libritts_r_102:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 102
en_us_libritts_r_107:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 107
en_us_libritts_r_130:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 130
en_us_libritts_r_132:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 132
en_us_libritts_r_134:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 134
en_us_libritts_r_136:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 136
en_us_libritts_r_137:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 137
en_us_libritts_r_150:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 150
en_us_libritts_r_159:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 159
en_us_libritts_r_162:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 162
en_us_libritts_r_163:
model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
speaker: 163
# Single-speaker Piper voices (to be downloaded) # Default voice - using Qwen's example clone audio
en_us_amy: alloy:
model: /app/voices/en/en_US/amy/medium/en_US-amy-medium.onnx ref_audio: https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/clone.wav
speaker: # default ref_text: "Okay. Yeah. I resent you. I love you. I respect you. But you know what? You blew it!"
en_us_arctic: language: English
model: /app/voices/en/en_US/arctic/medium/en_US-arctic-medium.onnx
speaker: # default
en_us_bryce:
model: /app/voices/en/en_US/bryce/medium/en_US-bryce-medium.onnx
speaker: # default
en_us_danny:
model: /app/voices/en/en_US/danny/low/en_US-danny-low.onnx
speaker: # default
en_us_hfc_female:
model: /app/voices/en/en_US/hfc_female/medium/en_US-hfc_female-medium.onnx
speaker: # default
en_us_hfc_male:
model: /app/voices/en/en_US/hfc_male/medium/en_US-hfc_male-medium.onnx
speaker: # default
en_us_joe:
model: /app/voices/en/en_US/joe/medium/en_US-joe-medium.onnx
speaker: # default
en_us_john:
model: /app/voices/en/en_US/john/medium/en_US-john-medium.onnx
speaker: # default
en_us_kathleen:
model: /app/voices/en/en_US/kathleen/low/en_US-kathleen-low.onnx
speaker: # default
en_us_kristin:
model: /app/voices/en/en_US/kristin/medium/en_US-kristin-medium.onnx
speaker: # default
en_us_kusal:
model: /app/voices/en/en_US/kusal/medium/en_US-kusal-medium.onnx
speaker: # default
en_us_l2arctic:
model: /app/voices/en/en_US/l2arctic/medium/en_US-l2arctic-medium.onnx
speaker: # default (multi-speaker model)
en_us_lessac:
model: /app/voices/en/en_US/lessac/medium/en_US-lessac-medium.onnx
speaker: # default (multi-speaker model)
en_us_libritts:
model: /app/voices/en/en_US/libritts/high/en_US-libritts-high.onnx
speaker: # default (multi-speaker model)
en_us_ljspeech:
model: /app/voices/en/en_US/ljspeech/medium/en_US-ljspeech-medium.onnx
speaker: # default
en_us_norman:
model: /app/voices/en/en_US/norman/medium/en_US-norman-medium.onnx
speaker: # default
en_us_reza_ibrahim:
model: /app/voices/en/en_US/reza_ibrahim/medium/en_US-reza_ibrahim-medium.onnx
speaker: # default
en_us_ryan:
model: /app/voices/en/en_US/ryan/high/en_US-ryan-high.onnx
speaker: # default
en_us_sam:
model: /app/voices/en/en_US/sam/medium/en_US-sam-medium.onnx
speaker: # default
# English GB voices # Echo - same sample, different name for compatibility
en_gb_alan:
model: /app/voices/en/en_GB/alan/medium/en_GB-alan-medium.onnx
speaker: # default
en_gb_alba:
model: /app/voices/en/en_GB/alba/medium/en_GB-alba-medium.onnx
speaker: # default
en_gb_aru:
model: /app/voices/en/en_GB/aru/medium/en_GB-aru-medium.onnx
speaker: # default (multi-speaker model)
en_gb_cori:
model: /app/voices/en/en_GB/cori/medium/en_GB-cori-medium.onnx
speaker: # default (multi-speaker model)
en_gb_jenny_dioco:
model: /app/voices/en/en_GB/jenny_dioco/medium/en_GB-jenny_dioco-medium.onnx
speaker: # default
en_gb_northern_english_male:
model: /app/voices/en/en_GB/northern_english_male/medium/en_GB-northern_english_male-medium.onnx
speaker: # default
en_gb_semaine:
model: /app/voices/en/en_GB/semaine/medium/en_GB-semaine-medium.onnx
speaker: # default
en_gb_southern_english_female:
model: /app/voices/en/en_GB/southern_english_female/low/en_GB-southern_english_female-low.onnx
speaker: # default
en_gb_vctk:
model: /app/voices/en/en_GB/vctk/medium/en_GB-vctk-medium.onnx
speaker: # default (multi-speaker model)
tts-1-hd:
alloy-alt:
model: xtts
speaker: voices/alloy-alt.wav
alloy:
model: xtts
speaker: voices/alloy.wav
echo: echo:
model: xtts ref_audio: https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/clone.wav
speaker: voices/echo.wav ref_text: "Okay. Yeah. I resent you. I love you. I respect you. But you know what? You blew it!"
language: English
# Fable - same sample
fable: fable:
model: xtts ref_audio: https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/clone.wav
speaker: voices/fable.wav ref_text: "Okay. Yeah. I resent you. I love you. I respect you. But you know what? You blew it!"
language: English
# Onyx - same sample
onyx: onyx:
model: xtts ref_audio: https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/clone.wav
speaker: voices/onyx.wav ref_text: "Okay. Yeah. I resent you. I love you. I respect you. But you know what? You blew it!"
language: English
# Nova - same sample
nova: nova:
model: xtts ref_audio: https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/clone.wav
speaker: voices/nova.wav ref_text: "Okay. Yeah. I resent you. I love you. I respect you. But you know what? You blew it!"
language: English
# Shimmer - same sample
shimmer: shimmer:
model: xtts ref_audio: https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/clone.wav
speaker: voices/shimmer.wav ref_text: "Okay. Yeah. I resent you. I love you. I respect you. But you know what? You blew it!"
me: language: English
model: xtts_v2.0.2 # you can specify an older xtts version
speaker: voices/me.wav # this could be you # Custom voice example - add your own reference audio
language: auto # my_voice:
enable_text_splitting: True # ref_audio: voices/my_voice_sample.wav
length_penalty: 1.0 # ref_text: "The exact text spoken in the reference audio file"
repetition_penalty: 10 # language: English
speed: 1.0
temperature: 0.75 # Other models are disabled by default. Uncomment to enable.
top_k: 50 # See the git history for full voice configurations.
top_p: 0.85
comment: You can add a comment here also, which will be persistent and otherwise ignored. # tts-1:
tts-1-silero: # # Piper TTS voices (fast CPU inference)
# All 118 Silero v3_en voices with proper names # alloy:
# NOTE: Use native Silero voice names (en_0, en_1, etc.) for Silero # model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx
# OpenAI voice names (alloy, echo, etc.) are reserved for tts-1 and tts-1-hd only # speaker: 79
en_0:
language: en # tts-1-hd:
speaker: en_0 # # XTTS voice cloning
silero_speaker: v3_en # alloy:
en_1: # model: xtts
language: en # speaker: voices/alloy.wav
speaker: en_1
silero_speaker: v3_en # tts-1-silero:
en_2: # # Silero TTS (CPU-friendly)
language: en # en_0:
speaker: en_2 # language: en
silero_speaker: v3_en # speaker: en_0
en_3: # silero_speaker: v3_en
language: en
speaker: en_3 # tts-1-kokoro:
silero_speaker: v3_en # # Kokoro TTS (lightweight 82M params)
en_4: # alloy:
language: en # lang_code: a
speaker: en_4 # kokoro_voice: af_alloy
silero_speaker: v3_en
en_5:
language: en
speaker: en_5
silero_speaker: v3_en
en_6:
language: en
speaker: en_6
silero_speaker: v3_en
en_7:
language: en
speaker: en_7
silero_speaker: v3_en
en_8:
language: en
speaker: en_8
silero_speaker: v3_en
en_9:
language: en
speaker: en_9
silero_speaker: v3_en
en_10:
language: en
speaker: en_10
silero_speaker: v3_en
en_11:
language: en
speaker: en_11
silero_speaker: v3_en
en_12:
language: en
speaker: en_12
silero_speaker: v3_en
en_13:
language: en
speaker: en_13
silero_speaker: v3_en
en_14:
language: en
speaker: en_14
silero_speaker: v3_en
en_15:
language: en
speaker: en_15
silero_speaker: v3_en
en_16:
language: en
speaker: en_16
silero_speaker: v3_en
en_17:
language: en
speaker: en_17
silero_speaker: v3_en
en_18:
language: en
speaker: en_18
silero_speaker: v3_en
en_19:
language: en
speaker: en_19
silero_speaker: v3_en
en_20:
language: en
speaker: en_20
silero_speaker: v3_en
en_21:
language: en
speaker: en_21
silero_speaker: v3_en
en_22:
language: en
speaker: en_22
silero_speaker: v3_en
en_23:
language: en
speaker: en_23
silero_speaker: v3_en
en_24:
language: en
speaker: en_24
silero_speaker: v3_en
en_25:
language: en
speaker: en_25
silero_speaker: v3_en
en_26:
language: en
speaker: en_26
silero_speaker: v3_en
en_27:
language: en
speaker: en_27
silero_speaker: v3_en
en_28:
language: en
speaker: en_28
silero_speaker: v3_en
en_29:
language: en
speaker: en_29
silero_speaker: v3_en
en_30:
language: en
speaker: en_30
silero_speaker: v3_en
en_31:
language: en
speaker: en_31
silero_speaker: v3_en
en_32:
language: en
speaker: en_32
silero_speaker: v3_en
en_33:
language: en
speaker: en_33
silero_speaker: v3_en
en_34:
language: en
speaker: en_34
silero_speaker: v3_en
en_35:
language: en
speaker: en_35
silero_speaker: v3_en
en_36:
language: en
speaker: en_36
silero_speaker: v3_en
en_37:
language: en
speaker: en_37
silero_speaker: v3_en
en_38:
language: en
speaker: en_38
silero_speaker: v3_en
en_39:
language: en
speaker: en_39
silero_speaker: v3_en
en_40:
language: en
speaker: en_40
silero_speaker: v3_en
en_41:
language: en
speaker: en_41
silero_speaker: v3_en
en_42:
language: en
speaker: en_42
silero_speaker: v3_en
en_43:
language: en
speaker: en_43
silero_speaker: v3_en
en_44:
language: en
speaker: en_44
silero_speaker: v3_en
en_45:
language: en
speaker: en_45
silero_speaker: v3_en
en_46:
language: en
speaker: en_46
silero_speaker: v3_en
en_47:
language: en
speaker: en_47
silero_speaker: v3_en
en_48:
language: en
speaker: en_48
silero_speaker: v3_en
en_49:
language: en
speaker: en_49
silero_speaker: v3_en
en_50:
language: en
speaker: en_50
silero_speaker: v3_en
en_51:
language: en
speaker: en_51
silero_speaker: v3_en
en_52:
language: en
speaker: en_52
silero_speaker: v3_en
en_53:
language: en
speaker: en_53
silero_speaker: v3_en
en_54:
language: en
speaker: en_54
silero_speaker: v3_en
en_55:
language: en
speaker: en_55
silero_speaker: v3_en
en_56:
language: en
speaker: en_56
silero_speaker: v3_en
en_57:
language: en
speaker: en_57
silero_speaker: v3_en
en_58:
language: en
speaker: en_58
silero_speaker: v3_en
en_59:
language: en
speaker: en_59
silero_speaker: v3_en
en_60:
language: en
speaker: en_60
silero_speaker: v3_en
en_61:
language: en
speaker: en_61
silero_speaker: v3_en
en_62:
language: en
speaker: en_62
silero_speaker: v3_en
en_63:
language: en
speaker: en_63
silero_speaker: v3_en
en_64:
language: en
speaker: en_64
silero_speaker: v3_en
en_65:
language: en
speaker: en_65
silero_speaker: v3_en
en_66:
language: en
speaker: en_66
silero_speaker: v3_en
en_67:
language: en
speaker: en_67
silero_speaker: v3_en
en_68:
language: en
speaker: en_68
silero_speaker: v3_en
en_69:
language: en
speaker: en_69
silero_speaker: v3_en
en_70:
language: en
speaker: en_70
silero_speaker: v3_en
en_71:
language: en
speaker: en_71
silero_speaker: v3_en
en_72:
language: en
speaker: en_72
silero_speaker: v3_en
en_73:
language: en
speaker: en_73
silero_speaker: v3_en
en_74:
language: en
speaker: en_74
silero_speaker: v3_en
en_75:
language: en
speaker: en_75
silero_speaker: v3_en
en_76:
language: en
speaker: en_76
silero_speaker: v3_en
en_77:
language: en
speaker: en_77
silero_speaker: v3_en
en_78:
language: en
speaker: en_78
silero_speaker: v3_en
en_79:
language: en
speaker: en_79
silero_speaker: v3_en
en_80:
language: en
speaker: en_80
silero_speaker: v3_en
en_81:
language: en
speaker: en_81
silero_speaker: v3_en
en_82:
language: en
speaker: en_82
silero_speaker: v3_en
en_83:
language: en
speaker: en_83
silero_speaker: v3_en
en_84:
language: en
speaker: en_84
silero_speaker: v3_en
en_85:
language: en
speaker: en_85
silero_speaker: v3_en
en_86:
language: en
speaker: en_86
silero_speaker: v3_en
en_87:
language: en
speaker: en_87
silero_speaker: v3_en
en_88:
language: en
speaker: en_88
silero_speaker: v3_en
en_89:
language: en
speaker: en_89
silero_speaker: v3_en
en_90:
language: en
speaker: en_90
silero_speaker: v3_en
en_91:
language: en
speaker: en_91
silero_speaker: v3_en
en_92:
language: en
speaker: en_92
silero_speaker: v3_en
en_93:
language: en
speaker: en_93
silero_speaker: v3_en
en_94:
language: en
speaker: en_94
silero_speaker: v3_en
en_95:
language: en
speaker: en_95
silero_speaker: v3_en
en_96:
language: en
speaker: en_96
silero_speaker: v3_en
en_97:
language: en
speaker: en_97
silero_speaker: v3_en
en_98:
language: en
speaker: en_98
silero_speaker: v3_en
en_99:
language: en
speaker: en_99
silero_speaker: v3_en
en_100:
language: en
speaker: en_100
silero_speaker: v3_en
en_101:
language: en
speaker: en_101
silero_speaker: v3_en
en_102:
language: en
speaker: en_102
silero_speaker: v3_en
en_103:
language: en
speaker: en_103
silero_speaker: v3_en
en_104:
language: en
speaker: en_104
silero_speaker: v3_en
en_105:
language: en
speaker: en_105
silero_speaker: v3_en
en_106:
language: en
speaker: en_106
silero_speaker: v3_en
en_107:
language: en
speaker: en_107
silero_speaker: v3_en
en_108:
language: en
speaker: en_108
silero_speaker: v3_en
en_109:
language: en
speaker: en_109
silero_speaker: v3_en
en_110:
language: en
speaker: en_110
silero_speaker: v3_en
en_111:
language: en
speaker: en_111
silero_speaker: v3_en
en_112:
language: en
speaker: en_112
silero_speaker: v3_en
en_113:
language: en
speaker: en_113
silero_speaker: v3_en
en_114:
language: en
speaker: en_114
silero_speaker: v3_en
en_115:
language: en
speaker: en_115
silero_speaker: v3_en
en_116:
language: en
speaker: en_116
silero_speaker: v3_en
en_117:
language: en
speaker: en_117
silero_speaker: v3_en
random:
language: en
speaker: random
silero_speaker: v3_en
# Russian voices (v3_ru/ru_v3 model) - use ru_v3 for better compatibility
ru_aidar:
language: ru
speaker: aidar
silero_speaker: ru_v3
ru_baya:
language: ru
speaker: baya
silero_speaker: ru_v3
ru_kseniya:
language: ru
speaker: kseniya
silero_speaker: ru_v3
ru_xenia:
language: ru
speaker: xenia
silero_speaker: ru_v3
ru_eugene:
language: ru
speaker: eugene
silero_speaker: ru_v3
ru_random:
language: ru
speaker: random
silero_speaker: ru_v3
# German voices (v3_de model)
de_bernd_ungerer:
language: de
speaker: bernd_ungerer
silero_speaker: v3_de
de_eva_k:
language: de
speaker: eva_k
silero_speaker: v3_de
de_friedrich:
language: de
speaker: friedrich
silero_speaker: v3_de
de_hokuspokus:
language: de
speaker: hokuspokus
silero_speaker: v3_de
de_karlsson:
language: de
speaker: karlsson
silero_speaker: v3_de
de_random:
language: de
speaker: random
silero_speaker: v3_de
# Spanish voices (v3_es model)
es_0:
language: es
speaker: es_0
silero_speaker: v3_es
es_1:
language: es
speaker: es_1
silero_speaker: v3_es
es_2:
language: es
speaker: es_2
silero_speaker: v3_es
es_random:
language: es
speaker: random
silero_speaker: v3_es
# French voices (v3_fr model)
fr_0:
language: fr
speaker: fr_0
silero_speaker: v3_fr
fr_1:
language: fr
speaker: fr_1
silero_speaker: v3_fr
fr_2:
language: fr
speaker: fr_2
silero_speaker: v3_fr
fr_3:
language: fr
speaker: fr_3
silero_speaker: v3_fr
fr_4:
language: fr
speaker: fr_4
silero_speaker: v3_fr
fr_5:
language: fr
speaker: fr_5
silero_speaker: v3_fr
fr_random:
language: fr
speaker: random
silero_speaker: v3_fr
tts-1-kokoro:
# OpenAI-compatible voice aliases (Kokoro's intentional OpenAI-themed voices)
alloy:
lang_code: a
kokoro_voice: af_alloy
echo:
lang_code: a
kokoro_voice: am_echo
fable:
lang_code: b
kokoro_voice: bm_fable
onyx:
lang_code: a
kokoro_voice: am_onyx
nova:
lang_code: a
kokoro_voice: af_nova
shimmer:
lang_code: a
kokoro_voice: af_sky
# Female American voices
af_heart:
lang_code: a
kokoro_voice: af_heart
af_bella:
lang_code: a
kokoro_voice: af_bella
af_nicole:
lang_code: a
kokoro_voice: af_nicole
af_aoede:
lang_code: a
kokoro_voice: af_aoede
af_kore:
lang_code: a
kokoro_voice: af_kore
af_sarah:
lang_code: a
kokoro_voice: af_sarah
af_nova:
lang_code: a
kokoro_voice: af_nova
af_sky:
lang_code: a
kokoro_voice: af_sky
af_alloy:
lang_code: a
kokoro_voice: af_alloy
af_jessica:
lang_code: a
kokoro_voice: af_jessica
af_river:
lang_code: a
kokoro_voice: af_river
# Male American voices
am_michael:
lang_code: a
kokoro_voice: am_michael
am_fenrir:
lang_code: a
kokoro_voice: am_fenrir
am_puck:
lang_code: a
kokoro_voice: am_puck
am_echo:
lang_code: a
kokoro_voice: am_echo
am_eric:
lang_code: a
kokoro_voice: am_eric
am_liam:
lang_code: a
kokoro_voice: am_liam
am_onyx:
lang_code: a
kokoro_voice: am_onyx
am_santa:
lang_code: a
kokoro_voice: am_santa
am_adam:
lang_code: a
kokoro_voice: am_adam
# Female British voices
bf_emma:
lang_code: b
kokoro_voice: bf_emma
bf_isabella:
lang_code: b
kokoro_voice: bf_isabella
bf_alice:
lang_code: b
kokoro_voice: bf_alice
bf_lily:
lang_code: b
kokoro_voice: bf_lily
# Male British voices
bm_george:
lang_code: b
kokoro_voice: bm_george
bm_fable:
lang_code: b
kokoro_voice: bm_fable
bm_lewis:
lang_code: b
kokoro_voice: bm_lewis
bm_daniel:
lang_code: b
kokoro_voice: bm_daniel