feat: add webp and use yaml

This commit is contained in:
Anna 2021-02-17 15:45:36 -05:00
parent e34b6f93b0
commit 20e8e7d3fe
Signed by: anna
GPG Key ID: 0B391D8F06FCD9E0
5 changed files with 567 additions and 755 deletions

1202
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
[package]
name = "screenshot_organiser"
version = "0.1.0"
authors = ["Kyle Clemens <git@kyleclemens.com>"]
authors = ["Anna Clemens <git@annaclemens.io>"]
edition = "2018"
@ -9,14 +9,15 @@ edition = "2018"
chrono = "0.4"
regex = "1"
notify = "4"
image = "0.20"
image = "0.23"
serde = "1"
serde_derive = "1"
serde_json = "1"
serde_regex = "0.3"
failure = "0.1"
serde_yaml = "0.8"
serde_regex = "1"
anyhow = "1"
tempdir = "0.3"
num_cpus = "1"
rayon = "1"
ctrlc = "3"
crossbeam-channel = "0.3"
crossbeam-channel = "0.5"
libwebp = { version = "0.1", features = ["static"] }

52
config.yaml Normal file
View File

@ -0,0 +1,52 @@
# You should set FFXIV/ReShade to output PNG files.
options:
# This is the directory that your screenshots are stored in.
screenshots_dir: './screenshots'
# File names must match a regular expression in this list in order
# to be processed.
#
# The two defaults are FFXIV's format and ReShade's format.
#
# Each regular expression must contain groups for month, day, year,
# hour, minute, and second.
match:
- '(?P<year>\d{4})-(?P<month>\d{2})-(?<day>\d{2})_(?P<hour>\d{2})-(?P<minute>\d{2})-(?P<second>\d{2})_.*.png'
- 'ffxiv_(?P<month>\d{2})(?P<day>\d{2})(?P<year>\d{4})_(?P<hour>\d{2})(?P<minute>\d{2})(?P<second>\d{2})(?:_\d{3})?.(?:jpg|png)'
- 'ffxiv_dx11 (?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2}) (?P<hour>\d{2})-(?P<minute>\d{2})-(?P<second>\d{2}).(?:png|bmp)'
# How long to delay events before giving them to the program. This
# usually doesn't need to be changed.
event_delay: 1000
# A list of jobs to run on the files.
#
# The two types are convert and move.
pipeline:
# Convert screenshots to lossless webp.
- job: convert
options:
to:
# The format to convert the screenshot to.
#
# Supported options: png, jpg*, gif, bmp, ico, webp*
# Options marked with * require a quality to be specified.
format: webp
# The quality of the output. This is only necessary for jpg and webp.
#
# For jpg, the number can be between 0 and 100. Smaller
# numbers are lower quality but lower file size.
#
# For webp, the number can be -1 to indicate lossless
# encoding. It can also be between 0 and 100, with lower
# numbers having lower quality but lower file size.
quality: -1
# Whether to keep the original file. If this is false, the
# original will be deleted.
keep_original: false
- job: move
options:
# The path to move the file to without its extension. You can
# specify directories to sort files into directories.
to: '%Y/%m/%d/ffxiv.%Y-%m-%d.%H-%M-%S'
# Whether to use your local time zone or UTC.
local: true

View File

@ -2,11 +2,14 @@ use crate::{Result, config::Config, state::State};
use chrono::Local;
use failure::bail;
use anyhow::bail;
use image::{ImageFormat, ImageOutputFormat};
use std::fs::OpenOptions;
use std::{
fs::OpenOptions,
io::Write,
};
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
@ -51,7 +54,21 @@ impl Job {
.create(true)
.open(&f)?;
i.write_to(&mut dest, to.as_image_output_format())?;
match to {
FileType::WebP { quality } => {
let rgba = i.into_rgba();
let flat = rgba.into_flat_samples();
let stride = flat.layout.width_stride as u32 * flat.layout.width;
let data = if quality < 0 {
libwebp::WebPEncodeLosslessRGBA(&flat.samples, flat.layout.width, flat.layout.height, stride)?
} else {
let quality = std::cmp::min(100, quality);
libwebp::WebPEncodeRGBA(&flat.samples, flat.layout.width, flat.layout.height, stride, f32::from(quality))?
};
dest.write_all(&data)?;
},
_ => i.write_to(&mut dest, to.as_image_output_format())?,
}
if keep {
add = Some(old_f);
@ -83,13 +100,7 @@ impl Job {
if let Some(p) = file_path.parent() {
std::fs::create_dir_all(p)?;
}
match std::fs::rename(&f, &file_path) {
Err(ref e) if cfg!(windows) && e.raw_os_error() == Some(17) => {
std::fs::copy(&f, &file_path)?;
std::fs::remove_file(&f)?;
},
_ => {},
}
std::fs::copy(&f, &file_path)?;
*f = file_path;
}
@ -108,6 +119,10 @@ pub enum FileType {
Gif,
Bmp,
Ico,
WebP {
// less than 0 for lossless
quality: i8,
},
}
impl FileType {
@ -118,22 +133,24 @@ impl FileType {
FileType::Gif => "gif",
FileType::Bmp => "bmp",
FileType::Ico => "ico",
FileType::WebP { .. } => "webp",
}
}
fn as_image_format(self) -> ImageFormat {
match self {
FileType::Png => ImageFormat::PNG,
FileType::Jpg { .. } => ImageFormat::JPEG,
FileType::Gif => ImageFormat::GIF,
FileType::Bmp => ImageFormat::BMP,
FileType::Ico => ImageFormat::ICO,
FileType::Png => ImageFormat::Png,
FileType::Jpg { .. } => ImageFormat::Jpeg,
FileType::Gif => ImageFormat::Gif,
FileType::Bmp => ImageFormat::Bmp,
FileType::Ico => ImageFormat::Ico,
FileType::WebP { .. } => ImageFormat::WebP,
}
}
fn as_image_output_format(self) -> ImageOutputFormat {
match self {
FileType::Jpg { quality } => ImageOutputFormat::JPEG(quality),
FileType::Jpg { quality } => ImageOutputFormat::Jpeg(quality),
_ => self.as_image_format().into(),
}
}

View File

@ -13,7 +13,7 @@ use crossbeam_channel::Receiver;
use chrono::{DateTime, Duration, Local, TimeZone, Utc};
use failure::Error;
use anyhow::Error;
use notify::{DebouncedEvent, RecursiveMode, Watcher};
@ -39,12 +39,12 @@ fn main() -> Result<()> {
let config_path = match std::env::args().nth(1) {
Some(x) => x,
None => "config.json".into(),
None => "config.yaml".into(),
};
println!("Attempting to read config from `{}`.", config_path);
let f = fs::File::open(config_path)?;
let config: Config = serde_json::from_reader(f)?;
let config: Config = serde_yaml::from_reader(f)?;
println!("Config successfully read.");