use anyhow::{Context, Result};
use base64::{engine::general_purpose, Engine as _};
use image::imageops::FilterType;
use std::io::Cursor;
use std::path::PathBuf;
use walkdir::WalkDir;
#[derive(clap::Args, Debug)]
pub struct LqipArgs {
#[arg(short, long)]
pub target: String,
#[arg(long, default_value_t = 20)]
pub width: u32,
#[arg(long, default_value_t = 15)]
pub height: u32,
#[arg(short, long)]
pub recursive: bool,
#[arg(long, default_value = "text")]
pub format: String,
}
pub async fn run(args: LqipArgs) -> Result<()> {
let target_path = PathBuf::from(&args.target);
if !target_path.exists() {
anyhow::bail!("Target path does not exist: {target_path:?}");
}
let mut images = Vec::new();
if target_path.is_file() {
images.push(target_path);
} else {
let walker = WalkDir::new(&target_path);
let walker = if args.recursive {
walker
} else {
walker.max_depth(1)
};
for entry in walker.into_iter().filter_map(std::result::Result::ok) {
let path = entry.path();
if let Some(ext) = path.extension() {
let ext_str = ext.to_string_lossy().to_lowercase();
if ["jpg", "jpeg", "png", "webp"].contains(&ext_str.as_str()) {
images.push(path.to_path_buf());
}
}
}
}
let mut results = Vec::new();
for path in images {
let img = image::open(&path).context(format!("Failed to open image: {path:?}"))?;
let resized = img.resize(args.width, args.height, FilterType::Triangle);
let mut buffer = Cursor::new(Vec::new());
let format = image::ImageFormat::from_path(&path).unwrap_or(image::ImageFormat::Png);
resized
.write_to(&mut buffer, format)
.context("Failed to encode resized image")?;
let b64 = general_purpose::STANDARD.encode(buffer.get_ref());
let mime = match format {
image::ImageFormat::Png => "image/png",
image::ImageFormat::Jpeg => "image/jpeg",
image::ImageFormat::WebP => "image/webp",
_ => "image/png", };
let data_uri = format!("data:{mime};base64,{b64}");
if args.format == "json" {
results.push(serde_json::json!({
"src": path.file_stem().unwrap().to_string_lossy(),
"path": path.to_string_lossy(),
"lqip": data_uri
}));
} else {
println!("File: {path:?}\nLQIP: {data_uri}\n");
}
}
if args.format == "json" {
println!("{}", serde_json::to_string_pretty(&results)?);
}
Ok(())
}