mirror of
https://github.com/alfg/mp4-rust.git
synced 2024-05-20 01:08:06 +00:00
ace2799c75
This introduces the 'Metadata' trait to enable access to common video metadata such title, year, cover art and more. Reading 'title', 'description', 'poster' and 'year' metadata is implemented here.
179 lines
5.1 KiB
Rust
179 lines
5.1 KiB
Rust
use std::collections::HashMap;
|
|
use std::io::{Read, Seek, SeekFrom};
|
|
use std::time::Duration;
|
|
|
|
use crate::*;
|
|
|
|
#[derive(Debug)]
|
|
pub struct Mp4Reader<R> {
|
|
reader: R,
|
|
pub ftyp: FtypBox,
|
|
pub moov: MoovBox,
|
|
pub moofs: Vec<MoofBox>,
|
|
pub emsgs: Vec<EmsgBox>,
|
|
|
|
tracks: HashMap<u32, Mp4Track>,
|
|
size: u64,
|
|
}
|
|
|
|
impl<R: Read + Seek> Mp4Reader<R> {
|
|
pub fn read_header(mut reader: R, size: u64) -> Result<Self> {
|
|
let start = reader.seek(SeekFrom::Current(0))?;
|
|
|
|
let mut ftyp = None;
|
|
let mut moov = None;
|
|
let mut moofs = Vec::new();
|
|
let mut emsgs = Vec::new();
|
|
|
|
let mut current = start;
|
|
while current < size {
|
|
// Get box header.
|
|
let header = BoxHeader::read(&mut reader)?;
|
|
let BoxHeader { name, size: s } = header;
|
|
|
|
// Break if size zero BoxHeader, which can result in dead-loop.
|
|
if s == 0 {
|
|
break;
|
|
}
|
|
|
|
// Match and parse the atom boxes.
|
|
match name {
|
|
BoxType::FtypBox => {
|
|
ftyp = Some(FtypBox::read_box(&mut reader, s)?);
|
|
}
|
|
BoxType::FreeBox => {
|
|
skip_box(&mut reader, s)?;
|
|
}
|
|
BoxType::MdatBox => {
|
|
skip_box(&mut reader, s)?;
|
|
}
|
|
BoxType::MoovBox => {
|
|
moov = Some(MoovBox::read_box(&mut reader, s)?);
|
|
}
|
|
BoxType::MoofBox => {
|
|
let moof = MoofBox::read_box(&mut reader, s)?;
|
|
moofs.push(moof);
|
|
}
|
|
BoxType::EmsgBox => {
|
|
let emsg = EmsgBox::read_box(&mut reader, s)?;
|
|
emsgs.push(emsg);
|
|
}
|
|
_ => {
|
|
// XXX warn!()
|
|
skip_box(&mut reader, s)?;
|
|
}
|
|
}
|
|
current = reader.seek(SeekFrom::Current(0))?;
|
|
}
|
|
|
|
if ftyp.is_none() {
|
|
return Err(Error::BoxNotFound(BoxType::FtypBox));
|
|
}
|
|
if moov.is_none() {
|
|
return Err(Error::BoxNotFound(BoxType::MoovBox));
|
|
}
|
|
|
|
let size = current - start;
|
|
let mut tracks = if let Some(ref moov) = moov {
|
|
if moov.traks.iter().any(|trak| trak.tkhd.track_id == 0) {
|
|
return Err(Error::InvalidData("illegal track id 0"));
|
|
}
|
|
moov.traks
|
|
.iter()
|
|
.map(|trak| (trak.tkhd.track_id, Mp4Track::from(trak)))
|
|
.collect()
|
|
} else {
|
|
HashMap::new()
|
|
};
|
|
|
|
// Update tracks if any fragmented (moof) boxes are found.
|
|
if !moofs.is_empty() {
|
|
let mut default_sample_duration = 0;
|
|
if let Some(ref moov) = moov {
|
|
if let Some(ref mvex) = &moov.mvex {
|
|
default_sample_duration = mvex.trex.default_sample_duration
|
|
}
|
|
}
|
|
|
|
for moof in moofs.iter() {
|
|
for traf in moof.trafs.iter() {
|
|
let track_id = traf.tfhd.track_id;
|
|
if let Some(track) = tracks.get_mut(&track_id) {
|
|
track.default_sample_duration = default_sample_duration;
|
|
track.trafs.push(traf.clone())
|
|
} else {
|
|
return Err(Error::TrakNotFound(track_id));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(Mp4Reader {
|
|
reader,
|
|
ftyp: ftyp.unwrap(),
|
|
moov: moov.unwrap(),
|
|
moofs,
|
|
emsgs,
|
|
size,
|
|
tracks,
|
|
})
|
|
}
|
|
|
|
pub fn size(&self) -> u64 {
|
|
self.size
|
|
}
|
|
|
|
pub fn major_brand(&self) -> &FourCC {
|
|
&self.ftyp.major_brand
|
|
}
|
|
|
|
pub fn minor_version(&self) -> u32 {
|
|
self.ftyp.minor_version
|
|
}
|
|
|
|
pub fn compatible_brands(&self) -> &[FourCC] {
|
|
&self.ftyp.compatible_brands
|
|
}
|
|
|
|
pub fn duration(&self) -> Duration {
|
|
Duration::from_millis(self.moov.mvhd.duration * 1000 / self.moov.mvhd.timescale as u64)
|
|
}
|
|
|
|
pub fn timescale(&self) -> u32 {
|
|
self.moov.mvhd.timescale
|
|
}
|
|
|
|
pub fn is_fragmented(&self) -> bool {
|
|
!self.moofs.is_empty()
|
|
}
|
|
|
|
pub fn tracks(&self) -> &HashMap<u32, Mp4Track> {
|
|
&self.tracks
|
|
}
|
|
|
|
pub fn sample_count(&self, track_id: u32) -> Result<u32> {
|
|
if let Some(track) = self.tracks.get(&track_id) {
|
|
Ok(track.sample_count())
|
|
} else {
|
|
Err(Error::TrakNotFound(track_id))
|
|
}
|
|
}
|
|
|
|
pub fn read_sample(&mut self, track_id: u32, sample_id: u32) -> Result<Option<Mp4Sample>> {
|
|
if let Some(track) = self.tracks.get(&track_id) {
|
|
track.read_sample(&mut self.reader, sample_id)
|
|
} else {
|
|
Err(Error::TrakNotFound(track_id))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<R> Mp4Reader<R> {
|
|
pub fn metadata(&self) -> impl Metadata<'_> {
|
|
self.moov
|
|
.udta
|
|
.as_ref()
|
|
.and_then(|udta| udta.meta.as_ref().and_then(|meta| meta.ilst.as_ref()))
|
|
}
|
|
}
|