
使用 FFmpeg 的 drawtext 滤镜叠加多行文字水印时,若直接并列调用两个 [0:v] 输入的滤镜,第二行将被忽略;必须通过标签链式传递输出,使第二行作用于第一行处理后的视频流。
使用 ffmpeg 的 `drawtext` 滤镜叠加多行文字水印时,若直接并列调用两个 `[0:v]` 输入的滤镜,第二行将被忽略;必须通过标签链式传递输出,使第二行作用于第一行处理后的视频流。
在 FFmpeg 复杂滤镜(complexFilter)中,每个滤镜链需明确输入与输出的帧数据流关系。当多个 drawtext 滤镜均以 [0:v] 为输入时,FFmpeg 会将其视为并行操作,而默认只保留最后一个滤镜的输出(或因流覆盖导致前序结果被丢弃),因此只有第一行水印可见,第二行实际未生效。
✅ 正确做法是采用滤镜链式连接(label-based chaining):
- 第一个 drawtext 的输出显式标记为一个临时标签(如 [filter_1]);
- 第二个 drawtext 以该标签作为输入(即 [filter_1]),而非重复使用 [0:v];
- 后续所有操作均基于此串联流进行。
以下是修复后的完整代码示例(适配 fluent-ffmpeg):
ffmpeg()
.input(tempLocalPath)
.complexFilter([
// 第一行水印 → 输出标记为 [filter_1]
`[0:v]drawtext=text='${watermarkTextLine1}':x=(w-tw)/2:y=(h-460):fontsize=24:fontcolor=white:fontfile=/Windows/Fonts/arial.ttf[filter_1]`,
// 第二行水印 → 输入来自 [filter_1],叠加在其基础上
`[filter_1]drawtext=text='${watermarkTextLine2}':x=(w-tw)/2:y=(h-200):fontsize=32:fontcolor=red:fontfile=/Windows/Fonts/arial.ttf`
])
.output(
path.join(tempLocalDir, video_cid.replace(".mp4", "-") + "watermarked.mp4")
)
.on("end", () => {
res.download(
path.join(tempLocalDir, video_cid.replace(".mp4", "-") + "watermarked.mp4"),
"video.mp4",
(err) => {
if (err) console.error("Download error:", err);
fs.unlink(tempLocalPath, (unlinkErr) => {
if (unlinkErr) console.error("Cleanup error:", unlinkErr);
});
}
);
})
.inputFormat("mp4")
.run();⚠️ 注意事项:
- 标签命名唯一性:确保中间标签(如 [filter_1])不与其他滤镜冲突,避免解析错误;
- 字体路径兼容性:/Windows/Fonts/arial.ttf 仅适用于 Windows 环境;Linux/macOS 需替换为系统真实路径(如 /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf)或使用 fontfile= 的绝对路径 + URL 编码(含空格时);
- 坐标合理性:y=(h-460) 和 y=(h-200) 需根据视频分辨率动态校验,建议改用相对位置(如 y=h-th-20 表示距底部 20px)提升鲁棒性;
- 编码性能:多层 drawtext 会增加 CPU 负载,高并发场景建议预渲染水印图层后用 overlay 替代。
总结:FFmpeg 复杂滤镜本质是 DAG(有向无环图),显式标签是控制数据流向的唯一可靠方式。切勿假设多个 [0:v] 滤镜会自动叠加——它们是竞争关系,而非组合关系。掌握标签链式写法,是实现多图层、多文字、多特效叠加的基础能力。

















