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 /// Creates a `FilterGraph` from a pre-built [`FilterGraphInner`].
54 ///
55 /// Used by [`MultiTrackComposer`](crate::MultiTrackComposer) and
56 /// [`MultiTrackAudioMixer`](crate::MultiTrackAudioMixer) to wrap
57 /// source-only filter graphs that need no external `buffersrc`.
58 pub(crate) fn from_prebuilt(inner: FilterGraphInner) -> Self {
59 Self {
60 inner,
61 output_resolution: None,
62 }
63 }
64
65 /// Returns the output resolution produced by this graph's `scale` filter step,
66 /// if one was configured.
67 ///
68 /// When multiple `scale` steps are chained, the **last** one's dimensions are
69 /// returned. Returns `None` when no `scale` step was added.
70 #[must_use]
71 pub fn output_resolution(&self) -> Option<(u32, u32)> {
72 self.output_resolution
73 }
74
75 /// Push a video frame into input slot `slot`.
76 ///
77 /// On the first call the filter graph is initialised using this frame's
78 /// format, resolution, and time base.
79 ///
80 /// # Errors
81 ///
82 /// - [`FilterError::InvalidInput`] if `slot` is out of range.
83 /// - [`FilterError::BuildFailed`] if the graph cannot be initialised.
84 /// - [`FilterError::ProcessFailed`] if the `FFmpeg` push fails.
85 pub fn push_video(&mut self, slot: usize, frame: &VideoFrame) -> Result<(), FilterError> {
86 self.inner.push_video(slot, frame)
87 }
88
89 /// Pull the next filtered video frame, if one is available.
90 ///
91 /// Returns `None` when the internal `FFmpeg` buffer is empty (EAGAIN) or
92 /// at end-of-stream.
93 ///
94 /// # Errors
95 ///
96 /// Returns [`FilterError::ProcessFailed`] on an unexpected `FFmpeg` error.
97 pub fn pull_video(&mut self) -> Result<Option<VideoFrame>, FilterError> {
98 self.inner.pull_video()
99 }
100
101 /// Push an audio frame into input slot `slot`.
102 ///
103 /// On the first call the audio filter graph is initialised using this
104 /// frame's format, sample rate, and channel count.
105 ///
106 /// # Errors
107 ///
108 /// - [`FilterError::InvalidInput`] if `slot` is out of range.
109 /// - [`FilterError::BuildFailed`] if the graph cannot be initialised.
110 /// - [`FilterError::ProcessFailed`] if the `FFmpeg` push fails.
111 pub fn push_audio(&mut self, slot: usize, frame: &AudioFrame) -> Result<(), FilterError> {
112 self.inner.push_audio(slot, frame)
113 }
114
115 /// Pull the next filtered audio frame, if one is available.
116 ///
117 /// Returns `None` when the internal `FFmpeg` buffer is empty (EAGAIN) or
118 /// at end-of-stream.
119 ///
120 /// # Errors
121 ///
122 /// Returns [`FilterError::ProcessFailed`] on an unexpected `FFmpeg` error.
123 pub fn pull_audio(&mut self) -> Result<Option<AudioFrame>, FilterError> {
124 self.inner.pull_audio()
125 }
126}