ingest: stamp canonical date_taken on every InsertImageExif
Wires `date_resolver::resolve_date_taken` into the three call sites that build `InsertImageExif`: - `process_new_files` (file watcher) — every newly-registered file gets the resolver's verdict so videos and EXIF-stripped images land with a real date instead of NULL. - Upload handler — same waterfall on the post-multipart-write path. - GPS-write handler — re-runs the waterfall after exiftool writes GPS and re-reads the EXIF, in case a previously fs_time-sourced row now has a real EXIF date to upgrade to. This is a behavior change vs. the pre-rewrite `/memories` request-time priority: EXIF now beats filename when both are present. A photo named `Screenshot_2014-06-01.png` whose EXIF `DateTime` is 2021 now appears under 2021. The reverse case (no EXIF, parseable filename) is unchanged and continues to surface the filename date with `date_taken_source = 'filename'`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
35
src/main.rs
35
src/main.rs
@@ -504,6 +504,11 @@ async fn set_image_gps(
|
|||||||
};
|
};
|
||||||
let now = Utc::now().timestamp();
|
let now = Utc::now().timestamp();
|
||||||
let normalized_path = body.path.replace('\\', "/");
|
let normalized_path = body.path.replace('\\', "/");
|
||||||
|
// Re-run the canonical-date waterfall on every GPS write — exiftool
|
||||||
|
// writing GPS doesn't change the capture date, but if the row was
|
||||||
|
// previously sourced from `fs_time` the re-read may have given us a
|
||||||
|
// real EXIF date this time, and we want to upgrade the source.
|
||||||
|
let resolved_date = date_resolver::resolve_date_taken(&full_path, extracted.date_taken);
|
||||||
let insert_exif = InsertImageExif {
|
let insert_exif = InsertImageExif {
|
||||||
library_id: resolved_library.id,
|
library_id: resolved_library.id,
|
||||||
file_path: normalized_path.clone(),
|
file_path: normalized_path.clone(),
|
||||||
@@ -520,7 +525,7 @@ async fn set_image_gps(
|
|||||||
aperture: extracted.aperture.map(|v| v as f32),
|
aperture: extracted.aperture.map(|v| v as f32),
|
||||||
shutter_speed: extracted.shutter_speed,
|
shutter_speed: extracted.shutter_speed,
|
||||||
iso: extracted.iso,
|
iso: extracted.iso,
|
||||||
date_taken: extracted.date_taken,
|
date_taken: resolved_date.map(|r| r.timestamp),
|
||||||
// Created_time is preserved by update_exif (it doesn't touch the
|
// Created_time is preserved by update_exif (it doesn't touch the
|
||||||
// column); pass any int — it's ignored in the UPDATE statement.
|
// column); pass any int — it's ignored in the UPDATE statement.
|
||||||
created_time: now,
|
created_time: now,
|
||||||
@@ -538,8 +543,7 @@ async fn set_image_gps(
|
|||||||
// with a usable signal; failure just leaves prior values in place.
|
// with a usable signal; failure just leaves prior values in place.
|
||||||
phash_64: perceptual_hash::compute(&full_path).map(|h| h.phash_64),
|
phash_64: perceptual_hash::compute(&full_path).map(|h| h.phash_64),
|
||||||
dhash_64: perceptual_hash::compute(&full_path).map(|h| h.dhash_64),
|
dhash_64: perceptual_hash::compute(&full_path).map(|h| h.dhash_64),
|
||||||
// Replaced in a follow-up commit with the canonical-date resolver's output.
|
date_taken_source: resolved_date.map(|r| r.source.as_str().to_string()),
|
||||||
date_taken_source: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let updated = {
|
let updated = {
|
||||||
@@ -752,6 +756,10 @@ async fn upload_image(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let perceptual = perceptual_hash::compute(&uploaded_path);
|
let perceptual = perceptual_hash::compute(&uploaded_path);
|
||||||
|
let resolved_date = date_resolver::resolve_date_taken(
|
||||||
|
&uploaded_path,
|
||||||
|
exif_data.date_taken,
|
||||||
|
);
|
||||||
let insert_exif = InsertImageExif {
|
let insert_exif = InsertImageExif {
|
||||||
library_id: target_library.id,
|
library_id: target_library.id,
|
||||||
file_path: relative_path.clone(),
|
file_path: relative_path.clone(),
|
||||||
@@ -768,15 +776,15 @@ async fn upload_image(
|
|||||||
aperture: exif_data.aperture.map(|v| v as f32),
|
aperture: exif_data.aperture.map(|v| v as f32),
|
||||||
shutter_speed: exif_data.shutter_speed,
|
shutter_speed: exif_data.shutter_speed,
|
||||||
iso: exif_data.iso,
|
iso: exif_data.iso,
|
||||||
date_taken: exif_data.date_taken,
|
date_taken: resolved_date.map(|r| r.timestamp),
|
||||||
created_time: timestamp,
|
created_time: timestamp,
|
||||||
last_modified: timestamp,
|
last_modified: timestamp,
|
||||||
content_hash,
|
content_hash,
|
||||||
size_bytes,
|
size_bytes,
|
||||||
phash_64: perceptual.map(|h| h.phash_64),
|
phash_64: perceptual.map(|h| h.phash_64),
|
||||||
dhash_64: perceptual.map(|h| h.dhash_64),
|
dhash_64: perceptual.map(|h| h.dhash_64),
|
||||||
// Replaced in a follow-up commit with the canonical-date resolver's output.
|
date_taken_source: resolved_date
|
||||||
date_taken_source: None,
|
.map(|r| r.source.as_str().to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(mut dao) = exif_dao.lock() {
|
if let Ok(mut dao) = exif_dao.lock() {
|
||||||
@@ -2382,6 +2390,16 @@ fn process_new_files(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Canonical date_taken via the waterfall — kamadak-exif (already
|
||||||
|
// computed above) → exiftool fallback for videos / MakerNote /
|
||||||
|
// QuickTime → filename regex → earliest_fs_time. Source is
|
||||||
|
// recorded so the per-tick backfill drain can re-run weak
|
||||||
|
// resolutions later.
|
||||||
|
let resolved_date = date_resolver::resolve_date_taken(
|
||||||
|
&file_path,
|
||||||
|
exif_fields.as_ref().and_then(|e| e.date_taken),
|
||||||
|
);
|
||||||
|
|
||||||
let insert_exif = InsertImageExif {
|
let insert_exif = InsertImageExif {
|
||||||
library_id: library.id,
|
library_id: library.id,
|
||||||
file_path: relative_path.clone(),
|
file_path: relative_path.clone(),
|
||||||
@@ -2408,15 +2426,14 @@ fn process_new_files(
|
|||||||
.and_then(|e| e.aperture.map(|v| v as f32)),
|
.and_then(|e| e.aperture.map(|v| v as f32)),
|
||||||
shutter_speed: exif_fields.as_ref().and_then(|e| e.shutter_speed.clone()),
|
shutter_speed: exif_fields.as_ref().and_then(|e| e.shutter_speed.clone()),
|
||||||
iso: exif_fields.as_ref().and_then(|e| e.iso),
|
iso: exif_fields.as_ref().and_then(|e| e.iso),
|
||||||
date_taken: exif_fields.as_ref().and_then(|e| e.date_taken),
|
date_taken: resolved_date.map(|r| r.timestamp),
|
||||||
created_time: timestamp,
|
created_time: timestamp,
|
||||||
last_modified: timestamp,
|
last_modified: timestamp,
|
||||||
content_hash,
|
content_hash,
|
||||||
size_bytes,
|
size_bytes,
|
||||||
phash_64: perceptual.map(|h| h.phash_64),
|
phash_64: perceptual.map(|h| h.phash_64),
|
||||||
dhash_64: perceptual.map(|h| h.dhash_64),
|
dhash_64: perceptual.map(|h| h.dhash_64),
|
||||||
// Replaced in a follow-up commit with the canonical-date resolver's output.
|
date_taken_source: resolved_date.map(|r| r.source.as_str().to_string()),
|
||||||
date_taken_source: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut dao = exif_dao.lock().expect("Unable to lock ExifDao");
|
let mut dao = exif_dao.lock().expect("Unable to lock ExifDao");
|
||||||
|
|||||||
Reference in New Issue
Block a user