Ahosting Logo
Knowledge Base

How to Add Watermarks and Overlays with FFmpeg

Overlay position is arithmetic, not a keywordThe variables· capital W and H are the video dimensions· lowercase w and h are the overlay dimensionsSo a corner is an expression· bottom right is W minus w minus a margin· and centring is W minus w over two· which is why hardcoded pixel values break on other videosIt also means re-encodingAn overlay is a filter, and a filter means the video is decoded and encoded again. There is nocopied watermark.

Adding a logo or text to a video is one filter, and the parts that go wrong are the positioning arithmetic and the fact that overlaying forces a re-encode. Both are worth understanding before you build a pipeline around it.

A logo in the corner

ffmpeg -i input.mp4 -i logo.png \
 -filter_complex "overlay=W-w-20:H-h-20" \
 -c:a copy output.mp4

The position is arithmetic, not a keyword. W and H are the video's dimensions; w and h are the overlay's. So W-w-20 means "the video width, minus the logo width, minus 20 pixels": twenty pixels in from the right edge.

The four corners:

overlay=20:20 # top left
overlay=W-w-20:20 # top right
overlay=20:H-h-20 # bottom left
overlay=W-w-20:H-h-20 # bottom right
overlay=(W-w)/2:(H-h)/2 # centred

-c:a copy keeps the audio untouched. Without it FFmpeg re-encodes the audio for no reason, which costs time and quality.

Scale the logo to the video

A logo sized for 1080p is enormous on a 480p rendition and invisible on 4K. Scale it relative to the video instead of using a fixed size:

ffmpeg -i input.mp4 -i logo.png \
 -filter_complex "[1]scale=iw*0.15:-1[wm];[0][wm]overlay=W-w-20:H-h-20" \
 -c:a copy output.mp4

That is not quite what people expect: iw there refers to the logo's own width, so this scales the logo to 15% of its original size rather than of the video. For a genuinely proportional watermark across renditions, generate a correctly-sized logo per rendition rather than trying to express it in one filter. It is simpler and it produces better results.

Transparency

Use a PNG with an alpha channel and FFmpeg respects it. To make an opaque logo semi-transparent:

ffmpeg -i input.mp4 -i logo.png \
 -filter_complex "[1]format=rgba,colorchannelmixer=aa=0.4[wm];[0][wm]overlay=W-w-20:H-h-20" \
 -c:a copy output.mp4

aa=0.4 is 40% opacity. Between 0.3 and 0.5 is usually enough to be visible without competing with the content.

Text instead of an image

ffmpeg -i input.mp4 \
 -vf "drawtext=text='example.com':fontfile=/path/to/font.ttf:fontsize=24:[email protected]:x=w-tw-20:y=h-th-20" \
 -c:a copy output.mp4

Here tw and th are the text's rendered dimensions. You usually need an explicit fontfile path: the error when it is missing complains about the font rather than about the path, which sends people looking in the wrong place.

A subtle shadow or box makes text readable over any background:

drawtext=...:box=1:[email protected]:boxborderw=8

A watermark for part of the video only

ffmpeg -i input.mp4 -i logo.png \
 -filter_complex "[0][1]overlay=W-w-20:H-h-20:enable='between(t,5,15)'" \
 -c:a copy output.mp4

enable takes an expression in seconds. Useful for an intro badge or a lower third that should not sit there for the whole video.

Overlaying always re-encodes

This is the cost worth planning around. You are modifying the picture, so the video cannot be stream-copied. Every frame is decoded, changed and encoded again.

Two consequences. It takes real time, proportional to the length and resolution. And it is a generation of quality loss on already-compressed source, so set a sensible CRF rather than accepting whatever default applies:

ffmpeg -i input.mp4 -i logo.png \
 -filter_complex "overlay=W-w-20:H-h-20" \
 -c:v libx264 -crf 20 -preset medium -c:a copy output.mp4

A slightly lower CRF than usual is reasonable here, because you are encoding material that has already lost detail once.

Watermark once, at the right point

If you produce several renditions, watermark the master and derive the renditions from it, not the reverse. Watermarking each rendition separately means several encodes of the same overlay work, and inconsistent logo sizes.

And if the goal is attribution rather than deterrence, remember a watermark travels with the file wherever it is copied, which is exactly what hotlink protection does not do. Hotlink protection explains the other problem.

Check the result

Play the output rather than trusting the command. Look at the logo on a dark scene and a light scene, and check it is not covering something important in either.

Then check a small-screen rendition specifically. A watermark that reads well at 1080p is frequently unreadable at 480p, and that is the rendition most mobile viewers get.

The same filter combines two videos in place of a video and an image, with one extra decision about length. How to Composite Videos: Picture-in-Picture and Side by Side has the detail.

For text in place of an image the filter is different, and two things account for nearly every failed command. How to Add Text and Timecode Overlays with drawtext has the detail.

Position it so it survives different aspect ratios

A watermark placed at fixed coordinates sits correctly on the video you tested and wrongly on everything else.

ffmpeg -i in.mp4 -i logo.png -filter_complex \
  "[1][0]scale2ref=w=iw*0.12:h=ow/mdar[wm][vid];[vid][wm]overlay=W-w-20:H-h-20" -c:a copy out.mp4

Expressing the position relative to the frame dimensions rather than in pixels means the same command works on any input. The logo stays the same distance from the corner whether the video is wide or tall.

Scaling the logo relative to the video width does the same for size. A fixed pixel logo that looks right on a large frame covers a quarter of a small one, which is the usual complaint after a batch run.

Do the work once, not per delivery

Overlaying forces a re-encode, so applying the watermark at the wrong point in a pipeline multiplies the cost.

Apply it once to a master, then derive every delivery format from that master by copying or by a single encode. Applying it separately to each output means encoding the same content several times.

ffmpeg -i master.mp4 -c copy -movflags +faststart web.mp4
ffprobe -v error -show_entries stream=codec_name,width,height -of csv=p=0 web.mp4

Where a watermark must differ per destination, that is a genuine reason to encode separately. Where it does not, the difference in machine time across a library is substantial and entirely avoidable.

Check it is legible where it will be seen

A watermark judged on a large screen frequently disappears on a phone, which is where most viewing happens.

ffmpeg -i out.mp4 -vf "scale=360:-2" -frames:v 1 -ss 5 check-small.png
ffmpeg -i out.mp4 -frames:v 1 -ss 5 check-full.png

Extract a frame at the size a viewer will actually see and look at it. Fine lines and thin text vanish at small sizes, and a logo with transparency over a light background can disappear entirely.

The practical answer is a simple shape with sufficient contrast, placed over a region that is usually not busy. Testing on three frames from different parts of the video is enough to find the case where it lands on a white background.