Files
DarkRoom/third_party/rawler-0.7.2/src/formats/jfif.rs
T
dtourolle 77a1925bac Vendor rawler 0.7.2 unmodified
The crate as crates.io publishes it, minus .cargo-ok, its Cargo.lock and
data/testdata (13 MB of sample files only its own tests read). Not yet
routed through [patch.crates-io]; the next commit is the patch.
2026-09-27 17:33:19 -04:00

323 lines
8.2 KiB
Rust

use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Read, Seek, SeekFrom};
use thiserror::Error;
use crate::formats::tiff::IFD;
use crate::rawsource::RawSource;
pub type Result<T> = std::result::Result<T, JfifError>;
/// Error variants for JFIF parser
#[derive(Debug, Error)]
pub enum JfifError {
/// Overflow of input, size constraints...
#[error("Overflow error: {}", _0)]
Overflow(String),
#[error("General error: {}", _0)]
General(String),
#[error("Format mismatch: {}", _0)]
FormatMismatch(String),
/// Error on internal cursor type
#[error("I/O error: {:?}", _0)]
Io(#[from] std::io::Error),
}
pub trait ReadSegment<T>: Sized {
fn read_segment(_: T, symbol: u16) -> Result<Self>;
}
#[derive(Debug, Clone, Default)]
pub struct App0 {
pub len: u64,
pub version: u16,
pub density_units: u8,
pub xdensity: u16,
pub ydensity: u16,
pub xthumbnail: u8,
pub ythumbnail: u8,
pub thumbnail: Option<Vec<u8>>,
}
impl<R: Read + Seek> ReadSegment<&mut R> for App0 {
fn read_segment(reader: &mut R, _symbol: u16) -> Result<Self> {
//let pos = reader.stream_position()?;
let len: u64 = reader.read_u16::<BigEndian>()? as u64;
const APP1_JFIF_MARKER: [u8; 5] = [b'J', b'F', b'I', b'F', b'\0'];
if len as usize >= APP1_JFIF_MARKER.len() {
let mut jfif_str = [0; 5];
reader.read_exact(&mut jfif_str)?;
if jfif_str == APP1_JFIF_MARKER {
let version = reader.read_u16::<BigEndian>()?;
let density_units = reader.read_u8()?;
let xdensity = reader.read_u16::<BigEndian>()?;
let ydensity = reader.read_u16::<BigEndian>()?;
let xthumbnail = reader.read_u8()?;
let ythumbnail = reader.read_u8()?;
let thumbnail = if xthumbnail * ythumbnail > 0 {
let mut data = vec![0; (3 * xthumbnail * ythumbnail) as usize];
reader.read_exact(&mut data)?;
Some(data)
} else {
None
};
Ok(Self {
len,
version,
density_units,
xdensity,
ydensity,
xthumbnail,
ythumbnail,
thumbnail,
})
} else {
Err(JfifError::FormatMismatch("Failed to find JFIF marker in APP0 segment".into()))
}
} else {
Err(JfifError::FormatMismatch("APP0 segment is too short".into()))
}
}
}
#[derive(Debug, Clone)]
pub struct App1 {
pub len: u64,
pub payload: Payload,
}
#[derive(Debug, Clone)]
pub struct SOS {
pub len: u64,
}
impl<R: Read + Seek> ReadSegment<&mut R> for SOS {
fn read_segment(reader: &mut R, _symbol: u16) -> Result<Self> {
let pos = reader.stream_position()?;
let mut prev = 0;
loop {
let v = reader.read_u8()?;
if prev == 0xFF && v == 0xFF {
prev = 0;
continue;
}
if prev == 0xFF && (v & !0x7) == 0xD0 {
prev = 0;
continue;
}
if prev == 0xFF && v == 0 {
prev = 0;
continue;
}
if prev == 0xFF {
reader.seek(SeekFrom::Current(-2))?;
break;
}
prev = v;
}
let len = reader.stream_position()? - pos;
Ok(Self { len })
}
}
#[derive(Debug, Clone)]
pub enum Payload {
Exif(IFD),
Xpacket(Vec<u8>),
Unknown,
}
impl<R: Read + Seek> ReadSegment<&mut R> for App1 {
fn read_segment(reader: &mut R, _symbol: u16) -> Result<Self> {
let pos = reader.stream_position()?;
let len: u64 = reader.read_u16::<BigEndian>()? as u64;
const APP1_EXIF_MARKER: [u8; 6] = [b'E', b'x', b'i', b'f', b'\0', b'\0'];
if len as usize >= APP1_EXIF_MARKER.len() {
let mut exif_str = [0; 6];
reader.read_exact(&mut exif_str)?;
if exif_str == APP1_EXIF_MARKER {
let ifd = IFD::new_root(reader, pos as u32 + 2 + 6);
reader.seek(SeekFrom::Start(pos + len))?;
/*
for i in ifd.dump::<TiffCommonTag>(10) {
println!("{}", i);
}
for i in ifd.get_sub_ifds(TiffCommonTag::ExifIFDPointer).unwrap()[0].dump::<ExifTag>(10) {
println!("{}", i);
}
*/
if let Ok(ifd) = ifd {
return Ok(Self {
len,
payload: Payload::Exif(ifd),
});
}
return Err(JfifError::General("Failed to read exif".into()));
} else {
reader.seek(SeekFrom::Current(-(exif_str.len() as i64)))?;
}
}
const APP1_XMP_MARKER: &str = concat!("http://ns.adobe.com/xap/1.0/", '\0');
if len as usize >= APP1_XMP_MARKER.len() {
let mut xmp_str = [0; APP1_XMP_MARKER.len()];
reader.read_exact(&mut xmp_str)?;
if xmp_str == APP1_XMP_MARKER.as_bytes() {
log::debug!("Found APP1 XMP marker");
let mut xpacket = vec![0; len as usize - xmp_str.len() - 2];
reader.read_exact(&mut xpacket)?;
reader.seek(SeekFrom::Start(pos + len))?;
return Ok(Self {
len,
payload: Payload::Xpacket(xpacket),
});
} else {
reader.seek(SeekFrom::Current(-(xmp_str.len() as i64)))?;
}
}
reader.seek(SeekFrom::Start(pos + len))?;
Ok(Self {
len,
payload: Payload::Unknown,
})
}
}
#[derive(Debug, Clone)]
pub enum Segment {
SOI { offset: u64 },
APP0 { offset: u64, app0: App0 },
APP1 { offset: u64, app1: App1 },
SOS { offset: u64, sos: SOS },
EOI,
Unknown { offset: u64, marker: u16 },
}
#[derive(Debug, Clone)]
pub struct Jfif {
pub segments: Vec<Segment>,
}
impl Jfif {
pub fn parse<R: Read + Seek>(mut reader: R) -> Result<Self> {
let mut segments = Vec::new();
let mut pos = reader.stream_position()?;
let mut sym = Some(reader.read_u16::<BigEndian>()?);
while let Some(symbol) = sym {
log::debug!("Found symbol: {:X}", symbol);
let segment = match symbol {
0xFFD8 => Segment::SOI { offset: pos },
0xFFD9 => Segment::EOI,
0xFFE0 => Segment::APP0 {
offset: pos,
app0: App0::read_segment(&mut reader, symbol)?,
},
0xFFE1 => match App1::read_segment(&mut reader, symbol) {
Ok(app1) => Segment::APP1 { offset: pos, app1 },
Err(err) => {
log::warn!("Failed to read APP1 (EXIF) segement, maybe corrupt: {:?}", err);
continue;
}
},
0xFFDA => Segment::SOS {
offset: pos,
sos: SOS::read_segment(&mut reader, symbol)?,
},
_ => {
log::debug!("Unhandled JFIF segment marker: {:X}", symbol);
let len: u64 = reader.read_u16::<BigEndian>()? as u64;
reader.seek(SeekFrom::Current(len as i64 - 2))?;
Segment::Unknown { offset: pos, marker: symbol }
}
};
if segments.is_empty() && sym != Some(0xFFD8) {
return Err(JfifError::General("first marker must be SOI".into()));
}
segments.push(segment);
if symbol == 0xFFD9 {
break; // EOI reached
}
pos = reader.stream_position()?;
sym = reader.read_u16::<BigEndian>().ok();
}
if !segments.is_empty() {
Ok(Self { segments })
} else {
Err(JfifError::FormatMismatch("JFIF contains no segements".into()))
}
}
pub fn new(file: &RawSource) -> Result<Self> {
Self::parse(&mut file.reader())
}
pub fn exif_ifd(&self) -> Option<&IFD> {
self.segments.iter().find_map(|seg| match seg {
Segment::APP1 {
app1: App1 {
payload: Payload::Exif(ifd), ..
},
..
} => Some(ifd),
_ => None,
})
}
pub fn xpacket(&self) -> Option<&Vec<u8>> {
self.segments.iter().find_map(|seg| match seg {
Segment::APP1 {
app1: App1 {
payload: Payload::Xpacket(xpacket),
..
},
..
} => Some(xpacket),
_ => None,
})
}
}
pub fn is_jfif(file: &RawSource) -> bool {
match file.subview(0, 4) {
Ok(buf) => {
let result = buf[0..4] == [0xFF, 0xD8, 0xFF, 0xE0];
if !result {
//panic!("Failed: {:x} {:x} {:x} {:x}", buf[0], buf[1], buf[2], buf[3]);
}
result
}
Err(err) => {
log::error!("is_jfif(): {:?}", err);
false
}
}
}
pub fn is_exif(file: &RawSource) -> bool {
match file.subview(0, 4) {
Ok(buf) => buf[0..4] == [0xFF, 0xD8, 0xFF, 0xE1],
Err(err) => {
log::error!("is_exif(): {:?}", err);
false
}
}
}