使用ffmpeg+python实现自动给视频添加移动水印

张开发
2026/4/8 18:39:39 15 分钟阅读

分享文章

使用ffmpeg+python实现自动给视频添加移动水印
使用ffmpegpython实现自动给视频添加移动水印前言一、下载ffmpeg并配置环境变量二、使用ffmpeg实现四个角轮流出现水印三、下载python并配置环境变量四、使用ffmpegpython实现视频水印随机刷新前言准备工作下载ffmpeg及python并配置好环境变量提前使用图像编辑软件制作好水印的图片要保留透明图层格式为png将水印图片和视频文件放在同一目录下方法一使用ffmpeg实现视频水印四角轮流刷新方法二使用ffmpegpython实现视频水印随机刷新一、下载ffmpeg并配置环境变量ffmpeg下载地址https://www.ffmpeg.org/download.html#build-windowswinR输入“sysdm.cpl”—“高级”—“环境变量”验证环境变量OKwinR输入“ffmpeg -version”二、使用ffmpeg实现四个角轮流出现水印复制以下代码并保存为xxx.bat文件将文件放在视频和水印同一目录下双击执行即可水印图片名称必须改为“logo.png”echo off chcp 65001 nul title GPU 编码 - 四角轮流水印 (修复版) echo echo 功能水印每 10 秒顺时针切换一个角落 echo 顺序左上 - 右上 - 右下 - 左下 echo ⏱️ 周期每 40 秒完成一轮循环 echo :: --- 配置区域 --- set INTERVAL10 set PADDING50 set INPUT_FILE set OUTPUT_FILEoutput_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]overlayxif(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%))):yif(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/winR输入“sysdm.cpl”—“高级”—“环境变量”验证环境变量OK四、使用ffmpegpython实现视频水印随机刷新复制以下代码并保存为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_outputTrue, textTrue, checkTrue) 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. 构建复杂滤镜 (核心优化点) # 策略使用 enablebetween(t, start, end) 在一个滤镜链中完成所有逻辑 # 这样 FFmpeg 只需要启动一次视频只解码一次编码一次。 overlay_exprs [] for i, (start, end, x, y) in enumerate(segments): # 语法: overlayxy:enablebetween(t, start, end) # 注意如果多个区间重叠会有问题但这里是互斥的所以可以直接叠加 # 为了性能我们使用一个复杂的 expression 或者级联 overlay # 级联方式[0][1]overlay...[v1]; [v1][1]overlay...[v2] ... # 但更聪明的方式是使用 eval 表达式动态计算 x,y不过为了代码可读性和稳定性 # 针对几十段的情况级联 overlay 是最稳妥且比切片快得多的方法。 condition fbetween(t\\,{start}\\,{end}) # 转义逗号 expr foverlayx{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 fv{i} # 第一个特殊处理后续都依赖上一个输出 if i 0: part f[0:v][1:v]{expr}[{out_label}] else: prev_label fv{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 fv{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: # 使用 shellFalse 更安全直接传列表 proc subprocess.Popen(cmd, stderrsubprocess.PIPE, universal_newlinesTrue) # 简单的进度监控 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

更多文章