From 943c012d2cbed20fe65447be55541c4298af28ac Mon Sep 17 00:00:00 2001 From: Jensenn Date: Mon, 11 Sep 2023 12:56:38 -0600 Subject: [PATCH 1/6] Add boxes needed for common encryption --- examples/mp4dump.rs | 21 ++++++ src/mp4box/enca.rs | 131 +++++++++++++++++++++++++++++++++ src/mp4box/encv.rs | 175 ++++++++++++++++++++++++++++++++++++++++++++ src/mp4box/frma.rs | 63 ++++++++++++++++ src/mp4box/mod.rs | 42 ++++++++++- src/mp4box/saio.rs | 119 ++++++++++++++++++++++++++++++ src/mp4box/saiz.rs | 117 +++++++++++++++++++++++++++++ src/mp4box/schi.rs | 92 +++++++++++++++++++++++ src/mp4box/schm.rs | 82 +++++++++++++++++++++ src/mp4box/senc.rs | 134 +++++++++++++++++++++++++++++++++ src/mp4box/sinf.rs | 121 ++++++++++++++++++++++++++++++ src/mp4box/stsd.rs | 24 ++++++ src/mp4box/tenc.rs | 129 ++++++++++++++++++++++++++++++++ src/mp4box/traf.rs | 47 +++++++++++- src/track.rs | 67 +++++++++++++++++ src/types.rs | 25 +++++++ 16 files changed, 1387 insertions(+), 2 deletions(-) create mode 100644 src/mp4box/enca.rs create mode 100644 src/mp4box/encv.rs create mode 100644 src/mp4box/frma.rs create mode 100644 src/mp4box/saio.rs create mode 100644 src/mp4box/saiz.rs create mode 100644 src/mp4box/schi.rs create mode 100644 src/mp4box/schm.rs create mode 100644 src/mp4box/senc.rs create mode 100644 src/mp4box/sinf.rs create mode 100644 src/mp4box/tenc.rs diff --git a/examples/mp4dump.rs b/examples/mp4dump.rs index 6a97d9a..c4c2fc6 100644 --- a/examples/mp4dump.rs +++ b/examples/mp4dump.rs @@ -99,6 +99,27 @@ fn get_boxes(file: File) -> Result> { if let Some(ref mp4a) = &stbl.stsd.mp4a { boxes.push(build_box(mp4a)); } + let mut sinf = None; + if let Some(ref encv) = &stbl.stsd.encv { + boxes.push(build_box(encv)); + sinf = Some(&encv.sinf) + } + if let Some(ref enca) = &stbl.stsd.enca { + boxes.push(build_box(enca)); + sinf = Some(&enca.sinf) + } + if let Some(sinf) = sinf { + boxes.push(build_box(sinf)); + if let Some(ref schm) = sinf.schm { + boxes.push(build_box(schm)); + } + if let Some(ref schi) = sinf.schi { + boxes.push(build_box(schi)); + if let Some(ref tenc) = schi.tenc { + boxes.push(build_box(tenc)); + } + } + } boxes.push(build_box(&stbl.stts)); if let Some(ref ctts) = &stbl.ctts { boxes.push(build_box(ctts)); diff --git a/src/mp4box/enca.rs b/src/mp4box/enca.rs new file mode 100644 index 0000000..1857fa7 --- /dev/null +++ b/src/mp4box/enca.rs @@ -0,0 +1,131 @@ +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +const RESERVED_DATA_SIZE: u64 = 28; + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct EncaBox { + #[serde(skip_serializing_if = "Option::is_none")] + pub mp4a: Option, + + pub sinf: SinfBox, +} + +impl EncaBox { + pub fn get_type(&self) -> BoxType { + BoxType::EncaBox + } + + pub fn get_size(&self) -> u64 { + let mut size = 0; + if let Some(ref mp4a) = self.mp4a { + // HEADER_SIZE intentionally omitted + size += mp4a.box_size(); + } else { + size += HEADER_SIZE + RESERVED_DATA_SIZE; + } + size += self.sinf.box_size(); + size + } +} + +impl Mp4Box for EncaBox { + fn box_type(&self) -> BoxType { + self.get_type() + } + + fn box_size(&self) -> u64 { + self.get_size() + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let child_summary = if let Some(ref mp4a) = self.mp4a { + mp4a.summary() + } else { + Err(Error::InvalidData("")) + }; + let mut s = format!("original_format={}", &self.sinf.frma.original_format); + if let Ok(summary) = child_summary { + s.push(' '); + s.push_str(&summary); + } + Ok(s) + } +} + +impl ReadBox<&mut R> for EncaBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let mut mp4a = None; + let mut sinf = None; + + // skip current container items + skip_bytes(reader, RESERVED_DATA_SIZE)?; + + let mut current = reader.stream_position()?; + let end = start + size; + while current < end { + // Get box header. + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + if s > size { + return Err(Error::InvalidData( + "enca box contains a box with a larger size than it", + )); + } + + match name { + BoxType::SinfBox => { + sinf = Some(SinfBox::read_box(reader, s)?); + break; + } + _ => { + skip_box(reader, s)?; + } + } + + current = reader.stream_position()?; + } + + let sinf = sinf.ok_or(Error::BoxNotFound(BoxType::SinfBox))?; + + reader.seek(SeekFrom::Start(start + HEADER_SIZE))?; + + let original_format: BoxType = sinf.frma.original_format.into(); + if original_format == BoxType::Mp4aBox { + mp4a = Some(Mp4aBox::read_box(reader, size)?); + } + + skip_bytes_to(reader, start + size)?; + + Ok(EncaBox { mp4a, sinf }) + } +} + +impl WriteBox<&mut W> for EncaBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + if let Some(ref mp4a) = self.mp4a { + // the enca box header is used, so the header from this box + // must be removed + let mut buf = Vec::with_capacity(mp4a.box_size() as usize); + mp4a.write_box(&mut buf)?; + writer.write_all(&buf[HEADER_SIZE as usize..])?; + } else { + writer.write_all(&[0; RESERVED_DATA_SIZE as usize])?; + } + + self.sinf.write_box(writer)?; + + Ok(size) + } +} diff --git a/src/mp4box/encv.rs b/src/mp4box/encv.rs new file mode 100644 index 0000000..2c08965 --- /dev/null +++ b/src/mp4box/encv.rs @@ -0,0 +1,175 @@ +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +const RESERVED_DATA_SIZE: u64 = 78; + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct EncvBox { + #[serde(skip_serializing_if = "Option::is_none")] + pub avc1: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub hev1: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub vp09: Option, + + pub sinf: SinfBox, +} + +impl EncvBox { + pub fn get_type(&self) -> BoxType { + BoxType::EncvBox + } + + pub fn get_size(&self) -> u64 { + let mut size = 0; + if let Some(ref avc1) = self.avc1 { + // HEADER_SIZE intentionally omitted + size += avc1.box_size(); + } else if let Some(ref hev1) = self.hev1 { + // HEADER_SIZE intentionally omitted + size += hev1.box_size(); + } else if let Some(ref vp09) = self.vp09 { + // HEADER_SIZE intentionally omitted + size += vp09.box_size(); + } else { + size += HEADER_SIZE + RESERVED_DATA_SIZE; + } + size += self.sinf.box_size(); + size + } +} + +impl Mp4Box for EncvBox { + fn box_type(&self) -> BoxType { + self.get_type() + } + + fn box_size(&self) -> u64 { + self.get_size() + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let child_summary = if let Some(ref avc1) = self.avc1 { + avc1.summary() + } else if let Some(ref hev1) = self.hev1 { + hev1.summary() + } else if let Some(ref vp09) = self.vp09 { + vp09.summary() + } else { + Err(Error::InvalidData("")) + }; + let mut s = format!("original_format={}", &self.sinf.frma.original_format); + if let Ok(summary) = child_summary { + s.push(' '); + s.push_str(&summary); + } + Ok(s) + } +} + +impl ReadBox<&mut R> for EncvBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let mut avc1 = None; + let mut hev1 = None; + let mut vp09 = None; + let mut sinf = None; + + // skip current container items + skip_bytes(reader, RESERVED_DATA_SIZE)?; + + let mut current = reader.stream_position()?; + let end = start + size; + while current < end { + // Get box header. + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + if s > size { + return Err(Error::InvalidData( + "encv box contains a box with a larger size than it", + )); + } + + match name { + BoxType::SinfBox => { + sinf = Some(SinfBox::read_box(reader, s)?); + break; + } + _ => { + skip_box(reader, s)?; + } + } + + current = reader.stream_position()?; + } + + let sinf = sinf.ok_or(Error::BoxNotFound(BoxType::SinfBox))?; + + reader.seek(SeekFrom::Start(start + HEADER_SIZE))?; + + let original_format: BoxType = sinf.frma.original_format.into(); + match original_format { + BoxType::Avc1Box => { + avc1 = Some(Avc1Box::read_box(reader, size)?); + } + BoxType::Hev1Box => { + hev1 = Some(Hev1Box::read_box(reader, size)?); + } + BoxType::Vp09Box => { + vp09 = Some(Vp09Box::read_box(reader, size)?); + } + _ => (), + } + + skip_bytes_to(reader, start + size)?; + + Ok(EncvBox { + avc1, + hev1, + vp09, + sinf, + }) + } +} + +impl WriteBox<&mut W> for EncvBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + if let Some(ref avc1) = self.avc1 { + // the encv box header is used, so the header from this box + // must be removed + let mut buf = Vec::with_capacity(avc1.box_size() as usize); + avc1.write_box(&mut buf)?; + writer.write_all(&buf[HEADER_SIZE as usize..])?; + } else if let Some(ref hev1) = self.hev1 { + // the encv box header is used, so the header from this box + // must be removed + let mut buf = Vec::with_capacity(hev1.box_size() as usize); + hev1.write_box(&mut buf)?; + writer.write_all(&buf[HEADER_SIZE as usize..])?; + } else if let Some(ref vp09) = self.vp09 { + // the encv box header is used, so the header from this box + // must be removed + let mut buf = Vec::with_capacity(vp09.box_size() as usize); + vp09.write_box(&mut buf)?; + writer.write_all(&buf[HEADER_SIZE as usize..])?; + } else { + writer.write_all(&[0; RESERVED_DATA_SIZE as usize])?; + } + + self.sinf.write_box(writer)?; + + Ok(size) + } +} diff --git a/src/mp4box/frma.rs b/src/mp4box/frma.rs new file mode 100644 index 0000000..f38aca3 --- /dev/null +++ b/src/mp4box/frma.rs @@ -0,0 +1,63 @@ +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct FrmaBox { + pub original_format: FourCC, +} + +impl FrmaBox { + pub fn get_type(&self) -> BoxType { + BoxType::FrmaBox + } + + pub fn get_size(&self) -> u64 { + HEADER_SIZE + 4 + } +} + +impl Mp4Box for FrmaBox { + fn box_type(&self) -> BoxType { + self.get_type() + } + + fn box_size(&self) -> u64 { + self.get_size() + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = format!("original_format={}", self.original_format,); + Ok(s) + } +} + +impl ReadBox<&mut R> for FrmaBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let original_format = reader.read_u32::()?; + + skip_bytes_to(reader, start + size)?; + + Ok(FrmaBox { + original_format: original_format.into(), + }) + } +} + +impl WriteBox<&mut W> for FrmaBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + writer.write_u32::(self.original_format.into())?; + + Ok(size) + } +} diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index 4bbdd41..8ac7ede 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -29,6 +29,13 @@ //! hev1 //! mp4a //! tx3g +//! enca +//! encv +//! sinf +//! frma +//! schm +//! schi +//! tenc //! stts //! stsc //! stsz @@ -52,6 +59,9 @@ //! tfhd //! tfdt //! trun +//! saiz +//! saio +//! senc //! mdat //! free //! @@ -69,7 +79,10 @@ pub(crate) mod data; pub(crate) mod dinf; pub(crate) mod edts; pub(crate) mod elst; +pub(crate) mod enca; +pub(crate) mod encv; pub(crate) mod emsg; +pub(crate) mod frma; pub(crate) mod ftyp; pub(crate) mod hdlr; pub(crate) mod hev1; @@ -85,6 +98,12 @@ pub(crate) mod moov; pub(crate) mod mp4a; pub(crate) mod mvex; pub(crate) mod mvhd; +pub(crate) mod saio; +pub(crate) mod saiz; +pub(crate) mod schi; +pub(crate) mod schm; +pub(crate) mod senc; +pub(crate) mod sinf; pub(crate) mod smhd; pub(crate) mod stbl; pub(crate) mod stco; @@ -93,6 +112,7 @@ pub(crate) mod stsd; pub(crate) mod stss; pub(crate) mod stsz; pub(crate) mod stts; +pub(crate) mod tenc; pub(crate) mod tfdt; pub(crate) mod tfhd; pub(crate) mod tkhd; @@ -113,7 +133,10 @@ pub use data::DataBox; pub use dinf::DinfBox; pub use edts::EdtsBox; pub use elst::ElstBox; +pub use enca::EncaBox; +pub use encv::EncvBox; pub use emsg::EmsgBox; +pub use frma::FrmaBox; pub use ftyp::FtypBox; pub use hdlr::HdlrBox; pub use hev1::Hev1Box; @@ -129,6 +152,12 @@ pub use moov::MoovBox; pub use mp4a::Mp4aBox; pub use mvex::MvexBox; pub use mvhd::MvhdBox; +pub use saio::SaioBox; +pub use saiz::SaizBox; +pub use schi::SchiBox; +pub use schm::SchmBox; +pub use senc::SencBox; +pub use sinf::SinfBox; pub use smhd::SmhdBox; pub use stbl::StblBox; pub use stco::StcoBox; @@ -137,6 +166,7 @@ pub use stsd::StsdBox; pub use stss::StssBox; pub use stsz::StszBox; pub use stts::SttsBox; +pub use tenc::TencBox; pub use tfdt::TfdtBox; pub use tfhd::TfhdBox; pub use tkhd::TkhdBox; @@ -238,7 +268,17 @@ boxtype! { CovrBox => 0x636f7672, DescBox => 0x64657363, WideBox => 0x77696465, - WaveBox => 0x77617665 + WaveBox => 0x77617665, + EncaBox => 0x656e6361, + EncvBox => 0x656e6376, + SinfBox => 0x73696e66, + FrmaBox => 0x66726d61, + SchmBox => 0x7363686d, + SchiBox => 0x73636869, + TencBox => 0x74656e63, + SaizBox => 0x7361697a, + SaioBox => 0x7361696f, + SencBox => 0x73656e63 } pub trait Mp4Box: Sized { diff --git a/src/mp4box/saio.rs b/src/mp4box/saio.rs new file mode 100644 index 0000000..7cacb30 --- /dev/null +++ b/src/mp4box/saio.rs @@ -0,0 +1,119 @@ +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct SaioBox { + pub version: u8, + pub aux_info: Option, + pub entry_count: u32, + pub offsets: Vec, +} + +impl SaioBox { + pub const FLAG_AUX_INFO_TYPE: u32 = 0x01; + + pub fn get_type(&self) -> BoxType { + BoxType::SaioBox + } + + pub fn get_size(&self) -> u64 { + let mut size = HEADER_SIZE + HEADER_EXT_SIZE + 4; + if self.aux_info.is_some() { + size += 8; + } + if self.version == 0 { + size += 4 * self.entry_count as u64; + } else { + size += 8 * self.entry_count as u64; + } + size + } +} + +impl Mp4Box for SaioBox { + fn box_type(&self) -> BoxType { + self.get_type() + } + + fn box_size(&self) -> u64 { + self.get_size() + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = format!("entry_count={}", self.entry_count); + Ok(s) + } +} + +impl ReadBox<&mut R> for SaioBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let mut aux_info = None; + if SaioBox::FLAG_AUX_INFO_TYPE & flags != 0 { + let aux_info_type = reader.read_u32::()?; + let aux_info_type_parameter = reader.read_u32::()?; + aux_info = Some(AuxiliaryInfoType { + aux_info_type, + aux_info_type_parameter, + }); + } + + let sample_count = reader.read_u32::()?; + + let mut offsets = Vec::with_capacity(sample_count as usize); + for _ in 0..sample_count as usize { + let offset = if version == 0 { + reader.read_u32::()? as u64 + } else { + reader.read_u64::()? + }; + offsets.push(offset); + } + + skip_bytes_to(reader, start + size)?; + + Ok(SaioBox { + version, + aux_info, + entry_count: sample_count, + offsets, + }) + } +} + +impl WriteBox<&mut W> for SaioBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + if let Some(ref aux_info) = self.aux_info { + write_box_header_ext(writer, self.version, SaioBox::FLAG_AUX_INFO_TYPE)?; + writer.write_u32::(aux_info.aux_info_type)?; + writer.write_u32::(aux_info.aux_info_type_parameter)?; + } else { + write_box_header_ext(writer, self.version, 0)?; + } + + writer.write_u32::(self.entry_count)?; + + for i in 0..self.entry_count as usize { + let offset = self.offsets.get(i).copied().unwrap_or(0); + if self.version == 0 { + writer.write_u32::(offset as u32)?; + } else { + writer.write_u64::(offset)?; + }; + } + + Ok(size) + } +} diff --git a/src/mp4box/saiz.rs b/src/mp4box/saiz.rs new file mode 100644 index 0000000..b3d3528 --- /dev/null +++ b/src/mp4box/saiz.rs @@ -0,0 +1,117 @@ +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct SaizBox { + pub aux_info: Option, + pub default_sample_info_size: u8, + pub sample_count: u32, + pub sample_info_sizes: Vec, +} + +impl SaizBox { + pub const FLAG_AUX_INFO_TYPE: u32 = 0x01; + + pub fn get_type(&self) -> BoxType { + BoxType::SaizBox + } + + pub fn get_size(&self) -> u64 { + let mut size = HEADER_SIZE + HEADER_EXT_SIZE + 5; + if self.aux_info.is_some() { + size += 8; + } + if self.default_sample_info_size == 0 { + size += self.sample_count as u64; + } + size + } +} + +impl Mp4Box for SaizBox { + fn box_type(&self) -> BoxType { + self.get_type() + } + + fn box_size(&self) -> u64 { + self.get_size() + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = format!( + "sample_info_size={} sample_count={}", + self.default_sample_info_size, self.sample_count + ); + Ok(s) + } +} + +impl ReadBox<&mut R> for SaizBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let (_version, flags) = read_box_header_ext(reader)?; + + let mut aux_info = None; + if SaizBox::FLAG_AUX_INFO_TYPE & flags != 0 { + let aux_info_type = reader.read_u32::()?; + let aux_info_type_parameter = reader.read_u32::()?; + aux_info = Some(AuxiliaryInfoType { + aux_info_type, + aux_info_type_parameter, + }); + } + + let default_sample_info_size = reader.read_u8()?; + let sample_count = reader.read_u32::()?; + + let mut sample_info_sizes = Vec::new(); + if default_sample_info_size == 0 { + sample_info_sizes = Vec::with_capacity(sample_count as usize); + for _ in 0..sample_count as usize { + sample_info_sizes.push(reader.read_u8()?); + } + }; + + skip_bytes_to(reader, start + size)?; + + Ok(SaizBox { + aux_info, + default_sample_info_size, + sample_count, + sample_info_sizes, + }) + } +} + +impl WriteBox<&mut W> for SaizBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + if let Some(ref aux_info) = self.aux_info { + write_box_header_ext(writer, 0, SaizBox::FLAG_AUX_INFO_TYPE)?; + writer.write_u32::(aux_info.aux_info_type)?; + writer.write_u32::(aux_info.aux_info_type_parameter)?; + } else { + write_box_header_ext(writer, 0, 0)?; + } + + writer.write_u8(self.default_sample_info_size)?; + writer.write_u32::(self.sample_count)?; + + if self.default_sample_info_size == 0 { + for i in 0..self.sample_count as usize { + let sample_info_size = self.sample_info_sizes.get(i).copied().unwrap_or(0); + writer.write_u8(sample_info_size)?; + } + } + Ok(size) + } +} diff --git a/src/mp4box/schi.rs b/src/mp4box/schi.rs new file mode 100644 index 0000000..c2bf37e --- /dev/null +++ b/src/mp4box/schi.rs @@ -0,0 +1,92 @@ +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct SchiBox { + pub tenc: Option, +} + +impl SchiBox { + pub fn get_type(&self) -> BoxType { + BoxType::SchiBox + } + + pub fn get_size(&self) -> u64 { + let mut size = HEADER_SIZE; + if let Some(ref tenc) = self.tenc { + size += tenc.get_size(); + } + size + } +} + +impl Mp4Box for SchiBox { + fn box_type(&self) -> BoxType { + self.get_type() + } + + fn box_size(&self) -> u64 { + self.get_size() + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = String::new(); + Ok(s) + } +} + +impl ReadBox<&mut R> for SchiBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let mut tenc = None; + + let mut current = reader.stream_position()?; + let end = start + size; + while current < end { + // Get box header. + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + if s > size { + return Err(Error::InvalidData( + "schi box contains a box with a larger size than it", + )); + } + + match name { + BoxType::TencBox => { + tenc = Some(TencBox::read_box(reader, s)?); + } + _ => { + // XXX warn!() + skip_box(reader, s)?; + } + } + + current = reader.stream_position()?; + } + + skip_bytes_to(reader, start + size)?; + + Ok(SchiBox { tenc }) + } +} + +impl WriteBox<&mut W> for SchiBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + if let Some(ref tenc) = self.tenc { + tenc.write_box(writer)?; + } + + Ok(size) + } +} diff --git a/src/mp4box/schm.rs b/src/mp4box/schm.rs new file mode 100644 index 0000000..10fa4d8 --- /dev/null +++ b/src/mp4box/schm.rs @@ -0,0 +1,82 @@ +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct SchmBox { + pub version: u8, + pub scheme_type: FourCC, + pub scheme_version: u32, +} + +impl SchmBox { + pub const FLAG_SCHEME_URI: u32 = 0x01; + + pub fn get_type(&self) -> BoxType { + BoxType::SchmBox + } + + pub fn get_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 8 + } +} + +impl Mp4Box for SchmBox { + fn box_type(&self) -> BoxType { + self.get_type() + } + + fn box_size(&self) -> u64 { + self.get_size() + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = format!( + "scheme_type={} scheme_version={}", + self.scheme_type, self.scheme_version + ); + Ok(s) + } +} + +impl ReadBox<&mut R> for SchmBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let scheme_type = reader.read_u32::()?; + let scheme_version = reader.read_u32::()?; + + if SchmBox::FLAG_SCHEME_URI & flags != 0 { + // todo + } + + skip_bytes_to(reader, start + size)?; + + Ok(SchmBox { + version, + scheme_type: scheme_type.into(), + scheme_version, + }) + } +} + +impl WriteBox<&mut W> for SchmBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + write_box_header_ext(writer, self.version, 0)?; + + writer.write_u32::(self.scheme_type.into())?; + writer.write_u32::(self.scheme_version)?; + + Ok(size) + } +} diff --git a/src/mp4box/senc.rs b/src/mp4box/senc.rs new file mode 100644 index 0000000..ff4d164 --- /dev/null +++ b/src/mp4box/senc.rs @@ -0,0 +1,134 @@ +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct SencBox { + pub version: u8, + pub flags: u32, + pub sample_count: u32, + pub sample_data: Vec, +} + +impl SencBox { + pub const FLAG_USE_SUBSAMPLE_ENCRYPTION: u32 = 0x02; + + pub fn get_type(&self) -> BoxType { + BoxType::SencBox + } + + pub fn get_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 4 + (self.sample_data.len() as u64) + } + + pub fn get_sample_info(&self, iv_size: u8) -> Result> { + if iv_size != 16 && iv_size != 8 && iv_size != 0 { + return Err(Error::InvalidData("invalid iv_size")); + } + let mut reader = &self.sample_data[..]; + let mut infos = Vec::with_capacity(self.sample_count as usize); + for _ in 0..self.sample_count { + let mut iv = vec![0; iv_size as usize]; + if iv_size != 0 { + reader.read_exact(&mut iv)?; + } + let mut subsamples = Vec::new(); + if SencBox::FLAG_USE_SUBSAMPLE_ENCRYPTION & self.flags != 0 { + let subsample_count = reader.read_u16::()?; + subsamples = Vec::with_capacity(subsample_count as usize); + for _ in 0..subsample_count { + let bytes_of_clear_data = reader.read_u16::()?; + let bytes_of_encrypted_data = reader.read_u32::()?; + subsamples.push(SubSampleInfo { + bytes_of_clear_data, + bytes_of_encrypted_data, + }); + } + } + infos.push(SampleInfo { iv, subsamples }); + } + Ok(infos) + } + + pub fn set_sample_info(&mut self, infos: &[SampleInfo], iv_size: u8) -> Result<()> { + if iv_size != 16 && iv_size != 8 && iv_size != 0 { + return Err(Error::InvalidData("invalid iv_size")); + } + let mut buf = Vec::new(); + for info in infos { + if iv_size != 0 { + buf.write_all(&info.iv[..iv_size as usize])?; + } + if SencBox::FLAG_USE_SUBSAMPLE_ENCRYPTION & self.flags != 0 { + buf.write_u16::(info.subsamples.len() as u16)?; + for subsample in &info.subsamples { + buf.write_u16::(subsample.bytes_of_clear_data)?; + buf.write_u32::(subsample.bytes_of_encrypted_data)?; + } + } + } + self.sample_data = buf; + Ok(()) + } +} + +impl Mp4Box for SencBox { + fn box_type(&self) -> BoxType { + self.get_type() + } + + fn box_size(&self) -> u64 { + self.get_size() + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = format!("sample_count={}", self.sample_count); + Ok(s) + } +} + +impl ReadBox<&mut R> for SencBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let sample_count = reader.read_u32::()?; + + // the senc box cannot be properly parsed without IV_size + // which is only available from other boxes. Store the raw + // data for parsing with member functions later + let data_size = start + size - reader.stream_position()?; + let mut sample_data = vec![0; data_size as usize]; + reader.read_exact(&mut sample_data)?; + + skip_bytes_to(reader, start + size)?; + + Ok(SencBox { + version, + flags, + sample_count, + sample_data, + }) + } +} + +impl WriteBox<&mut W> for SencBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + write_box_header_ext(writer, self.version, self.flags)?; + + writer.write_u32::(self.sample_count)?; + + writer.write_all(&self.sample_data)?; + + Ok(size) + } +} diff --git a/src/mp4box/sinf.rs b/src/mp4box/sinf.rs new file mode 100644 index 0000000..54f8d0c --- /dev/null +++ b/src/mp4box/sinf.rs @@ -0,0 +1,121 @@ +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct SinfBox { + pub frma: FrmaBox, + pub schm: Option, + pub schi: Option, +} + +impl SinfBox { + pub fn get_type(&self) -> BoxType { + BoxType::SinfBox + } + + pub fn get_size(&self) -> u64 { + let mut size = HEADER_SIZE + self.frma.get_size(); + if let Some(ref schm) = self.schm { + size += schm.get_size(); + } + if let Some(ref schi) = self.schi { + size += schi.get_size(); + } + size + } +} + +impl Mp4Box for SinfBox { + fn box_type(&self) -> BoxType { + self.get_type() + } + + fn box_size(&self) -> u64 { + self.get_size() + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = String::new(); + Ok(s) + } +} + +impl ReadBox<&mut R> for SinfBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let mut frma = None; + let mut schm = None; + let mut schi = None; + + let mut current = reader.stream_position()?; + let end = start + size; + while current < end { + // Get box header. + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + if s > size { + return Err(Error::InvalidData( + "sinf box contains a box with a larger size than it", + )); + } + + match name { + BoxType::FrmaBox => { + frma = Some(FrmaBox::read_box(reader, s)?); + } + BoxType::SchmBox => { + schm = Some(SchmBox::read_box(reader, s)?); + } + BoxType::SchiBox => { + schi = Some(SchiBox::read_box(reader, s)?); + } + _ => { + // XXX warn!() + skip_box(reader, s)?; + } + } + + current = reader.stream_position()?; + } + + if frma.is_none() { + return Err(Error::BoxNotFound(BoxType::FrmaBox)); + } + + skip_bytes_to(reader, start + size)?; + + Ok(SinfBox { + frma: frma.unwrap(), + schm, + schi, + }) + } +} + +impl WriteBox<&mut W> for SinfBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + println!("sinf size: {}", size); + + BoxHeader::new(self.box_type(), size).write(writer)?; + + self.frma.write_box(writer)?; + + if let Some(schm) = &self.schm { + schm.write_box(writer)?; + } + + if let Some(schi) = &self.schi { + schi.write_box(writer)?; + } + + Ok(size) + } +} diff --git a/src/mp4box/stsd.rs b/src/mp4box/stsd.rs index af947c6..dfbf99f 100644 --- a/src/mp4box/stsd.rs +++ b/src/mp4box/stsd.rs @@ -25,6 +25,12 @@ pub struct StsdBox { #[serde(skip_serializing_if = "Option::is_none")] pub tx3g: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub enca: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub encv: Option, } impl StsdBox { @@ -44,6 +50,10 @@ impl StsdBox { size += mp4a.box_size(); } else if let Some(ref tx3g) = self.tx3g { size += tx3g.box_size(); + } else if let Some(ref enca) = self.enca { + size += enca.box_size(); + } else if let Some(ref encv) = self.encv { + size += encv.box_size(); } size } @@ -81,6 +91,8 @@ impl ReadBox<&mut R> for StsdBox { let mut vp09 = None; let mut mp4a = None; let mut tx3g = None; + let mut enca = None; + let mut encv = None; // Get box header. let header = BoxHeader::read(reader)?; @@ -107,6 +119,12 @@ impl ReadBox<&mut R> for StsdBox { BoxType::Tx3gBox => { tx3g = Some(Tx3gBox::read_box(reader, s)?); } + BoxType::EncaBox => { + enca = Some(EncaBox::read_box(reader, s)?); + } + BoxType::EncvBox => { + encv = Some(EncvBox::read_box(reader, s)?); + } _ => {} } @@ -120,6 +138,8 @@ impl ReadBox<&mut R> for StsdBox { vp09, mp4a, tx3g, + enca, + encv, }) } } @@ -143,6 +163,10 @@ impl WriteBox<&mut W> for StsdBox { mp4a.write_box(writer)?; } else if let Some(ref tx3g) = self.tx3g { tx3g.write_box(writer)?; + } else if let Some(ref enca) = self.enca { + enca.write_box(writer)?; + } else if let Some(ref encv) = self.encv { + encv.write_box(writer)?; } Ok(size) diff --git a/src/mp4box/tenc.rs b/src/mp4box/tenc.rs new file mode 100644 index 0000000..ee865b6 --- /dev/null +++ b/src/mp4box/tenc.rs @@ -0,0 +1,129 @@ +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct TencBox { + pub version: u8, + pub default_crypt_byte_block: u8, + pub default_skip_byte_block: u8, + pub default_is_protected: bool, + pub default_per_sample_iv_size: u8, + pub default_kid: [u8; 16], + pub default_constant_iv: Vec, +} + +impl TencBox { + pub fn get_type(&self) -> BoxType { + BoxType::TencBox + } + + pub fn get_size(&self) -> u64 { + let mut size = HEADER_SIZE + HEADER_EXT_SIZE + 20; + if self.default_is_protected && self.default_per_sample_iv_size == 0 { + size += 1 + (self.default_constant_iv.len() & 0xff) as u64; + } + size + } +} + +impl Mp4Box for TencBox { + fn box_type(&self) -> BoxType { + self.get_type() + } + + fn box_size(&self) -> u64 { + self.get_size() + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let mut s = format!( + "crypt_byte_block={} skip_byte_block={} protected={} iv_size={} kid={:x?}", + self.default_crypt_byte_block, + self.default_skip_byte_block, + self.default_is_protected, + self.default_per_sample_iv_size, + self.default_kid, + ); + if !self.default_constant_iv.is_empty() { + s.push_str(&format!(" constant_iv={:x?}", self.default_constant_iv)); + } + Ok(s) + } +} + +impl ReadBox<&mut R> for TencBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let mut default_crypt_byte_block = 0; + let mut default_skip_byte_block = 0; + + let (version, _flags) = read_box_header_ext(reader)?; + + let _reserved = reader.read_u8()?; + let val = reader.read_u8()?; + if version > 0 { + default_crypt_byte_block = val >> 4; + default_skip_byte_block = val & 0xf; + } + let default_is_protected = reader.read_u8()? != 0; + let default_per_sample_iv_size = reader.read_u8()?; + + let mut default_kid = [0; 16]; + reader.read_exact(&mut default_kid)?; + + let mut default_constant_iv = Vec::new(); + if default_is_protected && default_per_sample_iv_size == 0 { + let default_constant_iv_size = reader.read_u8()?; + if default_constant_iv_size > 0 { + default_constant_iv = vec![0; default_constant_iv_size as usize]; + reader.read_exact(&mut default_constant_iv[..])?; + } + } + + skip_bytes_to(reader, start + size)?; + + Ok(TencBox { + version, + default_crypt_byte_block, + default_skip_byte_block, + default_is_protected, + default_per_sample_iv_size, + default_kid, + default_constant_iv, + }) + } +} + +impl WriteBox<&mut W> for TencBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + write_box_header_ext(writer, self.version, 0)?; + + writer.write_u8(0)?; // reserved + let val = if self.version > 0 { + (self.default_crypt_byte_block << 4) | (self.default_skip_byte_block & 0xf) + } else { + 0 + }; + writer.write_u8(val)?; + writer.write_u8(self.default_is_protected as u8)?; + writer.write_u8(self.default_per_sample_iv_size)?; + writer.write_all(&self.default_kid)?; + if self.default_is_protected && self.default_per_sample_iv_size == 0 { + let default_constant_iv_size = (self.default_constant_iv.len() & 0xff) as u8; + writer.write_u8(default_constant_iv_size)?; + writer.write_all(&self.default_constant_iv[0..default_constant_iv_size as usize])?; + } + + Ok(size) + } +} diff --git a/src/mp4box/traf.rs b/src/mp4box/traf.rs index d53d713..77a2c1b 100644 --- a/src/mp4box/traf.rs +++ b/src/mp4box/traf.rs @@ -2,13 +2,25 @@ use serde::Serialize; use std::io::{Read, Seek, Write}; use crate::mp4box::*; -use crate::mp4box::{tfdt::TfdtBox, tfhd::TfhdBox, trun::TrunBox}; #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] pub struct TrafBox { pub tfhd: TfhdBox, + + #[serde(skip_serializing_if = "Option::is_none")] pub tfdt: Option, + + #[serde(skip_serializing_if = "Option::is_none")] pub trun: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub saiz: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub saio: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub senc: Option, } impl TrafBox { @@ -25,6 +37,15 @@ impl TrafBox { if let Some(ref trun) = self.trun { size += trun.box_size(); } + if let Some(ref saiz) = self.saiz { + size += saiz.box_size(); + } + if let Some(ref saio) = self.saio { + size += saio.box_size(); + } + if let Some(ref senc) = self.senc { + size += senc.box_size(); + } size } } @@ -55,6 +76,9 @@ impl ReadBox<&mut R> for TrafBox { let mut tfhd = None; let mut tfdt = None; let mut trun = None; + let mut saiz = None; + let mut saio = None; + let mut senc = None; let mut current = reader.stream_position()?; let end = start + size; @@ -78,6 +102,15 @@ impl ReadBox<&mut R> for TrafBox { BoxType::TrunBox => { trun = Some(TrunBox::read_box(reader, s)?); } + BoxType::SaizBox => { + saiz = Some(SaizBox::read_box(reader, s)?); + } + BoxType::SaioBox => { + saio = Some(SaioBox::read_box(reader, s)?); + } + BoxType::SencBox => { + senc = Some(SencBox::read_box(reader, s)?); + } _ => { // XXX warn!() skip_box(reader, s)?; @@ -97,6 +130,9 @@ impl ReadBox<&mut R> for TrafBox { tfhd: tfhd.unwrap(), tfdt, trun, + saiz, + saio, + senc, }) } } @@ -113,6 +149,15 @@ impl WriteBox<&mut W> for TrafBox { if let Some(ref trun) = self.trun { trun.write_box(writer)?; } + if let Some(ref saiz) = self.saiz { + saiz.write_box(writer)?; + } + if let Some(ref saio) = self.saio { + saio.write_box(writer)?; + } + if let Some(ref senc) = self.senc { + senc.write_box(writer)?; + } Ok(size) } diff --git a/src/track.rs b/src/track.rs index 7eada83..5c52514 100644 --- a/src/track.rs +++ b/src/track.rs @@ -129,6 +129,22 @@ impl Mp4Track { Ok(MediaType::AAC) } else if self.trak.mdia.minf.stbl.stsd.tx3g.is_some() { Ok(MediaType::TTXT) + } else if let Some(ref enca) = self.trak.mdia.minf.stbl.stsd.enca { + if enca.mp4a.is_some() { + Ok(MediaType::AAC) + } else { + Err(Error::InvalidData("unsupported media type")) + } + } else if let Some(ref encv) = self.trak.mdia.minf.stbl.stsd.encv { + if encv.avc1.is_some() { + Ok(MediaType::H264) + } else if encv.hev1.is_some() { + Ok(MediaType::H265) + } else if encv.vp09.is_some() { + Ok(MediaType::VP9) + } else { + Err(Error::InvalidData("unsupported media type")) + } } else { Err(Error::InvalidData("unsupported media type")) } @@ -145,6 +161,22 @@ impl Mp4Track { Ok(FourCC::from(BoxType::Mp4aBox)) } else if self.trak.mdia.minf.stbl.stsd.tx3g.is_some() { Ok(FourCC::from(BoxType::Tx3gBox)) + } else if let Some(ref enca) = self.trak.mdia.minf.stbl.stsd.enca { + if enca.mp4a.is_some() { + Ok(FourCC::from(BoxType::Mp4aBox)) + } else { + Err(Error::InvalidData("unsupported sample entry box")) + } + } else if let Some(ref encv) = self.trak.mdia.minf.stbl.stsd.encv { + if encv.avc1.is_some() { + Ok(FourCC::from(BoxType::Avc1Box)) + } else if encv.hev1.is_some() { + Ok(FourCC::from(BoxType::Hev1Box)) + } else if encv.vp09.is_some() { + Ok(FourCC::from(BoxType::Vp09Box)) + } else { + Err(Error::InvalidData("unsupported sample entry box")) + } } else { Err(Error::InvalidData("unsupported sample entry box")) } @@ -301,6 +333,41 @@ impl Mp4Track { } } + pub fn protection_scheme_info(&self) -> Option<&SinfBox> { + if let Some(ref enca) = self.trak.mdia.minf.stbl.stsd.enca { + Some(&enca.sinf) + } else if let Some(ref encv) = self.trak.mdia.minf.stbl.stsd.encv { + Some(&encv.sinf) + } else { + None + } + } + + pub fn protection_sample_info(&self) -> Result> { + let mut sample_infos = Vec::new(); + let sinf = self + .protection_scheme_info() + .ok_or(Error::InvalidData("missing protection info"))?; + let iv_size = sinf + .schi + .as_ref() + .and_then(|schi| { + schi.tenc + .as_ref() + .map(|tenc| tenc.default_per_sample_iv_size) + }) + .unwrap_or(16); + for traf in &self.trafs { + if let Some(ref senc) = traf.senc { + let si = senc.get_sample_info(iv_size)?; + sample_infos.extend(si); + } else { + // TODO - read protection info based on the saiz and saio boxes + } + } + Ok(sample_infos) + } + fn stsc_index(&self, sample_id: u32) -> Result { if self.trak.mdia.minf.stbl.stsc.entries.is_empty() { return Err(Error::InvalidData("no stsc entries")); diff --git a/src/types.rs b/src/types.rs index 540f7fb..19e75a3 100644 --- a/src/types.rs +++ b/src/types.rs @@ -138,6 +138,13 @@ impl From for FourCC { } } +impl From for BoxType { + fn from(fourcc: FourCC) -> BoxType { + let int_val: u32 = fourcc.into(); + BoxType::from(int_val) + } +} + impl fmt::Debug for FourCC { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let code: u32 = self.into(); @@ -739,3 +746,21 @@ impl<'a, T: Metadata<'a>> Metadata<'a> for Option { self.as_ref().and_then(|t| t.summary()) } } + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct AuxiliaryInfoType { + pub aux_info_type: u32, + pub aux_info_type_parameter: u32, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct SubSampleInfo { + pub bytes_of_clear_data: u16, + pub bytes_of_encrypted_data: u32, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct SampleInfo { + pub iv: Vec, + pub subsamples: Vec, +} From fb694a602f971aaf801e81c16af13125549a329f Mon Sep 17 00:00:00 2001 From: Jensenn Date: Wed, 20 Sep 2023 12:20:22 -0600 Subject: [PATCH 2/6] Fix formatting error and erroneous dbg message --- src/mp4box/mod.rs | 4 ++-- src/mp4box/sinf.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index 8ac7ede..646993b 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -79,9 +79,9 @@ pub(crate) mod data; pub(crate) mod dinf; pub(crate) mod edts; pub(crate) mod elst; +pub(crate) mod emsg; pub(crate) mod enca; pub(crate) mod encv; -pub(crate) mod emsg; pub(crate) mod frma; pub(crate) mod ftyp; pub(crate) mod hdlr; @@ -133,9 +133,9 @@ pub use data::DataBox; pub use dinf::DinfBox; pub use edts::EdtsBox; pub use elst::ElstBox; +pub use emsg::EmsgBox; pub use enca::EncaBox; pub use encv::EncvBox; -pub use emsg::EmsgBox; pub use frma::FrmaBox; pub use ftyp::FtypBox; pub use hdlr::HdlrBox; diff --git a/src/mp4box/sinf.rs b/src/mp4box/sinf.rs index 54f8d0c..c1615ea 100644 --- a/src/mp4box/sinf.rs +++ b/src/mp4box/sinf.rs @@ -102,7 +102,6 @@ impl ReadBox<&mut R> for SinfBox { impl WriteBox<&mut W> for SinfBox { fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); - println!("sinf size: {}", size); BoxHeader::new(self.box_type(), size).write(writer)?; From 85461b82720f678a8a343e4e4d9c1c6e2924e883 Mon Sep 17 00:00:00 2001 From: Jensenn Date: Mon, 25 Sep 2023 15:24:14 -0600 Subject: [PATCH 3/6] Prevent possible subtraction underflow --- src/mp4box/senc.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mp4box/senc.rs b/src/mp4box/senc.rs index ff4d164..5905b5b 100644 --- a/src/mp4box/senc.rs +++ b/src/mp4box/senc.rs @@ -103,7 +103,9 @@ impl ReadBox<&mut R> for SencBox { // the senc box cannot be properly parsed without IV_size // which is only available from other boxes. Store the raw // data for parsing with member functions later - let data_size = start + size - reader.stream_position()?; + let data_size = (start + size) + .checked_sub(reader.stream_position()?) + .ok_or(Error::InvalidData("senc size too small"))?; let mut sample_data = vec![0; data_size as usize]; reader.read_exact(&mut sample_data)?; From 7b82b6399f33cb84311cae4a6d07ade49ab0cca7 Mon Sep 17 00:00:00 2001 From: Jensenn Date: Thu, 7 Dec 2023 08:31:17 -0700 Subject: [PATCH 4/6] Add pssh box --- src/mp4box/mod.rs | 6 ++- src/mp4box/moov.rs | 16 +++++++ src/mp4box/pssh.rs | 109 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/mp4box/pssh.rs diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index 646993b..6a500c0 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -52,6 +52,7 @@ //! mvex //! mehd //! trex +//! pssh //! emsg //! moof //! mfhd @@ -98,6 +99,7 @@ pub(crate) mod moov; pub(crate) mod mp4a; pub(crate) mod mvex; pub(crate) mod mvhd; +pub(crate) mod pssh; pub(crate) mod saio; pub(crate) mod saiz; pub(crate) mod schi; @@ -152,6 +154,7 @@ pub use moov::MoovBox; pub use mp4a::Mp4aBox; pub use mvex::MvexBox; pub use mvhd::MvhdBox; +pub use pssh::PsshBox; pub use saio::SaioBox; pub use saiz::SaizBox; pub use schi::SchiBox; @@ -278,7 +281,8 @@ boxtype! { TencBox => 0x74656e63, SaizBox => 0x7361697a, SaioBox => 0x7361696f, - SencBox => 0x73656e63 + SencBox => 0x73656e63, + PsshBox => 0x70737368 } pub trait Mp4Box: Sized { diff --git a/src/mp4box/moov.rs b/src/mp4box/moov.rs index ac19381..a3c0741 100644 --- a/src/mp4box/moov.rs +++ b/src/mp4box/moov.rs @@ -20,6 +20,9 @@ pub struct MoovBox { #[serde(skip_serializing_if = "Option::is_none")] pub udta: Option, + + #[serde(rename = "pssh")] + pub psshs: Vec, } impl MoovBox { @@ -38,6 +41,9 @@ impl MoovBox { if let Some(udta) = &self.udta { size += udta.box_size(); } + for pssh in &self.psshs { + size += pssh.box_size(); + } size } } @@ -70,6 +76,7 @@ impl ReadBox<&mut R> for MoovBox { let mut udta = None; let mut mvex = None; let mut traks = Vec::new(); + let mut psshs = Vec::new(); let mut current = reader.stream_position()?; let end = start + size; @@ -100,6 +107,10 @@ impl ReadBox<&mut R> for MoovBox { BoxType::UdtaBox => { udta = Some(UdtaBox::read_box(reader, s)?); } + BoxType::PsshBox => { + let pssh = PsshBox::read_box(reader, s)?; + psshs.push(pssh); + } _ => { // XXX warn!() skip_box(reader, s)?; @@ -121,6 +132,7 @@ impl ReadBox<&mut R> for MoovBox { udta, mvex, traks, + psshs, }) } } @@ -140,6 +152,9 @@ impl WriteBox<&mut W> for MoovBox { if let Some(udta) = &self.udta { udta.write_box(writer)?; } + for pssh in &self.psshs { + pssh.write_box(writer)?; + } Ok(0) } } @@ -158,6 +173,7 @@ mod tests { traks: vec![], meta: Some(MetaBox::default()), udta: Some(UdtaBox::default()), + psshs: vec![], }; let mut buf = Vec::new(); diff --git a/src/mp4box/pssh.rs b/src/mp4box/pssh.rs new file mode 100644 index 0000000..34f2019 --- /dev/null +++ b/src/mp4box/pssh.rs @@ -0,0 +1,109 @@ +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] +pub struct PsshBox { + pub version: u8, + pub system_id: [u8; 16], + pub kids: Vec<[u8; 16]>, + pub data: Vec, +} + +impl PsshBox { + pub fn get_type(&self) -> BoxType { + BoxType::PsshBox + } + + pub fn get_size(&self) -> u64 { + let mut s = HEADER_SIZE + HEADER_EXT_SIZE + 16; + if self.version > 0 { + s += 4 + (16 * self.kids.len() as u64); + } + s += 4 + self.data.len() as u64; + s + } +} + +impl Mp4Box for PsshBox { + fn box_type(&self) -> BoxType { + self.get_type() + } + + fn box_size(&self) -> u64 { + self.get_size() + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = format!( + "system_id={:02x?} data_size={}", + self.system_id, + self.data.len(), + ); + Ok(s) + } +} + +impl ReadBox<&mut R> for PsshBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let (version, _flags) = read_box_header_ext(reader)?; + + let mut system_id = [0; 16]; + reader.read_exact(&mut system_id)?; + + let mut kids = Vec::new(); + if version > 0 { + let kid_count = reader.read_u32::()?; + kids.reserve(kid_count as usize); + for _ in 0..kid_count { + let mut kid = [0; 16]; + reader.read_exact(&mut kid)?; + kids.push(kid); + } + } + + let data_size = reader.read_u32::()?; + + let mut data = vec![0; data_size as usize]; + reader.read_exact(&mut data)?; + + skip_bytes_to(reader, start + size)?; + + Ok(PsshBox { + version, + system_id, + kids, + data, + }) + } +} + +impl WriteBox<&mut W> for PsshBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + write_box_header_ext(writer, self.version, 0)?; + + writer.write_all(&self.system_id)?; + + if self.version > 0 { + writer.write_u32::(self.kids.len() as u32)?; + for kid in &self.kids { + writer.write_all(kid)?; + } + } + + writer.write_u32::(self.data.len() as u32)?; + writer.write_all(&self.data)?; + + Ok(size) + } +} From 3fa38567974a8178b2eeeeaff2868acdeb83fcde Mon Sep 17 00:00:00 2001 From: Jensenn Date: Tue, 20 Feb 2024 15:13:40 -0700 Subject: [PATCH 5/6] Fix track functions when encrypted --- src/track.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/track.rs b/src/track.rs index 5c52514..bcdc2bf 100644 --- a/src/track.rs +++ b/src/track.rs @@ -208,7 +208,15 @@ impl Mp4Track { } pub fn sample_freq_index(&self) -> Result { + let mut mp4a_opt: Option<&Mp4aBox> = None; if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { + mp4a_opt = Some(mp4a); + } else if let Some(ref enca) = self.trak.mdia.minf.stbl.stsd.enca { + if let Some(ref mp4a) = enca.mp4a { + mp4a_opt = Some(mp4a); + } + } + if let Some(ref mp4a) = mp4a_opt { if let Some(ref esds) = mp4a.esds { SampleFreqIndex::try_from(esds.es_desc.dec_config.dec_specific.freq_index) } else { @@ -220,7 +228,15 @@ impl Mp4Track { } pub fn channel_config(&self) -> Result { + let mut mp4a_opt: Option<&Mp4aBox> = None; if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { + mp4a_opt = Some(mp4a); + } else if let Some(ref enca) = self.trak.mdia.minf.stbl.stsd.enca { + if let Some(ref mp4a) = enca.mp4a { + mp4a_opt = Some(mp4a); + } + } + if let Some(ref mp4a) = mp4a_opt { if let Some(ref esds) = mp4a.esds { ChannelConfig::try_from(esds.es_desc.dec_config.dec_specific.chan_conf) } else { @@ -246,7 +262,15 @@ impl Mp4Track { } pub fn bitrate(&self) -> u32 { + let mut mp4a_opt: Option<&Mp4aBox> = None; if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { + mp4a_opt = Some(mp4a); + } else if let Some(ref enca) = self.trak.mdia.minf.stbl.stsd.enca { + if let Some(ref mp4a) = enca.mp4a { + mp4a_opt = Some(mp4a); + } + } + if let Some(ref mp4a) = mp4a_opt { if let Some(ref esds) = mp4a.esds { esds.es_desc.dec_config.avg_bitrate } else { @@ -281,7 +305,15 @@ impl Mp4Track { } pub fn video_profile(&self) -> Result { + let mut avc1_opt: Option<&Avc1Box> = None; if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 { + avc1_opt = Some(avc1); + } else if let Some(ref encv) = self.trak.mdia.minf.stbl.stsd.encv { + if let Some(ref avc1) = encv.avc1 { + avc1_opt = Some(avc1); + } + } + if let Some(ref avc1) = avc1_opt { AvcProfile::try_from(( avc1.avcc.avc_profile_indication, avc1.avcc.profile_compatibility, @@ -292,7 +324,15 @@ impl Mp4Track { } pub fn sequence_parameter_set(&self) -> Result<&[u8]> { + let mut avc1_opt: Option<&Avc1Box> = None; if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 { + avc1_opt = Some(avc1); + } else if let Some(ref encv) = self.trak.mdia.minf.stbl.stsd.encv { + if let Some(ref avc1) = encv.avc1 { + avc1_opt = Some(avc1); + } + } + if let Some(ref avc1) = avc1_opt { match avc1.avcc.sequence_parameter_sets.get(0) { Some(nal) => Ok(nal.bytes.as_ref()), None => Err(Error::EntryInStblNotFound( @@ -307,7 +347,15 @@ impl Mp4Track { } pub fn picture_parameter_set(&self) -> Result<&[u8]> { + let mut avc1_opt: Option<&Avc1Box> = None; if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 { + avc1_opt = Some(avc1); + } else if let Some(ref encv) = self.trak.mdia.minf.stbl.stsd.encv { + if let Some(ref avc1) = encv.avc1 { + avc1_opt = Some(avc1); + } + } + if let Some(ref avc1) = avc1_opt { match avc1.avcc.picture_parameter_sets.get(0) { Some(nal) => Ok(nal.bytes.as_ref()), None => Err(Error::EntryInStblNotFound( @@ -322,7 +370,15 @@ impl Mp4Track { } pub fn audio_profile(&self) -> Result { + let mut mp4a_opt: Option<&Mp4aBox> = None; if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { + mp4a_opt = Some(mp4a); + } else if let Some(ref enca) = self.trak.mdia.minf.stbl.stsd.enca { + if let Some(ref mp4a) = enca.mp4a { + mp4a_opt = Some(mp4a); + } + } + if let Some(ref mp4a) = mp4a_opt { if let Some(ref esds) = mp4a.esds { AudioObjectType::try_from(esds.es_desc.dec_config.dec_specific.profile) } else { From 94b9bff82fadb99219c14e441627adf68048c5ba Mon Sep 17 00:00:00 2001 From: Jensenn Date: Tue, 20 Feb 2024 15:41:21 -0700 Subject: [PATCH 6/6] clippy fixes --- src/track.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/track.rs b/src/track.rs index bcdc2bf..05046dc 100644 --- a/src/track.rs +++ b/src/track.rs @@ -216,7 +216,7 @@ impl Mp4Track { mp4a_opt = Some(mp4a); } } - if let Some(ref mp4a) = mp4a_opt { + if let Some(mp4a) = mp4a_opt { if let Some(ref esds) = mp4a.esds { SampleFreqIndex::try_from(esds.es_desc.dec_config.dec_specific.freq_index) } else { @@ -236,7 +236,7 @@ impl Mp4Track { mp4a_opt = Some(mp4a); } } - if let Some(ref mp4a) = mp4a_opt { + if let Some(mp4a) = mp4a_opt { if let Some(ref esds) = mp4a.esds { ChannelConfig::try_from(esds.es_desc.dec_config.dec_specific.chan_conf) } else { @@ -270,7 +270,7 @@ impl Mp4Track { mp4a_opt = Some(mp4a); } } - if let Some(ref mp4a) = mp4a_opt { + if let Some(mp4a) = mp4a_opt { if let Some(ref esds) = mp4a.esds { esds.es_desc.dec_config.avg_bitrate } else { @@ -313,7 +313,7 @@ impl Mp4Track { avc1_opt = Some(avc1); } } - if let Some(ref avc1) = avc1_opt { + if let Some(avc1) = avc1_opt { AvcProfile::try_from(( avc1.avcc.avc_profile_indication, avc1.avcc.profile_compatibility, @@ -332,8 +332,8 @@ impl Mp4Track { avc1_opt = Some(avc1); } } - if let Some(ref avc1) = avc1_opt { - match avc1.avcc.sequence_parameter_sets.get(0) { + if let Some(avc1) = avc1_opt { + match avc1.avcc.sequence_parameter_sets.first() { Some(nal) => Ok(nal.bytes.as_ref()), None => Err(Error::EntryInStblNotFound( self.track_id(), @@ -355,8 +355,8 @@ impl Mp4Track { avc1_opt = Some(avc1); } } - if let Some(ref avc1) = avc1_opt { - match avc1.avcc.picture_parameter_sets.get(0) { + if let Some(avc1) = avc1_opt { + match avc1.avcc.picture_parameter_sets.first() { Some(nal) => Ok(nal.bytes.as_ref()), None => Err(Error::EntryInStblNotFound( self.track_id(), @@ -378,7 +378,7 @@ impl Mp4Track { mp4a_opt = Some(mp4a); } } - if let Some(ref mp4a) = mp4a_opt { + if let Some(mp4a) = mp4a_opt { if let Some(ref esds) = mp4a.esds { AudioObjectType::try_from(esds.es_desc.dec_config.dec_specific.profile) } else {