1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
//! Apply a filter graph directly, managing the push/pull loop without Pipeline.
//!
//! Demonstrates the low-level filter graph API:
//! - `FilterGraph::push_video()` / `pull_video()` — feed and drain video frames
//! - `FilterGraph::push_audio()` / `pull_audio()` — feed and drain audio frames
//!
//! The `Pipeline` type manages these calls internally; this example shows the
//! underlying loop for callers who need frame-level control (e.g. custom
//! schedulers, real-time processing, or mixing multiple sources manually).
//!
//! # Usage
//!
//! ```bash
//! cargo run --example filter_direct --features "decode encode filter" -- \
//! --input input.mp4 \
//! --output output.mp4
//! ```
use std::{path::Path, process};
use avio::{AudioCodec, AudioDecoder, FilterGraphBuilder, VideoCodec, VideoDecoder, VideoEncoder};
fn main() {
let mut args = std::env::args().skip(1);
let mut input = None::<String>;
let mut output = None::<String>;
while let Some(flag) = args.next() {
match flag.as_str() {
"--input" | "-i" => input = Some(args.next().unwrap_or_default()),
"--output" | "-o" => output = Some(args.next().unwrap_or_default()),
other => {
eprintln!("Unknown flag: {other}");
process::exit(1);
}
}
}
let input = input.unwrap_or_else(|| {
eprintln!("Usage: filter_direct --input <file> --output <file>");
process::exit(1);
});
let output = output.unwrap_or_else(|| {
eprintln!("--output is required");
process::exit(1);
});
let in_name = Path::new(&input)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(&input);
let out_name = Path::new(&output)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(&output);
// ── Probe source ──────────────────────────────────────────────────────────
let probe = match VideoDecoder::open(&input).build() {
Ok(d) => d,
Err(e) => {
eprintln!("Error opening video: {e}");
process::exit(1);
}
};
let src_w = probe.width();
let src_h = probe.height();
let fps = probe.frame_rate();
drop(probe);
let audio_probe = AudioDecoder::open(&input).build().ok();
let (sample_rate, channels) = audio_probe
.as_ref()
.map_or((48_000, 2), |d| (d.sample_rate(), d.channels()));
drop(audio_probe);
// Scale to 1280×720 (or source size if smaller)
let out_w = src_w.min(1280);
let out_h = src_h.min(720);
println!("Input: {in_name} {src_w}×{src_h} {fps:.2} fps");
println!("Output: {out_name} {out_w}×{out_h} scale + volume(0.8)");
println!();
// ── Build filter graph ────────────────────────────────────────────────────
//
// FilterGraphBuilder chains filter operations. The resulting FilterGraph
// exposes push_video/pull_video for the video path and push_audio/pull_audio
// for the audio path on the same graph object.
let mut filter = match FilterGraphBuilder::new()
.scale(out_w, out_h, avio::ScaleAlgorithm::Fast)
.volume(0.8)
.build()
{
Ok(fg) => fg,
Err(e) => {
eprintln!("Error building filter graph: {e}");
process::exit(1);
}
};
// ── Build encoder ─────────────────────────────────────────────────────────
let mut enc_builder = VideoEncoder::create(&output)
.video(out_w, out_h, fps)
.video_codec(VideoCodec::H264);
if sample_rate > 0 {
enc_builder = enc_builder
.audio(sample_rate, channels)
.audio_codec(AudioCodec::Aac);
}
let mut encoder = match enc_builder.build() {
Ok(e) => e,
Err(e) => {
eprintln!("Error building encoder: {e}");
process::exit(1);
}
};
println!("Encoding...");
// ── Video loop: decode → push_video → pull_video → encode ─────────────────
//
// push_video(slot, frame) feeds a raw frame into filter slot 0.
// pull_video() drains one filtered frame; returns None when the filter
// is buffering (e.g. between inputs) — keep feeding until Some is returned.
let mut vdec = match VideoDecoder::open(&input).build() {
Ok(d) => d,
Err(e) => {
eprintln!("Error opening video decoder: {e}");
process::exit(1);
}
};
let mut video_frames: u64 = 0;
loop {
let raw = match vdec.decode_one() {
Ok(Some(f)) => f,
Ok(None) => break,
Err(e) => {
eprintln!("Video decode error: {e}");
process::exit(1);
}
};
if let Err(e) = filter.push_video(0, &raw) {
eprintln!("Filter push_video error: {e}");
process::exit(1);
}
// pull_video may return None if the filter needs more input before
// producing output — loop until drained.
loop {
match filter.pull_video() {
Ok(Some(filtered)) => {
if let Err(e) = encoder.push_video(&filtered) {
eprintln!("Encode push_video error: {e}");
process::exit(1);
}
video_frames += 1;
}
Ok(None) => break, // filter needs more input
Err(e) => {
eprintln!("Filter pull_video error: {e}");
process::exit(1);
}
}
}
}
// ── Audio loop: decode → push_audio → pull_audio → encode ─────────────────
//
// push_audio(slot, frame) feeds a raw audio frame into the filter graph.
// pull_audio() drains one filtered audio frame.
// The same filter graph object handles both video and audio paths.
let mut audio_frames: u64 = 0;
if let Ok(mut adec) = AudioDecoder::open(&input).build() {
loop {
let raw = match adec.decode_one() {
Ok(Some(f)) => f,
Ok(None) => break,
Err(e) => {
eprintln!("Audio decode error: {e}");
break;
}
};
if let Err(e) = filter.push_audio(0, &raw) {
eprintln!("Filter push_audio error: {e}");
break;
}
loop {
match filter.pull_audio() {
Ok(Some(filtered)) => {
if let Err(e) = encoder.push_audio(&filtered) {
eprintln!("Encode push_audio error: {e}");
break;
}
audio_frames += 1;
}
Ok(None) => break,
Err(e) => {
eprintln!("Filter pull_audio error: {e}");
break;
}
}
}
}
}
// ── Finalise ──────────────────────────────────────────────────────────────
if let Err(e) = encoder.finish() {
eprintln!("Error finalising output: {e}");
process::exit(1);
}
let size_str = match std::fs::metadata(&output) {
Ok(m) => {
#[allow(clippy::cast_precision_loss)]
let kb = m.len() as f64 / 1024.0;
if kb < 1024.0 {
format!("{kb:.0} KB")
} else {
format!("{:.1} MB", kb / 1024.0)
}
}
Err(_) => "(unknown size)".to_string(),
};
println!(
"Done. {out_name} {size_str} video_frames={video_frames} audio_frames={audio_frames}"
);
}