第 15 章 视频处理:ffmpeg 与 Whisper 实战

🎬 本章特色:系统掌握 ffmpeg 音视频处理与 Whisper 语音识别两大开源利器,结合 OpenClaw 的 zhz-video-compress、zhz-transcript-summarize、zhz-video-logo、zhz-subtitle 四大技能,从视频压缩、内容转录总结、批量加水印到双语字幕生成,用命令行一站式解决视频后期处理需求。

本章目录:

节 内容 你将学到
1 ffmpeg:音视频处理瑞士军刀 ffmpeg 是什么、音视频编码基础、三平台安装、常用命令速查
2 Whisper:开源语音识别引擎 Whisper 是什么、模型原理与选型、faster-whisper 安装、语音转文字基础
3 视频压缩:zhz-video-compress 技能 视频压缩原理,使用zhz-video-compress 技能批量压缩实战
4 视频转录与总结:zhz-transcript-summarize 技能 ffmpeg 提取音频 + Whisper 转录 + AI 摘要,30 分钟播客变文字要点
5 视频批量加水印:zhz-video-logo 技能 zhz-video-logo 技能、技术原理、批量加水印实战
6 视频双语字幕:zhz-subtitle 技能 技能安装、8 步自动化工作流、中文演讲视频配英文字幕实战
7 总结与进阶 核心回顾、工作流组合、课后练习

1. ffmpeg:音视频处理瑞士军刀

1.1 ffmpeg 是什么

ffmpeg 是一个开源的多媒体处理工具,几乎可以处理所有已知的音频和视频格式。它诞生于 2000 年,由法国程序员 Fabrice Bellard 发起,经过二十多年的社区共同维护,已经成为音视频领域的基石级软件。

你可能没有直接使用过 ffmpeg,但你每天都在享受它的成果。VLC 播放器能播放几乎所有格式的视频,YouTube 能把上传的视频自动转成多种清晰度,这些软件的底层都依赖 ffmpeg 的编解码库。可以说,ffmpeg 是音视频世界的"基础设施",就像 Linux 内核之于操作系统一样。

ffmpeg 的核心特点有三个。第一是全能:它支持数百种音视频编解码器和容器格式,从古老的 AVI 到最新的 AV1,从 WAV 到 MP3,几乎没有它不能处理的格式。第二是跨平台:Windows、macOS、Linux 三大平台都有官方支持,安装后即可使用。第三是命令行驱动:所有操作都通过命令行完成,这意味着你可以轻松编写脚本实现批量处理,也可以让 AI 智能体直接调用它来完成音视频任务。

在 OpenClaw 的工作流中,ffmpeg 扮演着"底层工具"的角色。当你让 OpenClaw 压缩视频、提取音频、加字幕、截取片段时,它在幕后调用的就是 ffmpeg。理解了 ffmpeg 的基本原理和命令,你就能更好地理解 OpenClaw 的音视频处理能力,也能在需要时手动执行更复杂的操作。

1.2 音视频编码基础

在使用 ffmpeg 之前,你需要理解几个核心概念。这些概念技术性强,掌握了它们之后,你就能做出更好的压缩和转换决策。

音频的数字化

我们日常听到的声音是一种连续的模拟信号,计算机无法直接存储和处理这种连续波形,必须经过采样和量化两个步骤,把它转换为二进制数字数据。

采样是在时间轴上对连续声波进行离散化取值。你可以想象成每毫秒对声波"拍快照",拍的次数越多(采样频率越高),还原出的波形就越接近原声。CD 音质的采样频率是 44.1kHz,也就是每秒采样 44100 次。电话语音的采样频率通常只有 8kHz,采样频率较低,所以听起来发闷。

量化是把每个采样点的振幅值映射为有限个离散等级。例如 256 个量化级别对应 2 的 8 次方,即用 8 位二进制数表示一个采样点。量化位数越高,声音的动态范围越大,细节越丰富。CD 音质使用 16 位量化,专业录音棚使用 24 位甚至 32 位。

声道数是指音频信号中独立音轨的数量,也就是录制或播放时有多少路声音同时存在。它决定了声音的"空间感",是平面的单点声源,还是有方位感的立体声场。有单声道,双声道和多声道。双声道的数据量正好是单声道的两倍。

音频的采样示意图
图15-1 音频的采样示意图

音频的量化示意图
图15-2 音频的量化示意图

有了采样频率、量化位数和声道数,就可以计算一段未压缩音频的存储容量:

音频存储容量 (字节) = 采样频率 × 量化位数 × 声道数 × 时长(秒) ÷ 8

举个例子:一段 100 秒的双声道音乐,采样频率 44.1kHz,量化位数 16 位,它的未压缩大小为 44100 × 16 × 2 × 100 ÷ 8 = 17,640,000 字节,约 17 MB。如果是一首 4 分钟的歌曲,未压缩大小约 42 MB。这就是为什么早期 MP3 播放器宣传"把 1000 首歌装进口袋"时需要压缩格式的原因。

图像的数字化

和视频密切相关的另一个基础概念是图像数字化。数字图像由许多彩色或灰度点按横纵排列组成,这些点称为像素(Pixel)。图像尺寸通常表示为"水平像素数 × 垂直像素数",例如 1920×1080 表示水平方向有 1920 个像素,垂直方向有 1080 个像素。

每个像素的颜色用一定位数的二进制数表示,称为颜色深度。颜色深度为 n 位时,可以表示 2 的 n 次方种颜色。常见的颜色深度有 1 位(黑白两色)、8 位(256 级灰度或 256 色)、24 位(约 1677 万色,也叫"真彩色")。颜色深度越大,色彩越丰富,但占用的存储空间也越大。

未压缩图像的一帧存储容量计算公式:

图像存储容量 (字节) = 水平像素数 × 垂直像素数 × 颜色深度 ÷ 8

两个计算示例:一张 90×72 像素的黑白图(颜色深度 1 位),大小为 90 × 72 × 1 ÷ 8 = 810 字节。同样尺寸的 24 位彩色图,大小为 90 × 72 × 24 ÷ 8 = 19,440 字节,约 19 KB。可以看出,同样的分辨率,24 位彩色图的数据量是黑白图的 24 倍。

理解了单帧图像的数据量之后,你就能明白视频数据为什么如此庞大了。一段 1080p(1920×1080)、24 位色深、30fps 的未压缩视频,每秒钟的原始数据量为 1920 × 1080 × 24 × 30 ÷ 8 ≈ 186 MB,一分钟就是 11 GB。这就是为什么视频编码压缩如此重要。

编码(Codec)与容器(Container)的区别

这是初学者最容易混淆的两个概念。用一个比喻来理解:容器是一个"包装盒",编码是"包装里面的东西的压缩方式"。

容器(Container)是文件的"外壳",它决定了文件里能装哪些类型的数据。常见的容器格式有 MP4、MKV、AVI、MOV 等。一个 MP4 文件里可以同时包含视频流、音频流、字幕流和元数据(标题、作者、章节等)。容器就像一个快递箱,里面可以装很多东西。

编码(Codec)则是视频或音频数据的"压缩算法"。正如前面计算的,一段 1 分钟的 1080p 未压缩视频原始数据量超过 11 GB,显然不适合存储和传输。编码器的作用就是把这些原始数据压缩到可接受的大小,同时尽量保持画面质量。常见的视频编码有 H.264(目前最广泛使用)、H.265(也叫 HEVC,压缩效率更高)、VP9(Google 开发,YouTube 主力编码)、AV1(新一代开源编码,压缩效率最高但编码速度慢)。常见的音频编码有 AAC(MP4 容器的标配)、MP3(老牌格式,兼容性最好)、FLAC(无损压缩)、Opus(低延迟,适合实时通信)。

一个 MP4 容器里通常装着 H.264 编码的视频和 AAC 编码的音频。但 MP4 也可以装 H.265 视频,MKV 容器则可以装几乎任何编码的数据。容器和编码是独立的两个维度。

MP4容器

常见容器格式对比

容器 特点 适用场景
MP4 兼容性最强,几乎所有设备和平台都支持 手机拍摄、网页播放、社交平台
MKV 开源格式,封装能力最强;支持多音轨、多字幕、章节信息 高清电影收藏、蓝光提取
MOV 苹果开发,支持专业编码和多轨道编辑信息 影视后期剪辑、苹果生态
AVI 微软 1992 年推出的老格式;结构老旧,不支持多字幕/多音轨,文件体积大 老旧设备兼容、历史资料

常见视频编码对比

编码 特点 适用场景
H.264(AVC) 兼容性极高,几乎所有设备都能硬解;算法成熟、延迟低 直播、视频会议、日常分享
H.265(HEVC) 同等画质下码率比 H.264 降低约 50%,文件体积减半;但编码复杂度高,老旧设备可能不支持 4K/8K 超高清、监控存储

简单记忆:H.265 是 H.264 的升级版,画质相同但体积减半,对设备要求更高。

常见音频格式对比

格式 压缩方式 特点 典型用途
WAV 未压缩 音质高,与原声基本一致,文件大 专业音频存储
MP3 有损压缩 去除人耳不敏感的高频,失真小,文件较小 音乐播放
AAC 有损压缩 同码率下音质优于 MP3,支持多声道 视频默认音频编码
FLAC 无损压缩 还原后与原始数据一致,压缩比例小 高保真音乐
Opus 有损压缩 极低延迟,适合实时通信 语音通话、视频会议

AAC(Advanced Audio Coding) 是 MP3 的继任者,已成为 MP4 等视频容器的默认音频编码。在同码率下,AAC 的音质优于 MP3,128kbps 的 AAC 大约等效于 160kbps 的 MP3。

YUV420P:视频编码的"原材料"

在深入码率概念之前,有必要了解一种重要的色彩采样格式:YUV420P。它是 H.264、H.265 等主流视频编码默认采用的输入格式,理解它有助于你明白为什么视频压缩能做得这么小。

YUV420P 把每个像素的颜色拆分成三个分量:Y 表示亮度(Luma),U 和 V 表示色度(Chroma)。关键设计在于:Y 分量保留全分辨率,而 U 和 V 分量在水平和垂直方向各降采样一半。也就是说,每 2×2 共 4 个像素共享一组 U 和 V 值。

这个设计利用了一个人眼特性:人眼对亮度的敏感度远高于对色度的敏感度。你可以做一个实验,把一张彩色照片转成灰度图(只保留亮度),你会发现照片中的绝大部分信息仍然清晰可辨。YUV420P 正是利用这一点,把色度数据砍掉一半,总数据量降为 RGB 格式的约 50%,而人眼几乎察觉不到差异。

一帧 YUV420P 数据的大小计算很简单:宽 × 高 × 1.5(字节)。比如一帧 1080p 画面的 YUV420P 数据为 1920 × 1080 × 1.5 = 3,110,400 字节,约 3 MB。30fps 的视频每秒就是 90 MB 的原始数据。编码器的任务就是把这些数据进一步压缩到几 MB 甚至更小。

码率(Bitrate)

码率是指每秒钟的数据量,通常用 kbps(千比特每秒)衡量。在相同编码和相同内容下,码率越高,画质越好,但文件也越大。码率是控制文件大小的最直接参数:同样时长的视频,码率减半,文件大小也大致减半。

不同分辨率下,H.264 和 H.265 的推荐码率参考值如下:

分辨率 H.264 推荐码率 H.265 推荐码率 说明
720p (1280×720) 2500 ~ 4000 kbps 1500 ~ 2500 kbps 网络视频、手机观看
1080p (1920×1080) 4000 ~ 8000 kbps 2500 ~ 5000 kbps 日常录制、教学视频
4K (3840×2160) 15000 ~ 25000 kbps 8000 ~ 15000 kbps 高质量创作、大屏播放

可以看到,H.265 在同等画质下所需码率大约是 H.264 的一半,这也是它被称为"体积减半"的原因。如果你的视频主要在电脑和手机上观看,1080p 配 4000 kbps(H.264)或 2500 kbps(H.265)已经能得到很好的画质。

分辨率(Resolution)

分辨率是指画面的像素数量,用“宽 × 高”表示。常见的分辨率有 720p(1280×720)、1080p(1920×1080)、4K(3840×2160)。分辨率越高,画面细节越丰富,但数据量也越大。在压缩视频时,降低分辨率是最有效的“瘦身”手段之一,但也最影响画质。分辨率对数据量的影响大致是平方关系,比如 1080p 降到 720p,像素数只剩约 44%,文件大小也下降到一半左右。

帧率(Frame Rate)

帧率是指每秒钟显示的画面数量,用 fps(frames per second)表示。常见的帧率有 24 fps(传统电影标准)、30 fps(网络视频常用)、60 fps(游戏和高帧率视频)。帧率越高,画面越流畅,需要处理的帧也越多,数据量通常会增加。对于大多数非游戏类视频,30 fps 已经足够。

从原始数据到最终文件的完整流程

把上面所有概念串起来,一个音视频文件从原始数据到最终文件经历以下过程:

原始数据到最终文件的完整流程

理解了这些概念之后,你就能明白 ffmpeg 的命令在做什么了。比如"把视频从 1080p 压缩到 720p,码率设为 2000 kbps,使用 H.265 编码",这句话里的每一个参数你都知道是什么意思了。

1.3 安装 ffmpeg

ffmpeg 在两大平台上的安装都很简单。

Windows 安装

推荐使用 winget 一键安装:

winget install ffmpeg

winget 是微软官方推出的 Windows 包管理器(Windows Package Manager),命令行工具名就是 winget。你可以把它理解成 Windows 上的命令行应用商店。用来搜索、安装、更新、卸载软件。

安装完成后,打开一个新的 PowerShell 窗口,验证安装:

ffmpeg -version

如果看到版本号输出(例如 ffmpeg version 6.1.1),说明安装成功。

如果你更喜欢手动安装,可以到 ffmpeg 官网 https://ffmpeg.org/download.html 下载 Windows 构建包,解压后将 bin 目录添加到系统环境变量 PATH 中。

Linux 安装

Ubuntu 或 Debian 系统:

sudo apt update
sudo apt install ffmpeg

1.4 常用命令

安装好 ffmpeg 之后,下面是日常最常用的命令。建议你逐一尝试,熟悉 ffmpeg 的工作方式。

查看文件信息

# 查看视频的详细编码信息
ffprobe -v quiet -print_format json -show_format -show_streams input.mp4

ffprobe 是 ffmpeg 套件中的"探测器",用来查看媒体文件的编码信息。上面的命令会以 JSON 格式输出视频的所有信息,包括分辨率、帧率、码率、编码格式、时长等。

分辨率:
"coded_width": 1920,
"coded_height": 1024,
帧率:

"avg_frame_rate": "30000/1001"

编码格式:

"codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",

时长:

"duration": "1248.347100",

如果你只需要简洁的概览:

ffmpeg -i input.mp4

ffmpeg 会在最后几行输出视频和音频流的基本信息。例如时长和码率:

Duration: 00:20:48.35, start: 0.000000, bitrate: 201 kb/s

-i 表示指定输入文件。

格式转换

# 把 AVI 转成 MP4
ffmpeg -i input.avi output.mp4

# 把 MP4 转成 MKV(仅更换容器,不重新编码,速度极快)
ffmpeg -i input.mp4 -c copy output.mkv

第二条命令中的 -c copy 是一个非常重要的参数,它告诉 ffmpeg"直接复制音视频数据,不要重新编码"。当源格式和目标格式兼容时(比如 MP4 转 MKV),这样做可以在几秒钟内完成转换,而且画质完全不变。

提取音频

# 从视频中提取音频,保存为 MP3
ffmpeg -i video.mp4 -vn -acodec libmp3lame -q:a 2 audio.mp3

-vn 表示不要视频,-acodec libmp3lame 指定使用 MP3 编码,-q:a 2 设置音频的编码质量级别(范围 0-9,数值越小质量越高)。0-3 通常产生"透明"效果(人耳难以察觉差异),4 是默认值,6 以上质量明显下降。)。

截取视频片段

# 从第 10 秒开始,截取 30 秒
ffmpeg -i input.mp4 -ss 00:00:10 -t 30 -c copy clip.mp4

-ss 指定起始时间,-t 指定时长。加上 -c copy 可以不重新编码,截取速度非常快。

调整分辨率

# 把 1080p 视频缩放到 720p
ffmpeg -i input.mp4 -vf "scale=1280:720" -c:a copy output_720p.mp4

-vf 是视频滤镜参数,scale=1280:720 表示缩放到 1280×720 像素。-c:a copy 表示音频直接复制,不重新编码。

生成缩略图

# 在视频第 5 秒处截取一帧画面保存
ffmpeg -i input.mp4 -ss 00:00:05 -vframes 1 thumbnail.jpg

-ss 表示指定起始时间。-vframes 1 表示只输出 1 帧画面。

掌握了以上这些基础命令,你已经可以用 ffmpeg 处理大部分日常需求了。后面的章节会在此基础上介绍更高级的用法。


2. Whisper:开源语音识别引擎

2.1 Whisper 是什么

Whisper 是 OpenAI 在 2022 年 9 月开源发布的一款自动语音识别(ASR)模型。它的目标是把人类说话的声音自动转换成文字,而且能做到多语言、多口音、高准确率。

在 Whisper 出现之前,高质量的语音识别基本上是商业公司的专利。Google 的语音识别、百度的语音转文字、科大讯飞的语音输入,这些都是闭源的商业产品,要么需要联网调用 API,要么需要购买授权。Whisper 的出现改变了这个局面:它是一个完全开源的模型,任何人都可以免费下载并在本地运行,不需要联网和付费。

Whisper 的训练数据量非常大。OpenAI 从互联网上收集了大约 68 万小时的多语言音频和对应的文字标注,用这些数据训练出了一个 encoder-decoder 架构的 Transformer 模型。作为对比,早期的语音识别模型通常只有几千到几万小时的训练数据。海量的训练数据让 Whisper 拥有了极强的泛化能力:它不仅能识别标准播音腔的普通话,也能应对各种口音、方言腔调、背景噪音、多人对话等复杂场景。

Whisper 的核心能力包括四个方面。第一是多语言识别:支持 99 种语言的语音转文字,中文、英文、日文、法文等主流语言表现尤为出色。第二是语音翻译:可以直接把非英语的语音翻译成英文文字输出,比如输入中文语音,直接输出英文文本。第三是时间戳标注:能精确标注每句话的起止时间,这对生成字幕至关重要。第四是语言检测:即使你不告诉它音频是什么语言,Whisper 也能自动检测出来。

在 OpenClaw 的工作流中,Whisper 承担着"语音转文字"的核心角色。当你让 OpenClaw 转录一段视频、生成字幕、总结播客内容时,第一步就是用 Whisper 把音频转换成文字。后面介绍的zhz-video-compress 技能、zhz-transcript-summarize 技能和 zhz-subtitle 技能就是基于 Whisper 构建的。

2.2 模型选型:从 tiny 到 large-v3

Whisper 提供了 5 种不同大小的模型,从最小的 tiny 到最大的 large-v3,分别适用于不同的场景。选择合适的模型是效率和质量的关键平衡点。

模型 参数量 显存需求 相对速度 中文准确率 英文准确率 适用场景
tiny 39M ~1 GB 最快 一般 较好 快速预览、测试
base 74M ~1 GB 很快 较好 好 日常使用、短视频
small 244M ~2 GB 中等 好 很好 长音频、中文内容
medium 769M ~5 GB 较慢 很好 很好 高精度需求
large-v3 1550M ~10 GB 最慢 最好 最好 专业转录、多语言

选择建议如下。如果你只是转录英文内容或做快速预览,base 模型已经足够好用,速度也很快。如果你需要转录中文内容,建议至少使用 small 模型,因为中文的识别难度比英文高不少,小模型容易出现错字和漏字。如果你追求最高准确率,而且电脑有独立显卡(显存 5 GB 以上),可以选择 medium 或 large-v3。对于大多数 OpenClaw 用户来说,base 或 small 是最实用的选择。

2.3 faster-whisper:快 4 倍的替代实现

原版 Whisper 使用 PyTorch 框架实现,虽然功能完整,但运行速度不是最优的。社区开发者在此基础上开发了 faster-whisper,它使用 CTranslate2 推理引擎,在保持同样准确率的前提下,速度提升了约 4 倍,内存占用也显著降低。

安装 faster-whisper 非常简单:

pip install faster-whisper

安装完成后,你可以用 Python 脚本来转录音频:

from faster_whisper import WhisperModel

# 加载模型(首次运行会自动下载)
model = WhisperModel("base", device="cpu", compute_type="int8")

# 转录音频文件
segments, info = model.transcribe("audio.mp3", language="zh")

# 输出转录结果
print(f"检测到的语言: {info.language} (概率: {info.language_probability:.2f})")
print(f"音频时长: {info.duration:.1f} 秒")
print()

for segment in segments:
    print(f"[{segment.start:.1f}s ~ {segment.end:.1f}s] {segment.text}")

这段代码做了四件事:加载 base 模型,指定 CPU 推理和 int8 量化(减少内存占用),转录指定的音频文件,最后逐段输出转录结果和对应的时间戳。

首次运行时,faster-whisper 会自动从 Hugging Face 下载对应的模型文件。如果你在国内,可能需要设置镜像源来加速下载:

# 设置 Hugging Face 镜像(国内用户)
set HF_ENDPOINT=https://hf-mirror.com
pip install faster-whisper

或者在 Python 脚本的最开头加上环境变量设置:

import os
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"

2.4 基础用法:一段音频转文字

让我们用一个完整的例子来体验 Whisper 的工作流程。假设你有一段名为 meeting.mp3 的会议录音,想要把它转成文字。

from faster_whisper import WhisperModel
import os

os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"   # 国内镜像

# 加载 small 模型(中文效果更好)
model = WhisperModel("small", device="cpu", compute_type="int8")

# 转录音频,自动检测语言
segments, info = model.transcribe("token.m4a")

# 保存转录结果
with open("token.txt", "w", encoding="utf-8") as f:
    for segment in segments:
        f.write(segment.text + "\n")
        print(segment.text)

print(f"转录完成!共 {info.duration:.0f} 秒,语言: {info.language}")
#什么是token, Token是大语言模型处理文本时的最小计量单位, 翻译为中文,名称是词元。

运行后,你会在同目录下得到一个 token.txt 文件,里面是完整的转录文字。整个过程完全在本地完成,不需要联网(首次下载模型除外),也不需要任何 API 密钥。

这就是 Whisper 的核心价值:免费、开源、本地运行、支持多语言。有了它和 ffmpeg 的配合,你就能处理几乎所有的音视频转录需求了。


3. 视频压缩:zhz-video-compress 技能

3.1 需求背景

手机和相机录制的视频文件往往大得惊人。一段 30 分钟的 1080p 手机录像,文件大小轻松达到 3 到 5 GB。如果你经常录制屏幕教学视频、视频会议录像、Vlog 素材,磁盘空间会被迅速吃光。

压缩视频的需求来自几个现实场景。第一是节省存储空间。硬盘再大也有装满的一天,尤其是视频这种动辄几十 GB 的素材,日积月累很快就会爆盘。第二是便于传输和分享。原片体积太大,上传到网盘、发微信、发邮件都慢甚至超限,压缩后的版本传输成本大幅下降。第三是归档整理。长期拍摄积累的素材如果按拍摄时间归档,查找和管理会方便很多。

这些需求共同指向一个动作:批量压缩。一次旅行拍了上百段素材,一次课程录了 30 集,手动逐条压缩不现实。你需要一个能递归扫描目录、自动判断哪些该压、哪些已经压过、安全可靠的批量工具。

3.2 技术原理

视频压缩的本质,是去掉原始数据里的"冗余信息",在可接受的画质损失下大幅减小体积。要理解压缩,需要先理解几个核心概念。

CRF(Constant Rate Factor,恒定质量因子)

CRF(Constant Rate Factor,恒定质量因子)是 H.264/H.265 编码器的质量控制参数,与固定码率相反,它让编码器按画面复杂度动态分配码率,使全片主观画质保持一致。

CRF 是控制压缩质量的核心参数。它的取值范围是 0 到 51,数值越小画质越好,文件也越大。对于 H.264 编码,CRF 23 是默认值,画质良好;CRF 28 画质可接受、文件更小。对于 H.265(HEVC)编码,同样的画质对应的 CRF 值大约比 H.264 低 4 到 6,也就是说 H.264 的 CRF 23 大约等于 H.265 的 CRF 28。

preset(编码预设)

preset 控制编码的速度和效率。取值从快到慢依次是 ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、veryslow。越慢的预设,编码器花更多时间寻找最优压缩方案,最终文件更小但耗时更长。

H.265 为什么比 H.264 更能压

H.265 也叫 HEVC,是 H.264 的下一代标准。在同等画质下,H.265 的码率大约只需 H.264 的一半,文件体积减半。代价是编码复杂度更高、编码速度只有 H.264 的 1/2 到 1/3,且老旧设备可能不支持硬解。所以"体积优先"用 H.265,"兼容性优先"用 H.264。

为什么有的视频压不动

压缩不是万能的。如果文件本来就小(比如不到 10 MB),或者码率已经很低,压缩的意义也不大。这些"压不动"的情况,脚本会分两类处理:文件太小、码率过低在压缩前就自动跳过;压缩后若收益不足 5% 会自动保留原片,不做无意义替换。

批量压缩的两个工程难点

一是幂等性,也就是避免对已经压过的视频重复压缩,否则每跑一次就再转码一次,浪费时间还可能累积画质损失。二是安全性,转码中途失败不能损坏原片。批量工具必须先把结果写到临时文件,校验通过后才落盘,而且默认保留原片、不会替换原视频,压缩收益不足 5% 时宁可保留原片也不做无意义替换。

3.3 zhz-video-compress 技能

zhz-video-compress 是一个 OpenClaw 技能,它把上面的编码原理封装成一个纯 Python 标准库脚本 video_compress.py。脚本不依赖 OpenClaw,只依赖系统里的 ffmpeg 和 ffprobe,可以直接独立运行。

安装技能

在 OpenClaw 中通过 clawhub 安装该技能:

clawhub install zhz-video-compress

安装成功后输出类似:

✔ Installed zhz-video-compress v1.0.0 -> C:\Users\zhang\.openclaw\workspace\skills\zhz-video-compress

安装后技能位于 C:\Users\<用户名>\.openclaw\workspace\skills\zhz-video-compress 目录,其中 scripts/video_compress.py 即为主脚本,SKILL.md 为操作手册。

技能能力

技能支持递归扫描目录或处理单个视频,用 ffmpeg 转 H.265(默认)或 H.264 大幅压缩体积。它的核心价值在于"智能"和"安全"两点:

能力 说明
语义化画质档位 --quality 封装 H265/H264 + CRF,无需了解底层编码参数
速度档位 --speed 封装 preset,在速度与体积间取舍
智能跳过 已压缩、文件太小、码率过低,命中任一即跳过
安全优先 先写临时文件校验通过才落盘,失败或无收益保留原片,默认不替换原视频
分辨率缩放 --max-height 把 4K 等比降到 1080p 等
归档整理 --organize 按拍摄年月归档到 YYYY-MM 子目录
自定义输出 --out-dir 输出到指定目录(默认保留原片,输出到同目录 _compressed 文件)
强制重压 --force 忽略 comment 标志与台账,强制重新压缩
扩展名 支持 15 种常见视频扩展名,可用 --extensions 追加(如 .dav)

画质与速度档位

压缩必须重新编码视频,技能把底层编码参数封装成语义化档位,无需了解 ffmpeg 选项:

档位 编码 CRF 说明
--quality small H265 28 体积优先,默认最常用
--quality balanced H265 24 画质体积平衡
--quality smallest H265 32 最小体积
--quality best H264 18 最佳画质,兼容性也最好

速度档位有 --speed fast(更快,体积略增)、--speed balanced(默认)、--speed slow(更省体积,耗时更长)。高级用户仍可用 --codec / --crf / --preset 直接覆盖档位,但一般无需使用。

智能跳过机制

脚本用三重判定来决定是否跳过某个文件,命中任一即不处理:

  1. comment 标志:压缩时向文件 metadata 的 comment 字段追加标志(如 zhz-video-compress|v1|h265|crf28|medium|时间戳),原有 comment 内容用 ; 拼接保留,跨目录跨机器都能识别,是最可靠的判定;
  2. 台账记录:在扫描目录维护 .video_compress_ledger.json,记录每个文件的 size + duration 指纹;
  3. 体积与码率过滤:文件小于 10 MB(--min-size)或码率低于 300 kbps(--skip-below-kbps)跳过。

压缩后若收益不足 5% 自动保留原片(见"安全设计"一节)。

安全设计

压缩必须先转码,脚本先写到临时文件 .vcompress_tmp_*,完成后校验三件事:能否解析、是否含视频流、时长是否一致。校验通过才落盘,失败保留原片。如果压缩收益不足 5%(新文件体积 ≥ 原片的 95%),也保留原片,不做无意义替换。默认保留原片,不会替换原视频,只有显式加 --replace 才会原地覆盖。中途 Ctrl+C 不会损坏原件。

音频方面,音轨为 AAC 或 MP3 时直接复制不转码,其余格式转成 AAC。字幕方面,MP4 中转成 mov_text,MKV 中直接复制。另外要注意,--replace 原地替换时如果源容器与目标不一致(如 .avi 换成 .mp4),原文件会被替换成新扩展名的文件;只有默认(不加 --replace)时原片才始终保留。

3.4 使用命令验证

下面用一个完整场景演示 zhz-video-compress 的使用。假设你有一批手机拍摄的旅行素材放在 D:\Travel 目录下,全是 1080p 或 4K 的原始视频,总共有几十 GB,现在要压缩整理。

第零步:前置检查

先确认 ffmpeg 和 ffprobe 可用:

ffmpeg -version
ffprobe -version

两个命令都能输出版本号才算就绪。如果系统没装 ffmpeg,先安装(Windows 可用 winget install ffmpeg),或用环境变量 FFMPEG_PATH / FFPROBE_PATH 指定可执行文件路径。同时确认目标目录存在、磁盘空间足够(默认保留原片输出时需要约等于源文件大小的临时空间)。

第一步:dry-run 预览

正式压缩前先预览,确认待处理清单和跳过情况:

python video_compress.py "D:\Travel" --dry-run

这一步不会真正转码,只打印出每个文件的分辨率、编码、体积、码率,以及哪些会被压缩、哪些会被跳过、为什么跳过。首行会显示生效的画质档位和速度档位。

第二步:正式压缩

确认无误后正式执行:

python video_compress.py "D:\Travel"

默认使用 H.265 体积优先档位(CRF 28)。脚本递归扫描目录下所有视频,逐个转码。默认保留原片,输出到同目录 *_compressed 文件,不会替换原视频;也可以用 --out-dir 指定输出目录(注意 --out-dir 不能位于扫描目录内部,否则会重复扫描输出文件):

# 输出到指定目录(原片保留不动)
python video_compress.py "D:\Travel" --out-dir "E:\Compressed"

完成后汇总行显示"压缩完成 N | 跳过 M | 无收益 K | 失败 0",并打印总共节省的空间和百分比。

因为 H.265 转码速度约为 H.264 的一半到三分之一,几十 GB 的素材可能需要数小时,建议后台运行并跟踪进度。

第三步:需要更高画质或降分辨率

如果这批素材里有些是重要场景想保留更高画质,可以改用平衡档位;如果 4K 素材太多,可以顺手降到 1080p:

# 画质与体积平衡
python video_compress.py "D:\Travel" --quality balanced

# 4K 降到 1080p,同时按拍摄年月归档
python video_compress.py "D:\Travel" --max-height 1080 --organize

--organize 会把压缩结果按拍摄年月归档到 YYYY-MM/ 子目录,方便后续查找。

第四步:需要原地替换时才用 --replace

默认情况下原片始终保留,不会替换。只有当你确实想用压缩后的文件覆盖原片时,才显式加 --replace:

python video_compress.py "D:\Travel" --replace

注意,--replace 原地替换时如果源容器与目标不一致(如 .avi 转成 .mp4),原文件会被替换成新扩展名的文件,请谨慎使用。

第五步:幂等性验证

再次用相同命令运行一遍,脚本应全部显示"跳过",证明已压缩的文件不会被重复处理:

python video_compress.py "D:\Travel"

这个特性让技能可以安全地反复运行,中途中断后重新执行也不会出错。如果你确实想对已压缩过的文件重新压缩(比如换了更高画质档位),可以加 --force 忽略 comment 标志与台账,强制重压;--force 只会更新 comment 标志,不会重复堆叠。

3.5 实战案例:用一句提示词压缩整理目录

前几节拆解了编码原理和技能能力,这一节回到在OpenClaw真实使用。你根本不用记那些 ffmpeg 参数和脚本命令,只要在 OpenClaw 的 WebChat 里说一句话:

压缩整理以下目录视频 D:\Travel

下面结合这次真实调用,一步步拆开 agent 在背后是怎么按技能执行的,以及每一步背后的原理。

第零步:匹配技能,读取 SKILL.md

agent 收到这句话后,先用「压缩」「整理目录视频」这类触发词匹配到 zhz-video-compress,然后读取它的 SKILL.md。SKILL.md 写明了这个技能的核心纪律:始终先 dry-run 预览,确认无误再实际执行。这背后是批量转码的一个重要工程判断——转码不可逆且耗时数小时,一旦参数搞错(比如误用了 --replace 覆盖原片、或画质档位选错),代价很大。所以"先预览、再执行"被写进了操作手册,agent 会严格照做,而不是直接开跑。

第一步:dry-run 预览,只算不转

agent 按 SKILL.md 先跑预览:

python {baseDir}/scripts/video_compress.py "D:\Travel" --dry-run

这一步对应第 3.3 节的"智能跳过"机制,但它只打印、不转码。脚本递归扫描 D:\Travel,对每个视频用 ffprobe 读元数据(分辨率、编码、体积、码率),然后按三重判定逐条判断:该压、还是跳过、为什么跳过。首行会显示生效的画质档位和速度档位。

这一步的原理价值在于:它把"哪些要压、哪些不用压"在真正动手前就全部摊开。第 3.2 节提到的"压不动"情况(源已经是 H.265、文件太小、码率过低)在这里被提前识别,避免了几十 GB 素材里混着的那些"压了也白压"的文件白白浪费转码时间。

第二步:正式压缩,安全优先

预览确认无误后,agent 执行正式压缩:

python {baseDir}/scripts/video_compress.py "D:\Travel"

默认使用 H.265 体积优先档位(CRF 28)。脚本递归扫描、逐个转码,完成后汇总行显示"压缩完成 N | 跳过 M | 无收益 K | 失败 0",并打印总共节省的空间和百分比。

这一步落地了第 3.2 节讲的"两个工程难点":

  1. 幂等性:压缩时向文件 metadata 的 comment 字段追加 zhz-video-compress|v1|h265|crf28|medium|时间戳 标志,同时在扫描根目录写台账。已经压过的文件,下次直接命中这两个标记被跳过,不会重复转码。
  2. 安全性:先写到临时文件 .vcompress_tmp_*,转码完成后校验三件事(能否解析、是否含视频流、时长是否一致),通过才落盘;失败或收益不足 5% 一律保留原片。默认不替换原视频,只是另存一个新的 *_compressed 文件。

所以哪怕中途断了、哪怕有一条转码失败,原片都完好无损,重新执行即可,这就是"安全可反复运行"的含义。

第三步:默认保留原片,需要时才加参数

如果 agent 需要按更具体的需求调整,只需在压缩命令上追加对应参数,原理都封装在语义化档位里、无需碰 ffmpeg:

# 画质与体积平衡
python {baseDir}/scripts/video_compress.py "D:\Travel" --quality balanced

# 4K 降到 1080p,同时按拍摄年月归档到 YYYY-MM/
python {baseDir}/scripts/video_compress.py "D:\Travel" --max-height 1080 --organize

# 输出到指定目录(默认保留原片)
python {baseDir}/scripts/video_compress.py "D:\Travel" --out-dir "E:\Compressed"

# 只有显式要求原地替换时才加 --replace(会覆盖原片)
python {baseDir}/scripts/video_compress.py "D:\Travel" --replace

几点对应关系:--quality balanced 对应 H.265 CRF 24,是画质与体积的折中;--max-height 1080 对应第 3.3 节的分辨率缩放,把 4K 等比降到 1080p;--organize 按拍摄年月归档;--out-dir 指定输出目录、原片始终保留;--replace 是唯一会覆盖原片的开关,默认永远不动原片。如果确实要重压已压缩过的文件,再加 --force。

为什么一句话能反复用:最后是幂等性验证。如果 agent 用相同命令再跑一遍,应该全部显示"跳过",证明已压缩的文件没有被重复处理。这一步正是第 3.2 节那句"批量压缩的两个工程难点"之一的兑现——这个技能可以安全地反复运行,中途中断、换台机器、甚至台账文件丢失(还有 comment 标志兜底),都不会把已经压过的视频再压一遍。

4. 视频转录与总结:zhz-transcript-summarize 技能

4.1 场景描述

你手头有一堆的技术视频。同事发来的架构评审录屏、课程平台的几十集分享、收藏夹里上百个稍后再看。每一段都可能藏着你需要的核心知识。你想要:

  1. 快速了解每个视频讲了什么,不用逐帧看完
  2. 抓住其中的核心结论、关键经验和可用方案
  3. 面对大量同类视频,先扫要点,再决定哪些值得细看

这在日常学习和工作中是非常常见的需求。看完一段 30 分钟的视频需要整整 30 分钟,但读同样内容的要点总结只需要几分钟。面对几十上百个视频,如果都从头看到尾,时间成本根本付不起。

传统做法是自己提取音频、跑语音识别、再手动整理要点。每换一个视频就要重复一遍,视频一多就完全不可行。

zhz-transcript-summarize 技能正是为了解决这些问题而生的。

4.2 技能概述

zhz-transcript-summarize 是一个 OpenClaw 技能,它把"本地转写 + 要点摘要"拆成两个互相独立的阶段:

阶段 脚本 作用
阶段一:转录 transcribe.py 音视频转文字,结果按内容哈希缓存
阶段二:摘要 summarize.py + render_html.py 准备摘要输入、渲染 HTML(要点由 AI 生成)

这两个阶段的解耦是技能最核心的设计。转录稿按文件内容哈希缓存,同一文件只转一次。之后你想换角度重新总结,比如第一次总结"讲了什么",第二次只想"提取行动项",第三次想做会议纪要,都只需要重跑阶段二,完全跳过转录,几秒钟就能出结果。

环境依赖

依赖 用途 版本
Python 运行脚本 3.12+
ffmpeg 提取音频 6.1.1
faster-whisper 语音识别(本地) 1.2.1(脚本自动安装)

脚本独立于大模型运行,转录阶段音频永不离开本机,也无需任何 API key。

安装技能

在 OpenClaw 中通过 clawhub 安装该技能:

clawhub install zhz-transcript-summarize

安装成功后输出类似:

✔ Installed zhz-transcript-summarize v1.0.0 -> C:\Users\zhang\skills\zhz-transcript-summarize

4.3 阶段一:转录(缓存命中则跳过)

转录阶段就是把音视频文件转成文字,输出一份带时间戳的转录稿 JSON,并缓存起来。

python {baseDir}/scripts/transcribe.py "D:\podcast\podcast-ep12.mp3" --model small

这条命令内部依次做四件事:提取音频 → 加载模型转录 → 写缓存 → 打印报告。缓存命中时前三步全部跳过,直接跳到第四步。

第一步:提取音频

用 ffmpeg 把音视频统一转成 16kHz 单声道 WAV,输出到临时目录、用完自动清理:

ffmpeg -y -i <源文件> -vn -ac 1 -ar 16000 -c:a pcm_s16le <临时>.wav

-vn 丢掉视频流,-ac 1 单声道,-ar 16000 采样率 16kHz(Whisper 标准输入),-c:a pcm_s16le 无损 PCM。语音识别只关心声音,统一格式保证不同来源文件的输入一致。

第二步:加载模型转录

faster-whisper 加载指定模型,逐段转录,得到带时间戳的 segments 和全文。

第三步:写缓存

按 文件内容 SHA-256 + 模型名 生成缓存键,向 ~/.cache/zhz-transcript/ 写两份文件:

文件 内容
<hash>-<model>.json 元数据、全文、带时间戳的 segments
<hash>-<model>.txt 纯文本全文

缓存命中:未加 --force 且 JSON 已存在时,直接读取缓存、打印报告并返回,完全不调用 ffmpeg 和 Whisper。缓存损坏会提示加 --force 重转。文件名或位置变化不影响命中,只有内容真正改变才会生成新哈希——这正是「同一文件重复运行直接命中」的实现基础。

第四步:打印报告

无论缓存命中还是新转录,最后都输出一份报告:

✅ 缓存命中(无需重新转录)
来源: 1027.mp4
时长: 2:00
语言: zh (置信度 1.0)
段落: 39
缓存 JSON: C:\Users\zhang\.cache\zhz-transcript\22351d8fcfd1ce342e2db34b638218293ab14f0254fde9696e32ea3efc5dfcf6-small.json
纯文本: C:\Users\zhang\.cache\zhz-transcript\22351d8fcfd1ce342e2db34b638218293ab14f0254fde9696e32ea3efc5dfcf6-small.txt

时长超过 1 小时显示 h:mm:ss,否则 m:ss。加 --print 可把全文额外打印到 stdout。

日志约定:INFO 走 stdout,错误以 [错误] 前缀走 stderr,失败时退出码为 1。这几行报告也是 agent 判断「这一步成功了吗」的直接依据——段落数、语言、时长是否符合预期,后续 summarize.py 该传哪个 --model 才能命中缓存,全在这里。

缓存键是文件内容的 SHA-256 加上模型名,所以同一文件重复运行会直接命中缓存,除非显式加 --force 强制重新转录。

缓存默认存放在 ~/.cache/zhz-transcript/ 目录(Windows 即 C:\Users\<user>\.cache\zhz-transcript\)。文件改名或移动位置不影响命中,只有内容真正变了(重新编辑或裁剪)才会生成新哈希重新转录。缓存永久保留,可以自己手动清理。

几个常用选项:

选项 值 说明
--model tiny / base(默认)/ small / medium / large-v3 Whisper 模型大小
--lang auto(默认)/ zh / en / ... 语言,auto 为自动检测
--force 无值 忽略缓存强制重新转录
--device auto / cpu / cuda 计算设备,有 NVIDIA 显卡可用 cuda 加速
--print 无值 把转录全文打印到 stdout
--cache-dir 路径 自定义缓存目录(默认 ~/.cache/zhz-transcript)

对于 30 分钟的中文音频,small 模型在 CPU 上大约需要 5 到 15 分钟。如果你有 NVIDIA 显卡,加 --device cuda 可以大幅加速。首次运行会自动下载 Whisper 模型到 ~/.cache/huggingface/,脚本已内置 HF 镜像,国内无需额外配置。

4.4 阶段二:摘要

转录完成后,进入摘要阶段。这一步先用 summarize.py 准备摘要输入,再由 AI 生成要点,最后可选用 render_html.py 渲染成可独立打开的 HTML。

第一步:准备摘要输入

python {baseDir}/scripts/summarize.py "D:\podcast\podcast-ep12.mp3" \
  --focus "云原生架构选型理由与踩坑经验" \
  --audience "开发者" \
  --format key_summary \
  --length medium \
  --lang zh

如果转录时用了非默认模型(如 --model small),这里也要传 --model small 才能精确命中对应缓存,否则会回退到同内容哈希的任一缓存(脚本会给出警告)。

这一步是确定性的准备工作,它本身不做摘要生成,而是在 summaries/<name>/ 目录下产出几个文件:

文件 说明
context.md 转录稿全文(已按术语表更正,若提供了术语表)
summary_prompt.md 模板 + 本次参数(焦点 / 受众 / 长度 / 语言)
glossary_changes.md 术语更正记录(仅当提供术语表且有命中)
timeline.md 时间轴骨架(超过 20 分钟自动生成)
subtitles.srt / .vtt 字幕(仅当加了 --format-sub;基于原始转录稿,不含术语纠错)

第二步:生成摘要

AI 读取 context.md 全文和 summary_prompt.md 的模板参数,按模板生成摘要,写入 summaries/<name>/summary.md。这一步由当前代理会话的模型完成。

第三步(可选):输出 HTML

python {baseDir}/scripts/render_html.py "summaries/<name>/summary.md" --title "会议记录"

把摘要渲染成同目录同名的 .html,双击即可用浏览器独立打开。它用纯 Python 标准库实现,内联 CSS、无外部依赖,离线可用。常用选项有:--title 指定页面标题(默认取第一个一级标题或文件名)、--out 指定输出路径、--meta 在标题下方加元信息、--lang 设置 HTML 的 lang 属性(默认 zh)。

4.5 实战:用一句提示词总结视频

前面几小节都是手动敲 transcribe.py 和 summarize.py,那是为了看清技能内部的实现。真实使用中你根本不用记这些命令,只要在 OpenClaw 的 WebChat 里发一句话:

请总结视频内容:C:\Users\zhang\Videos\Captures\1027.mp4

剩下的交给 agent。下面结合这次真实调用,一步步拆开 agent 在背后是怎么按技能执行的,以及每一步的原理。

第零步:匹配技能,读取 SKILL.md

agent 收到提示词后,先用「总结视频内容」「总结」这类触发词匹配到 zhz-transcript-summarize,然后读取它的 SKILL.md。SKILL.md 是技能的完整操作手册,写清了两阶段流程、三个脚本的用法、缓存规则、文件结构。agent 不靠临时发挥,而是严格照手册执行。这是 OpenClaw 技能机制的核心:把可复用流程固化成「文档 + 脚本」,agent 只做编排者,把各阶段串起来。

第一步:阶段一,转录

agent 按 SKILL.md 执行 transcribe.py,把视频路径传进去:

python {baseDir}/scripts/transcribe.py "C:\Users\zhang\Videos\Captures\1027.mp4" --model small --lang zh

这一步对应第 4.3 节,脚本内部做了四件事:

  1. 用 ffmpeg 把视频里的音频轨道抽出来,转成 16kHz 单声道 wav(语音识别的标准输入)。
  2. 加载 Whisper 模型,逐段转录,得到带时间戳的 segments 和全文。
  3. 计算文件内容的 SHA-256,连同模型名组成缓存键,把结果(元数据、全文、segments)写入缓存 JSON,再输出一份 .txt。
  4. 打印来源、时长、语言、段落数等报告信息。

本次视频约 2 分钟,识别出 39 个段落、语言为中文,很快完成。这里有个值得注意的实现细节:脚本会优先尝试 CUDA(GPU),失败时自动回退到 CPU,比如检测到缺少 cublas dll 就会静默降级。鲁棒性被下沉到了脚本里,agent 和用户都不用关心这台机器有没有显卡。

第二步:阶段二,准备摘要输入

转录完成后,agent 继续按 SKILL.md 执行 summarize.py:

python {baseDir}/scripts/summarize.py "C:\Users\zhang\Videos\Captures\1027.mp4" --model small --format key_summary --lang zh

这一步对应第 4.4 节的第一步。注意它本身不生成任何摘要,只做确定性的准备工作:从缓存按内容哈希找到转录稿,在 summaries/1027/ 目录下产出 context.md(转录全文)和 summary_prompt.md(模板 + 本次参数)。脚本仍然是纯本地确定性逻辑,没有大模型参与。

第三步:模型生成摘要

这步才是 AI 真正上场的地方。agent 读取 summaries/1027/context.md 的转录全文,以及 summary_prompt.md 里的模板和参数(焦点、受众、长度、语言),然后由当前会话的模型按 key_summary 模板生成要点摘要,写入 summaries/1027/summary.md。

这里体现了技能设计的第二条原理:确定性的部分交给脚本,理解与提炼的部分交给模型。转写、缓存、文件准备这些机械工作由脚本完成,可复现、可缓存;而「读懂这段家长会讲了什么、抓出三所试点学校和招生人数」这种需要语义理解的事,才交给大模型。

第四步(可选):渲染 HTML

如果想让结果方便转发,agent 可以再执行 render_html.py 把 summary.md 渲染成可双击打开的 HTML,对应第 4.4 节第三步。

为什么同样一句话能反复用:最实用的点在于两阶段解耦。总结完这次视频后,如果你想换个角度,比如在 WebChat 里继续说「只提取行动项,做成会议纪要」,agent 会直接命中第一步已生成的缓存,跳过转录,只重跑阶段二,几秒钟就出结果。转录只做一次,之后每次换焦点、换模板、换长度都近乎零成本。这就是第 4.2 节强调的「两阶段」设计在真实使用中的价值。

5. 视频批量加水印:zhz-video-logo 技能

5.1 场景描述

当你制作了一系列教学视频或产品介绍视频后,通常需要在视频的固定位置加上公司 logo 或水印。这既能标识内容的来源和版权,也能防止被盗用。手动给每个视频加 logo 既费时又容易出错,一旦视频数量多起来,逐条处理几乎不可能完成。

这时你面对的是一个典型的"批量重复劳动"场景。传统做法是用 ffmpeg 的 overlay 滤镜手动写命令,或者自己写 Python 脚本遍历目录调用 ffmpeg。但这样做有几个痛点:要自己处理位置坐标、透明度、尺寸缩放这些参数,要自己判断哪些视频已经加过水印以避免重复叠加,还要担心处理中途失败会不会损坏原文件。对于普通用户来说,门槛不低。

OpenClaw 的 zhz-video-logo 技能正是为解决这些问题而生的。它把 ffmpeg 的底层能力封装成一个易用的命令,你只需要说一句话,就能批量给整个目录的视频加上统一的 logo 或文字水印。

5.2 需求背景

视频加 logo 的需求来自几个真实的业务场景。

第一是版权保护。知识付费课程、企业培训视频、产品宣传片,这些内容投入了大量制作成本,一旦被下载转发,没有水印就无法追溯来源。加上公司 logo 后,即使视频被二次传播,也能标识版权归属。

第二是品牌标识。统一的 logo 让同一系列的视频有统一的视觉识别,观众一眼就能认出这是哪个机构或老师的内容。这在多平台分发时尤其重要,抖音、B 站、YouTube、视频号同时发布,logo 就是你的品牌名片。

第三是防盗用。电商平台、自媒体经常出现盗用他人视频的情况。水印虽然不能完全阻止盗用,但能显著提高盗用成本,让盗用者在裁剪水印时损失画面质量。

这些需求都有一个共同特点:处理的视频往往是"一批",而不是"一个"。一次课程录了 30 集,一次活动拍了 50 条素材,手动逐条加 logo 是不现实的。批量处理能力是这个场景的核心诉求。

5.3 技术原理

加水印的本质,是把一张图片(logo)或一段文字渲染成图片,叠加到视频的每一帧画面上。这个过程在 ffmpeg 里通过 overlay 滤镜完成。

叠加的基本思路很简单:把 logo 图片作为第二个输入,和视频画面按照指定的位置坐标进行像素混合。混合时有两个关键参数。一个是位置,用 overlay=X:Y 指定 logo 左上角在画面中的坐标。比如 overlay=W-w-10:H-h-10 表示放在右下角,其中 W、H 是视频的宽高,w、h 是 logo 的宽高,10 是距离边缘的像素数。另一个是透明度,通过调整 logo 图片的 alpha 通道实现,colorchannelmixer=aa=0.5 就是把透明度设为 50%。

尺寸适配也是常见需求。直接叠加原始尺寸的 logo,在 1080p 视频上合适,在 4K 视频上就显得太小。ffmpeg 可以用 scale2ref 让 logo 按主视频宽度的百分比缩放,这样同一个 logo 在不同分辨率的视频上视觉比例一致。

有一个必须理解的技术约束:加水印必须重新编码视频。它和压缩不同,压缩可以选择 -c copy 直接复制流,但叠加 logo 改变了每一帧的画面内容,所以整段视频都要重新转码一遍,耗时明显长于纯压缩。这也是为什么"批量"处理时需要后台运行、耐心等待。

此外,批量处理还有两个工程上的难点。一是幂等性,也就是避免对已经加过水印的视频重复叠加,否则每跑一次 logo 就叠一层。zhz-video-logo 用两种方式解决:在视频文件的 comment 元数据里写入标志,以及在目录里维护一份台账记录文件指纹。二是安全性,转码中途失败不能损坏原片。zhz-video-logo 的做法是先写临时文件,校验通过后才落盘,而且默认保留原片,不会替换原视频,只有显式加 --replace 才会原地覆盖。

5.4 zhz-video-logo 技能

zhz-video-logo 是一个 OpenClaw 技能,它把上面的 ffmpeg 底层能力封装成一个纯 Python 标准库脚本 video_logo.py。脚本本身不依赖 OpenClaw,只依赖系统里的 ffmpeg 和 ffprobe,文字水印模式额外需要 Pillow。

安装技能

使用前先通过 clawhub 安装该技能:

clawhub install zhz-video-logo

安装成功后输出类似:

✔ Installed zhz-video-logo v1.0.0 -> C:\Users\zhang\.openclaw\workspace\skills\zhz-video-logo

技能会被安装到 OpenClaw 工作区的 skills/zhz-video-logo 目录下,其中 scripts/video_logo.py 即为主脚本,SKILL.md 为操作手册。后续命令中的 {baseDir} 就指向该技能目录。

技能能力

技能支持两种水印来源:

水印类型 说明 示例
图片 logo PNG/JPG 等图片文件,推荐带透明通道的 PNG --logo "D:\assets\company.png"
文字水印 用 Pillow 渲染成透明 PNG 再叠加,支持中文 --text "张老师课程"

位置支持 9 个预设方位(四角、四边、居中)和自定义像素坐标,透明度 0 到 100% 可调,水印尺寸可以固定像素宽度,也可以按视频宽度的百分比缩放,随分辨率自适应。

核心命令

一个最简单的图片 logo 加水印命令:

python video_logo.py "D:\Videos" --logo "D:\assets\company.png" --position bottomright --opacity 70

常用的位置与透明度参数:

场景 命令片段
右下角(默认,最常用) --position bottomright
左上角 --position topleft
居中水印(半透明覆盖) --position center --opacity 25
精确像素坐标 --position 100,50
半透明水印 --opacity 50
固定宽度缩放 --logo-width 160
随分辨率自适应 --logo-scale 12(宽=视频宽的 12%)

文字水印用 --text 指定内容,支持中文字体和字号颜色:

python video_logo.py "D:\Videos" --text "内部资料" --position center --opacity 25

文字水印还可以指定字体、字号、颜色,比如 --font C:/Windows/Fonts/simhei.ttf --font-size 64 --font-color yellow。

画质档位

加水印必须重新编码,技能把编码参数封装成语义化的画质档位:

档位 编码 说明
--quality best H264 最佳画质
--quality balanced H264 默认平衡,兼容性最好
--quality small H265 体积优先
--quality smallest H265 最小体积

关键设计

技能有两个非常好的设计。

第一是幂等性:已经加过水印的视频会自动跳过,不会重复叠加。它靠两种方式判定,一是在文件 metadata 的 comment 字段写入标志,二是在目录里维护 .video_logo_ledger.json 台账记录文件指纹。

第二是安全性:先写临时文件,校验通过(能解析、含视频流、时长一致)后才落盘,中途失败或 Ctrl+C 都不会损坏原件。默认保留原片,不会替换原视频;只有显式加 --replace 才会原地覆盖。

5.5 实战案例

前面几节演示的都是命令行用法,你需要记住 video_logo.py 的参数名、位置选项、透明度取值范围。对熟练用户来说没问题,但对只想"加个水印"的普通用户,这仍然是一道门槛。OpenClaw 的价值就在于,你完全不需要记住这些命令,只需要在 Web Chat 里说一句话。

下面用一个完整场景演示。假设你有一套课程视频放在 D:\Courses 目录下,想给全部视频右下角加上"词元创想"四个字的文字水印。直接在 Web Chat 里发送:

请将以下课程加logo"词元创想" D:\Courses

就这么一句话,不用提参数,不用写命令。OpenClaw 收到后会自动完成下面几件事。

第零步:匹配技能,读取 SKILL.md

agent 收到这句话后,第一步不是直接动手,而是先判断这属于哪类任务。"加 logo""水印"是批量处理视频的典型需求,agent 会匹配到 zhz-video-logo 这个技能,并读取它的 SKILL.md 说明文件,搞清楚这个技能能做什么、有哪些参数、正确的调用方式。只有读懂了技能的使用说明,后续的命令才会发对。这一步是 OpenClaw 技能机制的核心:先匹配技能、读文档,再执行,而不是凭印象瞎猜。

第一步:理解意图,dry-run 预览

agent 会先理解这句话:加 logo 用的是文字"词元创想",目标目录是 D:\Courses。它不会直接开跑,而是先执行 dry-run 预览,把待处理清单反馈给你,包括目录下有多少个视频、每个视频的分辨率、编码、体积、时长,以及将要生效的水印内容、位置和透明度。你确认无误后,它才进入正式处理。

第二步:后台执行,汇报进度

确认后,agent 把正式加水印的任务放到后台运行,边处理边回报进度。因为加水印必须重新编码,视频多的时候耗时较长,agent 不会空转傻等,而是用 exec 起后台任务,通过 process poll 跟进,处理完一批就汇报一次。全部完成后,汇总行显示"完成 N | 跳过 M | 失败 0"。

第三步:自动验证效果

退出码为 0 不代表水印真的可见。agent 会抽一个输出文件截取一帧,确认"词元创想"四个字的位置、大小和透明度符合预期,再把验证结果告诉你。如果位置或透明度不对,它会调整参数重跑。

第四步:幂等性兜底

如果因为中途中断需要重跑,你只需要把同一句话再说一遍。技能会自动跳过已经加过水印的文件,不会重复叠加,所以反复运行、中断重启都很安全。

整个过程你只做两件事:发一句话、确认一下结果。剩下的意图理解、dry-run 预览、后台转码、截帧验证、幂等判断,全部由 OpenClaw 和 zhz-video-logo 技能自动完成。这也是技能相比裸 ffmpeg 命令最大的价值:把"批量加水印"从一个需要记忆参数的工程操作,变成一句人话就能完成的事情。

如果你有明确的输出要求,也可以在这句话里直接说,比如"输出到 E 盘 Branded 目录",agent 会对应加上 --out-dir "E:\Branded"。默认情况下技能保留原片、不会替换原视频,输出文件在同目录下、文件名追加 _watermark 后缀。只有当你想覆盖原片时,才需要显式说明用 --replace;如果内容偏版权保护、想在体积上兼顾瘦身,也可以让 agent 用 --quality small 换 H265 编码。

6. 视频双语字幕:zhz-subtitle 技能

6.1 为什么需要双语字幕

在全球化的技术交流中,双语字幕的需求越来越普遍。你录制了一段中文技术演讲,想发到 YouTube 让海外开发者也能观看;你参加了一场英文线上会议,想要配上中文字幕方便团队回顾;你在做跨境教育内容,需要让不同语言的观众都能理解。这些场景都需要双语字幕。

手动制作双语字幕是一项极其耗时的工作。你需要先听写原文,再逐句翻译,然后精确对齐每一句话的时间轴。一段 30 分钟的视频,人工制作双语字幕通常需要 3 到 5 个小时。而 OpenClaw 的 zhz-subtitle 技能可以把这个过程完全自动化,你只需要告诉它视频文件在哪里,剩下的事情它全部搞定。

6.2 技能概述与安装

zhz-subtitle 是一个 OpenClaw 技能,它把前面几节介绍的 ffmpeg(音频提取)和 Whisper(语音转文字)串联起来,再加上 AI 翻译能力,实现了从视频到双语 SRT 字幕的全自动化流程。

环境依赖

依赖 用途 安装方式
Python 3.12+ 运行脚本 系统自带
ffmpeg 音频提取 winget install ffmpeg 或 brew install ffmpeg
faster-whisper 语音识别 pip install faster-whisper(脚本自动安装)

安装技能

使用前先通过 clawhub 安装技能本体:

clawhub install zhz-subtitle

安装成功后输出类似:

✔ Installed zhz-subtitle v1.0.0 -> C:\Users\zhang\.openclaw\workspace\skills\zhz-subtitle

技能会被安装到 OpenClaw 工作区的 skills/zhz-subtitle 目录,后续 6.3 节命令中的 {baseDir}/scripts/ 就指向该目录下的脚本。

安装完成后,在技能被调用时自动创建以下文件结构:

输入文件:{name}.{ext}
工作目录:subtitles/{name}/(中间文件)
最终输出:subtitles/{name}.srt(双语字幕)

工作目录中的中间文件包括:

文件 说明
{name}.mp3 从视频中提取的音频
{name}.transcript.json 转录结果(含时间戳)
{name}_translated.json 翻译后的 JSON 段落
{name}.en.srt 英文字幕文件
{name}.chs.srt 中文字幕文件
{name}.srt 最终双语字幕文件

6.3 八步自动化工作流

zhz-subtitle 的完整工作流程分为 8 个步骤,每一步都有明确的完成标准。下面详细介绍每一步做了什么。

字幕生成流程

第一步:环境检查与验证

技能首先验证输入文件是否存在,检查 ffmpeg 和 faster-whisper 是否可用,然后创建工作目录 subtitles/{name}/。这是整个流程的安全阀,确保后续步骤不会因为缺少依赖而中途失败。

第二步:提取音频

如果输入是视频文件(MP4、MKV 等),用 ffmpeg 提取音频为 MP3 格式。如果输入本身就是音频文件(MP3、WAV 等),这一步会自动跳过。

python {baseDir}/scripts/extract_audio.py "<视频路径>" "subtitles/{name}/{name}.mp3"

第三步:语音转文字

使用 faster-whisper 转录音频,生成带精确时间戳的 JSON 数据和对应语言的 SRT 文件。这一步会自动检测音频的语言(中文或英文),后续步骤会根据检测结果决定翻译方向。

python {baseDir}/scripts/transcribe.py "subtitles/{name}/{name}.mp3" [model_size] [language]

可用的模型参数包括 tiny、base(默认)、small、medium、large-v3。对于中文内容,建议使用 small 或更大的模型以获得更好的识别准确率。

第四步:翻译

AI 大模型逐段翻译转录文字。翻译方向由第三步的语言检测结果决定:原文为中文则翻译成英文,原文为英文则翻译成中文。

具体执行方式是:Agent 读取转录 JSON,逐段翻译并保持相同的 JSON 结构(start、end、text 字段),将翻译结果写入 Python 脚本文件 subtitles/{name}/{name}_translate.py,再执行该脚本生成翻译 JSON。

这里有一个必须遵守的实现细节:翻译 JSON 必须通过 Python 脚本生成。因为翻译文本常含中文引号("")、书名号等标点,直接写 JSON 文件会导致解析失败,内联 Python 代码又会因 shell 转义崩溃。正确做法是把 json.dump() 调用写入 .py 文件再执行,脚本模板如下:

import json

data = {
    "language": "zh",  # 或 "en"
    "duration": <音频时长>,
    "segments": [
        {"start": 0.0, "end": 5.0, "text": "翻译文本"},
        # ... 更多段落
    ]
}

with open(r"subtitles/{name}/{name}_translated.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)
print(f"翻译完成:{len(data['segments'])} 段")

翻译过程遵循以下原则:保持原文语义和语气、字幕翻译应简洁避免过长、保留专有名词(人名、地名)、教育内容保持术语准确、纯数字段落不翻译。

对于长视频(段落数超过 50 条),技能会自动分批翻译(每批 30 到 50 条),最后合并为完整的翻译结果,避免单次处理过多内容导致遗漏。

第五步:生成翻译字幕

把翻译后的 JSON 数据转换为标准 SRT 格式。如果翻译成英文,生成 .en.srt;如果翻译成中文,生成 .chs.srt。

第六步:合并双语字幕

把原文 SRT 和翻译 SRT 合并为一个双语 SRT 文件。每条字幕显示两行,默认英文在前、中文在后。合并时会自动校验条目数,如果中英文条目数不一致会输出警告。

python {baseDir}/scripts/merge_srt.py "subtitles/{name}/{name}.en.srt" "subtitles/{name}/{name}.chs.srt" "subtitles/{name}.srt" [translated_first]

第七步:验证

自动检查最终字幕文件的质量:SRT 格式是否正确、时间戳是否有重叠、是否同时包含中英文、条目数是否一致。如果发现问题会尝试修复,修复后重新验证。

python {baseDir}/scripts/verify_srt.py "subtitles/{name}.srt"

第八步:完成报告

输出最终结果:

✅ 双语字幕生成完成!
文件:subtitles/{name}.srt
条目:{count} 条双语字幕
语言:中文 ↔ English
时长:{duration}
原文语言:{original_lang}

6.4 实战:一句提示词配双语字幕

前面 6.3 把 zhz-subtitle 的八步工作流拆开讲了一遍,那是为了看清内部每一步做了什么。回到真实使用,你根本不用关心这些命令和脚本,只要在 OpenClaw 的 WebChat 里发一句话:

帮我给这个视频配双语字幕:D:\Videos\tech_talk.mp4

剩下的事全部交给 agent。下面结合这句话,一步步拆开 agent 在背后是怎么按技能执行的,以及每一步背后的原理。

第零步:匹配技能,读取 SKILL.md

agent 收到提示词后,先用「字幕」「双语字幕」这类触发词匹配到 zhz-subtitle,然后读取它的 SKILL.md。SKILL.md 是这个技能的完整操作手册,写明了八步工作流、每一步的完成标准、以及几个必须遵守的实现细节。agent 不靠临时发挥,而是照着手册一步步执行。这就是 OpenClaw 技能机制的核心:把「视频 → 双语字幕」这条复杂链路固化成可复用的文档加脚本,agent 只做编排者。

第一、二步:环境检查,提取音频

agent 先验证输入文件存在、ffmpeg 和 faster-whisper 可用,然后创建工作目录 subtitles/tech_talk/。因为输入是 MP4 视频,agent 用 ffmpeg 把它抽成音频:

python {baseDir}/scripts/extract_audio.py "D:\Videos\tech_talk.mp4" "subtitles/tech_talk/tech_talk.mp3"

这一步的原理很简单:语音识别只关心声音,视频里的画面信息对 Whisper 没有用处,反而会白白增加解码负担。所以先把音频轨道单独提出来,后续转录、翻译、合并全部在这份音频上工作,视频文件本身全程不动。

第三步:语音转文字,语言检测定方向

接着 agent 用 faster-whisper 把音频转成带时间戳的转录 JSON:

python {baseDir}/scripts/transcribe.py "subtitles/tech_talk/tech_talk.mp3" small

这一步对应 6.3 的第三步,同时产出一份对应原文语言的 SRT(中文原文就生成 .chs.srt)。这里有个关键的自动化设计:Whisper 会自动检测语言,检测结果决定后续的翻译方向。原文是中文,就翻译成英文;原文是英文,就翻译成中文。用户从头到尾不需要告诉 agent「这是中文视频」,方向由检测结果自动决定。

第四步:AI 逐段翻译,一个必须遵守的实现细节

这是整个流程里 AI 真正上场的环节。agent 读取转录 JSON,逐段把中文翻译成英文,保持相同的 start / end / text 结构,让译文和原文逐条对齐。

这里藏着技能里一个很讲究的工程细节:翻译结果必须通过 Python 脚本生成,而不是直接写 JSON 文件。原因是翻译文本里常带中文引号「」「」、书名号《》等标点,直接写 JSON 会因为这些标点导致解析失败;如果用 exec 内联 Python 代码,又会因 shell 转义而崩溃。正确做法是把 json.dump() 调用写进一个 .py 文件再执行,让 Python 自己处理所有编码和转义。

另外还有两条翻译纪律:

  1. 分批翻译:段落超过 50 条时,拆成每批 30 到 50 条逐批翻译,最后合并。避免一次性塞进过长上下文导致遗漏或出错。
  2. 纯数字不翻译:遇到「1」「1-20」这类纯数字段落,译文保持原文不变,防止模型把数字翻成没意义的内容。

翻译原则还包括保持语义语气、字幕措辞简洁避免过长、保留人名地名专有名词、技术术语准确(比如「容器编排」翻成 Container Orchestration 而不是字面直译)。

第五、六步:生成译文字幕,合并双语

翻译完成后,agent 把译文 JSON 转成对应的英文 SRT(.en.srt),再把原文 SRT 和译文 SRT 合并:

python {baseDir}/scripts/merge_srt.py "subtitles/tech_talk/tech_talk.en.srt" \
  "subtitles/tech_talk/tech_talk.chs.srt" "subtitles/tech_talk.srt"

合并默认英文在上、中文在下,每条字幕两行。合并时会自动校验中英文条目数,不一致就输出警告,避免出现「某一句只有英文没有中文」的错位。

第七步:验证,第八步:完成报告

最后 agent 跑一遍验证脚本,检查 SRT 格式、时间戳是否重叠、是否同时包含中英文、条目数是否一致,有问题就修复后重验。通过后输出完成报告,告诉你最终文件路径 subtitles/tech_talk.srt、条目数、语言和时长。

为什么中途改需求近乎零成本:这背后是技能的分阶段文件结构。整个过程会留下音频、转录 JSON、译文 JSON、单语 SRT 等一系列中间文件。如果你想调整显示顺序,比如在 WebChat 里继续说「重新生成字幕,中文排在前面」,agent 根本不用重新转录、重新翻译,直接从缓存好的单语 SRT 重新合并一次,几秒钟就能得到新结果。转录和翻译这类重活只做一次,之后的调整都只是在成品文件上做轻量操作。

总结:zhz-subtitle 把「配双语字幕」这条原本要人工花 3 到 5 小时的链路,拆成了「音频提取 → 语音转文字 → AI 翻译 → 生成字幕 → 合并 → 验证」八个有明确完成标准的步骤,由 agent 照着 SKILL.md 自动编排。

核心设计有三点:一是语言检测自动决定翻译方向,用户无需说明;二是翻译结果用 Python 脚本生成 JSON,绕开中文标点和 shell 转义的坑;三是中间文件全程留存,让重跑、改顺序这类调整只做轻量操作。用户要做的,只有那句「帮我给这个视频配双语字幕」。


7. 总结与进阶

7.1 本章核心知识回顾

知识点 关键内容 常用命令/参数
ffmpeg 基础 开源音视频处理工具,命令行驱动 ffmpeg -i、ffprobe
编码与容器 编码是压缩算法,容器是文件外壳 H.264、H.265、MP4、MKV
视频压缩 zhz-video-compress 技能封装 CRF 与 preset,H265 默认体积优先 --quality、--speed、--max-height
批量压缩 递归扫描目录,智能跳过已压缩/小文件 comment 标志 + 台账 + 编码判断
Whisper OpenAI 开源语音识别模型,支持 99 种语言 faster-whisper、5 种模型大小
语音转录与总结 zhz-transcript-summarize 技能两阶段:内容哈希缓存 + 模板化摘要 transcribe.py、summarize.py、--focus、--glossary
批量加水印 zhz-video-logo 技能封装 overlay 滤镜,图片/文字水印均可 --logo、--text、--position
水印安全与幂等 comment 标志 + 台账防重复,临时文件校验后落盘,默认保留原片 --dry-run、--force、--replace
双语字幕 zhz-subtitle 技能自动化 8 步流程 Whisper 转录 + 大模型翻译 + SRT 合并

7.2 工作流组合

ffmpeg、Whisper 和 AI 大模型可以灵活组合,构建出更多实用的音视频处理工作流。以下是几种常见的组合方式:

工作流一:视频压缩 + 转录 + 字幕

适合处理自己录制的大量教学视频。先批量压缩节省存储空间,再转录内容建立可搜索的文字索引,最后生成字幕方便观众观看。

原始视频 → ffmpeg 压缩 → 压缩视频存储
                ↓
          ffmpeg 提取音频 → Whisper 转录 → AI 总结
                ↓
          zhz-subtitle → 双语字幕

工作流二:批量加 logo + 压缩 + 发布

适合视频创作者在发布前的最后加工。统一加上品牌 logo,压缩到适合网络传播的大小,然后上传到视频平台。

原始视频 → 批量加 logo → ffmpeg 压缩 → 上传发布

工作流三:会议录像 → 会议纪要

适合工作会议和培训录像。先转录成文字,再由 AI 提取会议纪要,包括决议事项、行动项和责任人。

会议录像 → ffmpeg 提取音频 → Whisper 转录 → AI 生成会议纪要

这些工作流的核心思路都是一样的:用 ffmpeg 处理音视频格式,用 Whisper 把声音变成文字,用大语言模型理解和加工文字内容。掌握了这三个工具,你就拥有了一个完整的音视频处理工具箱。

7.3 课后练习

练习 1:视频压缩实战

找一段你自己录制的视频(手机拍摄或屏幕录制均可),使用 zhz-video-compress 技能将它压缩。先用 dry-run 预览,再正式执行,记录压缩前后的文件大小、分辨率和码率,对比画面质量是否有明显损失。尝试分别使用 --quality balanced 和 --quality smallest 档位,比较两者的文件大小和画质差异。

练习 2:播客转录与总结

找一段 10 分钟以上的中文播客或演讲视频,使用 zhz-transcript-summarize 技能先转录、再总结。先用 transcribe.py 转录并缓存,再用 summarize.py 生成要点摘要。试着换一个 --focus 重新总结一次,观察是否跳过了转录阶段。如果源内容有术语或英文单词,准备一份 glossary.txt 术语表,对比更正前后的摘要差异。

练习 3:批量加 logo

准备 3 到 5 个短视频和一张 logo 图片,使用 zhz-video-logo 技能,给所有视频统一加上 logo。要求 logo 显示在右下角,宽度 100 像素,透明度 50%。先用 dry-run 预览,再正式执行,最后查看验证每个视频的输出效果是否正确。

练习 4:双语字幕生成(进阶)

使用 zhz-subtitle 技能,给一段 5 到 10 分钟的中文视频生成双语字幕。用文本编辑器打开生成的 SRT 文件,检查时间戳是否准确、翻译是否合理。把 SRT 文件和视频放在同一目录下,用播放器加载字幕验证显示效果。

本文为《OpenClaw 实战指南》系列第 15 篇。