Date a DNG whose IFDs follow its pixels: read the head and the tail
The scan reads the first 256 KB of a file for its metadata. A camera writes its IFDs at the front, so that is the whole structure; the linear DNG a merge writes puts its first IFD after the pixels, and rawler, given the head alone, finds no decoder in it. The composite was catalogued without a date and sorted to the very end of the grid, after every dated photograph — which is where a panorama merged on the tablet went unfound. dr-decode's own TIFF reader now reads through a head and a tail at a known offset; trailing_ifd says where the tail starts and metadata_split reads the two together. The scan, when the head fails and points beyond itself, fetches from the IFD to the end — kilobytes — and dates the file from both. Tested against the writer's own output.
This commit is contained in:
+34
-5
@@ -3230,7 +3230,7 @@ async fn fetch_preview(
|
||||
// The same bytes carry EXIF. Reading it here is free — the alternative is
|
||||
// a second 256 KB fetch per image over the whole library.
|
||||
if req.needs_metadata {
|
||||
collect_metadata(&header, req, found_metadata);
|
||||
collect_metadata(backend, &id, &header, req, found_metadata).await;
|
||||
}
|
||||
|
||||
// Read unconditionally, unlike the rest of the EXIF above: `needs_metadata`
|
||||
@@ -3350,9 +3350,38 @@ pub(crate) fn store_thumbnail(
|
||||
///
|
||||
/// Shared by both paths — the thumbnail fetch, which gets the header anyway,
|
||||
/// and the header-only pass for images whose pixels were already cached.
|
||||
fn collect_metadata(header: &[u8], req: &ThumbnailRequest, out: &mut Vec<MetadataFound>) {
|
||||
let Ok(md) = dr_decode::metadata(header) else {
|
||||
return;
|
||||
async fn collect_metadata(
|
||||
backend: &dyn RemoteBackend,
|
||||
id: &RemoteId,
|
||||
header: &[u8],
|
||||
req: &ThumbnailRequest,
|
||||
out: &mut Vec<MetadataFound>,
|
||||
) {
|
||||
let md = match dr_decode::metadata(header) {
|
||||
Ok(md) => md,
|
||||
Err(first) => {
|
||||
// A file whose IFDs follow its pixels — the linear DNG a merge
|
||||
// writes — has nothing for the decoder in its head but a
|
||||
// pointer. Its structure is a few kilobytes at the end; fetch
|
||||
// that and read the two ranges together, rather than leave the
|
||||
// composite undated at the end of the grid.
|
||||
let Some(at) = dr_decode::trailing_ifd(header).filter(|at| *at < req.size) else {
|
||||
return;
|
||||
};
|
||||
match backend.get(id, Some(at..req.size)).await {
|
||||
Ok(tail) => match dr_decode::metadata_split(header, &tail, at) {
|
||||
Ok(md) => md,
|
||||
Err(e) => {
|
||||
log::debug!("metadata: {}: head {first}; head and tail {e}", req.path);
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::debug!("metadata: {}: tail not fetched: {e}", req.path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
out.push(MetadataFound {
|
||||
image_id: req.image_id,
|
||||
@@ -3442,7 +3471,7 @@ async fn read_metadata_only(
|
||||
for attempt in 1..=ATTEMPTS {
|
||||
match backend.get(&id, Some(0..dr_decode::HEADER_BYTES)).await {
|
||||
Ok(header) => {
|
||||
collect_metadata(&header, req, found);
|
||||
collect_metadata(backend, &id, &header, req, found).await;
|
||||
return true;
|
||||
}
|
||||
Err(e) if e.is_transient() && attempt < ATTEMPTS => {
|
||||
|
||||
Reference in New Issue
Block a user