Encode WebP thumbnails at quality 80
This commit is contained in:
20
Cargo.lock
generated
20
Cargo.lock
generated
@@ -961,6 +961,7 @@ dependencies = [
|
||||
"usearch",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
"webp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5209,6 +5210,16 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libwebp-sys"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54cd30df7c7165ce74a456e4ca9732c603e8dc5e60784558c1c6dc047f876733"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"glob",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libxdo"
|
||||
version = "0.6.0"
|
||||
@@ -10882,6 +10893,15 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webp"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c071456adef4aca59bf6a583c46b90ff5eb0b4f758fc347cea81290288f37ce1"
|
||||
dependencies = [
|
||||
"libwebp-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.9"
|
||||
|
||||
@@ -34,6 +34,7 @@ axum = "0.8"
|
||||
walkdir = "2"
|
||||
futures = "0.3"
|
||||
image = "0.25"
|
||||
webp = { version = "0.3.1", default-features = false }
|
||||
rust-stemmers = "1"
|
||||
sys-locale = "0.3"
|
||||
fluent-bundle = "0.16"
|
||||
|
||||
@@ -8,7 +8,7 @@ The project is under active development. Core blogging workflows are broadly ava
|
||||
|
||||
- Native Iced desktop workspace with localized menus, tabs, anchored editor popovers, automatically paged post/media sidebars, relative-dated entity lists with row deletion, dialogs, tasks, embedded Wry previews, and a live Pico CSS theme editor.
|
||||
- Post and translation authoring with change-aware draft/published/archive lifecycle, file-backed change discard, canonical draft reopening after manual translation edits, non-disruptive automatic translation, desktop archive/unarchive actions, in-place published-frontmatter updates, metadata, tags, categories, live link/backlink graphs, media, and batch gallery-image import.
|
||||
- Media import, thumbnails, metadata translations, filters, validation, post assignment, and sequential drag-and-drop insertion into post editors.
|
||||
- Media import, q80 WebP thumbnails (plus q85 AI JPEGs), metadata translations, filters, validation, post assignment, and sequential drag-and-drop insertion into post editors.
|
||||
- WordPress WXR migration with saved analyses, HTML-to-Markdown and shortcode conversion, conflict/taxonomy review, recoverable 500-item execution batches, media-parent linking, progress reporting, and optional AI-assisted taxonomy mapping.
|
||||
- Post, Liquid template, and Lua script editing with dedicated syntax highlighting, explicit syntax-check feedback, change-aware published/draft lifecycle, normalized collision-safe template/script slug changes, reference-safe template renames, and publish-time enforcement of the bDS2 Liquid tag/filter/operator subset, using a custom Ropey/Syntect/Cosmic Text editor and the documented, bDS2-signature-compatible project-scoped [`bds` host API](docs/scripting/API_REFERENCE.md) across utilities, rendered macros, and Blogmark transforms, including airplane-gated Git sync.
|
||||
- SQLite and filesystem persistence with frontmatter, relationship-safe media sidecars, rebuild, bidirectional metadata diff/repair including project categories and publishing preferences, stale post-path cleanup on republish, bDS2-compatible checksums and NFD slug generation, and FTS5 search.
|
||||
|
||||
@@ -19,6 +19,7 @@ unicode-normalization = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
walkdir = { workspace = true }
|
||||
image = { workspace = true }
|
||||
webp = { workspace = true }
|
||||
rust-stemmers = { workspace = true }
|
||||
sys-locale = { workspace = true }
|
||||
fluent-bundle = { workspace = true }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use image::imageops::FilterType;
|
||||
use image::{DynamicImage, GenericImageView, ImageFormat, ImageReader};
|
||||
use image::{DynamicImage, GenericImageView, ImageReader};
|
||||
use std::fs;
|
||||
use std::io::Cursor;
|
||||
use std::path::Path;
|
||||
@@ -101,13 +101,11 @@ pub fn generate_thumbnail(
|
||||
|
||||
match size.format {
|
||||
ThumbnailFormat::Webp => {
|
||||
// Encode as WebP via image crate
|
||||
let mut buf = Cursor::new(Vec::new());
|
||||
resized
|
||||
.write_to(&mut buf, ImageFormat::WebP)
|
||||
.map_err(|e| format!("WebP encode: {e}"))?;
|
||||
let _ = quality; // image crate WebP encoder doesn't expose quality directly
|
||||
fs::write(dest, buf.into_inner()).map_err(|e| format!("write: {e}"))?;
|
||||
let rgba = resized.to_rgba8();
|
||||
let encoded = webp::Encoder::from_rgba(rgba.as_raw(), rgba.width(), rgba.height())
|
||||
.encode_simple(false, f32::from(quality))
|
||||
.map_err(|e| format!("WebP encode: {e:?}"))?;
|
||||
fs::write(dest, &*encoded).map_err(|e| format!("write: {e}"))?;
|
||||
}
|
||||
ThumbnailFormat::Jpeg => {
|
||||
let rgb = resized.to_rgb8();
|
||||
@@ -325,6 +323,8 @@ mod tests {
|
||||
// 100x80 is smaller than 150 wide, no enlargement
|
||||
assert_eq!(w, 100);
|
||||
assert_eq!(h, 80);
|
||||
let encoded = fs::read(&dest).unwrap();
|
||||
assert_eq!(&encoded[12..16], b"VP8 ", "thumbnail must use lossy WebP");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user