hypertwist/Solvers/piper/piper_tts_wrapper.py
2026-06-11 06:32:20 +00:00

99 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""
HyperTwist IPC wrapper for rhasspy/piper TTS engine.
Usage:
python piper_tts_wrapper.py --text "Hello world" --model path/to/model.onnx --output output.wav
Returns:
Path to generated WAV file on success
"""
import argparse
import os
import shutil
import subprocess
def find_piper_exe():
"""Find the piper executable in known HyperTwist solver paths or PATH."""
script_dir = os.path.dirname(os.path.abspath(__file__))
candidates = [
os.path.join(script_dir, "piper", "piper", "piper.exe"),
os.path.join(script_dir, "..", "..", "..", "Binaries", "Win64", "Solvers", "piper", "piper", "piper.exe"),
os.path.join(script_dir, "piper", "piper.exe"),
os.path.join(script_dir, "..", "piper", "piper", "piper.exe"),
]
for candidate in candidates:
resolved = os.path.abspath(candidate)
if os.path.isfile(resolved):
return resolved
for candidate in ("piper.exe", "piper"):
resolved = shutil.which(candidate)
if resolved:
return os.path.abspath(resolved)
return None
def find_espeak_data(piper_exe):
"""Locate espeak-ng-data directory next to the piper executable."""
exe_dir = os.path.dirname(os.path.abspath(piper_exe))
candidates = [
os.path.join(exe_dir, "espeak-ng-data"),
os.path.join(exe_dir, "..", "espeak-ng-data"),
]
for candidate in candidates:
resolved = os.path.abspath(candidate)
if os.path.isdir(resolved):
return resolved
return None
def synthesize(text: str, model_path: str, config_path: str, output_path: str) -> str:
"""Synthesize speech using piper."""
if not os.path.exists(model_path):
return f"ERROR: Model not found: {model_path}"
if not os.path.exists(config_path):
return f"ERROR: Config not found: {config_path}"
piper_exe = find_piper_exe()
if not piper_exe:
return "ERROR: piper executable not found. Place it in Solvers/piper/piper/ or add to PATH."
espeak_data = find_espeak_data(piper_exe)
output_dir = os.path.dirname(os.path.abspath(output_path))
if output_dir:
os.makedirs(output_dir, exist_ok=True)
cmd = [
piper_exe,
"--model", os.path.abspath(model_path),
"--config", os.path.abspath(config_path),
"--output_file", os.path.abspath(output_path),
]
if espeak_data:
cmd.extend(["--espeak_data", espeak_data])
try:
result = subprocess.run(cmd, input=text, text=True, capture_output=True, timeout=60)
if result.returncode == 0:
if not os.path.exists(output_path):
return f"ERROR: Piper reported success but did not create output: {output_path}"
return os.path.abspath(output_path)
return f"ERROR: {result.stderr}"
except Exception as e:
return f"ERROR: {e}"
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="HyperTwist Piper TTS Wrapper")
parser.add_argument("--text", required=True, help="Text to synthesize")
parser.add_argument("--model", required=True, help="Path to .onnx voice model")
parser.add_argument("--config", help="Path to model config JSON")
parser.add_argument("--output", default="output.wav", help="Output WAV path")
args = parser.parse_args()
config_path = args.config or args.model.replace(".onnx", ".onnx.json")
result = synthesize(args.text, args.model, config_path, args.output)
print(result)