Skip to main content

aither_openai/
audio.rs

1use crate::{
2    client::{Config, OpenAI},
3    error::OpenAIError,
4};
5use aither_core::audio::{AudioGenerator, AudioTranscriber, Data};
6use futures_lite::StreamExt;
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9use zenwave::{
10    Client, client, header,
11    multipart::{MultipartPart, encode as encode_multipart},
12};
13
14impl AudioGenerator for OpenAI {
15    type Error = OpenAIError;
16
17    fn generate(
18        &self,
19        prompt: &str,
20    ) -> impl futures_core::Stream<Item = Result<Data, Self::Error>> + Send {
21        let cfg = self.config();
22        let text = prompt.to_owned();
23        futures_lite::stream::iter(vec![synthesize(cfg, text)]).then(|fut| fut)
24    }
25}
26
27impl AudioTranscriber for OpenAI {
28    type Error = OpenAIError;
29
30    fn transcribe(
31        &self,
32        audio: &[u8],
33    ) -> impl futures_core::Stream<Item = Result<String, Self::Error>> + Send {
34        let cfg = self.config();
35        let payload = audio.to_vec();
36        futures_lite::stream::iter(vec![transcribe_once(cfg, payload)]).then(|fut| fut)
37    }
38}
39
40async fn synthesize(cfg: Arc<Config>, text: String) -> Result<Vec<u8>, OpenAIError> {
41    let endpoint = cfg.request_url("/audio/speech");
42    let mut backend = client();
43    let mut builder = backend
44        .post(endpoint)
45        .map_err(OpenAIError::Http)?
46        .header(header::AUTHORIZATION.as_str(), cfg.request_auth())
47        .map_err(OpenAIError::Http)?
48        .header(header::USER_AGENT.as_str(), "aither-openai/0.1")
49        .map_err(OpenAIError::Http)?;
50    if let Some(org) = &cfg.organization {
51        builder = builder
52            .header("OpenAI-Organization", org.clone())
53            .map_err(OpenAIError::Http)?;
54    }
55
56    let request = SpeechRequest {
57        model: &cfg.audio_model,
58        input: &text,
59        voice: &cfg.audio_voice,
60        format: &cfg.audio_format,
61    };
62    let bytes = builder
63        .json_body(&request)
64        .map_err(OpenAIError::Http)?
65        .bytes()
66        .await
67        .map_err(OpenAIError::Http)?;
68
69    Ok(bytes.to_vec())
70}
71
72async fn transcribe_once(cfg: Arc<Config>, audio: Vec<u8>) -> Result<String, OpenAIError> {
73    let endpoint = cfg.request_url("/audio/transcriptions");
74    let mut backend = client();
75    let mut builder = backend
76        .post(endpoint)
77        .map_err(OpenAIError::Http)?
78        .header(header::AUTHORIZATION.as_str(), cfg.request_auth())
79        .map_err(OpenAIError::Http)?
80        .header(header::USER_AGENT.as_str(), "aither-openai/0.1")
81        .map_err(OpenAIError::Http)?;
82    if let Some(org) = &cfg.organization {
83        builder = builder
84            .header("OpenAI-Organization", org.clone())
85            .map_err(OpenAIError::Http)?;
86    }
87
88    let parts = vec![
89        MultipartPart::text("model", cfg.transcription_model.clone()),
90        MultipartPart::text("response_format", "json"),
91        MultipartPart::binary("file", "audio.wav", "application/octet-stream", audio),
92    ];
93
94    let (boundary, body) = encode_multipart(parts);
95    let response: TranscriptionResponse = builder
96        .header(
97            header::CONTENT_TYPE.as_str(),
98            format!("multipart/form-data; boundary={boundary}"),
99        )
100        .map_err(OpenAIError::Http)?
101        .bytes_body(body)
102        .json()
103        .await
104        .map_err(OpenAIError::Http)?;
105
106    Ok(response.text)
107}
108
109#[derive(Debug, Serialize)]
110struct SpeechRequest<'a> {
111    model: &'a str,
112    input: &'a str,
113    voice: &'a str,
114    #[serde(rename = "response_format")]
115    format: &'a str,
116}
117
118#[derive(Debug, Deserialize)]
119struct TranscriptionResponse {
120    text: String,
121}