Skip to main content

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}