use std::collections::VecDeque;
use std::path::Path;
use std::time::Duration;
use crate::demux::Demuxer;
use crate::decode::Decoder;
use crate::error::{Error, Result};
use crate::frame::Frame;
use crate::plugin::Plugin;
const QUEUE: usize = 30;
pub struct VideoSource {
pub persistent_id: String,
demuxer: Box<dyn Demuxer>,
decoder: Box<dyn Decoder>,
video_track_id: u32,
frame_duration: Option<Duration>,
frame_queue: VecDeque<Frame>,
plugins: Vec<Box<dyn Plugin>>,
eof_reached: bool,
discard_before: Option<Duration>,
origin: Option<Duration>,
cache: Option<Cached>,
}
struct Cached {
cache: FrameCache,
next: u64,
decoder_in_step: bool,
}
impl VideoSource {
#[cfg(feature = "demux")]
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
Self::from_demuxer(crate::open_media(path)?, path)
}
#[cfg(feature = "demux")]
pub fn from_reader(reader: Box<dyn crate::ReadSeek>, name: &str) -> Result<Self> {
Self::from_demuxer(crate::open_media_reader(reader, name)?, Path::new(name))
}
#[cfg(feature = "demux")]
fn from_demuxer(demuxer: Box<dyn crate::Demuxer>, path: &Path) -> Result<Self> {
let media_info = demuxer.info();
let video_track = media_info
.video()
.ok_or_else(|| crate::Error::Unsupported {
what: format!("{}: no video track in it", path.display()),
})?;
let video_track_id = video_track.id;
let frame_duration = video_track
.frame_rate
.map(|rate| rate.frame_duration())
.filter(|duration| !duration.is_zero());
let config = crate::decode::DecoderConfig::from_track(video_track)?;
let decoder = crate::decode::open(&config)?;
let persistent_id = format!("{:x}", {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
path.to_string_lossy().hash(&mut hasher);
hasher.finish()
});
Ok(VideoSource {
persistent_id,
demuxer,
decoder,
video_track_id,
frame_duration,
frame_queue: VecDeque::with_capacity(QUEUE),
plugins: Vec::new(),
eof_reached: false,
discard_before: None,
origin: None,
cache: None,
})
}
pub fn add_plugin(&mut self, plugin: Box<dyn Plugin>) {
self.plugins.push(plugin);
}
pub fn persistent_id(&self) -> &str {
&self.persistent_id
}
pub fn info(&self) -> &crate::media::MediaInfo {
self.demuxer.info()
}
pub fn decoder(&self) -> &dyn Decoder {
self.decoder.as_ref()
}
pub fn frame_duration(&self) -> Option<Duration> {
self.frame_duration
}
pub fn set_cache(&mut self, cache: FrameCache) -> Result<()> {
if self.frame_duration.is_none() {
return Err(Error::unsupported(
"this file states no frame rate, and a frame cache is addressed by frame number",
));
}
self.origin()?;
self.cache = Some(Cached {
cache,
next: 0,
decoder_in_step: false,
});
self.rewind()
}
pub fn advance(&mut self, _delta: Duration) -> Result<()> {
while self.frame_queue.len() < QUEUE && !self.eof_reached {
if self.serve_from_cache()? {
continue;
}
match self.demuxer.next_packet()? {
Some(packet) => {
if packet.track_id == self.video_track_id {
if let Some(frame) = self.decoder.decode(&packet)? {
self.take(frame)?;
}
}
}
None => {
for frame in self.decoder.flush()? {
self.take(frame)?;
}
let in_step = self
.cache
.as_ref()
.is_none_or(|cached| cached.decoder_in_step);
self.eof_reached = in_step && !self.cache_has_next();
}
}
}
Ok(())
}
pub fn origin(&mut self) -> Result<Duration> {
if let Some(origin) = self.origin {
return Ok(origin);
}
self.frame_queue.clear();
self.reposition(Duration::ZERO)?;
let origin = loop {
match self.demuxer.next_packet()? {
Some(packet) if packet.track_id == self.video_track_id => {
if let Some(frame) = self.decoder.decode(&packet)? {
break frame.pts();
}
}
Some(_) => {}
None => {
break self
.decoder
.flush()?
.first()
.map_or(Duration::ZERO, Frame::pts)
}
}
};
self.origin = Some(origin);
self.reposition(Duration::ZERO)?;
Ok(origin)
}
pub fn seek(&mut self, position: Duration) -> Result<()> {
self.frame_queue.clear();
self.reposition(position)?;
if let (Some(cached), Some(duration)) = (self.cache.as_mut(), self.frame_duration) {
let origin = self.origin.unwrap_or_default();
cached.next = frame_number(position.saturating_sub(origin), duration);
cached.decoder_in_step = true;
}
Ok(())
}
fn reposition(&mut self, position: Duration) -> Result<()> {
self.demuxer.seek(position)?;
self.decoder.reset()?;
self.eof_reached = false;
let slack = self.frame_duration.unwrap_or_default() / 2;
self.discard_before = Some(position.saturating_sub(slack)).filter(|at| !at.is_zero());
Ok(())
}
pub fn rewind(&mut self) -> Result<()> {
self.seek(Duration::ZERO)
}
pub fn current_frame(&self) -> Option<&Frame> {
self.frame_queue.front()
}
pub fn pop_frame(&mut self) -> Option<Frame> {
self.frame_queue.pop_front()
}
pub fn queued_frames(&self) -> usize {
self.frame_queue.len()
}
pub fn is_finished(&self) -> bool {
self.eof_reached && self.frame_queue.is_empty()
}
fn take(&mut self, frame: Frame) -> Result<()> {
if let Some(before) = self.discard_before {
if frame.pts() < before {
return Ok(());
}
self.discard_before = None;
}
if let (Some(cached), Some(duration)) = (self.cache.as_mut(), self.frame_duration) {
let origin = self.origin.unwrap_or_default();
let number = frame_number(frame.pts().saturating_sub(origin), duration);
if number < cached.next {
return Ok(());
}
if cached.cache.get(number).is_some() {
cached.decoder_in_step = false;
return Ok(());
}
cached.next = number + 1;
}
let mut frame = frame;
for plugin in &mut self.plugins {
frame = plugin.process(frame)?;
}
self.frame_queue.push_back(frame);
Ok(())
}
fn serve_from_cache(&mut self) -> Result<bool> {
let (Some(cached), Some(duration)) = (self.cache.as_mut(), self.frame_duration) else {
return Ok(false);
};
let origin = self.origin.unwrap_or_default();
if let Some(frame) = cached.cache.get(cached.next) {
let frame = frame.with_pts(origin + frame_time(cached.next, duration));
cached.next += 1;
cached.decoder_in_step = false;
let mut frame = frame;
for plugin in &mut self.plugins {
frame = plugin.process(frame)?;
}
self.frame_queue.push_back(frame);
return Ok(true);
}
if !cached.decoder_in_step {
cached.decoder_in_step = true;
let position = origin + frame_time(cached.next, duration);
self.reposition(position)?;
}
Ok(false)
}
fn cache_has_next(&self) -> bool {
self.cache
.as_ref()
.is_some_and(|cached| cached.cache.get(cached.next).is_some())
}
}
fn frame_number(position: Duration, frame_duration: Duration) -> u64 {
let frame = frame_duration.as_nanos().max(1);
((position.as_nanos() + frame / 2) / frame) as u64
}
fn frame_time(number: u64, frame_duration: Duration) -> Duration {
let nanos = u128::from(number) * frame_duration.as_nanos();
Duration::from_nanos(nanos.min(u128::from(u64::MAX)) as u64)
}
#[derive(Clone, Debug)]
pub struct FrameCache {
ranges: Vec<(u64, u64)>,
offsets: Vec<usize>,
frames: Vec<Frame>,
}
impl FrameCache {
pub fn new(mut ranges: Vec<(u64, u64)>, frames: Vec<Frame>) -> Result<Self> {
ranges.sort_unstable();
let mut offsets = Vec::with_capacity(ranges.len());
let mut total = 0_usize;
let mut previous_end = 0_u64;
for (index, &(start, end)) in ranges.iter().enumerate() {
if start >= end {
return Err(Error::unsupported(format!(
"frame range {start}..{end} holds no frames"
)));
}
if index > 0 && start < previous_end {
return Err(Error::unsupported(format!(
"frame range {start}..{end} overlaps the one before it, which ends at {previous_end}"
)));
}
offsets.push(total);
total += usize::try_from(end - start).map_err(|_| {
Error::unsupported(format!("frame range {start}..{end} is too long to hold"))
})?;
previous_end = end;
}
if total != frames.len() {
return Err(Error::unsupported(format!(
"the ranges cover {total} frames and {} were given",
frames.len()
)));
}
Ok(FrameCache {
ranges,
offsets,
frames,
})
}
pub fn get(&self, number: u64) -> Option<&Frame> {
let index = self
.ranges
.partition_point(|&(start, _)| start <= number)
.checked_sub(1)?;
let (start, end) = self.ranges[index];
if number >= end {
return None;
}
self.frames
.get(self.offsets[index] + (number - start) as usize)
}
pub fn ranges(&self) -> &[(u64, u64)] {
&self.ranges
}
pub fn len(&self) -> usize {
self.frames.len()
}
pub fn is_empty(&self) -> bool {
self.frames.is_empty()
}
pub fn byte_len(&self) -> usize {
self.frames.iter().map(Frame::byte_len).sum()
}
}
#[cfg(feature = "demux")]
pub fn cache_frames(path: impl AsRef<Path>, ranges: &[(u64, u64)]) -> Result<Vec<Frame>> {
let mut source = VideoSource::from_file(path)?;
let duration = source.frame_duration().ok_or_else(|| {
Error::unsupported("this file states no frame rate, so its frames cannot be numbered")
})?;
let origin = source.origin()?;
let mut sorted = ranges.to_vec();
sorted.sort_unstable();
let mut frames = Vec::new();
for (start, end) in sorted {
source.seek(origin + frame_time(start, duration))?;
let mut number = start;
while number < end {
source.advance(Duration::ZERO)?;
let Some(frame) = source.pop_frame() else {
return Err(Error::Decode {
encoding: source.decoder().encoding(),
reason: format!(
"frame range {start}..{end} runs past the end of the film, at frame {number}"
),
});
};
frames.push(frame);
number += 1;
}
}
Ok(frames)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::color::ColorSpace;
use crate::frame::PixelFormat;
fn frame(tag: u8) -> Frame {
Frame::packed(
2,
2,
PixelFormat::Rgba8,
ColorSpace::srgb(),
Duration::ZERO,
vec![tag; 16],
)
.unwrap()
}
fn tag(frame: &Frame) -> u8 {
frame.data()[0]
}
#[test]
fn a_cache_finds_each_frame_by_number() {
let cache = FrameCache::new(
vec![(10, 12), (0, 2)],
vec![frame(0), frame(1), frame(10), frame(11)],
)
.unwrap();
assert_eq!(cache.get(0).map(tag), Some(0));
assert_eq!(cache.get(1).map(tag), Some(1));
assert!(cache.get(2).is_none());
assert!(cache.get(9).is_none());
assert_eq!(cache.get(10).map(tag), Some(10));
assert_eq!(cache.get(11).map(tag), Some(11));
assert!(cache.get(12).is_none());
assert_eq!(cache.len(), 4);
}
#[test]
fn a_cache_that_does_not_add_up_is_refused() {
assert!(FrameCache::new(vec![(0, 3)], vec![frame(0), frame(1)]).is_err());
assert!(FrameCache::new(vec![(5, 5)], vec![]).is_err());
assert!(FrameCache::new(vec![(0, 4), (2, 6)], vec![frame(0); 8]).is_err());
}
#[test]
fn frame_numbers_round_rather_than_floor() {
let frame = Duration::from_nanos(41_708_333);
assert_eq!(frame_number(Duration::ZERO, frame), 0);
assert_eq!(frame_number(frame * 24 - Duration::from_nanos(3), frame), 24);
assert_eq!(frame_number(frame * 24 + Duration::from_nanos(3), frame), 24);
}
}