use crate::ts_parser::{parse_ts_packets, MediaData};
use std::io::{self, ErrorKind};
pub fn extract_thumbnail_from_ts(ts_data: &[u8]) -> io::Result<Vec<u8>> {
let media_data = parse_ts_packets(ts_data)?;
extract_first_iframe(&media_data)
}
pub fn extract_thumbnail_from_mp4(mp4_data: &[u8]) -> io::Result<Vec<u8>> {
let first_sample = find_first_video_sample(mp4_data)?;
Ok(first_sample)
}
fn extract_first_iframe(media_data: &MediaData) -> io::Result<Vec<u8>> {
if media_data.video_stream.is_empty() {
return Err(io::Error::new(
ErrorKind::InvalidData,
"No video data found",
));
}
let mut offset = 0;
let data = &media_data.video_stream;
while offset < data.len() {
let start_code_len = if offset + 3 < data.len()
&& data[offset] == 0x00
&& data[offset + 1] == 0x00
&& data[offset + 2] == 0x01
{
3
} else if offset + 4 < data.len()
&& data[offset] == 0x00
&& data[offset + 1] == 0x00
&& data[offset + 2] == 0x00
&& data[offset + 3] == 0x01
{
4
} else {
offset += 1;
continue;
};
let nal_start = offset + start_code_len;
if nal_start >= data.len() {
break;
}
let nal_type = data[nal_start] & 0x1F;
if nal_type == 5 {
let mut frame_end = nal_start + 1;
let mut frame_data = Vec::new();
if let Some(ref sps) = media_data.sps {
frame_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
frame_data.extend_from_slice(sps);
}
if let Some(ref pps) = media_data.pps {
frame_data.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
frame_data.extend_from_slice(pps);
}
while frame_end < data.len() {
if frame_end + 3 <= data.len()
&& data[frame_end] == 0x00
&& data[frame_end + 1] == 0x00
{
if data[frame_end + 2] == 0x01 {
break;
} else if frame_end + 4 <= data.len()
&& data[frame_end + 2] == 0x00
&& data[frame_end + 3] == 0x01
{
break;
}
}
frame_end += 1;
}
frame_data.extend_from_slice(&data[offset..frame_end]);
return Ok(frame_data);
}
offset += 1;
}
Err(io::Error::new(
ErrorKind::NotFound,
"No I-frame found in video stream",
))
}
fn find_first_video_sample(mp4_data: &[u8]) -> io::Result<Vec<u8>> {
let mdat_offset = find_box(mp4_data, b"mdat")?;
let _mdat_size = read_u32(mp4_data, mdat_offset) as usize;
let mdat_data_start = mdat_offset + 8;
if mdat_data_start >= mp4_data.len() {
return Err(io::Error::new(ErrorKind::InvalidData, "Invalid mdat box"));
}
let moov_offset = find_box(mp4_data, b"moov")?;
let trak_offset = find_box_in_container(mp4_data, moov_offset, b"trak")?;
let mdia_offset = find_box_in_container(mp4_data, trak_offset, b"mdia")?;
let minf_offset = find_box_in_container(mp4_data, mdia_offset, b"minf")?;
let stbl_offset = find_box_in_container(mp4_data, minf_offset, b"stbl")?;
let stsz_offset = find_box_in_container(mp4_data, stbl_offset, b"stsz")?;
let first_sample_size = read_first_sample_size(mp4_data, stsz_offset)?;
if mdat_data_start + first_sample_size > mp4_data.len() {
return Err(io::Error::new(
ErrorKind::InvalidData,
"Sample size exceeds mdat bounds",
));
}
let sample_data = mp4_data[mdat_data_start..mdat_data_start + first_sample_size].to_vec();
let annexb_data = convert_avcc_to_annexb(&sample_data)?;
Ok(annexb_data)
}
fn convert_avcc_to_annexb(avcc_data: &[u8]) -> io::Result<Vec<u8>> {
let mut annexb = Vec::new();
let mut offset = 0;
while offset + 4 <= avcc_data.len() {
let nal_length = read_u32(avcc_data, offset) as usize;
offset += 4;
if offset + nal_length > avcc_data.len() {
break;
}
annexb.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
annexb.extend_from_slice(&avcc_data[offset..offset + nal_length]);
offset += nal_length;
}
Ok(annexb)
}
fn find_box(data: &[u8], box_type: &[u8; 4]) -> io::Result<usize> {
let mut offset = 0;
while offset + 8 <= data.len() {
let size = read_u32(data, offset) as usize;
if size < 8 {
return Err(io::Error::new(ErrorKind::InvalidData, "Invalid box size"));
}
if &data[offset + 4..offset + 8] == box_type {
return Ok(offset);
}
offset += size;
}
Err(io::Error::new(
ErrorKind::NotFound,
format!(
"Box {:?} not found",
std::str::from_utf8(box_type).unwrap_or("unknown")
),
))
}
fn find_box_in_container(
data: &[u8],
container_offset: usize,
box_type: &[u8; 4],
) -> io::Result<usize> {
let container_size = read_u32(data, container_offset) as usize;
let mut offset = container_offset + 8;
let container_end = container_offset + container_size;
while offset + 8 <= container_end && offset + 8 <= data.len() {
let size = read_u32(data, offset) as usize;
if size < 8 {
return Err(io::Error::new(ErrorKind::InvalidData, "Invalid box size"));
}
if &data[offset + 4..offset + 8] == box_type {
return Ok(offset);
}
offset += size;
}
Err(io::Error::new(
ErrorKind::NotFound,
format!(
"Box {:?} not found in container",
std::str::from_utf8(box_type).unwrap_or("unknown")
),
))
}
fn read_first_sample_size(data: &[u8], stsz_offset: usize) -> io::Result<usize> {
if stsz_offset + 20 > data.len() {
return Err(io::Error::new(ErrorKind::InvalidData, "Invalid stsz box"));
}
let sample_size = read_u32(data, stsz_offset + 12) as usize;
if sample_size != 0 {
return Ok(sample_size);
}
if stsz_offset + 24 > data.len() {
return Err(io::Error::new(
ErrorKind::InvalidData,
"Invalid stsz box - no sample sizes",
));
}
let first_sample_size = read_u32(data, stsz_offset + 20) as usize;
Ok(first_sample_size)
}
fn read_u32(data: &[u8], offset: usize) -> u32 {
u32::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_convert_avcc_to_annexb() {
let avcc = vec![
0x00, 0x00, 0x00, 0x05, 0x67, 0x01, 0x02, 0x03, 0x04, ];
let annexb = convert_avcc_to_annexb(&avcc).unwrap();
assert_eq!(annexb[0..4], [0x00, 0x00, 0x00, 0x01]); assert_eq!(annexb[4..9], [0x67, 0x01, 0x02, 0x03, 0x04]); }
}