1use crate::audio::response::TranscriptionResponse;
31use crate::common::auth::AuthProvider;
32use crate::common::client::create_http_client;
33use crate::common::errors::{ErrorResponse, OpenAIToolError, Result};
34use request::multipart::{Form, Part};
35use serde::{Deserialize, Serialize};
36use std::path::Path;
37use std::time::Duration;
38
39const AUDIO_PATH: &str = "audio";
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
44#[non_exhaustive]
45pub enum TtsModel {
46 #[serde(rename = "tts-1")]
48 #[default]
49 Tts1,
50 #[serde(rename = "tts-1-hd")]
52 Tts1Hd,
53 #[serde(rename = "gpt-4o-mini-tts")]
55 Gpt4oMiniTts,
56 #[serde(rename = "tts-1-1106")]
58 Tts1_1106,
59 #[serde(rename = "tts-1-hd-1106")]
61 Tts1Hd1106,
62}
63
64impl TtsModel {
65 pub fn as_str(&self) -> &'static str {
67 match self {
68 Self::Tts1 => "tts-1",
69 Self::Tts1Hd => "tts-1-hd",
70 Self::Gpt4oMiniTts => "gpt-4o-mini-tts",
71 Self::Tts1_1106 => "tts-1-1106",
72 Self::Tts1Hd1106 => "tts-1-hd-1106",
73 }
74 }
75
76 pub fn supports_instructions(&self) -> bool {
91 matches!(self, Self::Gpt4oMiniTts)
92 }
93}
94
95impl std::fmt::Display for TtsModel {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 write!(f, "{}", self.as_str())
98 }
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
103#[serde(rename_all = "lowercase")]
104#[non_exhaustive]
105pub enum Voice {
106 #[default]
108 Alloy,
109 Ash,
111 Ballad,
113 Cedar,
115 Coral,
117 Echo,
119 Fable,
121 Marin,
123 Nova,
125 Onyx,
127 Sage,
129 Shimmer,
131 Verse,
133}
134
135impl Voice {
136 pub fn as_str(&self) -> &'static str {
138 match self {
139 Self::Alloy => "alloy",
140 Self::Ash => "ash",
141 Self::Ballad => "ballad",
142 Self::Cedar => "cedar",
143 Self::Coral => "coral",
144 Self::Echo => "echo",
145 Self::Fable => "fable",
146 Self::Marin => "marin",
147 Self::Nova => "nova",
148 Self::Onyx => "onyx",
149 Self::Sage => "sage",
150 Self::Shimmer => "shimmer",
151 Self::Verse => "verse",
152 }
153 }
154}
155
156impl std::fmt::Display for Voice {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 write!(f, "{}", self.as_str())
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
164#[serde(rename_all = "lowercase")]
165#[non_exhaustive]
166pub enum AudioFormat {
167 #[default]
169 Mp3,
170 Opus,
172 Aac,
174 Flac,
176 Wav,
178 Pcm,
180}
181
182impl AudioFormat {
183 pub fn as_str(&self) -> &'static str {
185 match self {
186 Self::Mp3 => "mp3",
187 Self::Opus => "opus",
188 Self::Aac => "aac",
189 Self::Flac => "flac",
190 Self::Wav => "wav",
191 Self::Pcm => "pcm",
192 }
193 }
194
195 pub fn file_extension(&self) -> &'static str {
197 self.as_str()
198 }
199}
200
201impl std::fmt::Display for AudioFormat {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 write!(f, "{}", self.as_str())
204 }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
209#[non_exhaustive]
210pub enum SttModel {
211 #[serde(rename = "whisper-1")]
213 #[default]
214 Whisper1,
215 #[serde(rename = "gpt-4o-transcribe")]
217 Gpt4oTranscribe,
218 #[serde(rename = "gpt-4o-mini-transcribe")]
220 Gpt4oMiniTranscribe,
221 #[serde(rename = "gpt-4o-transcribe-diarize")]
223 Gpt4oTranscribeDiarize,
224 #[serde(rename = "gpt-transcribe")]
226 GptTranscribe,
227 #[serde(rename = "gpt-live-transcribe")]
232 GptLiveTranscribe,
233 #[serde(rename = "gpt-realtime-whisper")]
238 GptRealtimeWhisper,
239}
240
241impl SttModel {
242 pub fn as_str(&self) -> &'static str {
244 match self {
245 Self::Whisper1 => "whisper-1",
246 Self::Gpt4oTranscribe => "gpt-4o-transcribe",
247 Self::Gpt4oMiniTranscribe => "gpt-4o-mini-transcribe",
248 Self::Gpt4oTranscribeDiarize => "gpt-4o-transcribe-diarize",
249 Self::GptTranscribe => "gpt-transcribe",
250 Self::GptLiveTranscribe => "gpt-live-transcribe",
251 Self::GptRealtimeWhisper => "gpt-realtime-whisper",
252 }
253 }
254
255 pub fn supports_file_transcription(&self) -> bool {
263 !matches!(self, Self::GptLiveTranscribe | Self::GptRealtimeWhisper)
264 }
265}
266
267impl std::fmt::Display for SttModel {
268 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 write!(f, "{}", self.as_str())
270 }
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
275#[serde(rename_all = "snake_case")]
276#[non_exhaustive]
277pub enum TranscriptionFormat {
278 #[default]
280 Json,
281 Text,
283 Srt,
285 VerboseJson,
287 Vtt,
289}
290
291impl TranscriptionFormat {
292 pub fn as_str(&self) -> &'static str {
294 match self {
295 Self::Json => "json",
296 Self::Text => "text",
297 Self::Srt => "srt",
298 Self::VerboseJson => "verbose_json",
299 Self::Vtt => "vtt",
300 }
301 }
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
306#[serde(rename_all = "lowercase")]
307#[non_exhaustive]
308pub enum TimestampGranularity {
309 Word,
311 Segment,
313}
314
315impl TimestampGranularity {
316 pub fn as_str(&self) -> &'static str {
318 match self {
319 Self::Word => "word",
320 Self::Segment => "segment",
321 }
322 }
323}
324
325#[derive(Debug, Clone, Default)]
327pub struct TtsOptions {
328 pub model: TtsModel,
330 pub voice: Voice,
332 pub response_format: AudioFormat,
334 pub speed: Option<f32>,
336 pub instructions: Option<String>,
350}
351
352#[derive(Debug, Clone, Default)]
354pub struct TranscribeOptions {
355 pub model: Option<SttModel>,
357 pub language: Option<String>,
359 pub prompt: Option<String>,
361 pub response_format: Option<TranscriptionFormat>,
363 pub temperature: Option<f32>,
365 pub timestamp_granularities: Option<Vec<TimestampGranularity>>,
367}
368
369#[derive(Debug, Clone, Default)]
371pub struct TranslateOptions {
372 pub model: Option<SttModel>,
374 pub prompt: Option<String>,
376 pub response_format: Option<TranscriptionFormat>,
378 pub temperature: Option<f32>,
380}
381
382#[derive(Debug, Clone, Serialize)]
384struct TtsRequest {
385 model: String,
386 input: String,
387 voice: String,
388 #[serde(skip_serializing_if = "Option::is_none")]
389 response_format: Option<String>,
390 #[serde(skip_serializing_if = "Option::is_none")]
391 speed: Option<f32>,
392 #[serde(skip_serializing_if = "Option::is_none")]
394 instructions: Option<String>,
395}
396
397pub struct Audio {
424 auth: AuthProvider,
426 timeout: Option<Duration>,
428}
429
430impl Audio {
431 pub fn new() -> Result<Self> {
450 let auth = AuthProvider::openai_from_env()?;
451 Ok(Self { auth, timeout: None })
452 }
453
454 pub fn with_auth(auth: AuthProvider) -> Self {
456 Self { auth, timeout: None }
457 }
458
459 pub fn azure() -> Result<Self> {
461 let auth = AuthProvider::azure_from_env()?;
462 Ok(Self { auth, timeout: None })
463 }
464
465 pub fn detect_provider() -> Result<Self> {
467 let auth = AuthProvider::from_env()?;
468 Ok(Self { auth, timeout: None })
469 }
470
471 pub fn with_url<S: Into<String>>(base_url: S, api_key: S) -> Self {
473 let auth = AuthProvider::from_url_with_key(base_url, api_key);
474 Self { auth, timeout: None }
475 }
476
477 pub fn from_url<S: Into<String>>(url: S) -> Result<Self> {
479 let auth = AuthProvider::from_url(url)?;
480 Ok(Self { auth, timeout: None })
481 }
482
483 pub fn auth(&self) -> &AuthProvider {
485 &self.auth
486 }
487
488 pub fn timeout(&mut self, timeout: Duration) -> &mut Self {
498 self.timeout = Some(timeout);
499 self
500 }
501
502 fn create_client(&self) -> Result<(request::Client, request::header::HeaderMap)> {
504 let client = create_http_client(self.timeout)?;
505 let mut headers = request::header::HeaderMap::new();
506 self.auth.apply_headers(&mut headers)?;
507 headers.insert("User-Agent", request::header::HeaderValue::from_static("openai-tools-rust"));
508 Ok((client, headers))
509 }
510
511 pub async fn text_to_speech(&self, text: &str, options: TtsOptions) -> Result<Vec<u8>> {
548 let (client, mut headers) = self.create_client()?;
549 headers.insert("Content-Type", request::header::HeaderValue::from_static("application/json"));
550
551 let instructions = if options.instructions.is_some() {
553 if options.model.supports_instructions() {
554 options.instructions
555 } else {
556 tracing::warn!("Model '{}' does not support instructions parameter. Ignoring instructions.", options.model);
557 None
558 }
559 } else {
560 None
561 };
562
563 let request_body = TtsRequest {
564 model: options.model.as_str().to_string(),
565 input: text.to_string(),
566 voice: options.voice.as_str().to_string(),
567 response_format: Some(options.response_format.as_str().to_string()),
568 speed: options.speed,
569 instructions,
570 };
571
572 let body = serde_json::to_string(&request_body).map_err(OpenAIToolError::SerdeJsonError)?;
573
574 let url = format!("{}/speech", self.auth.endpoint(AUDIO_PATH));
575
576 let response = client.post(&url).headers(headers).body(body).send().await.map_err(OpenAIToolError::RequestError)?;
577
578 let bytes = response.bytes().await.map_err(OpenAIToolError::RequestError)?;
579
580 Ok(bytes.to_vec())
581 }
582
583 pub async fn transcribe(&self, audio_path: &str, options: TranscribeOptions) -> Result<TranscriptionResponse> {
616 let audio_content = tokio::fs::read(audio_path).await.map_err(|e| OpenAIToolError::Error(format!("Failed to read audio file: {}", e)))?;
617
618 let filename = Path::new(audio_path).file_name().and_then(|n| n.to_str()).unwrap_or("audio.mp3").to_string();
619
620 self.transcribe_bytes(&audio_content, &filename, options).await
621 }
622
623 pub async fn transcribe_bytes(&self, audio_data: &[u8], filename: &str, options: TranscribeOptions) -> Result<TranscriptionResponse> {
658 let (client, headers) = self.create_client()?;
659
660 let audio_part = Part::bytes(audio_data.to_vec())
661 .file_name(filename.to_string())
662 .mime_str("audio/mpeg")
663 .map_err(|e| OpenAIToolError::Error(format!("Failed to set MIME type: {}", e)))?;
664
665 let mut form = Form::new().part("file", audio_part);
666
667 let model = options.model.unwrap_or_default();
669 form = form.text("model", model.as_str().to_string());
670
671 if let Some(language) = options.language {
673 form = form.text("language", language);
674 }
675 if let Some(prompt) = options.prompt {
676 form = form.text("prompt", prompt);
677 }
678 if let Some(response_format) = options.response_format {
679 form = form.text("response_format", response_format.as_str().to_string());
680 }
681 if let Some(temperature) = options.temperature {
682 form = form.text("temperature", temperature.to_string());
683 }
684 if let Some(granularities) = options.timestamp_granularities {
685 for g in granularities {
686 form = form.text("timestamp_granularities[]", g.as_str().to_string());
687 }
688 }
689
690 let url = format!("{}/transcriptions", self.auth.endpoint(AUDIO_PATH));
691
692 let response = client.post(&url).headers(headers).multipart(form).send().await.map_err(OpenAIToolError::RequestError)?;
693
694 let status = response.status();
695 let content = response.text().await.map_err(OpenAIToolError::RequestError)?;
696
697 if cfg!(test) {
698 tracing::info!("Response content: {}", content);
699 }
700
701 if !status.is_success() {
702 if let Ok(error_resp) = serde_json::from_str::<ErrorResponse>(&content) {
703 return Err(OpenAIToolError::Error(error_resp.error.message.unwrap_or_default()));
704 }
705 return Err(OpenAIToolError::Error(format!("API error ({}): {}", status, content)));
706 }
707
708 serde_json::from_str::<TranscriptionResponse>(&content).map_err(OpenAIToolError::SerdeJsonError)
709 }
710
711 pub async fn translate(&self, audio_path: &str, options: TranslateOptions) -> Result<TranscriptionResponse> {
742 let audio_content = tokio::fs::read(audio_path).await.map_err(|e| OpenAIToolError::Error(format!("Failed to read audio file: {}", e)))?;
743
744 let filename = Path::new(audio_path).file_name().and_then(|n| n.to_str()).unwrap_or("audio.mp3").to_string();
745
746 self.translate_bytes(&audio_content, &filename, options).await
747 }
748
749 pub async fn translate_bytes(&self, audio_data: &[u8], filename: &str, options: TranslateOptions) -> Result<TranscriptionResponse> {
762 let (client, headers) = self.create_client()?;
763
764 let audio_part = Part::bytes(audio_data.to_vec())
765 .file_name(filename.to_string())
766 .mime_str("audio/mpeg")
767 .map_err(|e| OpenAIToolError::Error(format!("Failed to set MIME type: {}", e)))?;
768
769 let mut form = Form::new().part("file", audio_part);
770
771 let model = options.model.unwrap_or(SttModel::Whisper1);
773 form = form.text("model", model.as_str().to_string());
774
775 if let Some(prompt) = options.prompt {
777 form = form.text("prompt", prompt);
778 }
779 if let Some(response_format) = options.response_format {
780 form = form.text("response_format", response_format.as_str().to_string());
781 }
782 if let Some(temperature) = options.temperature {
783 form = form.text("temperature", temperature.to_string());
784 }
785
786 let url = format!("{}/translations", self.auth.endpoint(AUDIO_PATH));
787
788 let response = client.post(&url).headers(headers).multipart(form).send().await.map_err(OpenAIToolError::RequestError)?;
789
790 let status = response.status();
791 let content = response.text().await.map_err(OpenAIToolError::RequestError)?;
792
793 if cfg!(test) {
794 tracing::info!("Response content: {}", content);
795 }
796
797 if !status.is_success() {
798 if let Ok(error_resp) = serde_json::from_str::<ErrorResponse>(&content) {
799 return Err(OpenAIToolError::Error(error_resp.error.message.unwrap_or_default()));
800 }
801 return Err(OpenAIToolError::Error(format!("API error ({}): {}", status, content)));
802 }
803
804 serde_json::from_str::<TranscriptionResponse>(&content).map_err(OpenAIToolError::SerdeJsonError)
805 }
806}
807
808#[cfg(test)]
809mod tests {
810 use super::*;
811
812 #[test]
817 fn test_tts_model_as_str() {
818 assert_eq!(TtsModel::Tts1.as_str(), "tts-1");
819 assert_eq!(TtsModel::Tts1Hd.as_str(), "tts-1-hd");
820 assert_eq!(TtsModel::Gpt4oMiniTts.as_str(), "gpt-4o-mini-tts");
821 }
822
823 #[test]
824 fn test_tts_model_supports_instructions() {
825 assert!(TtsModel::Gpt4oMiniTts.supports_instructions());
827 assert!(!TtsModel::Tts1.supports_instructions());
828 assert!(!TtsModel::Tts1Hd.supports_instructions());
829 }
830
831 #[test]
832 fn test_tts_model_default() {
833 let model = TtsModel::default();
834 assert_eq!(model, TtsModel::Tts1);
835 }
836
837 #[test]
838 fn test_tts_model_display() {
839 assert_eq!(format!("{}", TtsModel::Gpt4oMiniTts), "gpt-4o-mini-tts");
840 }
841
842 #[test]
847 fn test_voice_as_str_all_voices() {
848 assert_eq!(Voice::Alloy.as_str(), "alloy");
849 assert_eq!(Voice::Ash.as_str(), "ash");
850 assert_eq!(Voice::Ballad.as_str(), "ballad");
851 assert_eq!(Voice::Cedar.as_str(), "cedar");
852 assert_eq!(Voice::Coral.as_str(), "coral");
853 assert_eq!(Voice::Echo.as_str(), "echo");
854 assert_eq!(Voice::Fable.as_str(), "fable");
855 assert_eq!(Voice::Marin.as_str(), "marin");
856 assert_eq!(Voice::Nova.as_str(), "nova");
857 assert_eq!(Voice::Onyx.as_str(), "onyx");
858 assert_eq!(Voice::Sage.as_str(), "sage");
859 assert_eq!(Voice::Shimmer.as_str(), "shimmer");
860 assert_eq!(Voice::Verse.as_str(), "verse");
861 }
862
863 #[test]
864 fn test_voice_new_voices() {
865 assert_eq!(Voice::Ballad.as_str(), "ballad");
867 assert_eq!(Voice::Cedar.as_str(), "cedar");
868 assert_eq!(Voice::Marin.as_str(), "marin");
869 assert_eq!(Voice::Verse.as_str(), "verse");
870 }
871
872 #[test]
873 fn test_voice_default() {
874 let voice = Voice::default();
875 assert_eq!(voice, Voice::Alloy);
876 }
877
878 #[test]
879 fn test_voice_serialization() {
880 let voice = Voice::Coral;
881 let json = serde_json::to_string(&voice).unwrap();
882 assert_eq!(json, "\"coral\"");
883
884 let ballad = Voice::Ballad;
886 let json = serde_json::to_string(&ballad).unwrap();
887 assert_eq!(json, "\"ballad\"");
888 }
889
890 #[test]
891 fn test_voice_deserialization() {
892 let voice: Voice = serde_json::from_str("\"coral\"").unwrap();
893 assert_eq!(voice, Voice::Coral);
894
895 let cedar: Voice = serde_json::from_str("\"cedar\"").unwrap();
897 assert_eq!(cedar, Voice::Cedar);
898
899 let marin: Voice = serde_json::from_str("\"marin\"").unwrap();
900 assert_eq!(marin, Voice::Marin);
901 }
902
903 #[test]
908 fn test_tts_options_default() {
909 let options = TtsOptions::default();
910 assert_eq!(options.model, TtsModel::Tts1);
911 assert_eq!(options.voice, Voice::Alloy);
912 assert_eq!(options.response_format, AudioFormat::Mp3);
913 assert!(options.speed.is_none());
914 assert!(options.instructions.is_none());
915 }
916
917 #[test]
918 fn test_tts_options_with_instructions() {
919 let options = TtsOptions {
920 model: TtsModel::Gpt4oMiniTts,
921 voice: Voice::Coral,
922 instructions: Some("Speak in a cheerful tone.".to_string()),
923 ..Default::default()
924 };
925 assert_eq!(options.model, TtsModel::Gpt4oMiniTts);
926 assert_eq!(options.instructions, Some("Speak in a cheerful tone.".to_string()));
927 }
928
929 #[test]
934 fn test_tts_request_serialization_with_instructions() {
935 let request = TtsRequest {
936 model: "gpt-4o-mini-tts".to_string(),
937 input: "Hello, world!".to_string(),
938 voice: "coral".to_string(),
939 response_format: Some("mp3".to_string()),
940 speed: None,
941 instructions: Some("Speak cheerfully.".to_string()),
942 };
943 let json = serde_json::to_value(&request).unwrap();
944
945 assert_eq!(json["model"], "gpt-4o-mini-tts");
946 assert_eq!(json["input"], "Hello, world!");
947 assert_eq!(json["voice"], "coral");
948 assert_eq!(json["response_format"], "mp3");
949 assert_eq!(json["instructions"], "Speak cheerfully.");
950 assert!(json.get("speed").is_none());
951 }
952
953 #[test]
954 fn test_tts_request_serialization_without_instructions() {
955 let request = TtsRequest {
956 model: "tts-1".to_string(),
957 input: "Hello".to_string(),
958 voice: "alloy".to_string(),
959 response_format: Some("mp3".to_string()),
960 speed: Some(1.0),
961 instructions: None,
962 };
963 let json = serde_json::to_value(&request).unwrap();
964
965 assert_eq!(json["model"], "tts-1");
966 assert_eq!(json["speed"], 1.0);
967 assert!(json.get("instructions").is_none());
969 }
970
971 #[test]
972 fn test_tts_request_skip_serializing_none_fields() {
973 let request = TtsRequest {
974 model: "tts-1".to_string(),
975 input: "Test".to_string(),
976 voice: "echo".to_string(),
977 response_format: None,
978 speed: None,
979 instructions: None,
980 };
981 let json = serde_json::to_value(&request).unwrap();
982
983 assert!(json.get("model").is_some());
985 assert!(json.get("input").is_some());
986 assert!(json.get("voice").is_some());
987
988 assert!(json.get("response_format").is_none());
990 assert!(json.get("speed").is_none());
991 assert!(json.get("instructions").is_none());
992 }
993
994 #[test]
999 fn test_audio_format_as_str() {
1000 assert_eq!(AudioFormat::Mp3.as_str(), "mp3");
1001 assert_eq!(AudioFormat::Opus.as_str(), "opus");
1002 assert_eq!(AudioFormat::Aac.as_str(), "aac");
1003 assert_eq!(AudioFormat::Flac.as_str(), "flac");
1004 assert_eq!(AudioFormat::Wav.as_str(), "wav");
1005 assert_eq!(AudioFormat::Pcm.as_str(), "pcm");
1006 }
1007
1008 #[test]
1009 fn test_audio_format_file_extension() {
1010 assert_eq!(AudioFormat::Mp3.file_extension(), "mp3");
1011 assert_eq!(AudioFormat::Wav.file_extension(), "wav");
1012 }
1013
1014 #[test]
1019 fn test_stt_model_as_str() {
1020 assert_eq!(SttModel::Whisper1.as_str(), "whisper-1");
1021 assert_eq!(SttModel::Gpt4oTranscribe.as_str(), "gpt-4o-transcribe");
1022 }
1023
1024 #[test]
1029 fn test_transcription_format_as_str() {
1030 assert_eq!(TranscriptionFormat::Json.as_str(), "json");
1031 assert_eq!(TranscriptionFormat::Text.as_str(), "text");
1032 assert_eq!(TranscriptionFormat::Srt.as_str(), "srt");
1033 assert_eq!(TranscriptionFormat::VerboseJson.as_str(), "verbose_json");
1034 assert_eq!(TranscriptionFormat::Vtt.as_str(), "vtt");
1035 }
1036
1037 #[test]
1042 fn test_timestamp_granularity_as_str() {
1043 assert_eq!(TimestampGranularity::Word.as_str(), "word");
1044 assert_eq!(TimestampGranularity::Segment.as_str(), "segment");
1045 }
1046
1047 fn new_stt_models() -> Vec<(SttModel, &'static str)> {
1055 vec![
1056 (SttModel::GptTranscribe, "gpt-transcribe"),
1057 (SttModel::GptLiveTranscribe, "gpt-live-transcribe"),
1058 (SttModel::GptRealtimeWhisper, "gpt-realtime-whisper"),
1059 (SttModel::Gpt4oMiniTranscribe, "gpt-4o-mini-transcribe"),
1060 ]
1061 }
1062
1063 #[test]
1064 fn test_new_stt_models_as_str() {
1065 for (model, expected) in new_stt_models() {
1066 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1067 }
1068 }
1069
1070 #[test]
1071 fn test_new_stt_models_serialization() {
1072 for (model, expected) in new_stt_models() {
1073 let json = serde_json::to_string(&model).unwrap();
1074 assert_eq!(json, format!("\"{}\"", expected), "Wrong serialization for {:?}", model);
1075 let deserialized: SttModel = serde_json::from_str(&json).unwrap();
1076 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1077 }
1078 }
1079
1080 #[test]
1083 fn test_realtime_only_stt_models_are_flagged() {
1084 assert!(!SttModel::GptLiveTranscribe.supports_file_transcription());
1085 assert!(!SttModel::GptRealtimeWhisper.supports_file_transcription());
1086
1087 assert!(SttModel::GptTranscribe.supports_file_transcription());
1088 assert!(SttModel::Whisper1.supports_file_transcription());
1089 assert!(SttModel::Gpt4oTranscribe.supports_file_transcription());
1090 assert!(SttModel::Gpt4oMiniTranscribe.supports_file_transcription());
1091 }
1092
1093 #[test]
1096 fn test_previously_missing_tts_models() {
1097 for (model, expected) in [(TtsModel::Tts1_1106, "tts-1-1106"), (TtsModel::Tts1Hd1106, "tts-1-hd-1106")] {
1098 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1099 let json = serde_json::to_string(&model).unwrap();
1100 assert_eq!(json, format!("\"{}\"", expected), "Wrong serialization for {:?}", model);
1101 }
1102 }
1103
1104 #[test]
1105 fn test_diarizing_stt_model() {
1106 assert_eq!(SttModel::Gpt4oTranscribeDiarize.as_str(), "gpt-4o-transcribe-diarize");
1107 assert!(SttModel::Gpt4oTranscribeDiarize.supports_file_transcription());
1108 }
1109}