png_ect 1.0.0

A command-line tool for compressing png images using ECT.
Documentation
use rayon::prelude::*;
use std::error::Error;
use std::fmt;
use std::io;
use std::path::Path;
use std::process::Command;
use std::str;
use walkdir::WalkDir;

#[derive(Debug)]
pub struct AppError {
    pub message: String,
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl Error for AppError {}

pub fn process_image(path: &Path, compression_level: Option<u8>) -> Result<(), Box<dyn Error>> {
    let compression_arg = match compression_level {
        Some(level) => format!("-{}", level),
        None => String::from("-9"),
    };

    let output = Command::new("ect")
        .arg(&compression_arg)
        .arg("--strict")
        .arg("-keep")
        .arg("--mt-file")
        .arg("-q")
        .arg(path)
        .output()?;

    let stdout_str = str::from_utf8(&output.stdout).map_err(|_| {
        io::Error::new(
            io::ErrorKind::Other,
            "Failed to convert stdout output to UTF-8",
        )
    })?;

    if !stdout_str.trim().is_empty() {
        return Err(Box::new(io::Error::new(
            io::ErrorKind::Other,
            format!("Command execution failed: {}", stdout_str),
        )));
    }

    Ok(())
}

pub fn process_directory(input_path: &Path, compression_level: Option<u8>) -> Result<(), AppError> {
    if input_path.is_dir() {
        let paths: Vec<_> = WalkDir::new(input_path)
            .into_iter()
            .filter_map(|e| e.ok())
            .filter(|e| {
                e.path().is_file() && e.path().extension().map_or(false, |ext| ext == "png")
            })
            .map(|e| e.path().to_owned())
            .collect();

        paths.par_iter().for_each(|path| {
            if let Err(e) = process_image(path, compression_level) {
                eprintln!("Failed to process {}: {}", path.display(), e);
            }
        });
    } else if input_path.is_file() {
        if input_path.extension().map_or(false, |ext| ext == "png") {
            if let Err(e) = process_image(input_path, compression_level) {
                eprintln!("Failed to process {}: {}", input_path.display(), e);
            }
        } else {
            eprintln!("File is not a PNG: {}", input_path.display());
        }
    } else {
        return Err(AppError {
            message: format!("Invalid input path: {}", input_path.display()),
        });
    }
    Ok(())
}