Reading from stdin (#118)
This commit is contained in:
parent
24735b6ab3
commit
cb52d8466a
8 changed files with 104 additions and 54 deletions
65
src/lib.rs
65
src/lib.rs
|
|
@ -19,8 +19,9 @@ use png::PngData;
|
|||
#[cfg(feature = "parallel")]
|
||||
use rayon::prelude::*;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt;
|
||||
use std::fs::{copy, File};
|
||||
use std::io::{stdout, BufWriter, Write};
|
||||
use std::io::{stdin, stdout, BufWriter, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::sync::Mutex;
|
||||
|
|
@ -62,6 +63,37 @@ impl OutFile {
|
|||
}
|
||||
}
|
||||
|
||||
/// Where to read images from
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum InFile {
|
||||
Path(PathBuf),
|
||||
StdIn,
|
||||
}
|
||||
|
||||
impl InFile {
|
||||
pub fn path(&self) -> Option<&Path> {
|
||||
match *self {
|
||||
InFile::Path(ref p) => Some(p.as_path()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for InFile {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
InFile::Path(ref p) => write!(f, "{}", p.display()),
|
||||
InFile::StdIn => f.write_str("stdin"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<PathBuf>> From<T> for InFile {
|
||||
fn from(s: T) -> Self {
|
||||
InFile::Path(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// Options controlling the output of the `optimize` function
|
||||
pub struct Options {
|
||||
|
|
@ -287,7 +319,7 @@ impl Default for Options {
|
|||
}
|
||||
|
||||
/// Perform optimization on the input file using the options provided
|
||||
pub fn optimize(input_path: &Path, output: &OutFile, opts: &Options) -> Result<(), PngError> {
|
||||
pub fn optimize(input: &InFile, output: &OutFile, opts: &Options) -> Result<(), PngError> {
|
||||
// Initialize the thread pool with correct number of threads
|
||||
#[cfg(feature = "parallel")]
|
||||
let thread_count = opts.threads;
|
||||
|
|
@ -298,10 +330,18 @@ pub fn optimize(input_path: &Path, output: &OutFile, opts: &Options) -> Result<(
|
|||
|
||||
// Read in the file and try to decode as PNG.
|
||||
if opts.verbosity.is_some() {
|
||||
eprintln!("Processing: {}", input_path.to_str().unwrap());
|
||||
eprintln!("Processing: {}", input);
|
||||
}
|
||||
|
||||
let in_data = PngData::read_file(input_path)?;
|
||||
let in_data = match *input {
|
||||
InFile::Path(ref input_path) => PngData::read_file(input_path)?,
|
||||
InFile::StdIn => {
|
||||
let mut data = Vec::new();
|
||||
stdin().read_to_end(&mut data)
|
||||
.map_err(|e| PngError::new(&format!("Error reading stdin: {}", e)))?;
|
||||
data
|
||||
}
|
||||
};
|
||||
let mut png = PngData::from_slice(&in_data, opts.fix_errors)?;
|
||||
|
||||
// Run the optimizer on the decoded PNG.
|
||||
|
|
@ -309,9 +349,9 @@ pub fn optimize(input_path: &Path, output: &OutFile, opts: &Options) -> Result<(
|
|||
|
||||
if is_fully_optimized(in_data.len(), optimized_output.len(), opts) {
|
||||
eprintln!("File already optimized");
|
||||
match *output {
|
||||
match (output, input) {
|
||||
// if p is None, it also means same as the input path
|
||||
OutFile::Path(ref p) if p.as_ref().map_or(true, |p| p == input_path) => {
|
||||
(&OutFile::Path(ref p), &InFile::Path(ref input_path)) if p.as_ref().map_or(true, |p| p == input_path) => {
|
||||
return Ok(());
|
||||
},
|
||||
_ => {
|
||||
|
|
@ -327,14 +367,15 @@ pub fn optimize(input_path: &Path, output: &OutFile, opts: &Options) -> Result<(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
match *output {
|
||||
OutFile::StdOut => {
|
||||
match (output, input) {
|
||||
(&OutFile::StdOut, _) |
|
||||
(&OutFile::Path(None), &InFile::StdIn) => {
|
||||
let mut buffer = BufWriter::new(stdout());
|
||||
buffer.write_all(&optimized_output)
|
||||
.map_err(|e| PngError::new(&format!("Unable to write to stdout: {}", e)))?;
|
||||
},
|
||||
OutFile::Path(ref output_path) => {
|
||||
let output_path = output_path.as_ref().map(|p| p.as_path()).unwrap_or(input_path);
|
||||
(&OutFile::Path(ref output_path), _) => {
|
||||
let output_path = output_path.as_ref().map(|p| p.as_path()).unwrap_or_else(|| input.path().unwrap());
|
||||
if opts.backup {
|
||||
perform_backup(output_path)?;
|
||||
}
|
||||
|
|
@ -343,7 +384,9 @@ pub fn optimize(input_path: &Path, output: &OutFile, opts: &Options) -> Result<(
|
|||
"Unable to write to file {}: {}", output_path.display(), err
|
||||
)))?;
|
||||
if opts.preserve_attrs {
|
||||
copy_permissions(input_path, &out_file, opts.verbosity);
|
||||
if let Some(input_path) = input.path() {
|
||||
copy_permissions(input_path, &out_file, opts.verbosity);
|
||||
}
|
||||
}
|
||||
|
||||
let mut buffer = BufWriter::new(out_file);
|
||||
|
|
|
|||
38
src/main.rs
38
src/main.rs
|
|
@ -15,7 +15,7 @@ use clap::{App, Arg, ArgMatches};
|
|||
use oxipng::AlphaOptim;
|
||||
use oxipng::Deflaters;
|
||||
use oxipng::Headers;
|
||||
use oxipng::OutFile;
|
||||
use oxipng::{InFile, OutFile};
|
||||
use oxipng::{Options, PngError};
|
||||
use std::collections::HashSet;
|
||||
use std::fs::DirBuilder;
|
||||
|
|
@ -29,7 +29,7 @@ fn main() {
|
|||
.author("Joshua Holmer <jholmer.in@gmail.com>")
|
||||
.about("Losslessly improves compression of PNG files")
|
||||
.arg(Arg::with_name("files")
|
||||
.help("File(s) to compress")
|
||||
.help("File(s) to compress (use \"-\" for stdin)")
|
||||
.index(1)
|
||||
.multiple(true)
|
||||
.use_delimiter(false)
|
||||
|
|
@ -233,7 +233,7 @@ fn main() {
|
|||
};
|
||||
|
||||
let files = collect_files(matches.values_of("files").unwrap()
|
||||
.map(PathBuf::from).collect(), &out_dir, &out_file, opts.recursive);
|
||||
.map(PathBuf::from).collect(), &out_dir, &out_file, opts.recursive, true);
|
||||
|
||||
let res: Result<(), PngError> = files.into_iter().map(|(input, output)| {
|
||||
oxipng::optimize(&input, &output, &opts)
|
||||
|
|
@ -245,30 +245,38 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
fn collect_files(files: Vec<PathBuf>, out_dir: &Option<PathBuf>, out_file: &OutFile, recursive: bool) -> Vec<(PathBuf, OutFile)> {
|
||||
let mut out = Vec::new();
|
||||
fn collect_files(files: Vec<PathBuf>, out_dir: &Option<PathBuf>, out_file: &OutFile, recursive: bool, allow_stdin: bool) -> Vec<(InFile, OutFile)> {
|
||||
let mut in_out_pairs = Vec::new();
|
||||
let allow_stdin = allow_stdin && files.len() == 1;
|
||||
for input in files {
|
||||
if input.is_dir() {
|
||||
let using_stdin = allow_stdin && input.to_str().map_or(false, |p| p == "-");
|
||||
if !using_stdin && input.is_dir() {
|
||||
if recursive {
|
||||
let files = input
|
||||
.read_dir()
|
||||
.unwrap()
|
||||
.map(|x| x.unwrap().path().to_owned())
|
||||
.collect();
|
||||
out.extend(collect_files(files, out_dir, out_file, recursive));
|
||||
in_out_pairs.extend(collect_files(files, out_dir, out_file, recursive, false));
|
||||
} else {
|
||||
eprintln!("{} is a directory, skipping", input.display());
|
||||
}
|
||||
continue;
|
||||
};
|
||||
let out_file = if let Some(ref out_dir) = *out_dir {
|
||||
let out_path = Some(out_dir.join(input.file_name().unwrap()));
|
||||
OutFile::Path(out_path)
|
||||
} else {
|
||||
out.push(if let Some(ref out_dir) = *out_dir {
|
||||
let out_path = Some(out_dir.join(input.file_name().unwrap()));
|
||||
(input, OutFile::Path(out_path))
|
||||
} else {
|
||||
(input, (*out_file).clone())
|
||||
});
|
||||
}
|
||||
(*out_file).clone()
|
||||
};
|
||||
let in_file = if using_stdin {
|
||||
InFile::StdIn
|
||||
} else {
|
||||
InFile::Path(input)
|
||||
};
|
||||
in_out_pairs.push((in_file, out_file));
|
||||
}
|
||||
out
|
||||
in_out_pairs
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "clippy", allow(cyclomatic_complexity))]
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ extern crate oxipng;
|
|||
|
||||
use oxipng::colors::{BitDepth, ColorType};
|
||||
use oxipng::png;
|
||||
use oxipng::OutFile;
|
||||
use oxipng::{InFile, OutFile};
|
||||
use std::collections::HashSet;
|
||||
use std::fs::remove_file;
|
||||
use std::path::Path;
|
||||
|
|
@ -36,7 +36,7 @@ fn test_it_converts(
|
|||
assert_eq!(png.ihdr_data.color_type, color_type_in);
|
||||
assert_eq!(png.ihdr_data.bit_depth, bit_depth_in);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
extern crate oxipng;
|
||||
|
||||
use oxipng::OutFile;
|
||||
use oxipng::{InFile, OutFile};
|
||||
use oxipng::colors::{BitDepth, ColorType};
|
||||
use oxipng::deflate::Deflaters;
|
||||
use oxipng::headers::Headers;
|
||||
|
|
@ -35,7 +35,7 @@ fn test_it_converts(
|
|||
assert_eq!(png.ihdr_data.color_type, color_type_in);
|
||||
assert_eq!(png.ihdr_data.bit_depth, bit_depth_in);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -85,7 +85,7 @@ fn strip_headers_list() {
|
|||
assert!(png.aux_headers.contains_key("iTXt"));
|
||||
assert!(png.aux_headers.contains_key("iCCP"));
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -119,7 +119,7 @@ fn strip_headers_safe() {
|
|||
assert!(png.aux_headers.contains_key("iTXt"));
|
||||
assert!(png.aux_headers.contains_key("iCCP"));
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -153,7 +153,7 @@ fn strip_headers_all() {
|
|||
assert!(png.aux_headers.contains_key("iTXt"));
|
||||
assert!(png.aux_headers.contains_key("iCCP"));
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -187,7 +187,7 @@ fn strip_headers_none() {
|
|||
assert!(png.aux_headers.contains_key("iTXt"));
|
||||
assert!(png.aux_headers.contains_key("iCCP"));
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -219,7 +219,7 @@ fn interlacing_0_to_1() {
|
|||
|
||||
assert_eq!(png.ihdr_data.interlaced, 0);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -249,7 +249,7 @@ fn interlacing_1_to_0() {
|
|||
|
||||
assert_eq!(png.ihdr_data.interlaced, 1);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -281,7 +281,7 @@ fn interlacing_0_to_1_small_files() {
|
|||
assert_eq!(png.ihdr_data.color_type, ColorType::Indexed);
|
||||
assert_eq!(png.ihdr_data.bit_depth, BitDepth::Eight);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -315,7 +315,7 @@ fn interlacing_1_to_0_small_files() {
|
|||
assert_eq!(png.ihdr_data.color_type, ColorType::Indexed);
|
||||
assert_eq!(png.ihdr_data.bit_depth, BitDepth::Eight);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -350,7 +350,7 @@ fn interlaced_0_to_1_other_filter_mode() {
|
|||
|
||||
assert_eq!(png.ihdr_data.interlaced, 0);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -400,7 +400,7 @@ fn fix_errors() {
|
|||
assert_eq!(png.ihdr_data.color_type, ColorType::RGBA);
|
||||
assert_eq!(png.ihdr_data.bit_depth, BitDepth::Eight);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
extern crate oxipng;
|
||||
|
||||
use oxipng::OutFile;
|
||||
use oxipng::{InFile, OutFile};
|
||||
use oxipng::colors::{BitDepth, ColorType};
|
||||
use oxipng::png;
|
||||
use std::collections::HashSet;
|
||||
|
|
@ -34,7 +34,7 @@ fn test_it_converts(
|
|||
assert_eq!(png.ihdr_data.bit_depth, bit_depth_in);
|
||||
assert_eq!(png.ihdr_data.interlaced, 1);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ extern crate oxipng;
|
|||
use oxipng::OutFile;
|
||||
use std::default::Default;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::io::prelude::*;
|
||||
|
||||
#[test]
|
||||
|
|
@ -50,7 +49,7 @@ fn optimize() {
|
|||
let mut opts: oxipng::Options = Default::default();
|
||||
opts.verbosity = Some(1);
|
||||
|
||||
let result = oxipng::optimize(Path::new("tests/files/fully_optimized.png"), &OutFile::Path(None), &opts);
|
||||
let result = oxipng::optimize(&"tests/files/fully_optimized.png".into(), &OutFile::Path(None), &opts);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +58,7 @@ fn optimize_corrupted() {
|
|||
let mut opts: oxipng::Options = Default::default();
|
||||
opts.verbosity = Some(1);
|
||||
|
||||
let result = oxipng::optimize(Path::new("tests/files/corrupted_header.png"), &OutFile::Path(None), &opts);
|
||||
let result = oxipng::optimize(&"tests/files/corrupted_header.png".into(), &OutFile::Path(None), &opts);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +67,6 @@ fn optimize_apng() {
|
|||
let mut opts: oxipng::Options = Default::default();
|
||||
opts.verbosity = Some(1);
|
||||
|
||||
let result = oxipng::optimize(Path::new("tests/files/apng_file.png"), &OutFile::Path(None), &opts);
|
||||
let result = oxipng::optimize(&"tests/files/apng_file.png".into(), &OutFile::Path(None), &opts);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
extern crate oxipng;
|
||||
|
||||
use oxipng::OutFile;
|
||||
use oxipng::{InFile, OutFile};
|
||||
use oxipng::colors::{AlphaOptim, BitDepth, ColorType};
|
||||
use oxipng::png;
|
||||
use std::collections::HashSet;
|
||||
|
|
@ -38,7 +38,7 @@ fn test_it_converts(
|
|||
assert_eq!(png.ihdr_data.bit_depth, bit_depth_in);
|
||||
assert_eq!(png.ihdr_data.interlaced, 0);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -718,7 +718,7 @@ fn palette_should_be_reduced_with_dupes() {
|
|||
assert_eq!(png.ihdr_data.bit_depth, BitDepth::Eight);
|
||||
assert_eq!(png.palette.unwrap().len(), 43 * 3);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -751,7 +751,7 @@ fn palette_should_be_reduced_with_unused() {
|
|||
assert_eq!(png.ihdr_data.bit_depth, BitDepth::Eight);
|
||||
assert_eq!(png.palette.unwrap().len(), 35 * 3);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -784,7 +784,7 @@ fn palette_should_be_reduced_with_both() {
|
|||
assert_eq!(png.ihdr_data.bit_depth, BitDepth::Eight);
|
||||
assert_eq!(png.palette.unwrap().len(), 43 * 3);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ extern crate oxipng;
|
|||
|
||||
use oxipng::colors::{BitDepth, ColorType};
|
||||
use oxipng::png;
|
||||
use oxipng::OutFile;
|
||||
use oxipng::{InFile, OutFile};
|
||||
use std::collections::HashSet;
|
||||
use std::fs::remove_file;
|
||||
use std::path::Path;
|
||||
|
|
@ -34,7 +34,7 @@ fn test_it_converts(
|
|||
assert_eq!(png.ihdr_data.color_type, color_type_in);
|
||||
assert_eq!(png.ihdr_data.bit_depth, bit_depth_in);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
@ -79,7 +79,7 @@ fn issue_42() {
|
|||
assert_eq!(png.ihdr_data.color_type, ColorType::GrayscaleAlpha);
|
||||
assert_eq!(png.ihdr_data.bit_depth, BitDepth::Eight);
|
||||
|
||||
match oxipng::optimize(&input, &output, &opts) {
|
||||
match oxipng::optimize(&InFile::Path(input), &output, &opts) {
|
||||
Ok(_) => (),
|
||||
Err(x) => panic!("{}", x),
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue