added chapter 4
parent
32ce7ac4d5
commit
839540aa0f
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "imagecli"
|
||||
version = "0.1.0"
|
||||
authors = ["peshwar9"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
name = "imagix"
|
||||
path = "src/imagix/mod.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "imagecli"
|
||||
path = "src/imagecli.rs"
|
||||
|
||||
[dependencies]
|
||||
image = "0.23.7"
|
||||
structopt = "0.3.15"
|
@ -0,0 +1,63 @@
|
||||
mod imagix;
|
||||
use ::imagix::error::ImagixError;
|
||||
use ::imagix::resize::{process_resize_request, Mode, SizeOption};
|
||||
use ::imagix::stats::get_stats;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
// Define commandline arguments in a struct
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
#[structopt(
|
||||
name = "resize",
|
||||
about = "This is a tool for image resizing and stats",
|
||||
help = "Specify subcommand resize or stats. For help, type imagecli resize --help or imagecli stats --help"
|
||||
)]
|
||||
enum Commandline {
|
||||
#[structopt(help = "Specify size(small/medium/large) , mode(single/all) and srcfolder")]
|
||||
Resize {
|
||||
#[structopt(long)]
|
||||
size: SizeOption,
|
||||
#[structopt(long)]
|
||||
mode: Mode,
|
||||
#[structopt(long, parse(from_os_str))]
|
||||
srcfolder: PathBuf,
|
||||
},
|
||||
#[structopt(help = "Specify srcfolder")]
|
||||
Stats {
|
||||
#[structopt(long, parse(from_os_str))]
|
||||
srcfolder: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Commandline = Commandline::from_args();
|
||||
match args {
|
||||
Commandline::Resize {
|
||||
size,
|
||||
mode,
|
||||
srcfolder,
|
||||
} => {
|
||||
let mut src_folder = srcfolder;
|
||||
match process_resize_request(size, mode, &mut src_folder) {
|
||||
Ok(_) => println!("Image(s) resized successfully"),
|
||||
Err(e) => match e {
|
||||
ImagixError::FileIOError(e) => println!("{}", e),
|
||||
ImagixError::UserInputError(e) => println!("{}", e),
|
||||
ImagixError::ImageResizingError(e) => println!("{}", e),
|
||||
_ => println!("Error in processing"),
|
||||
},
|
||||
};
|
||||
}
|
||||
Commandline::Stats { srcfolder } => match get_stats(srcfolder) {
|
||||
Ok((count, size)) => println!(
|
||||
"Found {:?} image files with aggregate size of {:?} MB",
|
||||
count, size
|
||||
),
|
||||
Err(e) => match e {
|
||||
ImagixError::FileIOError(e) => println!("{}", e),
|
||||
ImagixError::UserInputError(e) => println!("{}", e),
|
||||
_ => println!("Error in processing"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
use image::error;
|
||||
use std::convert::From;
|
||||
use std::{fmt, io};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ImagixError {
|
||||
FileIOError(String),
|
||||
UserInputError(String),
|
||||
ImageResizingError(String),
|
||||
FormatError(String),
|
||||
}
|
||||
|
||||
impl From<io::Error> for ImagixError {
|
||||
fn from(error: io::Error) -> Self {
|
||||
ImagixError::FileIOError("Error in File I/O".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<error::ImageError> for ImagixError {
|
||||
fn from(error: error::ImageError) -> Self {
|
||||
ImagixError::ImageResizingError("Error in image processing".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::ErrorKind> for ImagixError {
|
||||
fn from(error: io::ErrorKind) -> Self {
|
||||
ImagixError::UserInputError("Error in user input".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ImagixError {
|
||||
fn fmt(&self, out: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
write!(
|
||||
out,
|
||||
"{}",
|
||||
ImagixError::FormatError("Error occurred".to_string())
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
pub mod error;
|
||||
pub mod resize;
|
||||
pub mod stats;
|
@ -0,0 +1,176 @@
|
||||
use image::ImageFormat;
|
||||
use std::path::PathBuf;
|
||||
use std::result::Result;
|
||||
use std::str::FromStr;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{fmt, fs, io};
|
||||
|
||||
use super::error::ImagixError;
|
||||
|
||||
struct Elapsed(Duration);
|
||||
|
||||
impl Elapsed {
|
||||
fn from(start: &Instant) -> Self {
|
||||
Elapsed(start.elapsed())
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum SizeOption {
|
||||
Small,
|
||||
Medium,
|
||||
Large,
|
||||
}
|
||||
impl fmt::Display for Elapsed {
|
||||
fn fmt(&self, out: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match (self.0.as_secs(), self.0.subsec_nanos()) {
|
||||
(0, n) if n < 1000 => write!(out, "{} ns", n),
|
||||
(0, n) if n < 1000_000 => write!(out, "{} µs", n / 1000),
|
||||
(0, n) => write!(out, "{} ms", n / 1000_000),
|
||||
(s, n) if s < 10 => write!(out, "{}.{:02} s", s, n / 10_000_000),
|
||||
(s, _) => write!(out, "{} s", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FromStr for SizeOption {
|
||||
type Err = ImagixError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"small" => Ok(SizeOption::Small),
|
||||
"medium" => Ok(SizeOption::Medium),
|
||||
"large" => Ok(SizeOption::Large),
|
||||
_ => Err(ImagixError::UserInputError(
|
||||
"Invalid input for size".to_string(),
|
||||
)),
|
||||
//default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Mode {
|
||||
type Err = ImagixError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"single" => Ok(Mode::Single),
|
||||
"all" => Ok(Mode::All),
|
||||
_ => Err(ImagixError::UserInputError(
|
||||
"Wrong value for mode".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Mode {
|
||||
Single,
|
||||
All,
|
||||
}
|
||||
|
||||
pub fn process_resize_request(
|
||||
size: SizeOption,
|
||||
mode: Mode,
|
||||
src_folder: &mut PathBuf,
|
||||
) -> Result<(), ImagixError> {
|
||||
let size = match size {
|
||||
SizeOption::Small => 200,
|
||||
SizeOption::Medium => 400,
|
||||
SizeOption::Large => 800,
|
||||
};
|
||||
let _ = match mode {
|
||||
Mode::All => resize_all(size, src_folder)?,
|
||||
Mode::Single => resize_single(size, src_folder)?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resize_single(size: u32, src_folder: &mut PathBuf) -> Result<(), ImagixError> {
|
||||
let mut src_folder = src_folder;
|
||||
// Get file stem from src_folder
|
||||
resize_image(size, &mut src_folder)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resize_all(size: u32, src_folder: &mut PathBuf) -> Result<(), ImagixError> {
|
||||
if let Ok(entries) = get_image_files(src_folder.to_path_buf()) {
|
||||
for mut entry in entries {
|
||||
resize_image(size, &mut entry)?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resize_image(size: u32, src_folder: &mut PathBuf) -> Result<(), ImagixError> {
|
||||
// Construct destination file name th .png extension
|
||||
let new_file_name = src_folder
|
||||
.file_stem()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.ok_or(std::io::ErrorKind::InvalidInput)
|
||||
// .map(|f| format!("{}-{:?}.png", f, size));
|
||||
.map(|f| format!("{}.png", f));
|
||||
|
||||
// Construct path to destination folder i.e. create /tmp under source folder if not exists
|
||||
let mut dest_folder = src_folder.clone();
|
||||
dest_folder.pop();
|
||||
dest_folder.push("tmp/");
|
||||
if !dest_folder.exists() {
|
||||
fs::create_dir(&dest_folder)?;
|
||||
}
|
||||
dest_folder.pop();
|
||||
dest_folder.push("tmp/tmp.png");
|
||||
dest_folder.set_file_name(new_file_name?.as_str());
|
||||
|
||||
// Open source image file, scale it to desired size and write output to destination-folder/destination-file
|
||||
let timer = Instant::now();
|
||||
let img = image::open(&src_folder)?;
|
||||
let scaled = img.thumbnail(size, size);
|
||||
let mut output = fs::File::create(&dest_folder)?;
|
||||
scaled.write_to(&mut output, ImageFormat::Png)?;
|
||||
println!(
|
||||
"Thumbnailed file: {:?} to size {}x{} in {}. Output file in {:?}",
|
||||
src_folder,
|
||||
size,
|
||||
size,
|
||||
Elapsed::from(&timer),
|
||||
dest_folder
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_image_files(src_folder: PathBuf) -> Result<Vec<PathBuf>, ImagixError> {
|
||||
let entries = fs::read_dir(src_folder)
|
||||
.map_err(|e| ImagixError::UserInputError("Invalid source folder".to_string()))?
|
||||
.map(|res| res.map(|e| e.path()))
|
||||
.collect::<Result<Vec<_>, io::Error>>()?
|
||||
.into_iter()
|
||||
.filter(|r| {
|
||||
r.extension() == Some("JPG".as_ref())
|
||||
|| r.extension() == Some("jpg".as_ref())
|
||||
|| r.extension() == Some("PNG".as_ref())
|
||||
|| r.extension() == Some("png".as_ref())
|
||||
})
|
||||
.collect();
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_single_image_resize() {
|
||||
let mut path = PathBuf::from("/tmp/images/image1.jpg");
|
||||
let destination_path = PathBuf::from("/tmp/images/tmp/image1.png");
|
||||
match process_resize_request(SizeOption::Small, Mode::Single, &mut path) {
|
||||
Ok(dest) => assert_eq!(true, destination_path.exists()),
|
||||
Err(e) => println!("Error in single image: {:?}", e),
|
||||
}
|
||||
assert_eq!(true, destination_path.exists());
|
||||
}
|
||||
#[test]
|
||||
fn test_multiple_image_resize() {
|
||||
let mut path = PathBuf::from("/tmp/images/");
|
||||
let _res = process_resize_request(SizeOption::Small, Mode::All, &mut path);
|
||||
let destination_path1 = PathBuf::from("/tmp/images/tmp/image1.png");
|
||||
let destination_path2 = PathBuf::from("/tmp/images/tmp/image2.png");
|
||||
assert_eq!(true, destination_path1.exists());
|
||||
assert_eq!(true, destination_path2.exists());
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
use super::error::ImagixError;
|
||||
use super::resize::get_image_files;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn get_stats(src_folder: PathBuf) -> Result<(usize, u64), ImagixError> {
|
||||
let image_files = get_image_files(src_folder.to_path_buf())?;
|
||||
let size = image_files
|
||||
.iter()
|
||||
.map(move |f| f.metadata().unwrap().len())
|
||||
.sum::<u64>();
|
||||
Ok((image_files.len(), size / 1000000))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_get_stats() {
|
||||
let path = PathBuf::from("/tmp/images");
|
||||
let (count, size) = get_stats(path).unwrap();
|
||||
assert_eq!(count, 2);
|
||||
assert_eq!(size, 8);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue