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}