use std::path::{PathBuf, Path}; use std::fs; use std::io; use std::io::{Read, Seek}; use image::io::Reader as ImageReader; use image::error::ImageError; use image::imageops; use serde::Serialize; use crate::context::Context; #[derive(Debug)] pub enum LoadError { Io(io::Error), ExifParser(exif::Error) } #[derive(Debug)] pub enum ConversionError { Io(io::Error), ImageError(ImageError), } #[derive(Debug, Serialize)] pub struct Picture { pub taken: Option, hash: String, pub path: PathBuf, } pub struct Converter<'a> { imgdata: Option, pic: &'a Picture, } fn hash_reader(reader: &mut R) -> Result { let mut hash = md5::Context::new(); let mut buff = [0; 1024]; loop { let count = reader.read(&mut buff)?; if count == 0 { // Reached end, stopping break } hash.consume(&buff[..count]) } Ok(format!("{:?}", hash.compute())) } impl Picture { /// Hash file content and load exif data. pub fn new_from_file(path: &Path) -> Result { let file = fs::File::open(path)?; let mut reader = io::BufReader::new(&file); let taken = match exif::Reader::new().read_from_container(&mut reader) { Ok(exif) => exif.get_field(exif::Tag::DateTimeOriginal, exif::In::PRIMARY) .map(|field| field.display_value().with_unit(&exif).to_string()), Err(err) => { println!("Could not load exif data for {}: {}", path.to_str().unwrap(), err); None } }; // Move back to start of file for hashing reader.seek(io::SeekFrom::Start(0))?; Ok(Picture { taken, hash: hash_reader(&mut reader)?, path: path.to_path_buf(), }) } pub fn convert(&self) -> Result { Ok(Converter { imgdata: None, pic: self, }) } } impl Converter<'_> { fn convert_image(&mut self, size: u32, dest: &Path) -> Result<(), ImageError> { let scaled = self.get_imgdata()?.resize( size, std::u32::MAX, imageops::FilterType::Lanczos3); scaled.save(dest) } fn get_imgdata(&mut self) -> Result<&image::DynamicImage, ImageError> { let picpath = &self.pic.path; match self.imgdata { None => self.imgdata = Some( ImageReader::open(picpath)?.decode()? ), _ => () } Ok(self.imgdata.as_ref().unwrap()) } pub fn get_size(&mut self, ctx: &Context, size: u32) -> Result { let hash = md5::compute(format!("{},{},{}", size, ctx.options.ext, self.pic.hash)); let name = format!("{:?}.{}", hash, ctx.options.ext); let path = ctx.imgdir.join(name); match path.exists() { true => { println!("Image of size {} already exists", size); Ok(path) }, false => { println!("Scaling image to size {}", size); self.convert_image(size, &path)?; Ok(path) } } } } impl From for LoadError { fn from(error: io::Error) -> Self { Self::Io(error) } } impl From for LoadError { fn from(error: exif::Error) -> Self { Self::ExifParser(error) } } impl From for ConversionError { fn from(error: io::Error) -> Self { Self::Io(error) } } impl From for ConversionError { fn from(error: ImageError) -> Self { Self::ImageError(error) } }