Adding branding elements, dynamic watermarks, or user attribution to videos is a core requirement for video hosting platforms, social networks, and enterprise media systems.

Doing this at scale requires utilizing FFmpeg’s video filter graph. However, compiling the necessary libraries (like libfreetype for rendering text or libpng for rendering overlay images) and scaling the CPU cores to encode them quickly is difficult to manage on standard application hosts.

In this guide, we show you how to write custom FFmpeg watermarking filter arguments and execute them in the cloud using Xora’s API.


1. The FFmpeg Watermark Filters: drawtext and overlay

FFmpeg provides two primary filters for watermarking:

  • drawtext: Renders text directly onto video frames. Useful for dynamic attributes like user IDs, timestamps, or “CONFIDENTIAL” text.
  • overlay: Layers a secondary image (like a transparent PNG logo) on top of the primary video stream.

The CLI syntax

Here is how you would run these commands locally on your terminal:

Example A: Overlaying Text

Terminal window
ffmpeg -i input.mp4 -vf "drawtext=text='My Brand':x=20:y=20:fontsize=24:fontcolor=white" -c:v libx264 output.mp4
Terminal window
ffmpeg -i input.mp4 -i logo.png -filter_complex "[0:v][1:v]overlay=10:10" -c:v libx264 -c:a copy output.mp4

2. Mapping CLI Arguments to Xora’s JSON API

When executing in the cloud via Xora, you map your input sources and parameters to an argument array. This avoids shell escaping headaches and keeps your application code clean.

Request Payload for Text Watermarking

To add a text watermark, use the following payload configuration:

{
"mode": "ffmpeg",
"input_files": {
"in_1": "https://example.com/raw-video.mov"
},
"output_files": {
"out_1": "text-watermarked.mp4"
},
"ffmpeg": {
"args": [
"-i", "{{in_1}}",
"-vf", "drawtext=text='Copyright Xora':x=w-tw-20:y=h-th-20:fontsize=18:fontcolor=white@0.6",
"-c:v", "libx264",
"-crf", "23",
"{{out_1}}"
]
}
}

Note: In the drawtext options, w-tw-20 positioning automatically places the text in the bottom-right corner by subtracting the text width (tw) from the video width (w), leaving a 20-pixel margin.


3. Dynamic Logo Positioning Expressions

When overlaying a PNG logo, you can use mathematical expressions inside the overlay filter to control placement, regardless of the video’s resolution (e.g. 1080p vs. 720p).

Here is how the overlay positioning coordinates map:

  • W: Width of the primary video.
  • H: Height of the primary video.
  • w: Width of the overlay logo.
  • h: Height of the overlay logo.

Placement Cheatsheet

  • Top Left: overlay=20:20
  • Top Right: overlay=W-w-20:20
  • Bottom Left: overlay=20:H-h-20
  • Bottom Right: overlay=W-w-20:H-h-20
  • Center: overlay=(W-w)/2:(H-h)/2

Request Payload for Image Watermarking

To overlay a PNG logo from a URL, define both inputs in your request. In Xora, you can specify multiple inputs and assign them to parameters:

{
"mode": "ffmpeg",
"input_files": {
"in_video": "https://example.com/raw-video.mov",
"in_logo": "https://example.com/logo.png"
},
"output_files": {
"out_1": "image-watermarked.mp4"
},
"ffmpeg": {
"args": [
"-i", "{{in_video}}",
"-i", "{{in_logo}}",
"-filter_complex", "[0:v][1:v]overlay=W-w-30:30",
"-c:v", "libx264",
"-crf", "23",
"{{out_1}}"
]
}
}

4. Production Integration Snippet

Here is a complete Python script demonstrating how to trigger a watermarking job programmatically and save the final video output directly to your Cloudflare R2 bucket:

import requests
payload = {
"mode": "ffmpeg",
"input_files": {
"in_video": "https://storage.googleapis.com/xora-raw/sample.mov",
"in_logo": "https://storage.googleapis.com/xora-raw/logo.png"
},
"output_files": {
"out_video": "branded-output.mp4"
},
"ffmpeg": {
"args": [
"-i", "{{in_video}}",
"-i", "{{in_logo}}",
"-filter_complex", "[0:v][1:v]overlay=20:H-h-20",
"-c:v", "libx264",
"-crf", "22",
"-movflags", "+faststart",
"{{out_video}}"
]
},
"delivery": {
"provider": "r2",
"credentials": {
"access_key_id": "env:R2_ACCESS_KEY",
"secret_access_key": "env:R2_SECRET_KEY"
},
"bucket": "my-r2-cdn",
"path": "videos/branded/"
}
}
headers = {
"Authorization": "Bearer rmux_live_k8a9f...",
"Content-Type": "application/json"
}
response = requests.post(
"https://api.xora.sh/v1/jobs",
json=payload,
headers=headers
)
print(response.json())