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.
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
use super::{BoxHeader, FourCC, ReadBox, Result, read_box_header_ext};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct Co64Box {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entries: Vec<u64>,
|
||||
}
|
||||
|
||||
impl Co64Box {
|
||||
pub const TYP: FourCC = FourCC::with(['c', 'o', '6', '4']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Co64Box {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
for _i in 0..entry_count {
|
||||
let chunk_offset = reader.read_u64::<BigEndian>()?;
|
||||
entries.push(chunk_offset);
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
version,
|
||||
flags,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct DinfBox {
|
||||
pub header: BoxHeader,
|
||||
}
|
||||
|
||||
impl DinfBox {
|
||||
pub const TYP: FourCC = FourCC::with(['d', 'i', 'n', 'f']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for DinfBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self { header })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct CcdtBox {
|
||||
pub header: BoxHeader,
|
||||
pub image_type: u64,
|
||||
pub dual_pixel: u32,
|
||||
pub trak_index: u32,
|
||||
}
|
||||
|
||||
impl CcdtBox {
|
||||
pub const TYP: FourCC = FourCC::with(['C', 'C', 'D', 'T']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for CcdtBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let image_type = reader.read_u64::<BigEndian>()?;
|
||||
let dual_pixel = reader.read_u32::<BigEndian>()?;
|
||||
let trak_index = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
image_type,
|
||||
dual_pixel,
|
||||
trak_index,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
use super::{
|
||||
super::{BmffError, BoxHeader, FourCC, ReadBox, Result, read_box_header_ext},
|
||||
ccdt::CcdtBox,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct CctpBox {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub unknown1: u32,
|
||||
pub lines: u32,
|
||||
pub ccdts: Vec<CcdtBox>,
|
||||
}
|
||||
|
||||
impl CctpBox {
|
||||
pub const TYP: FourCC = FourCC::with(['C', 'C', 'T', 'P']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for CctpBox {
|
||||
fn read_box(mut reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
let unknown1 = reader.read_u32::<BigEndian>()?;
|
||||
let lines = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
let mut ccdts = Vec::new();
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
|
||||
while current < header.end_offset() {
|
||||
// get box?
|
||||
|
||||
let header = BoxHeader::parse(&mut reader)?;
|
||||
|
||||
match header.typ {
|
||||
CcdtBox::TYP => {
|
||||
let ccdt = CcdtBox::read_box(&mut reader, header)?;
|
||||
ccdts.push(ccdt);
|
||||
}
|
||||
|
||||
_ => {
|
||||
debug!("Vendor box found in CCTP: {:?}", header.typ);
|
||||
return Err(BmffError::Parse(format!("Invalid box found in CCTP: {:?}", header.typ)));
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
version,
|
||||
flags,
|
||||
unknown1,
|
||||
lines,
|
||||
ccdts,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{
|
||||
super::{BmffError, BoxHeader, FourCC, ReadBox, Result, read_box_header_ext},
|
||||
iad1::Iad1Box,
|
||||
};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Cdi1Box {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub iad1: Iad1Box,
|
||||
}
|
||||
|
||||
impl Cdi1Box {
|
||||
pub const TYP: FourCC = FourCC::with(['C', 'D', 'I', '1']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Cdi1Box {
|
||||
fn read_box(mut reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let mut iad1 = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
|
||||
while current < header.end_offset() {
|
||||
// get box?
|
||||
|
||||
let header = BoxHeader::parse(&mut reader)?;
|
||||
|
||||
match header.typ {
|
||||
Iad1Box::TYP => {
|
||||
iad1 = Some(Iad1Box::read_box(&mut reader, header)?);
|
||||
}
|
||||
|
||||
_ => {
|
||||
debug!("Vendor box found in CDI1: {:?}", header.typ);
|
||||
return Err(BmffError::Parse(format!("Invalid box found in CDI1: {:?}", header.typ)));
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
version,
|
||||
flags,
|
||||
iad1: iad1.ok_or_else(|| BmffError::Parse("IAD1 box not found, corrupt file?".into()))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Cmp1Box {
|
||||
pub header: BoxHeader,
|
||||
pub unknown1: i16,
|
||||
pub header_size: u16,
|
||||
pub version: u16,
|
||||
pub version_sub: u16,
|
||||
pub f_width: u32,
|
||||
pub f_height: u32,
|
||||
pub tile_width: u32,
|
||||
pub tile_height: u32,
|
||||
pub n_bits: u8,
|
||||
pub n_planes: u8,
|
||||
pub cfa_layout: u8,
|
||||
pub enc_type: u8,
|
||||
pub image_levels: u8,
|
||||
pub has_tile_cols: u8,
|
||||
pub has_tile_rows: u8,
|
||||
pub mdat_hdr_size: u32,
|
||||
pub ext_header: u32,
|
||||
pub median_bits: u8,
|
||||
// TODO: missing fields
|
||||
}
|
||||
|
||||
impl Cmp1Box {
|
||||
pub const TYP: FourCC = FourCC::with(['C', 'M', 'P', '1']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Cmp1Box {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let unknown1 = reader.read_i16::<BigEndian>()?;
|
||||
let header_size = reader.read_u16::<BigEndian>()?;
|
||||
let version = reader.read_u16::<BigEndian>()?;
|
||||
let version_sub = reader.read_u16::<BigEndian>()?;
|
||||
let f_width = reader.read_u32::<BigEndian>()?;
|
||||
let f_height = reader.read_u32::<BigEndian>()?;
|
||||
let tile_width = reader.read_u32::<BigEndian>()?;
|
||||
let tile_height = reader.read_u32::<BigEndian>()?;
|
||||
let n_bits = reader.read_u8()?;
|
||||
|
||||
let (n_planes, cfa_layout) = {
|
||||
let tmp = reader.read_u8()?;
|
||||
(tmp >> 4, tmp & 0xF)
|
||||
};
|
||||
|
||||
let (enc_type, image_levels) = {
|
||||
let tmp = reader.read_u8()?;
|
||||
(tmp >> 4, tmp & 0xF)
|
||||
};
|
||||
|
||||
let (has_tile_cols, has_tile_rows) = {
|
||||
let tmp = reader.read_u8()?;
|
||||
(tmp >> 7, tmp & 1)
|
||||
};
|
||||
|
||||
let mdat_hdr_size = reader.read_u32::<BigEndian>()?;
|
||||
let ext_header = reader.read_u32::<BigEndian>()?; // 32
|
||||
|
||||
// Median bit precision for enc_type 3 is not always same as
|
||||
// image bit precision. For CRM movie files, there is an extended header.
|
||||
let mut median_bits = n_bits;
|
||||
|
||||
// CRM Movie file
|
||||
if (ext_header & 0x80000000 != 0) && n_planes == 4 && header.size >= 56 {
|
||||
let _unknow = reader.read_u32::<BigEndian>()?; // 36
|
||||
let _unknow = reader.read_u32::<BigEndian>()?; // 40
|
||||
let _unknow = reader.read_u32::<BigEndian>()?; // 44
|
||||
let _unknow = reader.read_u32::<BigEndian>()?; // 48
|
||||
let _unknow = reader.read_u32::<BigEndian>()?; // 52
|
||||
let use_median_bits = reader.read_u32::<BigEndian>()? & 0x40000000 != 0; // 56
|
||||
if use_median_bits && header.size > 84 {
|
||||
let _unknow = reader.read_u32::<BigEndian>()?; // 60
|
||||
let _unknow = reader.read_u32::<BigEndian>()?; // 64
|
||||
let _unknow = reader.read_u32::<BigEndian>()?; // 68
|
||||
let _unknow = reader.read_u32::<BigEndian>()?; // 72
|
||||
let _unknow = reader.read_u32::<BigEndian>()?; // 76
|
||||
let _unknow = reader.read_u32::<BigEndian>()?; // 80
|
||||
median_bits = reader.read_u8()?; // 84
|
||||
}
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
unknown1,
|
||||
header_size,
|
||||
version,
|
||||
version_sub,
|
||||
f_width,
|
||||
f_height,
|
||||
tile_width,
|
||||
tile_height,
|
||||
n_bits,
|
||||
n_planes,
|
||||
cfa_layout,
|
||||
enc_type,
|
||||
image_levels,
|
||||
has_tile_cols,
|
||||
has_tile_rows,
|
||||
mdat_hdr_size,
|
||||
ext_header,
|
||||
median_bits,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use crate::{formats::bmff::BmffError, formats::tiff::GenericTiffReader};
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Cmt1Box {
|
||||
pub header: BoxHeader,
|
||||
pub data: Vec<u8>,
|
||||
pub tiff: GenericTiffReader,
|
||||
}
|
||||
|
||||
impl Cmt1Box {
|
||||
pub const TYP: FourCC = FourCC::with(['C', 'M', 'T', '1']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Cmt1Box {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let current = reader.stream_position()?;
|
||||
let data_len = header.end_offset() - current;
|
||||
let mut data = vec![0; data_len as usize];
|
||||
reader.read_exact(&mut data)?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
let tiff = GenericTiffReader::new_with_buffer(&data, 0, 0, None).map_err(|e| BmffError::Parse(e.to_string()))?;
|
||||
|
||||
Ok(Self { header, data, tiff })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use crate::{formats::bmff::BmffError, formats::tiff::GenericTiffReader};
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Cmt2Box {
|
||||
pub header: BoxHeader,
|
||||
pub data: Vec<u8>,
|
||||
pub tiff: GenericTiffReader,
|
||||
}
|
||||
|
||||
impl Cmt2Box {
|
||||
pub const TYP: FourCC = FourCC::with(['C', 'M', 'T', '2']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Cmt2Box {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let current = reader.stream_position()?;
|
||||
let data_len = header.end_offset() - current;
|
||||
let mut data = vec![0; data_len as usize];
|
||||
reader.read_exact(&mut data)?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
let tiff = GenericTiffReader::new_with_buffer(&data, 0, 0, None).map_err(|e| BmffError::Parse(e.to_string()))?;
|
||||
|
||||
Ok(Self { header, data, tiff })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use crate::{formats::bmff::BmffError, formats::tiff::GenericTiffReader};
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Cmt3Box {
|
||||
pub header: BoxHeader,
|
||||
pub data: Vec<u8>,
|
||||
pub tiff: GenericTiffReader,
|
||||
}
|
||||
|
||||
impl Cmt3Box {
|
||||
pub const TYP: FourCC = FourCC::with(['C', 'M', 'T', '3']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Cmt3Box {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let current = reader.stream_position()?;
|
||||
let data_len = header.end_offset() - current;
|
||||
let mut data = vec![0; data_len as usize];
|
||||
reader.read_exact(&mut data)?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
let tiff = GenericTiffReader::new_with_buffer(&data, 0, 0, None).map_err(|e| BmffError::Parse(e.to_string()))?;
|
||||
|
||||
Ok(Self { header, data, tiff })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use crate::{formats::bmff::BmffError, formats::tiff::GenericTiffReader};
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Cmt4Box {
|
||||
pub header: BoxHeader,
|
||||
pub data: Vec<u8>,
|
||||
pub tiff: GenericTiffReader,
|
||||
}
|
||||
|
||||
impl Cmt4Box {
|
||||
pub const TYP: FourCC = FourCC::with(['C', 'M', 'T', '4']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Cmt4Box {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let current = reader.stream_position()?;
|
||||
let data_len = header.end_offset() - current;
|
||||
let mut data = vec![0; data_len as usize];
|
||||
reader.read_exact(&mut data)?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
let tiff = GenericTiffReader::new_with_buffer(&data, 0, 0, None).map_err(|e| BmffError::Parse(e.to_string()))?;
|
||||
|
||||
Ok(Self { header, data, tiff })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct CncvBox {
|
||||
pub header: BoxHeader,
|
||||
pub compressor: String,
|
||||
}
|
||||
|
||||
impl CncvBox {
|
||||
pub const TYP: FourCC = FourCC::with(['C', 'N', 'C', 'V']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for CncvBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let mut buf = [0_u8; 30];
|
||||
|
||||
reader.read_exact(&mut buf)?;
|
||||
|
||||
let compressor = String::from_utf8_lossy(&buf);
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
compressor: compressor.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CnopBox {
|
||||
pub header: BoxHeader,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl CnopBox {
|
||||
pub const TYP: FourCC = FourCC::with(['C', 'N', 'O', 'P']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for CnopBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let current = reader.stream_position()?;
|
||||
let data_len = header.end_offset() - current;
|
||||
let mut data = vec![0; data_len as usize];
|
||||
reader.read_exact(&mut data)?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self { header, data })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{
|
||||
super::{BmffError, BoxHeader, ReadBox, Result},
|
||||
cctp::CctpBox,
|
||||
cmt1::Cmt1Box,
|
||||
cmt2::Cmt2Box,
|
||||
cmt3::Cmt3Box,
|
||||
cmt4::Cmt4Box,
|
||||
cncv::CncvBox,
|
||||
cnop::CnopBox,
|
||||
ctbo::CtboBox,
|
||||
thmb::ThmbBox,
|
||||
};
|
||||
use crate::formats::bmff::{free::FreeBox, skip::SkipBox};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Cr3DescBox {
|
||||
pub header: BoxHeader,
|
||||
pub cncv: CncvBox,
|
||||
pub cctp: CctpBox,
|
||||
pub ctbo: CtboBox,
|
||||
pub cmt1: Cmt1Box,
|
||||
pub cmt2: Cmt2Box,
|
||||
pub cmt3: Cmt3Box,
|
||||
pub cmt4: Cmt4Box,
|
||||
pub thmb: ThmbBox,
|
||||
}
|
||||
|
||||
impl Cr3DescBox {
|
||||
//pub const TYP: FourCC = FourCC::with(['u', 'u', 'i', 'd']);
|
||||
pub const UUID: [u8; 16] = [0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0, 0x81, 0x11, 0xf4, 0xce, 0x46, 0x2b, 0x6a, 0x48];
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Cr3DescBox {
|
||||
fn read_box(mut reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let mut cncv = None;
|
||||
let mut cctp = None;
|
||||
let mut ctbo = None;
|
||||
let mut cmt1 = None;
|
||||
let mut cmt2 = None;
|
||||
let mut cmt3 = None;
|
||||
let mut cmt4 = None;
|
||||
let mut thmb = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
|
||||
while current < header.end_offset() {
|
||||
// get box?
|
||||
|
||||
let header = BoxHeader::parse(&mut reader)?;
|
||||
|
||||
match header.typ {
|
||||
CncvBox::TYP => {
|
||||
cncv = Some(CncvBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
CctpBox::TYP => {
|
||||
cctp = Some(CctpBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
CtboBox::TYP => {
|
||||
ctbo = Some(CtboBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
Cmt1Box::TYP => {
|
||||
cmt1 = Some(Cmt1Box::read_box(&mut reader, header)?);
|
||||
}
|
||||
Cmt2Box::TYP => {
|
||||
cmt2 = Some(Cmt2Box::read_box(&mut reader, header)?);
|
||||
}
|
||||
Cmt3Box::TYP => {
|
||||
cmt3 = Some(Cmt3Box::read_box(&mut reader, header)?);
|
||||
}
|
||||
Cmt4Box::TYP => {
|
||||
cmt4 = Some(Cmt4Box::read_box(&mut reader, header)?);
|
||||
}
|
||||
CnopBox::TYP => {
|
||||
let _ignore = Some(CnopBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
ThmbBox::TYP => {
|
||||
thmb = Some(ThmbBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
SkipBox::TYP => {
|
||||
let _ignore = SkipBox::read_box(&mut reader, header)?;
|
||||
}
|
||||
FreeBox::TYP => {
|
||||
let _ignore = FreeBox::read_box(&mut reader, header)?;
|
||||
}
|
||||
|
||||
_ => return Err(BmffError::Parse(format!("Unknown box in Cr3desc: {}", header.typ))),
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
cncv: cncv.ok_or_else(|| BmffError::Parse("cncv box not found, corrupt file?".into()))?,
|
||||
cctp: cctp.ok_or_else(|| BmffError::Parse("cctp box not found, corrupt file?".into()))?,
|
||||
ctbo: ctbo.ok_or_else(|| BmffError::Parse("ctbo box not found, corrupt file?".into()))?,
|
||||
cmt1: cmt1.ok_or_else(|| BmffError::Parse("cmt1 box not found, corrupt file?".into()))?,
|
||||
cmt2: cmt2.ok_or_else(|| BmffError::Parse("cmt2 box not found, corrupt file?".into()))?,
|
||||
cmt3: cmt3.ok_or_else(|| BmffError::Parse("cmt3 box not found, corrupt file?".into()))?,
|
||||
cmt4: cmt4.ok_or_else(|| BmffError::Parse("cmt4 box not found, corrupt file?".into()))?,
|
||||
thmb: thmb.ok_or_else(|| BmffError::Parse("thmb box not found, corrupt file?".into()))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::super::{BoxHeader, ReadBox, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct Cr3XpacketBox {
|
||||
pub header: BoxHeader,
|
||||
}
|
||||
|
||||
impl Cr3XpacketBox {
|
||||
//pub const TYP: FourCC = FourCC::with(['u', 'u', 'i', 'd']);
|
||||
pub const UUID: [u8; 16] = [0xbe, 0x7a, 0xcf, 0xcb, 0x97, 0xa9, 0x42, 0xe8, 0x9c, 0x71, 0x99, 0x94, 0x91, 0xe3, 0xaf, 0xac];
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Cr3XpacketBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self { header })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{
|
||||
super::{BmffError, BoxHeader, FourCC, ReadBox, Result},
|
||||
cdi1::Cdi1Box,
|
||||
cmp1::Cmp1Box,
|
||||
hevc::HevcBox,
|
||||
jpeg::JpegBox,
|
||||
};
|
||||
use crate::formats::bmff::free::FreeBox;
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct CrawBox {
|
||||
pub header: BoxHeader,
|
||||
//pub version: u8,
|
||||
//pub flags: u32,
|
||||
pub reserved: [u8; 6],
|
||||
pub data_ref_index: u16,
|
||||
pub unknown1: [u8; 16],
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub x_resolution: [u16; 2],
|
||||
pub y_resolution: [u16; 2],
|
||||
pub unknown2: u32,
|
||||
pub unknown3: u16,
|
||||
pub compr_name: [u8; 32],
|
||||
pub bit_depth: u16,
|
||||
pub unknown4: u16,
|
||||
pub img_flags: u16,
|
||||
pub img_type: u16,
|
||||
|
||||
pub jpeg: Option<JpegBox>,
|
||||
pub hevc: Option<HevcBox>,
|
||||
pub cmp1: Option<Cmp1Box>,
|
||||
pub cdi1: Option<Cdi1Box>,
|
||||
}
|
||||
|
||||
impl CrawBox {
|
||||
pub const TYP: FourCC = FourCC::with(['C', 'R', 'A', 'W']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for CrawBox {
|
||||
fn read_box(mut reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
//let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let mut reserved = [0_u8; 6];
|
||||
reader.read_exact(&mut reserved)?;
|
||||
let data_ref_index = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
let mut unknown1 = [0; 16];
|
||||
reader.read_exact(&mut unknown1)?;
|
||||
|
||||
//let _sample_entry = reader.read_u64::<BigEndian>()?;
|
||||
|
||||
let width = reader.read_u16::<BigEndian>()?;
|
||||
let height = reader.read_u16::<BigEndian>()?;
|
||||
let x_resolution = [reader.read_u16::<BigEndian>()?, reader.read_u16::<BigEndian>()?];
|
||||
let y_resolution = [reader.read_u16::<BigEndian>()?, reader.read_u16::<BigEndian>()?];
|
||||
let unknown2 = reader.read_u32::<BigEndian>()?;
|
||||
let unknown3 = reader.read_u16::<BigEndian>()?;
|
||||
let mut compr_name = [0_u8; 32];
|
||||
reader.read_exact(&mut compr_name)?;
|
||||
let bit_depth = reader.read_u16::<BigEndian>()?;
|
||||
let unknown4 = reader.read_u16::<BigEndian>()?;
|
||||
let img_flags = reader.read_u16::<BigEndian>()?;
|
||||
let img_type = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
let mut jpeg = None;
|
||||
let mut hevc = None;
|
||||
let mut cmp1 = None;
|
||||
let mut cdi1 = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
|
||||
while current < header.end_offset() {
|
||||
// get box?
|
||||
|
||||
let header = BoxHeader::parse(&mut reader)?;
|
||||
|
||||
match header.typ {
|
||||
JpegBox::TYP => {
|
||||
jpeg = Some(JpegBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
HevcBox::TYP => {
|
||||
hevc = Some(HevcBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
Cmp1Box::TYP => {
|
||||
cmp1 = Some(Cmp1Box::read_box(&mut reader, header)?);
|
||||
}
|
||||
Cdi1Box::TYP => {
|
||||
cdi1 = Some(Cdi1Box::read_box(&mut reader, header)?);
|
||||
}
|
||||
FreeBox::TYP => {
|
||||
let _ = Some(FreeBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
_ => {
|
||||
//debug!("Unknown box???");
|
||||
return Err(BmffError::Parse(format!("Invalid box found in CRAW: {:?}", header.typ)));
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
//version,
|
||||
//flags,
|
||||
reserved,
|
||||
data_ref_index,
|
||||
unknown1,
|
||||
width,
|
||||
height,
|
||||
x_resolution,
|
||||
y_resolution,
|
||||
unknown2,
|
||||
unknown3,
|
||||
compr_name,
|
||||
bit_depth,
|
||||
unknown4,
|
||||
img_flags,
|
||||
img_type,
|
||||
jpeg,
|
||||
hevc,
|
||||
cmp1,
|
||||
cdi1,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct CtboBox {
|
||||
pub header: BoxHeader,
|
||||
}
|
||||
|
||||
impl CtboBox {
|
||||
pub const TYP: FourCC = FourCC::with(['C', 'T', 'B', 'O']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for CtboBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
// TODO: add CTBO records
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self { header })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct CtmdBox {
|
||||
pub header: BoxHeader,
|
||||
//pub version: u8,
|
||||
//pub flags: u32,
|
||||
pub reserved: [u8; 6],
|
||||
pub data_ref_index: u16,
|
||||
pub rec_count: u32,
|
||||
pub records: Vec<CtmdRecord>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct CtmdRecord {
|
||||
pub unknown1: u8, // 0x00, sometimes 0x01
|
||||
pub unknown2: u8, // 0x00, seomtimes 0x01
|
||||
pub rec_type: u16,
|
||||
pub rec_size: u32,
|
||||
}
|
||||
|
||||
impl CtmdBox {
|
||||
pub const TYP: FourCC = FourCC::with(['C', 'T', 'M', 'D']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for CtmdBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
//let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
//let mut reserved = [0_u8; 6];
|
||||
//reader.read_exact(&mut reserved)?;
|
||||
|
||||
//let _reference_index = reader.read_u16::<BigEndian>()?;
|
||||
let mut reserved = [0_u8; 6];
|
||||
reader.read_exact(&mut reserved)?;
|
||||
let data_ref_index = reader.read_u16::<BigEndian>()?;
|
||||
let rec_count = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
let mut records = Vec::with_capacity(rec_count as usize);
|
||||
|
||||
//let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
|
||||
for _ in 0..rec_count {
|
||||
// get box?
|
||||
|
||||
let record = CtmdRecord {
|
||||
unknown1: reader.read_u8()?,
|
||||
unknown2: reader.read_u8()?,
|
||||
rec_type: reader.read_u16::<BigEndian>()?,
|
||||
rec_size: reader.read_u32::<BigEndian>()?,
|
||||
};
|
||||
records.push(record);
|
||||
//current = reader.seek(SeekFrom::Current(0))?;
|
||||
}
|
||||
|
||||
assert!(reader.stream_position()? == header.end_offset());
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
//version,
|
||||
//flags,
|
||||
reserved,
|
||||
data_ref_index,
|
||||
rec_count,
|
||||
records,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct HevcBox {
|
||||
pub header: BoxHeader,
|
||||
}
|
||||
|
||||
impl HevcBox {
|
||||
pub const TYP: FourCC = FourCC::with(['H', 'E', 'V', 'C']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for HevcBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self { header })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result, read_box_header_ext};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Iad1Box {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub img_width: u16,
|
||||
pub img_height: u16,
|
||||
pub unknown1: u16,
|
||||
pub image_type: u16,
|
||||
pub unknown2: u16,
|
||||
pub unknown3: u16,
|
||||
pub iad1_type: Iad1Type,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Iad1Type {
|
||||
Small(Iad1Small),
|
||||
Big(Iad1Big),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct Iad1Small {
|
||||
pub crop_left_offset: u16,
|
||||
pub crop_top_offset: u16,
|
||||
pub crop_right_offset: u16,
|
||||
pub crop_bottom_offset: u16,
|
||||
pub sensor_x_start: u16,
|
||||
pub sensor_y_start: u16,
|
||||
pub sensor_x_end: u16,
|
||||
pub sensor_y_end: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct Iad1Big {
|
||||
pub crop_left_offset: u16,
|
||||
pub crop_top_offset: u16,
|
||||
pub crop_right_offset: u16,
|
||||
pub crop_bottom_offset: u16,
|
||||
pub lob_left_offset: u16,
|
||||
pub lob_top_offset: u16,
|
||||
pub lob_right_offset: u16,
|
||||
pub lob_bottom_offset: u16,
|
||||
pub tob_left_offset: u16,
|
||||
pub tob_top_offset: u16,
|
||||
pub tob_right_offset: u16,
|
||||
pub tob_bottom_offset: u16,
|
||||
pub active_area_left_offset: u16,
|
||||
pub active_area_top_offset: u16,
|
||||
pub active_area_right_offset: u16,
|
||||
pub active_area_bottom_offset: u16,
|
||||
}
|
||||
|
||||
impl Iad1Box {
|
||||
pub const TYP: FourCC = FourCC::with(['I', 'A', 'D', '1']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Iad1Box {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let img_width = reader.read_u16::<BigEndian>()?;
|
||||
let img_height = reader.read_u16::<BigEndian>()?;
|
||||
let unknown1 = reader.read_u16::<BigEndian>()?;
|
||||
let image_type = reader.read_u16::<BigEndian>()?;
|
||||
let unknown2 = reader.read_u16::<BigEndian>()?;
|
||||
let unknown3 = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
let iad1_type = match image_type {
|
||||
0 => Iad1Type::Small(Iad1Small {
|
||||
crop_left_offset: reader.read_u16::<BigEndian>()?,
|
||||
crop_top_offset: reader.read_u16::<BigEndian>()?,
|
||||
crop_right_offset: reader.read_u16::<BigEndian>()?,
|
||||
crop_bottom_offset: reader.read_u16::<BigEndian>()?,
|
||||
sensor_x_start: reader.read_u16::<BigEndian>()?,
|
||||
sensor_y_start: reader.read_u16::<BigEndian>()?,
|
||||
sensor_x_end: reader.read_u16::<BigEndian>()?,
|
||||
sensor_y_end: reader.read_u16::<BigEndian>()?,
|
||||
}),
|
||||
2 => Iad1Type::Big(Iad1Big {
|
||||
crop_left_offset: reader.read_u16::<BigEndian>()?,
|
||||
crop_top_offset: reader.read_u16::<BigEndian>()?,
|
||||
crop_right_offset: reader.read_u16::<BigEndian>()?,
|
||||
crop_bottom_offset: reader.read_u16::<BigEndian>()?,
|
||||
lob_left_offset: reader.read_u16::<BigEndian>()?,
|
||||
lob_top_offset: reader.read_u16::<BigEndian>()?,
|
||||
lob_right_offset: reader.read_u16::<BigEndian>()?,
|
||||
lob_bottom_offset: reader.read_u16::<BigEndian>()?,
|
||||
tob_left_offset: reader.read_u16::<BigEndian>()?,
|
||||
tob_top_offset: reader.read_u16::<BigEndian>()?,
|
||||
tob_right_offset: reader.read_u16::<BigEndian>()?,
|
||||
tob_bottom_offset: reader.read_u16::<BigEndian>()?,
|
||||
active_area_left_offset: reader.read_u16::<BigEndian>()?,
|
||||
active_area_top_offset: reader.read_u16::<BigEndian>()?,
|
||||
active_area_right_offset: reader.read_u16::<BigEndian>()?,
|
||||
active_area_bottom_offset: reader.read_u16::<BigEndian>()?,
|
||||
}),
|
||||
_ => {
|
||||
panic!("Invalid iad1 type"); // TODO
|
||||
}
|
||||
};
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
version,
|
||||
flags,
|
||||
img_width,
|
||||
img_height,
|
||||
unknown1,
|
||||
image_type,
|
||||
unknown2,
|
||||
unknown3,
|
||||
iad1_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result, read_box_header_ext};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct JpegBox {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
impl JpegBox {
|
||||
pub const TYP: FourCC = FourCC::with(['J', 'P', 'E', 'G']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for JpegBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self { header, version, flags })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
pub mod ccdt;
|
||||
pub mod cctp;
|
||||
pub mod cdi1;
|
||||
pub mod cmp1;
|
||||
pub mod cmt1;
|
||||
pub mod cmt2;
|
||||
pub mod cmt3;
|
||||
pub mod cmt4;
|
||||
pub mod cncv;
|
||||
pub mod cnop;
|
||||
pub mod cr3desc; // TODO rename to cr3box
|
||||
pub mod cr3xpacket;
|
||||
pub mod craw;
|
||||
pub mod ctbo;
|
||||
pub mod ctmd;
|
||||
pub mod hevc;
|
||||
pub mod iad1;
|
||||
pub mod jpeg;
|
||||
pub mod thmb;
|
||||
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct ThmbBox {
|
||||
pub header: BoxHeader,
|
||||
}
|
||||
|
||||
impl ThmbBox {
|
||||
pub const TYP: FourCC = FourCC::with(['T', 'H', 'M', 'B']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for ThmbBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
// TODO
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self { header })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
pub struct FreeBox {
|
||||
pub header: BoxHeader,
|
||||
}
|
||||
|
||||
impl FreeBox {
|
||||
pub const TYP: FourCC = FourCC::with(['f', 'r', 'e', 'e']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for FreeBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self { header })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BmffError, BoxHeader, FourCC, ReadBox, Result};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct FtypBox {
|
||||
pub header: BoxHeader,
|
||||
pub major_brand: FourCC,
|
||||
pub minor_version: u32,
|
||||
pub compatible_brands: Vec<FourCC>,
|
||||
}
|
||||
|
||||
impl FtypBox {
|
||||
pub const TYP: FourCC = FourCC::with(['f', 't', 'y', 'p']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for FtypBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let major = reader.read_u32::<BigEndian>()?;
|
||||
let minor = reader.read_u32::<BigEndian>()?;
|
||||
if header.size % 4 != 0 {
|
||||
return Err(BmffError::Parse("invalid ftyp size".into()));
|
||||
}
|
||||
let brand_count = (header.size - 16) / 4; // header + major + minor
|
||||
|
||||
let mut brands = Vec::new();
|
||||
for _ in 0..brand_count {
|
||||
let b = reader.read_u32::<BigEndian>()?;
|
||||
brands.push(From::from(b));
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
major_brand: From::from(major),
|
||||
minor_version: minor,
|
||||
compatible_brands: brands,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BoxHeader, FourCC, ReadBox, Result, read_box_header_ext};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct HdlrBox {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
impl HdlrBox {
|
||||
pub const TYP: FourCC = FourCC::with(['h', 'd', 'l', 'r']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for HdlrBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
// TODO?
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self { header, version, flags })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct MdatBox {
|
||||
pub header: BoxHeader,
|
||||
}
|
||||
|
||||
impl MdatBox {
|
||||
pub const TYP: FourCC = FourCC::with(['m', 'd', 'a', 't']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MdatBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self { header })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BoxHeader, FourCC, ReadBox, Result, read_box_header_ext};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct MdhdBox {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u64,
|
||||
pub modification_time: u64,
|
||||
pub timescale: u32,
|
||||
pub duration: u64,
|
||||
pub language: String,
|
||||
}
|
||||
|
||||
impl MdhdBox {
|
||||
pub const TYP: FourCC = FourCC::with(['m', 'd', 'h', 'd']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MdhdBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, timescale, duration) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
let _language_code = reader.read_u16::<BigEndian>()?;
|
||||
//let language = language_string(language_code); // TODO
|
||||
let language = String::from("FIXME");
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
timescale,
|
||||
duration,
|
||||
language,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BmffError, BoxHeader, FourCC, ReadBox, Result, hdlr::HdlrBox, mdhd::MdhdBox, minf::MinfBox, vendor::VendorBox};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct MdiaBox {
|
||||
pub header: BoxHeader,
|
||||
pub mdhd: MdhdBox,
|
||||
pub hdlr: HdlrBox,
|
||||
pub minf: MinfBox,
|
||||
pub vendor: Vec<VendorBox>,
|
||||
}
|
||||
|
||||
impl MdiaBox {
|
||||
pub const TYP: FourCC = FourCC::with(['m', 'd', 'i', 'a']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MdiaBox {
|
||||
fn read_box(mut reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let mut mdhd = None;
|
||||
let mut hdlr = None;
|
||||
let mut minf = None;
|
||||
|
||||
let mut vendors = Vec::new();
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
|
||||
while current < header.end_offset() {
|
||||
// get box?
|
||||
|
||||
let header = BoxHeader::parse(&mut reader)?;
|
||||
|
||||
//let ftyp = Some(FtypBox::read_box(&mut file, header)?);
|
||||
|
||||
match header.typ {
|
||||
MdhdBox::TYP => {
|
||||
mdhd = Some(MdhdBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
HdlrBox::TYP => {
|
||||
hdlr = Some(HdlrBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
MinfBox::TYP => {
|
||||
minf = Some(MinfBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
|
||||
_ => {
|
||||
debug!("Vendor box found in mdia: {:?}", header.typ);
|
||||
let vendor = VendorBox::read_box(&mut reader, header)?;
|
||||
vendors.push(vendor);
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
mdhd: mdhd.ok_or_else(|| BmffError::Parse("mdhd box not found, corrupt file?".into()))?,
|
||||
hdlr: hdlr.ok_or_else(|| BmffError::Parse("hdlr box not found, corrupt file?".into()))?,
|
||||
minf: minf.ok_or_else(|| BmffError::Parse("minf box not found, corrupt file?".into()))?,
|
||||
vendor: vendors,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BmffError, BoxHeader, FourCC, ReadBox, Result, dinf::DinfBox, stbl::StblBox, vendor::VendorBox, vmhd::VmhdBox};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct MinfBox {
|
||||
pub header: BoxHeader,
|
||||
pub vmhd: Option<VmhdBox>,
|
||||
|
||||
//#[serde(skip_serializing_if = "Option::is_none")]
|
||||
//pub smhd: Option<SmhdBox>,
|
||||
pub dinf: DinfBox,
|
||||
pub stbl: StblBox,
|
||||
pub vendor: Vec<VendorBox>,
|
||||
}
|
||||
|
||||
impl MinfBox {
|
||||
pub const TYP: FourCC = FourCC::with(['m', 'i', 'n', 'f']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MinfBox {
|
||||
fn read_box(mut reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let mut vmhd = None;
|
||||
//let mut smhd = None;
|
||||
let mut dinf = None;
|
||||
let mut stbl = None;
|
||||
|
||||
let mut vendors = Vec::new();
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
|
||||
while current < header.end_offset() {
|
||||
// get box?
|
||||
|
||||
let header = BoxHeader::parse(&mut reader)?;
|
||||
|
||||
match header.typ {
|
||||
VmhdBox::TYP => {
|
||||
vmhd = Some(VmhdBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
DinfBox::TYP => {
|
||||
dinf = Some(DinfBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
StblBox::TYP => {
|
||||
stbl = Some(StblBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
_ => {
|
||||
debug!("Vendor box found in minf: {:?}", header.typ);
|
||||
let vendor = VendorBox::read_box(&mut reader, header)?;
|
||||
vendors.push(vendor);
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
vmhd,
|
||||
dinf: dinf.ok_or_else(|| BmffError::Parse("dinf box not found, corrupt file?".into()))?,
|
||||
stbl: stbl.ok_or_else(|| BmffError::Parse("stbl box not found, corrupt file?".into()))?,
|
||||
vendor: vendors,
|
||||
})
|
||||
}
|
||||
}
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use std::{
|
||||
fmt,
|
||||
fs::File,
|
||||
io::{self, Cursor, Read, Seek, SeekFrom},
|
||||
};
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod co64;
|
||||
pub mod dinf;
|
||||
pub mod ext_cr3;
|
||||
pub mod free;
|
||||
pub mod ftyp;
|
||||
pub mod hdlr;
|
||||
pub mod mdat;
|
||||
pub mod mdhd;
|
||||
pub mod mdia;
|
||||
pub mod minf;
|
||||
pub mod moov;
|
||||
pub mod mvhd;
|
||||
pub mod skip;
|
||||
pub mod stbl;
|
||||
pub mod stsc;
|
||||
pub mod stsd;
|
||||
pub mod stsz;
|
||||
pub mod stts;
|
||||
pub mod tkhd;
|
||||
pub mod trak;
|
||||
pub mod vendor;
|
||||
pub mod vmhd;
|
||||
|
||||
use ftyp::FtypBox;
|
||||
use mdat::MdatBox;
|
||||
use moov::MoovBox;
|
||||
use uuid::Uuid;
|
||||
use vendor::VendorBox;
|
||||
|
||||
use self::ext_cr3::cr3xpacket::Cr3XpacketBox;
|
||||
|
||||
type BoxUuid = Uuid;
|
||||
|
||||
pub const UUID_TYP: FourCC = FourCC::with(['u', 'u', 'i', 'd']);
|
||||
|
||||
pub fn read_box_header_ext<R: Read>(reader: &mut R) -> Result<(u8, u32)> {
|
||||
let version = reader.read_u8()?;
|
||||
let flags = reader.read_u24::<BigEndian>()?;
|
||||
Ok((version, flags))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct BoxHeader {
|
||||
pub size: u64,
|
||||
pub typ: FourCC,
|
||||
pub uuid: Option<BoxUuid>,
|
||||
pub offset: u64, // File offset
|
||||
pub header_len: u64,
|
||||
// Full Box fields
|
||||
//pub version: Option<u8>,
|
||||
//pub flags: Option<u32>,
|
||||
}
|
||||
|
||||
impl BoxHeader {
|
||||
pub fn parse<R: Read + Seek>(mut reader: R) -> Result<Self> {
|
||||
let start = reader.stream_position()?;
|
||||
let mut size = reader.read_u32::<BigEndian>()? as u64;
|
||||
let typ = reader.read_u32::<BigEndian>()?.into();
|
||||
if size == 1 {
|
||||
size = reader.read_u64::<BigEndian>()?;
|
||||
}
|
||||
let mut uuid = None;
|
||||
if typ == UUID_TYP {
|
||||
let mut buf = [0; 16];
|
||||
reader.read_exact(&mut buf)?;
|
||||
uuid = Some(Uuid::from_bytes(buf));
|
||||
}
|
||||
|
||||
let current = reader.stream_position()?;
|
||||
Ok(BoxHeader {
|
||||
size,
|
||||
typ,
|
||||
uuid,
|
||||
offset: start,
|
||||
header_len: current - start,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn end_offset(&self) -> u64 {
|
||||
self.offset + self.size
|
||||
}
|
||||
|
||||
pub fn make_view<'a>(&self, buffer: &'a [u8], skip: usize, limit: usize) -> &'a [u8] {
|
||||
let start = (self.offset + self.header_len) as usize + skip;
|
||||
if limit == 0 {
|
||||
&buffer[start..start + (self.size - self.header_len) as usize - skip]
|
||||
} else {
|
||||
&buffer[start..start + limit]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BmffBox: Sized {
|
||||
fn size(&self) -> u64;
|
||||
|
||||
fn data_size(&self) -> u64;
|
||||
|
||||
fn box_offset(&self) -> usize;
|
||||
|
||||
fn box_data_offset(&self) -> usize;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BmffError {
|
||||
#[error("I/O error while writing DNG")]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
#[error("Parser error: {}", _0)]
|
||||
Parse(String),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, BmffError>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct FileBox {
|
||||
pub ftyp: FtypBox,
|
||||
pub moov: MoovBox,
|
||||
pub mdat: MdatBox,
|
||||
pub vendor: Vec<VendorBox>,
|
||||
pub cr3xpacket: Option<Cr3XpacketBox>,
|
||||
}
|
||||
|
||||
impl FileBox {
|
||||
pub fn parse<R: Read + Seek>(mut file: R) -> Result<FileBox> {
|
||||
let size = file.seek(SeekFrom::End(0))?;
|
||||
let mut current = file.seek(SeekFrom::Start(0))?;
|
||||
|
||||
let mut ftyp = None;
|
||||
let mut moov = None;
|
||||
let mut mdat = None;
|
||||
let mut vendors = Vec::new();
|
||||
let mut cr3xpacket = None;
|
||||
|
||||
while current < size {
|
||||
// get box?
|
||||
|
||||
let header = BoxHeader::parse(&mut file)?;
|
||||
|
||||
//let ftyp = Some(FtypBox::read_box(&mut file, header)?);
|
||||
|
||||
match header.typ {
|
||||
FtypBox::TYP => {
|
||||
debug!("Found ftyp box");
|
||||
ftyp = Some(FtypBox::read_box(&mut file, header)?);
|
||||
}
|
||||
MoovBox::TYP => {
|
||||
debug!("Found moov box");
|
||||
moov = Some(MoovBox::read_box(&mut file, header)?);
|
||||
}
|
||||
MdatBox::TYP => {
|
||||
debug!("Found mdat box");
|
||||
mdat = Some(MdatBox::read_box(&mut file, header)?);
|
||||
}
|
||||
_ => {
|
||||
if let Some(uuid) = header.uuid {
|
||||
if uuid == Uuid::from_bytes(Cr3XpacketBox::UUID) {
|
||||
cr3xpacket = Some(Cr3XpacketBox::read_box(&mut file, header)?);
|
||||
} else {
|
||||
debug!("Vendor box found in filebox: {:?}", header.typ);
|
||||
let vendor = VendorBox::read_box(&mut file, header)?;
|
||||
vendors.push(vendor);
|
||||
}
|
||||
} else {
|
||||
debug!("Vendor box found in filebox: {:?}", header.typ);
|
||||
let vendor = VendorBox::read_box(&mut file, header)?;
|
||||
vendors.push(vendor);
|
||||
}
|
||||
}
|
||||
}
|
||||
if file.stream_position()? != current {
|
||||
current = file.stream_position()?;
|
||||
} else {
|
||||
// If parsing has not moved cursor, we probably have a corrupt file or it's not a BMFF at all.
|
||||
return Err(BmffError::Parse("Unable to find valid BMFF box, corrupt file?".into()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
ftyp: ftyp.ok_or_else(|| BmffError::Parse("ftyp box not found, corrupt file?".into()))?,
|
||||
moov: moov.ok_or_else(|| BmffError::Parse("moov box not found, corrupt file?".into()))?,
|
||||
mdat: mdat.ok_or_else(|| BmffError::Parse("mdat box not found, corrupt file?".into()))?,
|
||||
vendor: vendors,
|
||||
cr3xpacket,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_file(file: &mut File) -> Result<FileBox> {
|
||||
let filebox = FileBox::parse(file)?;
|
||||
Ok(filebox)
|
||||
}
|
||||
|
||||
pub fn parse_buffer(buf: &[u8]) -> Result<FileBox> {
|
||||
// TODO: add AsRef<u8>
|
||||
let mut cursor = Cursor::new(buf);
|
||||
let filebox = FileBox::parse(&mut cursor)?;
|
||||
Ok(filebox)
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct FourCC {
|
||||
pub value: [u8; 4],
|
||||
}
|
||||
|
||||
impl Serialize for FourCC {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for FourCC {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<FourCC, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
let s = String::deserialize(deserializer)?;
|
||||
if s.len() != 4 {
|
||||
Err(D::Error::custom(format!("Invalid FourCC value: {}", s)))
|
||||
} else {
|
||||
Ok(FourCC {
|
||||
value: [s.as_bytes()[0], s.as_bytes()[1], s.as_bytes()[2], s.as_bytes()[3]],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FourCC {
|
||||
const fn with(v: [char; 4]) -> Self {
|
||||
Self {
|
||||
value: [v[0] as u8, v[1] as u8, v[2] as u8, v[3] as u8],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for FourCC {
|
||||
fn from(number: u32) -> Self {
|
||||
FourCC { value: number.to_be_bytes() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FourCC> for u32 {
|
||||
fn from(fourcc: FourCC) -> u32 {
|
||||
(&fourcc).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FourCC> for u32 {
|
||||
fn from(fourcc: &FourCC) -> u32 {
|
||||
u32::from_be_bytes(fourcc.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for FourCC {
|
||||
type Err = BmffError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
if let [a, b, c, d] = s.as_bytes() {
|
||||
Ok(Self { value: [*a, *b, *c, *d] })
|
||||
} else {
|
||||
Err(BmffError::Parse("expected exactly four bytes in string".into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 4]> for FourCC {
|
||||
fn from(value: [u8; 4]) -> FourCC {
|
||||
FourCC { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let code: u32 = self.into();
|
||||
let string = String::from_utf8_lossy(&self.value[..]);
|
||||
write!(f, "{} / {:#010X}", string, code)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", String::from_utf8_lossy(&self.value[..]))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ReadBox<T>: Sized {
|
||||
fn read_box(_: T, header: BoxHeader) -> Result<Self>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Bmff {
|
||||
pub filebox: FileBox,
|
||||
}
|
||||
|
||||
impl Bmff {
|
||||
pub fn new<R: Read + Seek>(file: R) -> Result<Self> {
|
||||
let filebox = FileBox::parse(file)?;
|
||||
Ok(Self { filebox })
|
||||
}
|
||||
|
||||
pub fn new_buf(buf: &[u8]) -> Result<Self> {
|
||||
let filebox = parse_buffer(buf)?;
|
||||
Ok(Self { filebox })
|
||||
}
|
||||
|
||||
pub fn compatible_brand(&self, _brand: &str) -> bool {
|
||||
true // FIXME
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BmffError, BoxHeader, FourCC, ReadBox, Result, ext_cr3::cr3desc::Cr3DescBox, mvhd::MvhdBox, trak::TrakBox, vendor::VendorBox};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct MoovBox {
|
||||
pub header: BoxHeader,
|
||||
pub mvhd: MvhdBox,
|
||||
|
||||
//#[serde(skip_serializing_if = "Option::is_none")]
|
||||
//pub mvex: Option<MvexBox>,
|
||||
#[serde(rename = "trak")]
|
||||
pub traks: Vec<TrakBox>,
|
||||
|
||||
// UUID Box 85c0b687-820f-11e0-8111-f4ce462b6a48
|
||||
pub cr3desc: Option<Cr3DescBox>,
|
||||
|
||||
pub vendor: Vec<VendorBox>,
|
||||
}
|
||||
|
||||
impl MoovBox {
|
||||
pub const TYP: FourCC = FourCC::with(['m', 'o', 'o', 'v']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
||||
fn read_box(mut reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let mut mvhd = None;
|
||||
let mut traks = Vec::new();
|
||||
|
||||
let mut cr3desc = None;
|
||||
|
||||
let mut vendors = Vec::new();
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
|
||||
while current < header.end_offset() {
|
||||
// get box?
|
||||
|
||||
let header = BoxHeader::parse(&mut reader)?;
|
||||
|
||||
match header.typ {
|
||||
MvhdBox::TYP => {
|
||||
mvhd = Some(MvhdBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
TrakBox::TYP => {
|
||||
let trak = TrakBox::read_box(&mut reader, header)?;
|
||||
traks.push(trak);
|
||||
}
|
||||
|
||||
_ => {
|
||||
if let Some(uuid) = header.uuid {
|
||||
if uuid == Uuid::from_bytes(Cr3DescBox::UUID) {
|
||||
cr3desc = Some(Cr3DescBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
} else {
|
||||
debug!("Vendor box found in moov: {:?}", header.typ);
|
||||
let vendor = VendorBox::read_box(&mut reader, header)?;
|
||||
vendors.push(vendor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
mvhd: mvhd.ok_or_else(|| BmffError::Parse("mdhd box not found, corrupt file?".into()))?,
|
||||
traks,
|
||||
cr3desc,
|
||||
vendor: vendors,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BoxHeader, FourCC, ReadBox, Result, read_box_header_ext};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct MvhdBox {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u64,
|
||||
pub modification_time: u64,
|
||||
pub timescale: u32,
|
||||
pub duration: u64,
|
||||
//#[serde(with = "value_u32")]
|
||||
//pub rate: FixedPointU16,
|
||||
}
|
||||
|
||||
impl MvhdBox {
|
||||
pub const TYP: FourCC = FourCC::with(['m', 'v', 'h', 'd']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MvhdBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, timescale, duration) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
//let rate = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?); // TODO
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
timescale,
|
||||
duration,
|
||||
//rate,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BoxHeader, FourCC, ReadBox, Result};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
pub struct SkipBox {
|
||||
pub header: BoxHeader,
|
||||
}
|
||||
|
||||
impl SkipBox {
|
||||
pub const TYP: FourCC = FourCC::with(['s', 'k', 'i', 'p']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for SkipBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self { header })
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BmffError, BoxHeader, FourCC, ReadBox, Result, co64::Co64Box, stsc::StscBox, stsd::StsdBox, stsz::StszBox, stts::SttsBox, vendor::VendorBox};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct StblBox {
|
||||
pub header: BoxHeader,
|
||||
pub stsd: StsdBox,
|
||||
pub stts: SttsBox,
|
||||
//pub ctts: Option<CttsBox>,
|
||||
pub stsc: StscBox,
|
||||
pub stsz: StszBox,
|
||||
pub co64: Option<Co64Box>,
|
||||
pub vendor: Vec<VendorBox>,
|
||||
}
|
||||
|
||||
impl StblBox {
|
||||
pub const TYP: FourCC = FourCC::with(['s', 't', 'b', 'l']);
|
||||
|
||||
pub fn get_sample_offset(&self, sample: u32) -> Option<(usize, usize)> {
|
||||
if let Some(co64) = self.co64.as_ref() {
|
||||
assert!(sample > 0, "sample number must be greater than 0");
|
||||
let desc = self.stsc.get_entry_for_sample(sample);
|
||||
// Chunk number for our sample
|
||||
let chunk = desc.first_chunk + (((sample - 1) - (desc.first_sample - 1)) / desc.samples_per_chunk);
|
||||
// Index of sample inside chunk
|
||||
let chunk_sample_idx = (sample - desc.first_sample) % desc.samples_per_chunk;
|
||||
// Offset of chunk
|
||||
let chunk_offset = co64.entries[chunk as usize - 1];
|
||||
|
||||
// Offset of sample in chunk
|
||||
let mut sample_offset = 0;
|
||||
for i in 0..chunk_sample_idx {
|
||||
sample_offset += self.stsz.sample_size(sample - chunk_sample_idx + i);
|
||||
}
|
||||
let sample_size = self.stsz.sample_size(sample);
|
||||
|
||||
Some((chunk_offset as usize + sample_offset as usize, sample_size as usize))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StblBox {
|
||||
fn read_box(mut reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let mut stsd = None;
|
||||
let mut stts = None;
|
||||
// let mut ctts = None;
|
||||
//let mut stss = None;
|
||||
let mut stsc = None;
|
||||
let mut stsz = None;
|
||||
//let mut stco = None;
|
||||
let mut co64 = None;
|
||||
|
||||
let mut vendors = Vec::new();
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
|
||||
while current < header.end_offset() {
|
||||
// get box?
|
||||
|
||||
let header = BoxHeader::parse(&mut reader)?;
|
||||
|
||||
//let ftyp = Some(FtypBox::read_box(&mut file, header)?);
|
||||
|
||||
match header.typ {
|
||||
StsdBox::TYP => {
|
||||
stsd = Some(StsdBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
SttsBox::TYP => {
|
||||
stts = Some(SttsBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
StscBox::TYP => {
|
||||
stsc = Some(StscBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
StszBox::TYP => {
|
||||
stsz = Some(StszBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
Co64Box::TYP => {
|
||||
co64 = Some(Co64Box::read_box(&mut reader, header)?);
|
||||
}
|
||||
|
||||
_ => {
|
||||
debug!("Vendor box found in stbl: {:?}", header.typ);
|
||||
let vendor = VendorBox::read_box(&mut reader, header)?;
|
||||
vendors.push(vendor);
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
stsd: stsd.ok_or_else(|| BmffError::Parse("stsd box not found, corrupt file?".into()))?,
|
||||
stts: stts.ok_or_else(|| BmffError::Parse("stts box not found, corrupt file?".into()))?,
|
||||
stsc: stsc.ok_or_else(|| BmffError::Parse("stsc box not found, corrupt file?".into()))?,
|
||||
stsz: stsz.ok_or_else(|| BmffError::Parse("stsz box not found, corrupt file?".into()))?,
|
||||
co64,
|
||||
vendor: vendors,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BoxHeader, FourCC, ReadBox, Result, read_box_header_ext};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct StscBox {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entries: Vec<StscEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct StscEntry {
|
||||
pub first_chunk: u32,
|
||||
pub samples_per_chunk: u32,
|
||||
pub sample_description_index: u32,
|
||||
pub first_sample: u32,
|
||||
}
|
||||
|
||||
impl StscBox {
|
||||
pub const TYP: FourCC = FourCC::with(['s', 't', 's', 'c']);
|
||||
|
||||
pub fn get_entry_for_sample(&self, sample: u32) -> &StscEntry {
|
||||
assert!(sample > 0, "Sample numbering starts with 1");
|
||||
assert_eq!(self.entries.is_empty(), false, "stsc box must contains at least one entry");
|
||||
assert_eq!(self.entries[0].first_sample, 1, "First entry must start with first sample");
|
||||
match self.entries.binary_search_by(|entry| entry.first_sample.cmp(&sample)) {
|
||||
Ok(i) => &self.entries[i],
|
||||
Err(i) => &self.entries[i - 1],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StscBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
|
||||
// Reader for a single stsc entry
|
||||
let mut read_entry = || -> Result<StscEntry> {
|
||||
Ok(StscEntry {
|
||||
first_chunk: reader.read_u32::<BigEndian>()?,
|
||||
samples_per_chunk: reader.read_u32::<BigEndian>()?,
|
||||
sample_description_index: reader.read_u32::<BigEndian>()?,
|
||||
first_sample: 0,
|
||||
})
|
||||
};
|
||||
|
||||
if entry_count > 0 {
|
||||
// Read first entry and hold it back
|
||||
let mut holdback = read_entry()?;
|
||||
holdback.first_sample = 1;
|
||||
|
||||
for _ in 1..entry_count {
|
||||
let mut entry = read_entry()?;
|
||||
// Now we know the chunk count to calc the amount of samples in holdback
|
||||
entry.first_sample = holdback.first_sample + ((entry.first_chunk - holdback.first_sample) * holdback.samples_per_chunk);
|
||||
entries.push(holdback);
|
||||
holdback = entry;
|
||||
}
|
||||
// Finalize entry list
|
||||
entries.push(holdback);
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
version,
|
||||
flags,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{
|
||||
BoxHeader, FourCC, ReadBox, Result,
|
||||
ext_cr3::{craw::CrawBox, ctmd::CtmdBox},
|
||||
read_box_header_ext,
|
||||
vendor::VendorBox,
|
||||
};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct StsdBox {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub craw: Option<CrawBox>,
|
||||
pub ctmd: Option<CtmdBox>,
|
||||
pub vendor: Vec<VendorBox>,
|
||||
}
|
||||
|
||||
impl StsdBox {
|
||||
pub const TYP: FourCC = FourCC::with(['s', 't', 's', 'd']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
|
||||
fn read_box(mut reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
// Canon CR3 boxes
|
||||
let mut craw = None;
|
||||
let mut ctmd = None;
|
||||
|
||||
let mut vendors = Vec::new();
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // FIXME XXX entry_count
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
|
||||
while current < header.end_offset() {
|
||||
// get box?
|
||||
|
||||
let header = BoxHeader::parse(&mut reader)?;
|
||||
|
||||
match header.typ {
|
||||
CrawBox::TYP => {
|
||||
assert_eq!(craw, None, "Found second CRAW box");
|
||||
craw = Some(CrawBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
CtmdBox::TYP => {
|
||||
assert_eq!(ctmd, None, "Found second CTMD box");
|
||||
ctmd = Some(CtmdBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
// TODO: Multiple CRAW boxes can occour in CRM files?! BMFF Spec says the SampleBox is an array.
|
||||
_ => {
|
||||
debug!("Vendor box found in stsd: {:?}", header.typ);
|
||||
let vendor = VendorBox::read_box(&mut reader, header)?;
|
||||
vendors.push(vendor);
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
version,
|
||||
flags,
|
||||
craw,
|
||||
ctmd,
|
||||
vendor: vendors,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BoxHeader, FourCC, ReadBox, Result, read_box_header_ext};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct StszBox {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub sample_size: u32,
|
||||
pub sample_count: u32,
|
||||
pub sample_sizes: Vec<u32>,
|
||||
}
|
||||
|
||||
impl StszBox {
|
||||
pub const TYP: FourCC = FourCC::with(['s', 't', 's', 'z']);
|
||||
|
||||
pub fn sample_size(&self, sample: u32) -> u32 {
|
||||
if self.sample_size > 0 {
|
||||
self.sample_size
|
||||
} else {
|
||||
self.sample_sizes[sample as usize - 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StszBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let sample_size = reader.read_u32::<BigEndian>()?;
|
||||
let sample_count = reader.read_u32::<BigEndian>()?;
|
||||
let mut sample_sizes = Vec::with_capacity(sample_count as usize);
|
||||
if sample_size == 0 {
|
||||
for _ in 0..sample_count {
|
||||
let sample_number = reader.read_u32::<BigEndian>()?;
|
||||
sample_sizes.push(sample_number);
|
||||
}
|
||||
} else {
|
||||
// If the sample_size is non-zero, it is the only sample_size
|
||||
sample_sizes.push(sample_size);
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
version,
|
||||
flags,
|
||||
sample_size,
|
||||
sample_count,
|
||||
sample_sizes,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BoxHeader, FourCC, ReadBox, Result, read_box_header_ext};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct SttsBox {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entries: Vec<SttsEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct SttsEntry {
|
||||
pub sample_count: u32,
|
||||
pub sample_delta: u32,
|
||||
}
|
||||
|
||||
impl SttsBox {
|
||||
pub const TYP: FourCC = FourCC::with(['s', 't', 't', 's']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for SttsBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
for _i in 0..entry_count {
|
||||
let entry = SttsEntry {
|
||||
sample_count: reader.read_u32::<BigEndian>()?,
|
||||
sample_delta: reader.read_u32::<BigEndian>()?,
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
version,
|
||||
flags,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BoxHeader, FourCC, ReadBox, Result, read_box_header_ext};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct TkhdBox {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
impl TkhdBox {
|
||||
pub const TYP: FourCC = FourCC::with(['t', 'k', 'h', 'd']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for TkhdBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
// TODO?
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self { header, version, flags })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BmffError, BoxHeader, FourCC, ReadBox, Result, mdia::MdiaBox, tkhd::TkhdBox, vendor::VendorBox};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct TrakBox {
|
||||
pub header: BoxHeader,
|
||||
pub tkhd: TkhdBox,
|
||||
|
||||
//#[serde(skip_serializing_if = "Option::is_none")]
|
||||
//pub edts: Option<EdtsBox>,
|
||||
pub mdia: MdiaBox,
|
||||
pub vendor: Vec<VendorBox>,
|
||||
}
|
||||
|
||||
impl TrakBox {
|
||||
pub const TYP: FourCC = FourCC::with(['t', 'r', 'a', 'k']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
||||
fn read_box(mut reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let mut tkhd = None;
|
||||
let mut mdia = None;
|
||||
|
||||
let mut vendors = Vec::new();
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
|
||||
while current < header.end_offset() {
|
||||
// get box?
|
||||
|
||||
let header = BoxHeader::parse(&mut reader)?;
|
||||
|
||||
//let ftyp = Some(FtypBox::read_box(&mut file, header)?);
|
||||
|
||||
match header.typ {
|
||||
TkhdBox::TYP => {
|
||||
tkhd = Some(TkhdBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
MdiaBox::TYP => {
|
||||
mdia = Some(MdiaBox::read_box(&mut reader, header)?);
|
||||
}
|
||||
|
||||
_ => {
|
||||
debug!("Vendor box found in trak: {:?}", header.typ);
|
||||
let vendor = VendorBox::read_box(&mut reader, header)?;
|
||||
vendors.push(vendor);
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
tkhd: tkhd.ok_or_else(|| BmffError::Parse("tkhd box not found, corrupt file?".into()))?,
|
||||
mdia: mdia.ok_or_else(|| BmffError::Parse("mdia box not found, corrupt file?".into()))?,
|
||||
vendor: vendors,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BoxHeader, ReadBox, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct VendorBox {
|
||||
pub header: BoxHeader,
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for VendorBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
reader.seek(SeekFrom::Start(header.offset + header.size))?;
|
||||
|
||||
Ok(Self { header })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2020 Alfred Gutierrez
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{BoxHeader, FourCC, ReadBox, Result, read_box_header_ext};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct VmhdBox {
|
||||
pub header: BoxHeader,
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub graphics_mode: u16,
|
||||
pub op_color: RgbColor,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct RgbColor {
|
||||
pub red: u16,
|
||||
pub green: u16,
|
||||
pub blue: u16,
|
||||
}
|
||||
|
||||
impl VmhdBox {
|
||||
pub const TYP: FourCC = FourCC::with(['v', 'm', 'h', 'd']);
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for VmhdBox {
|
||||
fn read_box(reader: &mut R, header: BoxHeader) -> Result<Self> {
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let graphics_mode = reader.read_u16::<BigEndian>()?;
|
||||
let op_color = RgbColor {
|
||||
red: reader.read_u16::<BigEndian>()?,
|
||||
green: reader.read_u16::<BigEndian>()?,
|
||||
blue: reader.read_u16::<BigEndian>()?,
|
||||
};
|
||||
|
||||
reader.seek(SeekFrom::Start(header.end_offset()))?;
|
||||
|
||||
Ok(Self {
|
||||
header,
|
||||
version,
|
||||
flags,
|
||||
graphics_mode,
|
||||
op_color,
|
||||
})
|
||||
}
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{bits::*, rawsource::RawSource};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum CiffTag {
|
||||
//Null = 0x0000,
|
||||
ColorInfo1 = 0x0032,
|
||||
MakeModel = 0x080a,
|
||||
//ShotInfo = 0x102a,
|
||||
ColorInfo2 = 0x102c,
|
||||
WhiteBalance = 0x10a9,
|
||||
SensorInfo = 0x1031,
|
||||
//ImageInfo = 0x1810,
|
||||
DecoderTable = 0x1835,
|
||||
//RawData = 0x2005,
|
||||
//SubIFD = 0x300a,
|
||||
//Exif = 0x300b,
|
||||
}
|
||||
|
||||
fn ct(tag: CiffTag) -> u16 {
|
||||
tag as u16
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CiffEntry {
|
||||
pub tag: u16,
|
||||
pub typ: u16,
|
||||
pub count: usize,
|
||||
pub bytesize: usize,
|
||||
pub data_offset: usize,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CiffIFD {
|
||||
entries: HashMap<u16, CiffEntry>,
|
||||
subifds: Vec<CiffIFD>,
|
||||
}
|
||||
|
||||
pub fn is_ciff(file: &RawSource) -> bool {
|
||||
match file.subview(0, 14) {
|
||||
Ok(buf) => buf[6..14] == b"HEAPCCDR"[..],
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl CiffIFD {
|
||||
pub fn new_file(file: &RawSource) -> Result<CiffIFD, String> {
|
||||
let data = file.as_vec().map_err(|err| format!("Failed to read whole file: {:?}", err))?;
|
||||
|
||||
CiffIFD::new(&data, LEu32(&data, 2) as usize, data.len(), 1)
|
||||
}
|
||||
|
||||
pub fn new(buf: &[u8], start: usize, end: usize, depth: u32) -> Result<CiffIFD, String> {
|
||||
let mut entries = HashMap::new();
|
||||
let mut subifds = Vec::new();
|
||||
|
||||
let valuedata_size = LEu32(buf, end - 4) as usize;
|
||||
let dircount = LEu16(buf, start + valuedata_size) as usize;
|
||||
|
||||
for i in 0..dircount {
|
||||
let entry_offset: usize = start + valuedata_size + 2 + i * 10;
|
||||
let e = CiffEntry::new(buf, start, entry_offset)?;
|
||||
if e.typ == 0x2800 || e.typ == 0x3000 {
|
||||
// SubIFDs
|
||||
if depth < 10 {
|
||||
// Avoid infinite looping IFDs
|
||||
let ifd = CiffIFD::new(buf, e.data_offset, e.data_offset + e.bytesize, depth + 1);
|
||||
match ifd {
|
||||
Ok(val) => {
|
||||
subifds.push(val);
|
||||
}
|
||||
Err(_) => {
|
||||
entries.insert(e.tag, e);
|
||||
} // Ignore unparsable IFDs
|
||||
}
|
||||
}
|
||||
} else {
|
||||
entries.insert(e.tag, e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CiffIFD { entries, subifds })
|
||||
}
|
||||
|
||||
pub fn find_entry(&self, tag: CiffTag) -> Option<&CiffEntry> {
|
||||
if self.entries.contains_key(&ct(tag)) {
|
||||
self.entries.get(&ct(tag))
|
||||
} else {
|
||||
for ifd in &self.subifds {
|
||||
if let Some(x) = ifd.find_entry(tag) {
|
||||
return Some(x);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CiffEntry {
|
||||
pub fn new(buf: &[u8], value_data: usize, offset: usize) -> Result<CiffEntry, String> {
|
||||
let p = LEu16(buf, offset);
|
||||
let tag = p & 0x3fff;
|
||||
let datalocation = (p & 0xc000) as usize;
|
||||
let typ = p & 0x3800;
|
||||
|
||||
let (bytesize, data_offset) = match datalocation {
|
||||
// Data is offset in value_data
|
||||
0x0000 => (LEu32(buf, offset + 2) as usize, LEu32(buf, offset + 6) as usize + value_data),
|
||||
// Data is stored directly in entry
|
||||
0x4000 => (8, offset + 2),
|
||||
val => return Err(format!("CIFF: Don't know about data location {:x}", val)),
|
||||
};
|
||||
let data = &buf[data_offset..data_offset + bytesize];
|
||||
let count = bytesize >> CiffEntry::element_shift(typ);
|
||||
|
||||
Ok(CiffEntry {
|
||||
tag,
|
||||
typ,
|
||||
count,
|
||||
bytesize,
|
||||
data_offset,
|
||||
data: Vec::from(data),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn element_shift(typ: u16) -> usize {
|
||||
match typ {
|
||||
// Byte and ASCII
|
||||
0x0000 | 0x8000 => 0,
|
||||
// Short
|
||||
0x1000 => 1,
|
||||
// Long, Mix, Sub1 and Sub2
|
||||
0x1800 | 0x2000 | 0x2800 | 0x3000 => 2,
|
||||
// Default to 0
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_strings(&self) -> Vec<String> {
|
||||
String::from_utf8_lossy(&self.data).split_terminator('\0').map(|x| x.to_string()).collect()
|
||||
}
|
||||
|
||||
pub fn get_u32(&self, idx: usize) -> u32 {
|
||||
match self.typ {
|
||||
0x0000 | 0x8000 => self.data[idx] as u32,
|
||||
0x1000 => LEu16(&self.data, idx * 2) as u32,
|
||||
0x1800 | 0x2000 | 0x2800 | 0x3000 => LEu32(&self.data, idx * 4),
|
||||
_ => panic!("{}", format!("Trying to read typ {} for a u32", self.typ)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_usize(&self, idx: usize) -> usize {
|
||||
self.get_u32(idx) as usize
|
||||
}
|
||||
pub fn get_f32(&self, idx: usize) -> f32 {
|
||||
self.get_u32(idx) as f32
|
||||
}
|
||||
pub fn get_force_u16(&self, idx: usize) -> u16 {
|
||||
LEu16(&self.data, idx * 2)
|
||||
}
|
||||
}
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
pub mod bmff;
|
||||
pub mod ciff;
|
||||
pub mod jfif;
|
||||
pub mod tiff;
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
bits::Endian,
|
||||
formats::tiff::{Rational, SRational, TiffAscii, TiffError, Value, apply_corr, reader::ReadByteOrder},
|
||||
};
|
||||
|
||||
use super::{Result, reader::EndianReader};
|
||||
|
||||
const TYPE_BYTE: u16 = 1;
|
||||
const TYPE_ASCII: u16 = 2;
|
||||
const TYPE_SHORT: u16 = 3;
|
||||
const TYPE_LONG: u16 = 4;
|
||||
const TYPE_RATIONAL: u16 = 5;
|
||||
const TYPE_SBYTE: u16 = 6;
|
||||
const TYPE_UNDEFINED: u16 = 7;
|
||||
const TYPE_SSHORT: u16 = 8;
|
||||
const TYPE_SLONG: u16 = 9;
|
||||
const TYPE_SRATIONAL: u16 = 10;
|
||||
const TYPE_FLOAT: u16 = 11;
|
||||
const TYPE_DOUBLE: u16 = 12;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Entry {
|
||||
pub tag: u16,
|
||||
pub value: Value,
|
||||
// Embedded value for writer, offset for reader
|
||||
// This is only None when building an IFD for writing.
|
||||
pub embedded: Option<u32>, // TODO: rename it
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Entry {
|
||||
type Target = Value;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawEntry<'a> {
|
||||
pub entry: &'a Entry,
|
||||
pub endian: Endian,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> RawEntry<'a> {
|
||||
pub fn get_force_u32(&self, idx: usize) -> u32 {
|
||||
self.endian.read_u32(&self.data, idx * 4)
|
||||
}
|
||||
|
||||
pub fn get_force_u16(&self, idx: usize) -> u16 {
|
||||
self.endian.read_u16(&self.data, idx * 2)
|
||||
}
|
||||
}
|
||||
|
||||
// 0-1-2-3-4-5-6-7-8-9-10-11-12-13
|
||||
const DATASHIFTS: [u8; 14] = [0, 0, 0, 1, 2, 3, 0, 0, 1, 2, 3, 2, 3, 2];
|
||||
|
||||
impl Entry {
|
||||
pub fn value_type(&self) -> u16 {
|
||||
self.value.value_type()
|
||||
}
|
||||
|
||||
pub fn count(&self) -> u32 {
|
||||
self.value.count() as u32
|
||||
}
|
||||
|
||||
/// Returns the offset
|
||||
/// It is already corrected by `corr` but needs to be summed
|
||||
/// with `base` offset.
|
||||
pub fn offset(&self) -> Option<usize> {
|
||||
self.embedded.map(|v| v as usize)
|
||||
}
|
||||
|
||||
pub fn parse<R: Read + Seek>(reader: &mut EndianReader<R>, base: u32, corr: i32, tag: u16) -> Result<Entry> {
|
||||
let pos = reader.position()? - 2; // TODO -2 because tag is already read
|
||||
|
||||
let typ = reader.read_u16()?;
|
||||
let count = reader.read_u32()?;
|
||||
|
||||
// If we don't know the type assume byte data (undefined)
|
||||
let compat_typ = if typ == 0 || typ > 12 { 7 } else { typ };
|
||||
|
||||
let bytesize: usize = (count as usize) << DATASHIFTS[compat_typ as usize];
|
||||
let offset: u32 = if bytesize <= 4 {
|
||||
reader.position()? - base
|
||||
} else {
|
||||
apply_corr(reader.read_u32()?, corr)
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Tag: {:#x}, Typ: {:#x}, count: {}, offset: {}, base: {}, corr: {}",
|
||||
tag, typ, count, offset, base, corr
|
||||
);
|
||||
|
||||
if offset == u32::MAX || base.checked_add(offset).is_none() {
|
||||
// We hit an invalid offset, ignoring this tag
|
||||
// This happens for Olympus E-P2 images in ImageProc IFD for example.
|
||||
return Err(TiffError::Overflow(format!("Offset {} is invalid for tag 0x{:X}", offset, tag)));
|
||||
}
|
||||
|
||||
reader.goto(base + offset)?;
|
||||
let entry = match typ {
|
||||
TYPE_BYTE => {
|
||||
let mut v = vec![0; count as usize];
|
||||
reader.read_u8_into(&mut v)?;
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::Byte(v),
|
||||
embedded: Some(offset),
|
||||
}
|
||||
}
|
||||
TYPE_ASCII => {
|
||||
let mut v = vec![0; count as usize];
|
||||
reader.read_u8_into(&mut v)?;
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::Ascii(TiffAscii::new_from_raw(&v)),
|
||||
embedded: Some(offset),
|
||||
}
|
||||
}
|
||||
TYPE_SHORT => {
|
||||
let mut v = vec![0; count as usize];
|
||||
reader.read_u16_into(&mut v)?;
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::Short(v),
|
||||
embedded: Some(offset),
|
||||
}
|
||||
}
|
||||
TYPE_LONG => {
|
||||
let mut v = vec![0; count as usize];
|
||||
reader.read_u32_into(&mut v)?;
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::Long(v),
|
||||
embedded: Some(offset),
|
||||
}
|
||||
}
|
||||
TYPE_RATIONAL => {
|
||||
let mut tmp = vec![0; count as usize * 2]; // Rational is 2x u32
|
||||
reader.read_u32_into(&mut tmp)?;
|
||||
|
||||
let mut v = Vec::with_capacity(count as usize);
|
||||
for chunk in tmp.chunks_exact(2) {
|
||||
v.push(Rational::new(chunk[0], chunk[1]));
|
||||
}
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::Rational(v),
|
||||
embedded: Some(offset),
|
||||
}
|
||||
}
|
||||
TYPE_SBYTE => {
|
||||
let mut v = vec![0; count as usize];
|
||||
reader.read_i8_into(&mut v)?;
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::SByte(v),
|
||||
embedded: Some(offset),
|
||||
}
|
||||
}
|
||||
TYPE_UNDEFINED => {
|
||||
let mut v = vec![0; count as usize];
|
||||
reader.read_u8_into(&mut v)?;
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::Undefined(v),
|
||||
embedded: Some(offset),
|
||||
}
|
||||
}
|
||||
TYPE_SSHORT => {
|
||||
let mut v = vec![0; count as usize];
|
||||
reader.read_i16_into(&mut v)?;
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::SShort(v),
|
||||
embedded: Some(offset),
|
||||
}
|
||||
}
|
||||
TYPE_SLONG => {
|
||||
let mut v = vec![0; count as usize];
|
||||
reader.read_i32_into(&mut v)?;
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::SLong(v),
|
||||
embedded: Some(offset),
|
||||
}
|
||||
}
|
||||
TYPE_SRATIONAL => {
|
||||
let mut tmp = vec![0; count as usize * 2]; // SRational is 2x i32
|
||||
reader.read_i32_into(&mut tmp)?;
|
||||
|
||||
let mut v = Vec::with_capacity(count as usize);
|
||||
for chunk in tmp.chunks_exact(2) {
|
||||
v.push(SRational::new(chunk[0], chunk[1]));
|
||||
}
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::SRational(v),
|
||||
embedded: Some(offset),
|
||||
}
|
||||
}
|
||||
TYPE_FLOAT => {
|
||||
let mut v = vec![0.0; count as usize];
|
||||
reader.read_f32_into(&mut v)?;
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::Float(v),
|
||||
embedded: Some(offset),
|
||||
}
|
||||
}
|
||||
TYPE_DOUBLE => {
|
||||
let mut v = vec![0.0; count as usize];
|
||||
reader.read_f64_into(&mut v)?;
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::Double(v),
|
||||
embedded: Some(offset),
|
||||
}
|
||||
}
|
||||
x => {
|
||||
let mut v = vec![0; count as usize];
|
||||
reader.read_u8_into(&mut v)?;
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::Unknown(x, v),
|
||||
embedded: Some(offset),
|
||||
}
|
||||
}
|
||||
};
|
||||
reader.goto(pos + 12)?; // Size of IFD entry
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
pub fn type_name(&self) -> String {
|
||||
self.value.value_type_name()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::IFD;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Reader for TIFF files
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct TiffFile {
|
||||
/// Chain of all IFDs in TIFF
|
||||
pub chain: Vec<IFD>,
|
||||
/// Base offset, starting from file or buffer (good for embedded TIFF in other structures)
|
||||
pub base: u32,
|
||||
/// Offset correction value
|
||||
pub corr: i32,
|
||||
}
|
||||
|
||||
impl TiffFile {
|
||||
pub fn new(base: u32, corr: i32) -> Self {
|
||||
Self { base, corr, chain: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn push_ifd(&mut self, ifd: IFD) {
|
||||
self.chain.push(ifd);
|
||||
}
|
||||
}
|
||||
+679
@@ -0,0 +1,679 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{
|
||||
Entry, Result, TiffError, Value, apply_corr,
|
||||
entry::RawEntry,
|
||||
read_from_file,
|
||||
reader::{EndianReader, ReadByteOrder},
|
||||
};
|
||||
use crate::{
|
||||
bits::Endian,
|
||||
rawsource::RawSource,
|
||||
tags::{ExifTag, TiffCommonTag, TiffTag},
|
||||
};
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
io::{Read, Seek, SeekFrom},
|
||||
};
|
||||
|
||||
const MAX_IFD_ENTRIES: usize = 4096;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OffsetMode {
|
||||
Absolute,
|
||||
RelativeToIFD,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DataMode {
|
||||
Strips,
|
||||
Tiles,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct IFD {
|
||||
pub offset: u32,
|
||||
pub base: u32,
|
||||
pub corr: i32,
|
||||
pub next_ifd: u32,
|
||||
pub entries: BTreeMap<u16, Entry>,
|
||||
pub endian: Endian,
|
||||
pub sub: HashMap<u16, Vec<IFD>>,
|
||||
pub chain: Vec<IFD>,
|
||||
}
|
||||
|
||||
// TODO: fixme
|
||||
impl IFD {
|
||||
/// Construct new IFD from reader at specific base
|
||||
pub fn new_root<R: Read + Seek>(reader: &mut R, base: u32) -> Result<IFD> {
|
||||
Self::new_root_with_correction(reader, 0, base, 0, 10, &[TiffCommonTag::SubIFDs.into(), TiffCommonTag::ExifIFDPointer.into()])
|
||||
}
|
||||
|
||||
pub fn new_root_with_correction<R: Read + Seek>(reader: &mut R, offset: u32, base: u32, corr: i32, max_chain: usize, sub_tags: &[u16]) -> Result<IFD> {
|
||||
reader.seek(SeekFrom::Start((base + offset) as u64))?;
|
||||
let endian = match reader.read_u16::<LittleEndian>()? {
|
||||
0x4949 => Endian::Little,
|
||||
0x4d4d => Endian::Big,
|
||||
x => {
|
||||
return Err(TiffError::General(format!("TIFF: don't know marker 0x{:x}", x)));
|
||||
}
|
||||
};
|
||||
let mut reader = EndianReader::new(reader, endian);
|
||||
let magic = reader.read_u16()?;
|
||||
if magic != 42 {
|
||||
return Err(TiffError::General(format!("Invalid magic marker for TIFF: {}", magic)));
|
||||
}
|
||||
let mut next_ifd = reader.read_u32()?;
|
||||
if next_ifd == 0 {
|
||||
return Err(TiffError::General("Invalid TIFF header, contains no root IFD".to_string()));
|
||||
}
|
||||
|
||||
let reader = reader.into_inner();
|
||||
let mut multi_sub_tags = vec![];
|
||||
multi_sub_tags.extend_from_slice(sub_tags);
|
||||
|
||||
next_ifd = apply_corr(next_ifd, corr);
|
||||
let mut root = IFD::new(reader, next_ifd, base, corr, endian, &multi_sub_tags)?;
|
||||
if root.entries.is_empty() {
|
||||
return Err(TiffError::General("TIFF is invalid, IFD must contain at least one entry".to_string()));
|
||||
}
|
||||
next_ifd = root.next_ifd;
|
||||
|
||||
while next_ifd != 0 {
|
||||
next_ifd = apply_corr(next_ifd, corr);
|
||||
let ifd = IFD::new(reader, next_ifd, base, corr, endian, &multi_sub_tags)?;
|
||||
if ifd.entries.is_empty() {
|
||||
return Err(TiffError::General("TIFF is invalid, IFD must contain at least one entry".to_string()));
|
||||
}
|
||||
next_ifd = ifd.next_ifd;
|
||||
root.chain.push(ifd);
|
||||
|
||||
if root.chain.len() > max_chain && max_chain > 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(root)
|
||||
}
|
||||
|
||||
pub fn new<R: Read + Seek>(reader: &mut R, offset: u32, base: u32, corr: i32, endian: Endian, sub_tags: &[u16]) -> Result<IFD> {
|
||||
reader.seek(SeekFrom::Start((base + offset) as u64))?;
|
||||
let mut sub_ifd_offsets = HashMap::new();
|
||||
let mut reader = EndianReader::new(reader, endian);
|
||||
let entry_count = reader.read_u16()?;
|
||||
|
||||
if entry_count as usize > MAX_IFD_ENTRIES {
|
||||
log::warn!(
|
||||
"TIFF: IFD entry count {} is suspicious (limit {}). The file might be corrupt.",
|
||||
entry_count,
|
||||
MAX_IFD_ENTRIES
|
||||
);
|
||||
}
|
||||
|
||||
let mut entries = BTreeMap::new();
|
||||
let mut sub = HashMap::new();
|
||||
let mut next_pos = reader.position()?;
|
||||
debug!("Parse entries");
|
||||
let mut consecutive_errors = 0;
|
||||
|
||||
for i in 0..entry_count {
|
||||
if i as usize >= MAX_IFD_ENTRIES {
|
||||
log::warn!(
|
||||
"TIFF: Reached maximum IFD entry limit ({}). Stopping parse to prevent infinite loops.",
|
||||
MAX_IFD_ENTRIES
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if let Err(e) = reader.goto(next_pos) {
|
||||
log::warn!("Truncated IFD: Could not seek to next entry position. Stopping parse. Error: {}", e);
|
||||
break;
|
||||
}
|
||||
|
||||
next_pos += 12;
|
||||
|
||||
let tag = match reader.read_u16() {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
log::warn!("Truncated IFD: Could not read tag ID (Index {}). Stopping parse. Error: {}", i, e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
match Entry::parse(&mut reader, base, corr, tag) {
|
||||
Ok(entry) => {
|
||||
consecutive_errors = 0;
|
||||
|
||||
if sub_tags.contains(&tag) {
|
||||
match &entry.value {
|
||||
Value::Long(offsets) => {
|
||||
sub_ifd_offsets.insert(tag, offsets.clone());
|
||||
}
|
||||
Value::Unknown(tag, offsets) => {
|
||||
sub_ifd_offsets.insert(*tag, vec![offsets[0] as u32]);
|
||||
}
|
||||
Value::Undefined(_) => {
|
||||
sub_ifd_offsets.insert(tag, vec![entry.offset().unwrap() as u32]);
|
||||
}
|
||||
val => {
|
||||
log::info!(
|
||||
"Found IFD offset tag, but type mismatch: {:?}. Ignoring SubIFD parsing for tag 0x{:X}",
|
||||
val,
|
||||
tag
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
entries.insert(entry.tag, entry);
|
||||
}
|
||||
Err(err) => {
|
||||
consecutive_errors += 1;
|
||||
log::warn!("Failed to parse TIFF tag 0x{:X} (Index {}). Error: {:?}", tag, i, err);
|
||||
|
||||
// If we fail 5 times in a row, the IFD is likely garbage or physically truncated.
|
||||
if consecutive_errors >= 5 {
|
||||
log::warn!("Too many consecutive parsing errors ({}). Stopping parse to prevent flood.", consecutive_errors);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some TIFF writers skip the next ifd pointer
|
||||
// If we get an I/O error, we fallback to 0, signaling the end of IFD chains.
|
||||
let next_ifd = match reader.read_u32() {
|
||||
Ok(ptr) => ptr,
|
||||
Err(e) => {
|
||||
debug!(
|
||||
"TIFF IFD reader failed to get next IFD pointer, fallback to 0 and continue. Original error was: {}",
|
||||
e
|
||||
);
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
// Process SubIFDs
|
||||
let pos = reader.position()?;
|
||||
let reader = reader.into_inner();
|
||||
for subs in sub_ifd_offsets {
|
||||
let mut ifds = Vec::new();
|
||||
for offset in subs.1 {
|
||||
match Self::new(reader, apply_corr(offset, corr), base, corr, endian, &[]) {
|
||||
Ok(ifd) => ifds.push(ifd),
|
||||
Err(err) => {
|
||||
log::warn!("Error while processing TIFF sub-IFD for tag 0x{:X}, ignoring it: {}", subs.0, err);
|
||||
}
|
||||
};
|
||||
}
|
||||
sub.insert(subs.0, ifds);
|
||||
}
|
||||
EndianReader::new(reader, endian).goto(pos)?; // restore
|
||||
Ok(IFD {
|
||||
offset,
|
||||
base,
|
||||
corr,
|
||||
next_ifd: if next_ifd == 0 { 0 } else { apply_corr(next_ifd, corr) },
|
||||
entries,
|
||||
endian,
|
||||
sub,
|
||||
chain: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn copy_tag(dst: &mut Self, src: &Self, tag: impl Into<u16>) {
|
||||
if let Some(entry) = src.get_entry(tag.into()) {
|
||||
dst.entries.insert(entry.tag, entry.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value_iter(&self) -> impl Iterator<Item = (&u16, &Value)> {
|
||||
self.entries().iter().map(|(tag, entry)| (tag, &entry.value))
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn new<R: Read + Seek>(reader: &mut R, offset: u32, base: u32, corr: i32, endian: Endian, sub_tags: &[u16]) -> Result<Self> {
|
||||
reader.seek(SeekFrom::Start((base + offset) as u64))?;
|
||||
let mut sub_ifd_offsets = Vec::new();
|
||||
let mut reader = EndianReader::new(reader, endian);
|
||||
let entry_count = reader.read_u16()?;
|
||||
let mut entries = BTreeMap::new();
|
||||
let mut sub = Vec::new();
|
||||
for _ in 0..entry_count {
|
||||
//let embedded = reader.read_u32()?;
|
||||
let tag = reader.read_u16()?;
|
||||
if tag == LegacyTiffRootTag::SubIFDs.into() || sub_tags.contains(&tag) {
|
||||
let entry = Entry::parse(&mut reader, base, corr, tag)?;
|
||||
match entry.value {
|
||||
Value::Long(offsets) => {
|
||||
sub_ifd_offsets.extend_from_slice(&offsets);
|
||||
}
|
||||
_ => {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let entry = Entry::parse(&mut reader, base, corr, tag)?;
|
||||
entries.insert(entry.tag, entry);
|
||||
}
|
||||
}
|
||||
let next_ifd = reader.read_u32()?;
|
||||
|
||||
// Process SubIFDs
|
||||
let pos = reader.position()?;
|
||||
let reader = reader.into_inner();
|
||||
for offset in sub_ifd_offsets {
|
||||
let ifd = IFD::new(reader, apply_corr(offset, corr), base, corr, endian, sub_tags)?;
|
||||
sub.push(ifd);
|
||||
}
|
||||
EndianReader::new(reader, endian).goto(pos)?; // restore
|
||||
|
||||
Ok(Self {
|
||||
offset,
|
||||
base,
|
||||
corr,
|
||||
next_ifd: if next_ifd == 0 { 0 } else { apply_corr(next_ifd, corr) },
|
||||
entries,
|
||||
endian,
|
||||
sub,
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
/// Extend the IFD with sub-IFDs from a specific tag.
|
||||
/// The IFD corrections are used from current IFD.
|
||||
pub fn extend_sub_ifds<R: Read + Seek>(&mut self, reader: &mut R, tag: u16) -> Result<Option<&Vec<Self>>> {
|
||||
if let Some(entry) = self.get_entry(tag) {
|
||||
let mut subs = Vec::new();
|
||||
match &entry.value {
|
||||
Value::Long(offsets) => {
|
||||
for off in offsets {
|
||||
let ifd = Self::new_root_with_correction(reader, *off, self.base, self.corr, 10, &[])?;
|
||||
subs.push(ifd);
|
||||
}
|
||||
self.sub.insert(tag, subs);
|
||||
Ok(self.sub.get(&tag))
|
||||
}
|
||||
val => {
|
||||
debug!("Found IFD offset tag, but type mismatch: {:?}", val);
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extend_sub_ifds_custom<R, F>(&mut self, reader: &mut R, tag: u16, op: F) -> Result<Option<&Vec<Self>>>
|
||||
where
|
||||
R: Read + Seek,
|
||||
F: FnOnce(&mut R, &IFD, &Entry) -> Result<Option<Vec<IFD>>>,
|
||||
{
|
||||
if let Some(entry) = self.get_entry(tag) {
|
||||
if let Some(subs) = op(reader, self, entry)? {
|
||||
self.sub.insert(tag, subs);
|
||||
Ok(self.sub.get(&tag))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sub_ifds(&self) -> &HashMap<u16, Vec<IFD>> {
|
||||
&self.sub
|
||||
}
|
||||
|
||||
pub fn entry_count(&self) -> u16 {
|
||||
self.entries.len() as u16
|
||||
}
|
||||
|
||||
pub fn next_ifd(&self) -> u32 {
|
||||
self.next_ifd
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> &BTreeMap<u16, Entry> {
|
||||
&self.entries
|
||||
}
|
||||
|
||||
pub fn get_entry<T: TiffTag>(&self, tag: T) -> Option<&Entry> {
|
||||
self.entries.get(&tag.into())
|
||||
}
|
||||
|
||||
pub fn get_entry_subs<T: TiffTag>(&self, tag: T) -> Option<&Entry> {
|
||||
for subs in &self.sub {
|
||||
for ifd in subs.1 {
|
||||
if let Some(entry) = ifd.get_entry_recursive(tag) {
|
||||
return Some(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_entry_recursive<T: TiffTag>(&self, tag: T) -> Option<&Entry> {
|
||||
self.entries.get(&tag.into()).or_else(|| self.get_entry_subs(tag))
|
||||
}
|
||||
|
||||
pub fn get_entry_raw<'a, T: TiffTag, R: Read + Seek>(&'a self, tag: T, file: &mut R) -> Result<Option<RawEntry<'a>>> {
|
||||
if let Some(entry) = self.get_entry(tag) {
|
||||
return Ok(Some(RawEntry {
|
||||
entry,
|
||||
endian: self.endian,
|
||||
data: read_from_file(file, self.base + entry.offset().unwrap() as u32, entry.byte_size())?,
|
||||
}));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Get the data of a tag by just reading as many `len` bytes from offet.
|
||||
pub fn get_entry_raw_with_len<'a, T: TiffTag, R: Read + Seek>(&'a self, tag: T, file: &mut R, len: usize) -> Result<Option<RawEntry<'a>>> {
|
||||
if let Some(entry) = self.get_entry(tag) {
|
||||
return Ok(Some(RawEntry {
|
||||
entry,
|
||||
endian: self.endian,
|
||||
data: read_from_file(file, self.base + entry.offset().unwrap() as u32, len)?,
|
||||
}));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn get_sub_ifd_all<T: TiffTag>(&self, tag: T) -> Option<&Vec<IFD>> {
|
||||
self.sub.get(&tag.into())
|
||||
}
|
||||
|
||||
pub fn get_sub_ifd<T: TiffTag>(&self, tag: T) -> Option<&IFD> {
|
||||
if let Some(ifds) = self.get_sub_ifd_all(tag) {
|
||||
if ifds.len() == 1 {
|
||||
ifds.get(0)
|
||||
} else {
|
||||
log::warn!(
|
||||
"get_sub_ifd() for tag {:?} found more IFDs than expected: {}. Fallback to first IFD!",
|
||||
tag,
|
||||
ifds.len()
|
||||
);
|
||||
ifds.get(0)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_new_sub_file_type(&self) -> Option<u16> {
|
||||
if let Some(entry) = self.get_entry(TiffCommonTag::NewSubFileType) {
|
||||
Some(entry.force_u16(0))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_ifds_with_tag<T: TiffTag>(&self, tag: T) -> Vec<&IFD> {
|
||||
let mut ifds = Vec::new();
|
||||
if self.get_entry(tag).is_some() {
|
||||
ifds.push(self);
|
||||
}
|
||||
// Now search in all sub IFDs
|
||||
for subs in self.sub_ifds() {
|
||||
for ifd in subs.1 {
|
||||
ifds.append(&mut ifd.find_ifds_with_tag(tag));
|
||||
}
|
||||
}
|
||||
ifds
|
||||
}
|
||||
|
||||
pub fn find_first_ifd_with_tag<T: TiffTag>(&self, tag: T) -> Option<&IFD> {
|
||||
self.find_ifds_with_tag(tag).get(0).copied()
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn get_ifd<T: TiffTagEnum, R: Read + Seek>(&self, tag: T, reader: &mut R) -> Result<Option<IFD>> {
|
||||
if let Some(offset) = self.get_entry(tag) {
|
||||
match &offset.value {
|
||||
Value::Long(v) => {
|
||||
debug!("IFD offset: {}", v[0]);
|
||||
Ok(Some(IFD::new(reader, apply_corr(v[0], self.corr), self.base, self.corr, self.endian, &[])?))
|
||||
}
|
||||
_ => {
|
||||
return Err(TiffError::General(format!(
|
||||
"TIFF tag {:?} is not of type LONG, thus can not be used as IFD offset in get_ifd().",
|
||||
tag
|
||||
)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn has_entry<T: TiffTag>(&self, tag: T) -> bool {
|
||||
self.get_entry(tag).is_some()
|
||||
}
|
||||
|
||||
pub fn sub_buf<R: Read + Seek>(&self, reader: &mut R, offset: usize, len: usize) -> Result<Vec<u8>> {
|
||||
//&buf[self.start_offset+offset..self.start_offset+offset+len]
|
||||
let mut buf = vec![0; len];
|
||||
reader.seek(SeekFrom::Start(self.base as u64 + offset as u64))?;
|
||||
reader.read_exact(&mut buf)?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
pub fn contains_singlestrip_image(&self) -> bool {
|
||||
self.get_entry(TiffCommonTag::StripOffsets).map(Entry::count).unwrap_or(0) == 1
|
||||
}
|
||||
|
||||
pub fn singlestrip_data_rawsource<'a>(&self, rawsource: &'a RawSource) -> Result<&'a [u8]> {
|
||||
assert!(self.contains_singlestrip_image());
|
||||
|
||||
let offset = self
|
||||
.get_entry(TiffCommonTag::StripOffsets)
|
||||
.ok_or_else(|| TiffError::General(("tag not found").to_string()))?
|
||||
.value
|
||||
.force_u32(0);
|
||||
let len = self
|
||||
.get_entry(TiffCommonTag::StripByteCounts)
|
||||
.ok_or_else(|| TiffError::General(("tag not found").to_string()))?
|
||||
.value
|
||||
.force_usize(0);
|
||||
|
||||
Ok(rawsource.subview((self.base + offset) as u64, len as u64)?)
|
||||
}
|
||||
|
||||
/// Return byte slices to strip data.
|
||||
/// If there exists a single strip only or if all strips are continous,
|
||||
/// the second return value contains the whole strip data in a single slice.
|
||||
pub fn strip_data<'a>(&self, rawsource: &'a RawSource) -> Result<(Vec<&'a [u8]>, Option<&'a [u8]>)> {
|
||||
if !self.has_entry(TiffCommonTag::StripOffsets) {
|
||||
return Err(TiffError::General("IFD contains no strip data".into()));
|
||||
}
|
||||
let offsets = if let Some(Entry { value: Value::Long(data), .. }) = self.get_entry(TiffCommonTag::StripOffsets) {
|
||||
data
|
||||
} else {
|
||||
return Err(TiffError::General("Invalid datatype for StripOffsets".to_string()));
|
||||
};
|
||||
let sizes = if let Some(Entry { value: Value::Long(data), .. }) = self.get_entry(TiffCommonTag::StripByteCounts) {
|
||||
data
|
||||
} else {
|
||||
return Err(TiffError::General("Invalid datatype for StripByteCounts".to_string()));
|
||||
};
|
||||
|
||||
if offsets.len() != sizes.len() {
|
||||
return Err(TiffError::General(format!(
|
||||
"Can't get data from strips: offsets has len {} but sizes has len {}",
|
||||
offsets.len(),
|
||||
sizes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
// Check if all slices are continous
|
||||
let (is_continous, end_off) =
|
||||
offsets.iter().zip(sizes.iter()).fold(
|
||||
(true, offsets[0]),
|
||||
|acc, val| {
|
||||
if acc.0 && acc.1 == *val.0 { (true, acc.1 + *val.1) } else { (false, 0) }
|
||||
},
|
||||
);
|
||||
|
||||
let mut subviews = Vec::with_capacity(offsets.len());
|
||||
for (offset, size) in offsets.iter().zip(sizes.iter()) {
|
||||
subviews.push(rawsource.subview((self.base + *offset) as u64, *size as u64)?);
|
||||
}
|
||||
|
||||
let continous = if is_continous {
|
||||
Some(rawsource.subview((self.base + offsets[0]) as u64, (end_off - offsets[0]) as u64)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok((subviews, continous))
|
||||
}
|
||||
|
||||
pub fn tile_data<'a>(&self, rawsource: &'a RawSource) -> Result<Vec<&'a [u8]>> {
|
||||
let offsets = if let Some(Entry { value: Value::Long(data), .. }) = self.get_entry(TiffCommonTag::TileOffsets) {
|
||||
data
|
||||
} else {
|
||||
return Err(TiffError::General("Invalid datatype for TileOffsets".to_string()));
|
||||
};
|
||||
|
||||
let byte_counts = if let Some(Entry { value: Value::Long(data), .. }) = self.get_entry(TiffCommonTag::TileByteCounts) {
|
||||
data
|
||||
} else {
|
||||
return Err(TiffError::General("Invalid datatype for TileByteCounts".to_string()));
|
||||
};
|
||||
|
||||
let mut tile_slices = Vec::with_capacity(offsets.len());
|
||||
offsets.iter().zip(byte_counts.iter()).for_each(|(offset, size)| {
|
||||
tile_slices.push(rawsource.subview(*offset as u64, *size as u64).map_err(TiffError::Io));
|
||||
});
|
||||
Ok(tile_slices.into_iter().collect::<Result<Vec<_>>>()?)
|
||||
}
|
||||
|
||||
/// Check for the data mode (Strips or Tiles)
|
||||
pub fn data_mode(&self) -> Result<DataMode> {
|
||||
if self.has_entry(TiffCommonTag::StripOffsets) {
|
||||
Ok(DataMode::Strips)
|
||||
} else if self.has_entry(TiffCommonTag::TileOffsets) {
|
||||
Ok(DataMode::Tiles)
|
||||
} else {
|
||||
Err(TiffError::General("IFD has no StripOffsets or TileOffsets tag".into()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_makernote<R: Read + Seek>(&self, reader: &mut R, offset_mode: OffsetMode, sub_tags: &[u16]) -> Result<Option<IFD>> {
|
||||
if let Some(exif) = self.get_entry(ExifTag::MakerNotes) {
|
||||
let offset = exif.offset().unwrap() as u32;
|
||||
debug!("Makernote offset: {}", offset);
|
||||
match &exif.value {
|
||||
Value::Undefined(data) => {
|
||||
let mut off = 0;
|
||||
let mut endian = self.endian;
|
||||
|
||||
// Olympus starts the makernote with their own name, sometimes truncated
|
||||
if data[0..5] == b"OLYMP"[..] {
|
||||
off += 8;
|
||||
if data[0..7] == b"OLYMPUS"[..] {
|
||||
off += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Epson starts the makernote with its own name
|
||||
if data[0..5] == b"EPSON"[..] {
|
||||
off += 8;
|
||||
}
|
||||
|
||||
// Fujifilm has 12 extra bytes
|
||||
if data[0..8] == b"FUJIFILM"[..] {
|
||||
off += 12;
|
||||
}
|
||||
|
||||
// Sony has 12 extra bytes
|
||||
if data[0..9] == b"SONY DSC "[..] {
|
||||
off += 12;
|
||||
}
|
||||
|
||||
// Pentax makernote starts with AOC\0 - If it's there, skip it
|
||||
if data[0..4] == b"AOC\0"[..] {
|
||||
off += 4;
|
||||
}
|
||||
|
||||
// Pentax can also start with PENTAX and in that case uses different offsets
|
||||
if data[0..6] == b"PENTAX"[..] {
|
||||
off += 8;
|
||||
let endian = if data[off..off + 2] == b"II"[..] { Endian::Little } else { Endian::Big };
|
||||
// All offsets in this IFD are relative to the start of this tag,
|
||||
// so wie use the offset as correction value.
|
||||
let corr = offset as i32;
|
||||
// The IFD itself starts 10 bytes after tag offset.
|
||||
return Ok(Some(IFD::new(reader, offset + 10, self.base, corr, endian, sub_tags)?));
|
||||
}
|
||||
|
||||
if data[0..7] == b"Nikon\0\x02"[..] {
|
||||
off += 10;
|
||||
let endian = if data[off..off + 2] == b"II"[..] { Endian::Little } else { Endian::Big };
|
||||
return Ok(Some(IFD::new(reader, 8, self.base + offset + 10, 0, endian, sub_tags)?));
|
||||
}
|
||||
|
||||
// Some have MM or II to indicate endianness - read that
|
||||
if data[off..off + 2] == b"II"[..] {
|
||||
off += 2;
|
||||
endian = Endian::Little;
|
||||
}
|
||||
if data[off..off + 2] == b"MM"[..] {
|
||||
off += 2;
|
||||
endian = Endian::Big;
|
||||
}
|
||||
|
||||
match offset_mode {
|
||||
OffsetMode::Absolute => Ok(Some(IFD::new(reader, offset + off as u32, self.base, self.corr, endian, sub_tags)?)),
|
||||
OffsetMode::RelativeToIFD => {
|
||||
// Value offsets are relative to IFD offset
|
||||
let corr = offset + off as u32;
|
||||
Ok(Some(IFD::new(reader, offset + off as u32, self.base, corr as i32, endian, sub_tags)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Err(TiffError::General("EXIF makernote has unknown type".to_string())),
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump<T: TiffTag>(&self, limit: usize) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
out.push(format!("IFD entries: {}\n", self.entries.len()));
|
||||
out.push(format!("{0:<34} | {1:<10} | {2:<6} | {3}\n", "Tag", "Type", "Count", "Data"));
|
||||
for (tag, entry) in &self.entries {
|
||||
let mut line = String::new();
|
||||
let tag_name = {
|
||||
if let Ok(name) = T::try_from(*tag) {
|
||||
format!("{:?}", name)
|
||||
} else {
|
||||
format!("<?{}>", tag)
|
||||
}
|
||||
};
|
||||
line.push_str(&format!(
|
||||
"{0:#06x} : {0:<6} {1:<20}| {2:<10} | {3:<6} | ",
|
||||
tag,
|
||||
tag_name,
|
||||
entry.type_name(),
|
||||
entry.count()
|
||||
));
|
||||
line.push_str(&entry.visual_rep(limit));
|
||||
out.push(line);
|
||||
}
|
||||
for subs in self.sub_ifds().iter() {
|
||||
for (i, sub) in subs.1.iter().enumerate() {
|
||||
out.push(format!("SubIFD({}:{})", subs.0, i));
|
||||
for line in sub.dump::<T>(limit) {
|
||||
out.push(format!(" {}", line));
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod entry;
|
||||
pub mod file;
|
||||
pub mod ifd;
|
||||
pub mod reader;
|
||||
pub mod value;
|
||||
pub mod writer;
|
||||
|
||||
pub use entry::Entry;
|
||||
pub use ifd::IFD;
|
||||
pub use reader::GenericTiffReader;
|
||||
pub use value::{IntoTiffValue, Rational, SRational, TiffAscii, Value};
|
||||
pub use writer::{DirectoryWriter, TiffWriter};
|
||||
|
||||
const TIFF_MAGIC: u16 = 42;
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum CompressionMethod {
|
||||
None = 1,
|
||||
Huffman = 2,
|
||||
Fax3 = 3,
|
||||
Fax4 = 4,
|
||||
LZW = 5,
|
||||
OldJPEG = 6, // TODO: RENAME
|
||||
// "Extended JPEG" or "new JPEG" style
|
||||
ModernJPEG = 7,
|
||||
Deflate = 8,
|
||||
OldDeflate = 0x80B2,
|
||||
PackBits = 0x8005,
|
||||
// DNG Extensions
|
||||
LossyJPEG = 34892,
|
||||
JPEGXL = 52546,
|
||||
}
|
||||
|
||||
impl ExtractFromIFD for CompressionMethod {
|
||||
fn extract(ifd: &IFD) -> Result<Option<Self>> {
|
||||
let tag = TiffCommonTag::Compression;
|
||||
if let Some(entry) = ifd.get_entry(tag) {
|
||||
let x = entry.value.force_u16(0);
|
||||
Ok(Some(
|
||||
Self::n(x).ok_or_else(|| TiffError::General(format!("Unknown value {} for {:?}", x, tag)))?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CompressionMethod> for Value {
|
||||
fn from(value: CompressionMethod) -> Self {
|
||||
Value::Short(vec![value as u16])
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum PhotometricInterpretation {
|
||||
WhiteIsZero = 0,
|
||||
BlackIsZero = 1,
|
||||
RGB = 2,
|
||||
RGBPalette = 3,
|
||||
TransparencyMask = 4,
|
||||
CMYK = 5,
|
||||
YCbCr = 6,
|
||||
CIELab = 8,
|
||||
// Defined by DNG
|
||||
CFA = 32803,
|
||||
LinearRaw = 34892,
|
||||
}
|
||||
|
||||
impl ExtractFromIFD for PhotometricInterpretation {
|
||||
fn extract(ifd: &IFD) -> Result<Option<Self>> {
|
||||
let tag = TiffCommonTag::PhotometricInt;
|
||||
if let Some(entry) = ifd.get_entry(tag) {
|
||||
let x = entry.value.force_u16(0);
|
||||
Ok(Some(
|
||||
Self::n(x).ok_or_else(|| TiffError::General(format!("Unknown value {} for {:?}", x, tag)))?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PhotometricInterpretation> for Value {
|
||||
fn from(value: PhotometricInterpretation) -> Self {
|
||||
Value::Short(vec![value as u16])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum PreviewColorSpace {
|
||||
Unknown = 0,
|
||||
GrayGamma = 1,
|
||||
SRgb = 2,
|
||||
AdobeRGB = 3,
|
||||
ProPhotoRGB = 5,
|
||||
}
|
||||
|
||||
impl ExtractFromIFD for PreviewColorSpace {
|
||||
fn extract(ifd: &IFD) -> Result<Option<Self>> {
|
||||
let tag = DngTag::PreviewColorSpace;
|
||||
if let Some(entry) = ifd.get_entry(tag) {
|
||||
let x = entry.value.force_u16(0);
|
||||
Ok(Some(
|
||||
Self::n(x).ok_or_else(|| TiffError::General(format!("Unknown value {} for {:?}", x, tag)))?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PreviewColorSpace> for Value {
|
||||
fn from(value: PreviewColorSpace) -> Self {
|
||||
Value::Long(vec![value as u32])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum PlanarConfiguration {
|
||||
Chunky = 1,
|
||||
Planar = 2,
|
||||
}
|
||||
|
||||
impl ExtractFromIFD for PlanarConfiguration {
|
||||
fn extract(ifd: &IFD) -> Result<Option<Self>> {
|
||||
let tag = ExifTag::PlanarConfiguration;
|
||||
if let Some(entry) = ifd.get_entry(tag) {
|
||||
let x = entry.value.force_u16(0);
|
||||
Ok(Some(
|
||||
Self::n(x).ok_or_else(|| TiffError::General(format!("Unknown value {} for {:?}", x, tag)))?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PlanarConfiguration> for Value {
|
||||
fn from(value: PlanarConfiguration) -> Self {
|
||||
Value::Short(vec![value as u16])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum Predictor {
|
||||
None = 1,
|
||||
Horizontal = 2,
|
||||
}
|
||||
|
||||
impl ExtractFromIFD for Predictor {
|
||||
fn extract(ifd: &IFD) -> Result<Option<Self>> {
|
||||
let tag = TiffCommonTag::Predictor;
|
||||
if let Some(entry) = ifd.get_entry(tag) {
|
||||
let x = entry.value.force_u16(0);
|
||||
Ok(Some(
|
||||
Self::n(x).ok_or_else(|| TiffError::General(format!("Unknown value {} for {:?}", x, tag)))?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Predictor> for Value {
|
||||
fn from(value: Predictor) -> Self {
|
||||
Value::Short(vec![value as u16])
|
||||
}
|
||||
}
|
||||
|
||||
/// Type to represent resolution units
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum ResolutionUnit {
|
||||
None = 1,
|
||||
Inch = 2,
|
||||
Centimeter = 3,
|
||||
}
|
||||
|
||||
impl ExtractFromIFD for ResolutionUnit {
|
||||
fn extract(ifd: &IFD) -> Result<Option<Self>> {
|
||||
let tag = TiffCommonTag::Predictor;
|
||||
if let Some(entry) = ifd.get_entry(tag) {
|
||||
let x = entry.value.force_u16(0);
|
||||
Ok(Some(
|
||||
Self::n(x).ok_or_else(|| TiffError::General(format!("Unknown value {} for {:?}", x, tag)))?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResolutionUnit> for Value {
|
||||
fn from(value: ResolutionUnit) -> Self {
|
||||
Value::Short(vec![value as u16])
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, enumn::N)]
|
||||
#[repr(u16)]
|
||||
pub enum SampleFormat {
|
||||
Uint = 1,
|
||||
Int = 2,
|
||||
IEEEFP = 3,
|
||||
Void = 4,
|
||||
}
|
||||
|
||||
impl From<SampleFormat> for Value {
|
||||
fn from(value: SampleFormat) -> Self {
|
||||
Value::Short(vec![value as u16])
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractFromIFD for SampleFormat {
|
||||
fn extract(ifd: &IFD) -> Result<Option<Self>> {
|
||||
let tag = TiffCommonTag::SampleFormat;
|
||||
if let Some(entry) = ifd.get_entry(tag) {
|
||||
let x = entry.value.force_u16(0);
|
||||
Ok(Some(
|
||||
Self::n(x).ok_or_else(|| TiffError::General(format!("Unknown value {} for {:?}", x, tag)))?,
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExtractFromIFD
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn extract(ifd: &IFD) -> Result<Option<Self>>;
|
||||
}
|
||||
|
||||
/// Error variants for compressor
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TiffError {
|
||||
/// 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),
|
||||
}
|
||||
|
||||
/// Result type for Compressor results
|
||||
pub type Result<T> = std::result::Result<T, TiffError>;
|
||||
|
||||
use crate::tags::{DngTag, ExifTag, TiffCommonTag};
|
||||
|
||||
/*
|
||||
impl From<Value> for Entry {
|
||||
fn from(value: Value) -> Self {
|
||||
Entry { value, embedded: None }
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub struct DataOffset {
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
fn apply_corr(offset: u32, corr: i32) -> u32 {
|
||||
((offset as i64) + (corr as i64)) as u32
|
||||
}
|
||||
|
||||
pub struct DirReader {}
|
||||
|
||||
fn read_from_file<R: Read + Seek>(file: &mut R, offset: u32, size: usize) -> Result<Vec<u8>> {
|
||||
file.seek(SeekFrom::Start(offset as u64))?;
|
||||
let mut buf = vec![0; size];
|
||||
file.read_exact(&mut buf)?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::{Cursor, Seek, SeekFrom};
|
||||
|
||||
use crate::{formats::tiff::reader::TiffReader, tags::TiffCommonTag};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn encode_tiff_test() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
let mut buf = Cursor::new(Vec::new());
|
||||
let mut tiff = TiffWriter::new(&mut buf)?;
|
||||
let mut dir = tiff.new_directory();
|
||||
|
||||
let offset = {
|
||||
let mut dir2 = tiff.new_directory();
|
||||
dir2.add_tag(32_u16, 23_u16);
|
||||
dir2.build(&mut tiff)?
|
||||
};
|
||||
|
||||
dir.add_tag(TiffCommonTag::ActiveArea, offset as u16);
|
||||
dir.add_tag(TiffCommonTag::ActiveArea, [23_u16, 45_u16]);
|
||||
dir.add_tag(TiffCommonTag::ActiveArea, &[23_u16, 45_u16][..]);
|
||||
dir.add_tag(TiffCommonTag::ActiveArea, "Fobbar");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_tiff_file_basic() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
let mut output = Cursor::new(Vec::new());
|
||||
let mut tiff = TiffWriter::new(&mut output)?;
|
||||
|
||||
let mut dir = tiff.new_directory();
|
||||
|
||||
let offset = {
|
||||
let mut dir2 = tiff.new_directory();
|
||||
dir2.add_tag(32_u16, 23_u16);
|
||||
dir2.build(&mut tiff)?
|
||||
};
|
||||
|
||||
dir.add_tag(TiffCommonTag::ExifIFDPointer, offset);
|
||||
|
||||
dir.add_tag(TiffCommonTag::ActiveArea, [9_u16, 10_u16, 11_u16, 12, 13, 14]);
|
||||
dir.add_tag(TiffCommonTag::BlackLevels, [9_u16, 10_u16]);
|
||||
dir.add_tag(TiffCommonTag::WhiteLevel, [11_u16]);
|
||||
dir.add_tag(TiffCommonTag::BitsPerSample, [12_u32]);
|
||||
dir.add_tag(TiffCommonTag::ResolutionUnit, [-5_i32]);
|
||||
dir.add_tag(TiffCommonTag::Artist, "AT");
|
||||
|
||||
tiff.build(dir)?;
|
||||
|
||||
//assert!(TiffReader::is_tiff(&mut output) == true);
|
||||
|
||||
let mut garbage_output: Vec<u8> = vec![0x4a, 0xee]; // Garbage
|
||||
garbage_output.extend_from_slice(&output.into_inner());
|
||||
|
||||
let mut garbage_output = Cursor::new(garbage_output);
|
||||
|
||||
garbage_output.seek(SeekFrom::Start(2))?;
|
||||
|
||||
let reader = GenericTiffReader::new(&mut garbage_output, 1, 1, Some(16), &[])?; // 1 byte offset correction
|
||||
|
||||
assert_eq!(reader.root_ifd().entry_count(), 7);
|
||||
assert!(reader.root_ifd().get_entry(TiffCommonTag::WhiteLevel).is_some());
|
||||
|
||||
assert!(matches!(
|
||||
reader.root_ifd().get_entry(TiffCommonTag::ExifIFDPointer).unwrap().value,
|
||||
Value::Long { .. }
|
||||
));
|
||||
assert!(matches!(
|
||||
reader.root_ifd().get_entry(TiffCommonTag::WhiteLevel).unwrap().value,
|
||||
Value::Short { .. }
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+429
@@ -0,0 +1,429 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use super::{Entry, IFD, Result, TiffError, apply_corr, entry::RawEntry, file::TiffFile};
|
||||
use crate::{
|
||||
bits::Endian,
|
||||
tags::{ExifTag, TiffCommonTag, TiffTag},
|
||||
};
|
||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
|
||||
pub trait TiffReader {
|
||||
fn file(&self) -> &TiffFile;
|
||||
fn file_mut(&mut self) -> &mut TiffFile;
|
||||
|
||||
fn chains(&self) -> &Vec<IFD> {
|
||||
&self.file().chain
|
||||
}
|
||||
|
||||
fn get_endian(&self) -> Endian {
|
||||
self.root_ifd().endian
|
||||
}
|
||||
|
||||
/// Returns a list of well-known tags representing SubIFDs.
|
||||
fn wellknown_sub_ifd_tags(&self) -> Vec<u16> {
|
||||
vec![
|
||||
TiffCommonTag::SubIFDs.into(),
|
||||
TiffCommonTag::ExifIFDPointer.into(),
|
||||
ExifTag::GPSInfo.into(),
|
||||
ExifTag::IccProfile.into(),
|
||||
]
|
||||
}
|
||||
|
||||
fn root_ifd(&self) -> &IFD {
|
||||
if self.file().chain.is_empty() {
|
||||
panic!("TIFF must have at least one root IFD but the IFD list is empty");
|
||||
}
|
||||
&self.file().chain[0]
|
||||
}
|
||||
|
||||
fn get_entry<T: TiffTag>(&self, tag: T) -> Option<&Entry> {
|
||||
for ifd in &self.file().chain {
|
||||
if let Some(x) = ifd.get_entry(tag) {
|
||||
return Some(x);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_entry_raw<'a, T: TiffTag, R: Read + Seek>(&'a self, tag: T, file: &mut R) -> Result<Option<RawEntry<'a>>> {
|
||||
for ifd in &self.file().chain {
|
||||
if let Some(entry) = ifd.get_entry_raw(tag, file)? {
|
||||
return Ok(Some(entry));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn has_entry<T: TiffTag>(&self, tag: T) -> bool {
|
||||
self.get_entry(tag).is_some()
|
||||
}
|
||||
|
||||
fn find_ifds_with_tag<T: TiffTag>(&self, tag: T) -> Vec<&IFD> {
|
||||
let mut ifds = Vec::new();
|
||||
for ifd in &self.file().chain {
|
||||
if ifd.has_entry(tag) {
|
||||
ifds.push(ifd);
|
||||
}
|
||||
// Now search in all sub IFDs
|
||||
for subs in ifd.sub_ifds() {
|
||||
for ifd in subs.1 {
|
||||
if ifd.has_entry(tag) {
|
||||
ifds.push(ifd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ifds
|
||||
}
|
||||
|
||||
fn find_ifds_with_filter<F: Fn(&IFD) -> bool>(&self, filter: F) -> Vec<&IFD> {
|
||||
let mut ifds = Vec::new();
|
||||
for ifd in &self.file().chain {
|
||||
if filter(ifd) {
|
||||
ifds.push(ifd);
|
||||
}
|
||||
// Now search in all sub IFDs
|
||||
for subs in ifd.sub_ifds() {
|
||||
for ifd in subs.1 {
|
||||
if filter(ifd) {
|
||||
ifds.push(ifd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ifds
|
||||
}
|
||||
|
||||
fn find_ifd_with_new_subfile_type(&self, typ: u32) -> Option<&IFD> {
|
||||
let list = self.find_ifds_with_tag(TiffCommonTag::NewSubFileType);
|
||||
list
|
||||
.iter()
|
||||
.find(|ifd| ifd.get_entry(TiffCommonTag::NewSubFileType).expect("IFD must contain this entry").force_u32(0) == typ)
|
||||
.copied()
|
||||
}
|
||||
|
||||
// TODO: legacy wrapper
|
||||
fn find_first_ifd<T: TiffTag>(&self, tag: T) -> Option<&IFD> {
|
||||
self.find_first_ifd_with_tag(tag)
|
||||
}
|
||||
|
||||
fn find_first_ifd_with_tag<T: TiffTag>(&self, tag: T) -> Option<&IFD> {
|
||||
let ifds = self.find_ifds_with_tag(tag);
|
||||
if ifds.is_empty() { None } else { Some(ifds[0]) }
|
||||
}
|
||||
|
||||
fn get_first_entry(&self, _tag: u16) -> Option<Entry> {
|
||||
unimplemented!();
|
||||
/*
|
||||
Some(Entry {
|
||||
value: (32 as u16).into(),
|
||||
embedded: None,
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
fn read_data<R: Read + Seek>(&self, file: &mut R, uncorr_offset: u32, buffer: &mut [u8]) -> Result<()> {
|
||||
file.seek(SeekFrom::Start(apply_corr(uncorr_offset, self.file().corr) as u64))?;
|
||||
file.read_exact(buffer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_ifd<R: Read + Seek>(&self, reader: &mut R, offset: u32, base: u32, corr: i32, endian: Endian, sub_tags: &[u16]) -> Result<IFD> {
|
||||
IFD::new(reader, offset, base, corr, endian, sub_tags)
|
||||
}
|
||||
|
||||
/// Construct a TIFF reader from Read capable objects
|
||||
///
|
||||
/// `corr` is a correction value that should be applied to offsets received
|
||||
/// from file structure.
|
||||
fn parse_file<R: Read + Seek>(&mut self, file: &mut R, max_chained: Option<usize>, sub_tags: &[u16]) -> Result<()> {
|
||||
let endian = match file.read_u16::<LittleEndian>()? {
|
||||
0x4949 => Endian::Little,
|
||||
0x4d4d => Endian::Big,
|
||||
x => {
|
||||
return Err(TiffError::General(format!("TIFF: don't know marker 0x{:x}", x)));
|
||||
}
|
||||
};
|
||||
let mut reader = EndianReader::new(file, endian);
|
||||
let magic = reader.read_u16()?;
|
||||
if magic != 42 {
|
||||
//return Err(TiffError::General(format!("Invalid magic marker for TIFF: {}", magic)));
|
||||
}
|
||||
let mut next_ifd = reader.read_u32()?;
|
||||
if next_ifd == 0 {
|
||||
return Err(TiffError::General("Invalid TIFF header, contains no root IFD".to_string()));
|
||||
}
|
||||
|
||||
let reader = reader.into_inner();
|
||||
|
||||
next_ifd = apply_corr(next_ifd, self.file().corr);
|
||||
let mut chain = Vec::new();
|
||||
while next_ifd != 0 {
|
||||
// TODO: check if offset is in range
|
||||
let mut multi_sub_tags = self.wellknown_sub_ifd_tags();
|
||||
multi_sub_tags.extend_from_slice(sub_tags);
|
||||
let ifd = IFD::new(reader, next_ifd, self.file().base, self.file().corr, endian, &multi_sub_tags)?;
|
||||
if ifd.entries.is_empty() {
|
||||
return Err(TiffError::General("TIFF is invalid, IFD must contain at least one entry".to_string()));
|
||||
}
|
||||
next_ifd = ifd.next_ifd;
|
||||
chain.push(ifd);
|
||||
if let Some(max) = max_chained {
|
||||
if chain.len() > max {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if chain.is_empty() {
|
||||
return Err(TiffError::General("TIFF is invalid, must contain at least one IFD".to_string()));
|
||||
}
|
||||
self.file_mut().chain = chain;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Reader for TIFF files
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct GenericTiffReader {
|
||||
file: TiffFile,
|
||||
}
|
||||
|
||||
impl TiffReader for GenericTiffReader {
|
||||
fn file(&self) -> &TiffFile {
|
||||
&self.file
|
||||
}
|
||||
|
||||
fn file_mut(&mut self) -> &mut TiffFile {
|
||||
&mut self.file
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericTiffReader {
|
||||
/// Check if buffer looks like a TIFF file
|
||||
pub fn is_tiff<T: AsRef<[u8]>>(buffer: T) -> bool {
|
||||
let buffer = buffer.as_ref();
|
||||
buffer[0] == 0x49 || buffer[0] == 0x4d // TODO
|
||||
}
|
||||
|
||||
pub fn little_endian(&self) -> bool {
|
||||
self.file.chain.first().unwrap().endian == Endian::Little
|
||||
}
|
||||
|
||||
/// Construct a TIFF reader from a byte buffer
|
||||
///
|
||||
/// Byte buffer must be a full TIFF file structure, endianess is detected from TIFF
|
||||
/// header.
|
||||
///
|
||||
/// `corr` is a correction value that should be applied to offsets received
|
||||
/// from file structure.
|
||||
pub fn new_with_buffer<T: AsRef<[u8]>>(buffer: T, base: u32, corr: i32, max_chained: Option<usize>) -> Result<Self> {
|
||||
let mut cursor = Cursor::new(buffer.as_ref());
|
||||
cursor.seek(SeekFrom::Start(base as u64))?;
|
||||
Self::new(&mut cursor, base, corr, max_chained, &[])
|
||||
}
|
||||
|
||||
/// Construct a TIFF reader from Read capable objects
|
||||
///
|
||||
/// `corr` is a correction value that should be applied to offsets received
|
||||
/// from file structure.
|
||||
pub fn new<R: Read + Seek>(file: &mut R, base: u32, corr: i32, max_chained: Option<usize>, sub_tags: &[u16]) -> Result<Self> {
|
||||
let mut ins = Self {
|
||||
file: TiffFile::new(base, corr),
|
||||
};
|
||||
ins.parse_file(file, max_chained, sub_tags)?;
|
||||
Ok(ins)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ReadByteOrder {
|
||||
fn read_u8(&mut self) -> std::io::Result<u8>;
|
||||
fn read_i8(&mut self) -> std::io::Result<i8>;
|
||||
fn read_u16(&mut self) -> std::io::Result<u16>;
|
||||
fn read_i16(&mut self) -> std::io::Result<i16>;
|
||||
fn read_u32(&mut self) -> std::io::Result<u32>;
|
||||
fn read_i32(&mut self) -> std::io::Result<i32>;
|
||||
fn read_u64(&mut self) -> std::io::Result<u64>;
|
||||
fn read_i64(&mut self) -> std::io::Result<i64>;
|
||||
fn read_f32(&mut self) -> std::io::Result<f32>;
|
||||
fn read_f64(&mut self) -> std::io::Result<f64>;
|
||||
|
||||
fn read_u8_into(&mut self, dst: &mut [u8]) -> std::io::Result<()>;
|
||||
fn read_i8_into(&mut self, dst: &mut [i8]) -> std::io::Result<()>;
|
||||
fn read_u16_into(&mut self, dst: &mut [u16]) -> std::io::Result<()>;
|
||||
fn read_i16_into(&mut self, dst: &mut [i16]) -> std::io::Result<()>;
|
||||
fn read_u32_into(&mut self, dst: &mut [u32]) -> std::io::Result<()>;
|
||||
fn read_i32_into(&mut self, dst: &mut [i32]) -> std::io::Result<()>;
|
||||
fn read_u64_into(&mut self, dst: &mut [u64]) -> std::io::Result<()>;
|
||||
fn read_i64_into(&mut self, dst: &mut [i64]) -> std::io::Result<()>;
|
||||
fn read_f32_into(&mut self, dst: &mut [f32]) -> std::io::Result<()>;
|
||||
fn read_f64_into(&mut self, dst: &mut [f64]) -> std::io::Result<()>;
|
||||
}
|
||||
|
||||
pub struct EndianReader<'a, R: Read + Seek + 'a> {
|
||||
endian: Endian,
|
||||
inner: &'a mut R,
|
||||
}
|
||||
|
||||
impl<'a, R: Read + Seek + 'a> EndianReader<'a, R> {
|
||||
pub fn new(inner: &'a mut R, endian: Endian) -> Self {
|
||||
Self { endian, inner }
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> &'a mut R {
|
||||
self.inner
|
||||
}
|
||||
|
||||
pub fn inner(&'a mut self) -> &'a mut R {
|
||||
self.inner
|
||||
}
|
||||
|
||||
pub fn position(&mut self) -> Result<u32> {
|
||||
Ok(self.inner.stream_position().map(|v| v as u32)?)
|
||||
}
|
||||
|
||||
// TODO: try_from?
|
||||
|
||||
pub fn goto(&mut self, offset: u32) -> Result<()> {
|
||||
self.inner.seek(SeekFrom::Start(offset as u64))?;
|
||||
Ok(())
|
||||
|
||||
// TODO: try_from?
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R: Read + Seek + 'a> ReadByteOrder for EndianReader<'a, R> {
|
||||
fn read_u16(&mut self) -> std::io::Result<u16> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_u16::<LittleEndian>(),
|
||||
Endian::Big => self.inner.read_u16::<BigEndian>(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u8(&mut self) -> std::io::Result<u8> {
|
||||
self.inner.read_u8()
|
||||
}
|
||||
|
||||
fn read_i8(&mut self) -> std::io::Result<i8> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_i8(),
|
||||
Endian::Big => self.inner.read_i8(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_i16(&mut self) -> std::io::Result<i16> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_i16::<LittleEndian>(),
|
||||
Endian::Big => self.inner.read_i16::<BigEndian>(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u32(&mut self) -> std::io::Result<u32> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_u32::<LittleEndian>(),
|
||||
Endian::Big => self.inner.read_u32::<BigEndian>(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_i32(&mut self) -> std::io::Result<i32> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_i32::<LittleEndian>(),
|
||||
Endian::Big => self.inner.read_i32::<BigEndian>(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u64(&mut self) -> std::io::Result<u64> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_u64::<LittleEndian>(),
|
||||
Endian::Big => self.inner.read_u64::<BigEndian>(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_i64(&mut self) -> std::io::Result<i64> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_i64::<LittleEndian>(),
|
||||
Endian::Big => self.inner.read_i64::<BigEndian>(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_f32(&mut self) -> std::io::Result<f32> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_f32::<LittleEndian>(),
|
||||
Endian::Big => self.inner.read_f32::<BigEndian>(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_f64(&mut self) -> std::io::Result<f64> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_f64::<LittleEndian>(),
|
||||
Endian::Big => self.inner.read_f64::<BigEndian>(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u8_into(&mut self, dst: &mut [u8]) -> std::io::Result<()> {
|
||||
self.inner.read_exact(dst)
|
||||
}
|
||||
|
||||
fn read_i8_into(&mut self, dst: &mut [i8]) -> std::io::Result<()> {
|
||||
self.inner.read_i8_into(dst)
|
||||
}
|
||||
|
||||
fn read_u16_into(&mut self, dst: &mut [u16]) -> std::io::Result<()> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_u16_into::<LittleEndian>(dst),
|
||||
Endian::Big => self.inner.read_u16_into::<BigEndian>(dst),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_i16_into(&mut self, dst: &mut [i16]) -> std::io::Result<()> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_i16_into::<LittleEndian>(dst),
|
||||
Endian::Big => self.inner.read_i16_into::<BigEndian>(dst),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u32_into(&mut self, dst: &mut [u32]) -> std::io::Result<()> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_u32_into::<LittleEndian>(dst),
|
||||
Endian::Big => self.inner.read_u32_into::<BigEndian>(dst),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_i32_into(&mut self, dst: &mut [i32]) -> std::io::Result<()> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_i32_into::<LittleEndian>(dst),
|
||||
Endian::Big => self.inner.read_i32_into::<BigEndian>(dst),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u64_into(&mut self, dst: &mut [u64]) -> std::io::Result<()> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_u64_into::<LittleEndian>(dst),
|
||||
Endian::Big => self.inner.read_u64_into::<BigEndian>(dst),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_i64_into(&mut self, dst: &mut [i64]) -> std::io::Result<()> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_i64_into::<LittleEndian>(dst),
|
||||
Endian::Big => self.inner.read_i64_into::<BigEndian>(dst),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_f32_into(&mut self, dst: &mut [f32]) -> std::io::Result<()> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_f32_into::<LittleEndian>(dst),
|
||||
Endian::Big => self.inner.read_f32_into::<BigEndian>(dst),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_f64_into(&mut self, dst: &mut [f64]) -> std::io::Result<()> {
|
||||
match self.endian {
|
||||
Endian::Little => self.inner.read_f64_into::<LittleEndian>(dst),
|
||||
Endian::Big => self.inner.read_f64_into::<BigEndian>(dst),
|
||||
}
|
||||
}
|
||||
}
|
||||
+1357
File diff suppressed because it is too large
Load Diff
+412
@@ -0,0 +1,412 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2021 Daniel Vogelbacher <daniel@chaospixel.com>
|
||||
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
io::{Seek, SeekFrom, Write},
|
||||
};
|
||||
|
||||
use byteorder::{LittleEndian, NativeEndian, WriteBytesExt};
|
||||
use image::EncodableLayout;
|
||||
use rayon::prelude::*;
|
||||
use weezl::LzwError;
|
||||
|
||||
use crate::{
|
||||
exif::Exif,
|
||||
imgop::Dim2,
|
||||
tags::{ExifGpsTag, ExifTag, TiffTag},
|
||||
};
|
||||
|
||||
use super::{Entry, Result, TIFF_MAGIC, TiffError, Value};
|
||||
|
||||
pub struct TiffWriter<W> {
|
||||
ifd_location: u64,
|
||||
pub writer: W,
|
||||
}
|
||||
|
||||
impl<W> TiffWriter<W>
|
||||
where
|
||||
W: Write + Seek,
|
||||
{
|
||||
pub fn new(writer: W) -> Result<Self> {
|
||||
let mut tmp = Self { writer, ifd_location: 0 };
|
||||
tmp.write_header()?;
|
||||
Ok(tmp)
|
||||
}
|
||||
|
||||
pub fn new_directory(&self) -> DirectoryWriter {
|
||||
DirectoryWriter::new()
|
||||
}
|
||||
|
||||
fn write_header(&mut self) -> Result<()> {
|
||||
#[cfg(target_endian = "little")]
|
||||
let boi: u8 = 0x49;
|
||||
#[cfg(not(target_endian = "little"))]
|
||||
let boi: u8 = 0x4d;
|
||||
|
||||
self.writer.write_all(&[boi, boi])?;
|
||||
self.writer.write_u16::<NativeEndian>(TIFF_MAGIC)?;
|
||||
self.ifd_location = self.writer.stream_position()?;
|
||||
self.writer.write_u32::<NativeEndian>(0_u32)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_strips_lzw(&mut self, data: &[u16], cpp: usize, dim: Dim2, mut strip_lines: usize) -> Result<(u32, Vec<(u32, u32)>)> {
|
||||
if strip_lines == 0 {
|
||||
if dim.h > 256 {
|
||||
strip_lines = 256;
|
||||
} else {
|
||||
strip_lines = dim.h; // Use single strip
|
||||
}
|
||||
}
|
||||
let mut tag_data = Vec::with_capacity(dim.h / strip_lines + 1);
|
||||
let strips = data
|
||||
.par_chunks(cpp * dim.w * strip_lines)
|
||||
.map(|strip| {
|
||||
let mut encoder = weezl::encode::Encoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
|
||||
encoder.encode(strip.as_bytes())
|
||||
})
|
||||
.collect::<std::result::Result<Vec<Vec<u8>>, LzwError>>()
|
||||
.map_err(|err| TiffError::General(err.to_string()))?;
|
||||
|
||||
for strip in strips {
|
||||
let offset = self.write_data(&strip)?;
|
||||
let bytes = strip.len() as u32;
|
||||
tag_data.push((offset, bytes));
|
||||
}
|
||||
Ok((strip_lines as u32, tag_data))
|
||||
}
|
||||
|
||||
pub fn write_data(&mut self, data: &[u8]) -> Result<u32>
|
||||
where
|
||||
W: Seek + Write,
|
||||
{
|
||||
self.pad_word_boundary()?;
|
||||
let offset = self.position()?;
|
||||
self.writer.write_all(data)?;
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
pub fn write_data_u16_le(&mut self, data: &[u16]) -> Result<u32>
|
||||
where
|
||||
W: Seek + Write,
|
||||
{
|
||||
self.pad_word_boundary()?;
|
||||
let offset = self.position()?;
|
||||
for v in data {
|
||||
self.writer.write_u16::<LittleEndian>(*v)?;
|
||||
}
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
pub fn write_data_f32_le(&mut self, data: &[f32]) -> Result<u32>
|
||||
where
|
||||
W: Seek + Write,
|
||||
{
|
||||
self.pad_word_boundary()?;
|
||||
let offset = self.position()?;
|
||||
for v in data {
|
||||
self.writer.write_f32::<LittleEndian>(*v)?;
|
||||
}
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
pub(crate) fn pad_word_boundary(&mut self) -> Result<()> {
|
||||
if self.position()? % 4 != 0 {
|
||||
let padding = [0, 0, 0];
|
||||
let padd_len = 4 - (self.position()? % 4);
|
||||
self.writer.write_all(&padding[..padd_len as usize])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn build(mut self, root_ifd: DirectoryWriter) -> Result<()> {
|
||||
let ifd0_offset = root_ifd.build(&mut self)?;
|
||||
self.writer.seek(SeekFrom::Start(self.ifd_location))?;
|
||||
self.writer.write_u32::<NativeEndian>(ifd0_offset)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> TiffWriter<W>
|
||||
where
|
||||
W: Seek,
|
||||
{
|
||||
pub fn position(&mut self) -> Result<u32> {
|
||||
Ok(self.writer.stream_position().map(|v| v as u32)?) // TODO: try_from?
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DirectoryWriter {
|
||||
// We use BTreeMap to make sure tags are written in correct order
|
||||
entries: BTreeMap<u16, Entry>,
|
||||
next_ifd: u32,
|
||||
}
|
||||
|
||||
impl DirectoryWriter {
|
||||
pub fn remove_tag<T: TiffTag>(&mut self, tag: T) {
|
||||
let tag: u16 = tag.into();
|
||||
self.entries.remove(&tag);
|
||||
}
|
||||
|
||||
pub fn add_tag<T: TiffTag, V: Into<Value>>(&mut self, tag: T, value: V) {
|
||||
let tag: u16 = tag.into();
|
||||
self.entries.insert(
|
||||
tag,
|
||||
Entry {
|
||||
tag,
|
||||
value: value.into(),
|
||||
embedded: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add_untyped_tag<V: Into<Value>>(&mut self, tag: u16, value: V) {
|
||||
self.entries.insert(
|
||||
tag,
|
||||
Entry {
|
||||
tag,
|
||||
value: value.into(),
|
||||
embedded: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn contains<T: TiffTag>(&self, tag: T) -> bool {
|
||||
self.entries.contains_key(&tag.into())
|
||||
}
|
||||
|
||||
pub fn copy<'a>(&mut self, iter: impl Iterator<Item = (&'a u16, &'a Value)>) {
|
||||
for entry in iter {
|
||||
if !self.entries.contains_key(entry.0) {
|
||||
self.add_untyped_tag(*entry.0, entry.1.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_with_override<'a>(&mut self, iter: impl Iterator<Item = (&'a u16, &'a Value)>) {
|
||||
for entry in iter {
|
||||
self.add_untyped_tag(*entry.0, entry.1.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_tag_undefined<T: TiffTag>(&mut self, tag: T, data: Vec<u8>) {
|
||||
let tag: u16 = tag.into();
|
||||
//let data = data.as_ref();
|
||||
//let offset = self.write_data(data)?;
|
||||
self.entries.insert(
|
||||
tag,
|
||||
Entry {
|
||||
tag,
|
||||
value: Value::Undefined(data),
|
||||
embedded: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add_value<T: TiffTag>(&mut self, tag: T, value: Value) {
|
||||
let tag: u16 = tag.into();
|
||||
self.entries.insert(tag, Entry { tag, value, embedded: None });
|
||||
}
|
||||
|
||||
pub fn entry_count(&self) -> u16 {
|
||||
self.entries.len() as u16
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entries: BTreeMap::new(),
|
||||
next_ifd: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
|
||||
pub fn build<W>(mut self, tiff: &mut TiffWriter<W>) -> Result<u32>
|
||||
where
|
||||
W: Seek + Write,
|
||||
{
|
||||
if self.entries.is_empty() {
|
||||
return Err(TiffError::General("IFD is empty, not allowed by TIFF specification".to_string()));
|
||||
}
|
||||
for &mut Entry {
|
||||
ref mut value,
|
||||
ref mut embedded,
|
||||
ref tag,
|
||||
} in self.entries.values_mut()
|
||||
{
|
||||
let data_bytes = 4;
|
||||
|
||||
if value.byte_size() > data_bytes {
|
||||
tiff.pad_word_boundary()?;
|
||||
let offset = tiff.position()?;
|
||||
value.write(&mut tiff.writer)?;
|
||||
embedded.replace(offset as u32);
|
||||
} else {
|
||||
if value.count() == 0 {
|
||||
panic!("TIFF value is empty, tag: {:?}", tag);
|
||||
}
|
||||
embedded.replace(value.as_embedded()?);
|
||||
}
|
||||
}
|
||||
|
||||
tiff.pad_word_boundary()?;
|
||||
let offset = tiff.position()?;
|
||||
|
||||
tiff.writer.write_all(&self.entry_count().to_ne_bytes())?;
|
||||
|
||||
for (tag, entry) in self.entries {
|
||||
tiff.writer.write_u16::<NativeEndian>(tag)?;
|
||||
tiff.writer.write_u16::<NativeEndian>(entry.value_type())?;
|
||||
tiff.writer.write_u32::<NativeEndian>(entry.count())?;
|
||||
tiff
|
||||
.writer
|
||||
.write_u32::<NativeEndian>(entry.embedded.expect("embedded attribute must contain a value"))?;
|
||||
}
|
||||
tiff.writer.write_u32::<NativeEndian>(self.next_ifd)?; // Next IFD
|
||||
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn add_entry(&mut self, entry: Entry) {
|
||||
self.ifd.insert(tag.into(), entry);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
impl crate::decoders::RawMetadata {
|
||||
pub fn write_exif_tags<W>(&self, tiff: &mut TiffWriter<W>, root_ifd: &mut DirectoryWriter, exif_ifd: &mut DirectoryWriter) -> crate::formats::tiff::Result<()>
|
||||
where
|
||||
W: Write + Seek,
|
||||
{
|
||||
self.fill_exif_root(tiff, root_ifd)?;
|
||||
Self::fill_exif_ifd(&self.exif, exif_ifd)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn fill_exif_ifd(exif: &Exif, exif_ifd: &mut DirectoryWriter) -> Result<()> {
|
||||
transfer_entry(exif_ifd, ExifTag::FNumber, &exif.fnumber)?;
|
||||
transfer_entry(exif_ifd, ExifTag::ApertureValue, &exif.aperture_value)?;
|
||||
transfer_entry(exif_ifd, ExifTag::BrightnessValue, &exif.brightness_value)?;
|
||||
transfer_entry(exif_ifd, ExifTag::ExposureBiasValue, &exif.exposure_bias)?;
|
||||
transfer_entry(exif_ifd, ExifTag::RecommendedExposureIndex, &exif.recommended_exposure_index)?;
|
||||
transfer_entry(exif_ifd, ExifTag::ExposureTime, &exif.exposure_time)?;
|
||||
transfer_entry(exif_ifd, ExifTag::ISOSpeedRatings, &exif.iso_speed_ratings)?;
|
||||
transfer_entry(exif_ifd, ExifTag::ISOSpeed, &exif.iso_speed)?;
|
||||
transfer_entry(exif_ifd, ExifTag::SensitivityType, &exif.sensitivity_type)?;
|
||||
transfer_entry(exif_ifd, ExifTag::ExposureProgram, &exif.exposure_program)?;
|
||||
transfer_entry(exif_ifd, ExifTag::TimeZoneOffset, &exif.timezone_offset)?;
|
||||
transfer_entry(exif_ifd, ExifTag::DateTimeOriginal, &exif.date_time_original)?;
|
||||
transfer_entry(exif_ifd, ExifTag::CreateDate, &exif.create_date)?;
|
||||
transfer_entry(exif_ifd, ExifTag::OffsetTime, &exif.offset_time)?;
|
||||
transfer_entry(exif_ifd, ExifTag::OffsetTimeOriginal, &exif.offset_time_original)?;
|
||||
transfer_entry(exif_ifd, ExifTag::OffsetTimeDigitized, &exif.offset_time_digitized)?;
|
||||
transfer_entry(exif_ifd, ExifTag::SubSecTime, &exif.sub_sec_time)?;
|
||||
transfer_entry(exif_ifd, ExifTag::SubSecTimeOriginal, &exif.sub_sec_time_original)?;
|
||||
transfer_entry(exif_ifd, ExifTag::SubSecTimeDigitized, &exif.sub_sec_time_digitized)?;
|
||||
transfer_entry(exif_ifd, ExifTag::ShutterSpeedValue, &exif.shutter_speed_value)?;
|
||||
transfer_entry(exif_ifd, ExifTag::MaxApertureValue, &exif.max_aperture_value)?;
|
||||
transfer_entry(exif_ifd, ExifTag::SubjectDistance, &exif.subject_distance)?;
|
||||
transfer_entry(exif_ifd, ExifTag::MeteringMode, &exif.metering_mode)?;
|
||||
transfer_entry(exif_ifd, ExifTag::LightSource, &exif.light_source)?;
|
||||
transfer_entry(exif_ifd, ExifTag::Flash, &exif.flash)?;
|
||||
transfer_entry(exif_ifd, ExifTag::FocalLength, &exif.focal_length)?;
|
||||
transfer_entry(exif_ifd, ExifTag::ImageNumber, &exif.image_number)?;
|
||||
transfer_entry(exif_ifd, ExifTag::ColorSpace, &exif.color_space)?;
|
||||
transfer_entry(exif_ifd, ExifTag::FlashEnergy, &exif.flash_energy)?;
|
||||
transfer_entry(exif_ifd, ExifTag::ExposureMode, &exif.exposure_mode)?;
|
||||
transfer_entry(exif_ifd, ExifTag::WhiteBalance, &exif.white_balance)?;
|
||||
transfer_entry(exif_ifd, ExifTag::SceneCaptureType, &exif.scene_capture_type)?;
|
||||
transfer_entry(exif_ifd, ExifTag::SubjectDistanceRange, &exif.subject_distance_range)?;
|
||||
transfer_entry(exif_ifd, ExifTag::OwnerName, &exif.owner_name)?;
|
||||
transfer_entry(exif_ifd, ExifTag::SerialNumber, &exif.serial_number)?;
|
||||
transfer_entry(exif_ifd, ExifTag::LensSerialNumber, &exif.lens_serial_number)?;
|
||||
transfer_entry(exif_ifd, ExifTag::LensSpecification, &exif.lens_spec)?;
|
||||
transfer_entry(exif_ifd, ExifTag::LensMake, &exif.lens_make)?;
|
||||
transfer_entry(exif_ifd, ExifTag::LensModel, &exif.lens_model)?;
|
||||
transfer_entry(exif_ifd, ExifTag::UserComment, &exif.user_comment)?;
|
||||
//transfer_entry(exif_ifd, ExifTag::MakerNotes, &exif.makernotes.as_ref().map(|x| Value::Undefined(x.clone())))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fill_exif_root<W>(&self, tiff: &mut TiffWriter<W>, root_ifd: &mut DirectoryWriter) -> Result<()>
|
||||
where
|
||||
W: Write + Seek,
|
||||
{
|
||||
transfer_entry(root_ifd, ExifTag::Orientation, &self.exif.orientation)?;
|
||||
transfer_entry(root_ifd, ExifTag::ModifyDate, &self.exif.modify_date)?;
|
||||
transfer_entry(root_ifd, ExifTag::Copyright, &self.exif.copyright)?;
|
||||
transfer_entry(root_ifd, ExifTag::Artist, &self.exif.artist)?;
|
||||
|
||||
if let Some(gps) = &self.exif.gps {
|
||||
let gps_offset = {
|
||||
let mut gps_ifd = DirectoryWriter::new();
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSVersionID, &gps.gps_version_id)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSLatitudeRef, &gps.gps_latitude_ref)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSLatitude, &gps.gps_latitude)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSLongitudeRef, &gps.gps_longitude_ref)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSLongitude, &gps.gps_longitude)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSAltitudeRef, &gps.gps_altitude_ref)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSAltitude, &gps.gps_altitude)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSTimeStamp, &gps.gps_timestamp)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSSatellites, &gps.gps_satellites)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSStatus, &gps.gps_status)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSMeasureMode, &gps.gps_measure_mode)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSDOP, &gps.gps_dop)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSSpeedRef, &gps.gps_speed_ref)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSSpeed, &gps.gps_speed)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSTrackRef, &gps.gps_track_ref)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSTrack, &gps.gps_track)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSImgDirectionRef, &gps.gps_img_direction_ref)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSImgDirection, &gps.gps_img_direction)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSMapDatum, &gps.gps_map_datum)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSDestLatitudeRef, &gps.gps_dest_latitude_ref)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSDestLatitude, &gps.gps_dest_latitude)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSDestLongitudeRef, &gps.gps_dest_longitude_ref)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSDestLongitude, &gps.gps_dest_longitude)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSDestBearingRef, &gps.gps_dest_bearing_ref)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSDestBearing, &gps.gps_dest_bearing)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSDestDistanceRef, &gps.gps_dest_distance_ref)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSDestDistance, &gps.gps_dest_distance)?;
|
||||
transfer_entry_undefined(&mut gps_ifd, ExifGpsTag::GPSProcessingMethod, &gps.gps_processing_method)?;
|
||||
transfer_entry_undefined(&mut gps_ifd, ExifGpsTag::GPSAreaInformation, &gps.gps_area_information)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSDateStamp, &gps.gps_date_stamp)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSDifferential, &gps.gps_differential)?;
|
||||
transfer_entry(&mut gps_ifd, ExifGpsTag::GPSHPositioningError, &gps.gps_h_positioning_error)?;
|
||||
if gps_ifd.entry_count() > 0 { Some(gps_ifd.build(tiff)?) } else { None }
|
||||
};
|
||||
if let Some(gps_offset) = gps_offset {
|
||||
root_ifd.add_tag(ExifTag::GPSInfo, [gps_offset]);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn transfer_entry<T, V>(ifd: &mut DirectoryWriter, tag: T, entry: &Option<V>) -> Result<()>
|
||||
where
|
||||
T: TiffTag,
|
||||
V: Into<Value> + Clone,
|
||||
{
|
||||
if let Some(entry) = entry {
|
||||
ifd.add_tag(tag, entry.clone());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn transfer_entry_undefined<T>(ifd: &mut DirectoryWriter, tag: T, entry: &Option<Vec<u8>>) -> Result<()>
|
||||
where
|
||||
T: TiffTag,
|
||||
{
|
||||
if let Some(entry) = entry {
|
||||
ifd.add_tag_undefined(tag, entry.clone());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user