ff_filter/graph/graph.rs
1//! [`FilterGraph`] struct definition and push/pull implementations.
2
3use ff_format::{AudioFrame, VideoFrame};
4
5use crate::error::FilterError;
6use crate::filter_inner::FilterGraphInner;
7
8use super::builder::FilterGraphBuilder;
9
10// ── FilterGraph ───────────────────────────────────────────────────────────────
11
12/// An `FFmpeg` libavfilter filter graph.
13///
14/// Constructed via [`FilterGraph::builder()`]. The underlying `AVFilterGraph` is
15/// initialised lazily on the first push call, deriving format information from
16/// the first frame.
17///
18/// # Examples
19///
20/// ```ignore
21/// use ff_filter::FilterGraph;
22///
23/// let mut graph = FilterGraph::builder()
24/// .scale(1280, 720)
25/// .build()?;
26///
27/// // Push decoded frames in …
28/// graph.push_video(0, &video_frame)?;
29///
30/// // … and pull filtered frames out.
31/// while let Some(frame) = graph.pull_video()? {
32/// // use frame
33/// }
34/// ```
35pub struct FilterGraph {
36 pub(crate) inner: FilterGraphInner,
37 pub(crate) output_resolution: Option<(u32, u32)>,
38}
39
40impl std::fmt::Debug for FilterGraph {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 f.debug_struct("FilterGraph").finish_non_exhaustive()
43 }
44}
45
46impl FilterGraph {
47 /// Create a new builder.
48 #[must_use]
49 pub fn builder() -> FilterGraphBuilder {
50 FilterGraphBuilder::new()
51 }
52
53 /// Returns the output resolution produced by this graph's `scale` filter step,
54 /// if one was configured.
55 ///
56 /// When multiple `scale` steps are chained, the **last** one's dimensions are
57 /// returned. Returns `None` when no `scale` step was added.
58 #[must_use]
59 pub fn output_resolution(&self) -> Option<(u32, u32)> {
60 self.output_resolution
61 }
62
63 /// Push a video frame into input slot `slot`.
64 ///
65 /// On the first call the filter graph is initialised using this frame's
66 /// format, resolution, and time base.
67 ///
68 /// # Errors
69 ///
70 /// - [`FilterError::InvalidInput`] if `slot` is out of range.
71 /// - [`FilterError::BuildFailed`] if the graph cannot be initialised.
72 /// - [`FilterError::ProcessFailed`] if the `FFmpeg` push fails.
73 pub fn push_video(&mut self, slot: usize, frame: &VideoFrame) -> Result<(), FilterError> {
74 self.inner.push_video(slot, frame)
75 }
76
77 /// Pull the next filtered video frame, if one is available.
78 ///
79 /// Returns `None` when the internal `FFmpeg` buffer is empty (EAGAIN) or
80 /// at end-of-stream.
81 ///
82 /// # Errors
83 ///
84 /// Returns [`FilterError::ProcessFailed`] on an unexpected `FFmpeg` error.
85 pub fn pull_video(&mut self) -> Result<Option<VideoFrame>, FilterError> {
86 self.inner.pull_video()
87 }
88
89 /// Push an audio frame into input slot `slot`.
90 ///
91 /// On the first call the audio filter graph is initialised using this
92 /// frame's format, sample rate, and channel count.
93 ///
94 /// # Errors
95 ///
96 /// - [`FilterError::InvalidInput`] if `slot` is out of range.
97 /// - [`FilterError::BuildFailed`] if the graph cannot be initialised.
98 /// - [`FilterError::ProcessFailed`] if the `FFmpeg` push fails.
99 pub fn push_audio(&mut self, slot: usize, frame: &AudioFrame) -> Result<(), FilterError> {
100 self.inner.push_audio(slot, frame)
101 }
102
103 /// Pull the next filtered audio frame, if one is available.
104 ///
105 /// Returns `None` when the internal `FFmpeg` buffer is empty (EAGAIN) or
106 /// at end-of-stream.
107 ///
108 /// # Errors
109 ///
110 /// Returns [`FilterError::ProcessFailed`] on an unexpected `FFmpeg` error.
111 pub fn pull_audio(&mut self) -> Result<Option<AudioFrame>, FilterError> {
112 self.inner.pull_audio()
113 }
114}