Add a Watermark to Video Programmatically with FFmpeg
Add text and logo watermarks to video programmatically: FFmpeg drawtext and overlay filters, placement for any resolution, and one API call per video.
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 well requires 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
ffmpeg -i input.mp4 -vf "drawtext=text='My Brand':x=20:y=20:fontsize=24:fontcolor=white" -c:v libx264 output.mp4Example B: Overlaying a PNG Logo
ffmpeg -i input.mp4 -i logo.png -filter_complex "[0:v][1:v]overlay=10:10" -c:v libx264 -c:a copy output.mp42. 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 Python script that sends a watermarking job for one video. With a default storage provider (paid plans), the result is written to your Cloudflare R2 bucket; without one, the completed job has a signed download URL. Do not send outputPath here: it is not supported for jobs that use input_files and output_files.
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}}" ] }, "webhookUrl": "https://your-app.com/webhooks/xora", "idempotencyKey": "branded-sample-mov"}
headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json"}
response = requests.post( "https://api.xora.sh/v1/jobs", json=payload, headers=headers)
print(response.json())To watermark many videos, run the same script once for each input URL, with a different idempotencyKey for each one. When a job ends, Xora sends a POST to webhookUrl with data.jobId, data.state and, for a completed job, the output files in data.output_files.
5. What it costs
A watermark re-encodes the video, so the cost depends on the length and resolution of the input. Xora bills credits for machine time plus the size of the output, and failed and rejected jobs cost nothing. See pricing for the plans and worked examples.
Related
- Custom FFmpeg commands: the full raw-args reference.
- FFmpeg cheat sheet: the common commands, each as an API call.
- How to resize video with FFmpeg.
Frequently asked questions
How do I add a logo watermark to a video with FFmpeg?
Use the overlay filter with the logo as a second input: ffmpeg -i input.mp4 -i logo.png -filter_complex "[0:v][1:v]overlay=W-w-20:H-h-20" -c:v libx264 -crf 23 -c:a copy output.mp4. W and H are the video size, w and h the logo size, so this puts the logo 20 pixels from the bottom-right corner at any resolution.
How do I add a text watermark with FFmpeg?
Use the drawtext filter: -vf "drawtext=text='My Brand':x=w-tw-20:y=h-th-20:fontsize=24:fontcolor=white@0.6". tw and th are the text width and height. FFmpeg needs a build with libfreetype for drawtext.
How do I watermark many videos automatically?
Send one API job for each video with the same FFmpeg arguments and a different input URL. With Xora, each job returns a signed URL for the result, or writes it to your own R2 bucket, and a webhook tells your app when each one is done.