mp4-track 0.1.0

Track-level MP4 reader and progressive writer with bounded parsing
Documentation
//! Remuxes one supported MP4 file into progressive output.

use mp4_track::{ChunkOffsetChoice, Mp4Reader, Mp4Writer, SampleInput, TrackConfig, WriterConfig};
use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
    let mut args = std::env::args().skip(1);
    let input = args.next().ok_or("usage: remux INPUT OUTPUT [co64]")?;
    let output = args.next().ok_or("usage: remux INPUT OUTPUT [co64]")?;
    let force_co64 = args.next().as_deref() == Some("co64");
    let mut reader = Mp4Reader::open(File::open(input)?)?;
    let config = WriterConfig {
        chunk_offsets: if force_co64 {
            ChunkOffsetChoice::Co64
        } else {
            ChunkOffsetChoice::Auto
        },
        ..WriterConfig::default()
    };
    let mut writer = Mp4Writer::new(File::create(output)?, config)?;
    let track_configs: Vec<_> = reader
        .tracks()
        .iter()
        .map(|track| TrackConfig {
            kind: track.kind,
            timescale: track.timescale,
            language: track.language.clone(),
            handler_name: track.handler_name.clone(),
            codec: track.codec.clone(),
            width: track.width,
            height: track.height,
            edit_list: track.edit_list.clone(),
        })
        .collect();
    let mut output_ids = Vec::new();
    for track in track_configs {
        output_ids.push(writer.add_track(track)?);
    }
    let mut order = Vec::new();
    for track in reader.tracks() {
        for sample in reader.samples(track.id)? {
            order.push((track.id, sample.index, sample.dts, track.timescale));
        }
    }
    order.sort_by(|left, right| {
        let a = u128::from(left.2).saturating_mul(u128::from(right.3));
        let b = u128::from(right.2).saturating_mul(u128::from(left.3));
        a.cmp(&b)
            .then_with(|| left.0.cmp(&right.0))
            .then_with(|| left.1.cmp(&right.1))
    });
    for (track_id, index, _, _) in order {
        let sample = reader.read_sample(track_id, index)?;
        let output_id = *output_ids
            .get(usize::try_from(track_id.0.saturating_sub(1))?)
            .ok_or("track id is not contiguous")?;
        writer.write_sample(
            output_id,
            &SampleInput {
                data: &sample.data,
                duration: sample.info.duration,
                cts_offset: i32::try_from(sample.info.cts_offset)?,
                is_sync: sample.info.is_sync,
            },
        )?;
    }
    writer.finish()?;
    Ok(())
}