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:
@ -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:
|
||||
|
||||
Reference in New Issue
Block a user