Runs scripts/download_diverse_voices.py which pulls speaker samples from LibriSpeech and assigns permanent human names via voice registry. Works with venv or system python. Also adds Voices section to help.
347 lines
13 KiB
Makefile
347 lines
13 KiB
Makefile
# 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-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 "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 venv - Create Python virtual environment"
|
|
@echo " make venv-run - Run server in virtual environment"
|
|
@echo ""
|
|
@echo "Testing:"
|
|
@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 "Voices:"
|
|
@echo " make voices-qwen - Download Qwen3-TTS cloned voice samples"
|
|
@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)
|
|
|
|
# ============================================================================
|
|
# 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-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-all: voices-qwen 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/"
|