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

使用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

相关推荐
琪伦的工具库2 小时前
批量字幕烧录方案对比:脚本 vs 在线工具 vs 桌面工具
音视频
第一程序员2 小时前
Python与数据库:SQLite、MySQL、PostgreSQL详解
python·github
我重来不说话2 小时前
H264监控视频播放器-专业级-自动解析HXVS/HXVF
音视频·播放器·h.264
Cha0DD10 小时前
【由浅入深探究langchain】第二十集-SQL Agent+Human-in-the-loop
人工智能·python·ai·langchain
Cha0DD10 小时前
【由浅入深探究langchain】第十九集-官方的SQL Agent示例
人工智能·python·ai·langchain
智算菩萨11 小时前
【Tkinter】4 Tkinter Entry 输入框控件深度解析:数据验证、密码输入与现代表单设计实战
python·ui·tkinter·数据验证·entry·输入框
七夜zippoe12 小时前
可解释AI:构建可信的机器学习系统——反事实解释与概念激活实战
人工智能·python·机器学习·可解释性·概念激活
YuanDaima204815 小时前
[CrewAI] 第15课|构建一个多代理系统来实现自动化简历定制和面试准备
人工智能·python·面试·agent·crewai
WHS-_-202215 小时前
Python 算法题学习笔记一
python·学习·算法