Thumb orientation + library filter on /photos/exif
Two follow-ups on the same feature branch: 1. Bake EXIF orientation into generated thumbnails. The `image` crate doesn't apply Orientation on load, and `save_with_format(..Jpeg)` drops EXIF — so portrait phone shots ended up sideways in any client that displays the cached thumb directly (no EXIF tag for the browser to compensate from). New `exif::read_orientation` reads the tag cheaply (no full EXIF parse) and `exif::apply_orientation` does the rotate/flip via image's existing `rotate90/180/270` + `fliph/flipv`. Applied in both branches of `generate_image_thumbnail` (RAW embedded- JPEG path and the regular `image::open` path). Existing thumbnails in the cache are still wrong-orientation; wipe the thumb dir or run a one-off backfill once this lands. 2. Optional `library` query param on `/photos/exif`. Accepts numeric id or name (same shape as `/image?library=...`), resolved via the existing `resolve_library_param` helper so a bad value 400s before we touch the DAO. Filter is applied post-query in the handler rather than pushed into `query_by_exif` to keep the DAO trait (and its test mocks) unchanged. Cheap enough at typical library counts; can be moved into SQL later if it ever isn't. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
20
src/files.rs
20
src/files.rs
@@ -1217,6 +1217,21 @@ pub async fn list_exif_summary(
|
||||
"date_to",
|
||||
req.date_to.map(|v| v.to_string()).unwrap_or_default(),
|
||||
));
|
||||
span.set_attribute(KeyValue::new(
|
||||
"library",
|
||||
req.library.clone().unwrap_or_default(),
|
||||
));
|
||||
|
||||
// Resolve the library filter up front so a bad id/name 400s before we
|
||||
// ever take the DAO mutex. None == union across all libraries.
|
||||
let library_filter =
|
||||
match crate::libraries::resolve_library_param(&app_state, req.library.as_deref()) {
|
||||
Ok(lib) => lib.map(|l| l.id),
|
||||
Err(msg) => {
|
||||
span.set_status(Status::error(msg.clone()));
|
||||
return Ok(HttpResponse::BadRequest().json(serde_json::json!({ "error": msg })));
|
||||
}
|
||||
};
|
||||
let cx = opentelemetry::Context::current_with_span(span);
|
||||
|
||||
// Pre-build an id → name map so we don't linear-scan libraries per row.
|
||||
@@ -1231,6 +1246,11 @@ pub async fn list_exif_summary(
|
||||
Ok(rows) => {
|
||||
let photos: Vec<ExifSummary> = rows
|
||||
.into_iter()
|
||||
// Library filter post-query: keeps the DAO trait (and its
|
||||
// mocks) unchanged. For typical 2–3 library setups the in-
|
||||
// memory pass over a date-bounded result set is negligible;
|
||||
// can be pushed into SQL later if it ever isn't.
|
||||
.filter(|r| library_filter.is_none_or(|id| r.library_id == id))
|
||||
.map(|r| ExifSummary {
|
||||
library_name: library_names.get(&r.library_id).cloned(),
|
||||
file_path: r.file_path,
|
||||
|
||||
Reference in New Issue
Block a user