mp4forge 0.8.0

Rust library and CLI for inspecting, probing, extracting, muxing, and rewriting MP4 structures
Documentation
#[cfg(feature = "mux")]
use std::io::Cursor;

#[cfg(feature = "mux")]
use mp4forge::boxes::iso14496_12::{AudioSampleEntry, SampleEntry, VisualSampleEntry};
#[cfg(feature = "mux")]
use mp4forge::codec::{CodecBox, marshal};
#[cfg(feature = "mux")]
use mp4forge::mux::{
    MuxFileConfig, MuxInterleavePolicy, MuxStagedMediaItem, MuxTrackConfig,
    plan_staged_media_items, write_mp4_mux,
};
#[cfg(feature = "mux")]
use mp4forge::{BoxInfo, FourCc};

#[cfg(not(feature = "mux"))]
fn main() {}

#[cfg(feature = "mux")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut sources = [
        Cursor::new(b"AAAAhelloBBBBxy".to_vec()),
        Cursor::new(b"zzzzSYNCtail".to_vec()),
    ];
    let plan = plan_staged_media_items(
        vec![
            MuxStagedMediaItem::new(0, 2, 10, 4, 13, 2),
            MuxStagedMediaItem::new(1, 1, 0, 5, 4, 4).with_sync_sample(true),
            MuxStagedMediaItem::new(0, 2, 0, 4, 4, 5)
                .with_composition_time_offset(2)
                .with_sync_sample(true),
        ],
        MuxInterleavePolicy::DecodeTime,
    )?;
    let file_config = MuxFileConfig::new(1_000)
        .with_major_brand(fourcc("isom"))
        .with_compatible_brand(fourcc("mp42"));
    let track_configs = vec![
        MuxTrackConfig::new_audio(1, 1_000, audio_sample_entry_box()?),
        MuxTrackConfig::new_video(2, 1_000, 640, 360, video_sample_entry_box()?),
    ];

    let mut output = Cursor::new(Vec::new());
    write_mp4_mux(
        &mut sources,
        &mut output,
        &file_config,
        &track_configs,
        &plan,
    )?;

    println!(
        "built one MP4 file with {} bytes and {} planned samples",
        output.get_ref().len(),
        plan.planned_items().len()
    );
    Ok(())
}

#[cfg(feature = "mux")]
fn audio_sample_entry_box() -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    encode_typed_box(
        &AudioSampleEntry {
            sample_entry: SampleEntry {
                box_type: fourcc("mp4a"),
                data_reference_index: 1,
            },
            channel_count: 2,
            sample_size: 16,
            sample_rate: 48_000_u32 << 16,
            ..AudioSampleEntry::default()
        },
        &[],
    )
}

#[cfg(feature = "mux")]
fn video_sample_entry_box() -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    encode_typed_box(
        &VisualSampleEntry {
            sample_entry: SampleEntry {
                box_type: fourcc("avc1"),
                data_reference_index: 1,
            },
            width: 640,
            height: 360,
            horizresolution: 72_u32 << 16,
            vertresolution: 72_u32 << 16,
            frame_count: 1,
            depth: 0x0018,
            pre_defined3: -1,
            ..VisualSampleEntry::default()
        },
        &[],
    )
}

#[cfg(feature = "mux")]
fn encode_typed_box<B>(
    box_value: &B,
    children: &[u8],
) -> Result<Vec<u8>, Box<dyn std::error::Error>>
where
    B: CodecBox,
{
    let mut payload = Vec::new();
    marshal(&mut payload, box_value, None)?;
    payload.extend_from_slice(children);

    let mut bytes = Cursor::new(Vec::new());
    BoxInfo::new(box_value.box_type(), 8 + u64::try_from(payload.len())?).write(&mut bytes)?;
    bytes.get_mut().extend_from_slice(&payload);
    Ok(bytes.into_inner())
}

#[cfg(feature = "mux")]
fn fourcc(value: &str) -> FourCc {
    FourCc::try_from(value).expect("valid fourcc literal")
}