#!/usr/bin/env python3 """Small standard-library client for the bundled llama-compatible TTS API.""" from __future__ import annotations import argparse import base64 import json import urllib.error import urllib.request from pathlib import Path def _parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Generate speech with VoxCPM2 FP8 W8A8 V1") parser.add_argument("--url", default="http://127.0.0.1:8080/v1/audio/speech") parser.add_argument("--text", required=True) parser.add_argument("--output", type=Path, default=Path("output.wav")) parser.add_argument("--reference-audio", type=Path) parser.add_argument("--reference-text", default="") parser.add_argument("--seed", type=int, default=42) parser.add_argument("--cfg-value", type=float, default=2.0) parser.add_argument("--inference-timesteps", type=int, default=10) parser.add_argument("--max-steps", type=int, default=200) parser.add_argument("--temperature", type=float, default=1.0) parser.add_argument("--timeout", type=float, default=600.0) parser.add_argument("--force", action="store_true") return parser def main() -> None: args = _parser().parse_args() output = args.output.expanduser().resolve() if output.exists() and not args.force: raise SystemExit(f"Refusing to overwrite {output}; pass --force to replace it") payload = { "model": "voxcpm", "input": args.text, "voice": "default", "response_format": "wav", "seed": args.seed, "cfg_value": args.cfg_value, "inference_timesteps": args.inference_timesteps, "max_steps": args.max_steps, "temperature": args.temperature, } if args.reference_audio is not None: reference = args.reference_audio.expanduser().resolve() payload["reference_audio"] = base64.b64encode(reference.read_bytes()).decode() if args.reference_text: payload["reference_text"] = args.reference_text request = urllib.request.Request( args.url, data=json.dumps(payload).encode(), headers={"Content-Type": "application/json"}, ) try: with urllib.request.urlopen(request, timeout=args.timeout) as response: content_type = response.headers.get_content_type() if content_type != "audio/wav": raise RuntimeError(f"Expected audio/wav, received {content_type}") audio = response.read() except urllib.error.HTTPError as exc: detail = exc.read().decode(errors="replace") raise SystemExit(f"TTS request failed with HTTP {exc.code}: {detail}") from exc output.parent.mkdir(parents=True, exist_ok=True) output.write_bytes(audio) print(f"Wrote {len(audio)} bytes to {output}") if __name__ == "__main__": main()