use anyhow::{anyhow, Result};
use base64::{engine::general_purpose, Engine};
use image::{DynamicImage, ImageFormat};
use std::path::Path;
use viuer::{print_from_file, Config as ViuerConfig};
#[derive(Debug, Clone)]
pub struct MediaFile {
pub path: String,
pub media_type: MediaType,
pub size: Option<(u32, u32)>, pub duration: Option<f64>, pub thumbnail: Option<String>, }
#[derive(Debug, Clone, PartialEq)]
pub enum MediaType {
Image,
Video,
Audio,
Unknown,
}
impl MediaFile {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_str = path.as_ref().to_string_lossy().to_string();
let media_type = detect_media_type(&path_str)?;
let mut file = MediaFile {
path: path_str,
media_type,
size: None,
duration: None,
thumbnail: None,
};
match file.media_type {
MediaType::Image => {
if let Ok(img) = image::open(&file.path) {
file.size = Some((img.width(), img.height()));
file.thumbnail = generate_thumbnail(&img)?;
}
}
MediaType::Video => {
file.duration = Some(0.0); }
MediaType::Audio => {
file.duration = Some(0.0);
}
MediaType::Unknown => {}
}
Ok(file)
}
pub fn display_in_terminal(&self, max_width: u16, max_height: u16) -> Result<()> {
match self.media_type {
MediaType::Image => {
let conf = ViuerConfig {
transparent: false,
absolute_offset: false,
width: Some(max_width as u32),
height: Some(max_height as u32),
..Default::default()
};
print_from_file(&self.path, &conf)
.map_err(|e| anyhow!("Failed to display image: {}", e))?;
}
MediaType::Video => {
println!("đŦ Video: {} (duration: {:?}s)", self.path, self.duration);
}
MediaType::Audio => {
println!("đĩ Audio: {} (duration: {:?}s)", self.path, self.duration);
}
MediaType::Unknown => {
println!("â Unknown media type: {}", self.path);
}
}
Ok(())
}
pub fn display_info(&self) -> String {
match self.media_type {
MediaType::Image => {
if let Some((w, h)) = self.size {
format!(
"đŧī¸ {}x{} - {}",
w,
h,
Path::new(&self.path)
.file_name()
.unwrap_or_default()
.to_string_lossy()
)
} else {
format!(
"đŧī¸ {}",
Path::new(&self.path)
.file_name()
.unwrap_or_default()
.to_string_lossy()
)
}
}
MediaType::Video => {
format!(
"đŦ {} ({:.1}s)",
Path::new(&self.path)
.file_name()
.unwrap_or_default()
.to_string_lossy(),
self.duration.unwrap_or(0.0)
)
}
MediaType::Audio => {
format!(
"đĩ {} ({:.1}s)",
Path::new(&self.path)
.file_name()
.unwrap_or_default()
.to_string_lossy(),
self.duration.unwrap_or(0.0)
)
}
MediaType::Unknown => {
format!(
"â {}",
Path::new(&self.path)
.file_name()
.unwrap_or_default()
.to_string_lossy()
)
}
}
}
}
fn detect_media_type(path: &str) -> Result<MediaType> {
let extension = Path::new(path)
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("")
.to_lowercase();
match extension.as_str() {
"jpg" | "jpeg" | "png" | "gif" | "bmp" | "webp" | "tiff" | "svg" => Ok(MediaType::Image),
"mp4" | "avi" | "mov" | "mkv" | "wmv" | "flv" | "webm" | "m4v" => Ok(MediaType::Video),
"mp3" | "wav" | "flac" | "aac" | "ogg" | "wma" | "m4a" => Ok(MediaType::Audio),
_ => Ok(MediaType::Unknown),
}
}
fn generate_thumbnail(img: &DynamicImage) -> Result<Option<String>> {
let thumbnail = img.thumbnail(64, 64);
let mut buffer = Vec::new();
thumbnail
.write_to(&mut std::io::Cursor::new(&mut buffer), ImageFormat::Png)
.map_err(|e| anyhow!("Failed to generate thumbnail: {}", e))?;
Ok(Some(general_purpose::STANDARD.encode(buffer)))
}
pub struct AudioPlayer {
}
impl AudioPlayer {
pub fn new() -> Self {
Self {}
}
pub fn play(&self, _path: &str) -> Result<()> {
println!("đĩ Playing audio...");
Ok(())
}
pub fn stop(&self) -> Result<()> {
println!("âšī¸ Stopped audio");
Ok(())
}
}
pub fn get_supported_formats() -> Vec<&'static str> {
vec![
"jpg", "jpeg", "png", "gif", "bmp", "webp", "tiff", "svg", "mp4", "avi", "mov", "mkv", "wmv", "flv", "webm", "m4v", "mp3", "wav", "flac", "aac", "ogg", "wma", "m4a",
]
}
pub fn is_media_file(path: &str) -> bool {
let extension = Path::new(path)
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("")
.to_lowercase();
get_supported_formats().contains(&extension.as_str())
}