use std::collections::BTreeMap;
use reqwest::multipart::{Form, Part};
use tokio_util::io::ReaderStream;
use super::SunoClient;
use super::types::{
AudioUploadInitResponse, AudioUploadStatus, CreateAudioUploadRequest, CreateImageUploadRequest,
FinishAudioUploadRequest, FinishImageUploadResponse, ImageUploadInitResponse,
InitializeAudioClipRequest, InitializeAudioClipResponse,
};
use crate::core::CliError;
use crate::net::http;
impl SunoClient {
pub async fn create_audio_upload(
&self,
req: &CreateAudioUploadRequest,
) -> Result<AudioUploadInitResponse, CliError> {
self.with_auth_retry(|| async {
let resp = self.post("/api/uploads/audio/").json(req).send().await?;
let resp = self.check_response(resp).await?;
Ok(resp.json().await?)
})
.await
}
pub async fn upload_presigned_audio_file(
&self,
url: &str,
fields: &BTreeMap<String, String>,
filename: &str,
file: tokio::fs::File,
content_length: u64,
) -> Result<(), CliError> {
let body = reqwest::Body::wrap_stream(ReaderStream::new(file));
let part = Part::stream_with_length(body, content_length).file_name(filename.to_string());
self.upload_presigned_part(url, fields, part).await
}
pub async fn create_image_upload(
&self,
req: &CreateImageUploadRequest,
) -> Result<ImageUploadInitResponse, CliError> {
self.with_auth_retry(|| async {
let resp = self.post("/api/uploads/image/").json(req).send().await?;
let resp = self.check_response(resp).await?;
Ok(resp.json().await?)
})
.await
}
pub async fn upload_presigned_image_form(
&self,
url: &str,
fields: &BTreeMap<String, String>,
filename: &str,
content_type: Option<&str>,
bytes: Vec<u8>,
) -> Result<(), CliError> {
let mut part = Part::bytes(bytes).file_name(filename.to_string());
if let Some(content_type) = content_type {
part = part
.mime_str(content_type)
.map_err(|e| CliError::Config(format!("invalid upload content type: {e}")))?;
}
self.upload_presigned_part(url, fields, part).await
}
async fn upload_presigned_part(
&self,
url: &str,
fields: &BTreeMap<String, String>,
file_part: Part,
) -> Result<(), CliError> {
let mut form = Form::new();
for (key, value) in fields {
form = form.text(key.clone(), value.clone());
}
form = form.part("file", file_part);
let resp = http::transfer_client()?
.post(url)
.multipart(form)
.send()
.await?;
self.check_response(resp).await?;
Ok(())
}
pub async fn finish_audio_upload(
&self,
upload_id: &str,
req: &FinishAudioUploadRequest,
) -> Result<(), CliError> {
self.with_auth_retry(|| async {
let resp = self
.post(&format!("/api/uploads/audio/{upload_id}/upload-finish/"))
.json(req)
.send()
.await?;
self.check_response(resp).await?;
Ok(())
})
.await
}
pub async fn finish_image_upload(
&self,
upload_id: &str,
) -> Result<FinishImageUploadResponse, CliError> {
self.with_auth_retry(|| async {
let resp = self
.post(&format!("/api/uploads/image/{upload_id}/upload-finish/"))
.json(&serde_json::json!({}))
.send()
.await?;
let resp = self.check_response(resp).await?;
Ok(resp.json().await?)
})
.await
}
pub async fn get_audio_upload(&self, upload_id: &str) -> Result<AudioUploadStatus, CliError> {
self.with_auth_retry(|| async {
let resp = self
.get(&format!("/api/uploads/audio/{upload_id}/"))
.send()
.await?;
let resp = self.check_response(resp).await?;
Ok(resp.json().await?)
})
.await
}
pub async fn initialize_audio_clip(
&self,
upload_id: &str,
req: &InitializeAudioClipRequest,
) -> Result<InitializeAudioClipResponse, CliError> {
self.with_auth_retry(|| async {
let resp = self
.post(&format!("/api/uploads/audio/{upload_id}/initialize-clip/"))
.json(req)
.send()
.await?;
let resp = self.check_response(resp).await?;
Ok(resp.json().await?)
})
.await
}
}