Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add write support for HEIC, HEIF, AVIF #210

Merged
merged 26 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2a7682e
Initial RIFF implementation
mauricefisher64 Mar 2, 2023
f9a76b7
AVI test
mauricefisher64 Mar 2, 2023
9bac350
webp support
mauricefisher64 Mar 2, 2023
73ddddd
Merge branch 'main' of https://github.com/contentauth/c2pa-rs into RIFF
mauricefisher64 Mar 3, 2023
8e050e4
Disable XMP default writting
mauricefisher64 Mar 3, 2023
4ba51de
Fix broken hashing
mauricefisher64 Mar 3, 2023
751f91d
Refactor asset handlers to control supported features
mauricefisher64 Mar 7, 2023
058556b
Fix clippy errors
mauricefisher64 Mar 7, 2023
7e65136
Moved XMP generation to the asset handlers
mauricefisher64 Mar 8, 2023
a0faa17
Remove XMP stuff from store since it is now performed by handlders
mauricefisher64 Mar 8, 2023
f597c04
XMP restrict some file types
mauricefisher64 Mar 8, 2023
da60aec
Code cleanup
mauricefisher64 Mar 9, 2023
3d6bb61
Merge branch 'main' of https://github.com/contentauth/c2pa-rs into RIFF
mauricefisher64 Mar 9, 2023
217a717
Update version
mauricefisher64 Mar 9, 2023
f7eae23
format issues
mauricefisher64 Mar 9, 2023
5364fa7
more formatting fixes
mauricefisher64 Mar 9, 2023
b4ed3e7
cleanup
mauricefisher64 Mar 9, 2023
33c5e7a
Bump major number since new error codes are available
mauricefisher64 Mar 9, 2023
a02017a
Fix PR comments
mauricefisher64 Mar 10, 2023
3721bfb
Minor cleanup
mauricefisher64 Mar 10, 2023
ac6ea37
Merge branch 'main' of https://github.com/contentauth/c2pa-rs into RIFF
mauricefisher64 Mar 10, 2023
2330403
Merge branch 'main' of https://github.com/contentauth/c2pa-rs into RIFF
mauricefisher64 Mar 10, 2023
556f39f
Support HEIC, HEIF, & AVIF
mauricefisher64 Mar 13, 2023
3c102db
Merge branch 'main' of https://github.com/contentauth/c2pa-rs into RIFF
mauricefisher64 Mar 13, 2023
edf7b88
PR comment fixes
mauricefisher64 Mar 14, 2023
1175099
Add comment for future enhancement
mauricefisher64 Mar 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
253 changes: 209 additions & 44 deletions sdk/src/asset_handlers/bmff_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,18 @@ const FULL_BOX_TYPES: &[&str; 80] = &[
"txtC", "mime", "uri ", "uriI", "hmhd", "sthd", "vvhd", "medc",
];

static SUPPORTED_TYPES: [&str; 6] = [
/*"avif", // disable for now while we test a little more
static SUPPORTED_TYPES: [&str; 12] = [
"avif",
"heif",
"heic",*/
"heic",
"mp4",
"m4a",
"mov",
"application/mp4",
"audio/mp4",
/*
"image/avif",
"image/heic",
"image/heif",*/
"image/heif",
"video/mp4",
];

Expand Down Expand Up @@ -158,7 +157,8 @@ boxtype! {
VpccBox => 0x76706343,
Vp09Box => 0x76703039,
MetaBox => 0x6D657461,
SchiBox => 0x73636869
SchiBox => 0x73636869,
IlocBox => 0x696C6F63
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
Expand Down Expand Up @@ -552,8 +552,9 @@ pub fn bmff_to_jumbf_exclusions(
Ok(exclusions)
}

// `stco` and `co64` elements contain absolute file offsets so they need to be adjusted based on whether content was added or removed.
fn adjust_stco_and_co64<W: Write + CAIRead>(
// `iloc`, `stco` and `co64` elements contain absolute file offsets so they need to be adjusted based on whether content was added or removed.
// todo: when fragment support is added adjust these (/moof/iloc, /moof/mfro, /moof/traf/saio, /sidx)
fn adjust_known_offsets<W: Write + CAIRead>(
output: &mut W,
bmff_tree: &Arena<BoxInfo>,
bmff_path_map: &HashMap<String, Vec<Token>>,
Expand Down Expand Up @@ -663,6 +664,174 @@ fn adjust_stco_and_co64<W: Write + CAIRead>(
}
}

// handle meta iloc
if let Some(iloc_list) = bmff_path_map.get("/meta/iloc") {
for iloc_token in iloc_list {
let iloc_box_info = &bmff_tree[*iloc_token].data;
if iloc_box_info.box_type != BoxType::IlocBox {
return Err(Error::InvalidAsset("Bad BMFF".to_string()));
}

// read iloc box and patch
output.seek(SeekFrom::Start(iloc_box_info.offset))?;

// read header
let header = BoxHeaderLite::read(output)
.map_err(|_err| Error::InvalidAsset("Bad BMFF".to_string()))?;
if header.name != BoxType::IlocBox {
return Err(Error::InvalidAsset("Bad BMFF".to_string()));
}

// read extended header
let (version, _flags) = read_box_header_ext(output)?; // box extensions

// read next 16 bits (in file byte order)
let mut iloc_header = [0u8, 2];
output.read_exact(&mut iloc_header)?;

// get offset size (high nibble)
let offset_size: u8 = (iloc_header[0] & 0xF0) >> 4;

// get length size (low nibble)
let length_size: u8 = iloc_header[0] & 0x0F;

// get box offset size (high nibble)
let base_offset_size: u8 = (iloc_header[1] & 0xF0) >> 4;

// get index size (low nibble)
let index_size: u8 = iloc_header[1] & 0x0F;

// get item count
let item_count = match version {
_v if version < 2 => output.read_u16::<BigEndian>()? as u32,
_v if version == 2 => output.read_u32::<BigEndian>()?,
_ => {
return Err(Error::InvalidAsset(
"Bad BMFF unknown iloc format".to_string(),
))
}
};

// walk the iloc items and patch
for _i in 0..item_count {
// read item id
let _item_id = match version {
_v if version < 2 => output.read_u16::<BigEndian>()? as u32,
2 => output.read_u32::<BigEndian>()?,
_ => {
return Err(Error::InvalidAsset(
"Bad BMFF: unknown iloc item".to_string(),
))
}
};

// read constuction method
let constuction_method = if version == 1 || version == 2 {
let mut cm_bytes = [0u8, 2];
output.read_exact(&mut cm_bytes)?;

// lower nibble of 2nd byte
cm_bytes[1] & 0x0F
} else {
0
};

// read data reference index
let _data_reference_index = output.read_u16::<BigEndian>()?;

let base_offset_file_pos = output.stream_position()?;
let base_offset = match base_offset_size {
0 => 0_u64,
4 => output.read_u32::<BigEndian>()? as u64,
8 => output.read_u64::<BigEndian>()?,
_ => {
return Err(Error::InvalidAsset(
"Bad BMFF: unknown iloc offset size".to_string(),
))
}
};

// patch the offsets if needed
if constuction_method == 0 {
// file offset construction method
if base_offset_size == 4 {
let new_offset = if adjust < 0 {
u32::try_from(base_offset).map_err(|_| {
Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
})? - u32::try_from(adjust.abs()).map_err(|_| {
Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
})?
} else {
u32::try_from(base_offset).map_err(|_| {
Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
})? + u32::try_from(adjust).map_err(|_| {
Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
})?
};

output.seek(SeekFrom::Start(base_offset_file_pos))?;
output.write_u32::<BigEndian>(new_offset)?;
}

if base_offset_size == 8 {
let new_offset = if adjust < 0 {
base_offset
- u64::try_from(adjust.abs()).map_err(|_| {
Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
})?
} else {
base_offset
+ u64::try_from(adjust).map_err(|_| {
Error::InvalidAsset("Bad BMFF offset adjustment".to_string())
})?
};

output.seek(SeekFrom::Start(base_offset_file_pos))?;
output.write_u64::<BigEndian>(new_offset)?;
}
}

// read extent count
let extent_count = output.read_u16::<BigEndian>()?;

// consume the extents
for _e in 0..extent_count {
let _extent_index = if version == 1 || (version == 2 && index_size > 0) {
match base_offset_size {
4 => Some(output.read_u32::<BigEndian>()? as u64),
8 => Some(output.read_u64::<BigEndian>()?),
_ => None,
}
} else {
None
};

let _extent_offset = match offset_size {
0 => 0_u64,
4 => output.read_u32::<BigEndian>()? as u64,
8 => output.read_u64::<BigEndian>()?,
_ => {
return Err(Error::InvalidAsset(
"Bad BMFF: unknown iloc extent_offset size".to_string(),
))
}
};

let _extent_length = match length_size {
0 => 0_u64,
4 => output.read_u32::<BigEndian>()? as u64,
8 => output.read_u64::<BigEndian>()?,
_ => {
return Err(Error::InvalidAsset(
"Bad BMFF: unknown iloc offset size".to_string(),
))
}
};
}
}
}
}

// restore seek point
output.seek(SeekFrom::Start(start_pos))?;
output.flush()?;
Expand Down Expand Up @@ -1064,44 +1233,40 @@ impl AssetIO for BmffIO {
temp_file.flush()?;

// Manipulating the UUID box means we may need some patch offsets if they are file absolute offsets.
match self.bmff_format.as_ref() {
"m4a" | "mp4" | "mov" => {
// create root node
let root_box = BoxInfo {
path: "".to_string(),
offset: 0,
size,
box_type: BoxType::Empty,
parent: None,
user_type: None,
version: None,
flags: None,
};

// rebuild box layout for output file
let (mut output_bmff_tree, root_token) = Arena::with_data(root_box);
let mut output_bmff_map: HashMap<String, Vec<Token>> = HashMap::new();
// create root node
let root_box = BoxInfo {
path: "".to_string(),
offset: 0,
size,
box_type: BoxType::Empty,
parent: None,
user_type: None,
version: None,
flags: None,
};

let size = temp_file.seek(SeekFrom::End(0))?;
temp_file.rewind()?;
build_bmff_tree(
&mut temp_file,
size,
&mut output_bmff_tree,
&root_token,
&mut output_bmff_map,
)?;
// map box layout of current output file
let (mut output_bmff_tree, root_token) = Arena::with_data(root_box);
let mut output_bmff_map: HashMap<String, Vec<Token>> = HashMap::new();

// adjust based on current layout
adjust_stco_and_co64(
&mut temp_file,
&output_bmff_tree,
&output_bmff_map,
offset_adjust,
)?;
}
_ => (), // todo: handle more patching cases as necessary
}
let size = temp_file.seek(SeekFrom::End(0))?;
temp_file.rewind()?;
build_bmff_tree(
&mut temp_file,
size,
&mut output_bmff_tree,
&root_token,
&mut output_bmff_map,
)?;

// adjust offsets based on current layout
adjust_known_offsets(
&mut temp_file,
&output_bmff_tree,
&output_bmff_map,
offset_adjust,
)?;

// copy temp file to asset
std::fs::rename(temp_file.path(), asset_path)
Expand Down Expand Up @@ -1224,7 +1389,7 @@ impl AssetIO for BmffIO {
)?;

// adjust based on current layout
adjust_stco_and_co64(
adjust_known_offsets(
&mut temp_file,
&output_bmff_tree,
&output_bmff_map,
Expand Down
Loading