mp4forge 0.8.0

Rust library and CLI for inspecting, probing, extracting, muxing, and rewriting MP4 structures
Documentation
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::{self, Cursor};

use mp4forge::FourCc;
use mp4forge::boxes::iso14496_12::Emsg;
use mp4forge::codec::marshal_dyn;
use mp4forge::walk::{WalkControl, WalkError, walk_structure};
use mp4forge::writer::Writer;

fn main() {
    if let Err(error) = run() {
        eprintln!("{error}");
        std::process::exit(1);
    }
}

fn run() -> Result<(), Box<dyn Error>> {
    let Some(output_path) = env::args().nth(1) else {
        return Err("usage: cargo run --example edit_emsg -- <output.mp4>".into());
    };

    let input = sample_emsg_file();
    let mut walk_reader = Cursor::new(input.clone());
    let mut copy_reader = Cursor::new(input);
    let output = File::create(output_path)?;
    let mut writer = Writer::new(output);

    walk_structure(&mut walk_reader, |handle| {
        if handle.info().box_type() == FourCc::from_bytes(*b"emsg") {
            writer.start_box(*handle.info()).map_err(walk_error)?;
            let (mut payload, _) = handle.read_payload()?;
            let Some(emsg) = payload.as_any_mut().downcast_mut::<Emsg>() else {
                return Err(walk_error("expected emsg payload"));
            };
            emsg.message_data = b"hello world".to_vec();
            marshal_dyn(&mut writer, payload.as_ref(), None)?;
            writer.end_box().map_err(walk_error)?;
        } else {
            writer
                .copy_box(&mut copy_reader, handle.info())
                .map_err(walk_error)?;
        }
        Ok(WalkControl::Continue)
    })?;

    Ok(())
}

fn walk_error(error: impl ToString) -> WalkError {
    WalkError::from(io::Error::other(error.to_string()))
}

fn sample_emsg_file() -> Vec<u8> {
    let mut emsg_payload = vec![0x00, 0x00, 0x00, 0x00];
    append_null_terminated_string(&mut emsg_payload, "urn:test");
    append_null_terminated_string(&mut emsg_payload, "demo");
    append_u32(&mut emsg_payload, 1000);
    append_u32(&mut emsg_payload, 0);
    append_u32(&mut emsg_payload, 5);
    append_u32(&mut emsg_payload, 1);
    emsg_payload.extend_from_slice(b"hello");

    let mut file = Vec::new();
    file.extend_from_slice(&box_bytes("free", &[0x01, 0x02, 0x03]));
    file.extend_from_slice(&box_bytes("emsg", &emsg_payload));
    file.extend_from_slice(&box_bytes("free", &[0x04, 0x05]));
    file
}

fn append_null_terminated_string(dst: &mut Vec<u8>, value: &str) {
    dst.extend_from_slice(value.as_bytes());
    dst.push(0x00);
}

fn append_u32(dst: &mut Vec<u8>, value: u32) {
    dst.extend_from_slice(&value.to_be_bytes());
}

fn box_bytes(box_type: &str, payload: &[u8]) -> Vec<u8> {
    let mut box_bytes = Vec::with_capacity(8 + payload.len());
    box_bytes.extend_from_slice(&((payload.len() + 8) as u32).to_be_bytes());
    box_bytes.extend_from_slice(box_type.as_bytes());
    box_bytes.extend_from_slice(payload);
    box_bytes
}