Makefile targets: - make hydrate: Sequential testing of ALL voices (227 total) - make load-test: 100 concurrent requests with random voices/models Load test results (with multiprocess workers): - 100 requests in 4 seconds (25 req/s) - 10 concurrent requests at a time - 100% success rate (no crashes!) - 15% voices returned full audio (voices downloaded) - 85% returned stub MP3s (voices not yet downloaded) Key insight: Server handles concurrent load perfectly with 4 workers - No deadlocks - No timeouts - Graceful handling even when voice files missing TODO: Run 'make voices' to download all Piper voices for full test
303 lines
14 KiB
Makefile
303 lines
14 KiB
Makefile
# Raccoon Mission: UncloseAI Speech Development Makefile
|
|
# Deploy to remote server with ease
|
|
# Configuration is loaded from vars.sh (copy vars.sh.example to vars.sh)
|
|
|
|
# Load configuration from vars.sh if it exists
|
|
ifneq (,$(wildcard vars.sh))
|
|
include vars.sh
|
|
export
|
|
endif
|
|
|
|
# Fallback defaults if vars.sh is not found
|
|
REMOTE_HOST ?= localhost
|
|
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
|
|
|
|
help:
|
|
@echo "🦝 Raccoon TTS Mission - Development Commands"
|
|
@echo ""
|
|
@echo "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 ""
|
|
@echo "Container:"
|
|
@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)"
|
|
|
|
sync:
|
|
@echo "📦 Syncing files to $(REMOTE_HOST)..."
|
|
rsync -avz --exclude '.git' --exclude '__pycache__' --exclude '*.pyc' \
|
|
--exclude 'voices/*' --exclude 'config/voice_to_speaker.yaml' \
|
|
./ $(REMOTE_USER)@$(REMOTE_HOST):$(REMOTE_PATH)/
|
|
@echo "📝 Ensuring speech.env exists..."
|
|
ssh $(REMOTE_USER)@$(REMOTE_HOST) "cd $(REMOTE_PATH) && [ -f speech.env ] || cp sample.env speech.env"
|
|
|
|
deploy: sync restart
|
|
@echo "✅ Deployment complete!"
|
|
|
|
restart:
|
|
@echo "🔄 Rebuilding and restarting container on $(REMOTE_HOST)..."
|
|
ssh $(REMOTE_USER)@$(REMOTE_HOST) "cd $(REMOTE_PATH) && docker compose up -d --build"
|
|
|
|
stop:
|
|
@echo "🛑 Stopping container on $(REMOTE_HOST)..."
|
|
ssh $(REMOTE_USER)@$(REMOTE_HOST) "cd $(REMOTE_PATH) && docker compose down"
|
|
|
|
start:
|
|
@echo "▶️ Starting container on $(REMOTE_HOST)..."
|
|
ssh $(REMOTE_USER)@$(REMOTE_HOST) "cd $(REMOTE_PATH) && docker compose up -d"
|
|
|
|
clean: stop
|
|
@echo "🧹 Removing container..."
|
|
ssh $(REMOTE_USER)@$(REMOTE_HOST) "cd $(REMOTE_PATH) && docker compose down -v"
|
|
|
|
logs:
|
|
@echo "📋 Tailing logs from $(REMOTE_HOST)..."
|
|
ssh $(REMOTE_USER)@$(REMOTE_HOST) "docker logs -f $(CONTAINER_NAME)"
|
|
|
|
test:
|
|
@echo "🧪 Testing TTS endpoint..."
|
|
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
|
|
@echo "✅ Test complete! Playing audio..."
|
|
@firefox /tmp/raccoon_test.mp3 || mpv /tmp/raccoon_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-piper:
|
|
@echo "🎤 Downloading all Piper voices..."
|
|
ssh $(REMOTE_USER)@$(REMOTE_HOST) "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 to download \$$name.onnx\"; \
|
|
curl -f -L \"\$$BASE_URL/\$$path/\$$name.onnx.json\" -o \"\$$name.onnx.json\" || echo \"Failed to download \$$name.onnx.json\"; \
|
|
}; \
|
|
echo \"=== Downloading 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 \"=== Downloading 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 \"=== All Piper voices downloaded! ===\"'"
|
|
@echo "✅ All Piper voices downloaded!"
|
|
|
|
voices-xtts:
|
|
@echo "🎤 Downloading XTTS speaker samples..."
|
|
ssh $(REMOTE_USER)@$(REMOTE_HOST) "docker exec $(CONTAINER_NAME) bash -c 'cd /app && ./scripts/download_samples.sh'"
|
|
@echo "✅ XTTS speaker samples downloaded!"
|
|
|
|
voices-kokoro:
|
|
@echo "🎤 Downloading Kokoro TTS models..."
|
|
ssh $(REMOTE_USER)@$(REMOTE_HOST) "docker exec $(CONTAINER_NAME) bash -c '\
|
|
mkdir -p /app/voices/kokoro && \
|
|
cd /app/voices/kokoro && \
|
|
huggingface-cli download hexgrad/kokoro-82m --local-dir .'"
|
|
@echo "✅ Kokoro models downloaded!"
|
|
|
|
test-kokoro:
|
|
@echo "🧪 Testing Kokoro fast synthesis..."
|
|
curl -X POST http://$(REMOTE_HOST):8000/v1/audio/speech \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"model":"tts-1-kokoro","voice":"alloy","input":"Testing Kokoro fast decoder synthesis"}' \
|
|
-o /tmp/kokoro_test.mp3
|
|
@echo "✅ Test complete! Playing audio..."
|
|
@firefox /tmp/kokoro_test.mp3 || mpv /tmp/kokoro_test.mp3 || echo "Install firefox or mpv to play audio"
|
|
|
|
test-xtts:
|
|
@echo "🧪 Testing XTTS HD endpoint (this may take 1-2 minutes on first run)..."
|
|
curl -X POST http://$(REMOTE_HOST):8000/v1/audio/speech \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"model":"tts-1-hd","voice":"alloy","input":"Testing XTTS high definition"}' \
|
|
-o /tmp/xtts_test.mp3
|
|
@echo "✅ Test complete! Playing audio..."
|
|
@firefox /tmp/xtts_test.mp3 || mpv /tmp/xtts_test.mp3 || echo "Install firefox or mpv to play audio"
|
|
|
|
push-all:
|
|
@echo "🚀 Pushing to all remotes..."
|
|
git push origin main
|
|
git push github main
|
|
@echo "✅ Pushed to origin and github!"
|
|
|
|
voices-silero:
|
|
@echo "🎤 Downloading Silero TTS models..."
|
|
ssh $(REMOTE_USER)@$(REMOTE_HOST) "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}\"\")\"'"
|
|
@echo "✅ Silero models downloaded!"
|
|
|
|
test-silero:
|
|
@echo "🧪 Testing Silero endpoint..."
|
|
curl -X POST http://$(REMOTE_HOST):8000/v1/audio/speech \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"model":"tts-1-silero","voice":"alloy","input":"Testing Silero fast synthesis"}' \
|
|
-o /tmp/silero_test.mp3
|
|
@echo "✅ Test complete! Playing audio..."
|
|
@firefox /tmp/silero_test.mp3 || mpv /tmp/silero_test.mp3 || echo "Install firefox or mpv to play audio"
|
|
|
|
voices-chatterbox:
|
|
@echo "🎤 Downloading Chatterbox models..."
|
|
ssh $(REMOTE_USER)@$(REMOTE_HOST) "docker exec $(CONTAINER_NAME) bash -c '\
|
|
mkdir -p /app/voices/chatterbox && \
|
|
cd /app/voices/chatterbox && \
|
|
huggingface-cli download resemble-ai/chatterbox --local-dir .'"
|
|
@echo "✅ Chatterbox models downloaded!"
|
|
|
|
test-chatterbox:
|
|
@echo "🧪 Testing Chatterbox with emotion control..."
|
|
curl -X POST http://$(REMOTE_HOST):8000/v1/audio/speech \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"model":"tts-1-chatter","voice":"alloy","input":"Testing emotional speech synthesis"}' \
|
|
-o /tmp/chatterbox_test.mp3
|
|
@echo "✅ Test complete! Playing audio..."
|
|
@firefox /tmp/chatterbox_test.mp3 || mpv /tmp/chatterbox_test.mp3 || echo "Install firefox or mpv to play audio"
|
|
|
|
hydrate:
|
|
@echo "🦝 Hydrating all TTS models by testing ALL voices..."
|
|
@echo "This will test all 227 voices across 4 engines (Piper, XTTS, Silero, Kokoro)"
|
|
@echo ""
|
|
@mkdir -p /tmp/hydrate_test
|
|
@curl -s http://$(REMOTE_HOST):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://$(REMOTE_HOST):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 "This will send 100 concurrent requests with random voices across all models"
|
|
@echo ""
|
|
@mkdir -p /tmp/load_test
|
|
@curl -s http://$(REMOTE_HOST):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://$(REMOTE_HOST):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/"
|