mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2025-03-04 14:57:17 +00:00
MP4: Better handle invalid atom sizes
This commit is contained in:
parent
d298199a07
commit
542f7dabc7
12 changed files with 367 additions and 183 deletions
|
@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Fixed
|
||||
- **PictureInformation**: Fix potential overflow on an invalid picture
|
||||
- **MP4**: The parser has received a major facelift, and shouldn't be so eager to allocate or trust user data (Fixes OOM)
|
||||
|
||||
## [0.7.1] - 2022-07-08
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ macro_rules! tag_methods {
|
|||
macro_rules! try_vec {
|
||||
($elem:expr; $size:expr) => {{
|
||||
let mut v = Vec::new();
|
||||
v.try_reserve($size)?;
|
||||
v.try_reserve_exact($size)?;
|
||||
v.resize($size, $elem);
|
||||
|
||||
v
|
||||
|
|
|
@ -39,25 +39,18 @@ pub(crate) struct AtomInfo {
|
|||
}
|
||||
|
||||
impl AtomInfo {
|
||||
pub(crate) fn read<R>(data: &mut R) -> Result<Self>
|
||||
pub(crate) fn read<R>(data: &mut R, mut reader_size: u64) -> Result<Self>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let start = data.stream_position()?;
|
||||
|
||||
let len = data.read_u32::<BigEndian>()?;
|
||||
let len_raw = u64::from(data.read_u32::<BigEndian>()?);
|
||||
|
||||
let mut ident = [0; 4];
|
||||
data.read_exact(&mut ident)?;
|
||||
|
||||
let mut atom_ident = AtomIdent::Fourcc(ident);
|
||||
|
||||
// Encountered a freeform identifier
|
||||
if &ident == b"----" {
|
||||
atom_ident = parse_freeform(data)?;
|
||||
}
|
||||
|
||||
let (len, extended) = match len {
|
||||
let (len, extended) = match len_raw {
|
||||
// The atom extends to the end of the file
|
||||
0 => {
|
||||
let pos = data.stream_position()?;
|
||||
|
@ -69,7 +62,7 @@ impl AtomInfo {
|
|||
},
|
||||
// There's an extended length
|
||||
1 => (data.read_u64::<BigEndian>()?, true),
|
||||
_ => (u64::from(len), false),
|
||||
_ => (len_raw, false),
|
||||
};
|
||||
|
||||
if len < 8 {
|
||||
|
@ -81,6 +74,26 @@ impl AtomInfo {
|
|||
)));
|
||||
}
|
||||
|
||||
// `len` includes itself
|
||||
if (len - 4) > reader_size {
|
||||
data.seek(SeekFrom::Current(-4))?;
|
||||
return Err(LoftyError::new(ErrorKind::TooMuchData));
|
||||
}
|
||||
|
||||
let mut atom_ident = AtomIdent::Fourcc(ident);
|
||||
|
||||
// Encountered a freeform identifier
|
||||
if &ident == b"----" {
|
||||
reader_size -= 8;
|
||||
if reader_size < 8 {
|
||||
return Err(LoftyError::new(ErrorKind::BadAtom(
|
||||
"Found an incomplete freeform identifier",
|
||||
)));
|
||||
}
|
||||
|
||||
atom_ident = parse_freeform(data, reader_size)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
start,
|
||||
len,
|
||||
|
@ -90,21 +103,21 @@ impl AtomInfo {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_freeform<R>(data: &mut R) -> Result<AtomIdent>
|
||||
fn parse_freeform<R>(data: &mut R, reader_size: u64) -> Result<AtomIdent>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let mean = freeform_chunk(data, b"mean")?;
|
||||
let name = freeform_chunk(data, b"name")?;
|
||||
let mean = freeform_chunk(data, b"mean", reader_size - 4)?;
|
||||
let name = freeform_chunk(data, b"name", reader_size - 8)?;
|
||||
|
||||
Ok(AtomIdent::Freeform { mean, name })
|
||||
}
|
||||
|
||||
fn freeform_chunk<R>(data: &mut R, name: &[u8]) -> Result<String>
|
||||
fn freeform_chunk<R>(data: &mut R, name: &[u8], reader_size: u64) -> Result<String>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let atom = AtomInfo::read(data)?;
|
||||
let atom = AtomInfo::read(data, reader_size)?;
|
||||
|
||||
match atom.ident {
|
||||
AtomIdent::Fourcc(ref fourcc) if fourcc == name => {
|
||||
|
|
|
@ -519,14 +519,21 @@ fn item_key_to_ident(key: &ItemKey) -> Option<AtomIdentRef<'_>> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::mp4::ilst::atom::AtomDataStorage;
|
||||
use crate::mp4::read::AtomReader;
|
||||
use crate::mp4::{AdvisoryRating, Atom, AtomData, AtomIdent, Ilst, Mp4File};
|
||||
use crate::tag::utils::test_utils;
|
||||
use crate::tag::utils::test_utils::read_path;
|
||||
use crate::{Accessor, AudioFile, ItemKey, Tag, TagExt, TagType};
|
||||
use std::io::{Cursor, Read, Seek, Write};
|
||||
|
||||
fn read_ilst(path: &str) -> Ilst {
|
||||
let tag = crate::tag::utils::test_utils::read_path(path);
|
||||
super::read::parse_ilst(&mut &tag[..], tag.len() as u64).unwrap()
|
||||
let len = tag.len();
|
||||
|
||||
let cursor = Cursor::new(tag);
|
||||
let mut reader = AtomReader::new(cursor).unwrap();
|
||||
|
||||
super::read::parse_ilst(&mut reader, len as u64).unwrap()
|
||||
}
|
||||
|
||||
fn verify_atom(ilst: &Ilst, ident: [u8; 4], data: &AtomData) {
|
||||
|
@ -589,8 +596,12 @@ mod tests {
|
|||
));
|
||||
|
||||
let tag = crate::tag::utils::test_utils::read_path("tests/tags/assets/ilst/test.ilst");
|
||||
let len = tag.len();
|
||||
|
||||
let parsed_tag = super::read::parse_ilst(&mut &tag[..], tag.len() as u64).unwrap();
|
||||
let cursor = Cursor::new(tag);
|
||||
let mut reader = AtomReader::new(cursor).unwrap();
|
||||
|
||||
let parsed_tag = super::read::parse_ilst(&mut reader, len as u64).unwrap();
|
||||
|
||||
assert_eq!(expected_tag, parsed_tag);
|
||||
}
|
||||
|
@ -602,19 +613,25 @@ mod tests {
|
|||
let mut writer = Vec::new();
|
||||
parsed_tag.dump_to(&mut writer).unwrap();
|
||||
|
||||
let cursor = Cursor::new(&writer[8..]);
|
||||
let mut reader = AtomReader::new(cursor).unwrap();
|
||||
|
||||
// Remove the ilst identifier and size
|
||||
let temp_parsed_tag =
|
||||
super::read::parse_ilst(&mut &writer[8..], (writer.len() - 8) as u64).unwrap();
|
||||
super::read::parse_ilst(&mut reader, (writer.len() - 8) as u64).unwrap();
|
||||
|
||||
assert_eq!(parsed_tag, temp_parsed_tag);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ilst_to_tag() {
|
||||
let tag_bytes =
|
||||
crate::tag::utils::test_utils::read_path("tests/tags/assets/ilst/test.ilst");
|
||||
let tag = crate::tag::utils::test_utils::read_path("tests/tags/assets/ilst/test.ilst");
|
||||
let len = tag.len();
|
||||
|
||||
let ilst = super::read::parse_ilst(&mut &tag_bytes[..], tag_bytes.len() as u64).unwrap();
|
||||
let cursor = Cursor::new(tag);
|
||||
let mut reader = AtomReader::new(cursor).unwrap();
|
||||
|
||||
let ilst = super::read::parse_ilst(&mut reader, len as u64).unwrap();
|
||||
|
||||
let tag: Tag = ilst.into();
|
||||
|
||||
|
@ -717,13 +734,20 @@ mod tests {
|
|||
let file_bytes = read_path("tests/files/assets/ilst_trailing_padding.m4a");
|
||||
assert!(Mp4File::read_from(&mut Cursor::new(&file_bytes), false).is_ok());
|
||||
|
||||
let ilst_bytes = &file_bytes[ILST_START..ILST_END];
|
||||
let mut ilst;
|
||||
let old_free_size;
|
||||
{
|
||||
let ilst_bytes = &file_bytes[ILST_START..ILST_END];
|
||||
|
||||
let old_free_size =
|
||||
u32::from_be_bytes(file_bytes[ILST_END..ILST_END + 4].try_into().unwrap());
|
||||
assert_eq!(old_free_size, PADDING_SIZE as u32);
|
||||
old_free_size =
|
||||
u32::from_be_bytes(file_bytes[ILST_END..ILST_END + 4].try_into().unwrap());
|
||||
assert_eq!(old_free_size, PADDING_SIZE as u32);
|
||||
|
||||
let mut ilst = super::read::parse_ilst(&mut &*ilst_bytes, ilst_bytes.len() as u64).unwrap();
|
||||
let cursor = Cursor::new(ilst_bytes);
|
||||
let mut reader = AtomReader::new(cursor).unwrap();
|
||||
|
||||
ilst = super::read::parse_ilst(&mut reader, ilst_bytes.len() as u64).unwrap();
|
||||
}
|
||||
|
||||
let mut file = tempfile::tempfile().unwrap();
|
||||
file.write_all(&file_bytes).unwrap();
|
||||
|
@ -781,7 +805,7 @@ mod tests {
|
|||
data: AtomDataStorage::Single(AtomData::UTF8(String::from("Foo artist"))),
|
||||
});
|
||||
|
||||
assert!(tag.save_to(&mut file).is_ok());
|
||||
tag.save_to(&mut file).unwrap();
|
||||
file.rewind().unwrap();
|
||||
|
||||
let mp4_file = Mp4File::read_from(&mut file, true).unwrap();
|
||||
|
@ -814,4 +838,15 @@ mod tests {
|
|||
&AtomData::UTF8(String::from("Classical")),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_sized_ilst() {
|
||||
let file = Mp4File::read_from(
|
||||
&mut Cursor::new(test_utils::read_path("tests/files/assets/zero/zero.ilst")),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(file.ilst, Some(Ilst::default()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,40 +8,40 @@ use crate::id3::v1::constants::GENRES;
|
|||
use crate::id3::v2::util::text_utils::utf16_decode;
|
||||
use crate::macros::try_vec;
|
||||
use crate::mp4::atom_info::AtomInfo;
|
||||
use crate::mp4::read::skip_unneeded;
|
||||
use crate::mp4::ilst::atom::AtomDataStorage;
|
||||
use crate::mp4::read::{skip_unneeded, AtomReader};
|
||||
use crate::picture::{MimeType, Picture, PictureType};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
|
||||
use crate::mp4::ilst::atom::AtomDataStorage;
|
||||
use byteorder::ReadBytesExt;
|
||||
|
||||
pub(in crate::mp4) fn parse_ilst<R>(reader: &mut R, len: u64) -> Result<Ilst>
|
||||
pub(in crate::mp4) fn parse_ilst<R>(reader: &mut AtomReader<R>, len: u64) -> Result<Ilst>
|
||||
where
|
||||
R: Read,
|
||||
R: Read + Seek,
|
||||
{
|
||||
let mut contents = try_vec![0; len as usize];
|
||||
reader.read_exact(&mut contents)?;
|
||||
|
||||
let mut cursor = Cursor::new(contents);
|
||||
|
||||
let mut ilst_reader = AtomReader::new(&mut cursor)?;
|
||||
|
||||
let mut tag = Ilst::default();
|
||||
|
||||
while let Ok(atom) = AtomInfo::read(&mut cursor) {
|
||||
while let Ok(atom) = ilst_reader.next() {
|
||||
if let AtomIdent::Fourcc(ref fourcc) = atom.ident {
|
||||
match fourcc {
|
||||
b"free" | b"skip" => {
|
||||
skip_unneeded(&mut cursor, atom.extended, atom.len)?;
|
||||
skip_unneeded(&mut ilst_reader, atom.extended, atom.len)?;
|
||||
continue;
|
||||
},
|
||||
b"covr" => {
|
||||
handle_covr(&mut cursor, &mut tag, &atom)?;
|
||||
handle_covr(&mut ilst_reader, &mut tag, &atom)?;
|
||||
continue;
|
||||
},
|
||||
// Upgrade this to a \xa9gen atom
|
||||
b"gnre" => {
|
||||
if let Some(atom_data) = parse_data_inner(&mut cursor, &atom)? {
|
||||
if let Some(atom_data) = parse_data_inner(&mut ilst_reader, &atom)? {
|
||||
let mut data = Vec::new();
|
||||
|
||||
for (flags, content) in atom_data {
|
||||
|
@ -64,7 +64,7 @@ where
|
|||
// Special case the "Album ID", as it has the code "BE signed integer" (21), but
|
||||
// must be interpreted as a "BE 64-bit Signed Integer" (74)
|
||||
b"plID" => {
|
||||
if let Some(atom_data) = parse_data_inner(&mut cursor, &atom)? {
|
||||
if let Some(atom_data) = parse_data_inner(&mut ilst_reader, &atom)? {
|
||||
let mut data = Vec::new();
|
||||
|
||||
for (code, content) in atom_data {
|
||||
|
@ -90,17 +90,17 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
parse_data(&mut cursor, &mut tag, atom)?;
|
||||
parse_data(&mut ilst_reader, &mut tag, atom)?;
|
||||
}
|
||||
|
||||
Ok(tag)
|
||||
}
|
||||
|
||||
fn parse_data<R>(data: &mut R, tag: &mut Ilst, atom_info: AtomInfo) -> Result<()>
|
||||
fn parse_data<R>(reader: &mut AtomReader<R>, tag: &mut Ilst, atom_info: AtomInfo) -> Result<()>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
if let Some(mut atom_data) = parse_data_inner(data, &atom_info)? {
|
||||
if let Some(mut atom_data) = parse_data_inner(reader, &atom_info)? {
|
||||
// Most atoms we encounter are only going to have 1 value, so store them as such
|
||||
if atom_data.len() == 1 {
|
||||
let (flags, content) = atom_data.remove(0);
|
||||
|
@ -129,17 +129,20 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_data_inner<R>(data: &mut R, atom_info: &AtomInfo) -> Result<Option<Vec<(u32, Vec<u8>)>>>
|
||||
fn parse_data_inner<R>(
|
||||
reader: &mut AtomReader<R>,
|
||||
atom_info: &AtomInfo,
|
||||
) -> Result<Option<Vec<(u32, Vec<u8>)>>>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
// An atom can contain multiple data atoms
|
||||
let mut ret = Vec::new();
|
||||
|
||||
let to_read = (atom_info.start + atom_info.len) - data.stream_position()?;
|
||||
let to_read = (atom_info.start + atom_info.len) - reader.position()?;
|
||||
let mut pos = 0;
|
||||
while pos < to_read {
|
||||
let data_atom = AtomInfo::read(data)?;
|
||||
let data_atom = reader.next()?;
|
||||
match data_atom.ident {
|
||||
AtomIdent::Fourcc(ref name) if name == b"data" => {},
|
||||
_ => {
|
||||
|
@ -150,15 +153,15 @@ where
|
|||
}
|
||||
|
||||
// We don't care about the version
|
||||
let _version = data.read_u8()?;
|
||||
let _version = reader.read_u8()?;
|
||||
|
||||
let mut flags = [0; 3];
|
||||
data.read_exact(&mut flags)?;
|
||||
reader.read_exact(&mut flags)?;
|
||||
|
||||
let flags = u32::from_be_bytes([0, flags[0], flags[1], flags[2]]);
|
||||
|
||||
// We don't care about the locale
|
||||
data.seek(SeekFrom::Current(4))?;
|
||||
reader.seek(SeekFrom::Current(4))?;
|
||||
|
||||
let content_len = (data_atom.len - 16) as usize;
|
||||
if content_len == 0 {
|
||||
|
@ -167,7 +170,7 @@ where
|
|||
}
|
||||
|
||||
let mut content = try_vec![0; content_len];
|
||||
data.read_exact(&mut content)?;
|
||||
reader.read_exact(&mut content)?;
|
||||
|
||||
pos += data_atom.len;
|
||||
ret.push((flags, content));
|
||||
|
@ -205,7 +208,10 @@ fn parse_int(bytes: &[u8]) -> Result<i32> {
|
|||
})
|
||||
}
|
||||
|
||||
fn handle_covr(reader: &mut Cursor<Vec<u8>>, tag: &mut Ilst, atom_info: &AtomInfo) -> Result<()> {
|
||||
fn handle_covr<R>(reader: &mut AtomReader<R>, tag: &mut Ilst, atom_info: &AtomInfo) -> Result<()>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
if let Some(atom_data) = parse_data_inner(reader, atom_info)? {
|
||||
let mut data = Vec::new();
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::macros::try_vec;
|
|||
use crate::mp4::atom_info::{AtomIdent, AtomInfo};
|
||||
use crate::mp4::ilst::r#ref::{AtomIdentRef, AtomRef};
|
||||
use crate::mp4::moov::Moov;
|
||||
use crate::mp4::read::{atom_tree, meta_is_full, nested_atom, verify_mp4};
|
||||
use crate::mp4::read::{atom_tree, meta_is_full, nested_atom, verify_mp4, AtomReader};
|
||||
use crate::mp4::AtomData;
|
||||
use crate::picture::{MimeType, Picture};
|
||||
|
||||
|
@ -18,15 +18,17 @@ pub(crate) fn write_to<'a, I: 'a>(data: &mut File, tag: &mut IlstRef<'a, I>) ->
|
|||
where
|
||||
I: IntoIterator<Item = &'a AtomData>,
|
||||
{
|
||||
verify_mp4(data)?;
|
||||
let mut reader = AtomReader::new(data)?;
|
||||
|
||||
let moov = Moov::find(data)?;
|
||||
let pos = data.stream_position()?;
|
||||
verify_mp4(&mut reader)?;
|
||||
|
||||
data.rewind()?;
|
||||
let moov = Moov::find(&mut reader)?;
|
||||
let pos = reader.position()?;
|
||||
|
||||
reader.rewind()?;
|
||||
|
||||
let mut file_bytes = Vec::new();
|
||||
data.read_to_end(&mut file_bytes)?;
|
||||
reader.read_to_end(&mut file_bytes)?;
|
||||
|
||||
let mut cursor = Cursor::new(file_bytes);
|
||||
cursor.seek(SeekFrom::Start(pos))?;
|
||||
|
@ -125,6 +127,8 @@ where
|
|||
&mut cursor,
|
||||
)?;
|
||||
|
||||
let data = reader.into_inner();
|
||||
|
||||
data.rewind()?;
|
||||
data.set_len(0)?;
|
||||
data.write_all(&cursor.into_inner())?;
|
||||
|
@ -142,7 +146,7 @@ fn save_to_existing(
|
|||
let replacement;
|
||||
let range;
|
||||
|
||||
let (ilst_idx, tree) = atom_tree(cursor, meta.len - 4, b"ilst")?;
|
||||
let (ilst_idx, tree) = atom_tree(cursor, meta.len - 8, b"ilst")?;
|
||||
|
||||
if tree.is_empty() {
|
||||
// Nothing to do
|
||||
|
|
|
@ -4,7 +4,7 @@ use super::trak::Trak;
|
|||
#[cfg(feature = "mp4_ilst")]
|
||||
use super::{
|
||||
ilst::{read::parse_ilst, Ilst},
|
||||
read::meta_is_full,
|
||||
read::{meta_is_full, AtomReader},
|
||||
};
|
||||
use crate::error::{FileDecodingError, Result};
|
||||
use crate::file::FileType;
|
||||
|
@ -19,19 +19,19 @@ pub(crate) struct Moov {
|
|||
}
|
||||
|
||||
impl Moov {
|
||||
pub(crate) fn find<R>(data: &mut R) -> Result<AtomInfo>
|
||||
pub(super) fn find<R>(reader: &mut AtomReader<R>) -> Result<AtomInfo>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let mut moov = None;
|
||||
|
||||
while let Ok(atom) = AtomInfo::read(data) {
|
||||
while let Ok(atom) = reader.next() {
|
||||
if atom.ident == AtomIdent::Fourcc(*b"moov") {
|
||||
moov = Some(atom);
|
||||
break;
|
||||
}
|
||||
|
||||
skip_unneeded(data, atom.extended, atom.len)?;
|
||||
skip_unneeded(reader, atom.extended, atom.len)?;
|
||||
}
|
||||
|
||||
if let Some(moov) = moov {
|
||||
|
@ -41,7 +41,7 @@ impl Moov {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse<R>(data: &mut R, read_properties: bool) -> Result<Self>
|
||||
pub(super) fn parse<R>(reader: &mut AtomReader<R>, read_properties: bool) -> Result<Self>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
|
@ -49,21 +49,21 @@ impl Moov {
|
|||
#[cfg(feature = "mp4_ilst")]
|
||||
let mut meta = None;
|
||||
|
||||
while let Ok(atom) = AtomInfo::read(data) {
|
||||
while let Ok(atom) = reader.next() {
|
||||
if let AtomIdent::Fourcc(fourcc) = atom.ident {
|
||||
match &fourcc {
|
||||
b"trak" if read_properties => traks.push(Trak::parse(data, &atom)?),
|
||||
b"trak" if read_properties => traks.push(Trak::parse(reader, &atom)?),
|
||||
#[cfg(feature = "mp4_ilst")]
|
||||
b"udta" => {
|
||||
meta = meta_from_udta(data, atom.len - 8)?;
|
||||
meta = meta_from_udta(reader, atom.len - 8)?;
|
||||
},
|
||||
_ => skip_unneeded(data, atom.extended, atom.len)?,
|
||||
_ => skip_unneeded(reader, atom.extended, atom.len)?,
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
skip_unneeded(data, atom.extended, atom.len)?
|
||||
skip_unneeded(reader, atom.extended, atom.len)?
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
|
@ -75,7 +75,7 @@ impl Moov {
|
|||
}
|
||||
|
||||
#[cfg(feature = "mp4_ilst")]
|
||||
fn meta_from_udta<R>(data: &mut R, len: u64) -> Result<Option<Ilst>>
|
||||
fn meta_from_udta<R>(reader: &mut AtomReader<R>, len: u64) -> Result<Option<Ilst>>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
|
@ -83,7 +83,7 @@ where
|
|||
let mut meta = (false, 0_u64);
|
||||
|
||||
while read < len {
|
||||
let atom = AtomInfo::read(data)?;
|
||||
let atom = reader.next()?;
|
||||
|
||||
if atom.ident == AtomIdent::Fourcc(*b"meta") {
|
||||
meta = (true, atom.len);
|
||||
|
@ -91,7 +91,7 @@ where
|
|||
}
|
||||
|
||||
read += atom.len;
|
||||
skip_unneeded(data, atom.extended, atom.len)?;
|
||||
skip_unneeded(reader, atom.extended, atom.len)?;
|
||||
}
|
||||
|
||||
if !meta.0 {
|
||||
|
@ -100,7 +100,7 @@ where
|
|||
|
||||
// It's possible for the `meta` atom to be non-full,
|
||||
// so we have to check for that case
|
||||
let full_meta_atom = meta_is_full(data)?;
|
||||
let full_meta_atom = meta_is_full(reader)?;
|
||||
|
||||
if full_meta_atom {
|
||||
read = 12;
|
||||
|
@ -111,7 +111,7 @@ where
|
|||
let mut islt = (false, 0_u64);
|
||||
|
||||
while read < meta.1 {
|
||||
let atom = AtomInfo::read(data)?;
|
||||
let atom = reader.next()?;
|
||||
|
||||
if atom.ident == AtomIdent::Fourcc(*b"ilst") {
|
||||
islt = (true, atom.len);
|
||||
|
@ -119,11 +119,11 @@ where
|
|||
}
|
||||
|
||||
read += atom.len;
|
||||
skip_unneeded(data, atom.extended, atom.len)?;
|
||||
skip_unneeded(reader, atom.extended, atom.len)?;
|
||||
}
|
||||
|
||||
if islt.0 {
|
||||
return parse_ilst(data, islt.1 - 8).map(Some);
|
||||
return parse_ilst(reader, islt.1 - 8).map(Some);
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::atom_info::{AtomIdent, AtomInfo};
|
||||
use super::read::{nested_atom, skip_unneeded};
|
||||
use super::read::{nested_atom, skip_unneeded, AtomReader};
|
||||
use super::trak::Trak;
|
||||
use crate::error::{ErrorKind, FileDecodingError, LoftyError, Result};
|
||||
use crate::file::FileType;
|
||||
|
@ -217,8 +217,8 @@ impl Mp4Properties {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read_properties<R>(
|
||||
data: &mut R,
|
||||
pub(super) fn read_properties<R>(
|
||||
reader: &mut AtomReader<R>,
|
||||
traks: &[Trak],
|
||||
file_length: u64,
|
||||
) -> Result<Mp4Properties>
|
||||
|
@ -236,42 +236,42 @@ where
|
|||
break;
|
||||
}
|
||||
|
||||
data.seek(SeekFrom::Start(mdia.start + 8))?;
|
||||
reader.seek(SeekFrom::Start(mdia.start + 8))?;
|
||||
|
||||
let mut read = 8;
|
||||
while read < mdia.len {
|
||||
let atom = AtomInfo::read(data)?;
|
||||
let atom = reader.next()?;
|
||||
read += atom.len;
|
||||
|
||||
if let AtomIdent::Fourcc(fourcc) = atom.ident {
|
||||
match &fourcc {
|
||||
b"mdhd" => {
|
||||
skip_unneeded(data, atom.extended, atom.len)?;
|
||||
skip_unneeded(reader, atom.extended, atom.len)?;
|
||||
mdhd = Some(atom)
|
||||
},
|
||||
b"hdlr" => {
|
||||
// The hdlr atom is followed by 8 zeros
|
||||
data.seek(SeekFrom::Current(8))?;
|
||||
reader.seek(SeekFrom::Current(8))?;
|
||||
|
||||
let mut handler_type = [0; 4];
|
||||
data.read_exact(&mut handler_type)?;
|
||||
reader.read_exact(&mut handler_type)?;
|
||||
|
||||
if &handler_type == b"soun" {
|
||||
audio_track = true
|
||||
}
|
||||
|
||||
skip_unneeded(data, atom.extended, atom.len - 12)?;
|
||||
skip_unneeded(reader, atom.extended, atom.len - 12)?;
|
||||
},
|
||||
b"minf" => minf = Some(atom),
|
||||
_ => {
|
||||
skip_unneeded(data, atom.extended, atom.len)?;
|
||||
skip_unneeded(reader, atom.extended, atom.len)?;
|
||||
},
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
skip_unneeded(data, atom.extended, atom.len)?;
|
||||
skip_unneeded(reader, atom.extended, atom.len)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -288,26 +288,26 @@ where
|
|||
},
|
||||
};
|
||||
|
||||
data.seek(SeekFrom::Start(mdhd.start + 8))?;
|
||||
reader.seek(SeekFrom::Start(mdhd.start + 8))?;
|
||||
|
||||
let version = data.read_u8()?;
|
||||
let _flags = data.read_uint::<BigEndian>(3)?;
|
||||
let version = reader.read_u8()?;
|
||||
let _flags = reader.read_uint(3)?;
|
||||
|
||||
let (timescale, duration) = if version == 1 {
|
||||
// We don't care about these two values
|
||||
let _creation_time = data.read_u64::<BigEndian>()?;
|
||||
let _modification_time = data.read_u64::<BigEndian>()?;
|
||||
let _creation_time = reader.read_u64()?;
|
||||
let _modification_time = reader.read_u64()?;
|
||||
|
||||
let timescale = data.read_u32::<BigEndian>()?;
|
||||
let duration = data.read_u64::<BigEndian>()?;
|
||||
let timescale = reader.read_u32()?;
|
||||
let duration = reader.read_u64()?;
|
||||
|
||||
(timescale, duration)
|
||||
} else {
|
||||
let _creation_time = data.read_u32::<BigEndian>()?;
|
||||
let _modification_time = data.read_u32::<BigEndian>()?;
|
||||
let _creation_time = reader.read_u32()?;
|
||||
let _modification_time = reader.read_u32()?;
|
||||
|
||||
let timescale = data.read_u32::<BigEndian>()?;
|
||||
let duration = data.read_u32::<BigEndian>()?;
|
||||
let timescale = reader.read_u32()?;
|
||||
let duration = reader.read_u32()?;
|
||||
|
||||
(timescale, u64::from(duration))
|
||||
};
|
||||
|
@ -321,14 +321,16 @@ where
|
|||
};
|
||||
|
||||
if let Some(minf) = minf {
|
||||
data.seek(SeekFrom::Start(minf.start + 8))?;
|
||||
reader.seek(SeekFrom::Start(minf.start + 8))?;
|
||||
|
||||
if let Some(stbl) = nested_atom(data, minf.len, b"stbl")? {
|
||||
if let Some(stsd) = nested_atom(data, stbl.len, b"stsd")? {
|
||||
if let Some(stbl) = nested_atom(reader, minf.len, b"stbl")? {
|
||||
if let Some(stsd) = nested_atom(reader, stbl.len, b"stsd")? {
|
||||
let mut stsd = try_vec![0; (stsd.len - 8) as usize];
|
||||
data.read_exact(&mut stsd)?;
|
||||
reader.read_exact(&mut stsd)?;
|
||||
|
||||
let mut stsd_reader = Cursor::new(&*stsd);
|
||||
let mut cursor = Cursor::new(&*stsd);
|
||||
|
||||
let mut stsd_reader = AtomReader::new(&mut cursor)?;
|
||||
|
||||
// Skipping 8 bytes
|
||||
// Version (1)
|
||||
|
@ -336,7 +338,7 @@ where
|
|||
// Number of entries (4)
|
||||
stsd_reader.seek(SeekFrom::Current(8))?;
|
||||
|
||||
let atom = AtomInfo::read(&mut stsd_reader)?;
|
||||
let atom = AtomInfo::read(&mut stsd_reader, stsd.len() as u64)?;
|
||||
|
||||
if let AtomIdent::Fourcc(ref fourcc) = atom.ident {
|
||||
match fourcc {
|
||||
|
@ -357,7 +359,7 @@ where
|
|||
|
||||
if properties.audio_bitrate == 0 {
|
||||
properties.audio_bitrate =
|
||||
(u128::from(mdat_length(data)? * 8) / duration_millis) as u32;
|
||||
(u128::from(mdat_length(reader)? * 8) / duration_millis) as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -368,7 +370,7 @@ where
|
|||
Ok(properties)
|
||||
}
|
||||
|
||||
fn mp4a_properties<R>(stsd: &mut R, properties: &mut Mp4Properties) -> Result<()>
|
||||
fn mp4a_properties<R>(stsd: &mut AtomReader<R>, properties: &mut Mp4Properties) -> Result<()>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
|
@ -393,23 +395,23 @@ where
|
|||
// Vendor (4)
|
||||
stsd.seek(SeekFrom::Current(16))?;
|
||||
|
||||
properties.channels = stsd.read_u16::<BigEndian>()? as u8;
|
||||
properties.channels = stsd.read_u16()? as u8;
|
||||
|
||||
// Skipping 4 bytes
|
||||
// Sample size (2)
|
||||
// Compression ID (2)
|
||||
stsd.seek(SeekFrom::Current(4))?;
|
||||
|
||||
properties.sample_rate = stsd.read_u32::<BigEndian>()?;
|
||||
properties.sample_rate = stsd.read_u32()?;
|
||||
|
||||
stsd.seek(SeekFrom::Current(2))?;
|
||||
|
||||
// This information is often followed by an esds (elementary stream descriptor) atom containing the bitrate
|
||||
if let Ok(esds) = AtomInfo::read(stsd) {
|
||||
if let Ok(esds) = stsd.next() {
|
||||
// There are 4 bytes we expect to be zeroed out
|
||||
// Version (1)
|
||||
// Flags (3)
|
||||
if esds.ident == AtomIdent::Fourcc(*b"esds") && stsd.read_u32::<BigEndian>()? == 0 {
|
||||
if esds.ident == AtomIdent::Fourcc(*b"esds") && stsd.read_u32()? == 0 {
|
||||
let descriptor = Descriptor::read(stsd)?;
|
||||
if descriptor.tag == ELEMENTARY_DESCRIPTOR_TAG {
|
||||
// Skipping 3 bytes
|
||||
|
@ -434,7 +436,7 @@ where
|
|||
// Max bitrate (4)
|
||||
stsd.seek(SeekFrom::Current(8))?;
|
||||
|
||||
let average_bitrate = stsd.read_u32::<BigEndian>()?;
|
||||
let average_bitrate = stsd.read_u32()?;
|
||||
|
||||
// Yet another descriptor to check
|
||||
let descriptor = Descriptor::read(stsd)?;
|
||||
|
@ -513,11 +515,11 @@ where
|
|||
stsd.read_exact(&mut ident)?;
|
||||
|
||||
if &ident == b"\0ALS\0" {
|
||||
properties.sample_rate = stsd.read_u32::<BigEndian>()?;
|
||||
properties.sample_rate = stsd.read_u32()?;
|
||||
|
||||
// Sample count
|
||||
stsd.seek(SeekFrom::Current(4))?;
|
||||
properties.channels = stsd.read_u16::<BigEndian>()? as u8 + 1;
|
||||
properties.channels = stsd.read_u16()? as u8 + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -533,7 +535,7 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn alac_properties<R>(stsd: &mut R, properties: &mut Mp4Properties) -> Result<()>
|
||||
fn alac_properties<R>(stsd: &mut AtomReader<R>, properties: &mut Mp4Properties) -> Result<()>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
|
@ -551,7 +553,7 @@ where
|
|||
// First alac atom's content (28)
|
||||
stsd.seek(SeekFrom::Start(44))?;
|
||||
|
||||
if let Ok(alac) = AtomInfo::read(stsd) {
|
||||
if let Ok(alac) = stsd.next() {
|
||||
if alac.ident == AtomIdent::Fourcc(*b"alac") {
|
||||
properties.codec = Mp4Codec::ALAC;
|
||||
|
||||
|
@ -578,15 +580,15 @@ where
|
|||
// Max frame size (4)
|
||||
stsd.seek(SeekFrom::Current(6))?;
|
||||
|
||||
properties.audio_bitrate = stsd.read_u32::<BigEndian>()? / 1000;
|
||||
properties.sample_rate = stsd.read_u32::<BigEndian>()?;
|
||||
properties.audio_bitrate = stsd.read_u32()? / 1000;
|
||||
properties.sample_rate = stsd.read_u32()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flac_properties<R>(stsd: &mut R, properties: &mut Mp4Properties) -> Result<()>
|
||||
fn flac_properties<R>(stsd: &mut AtomReader<R>, properties: &mut Mp4Properties) -> Result<()>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
|
@ -601,8 +603,8 @@ where
|
|||
// Vendor (4)
|
||||
stsd.seek(SeekFrom::Current(16))?;
|
||||
|
||||
properties.channels = stsd.read_u16::<BigEndian>()? as u8;
|
||||
properties.bit_depth = Some(stsd.read_u16::<BigEndian>()? as u8);
|
||||
properties.channels = stsd.read_u16()? as u8;
|
||||
properties.bit_depth = Some(stsd.read_u16()? as u8);
|
||||
|
||||
// Skipping 4 bytes
|
||||
//
|
||||
|
@ -610,11 +612,11 @@ where
|
|||
// Packet size (2)
|
||||
stsd.seek(SeekFrom::Current(4))?;
|
||||
|
||||
properties.sample_rate = u32::from(stsd.read_u16::<BigEndian>()?);
|
||||
properties.sample_rate = u32::from(stsd.read_u16()?);
|
||||
|
||||
let _reserved = stsd.read_u16::<BigEndian>()?;
|
||||
let _reserved = stsd.read_u16()?;
|
||||
|
||||
let dfla_atom = AtomInfo::read(stsd)?;
|
||||
let dfla_atom = stsd.next()?;
|
||||
match dfla_atom.ident {
|
||||
// There should be a dfla atom, but it's not worth erroring if absent.
|
||||
AtomIdent::Fourcc(ref fourcc) if fourcc == b"dfla" => {},
|
||||
|
@ -645,18 +647,18 @@ where
|
|||
}
|
||||
|
||||
// Used to calculate the bitrate, when it isn't readily available to us
|
||||
fn mdat_length<R>(data: &mut R) -> Result<u64>
|
||||
fn mdat_length<R>(reader: &mut AtomReader<R>) -> Result<u64>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
data.seek(SeekFrom::Start(0))?;
|
||||
reader.seek(SeekFrom::Start(0))?;
|
||||
|
||||
while let Ok(atom) = AtomInfo::read(data) {
|
||||
while let Ok(atom) = reader.next() {
|
||||
if atom.ident == AtomIdent::Fourcc(*b"mdat") {
|
||||
return Ok(atom.len);
|
||||
}
|
||||
|
||||
skip_unneeded(data, atom.extended, atom.len)?;
|
||||
skip_unneeded(reader, atom.extended, atom.len)?;
|
||||
}
|
||||
|
||||
Err(FileDecodingError::new(FileType::MP4, "Failed to find \"mdat\" atom").into())
|
||||
|
|
203
src/mp4/read.rs
203
src/mp4/read.rs
|
@ -7,14 +7,126 @@ use crate::file::FileType;
|
|||
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[cfg(feature = "mp4_ilst")]
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
|
||||
pub(in crate::mp4) fn verify_mp4<R>(data: &mut R) -> Result<String>
|
||||
pub(super) struct AtomReader<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let atom = AtomInfo::read(data)?;
|
||||
reader: R,
|
||||
remaining_size: u64,
|
||||
len: u64,
|
||||
}
|
||||
|
||||
impl<R> AtomReader<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
pub(super) fn new(mut reader: R) -> Result<Self> {
|
||||
use crate::traits::SeekStreamLen;
|
||||
|
||||
#[allow(unstable_name_collisions)]
|
||||
let len = reader.stream_len()?;
|
||||
Ok(Self {
|
||||
reader,
|
||||
remaining_size: len,
|
||||
len,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn read_u8(&mut self) -> std::io::Result<u8> {
|
||||
self.remaining_size = self.remaining_size.saturating_sub(1);
|
||||
self.reader.read_u8()
|
||||
}
|
||||
|
||||
pub(super) fn read_u16(&mut self) -> std::io::Result<u16> {
|
||||
self.remaining_size = self.remaining_size.saturating_sub(2);
|
||||
self.reader.read_u16::<BigEndian>()
|
||||
}
|
||||
|
||||
pub(super) fn read_u32(&mut self) -> std::io::Result<u32> {
|
||||
self.remaining_size = self.remaining_size.saturating_sub(4);
|
||||
self.reader.read_u32::<BigEndian>()
|
||||
}
|
||||
|
||||
pub(super) fn read_u64(&mut self) -> std::io::Result<u64> {
|
||||
self.remaining_size = self.remaining_size.saturating_sub(8);
|
||||
self.reader.read_u64::<BigEndian>()
|
||||
}
|
||||
|
||||
pub(super) fn read_uint(&mut self, size: usize) -> std::io::Result<u64> {
|
||||
self.remaining_size = self.remaining_size.saturating_sub(size as u64);
|
||||
self.reader.read_uint::<BigEndian>(size)
|
||||
}
|
||||
|
||||
pub(super) fn next(&mut self) -> Result<AtomInfo> {
|
||||
if self.remaining_size < 8 {
|
||||
return Err(LoftyError::new(ErrorKind::TooMuchData));
|
||||
}
|
||||
|
||||
AtomInfo::read(self, self.remaining_size)
|
||||
}
|
||||
|
||||
pub(super) fn position(&mut self) -> std::io::Result<u64> {
|
||||
self.reader.stream_position()
|
||||
}
|
||||
|
||||
pub(super) fn into_inner(self) -> R {
|
||||
self.reader
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Seek for AtomReader<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
|
||||
match pos {
|
||||
SeekFrom::Start(_) | SeekFrom::End(_) => {
|
||||
let ret = self.reader.seek(pos)?;
|
||||
let new_rem = self.len.saturating_sub(ret);
|
||||
|
||||
self.remaining_size = new_rem;
|
||||
Ok(ret)
|
||||
},
|
||||
SeekFrom::Current(s) => {
|
||||
if s.is_negative() {
|
||||
self.remaining_size = self.remaining_size.saturating_add(s.unsigned_abs());
|
||||
} else {
|
||||
self.remaining_size = self.remaining_size.saturating_sub(s as u64);
|
||||
}
|
||||
|
||||
self.reader.seek(pos)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn stream_position(&mut self) -> std::io::Result<u64> {
|
||||
self.position()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Read for AtomReader<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
if self.remaining_size == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let r = self.reader.read(buf)?;
|
||||
self.remaining_size = self.remaining_size.saturating_sub(r as u64);
|
||||
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::mp4) fn verify_mp4<R>(reader: &mut AtomReader<R>) -> Result<String>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let atom = reader.next()?;
|
||||
|
||||
if atom.ident != AtomIdent::Fourcc(*b"ftyp") {
|
||||
return Err(LoftyError::new(ErrorKind::UnknownFormat));
|
||||
|
@ -27,9 +139,9 @@ where
|
|||
}
|
||||
|
||||
let mut major_brand = vec![0; 4];
|
||||
data.read_exact(&mut major_brand)?;
|
||||
reader.read_exact(&mut major_brand)?;
|
||||
|
||||
data.seek(SeekFrom::Current((atom.len - 12) as i64))?;
|
||||
reader.seek(SeekFrom::Current((atom.len - 12) as i64))?;
|
||||
|
||||
String::from_utf8(major_brand)
|
||||
.map_err(|_| LoftyError::new(ErrorKind::BadAtom("Unable to parse \"ftyp\"'s major brand")))
|
||||
|
@ -39,53 +151,58 @@ pub(crate) fn read_from<R>(data: &mut R, read_properties: bool) -> Result<Mp4Fil
|
|||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let ftyp = verify_mp4(data)?;
|
||||
let mut reader = AtomReader::new(data)?;
|
||||
|
||||
Moov::find(data)?;
|
||||
let moov = Moov::parse(data, read_properties)?;
|
||||
let ftyp = verify_mp4(&mut reader)?;
|
||||
|
||||
let file_length = data.seek(SeekFrom::End(0))?;
|
||||
Moov::find(&mut reader)?;
|
||||
let moov = Moov::parse(&mut reader, read_properties)?;
|
||||
|
||||
let file_length = reader.seek(SeekFrom::End(0))?;
|
||||
|
||||
Ok(Mp4File {
|
||||
ftyp,
|
||||
#[cfg(feature = "mp4_ilst")]
|
||||
ilst: moov.meta,
|
||||
properties: if read_properties {
|
||||
super::properties::read_properties(data, &moov.traks, file_length)?
|
||||
super::properties::read_properties(&mut reader, &moov.traks, file_length)?
|
||||
} else {
|
||||
Mp4Properties::default()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn skip_unneeded<R>(data: &mut R, ext: bool, len: u64) -> Result<()>
|
||||
pub(super) fn skip_unneeded<R>(reader: &mut R, ext: bool, len: u64) -> Result<()>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
if ext {
|
||||
let pos = data.stream_position()?;
|
||||
let pos = reader.stream_position()?;
|
||||
|
||||
if let (pos, false) = pos.overflowing_add(len - 8) {
|
||||
data.seek(SeekFrom::Start(pos))?;
|
||||
reader.seek(SeekFrom::Start(pos))?;
|
||||
} else {
|
||||
return Err(LoftyError::new(ErrorKind::TooMuchData));
|
||||
}
|
||||
} else {
|
||||
data.seek(SeekFrom::Current(i64::from(len as u32) - 8))?;
|
||||
reader.seek(SeekFrom::Current(i64::from(len as u32) - 8))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn nested_atom<R>(data: &mut R, len: u64, expected: &[u8]) -> Result<Option<AtomInfo>>
|
||||
pub(super) fn nested_atom<R>(
|
||||
reader: &mut R,
|
||||
mut len: u64,
|
||||
expected: &[u8],
|
||||
) -> Result<Option<AtomInfo>>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let mut read = 8;
|
||||
let mut ret = None;
|
||||
|
||||
while read < len {
|
||||
let atom = AtomInfo::read(data)?;
|
||||
while len > 8 {
|
||||
let atom = AtomInfo::read(reader, len)?;
|
||||
|
||||
match atom.ident {
|
||||
AtomIdent::Fourcc(ref fourcc) if fourcc == expected => {
|
||||
|
@ -93,8 +210,8 @@ where
|
|||
break;
|
||||
},
|
||||
_ => {
|
||||
skip_unneeded(data, atom.extended, atom.len)?;
|
||||
read += atom.len
|
||||
skip_unneeded(reader, atom.extended, atom.len)?;
|
||||
len = len.saturating_sub(atom.len);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -104,21 +221,24 @@ where
|
|||
|
||||
#[cfg(feature = "mp4_ilst")]
|
||||
// Creates a tree of nested atoms
|
||||
pub(super) fn atom_tree<R>(data: &mut R, len: u64, up_to: &[u8]) -> Result<(usize, Vec<AtomInfo>)>
|
||||
pub(super) fn atom_tree<R>(
|
||||
reader: &mut R,
|
||||
mut len: u64,
|
||||
up_to: &[u8],
|
||||
) -> Result<(usize, Vec<AtomInfo>)>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let mut read = 8;
|
||||
let mut found_idx: usize = 0;
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let mut i = 0;
|
||||
|
||||
while read < len {
|
||||
let atom = AtomInfo::read(data)?;
|
||||
while len > 8 {
|
||||
let atom = AtomInfo::read(reader, len)?;
|
||||
|
||||
skip_unneeded(data, atom.extended, atom.len)?;
|
||||
read += atom.len;
|
||||
skip_unneeded(reader, atom.extended, atom.len)?;
|
||||
len = len.saturating_sub(atom.len);
|
||||
|
||||
if let AtomIdent::Fourcc(ref fourcc) = atom.ident {
|
||||
i += 1;
|
||||
|
@ -137,11 +257,11 @@ where
|
|||
}
|
||||
|
||||
#[cfg(feature = "mp4_ilst")]
|
||||
pub(super) fn meta_is_full<R>(data: &mut R) -> Result<bool>
|
||||
pub(super) fn meta_is_full<R>(reader: &mut R) -> Result<bool>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let meta_pos = data.stream_position()?;
|
||||
let meta_pos = reader.stream_position()?;
|
||||
|
||||
// A full `meta` atom should have the following:
|
||||
//
|
||||
|
@ -150,39 +270,20 @@ where
|
|||
//
|
||||
// However, it's possible that it is written as a normal atom,
|
||||
// meaning this would be the size of the next atom.
|
||||
let _version_flags = data.read_u32::<BigEndian>()?;
|
||||
let _version_flags = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
// Check if the next four bytes is one of the nested `meta` atoms
|
||||
let mut possible_ident = [0; 4];
|
||||
data.read_exact(&mut possible_ident)?;
|
||||
reader.read_exact(&mut possible_ident)?;
|
||||
|
||||
match &possible_ident {
|
||||
b"hdlr" | b"ilst" | b"mhdr" | b"ctry" | b"lang" => {
|
||||
data.seek(SeekFrom::Start(meta_pos))?;
|
||||
reader.seek(SeekFrom::Start(meta_pos))?;
|
||||
Ok(false)
|
||||
},
|
||||
_ => {
|
||||
data.seek(SeekFrom::Start(meta_pos + 4))?;
|
||||
reader.seek(SeekFrom::Start(meta_pos + 4))?;
|
||||
Ok(true)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::mp4::{Ilst, Mp4File};
|
||||
use crate::tag::utils::test_utils;
|
||||
use crate::AudioFile;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn zero_sized_ilst() {
|
||||
let file = Mp4File::read_from(
|
||||
&mut Cursor::new(test_utils::read_path("tests/files/assets/zero/zero.ilst")),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(file.ilst, Some(Ilst::default()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::atom_info::{AtomIdent, AtomInfo};
|
||||
use super::read::skip_unneeded;
|
||||
use super::read::{skip_unneeded, AtomReader};
|
||||
use crate::error::Result;
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
@ -9,7 +9,7 @@ pub(crate) struct Trak {
|
|||
}
|
||||
|
||||
impl Trak {
|
||||
pub(crate) fn parse<R>(data: &mut R, trak: &AtomInfo) -> Result<Self>
|
||||
pub(super) fn parse<R>(reader: &mut AtomReader<R>, trak: &AtomInfo) -> Result<Self>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
|
@ -18,15 +18,15 @@ impl Trak {
|
|||
let mut read = 8;
|
||||
|
||||
while read < trak.len {
|
||||
let atom = AtomInfo::read(data)?;
|
||||
let atom = reader.next()?;
|
||||
|
||||
if atom.ident == AtomIdent::Fourcc(*b"mdia") {
|
||||
mdia = Some(atom);
|
||||
data.seek(SeekFrom::Current((trak.len - read - 8) as i64))?;
|
||||
reader.seek(SeekFrom::Current((trak.len - read - 8) as i64))?;
|
||||
break;
|
||||
}
|
||||
|
||||
skip_unneeded(data, atom.extended, atom.len)?;
|
||||
skip_unneeded(reader, atom.extended, atom.len)?;
|
||||
read += atom.len;
|
||||
}
|
||||
|
||||
|
|
|
@ -196,3 +196,19 @@ pub trait TagExt: Accessor + Into<Tag> + Sized {
|
|||
/// NOTE: This will **not** remove any format-specific extras, such as flags
|
||||
fn clear(&mut self);
|
||||
}
|
||||
|
||||
// TODO: https://github.com/rust-lang/rust/issues/59359
|
||||
pub(crate) trait SeekStreamLen: std::io::Seek {
|
||||
fn stream_len(&mut self) -> crate::error::Result<u64> {
|
||||
use std::io::SeekFrom;
|
||||
|
||||
let current_pos = self.stream_position()?;
|
||||
let len = self.seek(SeekFrom::End(0))?;
|
||||
|
||||
self.seek(SeekFrom::Start(current_pos))?;
|
||||
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SeekStreamLen for T where T: std::io::Seek {}
|
||||
|
|
|
@ -1 +1,7 @@
|
|||
// TODO
|
||||
use crate::oom_test;
|
||||
use lofty::mp4::Mp4File;
|
||||
|
||||
#[test]
|
||||
fn oom1() {
|
||||
oom_test::<Mp4File>("mp4file_read_from/oom-db2665d79ec9c045bdb9c1e9a3d0c93e7e59393e");
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue