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(data: &mut R, traks: &[Trak]) -> Result 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::(3)?; let (timescale, duration) = if version == 1 { // We don't care about these two values let _creation_time = data.read_u64::()?; let _modification_time = data.read_u64::()?; let timescale = data.read_u32::()?; let duration = data.read_u64::()?; (timescale, duration) } else { let _creation_time = data.read_u32::()?; let _modification_time = data.read_u32::()?; let timescale = data.read_u32::()?; let duration = data.read_u32::()?; (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(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::()? 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::()?); 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::()? == 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::()?; // 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::()?; if average_bitrate > 0 { properties.bitrate = Some(average_bitrate / 1000) } } } } } Ok(()) } fn alac_properties(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::()?); properties.sample_rate = Some(data.read_u32::()?); } } Ok(()) }