ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Mac上使用ffmpeg和autosub自动化生成视频字幕的完整实践指南

Mac上使用ffmpeg和autosub自动化生成视频字幕的完整实践指南 你们有没有接过这种活一堆视频素材可能是会议录屏、课程讲解或者采访原片需要配上字幕才能发布。手动打轴是个体力活半小时的视频能干一整个下午眼睛都快废了。后来我搭了一条ffmpeg加autosub的自动化链路先用ffmpeg做音频预处理再用autosub做语音识别生成带时间轴的字幕文件最后用ffmpeg把字幕合成回视频。现在这套流程已经跑得比较稳了这篇就把完整操作方法、命令参数和踩过的坑都整理出来给想在mac上批量处理视频字幕的朋友做个参考。1. 先看全景ffmpeg加autosub这条链路到底怎么工作1.1 自动字幕生成的四个环节在mac上给视频自动加字幕本质上是把一条视频变成文字时间轴再叠回画面里去。拆开看一共四个环节。第一个环节是音频提取。视频文件里画面和声音是两路独立的流字幕识别只用得到声音所以要先从容器里把音频抽出来转成语音识别工具更吃得开的格式。这步ffmpeg是绝对主力一条命令搞定后面我会给完整参数。第二个环节是语音识别。这是整条链路的技术核心需要把音频里的说话内容逐句转成文字并且给每句话打上开始时间和结束时间。autosub做的就是这件事它的底层调用的是开源的SpeechRecognition库默认接的是Google的Web Speech语音识别接口识别结果会以带时间戳的文本输出。第三个环节是字幕整理。识别出来的原始结果一般都比较糙可能会有断句错乱、时间重叠、无意义词重复的情况需要做一轮后处理。这个阶段我用文本工具加脚本配合处理把自动识别的原料打磨成能发布的水准。第四个环节是合成回视频。字幕文件整理好后要么烧录进画面变成硬字幕要么封装进视频容器变成软字幕。这一步也是ffmpeg负责可以用字幕滤镜渲染也可以用流复制的方式内封。把这四个环节串起来单条视频的自动化处理可以在几分钟内完成而且一旦跑通批量处理只是加个循环的问题。这正好解决了手动打轴最耗时的痛点时间轴是识别引擎自动对齐的不需要一句一句去对波形。1.2 为什么不用剪映、PR里的自动字幕你可能会问剪映和Premiere Pro现在都有自动字幕功能为什么还要用命令行工具自己折腾这个问题我实际对比过区别还挺明显。剪映的自带字幕识别做得不错但它有两个绕不开的坎。一个是批量处理能力弱剪映的工作流是一个项目一个视频如果你的需求是把几十个视频都配上字幕就得一个个导入、生成、导出中间还要人工确认一堆弹窗。另一个是自动化程度低它本质上是图形界面软件没法通过脚本去驱动也没法嵌入到已有的批处理流水线里。PR倒是可以通过脚本做一定程度的自动化但PR本身的学习成本和订阅费用都不低而且自动字幕功能对中文的模型是在线服务在项目交付中有时候反而不如本地可控。ffmpeg加autosub这套方案的好处是全命令行、可脚本化、免费。你不需要打开任何图形界面把所有命令写进一个shell脚本回车之后等着收成品就行。对于给一批视频加字幕这种重复劳动效率差距是数量级的。1.3 这套方案的适用边界不过也得说实话autosub的识别质量和剪映、PR的在线识别引擎还是有差距的尤其是在中文的复杂场景下。它更适合这几类需求视频内容以清晰的单人语音为主、说话人语速正常、背景噪音不大你需要的是快速生成初稿字幕然后人工校对一遍或者你的项目对字幕的精细程度要求没那么高需要的是有字幕可看而不是字幕完美无缺。如果遇到多人对话、强烈的背景音乐、口音很重的语音autosub的识别错误率会明显上升这时候我的建议是把它当作打轴工具而不是识别工具——它生成的SRT里时间轴还是可用的文字部分人工改掉就行这比从零开始手动打轴还是快很多。想追求更高识别率的小伙伴可以用OpenAI开源的Whisper模型替代autosub做语音识别命令几乎一样后面我会在踩坑部分提一句怎么替换不影响整条ffmpeg链路。2. 装环境mac上装ffmpeg和autosub最容易栽的几个跟头2.1 检查Homebrew和ffmpeg是否就位在mac上装ffmpeg最省心的是走Homebrew。首先确认你机器上有没有Homebrew打开终端执行brew --version如果提示找不到命令说明还没装Homebrew需要先装一下。装Homebrew本身是个常规操作但国内网络环境下有时候下载很慢建议把默认源切到国内镜像清华、中科大、阿里云的镜像站都有现成教程。这一步不会影响ffmpeg和autosub的使用逻辑只是让安装过程能跑完。Homebrew就位后执行brew install ffmpeg这个包依赖的东西比较多包括libx264、libass、fdk-aac这些视频和音频处理库编译安装耗时较长通常要等几分钟耐心看进度条就行。这里有一个关键点很多人会忽略字幕滤镜subtitles依赖libass库。如果Homebrew的ffmpeg默认没有编进libass后面烧录字幕时会直接报错。验证有没有支持执行ffmpeg -filters | grep subtitles只要看到类似subtitles和ass的滤镜条目就说明libass已经编译进去了。我建议在装完ffmpeg后立刻做这个检查别等视频都处理到一半才发现滤镜不可用。2.2 autosub的安装方式和Python依赖autosub是一个Python命令行工具作者是agermanidisGitHub上直接能搜到。安装方式有两种一个是pip直接装一个是克隆仓库后本地装。pip install autosub不过在部分Python版本上pip装的autosub可能有兼容问题我实际用下来更推荐克隆仓库的方式git clone https://github.com/agermanidis/autosub.git cd autosub pip install -r requirements.txt它会依赖SpeechRecognition、pydub、audioread这几个核心库。在mac上如果遇到pydub相关的报错执行brew install ffmpeg时通常已经把依赖的底层解码库带上了问题不大。audioread偶尔会提示找不到解码器这时检查一下ffmpeg和ffprobe是否都在PATH里就行。还有一个容易踩的坑是Python版本。mac系统自带的python3可能版本比较老而autosub的依赖项对Python版本有要求。我建议直接用Homebrew装一个新版Pythonbrew install python装完用python3 --version确认版本然后pip用pip3而不是pip避免和系统自带的Python环境打架。2.3 跑通前的最小验证环境装好后不要直接上大视频先用一个不到20秒的短视频跑一遍冒烟测试确保每个环节都通。可以直接用ffmpeg生成一个带语音的测试视频或者找一小段自己录的素材。我在第一次搭环境时就直接跑了完整流程结果autosub报了一堆依赖错误排查半天才发现是Python环境串了。后来学乖了先跑最小验证先确认ffmpeg能抽音频再确认autosub能识别一段3秒的语音最后才处理真实视频。这样可以把问题迅速定位在具体环节不用对着日志猜。如果你用的是Apple Silicon芯片的MacIntel版本的Homebrew和ARM版本的Homebrew路径不一样装ffmpeg时要注意终端是不是x86模拟模式。这个问题在brew --version里能看出来如果路径是/opt/homebrew开头就是ARM版/usr/local开头则是旧版别混用。3. 核心步骤音频提取、语音识别到SRT字幕的完整命令3.1 用ffmpeg提取适合识别的音频ffmpeg处理音频的功力和画质无关关键是参数给得对不对。我提取字幕用音频的命令是这样ffmpeg -i 原始视频.mp4 -vn -acodec pcm_s16le -ar 16000 -ac 1 临时音频.wav拆解一下这行命令背后的思路。-vn是丢弃视频流只保留音频减少处理时间。-acodec pcm_s16le是把音频编码成16位无压缩的PCM格式也就是标准的WAV波形文件。为什么不直接用原视频的AAC音频因为AAC是有损压缩格式在语音识别时多一层解压就有多一层的误差无损PCM是识别引擎最欢迎的输入格式。-ar 16000是采样率这个参数是语音识别链路里最重要的一环。人类语音的有效频率范围一般到8kHz就差不多了16kHz的采样率既保留了语音特征又远小于音乐CD的44.1kHz文件体积小、识别速度快。你如果直接给autosub一个48kHz的音频它也能处理但识别速度和准确率都会受影响。-ac 1是把声道合并成单声道。多声道音频里不同声道只是采集位置不同对内容识别没有额外信息量反而可能引入声道间的相位干扰。单声道是语音识别的最佳输入形态。这套参数组合也可以直接套在短视频平台的下载素材上。如果你处理的素材本身音画不同步先别急着做字幕先用ffmpeg把源视频的时间戳校准了再说不然识别出来的字幕时间轴再准也是白搭。3.2 autosub语音识别关键参数与语言选择音频准备好后接下来交给autosubautosub -i 临时音频.wav -o 输出字幕.srt -S zh-CN -F srt参数的含义分别是-i指定输入音频-o指定输出字幕文件-S指定源语言-F指定输出格式。-S zh-CN是中文普通话识别如果处理的是英文视频改成-S en-US。语言代码的写法遵循BCP-47规范zh-CN代表简体中文en-US代表美式英语。有的版本也支持不写-S参数让它自动检测语言但自动检测会消耗额外时间不如直接指定来得稳定。-F srt是输出SRT格式字幕。如果你之后打算配合播放器做多语言切换也可以生成WebVTT格式-F vtt。SRT是目前兼容性最好的字幕格式主流的播放器和剪辑软件都直接支持我默认都输出SRT。如果你的视频需要双语字幕autosub还有一个翻译选项-L比如原始视频是英文想输出中文字幕可以这样autosub -i 临时音频.wav -o 输出字幕.srt -S en-US -L zh-CN -F srt它会先识别英文再自动翻译成中文。实测下来翻译质量一般属于能看懂但不够自然的程度建议只用来快速生成初稿。autosub识别的时候终端会输出当时的语音块信息和识别文本整个过程会持续一段时间取决于音频总时长和服务器的响应速度。识别崩溃时可以尝试调整并发数-C 1表示串行识别能有效降低超时风险。3.3 拿到SRT后的第一轮检查字幕文件生成后先用文本编辑器打开看一眼SRT的结构1 00:00:00,000 -- 00:00:03,200 大家好这段视频我们讲一下自动化字幕 2 00:00:03,300 -- 00:00:06,800 主要用到ffmpeg和autosub两个工具SRT格式很直白序号、时间轴、字幕文本三行一组组之间空一行。第一轮检查主要看两个东西文本内容是否成句、时间轴是否有明显的大段空缺。如果文本内容明显是识别错了不用纠结后面统一修正。如果时间轴出现大段空白比如一段1分钟的视频从第5秒到第30秒之间没有任何字幕块说明识别引擎在那些语音段上没有产出有效结果原因可能是背景噪音过大或者语音不连续这个放到第6部分排查。4. 字幕后处理给识别结果收拾成能用的样子4.1 处理断句错乱和时间重叠autosub输出的SRT和真正能发布的字幕之间通常还有一段距离。最常见的三个问题断句位置不合理、相邻字幕时间轴重叠、无意义的语气词被识别成文字。断句错乱的表现是大家好我们今天讲一下关于自动化字幕以及如何用ffmpeg实现这一整句被分成几个奇怪的块或者一个句子被雷劈一样截成两半。处理方式我一般分两步先做全文朗读一遍把明显的分句不合理用文本编辑器调整再结合时间轴判断如果某条字幕的时间太短并且文本很长说明这条字幕可能会一闪而过需要手动延长时间。时间重叠是指SRT中前一条字幕的结束时间大于后一条字幕的开始时间。播放器遇到这种情况会强制显示后一条但有的播放器会闪烁甚至报错。检查重叠可以用一个简单的Python脚本遍历SRT里相邻条的起止时间把重叠超过200毫秒的条目打印出来人工确认取舍。import re def check_overlap(srt_path): with open(srt_path, r, encodingutf-8) as f: lines f.read().splitlines() prev_end None for line in lines: m re.match(r(\d{2}:\d{2}:\d{2},\d{3}) -- (\d{2}:\d{2}:\d{2},\d{3}), line) if m: start, end m.groups() start time_to_ms(start) end time_to_ms(end) if prev_end is not None and start prev_end: print(f重叠: {start} {prev_end}) prev_end end我这里写的是示意逻辑时间转换函数time_to_ms帮你把时:分:秒,毫秒解析成毫秒整数就行。这种处理脚本网上很多不必从零造轮子。4.2 SRT转ASS用样式提升字幕观感SRT格式简单但样式表达能力很弱它只能控制文本内容和时间轴正文颜色、大小、描边都不好界定。如果你希望字幕在观众屏幕上更精致一点建议转成ASS格式这个格式可以定义字体、字号、颜色、位置、描边、阴影等。ffmpeg自带的库可以直接完成格式转换ffmpeg -i 输出字幕.srt 输出字幕.ass生成的ASS文件里有一个[V4 Styles]区块里面都是可以直接改的参数。我常用的是这几个Style: Default,PingFang SC,18,H00FFFFFF,H000000FF,H00000000,H96000000,0,0,0,0,100,100,0,0,1,1.5,1,2,10,10,10,1逐项解释第三位是字号18对应中等大小H00FFFFFF是字体颜色为白色后面跟着的H96000000是半透明黑色背景保证字幕在画面上不会和亮色景物混在一起1.5是描边宽度2是阴影深度。字体名部分建议直接用macOS系统自带的PingFang SC苹方这个字体的中文渲染效果在apple生态里是经过优化的。如果你想用别的字体上面这些参数同样适用。ASS文件做好后再叠加进视频时直接用ASS而不是SRT观感提升非常明显播放出来的字幕就和专业后期制作的字幕样式没什么区别了。4.3 整体时间偏移的批量修正视频在不同平台之间转发时字幕经常会出现整体偏移的情况可能整体提前或延后零点几秒。如果只是用手动方式逐条修改SRT里的时间轴对长视频来说简直是灾难。整体偏移的修正在SRT层面很简单就是给每条字幕的开始时间和结束时间加上或减去同样的时间偏移量。我用一段简短的Python脚本搞定import re, sys def time_to_ms(t): h, m, rest t.split(:) s, ms rest.split(,) return int(h)*3600000 int(m)*60000 int(s)*1000 int(ms) def ms_to_time(ms): h, ms divmod(ms, 3600000) m, ms divmod(ms, 60000) s, ms divmod(ms, 1000) return f{h:02d}:{m:02d}:{s:02d},{ms:03d} offset_ms int(sys.argv[1]) # 正数延后负数提前 with open(sys.argv[2], r, encodingutf-8) as f: content f.read() reg re.compile(r(\d{2}:\d{2}:\d{2},\d{3}) -- (\d{2}:\d{2}:\d{2},\d{3})) def replace(m): return f{ms_to_time(time_to_ms(m.group(1)) offset_ms)} -- {ms_to_time(time_to_ms(m.group(2)) offset_ms)} with open(sys.argv[3], w, encodingutf-8) as f: f.write(reg.sub(replace, content))保存成shift_srt.py用法是python3 shift_srt.py -300 输出字幕.srt 修正后.srt这个命令表示把整条字幕的时间轴提前0.3秒。每次合成字幕前建议先用播放器对比着听一两次确定偏移方向和时间量再批量改。5. 合成视频硬字幕与软字幕的选择和命令细节5.1 硬字幕先把字幕烧进画面硬字幕是把字幕直接渲染进视频画面相当于字幕成了画面的一部分。用户拿到视频后无论用什么播放器都能看到字幕不需要额外加载字幕文件这适合做分发到短视频平台、微信群的视频。代价是字幕不可关闭而且渲染一次就对画面做了一次压缩多少会损失一点画质。ffmpeg烧录硬字幕的命令ffmpeg -i 原始视频.mp4 -vf subtitles输出字幕.ass -c:a copy 成品视频.mp4这里的关键点在于-vf subtitles字幕文件这个滤镜。如果你传给滤镜的是SRT文件ffmpeg会先把它转成默认样式的纯文本字幕如果传的是ASS文件滤镜会严格按ASS里定义的样式和字体渲染。所以我上一部分才建议先转ASS这一步就是收获的时候。烧录时还有一个需要特别注意的点字幕文件路径里的特殊字符。如果路径里有冒号或单引号ffmpeg的字幕滤镜解析会出错。稳妥的做法是把字幕文件和视频文件放在同一个目录并且用相对路径比如cd /工作目录 ffmpeg -i 原始视频.mp4 -vf subtitlessub.ass -c:a copy output.mp4另外-c:a copy代表音频流直接复制不重新编码能省不少时间。但如果你想顺便压低输出体积可以把音频编码为aac并加一个码率参数。5.2 软字幕保留视频原画质软字幕和硬字幕相反它把字幕轨道封装进视频文件但并没有渲染进画面。用户播放时可以通过播放器开关字幕也可以自己换字体、调样式。这种方式对视频画质零损伤生成速度也快但要求播放器支持字幕轨道切换。在MP4容器里封装字幕命令是ffmpeg -i 原始视频.mp4 -i 输出字幕.srt -c copy -c:s mov_text 成品视频.mp4-c copy表示视频流和音频流直接复制不重新编码所以处理速度非常快。-c:s mov_text是MP4的标准字幕格式。但mov_text支持的字幕格式有限做不了太复杂的样式一般建议软字幕直接用SRT。如果目标设备是Apple的QuickTimemov_text的兼容性还可以。但很多第三方播放器比如VLC也可以读取内封字幕只是样式显示略显朴素。要是你打算做多音轨多字幕的发布需求可以封装成MKV容器用-c:s srt替代mov_text参数兼容性更好一些。软字幕的最大风险在于部分平台在上传视频时会剥离字幕轨道。我一般会确认分发目标的规则B站、YouTube都能识别内封字幕但微信视频号和抖音就不一定了。这种情况还是老老实实用硬字幕。5.3 批量处理的shell循环当字幕和视频都准备就绪批量处理就成了一件很爽的事情。我把常用命令写成一个脚本#!/bin/bash for f in *.mp4; do base${f%.*} echo 处理中: $f ffmpeg -y -i $f -vn -acodec pcm_s16le -ar 16000 -ac 1 ${base}.wav autosub -i ${base}.wav -o ${base}.srt -S zh-CN -F srt ffmpeg -y -i $f -vf subtitles${base}.ass -c:a copy ${base}_final.mp4 done第5行先提取音频第6行做语音识别第7行烧录字幕。三次操作的中间文件都以原文件名做前缀不会相互覆盖。-y参数表示覆盖同名文件避免交互式确认卡住脚本。脚本运行的前提是工作目录里每个视频对应的ASS文件已经生成。如果不想手动逐个转ASS也可以在脚本里加一行ffmpeg -y -i ${base}.srt ${base}.ass这样整个流程就是全自动的。多文件运行时建议先处理一两个样本确认输出质量没问题再全量跑别让一个错误参数浪费十几分钟的批处理时间。6. 踩坑实录autosub识别失败、不同步、乱码的排查链路6.1 autosub识别卡住或报错的排查流程autosub最常见的故障就两类完全识别不出内容以及识别中途卡死不动。完全识别不出内容时终端会打印类似Recognizer could not understand audio的错误或者干脆没有任何输出。排查链路我一般按顺序走先确认WAV文件本身有没有问题用ffprobe看一眼参数ffprobe 临时音频.wav重点看采样率、声道数、时长是否正常。如果采样率不是16k或声道数不是单声道重新用第3部分的命令抽取。如果音频参数正常那就确认autosub运行时的联网状态。autosub默认调用的识别服务对网络环境要求较高如果你的运行环境访问该服务不稳定识别就会卡住或报错。处理思路有两条一是搭建一个能稳定访问识别服务的运行环境二是在本地换上不需要联网的识别引擎比如Whisper。我的建议是后者离线识别不受网络波动影响而且中文识别质量相对更好代价是你得有一个足够跑模型性能的电脑。再用Whisper替代autosub时命令改成pip install openai-whisper whisper 临时音频.wav --language zh --model small --output_format srt它生成的SRT文件结构上完全可以复用后面ffmpeg合步骤不需要改链路其他部分。识别中途卡死多半和并发有关。autosub内部对长音频是分段并行识别的默认并发数在某些网络环境下会导致请求超时堆积。这时把并发数降到1autosub -i 临时音频.wav -o 输出字幕.srt -S zh-CN -F srt -C 1串行识别会慢一些但稳定性高很多。这个方法我实测对解决卡死问题很有效。6.2 字幕时间轴不对齐的处理字幕整体偏移的常见场景是原始视频从某个视频平台下载后被人为裁剪过或者视频本身的起始位置有黑场导致字幕整体延后。遇到这类问题先用播放器精确判断偏差量然后使用第4.3节的Python脚本做整体偏移修正。另一种情况不是整体偏移而是某些句子的时间轴局部错位这可能是因为语音识别引擎在背景噪音处产生了错误停顿或者是说话人中途换句的边界没对齐。这类局部问题没有通用的自动修复办法我和团队的处理方式是先看错位的句子占比如果只有零星几处用文本编辑器手动改时间码如果错位比例很高说明整个音频的识别质量不过关宁可重录或换识别模型也不要在一份烂时间轴上反复修修补补。还有一个小坑在mac上如果使用了硬字幕处理旧的H.264视频-c:a copy虽然复制了音频流但输出封装时时间戳可能因为-vf滤镜重排而偏移。遇到字幕和嘴形差十几毫秒的情况首先检查源视频本身是否音画同步用ffprobe看下视频流和音频流的start_time字段两者不一致时先做-itsoffset校正。6.3 中文硬字幕变方块字的解决方案这是mac上用ffmpeg烧录中文字幕最典型的一个坑字幕滤镜默认找不到中文字体渲染出来全是方框。原因很简单libass在渲染字幕时需要一个支持中文的字体但不是每个系统里都能自行定位到。解决办法是在subtitles滤镜里显式指定字体目录和字体名。macOS的中文字体放在/System/Library/Fonts/下苹方字体文件名是/System/Library/Fonts/PingFang.ttc但ffmpeg的force_style参数里不能直接传文件路径得配合fontsdir参数ffmpeg -i 原始视频.mp4 -vf subtitlessub.ass:fontsdir/System/Library/Fonts:force_styleFontNamePingFang SC,Fontsize18 -c:a copy output.mp4注意force_style里的FontName用的是字体对外显示的名称而不是文件名。PingFang SC对外显示名称就是PingFang SC如果你不确定字体显示名称可以在mac的字体册应用里查看或者用系统的fc-list工具来查。还有一种不那么优雅但很稳的办法找一个字体文件拷贝到脚本目录直接用绝对路径加载。比如把某个中文字体的otf文件放到脚本目录里命令改成ffmpeg -i 原始视频.mp4 -vf subtitlessub.ass:fontsdir./:force_styleFontNameMyFont -c:a copy output.mp4这样只要字体文件在渲染就不会出意外也方便在服务器上跑批处理时统一字体。这些坑我都是实际踩过一遍才总结出来的。现在跑这套流程已经不怎么看终端输出基本做到丢进去一批视频过一会儿回来收成品。最后再分享一个小习惯批量处理前我会先把所有视频缩略图画质压一遍确保每条命令在10秒内能完成再决定用完整视频跑不浪费试错时间。这套方案在2024年的MacBook上跑1080p视频平均每10分钟视频处理时间在5到8分钟之间已经算是很划算了。
返回列表