之前写了好几篇 FFmpeg 的博客,有人私信问我:“为什么抖音、快手下载下来的视频都有水印?水印是上传时加的,还是播放时实时盖上去的?能不能去掉?”我花了两周时间翻资料、看开源方案、自己搭环境模拟了一遍。这篇文章把短视频平台加水印的“幕后流程”拆开讲清楚——不是教你去水印,而是让你理解它是怎么运作的。
一、水印到底加在哪一层?
很多人以为水印是“写在视频画面上的”,没错,但关键在于什么时候加。短视频平台的水印添加分为三种策略:
| 策略 | 时机 | 优点 | 缺点 |
|---|---|---|---|
| 客户端上传前 | 拍摄/剪辑 App 内先加水印,再上传 | 省服务器 CPU | 水印可被本地破解,修改后上传 |
| 服务端接收后 | 上传原始视频 → 服务器重新编码 + 水印 | 安全可控 | 消耗大量计算资源 |
| CDN 边缘实时叠加 | 播放时动态加水印(不改变存储文件) | 灵活(可个性化水印) | 每次播放都要运算,延迟高 |
目前主流平台(抖音、快手、YouTube)是 服务端加水印 为主,同时客户端也可能会预加一层(防止直接录制屏幕绕过)。
二、水印的类型和放置位置
2.1 水印种类
- 静态图片水印:最常见,如抖音右下角的 “抖音” logo + 抖音号。
- 动态水印:随时间变化位置、透明度,增加去除难度。
- 文字水印:包含用户昵称、ID、时间戳。
- 盲水印(数字水印):人眼不可见,但算法可提取,用于版权追踪。
2.2 放置位置
- 角落:右下、左下(不干扰画面主体)
- 边缘飘动:每隔几秒位移一段距离
- 全屏半透明(防录屏):如一些付费课程
三、服务端如何用 FFmpeg 批量加水印
短视频平台每天数亿视频,不可能逐个手动转码,而是通过分布式转码集群 + 自动化的 FFmpeg 命令来完成。
3.1 静态图片水印(最基础)
ffmpeg -i video.mp4 -i logo.png -filter_complex "overlay=W-w-10:H-h-10" -codec:a copy output.mp4
1、W-w-10 表示右下角距离右边 10 像素。
2、H-h-10 表示底部距离 10 像素。
3.2 动态水印(位置飘动)
ffmpeg -i video.mp4 -i logo.png -filter_complex "overlay=x='if(lt(mod(t,10),5),10,W-w-10)':y=H-h-10" output.mp4
每 10 秒内,前 5 秒在左边,后 5 秒在右边。这样直接剪切拼接去水印几乎不可能。
3.3 文字水印(动态显示用户 ID)
ffmpeg -i video.mp4 -vf "drawtext=text='@username':x=10:y=10:fontsize=24:fontcolor=white" output.mp4
实际生产中,text 参数会从数据库动态读取每个视频的上传者信息。
3.4 批量处理 + 持久化水印策略
平台会有一个水印模板配置(JSON 或 DB),转码 worker 从消息队列取任务,动态生成完整的 ffmpeg 命令:
ffmpeg -i input_{id}.mp4 -i /watermark/logo.png -filter_complex "[1:v]scale=100:-1[logo];[0:v][logo]overlay=10:10" -c:a copy -preset veryfast output_{id}.mp4
四、客户端本地预加水印(防抓包)
如果只靠服务端加水印,用户可以抓包拿到上传前的原始视频。所以很多 App 在上传前,先在手机本地用 OpenGL / FFmpeg 加一层水印,再上传。
Android 示例(使用 FFmpeg 命令行,通过 Runtime.exec):
String[] cmd = {"ffmpeg", "-i", inputPath, "-i", watermarkPath,
"-filter_complex", "overlay=10:10", outputPath};
Runtime.getRuntime().exec(cmd);
iOS 示例(使用 AVFoundation):
通过 AVVideoComposition 在像素级叠加水印图层。
踩坑:手机本地编码性能差,加水印会发热、耗电,所以水印不能太复杂,通常只是一个小 logo + 半透明。
五、水印的“不可去除”设计
5.1 多帧随机位置
如果水印位置固定,可用视频修补算法(如 opencv 的 inpainting)自动填补去除。因此平台会让水印在每几秒跳动一次,或随机出现在不同位置,算法很难自动填充。
5.2 半透明混合 + 边缘羽化
直接让水印区域和背景像素值混合,去掉水印会同时破坏背景细节。
5.3 盲水印(频域水印)
把水印信息嵌入到视频的 DCT 系数中,即使画面被裁剪、压缩、加滤镜,仍可提取出原始 ID。YouTube 的 Content ID 就用了类似技术。
六、抖音的水印为什么是“用户 ID”?
抖音下载视频的水印格式通常是:@用户名。这既是品牌宣传,也是溯源手段——一旦有人把带水印的视频发到其他地方,平台可快速定位到最初下载的用户。
这个水印是在服务端合成的,因为用户名是动态读取的。
七、常见问题(平台研发角度)
-
加水印后视频体积变大很多
原因:水印区域破坏了原本的图像预测,编码器难以压缩。
解决:使用 -preset slower 让编码器花更多时间优化,或对水印区域单独降低量化参数。 -
水印被裁剪掉
解决方法:水印位置不要放在四个角,改为离边缘 5% 的位置;或者水印范围扩大、加入文字横跨边缘。 -
实时水印(直播场景)如何处理?
直播不能离线转码,需要在推流过程中实时叠加水印。用 FFmpeg 命令行可直接:
ffmpeg -i rtmp://input -i logo.png -filter_complex overlay=10:10 -f flv rtmp://output
也可以在 OBS 等推流软件中加。
- 不同分辨率视频如何保持水印大小一致?
固定像素的水印在 4K 下显得很小,在 720p 下显得巨大。解决:按原视频宽度的百分比缩放水印。
ffmpeg -i video.mp4 -i logo.png -filter_complex "[1:v]scale=iw*0.1:-1[logo];[0:v][logo]overlay=10:10" output.mp4
- 如何防止用户通过 --cookies 下载原始无水印视频?
平台需要对视频流进行 URL 动态签名,且签名中包含用户身份、时效等,服务端在生成输出流时实时加水印。这样即使拿到地址,也只能拿到带水印的版本。
八、用 FFmpeg 模拟一套完整的水印流程
以下脚本模拟短视频平台的服务端加水印:
#!/bin/bash
# 参数:视频文件、用户昵称、输出文件
INPUT=$1
NICKNAME=$2
OUTPUT=$3
# 生成临时带文字水印的视频
ffmpeg -i "$INPUT" -vf "drawtext=text='@$NICKNAME':x=w-tw-10:y=10:fontsize=24:fontcolor=white:shadowx=2:shadowy=2" temp1.mp4
# 叠加 logo 图片
ffmpeg -i temp1.mp4 -i logo.png -filter_complex "overlay=10:H-h-10" -c:a copy "$OUTPUT"
rm temp1.mp4
执行:./add_watermark.sh myvideo.mp4 "张三" final.mp4
九、道德声明
了解水印原理是为了更好地保护自己的作品,而不是用于去除他人水印。未经授权去除水印并二次分发属于侵权。如果你需要无水印视频素材,请使用 CC0 授权网站或联系原作者购买授权。
十、总结
1、短视频平台的水印不是简单贴一张图,而是综合了:
2、客户端预加水印(防抓包)
3、服务端动态合成(用户 ID、logo、飘动位置)
4、盲水印(版权追踪)
5、实时转码加水印(直播)
作为开发者,可以用 FFmpeg 轻松实现类似功能;作为普通用户,请尊重创作者的水印,不要试图去除。
写完这篇我突然意识到,之前用 yt-dlp 下载的 B 站视频右下角没有水印,并不是因为工具厉害,而是 B 站的水印策略不同——它不压入画面,而是在播放器层面显示,抓取时自然没有。下次再聊这个话题。