演示视频字幕制作器 是做什么的?
问题
屏幕录制的产品演示视频会因三个与产品本身无关的原因而让观众失望:UI 文字语言不对、文字太小难以阅读,以及视频通常没有旁白(静默录制)。观众看到点击操作,但无法理解其意图。字幕可以解决这个问题——但前提是字幕样式在复杂的 UI 上清晰可读,位置不遮挡正在演示的内容,并且时间轴精确,不会在切换时被截断。
本技能提供完整的处理流程,同时涵盖了那些只有亲身踩坑后才会发现的隐性失败模式。
何时使用
- "为这个演示视频添加字幕" / "为我们的韩国团队给这个录制内容加字幕"
- 静默屏幕录制(
.mp4,无音频流,或仅有背景音乐),必须通过字幕文本来讲述故事 - 向非英语观众展示的纯英文产品演示
- 字幕已存在,但位于底部中央并遮挡了演示面板——需要改为跟随焦点放置
- 生成供演示者口播的讲稿(请参阅“配套用途”)
不要使用本技能进行:合成语音配音(这属于 TTS 流程),或提取平台上已有的字幕。
安装(一条命令)
scripts/bootstrap.sh --dry-run # 仅检查:列出缺失的依赖
scripts/bootstrap.sh # 安装缺失的依赖(会询问一次)
scripts/bootstrap.sh --yes --lang ja # 无人值守;同时准备日语字体
bootstrap.sh 是幂等的——在已准备好的机器上不会执行任何操作。它会检测操作系统并选择合适的安装器(Homebrew / apt / dnf / pacman / winget / scoop),当没有包管理器可用时,会回退到将静态 ffmpeg 放入 ~/.local/bin。在任何一个需要渲染字幕的会话中,应首先运行此脚本;技能没有安装时钩子,因此这个脚本 就是 安装程序。
先决条件
首先运行内置检查脚本——它会检查机器并准确告知缺少什么:
scripts/doctor.sh "Hiragino Sans GB" # 传入您打算使用的中日韩字体族
检查的内容以及缺少时的安装命令:
| 要求 | 原因 | 安装命令 |
|---|---|---|
ffmpeg 内置 libass | 用于渲染 .ass 字幕(-vf ass=...) | macOS brew install ffmpeg;Windows:gyan.dev 完整版 构建;Debian apt install ffmpeg |
ffmpeg 构建中包含 --enable-fontconfig | 通过字体族名称解析字体;否则会静默回退到默认字体 | 与上述相同的构建已包含此功能 |
| 系统可解析的中日韩字体 | 字幕文字本身 | macOS:Hiragino Sans GB(预装)· Windows:Microsoft YaHei(预装)· Linux:apt install fonts-noto-cjk |
python3(可选) | 仅用于 export_cues.py;使用标准库,无需 pip 安装 | macOS:xcode-select --install;Linux:apt install python3 |
bash、zip(可选) | 脚本及打包分发 | 各系统均已预装 |
无需 Node、ImageMagick、pip 包或 API 密钥。具备视觉能力的模型(即代理本身)会读取帧——这无需安装。
两个需要注意的环境变量:
- 将
ffmpeg加入PATH,或通过环境变量指定其位置供脚本使用:FFMPEG=/path/to/ffmpeg scripts/burn.sh ...。 - Windows:libass 根据构建方式通过 fontconfig/DirectWrite 解析字体。如果字幕以错误的字体渲染,请安装 fontconfig(
scoop install fontconfig或 MSYS2)并重新检查:fc-match "Microsoft YaHei:bold"。
处理流程
按顺序从上到下执行。不要跳过第 2 阶段——后续每个阶段都依赖于了解 UI 几何信息。
第 1 阶段——信息收集(2 分钟)
scripts/probe.sh INPUT.mp4
记录:时长、分辨率、帧率、码率、是否存在音频流。在脚本内部运行音频检查(平均音量 + 静音映射)。
根据音频检查结果决定字幕类型:
| 音频情况 | 字幕类型 |
|---|---|
| 无音频流 | 叙事字幕 —— 您需要编写故事(“发生了什么 / 为什么重要”) |
| 有音频但无语音(仅音乐) | 同上。通过转录一段 10 秒的片段来确认:如果 ASR 工具在强音频下返回空或接近空的结果,则表示是音乐而非语音 |
| 存在语音 | 对话字幕 —— 首先带时间戳转录,然后清理;不要创作文案 |
第 2 阶段——视觉分析与几何信息(人们常跳过的阶段)
以 2 fps 提取帧(60 秒剪辑约 120 帧;先每隔 4 帧浏览一遍了解剧情,然后放大查看文字):
scripts/split_frames.sh INPUT.mp4 frames/ 2
在编写任何字幕之前,先构建三个制品:
-
节拍表 —
| 时间 | 屏幕上发生了什么变化 | 屏幕上的文字(逐字) |。一个“节拍”指任何变化:出现通知、滑入面板、点击按钮、滚动、页面切换、过渡。注意过渡类型(白色闪屏、切换、黑屏)——字幕不得跨越它们。 -
UI 文字,逐字读取,基于原始分辨率。 缩小的预览帧会使小 UI 文字模糊,导致转录错误。从原始文件裁剪:
ffmpeg -i IN.mp4 -ss 12.5 -frames:v 1 -vf "crop=W:H:X:Y,scale=2*iw:-1" out.jpg(将-ss放在-i后面以实现帧级精确提取;请参阅陷阱。) -
几何映射 — 以 源像素 为单位测量“活动”面板(正在演示的内容)的像素边界。请测量,不要凭感觉:
scripts/find_edge.sh INPUT.mp4 30 2880 300 # t=30s,从 x=2880 开始条带,宽度 300px然后读取返回的条带图像,注意面板左边缘在条带内的位置;将该偏移量加上条带的 x 值,得到绝对边缘值。如果布局发生变化(例如稍后打开了聊天面板),则对其他场景重复此操作。
根据几何映射得出的规则: 字幕应位于演示面板 相邻的空白区域,永远不要覆盖在面板之上。请参阅第 5 阶段。
第 3 阶段——编写字幕文案
两条规则至关重要:
- 每个时间片段一个想法。 最多 2–3 行短句。一个时间片段是一个句子,而非一段文字。
- 阅读速度预算。 中日韩:3.5–5.5 字符/秒为舒适;超过 6 会显得仓促;低于 3 感觉沉闷。拉丁文:12–18 字符/秒。请计算,不要猜测。
scripts/export_cues.py会在您创建.ass文件后打印每个片段的密度——根据该数值进行迭代调整。
片段时间规则:
- 片段 在视觉节拍发生的同时 开始,不要提前。如果通知在 1.0 秒时滑入,片段应在 1.0 秒时开始。
- 在过渡(白色闪屏/切换)之前 结束片段,至少留出 0.8 秒的静默时间。跨越过渡的字幕看起来像故障。
- 留有真实间隙。每分钟留出三到五秒的空白时间,可使视频有呼吸感;连续不断的字幕会让观众感到疲惫。
- 对于叙事字幕,在字幕中说明 原因,而非机制:“代理主动联系用户”优于“右上角出现了一个通知”。
在此处停止并获得文案的批准。 文字批准后再进行样式设计。样式设计完成后文字被拒绝,将导致第二次不必要的渲染。
第 3b 阶段——选择字幕语言
流程不限定语言,但在进行样式设计之前,必须针对每种语言做出三个决定。
1. 字体族 — 选择一个平台可解析的字体(通过 scripts/doctor.sh <lang> 验证):
| 字幕语言 | macOS | Windows | Linux |
|---|---|---|---|
| 中文 (zh) | Hiragino Sans GB / PingFang SC | Microsoft YaHei | Noto Sans CJK SC |
| 日语 (ja) | Hiragino Sans | Yu Gothic / Meiryo | Noto Sans CJK JP |
| 韩语 (ko) | Apple SD Gothic Neo | Malgun Gothic | Noto Sans CJK KR |
| 拉丁文 (en/fr/de/es…) | Helvetica Neue | Segoe UI | Inter / DejaVu Sans |
2. 阅读速度 — 预算取决于文字系统;scripts/export_cues.py 会自动选择正确的速度(可通过 --lang 覆盖),并标记超出预算的片段:
| 文字系统 | 舒适 | 标记为过快 |
|---|---|---|
| 汉字/假名 (zh, ja) | 3.5–5.5 字符/秒 | > 6.0 |
| 韩文 (ko) | 4.0–6.0 字符/秒 | > 6.5 |
| 拉丁文 | 12–18 字符/秒 | > 19 |
3. 断行 — 中文和日文没有词间空格:应在标点处(U+FF0C、U+3001、U+3002、U+FF1A)断行,且永远不要拆分产品名称。韩文和拉丁文在空格处断行;保持韩语助词与其名词相连。如果一行放不下,应先重新换行,再考虑缩小字体——更小的字体对可读性的损失比增加一行更大。
4. 按文字系统计算行宽 — 不同文字系统的字形宽度不同,因此相同的 Fontsize 会产生不同的行长。预算行数为 floor(可用像素 / (字体大小 * k)),其中 k 约为 1.0(汉字),1.05–1.1(韩文、假名,它们更宽),平均 0.5(拉丁文,随单词变化)。
以 4K 分辨率、Fontsize 165 测量:13 个韩文字符 ≈ 2,150 px(在 2,450 px 的槽位中安全),而 18 个字符 ≈ 2,970 px——这会导致溢出,将行推出画面左边缘。当一行放不下时,请重新换行;不要为了放下它而缩小字体。
双语字幕(原文 + 翻译):仅在两行都较短时使用。双语会增加阅读负担,并且在演示中会与 UI 竞争。除非客户另有要求,否则每个文件版本最好只使用一种语言;必须使用时,将原文放在上面,译文放在下面,并按 译文的 阅读速度预算整个块。
从右到左的文字系统(阿拉伯语、希伯来语):libass 支持从右到左,但字形塑造质量取决于构建中的 harfbuzz 版本。在提交之前渲染一个预览帧——检查字母是否正确连接,并且对齐锚点仍然有意义。
字体粗细因文字系统而异。 日语和韩语字体族在相同的 Fontsize 下通常看起来比中文字体族更大;用一个预览帧进行验证并根据视觉调整,而非死板规则。
第 4 阶段——样式(双层发光)
一个单独的 ASS 样式无法产生“白色文本、厚彩色描边和柔和彩色光晕”的效果——Outline 只能绘制一层。使用两个事件,每个片段分布在两层上:
| 图层 | 填充 | 描边 | 模糊 | 用途 |
|---|---|---|---|---|
| 0 | 透明(&HFF000000) | 宽、半透明紫色 | \blur 20–30 | 光晕 |
| 1 | 白色 | 较窄、实心紫色 | 无 / \blur1 | 可读文字 |
起始点(请参见 assets/caption-template.ass)。颜色格式为 &HAABBGGRR —— 透明度 00 为不透明,FF 为完全透明,字节顺序为 BGR:
紫色 RGB(124,46,240) → &H00F02E7C
光晕,35% 不透明 → &H55F02E7C
根据 PlayRes 缩放,而非凭感觉:4K 渲染使用 fontsize 180 / outline 16 / halo 28 / blur 24;1080p 下的相同外观为 80 / 7 / 12 / 12。将 PlayResX/Y 设置为 输出分辨率,并保持 ScaledBorderAndShadow: yes。
第 5 阶段——跟随焦点的放置
对于演示而言,底部中央放置是错误的:底部通常是 UI,而演示面板是唯一必须保持可见的内容。将每个片段锚定在 与所显示内容相邻的 空白区域:
| 此片段的焦点 | 锚点 | 原因 |
|---|---|---|
| 侧面板/聊天面板打开 | \an6\pos(<panel_left - 40>, <panel_vertical_middle>) | 右边缘紧贴面板,块向左生长,宽度变化无需重新计算 |
| 右上角的提示/通知 | \an3\pos(<toast_right>, <toast_bottom + 80>) | 右下锚点将块放置在提示正下方 |
| 全宽页面,无面板 | \an5\pos(<visual_center_x>, <y between two content blocks>) | 居中,但放置在测量出的间隙中——而非几何中心 |
| 含有大号内置文字的标题卡 | \an5\pos(center_x, 标题之下) | 几何中心会与视频本身的标题冲突 |
在写入之前计算最大行宽:max_line_px ≈ fontsize × characters。如果一行会超出空白区域,请重新换行(切勿仅为单个片段缩小字体)——在标点处断开并保持产品名称完整。
对于右对齐的块,在纯色背景上溢出几十个像素是可以的,看起来像有意为之;但溢出到演示面板上则不行。
保持锚点远离画面边缘。 描边和光晕会超出字形框约 outline + blur 像素(在 4K 分辨率下,outline 15 / blur 22 约为 40 px,再加上抗锯齿)。因此,一个距离右边缘 50 px 的 \an3 锚点会导致其光晕被裁剪,看起来像文字残缺。请将锚点放置在距画面边缘至少 150 px 的位置,并在预览帧中检查结果——裁剪在 .ass 文件中不可见,但在屏幕上显而易见。
第 6 阶段——渲染
scripts/burn.sh INPUT.mp4 subs.ass OUT.mp4 [crf]
这是 -i IN -vf "ass=subs.ass" -c:v libx264 -crf 17~18 -pix_fmt yuv420p -movflags +faststart,保留原始音频轨道。对于演示视频,硬字幕是合适的默认选择——它们能在任何播放器、上传和屏幕共享中存活;仅在要求时才额外提供 .ass/.srt 文件。
第 7 阶段——验证
首先渲染便宜的静态帧,然后验证编码后的文件:
scripts/preview_frame.sh INPUT.mp4 subs.ass 16.0 /tmp/check1.jpg
检查每个帧:文字是否覆盖演示面板、文字是否超出画面、意外的换行、本应结束的片段、光晕在明亮背景上是否模糊成一团。然后从 最终 文件中重新提取 3 帧(字幕已渲染,质量已固定)并再次确认。
陷阱(每一个都曾花费大量时间才能发现)
- 将
-ss放在-i之前会使ass滤镜失效。 输入定位会重置时间戳为零,因此滤镜会为每一帧渲染 t≈0 时的字幕状态。结果:视频渲染后完全没有字幕,并且 ffmpeg 不会打印任何错误。始终将-ss放在-i之后(输出定位),或添加-copyts。附带的脚本已经处理了这一点。 - 预览帧坐标会骗人。 一个缩放到 1280×720 但以原始分辨率显示给你的帧,其文本坐标会偏移。始终从原始文件裁剪:
-i IN.mp4 -vf "crop=W:H:X:Y,scale=200%"(-ss在-i之后)。 - ASS 没有发光层。 没有
\blur\bord组合能同时产生描边和发光。你必须使用两层,分别以不同的Outline和Blur设置。 - 片段跨越切换。 一个从切换前 300 毫秒延伸到切换后 200 毫秒的片段会在屏幕上产生半秒的视觉垃圾。
export_cues.py会检查这一点:scripts/audit_transitions.sh subs.ass 1.0。 - 将命令输出视为正确的。
ffmpeg对缺少的 libass 构建、缺少的字体以及负坐标都保持沉默。输出一个 2 MB 的文件并不意味着它已正确渲染。始终提取一个预览帧并目视检查。 - 相同字体大小的中日韩文字看起来更小。 拉丁文字体的 x 高度约为汉字的一半;一个
Fontsize 20的英文字母看起来是Fontsize 20汉字大小的一半。对于混合语言的字幕(例如“点击 Submit”),要么使用两个\fn覆盖,要么大幅增加中日韩文字的字体大小。
配套用途
这个管道会输出一个时间轴精确的逐字文本故事。你可以因此而免费获得一份讲稿:
scripts/export_cues.py subs.ass --script latin > script.txt
该脚本会打印一个制表符分隔的表:| cue_start | cue_end | text | duration | density |。去掉密度列,将时间戳转换为说话节奏(每 3–5 秒一个句子),你就得到了一份演示者可以口播的稿子。讲稿质量与字幕文案质量相同——在这个管道中,文案是事实性地生成的,而非创作性地。
服务端/自动化
所有脚本都导出 INPUT、FRAMES_DIR、SUBS、OUTPUT 并将函数化。对于 CI/CD 自动化:
INPUT=demo.mp4 FRAMES_DIR=./frames SUBS=./caption.ass OUTPUT=./demo_sub.mp4 scripts/burn.sh
对于需要高吞吐量的自动化,考虑设置 export CUES_DRY_RUN=1 以使 export_cues.py 在无 Python 的环境中对 JSON 文件进行操作。