#![forbid(unsafe_code)]
use oximedia_core::{CodecId, OxiError, OxiResult};
use std::io::Write;
use crate::StreamInfo;
#[derive(Debug)]
pub struct InitSegmentBuilder {
streams: Vec<StreamInfo>,
brand: String,
compatible_brands: Vec<String>,
creation_time: u64,
}
impl Default for InitSegmentBuilder {
fn default() -> Self {
Self::new()
}
}
impl InitSegmentBuilder {
#[must_use]
pub fn new() -> Self {
Self {
streams: Vec::new(),
brand: "iso5".into(),
compatible_brands: vec!["iso5".into(), "iso6".into(), "mp41".into()],
creation_time: 0,
}
}
pub fn add_stream(&mut self, info: StreamInfo) -> &mut Self {
self.streams.push(info);
self
}
pub fn with_brand(&mut self, brand: impl Into<String>) -> &mut Self {
self.brand = brand.into();
self
}
pub fn add_compatible_brand(&mut self, brand: impl Into<String>) -> &mut Self {
self.compatible_brands.push(brand.into());
self
}
pub const fn with_creation_time(&mut self, time: u64) -> &mut Self {
self.creation_time = time;
self
}
pub fn build(&self) -> OxiResult<Vec<u8>> {
if self.streams.is_empty() {
return Err(OxiError::InvalidData("No streams added".into()));
}
let mut data = Vec::new();
self.write_ftyp(&mut data)?;
self.write_moov(&mut data)?;
Ok(data)
}
fn write_ftyp(&self, writer: &mut Vec<u8>) -> OxiResult<()> {
let mut box_data = Vec::new();
box_data.extend_from_slice(self.brand.as_bytes().get(..4).unwrap_or(b"iso5"));
box_data.extend_from_slice(&0u32.to_be_bytes());
for brand in &self.compatible_brands {
box_data.extend_from_slice(brand.as_bytes().get(..4).unwrap_or(b" "));
}
self.write_box(writer, b"ftyp", &box_data)?;
Ok(())
}
fn write_moov(&self, writer: &mut Vec<u8>) -> OxiResult<()> {
let mut moov_data = Vec::new();
self.write_mvhd(&mut moov_data)?;
self.write_mvex(&mut moov_data)?;
for (index, stream) in self.streams.iter().enumerate() {
self.write_trak(&mut moov_data, stream, index)?;
}
self.write_box(writer, b"moov", &moov_data)?;
Ok(())
}
fn write_mvhd(&self, writer: &mut Vec<u8>) -> OxiResult<()> {
let mut mvhd_data = Vec::new();
mvhd_data.extend_from_slice(&0u32.to_be_bytes());
mvhd_data.extend_from_slice(&self.creation_time.to_be_bytes()[4..]);
mvhd_data.extend_from_slice(&self.creation_time.to_be_bytes()[4..]);
mvhd_data.extend_from_slice(&1000u32.to_be_bytes());
mvhd_data.extend_from_slice(&0u32.to_be_bytes());
mvhd_data.extend_from_slice(&0x0001_0000u32.to_be_bytes());
mvhd_data.extend_from_slice(&0x0100u16.to_be_bytes());
mvhd_data.extend_from_slice(&[0u8; 10]);
let matrix: [i32; 9] = [0x0001_0000, 0, 0, 0, 0x0001_0000, 0, 0, 0, 0x4000_0000];
for value in &matrix {
mvhd_data.extend_from_slice(&value.to_be_bytes());
}
mvhd_data.extend_from_slice(&[0u8; 24]);
#[allow(clippy::cast_possible_truncation)]
{
mvhd_data.extend_from_slice(&((self.streams.len() + 1) as u32).to_be_bytes());
}
self.write_box(writer, b"mvhd", &mvhd_data)?;
Ok(())
}
fn write_mvex(&self, writer: &mut Vec<u8>) -> OxiResult<()> {
let mut mvex_data = Vec::new();
for (index, _) in self.streams.iter().enumerate() {
self.write_trex(&mut mvex_data, index)?;
}
self.write_box(writer, b"mvex", &mvex_data)?;
Ok(())
}
fn write_trex(&self, writer: &mut Vec<u8>, track_index: usize) -> OxiResult<()> {
let mut trex_data = Vec::new();
trex_data.extend_from_slice(&0u32.to_be_bytes());
#[allow(clippy::cast_possible_truncation)]
{
trex_data.extend_from_slice(&((track_index + 1) as u32).to_be_bytes());
}
trex_data.extend_from_slice(&1u32.to_be_bytes());
trex_data.extend_from_slice(&0u32.to_be_bytes());
trex_data.extend_from_slice(&0u32.to_be_bytes());
trex_data.extend_from_slice(&0u32.to_be_bytes());
self.write_box(writer, b"trex", &trex_data)?;
Ok(())
}
fn write_trak(
&self,
writer: &mut Vec<u8>,
stream: &StreamInfo,
track_index: usize,
) -> OxiResult<()> {
let mut trak_data = Vec::new();
self.write_tkhd(&mut trak_data, stream, track_index)?;
self.write_mdia(&mut trak_data, stream)?;
self.write_box(writer, b"trak", &trak_data)?;
Ok(())
}
fn write_tkhd(
&self,
writer: &mut Vec<u8>,
_stream: &StreamInfo,
track_index: usize,
) -> OxiResult<()> {
let mut tkhd_data = Vec::new();
tkhd_data.extend_from_slice(&0x0000_0007_u32.to_be_bytes());
tkhd_data.extend_from_slice(&self.creation_time.to_be_bytes()[4..]);
tkhd_data.extend_from_slice(&self.creation_time.to_be_bytes()[4..]);
#[allow(clippy::cast_possible_truncation)]
{
tkhd_data.extend_from_slice(&((track_index + 1) as u32).to_be_bytes());
}
tkhd_data.extend_from_slice(&[0u8; 4]);
tkhd_data.extend_from_slice(&0u32.to_be_bytes());
tkhd_data.extend_from_slice(&[0u8; 8]);
tkhd_data.extend_from_slice(&0u16.to_be_bytes());
tkhd_data.extend_from_slice(&0u16.to_be_bytes());
tkhd_data.extend_from_slice(&0x0100u16.to_be_bytes());
tkhd_data.extend_from_slice(&[0u8; 2]);
let matrix: [i32; 9] = [0x0001_0000, 0, 0, 0, 0x0001_0000, 0, 0, 0, 0x4000_0000];
for value in &matrix {
tkhd_data.extend_from_slice(&value.to_be_bytes());
}
tkhd_data.extend_from_slice(&0u32.to_be_bytes());
tkhd_data.extend_from_slice(&0u32.to_be_bytes());
self.write_box(writer, b"tkhd", &tkhd_data)?;
Ok(())
}
fn write_mdia(&self, writer: &mut Vec<u8>, stream: &StreamInfo) -> OxiResult<()> {
let mut mdia_data = Vec::new();
self.write_mdhd(&mut mdia_data, stream)?;
self.write_hdlr(&mut mdia_data, stream)?;
self.write_minf(&mut mdia_data, stream)?;
self.write_box(writer, b"mdia", &mdia_data)?;
Ok(())
}
fn write_mdhd(&self, writer: &mut Vec<u8>, stream: &StreamInfo) -> OxiResult<()> {
let mut mdhd_data = Vec::new();
mdhd_data.extend_from_slice(&0u32.to_be_bytes());
mdhd_data.extend_from_slice(&self.creation_time.to_be_bytes()[4..]);
mdhd_data.extend_from_slice(&self.creation_time.to_be_bytes()[4..]);
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
{
mdhd_data.extend_from_slice(&(stream.timebase.den as u32).to_be_bytes());
}
mdhd_data.extend_from_slice(&0u32.to_be_bytes());
mdhd_data.extend_from_slice(&0x55c4u16.to_be_bytes());
mdhd_data.extend_from_slice(&[0u8; 2]);
self.write_box(writer, b"mdhd", &mdhd_data)?;
Ok(())
}
fn write_hdlr(&self, writer: &mut Vec<u8>, stream: &StreamInfo) -> OxiResult<()> {
let mut hdlr_data = Vec::new();
hdlr_data.extend_from_slice(&0u32.to_be_bytes());
hdlr_data.extend_from_slice(&[0u8; 4]);
let handler = match stream.codec {
CodecId::Av1 | CodecId::Vp8 | CodecId::Vp9 => b"vide",
CodecId::Opus | CodecId::Flac | CodecId::Vorbis => b"soun",
_ => b"data",
};
hdlr_data.extend_from_slice(handler);
hdlr_data.extend_from_slice(&[0u8; 12]);
hdlr_data.push(0);
self.write_box(writer, b"hdlr", &hdlr_data)?;
Ok(())
}
fn write_minf(&self, writer: &mut Vec<u8>, _stream: &StreamInfo) -> OxiResult<()> {
let minf_data = Vec::new();
self.write_box(writer, b"minf", &minf_data)?;
Ok(())
}
#[allow(
clippy::unused_self,
clippy::trivially_copy_pass_by_ref,
clippy::cast_possible_truncation
)]
fn write_box(&self, writer: &mut Vec<u8>, box_type: &[u8; 4], data: &[u8]) -> OxiResult<()> {
let size = (data.len() + 8) as u32;
writer
.write_all(&size.to_be_bytes())
.map_err(|e: std::io::Error| OxiError::from(e))?;
writer
.write_all(box_type)
.map_err(|e: std::io::Error| OxiError::from(e))?;
writer
.write_all(data)
.map_err(|e: std::io::Error| OxiError::from(e))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use oximedia_core::Rational;
#[test]
fn test_init_segment_builder() {
let mut builder = InitSegmentBuilder::new();
let mut stream_info = StreamInfo::new(0, CodecId::Opus, Rational::new(1, 48000));
stream_info.codec_params = crate::stream::CodecParams::audio(48000, 2);
builder
.add_stream(stream_info)
.with_brand("iso5")
.add_compatible_brand("mp41");
let result = builder.build();
assert!(result.is_ok());
let data = result.expect("operation should succeed");
assert!(!data.is_empty());
assert_eq!(&data[4..8], b"ftyp");
}
#[test]
fn test_init_segment_no_streams() {
let builder = InitSegmentBuilder::new();
let result = builder.build();
assert!(result.is_err());
}
}