#!/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)