use std::{
fs::{self},
io::{self},
path::{Path, PathBuf},
};
use blake3::Hash;
use image::{DynamicImage, ImageError, imageops::FilterType};
use serde::{Deserialize, Serialize};
use crate::cache_dir;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct ImageArt {
hash: Hash,
source: PathBuf,
}
pub trait CacheableArt {
fn cache_file(&self, x: u32, y: u32) -> io::Result<PathBuf>;
}
impl ImageArt {
pub fn from_file(source: impl Into<PathBuf>) -> Result<Self, ImageError> {
let source = source.into();
let image = image::open(&source)?;
let hash = blake3::hash(image.as_bytes());
Ok(Self { hash, source })
}
#[must_use]
pub fn hash(&self) -> Hash {
self.hash
}
#[must_use]
pub fn source(&self) -> &Path {
&self.source
}
}
impl CacheableArt for ImageArt {
fn cache_file(&self, x: u32, y: u32) -> io::Result<PathBuf> {
let cover_dir = cache_dir().join("covers");
let cache_file = cover_dir.join(format!("{id}_{x}_{x}.webp", id = self.hash()));
if cache_file.exists() {
return Ok(cache_file);
}
let image = image::open(&self.source).expect("Image art should be pre-validated");
cache_image(image, x, y, &cache_file)?;
Ok(cache_file)
}
}
pub fn hash_image(source: impl AsRef<Path>) -> Result<Hash, ImageError> {
let image = image::open(source)?;
Ok(blake3::hash(image.as_bytes()))
}
pub fn cache_image(image: DynamicImage, x: u32, y: u32, path: impl AsRef<Path>) -> io::Result<()> {
let image = image.resize_to_fill(x, y, FilterType::Lanczos3);
let encoder = webp::Encoder::from_image(&image).unwrap();
let bytes = encoder.encode(80.0);
let path = path.as_ref();
fs::create_dir_all(path.parent().unwrap())?;
fs::write(path, &*bytes)?;
Ok(())
}