Concurrent video watermarking with Rust & FFmpeg
In today's fast-paced media landscape, efficient video processing is crucial. Watermarking is a common requirement for branding and content protection, yet applying it sequentially to multiple videos can be a bottleneck. In this post, we explore how to harness Rust's robust concurrency features combined with FFmpeg's powerful processing capabilities to build a concurrent video watermarking tool.
Prerequisites
- Rust toolchain (1.65.0 or higher)
- The FFmpeg executable on your
PATH
Why concurrent watermarking?
Processing a large batch of videos one by one can be time-consuming. By leveraging Rust's lightweight threads, you can handle several videos in parallel, improving throughput and reducing overall processing time. This approach is effective both for personal projects and scalable back-end systems.
Tools and libraries
For our tool, we rely on:
- Rust: Renowned for its performance and strong type safety.
- FFmpeg: The industry-standard for video processing.
- Rust's standard-library process API to invoke FFmpeg without a shell.
These components together offer a robust environment for concurrent media processing.
Environment setup
Before starting, install the following dependencies:
-
Rust: Install via rustup.rs
-
FFmpeg: Install the command-line executable:
Ubuntu/Debian:
sudo apt-get update sudo apt-get install -y ffmpegmacOS:
brew install ffmpegWindows:
Download the pre-built binaries from gyan.dev and add them to your PATH.
No FFmpeg bindings or development headers are needed: the example invokes a separate process.
Building a basic watermarking tool
This example applies watermarks concurrently by invoking the FFmpeg CLI. Provide the two input
videos and watermark image before running it. The -y option replaces existing output files.
use std::process::Command;
use std::thread;
use std::io;
fn watermark_video(input: &str, watermark: &str, output: &str) -> io::Result<()> {
// Execute FFmpeg to overlay the watermark at position (10,10).
let status = Command::new("ffmpeg")
.args(&["-y", "-i", input, "-i", watermark, "-filter_complex", "overlay=10:10", output])
.status()?;
if status.success() {
println!("Successfully processed {}", input);
} else {
return Err(io::Error::new(io::ErrorKind::Other,
format!("FFmpeg failed for {} with {:?}", input, status.code())));
}
Ok(())
}
fn main() -> io::Result<()> {
let jobs = vec![
("video1.mp4", "watermark.png", "video1-watermarked.mp4"),
("video2.mp4", "watermark.png", "video2-watermarked.mp4"),
];
let handles: Vec<_> = jobs.into_iter().map(|(input, watermark, output)| {
let input = input.to_string();
let watermark = watermark.to_string();
let output = output.to_string();
thread::spawn(move || {
if let Err(e) = watermark_video(&input, &watermark, &output) {
eprintln!("Failed to process {}: {}", input, e);
}
})
}).collect();
for handle in handles {
handle.join().expect("Thread panicked");
}
Ok(())
}
Error handling and logging
Command::status() reports process-launch errors separately from FFmpeg's exit status. Check both.
The example logs individual job failures and continues with the remaining jobs; a production batch
runner should also aggregate those outcomes and report an unsuccessful batch to its caller.
Common issues and solutions
When integrating FFmpeg with Rust, you might encounter:
-
Missing executable
- Symptom: Launching the process fails with a file-not-found error.
- Solution: Install FFmpeg and check the worker process's
PATH.
-
Missing codecs or filters
- Symptom: FFmpeg reports an unavailable encoder or filter and exits unsuccessfully.
- Solution: Inspect the installed build with
ffmpeg -encodersandffmpeg -filters.
-
Memory management
- Symptom: Memory leaks or crashes.
- Solution: Bound concurrency; every FFmpeg child process consumes additional memory and CPU.
Performance optimization tips
- Determine the optimal thread count based on available CPU cores.
- Implement robust error recovery mechanisms.
- Monitor resource usage to avoid overloading the system.
- Consider using a thread pool for managing concurrent tasks.
- Ensure proper cleanup of FFmpeg resources after processing.
Integration into your workflow
This tool is suitable as a back-end service for media processing. For instance, in workflows requiring branded videos, you can deploy a microservice that automatically applies watermarks concurrently upon receiving video uploads.
Conclusion
Harnessing Rust's concurrency and FFmpeg's processing power allows you to build robust, high-performance video watermarking tools. This example provides a foundation for scalable media processing applications. For context, Transloadit uses FFmpeg in multiple robots—such as our 🤖/video/encode robot—to deliver efficient media processing solutions.
Happy coding, and may your video workflows be both efficient and robust!
