1use rayon::prelude::*;
2use std::error::Error;
3use std::fmt;
4use std::io;
5use std::path::Path;
6use std::process::Command;
7use std::str;
8use walkdir::WalkDir;
9
10#[derive(Debug)]
11pub struct AppError {
12 pub message: String,
13}
14
15impl fmt::Display for AppError {
16 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
17 write!(f, "{}", self.message)
18 }
19}
20
21impl Error for AppError {}
22
23pub fn process_image(path: &Path, compression_level: Option<u8>) -> Result<(), Box<dyn Error>> {
24 let compression_arg = match compression_level {
25 Some(level) => format!("-{}", level),
26 None => String::from("-9"),
27 };
28
29 let output = Command::new("ect")
30 .arg(&compression_arg)
31 .arg("--strict")
32 .arg("-keep")
33 .arg("--mt-file")
34 .arg("-q")
35 .arg(path)
36 .output()?;
37
38 let stdout_str = str::from_utf8(&output.stdout).map_err(|_| {
39 io::Error::new(
40 io::ErrorKind::Other,
41 "Failed to convert stdout output to UTF-8",
42 )
43 })?;
44
45 if !stdout_str.trim().is_empty() {
46 return Err(Box::new(io::Error::new(
47 io::ErrorKind::Other,
48 format!("Command execution failed: {}", stdout_str),
49 )));
50 }
51
52 Ok(())
53}
54
55pub fn process_directory(input_path: &Path, compression_level: Option<u8>) -> Result<(), AppError> {
56 if input_path.is_dir() {
57 let paths: Vec<_> = WalkDir::new(input_path)
58 .into_iter()
59 .filter_map(|e| e.ok())
60 .filter(|e| {
61 e.path().is_file() && e.path().extension().map_or(false, |ext| ext == "png")
62 })
63 .map(|e| e.path().to_owned())
64 .collect();
65
66 paths.par_iter().for_each(|path| {
67 if let Err(e) = process_image(path, compression_level) {
68 eprintln!("Failed to process {}: {}", path.display(), e);
69 }
70 });
71 } else if input_path.is_file() {
72 if input_path.extension().map_or(false, |ext| ext == "png") {
73 if let Err(e) = process_image(input_path, compression_level) {
74 eprintln!("Failed to process {}: {}", input_path.display(), e);
75 }
76 } else {
77 eprintln!("File is not a PNG: {}", input_path.display());
78 }
79 } else {
80 return Err(AppError {
81 message: format!("Invalid input path: {}", input_path.display()),
82 });
83 }
84 Ok(())
85}