From d602b7fb0076a05f1cd70c30e5591bd27b5c0a1c Mon Sep 17 00:00:00 2001 From: Bo Lopker Date: Mon, 23 Dec 2024 09:39:25 -0800 Subject: [PATCH] Auto convert files with the wrong extension --- src-tauri/src/compress.rs | 273 +++++++++++++++++--------------------- 1 file changed, 125 insertions(+), 148 deletions(-) diff --git a/src-tauri/src/compress.rs b/src-tauri/src/compress.rs index 984ea52..eb4a977 100644 --- a/src-tauri/src/compress.rs +++ b/src-tauri/src/compress.rs @@ -4,8 +4,8 @@ use crate::macos; use super::settings; use caesium; use caesium::parameters::CSParameters; -use image; -use image::DynamicImage; +use image::{self, ImageReader}; +use image::{DynamicImage, ImageFormat}; use serde; use specta::Type; use std::fs; @@ -43,7 +43,7 @@ pub enum FileEntryStatus { Error, } -#[derive(Debug, Eq, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, Type)] +#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize, Type)] pub enum ImageType { JPEG, PNG, @@ -52,6 +52,55 @@ pub enum ImageType { TIFF, } +impl ImageType { + // pub fn to_mime_type(&self) -> String { + // match self { + // ImageType::JPEG => "image/jpeg".to_string(), + // ImageType::PNG => "image/png".to_string(), + // ImageType::WEBP => "image/webp".to_string(), + // ImageType::GIF => "image/gif".to_string(), + // ImageType::TIFF => "image/tiff".to_string(), + // } + // } + pub fn extensions(&self) -> &[&str] { + match self { + ImageType::JPEG => ImageFormat::Jpeg.extensions_str(), + ImageType::PNG => ImageFormat::Png.extensions_str(), + ImageType::WEBP => ImageFormat::WebP.extensions_str(), + ImageType::GIF => ImageFormat::Gif.extensions_str(), + ImageType::TIFF => ImageFormat::Tiff.extensions_str(), + } + } + // pub fn from_extension(ext: &str) -> Option { + // match ext { + // "jpg" | "jpeg" => Some(ImageType::JPEG), + // "png" => Some(ImageType::PNG), + // "webp" => Some(ImageType::WEBP), + // "gif" => Some(ImageType::GIF), + // "tiff" => Some(ImageType::TIFF), + // _ => None, + // } + // } + pub fn to_casium_type(&self) -> caesium::SupportedFileTypes { + match self { + ImageType::JPEG => caesium::SupportedFileTypes::Jpeg, + ImageType::PNG => caesium::SupportedFileTypes::Png, + ImageType::WEBP => caesium::SupportedFileTypes::WebP, + ImageType::GIF => caesium::SupportedFileTypes::Gif, + ImageType::TIFF => caesium::SupportedFileTypes::Tiff, + } + } + pub fn preferred_extension(&self) -> &str { + match self { + ImageType::JPEG => "jpg", + ImageType::PNG => "png", + ImageType::WEBP => "webp", + ImageType::GIF => "gif", + ImageType::TIFF => "tiff", + } + } +} + #[derive(serde::Serialize, serde::Deserialize, Type)] #[serde(rename_all = "camelCase")] pub struct CompressResult { @@ -94,17 +143,7 @@ pub async fn process_img( // if not savings, delete temp file, return // if out path is same as original, delete original // move temp file to out path - let out_path = get_out_path(¶meters, &file.path); - - if file.path == out_path && !parameters.should_overwrite { - return Err(CompressError { - error: "Image would be overwritten. Enable Overwrite in settings to allow this." - .to_string(), - error_type: CompressErrorType::WontOverwrite, - }); - } - - let original_img = match read_image(&file.path) { + let (original_img, original_image_type) = match read_image(&file.path) { Ok(img) => img, Err(err) => { return Err(CompressError { @@ -114,18 +153,19 @@ pub async fn process_img( } }; + let out_path = get_out_path(¶meters, &file.path, &original_image_type); + + if file.path == out_path && !parameters.should_overwrite { + return Err(CompressError { + error: "Image would be overwritten. Enable Overwrite in settings to allow this." + .to_string(), + error_type: CompressErrorType::WontOverwrite, + }); + } + let csparams = create_csparameters(¶meters, original_img.width(), original_img.height()); drop(original_img); - let original_image_type = match guess_image_type(&file.path) { - Ok(img) => img, - Err(err) => { - return Err(CompressError { - error: err, - error_type: CompressErrorType::UnsupportedFileType, - }) - } - }; let should_convert = parameters.should_convert && parameters.convert_extension != original_image_type; @@ -197,70 +237,6 @@ pub async fn process_img( }) } -// #[derive(Debug)] -// struct ScanError { -// error: String, -// path: String, -// } - -// pub fn scan_directory_for_images( -// path: impl Into, -// ) -> impl Stream> { -// let (tx, rx) = mpsc::channel(100); -// let path = path.into(); -// if !path.is_dir() { -// if is_image(&path) { -// tx.send(Ok(path)); -// } else { -// tx.send(Err(ScanError { -// path: path.to_string_lossy().to_string(), -// error: "Not a directory or image".to_string(), -// })); -// } -// return ReceiverStream::new(rx); -// } -// tokio::spawn(async move { -// async fn visit_dirs( -// dir: PathBuf, -// tx: mpsc::Sender>, -// ) -> Result<(), String> { -// let read_dir = tokio::fs::read_dir(&dir).await.map_err(|e| e.to_string())?; - -// let mut read_dir = read_dir; -// while let Some(entry) = read_dir.next_entry().await.map_err(|e| e.to_string())? { -// let path = entry.path(); -// if is_image(&path) { -// if tx.send(Ok(path)).await.is_err() { -// break; -// } -// } else if path.is_dir() { -// let future = visit_dirs(path.clone(), tx.clone()); -// if let Err(e) = Box::pin(future).await { -// let _ = tx -// .send(Err(ScanError { -// path: path.to_string_lossy().to_string(), -// error: e, -// })) -// .await; -// } -// } -// } -// Ok(()) -// } - -// if let Err(e) = Box::pin(visit_dirs(path.clone(), tx.clone())).await { -// let _ = tx -// .send(Err(ScanError { -// path: path.to_string_lossy().to_string(), -// error: e, -// })) -// .await; -// } -// }); - -// ReceiverStream::new(rx) -// } - fn get_temp_path(path: &str) -> String { // /original/path/test.png -> /original/path/.test.png let path = Path::new(&path); @@ -272,42 +248,62 @@ fn get_temp_path(path: &str) -> String { .to_string() } -fn read_image(path: &str) -> Result { - let image = image::open(&path); - match image { - Ok(image) => Ok(image), - Err(err) => Err(format!("Error: {}", err)), +fn read_image(path: &str) -> Result<(DynamicImage, ImageType), String> { + let image = ImageReader::open(&path) + .map_err(|e| e.to_string())? + .with_guessed_format(); + + let format = match &image { + Ok(image) => match image.format() { + Some(ImageFormat::Jpeg) => Some(ImageType::JPEG), + Some(ImageFormat::Png) => Some(ImageType::PNG), + Some(ImageFormat::WebP) => Some(ImageType::WEBP), + Some(ImageFormat::Gif) => Some(ImageType::GIF), + Some(ImageFormat::Tiff) => Some(ImageType::TIFF), + f => { + return Err(format!( + "Error: Unsupported image type: {}", + f.unwrap().to_mime_type() + )) + } + }, + Err(_) => None, + }; + + if format.is_none() { + return Err("Error: Unsupported image type.".to_string()); } -} -fn guess_image_type(path: &str) -> Result { - let kind = - infer::get_from_path(path).map_err(|e| format!("Error determining file type: {}", e))?; - match kind { - Some(kind) => match kind.mime_type() { - "image/jpeg" => Ok(ImageType::JPEG), - "image/png" => Ok(ImageType::PNG), - "image/webp" => Ok(ImageType::WEBP), - "image/gif" => Ok(ImageType::GIF), - "image/tiff" => Ok(ImageType::TIFF), - _ => Err(format!( - "Error: Unsupported image type: {}", - kind.mime_type() - )), - }, - None => Err("Error: Could not determine image type.".to_string()), + match image { + Ok(image) => Ok((image.decode().map_err(|e| e.to_string())?, format.unwrap())), + Err(err) => Err(format!("Read error: {}", err)), } } -fn get_out_path(parameters: &settings::ProfileData, path: &str) -> String { +fn get_out_path( + parameters: &settings::ProfileData, + path: &str, + guessed_format: &ImageType, +) -> String { let path = Path::new(&path); + let file_extension = path + .extension() + .unwrap_or_default() + .to_string_lossy() + .to_string(); + let original_extension = match guessed_format + .extensions() + .contains(&file_extension.as_str()) + { + true => file_extension, + false => guessed_format.preferred_extension().to_string(), + }; let extension = match parameters.should_convert { - true => image_type_to_extension(parameters.convert_extension), - false => path - .extension() - .unwrap_or_default() - .to_string_lossy() + true => parameters + .convert_extension + .preferred_extension() .to_string(), + false => original_extension, }; let posfix = match parameters.add_posfix { true => parameters.postfix.clone(), @@ -316,16 +312,6 @@ fn get_out_path(parameters: &settings::ProfileData, path: &str) -> String { format!("{}{}.{}", remove_extension(&path), posfix, extension) } -fn image_type_to_extension(image_type: ImageType) -> String { - match image_type { - ImageType::JPEG => "jpg".to_string(), - ImageType::PNG => "png".to_string(), - ImageType::WEBP => "webp".to_string(), - ImageType::GIF => "gif".to_string(), - ImageType::TIFF => "tiff".to_string(), - } -} - fn create_csparameters( parameters: &settings::ProfileData, width: u32, @@ -372,18 +358,11 @@ fn convert_image( mut params: CSParameters, image_type: ImageType, ) -> Result { - let supported_type = match image_type { - ImageType::JPEG => caesium::SupportedFileTypes::Jpeg, - ImageType::PNG => caesium::SupportedFileTypes::Png, - ImageType::WEBP => caesium::SupportedFileTypes::WebP, - ImageType::GIF => caesium::SupportedFileTypes::Gif, - ImageType::TIFF => caesium::SupportedFileTypes::Tiff, - }; let result = caesium::convert( path.to_string(), out_path.to_string(), &mut params, - supported_type, + image_type.to_casium_type(), ); match result { @@ -510,47 +489,45 @@ mod tests { #[test] fn test_convert_image_type() { - let result = image_type_to_extension(ImageType::JPEG); + let result = ImageType::JPEG.extensions()[0]; assert_eq!(result, "jpg".to_string()); } - #[test] - fn test_guess_image_type() { - let result = guess_image_type("test/test.jpg"); - assert_eq!(result.unwrap(), ImageType::JPEG); - } - #[test] fn test_get_out_path() { let mut parameters = settings::ProfileData::new(); - let mut result = get_out_path(¶meters, &"test/test.png".to_string()); + let mut result = get_out_path(¶meters, &"test/test.png".to_string(), &ImageType::PNG); assert_eq!(result, "test/test.min.png".to_string()); parameters = settings::ProfileData::new(); - result = get_out_path(¶meters, &"test/test.jpeg".to_string()); + result = get_out_path(¶meters, &"test/test.jpeg".to_string(), &ImageType::JPEG); assert_eq!(result, "test/test.min.jpeg".to_string()); + parameters = settings::ProfileData::new(); + result = get_out_path(¶meters, &"test/test.jpg".to_string(), &ImageType::JPEG); + assert_eq!(result, "test/test.min.jpg".to_string()); + parameters = settings::ProfileData::new(); parameters.should_convert = true; parameters.convert_extension = ImageType::PNG; - result = get_out_path(¶meters, &"test/test.jpeg".to_string()); + result = get_out_path(¶meters, &"test/test.jpeg".to_string(), &ImageType::JPEG); assert_eq!(result, "test/test.min.png".to_string()); parameters = settings::ProfileData::new(); parameters.should_convert = false; parameters.convert_extension = ImageType::PNG; - result = get_out_path(¶meters, &"test/test.jpeg".to_string()); + result = get_out_path(¶meters, &"test/test.jpeg".to_string(), &ImageType::JPEG); assert_eq!(result, "test/test.min.jpeg".to_string()); parameters = settings::ProfileData::new(); parameters.add_posfix = false; - result = get_out_path(¶meters, &"test/test.jpeg".to_string()); - assert_eq!(result, "test/test.jpeg".to_string()); + result = get_out_path(¶meters, &"test/test.jpeg".to_string(), &ImageType::PNG); + assert_eq!(result, "test/test.png".to_string()); parameters = settings::ProfileData::new(); parameters.postfix = ".bong".to_string(); - result = get_out_path(¶meters, &"test/test.jpeg".to_string()); - assert_eq!(result, "test/test.bong.jpeg".to_string()); + result = get_out_path(¶meters, &"test/test.jpeg".to_string(), &ImageType::PNG); + assert_eq!(result, "test/test.bong.png".to_string()); } #[test]