1use serde::{Deserialize, Serialize};
2
3use crate::client::Client;
4use crate::error::Result;
5use crate::keys::StatusResponse;
6
7#[derive(Debug, Clone, Deserialize)]
9pub struct Voice {
10 pub voice_id: String,
12
13 pub name: String,
15
16 #[serde(default)]
18 pub provider: Option<String>,
19
20 #[serde(default)]
22 pub languages: Option<Vec<String>>,
23
24 #[serde(default)]
26 pub gender: Option<String>,
27
28 #[serde(default)]
30 pub is_cloned: Option<bool>,
31
32 #[serde(default)]
34 pub preview_url: Option<String>,
35}
36
37#[derive(Debug, Clone, Deserialize)]
39pub struct VoicesResponse {
40 pub voices: Vec<Voice>,
42}
43
44#[derive(Debug, Clone, Deserialize)]
46pub struct VoiceInfo {
47 pub voice_id: String,
49
50 pub name: String,
52
53 #[serde(default)]
55 pub category: String,
56
57 #[serde(default)]
59 pub description: Option<String>,
60
61 #[serde(default)]
63 pub preview_url: Option<String>,
64}
65
66#[derive(Debug, Clone)]
68pub struct CloneVoiceFile {
69 pub filename: String,
71
72 pub data: Vec<u8>,
74
75 pub mime_type: String,
77}
78
79#[derive(Debug, Clone, Deserialize)]
81pub struct CloneVoiceResponse {
82 pub voice_id: String,
84
85 pub name: String,
87
88 #[serde(default)]
90 pub status: Option<String>,
91}
92
93#[derive(Debug, Clone, Deserialize)]
99pub struct SharedVoice {
100 pub public_owner_id: String,
102
103 pub voice_id: String,
105
106 pub name: String,
108
109 #[serde(default)]
111 pub category: Option<String>,
112
113 #[serde(default)]
115 pub description: Option<String>,
116
117 #[serde(default)]
119 pub preview_url: Option<String>,
120
121 #[serde(default)]
123 pub gender: Option<String>,
124
125 #[serde(default)]
127 pub age: Option<String>,
128
129 #[serde(default)]
131 pub accent: Option<String>,
132
133 #[serde(default)]
135 pub language: Option<String>,
136
137 #[serde(default)]
139 pub use_case: Option<String>,
140
141 #[serde(default)]
143 pub rate: Option<f64>,
144
145 #[serde(default)]
147 pub cloned_by_count: Option<i64>,
148
149 #[serde(default)]
151 pub free_users_allowed: Option<bool>,
152}
153
154#[derive(Debug, Clone, Deserialize)]
156pub struct SharedVoicesResponse {
157 pub voices: Vec<SharedVoice>,
159
160 #[serde(default)]
162 pub next_cursor: Option<String>,
163
164 #[serde(default)]
166 pub has_more: bool,
167}
168
169#[derive(Debug, Clone, Serialize, Default)]
171pub struct VoiceLibraryQuery {
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub query: Option<String>,
175
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub page_size: Option<i32>,
179
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub cursor: Option<String>,
183
184 #[serde(skip_serializing_if = "Option::is_none")]
186 pub gender: Option<String>,
187
188 #[serde(skip_serializing_if = "Option::is_none")]
190 pub language: Option<String>,
191
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub use_case: Option<String>,
195}
196
197#[derive(Debug, Clone, Serialize, Default)]
199pub struct AddVoiceFromLibraryRequest {
200 pub public_owner_id: String,
202
203 pub voice_id: String,
205
206 #[serde(skip_serializing_if = "Option::is_none")]
208 pub name: Option<String>,
209}
210
211#[derive(Debug, Clone, Serialize, Default)]
213pub struct CloneVoiceRequest {
214 pub name: String,
216
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub description: Option<String>,
220
221 pub audio_samples: Vec<String>,
223}
224
225#[derive(Debug, Clone, Deserialize)]
227pub struct AddVoiceFromLibraryResponse {
228 pub voice_id: String,
230}
231
232fn encode_query_value(s: &str) -> String {
234 urlencoding::encode(s).into_owned()
235}
236
237impl Client {
238 pub async fn list_voices(&self) -> Result<VoicesResponse> {
240 let (resp, _meta) = self
241 .get_json::<VoicesResponse>("/qai/v1/voices")
242 .await?;
243 Ok(resp)
244 }
245
246 pub async fn clone_voice(
250 &self,
251 name: &str,
252 files: Vec<CloneVoiceFile>,
253 ) -> Result<CloneVoiceResponse> {
254 let mut form = reqwest::multipart::Form::new().text("name", name.to_string());
255
256 for file in files {
257 let part = reqwest::multipart::Part::bytes(file.data)
258 .file_name(file.filename)
259 .mime_str(&file.mime_type)
260 .map_err(|e| crate::error::Error::Http(e.into()))?;
261 form = form.part("files", part);
262 }
263
264 let (resp, _meta) = self
265 .post_multipart::<CloneVoiceResponse>("/qai/v1/voices/clone", form)
266 .await?;
267 Ok(resp)
268 }
269
270 pub async fn delete_voice(&self, id: &str) -> Result<StatusResponse> {
272 let path = format!("/qai/v1/voices/{id}");
273 let (resp, _meta) = self.delete_json::<StatusResponse>(&path).await?;
274 Ok(resp)
275 }
276
277 pub async fn voice_library(
279 &self,
280 query: &VoiceLibraryQuery,
281 ) -> Result<SharedVoicesResponse> {
282 let mut params = Vec::new();
283 if let Some(ref q) = query.query {
284 params.push(format!("query={}", encode_query_value(q)));
285 }
286 if let Some(ps) = query.page_size {
287 params.push(format!("page_size={ps}"));
288 }
289 if let Some(ref c) = query.cursor {
290 params.push(format!("cursor={}", encode_query_value(c)));
291 }
292 if let Some(ref g) = query.gender {
293 params.push(format!("gender={}", encode_query_value(g)));
294 }
295 if let Some(ref l) = query.language {
296 params.push(format!("language={}", encode_query_value(l)));
297 }
298 if let Some(ref u) = query.use_case {
299 params.push(format!("use_case={}", encode_query_value(u)));
300 }
301
302 let path = if params.is_empty() {
303 "/qai/v1/voices/library".to_string()
304 } else {
305 format!("/qai/v1/voices/library?{}", params.join("&"))
306 };
307
308 let (resp, _meta) = self
309 .get_json::<SharedVoicesResponse>(&path)
310 .await?;
311 Ok(resp)
312 }
313
314 pub async fn add_voice_from_library(
316 &self,
317 public_owner_id: &str,
318 voice_id: &str,
319 name: Option<&str>,
320 ) -> Result<AddVoiceFromLibraryResponse> {
321 let mut body = serde_json::json!({
322 "public_owner_id": public_owner_id,
323 "voice_id": voice_id,
324 });
325 if let Some(n) = name {
326 body["name"] = serde_json::Value::String(n.to_string());
327 }
328 let (resp, _meta) = self
329 .post_json::<serde_json::Value, AddVoiceFromLibraryResponse>(
330 "/qai/v1/voices/library/add",
331 &body,
332 )
333 .await?;
334 Ok(resp)
335 }
336}