aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorFelix Kaaman <tmtu@tmtu.ee>2021-01-07 20:32:49 +0100
committerFelix Kaaman <tmtu@tmtu.ee>2021-01-07 20:32:49 +0100
commit9e6e4962f2346a3fbd96ab3e6c331858ef6ec0d1 (patch)
tree14b580295f71243def9c8df9c242c33f9f2c66a8 /src
downloadfmp4-9e6e4962f2346a3fbd96ab3e6c331858ef6ec0d1.tar.gz
fmp4-9e6e4962f2346a3fbd96ab3e6c331858ef6ec0d1.zip
Initial commitHEADtrunk
Diffstat (limited to 'src')
-rw-r--r--src/boxes/avc1.rs62
-rw-r--r--src/boxes/avcc.rs76
-rw-r--r--src/boxes/co64.rs45
-rw-r--r--src/boxes/dinf.rs26
-rw-r--r--src/boxes/dref.rs47
-rw-r--r--src/boxes/ftyp.rs52
-rw-r--r--src/boxes/hdlr.rs48
-rw-r--r--src/boxes/mdhd.rs48
-rw-r--r--src/boxes/mdia.rs30
-rw-r--r--src/boxes/mehd.rs35
-rw-r--r--src/boxes/mfhd.rs32
-rw-r--r--src/boxes/minf.rs46
-rw-r--r--src/boxes/mod.rs38
-rw-r--r--src/boxes/moof.rs27
-rw-r--r--src/boxes/moov.rs46
-rw-r--r--src/boxes/mvex.rs27
-rw-r--r--src/boxes/mvhd.rs63
-rw-r--r--src/boxes/smhd.rs31
-rw-r--r--src/boxes/stbl.rs36
-rw-r--r--src/boxes/stsc.rs47
-rw-r--r--src/boxes/stsd.rs61
-rw-r--r--src/boxes/stsz.rs42
-rw-r--r--src/boxes/stts.rs46
-rw-r--r--src/boxes/tfdt.rs33
-rw-r--r--src/boxes/tfhd.rs131
-rw-r--r--src/boxes/tkhd.rs62
-rw-r--r--src/boxes/traf.rs45
-rw-r--r--src/boxes/trak.rs28
-rw-r--r--src/boxes/trex.rs47
-rw-r--r--src/boxes/trun.rs179
-rw-r--r--src/boxes/url.rs35
-rw-r--r--src/boxes/vmhd.rs31
-rw-r--r--src/lib.rs129
33 files changed, 1731 insertions, 0 deletions
diff --git a/src/boxes/avc1.rs b/src/boxes/avc1.rs
new file mode 100644
index 0000000..dc306e5
--- /dev/null
+++ b/src/boxes/avc1.rs
@@ -0,0 +1,62 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::AvcConfigurationBox;
+
+use std::mem::size_of;
+
+pub struct AvcSampleEntryBox {
+ pub width: u16,
+ pub height: u16,
+ pub avcc: AvcConfigurationBox,
+}
+
+impl Mp4Box for AvcSampleEntryBox {
+ const NAME: FourCC = FourCC(*b"avc1");
+
+ fn content_size(&self) -> u64 {
+ size_of::<u8>() as u64 * 6
+ + size_of::<u16>() as u64
+ + size_of::<u8>() as u64 * 16
+ + size_of::<u16>() as u64
+ + size_of::<u16>() as u64
+ + size_of::<u32>() as u64
+ + size_of::<u32>() as u64
+ + size_of::<u8>() as u64 * 4
+ + size_of::<u16>() as u64
+ + size_of::<u8>() as u64 * 32
+ + size_of::<u16>() as u64
+ + size_of::<i16>() as u64
+ + self.avcc.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.extend(&[0u8; 6]);
+ v.write_u16::<BigEndian>(1)?;
+
+ v.extend(&[0u8; 16]);
+
+ v.write_u16::<BigEndian>(self.width)?;
+ v.write_u16::<BigEndian>(self.height)?;
+ v.write_u32::<BigEndian>(0x0048_0000)?;
+ v.write_u32::<BigEndian>(0x0048_0000)?;
+ v.extend(&[0u8; 4]);
+ v.write_u16::<BigEndian>(1)?;
+ v.extend(&[0u8; 32]);
+ v.write_u16::<BigEndian>(0x0018)?;
+ v.write_i16::<BigEndian>(-1)?;
+
+ writer.put_slice(&v);
+
+ self.avcc.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/avcc.rs b/src/boxes/avcc.rs
new file mode 100644
index 0000000..e932543
--- /dev/null
+++ b/src/boxes/avcc.rs
@@ -0,0 +1,76 @@
+use byteorder::{BigEndian, WriteBytesExt};
+
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use std::mem::size_of;
+
+pub struct AvcConfigurationBox {
+ pub config: AvcDecoderConfigurationRecord,
+}
+
+impl Mp4Box for AvcConfigurationBox {
+ const NAME: FourCC = FourCC(*b"avcC");
+
+ fn content_size(&self) -> u64 {
+ self.config.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.config.write(writer)?;
+
+ Ok(())
+ }
+}
+
+pub struct AvcDecoderConfigurationRecord {
+ pub profile_indication: u8,
+ pub profile_compatibility: u8,
+ pub level_indication: u8,
+ pub sequence_parameter_set: Vec<u8>,
+ pub picture_parameter_set: Vec<u8>,
+}
+
+impl AvcDecoderConfigurationRecord {
+ fn size(&self) -> u64 {
+ size_of::<u8>() as u64 // configurationVersion
+ + size_of::<u8>() as u64 // AVCProfileIndication
+ + size_of::<u8>() as u64 // profile_compatibility
+ + size_of::<u8>() as u64 // AVCLevelIndication
+ + size_of::<u8>() as u64 // lengthSizeMinusOne
+ + size_of::<u8>() as u64 // numOfSequenceParameterSets
+ + size_of::<u16>() as u64 // sequenceParameterSetLength
+ + self.sequence_parameter_set.len() as u64
+ + size_of::<u8>() as u64 // numOfPictureParameterSets
+ + size_of::<u16>() as u64 // pictureParameterSetLength
+ + self.picture_parameter_set.len() as u64
+ }
+
+ fn write(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.push(1);
+ v.push(self.profile_indication);
+ v.push(self.profile_compatibility);
+ v.push(self.level_indication);
+ v.push(0b1111_1100 | 3);
+
+ v.push(0b1110_0000 | 1);
+ v.write_u16::<BigEndian>(self.sequence_parameter_set.len() as u16)?;
+ v.extend(&self.sequence_parameter_set);
+
+ v.push(1);
+ v.write_u16::<BigEndian>(self.picture_parameter_set.len() as u16)?;
+ v.extend(&self.picture_parameter_set);
+
+ assert_eq!(self.size(), v.len() as u64);
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/co64.rs b/src/boxes/co64.rs
new file mode 100644
index 0000000..d52c4b0
--- /dev/null
+++ b/src/boxes/co64.rs
@@ -0,0 +1,45 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct SampleToChunkEntry {
+ first_chunk: u32,
+ samples_per_chunk: u32,
+ sample_description_index: u32,
+}
+
+pub struct ChunkLargeOffsetBox {
+ pub chunk_offsets: Vec<u64>,
+}
+
+impl Mp4Box for ChunkLargeOffsetBox {
+ const NAME: FourCC = FourCC(*b"co64");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64 + (size_of::<u64>() as u64) * self.chunk_offsets.len() as u64
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(self.chunk_offsets.len() as u32)?;
+
+ for &chunk_offset in &self.chunk_offsets {
+ v.write_u64::<BigEndian>(chunk_offset)?;
+ }
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/dinf.rs b/src/boxes/dinf.rs
new file mode 100644
index 0000000..cff0f64
--- /dev/null
+++ b/src/boxes/dinf.rs
@@ -0,0 +1,26 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::DataReferenceBox;
+
+pub struct DataInformationBox {
+ pub dref: DataReferenceBox,
+}
+
+impl Mp4Box for DataInformationBox {
+ const NAME: FourCC = FourCC(*b"dinf");
+
+ fn content_size(&self) -> u64 {
+ self.dref.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.dref.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/dref.rs b/src/boxes/dref.rs
new file mode 100644
index 0000000..a962b72
--- /dev/null
+++ b/src/boxes/dref.rs
@@ -0,0 +1,47 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+use super::DataEntryUrlBox;
+
+pub struct DataReferenceBox {
+ pub entries: Vec<DataEntryUrlBox>,
+}
+
+impl Mp4Box for DataReferenceBox {
+ const NAME: FourCC = FourCC(*b"dref");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ let mut size = size_of::<u32>() as u64; // entry_count
+
+ for entry in &self.entries {
+ size += entry.size();
+ }
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 4];
+
+ BigEndian::write_u32(&mut contents[..], self.entries.len() as _);
+
+ writer.put_slice(&contents);
+
+ for entry in &self.entries {
+ entry.write(writer)?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/ftyp.rs b/src/boxes/ftyp.rs
new file mode 100644
index 0000000..f959e51
--- /dev/null
+++ b/src/boxes/ftyp.rs
@@ -0,0 +1,52 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use std::io::Write;
+use std::mem::size_of;
+
+use crate::{Mp4Box, Mp4BoxError};
+
+pub struct FileTypeBox {
+ major_brand: FourCC,
+ minor_version: u32,
+ compatible_brands: Vec<FourCC>,
+}
+
+impl FileTypeBox {
+ pub fn new(major_brand: FourCC, minor_version: u32, compatible_brands: Vec<FourCC>) -> Self {
+ FileTypeBox {
+ major_brand,
+ minor_version,
+ compatible_brands,
+ }
+ }
+}
+
+impl Mp4Box for FileTypeBox {
+ const NAME: FourCC = FourCC(*b"ftyp");
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64 + // major_brand
+ size_of::<u32>() as u64 + // minor_version
+ size_of::<u32>() as u64 * self.compatible_brands.len() as u64 // compatible_brands
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ Write::write_all(&mut v, &self.major_brand.0)?;
+ v.write_u32::<BigEndian>(self.minor_version)?;
+
+ for brand in &self.compatible_brands {
+ Write::write_all(&mut v, &brand.0)?;
+ }
+
+ assert_eq!(self.content_size() as usize, v.len());
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/hdlr.rs b/src/boxes/hdlr.rs
new file mode 100644
index 0000000..0e8dfc8
--- /dev/null
+++ b/src/boxes/hdlr.rs
@@ -0,0 +1,48 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct HandlerBox {
+ pub handler_type: u32,
+ pub name: String,
+}
+
+impl Mp4Box for HandlerBox {
+ const NAME: FourCC = FourCC(*b"hdlr");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64 + // pre_defined
+ size_of::<u32>() as u64 + // handler_type
+ size_of::<u32>() as u64 * 3 + // reserved
+ self.name.as_bytes().len() as u64 + // name
+ 1
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(0)?;
+ v.write_u32::<BigEndian>(self.handler_type)?;
+ v.write_u32::<BigEndian>(0)?;
+ v.write_u32::<BigEndian>(0)?;
+ v.write_u32::<BigEndian>(0)?;
+ v.extend(self.name.as_bytes());
+ v.push(0);
+
+ assert_eq!(v.len() as u64, self.content_size());
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mdhd.rs b/src/boxes/mdhd.rs
new file mode 100644
index 0000000..5befd67
--- /dev/null
+++ b/src/boxes/mdhd.rs
@@ -0,0 +1,48 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct MediaHeaderBox {
+ pub creation_time: u64,
+ pub modification_time: u64,
+ pub timescale: u32,
+ pub duration: u64,
+}
+
+impl Mp4Box for MediaHeaderBox {
+ const NAME: FourCC = FourCC(*b"mdhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(1, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u64>() as u64 + // creation_time
+ size_of::<u64>() as u64 + // modification_time
+ size_of::<u32>() as u64 + // timescale
+ size_of::<u64>() as u64 + // duration
+ size_of::<u16>() as u64 + // language
+ size_of::<u16>() as u64 // pre_defined
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 32];
+
+ BigEndian::write_u64(&mut contents[..], self.creation_time);
+ BigEndian::write_u64(&mut contents[8..], self.modification_time);
+ BigEndian::write_u32(&mut contents[16..], self.timescale);
+ BigEndian::write_u64(&mut contents[20..], self.duration);
+
+ BigEndian::write_u16(&mut contents[28..], 0); // language
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mdia.rs b/src/boxes/mdia.rs
new file mode 100644
index 0000000..f18a8e9
--- /dev/null
+++ b/src/boxes/mdia.rs
@@ -0,0 +1,30 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::{HandlerBox, MediaHeaderBox, MediaInformationBox};
+
+pub struct MediaBox {
+ pub mdhd: MediaHeaderBox,
+ pub hdlr: HandlerBox,
+ pub minf: MediaInformationBox,
+}
+
+impl Mp4Box for MediaBox {
+ const NAME: FourCC = FourCC(*b"mdia");
+
+ fn content_size(&self) -> u64 {
+ self.mdhd.size() + self.hdlr.size() + self.minf.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.mdhd.write(writer)?;
+ self.hdlr.write(writer)?;
+ self.minf.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mehd.rs b/src/boxes/mehd.rs
new file mode 100644
index 0000000..74082c1
--- /dev/null
+++ b/src/boxes/mehd.rs
@@ -0,0 +1,35 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct MovieExtendsHeaderBox {
+ pub fragment_duration: u64,
+}
+
+impl Mp4Box for MovieExtendsHeaderBox {
+ const NAME: FourCC = FourCC(*b"mehd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(1, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u64>() as u64 // fragment_duration
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 8];
+
+ BigEndian::write_u64(&mut contents[..], self.fragment_duration);
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mfhd.rs b/src/boxes/mfhd.rs
new file mode 100644
index 0000000..f5235c2
--- /dev/null
+++ b/src/boxes/mfhd.rs
@@ -0,0 +1,32 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+pub struct MovieFragmentHeaderBox {
+ pub sequence_number: u32,
+}
+
+impl Mp4Box for MovieFragmentHeaderBox {
+ const NAME: FourCC = FourCC(*b"mfhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ 4
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 4];
+ BigEndian::write_u32(&mut contents, self.sequence_number);
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/minf.rs b/src/boxes/minf.rs
new file mode 100644
index 0000000..70a6cc4
--- /dev/null
+++ b/src/boxes/minf.rs
@@ -0,0 +1,46 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::{DataInformationBox, SampleTableBox, SoundMediaHeaderBox, VideoMediaHeaderBox};
+
+pub enum MediaHeader {
+ Video(VideoMediaHeaderBox),
+ Sound(SoundMediaHeaderBox),
+}
+
+pub struct MediaInformationBox {
+ pub media_header: MediaHeader,
+ pub dinf: DataInformationBox,
+ pub stbl: SampleTableBox,
+}
+
+impl Mp4Box for MediaInformationBox {
+ const NAME: FourCC = FourCC(*b"minf");
+
+ fn content_size(&self) -> u64 {
+ let mut size = self.dinf.size() + self.stbl.size();
+
+ match &self.media_header {
+ MediaHeader::Video(vmhd) => size += vmhd.size(),
+ MediaHeader::Sound(smhd) => size += smhd.size(),
+ }
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ match &self.media_header {
+ MediaHeader::Video(vmhd) => vmhd.write(writer)?,
+ MediaHeader::Sound(smhd) => smhd.write(writer)?,
+ }
+
+ self.dinf.write(writer)?;
+ self.stbl.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mod.rs b/src/boxes/mod.rs
new file mode 100644
index 0000000..ce73e5f
--- /dev/null
+++ b/src/boxes/mod.rs
@@ -0,0 +1,38 @@
+mod avc1;
+mod avcc;
+mod co64;
+mod dinf;
+mod dref;
+mod ftyp;
+mod hdlr;
+mod mdhd;
+mod mdia;
+mod mehd;
+mod mfhd;
+mod minf;
+mod moof;
+mod moov;
+mod mvex;
+mod mvhd;
+mod smhd;
+mod stbl;
+mod stsc;
+mod stsd;
+mod stsz;
+mod stts;
+mod tfdt;
+mod tfhd;
+mod tkhd;
+mod traf;
+mod trak;
+mod trex;
+mod trun;
+mod url;
+mod vmhd;
+
+pub use self::{
+ avc1::*, avcc::*, co64::*, dinf::*, dref::*, ftyp::*, hdlr::*, mdhd::*, mdia::*, mehd::*,
+ mfhd::*, minf::*, moof::*, moov::*, mvex::*, mvhd::*, smhd::*, stbl::*, stsc::*, stsd::*,
+ stsz::*, stts::*, tfdt::*, tfhd::*, tkhd::*, traf::*, trak::*, trex::*, trun::*, url::*,
+ vmhd::*,
+};
diff --git a/src/boxes/moof.rs b/src/boxes/moof.rs
new file mode 100644
index 0000000..7e028f5
--- /dev/null
+++ b/src/boxes/moof.rs
@@ -0,0 +1,27 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::{Mp4Box, Mp4BoxError};
+
+use super::{MovieFragmentHeaderBox, TrackFragmentBox};
+
+pub struct MovieFragmentBox {
+ pub mfhd: MovieFragmentHeaderBox,
+ pub traf: TrackFragmentBox,
+}
+
+impl Mp4Box for MovieFragmentBox {
+ const NAME: FourCC = FourCC(*b"moof");
+
+ fn content_size(&self) -> u64 {
+ self.mfhd.size() + self.traf.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.mfhd.write(writer)?;
+ self.traf.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/moov.rs b/src/boxes/moov.rs
new file mode 100644
index 0000000..6140d59
--- /dev/null
+++ b/src/boxes/moov.rs
@@ -0,0 +1,46 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::{MovieExtendsBox, MovieHeaderBox, TrackBox};
+
+pub struct MovieBox {
+ pub mvhd: MovieHeaderBox,
+ pub mvex: Option<MovieExtendsBox>,
+ pub tracks: Vec<TrackBox>,
+}
+
+impl Mp4Box for MovieBox {
+ const NAME: FourCC = FourCC(*b"moov");
+
+ fn content_size(&self) -> u64 {
+ let mut size = self.mvhd.size();
+
+ if let Some(mvex) = &self.mvex {
+ size += mvex.size();
+ }
+
+ for track in &self.tracks {
+ size += track.size();
+ }
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.mvhd.write(writer)?;
+
+ if let Some(mvex) = &self.mvex {
+ mvex.write(writer)?;
+ }
+
+ for track in &self.tracks {
+ track.write(writer)?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mvex.rs b/src/boxes/mvex.rs
new file mode 100644
index 0000000..ede5da1
--- /dev/null
+++ b/src/boxes/mvex.rs
@@ -0,0 +1,27 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::{Mp4Box, Mp4BoxError};
+
+use super::{MovieExtendsHeaderBox, TrackExtendsBox};
+
+pub struct MovieExtendsBox {
+ pub mehd: MovieExtendsHeaderBox,
+ pub trex: TrackExtendsBox,
+}
+
+impl Mp4Box for MovieExtendsBox {
+ const NAME: FourCC = FourCC(*b"mvex");
+
+ fn content_size(&self) -> u64 {
+ self.mehd.size() + self.trex.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.mehd.write(writer)?;
+ self.trex.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/mvhd.rs b/src/boxes/mvhd.rs
new file mode 100644
index 0000000..2f8984c
--- /dev/null
+++ b/src/boxes/mvhd.rs
@@ -0,0 +1,63 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct MovieHeaderBox {
+ pub creation_time: u64,
+ pub modification_time: u64,
+ pub timescale: u32,
+ pub duration: u64,
+}
+
+impl Mp4Box for MovieHeaderBox {
+ const NAME: FourCC = FourCC(*b"mvhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(1, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u64>() as u64 + // creation_time
+ size_of::<u64>() as u64 + // modification_time
+ size_of::<u32>() as u64 + // timescale
+ size_of::<u64>() as u64 + // duration
+ size_of::<u32>() as u64 + // rate
+ size_of::<u16>() as u64 + // volume
+ size_of::<u16>() as u64 + // reserved
+ size_of::<u32>() as u64 * 2 + // reserved
+ size_of::<i32>() as u64 * 9 + // matrix
+ size_of::<u32>() as u64 * 6 + // pre_defined
+ size_of::<u32>() as u64 // next_track_ID
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 108];
+
+ BigEndian::write_u64(&mut contents[..], self.creation_time);
+ BigEndian::write_u64(&mut contents[8..], self.modification_time);
+ BigEndian::write_u32(&mut contents[16..], self.timescale);
+ BigEndian::write_u64(&mut contents[20..], self.duration);
+
+ BigEndian::write_i32(&mut contents[28..], 0x00010000);
+ BigEndian::write_i16(&mut contents[32..], 0x0100);
+
+ BigEndian::write_u16(&mut contents[34..], 0);
+ BigEndian::write_u64(&mut contents[36..], 0);
+
+ BigEndian::write_i32(&mut contents[44..], 0x00010000);
+ BigEndian::write_i32(&mut contents[60..], 0x00010000);
+ BigEndian::write_i32(&mut contents[76..], 0x40000000);
+
+ BigEndian::write_u32(&mut contents[104..], 1);
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/smhd.rs b/src/boxes/smhd.rs
new file mode 100644
index 0000000..dbb3869
--- /dev/null
+++ b/src/boxes/smhd.rs
@@ -0,0 +1,31 @@
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct SoundMediaHeaderBox {}
+
+impl Mp4Box for SoundMediaHeaderBox {
+ const NAME: FourCC = FourCC(*b"smhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u16>() as u64 + // balance
+ size_of::<u16>() as u64 // reserved
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let contents = [0u8; 4];
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/stbl.rs b/src/boxes/stbl.rs
new file mode 100644
index 0000000..1ea41ae
--- /dev/null
+++ b/src/boxes/stbl.rs
@@ -0,0 +1,36 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::{
+ ChunkLargeOffsetBox, SampleDescriptionBox, SampleSizeBox, SampleToChunkBox, TimeToSampleBox,
+};
+
+pub struct SampleTableBox {
+ pub stsd: SampleDescriptionBox,
+ pub stts: TimeToSampleBox,
+ pub stsc: SampleToChunkBox,
+ pub stsz: SampleSizeBox,
+ pub co64: ChunkLargeOffsetBox,
+}
+
+impl Mp4Box for SampleTableBox {
+ const NAME: FourCC = FourCC(*b"stbl");
+
+ fn content_size(&self) -> u64 {
+ self.stsd.size() + self.stts.size() + self.stsc.size() + self.stsz.size() + self.co64.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.stsd.write(writer)?;
+ self.stts.write(writer)?;
+ self.stsc.write(writer)?;
+ self.stsz.write(writer)?;
+ self.co64.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/stsc.rs b/src/boxes/stsc.rs
new file mode 100644
index 0000000..a8bb059
--- /dev/null
+++ b/src/boxes/stsc.rs
@@ -0,0 +1,47 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct SampleToChunkEntry {
+ first_chunk: u32,
+ samples_per_chunk: u32,
+ sample_description_index: u32,
+}
+
+pub struct SampleToChunkBox {
+ pub entries: Vec<SampleToChunkEntry>,
+}
+
+impl Mp4Box for SampleToChunkBox {
+ const NAME: FourCC = FourCC(*b"stsc");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64 + (size_of::<u32>() as u64 * 3) * self.entries.len() as u64
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(self.entries.len() as u32)?;
+
+ for entry in &self.entries {
+ v.write_u32::<BigEndian>(entry.first_chunk)?;
+ v.write_u32::<BigEndian>(entry.samples_per_chunk)?;
+ v.write_u32::<BigEndian>(entry.sample_description_index)?;
+ }
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/stsd.rs b/src/boxes/stsd.rs
new file mode 100644
index 0000000..35ee5b2
--- /dev/null
+++ b/src/boxes/stsd.rs
@@ -0,0 +1,61 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::FullBoxHeader;
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::AvcSampleEntryBox;
+
+use std::mem::size_of;
+
+pub enum SampleEntry {
+ Avc(AvcSampleEntryBox),
+}
+
+impl SampleEntry {
+ fn size(&self) -> u64 {
+ match self {
+ SampleEntry::Avc(avc) => avc.size(),
+ }
+ }
+}
+
+pub struct SampleDescriptionBox {
+ pub entries: Vec<SampleEntry>,
+}
+
+impl Mp4Box for SampleDescriptionBox {
+ const NAME: FourCC = FourCC(*b"stsd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ let mut size = size_of::<u32>() as u64;
+
+ for entry in &self.entries {
+ size += entry.size();
+ }
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 4];
+ BigEndian::write_u32(&mut contents, self.entries.len() as _);
+
+ writer.put_slice(&contents);
+
+ for entry in &self.entries {
+ match entry {
+ SampleEntry::Avc(avc) => avc.write(writer)?,
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/stsz.rs b/src/boxes/stsz.rs
new file mode 100644
index 0000000..7dc3eba
--- /dev/null
+++ b/src/boxes/stsz.rs
@@ -0,0 +1,42 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct SampleSizeBox {
+ pub sample_sizes: Vec<u32>,
+}
+
+impl Mp4Box for SampleSizeBox {
+ const NAME: FourCC = FourCC(*b"stsz");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64
+ + size_of::<u32>() as u64
+ + size_of::<u32>() as u64 * self.sample_sizes.len() as u64
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(0)?;
+ v.write_u32::<BigEndian>(self.sample_sizes.len() as u32)?;
+
+ for &size in &self.sample_sizes {
+ v.write_u32::<BigEndian>(size)?;
+ }
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/stts.rs b/src/boxes/stts.rs
new file mode 100644
index 0000000..b956a52
--- /dev/null
+++ b/src/boxes/stts.rs
@@ -0,0 +1,46 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct TimeToSampleEntry {
+ count: u32,
+ delta: u32,
+}
+
+pub struct TimeToSampleBox {
+ pub entries: Vec<TimeToSampleEntry>,
+}
+
+impl Mp4Box for TimeToSampleBox {
+ const NAME: FourCC = FourCC(*b"stts");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64
+ + (size_of::<u32>() as u64 + size_of::<u32>() as u64) * self.entries.len() as u64
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(self.entries.len() as _)?;
+
+ for entry in &self.entries {
+ v.write_u32::<BigEndian>(entry.count)?;
+ v.write_u32::<BigEndian>(entry.delta)?;
+ }
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/tfdt.rs b/src/boxes/tfdt.rs
new file mode 100644
index 0000000..9ea0747
--- /dev/null
+++ b/src/boxes/tfdt.rs
@@ -0,0 +1,33 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::{FullBoxHeader, Mp4Box, Mp4BoxError};
+
+use std::mem::size_of;
+
+pub struct TrackFragmentBaseMediaDecodeTimeBox {
+ pub base_media_decode_time: u64,
+}
+
+impl Mp4Box for TrackFragmentBaseMediaDecodeTimeBox {
+ const NAME: FourCC = FourCC(*b"tfdt");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(1, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u64>() as u64 // base_media_decode_time
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut content = [0u8; 8];
+ BigEndian::write_u64(&mut content[..], self.base_media_decode_time);
+
+ writer.put_slice(&content);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/tfhd.rs b/src/boxes/tfhd.rs
new file mode 100644
index 0000000..45c5301
--- /dev/null
+++ b/src/boxes/tfhd.rs
@@ -0,0 +1,131 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::{FullBoxHeader, Mp4Box, Mp4BoxError};
+
+use std::mem::size_of;
+
+bitflags::bitflags! {
+ pub struct TrackFragmentHeaderFlags: u32 {
+ const BASE_DATA_OFFSET_PRESENT = 0x000001;
+ const SAMPLE_DESCRIPTION_INDEX_PRESENT = 0x000002;
+ const DEFAULT_SAMPLE_DURATION_PRESENT = 0x000008;
+ const DEFAULT_SAMPLE_SIZE_PRESENT = 0x000010;
+ const DEFAULT_SAMPLE_FLAGS_PRESENT = 0x000020;
+ const DURATION_IS_EMPTY = 0x010000;
+ const DEFAULT_BASE_IS_MOOF = 0x020000;
+ }
+}
+
+pub struct TrackFragmentHeaderBox {
+ pub track_id: u32,
+ pub base_data_offset: Option<u64>,
+ pub sample_description_index: Option<u32>,
+ pub default_sample_duration: Option<u32>,
+ pub default_sample_size: Option<u32>,
+ pub default_sample_flags: Option<u32>,
+ pub duration_is_empty: bool,
+ pub default_base_is_moof: bool,
+}
+
+impl TrackFragmentHeaderBox {
+ fn flags_from_fields(&self) -> TrackFragmentHeaderFlags {
+ let mut flags = TrackFragmentHeaderFlags::empty();
+
+ if self.base_data_offset.is_some() {
+ flags.insert(TrackFragmentHeaderFlags::BASE_DATA_OFFSET_PRESENT);
+ }
+
+ if self.sample_description_index.is_some() {
+ flags.insert(TrackFragmentHeaderFlags::SAMPLE_DESCRIPTION_INDEX_PRESENT);
+ }
+
+ if self.default_sample_duration.is_some() {
+ flags.insert(TrackFragmentHeaderFlags::DEFAULT_SAMPLE_DURATION_PRESENT);
+ }
+
+ if self.default_sample_size.is_some() {
+ flags.insert(TrackFragmentHeaderFlags::DEFAULT_SAMPLE_SIZE_PRESENT);
+ }
+
+ if self.default_sample_flags.is_some() {
+ flags.insert(TrackFragmentHeaderFlags::DEFAULT_SAMPLE_FLAGS_PRESENT);
+ }
+
+ if self.duration_is_empty {
+ flags.insert(TrackFragmentHeaderFlags::DURATION_IS_EMPTY);
+ }
+
+ if self.default_base_is_moof {
+ flags.insert(TrackFragmentHeaderFlags::DEFAULT_BASE_IS_MOOF);
+ }
+
+ flags
+ }
+}
+
+impl Mp4Box for TrackFragmentHeaderBox {
+ const NAME: FourCC = FourCC(*b"tfhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, self.flags_from_fields().bits()))
+ }
+
+ fn content_size(&self) -> u64 {
+ let mut size = size_of::<u32>() as u64; // track_ID
+
+ if self.base_data_offset.is_some() {
+ size += size_of::<u64>() as u64;
+ }
+
+ if self.sample_description_index.is_some() {
+ size += size_of::<u32>() as u64;
+ }
+
+ if self.default_sample_duration.is_some() {
+ size += size_of::<u32>() as u64;
+ }
+
+ if self.default_sample_size.is_some() {
+ size += size_of::<u32>() as u64;
+ }
+
+ if self.default_sample_flags.is_some() {
+ size += size_of::<u32>() as u64;
+ }
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(self.track_id)?;
+
+ if let Some(base_data_offset) = self.base_data_offset {
+ v.write_u64::<BigEndian>(base_data_offset)?;
+ }
+
+ if let Some(sample_description_index) = self.sample_description_index {
+ v.write_u32::<BigEndian>(sample_description_index)?;
+ }
+
+ if let Some(default_sample_duration) = self.default_sample_duration {
+ v.write_u32::<BigEndian>(default_sample_duration)?;
+ }
+
+ if let Some(default_sample_size) = self.default_sample_size {
+ v.write_u32::<BigEndian>(default_sample_size)?;
+ }
+
+ if let Some(default_sample_flags) = self.default_sample_flags {
+ v.write_u32::<BigEndian>(default_sample_flags)?;
+ }
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/tkhd.rs b/src/boxes/tkhd.rs
new file mode 100644
index 0000000..9519940
--- /dev/null
+++ b/src/boxes/tkhd.rs
@@ -0,0 +1,62 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct TrackHeaderBox {
+ pub creation_time: u64,
+ pub modification_time: u64,
+ pub track_id: u32,
+ pub duration: u64,
+}
+
+impl Mp4Box for TrackHeaderBox {
+ const NAME: FourCC = FourCC(*b"tkhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(1, 0)) // TODO: flags matter
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u64>() as u64 + // creation_time
+ size_of::<u64>() as u64 + // modification_time
+ size_of::<u32>() as u64 + // track_ID
+ size_of::<u32>() as u64 + // reserved
+ size_of::<u64>() as u64 + // duration
+ size_of::<u32>() as u64 * 2 + // reserved
+ size_of::<u16>() as u64 + // layer
+ size_of::<u16>() as u64 + // alternate_group
+ size_of::<u16>() as u64 + // volume
+ size_of::<u16>() as u64 + // reserved
+ size_of::<i32>() as u64 * 9 + // matrix
+ size_of::<u32>() as u64 + // width
+ size_of::<u32>() as u64 // height
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 92];
+
+ BigEndian::write_u64(&mut contents[..], self.creation_time);
+ BigEndian::write_u64(&mut contents[8..], self.modification_time);
+ BigEndian::write_u32(&mut contents[16..], self.track_id);
+ BigEndian::write_u64(&mut contents[24..], self.duration);
+
+ BigEndian::write_i32(&mut contents[44..], 0); // volume
+
+ BigEndian::write_i32(&mut contents[46..], 0x00010000);
+ BigEndian::write_i32(&mut contents[62..], 0x00010000);
+ BigEndian::write_i32(&mut contents[78..], 0x40000000);
+
+ BigEndian::write_u32(&mut contents[84..], 1); // width
+ BigEndian::write_u32(&mut contents[88..], 2); // height
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/traf.rs b/src/boxes/traf.rs
new file mode 100644
index 0000000..42dbea2
--- /dev/null
+++ b/src/boxes/traf.rs
@@ -0,0 +1,45 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::{Mp4Box, Mp4BoxError};
+
+use super::{TrackFragmentBaseMediaDecodeTimeBox, TrackFragmentHeaderBox, TrackFragmentRunBox};
+
+pub struct TrackFragmentBox {
+ pub tfhd: TrackFragmentHeaderBox,
+ pub track_runs: Vec<TrackFragmentRunBox>,
+ pub base_media_decode_time: Option<TrackFragmentBaseMediaDecodeTimeBox>,
+}
+
+impl Mp4Box for TrackFragmentBox {
+ const NAME: FourCC = FourCC(*b"traf");
+
+ fn content_size(&self) -> u64 {
+ let mut size = self.tfhd.size();
+
+ for trun in &self.track_runs {
+ size += trun.size();
+ }
+
+ if let Some(base_media_decode_time) = &self.base_media_decode_time {
+ size += base_media_decode_time.size();
+ }
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.tfhd.write(writer)?;
+
+ if let Some(base_media_decode_time) = &self.base_media_decode_time {
+ base_media_decode_time.write(writer)?;
+ }
+
+ for run in &self.track_runs {
+ run.write(writer)?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/trak.rs b/src/boxes/trak.rs
new file mode 100644
index 0000000..bb3630f
--- /dev/null
+++ b/src/boxes/trak.rs
@@ -0,0 +1,28 @@
+use four_cc::FourCC;
+
+use bytes::BytesMut;
+
+use crate::Mp4Box;
+use crate::Mp4BoxError;
+
+use super::{MediaBox, TrackHeaderBox};
+
+pub struct TrackBox {
+ pub tkhd: TrackHeaderBox,
+ pub mdia: MediaBox,
+}
+
+impl Mp4Box for TrackBox {
+ const NAME: FourCC = FourCC(*b"trak");
+
+ fn content_size(&self) -> u64 {
+ self.tkhd.size() + self.mdia.size()
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ self.tkhd.write(writer)?;
+ self.mdia.write(writer)?;
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/trex.rs b/src/boxes/trex.rs
new file mode 100644
index 0000000..fa9f496
--- /dev/null
+++ b/src/boxes/trex.rs
@@ -0,0 +1,47 @@
+use byteorder::{BigEndian, ByteOrder};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct TrackExtendsBox {
+ pub track_id: u32,
+ pub default_sample_description_index: u32,
+ pub default_sample_duration: u32,
+ pub default_sample_size: u32,
+ pub default_sample_flags: u32,
+}
+
+impl Mp4Box for TrackExtendsBox {
+ const NAME: FourCC = FourCC(*b"trex");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u32>() as u64 + // track_ID
+ size_of::<u32>() as u64 + // default_sample_description_index
+ size_of::<u32>() as u64 + // default_sample_duration
+ size_of::<u32>() as u64 + // default_sample_size
+ size_of::<u32>() as u64 // default_sample_flags
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut contents = [0u8; 20];
+
+ BigEndian::write_u32(&mut contents[..], self.track_id);
+ BigEndian::write_u32(&mut contents[4..], self.default_sample_description_index);
+ BigEndian::write_u32(&mut contents[8..], self.default_sample_duration);
+ BigEndian::write_u32(&mut contents[12..], self.default_sample_size);
+ BigEndian::write_u32(&mut contents[16..], self.default_sample_flags);
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/trun.rs b/src/boxes/trun.rs
new file mode 100644
index 0000000..f093a41
--- /dev/null
+++ b/src/boxes/trun.rs
@@ -0,0 +1,179 @@
+use byteorder::{BigEndian, WriteBytesExt};
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use std::mem::size_of;
+
+use crate::{FullBoxHeader, Mp4Box, Mp4BoxError};
+
+bitflags::bitflags! {
+ pub struct TrackFragmentRunFlags: u32 {
+ const DATA_OFFSET_PRESENT = 0x00000001;
+ const FIRST_SAMPLE_FLAGS_PRESENT = 0x00000004;
+ const SAMPLE_DURATION_PRESENT = 0x00000100;
+ const SAMPLE_SIZE_PRESENT = 0x00000200;
+ const SAMPLE_FLAGS_PRESENT = 0x00000400;
+ const SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT = 0x00000800;
+ }
+}
+
+pub struct TrackFragmentSample {
+ pub duration: Option<u32>,
+ pub size: Option<u32>,
+ pub flags: Option<u32>,
+ pub composition_time_offset: Option<i32>,
+}
+
+pub struct TrackFragmentRunBox {
+ pub data_offset: Option<i32>,
+ pub first_sample_flags: Option<u32>,
+ pub samples: Vec<TrackFragmentSample>,
+}
+
+impl TrackFragmentRunBox {
+ fn sample_size(&self, flags: TrackFragmentRunFlags) -> u64 {
+ let mut sample_size = 0;
+
+ if flags.contains(TrackFragmentRunFlags::SAMPLE_DURATION_PRESENT) {
+ sample_size += 4; // sample_duration
+ }
+
+ if flags.contains(TrackFragmentRunFlags::SAMPLE_SIZE_PRESENT) {
+ sample_size += 4; // sample_size
+ }
+
+ if flags.contains(TrackFragmentRunFlags::SAMPLE_FLAGS_PRESENT) {
+ sample_size += 4; // sample_flags
+ }
+
+ if flags.contains(TrackFragmentRunFlags::SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT) {
+ sample_size += 4; // sample_composition_time_offset
+ }
+
+ sample_size
+ }
+
+ fn flags_from_fields(&self) -> TrackFragmentRunFlags {
+ let mut flags = TrackFragmentRunFlags::empty();
+
+ if self.data_offset.is_some() {
+ flags.insert(TrackFragmentRunFlags::DATA_OFFSET_PRESENT);
+ }
+
+ if self.first_sample_flags.is_some() {
+ flags.insert(TrackFragmentRunFlags::FIRST_SAMPLE_FLAGS_PRESENT);
+ }
+
+ if let Some(sample) = self.samples.get(0) {
+ if sample.duration.is_some() {
+ flags.insert(TrackFragmentRunFlags::SAMPLE_DURATION_PRESENT);
+ }
+
+ if sample.size.is_some() {
+ flags.insert(TrackFragmentRunFlags::SAMPLE_SIZE_PRESENT);
+ }
+
+ if sample.flags.is_some() {
+ flags.insert(TrackFragmentRunFlags::SAMPLE_FLAGS_PRESENT);
+ }
+
+ if sample.composition_time_offset.is_some() {
+ flags.insert(TrackFragmentRunFlags::SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT);
+ }
+ }
+
+ flags
+ }
+}
+
+impl Mp4Box for TrackFragmentRunBox {
+ const NAME: FourCC = FourCC(*b"trun");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, self.flags_from_fields().bits()))
+ }
+
+ fn content_size(&self) -> u64 {
+ let flags = self.flags_from_fields();
+
+ let mut size = 0;
+
+ size += size_of::<u32>() as u64; // sample_count
+
+ if flags.contains(TrackFragmentRunFlags::DATA_OFFSET_PRESENT) {
+ size += size_of::<i32>() as u64; // data_offset
+ }
+
+ if flags.contains(TrackFragmentRunFlags::FIRST_SAMPLE_FLAGS_PRESENT) {
+ size += size_of::<u32>() as u64; // first_sample_flags
+ }
+
+ size += self.sample_size(flags) * self.samples.len() as u64;
+
+ size
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.write_u32::<BigEndian>(self.samples.len() as u32)?; // TODO: check for truncation
+
+ if let Some(data_offset) = self.data_offset {
+ v.write_i32::<BigEndian>(data_offset)?;
+ }
+
+ if let Some(first_sample_flags) = self.first_sample_flags {
+ v.write_u32::<BigEndian>(first_sample_flags)?;
+ }
+
+ let flags = self.flags_from_fields();
+ for sample in &self.samples {
+ ensure_sample_fields_present(&sample, flags);
+
+ if let Some(duration) = sample.duration {
+ v.write_u32::<BigEndian>(duration)?;
+ }
+
+ if let Some(size) = sample.size {
+ v.write_u32::<BigEndian>(size)?;
+ }
+
+ if let Some(flags) = sample.flags {
+ v.write_u32::<BigEndian>(flags)?;
+ }
+
+ if let Some(composition_time_offset) = sample.composition_time_offset {
+ v.write_i32::<BigEndian>(composition_time_offset)?;
+ }
+ }
+
+ assert_eq!(v.len() as u64, self.content_size());
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
+
+fn ensure_sample_fields_present(sample: &TrackFragmentSample, flags: TrackFragmentRunFlags) {
+ let duration_should_be_present = flags.contains(TrackFragmentRunFlags::SAMPLE_DURATION_PRESENT);
+ let size_should_be_present = flags.contains(TrackFragmentRunFlags::SAMPLE_SIZE_PRESENT);
+ let flags_should_be_present = flags.contains(TrackFragmentRunFlags::SAMPLE_FLAGS_PRESENT);
+ let composition_time_offset_should_be_present =
+ flags.contains(TrackFragmentRunFlags::SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT);
+
+ let duration_is_present = sample.duration.is_some();
+ let size_is_present = sample.size.is_some();
+ let flags_is_present = sample.flags.is_some();
+ let composition_time_offset_is_present = sample.composition_time_offset.is_some();
+
+ // TODO: return error
+ assert_eq!(duration_should_be_present, duration_is_present);
+ assert_eq!(size_should_be_present, size_is_present);
+ assert_eq!(flags_should_be_present, flags_is_present);
+ assert_eq!(
+ composition_time_offset_should_be_present,
+ composition_time_offset_is_present
+ );
+}
diff --git a/src/boxes/url.rs b/src/boxes/url.rs
new file mode 100644
index 0000000..91b471d
--- /dev/null
+++ b/src/boxes/url.rs
@@ -0,0 +1,35 @@
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+pub struct DataEntryUrlBox {
+ pub location: String,
+}
+
+impl Mp4Box for DataEntryUrlBox {
+ const NAME: FourCC = FourCC(*b"url ");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0x000001))
+ }
+
+ fn content_size(&self) -> u64 {
+ self.location.as_bytes().len() as u64 + 1
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut v = Vec::new();
+
+ v.extend(self.location.as_bytes());
+ v.push(0);
+
+ assert_eq!(v.len() as u64, self.content_size());
+
+ writer.put_slice(&v);
+
+ Ok(())
+ }
+}
diff --git a/src/boxes/vmhd.rs b/src/boxes/vmhd.rs
new file mode 100644
index 0000000..93f8ddf
--- /dev/null
+++ b/src/boxes/vmhd.rs
@@ -0,0 +1,31 @@
+use four_cc::FourCC;
+
+use bytes::{BufMut, BytesMut};
+
+use crate::Mp4BoxError;
+use crate::{FullBoxHeader, Mp4Box};
+
+use std::mem::size_of;
+
+pub struct VideoMediaHeaderBox {}
+
+impl Mp4Box for VideoMediaHeaderBox {
+ const NAME: FourCC = FourCC(*b"vmhd");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 1))
+ }
+
+ fn content_size(&self) -> u64 {
+ size_of::<u16>() as u64 + // graphicsmode
+ (size_of::<u16>() as u64 * 3) // opcolor
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let contents = [0u8; 8];
+
+ writer.put_slice(&contents);
+
+ Ok(())
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..0630a15
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,129 @@
+#![allow(dead_code)]
+
+use std::io;
+
+use byteorder::{BigEndian, ByteOrder};
+use bytes::{BufMut, BytesMut};
+pub use four_cc::FourCC;
+
+mod boxes;
+
+pub use boxes::*;
+
+fn get_total_box_size<B: Mp4Box + ?Sized>(boks: &B) -> u64 {
+ let size = boks.content_size();
+
+ if boks.get_full_box_header().is_some() {
+ size + FullBoxHeader::SIZE + 8
+ } else {
+ size + 8
+ }
+}
+
+fn write_box_header<B: Mp4Box + ?Sized>(header: &mut [u8], size: u64) -> usize {
+ if size > u32::MAX as _ {
+ BigEndian::write_u32(&mut header[..], 1);
+ header[4..8].copy_from_slice(&B::NAME.0);
+ BigEndian::write_u64(&mut header[8..], size);
+
+ 16
+ } else {
+ BigEndian::write_u32(&mut header[..], size as u32);
+ header[4..8].copy_from_slice(&B::NAME.0);
+
+ 8
+ }
+}
+
+fn write_full_box_header(header: &mut [u8], box_header: FullBoxHeader) -> usize {
+ header[0] = box_header.version;
+ BigEndian::write_u24(&mut header[1..], box_header.flags);
+
+ 4
+}
+
+#[derive(Copy, Clone)]
+pub struct FullBoxHeader {
+ version: u8,
+ flags: u32,
+}
+
+impl FullBoxHeader {
+ pub const SIZE: u64 = 4;
+
+ pub fn new(version: u8, flags: u32) -> Self {
+ FullBoxHeader { version, flags }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Mp4BoxError {
+ #[error("Failed to write box: {0}")]
+ IoError(#[from] io::Error),
+}
+
+/// A trait interface for a MP4 box.
+pub trait Mp4Box {
+ const NAME: FourCC;
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ None
+ }
+
+ fn flags(&self) -> Option<u32> {
+ self.get_full_box_header().map(|h| h.flags)
+ }
+
+ /// The size of the contents of the box.
+ fn content_size(&self) -> u64;
+
+ fn size(&self) -> u64 {
+ get_total_box_size::<Self>(&self)
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError>;
+
+ fn write(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ let mut header = [0u8; 20];
+
+ /* println!(
+ "{}: {}/{}",
+ std::any::type_name::<Self>(),
+ self.size(),
+ self.content_size()
+ ); */
+
+ let mut size = write_box_header::<Self>(&mut header, self.size());
+ if let Some(box_header) = self.get_full_box_header() {
+ size += write_full_box_header(&mut header[size..], box_header);
+ }
+
+ writer.put_slice(&header[..size]);
+
+ self.write_box_contents(writer)?;
+
+ Ok(())
+ }
+}
+
+pub struct MediaDataBox<'a> {
+ pub data: &'a [u8],
+}
+
+impl<'a> Mp4Box for MediaDataBox<'a> {
+ const NAME: FourCC = FourCC(*b"mdat");
+
+ fn get_full_box_header(&self) -> Option<FullBoxHeader> {
+ Some(FullBoxHeader::new(0, 0))
+ }
+
+ fn content_size(&self) -> u64 {
+ self.data.len() as _
+ }
+
+ fn write_box_contents(&self, writer: &mut BytesMut) -> Result<(), Mp4BoxError> {
+ writer.put_slice(&self.data);
+
+ Ok(())
+ }
+}