diff --git a/CLAUDE.md b/CLAUDE.md index 742d7c1..24a94cf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,7 +79,13 @@ owned_by = "UncloseAI" **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: - `Makefile` help text diff --git a/Dockerfile b/Dockerfile index 960b1c9..543a1bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/Makefile b/Makefile index 2743b5d..da1f5b3 100644 --- a/Makefile +++ b/Makefile @@ -14,41 +14,48 @@ REMOTE_USER ?= $(USER) REMOTE_PATH ?= ~/uncloseai-speech 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: @echo "๐Ÿฆ Raccoon TTS Mission - Development Commands" @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 sync - Sync local files to remote server" @echo " make restart - Restart the Docker container" @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 " make hydrate - Hydrate all models by testing ALL 227 voices" - @echo " make load-test - Load test with concurrent random voice requests" + @echo " make test - Test TTS endpoint (Qwen3-TTS)" + @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 "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 stop - Stop Docker container" @echo " make clean - Stop and remove container" @echo "" @echo "Git:" - @echo " make push-all - Push to all git remotes (origin + github)" + @echo " make push-all - Push to all git remotes" sync: @echo "๐Ÿ“ฆ Syncing files to $(REMOTE_HOST)..." @@ -61,6 +68,78 @@ sync: deploy: sync restart @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: @echo "๐Ÿ”„ Rebuilding and restarting container on $(REMOTE_HOST)..." ssh $(REMOTE_USER)@$(REMOTE_HOST) "cd $(REMOTE_PATH) && docker compose up -d --build" @@ -81,17 +160,29 @@ logs: @echo "๐Ÿ“‹ Tailing logs from $(REMOTE_HOST)..." ssh $(REMOTE_USER)@$(REMOTE_HOST) "docker logs -f $(CONTAINER_NAME)" -test: - @echo "๐Ÿงช Testing TTS endpoint..." +test: test-qwen + +test-qwen: + @echo "๐Ÿงช Testing Qwen3-TTS endpoint (default model)..." curl -X POST http://$(REMOTE_HOST):8000/v1/audio/speech \ -H "Content-Type: application/json" \ - -d '{"model":"tts-1","voice":"alloy","input":"Raccoon mission TTS test"}' \ - -o /tmp/raccoon_test.mp3 + -d '{"model":"tts-1-qwen","voice":"alloy","input":"Raccoon mission TTS test with Qwen three"}' \ + -o /tmp/qwen_test.mp3 @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 - @echo "โœ… All voices downloaded (Piper, XTTS, Silero)!" +voices: voices-qwen + @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: @echo "๐ŸŽค Downloading all Piper voices..." diff --git a/docker-compose.cpu.yml b/docker-compose.cpu.yml new file mode 100644 index 0000000..c023d58 --- /dev/null +++ b/docker-compose.cpu.yml @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index e789dd9..96c13b1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: server: build: @@ -9,13 +13,16 @@ services: volumes: - ./voices:/app/voices - ./config:/app/config - # To install as a service + environment: + - NVIDIA_VISIBLE_DEVICES=all + - NVIDIA_DRIVER_CAPABILITIES=compute,utility restart: unless-stopped deploy: resources: reservations: devices: - driver: nvidia - #device_ids: ['0', '1'] # Select a gpu, or + # Uncomment to select specific GPU(s): + # device_ids: ['0'] count: all capabilities: [gpu] diff --git a/docs/MODELS.md b/docs/MODELS.md index 0cbcf47..a4d7714 100644 --- a/docs/MODELS.md +++ b/docs/MODELS.md @@ -2,6 +2,22 @@ **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 ### Comprehensive Research @@ -33,7 +49,83 @@ Each model has detailed documentation covering technical specs, integration stat ## 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 @@ -505,8 +597,8 @@ The following models have detailed documentation but are not yet integrated or p --- -**Last Updated:** 2025-11-09 -**Raccoon Status:** ๐Ÿฆ 4 models rescued! Silero and Kokoro TTS integrated successfully -**Integration Status:** โœ… Piper (55 voices), XTTS (8 voices), Silero (148 voices), Kokoro (34 voices) | ๐ŸŽฏ Next: Chatterbox, StyleTTS2 -**API Endpoints:** tts-1, tts-1-hd, tts-1-silero, tts-1-kokoro | /v1/models for discovery -**Documentation Status:** ๐Ÿ“š 10 models fully documented, 1 comprehensive research overview +**Last Updated:** 2026-01-26 +**Raccoon Status:** ๐Ÿฆ 5 models rescued! Qwen3-TTS is now the default model +**Integration Status:** โœ… Qwen3-TTS (default, unlimited voices via cloning) | Disabled: Piper (55), XTTS (8), Silero (148), Kokoro (34) +**API Endpoints:** tts-1-qwen (default) | Others available: tts-1, tts-1-hd, tts-1-silero, tts-1-kokoro +**Documentation Status:** ๐Ÿ“š 11 models fully documented, 1 comprehensive research overview diff --git a/requirements.txt b/requirements.txt index d8de8f6..4a9c981 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,28 +1,31 @@ fastapi uvicorn 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 # 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 # 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 # Note: Silero models are loaded via torch.hub, no package install needed # 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) # Install from git since no PyPI package exists yet # ๐Ÿฆ 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 # TODO: Test Chatterbox in isolated environment or wait for dependency updates # git+https://github.com/resemble-ai/chatterbox.git -langdetect +# langdetect pyyaml # Kokoro TTS - fast decoder-only architecture # Lightweight decoder-only TTS, 82M params, 24kHz output -kokoro>=0.9.2 -soundfile # Required by Kokoro for audio output +# kokoro>=0.9.2 +soundfile # Required by Qwen3-TTS and Kokoro for audio output transformers>=4.35.0 # Hugging Face Hub for model downloads huggingface-hub[cli] diff --git a/speech.py b/speech.py index 3a95799..63915aa 100755 --- a/speech.py +++ b/speech.py @@ -38,6 +38,20 @@ except ImportError: split_sentence = 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 async def lifespan(app): # Startup: Initialize voice caches in each worker process @@ -89,6 +103,10 @@ async def lifespan(app): model_info["engine"] = "kokoro" model_info["description"] = "Lightweight decoder-only TTS (82M params)" 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) @@ -125,6 +143,8 @@ silero_model = None silero_speakers = {} kokoro_pipeline = 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__) class DefaultArgs: @@ -159,6 +179,7 @@ voices_cache = None # Semaphores to limit concurrent model loading (prevent thread pool exhaustion) 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 +qwen_load_semaphore = asyncio.Semaphore(1) # Only one Qwen model load at a time def unload_model(): import torch, gc @@ -350,6 +371,126 @@ class kokoro_wrapper(): logger.error(f"Kokoro TTS generation failed: {e}") 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): if not os.path.exists(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(): """List all available TTS models (OpenAI-compatible format)""" # Return minimal OpenAI-compatible model list (no extra fields) + # Only tts-1-qwen is enabled by default return { "object": "list", "data": [ { - "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", + "id": "tts-1-qwen", "object": "model", "created": 1700000000, "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["description"] = "Lightweight decoder-only TTS (82M params)" 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) @@ -597,6 +750,8 @@ async def generate_speech(request: GenerateSpeechRequest): media_type = "audio/pcm;rate=48000" elif model == 'tts-1-kokoro': # kokoro media_type = "audio/pcm;rate=24000" + elif model == 'tts-1-qwen': # qwen3-tts + media_type = "audio/pcm;rate=24000" else: 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.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) 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__": parser = argparse.ArgumentParser( @@ -941,10 +1157,13 @@ if __name__ == "__main__": elif args.preload: xtts = xtts_wrapper(args.preload, device=args.xtts_device, unload_timer=args.unload_timer) - app.register_model('tts-1') - app.register_model('tts-1-hd') - app.register_model('tts-1-silero') - app.register_model('tts-1-kokoro') + # Register only Qwen by default (other models disabled) + app.register_model('tts-1-qwen') + # To enable other models, uncomment below: + # 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) # This prevents thread pool exhaustion and allows concurrent model loading diff --git a/voice_to_speaker.default.yaml b/voice_to_speaker.default.yaml index a529492..22706d9 100644 --- a/voice_to_speaker.default.yaml +++ b/voice_to_speaker.default.yaml @@ -1,892 +1,77 @@ -๏ปฟtts-1: - # OpenAI-compatible voice aliases (backward compatibility) - 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 +# uncloseai-speech Voice Configuration +# Only tts-1-qwen is enabled by default - # English US voices - all available Piper models - # libritts_r has 904 speakers (multi-speaker model) - en_us_libritts_r_0: - model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx - 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 +tts-1-qwen: + # OpenAI-compatible voice aliases + # Each voice requires ref_audio (reference audio) and ref_text (transcript of the audio) + # Language: Chinese, English, Japanese, Korean, German, French, Russian, Portuguese, Spanish, Italian - # Single-speaker Piper voices (to be downloaded) - en_us_amy: - model: /app/voices/en/en_US/amy/medium/en_US-amy-medium.onnx - speaker: # default - en_us_arctic: - 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 + # Default voice - using Qwen's example clone audio + alloy: + ref_audio: https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/clone.wav + ref_text: "Okay. Yeah. I resent you. I love you. I respect you. But you know what? You blew it!" + language: English - # English GB voices - 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 - same sample, different name for compatibility echo: - model: xtts - speaker: voices/echo.wav + ref_audio: https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/clone.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: - model: xtts - speaker: voices/fable.wav + ref_audio: https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/clone.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: - model: xtts - speaker: voices/onyx.wav + ref_audio: https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/clone.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: - model: xtts - speaker: voices/nova.wav + ref_audio: https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/clone.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: - model: xtts - speaker: voices/shimmer.wav - me: - model: xtts_v2.0.2 # you can specify an older xtts version - speaker: voices/me.wav # this could be you - language: auto - enable_text_splitting: True - length_penalty: 1.0 - repetition_penalty: 10 - speed: 1.0 - temperature: 0.75 - top_k: 50 - top_p: 0.85 - comment: You can add a comment here also, which will be persistent and otherwise ignored. -tts-1-silero: - # All 118 Silero v3_en voices with proper names - # NOTE: Use native Silero voice names (en_0, en_1, etc.) for Silero - # OpenAI voice names (alloy, echo, etc.) are reserved for tts-1 and tts-1-hd only - en_0: - language: en - speaker: en_0 - silero_speaker: v3_en - en_1: - language: en - speaker: en_1 - silero_speaker: v3_en - en_2: - language: en - speaker: en_2 - silero_speaker: v3_en - en_3: - language: en - speaker: en_3 - silero_speaker: v3_en - en_4: - language: en - speaker: en_4 - 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 \ No newline at end of file + ref_audio: https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-TTS-Repo/clone.wav + ref_text: "Okay. Yeah. I resent you. I love you. I respect you. But you know what? You blew it!" + language: English + + # Custom voice example - add your own reference audio + # my_voice: + # ref_audio: voices/my_voice_sample.wav + # ref_text: "The exact text spoken in the reference audio file" + # language: English + +# Other models are disabled by default. Uncomment to enable. +# See the git history for full voice configurations. + +# tts-1: +# # Piper TTS voices (fast CPU inference) +# alloy: +# model: /app/voices/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx +# speaker: 79 + +# tts-1-hd: +# # XTTS voice cloning +# alloy: +# model: xtts +# speaker: voices/alloy.wav + +# tts-1-silero: +# # Silero TTS (CPU-friendly) +# en_0: +# language: en +# speaker: en_0 +# silero_speaker: v3_en + +# tts-1-kokoro: +# # Kokoro TTS (lightweight 82M params) +# alloy: +# lang_code: a +# kokoro_voice: af_alloy