FEATURED · 精选文章

本地视频字幕制作全流程:Whisper语音转写与FFmpeg压制实践

发布时间 / 2026/9/5 22:47:39
来源 / 创域科博编辑部
栏目 / 资讯中心
本地视频字幕制作全流程:Whisper语音转写与FFmpeg压制实践 先看一句弹幕确定是MCU的锤基吗怎么像同人文里的。这句话经常出现在锤基双人剪辑的熟肉评论区。它背后其实是一个很具体的生产问题一段原本没有中文字幕的影视片段是怎么变成“带中文字幕的熟肉”并发布出来的如果抛开剧情不谈把它当成技术任务拆解你会发现这就是一条完整的本地视频字幕处理流程素材管理、语音转写、字幕翻译、样式校对、字幕压制、批量发布。这篇文章不聊CP剧情只聊熟肉生产的技术链路。我会按“环境准备 - 转写 - 翻译 - 校对 - 压制 - 批量处理”的顺序讲并给出可复制的命令和Python脚本。你会看到怎么用本地Whisper类模型把语音转成带时间戳的字幕怎么用翻译接口批量生成中文字幕怎么用FFmpeg把字幕烧录到视频里。需要提前说明的是下面的流程只适合处理你自己有合法素材来源的内容例如官方已授权素材、自己录制的片段、已购买版权的内容或者明确授权的二创素材。同人创作不等于自动获得版权豁免发布前一定要确认平台规范和素材授权范围。1. 核心能力速览能力项说明项目类型同人二创视频字幕熟肉制作工具链主要功能语音转字幕、字幕翻译、字幕校对、字幕压制、批量处理推荐硬件有NVIDIA显卡更好CPU也能跑但速度慢显存门槛需按模型实际测试支持平台Windows / Linux / macOS依赖Python环境启动方式Python脚本一键转写FFmpeg命令压制也可用FastAPI包装成服务是否支持API可以通过FastAPI等框架暴露转写和翻译接口是否支持批量任务支持目录级批量转写、批量翻译、批量压制典型产出SRT/VTT字幕文件、双语字幕、硬字幕压制视频适合场景个人授权素材二创、字幕组内部协作、影视素材整理、语音转写测试这里需要强调熟肉不只是“翻译字幕”。如果你的素材已经带时间轴那么核心工作就是翻译和校对如果素材没有字幕就需要先用语音识别生成带时间轴的文本如果素材是外文硬字幕无法直接从字幕轨道提取则需要先做画面OCR再进入翻译流程。后面的步骤会优先讨论“语音转字幕”的类型。2. 适用场景与使用边界2.1 适合谁想在授权素材基础上做二创剪辑并把双语字幕压进画面的创作者。字幕组或翻译组需要批量处理多集视频提高转写效率的协作小组。做Whisper类语音识别、字幕格式转换、FFmpeg批处理等技术验证的开发者。有大量视频素材需要整理成文字索引和字幕文件的内容管理者。2.2 不适合什么不适合没有素材版权、对源视频来源无法说明的发布需求。不适合追求“即时在线口译”的直播场景本流程是离线批量为主。不适合需要复杂特效字幕的高级影视包装场景那需要专业剪辑和字幕软件不只是FFmpeg压制。不适合对说话人区分有很高要求的场景Whisper类模型对多人重叠说话依然有误差它更多只负责转写字幕内容不负责区分角色。2.3 合规与版权边界这是整条链路里最重要的一步。漫威角色、影片片段、影视配乐都受版权保护锤基同人视频如果要在公开平台发布片段和音乐授权都要谨慎。即使不是商用平台仍可根据版权方要求下架内容。建议做到四点只处理自己有权使用的素材例如已购买素材库、官方剪辑包、自制实拍或明确授权的片段。不直接搬运带片源logo的完整剧集不在公开渠道发布未经授权的完整影视内容。涉及真人演员相关内容避免恶意剪辑、捏造事实和侵犯肖像权。发布前检查平台是否要求原创声明、是否有限制影视剪辑的规则。如果做不到第1点即使技术链路再顺畅后续发布风险也很大。这个问题建议在动工前用书面方式确认素材授权范围而不是最后再补救。3. 环境准备与前置条件3.1 操作系统与依赖我以Windows和Linux通用环境说明。主要依赖是Python 3.9 或更高版本。FFmpeg用于音频提取、视频转码、字幕压制。NVIDIA显卡驱动和CUDA仅GPU推理时需要。字幕编辑软件例如Aegisub或Subtitle Edit用于人工校对。一个可用的模型文件存放目录比如models/whisper/。如果没有NVIDIA显卡也可以只靠CPU推理但速度会明显变慢。显存占用取决于你选择Whisper模型的尺寸和音频长度。更稳妥的办法是先跑一个小片段用任务管理器观察占用再决定要不要换更大模型。3.2 检查已有环境打开终端逐条确认python --version ffmpeg -version ffprobe -version nvidia-smi如果报ffmpeg not found需要先安装FFmpeg。Windows可以下载解压版把bin目录加到系统PATHLinux直接使用包管理器安装macOS推荐用Homebrew。检查Python之后创建虚拟环境mkdir fanedit-subtitle cd fanedit-subtitle python -m venv venv # Windows激活 venv\Scripts\activate # Linux/macOS激活 source venv/bin/activate激活后终端提示符会变成(venv)接下来安装依赖不会污染系统Python环境。3.3 安装依赖核心依赖是faster-whisper另外需要ffmpeg-python和requests等辅助库pip install faster-whisper pip install ffmpeg-python pip install requests pydantic # 如果要做本地API服务 pip install fastapi uvicorn python-multipartfaster-whisper是CTranslate2实现的Whisper推理库推理速度通常比原版快内存占用也相对可控。缺点是部分Windows环境需要额外确认ctranslate2版本和CUDA版本是否匹配。如果遇到DLL加载失败可以在同环境里安装原版openai-whisper作为备选但速度会降低。3.4 项目目录建议把输入素材、模型、中间结果分开管理防止后面批量处理把目录搅乱。fanedit-subtitle/ ├─ venv/ ├─ input/ │ ├─ episode_01.mp4 │ └─ episode_02.mkv ├─ models/ │ └─ whisper/ ├─ work/ │ ├─ audio/ │ ├─ srt_raw/ │ ├─ srt_translated/ │ └─ review/ ├─ output/ ├─ scripts/ │ ├─ transcribe.py │ ├─ translate_srt.py │ └─ burn_subtitle.py └─ logs/work里保存中间文件output只放可以发布的成片和最终字幕。一旦某一步生成失败可快速定位是哪一步出了问题。4. 本地语音转字幕实操4.1 为什么先转音频字幕识别通常不需要处理视频画面的所有数据。先用FFmpeg把视频里的音频拉出来统一为16kHz单声道WAV可以显著降低转写时的输入数据量也让后续流程更稳定。ffmpeg -y -i input/episode_01.mp4 -vn -ac 1 -ar 16000 -c:a pcm_s16le work/audio/episode_01.wav这段命令的意思是从视频中提取音频忽略视频流转成单声道、16kHz、16位PCM格式。为什么用16kHzWhisper训练时常用16kHz作为输入采样率不需要读入高保真采样率也能保持识别效果还能减小音频解码压力。如果你的素材本身有噪声建议先做轻量去噪或增益归一化不要直接升采样。4.2 写一个转写脚本把下面的代码保存为scripts/transcribe.py。脚本会读取一个音频文件使用faster-whisper生成SRT字幕并输出到指定目录。import sys import time from pathlib import Path from faster_whisper import WhisperModel audio_path Path(sys.argv[1]) model_dir sys.argv[2] if len(sys.argv) 2 else models/whisper language sys.argv[3] if len(sys.argv) 3 else en output_dir Path(sys.argv[4]) if len(sys.argv) 4 else Path(work/srt_raw) output_dir.mkdir(parentsTrue, exist_okTrue) # compute_type 可根据设备调整int8 更省显存float16 精度更高 model WhisperModel( model_dir, deviceauto, compute_typeint8 ) start time.time() segments, info model.transcribe( str(audio_path), languagelanguage, beam_size5, vad_filterTrue ) srt_path output_dir / (audio_path.stem .srt) lines [] for i, segment in enumerate(segments, start1): start_ts segment.start end_ts segment.end text segment.text.strip() start_fmt f{int(start_ts // 3600):02d}:{int((start_ts % 3600) // 60):02d}:{int(start_ts % 60):02d},{int((start_ts % 1) * 1000):03d} end_fmt f{int(end_ts // 3600):02d}:{int((end_ts % 3600) // 60):02d}:{int(end_ts % 60):02d},{int((end_ts % 1) * 1000):03d} lines.append(f{i}\n{start_fmt} -- {end_fmt}\n{text}\n) srt_path.write_text(\n.join(lines), encodingutf-8) print(ffinished: {srt_path} in {time.time() - start:.2f}s)运行示例python scripts/transcribe.py work/audio/episode_01.wav models/whisper en work/srt_raw需要注意model_dir可以直接写模型名例如large-v3程序会尝试从远端下载但很多本地环境网络不稳定建议提前准备好模型文件并写到固定路径。如果素材中文为主把language改成zh。结果SRT是UTF-8编码。如果某段识别结果为空先查音频是否有长时间静音再决定要不要关掉vad_filter。4.3 判断转写是否成功判断标准不是“字幕文件生成了”就算成功而是看三条文本内容是否与语音大意一致。每一句时间戳的开始、结束是否跨行错乱。有没有把BGM、掌声、开关门声识别成文字。建议用Subtitle Edit打开SRT在播放窗口里直接听一段语音对比字幕位置。只生成文件不校对后面翻译会一起错。4.4 如果遇到“外文硬字幕”怎么办如果你处理的视频没有独立字幕轨道但画面里已经有外文硬字幕用语音识别无效。正确的解决思路是先抽帧再用OCR识别字幕区域内容配合上下文人工翻译。这不是本文主流程但很多老剧熟肉就是这样做出来的。常见OCR工具包括PaddleOCR、Tesseract需要先框选字幕区域再按时间轴连续抽帧去重。该步骤对显存要求不高但对CPU和内存要求较高。发布此类结果时同样要确认片源授权。5. 字幕翻译人工、接口、本地大模型5.1 优先做双语对照翻译如果只是自己看直接生成中文翻译可以接受如果要给别人看建议保留原SRT文本再生成“原文译文”对照字幕方便校对和理解。翻译环节的输入是第4步生成的SRT输出是带中文翻译的SRT或双语SRT。5.2 人工翻译加术语表影视对白里经常出现角色名、专有名词、梗。比如“锤基”就是粉丝对“雷神索尔和Loki”的简称。不同创作者对名词翻译有自己的习惯。所以翻译流程里加入一个glossary.txt是有用的Thor索尔 Loki洛基 Asgard阿斯加德 Infinity Stone无限宝石在人工翻译或机器翻译后统一做一次术语替换能减少同一个角色在一部片里出现多个译名的情况。5.3 用大模型翻译SRT如果你想用大模型批量翻译最简单的做法是调用官方API或自建兼容服务。这里给一个通用Python示例请求部分需要替换成你实际使用的接口地址、模型名、密钥。import requests API_URL http://127.0.0.1:8000/v1/chat/completions MODEL_NAME local-llm API_KEY your-api-key def translate_text(text, target_lang中文): payload { model: MODEL_NAME, messages: [ {role: system, content: f把字幕翻译成{target_lang}输出仅保留译文不附加解释。}, {role: user, content: text} ], temperature: 0.2 } headers {Authorization: fBearer {API_KEY}} resp requests.post(API_URL, jsonpayload, headersheaders, timeout60) resp.raise_for_status() return resp.json()[choices][0][message][content].strip()如果需要一次翻译整份SRT可以把文件按空行拆成index/time/text结构逐条调用接口再把结果写回去。重点注意不要连续发送大量短句接口限流会变严重建议按语义合并同一段对白后再翻译减少调用次数。需要加超时和重试机制。翻译接口偶尔会返回错误简单重试一次往往就能解决。不要让模型处理完整时间轴。时间轴由代码固定模型只负责翻译文本字段否则容易把行顺序打乱。自建本地模型一般需要一定显存资源文本长度越长越吃显存。启动前先跑一个短句测试。5.4 翻译结果格式化的好处推荐的输出结构是{ index: 12, source: He is such a drama queen., translation: 他真是个戏精。 }这种结构化数据方便后续合并成双语字幕、导入审校系统、统计术语一致性。如果你的批量任务有很多集建议把每集翻译结果保存为JSON不要只保存最终SRT因为审校时可能要修改最终SRT可以再从JSON重新生成。6. 字幕校对与格式修正6.1 时间轴检查机器生成的SRT通常会有这些问题断句过长、时间戳太短、标点不统一、换行位置怪异。字幕校对可以分成两类检查。第一类是格式检查用Subtitle Edit的“修复时间冲突”减少重叠行用脚本检查单条字幕超过两行或每行过长的情况。第二类是语义检查需要人来完成。把原始视频音频在Subtitle Edit里播放逐句确认译文是否准确。影视对白里的双关、俚语和文化梗不能完全交给模型。6.2 用脚本做基础质量门禁给一个简单检查脚本思路保存为scripts/check_srt.pyimport sys from pathlib import Path srt_path Path(sys.argv[1]) text srt_path.read_text(encodingutf-8) blocks text.strip().split(\n\n) issues [] for block in blocks: lines block.splitlines() if len(lines) 3: issues.append(f缺少时间轴或文本: {block[:50]}) continue time_line lines[1] if -- not in time_line: issues.append(f时间轴格式错误: {time_line}) continue content .join(lines[2:]) if len(content) 80: issues.append(f单条字幕过长: {content[:60]}...) if issues: print(\n.join(issues[:20])) sys.exit(1) else: print(check passed)7. 字幕压制FFmpeg软字幕与硬字幕7.1 软字幕软字幕是独立字幕轨。如果平台支持内嵌字幕用软字幕会很方便观众可以开关字幕、选择切换字幕。在FFmpeg中合并到MKV时可直接把SRT作为字幕流。ffmpeg -i input/episode_01.mp4 -i work/srt_translated/episode_01.zh.srt \ -c copy -c:s srt output/episode_01_with_sub.mkv这种命令不会重新编码视频速度快压制几乎不占CPU/GPU。缺点是很多视频平台要求上传MP4而MP4对字幕流的支持不如MKV友好。7.2 硬字幕如果你想直接得到能看到字幕的成片需要做字幕烧录。FFmpeg如果编译时带libass滤镜就可以用SRT/ASS格式烧录。命令示例ffmpeg -y -i input/episode_01.mp4 \ -vf subtitleswork/srt_translated/episode_01.zh.srt:force_styleFontNameNoto Sans CJK SC,FontSize16,PrimaryColourH00FFFFFF,Outline1,Shadow0 \ -c:v libx264 -crf 20 -preset medium \ -c:a aac -b:a 192k output/episode_01_hardsub.mp4压制硬字幕有几个通用经验字体选择中文字体例如思源黑体、Noto Sans CJK SC。没有中文字体会显示成方块。subtitles滤镜要求SRT文件路径里的冒号和反斜杠要做转义Windows路径尤其容易出错。更稳妥的做法是先进入工作目录再用相对路径调用FFmpeg。crf 20是质量和压缩比平衡的一种常见配置不是标准值如果追求高画质就降低CRF到16到18。压制完成后要抽帧检查字幕是否被画面底部遮挡再决定是否需要调整垂直偏移。7.3 批量压制需要批量处理多个文件时用Shell循环或Python循环调用FFmpeg。重点是在输出日志清楚的情况下不让单个文件失败阻断整个任务。for file in input/*.mp4; do base$(basename $file .mp4) if [ -f work/srt_translated/${base}.zh.srt ]; then ffmpeg -y -i $file \ -vf subtitleswork/srt_translated/${base}.zh.srt \ -c:v libx264 -crf 20 -preset medium \ -c:a aac -b:a 192k output/${base}_hardsub.mp4 else echo missing subtitle for $base fi done这样不会因为某一个文件缺字幕而导致整个任务中断同时也会报告哪条素材缺少字幕。如果是大视频集合建议把每个文件的命令写入日志避免控制台输出刷屏。8. 使用接口与批量任务设计8.1 用FastAPI暴露转写服务如果不想每段视频都手动执行脚本可以包一个转写服务把第4步的逻辑变成HTTP接口。这是一个通用模板需要按实际项目路径调整。保存为scripts/server.pyfrom fastapi import FastAPI, UploadFile, File, Form from pathlib import Path from faster_whisper import WhisperModel app FastAPI() model WhisperModel(models/whisper, deviceauto, compute_typeint8) def _fmt(ts: float): millis int((ts % 1) * 1000) seconds int(ts) return f{seconds // 3600:02d}:{(seconds % 3600) // 60:02d}:{seconds % 60:02d},{millis:03d} app.post(/api/transcribe) async def transcribe( file: UploadFile File(...), language: str Form(en) ): tmp Path(work/upload) / file.filename tmp.parent.mkdir(parentsTrue, exist_okTrue) tmp.write_bytes(await file.read()) segments, info model.transcribe(str(tmp), languagelanguage, vad_filterTrue) srt_lines [] for i, segment in enumerate(segments, start1): text segment.text.strip() start_ts segment.start end_ts segment.end srt_lines.append(f{i}\n{_fmt(start_ts)} -- {_fmt(end_ts)}\n{text}\n) return {srt: \n.join(srt_lines)}启动命令uvicorn scripts.server:app --host 127.0.0.1 --port 8000启动后可以用curl测试curl -X POST http://127.0.0.1:8000/api/transcribe \ -F filework/audio/episode_01.wav \ -F languageen注意这里只是演示接口形态。实际部署时要注意接口访问范围不要直接把带外网IP的8000端口暴露到公网。如果只是本地处理绑定127.0.0.1就够了。8.2 转写请求参数建议参数类型建议说明file音频文件wav/m4a/flac上传前最好先抽成16kHz音频languagestringzh/en等省略时自动检测但速度更慢beam_sizeint5数值高精度更好速度下降vad_filterbooltrue过滤静音节省计算word_timestampsboolfalse普通字幕不需要词级时间戳8.3 批量任务管理当你有几十个文件时最需要做的是任务状态追踪。建议每个视频分配一个任务记录状态包括pending、running、finished、failed。没有复杂队列时可以先用SQLite或JSON记录不必一开始引入任务队列中间件。工作流扫描input/目录把待处理文件写入队列。逐个抽音频、跑转写、翻译、压制。每一步完成后把中间结果写入work/状态更新为当前步骤。如果某一集失败跳过并记录日志全部跑完后重试failed任务。日志字段建议至少包含文件名、开始时间、结束时间、耗时、步骤、输出路径、错误信息。这样后续排查不会不知道哪一步出了问题。9. 资源占用与性能观察9.1 显存与内存怎么看执行转写脚本或FastAPI服务时如果使用NVIDIA显卡用以下命令观察实时显存占用nvidia-smi dmon -s mu -d 1如果要细看某个进程的占用可以用nvidia-smi --query-compute-appspid,used_memory,process_name --formatcsv在Windows上任务管理器的“性能”页面可以看到GPU显存占用。注意显存占用不是固定值它会随模型尺寸、输入音频时长、beam_size和是否开启词级时间戳变化。不要直接照搬别人“6G够
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻