1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::client::Client;
6use crate::error::Result;
7
8#[derive(Debug, Clone, Serialize, Default)]
10pub struct TextToSpeechRequest {
11 pub model: String,
13
14 pub text: String,
16
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub voice: Option<String>,
20
21 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
23 pub output_format: Option<String>,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub speed: Option<f64>,
28}
29
30pub type TtsRequest = TextToSpeechRequest;
32
33#[derive(Debug, Clone, Deserialize)]
35pub struct TextToSpeechResponse {
36 pub audio_base64: String,
38
39 pub format: String,
41
42 pub size_bytes: i64,
44
45 pub model: String,
47
48 #[serde(default)]
50 pub cost_ticks: i64,
51
52 #[serde(default)]
54 pub request_id: String,
55}
56
57pub type TtsResponse = TextToSpeechResponse;
59
60#[derive(Debug, Clone, Serialize, Default)]
62pub struct SpeechToTextRequest {
63 pub model: String,
65
66 pub audio_base64: String,
68
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub filename: Option<String>,
72
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub language: Option<String>,
76}
77
78pub type SttRequest = SpeechToTextRequest;
80
81#[derive(Debug, Clone, Deserialize)]
83pub struct SpeechToTextResponse {
84 pub text: String,
86
87 pub model: String,
89
90 #[serde(default)]
92 pub cost_ticks: i64,
93
94 #[serde(default)]
96 pub request_id: String,
97}
98
99pub type SttResponse = SpeechToTextResponse;
101
102#[derive(Debug, Clone, Serialize, Default)]
104pub struct MusicRequest {
105 pub model: String,
107
108 pub prompt: String,
110
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub duration_seconds: Option<i32>,
114}
115
116#[derive(Debug, Clone, Deserialize)]
118pub struct MusicResponse {
119 #[serde(default)]
121 pub audio_clips: Vec<MusicClip>,
122
123 #[serde(default)]
125 pub model: String,
126
127 #[serde(default)]
129 pub cost_ticks: i64,
130
131 #[serde(default)]
133 pub request_id: String,
134}
135
136#[derive(Debug, Clone, Deserialize)]
138pub struct MusicClip {
139 pub base64: String,
141
142 #[serde(default)]
144 pub format: String,
145
146 #[serde(default)]
148 pub size_bytes: i64,
149
150 #[serde(default)]
152 pub index: i32,
153}
154
155#[derive(Debug, Clone, Serialize, Default)]
157pub struct SoundEffectRequest {
158 pub prompt: String,
160
161 #[serde(skip_serializing_if = "Option::is_none")]
163 pub duration_seconds: Option<f64>,
164}
165
166#[derive(Debug, Clone, Deserialize)]
168pub struct SoundEffectResponse {
169 pub audio_base64: String,
171
172 pub format: String,
174
175 #[serde(default)]
177 pub size_bytes: i64,
178
179 #[serde(default)]
181 pub model: String,
182
183 #[serde(default)]
185 pub cost_ticks: i64,
186
187 #[serde(default)]
189 pub request_id: String,
190}
191
192#[derive(Debug, Clone, Deserialize)]
198pub struct AudioResponse {
199 #[serde(default)]
201 pub audio_base64: Option<String>,
202
203 #[serde(default)]
205 pub format: Option<String>,
206
207 #[serde(default)]
209 pub size_bytes: Option<i64>,
210
211 #[serde(default)]
213 pub model: Option<String>,
214
215 #[serde(default)]
217 pub cost_ticks: i64,
218
219 #[serde(default)]
221 pub request_id: String,
222
223 #[serde(flatten)]
225 pub extra: HashMap<String, serde_json::Value>,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize, Default)]
230pub struct DialogueTurn {
231 pub speaker: String,
233
234 pub text: String,
236
237 #[serde(skip_serializing_if = "Option::is_none")]
239 pub voice: Option<String>,
240}
241
242#[derive(Debug, Clone, Serialize)]
244pub struct DialogueVoice {
245 pub voice_id: String,
246 pub name: String,
247}
248
249#[derive(Debug, Clone, Serialize, Default)]
252pub struct DialogueRequest {
253 pub text: String,
255
256 pub voices: Vec<DialogueVoice>,
258
259 #[serde(skip_serializing_if = "Option::is_none")]
261 pub model: Option<String>,
262
263 #[serde(rename = "output_format", skip_serializing_if = "Option::is_none")]
265 pub output_format: Option<String>,
266
267 #[serde(skip_serializing_if = "Option::is_none")]
269 pub seed: Option<i32>,
270}
271
272impl DialogueRequest {
273 pub fn from_turns(turns: Vec<DialogueTurn>, model: Option<String>) -> Self {
276 let text = turns.iter()
278 .map(|t| format!("{}: {}", t.speaker, t.text))
279 .collect::<Vec<_>>()
280 .join("\n");
281
282 let mut seen = std::collections::HashSet::new();
284 let voices: Vec<DialogueVoice> = turns.iter()
285 .filter(|t| t.voice.is_some() && seen.insert(t.speaker.clone()))
286 .map(|t| DialogueVoice {
287 voice_id: t.voice.clone().unwrap_or_default(),
288 name: t.speaker.clone(),
289 })
290 .collect();
291
292 Self {
293 text,
294 voices,
295 model,
296 ..Default::default()
297 }
298 }
299}
300
301#[derive(Debug, Clone, Serialize, Default)]
303pub struct SpeechToSpeechRequest {
304 #[serde(skip_serializing_if = "Option::is_none")]
306 pub model: Option<String>,
307
308 pub audio_base64: String,
310
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub voice_id: Option<String>,
314
315 #[serde(skip_serializing_if = "Option::is_none")]
317 pub voice: Option<String>,
318
319 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
321 pub output_format: Option<String>,
322}
323
324#[derive(Debug, Clone, Serialize, Default)]
326pub struct IsolateVoiceRequest {
327 pub audio_base64: String,
329
330 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
332 pub output_format: Option<String>,
333}
334
335pub type IsolateRequest = IsolateVoiceRequest;
337
338#[derive(Debug, Clone, Serialize, Default)]
340pub struct RemixVoiceRequest {
341 pub audio_base64: String,
343
344 #[serde(skip_serializing_if = "Option::is_none")]
346 pub voice: Option<String>,
347
348 #[serde(skip_serializing_if = "Option::is_none")]
350 pub model: Option<String>,
351
352 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
354 pub output_format: Option<String>,
355}
356
357pub type RemixRequest = RemixVoiceRequest;
359
360#[derive(Debug, Clone, Serialize, Default)]
362pub struct DubRequest {
363 #[serde(skip_serializing_if = "Option::is_none")]
365 pub audio_base64: Option<String>,
366
367 #[serde(skip_serializing_if = "Option::is_none")]
369 pub filename: Option<String>,
370
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub source_url: Option<String>,
374
375 pub target_lang: String,
377
378 #[serde(skip_serializing_if = "Option::is_none")]
380 pub source_lang: Option<String>,
381
382 #[serde(skip_serializing_if = "Option::is_none")]
384 pub num_speakers: Option<i32>,
385
386 #[serde(skip_serializing_if = "Option::is_none")]
388 pub highest_resolution: Option<bool>,
389}
390
391#[derive(Debug, Clone, Serialize, Default)]
393pub struct AlignRequest {
394 pub audio_base64: String,
396
397 pub text: String,
399
400 #[serde(skip_serializing_if = "Option::is_none")]
402 pub language: Option<String>,
403}
404
405#[derive(Debug, Clone, Deserialize)]
407pub struct AlignmentSegment {
408 pub text: String,
410
411 pub start: f64,
413
414 pub end: f64,
416}
417
418#[derive(Debug, Clone, Deserialize)]
420pub struct AlignedWord {
421 pub text: String,
423
424 pub start_time: f64,
426
427 pub end_time: f64,
429
430 #[serde(default)]
432 pub confidence: f64,
433}
434
435#[derive(Debug, Clone, Deserialize)]
437pub struct AlignResponse {
438 #[serde(default)]
440 pub segments: Vec<AlignmentSegment>,
441
442 #[serde(default)]
444 pub alignment: Vec<AlignedWord>,
445
446 #[serde(default)]
448 pub model: String,
449
450 #[serde(default)]
452 pub cost_ticks: i64,
453
454 #[serde(default)]
456 pub request_id: String,
457}
458
459#[derive(Debug, Clone, Deserialize)]
465pub struct DialogueResponse {
466 pub audio_base64: String,
467 pub format: String,
468 #[serde(default)]
469 pub size_bytes: i64,
470 #[serde(default)]
471 pub model: String,
472 #[serde(default)]
473 pub cost_ticks: i64,
474 #[serde(default)]
475 pub request_id: String,
476}
477
478#[derive(Debug, Clone, Deserialize)]
480pub struct SpeechToSpeechResponse {
481 pub audio_base64: String,
482 pub format: String,
483 #[serde(default)]
484 pub size_bytes: i64,
485 #[serde(default)]
486 pub model: String,
487 #[serde(default)]
488 pub cost_ticks: i64,
489 #[serde(default)]
490 pub request_id: String,
491}
492
493#[derive(Debug, Clone, Deserialize)]
495pub struct IsolateVoiceResponse {
496 pub audio_base64: String,
497 pub format: String,
498 #[serde(default)]
499 pub size_bytes: i64,
500 #[serde(default)]
501 pub cost_ticks: i64,
502 #[serde(default)]
503 pub request_id: String,
504}
505
506#[derive(Debug, Clone, Deserialize)]
508pub struct RemixVoiceResponse {
509 #[serde(default)]
510 pub audio_base64: Option<String>,
511 pub format: String,
512 #[serde(default)]
513 pub size_bytes: i64,
514 #[serde(default)]
515 pub voice_id: Option<String>,
516 #[serde(default)]
517 pub cost_ticks: i64,
518 #[serde(default)]
519 pub request_id: String,
520}
521
522#[derive(Debug, Clone, Deserialize)]
524pub struct DubResponse {
525 pub dubbing_id: String,
526 pub audio_base64: String,
527 pub format: String,
528 #[serde(default)]
529 pub target_lang: String,
530 #[serde(default)]
531 pub status: String,
532 #[serde(default)]
533 pub processing_time_seconds: f64,
534 #[serde(default)]
535 pub cost_ticks: i64,
536 #[serde(default)]
537 pub request_id: String,
538}
539
540#[derive(Debug, Clone, Deserialize)]
542pub struct VoiceDesignResponse {
543 pub previews: Vec<VoicePreview>,
544 #[serde(default)]
545 pub cost_ticks: i64,
546 #[serde(default)]
547 pub request_id: String,
548}
549
550#[derive(Debug, Clone, Deserialize)]
552pub struct VoicePreview {
553 pub generated_voice_id: String,
554 pub audio_base64: String,
555 pub format: String,
556}
557
558#[derive(Debug, Clone, Deserialize)]
560pub struct StarfishTTSResponse {
561 #[serde(default)]
562 pub audio_base64: Option<String>,
563 #[serde(default)]
564 pub url: Option<String>,
565 pub format: String,
566 #[serde(default)]
567 pub size_bytes: i64,
568 #[serde(default)]
569 pub duration: f64,
570 #[serde(default)]
571 pub model: String,
572 #[serde(default)]
573 pub cost_ticks: i64,
574 #[serde(default)]
575 pub request_id: String,
576}
577
578#[derive(Debug, Clone, Serialize, Default)]
580pub struct MusicAdvancedRequest {
581 pub prompt: String,
582 #[serde(skip_serializing_if = "Option::is_none")]
583 pub duration_seconds: Option<i32>,
584 #[serde(skip_serializing_if = "Option::is_none")]
585 pub model: Option<String>,
586 #[serde(skip_serializing_if = "Option::is_none")]
587 pub finetune_id: Option<String>,
588}
589
590#[derive(Debug, Clone, Deserialize)]
592pub struct MusicAdvancedClip {
593 #[serde(default)]
594 pub base64: String,
595 #[serde(default)]
596 pub format: String,
597 #[serde(default)]
598 pub size: i64,
599}
600
601#[derive(Debug, Clone, Deserialize)]
603pub struct MusicAdvancedResponse {
604 #[serde(default)]
605 pub clips: Vec<MusicAdvancedClip>,
606 #[serde(default)]
607 pub model: String,
608 #[serde(default)]
609 pub cost_ticks: i64,
610 #[serde(default)]
611 pub request_id: String,
612}
613
614#[derive(Debug, Clone, Serialize, Deserialize)]
616pub struct MusicFinetuneInfo {
617 pub finetune_id: String,
618 pub name: String,
619 #[serde(default)]
620 pub description: Option<String>,
621 #[serde(default)]
622 pub status: String,
623 #[serde(default)]
624 pub model_id: Option<String>,
625 #[serde(default)]
626 pub created_at: Option<String>,
627}
628
629#[derive(Debug, Clone, Deserialize)]
631pub struct MusicFinetuneListResponse {
632 pub finetunes: Vec<MusicFinetuneInfo>,
633}
634
635#[derive(Debug, Clone, Serialize)]
637pub struct MusicFinetuneCreateRequest {
638 pub name: String,
639 #[serde(skip_serializing_if = "Option::is_none")]
640 pub description: Option<String>,
641 pub samples: Vec<String>,
642}
643
644#[derive(Debug, Clone, Serialize, Default)]
646pub struct VoiceDesignRequest {
647 #[serde(rename = "voice_description")]
649 pub description: String,
650
651 #[serde(rename = "sample_text")]
653 pub text: String,
654
655 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
657 pub output_format: Option<String>,
658}
659
660#[derive(Debug, Clone, Serialize, Default)]
662pub struct StarfishTTSRequest {
663 pub text: String,
665
666 #[serde(skip_serializing_if = "Option::is_none")]
668 pub voice_id: Option<String>,
669
670 #[serde(skip_serializing_if = "Option::is_none")]
672 pub voice: Option<String>,
673
674 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
676 pub output_format: Option<String>,
677
678 #[serde(skip_serializing_if = "Option::is_none")]
680 pub input_type: Option<String>,
681
682 #[serde(skip_serializing_if = "Option::is_none")]
684 pub speed: Option<f64>,
685
686 #[serde(skip_serializing_if = "Option::is_none")]
688 pub language: Option<String>,
689
690 #[serde(skip_serializing_if = "Option::is_none")]
692 pub locale: Option<String>,
693}
694
695#[derive(Debug, Clone, Serialize, Deserialize, Default)]
701pub struct MusicSection {
702 pub section_type: String,
703 #[serde(skip_serializing_if = "Option::is_none")]
704 pub lyrics: Option<String>,
705 #[serde(skip_serializing_if = "Option::is_none")]
706 pub style: Option<String>,
707 #[serde(skip_serializing_if = "Option::is_none")]
708 pub style_exclude: Option<String>,
709}
710
711#[derive(Debug, Clone, Serialize, Default)]
713pub struct ElevenMusicRequest {
714 pub model: String,
715 pub prompt: String,
716 #[serde(skip_serializing_if = "Option::is_none")]
717 pub sections: Option<Vec<MusicSection>>,
718 #[serde(skip_serializing_if = "Option::is_none")]
719 pub duration_seconds: Option<i32>,
720 #[serde(skip_serializing_if = "Option::is_none")]
721 pub language: Option<String>,
722 #[serde(skip_serializing_if = "Option::is_none")]
723 pub vocals: Option<bool>,
724 #[serde(skip_serializing_if = "Option::is_none")]
725 pub style: Option<String>,
726 #[serde(skip_serializing_if = "Option::is_none")]
727 pub style_exclude: Option<String>,
728 #[serde(skip_serializing_if = "Option::is_none")]
729 pub finetune_id: Option<String>,
730 #[serde(skip_serializing_if = "Option::is_none")]
731 pub edit_reference_id: Option<String>,
732 #[serde(skip_serializing_if = "Option::is_none")]
733 pub edit_instruction: Option<String>,
734}
735
736#[derive(Debug, Clone, Deserialize)]
738pub struct ElevenMusicClip {
739 #[serde(default)]
741 pub base64: String,
742 #[serde(default)]
744 pub format: String,
745 #[serde(default)]
747 pub size: i64,
748}
749
750#[derive(Debug, Clone, Deserialize)]
753pub struct ElevenMusicResponse {
754 #[serde(default)]
756 pub clips: Vec<ElevenMusicClip>,
757 #[serde(default)]
759 pub model: String,
760 #[serde(default)]
762 pub cost_ticks: i64,
763 #[serde(default)]
765 pub request_id: String,
766}
767
768#[derive(Debug, Clone, Serialize, Deserialize)]
770pub struct FinetuneInfo {
771 pub finetune_id: String,
772 pub name: String,
773 #[serde(default)]
774 pub status: String,
775 #[serde(default)]
776 pub created_at: Option<String>,
777}
778
779#[derive(Debug, Clone, Deserialize)]
781pub struct ListFinetunesResponse {
782 pub finetunes: Vec<FinetuneInfo>,
783}
784
785impl Client {
790 pub async fn speak(&self, req: &TextToSpeechRequest) -> Result<TextToSpeechResponse> {
792 let (mut resp, meta) = self
793 .post_json::<TextToSpeechRequest, TextToSpeechResponse>("/qai/v1/audio/tts", req)
794 .await?;
795 if resp.cost_ticks == 0 {
796 resp.cost_ticks = meta.cost_ticks;
797 }
798 if resp.request_id.is_empty() {
799 resp.request_id = meta.request_id;
800 }
801 Ok(resp)
802 }
803
804 pub async fn transcribe(&self, req: &SpeechToTextRequest) -> Result<SpeechToTextResponse> {
806 let (mut resp, meta) = self
807 .post_json::<SpeechToTextRequest, SpeechToTextResponse>("/qai/v1/audio/stt", req)
808 .await?;
809 if resp.cost_ticks == 0 {
810 resp.cost_ticks = meta.cost_ticks;
811 }
812 if resp.request_id.is_empty() {
813 resp.request_id = meta.request_id;
814 }
815 Ok(resp)
816 }
817
818 pub async fn sound_effects(&self, req: &SoundEffectRequest) -> Result<SoundEffectResponse> {
820 let (mut resp, meta) = self
821 .post_json::<SoundEffectRequest, SoundEffectResponse>(
822 "/qai/v1/audio/sound-effects",
823 req,
824 )
825 .await?;
826 if resp.cost_ticks == 0 {
827 resp.cost_ticks = meta.cost_ticks;
828 }
829 if resp.request_id.is_empty() {
830 resp.request_id = meta.request_id;
831 }
832 Ok(resp)
833 }
834
835 pub async fn generate_music(&self, req: &MusicRequest) -> Result<MusicResponse> {
837 let (mut resp, meta) = self
838 .post_json::<MusicRequest, MusicResponse>("/qai/v1/audio/music", req)
839 .await?;
840 if resp.cost_ticks == 0 {
841 resp.cost_ticks = meta.cost_ticks;
842 }
843 if resp.request_id.is_empty() {
844 resp.request_id = meta.request_id;
845 }
846 Ok(resp)
847 }
848
849 pub async fn dialogue(&self, req: &DialogueRequest) -> Result<AudioResponse> {
851 let (mut resp, meta) = self
852 .post_json::<DialogueRequest, AudioResponse>("/qai/v1/audio/dialogue", req)
853 .await?;
854 if resp.cost_ticks == 0 {
855 resp.cost_ticks = meta.cost_ticks;
856 }
857 if resp.request_id.is_empty() {
858 resp.request_id = meta.request_id;
859 }
860 Ok(resp)
861 }
862
863 pub async fn speech_to_speech(
865 &self,
866 req: &SpeechToSpeechRequest,
867 ) -> Result<AudioResponse> {
868 let (mut resp, meta) = self
869 .post_json::<SpeechToSpeechRequest, AudioResponse>(
870 "/qai/v1/audio/speech-to-speech",
871 req,
872 )
873 .await?;
874 if resp.cost_ticks == 0 {
875 resp.cost_ticks = meta.cost_ticks;
876 }
877 if resp.request_id.is_empty() {
878 resp.request_id = meta.request_id;
879 }
880 Ok(resp)
881 }
882
883 pub async fn isolate_voice(&self, req: &IsolateVoiceRequest) -> Result<AudioResponse> {
885 let (mut resp, meta) = self
886 .post_json::<IsolateVoiceRequest, AudioResponse>("/qai/v1/audio/isolate", req)
887 .await?;
888 if resp.cost_ticks == 0 {
889 resp.cost_ticks = meta.cost_ticks;
890 }
891 if resp.request_id.is_empty() {
892 resp.request_id = meta.request_id;
893 }
894 Ok(resp)
895 }
896
897 pub async fn remix_voice(&self, req: &RemixVoiceRequest) -> Result<AudioResponse> {
899 let (mut resp, meta) = self
900 .post_json::<RemixVoiceRequest, AudioResponse>("/qai/v1/audio/remix", req)
901 .await?;
902 if resp.cost_ticks == 0 {
903 resp.cost_ticks = meta.cost_ticks;
904 }
905 if resp.request_id.is_empty() {
906 resp.request_id = meta.request_id;
907 }
908 Ok(resp)
909 }
910
911 pub async fn dub(&self, req: &DubRequest) -> Result<AudioResponse> {
913 let (mut resp, meta) = self
914 .post_json::<DubRequest, AudioResponse>("/qai/v1/audio/dub", req)
915 .await?;
916 if resp.cost_ticks == 0 {
917 resp.cost_ticks = meta.cost_ticks;
918 }
919 if resp.request_id.is_empty() {
920 resp.request_id = meta.request_id;
921 }
922 Ok(resp)
923 }
924
925 pub async fn align(&self, req: &AlignRequest) -> Result<AlignResponse> {
927 let (mut resp, meta) = self
928 .post_json::<AlignRequest, AlignResponse>("/qai/v1/audio/align", req)
929 .await?;
930 if resp.cost_ticks == 0 {
931 resp.cost_ticks = meta.cost_ticks;
932 }
933 if resp.request_id.is_empty() {
934 resp.request_id = meta.request_id;
935 }
936 Ok(resp)
937 }
938
939 pub async fn voice_design(&self, req: &VoiceDesignRequest) -> Result<AudioResponse> {
941 let (mut resp, meta) = self
942 .post_json::<VoiceDesignRequest, AudioResponse>("/qai/v1/audio/voice-design", req)
943 .await?;
944 if resp.cost_ticks == 0 {
945 resp.cost_ticks = meta.cost_ticks;
946 }
947 if resp.request_id.is_empty() {
948 resp.request_id = meta.request_id;
949 }
950 Ok(resp)
951 }
952
953 pub async fn starfish_tts(&self, req: &StarfishTTSRequest) -> Result<AudioResponse> {
955 let (mut resp, meta) = self
956 .post_json::<StarfishTTSRequest, AudioResponse>("/qai/v1/audio/starfish-tts", req)
957 .await?;
958 if resp.cost_ticks == 0 {
959 resp.cost_ticks = meta.cost_ticks;
960 }
961 if resp.request_id.is_empty() {
962 resp.request_id = meta.request_id;
963 }
964 Ok(resp)
965 }
966
967 pub async fn generate_music_advanced(
969 &self,
970 req: &ElevenMusicRequest,
971 ) -> Result<ElevenMusicResponse> {
972 let (mut resp, meta) = self
973 .post_json::<ElevenMusicRequest, ElevenMusicResponse>(
974 "/qai/v1/audio/music/advanced",
975 req,
976 )
977 .await?;
978 if resp.cost_ticks == 0 {
979 resp.cost_ticks = meta.cost_ticks;
980 }
981 if resp.request_id.is_empty() {
982 resp.request_id = meta.request_id;
983 }
984 Ok(resp)
985 }
986
987 pub async fn list_finetunes(&self) -> Result<ListFinetunesResponse> {
989 let (resp, _) = self
990 .get_json::<ListFinetunesResponse>("/qai/v1/audio/finetunes")
991 .await?;
992 Ok(resp)
993 }
994
995 pub async fn create_finetune(
997 &self,
998 name: &str,
999 files: Vec<crate::voices::CloneVoiceFile>,
1000 ) -> Result<FinetuneInfo> {
1001 let mut form = reqwest::multipart::Form::new().text("name", name.to_string());
1002
1003 for file in files {
1004 let part = reqwest::multipart::Part::bytes(file.data)
1005 .file_name(file.filename)
1006 .mime_str(&file.mime_type)
1007 .map_err(|e| crate::error::Error::Http(e.into()))?;
1008 form = form.part("files", part);
1009 }
1010
1011 let (resp, _) = self
1012 .post_multipart::<FinetuneInfo>("/qai/v1/audio/finetunes", form)
1013 .await?;
1014 Ok(resp)
1015 }
1016
1017 pub async fn delete_finetune(&self, id: &str) -> Result<serde_json::Value> {
1019 let path = format!("/qai/v1/audio/finetunes/{id}");
1020 let (resp, _) = self.delete_json::<serde_json::Value>(&path).await?;
1021 Ok(resp)
1022 }
1023}