use oxideav_core::{Error, Rational, Result, TimeBase};
use crate::duration::{SceneDuration, TimeStamp};
use crate::object::Canvas;
use crate::render::{RenderedFrame, SceneRenderer};
use crate::scene::Scene;
#[derive(Clone, Debug)]
pub struct SourceFormat {
pub canvas: Canvas,
pub framerate: Rational,
pub time_base: TimeBase,
pub sample_rate: u32,
pub duration: SceneDuration,
}
impl SourceFormat {
pub fn from_scene(scene: &Scene) -> Self {
SourceFormat {
canvas: scene.canvas,
framerate: scene.framerate,
time_base: scene.time_base,
sample_rate: scene.sample_rate,
duration: scene.duration,
}
}
}
pub trait SceneSource {
fn format(&self) -> SourceFormat;
fn pull(&mut self) -> Result<Option<RenderedFrame>>;
}
pub trait SceneSink {
fn init(&mut self, format: &SourceFormat) -> Result<()>;
fn push(&mut self, frame: RenderedFrame) -> Result<()>;
fn finalise(&mut self) -> Result<()>;
}
pub fn drive(source: &mut dyn SceneSource, sink: &mut dyn SceneSink) -> Result<()> {
let fmt = source.format();
sink.init(&fmt)?;
let result = drive_loop(source, sink);
let fin = sink.finalise();
result.and(fin)
}
fn drive_loop(source: &mut dyn SceneSource, sink: &mut dyn SceneSink) -> Result<()> {
loop {
match source.pull()? {
Some(frame) => sink.push(frame)?,
None => return Ok(()),
}
}
}
pub struct RenderedSource<R: SceneRenderer> {
scene: Scene,
renderer: R,
next_frame: u64,
total_frames: Option<u64>,
prepared: bool,
}
impl<R: SceneRenderer> RenderedSource<R> {
pub fn new(scene: Scene, renderer: R) -> Self {
let total_frames = scene.frame_count();
RenderedSource {
scene,
renderer,
next_frame: 0,
total_frames,
prepared: false,
}
}
pub fn scene(&self) -> &Scene {
&self.scene
}
pub fn scene_mut(&mut self) -> &mut Scene {
&mut self.scene
}
pub fn next_timestamp(&self) -> TimeStamp {
self.scene.frame_to_timestamp(self.next_frame)
}
}
impl<R: SceneRenderer> SceneSource for RenderedSource<R> {
fn format(&self) -> SourceFormat {
SourceFormat::from_scene(&self.scene)
}
fn pull(&mut self) -> Result<Option<RenderedFrame>> {
if let Some(total) = self.total_frames {
if self.next_frame >= total {
return Ok(None);
}
}
if !self.prepared {
self.renderer.prepare(&self.scene)?;
self.prepared = true;
}
let t = self.next_timestamp();
let frame = self.renderer.render_at(&self.scene, t)?;
self.next_frame += 1;
Ok(Some(frame))
}
}
#[derive(Default)]
pub struct NullSink {
pub frames_received: u64,
pub bytes_received: u64,
pub format_seen: Option<SourceFormat>,
}
impl SceneSink for NullSink {
fn init(&mut self, format: &SourceFormat) -> Result<()> {
self.format_seen = Some(format.clone());
Ok(())
}
fn push(&mut self, frame: RenderedFrame) -> Result<()> {
self.frames_received += 1;
if let Some(v) = frame.video.as_ref() {
self.bytes_received += v.planes.iter().map(|p| p.data.len() as u64).sum::<u64>();
}
self.bytes_received += (frame.audio.len() * std::mem::size_of::<f32>()) as u64;
Ok(())
}
fn finalise(&mut self) -> Result<()> {
Ok(())
}
}
pub struct FnSink<F>
where
F: FnMut(&SourceFormat, RenderedFrame) -> Result<()>,
{
format: Option<SourceFormat>,
cb: F,
}
impl<F> FnSink<F>
where
F: FnMut(&SourceFormat, RenderedFrame) -> Result<()>,
{
pub fn new(cb: F) -> Self {
FnSink { format: None, cb }
}
}
impl<F> SceneSink for FnSink<F>
where
F: FnMut(&SourceFormat, RenderedFrame) -> Result<()>,
{
fn init(&mut self, format: &SourceFormat) -> Result<()> {
self.format = Some(format.clone());
Ok(())
}
fn push(&mut self, frame: RenderedFrame) -> Result<()> {
let fmt = self.format.as_ref().ok_or_else(|| {
Error::invalid("FnSink: push before init — call SceneSink::init first")
})?;
(self.cb)(fmt, frame)
}
fn finalise(&mut self) -> Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::render::StubRenderer;
struct CountingSource {
fmt: SourceFormat,
left: u32,
}
impl SceneSource for CountingSource {
fn format(&self) -> SourceFormat {
self.fmt.clone()
}
fn pull(&mut self) -> Result<Option<RenderedFrame>> {
if self.left == 0 {
return Ok(None);
}
self.left -= 1;
Ok(Some(RenderedFrame::default()))
}
}
#[test]
fn drive_runs_until_source_empty() {
let scene = Scene::default();
let fmt = SourceFormat::from_scene(&scene);
let mut src = CountingSource { fmt, left: 3 };
let mut sink = NullSink::default();
drive(&mut src, &mut sink).unwrap();
assert_eq!(sink.frames_received, 3);
assert!(sink.format_seen.is_some());
}
#[test]
fn rendered_source_stops_at_frame_count() {
let scene = Scene {
duration: SceneDuration::Finite(100),
..Scene::default()
};
assert_eq!(scene.frame_count(), Some(3));
let src = RenderedSource::new(scene, StubRenderer);
assert_eq!(src.next_timestamp(), 0);
}
#[test]
fn fn_sink_forwards_to_closure() {
let mut count = 0u32;
let mut sink = FnSink::new(|_fmt, _frame| {
count += 1;
Ok(())
});
let fmt = SourceFormat::from_scene(&Scene::default());
sink.init(&fmt).unwrap();
sink.push(RenderedFrame::default()).unwrap();
sink.push(RenderedFrame::default()).unwrap();
sink.finalise().unwrap();
assert_eq!(count, 2);
}
#[test]
fn fn_sink_rejects_push_before_init() {
let mut sink = FnSink::new(|_fmt, _frame| Ok(()));
let err = sink.push(RenderedFrame::default()).unwrap_err();
assert!(matches!(err, Error::InvalidData(_)));
}
}