如何在Node.js+Express.js中用FFmpeg添加随机移动视频水印
实现FFmpeg动态随机位置水印(每5秒变换)
需求:基于Node.js + Express + FFmpeg实现视频水印,要求邮箱水印每5秒随机变换一次位置,防止视频盗用。当前已能添加静态水印,但动态位置逻辑未实现。
现有代码
const email = 'name@gmail.com'; app.post('/addwatermark', upload.single('video'), (req, res) => { const fileName = path.basename(req.file.path) + '.mp4'; const outputPath = `public/reduceSize/${fileName}`; const outputPathWithWatermark = `public/reduceSize/${fileName}-wm.mp4`; let watermarkPosition = { x: 10, y: 650 }; // 创建输出目录(如果不存在) const outputDir = path.dirname(outputPathWithWatermark); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } // 发送心跳保持连接 res.write(': ping\n\n'); // 生成随机水印位置的函数 const changeWatermarkPosition = () => { watermarkPosition = { x: Math.floor(Math.random() * 100), y: Math.floor(Math.random() * 100) }; }; // 每5秒变更一次水印位置 setInterval(changeWatermarkPosition, 5000); const watermarkStyle = `-vf drawtext=text='${email}':x=(w-text_w-10):y=(h-text_h-10):fontcolor=white:fontsize=24:shadowcolor=black:shadowx=1:shadowy=1:box=1:y=${watermarkPosition.y}:fontcolor=red:fontsize=24:shadowcolor=black:shadowx=1:shadowy=1:box=1:boxcolor=black@0.5` ffmpeg(req.file.path) .addOption(watermarkStyle) .output(outputPathWithWatermark) .videoCodec('libx264') .audioCodec('aac') .size('50%') .on('error', (err) => { console.error(`FFmpeg error: ${err.message}`); res.status(500).json({ message: err.message }); }) .on('progress', (progress) => { // 每5秒设置水印位置 changeWatermarkPosition(); const mb = progress.targetSize / 1024; const data = { message: `Processing: ${mb.toFixed(2)} MB converted`, watermark: `Adding watermark to video: ${progress.framesProcessed} frames processed` }; console.log(data.message); res.write(`data: ${JSON.stringify(data)}\n\n`); }) .on('end', () => { console.log(`Video successfully converted to ${outputPathWithWatermark}`); // 可在此添加临时文件删除逻辑 }) .run(); });
解决方案
你当前的思路存在问题:Node.js的setInterval和变量修改无法影响已经启动的FFmpeg进程,因为FFmpeg启动后滤镜参数是固定的。必须利用FFmpeg自身的滤镜表达式,实现时间触发的位置动态变化。
核心实现逻辑
通过FFmpeg的drawtext滤镜结合以下函数实现需求:
t:当前视频处理的时间(秒)mod(t,5):计算当前时间与5的余数,余数为0时刚好是5秒的整数倍random(min, max):生成指定范围内的随机数,结合视频宽高w/h和水印文本尺寸text_w/text_h,确保水印不超出画面if(condition, true_val, false_val):仅在每5秒的节点更新位置,其余时间保持当前位置,避免水印闪烁
修改后的完整代码
const email = 'name@gmail.com'; app.post('/addwatermark', upload.single('video'), (req, res) => { const fileName = path.basename(req.file.path) + '.mp4'; const outputPathWithWatermark = `public/reduceSize/${fileName}-wm.mp4`; // 创建输出目录(如果不存在) const outputDir = path.dirname(outputPathWithWatermark); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } // 发送心跳保持连接 res.write(': ping\n\n'); // 动态水印滤镜:每5秒随机变换位置 const watermarkStyle = `-vf drawtext=text='${email}':` + `x='if(mod(t,5)=0, random(0, w-text_w), x)':` + `y='if(mod(t,5)=0, random(0, h-text_h), y)':` + `fontcolor=white:fontsize=24:shadowcolor=black:shadowx=1:shadowy=1:` + `box=1:boxcolor=black@0.5`; ffmpeg(req.file.path) .addOption(watermarkStyle) .output(outputPathWithWatermark) .videoCodec('libx264') .audioCodec('aac') .size('50%') .on('error', (err) => { console.error(`FFmpeg error: ${err.message}`); res.status(500).json({ message: err.message }); }) .on('progress', (progress) => { const mb = progress.targetSize / 1024; const data = { message: `Processing: ${mb.toFixed(2)} MB converted`, watermark: `Adding watermark to video: ${progress.framesProcessed} frames processed` }; console.log(data.message); res.write(`data: ${JSON.stringify(data)}\n\n`); }) .on('end', () => { console.log(`Video successfully converted to ${outputPathWithWatermark}`); // 可在此添加临时文件删除逻辑 }) .run(); });
关键参数说明
x='if(mod(t,5)=0, random(0, w-text_w), x)':每5秒触发一次x坐标随机更新,范围是0到视频宽度减去水印文本宽度,确保水印不超出右侧边界y='if(mod(t,5)=0, random(0, h-text_h), y)':同理y坐标,范围是0到视频高度减去水印文本高度,确保水印不超出底部边界- 移除了原本的
setInterval和changeWatermarkPosition函数,FFmpeg内部已处理位置更新逻辑
内容的提问来源于stack exchange,提问作者Ariful islam
相关产品推荐
相关产品推荐

