diff options
| author | Felix Kaaman <tmtu@tmtu.ee> | 2021-01-07 20:32:49 +0100 |
|---|---|---|
| committer | Felix Kaaman <tmtu@tmtu.ee> | 2021-01-07 20:32:49 +0100 |
| commit | 9e6e4962f2346a3fbd96ab3e6c331858ef6ec0d1 (patch) | |
| tree | 14b580295f71243def9c8df9c242c33f9f2c66a8 /src | |
| download | fmp4-9e6e4962f2346a3fbd96ab3e6c331858ef6ec0d1.tar.gz fmp4-9e6e4962f2346a3fbd96ab3e6c331858ef6ec0d1.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/boxes/avc1.rs | 62 | ||||
| -rw-r--r-- | src/boxes/avcc.rs | 76 | ||||
| -rw-r--r-- | src/boxes/co64.rs | 45 | ||||
| -rw-r--r-- | src/boxes/dinf.rs | 26 | ||||
| -rw-r--r-- | src/boxes/dref.rs | 47 | ||||
| -rw-r--r-- | src/boxes/ftyp.rs | 52 | ||||
| -rw-r--r-- | src/boxes/hdlr.rs | 48 | ||||
| -rw-r--r-- | src/boxes/mdhd.rs | 48 | ||||
| -rw-r--r-- | src/boxes/mdia.rs | 30 | ||||
| -rw-r--r-- | src/boxes/mehd.rs | 35 | ||||
| -rw-r--r-- | src/boxes/mfhd.rs | 32 | ||||
| -rw-r--r-- | src/boxes/minf.rs | 46 | ||||
| -rw-r--r-- | src/boxes/mod.rs | 38 | ||||
| -rw-r--r-- | src/boxes/moof.rs | 27 | ||||
| -rw-r--r-- | src/boxes/moov.rs | 46 | ||||
| -rw-r--r-- | src/boxes/mvex.rs | 27 | ||||
| -rw-r--r-- | src/boxes/mvhd.rs | 63 | ||||
| -rw-r--r-- | src/boxes/smhd.rs | 31 | ||||
| -rw-r--r-- | src/boxes/stbl.rs | 36 | ||||
| -rw-r--r-- | src/boxes/stsc.rs | 47 | ||||
| -rw-r--r-- | src/boxes/stsd.rs | 61 | ||||
| -rw-r--r-- | src/boxes/stsz.rs | 42 | ||||
| -rw-r--r-- | src/boxes/stts.rs | 46 | ||||
| -rw-r--r-- | src/boxes/tfdt.rs | 33 | ||||
| -rw-r--r-- | src/boxes/tfhd.rs | 131 | ||||
| -rw-r--r-- | src/boxes/tkhd.rs | 62 | ||||
| -rw-r--r-- | src/boxes/traf.rs | 45 | ||||
| -rw-r--r-- | src/boxes/trak.rs | 28 | ||||
| -rw-r--r-- | src/boxes/trex.rs | 47 | ||||
| -rw-r--r-- | src/boxes/trun.rs | 179 | ||||
| -rw-r--r-- | src/boxes/url.rs | 35 | ||||
| -rw-r--r-- | src/boxes/vmhd.rs | 31 | ||||
| -rw-r--r-- | src/lib.rs | 129 |
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(()) + } +} |
