png_ect/
lib.rs

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}