Files
vllm-omni/examples/online_serving/text_to_speech/auk/speech_client.py
T
2026-09-30 00:18:38 +00:00

104 lines
4.4 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""AuK speech generation and audio editing through /v1/audio/speech.
See the online TTS user guide for checkpoint preparation and serving. Local
reference audio is sent as a data URL; it need not exist on the server.
"""
from __future__ import annotations
import argparse
import base64
import mimetypes
import os
from pathlib import Path
import httpx
def reference_audio(value: str) -> str:
if value.startswith(("http://", "https://", "data:")):
return value
path = Path(value).expanduser()
mime = mimetypes.guess_type(path.name)[0] or "audio/wav"
return f"data:{mime};base64,{base64.b64encode(path.read_bytes()).decode('ascii')}"
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--api-base", default="http://localhost:8091")
parser.add_argument("--api-key", default=os.environ.get("OPENAI_API_KEY", "EMPTY"))
parser.add_argument("--model", required=True, help="Model name or assembled bundle path used by the server")
parser.add_argument(
"--instructions",
required=True,
help=(
"Complete AuK instruction after filling the cookbook template "
"(https://github.com/Tencent-Hunyuan/AuK/blob/main/docs/COOKBOOK.md)"
),
)
parser.add_argument("--ref-audio", help="Reference/source audio: local file, HTTP URL or data URL")
parser.add_argument("--duration-seconds", type=float, help="Target duration; defaults to source length with audio")
parser.add_argument("--seed", type=int)
parser.add_argument("--num-inference-steps", type=int)
parser.add_argument("--guidance-scale", type=float)
parser.add_argument("--sway", type=float)
parser.add_argument("--t-grid", type=float, nargs="+", help="Explicit increasing Euler time grid")
parser.add_argument("--vae-sample", action="store_true", help="Sample the VAE posterior instead of taking its mean")
parser.add_argument("--stream", action="store_true", help="Receive raw PCM over HTTP after full generation")
parser.add_argument("--response-format", choices=["wav", "pcm", "flac", "mp3", "opus"], default="wav")
parser.add_argument("--output", type=Path, help="Output file; defaults to output.wav or output.pcm")
parser.add_argument("--timeout", type=float, default=300)
args = parser.parse_args()
if args.duration_seconds is None and args.ref_audio is None:
parser.error("--duration-seconds is required without --ref-audio")
response_format = "pcm" if args.stream else args.response_format
payload = {
"model": args.model,
# OpenAI's shared Speech schema still declares input, while AuK's
# canonical request carries all content in the complete instruction.
"input": "",
"voice": "default",
"response_format": response_format,
}
for name in ("duration_seconds", "seed"):
value = getattr(args, name)
if value is not None:
payload[name] = value
payload["instructions"] = args.instructions
if args.ref_audio:
payload["ref_audio"] = reference_audio(args.ref_audio)
extra = {
name: getattr(args, name)
for name in ("num_inference_steps", "guidance_scale", "sway", "t_grid")
if getattr(args, name) is not None
}
if args.vae_sample:
extra["vae_sample"] = True
if extra:
payload["extra_params"] = extra
if args.stream:
payload.update(stream=True, stream_format="audio")
output = args.output or Path(f"output.{response_format}")
headers = {"Authorization": f"Bearer {args.api_key}"}
with httpx.Client(timeout=args.timeout) as client:
with client.stream(
"POST", f"{args.api_base.rstrip('/')}/v1/audio/speech", json=payload, headers=headers
) as response:
if response.is_error:
response.read()
raise RuntimeError(f"Speech API returned {response.status_code}: {response.text}")
with output.open("wb") as handle:
for chunk in response.iter_bytes():
handle.write(chunk)
print(f"Saved {output} ({output.stat().st_size} bytes). Native audio: 24000 Hz, mono.")
if response_format == "pcm":
print("PCM format: signed 16-bit little-endian, 24000 Hz, mono.")
if __name__ == "__main__":
main()