File size: 3,640 Bytes
16f5171 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | #!/usr/bin/env python
"""Call a project Fish TTS API with a zero-shot voice or saved reference ID."""
from __future__ import annotations
import argparse
import base64
from pathlib import Path
import requests
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--url", default="http://127.0.0.1:8080/v1/tts")
parser.add_argument("--text", required=True)
voice = parser.add_mutually_exclusive_group()
voice.add_argument("--reference-audio", type=Path)
voice.add_argument("--reference-id")
parser.add_argument("--reference-text")
parser.add_argument("--output", type=Path, default=Path("output.wav"))
parser.add_argument("--api-key")
parser.add_argument("--seed", type=int)
parser.add_argument("--max-new-tokens", type=int, default=1024)
parser.add_argument("--chunk-length", type=int, default=200)
parser.add_argument("--top-p", type=float, default=0.9)
parser.add_argument("--temperature", type=float, default=0.9)
parser.add_argument("--repetition-penalty", type=float, default=1.1)
parser.add_argument(
"--normalize",
action=argparse.BooleanOptionalAction,
default=True,
help="Enable or disable API loudness normalization",
)
parser.add_argument(
"--use-memory-cache",
choices=("on", "off"),
default="on",
help="Reuse cached reference codes/text, or force a fresh reference encode",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
references = []
if args.reference_audio is not None:
if not args.reference_audio.is_file():
raise SystemExit(f"Reference audio is missing: {args.reference_audio}")
if not args.reference_text or not args.reference_text.strip():
raise SystemExit("--reference-text is required with --reference-audio")
references.append(
{
"audio": base64.b64encode(args.reference_audio.read_bytes()).decode(
"ascii"
),
"text": args.reference_text,
}
)
elif args.reference_text:
raise SystemExit("--reference-text requires --reference-audio")
payload = {
"text": args.text,
"references": references,
"reference_id": args.reference_id,
"format": "wav",
"streaming": False,
"normalize": args.normalize,
"max_new_tokens": args.max_new_tokens,
"chunk_length": args.chunk_length,
"top_p": args.top_p,
"temperature": args.temperature,
"repetition_penalty": args.repetition_penalty,
"seed": args.seed,
"use_memory_cache": args.use_memory_cache,
}
headers = {"Accept": "audio/wav"}
if args.api_key:
headers["Authorization"] = f"Bearer {args.api_key}"
response = requests.post(
args.url,
json=payload,
headers=headers,
timeout=600,
)
if response.status_code != 200:
raise SystemExit(
f"TTS request failed ({response.status_code}): {response.text[:2000]}"
)
content_type = response.headers.get("content-type", "")
if "audio/" not in content_type and not response.content.startswith(b"RIFF"):
raise SystemExit(f"Unexpected response content type: {content_type}")
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_bytes(response.content)
print(f"audio={args.output}")
print(f"bytes={len(response.content)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
|