use std::time::Duration;
use ffmpeg_next::{Error as FfmpegError, Packet, Rational};
use crate::error::UnbundleError;
use crate::unbundle::MediaFile;
#[derive(Debug, Clone)]
pub struct KeyFrameMetadata {
pub packet_number: u64,
pub pts: Option<i64>,
pub timestamp: Option<Duration>,
pub size: usize,
}
#[derive(Debug, Clone)]
pub struct GroupOfPicturesInfo {
pub keyframes: Vec<KeyFrameMetadata>,
pub group_of_pictures_sizes: Vec<u64>,
pub average_group_of_pictures_size: f64,
pub min_group_of_pictures_size: u64,
pub max_group_of_pictures_size: u64,
pub total_video_packets: u64,
}
pub(crate) fn analyze_group_of_pictures_impl(
unbundler: &mut MediaFile,
video_stream_index: usize,
) -> Result<GroupOfPicturesInfo, UnbundleError> {
log::debug!(
"Analyzing Group of Pictures structure (stream={})",
video_stream_index
);
let time_base: Rational = unbundler
.input_context
.stream(video_stream_index)
.ok_or(UnbundleError::NoVideoStream)?
.time_base();
let mut keyframes: Vec<KeyFrameMetadata> = Vec::new();
let mut video_packet_count: u64 = 0;
let mut packet = Packet::empty();
loop {
match packet.read(&mut unbundler.input_context) {
Ok(()) => {
if packet.stream() as usize != video_stream_index {
continue;
}
if packet.is_key() {
let pts = packet.pts();
let timestamp = pts.map(|p| {
let secs = p as f64 * time_base.numerator() as f64
/ time_base.denominator().max(1) as f64;
Duration::from_secs_f64(secs.max(0.0))
});
keyframes.push(KeyFrameMetadata {
packet_number: video_packet_count,
pts,
timestamp,
size: packet.size(),
});
}
video_packet_count += 1;
}
Err(FfmpegError::Eof) => break,
Err(e) => return Err(UnbundleError::from(e)),
}
}
let mut group_of_pictures_sizes: Vec<u64> = Vec::new();
for i in 0..keyframes.len() {
let start = keyframes[i].packet_number;
let end = if i + 1 < keyframes.len() {
keyframes[i + 1].packet_number
} else {
video_packet_count
};
group_of_pictures_sizes.push(end - start);
}
let average_group_of_pictures_size = if group_of_pictures_sizes.is_empty() {
0.0
} else {
group_of_pictures_sizes.iter().sum::<u64>() as f64 / group_of_pictures_sizes.len() as f64
};
let min_group_of_pictures_size = group_of_pictures_sizes.iter().copied().min().unwrap_or(0);
let max_group_of_pictures_size = group_of_pictures_sizes.iter().copied().max().unwrap_or(0);
Ok(GroupOfPicturesInfo {
keyframes,
group_of_pictures_sizes,
average_group_of_pictures_size,
min_group_of_pictures_size,
max_group_of_pictures_size,
total_video_packets: video_packet_count,
})
}