Last updated: February 5, 2025

<span aria-hidden="true" id="concurrent-video-watermarking-with-rust--ffmpeg"></span>

# Concurrent video watermarking with Rust & FFmpeg

![Kevin van Zonneveld](/assets/images/teammates/avatar-kvz-4.jpg?dpl=dpl_3fBRD5jmFtSDABLXGJJyXU1nVXf8)

**Kevin van Zonneveld**

Co-founder · Amsterdam, The Netherlands · Show bio

[](https://x.com/kvz)[](https://github.com/kvz)

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.

<span aria-hidden="true" id="prerequisites"></span>

## Prerequisites

* Rust toolchain (1.65.0 or higher)
* FFmpeg development libraries (FFmpeg 7.1 recommended, 3.4 minimum)
* A C compiler (gcc/clang)
* pkg-config (on Unix-like systems)
* CMake (for some features)

<span aria-hidden="true" id="why-concurrent-watermarking"></span>

## 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.

<span aria-hidden="true" id="tools-and-libraries"></span>

## 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.
* ffmpeg-next crate: Rust bindings for FFmpeg, enabling direct integration of FFmpeg functions into Rust applications.

These components together offer a robust environment for concurrent media processing.

<span aria-hidden="true" id="environment-setup"></span>

## Environment setup

Before starting, install the following dependencies:

1. **Rust**: Install via [rustup.rs⁠](https://rustup.rs)
2. **FFmpeg**: Install the required development libraries:\ <span aria-hidden="true" id="ubuntudebian"></span>

### Ubuntu/Debian:

```bash
apt-get update && apt-get install -y \  
  ffmpeg \  
  libavcodec-dev \  
  libavformat-dev \  
  libavfilter-dev \  
  libavdevice-dev  
```

<span aria-hidden="true" id="macos"></span>

### MacOS:

```bash
brew install ffmpeg  
```

<span aria-hidden="true" id="windows"></span>

### Windows:

Download the pre-built binaries from [gyan.dev⁠](https://www.gyan.dev/ffmpeg/builds/) and add them to your PATH.
3\. **ffmpeg-next**: Add the following to your `Cargo.toml`:

```toml
[dependencies]  
ffmpeg-next = "6.0"  
```

<span aria-hidden="true" id="building-a-basic-watermarking-tool"></span>

## Building a basic watermarking tool

Below is a complete example that initializes the FFmpeg library using the ffmpeg-next crate and concurrently applies a watermark to videos by invoking the FFmpeg CLI. This approach leverages Rust's concurrency while ensuring high-performance processing.

```rust
use ffmpeg_next as ffmpeg;
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 {
        eprintln!("Error processing {}: ffmpeg exited with {:?}", input, status.code());
    }
    Ok(())
}

fn main() -> io::Result<()> {
    // Initialize the FFmpeg library.
    ffmpeg::init().expect("Failed to initialize FFmpeg");

    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(())
}

```

<span aria-hidden="true" id="error-handling-and-logging"></span>

## Error handling and logging

For production scenarios, integrate comprehensive error handling and logging. Use crates such as `log` and `thiserror` to capture and report errors effectively:

```rust
use log::{error, info};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum VideoError {
    #[error("FFmpeg error: {0}")]
    FFmpeg(#[from] ffmpeg::Error),
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("Invalid input path: {0}")]
    InvalidPath(String),
}

fn process_with_logging(input: &str) -> Result<(), VideoError> {
    info!("Starting processing for {}", input);

    if !std::path::Path::new(input).exists() {
        return Err(VideoError::InvalidPath(input.to_string()));
    }

    // Insert video processing logic here.
    info!("Successfully processed {}", input);
    Ok(())
}

```

<span aria-hidden="true" id="common-issues-and-solutions"></span>

## Common issues and solutions

When integrating FFmpeg with Rust, you might encounter:

1. **Missing FFmpeg libraries**
   * Symptom: Linker errors during compilation.
   * Solution: Ensure the development packages are installed as guided in the environment setup.
2. **Version mismatches**
   * Symptom: Runtime errors regarding incompatible FFmpeg versions.
   * Solution: Verify that your FFmpeg installation meets the version requirements of ffmpeg-next.
3. **Memory management**
   * Symptom: Memory leaks or crashes.
   * Solution: Follow RAII patterns and ensure all FFmpeg resources are properly released.

<span aria-hidden="true" id="performance-optimization-tips"></span>

## 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.

<span aria-hidden="true" id="integration-into-your-workflow"></span>

## 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.

<span aria-hidden="true" id="conclusion"></span>

## 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](/docs/robots/video-encode.md) robot—to deliver efficient media processing solutions.

Happy coding, and may your video workflows be both efficient and robust!

\#rust#ffmpeg#concurrency#video-encoding-service#video-encode-robot

### 👩‍💻 Join 20k+ developers

Sign up for our [monthly newsletter](/newsletters.md) to receive direct links to 3 exclusive tech — and 2 product updates. No less, no more.

Your email:

Get access

## File uploading and encoding. Made simple.

Transloadit streamlines file handling for developers, trusted by brands like Coursera and The New York Times. We’re known for a reliable API, top-notch support, and a strong commitment to open source, with projects like [Uppy⁠](https://uppy.io) and [Tus⁠](https://tus.io) setting standards in file processing.

[Sign up](/c/)[Book a Demo](https://survey.typeform.com/to/kRg47Xi5)

No credit card needed · 5 GB included in the free plan

Cancel anytime
