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:
@@ -183,22 +183,65 @@ struct Entry {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
/// The bytes a [`TiffReader`] reads: the head of a file, and optionally a
|
||||
/// second range from further in, at a known offset.
|
||||
///
|
||||
/// A camera writes its IFDs at the front, so the first 256 KB of a file
|
||||
/// is the whole structure. A file written strip by strip — the linear
|
||||
/// DNG a merge produces — has its first IFD at the *end*, after the
|
||||
/// pixels, and a reader that only has the head sees a pointer into
|
||||
/// nothing. Rather than fetch 800 MB to read a date, the caller fetches
|
||||
/// the head, asks [`crate::trailing_ifd`] where the IFD is, fetches that
|
||||
/// tail, and reads through both. Offsets are the file's own throughout;
|
||||
/// a read that falls in neither range is simply absent.
|
||||
#[derive(Clone, Copy)]
|
||||
struct Src<'a> {
|
||||
head: &'a [u8],
|
||||
tail: &'a [u8],
|
||||
/// Where `tail` starts in the file.
|
||||
tail_at: usize,
|
||||
}
|
||||
|
||||
impl<'a> Src<'a> {
|
||||
fn whole(data: &'a [u8]) -> Self {
|
||||
Src {
|
||||
head: data,
|
||||
tail: &[],
|
||||
tail_at: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, start: usize, len: usize) -> Option<&'a [u8]> {
|
||||
let end = start.checked_add(len)?;
|
||||
if let Some(b) = self.head.get(start..end) {
|
||||
return Some(b);
|
||||
}
|
||||
let s = start.checked_sub(self.tail_at)?;
|
||||
self.tail.get(s..s.checked_add(len)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// A minimal TIFF structure reader.
|
||||
///
|
||||
/// Deliberately not a general TIFF parser: it reads the IFD chain and entry
|
||||
/// values and nothing else, because that is all locating a preview needs.
|
||||
struct TiffReader<'a> {
|
||||
data: &'a [u8],
|
||||
data: Src<'a>,
|
||||
little_endian: bool,
|
||||
first_ifd: u32,
|
||||
}
|
||||
|
||||
impl<'a> TiffReader<'a> {
|
||||
fn new(data: &'a [u8]) -> Option<Self> {
|
||||
if data.len() < 8 {
|
||||
Self::over(Src::whole(data))
|
||||
}
|
||||
|
||||
fn over(data: Src<'a>) -> Option<Self> {
|
||||
let head = data.head;
|
||||
if head.len() < 8 {
|
||||
return None;
|
||||
}
|
||||
let little_endian = match &data[0..2] {
|
||||
let little_endian = match &head[0..2] {
|
||||
b"II" => true,
|
||||
b"MM" => false,
|
||||
_ => return None,
|
||||
@@ -362,9 +405,7 @@ impl<'a> TiffReader<'a> {
|
||||
};
|
||||
raw[..len.min(4)].to_vec()
|
||||
} else {
|
||||
self.data
|
||||
.get(e.value as usize..e.value as usize + len)?
|
||||
.to_vec()
|
||||
self.data.get(e.value as usize, len)?.to_vec()
|
||||
};
|
||||
|
||||
let s = String::from_utf8_lossy(&bytes);
|
||||
@@ -396,8 +437,7 @@ impl<'a> TiffReader<'a> {
|
||||
// same way to recover the original byte order.
|
||||
return None;
|
||||
}
|
||||
let start = e.value as usize;
|
||||
self.data.get(start..start.checked_add(len)?)
|
||||
self.data.get(e.value as usize, len)
|
||||
}
|
||||
|
||||
fn offsets(&self, e: &Entry) -> Vec<u32> {
|
||||
@@ -420,8 +460,8 @@ impl<'a> TiffReader<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u16(data: &[u8], at: usize, le: bool) -> Option<u16> {
|
||||
let b = data.get(at..at + 2)?;
|
||||
fn read_u16(data: Src<'_>, at: usize, le: bool) -> Option<u16> {
|
||||
let b = data.get(at, 2)?;
|
||||
Some(if le {
|
||||
u16::from_le_bytes([b[0], b[1]])
|
||||
} else {
|
||||
@@ -429,8 +469,8 @@ fn read_u16(data: &[u8], at: usize, le: bool) -> Option<u16> {
|
||||
})
|
||||
}
|
||||
|
||||
fn read_u32(data: &[u8], at: usize, le: bool) -> Option<u32> {
|
||||
let b = data.get(at..at + 4)?;
|
||||
fn read_u32(data: Src<'_>, at: usize, le: bool) -> Option<u32> {
|
||||
let b = data.get(at, 4)?;
|
||||
Some(if le {
|
||||
u32::from_le_bytes([b[0], b[1], b[2], b[3]])
|
||||
} else {
|
||||
@@ -460,7 +500,36 @@ pub fn jpeg_metadata(bytes: &[u8]) -> Result<crate::Metadata, crate::DecodeError
|
||||
/// no `DateTimeOriginal` for some DNGs whose tag sits plainly at byte 826 —
|
||||
/// and without this fallback those images are silently undated.
|
||||
pub fn tiff_metadata(tiff_data: &[u8]) -> Result<crate::Metadata, crate::DecodeError> {
|
||||
let reader = TiffReader::new(tiff_data)
|
||||
tiff_metadata_over(Src::whole(tiff_data))
|
||||
}
|
||||
|
||||
/// Where a TIFF-shaped file's first IFD is, when the head does not reach
|
||||
/// it: the offset to fetch from, so [`tiff_metadata_split`] can read it.
|
||||
/// `None` for a file that is not TIFF, or whose IFD the head already holds.
|
||||
pub fn trailing_ifd(head: &[u8]) -> Option<u64> {
|
||||
let r = TiffReader::new(head)?;
|
||||
let at = r.first_ifd as u64;
|
||||
(at >= head.len() as u64).then_some(at)
|
||||
}
|
||||
|
||||
/// [`tiff_metadata`] over a head and a tail fetched separately: the head
|
||||
/// from offset 0, the tail from `tail_at`. For the file whose IFDs follow
|
||||
/// its pixels.
|
||||
pub fn tiff_metadata_split(
|
||||
head: &[u8],
|
||||
tail: &[u8],
|
||||
tail_at: u64,
|
||||
) -> Result<crate::Metadata, crate::DecodeError> {
|
||||
tiff_metadata_over(Src {
|
||||
head,
|
||||
tail,
|
||||
tail_at: usize::try_from(tail_at)
|
||||
.map_err(|_| crate::DecodeError::Metadata("tail offset out of range".into()))?,
|
||||
})
|
||||
}
|
||||
|
||||
fn tiff_metadata_over(src: Src<'_>) -> Result<crate::Metadata, crate::DecodeError> {
|
||||
let reader = TiffReader::over(src)
|
||||
.ok_or_else(|| crate::DecodeError::Metadata("malformed EXIF header".into()))?;
|
||||
|
||||
let mut md = crate::Metadata::default();
|
||||
|
||||
Reference in New Issue
Block a user