Add option to disable parallel file processing (#695)

Fixes #685.
Fixes #572.
Also fixes #690 by using "logical CPUs".
This commit is contained in:
andrews05 2025-04-23 21:09:15 +12:00 committed by GitHub
parent d168fffb12
commit a44ba5f88b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 52 additions and 28 deletions

View file

@ -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),
)
}

View file

@ -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
}
}
}