1use std::fmt;
7
8use oximedia_core::{PixelFormat, SampleFormat};
9
10use crate::node::NodeId;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
14pub struct PortId(pub u32);
15
16impl fmt::Display for PortId {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 write!(f, "Port({})", self.0)
19 }
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum PortType {
25 Video,
27 Audio,
29 Data,
31}
32
33impl fmt::Display for PortType {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::Video => write!(f, "Video"),
37 Self::Audio => write!(f, "Audio"),
38 Self::Data => write!(f, "Data"),
39 }
40 }
41}
42
43#[derive(Clone, Debug, PartialEq)]
47pub enum PortFormat {
48 Video(VideoPortFormat),
50 Audio(AudioPortFormat),
52 Data(DataPortFormat),
54 Any,
56}
57
58impl PortFormat {
59 #[must_use]
61 pub fn is_compatible(&self, other: &Self) -> bool {
62 match (self, other) {
63 (Self::Any, _) | (_, Self::Any) => true,
64 (Self::Video(a), Self::Video(b)) => a.is_compatible(b),
65 (Self::Audio(a), Self::Audio(b)) => a.is_compatible(b),
66 (Self::Data(a), Self::Data(b)) => a.is_compatible(b),
67 _ => false,
68 }
69 }
70
71 #[must_use]
73 pub fn port_type(&self) -> Option<PortType> {
74 match self {
75 Self::Video(_) => Some(PortType::Video),
76 Self::Audio(_) => Some(PortType::Audio),
77 Self::Data(_) => Some(PortType::Data),
78 Self::Any => None,
79 }
80 }
81}
82
83#[allow(clippy::derivable_impls)]
84impl Default for PortFormat {
85 fn default() -> Self {
86 Self::Any
87 }
88}
89
90impl fmt::Display for PortFormat {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 Self::Video(v) => write!(f, "Video({v})"),
94 Self::Audio(a) => write!(f, "Audio({a})"),
95 Self::Data(d) => write!(f, "Data({d})"),
96 Self::Any => write!(f, "Any"),
97 }
98 }
99}
100
101#[derive(Clone, Debug, PartialEq)]
103pub struct VideoPortFormat {
104 pub pixel_format: Option<PixelFormat>,
106 pub width: Option<u32>,
108 pub height: Option<u32>,
110}
111
112impl VideoPortFormat {
113 #[must_use]
115 pub fn new(pixel_format: PixelFormat) -> Self {
116 Self {
117 pixel_format: Some(pixel_format),
118 width: None,
119 height: None,
120 }
121 }
122
123 #[must_use]
125 pub fn any() -> Self {
126 Self {
127 pixel_format: None,
128 width: None,
129 height: None,
130 }
131 }
132
133 #[must_use]
135 pub fn with_dimensions(mut self, width: u32, height: u32) -> Self {
136 self.width = Some(width);
137 self.height = Some(height);
138 self
139 }
140
141 #[must_use]
143 pub fn is_compatible(&self, other: &Self) -> bool {
144 let pixel_ok = self.pixel_format.is_none()
145 || other.pixel_format.is_none()
146 || self.pixel_format == other.pixel_format;
147
148 let width_ok = self.width.is_none() || other.width.is_none() || self.width == other.width;
149
150 let height_ok =
151 self.height.is_none() || other.height.is_none() || self.height == other.height;
152
153 pixel_ok && width_ok && height_ok
154 }
155}
156
157impl Default for VideoPortFormat {
158 fn default() -> Self {
159 Self::any()
160 }
161}
162
163impl fmt::Display for VideoPortFormat {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 let pixel = self
166 .pixel_format
167 .map_or("any".to_string(), |p| format!("{p:?}"));
168 let dims = match (self.width, self.height) {
169 (Some(w), Some(h)) => format!("{w}x{h}"),
170 _ => "any".to_string(),
171 };
172 write!(f, "{pixel}@{dims}")
173 }
174}
175
176#[derive(Clone, Debug, PartialEq)]
178pub struct AudioPortFormat {
179 pub sample_format: Option<SampleFormat>,
181 pub sample_rate: Option<u32>,
183 pub channels: Option<u32>,
185}
186
187impl AudioPortFormat {
188 #[must_use]
190 pub fn new(sample_format: SampleFormat) -> Self {
191 Self {
192 sample_format: Some(sample_format),
193 sample_rate: None,
194 channels: None,
195 }
196 }
197
198 #[must_use]
200 pub fn any() -> Self {
201 Self {
202 sample_format: None,
203 sample_rate: None,
204 channels: None,
205 }
206 }
207
208 #[must_use]
210 pub fn with_sample_rate(mut self, rate: u32) -> Self {
211 self.sample_rate = Some(rate);
212 self
213 }
214
215 #[must_use]
217 pub fn with_channels(mut self, channels: u32) -> Self {
218 self.channels = Some(channels);
219 self
220 }
221
222 #[must_use]
224 pub fn is_compatible(&self, other: &Self) -> bool {
225 let format_ok = self.sample_format.is_none()
226 || other.sample_format.is_none()
227 || self.sample_format == other.sample_format;
228
229 let rate_ok = self.sample_rate.is_none()
230 || other.sample_rate.is_none()
231 || self.sample_rate == other.sample_rate;
232
233 let channels_ok =
234 self.channels.is_none() || other.channels.is_none() || self.channels == other.channels;
235
236 format_ok && rate_ok && channels_ok
237 }
238}
239
240impl Default for AudioPortFormat {
241 fn default() -> Self {
242 Self::any()
243 }
244}
245
246impl fmt::Display for AudioPortFormat {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 let format = self
249 .sample_format
250 .map_or("any".to_string(), |s| format!("{s:?}"));
251 let rate = self
252 .sample_rate
253 .map_or("any".to_string(), |r| format!("{r}Hz"));
254 let channels = self
255 .channels
256 .map_or("any".to_string(), |c| format!("{c}ch"));
257 write!(f, "{format}@{rate}/{channels}")
258 }
259}
260
261#[derive(Clone, Debug, PartialEq, Default)]
263pub struct DataPortFormat {
264 pub mime_type: Option<String>,
266}
267
268impl DataPortFormat {
269 #[must_use]
271 pub fn new(mime_type: impl Into<String>) -> Self {
272 Self {
273 mime_type: Some(mime_type.into()),
274 }
275 }
276
277 #[must_use]
279 pub fn any() -> Self {
280 Self { mime_type: None }
281 }
282
283 #[must_use]
285 pub fn is_compatible(&self, other: &Self) -> bool {
286 self.mime_type.is_none() || other.mime_type.is_none() || self.mime_type == other.mime_type
287 }
288}
289
290impl fmt::Display for DataPortFormat {
291 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292 match &self.mime_type {
293 Some(t) => write!(f, "{t}"),
294 None => write!(f, "any"),
295 }
296 }
297}
298
299#[derive(Clone, Debug)]
301pub struct InputPort {
302 pub id: PortId,
304 pub name: String,
306 pub port_type: PortType,
308 pub format: PortFormat,
310 pub required: bool,
312}
313
314impl InputPort {
315 #[must_use]
317 pub fn new(id: PortId, name: impl Into<String>, port_type: PortType) -> Self {
318 let format = match port_type {
319 PortType::Video => PortFormat::Video(VideoPortFormat::any()),
320 PortType::Audio => PortFormat::Audio(AudioPortFormat::any()),
321 PortType::Data => PortFormat::Data(DataPortFormat::any()),
322 };
323
324 Self {
325 id,
326 name: name.into(),
327 port_type,
328 format,
329 required: true,
330 }
331 }
332
333 #[must_use]
335 pub fn with_format(mut self, format: PortFormat) -> Self {
336 self.format = format;
337 self
338 }
339
340 #[must_use]
342 pub fn optional(mut self) -> Self {
343 self.required = false;
344 self
345 }
346}
347
348#[derive(Clone, Debug)]
350pub struct OutputPort {
351 pub id: PortId,
353 pub name: String,
355 pub port_type: PortType,
357 pub format: PortFormat,
359}
360
361impl OutputPort {
362 #[must_use]
364 pub fn new(id: PortId, name: impl Into<String>, port_type: PortType) -> Self {
365 let format = match port_type {
366 PortType::Video => PortFormat::Video(VideoPortFormat::any()),
367 PortType::Audio => PortFormat::Audio(AudioPortFormat::any()),
368 PortType::Data => PortFormat::Data(DataPortFormat::any()),
369 };
370
371 Self {
372 id,
373 name: name.into(),
374 port_type,
375 format,
376 }
377 }
378
379 #[must_use]
381 pub fn with_format(mut self, format: PortFormat) -> Self {
382 self.format = format;
383 self
384 }
385}
386
387#[derive(Clone, Debug, PartialEq, Eq, Hash)]
389pub struct Connection {
390 pub from_node: NodeId,
392 pub from_port: PortId,
394 pub to_node: NodeId,
396 pub to_port: PortId,
398}
399
400impl Connection {
401 #[must_use]
403 pub fn new(from_node: NodeId, from_port: PortId, to_node: NodeId, to_port: PortId) -> Self {
404 Self {
405 from_node,
406 from_port,
407 to_node,
408 to_port,
409 }
410 }
411}
412
413impl fmt::Display for Connection {
414 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
415 write!(
416 f,
417 "{:?}:{:?} -> {:?}:{:?}",
418 self.from_node, self.from_port, self.to_node, self.to_port
419 )
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426
427 #[test]
428 fn test_port_id_display() {
429 let id = PortId(0);
430 assert_eq!(format!("{id}"), "Port(0)");
431 }
432
433 #[test]
434 fn test_port_type_display() {
435 assert_eq!(format!("{}", PortType::Video), "Video");
436 assert_eq!(format!("{}", PortType::Audio), "Audio");
437 assert_eq!(format!("{}", PortType::Data), "Data");
438 }
439
440 #[test]
441 fn test_video_format_compatibility() {
442 let any = VideoPortFormat::any();
443 let yuv420 = VideoPortFormat::new(PixelFormat::Yuv420p);
444 let yuv444 = VideoPortFormat::new(PixelFormat::Yuv444p);
445
446 assert!(any.is_compatible(&yuv420));
447 assert!(yuv420.is_compatible(&any));
448 assert!(yuv420.is_compatible(&yuv420));
449 assert!(!yuv420.is_compatible(&yuv444));
450 }
451
452 #[test]
453 fn test_audio_format_compatibility() {
454 let any = AudioPortFormat::any();
455 let f32_48k = AudioPortFormat::new(SampleFormat::F32).with_sample_rate(48000);
456 let f32_44k = AudioPortFormat::new(SampleFormat::F32).with_sample_rate(44100);
457
458 assert!(any.is_compatible(&f32_48k));
459 assert!(f32_48k.is_compatible(&any));
460 assert!(!f32_48k.is_compatible(&f32_44k));
461 }
462
463 #[test]
464 fn test_port_format_compatibility() {
465 let video = PortFormat::Video(VideoPortFormat::any());
466 let audio = PortFormat::Audio(AudioPortFormat::any());
467 let any = PortFormat::Any;
468
469 assert!(any.is_compatible(&video));
470 assert!(any.is_compatible(&audio));
471 assert!(!video.is_compatible(&audio));
472 }
473
474 #[test]
475 fn test_input_port() {
476 let port = InputPort::new(PortId(0), "input", PortType::Video).optional();
477 assert_eq!(port.id, PortId(0));
478 assert_eq!(port.name, "input");
479 assert_eq!(port.port_type, PortType::Video);
480 assert!(!port.required);
481 }
482
483 #[test]
484 fn test_output_port() {
485 let port = OutputPort::new(PortId(0), "output", PortType::Audio);
486 assert_eq!(port.id, PortId(0));
487 assert_eq!(port.name, "output");
488 assert_eq!(port.port_type, PortType::Audio);
489 }
490
491 #[test]
492 fn test_connection() {
493 let conn = Connection::new(NodeId(0), PortId(0), NodeId(1), PortId(0));
494 assert_eq!(conn.from_node, NodeId(0));
495 assert_eq!(conn.to_node, NodeId(1));
496 }
497}