diff --git a/examples/mp4info.rs b/examples/mp4info.rs index f3ef4bd..84d06d9 100644 --- a/examples/mp4info.rs +++ b/examples/mp4info.rs @@ -4,7 +4,7 @@ use std::io::prelude::*; use std::io::{self, BufReader}; use std::path::Path; -use mp4::{Mp4Track, Result, TrackType}; +use mp4::{Mp4Track, Result, TrackType, Error}; fn main() { let args: Vec = env::args().collect(); @@ -26,17 +26,23 @@ fn info>(filename: &P) -> Result<()> { let mp4 = mp4::Mp4Reader::read_header(reader, size)?; - println!("Metadata:"); - println!(" size : {}", mp4.size()); - println!(" major_brand : {}", mp4.major_brand()); + println!("File:"); + println!(" file size: {}", mp4.size()); + println!(" major_brand: {}", mp4.major_brand()); let mut compatible_brands = String::new(); for brand in mp4.compatible_brands().iter() { compatible_brands.push_str(&brand.to_string()); - compatible_brands.push_str(","); + compatible_brands.push_str(" "); } - println!(" compatible_brands: {}", compatible_brands); - println!("Duration: {:?}", mp4.duration()); + println!(" compatible_brands: {}\n", compatible_brands); + println!("Movie:"); + println!(" version: {}", mp4.moov.mvhd.version); + println!(" creation time: {}", creation_time(mp4.moov.mvhd.creation_time)); + println!(" duration: {:?}", mp4.duration()); + println!(" timescale: {:?}\n", mp4.timescale()); + + println!("Found {} Tracks", mp4.tracks().len()); for track in mp4.tracks().iter() { let media_info = match track.track_type()? { TrackType::Video => video_info(track)?, @@ -68,13 +74,35 @@ fn video_info(track: &Mp4Track) -> Result { } fn audio_info(track: &Mp4Track) -> Result { - Ok(format!( - "{} ({}) ({:?}), {} Hz, {}, {} kb/s", - track.media_type()?, - track.audio_profile()?, - track.box_type()?, - track.sample_freq_index()?.freq(), - track.channel_config()?, - track.bitrate() / 1000 - )) + if let Some(ref mp4a) = track.trak.mdia.minf.stbl.stsd.mp4a { + if mp4a.esds.is_some() { + Ok(format!( + "{} ({}) ({:?}), {} Hz, {}, {} kb/s", + track.media_type()?, + track.audio_profile()?, + track.box_type()?, + track.sample_freq_index()?.freq(), + track.channel_config()?, + track.bitrate() / 1000 + )) + } else { + Ok(format!( + "{} ({:?}), {} kb/s", + track.media_type()?, + track.box_type()?, + track.bitrate() / 1000 + )) + } + } else { + Err(Error::InvalidData("mp4a box not found")) + } } + +fn creation_time(creation_time: u64) -> u64 { + // convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01) + if creation_time >= 2082844800 { + creation_time - 2082844800 + } else { + creation_time + } +} \ No newline at end of file diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index 081f926..77d0b3f 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -9,7 +9,7 @@ pub struct Mp4aBox { pub channelcount: u16, pub samplesize: u16, pub samplerate: FixedPointU16, - pub esds: EsdsBox, + pub esds: Option, } impl Default for Mp4aBox { @@ -19,7 +19,7 @@ impl Default for Mp4aBox { channelcount: 2, samplesize: 16, samplerate: FixedPointU16::new(48000), - esds: EsdsBox::default(), + esds: Some(EsdsBox::default()), } } } @@ -31,7 +31,7 @@ impl Mp4aBox { channelcount: config.chan_conf as u16, samplesize: 16, samplerate: FixedPointU16::new(config.freq_index.freq() as u16), - esds: EsdsBox::new(config), + esds: Some(EsdsBox::new(config)), } } } @@ -42,7 +42,11 @@ impl Mp4Box for Mp4aBox { } fn box_size(&self) -> u64 { - HEADER_SIZE + 8 + 20 + self.esds.box_size() + let mut size = HEADER_SIZE + 8 + 20; + if let Some(ref esds) = self.esds { + size += esds.box_size(); + } + size } } @@ -62,21 +66,20 @@ impl ReadBox<&mut R> for Mp4aBox { let header = BoxHeader::read(reader)?; let BoxHeader { name, size: s } = header; + + let mut esds = None; if name == BoxType::EsdsBox { - let esds = EsdsBox::read_box(reader, s)?; - - skip_bytes_to(reader, start + size)?; - - Ok(Mp4aBox { - data_reference_index, - channelcount, - samplesize, - samplerate, - esds, - }) - } else { - Err(Error::InvalidData("esds not found")) + esds = Some(EsdsBox::read_box(reader, s)?); } + skip_bytes_to(reader, start + size)?; + + Ok(Mp4aBox { + data_reference_index, + channelcount, + samplesize, + samplerate, + esds, + }) } } @@ -95,7 +98,9 @@ impl WriteBox<&mut W> for Mp4aBox { writer.write_u32::(0)?; // reserved writer.write_u32::(self.samplerate.raw_value())?; - self.esds.write_box(writer)?; + if let Some(ref esds) = self.esds { + esds.write_box(writer)?; + } Ok(size) } @@ -524,7 +529,7 @@ mod tests { channelcount: 2, samplesize: 16, samplerate: FixedPointU16::new(48000), - esds: EsdsBox { + esds: Some(EsdsBox { version: 0, flags: 0, es_desc: ESDescriptor { @@ -544,7 +549,29 @@ mod tests { }, sl_config: SLConfigDescriptor::default(), }, - }, + }), + }; + let mut buf = Vec::new(); + src_box.write_box(&mut buf).unwrap(); + assert_eq!(buf.len(), src_box.box_size() as usize); + + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::Mp4aBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = Mp4aBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } + + #[test] + fn test_mp4a_no_esds() { + let src_box = Mp4aBox { + data_reference_index: 1, + channelcount: 2, + samplesize: 16, + samplerate: FixedPointU16::new(48000), + esds: None, }; let mut buf = Vec::new(); src_box.write_box(&mut buf).unwrap(); diff --git a/src/track.rs b/src/track.rs index be7c181..08c6550 100644 --- a/src/track.rs +++ b/src/track.rs @@ -53,7 +53,7 @@ impl From for TrackConfig { #[derive(Debug)] pub struct Mp4Track { - trak: TrakBox, + pub trak: TrakBox, } impl Mp4Track { @@ -126,7 +126,11 @@ impl Mp4Track { pub fn sample_freq_index(&self) -> Result { if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { - SampleFreqIndex::try_from(mp4a.esds.es_desc.dec_config.dec_specific.freq_index) + if let Some(ref esds) = mp4a.esds { + SampleFreqIndex::try_from(esds.es_desc.dec_config.dec_specific.freq_index) + } else { + Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox)) + } } else { Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox)) } @@ -134,7 +138,11 @@ impl Mp4Track { pub fn channel_config(&self) -> Result { if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { - ChannelConfig::try_from(mp4a.esds.es_desc.dec_config.dec_specific.chan_conf) + if let Some(ref esds) = mp4a.esds { + ChannelConfig::try_from(esds.es_desc.dec_config.dec_specific.chan_conf) + } else { + Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox)) + } } else { Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox)) } @@ -156,7 +164,12 @@ impl Mp4Track { pub fn bitrate(&self) -> u32 { if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { - mp4a.esds.es_desc.dec_config.avg_bitrate + if let Some(ref esds) = mp4a.esds { + esds.es_desc.dec_config.avg_bitrate + } else { + 0 + } + // mp4a.esds.es_desc.dec_config.avg_bitrate } else { let dur_sec = self.duration().as_secs(); if dur_sec > 0 { @@ -215,7 +228,11 @@ impl Mp4Track { pub fn audio_profile(&self) -> Result { if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { - AudioObjectType::try_from(mp4a.esds.es_desc.dec_config.dec_specific.profile) + if let Some(ref esds) = mp4a.esds { + AudioObjectType::try_from(esds.es_desc.dec_config.dec_specific.profile) + } else { + Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox)) + } } else { Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox)) } @@ -660,7 +677,9 @@ impl Mp4TrackWriter { let max_sample_size = self.max_sample_size(); if let Some(ref mut mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { - mp4a.esds.es_desc.dec_config.buffer_size_db = max_sample_size; + if let Some(ref mut esds) = mp4a.esds { + esds.es_desc.dec_config.buffer_size_db = max_sample_size; + } // TODO // mp4a.esds.es_desc.dec_config.max_bitrate // mp4a.esds.es_desc.dec_config.avg_bitrate