Batch EXIF endpoint: GET /photos/exif
Adds a single round-trip projection of `image_exif` for every photo whose
`date_taken` falls in `[date_from, date_to]`. Wraps the existing
`ExifDao::query_by_exif` DAO method which already handles the SQL filter
in one query against the covering index — the only missing piece was
HTTP plumbing.
Designed for window-scoped consumers like Apollo's photo-to-track
matcher, which currently does N+1 (one `/photos` listing + one
`/image/metadata` per photo). Because `/image/metadata` serializes on
`Data<Mutex<dyn ExifDao>>`, that pattern can take 10s+ for windows with
hundreds of photos. The new endpoint takes one mutex acquisition for
the whole batch.
Response shape:
{ photos: [
{ file_path, library_id, library_name,
camera_model, width, height,
gps_latitude, gps_longitude, date_taken } ],
total: N }
Two notes on scope:
- Photos with NULL `date_taken` are excluded by `query_by_exif`'s
semantics. Filename-extracted dates are not synthesized here; rare
callers that need that fallback can still hit `/image/metadata`.
- GPS columns are stored as f32 in image_exif to keep row size small;
the JSON shape widens to f64 so clients don't have to know about the
on-disk precision.
Library names are pre-mapped from `app_state.libraries` once and
stamped on each row, avoiding an O(rows × libraries) linear scan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -390,6 +390,36 @@ pub struct GpsPhotosResponse {
|
||||
pub total: usize,
|
||||
}
|
||||
|
||||
/// Single-row projection of `image_exif` rich enough to drive Apollo's
|
||||
/// photo-to-track matcher (and any similar window-scoped consumer) without
|
||||
/// a per-file `/image/metadata` round-trip. Returned by `/photos/exif`.
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ExifSummary {
|
||||
pub file_path: String,
|
||||
pub library_id: i32,
|
||||
pub library_name: Option<String>,
|
||||
pub camera_model: Option<String>,
|
||||
pub width: Option<i32>,
|
||||
pub height: Option<i32>,
|
||||
pub gps_latitude: Option<f64>,
|
||||
pub gps_longitude: Option<f64>,
|
||||
pub date_taken: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ExifBatchResponse {
|
||||
pub photos: Vec<ExifSummary>,
|
||||
pub total: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ExifBatchRequest {
|
||||
/// Lower bound (inclusive) for `image_exif.date_taken`, unix seconds.
|
||||
pub date_from: Option<i64>,
|
||||
/// Upper bound (inclusive). Same semantics as `date_to` on `/photos`.
|
||||
pub date_to: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PreviewClipRequest {
|
||||
pub path: String,
|
||||
|
||||
Reference in New Issue
Block a user