使用ffmpeg+python实现自动给视频添加移动水印
前言
准备工作:
下载ffmpeg及python并配置好环境变量
提前使用图像编辑软件制作好水印的图片(要保留透明图层格式为png)
将水印图片和视频文件放在同一目录下
方法一:使用ffmpeg实现视频水印四角轮流刷新
方法二:使用ffmpeg+python实现视频水印随机刷新
一、下载ffmpeg并配置环境变量
ffmpeg下载地址
https://www.ffmpeg.org/download.html#build-windows
win+R输入"sysdm.cpl"---"高级"---"环境变量"


验证环境变量OK
win+R输入"ffmpeg -version"

二、使用ffmpeg实现四个角轮流出现水印
复制以下代码并保存为xxx.bat文件,将文件放在视频和水印同一目录下,双击执行即可,水印图片名称必须改为"logo.png"
@echo off
chcp 65001 >nul
title GPU 编码 - 四角轮流水印 (修复版)
echo ==========================================
echo 🚀 功能:水印每 10 秒顺时针切换一个角落
echo 📍 顺序:左上 -> 右上 -> 右下 -> 左下
echo ⏱️ 周期:每 40 秒完成一轮循环
echo ==========================================
:: --- 配置区域 ---
set "INTERVAL=10"
set "PADDING=50"
set "INPUT_FILE="
set "OUTPUT_FILE=output_corner_loop.mp4"
:: 自动查找视频
for %%f in (*.mp4 *.mov *.avi *.mkv) do (
if "%%f" neq "%OUTPUT_FILE%" (
set "INPUT_FILE=%%f"
goto found
)
)
:found
if "%INPUT_FILE%"=="" (
echo ❌ 未找到视频文件!
pause
exit /b
)
if not exist "logo.png" (
echo ❌ 未找到 logo.png!
pause
exit /b
)
echo ✅ 找到视频:%INPUT_FILE%
echo ✅ 找到水印:logo.png
:: 计算周期
set /a "CYCLE=%INTERVAL% * 4"
set /a "T2=%INTERVAL% * 2"
set /a "T3=%INTERVAL% * 3"
echo ⏳ 正在构建命令...
:: 构建滤镜表达式 (注意:这里去掉了换行符 ^,全部写在一行,防止 CMD 解析错误)
:: 逻辑:
:: X 坐标: 0-T(左), T-2T(右), 2T-3T(右), 3T-4T(左)
:: Y 坐标: 0-T(上), T-2T(上), 2T-3T(下), 3T-4T(下)
set "FILTER=[0:v][1:v]overlay=x='if(lt(mod(t,%CYCLE%),%INTERVAL%),%PADDING%,if(lt(mod(t,%CYCLE%),%T2%),W-w-%PADDING%,if(lt(mod(t,%CYCLE%),%T3%),W-w-%PADDING%,%PADDING%)))':y='if(lt(mod(t,%CYCLE%),%INTERVAL%),%PADDING%,if(lt(mod(t,%CYCLE%),%T2%),%PADDING%,if(lt(mod(t,%CYCLE%),%T3%),H-h-%PADDING%,H-h-%PADDING%)))'"
:: 打印完整命令供调试 (你可以复制这行去命令行手动运行测试)
echo.
echo 🔍 完整执行命令如下 (如需手动调试可复制):
echo ffmpeg -i "%INPUT_FILE%" -i logo.png -filter_complex "%FILTER%" -c:v h264_nvenc -preset p4 -b:v 8M -c:a aac -y "%OUTPUT_FILE%"
echo.
echo ⚡ 开始渲染...
echo.
:: 执行命令
ffmpeg -i "%INPUT_FILE%" -i logo.png -filter_complex "%FILTER%" -c:v h264_nvenc -preset p4 -b:v 8M -c:a aac -y "%OUTPUT_FILE%"
if %errorlevel% equ 0 (
echo.
echo ==========================================
echo ✅ 成功!文件已保存为:%OUTPUT_FILE%
echo ==========================================
) else (
echo.
echo ==========================================
echo ❌ 处理失败!请检查上方红色错误信息。
echo ==========================================
)
pause
如果你只使用方法一,那么后面的内容就不用看了
三、下载python并配置环境变量
python下载地址
https://www.python.org/downloads/windows/
win+R输入"sysdm.cpl"---"高级"---"环境变量"


验证环境变量OK

四、使用ffmpeg+python实现视频水印随机刷新
复制以下代码并保存为xxx.py文件,将文件放在视频和水印同一目录下
import subprocess
import random
import os
import json
import math
import time
import sys
import re
# ================= 配置区域 =================
VIDEO_FILE = None
LOGO_FILE = "logo.png"
OUTPUT_FILE = "output_gpu_optimized.mp4"
INTERVAL = 3 # 每 3 秒跳动一次
PADDING = 50 # 边距
GPU_ENCODER = "h264_nvenc" # NVIDIA显卡: h264_nvenc, AMD: h264_amf, Intel: h264_qsv, 无显卡: libx264
PRESET = "p4" # nvenc预设: p1(最快) - p7(最慢)
# ================= 工具:带颜色的打印 =================
class Colors:
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
BLUE = '\033[94m'
END = '\033[0m'
BOLD = '\033[1m'
def log_info(msg): print(f"{Colors.BLUE}[INFO]{Colors.END} {msg}")
def log_success(msg): print(f"{Colors.GREEN}[SUCCESS]{Colors.END} {msg}")
def log_error(msg): print(f"{Colors.RED}[ERROR]{Colors.END} {msg}")
# ================= 1. 环境检查 =================
log_info("🔍 正在扫描当前目录...")
for f in os.listdir('.'):
if f.lower().endswith(('.mp4', '.mov', '.avi', '.mkv')) and f != OUTPUT_FILE and not f.startswith('~'):
VIDEO_FILE = f
break
if not VIDEO_FILE:
log_error("未找到视频文件!"); input("按回车退出..."); exit()
if not os.path.exists(LOGO_FILE):
log_error(f"未找到水印文件:{LOGO_FILE}"); input("按回车退出..."); exit()
log_success(f"找到视频:{VIDEO_FILE}")
log_success(f"找到水印:{LOGO_FILE}")
# ================= 2. 获取媒体信息 =================
def get_media_info(file_path):
cmd = ['ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_streams', '-select_streams', 'v:0', file_path]
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
data = json.loads(result.stdout)
stream = data['streams'][0]
return float(stream.get('duration', 0)), int(float(stream.get('width', 0))), int(float(stream.get('height', 0)))
except: return 0, 0, 0
vid_dur, vid_w, vid_h = get_media_info(VIDEO_FILE)
if vid_dur == 0:
log_error("无法读取视频信息"); input("按回车退出..."); exit()
logo_dur, logo_w, logo_h = get_media_info(LOGO_FILE)
if logo_w == 0: logo_w, logo_h = 122, 94
log_info(f"视频:{vid_w}x{vid_h}, {vid_dur:.2f}s | 水印:{logo_w}x{logo_h}")
# ================= 3. 生成坐标序列 (构建表达式) =================
num_segments = math.ceil(vid_dur / INTERVAL)
max_x = max(vid_w - logo_w - PADDING, PADDING)
max_y = max(vid_h - logo_h - PADDING, PADDING)
# 存储每个时间段对应的坐标
# 格式: [(start_time, end_time, x, y), ...]
segments = []
current_t = 0.0
for i in range(num_segments):
dur = min(INTERVAL, vid_dur - current_t)
end_t = current_t + dur
x = random.randint(PADDING, max_x)
y = random.randint(PADDING, max_y)
segments.append((current_t, end_t, x, y))
current_t = end_t
log_info(f"已规划 {num_segments} 个随机节点,正在构建单次渲染指令...")
# ================= 4. 构建复杂滤镜 (核心优化点) =================
# 策略:使用 enable='between(t, start, end)' 在一个滤镜链中完成所有逻辑
# 这样 FFmpeg 只需要启动一次,视频只解码一次,编码一次。
overlay_exprs = []
for i, (start, end, x, y) in enumerate(segments):
# 语法: overlay=x=y:enable='between(t, start, end)'
# 注意:如果多个区间重叠会有问题,但这里是互斥的,所以可以直接叠加
# 为了性能,我们使用一个复杂的 expression 或者级联 overlay
# 级联方式:[0][1]overlay=...[v1]; [v1][1]overlay=...[v2] ...
# 但更聪明的方式是使用 eval 表达式动态计算 x,y,不过为了代码可读性和稳定性,
# 针对几十段的情况,级联 overlay 是最稳妥且比切片快得多的方法。
condition = f"between(t\\,{start}\\,{end})"
# 转义逗号
expr = f"overlay=x={x}:y={y}:enable='{condition}'"
overlay_exprs.append(expr)
# 构建完整的 filter_complex
# 输入: 0:v (视频), 1:v (logo)
# 逻辑: 将logo不断叠加到视频流上,每个叠加层只在特定时间生效
filter_chain = ""
input_maps = "[0:v][1:v]"
current_output = "out"
# 如果段数太多,链式调用可能会很长,但比进程启动开销小得多
# 构造: [0:v][1:v]overlay=...[v1]; [v1][1:v]overlay=...[v2]...
complex_filter_parts = []
last_input = "0:v"
for i, expr in enumerate(overlay_exprs):
out_label = f"v{i}"
# 第一个特殊处理,后续都依赖上一个输出
if i == 0:
part = f"[0:v][1:v]{expr}[{out_label}]"
else:
prev_label = f"v{i-1}"
part = f"[{prev_label}][1:v]{expr}[{out_label}]"
complex_filter_parts.append(part)
filter_complex_str = ";".join(complex_filter_parts)
final_map_label = f"v{len(overlay_exprs)-1}"
log_info("🚀 开始单次极速渲染 (无中间文件)...")
start_time = time.time()
# ================= 5. 执行 FFmpeg =================
cmd = [
'ffmpeg', '-hide_banner', '-stats',
'-i', VIDEO_FILE,
'-i', LOGO_FILE,
'-filter_complex', filter_complex_str,
'-map', f'[{final_map_label}]',
'-map', '0:a?', # 如果有音频则映射,没有也不报错
'-c:v', GPU_ENCODER,
'-preset', PRESET,
'-b:v', '8M',
'-c:a', 'aac',
'-y',
OUTPUT_FILE
]
try:
# 使用 shell=False 更安全,直接传列表
proc = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
# 简单的进度监控
for line in proc.stderr:
if 'frame=' in line:
# 简单打印进度,不解析太细以免拖慢
sys.stdout.write('\r' + line.strip())
sys.stdout.flush()
proc.wait()
if proc.returncode == 0:
total_time = time.time() - start_time
print("\n") # 换行
log_success("="*40)
log_success(f"🎉 任务完成!总耗时:{total_time:.2f}秒")
log_success(f"📁 输出:{os.path.abspath(OUTPUT_FILE)}")
log_info("💡 提示:此模式无临时文件,无需清理。")
else:
log_error("渲染失败,请检查上方错误信息。")
except KeyboardInterrupt:
log_warn("\n用户中断。")
except Exception as e:
log_error(f"发生错误: {str(e)}")
input("\n按回车退出...")
在该目录中的地址栏直接输入"cmd"回车,可直接在该目录下打开cmd终端

在终端输入"python xxx.py"执行即可

end