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)]
44pub enum TtsModel {
45 #[serde(rename = "tts-1")]
47 #[default]
48 Tts1,
49 #[serde(rename = "tts-1-hd")]
51 Tts1Hd,
52 #[serde(rename = "gpt-4o-mini-tts")]
54 Gpt4oMiniTts,
55 #[serde(rename = "tts-1-1106")]
57 Tts1_1106,
58 #[serde(rename = "tts-1-hd-1106")]
60 Tts1Hd1106,
61}
62
63impl TtsModel {
64 pub fn as_str(&self) -> &'static str {
66 match self {
67 Self::Tts1 => "tts-1",
68 Self::Tts1Hd => "tts-1-hd",
69 Self::Gpt4oMiniTts => "gpt-4o-mini-tts",
70 Self::Tts1_1106 => "tts-1-1106",
71 Self::Tts1Hd1106 => "tts-1-hd-1106",
72 }
73 }
74
75 pub fn supports_instructions(&self) -> bool {
90 matches!(self, Self::Gpt4oMiniTts)
91 }
92}
93
94impl std::fmt::Display for TtsModel {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 write!(f, "{}", self.as_str())
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
102#[serde(rename_all = "lowercase")]
103pub enum Voice {
104 #[default]
106 Alloy,
107 Ash,
109 Ballad,
111 Cedar,
113 Coral,
115 Echo,
117 Fable,
119 Marin,
121 Nova,
123 Onyx,
125 Sage,
127 Shimmer,
129 Verse,
131}
132
133impl Voice {
134 pub fn as_str(&self) -> &'static str {
136 match self {
137 Self::Alloy => "alloy",
138 Self::Ash => "ash",
139 Self::Ballad => "ballad",
140 Self::Cedar => "cedar",
141 Self::Coral => "coral",
142 Self::Echo => "echo",
143 Self::Fable => "fable",
144 Self::Marin => "marin",
145 Self::Nova => "nova",
146 Self::Onyx => "onyx",
147 Self::Sage => "sage",
148 Self::Shimmer => "shimmer",
149 Self::Verse => "verse",
150 }
151 }
152}
153
154impl std::fmt::Display for Voice {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 write!(f, "{}", self.as_str())
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
162#[serde(rename_all = "lowercase")]
163pub enum AudioFormat {
164 #[default]
166 Mp3,
167 Opus,
169 Aac,
171 Flac,
173 Wav,
175 Pcm,
177}
178
179impl AudioFormat {
180 pub fn as_str(&self) -> &'static str {
182 match self {
183 Self::Mp3 => "mp3",
184 Self::Opus => "opus",
185 Self::Aac => "aac",
186 Self::Flac => "flac",
187 Self::Wav => "wav",
188 Self::Pcm => "pcm",
189 }
190 }
191
192 pub fn file_extension(&self) -> &'static str {
194 self.as_str()
195 }
196}
197
198impl std::fmt::Display for AudioFormat {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 write!(f, "{}", self.as_str())
201 }
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
206pub enum SttModel {
207 #[serde(rename = "whisper-1")]
209 #[default]
210 Whisper1,
211 #[serde(rename = "gpt-4o-transcribe")]
213 Gpt4oTranscribe,
214 #[serde(rename = "gpt-4o-mini-transcribe")]
216 Gpt4oMiniTranscribe,
217 #[serde(rename = "gpt-4o-transcribe-diarize")]
219 Gpt4oTranscribeDiarize,
220 #[serde(rename = "gpt-transcribe")]
222 GptTranscribe,
223 #[serde(rename = "gpt-live-transcribe")]
228 GptLiveTranscribe,
229 #[serde(rename = "gpt-realtime-whisper")]
234 GptRealtimeWhisper,
235}
236
237impl SttModel {
238 pub fn as_str(&self) -> &'static str {
240 match self {
241 Self::Whisper1 => "whisper-1",
242 Self::Gpt4oTranscribe => "gpt-4o-transcribe",
243 Self::Gpt4oMiniTranscribe => "gpt-4o-mini-transcribe",
244 Self::Gpt4oTranscribeDiarize => "gpt-4o-transcribe-diarize",
245 Self::GptTranscribe => "gpt-transcribe",
246 Self::GptLiveTranscribe => "gpt-live-transcribe",
247 Self::GptRealtimeWhisper => "gpt-realtime-whisper",
248 }
249 }
250
251 pub fn supports_file_transcription(&self) -> bool {
259 !matches!(self, Self::GptLiveTranscribe | Self::GptRealtimeWhisper)
260 }
261}
262
263impl std::fmt::Display for SttModel {
264 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265 write!(f, "{}", self.as_str())
266 }
267}
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
271#[serde(rename_all = "snake_case")]
272pub enum TranscriptionFormat {
273 #[default]
275 Json,
276 Text,
278 Srt,
280 VerboseJson,
282 Vtt,
284}
285
286impl TranscriptionFormat {
287 pub fn as_str(&self) -> &'static str {
289 match self {
290 Self::Json => "json",
291 Self::Text => "text",
292 Self::Srt => "srt",
293 Self::VerboseJson => "verbose_json",
294 Self::Vtt => "vtt",
295 }
296 }
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
301#[serde(rename_all = "lowercase")]
302pub enum TimestampGranularity {
303 Word,
305 Segment,
307}
308
309impl TimestampGranularity {
310 pub fn as_str(&self) -> &'static str {
312 match self {
313 Self::Word => "word",
314 Self::Segment => "segment",
315 }
316 }
317}
318
319#[derive(Debug, Clone, Default)]
321pub struct TtsOptions {
322 pub model: TtsModel,
324 pub voice: Voice,
326 pub response_format: AudioFormat,
328 pub speed: Option<f32>,
330 pub instructions: Option<String>,
344}
345
346#[derive(Debug, Clone, Default)]
348pub struct TranscribeOptions {
349 pub model: Option<SttModel>,
351 pub language: Option<String>,
353 pub prompt: Option<String>,
355 pub response_format: Option<TranscriptionFormat>,
357 pub temperature: Option<f32>,
359 pub timestamp_granularities: Option<Vec<TimestampGranularity>>,
361}
362
363#[derive(Debug, Clone, Default)]
365pub struct TranslateOptions {
366 pub model: Option<SttModel>,
368 pub prompt: Option<String>,
370 pub response_format: Option<TranscriptionFormat>,
372 pub temperature: Option<f32>,
374}
375
376#[derive(Debug, Clone, Serialize)]
378struct TtsRequest {
379 model: String,
380 input: String,
381 voice: String,
382 #[serde(skip_serializing_if = "Option::is_none")]
383 response_format: Option<String>,
384 #[serde(skip_serializing_if = "Option::is_none")]
385 speed: Option<f32>,
386 #[serde(skip_serializing_if = "Option::is_none")]
388 instructions: Option<String>,
389}
390
391pub struct Audio {
418 auth: AuthProvider,
420 timeout: Option<Duration>,
422}
423
424impl Audio {
425 pub fn new() -> Result<Self> {
444 let auth = AuthProvider::openai_from_env()?;
445 Ok(Self { auth, timeout: None })
446 }
447
448 pub fn with_auth(auth: AuthProvider) -> Self {
450 Self { auth, timeout: None }
451 }
452
453 pub fn azure() -> Result<Self> {
455 let auth = AuthProvider::azure_from_env()?;
456 Ok(Self { auth, timeout: None })
457 }
458
459 pub fn detect_provider() -> Result<Self> {
461 let auth = AuthProvider::from_env()?;
462 Ok(Self { auth, timeout: None })
463 }
464
465 pub fn with_url<S: Into<String>>(base_url: S, api_key: S) -> Self {
467 let auth = AuthProvider::from_url_with_key(base_url, api_key);
468 Self { auth, timeout: None }
469 }
470
471 pub fn from_url<S: Into<String>>(url: S) -> Result<Self> {
473 let auth = AuthProvider::from_url(url)?;
474 Ok(Self { auth, timeout: None })
475 }
476
477 pub fn auth(&self) -> &AuthProvider {
479 &self.auth
480 }
481
482 pub fn timeout(&mut self, timeout: Duration) -> &mut Self {
492 self.timeout = Some(timeout);
493 self
494 }
495
496 fn create_client(&self) -> Result<(request::Client, request::header::HeaderMap)> {
498 let client = create_http_client(self.timeout)?;
499 let mut headers = request::header::HeaderMap::new();
500 self.auth.apply_headers(&mut headers)?;
501 headers.insert("User-Agent", request::header::HeaderValue::from_static("openai-tools-rust"));
502 Ok((client, headers))
503 }
504
505 pub async fn text_to_speech(&self, text: &str, options: TtsOptions) -> Result<Vec<u8>> {
542 let (client, mut headers) = self.create_client()?;
543 headers.insert("Content-Type", request::header::HeaderValue::from_static("application/json"));
544
545 let instructions = if options.instructions.is_some() {
547 if options.model.supports_instructions() {
548 options.instructions
549 } else {
550 tracing::warn!("Model '{}' does not support instructions parameter. Ignoring instructions.", options.model);
551 None
552 }
553 } else {
554 None
555 };
556
557 let request_body = TtsRequest {
558 model: options.model.as_str().to_string(),
559 input: text.to_string(),
560 voice: options.voice.as_str().to_string(),
561 response_format: Some(options.response_format.as_str().to_string()),
562 speed: options.speed,
563 instructions,
564 };
565
566 let body = serde_json::to_string(&request_body).map_err(OpenAIToolError::SerdeJsonError)?;
567
568 let url = format!("{}/speech", self.auth.endpoint(AUDIO_PATH));
569
570 let response = client.post(&url).headers(headers).body(body).send().await.map_err(OpenAIToolError::RequestError)?;
571
572 let bytes = response.bytes().await.map_err(OpenAIToolError::RequestError)?;
573
574 Ok(bytes.to_vec())
575 }
576
577 pub async fn transcribe(&self, audio_path: &str, options: TranscribeOptions) -> Result<TranscriptionResponse> {
610 let audio_content = tokio::fs::read(audio_path).await.map_err(|e| OpenAIToolError::Error(format!("Failed to read audio file: {}", e)))?;
611
612 let filename = Path::new(audio_path).file_name().and_then(|n| n.to_str()).unwrap_or("audio.mp3").to_string();
613
614 self.transcribe_bytes(&audio_content, &filename, options).await
615 }
616
617 pub async fn transcribe_bytes(&self, audio_data: &[u8], filename: &str, options: TranscribeOptions) -> Result<TranscriptionResponse> {
652 let (client, headers) = self.create_client()?;
653
654 let audio_part = Part::bytes(audio_data.to_vec())
655 .file_name(filename.to_string())
656 .mime_str("audio/mpeg")
657 .map_err(|e| OpenAIToolError::Error(format!("Failed to set MIME type: {}", e)))?;
658
659 let mut form = Form::new().part("file", audio_part);
660
661 let model = options.model.unwrap_or_default();
663 form = form.text("model", model.as_str().to_string());
664
665 if let Some(language) = options.language {
667 form = form.text("language", language);
668 }
669 if let Some(prompt) = options.prompt {
670 form = form.text("prompt", prompt);
671 }
672 if let Some(response_format) = options.response_format {
673 form = form.text("response_format", response_format.as_str().to_string());
674 }
675 if let Some(temperature) = options.temperature {
676 form = form.text("temperature", temperature.to_string());
677 }
678 if let Some(granularities) = options.timestamp_granularities {
679 for g in granularities {
680 form = form.text("timestamp_granularities[]", g.as_str().to_string());
681 }
682 }
683
684 let url = format!("{}/transcriptions", self.auth.endpoint(AUDIO_PATH));
685
686 let response = client.post(&url).headers(headers).multipart(form).send().await.map_err(OpenAIToolError::RequestError)?;
687
688 let status = response.status();
689 let content = response.text().await.map_err(OpenAIToolError::RequestError)?;
690
691 if cfg!(test) {
692 tracing::info!("Response content: {}", content);
693 }
694
695 if !status.is_success() {
696 if let Ok(error_resp) = serde_json::from_str::<ErrorResponse>(&content) {
697 return Err(OpenAIToolError::Error(error_resp.error.message.unwrap_or_default()));
698 }
699 return Err(OpenAIToolError::Error(format!("API error ({}): {}", status, content)));
700 }
701
702 serde_json::from_str::<TranscriptionResponse>(&content).map_err(OpenAIToolError::SerdeJsonError)
703 }
704
705 pub async fn translate(&self, audio_path: &str, options: TranslateOptions) -> Result<TranscriptionResponse> {
736 let audio_content = tokio::fs::read(audio_path).await.map_err(|e| OpenAIToolError::Error(format!("Failed to read audio file: {}", e)))?;
737
738 let filename = Path::new(audio_path).file_name().and_then(|n| n.to_str()).unwrap_or("audio.mp3").to_string();
739
740 self.translate_bytes(&audio_content, &filename, options).await
741 }
742
743 pub async fn translate_bytes(&self, audio_data: &[u8], filename: &str, options: TranslateOptions) -> Result<TranscriptionResponse> {
756 let (client, headers) = self.create_client()?;
757
758 let audio_part = Part::bytes(audio_data.to_vec())
759 .file_name(filename.to_string())
760 .mime_str("audio/mpeg")
761 .map_err(|e| OpenAIToolError::Error(format!("Failed to set MIME type: {}", e)))?;
762
763 let mut form = Form::new().part("file", audio_part);
764
765 let model = options.model.unwrap_or(SttModel::Whisper1);
767 form = form.text("model", model.as_str().to_string());
768
769 if let Some(prompt) = options.prompt {
771 form = form.text("prompt", prompt);
772 }
773 if let Some(response_format) = options.response_format {
774 form = form.text("response_format", response_format.as_str().to_string());
775 }
776 if let Some(temperature) = options.temperature {
777 form = form.text("temperature", temperature.to_string());
778 }
779
780 let url = format!("{}/translations", self.auth.endpoint(AUDIO_PATH));
781
782 let response = client.post(&url).headers(headers).multipart(form).send().await.map_err(OpenAIToolError::RequestError)?;
783
784 let status = response.status();
785 let content = response.text().await.map_err(OpenAIToolError::RequestError)?;
786
787 if cfg!(test) {
788 tracing::info!("Response content: {}", content);
789 }
790
791 if !status.is_success() {
792 if let Ok(error_resp) = serde_json::from_str::<ErrorResponse>(&content) {
793 return Err(OpenAIToolError::Error(error_resp.error.message.unwrap_or_default()));
794 }
795 return Err(OpenAIToolError::Error(format!("API error ({}): {}", status, content)));
796 }
797
798 serde_json::from_str::<TranscriptionResponse>(&content).map_err(OpenAIToolError::SerdeJsonError)
799 }
800}
801
802#[cfg(test)]
803mod tests {
804 use super::*;
805
806 #[test]
811 fn test_tts_model_as_str() {
812 assert_eq!(TtsModel::Tts1.as_str(), "tts-1");
813 assert_eq!(TtsModel::Tts1Hd.as_str(), "tts-1-hd");
814 assert_eq!(TtsModel::Gpt4oMiniTts.as_str(), "gpt-4o-mini-tts");
815 }
816
817 #[test]
818 fn test_tts_model_supports_instructions() {
819 assert!(TtsModel::Gpt4oMiniTts.supports_instructions());
821 assert!(!TtsModel::Tts1.supports_instructions());
822 assert!(!TtsModel::Tts1Hd.supports_instructions());
823 }
824
825 #[test]
826 fn test_tts_model_default() {
827 let model = TtsModel::default();
828 assert_eq!(model, TtsModel::Tts1);
829 }
830
831 #[test]
832 fn test_tts_model_display() {
833 assert_eq!(format!("{}", TtsModel::Gpt4oMiniTts), "gpt-4o-mini-tts");
834 }
835
836 #[test]
841 fn test_voice_as_str_all_voices() {
842 assert_eq!(Voice::Alloy.as_str(), "alloy");
843 assert_eq!(Voice::Ash.as_str(), "ash");
844 assert_eq!(Voice::Ballad.as_str(), "ballad");
845 assert_eq!(Voice::Cedar.as_str(), "cedar");
846 assert_eq!(Voice::Coral.as_str(), "coral");
847 assert_eq!(Voice::Echo.as_str(), "echo");
848 assert_eq!(Voice::Fable.as_str(), "fable");
849 assert_eq!(Voice::Marin.as_str(), "marin");
850 assert_eq!(Voice::Nova.as_str(), "nova");
851 assert_eq!(Voice::Onyx.as_str(), "onyx");
852 assert_eq!(Voice::Sage.as_str(), "sage");
853 assert_eq!(Voice::Shimmer.as_str(), "shimmer");
854 assert_eq!(Voice::Verse.as_str(), "verse");
855 }
856
857 #[test]
858 fn test_voice_new_voices() {
859 assert_eq!(Voice::Ballad.as_str(), "ballad");
861 assert_eq!(Voice::Cedar.as_str(), "cedar");
862 assert_eq!(Voice::Marin.as_str(), "marin");
863 assert_eq!(Voice::Verse.as_str(), "verse");
864 }
865
866 #[test]
867 fn test_voice_default() {
868 let voice = Voice::default();
869 assert_eq!(voice, Voice::Alloy);
870 }
871
872 #[test]
873 fn test_voice_serialization() {
874 let voice = Voice::Coral;
875 let json = serde_json::to_string(&voice).unwrap();
876 assert_eq!(json, "\"coral\"");
877
878 let ballad = Voice::Ballad;
880 let json = serde_json::to_string(&ballad).unwrap();
881 assert_eq!(json, "\"ballad\"");
882 }
883
884 #[test]
885 fn test_voice_deserialization() {
886 let voice: Voice = serde_json::from_str("\"coral\"").unwrap();
887 assert_eq!(voice, Voice::Coral);
888
889 let cedar: Voice = serde_json::from_str("\"cedar\"").unwrap();
891 assert_eq!(cedar, Voice::Cedar);
892
893 let marin: Voice = serde_json::from_str("\"marin\"").unwrap();
894 assert_eq!(marin, Voice::Marin);
895 }
896
897 #[test]
902 fn test_tts_options_default() {
903 let options = TtsOptions::default();
904 assert_eq!(options.model, TtsModel::Tts1);
905 assert_eq!(options.voice, Voice::Alloy);
906 assert_eq!(options.response_format, AudioFormat::Mp3);
907 assert!(options.speed.is_none());
908 assert!(options.instructions.is_none());
909 }
910
911 #[test]
912 fn test_tts_options_with_instructions() {
913 let options = TtsOptions {
914 model: TtsModel::Gpt4oMiniTts,
915 voice: Voice::Coral,
916 instructions: Some("Speak in a cheerful tone.".to_string()),
917 ..Default::default()
918 };
919 assert_eq!(options.model, TtsModel::Gpt4oMiniTts);
920 assert_eq!(options.instructions, Some("Speak in a cheerful tone.".to_string()));
921 }
922
923 #[test]
928 fn test_tts_request_serialization_with_instructions() {
929 let request = TtsRequest {
930 model: "gpt-4o-mini-tts".to_string(),
931 input: "Hello, world!".to_string(),
932 voice: "coral".to_string(),
933 response_format: Some("mp3".to_string()),
934 speed: None,
935 instructions: Some("Speak cheerfully.".to_string()),
936 };
937 let json = serde_json::to_value(&request).unwrap();
938
939 assert_eq!(json["model"], "gpt-4o-mini-tts");
940 assert_eq!(json["input"], "Hello, world!");
941 assert_eq!(json["voice"], "coral");
942 assert_eq!(json["response_format"], "mp3");
943 assert_eq!(json["instructions"], "Speak cheerfully.");
944 assert!(json.get("speed").is_none());
945 }
946
947 #[test]
948 fn test_tts_request_serialization_without_instructions() {
949 let request = TtsRequest {
950 model: "tts-1".to_string(),
951 input: "Hello".to_string(),
952 voice: "alloy".to_string(),
953 response_format: Some("mp3".to_string()),
954 speed: Some(1.0),
955 instructions: None,
956 };
957 let json = serde_json::to_value(&request).unwrap();
958
959 assert_eq!(json["model"], "tts-1");
960 assert_eq!(json["speed"], 1.0);
961 assert!(json.get("instructions").is_none());
963 }
964
965 #[test]
966 fn test_tts_request_skip_serializing_none_fields() {
967 let request = TtsRequest {
968 model: "tts-1".to_string(),
969 input: "Test".to_string(),
970 voice: "echo".to_string(),
971 response_format: None,
972 speed: None,
973 instructions: None,
974 };
975 let json = serde_json::to_value(&request).unwrap();
976
977 assert!(json.get("model").is_some());
979 assert!(json.get("input").is_some());
980 assert!(json.get("voice").is_some());
981
982 assert!(json.get("response_format").is_none());
984 assert!(json.get("speed").is_none());
985 assert!(json.get("instructions").is_none());
986 }
987
988 #[test]
993 fn test_audio_format_as_str() {
994 assert_eq!(AudioFormat::Mp3.as_str(), "mp3");
995 assert_eq!(AudioFormat::Opus.as_str(), "opus");
996 assert_eq!(AudioFormat::Aac.as_str(), "aac");
997 assert_eq!(AudioFormat::Flac.as_str(), "flac");
998 assert_eq!(AudioFormat::Wav.as_str(), "wav");
999 assert_eq!(AudioFormat::Pcm.as_str(), "pcm");
1000 }
1001
1002 #[test]
1003 fn test_audio_format_file_extension() {
1004 assert_eq!(AudioFormat::Mp3.file_extension(), "mp3");
1005 assert_eq!(AudioFormat::Wav.file_extension(), "wav");
1006 }
1007
1008 #[test]
1013 fn test_stt_model_as_str() {
1014 assert_eq!(SttModel::Whisper1.as_str(), "whisper-1");
1015 assert_eq!(SttModel::Gpt4oTranscribe.as_str(), "gpt-4o-transcribe");
1016 }
1017
1018 #[test]
1023 fn test_transcription_format_as_str() {
1024 assert_eq!(TranscriptionFormat::Json.as_str(), "json");
1025 assert_eq!(TranscriptionFormat::Text.as_str(), "text");
1026 assert_eq!(TranscriptionFormat::Srt.as_str(), "srt");
1027 assert_eq!(TranscriptionFormat::VerboseJson.as_str(), "verbose_json");
1028 assert_eq!(TranscriptionFormat::Vtt.as_str(), "vtt");
1029 }
1030
1031 #[test]
1036 fn test_timestamp_granularity_as_str() {
1037 assert_eq!(TimestampGranularity::Word.as_str(), "word");
1038 assert_eq!(TimestampGranularity::Segment.as_str(), "segment");
1039 }
1040
1041 fn new_stt_models() -> Vec<(SttModel, &'static str)> {
1049 vec![
1050 (SttModel::GptTranscribe, "gpt-transcribe"),
1051 (SttModel::GptLiveTranscribe, "gpt-live-transcribe"),
1052 (SttModel::GptRealtimeWhisper, "gpt-realtime-whisper"),
1053 (SttModel::Gpt4oMiniTranscribe, "gpt-4o-mini-transcribe"),
1054 ]
1055 }
1056
1057 #[test]
1058 fn test_new_stt_models_as_str() {
1059 for (model, expected) in new_stt_models() {
1060 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1061 }
1062 }
1063
1064 #[test]
1065 fn test_new_stt_models_serialization() {
1066 for (model, expected) in new_stt_models() {
1067 let json = serde_json::to_string(&model).unwrap();
1068 assert_eq!(json, format!("\"{}\"", expected), "Wrong serialization for {:?}", model);
1069 let deserialized: SttModel = serde_json::from_str(&json).unwrap();
1070 assert_eq!(deserialized, model, "Serialization roundtrip failed for {:?}", model);
1071 }
1072 }
1073
1074 #[test]
1077 fn test_realtime_only_stt_models_are_flagged() {
1078 assert!(!SttModel::GptLiveTranscribe.supports_file_transcription());
1079 assert!(!SttModel::GptRealtimeWhisper.supports_file_transcription());
1080
1081 assert!(SttModel::GptTranscribe.supports_file_transcription());
1082 assert!(SttModel::Whisper1.supports_file_transcription());
1083 assert!(SttModel::Gpt4oTranscribe.supports_file_transcription());
1084 assert!(SttModel::Gpt4oMiniTranscribe.supports_file_transcription());
1085 }
1086
1087 #[test]
1090 fn test_previously_missing_tts_models() {
1091 for (model, expected) in [(TtsModel::Tts1_1106, "tts-1-1106"), (TtsModel::Tts1Hd1106, "tts-1-hd-1106")] {
1092 assert_eq!(model.as_str(), expected, "Wrong model ID for {:?}", model);
1093 let json = serde_json::to_string(&model).unwrap();
1094 assert_eq!(json, format!("\"{}\"", expected), "Wrong serialization for {:?}", model);
1095 }
1096 }
1097
1098 #[test]
1099 fn test_diarizing_stt_model() {
1100 assert_eq!(SttModel::Gpt4oTranscribeDiarize.as_str(), "gpt-4o-transcribe-diarize");
1101 assert!(SttModel::Gpt4oTranscribeDiarize.supports_file_transcription());
1102 }
1103}