diff --git a/src/main.rs b/src/main.rs
@@ -1,3 +1,5 @@
+use image::io::Reader as ImageReader;
+use image::DynamicImage;
use std::env;
use std::fmt::{self, Display};
use std::fs::{self, File};
@@ -25,6 +27,14 @@ const PAGE_TITLE: &str = "Pablo's Photo Gallery";
const AUTHOR: &str = "Pablo";
const LICENSE: &str = "GPLv3";
+/// WebP image quality
+const IMAGE_QUALITY: f32 = 50.0;
+
+/// Target height of the thumbnails, depending on wether the image is vertical
+/// or horizontal
+const HORIZONTAL_THUMB_HEIGHT: u32 = 300;
+const VERTICAL_THUMB_HEIGHT: u32 = 800;
+
fn main() -> io::Result<()> {
let args: Vec<String> = env::args().collect();
@@ -89,9 +99,6 @@ fn render_gallery(pics: Vec<Picture>) -> io::Result<()> {
}
// ========================================================================
- let mut thumb_path = PathBuf::from(TARGET_PATH);
- thumb_path.push(THUMBS_PATH);
-
let rendering_pool = ThreadPool::with_name(
String::from("thumbnails renderer"),
num_cpus::get() + 1
@@ -101,9 +108,8 @@ fn render_gallery(pics: Vec<Picture>) -> io::Result<()> {
for pic in &pics {
let sender = sender.clone();
let pic = pic.clone();
- let thumb_path = thumb_path.clone();
rendering_pool.execute(move || {
- sender.send(pic.render_thumbnail(&thumb_path))
+ sender.send(render_thumbnail(pic))
.expect("channel should still be alive awaiting for the completion of this task");
});
}
@@ -314,6 +320,81 @@ fn usage_config() {
eprintln!(" ...");
}
+/// Returns `true` if rendering succeded (or was skipped) and `false`
+/// otherwise. All warinings and error messages should be logged in here.
+// TODO: Render GIF files as mp4 instead
+fn render_thumbnail(pic: Picture) -> bool {
+ let mut thumb_path = PathBuf::from(TARGET_PATH);
+ thumb_path.push(THUMBS_PATH);
+ thumb_path.push(pic.file_name.clone() + ".webp");
+
+ // Only try to render thumbnail in case the thumbnail file in the machine
+ // is older than the source file
+ if let (Ok(thumb_meta), Ok(img_meta)) = (fs::metadata(&thumb_path), fs::metadata(&pic.path)) {
+ let thumb_mod_date = thumb_meta.modified()
+ .expect("os should support file modification date");
+ let img_mod_date = img_meta.modified()
+ .expect("os should support file modification date");
+
+ if thumb_mod_date > img_mod_date {
+ println!("WARNING: Skipped rendering the WebP thumbnail for {name:?}.\nUpdate {path:?} to overwrite this behaviour",
+ name = pic.file_name, path = pic.path);
+ return true;
+ }
+ }
+
+ let mut thumb_file = match File::create(&thumb_path) {
+ Ok(f) => f,
+ Err(err) => {
+ eprintln!("ERROR: Couldn't open WebP thumbnail file {thumb_path:?}: {err}");
+ return false;
+ }
+ };
+
+ let img_reader = match ImageReader::open(&pic.path) {
+ Ok(r) => r,
+ Err(err) => {
+ eprintln!(
+ "ERROR: Couldn't open file {path:?} to render WebP thumbnail: {err}",
+ path = pic.file_name
+ );
+ return false;
+ }
+ };
+
+ let img = match img_reader.decode() {
+ Ok(img) => img,
+ Err(err) => {
+ eprintln!(
+ "ERROR: Faileded to decode image file {name:?}: {err}",
+ name = pic.file_name
+ );
+ return false;
+ }
+ };
+
+ let h = if img.width() > img.height() {
+ HORIZONTAL_THUMB_HEIGHT
+ } else {
+ VERTICAL_THUMB_HEIGHT
+ };
+ let w = (h * img.width()) / img.height();
+
+ // We should make sure that the image is in the RGBA8 format so that
+ // the webp crate can encode it
+ let img = DynamicImage::from(img.thumbnail(w, h).into_rgba8());
+ let mem = webp::Encoder::from_image(&img)
+ .expect("image should be in the RGBA8 format")
+ .encode(IMAGE_QUALITY);
+
+ if let Err(err) = thumb_file.write_all(&mem) {
+ eprintln!("ERROR: Couldn't write WebP thumnail to file {thumb_path:?}: {err}");
+ return false;
+ }
+
+ true
+}
+
impl<'a> Display for Escaped<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
for c in self.0.chars() {