uncloseai-speech/CLAUDE.md
russell@unturf.com a148088cb0 Fix Qwen TTS deadlock, reduce workers to 1 for GPU
- Fix subprocess deadlock in Qwen TTS by using threading for stdin write
  (prevents pipe buffer deadlock on large audio output)
- Set WORKERS=1 for GPU models to avoid VRAM duplication
  (4 workers × 3GB model = OOM, 1 worker works fine)
- Update CLAUDE.md: use git push/pull instead of rsync for deployment
2026-01-26 19:14:19 -05:00

11 KiB

Instructions for Claude Code

Project: uncloseai-speech - Raccoon Mission TTS System License: AGPL v3 (must provide source code to network service users)

Brand Identity

CRITICAL: Always use consistent naming across all files.

Project Name

  • Correct: uncloseai-speech (lowercase, hyphenated)
  • Wrong: "UncloseAI Speech", "Uncloseai Speech", "UncloseAI-Speech"

Organization Name

  • Correct: uncloseai (lowercase, one word)
  • Wrong: "UncloseAI", "Unclose AI", "UnClose AI"

Usage Guidelines

  • In code: Use uncloseai-speech for project references
  • In documentation: Use uncloseai-speech for project name
  • In comments: Use uncloseai-speech consistently
  • Repository URLs: uncloseai-speech (lowercase, hyphenated)
  • Docker images: uncloseai-speech (lowercase, hyphenated)
  • API responses: Use "owned_by": "uncloseai" (lowercase, one word)

Examples

# Correct
description='uncloseai-speech API Server'
owned_by = "uncloseai"

# Wrong
description='UncloseAI Speech API Server'
owned_by = "UncloseAI"
# Correct
# uncloseai-speech

**Raccoon Mission:** Rescue abandoned TTS models and integrate them into uncloseai-speech

# Wrong
# UncloseAI Speech

**Raccoon Mission:** Rescue abandoned TTS models and integrate them into UncloseAI Speech

Core Principles

1. Makefile-First Development

ALWAYS prefer Makefile targets over manual commands.

  • DO: make deploy, make voices, make test
  • DON'T: Manual ssh commands, docker commands, curl commands

When adding new functionality:

  1. Add it to the Makefile first
  2. Document it in make help
  3. Test it works from scratch
  4. Only then modify other files if needed

Makefile is the source of truth for all deployment and development tasks.

2. Work Locally, Deploy Remotely

  • Local development: /home/fox/git/uncloseai-speech/
  • Remote server: ai.foxhop.net (configured in vars.sh, gitignored)
  • Never create remote directories manually - let Makefile handle it
  • Always test from scratch - make clean then make deploy

3. Configuration Management

  • vars.sh - Deployment secrets (gitignored, never commit)
  • vars.sh.example - Template for users (commit this)
  • sample.env - Default environment (commit this)
  • speech.env - Runtime environment (created automatically by Makefile)

Key environment variables:

  • WORKERS - Number of uvicorn workers (default: 1 for GPU models like Qwen3-TTS)
    • Use 1 for GPU-bound models to avoid VRAM duplication across workers
    • Increase for CPU-bound models like Piper (e.g., WORKERS=4)

Never view or log secrets - source them and use them.

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
  • docs/MODELS.md for new TTS engines
  • docs/MIRRORS.md for binary downloads
  • docs/AUDIT.md for file changes
  • This file (docs/CLAUDE.md) for new patterns

Common Tasks

Full Deployment from Scratch

# 1. Clean everything
make clean

# 2. Deploy (commit, push, pull on server, rebuild container)
git push && tmux send-keys -t 0:1 'cd ~/git/uncloseai-speech && git pull && sudo docker-compose up --build -d' Enter

# 3. Download voices (Piper + XTTS samples)
make voices

# 4. Test
make test
make test-xtts

Adding a New TTS Engine

  1. Document it in docs/MODELS.md first
  2. Add download target to Makefile (e.g., voices-silero)
  3. Implement engine wrapper in speech.py or src/engines/
  4. Add test target (e.g., test-silero)
  5. Update make voices to include it
  6. Test full cycle: commit, push, pull on server, rebuild, test

Debugging Issues

make logs          # Tail live logs
make logs | grep ERROR  # Filter errors

Never use raw docker/ssh commands - extend Makefile if needed.

File Organization

Scripts vs Docs

  • scripts/ - Executable utilities (add_voice.py, download_samples.sh, etc.)
  • docs/ - Documentation ONLY (no executable code)
  • Dockerfiles, startup.sh - Root level (build artifacts)
  • Makefile - Root level (primary interface)

Never put executable scripts in docs/ directory.

Current Structure (as of 2025-11-09)

uncloseai-speech/
├── Makefile              # PRIMARY INTERFACE - always update first
├── vars.sh               # Secrets (gitignored)
├── vars.sh.example       # Template
├── speech.py             # Main server (will refactor to src/)
├── openedai.py           # API models
├── voice_to_speaker.default.yaml  # Voice config
├── docs/
│   ├── CLAUDE.md         # This file
│   ├── AUDIT.md          # Repository audit
│   ├── MODELS.md         # TTS engines
│   └── MIRRORS.md        # Binary mirror strategy
├── scripts/
│   ├── add_voice.py
│   ├── say.py
│   ├── test_voices.sh
│   └── download_samples.sh
├── Dockerfile
├── docker-compose.yml
└── startup.sh

TTS Engine Status

Default Model (Qwen3-TTS)

  • Qwen3-TTS (tts-1-qwen) - DEFAULT - 1.7B params, 10 languages, voice cloning, 97ms latency

Other Engines (disabled by default, enable in voice_to_speaker.yaml)

  • Piper TTS (tts-1) - 55 voices, fast CPU inference
  • XTTS v2 (tts-1-hd) - Voice cloning, multilingual
  • Silero TTS (tts-1-silero) - 142 voices, 5 languages, auto-downloads
  • Kokoro TTS (tts-1-kokoro) - 32 voices, lightweight (82M params)

Why Qwen3-TTS is Default

  • State-of-the-art quality with voice cloning
  • Actively maintained by Alibaba
  • Apache 2.0 license (commercial-friendly)
  • 10 languages: Chinese, English, Japanese, Korean, German, French, Russian, Portuguese, Spanish, Italian
  • Fast first-packet latency (97ms)
  • Easy voice cloning from 3-second samples

See docs/MODELS.md for complete roadmap and detailed model documentation.

Production Deployment

Production Server: 3090-ai.foxhop.net

  • URL: https://speech.ai.unturf.com (proxied via ai.foxhop.net)
  • Repo Location: /home/fox/git/uncloseai-speech
  • Container: uncloseai-speech_server_1 (image: uncloseai-speech:local)
  • tmux access: tmux send-keys -t 0:1 'command' Enter (window 0:1 is 3090-ai)

Deployment Workflow

We use git, not rsync. Commit locally, push, pull on server, rebuild.

Local:
  /home/fox/git/uncloseai-speech/

  ↓ git commit && git push

Remote (3090-ai.foxhop.net):
  /home/fox/git/uncloseai-speech/

  ↓ git pull && sudo docker-compose up --build -d

Container:
  /app/
  ├── speech.py
  ├── voices/samples/  (cloned-voices mounted)
  └── config/
      └── voice_to_speaker.yaml

Quick Production Commands

# Use tmux-hosts to see available tmux windows
tmux-hosts

# Push changes and deploy
git add -A && git commit -m "message" && git push
tmux send-keys -t 0:1 'cd ~/git/uncloseai-speech && git pull && sudo docker-compose up --build -d' Enter

# Check container status
tmux send-keys -t 0:1 'sudo docker ps | grep speech' Enter

# View logs
tmux send-keys -t 0:1 'sudo docker logs --tail 50 uncloseai-speech_server_1' Enter

# Restart container
tmux send-keys -t 0:1 'cd ~/git/uncloseai-speech && sudo docker-compose restart' Enter

Testing Philosophy

Full stack testing workflow:

  1. Commit and push changes
  2. Pull on server and rebuild: git pull && sudo docker-compose up --build -d
  3. Wait for model to load (check logs)
  4. Test via curl or browser

Never assume - always test from scratch after changes.

Raccoon Mission Values

  1. Resilience - Assume upstream dies, plan mirrors
  2. Simplicity - Makefile > manual commands
  3. Documentation - Write docs before code
  4. Liberation - Keep TTS libre (AGPL v3)
  5. Unification - All TTS engines, one API

AGPL v3 Compliance

This project is AGPL v3 licensed. The key obligation: anyone who uses this TTS service over a network must be able to access the source code.

What This Means

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.

Requirements for Operators

When running uncloseai-speech as a network service, you must provide:

  • Complete source code of the running version
  • Any modifications you've made
  • Build instructions

How to Comply

  1. Link in API response - Add source URL to /v1/models or root endpoint
  2. Link in documentation - Include repository URL in service docs
  3. Host source code - Keep your fork in a public git repository

Example Implementation

# In API responses
"source_code": "https://github.com/uncloseai/uncloseai-speech"

Why AGPL?

  • Ensures forks remain open source
  • Community improvements flow back to the project
  • Prevents proprietary TTS services from using our work without sharing back
  • Aligns with Raccoon Mission: Keep TTS libre

Common Mistakes to Avoid

DON'T create directories with raw ssh DO add Makefile target for deployment

DON'T use rsync - we use git DO commit, push, pull on server, then rebuild with docker-compose

DON'T put scripts in docs/ DO put scripts in scripts/, reference from docs

DON'T hardcode paths/hosts DO use vars.sh variables

DON'T forget to commit and push before deploying DO commit, push, pull on server, rebuild container

When Things Break

  1. Check logs: tmux send-keys -t 0:1 'sudo docker logs --tail 50 uncloseai-speech_server_1' Enter
  2. Check GPU memory: tmux send-keys -t 0:1 'nvidia-smi' Enter
  3. Restart container: tmux send-keys -t 0:1 'sudo docker-compose restart' Enter
  4. Rebuild: git pull && sudo docker-compose up --build -d
  5. Check voice files: tmux send-keys -t 0:1 'ls voices/samples/' Enter

Multiprocess Architecture (uvicorn workers=4)

Key pattern: Worker processes spawn as fresh imports, don't run __main__ block.

Solutions implemented:

  1. Caches - Initialize in lifespan context manager (runs per worker)
  2. Args - Use DefaultArgs class at module level, override in __main__

See speech.py:23-113 for implementation.

Future Refactoring (Planned)

  • Move speech.py, openedai.py, audio_reader.pysrc/
  • Create engine abstraction layer in src/engines/
  • Unified voice config with engine selection
  • Binary mirror implementation (MinIO on ai.foxhop.net)

See docs/AUDIT.md for detailed refactoring plan.


Remember: Makefile first, documentation second, code third. Test from scratch every time.

🦝 Raccoon Mission: Keep TTS libre, rescue abandoned models, unify all engines.