Documentation Index
Fetch the complete documentation index at: https://mintlify.com/charliethomson/libffmpeg/llms.txt
Use this file to discover all available pages before exploring further.
This example demonstrates how to monitor FFmpeg’s progress in real-time, displaying percentage complete, encoding speed, bitrate, and other metrics during transcoding.
Complete Example
This is the full transcode_with_progress.rs example from the libffmpeg repository:
use std::{path::PathBuf, time::Duration};
use clap::Parser;
use libffmpeg::ffmpeg::ffmpeg;
use tokio_util::{future::FutureExt, sync::CancellationToken};
#[derive(Debug, Parser)]
struct Args {
#[arg(short, long)]
input: PathBuf,
#[arg(short, long)]
output: PathBuf,
#[arg(short, long)]
time: Option<u64>,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
let root_token = CancellationToken::new();
let transcode_token = root_token.child_token();
let exit_token = root_token.child_token();
libsignal::cancel_after_signal(root_token.clone());
let video_duration =
libffmpeg::util::get_duration(&args.input, root_token.child_token()).await?;
let total = args.time.unwrap_or(video_duration.as_secs()) as f64;
let monitor = libffmpeg::libcmd::CommandMonitor::with_capacity(100);
let monitor_fut = {
let mut client = monitor.client.clone();
let exit_token = exit_token.clone();
tokio::spawn(async move {
let mut progress = libffmpeg::ffmpeg::progress::PartialProgress::default();
while let Some(Some(delivery)) =
client.recv().with_cancellation_token(&exit_token).await
{
match delivery {
libffmpeg::libcmd::CommandMonitorMessage::Stdout { line } => {
if !progress.with_line(&line) {
println!("{}[O] {}{}", "\x1b[32m", line, "\x1b[0m");
continue;
}
if let Some(update) = progress.finish() {
println!(
"{}[P] {:.2}% ({} / {}); bitrate={} @ {}fps ({:.1}x realtime) {}",
"\x1b[33m",
100f64 * update.out_time.as_secs_f64() / total,
humantime::format_duration(update.out_time),
humantime::format_duration(Duration::from_secs_f64(total)),
humansize::format_size_i(
update.bitrate,
humansize::DECIMAL.suffix("ps")
),
update.fps,
update.speed,
"\x1b[0m"
);
}
}
libffmpeg::libcmd::CommandMonitorMessage::Stderr { line } => {
eprintln!("{}[E] {}{}", "\x1b[31m", line, "\x1b[0m")
}
}
}
})
};
let result = ffmpeg(transcode_token, &monitor.server, |cmd| {
cmd.arg("-i").arg(&args.input);
cmd.arg("-t").arg(total.to_string());
cmd.arg("-c:v").arg("libx264");
cmd.arg("-preset").arg("fast");
cmd.arg("-crf").arg("23");
cmd.arg("-c:a").arg("aac");
cmd.arg("-b:a").arg("128k");
cmd.arg("-progress").arg("pipe:1");
cmd.arg("-y");
cmd.arg(&args.output);
})
.await?;
exit_token.cancel();
if let Err(e) = monitor_fut.await {
eprintln!("Failed to wait for monitor: {}", e);
}
println!("");
if result
.exit_code
.as_ref()
.map(|exit| exit.success)
.unwrap_or_default()
{
println!("Transcoding completed successfully");
} else {
println!("Transcoding failed: {:#?}", result);
}
Ok(())
}
Breaking Down the Code
1. Setup Cancellation Tokens
let root_token = CancellationToken::new();
let transcode_token = root_token.child_token();
let exit_token = root_token.child_token();
libsignal::cancel_after_signal(root_token.clone());
This creates a cancellation hierarchy:
root_token: The main cancellation source, triggered by Ctrl+C
transcode_token: Child token for the FFmpeg process
exit_token: Child token for the monitoring task
When root_token is cancelled, all child tokens are automatically cancelled.
2. Get Video Duration
let video_duration =
libffmpeg::util::get_duration(&args.input, root_token.child_token()).await?;
let total = args.time.unwrap_or(video_duration.as_secs()) as f64;
Before starting transcoding, we probe the input file to get its duration. This allows us to:
- Calculate percentage progress during transcoding
- Use the full video duration if no time limit is specified
3. Create Command Monitor
let monitor = libffmpeg::libcmd::CommandMonitor::with_capacity(100);
The CommandMonitor is a bounded channel system that captures FFmpeg’s stdout and stderr output. The capacity of 100 means it can buffer up to 100 messages.
Components:
monitor.server: Passed to the FFmpeg function to capture output
monitor.client: Used to receive and process the output lines
4. Spawn Monitoring Task
let monitor_fut = {
let mut client = monitor.client.clone();
let exit_token = exit_token.clone();
tokio::spawn(async move {
let mut progress = libffmpeg::ffmpeg::progress::PartialProgress::default();
while let Some(Some(delivery)) =
client.recv().with_cancellation_token(&exit_token).await
{
// Process messages...
}
})
};
This spawns an asynchronous task that:
- Receives output lines from FFmpeg via the monitor client
- Parses progress information
- Displays formatted progress updates
- Continues until the exit token is cancelled
5. Parse Progress Output
let mut progress = libffmpeg::ffmpeg::progress::PartialProgress::default();
match delivery {
libffmpeg::libcmd::CommandMonitorMessage::Stdout { line } => {
if !progress.with_line(&line) {
// Line wasn't a progress update, print it normally
println!("{}[O] {}{}", "\x1b[32m", line, "\x1b[0m");
continue;
}
if let Some(update) = progress.finish() {
// Complete progress update received, display it
println!("...");
}
}
libffmpeg::libcmd::CommandMonitorMessage::Stderr { line } => {
eprintln!("{}[E] {}{}", "\x1b[31m", line, "\x1b[0m")
}
}
PartialProgress is an accumulator that:
- Receives FFmpeg progress lines one at a time via
with_line()
- Returns
true if the line was a recognized progress field
- Returns
false if the line is regular output (not progress data)
- Produces a complete
Progress snapshot via finish() when a full block is received
cmd.arg("-progress").arg("pipe:1");
This tells FFmpeg to output machine-readable progress information to stdout in this format:
frame=120
fps=30.00
bitrate=1500.0kbits/s
total_size=2048000
out_time_us=4000000
dup_frames=0
drop_frames=0
speed=1.0x
progress=continue
Each progress block ends with a progress=continue or progress=end line.
if let Some(update) = progress.finish() {
println!(
"{}[P] {:.2}% ({} / {}); bitrate={} @ {}fps ({:.1}x realtime) {}",
"\x1b[33m",
100f64 * update.out_time.as_secs_f64() / total,
humantime::format_duration(update.out_time),
humantime::format_duration(Duration::from_secs_f64(total)),
humansize::format_size_i(update.bitrate, humansize::DECIMAL.suffix("ps")),
update.fps,
update.speed,
"\x1b[0m"
);
}
The Progress struct contains:
frame: Number of frames processed
fps: Current encoding speed (frames per second)
bitrate: Current bitrate in bytes per second
total_size: Total output size in bytes
out_time: Elapsed output time (position in the output stream)
dup_frames: Number of duplicated frames
drop_frames: Number of dropped frames
speed: Encoding speed as a multiplier of realtime (2.0 = 2x speed)
progress: State (Continue, End, or Unknown)
Example output:
[P] 45.23% (1m 30s / 3m 20s); bitrate=1.5 Mps @ 30fps (2.1x realtime)
8. Run FFmpeg with Monitoring
let result = ffmpeg(transcode_token, &monitor.server, |cmd| {
cmd.arg("-i").arg(&args.input);
cmd.arg("-progress").arg("pipe:1");
// ... other arguments
}).await?;
The ffmpeg() function (not ffmpeg_slim()) accepts a CommandMonitorServer that captures stdout and stderr, routing them through the monitor channel.
9. Cleanup
exit_token.cancel();
if let Err(e) = monitor_fut.await {
eprintln!("Failed to wait for monitor: {}", e);
}
After FFmpeg completes:
- Cancel the exit token to stop the monitoring task
- Wait for the monitoring task to finish
- Check the final result
Key Components
CommandMonitor
pub struct CommandMonitor {
pub client: CommandMonitorClient,
pub server: CommandMonitorServer,
}
A channel-based system for capturing command output:
- Server: Attached to the FFmpeg process to capture stdout/stderr
- Client: Used by your code to receive the output lines
PartialProgress
pub struct PartialProgress {
// Internal state...
}
impl PartialProgress {
pub fn with_line(&mut self, line: &str) -> bool;
pub fn finish(&self) -> Option<Progress>;
}
An accumulator for parsing FFmpeg’s -progress pipe:1 output line by line:
- Feed lines via
with_line()
- Call
finish() to get a complete Progress snapshot when ready
Progress
pub struct Progress {
pub frame: usize,
pub fps: f64,
pub bitrate: isize,
pub total_size: usize,
pub out_time: Duration,
pub dup_frames: usize,
pub drop_frames: usize,
pub speed: f64,
pub progress: ProgressState,
}
A complete progress snapshot containing all metrics from a single progress block.
Running the Example
# Basic usage
cargo run --example transcode_with_progress -- \
--input input.mp4 \
--output output.mp4
# Transcode only first 30 seconds
cargo run --example transcode_with_progress -- \
--input input.mp4 \
--output output.mp4 \
--time 30
Customizing Progress Display
You can customize how progress is displayed by modifying the monitoring task:
if let Some(update) = progress.finish() {
// Simple percentage only
println!("Progress: {:.1}%", 100.0 * update.out_time.as_secs_f64() / total);
// Or detailed with ETA calculation
let progress_ratio = update.out_time.as_secs_f64() / total;
let elapsed = start_time.elapsed().as_secs_f64();
let eta = elapsed / progress_ratio - elapsed;
println!("{}% complete, ETA: {:.0}s", progress_ratio * 100.0, eta);
}
See Also