use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use std::path::Path;
pub trait MediaData: Sized {
fn base64_data(&self) -> &str;
fn format_id(&self) -> &str;
fn mime_type(&self) -> String;
fn from_base64(base64_data: impl Into<String>, format: impl Into<String>) -> Self;
fn from_bytes(bytes: &[u8], format: impl Into<String>) -> Self {
Self::from_base64(BASE64.encode(bytes), format)
}
fn from_file(path: impl AsRef<Path>) -> crate::error::Result<Self> {
let path = path.as_ref();
let bytes = std::fs::read(path)?;
let format = Self::guess_format(path);
Ok(Self::from_bytes(&bytes, format))
}
fn from_file_async(
path: impl AsRef<Path> + Send,
) -> impl std::future::Future<Output = crate::error::Result<Self>> + Send
where
Self: Send,
{
let path = path.as_ref().to_path_buf();
async move {
let bytes = tokio::fs::read(&path).await?;
let format = Self::guess_format(&path);
Ok(Self::from_bytes(&bytes, format))
}
}
fn to_bytes(&self) -> crate::error::Result<Vec<u8>> {
Ok(BASE64.decode(self.base64_data())?)
}
fn guess_format(path: &Path) -> String;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "media_type")]
pub enum Media {
#[serde(rename = "image")]
Image(super::ImageData),
#[serde(rename = "audio")]
Audio(super::AudioData),
#[serde(rename = "video")]
Video(super::VideoData),
}
impl Media {
pub fn image(data: super::ImageData) -> Self {
Self::Image(data)
}
pub fn audio(data: super::AudioData) -> Self {
Self::Audio(data)
}
pub fn video(data: super::VideoData) -> Self {
Self::Video(data)
}
pub fn mime_type(&self) -> String {
match self {
Self::Image(img) => {
if img.mime_type == "url" {
"url".to_string()
} else {
img.mime_type.clone()
}
}
Self::Audio(audio) => audio.mime_type(),
Self::Video(video) => video.mime_type(),
}
}
pub fn is_image(&self) -> bool {
matches!(self, Self::Image(_))
}
pub fn is_audio(&self) -> bool {
matches!(self, Self::Audio(_))
}
pub fn is_video(&self) -> bool {
matches!(self, Self::Video(_))
}
pub fn as_image(&self) -> Option<&super::ImageData> {
match self {
Self::Image(img) => Some(img),
_ => None,
}
}
pub fn as_audio(&self) -> Option<&super::AudioData> {
match self {
Self::Audio(audio) => Some(audio),
_ => None,
}
}
pub fn as_video(&self) -> Option<&super::VideoData> {
match self {
Self::Video(video) => Some(video),
_ => None,
}
}
}
impl From<super::ImageData> for Media {
fn from(data: super::ImageData) -> Self {
Self::Image(data)
}
}
impl From<super::AudioData> for Media {
fn from(data: super::AudioData) -> Self {
Self::Audio(data)
}
}
impl From<super::VideoData> for Media {
fn from(data: super::VideoData) -> Self {
Self::Video(data)
}
}