summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian T <julian@jtle.dk>2021-07-22 21:36:15 +0200
committerJulian T <julian@jtle.dk>2021-07-22 21:36:15 +0200
commitc3c69b160f4c5fd851fd1a49c01f633a56351f5d (patch)
tree55cdca5f052717d1c7f9abab97e864f28dc17534
parent69bbd4ed3804833b15285cf8637e96e59135f223 (diff)
Add smart image conversion
-rw-r--r--.gitignore3
-rw-r--r--Cargo.lock23
-rw-r--r--Cargo.toml2
-rw-r--r--src/context.rs19
-rw-r--r--src/main.rs13
-rw-r--r--src/picture.rs149
-rw-r--r--src/piece.rs22
7 files changed, 227 insertions, 4 deletions
diff --git a/.gitignore b/.gitignore
index 35d134e..6973ddf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
-dist/imgs/*
-
+build
# Added by cargo
diff --git a/Cargo.lock b/Cargo.lock
index 5f1e4cd..8b9e736 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -258,6 +258,8 @@ version = "0.1.0"
dependencies = [
"glob",
"image",
+ "kamadak-exif",
+ "md5",
"serde",
"serde_yaml",
"structopt",
@@ -401,6 +403,15 @@ dependencies = [
]
[[package]]
+name = "kamadak-exif"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70494964492bf8e491eb3951c5d70c9627eb7100ede6cc56d748b9a3f302cfb6"
+dependencies = [
+ "mutate_once",
+]
+
+[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -434,6 +445,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
+name = "md5"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
+
+[[package]]
name = "memchr"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -468,6 +485,12 @@ dependencies = [
]
[[package]]
+name = "mutate_once"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"
+
+[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 5b0a26f..385d8a6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,3 +13,5 @@ serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
structopt = "0.3"
glob = "0.3"
+md5 = "0.7"
+kamadak-exif = "0.5"
diff --git a/src/context.rs b/src/context.rs
index 6bc4c05..55f3f97 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -14,6 +14,15 @@ pub struct Options {
pub config: Option<PathBuf>,
#[structopt(help = "Picture location")]
pub load: PathBuf,
+ #[structopt(short, long, default_value = "png", help = "Image extension to use for converted files")]
+ pub ext: String,
+ #[structopt(long, short, default_value = "build", help = "Where to build site")]
+ pub builddir: PathBuf,
+
+ #[structopt(long, default_value = "1080", help = "Scaled size for image")]
+ pub size_scaled: u32,
+ #[structopt(long, default_value = "720", help = "Thumbnail size for image")]
+ pub size_thumb: u32,
}
#[derive(Debug)]
@@ -36,16 +45,26 @@ pub struct Config {
pub struct Context {
pub options: Options,
pub config: Config,
+ pub imgdir: PathBuf,
}
impl Context {
pub fn new_with_args() -> Result<Context, ConfigError> {
let opts = Options::from_args();
let config = Config::load_with_options(&opts)?;
+ let imgdir = opts.builddir.join("imgs");
+
+ // Create img dir
+ if let Err(err) = fs::create_dir(&imgdir) {
+ if err.kind() != io::ErrorKind::AlreadyExists {
+ return Err(ConfigError::from(err));
+ }
+ }
Ok(Context {
options: opts,
config: config,
+ imgdir: imgdir,
})
}
diff --git a/src/main.rs b/src/main.rs
index 06346a2..28fe0c9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,14 +1,23 @@
mod context;
+mod picture;
+mod piece;
+
use context::Context;
+use picture::Picture;
+use piece::Piece;
fn main() {
println!("Hello, world!");
let ctx = Context::new_with_args().unwrap();
- println!("{:?}", ctx);
-
for file in ctx.get_image_files().unwrap() {
println!("{}", file.display());
+
+ let pic = Picture::new_from_file(&file).unwrap();
+ println!("{:?}", pic);
+
+ let piece = Piece::new(&ctx, pic).unwrap();
+ println!("{:?}", piece);
}
}
diff --git a/src/picture.rs b/src/picture.rs
new file mode 100644
index 0000000..f92876c
--- /dev/null
+++ b/src/picture.rs
@@ -0,0 +1,149 @@
+use std::path::{PathBuf, Path};
+use std::fs;
+use std::io;
+use std::io::{Read, Seek};
+use image::io::Reader as ImageReader;
+use image::error::ImageError;
+use image::imageops;
+
+use crate::context::Context;
+
+#[derive(Debug)]
+pub enum LoadError {
+ Io(io::Error),
+ ExifParser(exif::Error)
+}
+
+#[derive(Debug)]
+pub enum ConversionError {
+ Io(io::Error),
+ ImageError(ImageError),
+}
+
+#[derive(Debug)]
+pub struct Picture {
+ taken: Option<String>,
+ hash: md5::Digest,
+ path: PathBuf,
+}
+
+pub struct Converter<'a> {
+ imgdata: Option<image::DynamicImage>,
+ pic: &'a Picture,
+}
+
+fn hash_reader<R: Read>(reader: &mut R) -> Result<md5::Digest, io::Error> {
+ let mut hash = md5::Context::new();
+ let mut buff = [0; 1024];
+
+ loop {
+ let count = reader.read(&mut buff)?;
+ if count == 0 {
+ // Reached end, stopping
+ break
+ }
+
+ hash.consume(&buff[..count])
+ }
+
+ Ok(hash.compute())
+}
+
+impl Picture {
+ /// Hash file content and load exif data.
+ pub fn new_from_file(path: &Path) -> Result<Self, LoadError> {
+ let file = fs::File::open(path)?;
+ let mut reader = io::BufReader::new(&file);
+
+ let taken = match exif::Reader::new().read_from_container(&mut reader) {
+ Ok(exif) => exif.get_field(exif::Tag::DateTimeOriginal, exif::In::PRIMARY)
+ .map(|field| field.display_value().with_unit(&exif).to_string()),
+ Err(err) => {
+ println!("Could not load exif data for {}: {}", path.to_str().unwrap(), err);
+ None
+ }
+ };
+
+ // Move back to start of file for hashing
+ reader.seek(io::SeekFrom::Start(0))?;
+
+ Ok(Picture {
+ taken,
+ hash: hash_reader(&mut reader)?,
+ path: path.to_path_buf(),
+ })
+ }
+
+ pub fn convert(&self) -> Result<Converter, ConversionError> {
+ Ok(Converter {
+ imgdata: None,
+ pic: self,
+ })
+ }
+}
+
+impl Converter<'_> {
+ fn convert_image(&mut self, size: u32, dest: &Path) -> Result<(), ImageError> {
+ let scaled = self.get_imgdata()?.resize(
+ size,
+ std::u32::MAX,
+ imageops::FilterType::Lanczos3);
+
+ scaled.save(dest)
+ }
+
+ fn get_imgdata(&mut self) -> Result<&image::DynamicImage, ImageError> {
+ let picpath = &self.pic.path;
+
+ match self.imgdata {
+ None => self.imgdata = Some(
+ ImageReader::open(picpath)?.decode()?
+ ),
+ _ => ()
+ }
+
+ Ok(self.imgdata.as_ref().unwrap())
+ }
+
+ pub fn get_size(&mut self, ctx: &Context, size: u32) -> Result<PathBuf, ImageError> {
+ let hash = md5::compute(format!("{},{},{:?}", size, ctx.options.ext, self.pic.hash));
+ let name = format!("{:?}.{}", hash, ctx.options.ext);
+ let path = ctx.imgdir.join(name);
+
+ match path.exists() {
+ true => {
+ println!("Image of size {} already exists", size);
+ Ok(path)
+ },
+ false => {
+ println!("Scaling image to size {}", size);
+ self.convert_image(size, &path)?;
+ Ok(path)
+ }
+ }
+ }
+}
+
+impl From<io::Error> for LoadError {
+ fn from(error: io::Error) -> Self {
+ Self::Io(error)
+ }
+}
+
+impl From<exif::Error> for LoadError {
+ fn from(error: exif::Error) -> Self {
+ Self::ExifParser(error)
+ }
+}
+
+impl From<io::Error> for ConversionError {
+ fn from(error: io::Error) -> Self {
+ Self::Io(error)
+ }
+}
+
+impl From<ImageError> for ConversionError {
+ fn from(error: ImageError) -> Self {
+ Self::ImageError(error)
+ }
+}
diff --git a/src/piece.rs b/src/piece.rs
new file mode 100644
index 0000000..a0cc18e
--- /dev/null
+++ b/src/piece.rs
@@ -0,0 +1,22 @@
+use std::path::PathBuf;
+use crate::picture::{Picture, ConversionError};
+use crate::context::Context;
+
+#[derive(Debug)]
+pub struct Piece {
+ pic: Picture,
+ scaled_path: PathBuf,
+ thumb_path: PathBuf,
+}
+
+impl Piece {
+ pub fn new(ctx: &Context, pic: Picture) -> Result<Self, ConversionError> {
+ let mut conv = pic.convert()?;
+ let scaled_path = conv.get_size(ctx, ctx.options.size_scaled)?;
+ let thumb_path = conv.get_size(ctx, ctx.options.size_thumb)?;
+
+ Ok(Piece {
+ pic, scaled_path, thumb_path,
+ })
+ }
+}