# Raccoon Mission: uncloseai-speech Makefile # All commands run locally. Use git push/pull to sync between machines. CONTAINER_NAME ?= uncloseai-speech-server-1 .PHONY: help deploy restart logs test clean stop start voices voices-qwen voices-f5 voices-piper voices-xtts voices-kokoro test-kokoro voices-silero test-silero voices-chatterbox test-chatterbox push-all hydrate load-test test-qwen test-f5 venv venv-run local local-cpu whisper-refs apt-deps help: @echo "Raccoon TTS Mission - Development Commands" @echo "" @echo "Docker (GPU):" @echo " make deploy - Build and start container (GPU)" @echo " make deploy-cpu - Build and start container (CPU only)" @echo " make restart - Rebuild and restart container" @echo " make stop - Stop container" @echo " make start - Start container (no rebuild)" @echo " make clean - Stop and remove container + volumes" @echo " make logs - Tail container logs" @echo "" @echo "No Docker:" @echo " make apt-deps - Install required system packages (ffmpeg, sox, curl, git)" @echo " make venv - Create Python virtual environment" @echo " make venv-run - Run server in virtual environment" @echo "" @echo "Testing (default engines):" @echo " make test - Test TTS endpoint (Qwen3-TTS)" @echo " make test-qwen - Test Qwen3-TTS voice cloning" @echo " make test-f5 - Test F5-TTS voice cloning" @echo " make hydrate - Test all configured voices" @echo " make load-test - Concurrent load test" @echo "" @echo "Voices:" @echo " make voices-qwen - Download Qwen3-TTS cloned voice samples" @echo " make voices-f5 - Prepare F5-TTS voices (reuses Qwen samples)" @echo " make whisper-refs - Re-transcribe cloned-voices/*.wav with whisper-large-v3 (GPU)" @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 "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 "" @echo "Git:" @echo " make push-all - Push to all git remotes" # ============================================================================ # Docker # ============================================================================ deploy: @[ -f speech.env ] || cp sample.env speech.env sudo docker compose up -d --build deploy-cpu: @[ -f speech.env ] || cp sample.env speech.env sudo docker compose -f docker-compose.cpu.yml up -d --build restart: sudo docker compose up -d --build stop: sudo docker compose down start: sudo docker compose up -d clean: stop sudo docker compose down -v logs: sudo docker logs -f $(CONTAINER_NAME) # ============================================================================ # 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 "Done. Run: make venv-run" venv-run: @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 $(VENV_DIR)/bin/python speech.py venv-clean: rm -rf $(VENV_DIR) # Required system packages for the venv path (mirrors Dockerfile apt install + sox). # Skip if you only ever use the Docker path — the image carries these itself. apt-deps: sudo apt-get update sudo apt-get install -y --no-install-recommends curl ffmpeg git sox libsox-fmt-all # ============================================================================ # Testing # ============================================================================ test: test-qwen test-qwen: @echo "Testing Qwen3-TTS endpoint..." curl -X POST http://localhost:8000/v1/audio/speech \ -H "Content-Type: application/json" \ -d '{"model":"tts-1-qwen","voice":"aria","input":"Raccoon mission TTS test with Qwen three"}' \ -o /tmp/qwen_test.mp3 @echo "" @echo "Saved to /tmp/qwen_test.mp3" test-f5: @echo "Testing F5-TTS endpoint..." curl -X POST http://localhost:8000/v1/audio/speech \ -H "Content-Type: application/json" \ -d '{"model":"tts-1-f5","voice":"aria","input":"Raccoon mission TTS test with F5 flow matching"}' \ -o /tmp/f5_test.mp3 @echo "" @echo "Saved to /tmp/f5_test.mp3" test-kokoro: @echo "Testing Kokoro fast synthesis..." curl -X POST http://localhost:8000/v1/audio/speech \ -H "Content-Type: application/json" \ -d '{"model":"tts-1-kokoro","voice":"aria","input":"Testing Kokoro fast decoder synthesis"}' \ -o /tmp/kokoro_test.mp3 @echo "" @echo "Saved to /tmp/kokoro_test.mp3" test-xtts: @echo "Testing XTTS HD endpoint..." curl -X POST http://localhost:8000/v1/audio/speech \ -H "Content-Type: application/json" \ -d '{"model":"tts-1-hd","voice":"aria","input":"Testing XTTS high definition"}' \ -o /tmp/xtts_test.mp3 @echo "" @echo "Saved to /tmp/xtts_test.mp3" test-silero: @echo "Testing Silero endpoint..." curl -X POST http://localhost:8000/v1/audio/speech \ -H "Content-Type: application/json" \ -d '{"model":"tts-1-silero","voice":"aria","input":"Testing Silero fast synthesis"}' \ -o /tmp/silero_test.mp3 @echo "" @echo "Saved to /tmp/silero_test.mp3" test-chatterbox: @echo "Testing Chatterbox with emotion control..." curl -X POST http://localhost:8000/v1/audio/speech \ -H "Content-Type: application/json" \ -d '{"model":"tts-1-chatter","voice":"aria","input":"Testing emotional speech synthesis"}' \ -o /tmp/chatterbox_test.mp3 @echo "" @echo "Saved to /tmp/chatterbox_test.mp3" # ============================================================================ # Voice Downloads # ============================================================================ voices: voices-qwen @echo "Qwen3-TTS ready (model downloads automatically on first use)" voices-qwen: @echo "Downloading Qwen3-TTS voice samples from LibriSpeech..." @if [ -d ".venv" ]; then \ .venv/bin/python scripts/download_diverse_voices.py; \ else \ python3 scripts/download_diverse_voices.py; \ fi @echo "Qwen3-TTS voice samples ready in cloned-voices/" @echo " Model (~3.4GB) downloads automatically on first use" voices-f5: voices-qwen @echo "F5-TTS reuses the same cloned-voices/ samples as Qwen3-TTS" @echo " Model (~1.5GB: F5-TTS_v1 + Vocos) downloads automatically on first use" whisper-refs: @echo "Transcribing cloned-voices/*.wav with whisper-large-v3 (GPU)..." @if [ -d ".venv" ]; then \ .venv/bin/python scripts/whisper_refs.py; \ else \ python3 scripts/whisper_refs.py; \ fi @echo "Done. Review diff: git diff voice_to_speaker.default.yaml cloned-voices/voices_metadata.json" voices-all: voices-qwen voices-f5 voices-piper voices-xtts voices-silero @echo "All voices downloaded!" voices-piper: @echo "Downloading all Piper voices..." sudo docker exec $(CONTAINER_NAME) bash -c '\ set -e; \ BASE_URL="https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0"; \ download_voice() { \ local path="$$1"; \ local name="$$2"; \ mkdir -p "/app/voices/$$path"; \ cd "/app/voices/$$path"; \ echo "Downloading $$name..."; \ curl -f -L "$$BASE_URL/$$path/$$name.onnx" -o "$$name.onnx" || echo "Failed: $$name.onnx"; \ curl -f -L "$$BASE_URL/$$path/$$name.onnx.json" -o "$$name.onnx.json" || echo "Failed: $$name.onnx.json"; \ }; \ echo "=== English US voices ==="; \ download_voice "en/en_US/libritts_r/medium" "en_US-libritts_r-medium"; \ download_voice "en/en_US/amy/medium" "en_US-amy-medium"; \ download_voice "en/en_US/arctic/medium" "en_US-arctic-medium"; \ download_voice "en/en_US/bryce/medium" "en_US-bryce-medium"; \ download_voice "en/en_US/danny/low" "en_US-danny-low"; \ download_voice "en/en_US/hfc_female/medium" "en_US-hfc_female-medium"; \ download_voice "en/en_US/hfc_male/medium" "en_US-hfc_male-medium"; \ download_voice "en/en_US/joe/medium" "en_US-joe-medium"; \ download_voice "en/en_US/john/medium" "en_US-john-medium"; \ download_voice "en/en_US/kathleen/low" "en_US-kathleen-low"; \ download_voice "en/en_US/kristin/medium" "en_US-kristin-medium"; \ download_voice "en/en_US/kusal/medium" "en_US-kusal-medium"; \ download_voice "en/en_US/l2arctic/medium" "en_US-l2arctic-medium"; \ download_voice "en/en_US/lessac/medium" "en_US-lessac-medium"; \ download_voice "en/en_US/libritts/high" "en_US-libritts-high"; \ download_voice "en/en_US/ljspeech/medium" "en_US-ljspeech-medium"; \ download_voice "en/en_US/norman/medium" "en_US-norman-medium"; \ download_voice "en/en_US/reza_ibrahim/medium" "en_US-reza_ibrahim-medium"; \ download_voice "en/en_US/ryan/high" "en_US-ryan-high"; \ download_voice "en/en_US/sam/medium" "en_US-sam-medium"; \ echo "=== English GB voices ==="; \ download_voice "en/en_GB/northern_english_male/medium" "en_GB-northern_english_male-medium"; \ download_voice "en/en_GB/alan/medium" "en_GB-alan-medium"; \ download_voice "en/en_GB/alba/medium" "en_GB-alba-medium"; \ download_voice "en/en_GB/aru/medium" "en_GB-aru-medium"; \ download_voice "en/en_GB/cori/medium" "en_GB-cori-medium"; \ download_voice "en/en_GB/jenny_dioco/medium" "en_GB-jenny_dioco-medium"; \ download_voice "en/en_GB/semaine/medium" "en_GB-semaine-medium"; \ download_voice "en/en_GB/southern_english_female/low" "en_GB-southern_english_female-low"; \ download_voice "en/en_GB/vctk/medium" "en_GB-vctk-medium"; \ echo "=== Done ==="' voices-xtts: @echo "Downloading XTTS speaker samples..." sudo docker exec $(CONTAINER_NAME) bash -c 'cd /app && ./scripts/download_samples.sh' voices-kokoro: @echo "Downloading Kokoro TTS models..." sudo docker exec $(CONTAINER_NAME) bash -c '\ mkdir -p /app/voices/kokoro && \ cd /app/voices/kokoro && \ huggingface-cli download hexgrad/kokoro-82m --local-dir .' voices-silero: @echo "Downloading Silero TTS models..." sudo docker exec $(CONTAINER_NAME) bash -c '\ cd /app/voices && \ python3 -c "import torch; \ for lang in [\"en\", \"ru\", \"de\", \"es\", \"fr\"]: \ model, *_ = torch.hub.load(repo_or_dir=\"snakers4/silero-models\", model=\"silero_tts\", language=lang, speaker=\"v4_\"+lang if lang==\"en\" else \"v3_\"+lang); \ print(f\"Downloaded Silero {lang}\")"' voices-chatterbox: @echo "Downloading Chatterbox models..." sudo docker exec $(CONTAINER_NAME) bash -c '\ mkdir -p /app/voices/chatterbox && \ cd /app/voices/chatterbox && \ huggingface-cli download resemble-ai/chatterbox --local-dir .' # ============================================================================ # Git # ============================================================================ push-all: git push origin main git push github main # ============================================================================ # Stress Testing # ============================================================================ hydrate: @echo "Hydrating all TTS models by testing ALL voices..." @echo "" @mkdir -p /tmp/hydrate_test @curl -s http://localhost:8000/v1/voices | jq -r '.data[] as $$model | $$model.voices[] | "\($$model.id):\(.)"' > /tmp/hydrate_voices.txt @TOTAL=$$(wc -l < /tmp/hydrate_voices.txt); \ COUNT=0; \ FAILED=0; \ START_TIME=$$(date +%s); \ while IFS=: read -r MODEL VOICE; do \ COUNT=$$((COUNT + 1)); \ printf "[%3d/%3d] Testing %-20s %-30s ... " "$$COUNT" "$$TOTAL" "$$MODEL" "$$VOICE"; \ if curl -s -X POST http://localhost:8000/v1/audio/speech \ -H "Content-Type: application/json" \ -d "{\"voice\":\"$$VOICE\",\"input\":\"Hydration test\"}" \ -o /tmp/hydrate_test/$${MODEL}_$${VOICE}.mp3 2>&1 | grep -q "error"; then \ echo "FAILED"; \ FAILED=$$((FAILED + 1)); \ else \ SIZE=$$(stat -c%s /tmp/hydrate_test/$${MODEL}_$${VOICE}.mp3 2>/dev/null || echo 0); \ if [ "$$SIZE" -gt 1000 ]; then \ echo "OK ($${SIZE} bytes)"; \ else \ echo "SMALL ($${SIZE} bytes)"; \ FAILED=$$((FAILED + 1)); \ fi; \ fi; \ done < /tmp/hydrate_voices.txt; \ END_TIME=$$(date +%s); \ DURATION=$$((END_TIME - START_TIME)); \ echo ""; \ echo "Hydration complete!"; \ echo " Total voices: $$TOTAL"; \ echo " Successful: $$((TOTAL - FAILED))"; \ echo " Failed: $$FAILED"; \ echo " Duration: $${DURATION}s"; \ echo " Output: /tmp/hydrate_test/" load-test: @echo "Load testing TTS service with random concurrent requests..." @echo "" @mkdir -p /tmp/load_test @curl -s http://localhost:8000/v1/voices | jq -r '.data[] as $$model | $$model.voices[] | "\($$model.id):\(.)"' > /tmp/load_test_voices.txt @TOTAL_VOICES=$$(wc -l < /tmp/load_test_voices.txt); \ REQUESTS=100; \ CONCURRENT=10; \ echo "Available voices: $$TOTAL_VOICES"; \ echo "Total requests: $$REQUESTS"; \ echo "Concurrent: $$CONCURRENT"; \ echo ""; \ START_TIME=$$(date +%s); \ seq 1 $$REQUESTS | xargs -P$$CONCURRENT -I{} bash -c " \ TOTAL_VOICES=\$$(wc -l < /tmp/load_test_voices.txt); \ LINE=\$$((RANDOM % \$$TOTAL_VOICES + 1)); \ VOICE_SPEC=\$$(sed -n \"\$${LINE}p\" /tmp/load_test_voices.txt); \ MODEL=\$$(echo \$$VOICE_SPEC | cut -d: -f1); \ VOICE=\$$(echo \$$VOICE_SPEC | cut -d: -f2); \ NUM={}; \ START=\$$(date +%s%3N); \ if curl -s -X POST http://localhost:8000/v1/audio/speech \ -H 'Content-Type: application/json' \ -d '{\"model\":\"'\$$MODEL'\",\"voice\":\"'\$$VOICE'\",\"input\":\"Load test number '\$$NUM'\"}' \ -o /tmp/load_test/request_\$${NUM}.mp3 2>&1; then \ END=\$$(date +%s%3N); \ DURATION=\$$((END - START)); \ SIZE=\$$(stat -c%s /tmp/load_test/request_\$${NUM}.mp3 2>/dev/null || echo 0); \ printf '[%3d] %-20s %-25s %5dms %6d bytes\n' \"\$$NUM\" \"\$$MODEL\" \"\$$VOICE\" \"\$$DURATION\" \"\$$SIZE\"; \ else \ printf '[%3d] %-20s %-25s FAILED\n' \"\$$NUM\" \"\$$MODEL\" \"\$$VOICE\"; \ fi \ "; \ END_TIME=$$(date +%s); \ DURATION=$$((END_TIME - START_TIME)); \ SUCCESS=$$(ls /tmp/load_test/*.mp3 2>/dev/null | wc -l); \ VALID=$$(find /tmp/load_test -name '*.mp3' -size +1000c 2>/dev/null | wc -l); \ echo ""; \ echo "Load test complete!"; \ echo " Total requests: $$REQUESTS"; \ echo " Files created: $$SUCCESS"; \ echo " Valid audio (>1KB): $$VALID"; \ echo " Failed: $$((REQUESTS - VALID))"; \ echo " Duration: $${DURATION}s"; \ echo " Avg: $$((DURATION * 1000 / REQUESTS))ms per request"; \ echo " Throughput: $$((REQUESTS / DURATION)) req/s"; \ echo " Output: /tmp/load_test/"