use serde::{Deserialize, Serialize};
use crate::client::Client;
use crate::error::Result;
use crate::keys::StatusResponse;
#[derive(Debug, Clone, Deserialize)]
pub struct Voice {
pub voice_id: String,
pub name: String,
#[serde(default)]
pub provider: Option<String>,
#[serde(default)]
pub languages: Option<Vec<String>>,
#[serde(default)]
pub gender: Option<String>,
#[serde(default)]
pub is_cloned: Option<bool>,
#[serde(default)]
pub preview_url: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct VoicesResponse {
pub voices: Vec<Voice>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct VoiceInfo {
pub voice_id: String,
pub name: String,
#[serde(default)]
pub category: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub preview_url: Option<String>,
}
#[derive(Debug, Clone)]
pub struct CloneVoiceFile {
pub filename: String,
pub data: Vec<u8>,
pub mime_type: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CloneVoiceResponse {
pub voice_id: String,
pub name: String,
#[serde(default)]
pub status: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SharedVoice {
pub public_owner_id: String,
pub voice_id: String,
pub name: String,
#[serde(default)]
pub category: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub preview_url: Option<String>,
#[serde(default)]
pub gender: Option<String>,
#[serde(default)]
pub age: Option<String>,
#[serde(default)]
pub accent: Option<String>,
#[serde(default)]
pub language: Option<String>,
#[serde(default)]
pub use_case: Option<String>,
#[serde(default)]
pub rate: Option<f64>,
#[serde(default)]
pub cloned_by_count: Option<i64>,
#[serde(default)]
pub free_users_allowed: Option<bool>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SharedVoicesResponse {
pub voices: Vec<SharedVoice>,
#[serde(default)]
pub next_cursor: Option<String>,
#[serde(default)]
pub has_more: bool,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct VoiceLibraryQuery {
#[serde(skip_serializing_if = "Option::is_none")]
pub query: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page_size: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gender: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub use_case: Option<String>,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct AddVoiceFromLibraryRequest {
pub public_owner_id: String,
pub voice_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct CloneVoiceRequest {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub audio_samples: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AddVoiceFromLibraryResponse {
pub voice_id: String,
}
fn encode_query_value(s: &str) -> String {
urlencoding::encode(s).into_owned()
}
impl Client {
pub async fn list_voices(&self) -> Result<VoicesResponse> {
let (resp, _meta) = self
.get_json::<VoicesResponse>("/qai/v1/voices")
.await?;
Ok(resp)
}
pub async fn clone_voice(
&self,
name: &str,
files: Vec<CloneVoiceFile>,
) -> Result<CloneVoiceResponse> {
let mut form = reqwest::multipart::Form::new().text("name", name.to_string());
for file in files {
let part = reqwest::multipart::Part::bytes(file.data)
.file_name(file.filename)
.mime_str(&file.mime_type)
.map_err(|e| crate::error::Error::Http(e.into()))?;
form = form.part("files", part);
}
let (resp, _meta) = self
.post_multipart::<CloneVoiceResponse>("/qai/v1/voices/clone", form)
.await?;
Ok(resp)
}
pub async fn delete_voice(&self, id: &str) -> Result<StatusResponse> {
let path = format!("/qai/v1/voices/{id}");
let (resp, _meta) = self.delete_json::<StatusResponse>(&path).await?;
Ok(resp)
}
pub async fn voice_library(
&self,
query: &VoiceLibraryQuery,
) -> Result<SharedVoicesResponse> {
let mut params = Vec::new();
if let Some(ref q) = query.query {
params.push(format!("query={}", encode_query_value(q)));
}
if let Some(ps) = query.page_size {
params.push(format!("page_size={ps}"));
}
if let Some(ref c) = query.cursor {
params.push(format!("cursor={}", encode_query_value(c)));
}
if let Some(ref g) = query.gender {
params.push(format!("gender={}", encode_query_value(g)));
}
if let Some(ref l) = query.language {
params.push(format!("language={}", encode_query_value(l)));
}
if let Some(ref u) = query.use_case {
params.push(format!("use_case={}", encode_query_value(u)));
}
let path = if params.is_empty() {
"/qai/v1/voices/library".to_string()
} else {
format!("/qai/v1/voices/library?{}", params.join("&"))
};
let (resp, _meta) = self
.get_json::<SharedVoicesResponse>(&path)
.await?;
Ok(resp)
}
pub async fn add_voice_from_library(
&self,
public_owner_id: &str,
voice_id: &str,
name: Option<&str>,
) -> Result<AddVoiceFromLibraryResponse> {
let mut body = serde_json::json!({
"public_owner_id": public_owner_id,
"voice_id": voice_id,
});
if let Some(n) = name {
body["name"] = serde_json::Value::String(n.to_string());
}
let (resp, _meta) = self
.post_json::<serde_json::Value, AddVoiceFromLibraryResponse>(
"/qai/v1/voices/library/add",
&body,
)
.await?;
Ok(resp)
}
}