2021-11-21 20:18:19 +00:00
|
|
|
use super::atom_info::AtomInfo;
|
|
|
|
use super::ilst::AtomIdent;
|
2021-09-03 00:01:40 +00:00
|
|
|
use super::read::nested_atom;
|
|
|
|
use super::read::skip_unneeded;
|
|
|
|
use super::trak::Trak;
|
2021-10-01 23:30:51 +00:00
|
|
|
use super::{Mp4Codec, Mp4Properties};
|
2021-09-03 00:01:40 +00:00
|
|
|
use crate::error::{LoftyError, Result};
|
|
|
|
|
|
|
|
use std::io::{Cursor, Read, Seek, SeekFrom};
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
use byteorder::{BigEndian, ReadBytesExt};
|
|
|
|
|
2021-10-01 23:30:51 +00:00
|
|
|
pub(crate) fn read_properties<R>(data: &mut R, traks: &[Trak]) -> Result<Mp4Properties>
|
2021-09-03 00:01:40 +00:00
|
|
|
where
|
|
|
|
R: Read + Seek,
|
|
|
|
{
|
|
|
|
// We need the mdhd and minf atoms from the audio track
|
|
|
|
let mut audio_track = false;
|
|
|
|
let mut mdhd = None;
|
|
|
|
let mut minf = None;
|
|
|
|
|
|
|
|
// We have to search through the traks with a mdia atom to find the audio track
|
|
|
|
for mdia in traks.iter().filter_map(|trak| trak.mdia.as_ref()) {
|
|
|
|
if audio_track {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
data.seek(SeekFrom::Start(mdia.start + 8))?;
|
|
|
|
|
|
|
|
let mut read = 8;
|
|
|
|
|
|
|
|
while read < mdia.len {
|
2021-11-21 20:18:19 +00:00
|
|
|
let atom = AtomInfo::read(data)?;
|
2021-09-03 00:01:40 +00:00
|
|
|
|
2021-11-21 20:18:19 +00:00
|
|
|
if let AtomIdent::Fourcc(fourcc) = atom.ident {
|
|
|
|
match &fourcc {
|
|
|
|
b"mdhd" => {
|
|
|
|
skip_unneeded(data, atom.extended, atom.len)?;
|
|
|
|
mdhd = Some(atom)
|
2021-11-22 10:14:29 +00:00
|
|
|
},
|
2021-11-21 20:18:19 +00:00
|
|
|
b"hdlr" => {
|
|
|
|
// The hdlr atom is followed by 8 zeros
|
|
|
|
data.seek(SeekFrom::Current(8))?;
|
2021-09-03 00:01:40 +00:00
|
|
|
|
2021-11-21 20:18:19 +00:00
|
|
|
let mut handler_type = [0; 4];
|
|
|
|
data.read_exact(&mut handler_type)?;
|
2021-09-03 00:01:40 +00:00
|
|
|
|
2021-11-21 20:18:19 +00:00
|
|
|
if &handler_type == b"soun" {
|
|
|
|
audio_track = true
|
|
|
|
}
|
2021-09-03 00:01:40 +00:00
|
|
|
|
2021-11-21 20:18:19 +00:00
|
|
|
skip_unneeded(data, atom.extended, atom.len - 12)?;
|
2021-11-22 10:14:29 +00:00
|
|
|
},
|
2021-11-21 20:18:19 +00:00
|
|
|
b"minf" => minf = Some(atom),
|
|
|
|
_ => {
|
|
|
|
skip_unneeded(data, atom.extended, atom.len)?;
|
|
|
|
read += atom.len;
|
2021-11-22 10:14:29 +00:00
|
|
|
},
|
2021-10-01 21:59:53 +00:00
|
|
|
}
|
2021-11-21 20:18:19 +00:00
|
|
|
|
|
|
|
continue;
|
2021-09-03 00:01:40 +00:00
|
|
|
}
|
2021-11-21 20:18:19 +00:00
|
|
|
|
|
|
|
skip_unneeded(data, atom.extended, atom.len)?;
|
|
|
|
read += atom.len;
|
2021-09-03 00:01:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !audio_track {
|
|
|
|
return Err(LoftyError::Mp4("File contains no audio tracks"));
|
|
|
|
}
|
|
|
|
|
|
|
|
let duration = match mdhd {
|
|
|
|
Some(mdhd) => {
|
|
|
|
data.seek(SeekFrom::Start(mdhd.start + 8))?;
|
|
|
|
|
|
|
|
let version = data.read_u8()?;
|
|
|
|
let _flags = data.read_uint::<BigEndian>(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 timescale = data.read_u32::<BigEndian>()?;
|
|
|
|
let duration = data.read_u64::<BigEndian>()?;
|
|
|
|
|
|
|
|
(timescale, duration)
|
|
|
|
} else {
|
|
|
|
let _creation_time = data.read_u32::<BigEndian>()?;
|
|
|
|
let _modification_time = data.read_u32::<BigEndian>()?;
|
|
|
|
|
|
|
|
let timescale = data.read_u32::<BigEndian>()?;
|
|
|
|
let duration = data.read_u32::<BigEndian>()?;
|
|
|
|
|
|
|
|
(timescale, u64::from(duration))
|
|
|
|
};
|
|
|
|
|
|
|
|
Duration::from_millis(duration * 1000 / u64::from(timescale))
|
2021-11-22 10:14:29 +00:00
|
|
|
},
|
2021-09-03 00:01:40 +00:00
|
|
|
None => return Err(LoftyError::BadAtom("Expected atom \"trak.mdia.mdhd\"")),
|
|
|
|
};
|
|
|
|
|
|
|
|
// We create the properties here, since it is possible the other information isn't available
|
2021-10-01 23:30:51 +00:00
|
|
|
let mut properties = Mp4Properties {
|
|
|
|
codec: Mp4Codec::Unknown(String::new()),
|
2021-09-03 00:01:40 +00:00
|
|
|
duration,
|
2021-10-01 23:30:51 +00:00
|
|
|
bitrate: 0,
|
|
|
|
sample_rate: 0,
|
|
|
|
channels: 0,
|
2021-09-03 00:01:40 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(minf) = minf {
|
|
|
|
data.seek(SeekFrom::Start(minf.start + 8))?;
|
|
|
|
|
2021-11-21 20:18:19 +00:00
|
|
|
if let Some(stbl) = nested_atom(data, minf.len, b"stbl")? {
|
|
|
|
if let Some(stsd) = nested_atom(data, stbl.len, b"stsd")? {
|
2021-09-03 00:01:40 +00:00
|
|
|
let mut stsd = vec![0; (stsd.len - 8) as usize];
|
|
|
|
data.read_exact(&mut stsd)?;
|
|
|
|
|
|
|
|
let mut stsd_reader = Cursor::new(&*stsd);
|
|
|
|
|
2021-10-01 23:30:51 +00:00
|
|
|
// Skipping 8 bytes
|
2021-09-03 00:01:40 +00:00
|
|
|
// Version (1)
|
|
|
|
// Flags (3)
|
|
|
|
// Number of entries (4)
|
|
|
|
stsd_reader.seek(SeekFrom::Start(8))?;
|
|
|
|
|
2021-11-21 20:18:19 +00:00
|
|
|
let atom = AtomInfo::read(&mut stsd_reader)?;
|
|
|
|
|
|
|
|
if let AtomIdent::Fourcc(ref fourcc) = atom.ident {
|
|
|
|
match fourcc {
|
|
|
|
b"mp4a" => mp4a_properties(&mut stsd_reader, &mut properties)?,
|
|
|
|
b"alac" => alac_properties(&mut stsd_reader, &mut properties)?,
|
|
|
|
unknown => {
|
|
|
|
if let Ok(codec) = std::str::from_utf8(unknown) {
|
|
|
|
properties.codec = Mp4Codec::Unknown(codec.to_string())
|
|
|
|
}
|
2021-11-22 10:14:29 +00:00
|
|
|
},
|
2021-11-21 20:18:19 +00:00
|
|
|
}
|
2021-09-03 00:01:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(properties)
|
|
|
|
}
|
|
|
|
|
2021-10-01 23:30:51 +00:00
|
|
|
fn mp4a_properties<R>(data: &mut R, properties: &mut Mp4Properties) -> Result<()>
|
2021-09-03 00:01:40 +00:00
|
|
|
where
|
|
|
|
R: Read + Seek,
|
|
|
|
{
|
2021-10-01 23:30:51 +00:00
|
|
|
// Skipping 16 bytes
|
2021-09-03 00:01:40 +00:00
|
|
|
// Reserved (6)
|
|
|
|
// Data reference index (2)
|
|
|
|
// Version (2)
|
|
|
|
// Revision level (2)
|
|
|
|
// Vendor (4)
|
|
|
|
data.seek(SeekFrom::Current(16))?;
|
|
|
|
|
2021-10-01 23:30:51 +00:00
|
|
|
properties.channels = data.read_u16::<BigEndian>()? as u8;
|
2021-09-03 00:01:40 +00:00
|
|
|
|
2021-10-01 23:30:51 +00:00
|
|
|
// Skipping 4 bytes
|
2021-09-03 00:01:40 +00:00
|
|
|
// Sample size (2)
|
|
|
|
// Compression ID (2)
|
|
|
|
data.seek(SeekFrom::Current(4))?;
|
|
|
|
|
2021-10-01 23:30:51 +00:00
|
|
|
properties.sample_rate = data.read_u32::<BigEndian>()?;
|
2021-09-03 00:01:40 +00:00
|
|
|
|
|
|
|
data.seek(SeekFrom::Current(2))?;
|
|
|
|
|
|
|
|
// This information is often followed by an esds (elementary stream descriptor) atom containing the bitrate
|
2021-11-21 20:18:19 +00:00
|
|
|
if let Ok(esds) = AtomInfo::read(data) {
|
2021-09-03 00:01:40 +00:00
|
|
|
// There are 4 bytes we expect to be zeroed out
|
|
|
|
// Version (1)
|
|
|
|
// Flags (3)
|
2021-11-21 20:18:19 +00:00
|
|
|
if esds.ident == AtomIdent::Fourcc(*b"esds") && data.read_u32::<BigEndian>()? == 0 {
|
2021-09-03 00:01:40 +00:00
|
|
|
let mut descriptor = [0; 4];
|
|
|
|
data.read_exact(&mut descriptor)?;
|
|
|
|
|
|
|
|
// [0x03, 0x80, 0x80, 0x80] marks the start of the elementary stream descriptor.
|
|
|
|
// 0x03 being the object descriptor
|
|
|
|
if descriptor == [0x03, 0x80, 0x80, 0x80] {
|
2021-10-01 23:30:51 +00:00
|
|
|
// Skipping 4 bytes
|
2021-09-03 00:01:40 +00:00
|
|
|
// Descriptor length (1)
|
|
|
|
// Elementary stream ID (2)
|
|
|
|
// Flags (1)
|
|
|
|
let _info = data.read_u32::<BigEndian>()?;
|
|
|
|
|
|
|
|
// There is another descriptor embedded in the previous one
|
|
|
|
let mut specific_config = [0; 4];
|
|
|
|
data.read_exact(&mut specific_config)?;
|
|
|
|
|
|
|
|
// [0x04, 0x80, 0x80, 0x80] marks the start of the descriptor configuration
|
|
|
|
if specific_config == [0x04, 0x80, 0x80, 0x80] {
|
2021-10-01 23:30:51 +00:00
|
|
|
// Skipping 10 bytes
|
2021-09-03 00:01:40 +00:00
|
|
|
// Descriptor length (1)
|
|
|
|
// MPEG4 Audio (1)
|
|
|
|
// Stream type (1)
|
|
|
|
// Buffer size (3)
|
|
|
|
// Max bitrate (4)
|
|
|
|
let mut info = [0; 10];
|
|
|
|
data.read_exact(&mut info)?;
|
|
|
|
|
|
|
|
let average_bitrate = data.read_u32::<BigEndian>()?;
|
|
|
|
|
|
|
|
if average_bitrate > 0 {
|
2021-10-01 23:30:51 +00:00
|
|
|
properties.bitrate = average_bitrate / 1000
|
2021-09-03 00:01:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-10-01 23:30:51 +00:00
|
|
|
fn alac_properties<R>(data: &mut R, properties: &mut Mp4Properties) -> Result<()>
|
2021-09-03 00:01:40 +00:00
|
|
|
where
|
|
|
|
R: Read + Seek,
|
|
|
|
{
|
2021-10-01 23:30:51 +00:00
|
|
|
// With ALAC, we can expect the length to be exactly 88 (80 here since we removed the size and identifier)
|
2021-09-03 00:01:40 +00:00
|
|
|
if data.seek(SeekFrom::End(0))? != 80 {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unlike the mp4a atom, we cannot read the data that immediately follows it
|
|
|
|
// For ALAC, we have to skip the first "alac" atom entirely, and read the one that
|
|
|
|
// immediately follows it.
|
|
|
|
//
|
|
|
|
// We are skipping over 44 bytes total
|
|
|
|
// stsd information/alac atom header (16, see `read_properties`)
|
|
|
|
// First alac atom's content (28)
|
|
|
|
data.seek(SeekFrom::Start(44))?;
|
|
|
|
|
2021-11-21 20:18:19 +00:00
|
|
|
if let Ok(alac) = AtomInfo::read(data) {
|
|
|
|
if alac.ident == AtomIdent::Fourcc(*b"alac") {
|
2021-10-01 23:30:51 +00:00
|
|
|
// Skipping 13 bytes
|
2021-09-03 00:01:40 +00:00
|
|
|
// Version (4)
|
|
|
|
// Samples per frame (4)
|
|
|
|
// Compatible version (1)
|
|
|
|
// Sample size (1)
|
|
|
|
// Rice history mult (1)
|
|
|
|
// Rice initial history (1)
|
|
|
|
// Rice parameter limit (1)
|
|
|
|
data.seek(SeekFrom::Current(13))?;
|
|
|
|
|
2021-10-01 23:30:51 +00:00
|
|
|
properties.channels = data.read_u8()?;
|
2021-09-03 00:01:40 +00:00
|
|
|
|
2021-10-01 23:30:51 +00:00
|
|
|
// Skipping 6 bytes
|
2021-09-03 00:01:40 +00:00
|
|
|
// Max run (2)
|
|
|
|
// Max frame size (4)
|
|
|
|
data.seek(SeekFrom::Current(6))?;
|
|
|
|
|
2021-10-01 23:30:51 +00:00
|
|
|
properties.bitrate = data.read_u32::<BigEndian>()?;
|
|
|
|
properties.sample_rate = data.read_u32::<BigEndian>()?;
|
2021-09-03 00:01:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|