use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
const MOOV_CAP: u64 = 32 * 1024 * 1024;
fn read_header<R: Read + Seek>(r: &mut R, end: u64) -> Option<(u64, [u8; 4], u64)> {
let start = r.stream_position().ok()?;
let mut hdr = [0u8; 8];
r.read_exact(&mut hdr).ok()?;
let mut size = u32::from_be_bytes(hdr[0..4].try_into().ok()?) as u64;
let typ: [u8; 4] = hdr[4..8].try_into().ok()?;
let mut header_len = 8u64;
if size == 1 {
let mut ext = [0u8; 8];
r.read_exact(&mut ext).ok()?;
size = u64::from_be_bytes(ext);
header_len = 16;
} else if size == 0 {
size = end.checked_sub(start)?;
}
if size < header_len {
return None;
}
Some((size, typ, header_len))
}
fn read_moov<R: Read + Seek>(r: &mut R, end: u64) -> Option<Vec<u8>> {
loop {
let start = r.stream_position().ok()?;
if start >= end {
return None;
}
let (size, typ, header_len) = read_header(r, end)?;
if start.checked_add(size)? > end {
return None;
}
if &typ == b"moov" {
let payload = size - header_len;
if payload > MOOV_CAP {
return None;
}
let mut buf = vec![0u8; payload as usize];
r.read_exact(&mut buf).ok()?;
return Some(buf);
}
r.seek(SeekFrom::Start(start.checked_add(size)?)).ok()?;
}
}
fn find_vide(buf: &[u8], parent_is_mdia: bool) -> Result<bool, ()> {
let mut i = 0usize;
while i + 8 <= buf.len() {
let size = u32::from_be_bytes(buf[i..i + 4].try_into().map_err(|_| ())?) as usize;
let typ: [u8; 4] = buf[i + 4..i + 8].try_into().map_err(|_| ())?;
let (size, header_len) = if size == 1 {
if i + 16 > buf.len() {
return Err(());
}
let s = u64::from_be_bytes(buf[i + 8..i + 16].try_into().map_err(|_| ())?);
(usize::try_from(s).map_err(|_| ())?, 16usize)
} else if size == 0 {
(buf.len() - i, 8usize)
} else {
(size, 8usize)
};
if size < header_len || i + size > buf.len() {
return Err(());
}
let payload = &buf[i + header_len..i + size];
if parent_is_mdia && &typ == b"hdlr" {
if payload.len() >= 12 && &payload[8..12] == b"vide" {
return Ok(true);
}
}
if (&typ == b"trak" || &typ == b"mdia") && find_vide(payload, &typ == b"mdia")? {
return Ok(true);
}
i += size;
}
Ok(false)
}
pub(crate) fn has_video_track_in<R: Read + Seek>(r: &mut R, end: u64) -> bool {
match read_moov(r, end) {
Some(moov) => find_vide(&moov, false).unwrap_or(true),
None => true,
}
}
pub fn has_video_track(path: &Path) -> bool {
let path = path.to_path_buf();
crate::io_timeout::run_with_timeout(crate::io_timeout::DEFAULT_IO_TIMEOUT, move || {
let mut f = std::fs::File::open(&path).ok()?;
let end = f.seek(SeekFrom::End(0)).ok()?;
f.seek(SeekFrom::Start(0)).ok()?;
Some(has_video_track_in(&mut f, end))
})
.ok()
.flatten()
.unwrap_or(true)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
fn bx(typ: &[u8; 4], payload: &[u8]) -> Vec<u8> {
let size = (8 + payload.len()) as u32;
let mut v = size.to_be_bytes().to_vec();
v.extend_from_slice(typ);
v.extend_from_slice(payload);
v
}
fn hdlr(handler_type: &[u8; 4]) -> Vec<u8> {
let mut p = vec![0u8; 8];
p.extend_from_slice(handler_type);
p.extend_from_slice(b"\0"); bx(b"hdlr", &p)
}
fn trak(media_handler: &[u8; 4]) -> Vec<u8> {
let minf = bx(b"minf", &hdlr(b"alis"));
let mut mdia_payload = hdlr(media_handler);
mdia_payload.extend_from_slice(&minf);
bx(b"trak", &bx(b"mdia", &mdia_payload))
}
fn file_with(traks: &[Vec<u8>]) -> Vec<u8> {
let mut moov_payload = Vec::new();
for t in traks {
moov_payload.extend_from_slice(t);
}
let mut out = bx(b"ftyp", b"qt ");
out.extend_from_slice(&bx(b"moov", &moov_payload));
out.extend_from_slice(&bx(b"mdat", &[0u8; 32]));
out
}
fn probe(bytes: Vec<u8>) -> bool {
let n = bytes.len() as u64;
has_video_track_in(&mut Cursor::new(bytes), n)
}
#[test]
fn a_file_with_a_video_track_reports_true() {
assert!(probe(file_with(&[trak(b"vide"), trak(b"soun")])));
}
#[test]
fn an_audio_only_file_reports_false() {
assert!(!probe(file_with(&[trak(b"soun")])));
}
#[test]
fn a_data_handler_is_not_mistaken_for_a_media_handler() {
let mut f = file_with(&[trak(b"soun")]);
f.extend_from_slice(&bx(b"udta", &bx(b"meta", &hdlr(b"mdir"))));
assert!(!probe(f));
}
#[test]
fn video_after_audio_is_still_found() {
assert!(probe(file_with(&[trak(b"soun"), trak(b"vide")])));
}
#[test]
fn no_moov_fails_open() {
let mut f = bx(b"ftyp", b"qt ");
f.extend_from_slice(&bx(b"mdat", &[0u8; 16]));
assert!(probe(f), "unparseable input must be treated as having video");
}
#[test]
fn truncated_input_fails_open() {
let full = file_with(&[trak(b"soun")]);
assert!(probe(full[..20].to_vec()));
}
#[test]
fn empty_input_fails_open() {
assert!(probe(Vec::new()));
}
#[test]
fn a_box_smaller_than_its_own_header_fails_open_without_looping() {
let mut f = 3u32.to_be_bytes().to_vec();
f.extend_from_slice(b"moov");
assert!(probe(f));
}
#[test]
fn a_child_claiming_to_extend_past_its_parent_fails_open() {
let mut child = 9999u32.to_be_bytes().to_vec();
child.extend_from_slice(b"trak");
assert!(probe(bx(b"moov", &child)));
}
#[test]
fn a_64_bit_size_header_is_handled() {
let inner = bx(b"moov", &trak(b"vide"));
let mut f = 1u32.to_be_bytes().to_vec();
f.extend_from_slice(b"free");
f.extend_from_slice(&(16u64 + 8).to_be_bytes());
f.extend_from_slice(&[0u8; 8]);
f.extend_from_slice(&inner);
assert!(probe(f));
}
#[test]
fn an_oversized_moov_fails_open_rather_than_allocating() {
let mut f = bx(b"ftyp", b"qt ");
let huge = (MOOV_CAP + 1_000_000) as u32;
f.extend_from_slice(&huge.to_be_bytes());
f.extend_from_slice(b"moov");
assert!(probe(f));
}
fn write_temp(name: &str, bytes: &[u8]) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("videre_vp_{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let p = dir.join(name);
std::fs::write(&p, bytes).unwrap();
p
}
#[test]
fn has_video_track_reads_a_real_file() {
let p = write_temp("audio.mov", &file_with(&[trak(b"soun")]));
assert!(!has_video_track(&p));
let p = write_temp("video.mov", &file_with(&[trak(b"vide")]));
assert!(has_video_track(&p));
}
#[test]
fn a_nonexistent_path_fails_open() {
assert!(has_video_track(std::path::Path::new("/nonexistent/nope.mov")));
}
#[test]
fn an_empty_file_fails_open() {
let p = write_temp("empty.mov", b"");
assert!(has_video_track(&p));
}
#[test]
fn a_jpeg_fails_open() {
let p = write_temp("not_a_video.jpg", b"\xff\xd8\xff\xe0\x00\x10JFIF\0\x01");
assert!(has_video_track(&p));
}
}