///|
/// A request to the `/audio/speech` (text-to-speech) endpoint.
pub(all) struct SpeechRequest {
model : String
input : String
voice : String
mut response_format : String?
mut speed : Double?
}
///|
/// Create a text-to-speech request.
pub fn SpeechRequest::new(
model : String,
input : String,
voice : String,
) -> SpeechRequest {
{ model, input, voice, response_format: None, speed: None }
}
///|
/// Set the audio output format, e.g. `"mp3"`, `"wav"`, `"opus"`.
pub fn SpeechRequest::response_format(
self : SpeechRequest,
fmt : String,
) -> SpeechRequest {
self.response_format = Some(fmt)
self
}
///|
/// Set the playback speed (0.25–4.0).
pub fn SpeechRequest::speed(self : SpeechRequest, s : Double) -> SpeechRequest {
self.speed = Some(s)
self
}
///|
pub impl ToJson for SpeechRequest with fn to_json(self : SpeechRequest) -> Json {
let obj : Map[String, Json] = {
"model": Json::string(self.model),
"input": Json::string(self.input),
"voice": Json::string(self.voice),
}
if self.response_format is Some(f) {
obj["response_format"] = Json::string(f)
}
if self.speed is Some(s) {
obj["speed"] = Json::number(s)
}
Json::object(obj)
}
///|
/// Synthesize speech from text. Returns the raw audio bytes.
pub async fn Client::speech(
self : Client,
request : SpeechRequest,
) -> Bytes raise LLMError {
let http = self.http()
let resp = http
.post(self.endpoint("/audio/speech"))
.json(request.to_json())
.send() catch {
err => raise Transport(err.to_string())
}
let code = resp.response.code
guard code >= 200 && code < 300 else {
let text = resp.text() catch { _ => "" }
raise ApiError(code~, message=text)
}
resp.binary()
}
///|
/// A response from the `/audio/transcriptions` endpoint.
pub(all) struct TranscriptionResponse {
text : String
language : String?
duration : Double?
} derive(Debug)
///|
pub impl @json.FromJson for TranscriptionResponse with fn from_json(
json : Json,
path : @json.JsonPath,
) -> TranscriptionResponse {
guard json is Object(obj) else {
raise @json.JsonDecodeError(
(path, "TranscriptionResponse: expected object"),
)
}
let text = match obj.get("text") {
Some(String(s)) => s
_ => ""
}
let language = match obj.get("language") {
Some(String(s)) => Some(s)
_ => None
}
let duration = match obj.get("duration") {
Some(Number(n, ..)) => Some(n)
_ => None
}
{ text, language, duration }
}
///|
/// Parse a transcription response from raw JSON text (as returned by the API).
///
/// The transcription endpoint itself is multipart/form-data for the audio
/// upload, which is out of scope for the JSON client; this helper covers the
/// response side so callers using a multipart layer can decode the result.
pub fn parse_transcription(
json_text : String,
) -> TranscriptionResponse raise LLMError {
let json = @json.parse(json_text) catch {
err => raise Decode(err.to_string())
}
@json.from_json(json) catch {
err => raise Decode(err.to_string())
}
}
///|
/// The supported audio output formats for speech synthesis.
pub let speech_formats : Array[String] = [
"mp3", "opus", "aac", "flac", "wav", "pcm",
]
///|
/// Whether `fmt` is a recognized speech output format.
pub fn is_valid_speech_format(fmt : String) -> Bool {
for f in speech_formats {
if f == fmt {
return true
}
}
false
}