The prior docs claimed prod = 3090-ai running uncloseai-speech-server-1 via docker compose. Reality (verified today): prod is bare python on the 4090 (ai.foxhop.net) using the f5-sidecar venv at /mnt/data/f5-sidecar/venv, fronted by Caddy on 80/443. The container on 3090-ai exited 2 months ago and docker compose v2 isn't installed there. Cost of the drift today: one wrong-host deploy attempt that bounced off dead infrastructure. This rewrites Production Deployment, Quick Production Commands, Testing, When Things Break, and Common Mistakes to match reality. Also drops hardcoded tmux window numbers (they shift) — every example now reads <prod-window> with a reminder to rediscover via tmux-hosts. |
||
|---|---|---|
| .github/workflows | ||
| cloned-voices | ||
| config | ||
| docs | ||
| scripts | ||
| voices | ||
| .gitignore | ||
| audio_reader.py | ||
| boot_docker_compose_ubuntu.sh | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| docker-compose.cpu.yml | ||
| docker-compose.min.yml | ||
| docker-compose.rocm.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| Dockerfile.min | ||
| download_voices_tts-1-hd.sh | ||
| download_voices_tts-1.sh | ||
| LICENSE | ||
| Makefile | ||
| openedai.py | ||
| pre_process_map.default.yaml | ||
| README.md | ||
| requirements-min.txt | ||
| requirements-rocm.txt | ||
| requirements.txt | ||
| sample.env | ||
| speech.py | ||
| startup.min.sh | ||
| startup.sh | ||
| TESTING.md | ||
| vars.sh.example | ||
| voice_registry.json | ||
| voice_to_speaker.default.yaml | ||
uncloseai-speech
OpenAI-compatible text-to-speech API server with state-of-the-art voice cloning.
Default Engines:
- Qwen3-TTS - 1.7B parameters, 10 languages, 97ms latency (
tts-1-qwen) - F5-TTS - 336M parameters, flow-matching zero-shot voice cloning, lower VRAM (
tts-1-f5)
Quick Start
git clone https://github.com/uncloseai/uncloseai-speech.git
cd uncloseai-speech
# Option 1: Docker with GPU (recommended)
make local
# Option 2: Docker CPU only
make local-cpu
# Option 3: Python venv (no Docker)
make venv && make venv-run
Test the API:
curl http://localhost:8000/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{"input":"Hello from Qwen TTS!","voice":"alloy"}' \
-o test.mp3
Requirements
| Setup | GPU | RAM | Disk | Notes |
|---|---|---|---|---|
| Docker + GPU | NVIDIA 8GB+ VRAM | 8GB | 5GB | Recommended, fastest |
| Docker + CPU | None | 16GB | 5GB | ~10x slower |
| Python venv | Optional | 16GB | 5GB | Direct install |
GPU Setup (NVIDIA)
Install nvidia-container-toolkit:
# Ubuntu/Debian
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
Verify GPU access:
docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi
Installation
Docker with GPU
cp sample.env speech.env
make local
# Or: docker compose up -d --build
Docker CPU Only
cp sample.env speech.env
make local-cpu
# Or: docker compose -f docker-compose.cpu.yml up -d --build
Python Virtual Environment
# Create and activate venv
make venv
# Run the server
make venv-run
# Or manually:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python speech.py
AMD GPU (ROCm)
docker compose -f docker-compose.rocm.yml up -d --build
API Reference
Generate Speech
POST /v1/audio/speech
| Parameter | Type | Default | Description |
|---|---|---|---|
input |
string | required | Text to synthesize |
voice |
string | alloy |
Voice name |
model |
string | tts-1-qwen |
Model ID |
response_format |
string | mp3 |
mp3, opus, aac, flac, wav, pcm |
speed |
float | 1.0 |
Speed multiplier (0.25-4.0) |
Example:
curl http://localhost:8000/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"model": "tts-1-qwen",
"voice": "alloy",
"input": "The quick brown fox jumped over the lazy dog.",
"response_format": "mp3",
"speed": 1.0
}' -o speech.mp3
List Models
GET /v1/models
List Voices
GET /v1/voices
Returns all voices with metadata including engine, sample rate, and language support.
Python SDK Usage
import openai
client = openai.OpenAI(
api_key="not-needed",
base_url="http://localhost:8000/v1",
)
# Basic usage
with client.audio.speech.with_streaming_response.create(
model="tts-1-qwen",
voice="alloy",
input="Hello world!"
) as response:
response.stream_to_file("speech.mp3")
# With options
with client.audio.speech.with_streaming_response.create(
model="tts-1-qwen",
voice="nova",
input="This is faster speech.",
response_format="opus",
speed=1.2
) as response:
response.stream_to_file("speech.opus")
Voice Cloning
Qwen3-TTS clones any voice from a 3+ second audio sample.
1. Prepare Reference Audio
- Length: 3-30 seconds (6-10 optimal)
- Quality: Clear speech, minimal noise
- Format: WAV, MP3, or URL
2. Configure Voice
Edit config/voice_to_speaker.yaml:
tts-1-qwen:
my_voice:
ref_audio: voices/my_sample.wav # Local file or URL
ref_text: "Exact transcript of the audio."
language: English
3. Use the Voice
curl http://localhost:8000/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{"voice":"my_voice","input":"Hello in my cloned voice!"}' \
-o output.mp3
Supported Languages
Chinese, English, Japanese, Korean, German, French, Russian, Portuguese, Spanish, Italian
Default Voices
| Voice | Description |
|---|---|
alloy |
Neutral, balanced |
echo |
Warm, conversational |
fable |
Expressive, storytelling |
onyx |
Deep, authoritative |
nova |
Friendly, upbeat |
shimmer |
Soft, gentle |
All voices use Qwen3-TTS voice cloning with pre-configured reference audio.
Configuration
Environment Variables
Edit speech.env:
TTS_HOME=voices # Model cache directory
HF_HOME=voices # HuggingFace cache
EXTRA_ARGS=--log-level INFO # Additional server args
Server Arguments
--xtts_device DEVICE Device: cuda, cpu, none (default: auto-detect)
--workers N Worker processes (default: 4)
--port PORT Listen port (default: 8000)
--host HOST Bind address (default: 0.0.0.0)
--log-level LEVEL DEBUG, INFO, WARNING, ERROR, CRITICAL
Makefile Commands
make help # Show all commands
# Local Development
make local # Docker with GPU
make local-cpu # Docker CPU only
make venv # Create Python venv
make venv-run # Run in venv
# Testing
make test # Test API
make logs # View logs
# Remote Deployment
make deploy # Sync + restart remote
make sync # Sync files only
make restart # Restart container
# Container
make start # Start container
make stop # Stop container
make clean # Remove container
Engines
Enabled by default:
| Model | Engine | Voices | Speed | Notes |
|---|---|---|---|---|
tts-1-qwen |
Qwen3-TTS | 40 | Fast | Voice cloning, 10 languages, 1.7B params |
tts-1-f5 |
F5-TTS | 40 | Faster | Voice cloning, flow-matching, 336M params, lower VRAM |
Disabled by default — enable by uncommenting in config/voice_to_speaker.yaml and requirements.txt:
| Model | Engine | Voices | Speed | Notes |
|---|---|---|---|---|
tts-1 |
Piper | 55 | Fast | CPU optimized |
tts-1-hd |
XTTS v2 | 8 | Medium | Voice cloning |
tts-1-silero |
Silero | 148 | Fast | 5 languages |
tts-1-kokoro |
Kokoro | 34 | Fast | 82M params |
See docs/MODELS.md for details.
Troubleshooting
Model Download Fails
# Check logs
docker logs uncloseai-speech-server-1
# Manual download
docker exec -it uncloseai-speech-server-1 \
huggingface-cli download Qwen/Qwen3-TTS-12Hz-1.7B-Base
Out of GPU Memory
Qwen3-TTS needs ~6GB VRAM. Options:
- Add to
speech.env:EXTRA_ARGS=--xtts_device cpu - Reduce workers:
EXTRA_ARGS=--workers 1 - Use CPU-only:
make local-cpu
Slow Generation
- GPU: ~1-2 seconds per sentence
- CPU: ~10-20 seconds per sentence
For faster CPU inference, enable Piper or Silero engines.
Voice Quality Issues
- Use 6-10 seconds of clear reference audio
- Ensure transcript exactly matches audio
- Avoid background noise
- Match language setting to audio language
Architecture
┌─────────────────────────────────────────────┐
│ Client │
│ (OpenAI SDK / curl) │
└─────────────────┬───────────────────────────┘
│ HTTP POST /v1/audio/speech
▼
┌─────────────────────────────────────────────┐
│ FastAPI Server │
│ (speech.py, port 8000) │
├─────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ Voice Config │
│ │ Qwen3-TTS │◄─────────────────────────┐ │
│ │ (default) │ config/voice_to_speaker │ │
│ └──────┬──────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────┐ │ │
│ │ FFmpeg │ Audio encoding │ │
│ │ (mp3/opus) │ │ │
│ └──────┬──────┘ │ │
│ │ │ │
└─────────┼──────────────────────────────────┘
│
▼ Audio stream
Client
License
AGPL v3 - This software is licensed under the GNU Affero General Public License v3.
Key AGPL v3 Obligations
-
Network use triggers copyleft - Unlike regular GPL, AGPL closes the "SaaS loophole". If you run uncloseai-speech as a service (even without distributing binaries), users have the right to request source code.
-
What you must provide:
- Complete source code of the running version
- Any modifications you've made
- Build instructions
-
How to comply:
- Link to your source repository in API responses or docs
- Offer source code download from the same server
- Keep your modifications in a public git repo
Practical Implementation
For a service at ai.foxhop.net, you could:
# Add to API response headers or /source endpoint
"source_code": "https://github.com/uncloseai/uncloseai-speech"
Or include it in your API's /models or root endpoint response.
Why AGPL for TTS?
From the Raccoon Mission values:
- Liberation - Keeps TTS libre
- Resilience - Ensures forks remain open
- Unification - Community improvements flow back