Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.target/
target/
Cargo.lock
.idea/
198 changes: 185 additions & 13 deletions src/isobmff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use crate::util::{read64, BufReadExt as _, ReadExt as _};
// Same for "msf1" [ISO23008-12 B.4.2] [ISO23008-12 B.4.4].
static HEIF_BRANDS: &[[u8; 4]] = &[*b"mif1", *b"msf1"];

const MAX_EXIF_SIZE: usize = 65535;
pub(crate) const MAX_EXIF_SIZE: usize = 65535;

// Most errors in this file are Error::InvalidFormat.
impl From<&'static str> for Error {
Expand Down Expand Up @@ -343,25 +343,25 @@ pub fn is_heif(buf: &[u8]) -> bool {
false
}

struct BoxSplitter<'a> {
pub(crate) struct BoxSplitter<'a> {
inner: &'a [u8],
}

impl<'a> BoxSplitter<'a> {
fn new(slice: &'a [u8]) -> BoxSplitter<'a> {
pub(crate) fn new(slice: &'a [u8]) -> BoxSplitter<'a> {
Self { inner: slice }
}

fn is_empty(&self) -> bool {
pub(crate) fn is_empty(&self) -> bool {
self.inner.is_empty()
}

fn len(&self) -> usize {
pub(crate) fn len(&self) -> usize {
self.inner.len()
}

// Returns type and body.
fn child_box(&mut self) -> Result<(&'a [u8], BoxSplitter<'a>), Error> {
pub(crate) fn child_box(&mut self) -> Result<(&'a [u8], BoxSplitter<'a>), Error> {
let size = self.uint32()? as usize;
let boxtype = self.slice(4)?;
let body_len = match size {
Expand All @@ -376,7 +376,7 @@ impl<'a> BoxSplitter<'a> {
}

// Returns 0-, 4-, or 8-byte unsigned integer.
fn size048(&mut self, size: usize) -> Result<Option<u64>, Error> {
pub(crate) fn size048(&mut self, size: usize) -> Result<Option<u64>, Error> {
match size {
0 => Ok(Some(0)),
4 => self.uint32().map(u64::from).map(Some),
Expand All @@ -386,34 +386,206 @@ impl<'a> BoxSplitter<'a> {
}

// Returns version and flags.
fn fullbox_header(&mut self) -> Result<(u32, u32), Error> {
pub(crate) fn fullbox_header(&mut self) -> Result<(u32, u32), Error> {
let tmp = self.uint32()?;
Ok((tmp >> 24, tmp & 0xffffff))
}

fn uint16(&mut self) -> Result<u16, Error> {
pub(crate) fn uint16(&mut self) -> Result<u16, Error> {
self.slice(2).map(|num| BigEndian::loadu16(num, 0))
}

fn uint32(&mut self) -> Result<u32, Error> {
pub(crate) fn uint32(&mut self) -> Result<u32, Error> {
self.slice(4).map(|num| BigEndian::loadu32(num, 0))
}

fn uint64(&mut self) -> Result<u64, Error> {
pub(crate) fn uint64(&mut self) -> Result<u64, Error> {
self.slice(8).map(|num| BigEndian::loadu64(num, 0))
}

fn array4(&mut self) -> Result<[u8; 4], Error> {
pub(crate) fn array4(&mut self) -> Result<[u8; 4], Error> {
self.slice(4).map(|x| x.try_into().expect("never fails"))
}

fn slice(&mut self, at: usize) -> Result<&'a [u8], Error> {
pub(crate) fn slice(&mut self, at: usize) -> Result<&'a [u8], Error> {
let slice = self.inner.get(..at).ok_or("Box too small")?;
self.inner = &self.inner[at..];
Ok(slice)
}
}

pub mod crx {
use std::io::{BufRead, ErrorKind, Seek, SeekFrom};

use super::BoxSplitter;

use crate::endian::{BigEndian, Endian};
use crate::error::Error;
use crate::util::{read64, BufReadExt as _, ReadExt as _};

// Canon CR3 uses isobmff container format
static CANON_FORMATS: &[[u8; 4]] = &[*b"crx "];
static CANON_UUID:&[u8; 16] = &[0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0, 0x81, 0x11, 0xf4, 0xce, 0x46, 0x2b, 0x6a, 0x48];

#[allow(unused)]
pub fn get_exif_attr<R>(reader: &mut R) -> Result<Vec<u8>, Error>
where
R: BufRead + Seek,
{
Ok(get_exif_attr_vec(reader)?.first().cloned().unwrap_or_else(|| Vec::new()))
}

pub fn get_exif_attr_vec<R>(reader: &mut R) -> Result<Vec<Vec<u8>>, Error>
where
R: BufRead + Seek,
{
let mut parser = Parser::new(reader);
match parser.parse() {
Err(Error::Io(ref e)) if e.kind() == ErrorKind::UnexpectedEof => Err("Broken CR3 file".into()),
Err(e) => Err(e),
// We can only parse the first segment
Ok(buf) => Ok(buf)
}
}

#[derive(Debug)]
struct Parser<R> {
reader: R,
// Whether the file type box has been checked.
ftyp_checked: bool,
}

impl<R> Parser<R>
where
R: BufRead + Seek
{
fn new(reader: R) -> Self { Self { reader, ftyp_checked: false } }

/// Extracts the Exif attributes from raw Exif data for CR3 the result are 4 non-contiguous buffer segments
/// If an error occurred, `exif::Error` is returned.
fn parse(&mut self) -> Result<Vec<Vec<u8>>, Error> {
while let Some((size, boxtype)) = self.read_box_header()? {
match &boxtype {
b"ftyp" => {
let buf = self.read_file_level_box(size)?;
self.parse_ftyp(BoxSplitter::new(&buf))?;
self.ftyp_checked = true;
}
b"moov" => {
if !self.ftyp_checked {
return Err("moov found before FileTypeBox".into());
}
let buf = self.read_file_level_box(size)?;
let mut out_buf = Vec::new();
self.parse_moov(&mut out_buf, BoxSplitter::new(&buf))?;
if !out_buf.is_empty() {
return Ok(out_buf);
}
}
_ => {
self.skip_file_level_box(size)?;
}
}
}
Err(Error::NotFound("CR3"))
}

fn parse_moov(&mut self, out_data: &mut Vec<Vec<u8>>, mut split: BoxSplitter) -> Result<(), Error> {
//let indent = indent + 1;
while let Ok((boxtype, mut boxbody)) = split.child_box() {
let size = boxbody.len();
match boxtype {
b"uuid" => {
if boxbody.slice(16)? == CANON_UUID {
self.parse_moov(out_data, boxbody)?;
}
}
b"CMT1" | b"CMT2" | b"CMT3" | b"CMT4" => {
out_data.push(Vec::from(boxbody.slice(size)?));
}
_ => {
boxbody.slice(size)?;
}
}
}
Ok(())
}

// Reads size, type, and largesize,
// and returns body size and type.
// If no byte can be read due to EOF, None is returned.
fn read_box_header(&mut self) -> Result<Option<(u64, [u8; 4])>, Error> {
if self.reader.is_eof()? {
return Ok(None);
}
let mut buf = [0; 8];
self.reader.read_exact(&mut buf)?;
let size = match BigEndian::loadu32(&buf, 0) {
0 => Some(std::u64::MAX),
1 => read64(&mut self.reader)?.checked_sub(16),
x => u64::from(x).checked_sub(8),
}
.ok_or("Invalid box size")?;
let boxtype = buf[4..8].try_into().expect("never fails");
Ok(Some((size, boxtype)))
}

fn read_file_level_box(&mut self, size: u64) -> Result<Vec<u8>, Error> {
let mut buf;
match size {
std::u64::MAX => {
buf = Vec::new();
self.reader.read_to_end(&mut buf)?;
}
_ => {
let size = size.try_into().or(Err("Box is larger than the address space"))?;
buf = Vec::new();
self.reader.read_exact_len(&mut buf, size)?;
}
}
Ok(buf)
}

fn skip_file_level_box(&mut self, size: u64) -> Result<(), Error> {
match size {
std::u64::MAX => self.reader.seek(SeekFrom::End(0))?,
_ => self.reader.seek(SeekFrom::Current(size.try_into().or(Err("Large seek not supported"))?))?,
};
Ok(())
}

fn parse_ftyp(&mut self, mut boxp: BoxSplitter) -> Result<(), Error> {
let head = boxp.slice(8)?;
let _major_brand = &head[0..4];
let _minor_version = BigEndian::loadu32(&head, 4);
while let Ok(compat_brand) = boxp.array4() {
if CANON_FORMATS.contains(&compat_brand) {
return Ok(());
}
}
Err("No compatible brand recognized in ISO base media file".into())
}
}

pub fn is_crx(buf: &[u8]) -> bool {
let mut boxp = BoxSplitter::new(buf);
while let Ok((boxtype, mut body)) = boxp.child_box() {
if boxtype == b"ftyp" {
let _major_brand_minor_version = if body.slice(8).is_err() {
return false;
};
while let Ok(compat_brand) = body.array4() {
if CANON_FORMATS.contains(&compat_brand) {
return true;
}
}
return false;
}
}
false
}
}

#[cfg(test)]
mod tests {
use std::io::Cursor;
Expand Down
36 changes: 35 additions & 1 deletion src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,34 @@ impl Reader {
})
}

/// Parses the Exif attributes from raw Exif data.
/// If an error occurred, `exif::Error` is returned.
pub fn read_raw_vec(&self, buffers: Vec<Vec<u8>>) -> Result<Exif, Error> {
let mut data = Vec::new();
let mut parser = tiff::Parser::new();
// Join all buffers together
for buffer in &buffers {
data.extend_from_slice(buffer);
}
let mut offset = 0;
for (idx, buffer) in buffers.iter().enumerate() {
let default_context = match idx {
0 => crate::tag::Context::Tiff,
_ => crate::tag::Context::Exif,
};
parser.parse_with_context_offset(&data[offset..offset + buffer.len()], default_context, offset as u32)?;
offset += buffer.len();
}
let entry_map = parser.entries.iter().enumerate()
.map(|(i, e)| (e.ifd_num_tag(), i)).collect();
Ok(Exif {
buf: data,
entries: parser.entries,
entry_map: entry_map,
little_endian: parser.little_endian,
})
}

/// Reads an image file and parses the Exif attributes in it.
/// If an error occurred, `exif::Error` is returned.
///
Expand All @@ -96,7 +124,9 @@ impl Reader {
/// This method is provided for the convenience even though
/// parsing containers is basically out of the scope of this library.
pub fn read_from_container<R>(&self, reader: &mut R) -> Result<Exif, Error>
where R: io::BufRead + io::Seek {
where
R: io::BufRead + io::Seek,
{
let mut buf = Vec::new();
reader.by_ref().take(4096).read_to_end(&mut buf)?;
if tiff::is_tiff(&buf) {
Expand All @@ -108,6 +138,10 @@ impl Reader {
} else if isobmff::is_heif(&buf) {
reader.seek(io::SeekFrom::Start(0))?;
buf = isobmff::get_exif_attr(reader)?;
} else if isobmff::crx::is_crx(&buf) {
reader.seek(io::SeekFrom::Start(0))?;
let buf_vec = isobmff::crx::get_exif_attr_vec(reader)?;
return self.read_raw_vec(buf_vec);
} else if webp::is_webp(&buf) {
buf = webp::get_exif_attr(&mut buf.chain(reader))?;
} else {
Expand Down
Loading