chatterbox-test / chatterbox_invocation.py
ShubhamSantoki's picture
Upload 7 files
e8f1930 verified
Raw History Blame Contribute Delete
5.45 kB
"""Invoke the Chatterbox Multilingual TTS Azure ML online endpoint.
Request/response schema here is taken directly from the deployed scoring
script (chatterbox-tts-endpoint/deployment/score.py), not guessed:
request {text, language_id, audio_prompt_base64?, exaggeration?,
cfg_weight?, temperature?}
response {audio_base64, sample_rate, format: "wav", language_id}
or {"error": "..."} (score.py can return an error body with a
2xx status for request-validation failures, so callers must
check for an "error" key rather than trusting response.ok alone)
"""
import argparse
import json
import os
from typing import Any, Optional
import requests
from config import DEFAULT_URL, DEFAULT_DEPLOYMENT
class ChatterboxError(RuntimeError):
"""Raised when the endpoint returns a validation/error body."""
def invoke_chatterbox(
text: str,
api_key: str,
url: str = DEFAULT_URL,
language_id: str = "en",
audio_prompt_base64: Optional[str] = None,
exaggeration: Optional[float] = None,
cfg_weight: Optional[float] = None,
temperature: Optional[float] = None,
deployment_name: Optional[str] = DEFAULT_DEPLOYMENT,
timeout: float = 60.0,
) -> dict:
"""Call the Chatterbox TTS endpoint and return the parsed JSON response.
Args:
text: Text to synthesize.
api_key: Primary/secondary key, AMLToken, or Microsoft Entra ID token for the endpoint.
url: Endpoint scoring URL.
language_id: One of score.py's SUPPORTED_LANGUAGES (see resolve_languages.py
to fetch the live list from the endpoint itself).
audio_prompt_base64: Base64-encoded WAV reference clip for zero-shot voice
cloning (<=10MB decoded). Omit for the default voice.
exaggeration: Emotional intensity, 0.0-2.0 (endpoint default 0.5).
cfg_weight: Pacing/deliberateness, 0.0-1.0 (endpoint default 0.5).
temperature: Delivery variability, 0.0-2.0 (endpoint default 0.8).
deployment_name: Specific deployment to target via the
"azureml-model-deployment" header. Pass None to let the
endpoint's traffic rules decide.
timeout: Request timeout in seconds.
Returns:
The parsed JSON response body: {"audio_base64", "sample_rate", "format", "language_id"}.
Raises:
ValueError: If api_key or text is empty.
ChatterboxError: If the endpoint returns an {"error": ...} body.
requests.HTTPError: If the HTTP request itself fails.
"""
if not api_key:
raise ValueError("A key should be provided to invoke the endpoint")
if not text or not text.strip():
raise ValueError("text must be non-empty")
request_body: dict[str, Any] = {"text": text, "language_id": language_id}
if exaggeration is not None:
request_body["exaggeration"] = exaggeration
if cfg_weight is not None:
request_body["cfg_weight"] = cfg_weight
if temperature is not None:
request_body["temperature"] = temperature
if audio_prompt_base64:
request_body["audio_prompt_base64"] = audio_prompt_base64
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
}
if deployment_name:
headers["azureml-model-deployment"] = deployment_name
response = requests.post(url, json=request_body, headers=headers, timeout=timeout)
if not response.ok:
print("Response headers:", dict(response.headers))
print("Response body:", response.text)
response.raise_for_status()
result = response.json()
if isinstance(result, dict) and "error" in result:
raise ChatterboxError(result["error"])
return result
def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Invoke the Chatterbox TTS endpoint")
parser.add_argument("--text", default="Hello, this is a test message for Chatterbox text to speech.")
parser.add_argument("--api-key", default=os.environ.get("CHATTERBOX_API_KEY", ""))
parser.add_argument("--url", default=DEFAULT_URL)
parser.add_argument("--language-id", default="en")
parser.add_argument("--audio-prompt-path", default=None, help="Local WAV file for voice cloning")
parser.add_argument("--exaggeration", type=float, default=None)
parser.add_argument("--cfg-weight", type=float, default=None)
parser.add_argument("--temperature", type=float, default=None)
parser.add_argument("--deployment-name", default=DEFAULT_DEPLOYMENT)
parser.add_argument("--timeout", type=float, default=60.0)
return parser.parse_args()
if __name__ == "__main__":
from audio_utils import encode_reference_clip
args = _parse_args()
audio_prompt_b64 = encode_reference_clip(args.audio_prompt_path) if args.audio_prompt_path else None
result = invoke_chatterbox(
text=args.text,
api_key=args.api_key,
url=args.url,
language_id=args.language_id,
audio_prompt_base64=audio_prompt_b64,
exaggeration=args.exaggeration,
cfg_weight=args.cfg_weight,
temperature=args.temperature,
deployment_name=args.deployment_name,
timeout=args.timeout,
)
printable = {k: v for k, v in result.items() if k != "audio_base64"}
printable["audio_base64_len"] = len(result.get("audio_base64", ""))
print(json.dumps(printable, indent=2))