oximedia_graph/filters/video/
passthrough.rs1use crate::error::{GraphError, GraphResult};
7use crate::frame::FilterFrame;
8use crate::node::{Node, NodeId, NodeState, NodeType};
9use crate::port::{InputPort, OutputPort, PortFormat, PortId, PortType, VideoPortFormat};
10
11pub struct PassthroughFilter {
18 id: NodeId,
19 name: String,
20 state: NodeState,
21 node_type: NodeType,
22 inputs: Vec<InputPort>,
23 outputs: Vec<OutputPort>,
24}
25
26impl PassthroughFilter {
27 #[must_use]
29 pub fn new(id: NodeId, name: impl Into<String>) -> Self {
30 let video_format = PortFormat::Video(VideoPortFormat::any());
31
32 Self {
33 id,
34 name: name.into(),
35 state: NodeState::Idle,
36 node_type: NodeType::Filter,
37 inputs: vec![InputPort::new(PortId(0), "input", PortType::Video)
38 .with_format(video_format.clone())],
39 outputs: vec![
40 OutputPort::new(PortId(0), "output", PortType::Video).with_format(video_format)
41 ],
42 }
43 }
44
45 #[must_use]
49 pub fn new_source(id: NodeId, name: impl Into<String>) -> Self {
50 let video_format = PortFormat::Video(VideoPortFormat::any());
51
52 Self {
53 id,
54 name: name.into(),
55 state: NodeState::Idle,
56 node_type: NodeType::Source,
57 inputs: vec![InputPort::new(PortId(0), "input", PortType::Video)
58 .with_format(video_format.clone())
59 .optional()],
60 outputs: vec![
61 OutputPort::new(PortId(0), "output", PortType::Video).with_format(video_format)
62 ],
63 }
64 }
65}
66
67impl Node for PassthroughFilter {
68 fn id(&self) -> NodeId {
69 self.id
70 }
71
72 fn name(&self) -> &str {
73 &self.name
74 }
75
76 fn node_type(&self) -> NodeType {
77 self.node_type
78 }
79
80 fn state(&self) -> NodeState {
81 self.state
82 }
83
84 fn set_state(&mut self, state: NodeState) -> GraphResult<()> {
85 if !self.state.can_transition_to(state) {
86 return Err(GraphError::InvalidStateTransition {
87 node: self.id,
88 from: self.state.to_string(),
89 to: state.to_string(),
90 });
91 }
92 self.state = state;
93 Ok(())
94 }
95
96 fn inputs(&self) -> &[InputPort] {
97 &self.inputs
98 }
99
100 fn outputs(&self) -> &[OutputPort] {
101 &self.outputs
102 }
103
104 fn process(&mut self, input: Option<FilterFrame>) -> GraphResult<Option<FilterFrame>> {
105 match input {
107 Some(frame) if frame.is_video() => Ok(Some(frame)),
108 Some(_) => Err(GraphError::PortTypeMismatch {
109 expected: "Video".to_string(),
110 actual: "Audio".to_string(),
111 }),
112 None => Ok(None),
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use oximedia_codec::VideoFrame;
121 use oximedia_core::PixelFormat;
122
123 #[test]
124 fn test_passthrough_creation() {
125 let filter = PassthroughFilter::new(NodeId(42), "test_filter");
126
127 assert_eq!(filter.id(), NodeId(42));
128 assert_eq!(filter.name(), "test_filter");
129 assert_eq!(filter.node_type(), NodeType::Filter);
130 assert_eq!(filter.state(), NodeState::Idle);
131 }
132
133 #[test]
134 fn test_passthrough_source() {
135 let filter = PassthroughFilter::new_source(NodeId(0), "source");
136
137 assert_eq!(filter.node_type(), NodeType::Source);
138 assert!(!filter.inputs()[0].required);
139 }
140
141 #[test]
142 fn test_passthrough_ports() {
143 let filter = PassthroughFilter::new(NodeId(0), "test");
144
145 assert_eq!(filter.inputs().len(), 1);
146 assert_eq!(filter.outputs().len(), 1);
147
148 let input = &filter.inputs()[0];
149 assert_eq!(input.port_type, PortType::Video);
150 assert!(input.required);
151
152 let output = &filter.outputs()[0];
153 assert_eq!(output.port_type, PortType::Video);
154 }
155
156 #[test]
157 fn test_passthrough_process() {
158 let mut filter = PassthroughFilter::new(NodeId(0), "test");
159
160 let video = VideoFrame::new(PixelFormat::Yuv420p, 1920, 1080);
162 let frame = FilterFrame::Video(video);
163
164 let result = filter.process(Some(frame)).expect("process should succeed");
166 assert!(result.is_some());
167 assert!(result.expect("value should be valid").is_video());
168 }
169
170 #[test]
171 fn test_passthrough_no_input() {
172 let mut filter = PassthroughFilter::new(NodeId(0), "test");
173
174 let result = filter.process(None).expect("process should succeed");
176 assert!(result.is_none());
177 }
178
179 #[test]
180 fn test_state_transitions() {
181 let mut filter = PassthroughFilter::new(NodeId(0), "test");
182
183 assert!(filter.set_state(NodeState::Processing).is_ok());
185 assert_eq!(filter.state(), NodeState::Processing);
186
187 assert!(filter.set_state(NodeState::Idle).is_ok());
188 assert_eq!(filter.state(), NodeState::Idle);
189
190 assert!(filter.set_state(NodeState::Done).is_ok());
191 assert_eq!(filter.state(), NodeState::Done);
192
193 assert!(filter.set_state(NodeState::Processing).is_err());
195 }
196}