mirror of
https://github.com/alfg/mp4-rust.git
synced 2024-06-02 13:39:54 +00:00
Add Mp4Reader::read_sample() that read media samples
This commit is contained in:
parent
5e8d7d6b25
commit
e824f11ac7
41
examples/mp4copy.rs
Normal file
41
examples/mp4copy.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::Result;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() < 3 {
|
||||
println!("Usage: mp4copy <source file> <target file>");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if let Err(err) = copy(&args[1], &args[2]) {
|
||||
let _ = writeln!(io::stderr(), "{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn copy<P: AsRef<Path>>(src_filename: &P, _dst_filename: &P) -> Result<()> {
|
||||
let src_file = File::open(src_filename)?;
|
||||
let size = src_file.metadata()?.len();
|
||||
let reader = BufReader::new(src_file);
|
||||
|
||||
let mut mp4 = mp4::Mp4Reader::new(reader);
|
||||
mp4.read(size)?;
|
||||
|
||||
for tix in 0..mp4.track_count()? {
|
||||
let track_id = tix + 1;
|
||||
let sample_count = mp4.sample_count(track_id)?;
|
||||
for six in 0..sample_count {
|
||||
let sample_id = six + 1;
|
||||
let sample = mp4.read_sample(track_id, sample_id)?.unwrap();
|
||||
println!("sample_id: {}, {}", sample_id, sample);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,90 +1,98 @@
|
|||
use mp4;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::{Result, Mp4Reader, TrackType};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
match args.len() {
|
||||
2 => {
|
||||
let filename = &args[1];
|
||||
let f = File::open(filename).unwrap();
|
||||
let size = f.metadata().unwrap().len();
|
||||
let reader = BufReader::new(f);
|
||||
if args.len() < 2 {
|
||||
println!("Usage: mp4info <filename>");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let mut mp4 = mp4::Mp4Reader::new(reader);
|
||||
mp4.read(size).unwrap();
|
||||
if let Err(err) = info(&args[1]) {
|
||||
let _ = writeln!(io::stderr(), "{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
println!("File:");
|
||||
println!(" size: {}", mp4.size());
|
||||
println!(" brands: {:?} {:?}\n",
|
||||
mp4.ftyp.major_brand, mp4.ftyp.compatible_brands);
|
||||
fn info<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
||||
let f = File::open(filename)?;
|
||||
let size = f.metadata()?.len();
|
||||
let reader = BufReader::new(f);
|
||||
|
||||
let moov = mp4.moov.unwrap();
|
||||
let mut mp4 = Mp4Reader::new(reader);
|
||||
mp4.read(size)?;
|
||||
|
||||
println!("Movie:");
|
||||
println!(" version: {:?}", moov.mvhd.version);
|
||||
println!(" creation time: {}",
|
||||
creation_time(moov.mvhd.creation_time));
|
||||
println!(" duration: {:?}", moov.mvhd.duration);
|
||||
println!(" timescale: {:?}\n", moov.mvhd.timescale);
|
||||
println!("File:");
|
||||
println!(" size: {}", mp4.size());
|
||||
println!(" brands: {:?} {:?}\n",
|
||||
mp4.ftyp.major_brand, mp4.ftyp.compatible_brands);
|
||||
|
||||
println!("Found {} Tracks", moov.traks.len());
|
||||
for trak in moov.traks.iter() {
|
||||
let tkhd = trak.tkhd.as_ref().unwrap();
|
||||
println!("Track: {:?}", tkhd.track_id);
|
||||
println!(" flags: {:?}", tkhd.flags);
|
||||
println!(" id: {:?}", tkhd.track_id);
|
||||
println!(" duration: {:?}", tkhd.duration);
|
||||
if tkhd.width != 0 && tkhd.height != 0 {
|
||||
println!(" width: {:?}", tkhd.width);
|
||||
println!(" height: {:?}", tkhd.height);
|
||||
if let Some(ref moov) = mp4.moov {
|
||||
println!("Movie:");
|
||||
println!(" version: {:?}", moov.mvhd.version);
|
||||
println!(" creation time: {}",
|
||||
creation_time(moov.mvhd.creation_time));
|
||||
println!(" duration: {:?}", moov.mvhd.duration);
|
||||
println!(" timescale: {:?}\n", moov.mvhd.timescale);
|
||||
|
||||
println!("Found {} Tracks", moov.traks.len());
|
||||
for trak in moov.traks.iter() {
|
||||
let tkhd = trak.tkhd.as_ref().unwrap();
|
||||
println!("Track: {:?}", tkhd.track_id);
|
||||
println!(" flags: {:?}", tkhd.flags);
|
||||
println!(" id: {:?}", tkhd.track_id);
|
||||
println!(" duration: {:?}", tkhd.duration);
|
||||
if tkhd.width != 0 && tkhd.height != 0 {
|
||||
println!(" width: {:?}", tkhd.width);
|
||||
println!(" height: {:?}", tkhd.height);
|
||||
}
|
||||
if let Some(ref mdia) = trak.mdia {
|
||||
let hdlr = mdia.hdlr.as_ref().unwrap();
|
||||
let mdhd = mdia.mdhd.as_ref().unwrap();
|
||||
let stts = mdia
|
||||
.minf
|
||||
.as_ref()
|
||||
.map(|m| m.stbl.as_ref().map(|s| s.stts.as_ref()).flatten())
|
||||
.flatten();
|
||||
|
||||
println!(" type: {:?}",
|
||||
get_handler_type(hdlr.handler_type.value.as_ref()));
|
||||
println!(" language: {:?}", mdhd.language);
|
||||
|
||||
println!(" media:");
|
||||
if let Some(ref s) = stts {
|
||||
println!(" sample count: {:?}", s.entries[0].sample_count);
|
||||
}
|
||||
if let Some(ref mdia) = trak.mdia {
|
||||
let hdlr = mdia.hdlr.as_ref().unwrap();
|
||||
let mdhd = mdia.mdhd.as_ref().unwrap();
|
||||
let stts = mdia
|
||||
.minf
|
||||
.as_ref()
|
||||
.map(|m| m.stbl.as_ref().map(|s| s.stts.as_ref()).flatten())
|
||||
.flatten();
|
||||
|
||||
println!(" type: {:?}",
|
||||
get_handler_type(hdlr.handler_type.value.as_ref()));
|
||||
println!(" language: {:?}", mdhd.language);
|
||||
|
||||
println!(" media:");
|
||||
println!(" timescale: {:?}", mdhd.timescale);
|
||||
println!(" duration: {:?} (media timescale units)",
|
||||
mdhd.duration);
|
||||
println!(" duration: {:?} (ms)",
|
||||
get_duration_ms(mdhd.duration, mdhd.timescale));
|
||||
if get_handler_type(hdlr.handler_type.value.as_ref()) == TrackType::Video {
|
||||
if let Some(ref s) = stts {
|
||||
println!(" sample count: {:?}", s.entries[0].sample_count);
|
||||
}
|
||||
println!(" timescale: {:?}", mdhd.timescale);
|
||||
println!(" duration: {:?} (media timescale units)",
|
||||
mdhd.duration);
|
||||
println!(" duration: {:?} (ms)",
|
||||
get_duration_ms(mdhd.duration, mdhd.timescale));
|
||||
if get_handler_type(hdlr.handler_type.value.as_ref()) == mp4::TrackType::Video {
|
||||
if let Some(ref s) = stts {
|
||||
println!(" frame rate: (computed): {:?}",
|
||||
get_framerate(s.entries[0].sample_count,
|
||||
mdhd.duration, mdhd.timescale));
|
||||
}
|
||||
println!(" frame rate: (computed): {:?}",
|
||||
get_framerate(s.entries[0].sample_count,
|
||||
mdhd.duration, mdhd.timescale));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("Usage: mp4info <filename>");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_handler_type(handler: &str) -> mp4::TrackType {
|
||||
let mut typ: mp4::TrackType = mp4::TrackType::Unknown;
|
||||
fn get_handler_type(handler: &str) -> TrackType {
|
||||
let mut typ: TrackType = TrackType::Unknown;
|
||||
match handler {
|
||||
"vide" => typ = mp4::TrackType::Video,
|
||||
"soun" => typ = mp4::TrackType::Audio,
|
||||
"meta" => typ = mp4::TrackType::Unknown,
|
||||
"vide" => typ = TrackType::Video,
|
||||
"soun" => typ = TrackType::Audio,
|
||||
"meta" => typ = TrackType::Unknown,
|
||||
_ => (),
|
||||
}
|
||||
return typ;
|
||||
|
|
102
src/atoms/ctts.rs
Normal file
102
src/atoms/ctts.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
use std::io::{Seek, Read, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct CttsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entries: Vec<CttsEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct CttsEntry {
|
||||
pub sample_count: u32,
|
||||
pub sample_offset: i32,
|
||||
}
|
||||
|
||||
impl Mp4Box for CttsBox {
|
||||
fn box_type() -> BoxType {
|
||||
BoxType::CttsBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entries.len() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for CttsBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = get_box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
for _ in 0..entry_count {
|
||||
let entry = CttsEntry {
|
||||
sample_count: reader.read_u32::<BigEndian>()?,
|
||||
sample_offset: reader.read_i32::<BigEndian>()?,
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
skip_read_to(reader, start + size)?;
|
||||
|
||||
Ok(CttsBox {
|
||||
version,
|
||||
flags,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for CttsBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(Self::box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
|
||||
for entry in self.entries.iter() {
|
||||
writer.write_u32::<BigEndian>(entry.sample_count)?;
|
||||
writer.write_i32::<BigEndian>(entry.sample_offset)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::atoms::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_ctts() {
|
||||
let src_box = CttsBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
entries: vec![
|
||||
CttsEntry {sample_count: 1, sample_offset: 200},
|
||||
CttsEntry {sample_count: 2, sample_offset: -100},
|
||||
],
|
||||
};
|
||||
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::CttsBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = CttsBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ impl Mp4Box for EdtsBox {
|
|||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(elst) = &self.elst {
|
||||
if let Some(ref elst) = self.elst {
|
||||
size += elst.box_size();
|
||||
}
|
||||
size
|
||||
|
@ -58,7 +58,7 @@ impl<W: Write> WriteBox<&mut W> for EdtsBox {
|
|||
let size = self.box_size();
|
||||
BoxHeader::new(Self::box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(elst) = &self.elst {
|
||||
if let Some(ref elst) = self.elst {
|
||||
elst.write_box(writer)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,13 +25,13 @@ impl Mp4Box for MdiaBox {
|
|||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(mdhd) = &self.mdhd {
|
||||
if let Some(ref mdhd) = self.mdhd {
|
||||
size += mdhd.box_size();
|
||||
}
|
||||
if let Some(hdlr) = &self.hdlr {
|
||||
if let Some(ref hdlr) = self.hdlr {
|
||||
size += hdlr.box_size();
|
||||
}
|
||||
if let Some(minf) = &self.minf {
|
||||
if let Some(ref minf) = self.minf {
|
||||
size += minf.box_size();
|
||||
}
|
||||
size
|
||||
|
@ -84,13 +84,13 @@ impl<W: Write> WriteBox<&mut W> for MdiaBox {
|
|||
let size = self.box_size();
|
||||
BoxHeader::new(Self::box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(mdhd) = &self.mdhd {
|
||||
if let Some(ref mdhd) = self.mdhd {
|
||||
mdhd.write_box(writer)?;
|
||||
}
|
||||
if let Some(hdlr) = &self.hdlr {
|
||||
if let Some(ref hdlr) = self.hdlr {
|
||||
hdlr.write_box(writer)?;
|
||||
}
|
||||
if let Some(minf) = &self.minf {
|
||||
if let Some(ref minf) = self.minf {
|
||||
minf.write_box(writer)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,13 +25,13 @@ impl Mp4Box for MinfBox {
|
|||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(vmhd) = &self.vmhd {
|
||||
if let Some(ref vmhd) = self.vmhd {
|
||||
size += vmhd.box_size();
|
||||
}
|
||||
if let Some(smhd) = &self.smhd {
|
||||
if let Some(ref smhd) = self.smhd {
|
||||
size += smhd.box_size();
|
||||
}
|
||||
if let Some(stbl) = &self.stbl {
|
||||
if let Some(ref stbl) = self.stbl {
|
||||
size += stbl.box_size();
|
||||
}
|
||||
size
|
||||
|
@ -87,13 +87,13 @@ impl<W: Write> WriteBox<&mut W> for MinfBox {
|
|||
let size = self.box_size();
|
||||
BoxHeader::new(Self::box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(vmhd) = &self.vmhd {
|
||||
if let Some(ref vmhd) = self.vmhd {
|
||||
vmhd.write_box(writer)?;
|
||||
}
|
||||
if let Some(smhd) = &self.smhd {
|
||||
if let Some(ref smhd) = self.smhd {
|
||||
smhd.write_box(writer)?;
|
||||
}
|
||||
if let Some(stbl) = &self.stbl {
|
||||
if let Some(ref stbl) = self.stbl {
|
||||
stbl.write_box(writer)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ mod smhd;
|
|||
mod stbl;
|
||||
mod stsd;
|
||||
mod stts;
|
||||
mod ctts;
|
||||
mod stss;
|
||||
mod stsc;
|
||||
mod stsz;
|
||||
|
@ -81,6 +82,7 @@ boxtype!{
|
|||
StblBox => 0x7374626c,
|
||||
StsdBox => 0x73747364,
|
||||
SttsBox => 0x73747473,
|
||||
CttsBox => 0x63747473,
|
||||
StssBox => 0x73747373,
|
||||
StscBox => 0x73747363,
|
||||
StszBox => 0x7374737A,
|
||||
|
|
|
@ -5,6 +5,7 @@ use crate::atoms::*;
|
|||
use crate::atoms::{
|
||||
stsd::StsdBox,
|
||||
stts::SttsBox,
|
||||
ctts::CttsBox,
|
||||
stss::StssBox,
|
||||
stsc::StscBox,
|
||||
stsz::StszBox,
|
||||
|
@ -17,6 +18,7 @@ use crate::atoms::{
|
|||
pub struct StblBox {
|
||||
pub stsd: Option<StsdBox>,
|
||||
pub stts: Option<SttsBox>,
|
||||
pub ctts: Option<CttsBox>,
|
||||
pub stss: Option<StssBox>,
|
||||
pub stsc: Option<StscBox>,
|
||||
pub stsz: Option<StszBox>,
|
||||
|
@ -37,25 +39,28 @@ impl Mp4Box for StblBox {
|
|||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(stsd) = &self.stsd {
|
||||
if let Some(ref stsd) = self.stsd {
|
||||
size += stsd.box_size();
|
||||
}
|
||||
if let Some(stts) = &self.stts {
|
||||
if let Some(ref stts) = self.stts {
|
||||
size += stts.box_size();
|
||||
}
|
||||
if let Some(stss) = &self.stss {
|
||||
if let Some(ref ctts) = self.ctts {
|
||||
size += ctts.box_size();
|
||||
}
|
||||
if let Some(ref stss) = self.stss {
|
||||
size += stss.box_size();
|
||||
}
|
||||
if let Some(stsc) = &self.stsc {
|
||||
if let Some(ref stsc) = self.stsc {
|
||||
size += stsc.box_size();
|
||||
}
|
||||
if let Some(stsz) = &self.stsz {
|
||||
if let Some(ref stsz) = self.stsz {
|
||||
size += stsz.box_size();
|
||||
}
|
||||
if let Some(stco) = &self.stco {
|
||||
if let Some(ref stco) = self.stco {
|
||||
size += stco.box_size();
|
||||
}
|
||||
if let Some(co64) = &self.co64 {
|
||||
if let Some(ref co64) = self.co64 {
|
||||
size += co64.box_size();
|
||||
}
|
||||
size
|
||||
|
@ -84,6 +89,10 @@ impl<R: Read + Seek> ReadBox<&mut R> for StblBox {
|
|||
let stts = SttsBox::read_box(reader, s)?;
|
||||
stbl.stts = Some(stts);
|
||||
}
|
||||
BoxType::CttsBox => {
|
||||
let ctts = CttsBox::read_box(reader, s)?;
|
||||
stbl.ctts = Some(ctts);
|
||||
}
|
||||
BoxType::StssBox => {
|
||||
let stss = StssBox::read_box(reader, s)?;
|
||||
stbl.stss = Some(stss);
|
||||
|
@ -123,25 +132,28 @@ impl<W: Write> WriteBox<&mut W> for StblBox {
|
|||
let size = self.box_size();
|
||||
BoxHeader::new(Self::box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(stsd) = &self.stsd {
|
||||
if let Some(ref stsd) = self.stsd {
|
||||
stsd.write_box(writer)?;
|
||||
}
|
||||
if let Some(stts) = &self.stts {
|
||||
if let Some(ref stts) = self.stts {
|
||||
stts.write_box(writer)?;
|
||||
}
|
||||
if let Some(stss) = &self.stss {
|
||||
if let Some(ref ctts) = self.ctts {
|
||||
ctts.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref stss) = self.stss {
|
||||
stss.write_box(writer)?;
|
||||
}
|
||||
if let Some(stsc) = &self.stsc {
|
||||
if let Some(ref stsc) = self.stsc {
|
||||
stsc.write_box(writer)?;
|
||||
}
|
||||
if let Some(stsz) = &self.stsz {
|
||||
if let Some(ref stsz) = self.stsz {
|
||||
stsz.write_box(writer)?;
|
||||
}
|
||||
if let Some(stco) = &self.stco {
|
||||
if let Some(ref stco) = self.stco {
|
||||
stco.write_box(writer)?;
|
||||
}
|
||||
if let Some(co64) = &self.co64 {
|
||||
if let Some(ref co64) = self.co64 {
|
||||
co64.write_box(writer)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -105,11 +105,13 @@ mod tests {
|
|||
first_chunk: 1,
|
||||
samples_per_chunk: 1,
|
||||
sample_description_index: 1,
|
||||
first_sample: 1,
|
||||
},
|
||||
StscEntry {
|
||||
first_chunk: 19026,
|
||||
samples_per_chunk: 14,
|
||||
sample_description_index: 1
|
||||
sample_description_index: 1,
|
||||
first_sample: 19026,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -21,9 +21,9 @@ impl Mp4Box for StsdBox {
|
|||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
if let Some(avc1) = &self.avc1 {
|
||||
if let Some(ref avc1) = self.avc1 {
|
||||
size += avc1.box_size();
|
||||
} else if let Some(mp4a) = &self.mp4a {
|
||||
} else if let Some(ref mp4a) = self.mp4a {
|
||||
size += mp4a.box_size();
|
||||
}
|
||||
size
|
||||
|
@ -75,9 +75,9 @@ impl<W: Write> WriteBox<&mut W> for StsdBox {
|
|||
|
||||
writer.write_u32::<BigEndian>(1)?; // entry_count
|
||||
|
||||
if let Some(avc1) = &self.avc1 {
|
||||
if let Some(ref avc1) = self.avc1 {
|
||||
avc1.write_box(writer)?;
|
||||
} else if let Some(mp4a) = &self.mp4a {
|
||||
} else if let Some(ref mp4a) = self.mp4a {
|
||||
mp4a.write_box(writer)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ mod tests {
|
|||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_stts32() {
|
||||
fn test_stts() {
|
||||
let src_box = SttsBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
|
@ -99,27 +99,4 @@ mod tests {
|
|||
let dst_box = SttsBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stts64() {
|
||||
let src_box = SttsBox {
|
||||
version: 1,
|
||||
flags: 0,
|
||||
entries: vec![
|
||||
SttsEntry {sample_count: 29726, sample_delta: 1024},
|
||||
SttsEntry {sample_count: 1, sample_delta: 512},
|
||||
],
|
||||
};
|
||||
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::SttsBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = SttsBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,9 @@ use crate::atoms::{
|
|||
edts::EdtsBox,
|
||||
mdia::MdiaBox,
|
||||
stbl::StblBox,
|
||||
// stsd::StsdBox,
|
||||
// stts::SttsBox,
|
||||
// stss::StssBox,
|
||||
stts::SttsBox,
|
||||
stsc::StscBox,
|
||||
stsz::StszBox,
|
||||
// stco::StcoBox,
|
||||
// co64::Co64Box,
|
||||
};
|
||||
|
||||
|
||||
|
@ -32,9 +28,9 @@ impl TrakBox {
|
|||
}
|
||||
|
||||
fn stbl(&self) -> Result<&StblBox> {
|
||||
if let Some(mdia) = &self.mdia {
|
||||
if let Some(minf) = &mdia.minf {
|
||||
if let Some(stbl) = &minf.stbl {
|
||||
if let Some(ref mdia) = self.mdia {
|
||||
if let Some(ref minf) = mdia.minf {
|
||||
if let Some(ref stbl) = minf.stbl {
|
||||
Ok(stbl)
|
||||
} else {
|
||||
Err(Error::BoxInTrakNotFound(self.id, BoxType::StblBox))
|
||||
|
@ -47,10 +43,20 @@ impl TrakBox {
|
|||
}
|
||||
}
|
||||
|
||||
fn stts(&self) -> Result<&SttsBox> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
if let Some(ref stts) = stbl.stts {
|
||||
Ok(stts)
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.id, BoxType::SttsBox))
|
||||
}
|
||||
}
|
||||
|
||||
fn stsc(&self) -> Result<&StscBox> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
if let Some(stsc) = &stbl.stsc {
|
||||
if let Some(ref stsc) = stbl.stsc {
|
||||
Ok(stsc)
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.id, BoxType::StscBox))
|
||||
|
@ -60,31 +66,31 @@ impl TrakBox {
|
|||
fn stsz(&self) -> Result<&StszBox> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
if let Some(stsz) = &stbl.stsz {
|
||||
if let Some(ref stsz) = stbl.stsz {
|
||||
Ok(stsz)
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.id, BoxType::StszBox))
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_to_stsc_index(&self, sample_id: u32) -> Result<usize> {
|
||||
fn stsc_index(&self, sample_id: u32) -> Result<usize> {
|
||||
let stsc = self.stsc()?;
|
||||
|
||||
for (i, entry) in stsc.entries.iter().enumerate() {
|
||||
if sample_id < entry.first_sample {
|
||||
assert_eq!(i, 0);
|
||||
assert_ne!(i, 0);
|
||||
return Ok(i - 1);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(stsc.entries.len(), 0);
|
||||
assert_ne!(stsc.entries.len(), 0);
|
||||
Ok(stsc.entries.len() - 1)
|
||||
}
|
||||
|
||||
fn chunk_offset(&self, chunk_id: u32) -> Result<u64> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
if let Some(stco) = &stbl.stco {
|
||||
if let Some(ref stco) = stbl.stco {
|
||||
if let Some(offset) = stco.entries.get(chunk_id as usize - 1) {
|
||||
return Ok(*offset as u64);
|
||||
} else {
|
||||
|
@ -92,7 +98,7 @@ impl TrakBox {
|
|||
chunk_id));
|
||||
}
|
||||
} else {
|
||||
if let Some(co64) = &stbl.co64 {
|
||||
if let Some(ref co64) = stbl.co64 {
|
||||
if let Some(offset) = co64.entries.get(chunk_id as usize - 1) {
|
||||
return Ok(*offset);
|
||||
} else {
|
||||
|
@ -106,6 +112,31 @@ impl TrakBox {
|
|||
}
|
||||
}
|
||||
|
||||
fn ctts_index(&self, sample_id: u32) -> Result<(usize, u32)> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
let ctts = if let Some(ref ctts) = stbl.ctts {
|
||||
ctts
|
||||
} else {
|
||||
return Err(Error::BoxInStblNotFound(self.id, BoxType::CttsBox));
|
||||
};
|
||||
|
||||
let mut sample_count = 1;
|
||||
for (i, entry) in ctts.entries.iter().enumerate() {
|
||||
if sample_id <= sample_count + entry.sample_count -1 {
|
||||
return Ok((i, sample_count))
|
||||
}
|
||||
sample_count += entry.sample_count;
|
||||
}
|
||||
|
||||
return Err(Error::EntryInStblNotFound(self.id, BoxType::CttsBox, sample_id));
|
||||
}
|
||||
|
||||
pub fn sample_count(&self) -> Result<u32> {
|
||||
let stsz = self.stsz()?;
|
||||
Ok(stsz.sample_sizes.len() as u32)
|
||||
}
|
||||
|
||||
pub fn sample_size(&self, sample_id: u32) -> Result<u32> {
|
||||
let stsz = self.stsz()?;
|
||||
if stsz.sample_size > 0 {
|
||||
|
@ -119,15 +150,10 @@ impl TrakBox {
|
|||
}
|
||||
|
||||
pub fn sample_offset(&self, sample_id: u32) -> Result<u64> {
|
||||
let stsc_index = self.sample_to_stsc_index(sample_id)?;
|
||||
let stsc_index = self.stsc_index(sample_id)?;
|
||||
|
||||
let stsc = self.stsc()?;
|
||||
let stsc_entry = if let Some(entry) = stsc.entries.get(stsc_index) {
|
||||
entry
|
||||
} else {
|
||||
return Err(Error::EntryInStblNotFound(self.id, BoxType::StscBox,
|
||||
stsc_index as u32 + 1));
|
||||
};
|
||||
let stsc_entry = stsc.entries.get(stsc_index).unwrap();
|
||||
|
||||
let first_chunk = stsc_entry.first_chunk;
|
||||
let first_sample = stsc_entry.first_sample;
|
||||
|
@ -137,7 +163,8 @@ impl TrakBox {
|
|||
|
||||
let chunk_offset = self.chunk_offset(chunk_id)?;
|
||||
|
||||
let first_sample_in_chunk = sample_id - (sample_id - first_sample) % samples_per_chunk;
|
||||
let first_sample_in_chunk = sample_id - (sample_id - first_sample)
|
||||
% samples_per_chunk;
|
||||
|
||||
let mut sample_offset = 0;
|
||||
for i in first_sample_in_chunk..sample_id {
|
||||
|
@ -146,6 +173,80 @@ impl TrakBox {
|
|||
|
||||
Ok(chunk_offset + sample_offset as u64)
|
||||
}
|
||||
|
||||
pub fn sample_time(&self, sample_id: u32) -> Result<(u64, u32)> {
|
||||
let stts = self.stts()?;
|
||||
|
||||
let mut sample_count = 1;
|
||||
let mut elapsed = 0;
|
||||
|
||||
for entry in stts.entries.iter() {
|
||||
if sample_id <= sample_count + entry.sample_count - 1 {
|
||||
let start_time = (sample_id - sample_count) as u64
|
||||
* entry.sample_delta as u64 + elapsed;
|
||||
return Ok((start_time, entry.sample_delta));
|
||||
}
|
||||
|
||||
sample_count += entry.sample_count;
|
||||
elapsed += entry.sample_count as u64 * entry.sample_delta as u64;
|
||||
}
|
||||
|
||||
return Err(Error::EntryInStblNotFound(self.id, BoxType::SttsBox, sample_id));
|
||||
}
|
||||
|
||||
pub fn sample_rendering_offset(&self, sample_id: u32) -> Result<i32> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
if let Some(ref ctts) = stbl.ctts {
|
||||
let (ctts_index, _) = self.ctts_index(sample_id)?;
|
||||
let ctts_entry = ctts.entries.get(ctts_index).unwrap();
|
||||
Ok(ctts_entry.sample_offset)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_sync_sample(&self, sample_id: u32) -> Result<bool> {
|
||||
let stbl = self.stbl()?;
|
||||
|
||||
if let Some(ref stss) = stbl.stss {
|
||||
match stss.entries.binary_search(&sample_id) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(_) => Ok(false)
|
||||
}
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_sample<R: Read + Seek>(
|
||||
&self,
|
||||
reader: &mut R,
|
||||
sample_id: u32,
|
||||
) -> Result<Option<Mp4Sample>> {
|
||||
let sample_offset = match self.sample_offset(sample_id) {
|
||||
Ok(offset) => offset,
|
||||
Err(Error::EntryInStblNotFound(_,_,_)) => return Ok(None),
|
||||
Err(err) => return Err(err)
|
||||
};
|
||||
let sample_size = self.sample_size(sample_id)?;
|
||||
|
||||
let mut buffer = vec![0x0u8; sample_size as usize];
|
||||
reader.seek(SeekFrom::Start(sample_offset))?;
|
||||
reader.read_exact(&mut buffer)?;
|
||||
|
||||
let (start_time, duration) = self.sample_time(sample_id)?;
|
||||
let rendering_offset = self.sample_rendering_offset(sample_id)?;
|
||||
let is_sync = self.is_sync_sample(sample_id)?;
|
||||
|
||||
Ok(Some(Mp4Sample {
|
||||
start_time,
|
||||
duration,
|
||||
rendering_offset,
|
||||
is_sync,
|
||||
bytes: Bytes::from(buffer),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for TrakBox {
|
||||
|
@ -155,13 +256,13 @@ impl Mp4Box for TrakBox {
|
|||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(tkhd) = &self.tkhd {
|
||||
if let Some(ref tkhd) = self.tkhd {
|
||||
size += tkhd.box_size();
|
||||
}
|
||||
if let Some(edts) = &self.edts {
|
||||
if let Some(ref edts) = self.edts {
|
||||
size += edts.box_size();
|
||||
}
|
||||
if let Some(mdia) = &self.mdia {
|
||||
if let Some(ref mdia) = self.mdia {
|
||||
size += mdia.box_size();
|
||||
}
|
||||
size
|
||||
|
@ -214,13 +315,13 @@ impl<W: Write> WriteBox<&mut W> for TrakBox {
|
|||
let size = self.box_size();
|
||||
BoxHeader::new(Self::box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(tkhd) = &self.tkhd {
|
||||
if let Some(ref tkhd) = self.tkhd {
|
||||
tkhd.write_box(writer)?;
|
||||
}
|
||||
if let Some(edts) = &self.edts {
|
||||
if let Some(ref edts) = self.edts {
|
||||
edts.write_box(writer)?;
|
||||
}
|
||||
if let Some(mdia) = &self.mdia {
|
||||
if let Some(ref mdia) = self.mdia {
|
||||
mdia.write_box(writer)?;
|
||||
}
|
||||
|
||||
|
|
72
src/lib.rs
72
src/lib.rs
|
@ -1,6 +1,8 @@
|
|||
use std::fmt;
|
||||
use std::io::{Seek, SeekFrom, Read};
|
||||
use std::convert::TryInto;
|
||||
use bytes::Buf;
|
||||
|
||||
pub use bytes::Bytes;
|
||||
|
||||
mod atoms;
|
||||
use crate::atoms::*;
|
||||
|
@ -19,12 +21,31 @@ pub enum TrackType {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Sample<B> {
|
||||
pub struct Mp4Sample {
|
||||
pub start_time: u64,
|
||||
pub duration: u32,
|
||||
pub rendering_offset: u32,
|
||||
pub rendering_offset: i32,
|
||||
pub is_sync: bool,
|
||||
pub data: B,
|
||||
pub bytes: Bytes,
|
||||
}
|
||||
|
||||
impl PartialEq for Mp4Sample {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.start_time == other.start_time
|
||||
&& self.duration == other.duration
|
||||
&& self.rendering_offset == other.rendering_offset
|
||||
&& self.is_sync == other.is_sync
|
||||
&& self.bytes.len() == other.bytes.len() // XXX for easy check
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Mp4Sample {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f,
|
||||
"start_time {}, duration {}, rendering_offset {}, is_sync {}, length {}",
|
||||
self.start_time, self.duration, self.rendering_offset, self.is_sync,
|
||||
self.bytes.len())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -87,16 +108,20 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_sample<B: Buf>(
|
||||
&mut self,
|
||||
track_id: u32,
|
||||
sample_id: u32,
|
||||
) -> Result<Option<Sample<B>>> {
|
||||
pub fn track_count(&self) -> Result<u32> {
|
||||
if let Some(ref moov) = self.moov {
|
||||
Ok(moov.traks.len() as u32)
|
||||
} else {
|
||||
Err(Error::BoxNotFound(MoovBox::box_type()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_count(&self, track_id: u32) -> Result<u32> {
|
||||
if track_id == 0 {
|
||||
return Err(Error::TrakNotFound(track_id));
|
||||
}
|
||||
|
||||
let moov = if let Some(moov) = &self.moov {
|
||||
let moov = if let Some(ref moov) = self.moov {
|
||||
moov
|
||||
} else {
|
||||
return Err(Error::BoxNotFound(MoovBox::box_type()));
|
||||
|
@ -108,11 +133,30 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
return Err(Error::TrakNotFound(track_id));
|
||||
};
|
||||
|
||||
let _sample_offset = trak.sample_offset(sample_id)?;
|
||||
let _sample_size = trak.sample_size(sample_id)?;
|
||||
trak.sample_count()
|
||||
}
|
||||
|
||||
// TODO
|
||||
pub fn read_sample(
|
||||
&mut self,
|
||||
track_id: u32,
|
||||
sample_id: u32,
|
||||
) -> Result<Option<Mp4Sample>> {
|
||||
if track_id == 0 {
|
||||
return Err(Error::TrakNotFound(track_id));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
let moov = if let Some(ref moov) = self.moov {
|
||||
moov
|
||||
} else {
|
||||
return Err(Error::BoxNotFound(MoovBox::box_type()));
|
||||
};
|
||||
|
||||
let trak = if let Some(trak) = moov.traks.get(track_id as usize - 1) {
|
||||
trak
|
||||
} else {
|
||||
return Err(Error::TrakNotFound(track_id));
|
||||
};
|
||||
|
||||
trak.read_sample(&mut self.reader, sample_id)
|
||||
}
|
||||
}
|
||||
|
|
49
tests/lib.rs
49
tests/lib.rs
|
@ -33,11 +33,48 @@ fn test_read_mp4() {
|
|||
}
|
||||
|
||||
// moov.
|
||||
let moov = mp4.moov.unwrap();
|
||||
assert_eq!(moov.mvhd.version, 0);
|
||||
assert_eq!(moov.mvhd.creation_time, 0);
|
||||
assert_eq!(moov.mvhd.duration, 62);
|
||||
assert_eq!(moov.mvhd.timescale, 1000);
|
||||
assert_eq!(moov.traks.len(), 2);
|
||||
assert!(mp4.moov.is_some());
|
||||
if let Some(ref moov) = mp4.moov {
|
||||
assert_eq!(moov.mvhd.version, 0);
|
||||
assert_eq!(moov.mvhd.creation_time, 0);
|
||||
assert_eq!(moov.mvhd.duration, 62);
|
||||
assert_eq!(moov.mvhd.timescale, 1000);
|
||||
assert_eq!(moov.traks.len(), 2);
|
||||
}
|
||||
|
||||
let sample_count = mp4.sample_count(1).unwrap();
|
||||
assert_eq!(sample_count, 0);
|
||||
|
||||
let sample_count = mp4.sample_count(2).unwrap();
|
||||
assert_eq!(sample_count, 3);
|
||||
let sample1 = mp4.read_sample(2, 1).unwrap().unwrap();
|
||||
assert_eq!(sample1.bytes.len(), 179);
|
||||
assert_eq!(sample1, mp4::Mp4Sample {
|
||||
start_time: 0,
|
||||
duration: 1024,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 179]),
|
||||
});
|
||||
|
||||
let sample2 = mp4.read_sample(2, 2).unwrap().unwrap();
|
||||
assert_eq!(sample2, mp4::Mp4Sample {
|
||||
start_time: 1024,
|
||||
duration: 1024,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 180]),
|
||||
});
|
||||
|
||||
let sample3 = mp4.read_sample(2, 3).unwrap().unwrap();
|
||||
assert_eq!(sample3, mp4::Mp4Sample {
|
||||
start_time: 2048,
|
||||
duration: 896,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 160]),
|
||||
});
|
||||
|
||||
let eos = mp4.read_sample(2, 4).unwrap();
|
||||
assert!(eos.is_none());
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue