Reading from stdin (#118)

This commit is contained in:
Kornel 2018-07-12 12:54:06 +01:00 committed by Josh Holmer
parent 24735b6ab3
commit cb52d8466a
8 changed files with 104 additions and 54 deletions

View file

@ -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);

View file

@ -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))]

View file

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

View file

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

View file

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

View file

@ -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());
}

View file

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

View file

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