#![allow(clippy::unwrap_used)]
mod fixtures;
use ff_encode::{VideoCodec, VideoEncoder};
use fixtures::{FileGuard, assert_valid_output_file, create_black_frame, test_output_path};
use std::path::Path;
fn top_level_box_offset(data: &[u8], ty: &[u8; 4]) -> Option<usize> {
let mut pos = 0usize;
while pos + 8 <= data.len() {
let size32 = u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
let box_type = &data[pos + 4..pos + 8];
if box_type == ty {
return Some(pos);
}
let box_size: u64 = if size32 == 1 {
if pos + 16 > data.len() {
return None;
}
u64::from_be_bytes([
data[pos + 8],
data[pos + 9],
data[pos + 10],
data[pos + 11],
data[pos + 12],
data[pos + 13],
data[pos + 14],
data[pos + 15],
])
} else if size32 == 0 {
return None;
} else {
u64::from(size32)
};
if box_size < 8 {
return None;
}
pos = pos.checked_add(usize::try_from(box_size).ok()?)?;
}
None
}
fn encode_tiny_mp4(path: &Path, faststart: bool, frames: usize) -> bool {
let builder = VideoEncoder::create(path)
.video(320, 240, 30.0)
.video_codec(VideoCodec::default());
let builder = if faststart {
builder.faststart()
} else {
builder
};
let mut encoder = match builder.build() {
Ok(enc) => enc,
Err(e) => {
println!("Skipping: MP4 video encoder unavailable: {e}");
return false;
}
};
for _ in 0..frames {
encoder
.push_video(&create_black_frame(320, 240))
.expect("Failed to push video frame");
}
encoder.finish().expect("Failed to finish encoding");
true
}
#[test]
fn faststart_should_place_moov_before_mdat() {
let output_path = test_output_path("faststart_on.mp4");
let _guard = FileGuard::new(output_path.clone());
if !encode_tiny_mp4(&output_path, true, 10) {
return;
}
assert_valid_output_file(&output_path);
let bytes = std::fs::read(&output_path).expect("Failed to read output file");
let moov = top_level_box_offset(&bytes, b"moov").expect("no moov box in output");
let mdat = top_level_box_offset(&bytes, b"mdat").expect("no mdat box in output");
assert!(
moov < mdat,
"faststart should place moov before mdat (moov={moov}, mdat={mdat})"
);
let info = ff_probe::open(&output_path).expect("Failed to probe output");
let video = info.primary_video().expect("No video stream in output");
assert!(
video.frame_count().unwrap_or(0) >= 10,
"Expected at least 10 frames, got {:?}",
video.frame_count()
);
}
#[test]
fn without_faststart_moov_should_follow_mdat() {
let output_path = test_output_path("faststart_off.mp4");
let _guard = FileGuard::new(output_path.clone());
if !encode_tiny_mp4(&output_path, false, 10) {
return;
}
assert_valid_output_file(&output_path);
let bytes = std::fs::read(&output_path).expect("Failed to read output file");
let moov = top_level_box_offset(&bytes, b"moov").expect("no moov box in output");
let mdat = top_level_box_offset(&bytes, b"mdat").expect("no mdat box in output");
assert!(
moov > mdat,
"without faststart the default MP4 layout places moov after mdat (moov={moov}, mdat={mdat})"
);
}