diff --git a/src/cli.rs b/src/cli.rs index 6cc8b80c..2d6f729a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -350,16 +350,31 @@ timeout before each transformation or compression trial, and will stop trying to the file if the timeout is exceeded. Note that this does not cut short any operations that \ are already in progress, so it is currently of limited effectiveness for large files with \ high compression levels.") - .value_name("secs") .long("timeout") + .value_name("secs") .value_parser(value_parser!(u64)), ) .arg( Arg::new("threads") - .help("Set number of threads to use [default: num CPU cores]") - .long("threads") + .help("Number of threads to use [default: num logical CPUs]") + .long_help("\ +Set the maximum number of threads to use. Oxipng uses multithreading to evaluate multiple \ +optimizations on the same file in parallel as well as process multiple files in parallel. \ +You can set this to a lower value if you need to limit memory or CPU usage. + +[default: num logical CPUs]") .short('t') + .long("threads") .value_name("num") .value_parser(value_parser!(usize)), ) + .arg( + Arg::new("parallel-files") + .help("Process multiple files sequentially") + .long_help("\ +Process multiple files sequentially rather than in parallel. Use this if you need \ +determinism in the processing order. Note this is not necessary if using '--threads 1'.") + .long("sequential") + .action(ArgAction::SetFalse), + ) } diff --git a/src/main.rs b/src/main.rs index 3299dc62..91b57126 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,32 +72,20 @@ fn main() -> ExitCode { true, ); - let summary = files - .into_par_iter() - .map(|(input, output)| { - match oxipng::optimize(&input, &output, &opts) { - // For optimizing single files, this will return the correct exit code always. - // For recursive optimization, the correct choice is a bit subjective. - // We're choosing to return a 0 exit code if ANY file in the set - // runs correctly. - // The reason for this is that recursion may pick up files that are not - // PNG files, and return an error for them. - // We don't really want to return an error code for those files. - Ok(_) => OptimizationResult::Ok, - Err(e @ PngError::C2PAMetadataPreventsChanges) => { - warn!("{input}: {e}"); - OptimizationResult::Skipped - } - Err(e) => { - error!("{input}: {e}"); - OptimizationResult::Failed - } - } - }) - .min() - .unwrap_or(OptimizationResult::Skipped); + let parallel_files = matches.get_flag("parallel-files"); + let summary = if parallel_files { + files + .into_par_iter() + .map(|(input, output)| process_file(&input, &output, &opts)) + .min() + } else { + files + .into_iter() + .map(|(input, output)| process_file(&input, &output, &opts)) + .min() + }; - match summary { + match summary.unwrap_or(OptimizationResult::Skipped) { OptimizationResult::Ok => ExitCode::SUCCESS, OptimizationResult::Failed => ExitCode::FAILURE, OptimizationResult::Skipped => ExitCode::from(3), @@ -421,3 +409,24 @@ fn parse_numeric_range_opts( Err(ERROR_MESSAGE.to_owned()) } + +fn process_file(input: &InFile, output: &OutFile, opts: &Options) -> OptimizationResult { + match oxipng::optimize(input, output, opts) { + // For optimizing single files, this will return the correct exit code always. + // For recursive optimization, the correct choice is a bit subjective. + // We're choosing to return a 0 exit code if ANY file in the set + // runs correctly. + // The reason for this is that recursion may pick up files that are not + // PNG files, and return an error for them. + // We don't really want to return an error code for those files. + Ok(_) => OptimizationResult::Ok, + Err(e @ PngError::C2PAMetadataPreventsChanges) => { + warn!("{input}: {e}"); + OptimizationResult::Skipped + } + Err(e) => { + error!("{input}: {e}"); + OptimizationResult::Failed + } + } +}