feat: Add XTTS v2 support, refactor Docker/GPU infra, and improve Piper engine

- Add XTTS v2 configuration to .env.example
- Refactor Dockerfile to multi-stage build with CUDA 12.1 support
- Update Makefile with Kokoro and XTTS test environment targets
- Refactor Piper engine (app/engines/piper.py) to use python module execution
- Add comprehensive documentation for Kokoro and XTTS plans
- Add helper scripts and patches for build process
This commit is contained in:
2025-12-13 11:37:58 +01:00
parent d6d1fe9d23
commit 91887ae296
19 changed files with 1625 additions and 46 deletions

View File

@ -6,7 +6,7 @@ Version: v0.1.1
Description:
Piper TTS engine adapter: real CLI invocation + output as WAV, OGG, or MP3.
Synthesizes WAV via Piper, converts to OGG/MP3 via ffmpeg-python if needed.
Uses dynamic model path: ./models/piper/[model]/model.onnx
Uses dynamic model path: settings.MODELS_DIR/piper/[model]/model.onnx
Author: Abby (ChatGPT)
Date: 2025-07-23
@ -28,14 +28,16 @@ logger = logging.getLogger(__name__)
class PiperEngine(TTSEngineBase):
def __init__(self):
self.piper_executable = shutil.which("piper")
# Use python -m piper instead of direct script execution
# This avoids shebang issues in multi-stage Docker builds
self.python_executable = shutil.which("python3.11") or shutil.which("python3") or shutil.which("python")
self.use_module_execution = True
self.ffmpeg_executable = shutil.which("ffmpeg")
self.PIPER_TIMEOUT_SECONDS = settings.PIPER_TIMEOUT_SECONDS
self.FFMPEG_TIMEOUT_SECONDS = settings.FFMPEG_TIMEOUT_SECONDS
def _load_config(self, model: str):
"""Load the model config JSON file to get speaker mappings."""
model_dir = f"/models/piper/{model}"
model_dir = os.path.join(settings.MODELS_DIR, "piper", model)
config_file = os.path.join(model_dir, f"{model}.onnx.json")
if os.path.isfile(config_file):
with open(config_file, 'r') as f:
@ -89,13 +91,13 @@ class PiperEngine(TTSEngineBase):
- Bug #7: Enhanced error context and logging
"""
# Validation
if not self.piper_executable:
raise RuntimeError("Piper executable not found. Please install it and ensure it's in your PATH.")
if not self.python_executable:
raise RuntimeError("Python executable not found. Cannot execute piper module.")
if not model:
raise ValueError("Model must be specified for Piper.")
# Validate model and config files (Bug #6)
model_dir = f"/models/piper/{model}"
model_dir = os.path.join(settings.MODELS_DIR, "piper", model)
model_file = os.path.join(model_dir, f"{model}.onnx")
config_file = os.path.join(model_dir, f"{model}.onnx.json")
@ -136,8 +138,8 @@ class PiperEngine(TTSEngineBase):
os.close(fd)
temp_files_to_cleanup.append(output_wav_path)
# Build command (Bug #1 - no --stdin flag, piper reads stdin by default)
cmd = [self.piper_executable, "--model", model_file, "--output-file", output_wav_path]
# Build command - use python -m piper to avoid shebang issues
cmd = [self.python_executable, "-m", "piper", "--model", model_file, "--output-file", output_wav_path]
if speaker:
speaker_id = self._get_speaker_id(speaker, model)
@ -279,7 +281,7 @@ class PiperEngine(TTSEngineBase):
logger.warning(f"Failed to cleanup temp file {temp_file}: {e}")
def list_models(self):
models_dir = "/models/piper/"
models_dir = os.path.join(settings.MODELS_DIR, "piper")
if not os.path.isdir(models_dir):
return []
return [name for name in os.listdir(models_dir)
@ -296,13 +298,13 @@ class PiperEngine(TTSEngineBase):
def healthcheck(self):
status = "ok"
if not self.piper_executable:
status = "missing_piper_executable"
if not self.python_executable:
status = "missing_python_executable"
return {"status": status, "engine": "piper"}
async def selftest(self):
if not self.piper_executable:
return {"selftest": False, "error": "Piper executable not found.", "engine": "piper"}
if not self.python_executable:
return {"selftest": False, "error": "Python executable not found.", "engine": "piper"}
try:
models = self.list_models()
if not models: