1use std::path::PathBuf;
4
5use base64::{engine::general_purpose, Engine as _};
6use thiserror::Error;
7
8#[derive(Debug, Error)]
10pub enum MediaError {
11 #[error("HTTP error: {0}")]
12 Http(#[from] reqwest::Error),
13 #[error("IO error: {0}")]
14 Io(#[from] std::io::Error),
15 #[error("Invalid media content: empty or corrupted")]
16 InvalidContent,
17}
18
19pub async fn download_media(
23 url: &str,
24 filename: Option<&str>,
25 save_dir: &PathBuf,
26) -> Result<PathBuf, MediaError> {
27 tokio::fs::create_dir_all(save_dir).await?;
29
30 let save_filename = match filename {
32 Some(s) => s.to_string(),
33 None => uuid::Uuid::new_v4().to_string(),
34 };
35 let save_path = save_dir.join(save_filename);
36
37 let client = reqwest::Client::new();
39 let response = client.get(url).send().await?;
40 let bytes = response.bytes().await?;
41
42 if bytes.is_empty() {
43 return Err(MediaError::InvalidContent);
44 }
45
46 tokio::fs::write(&save_path, &bytes).await?;
47
48 Ok(save_path)
49}
50
51pub async fn download_and_encode_base64(
55 url: &str,
56 content_type: &str,
57) -> Result<String, MediaError> {
58 let client = reqwest::Client::new();
59 let bytes = client.get(url).send().await?.bytes().await?;
60
61 if bytes.is_empty() {
62 return Err(MediaError::InvalidContent);
63 }
64
65 let base64 = general_purpose::STANDARD.encode(&bytes);
66 Ok(format!("data:{};base64,{}", content_type, base64))
67}