lofty-rs/src/logic/mp4/properties.rs

248 lines
6.6 KiB
Rust
Raw Normal View History

2021-09-03 00:01:40 +00:00
use super::atom::Atom;
use super::read::nested_atom;
use super::read::skip_unneeded;
use super::trak::Trak;
use crate::error::{LoftyError, Result};
use crate::types::properties::FileProperties;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::time::Duration;
use byteorder::{BigEndian, ReadBytesExt};
pub(crate) fn read_properties<R>(data: &mut R, traks: &[Trak]) -> Result<FileProperties>
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 {
let atom = Atom::read(data)?;
match &*atom.ident {
"mdhd" => {
skip_unneeded(data, atom.extended, atom.len)?;
mdhd = Some(atom)
},
"hdlr" => {
// The hdlr atom is followed by 8 zeros
data.seek(SeekFrom::Current(8))?;
let mut handler_type = [0; 4];
data.read_exact(&mut handler_type)?;
if &handler_type == b"soun" {
audio_track = true
}
skip_unneeded(data, atom.extended, atom.len - 12)?;
},
"minf" => minf = Some(atom),
_ => {
skip_unneeded(data, atom.extended, atom.len)?;
read += atom.len
},
}
}
}
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))
},
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
let mut properties = FileProperties {
duration,
bitrate: None,
sample_rate: None,
channels: None,
};
if let Some(minf) = minf {
data.seek(SeekFrom::Start(minf.start + 8))?;
if let Some(stbl) = nested_atom(data, minf.len, "stbl")? {
if let Some(stsd) = nested_atom(data, stbl.len, "stsd")? {
let mut stsd = vec![0; (stsd.len - 8) as usize];
data.read_exact(&mut stsd)?;
let mut stsd_reader = Cursor::new(&*stsd);
// There are 8 bytes we don't care about
// Version (1)
// Flags (3)
// Number of entries (4)
stsd_reader.seek(SeekFrom::Start(8))?;
let atom = Atom::read(&mut stsd_reader)?;
match &*atom.ident {
"mp4a" => mp4a_properties(&mut stsd_reader, &mut properties)?,
"alac" => alac_properties(&mut stsd_reader, &mut properties)?,
_ => {},
}
}
}
}
Ok(properties)
}
fn mp4a_properties<R>(data: &mut R, properties: &mut FileProperties) -> Result<()>
where
R: Read + Seek,
{
// There are 16 bytes we don't care about
// Reserved (6)
// Data reference index (2)
// Version (2)
// Revision level (2)
// Vendor (4)
data.seek(SeekFrom::Current(16))?;
properties.channels = Some(data.read_u16::<BigEndian>()? as u8);
// There are 4 bytes we don't care about
// Sample size (2)
// Compression ID (2)
data.seek(SeekFrom::Current(4))?;
properties.sample_rate = Some(data.read_u32::<BigEndian>()?);
data.seek(SeekFrom::Current(2))?;
// This information is often followed by an esds (elementary stream descriptor) atom containing the bitrate
if let Ok(esds) = Atom::read(data) {
// There are 4 bytes we expect to be zeroed out
// Version (1)
// Flags (3)
if &*esds.ident == "esds" && data.read_u32::<BigEndian>()? == 0 {
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] {
// There are 4 bytes here we don't care about
// 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] {
// There are 10 bytes here we don't care about
// 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 {
properties.bitrate = Some(average_bitrate / 1000)
}
}
}
}
}
Ok(())
}
fn alac_properties<R>(data: &mut R, properties: &mut FileProperties) -> Result<()>
where
R: Read + Seek,
{
// With ALAC, we can expect the length to be exactly 88 (64 here since we removed the size and identifier)
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))?;
if let Ok(alac) = Atom::read(data) {
if &*alac.ident == "alac" {
// There are 13 bytes we don't care about
// 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))?;
properties.channels = Some(data.read_u8()?);
// There are 6 bytes we don't care about
// Max run (2)
// Max frame size (4)
data.seek(SeekFrom::Current(6))?;
properties.bitrate = Some(data.read_u32::<BigEndian>()?);
properties.sample_rate = Some(data.read_u32::<BigEndian>()?);
}
}
Ok(())
}