Encode WebP thumbnails at quality 80

This commit is contained in:
2026-07-22 18:33:20 +02:00
parent 70503c8bc2
commit bdfe57b66f
5 changed files with 31 additions and 9 deletions

20
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View File

@@ -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.

View File

@@ -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 }

View File

@@ -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]