use std::sync::Arc;
use pixelflow_core::{
Clip, ClipFormat, ClipMedia, ClipResolution, ErrorCategory, ErrorCode, Frame, FrameCount,
FrameExecutor, FrameRate, FrameRequest, Graph, GraphBuilder, PixelFlowError, Rational,
RenderExecutorMap, Result,
};
pub struct SyntheticClip {
graph: Graph,
clip: Clip,
executors: RenderExecutorMap,
}
impl SyntheticClip {
#[must_use]
pub const fn graph(&self) -> &Graph {
&self.graph
}
#[must_use]
pub const fn clip(&self) -> Clip {
self.clip
}
#[must_use]
pub fn executors(&self) -> RenderExecutorMap {
self.executors.clone()
}
}
struct FrameSequenceExecutor {
frames: Arc<[Frame]>,
}
impl FrameExecutor for FrameSequenceExecutor {
fn prepare(&self, request: FrameRequest<'_>) -> Result<Frame> {
self.frames
.get(request.frame_number())
.cloned()
.ok_or_else(|| {
PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("render.frame_out_of_range"),
format!("synthetic frame {} is out of range", request.frame_number()),
)
})
}
}
pub fn synthetic_clip_from_frames(
name: &str,
frames: Vec<Frame>,
frame_rate: Rational,
) -> Result<SyntheticClip> {
let Some(first) = frames.first() else {
return Err(PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("test.empty_synthetic_clip"),
"synthetic clip requires at least one frame",
));
};
for frame in &frames {
if frame.format() != first.format()
|| frame.width() != first.width()
|| frame.height() != first.height()
{
return Err(PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("test.inconsistent_synthetic_clip"),
"all synthetic clip frames must share format and dimensions",
));
}
}
let media = ClipMedia::new(
ClipFormat::Fixed(first.format().clone()),
ClipResolution::Fixed {
width: first.width(),
height: first.height(),
},
FrameCount::Finite(frames.len()),
FrameRate::Cfr(frame_rate),
);
let mut builder = GraphBuilder::new();
let clip = builder.source(name, media);
builder.set_output(clip);
let graph = builder.build();
let mut executors = RenderExecutorMap::new();
executors.insert(
clip.node_id(),
Arc::new(FrameSequenceExecutor {
frames: Arc::from(frames),
}),
);
Ok(SyntheticClip {
graph,
clip,
executors,
})
}
#[cfg(test)]
mod tests {
use pixelflow_core::{Rational, RenderEngine, RenderOptions, WorkerPoolConfig};
use crate::{
EXACT_GOLDEN_TOLERANCE, assert_plane_u8_near, synthetic_clip_from_frames,
synthetic_u8_frame,
};
#[test]
fn synthetic_clip_renders_in_memory_frames_without_media_files() {
let frames = vec![
synthetic_u8_frame("gray8", 2, 1, |_plane, x, _y| {
u8::try_from(x).expect("fixture sample fits u8")
})
.expect("frame 0"),
synthetic_u8_frame("gray8", 2, 1, |_plane, x, _y| {
u8::try_from(x + 10).expect("fixture sample fits u8")
})
.expect("frame 1"),
];
let clip = synthetic_clip_from_frames(
"synthetic",
frames,
Rational {
numerator: 24,
denominator: 1,
},
)
.expect("clip should build");
let rendered = RenderEngine::new(WorkerPoolConfig::new(1))
.render_ordered(
clip.graph().clone(),
clip.executors(),
RenderOptions::new(0, None),
)
.expect("render starts")
.collect::<pixelflow_core::Result<Vec<_>>>()
.expect("render succeeds");
assert_eq!(rendered.len(), 2);
let first = rendered.first().expect("first frame exists");
let second = rendered.get(1).expect("second frame exists");
assert_plane_u8_near(first, 0, &[&[0, 1]], EXACT_GOLDEN_TOLERANCE);
assert_plane_u8_near(second, 0, &[&[10, 11]], EXACT_GOLDEN_TOLERANCE);
}
#[test]
fn synthetic_clip_rejects_empty_inputs() {
let Err(error) = synthetic_clip_from_frames(
"synthetic",
Vec::new(),
Rational {
numerator: 24,
denominator: 1,
},
) else {
panic!("empty synthetic clip should fail");
};
assert_eq!(error.code().as_str(), "test.empty_synthetic_clip");
}
#[test]
fn synthetic_clip_rejects_mismatched_frame_shapes() {
let frames = vec![
synthetic_u8_frame("gray8", 2, 1, |_plane, _x, _y| 0).expect("frame 0"),
synthetic_u8_frame("gray8", 3, 1, |_plane, _x, _y| 1).expect("frame 1"),
];
let Err(error) = synthetic_clip_from_frames(
"synthetic",
frames,
Rational {
numerator: 24,
denominator: 1,
},
) else {
panic!("mismatched synthetic clip should fail");
};
assert_eq!(error.code().as_str(), "test.inconsistent_synthetic_clip");
}
}