ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python+FFmpeg实现逐字歌词字幕:ASS卡拉OK效果与视频烧录实战

Python+FFmpeg实现逐字歌词字幕:ASS卡拉OK效果与视频烧录实战 平时做短视频合成或二创剪辑时最让人头疼的往往不是视频本身而是字幕。尤其是英文歌里带拼字效果的口播或歌词比如 “D to the E, to the L-I-C-I-O-U-S”如果只是把一行字直接压上去节奏感和观赏性都会差很多。本文就围绕一个真实场景来写假设你在维护一个叫“盒盒短视频”的栏目需要给编号 260809 的物料做逐字歌词字幕画面里出现的艺人名字分别是玹硕、道荣、炡禹。文章会从字幕格式选型开始逐步演示如何用 Python 生成 ASS 卡拉 OK 字幕再用 FFmpeg 把字幕烧录进视频。全程命令和代码都是可以直接复制的适合短视频运营、二创剪辑、音视频开发入门者。1. 字幕技术选型为什么推荐 ASS 加 FFmpeg1.1 短视频字幕的三种常见做法给视频加字幕业内主要有三种做法方案原理优点缺点剪辑软件手动加字幕在 PR、剪映、Final Cut 中逐条添加文字直接、所见即所得无法批量处理时间轴一长效率低硬字幕烧录用 FFmpeg 等工具把文字直接画进画面自动、可控、可批量无法二次编辑封装软字幕生成 ASS/SRT 字幕文件播放器动态渲染可切换、可改样式部分平台不支持软字幕对于逐字变色、逐字母高亮这种效果最合适的不是 SRT而是 ASS。SRT 只能整行显示没法指示“哪个词先亮、哪个词后亮”而 ASS 自带的卡拉 OK 标签能精确控制每一个词、甚至每一个字母的高亮时间。1.2 为什么用 Python 生成字幕手动在 Aegisub 里逐条敲字幕也不是不行但缺点是慢。如果你的素材很长或者每周都要产出类似内容纯手工维护时间轴会非常痛苦。Python 的优势在于可以把“歌词文本”和“时间轴”变成结构化数据然后一键生成字幕文件。本文的核心流程是歌词文本 时间轴JSON - Python 脚本 - ASS 字幕 - FFmpeg 烧录 - 成片每一条链路都可以独立测试出错时也能快速定位到具体环节。2. 环境准备与项目结构2.1 软件环境本文示例基于以下环境版本不必完全一致思路是通用的操作系统Windows 10 / macOS / Ubuntu 均可Python 3.8 及以上FFmpeg 4.4 及以上编译时需要开启 libass 支持文本编辑器VS Code、Sublime Text 或任意编辑器先检查 FFmpeg 是否支持 ASS 滤镜ffmpeg -filters | grep ass如果输出里包含类似ass或subtitles的行说明当前 FFmpeg 支持字幕滤镜。如果没有建议换成完整版 FFmpeg或者用包管理器安装# Ubuntu / Debian sudo apt install ffmpeg libass-dev # macOS brew install ffmpeg --with-libassWindows 用户推荐从 FFmpeg 官网下载 full build 版本解压后配置好环境变量即可。2.2 项目目录设计为了后续扩展我们先把目录建清楚karaoke_project/ ├── input/ │ └── treasure_260809.mp4 ├── lyrics/ │ └── timeline.json ├── scripts/ │ ├── build_ass.py │ └── burn_subtitle.sh ├── fonts/ │ └── msyh.ttc └── output/ ├── treasure_260809.ass └── treasure_260809_burned.mp4其中input/存放原始视频素材lyrics/存放歌词和时间轴数据scripts/存放 Python 和 Shell 脚本fonts/存放自定义字体output/存放生成的字幕和成品视频。2.3 素材与字体准备示例中需要准备一段时长约 10 秒的视频素材。如果手头没有现成视频可以先用 FFmpeg 生成一段测试视频ffmpeg -f lavfi -i colorcblack:s1920x1080:r30 -t 10 -c:v libx264 -pix_fmt yuv420p input/treasure_260809.mp4这条命令会生成一段 10 秒、1920x1080、30 帧的黑屏测试视频。实际项目中替换成真实素材即可。中文字幕最容易踩的坑是字体。如果 FFmpeg 找不到中文字体中文会显示成方框。建议提前准备一个支持中文的字体文件例如微软雅黑msyh.ttc、思源黑体SourceHanSansSC-Regular.otf放到fonts/目录下。3. ASS 字幕与卡拉 OK 标签原理3.1 ASS 文件基础结构ASSAdvanced SubStation Alpha是一种功能强大的字幕格式。一个最简单的 ASS 文件包含四部分[Script Info] ScriptType: v4.00 PlayResX: 1920 PlayResY: 1080 [V4 Styles] Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding Style: Karaoke,Arial Black,80,H00FFFFFF,H0000FFFF,H00000000,H64000000,-1,0,0,0,100,100,0,0,1,4,2,2,40,40,40,1 [Events] Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text Dialogue: 0,0:00:00.00,0:00:02.00,Karaoke,,0,0,0,,{\k50}Hello几个关键点PlayResX和PlayResY是字幕画布分辨率通常和视频分辨率一致推荐设为 1920x1080Style行定义字幕样式包括字体、字号、颜色、描边、阴影、对齐方式Event行是真正显示的字幕内容格式是Dialogue: 图层,开始时间,结束时间,样式,...{\k50}Hello是卡拉 OK 标签表示Hello这个词会在 50 厘秒内完成高亮填充。ASS 中颜色的格式是HAABBGGRR其中 AA 是透明度BB 是蓝色通道GG 是绿色通道RR 是红色通道。注意这里不是常规的 RGB 顺序而是 BGR。3.2 \k 标签的工作方式在 ASS 里卡拉 OK 标签常用三个标签含义\k匀速填充高亮填充时间单位是厘秒1/100 秒\K填充完再跳变时间单位同样是厘秒\kf等同于\K用于向后兼容举个例子{\k100}D {\k50}to {\k50}the E这段字幕会先高亮D持续 1 秒接着高亮to持续 0.5 秒最后高亮the E持续 0.5 秒。高亮颜色由样式里的 SecondaryColour 控制默认文字颜色由 PrimaryColour 控制。这里需要特别注意\k标签是“放在某个词前面”的控制的是它后面紧接着的那个片段。如果你写过 CSS可以把它理解为一种“前置样式指令”。3.3 拼字文本的拆分规则回到标题里的这句 “D to the E, to the L-I-C-I-O-U-S”。它是一句典型的拼字歌词把 “Delicious” 拆成了字母。如果整句作为一个卡拉 OK 块效果是整句一起变没有“逐个字母点亮”的感觉。所以要实现最理想的效果需要把文本拆成高亮单位普通单词按空格拆分带连字符的单词按字母逐个拆分标点符号可以独立作为单位也可以合并到前一个词。例如L-I-C-I-O-U-S会被拆成L、I、C、I、O、U、S七个字母每个字母分配一段高亮时间。4. 实战用 Python 生成逐字歌词字幕4.1 编写时间轴 JSON为了让脚本可复用我们把歌词和时间轴单独放到一个 JSON 文件里避免每次改时间都动代码。文件路径lyrics/timeline.json{ video_width: 1920, video_height: 1080, font_name: Microsoft YaHei, lines: [ { start: 0.5, duration: 3.5, text: D to the E, to the L-I-C-I-O-U-S }, { start: 4.2, duration: 1.5, text: TREASURE }, { start: 6.0, duration: 2.5, text: 玹硕 道荣 炡禹 } ] }这里font_name写的是系统字体名不一定等于字体文件名。比如微软雅黑的文件名是msyh.ttc但字幕里使用的字体名通常要写Microsoft YaHei。不同系统字体名可能略有差异Windows 上一般写Microsoft YaHeimacOS 上可以写PingFang SCLinux 上可以写Noto Sans CJK SC。4.2 实现卡拉 OK 文本拆分文件路径scripts/build_ass.py# -*- coding: utf-8 -*- 根据 lyrics/timeline.json 生成 ASS 卡拉 OK 字幕。 用法python scripts/build_ass.py import json import os BASE_DIR os.path.dirname(os.path.dirname(os.path.abspath(__file__))) TIMELINE_PATH os.path.join(BASE_DIR, lyrics, timeline.json) OUTPUT_PATH os.path.join(BASE_DIR, output, treasure_260809.ass) def seconds_to_ass_time(sec): 将秒数转换为 ASS 时间格式H:MM:SS.CC total_cs int(round(sec * 100)) h, rem divmod(total_cs, 3600 * 100) m, rem divmod(rem, 60 * 100) s, cs divmod(rem, 100) return f{h}:{m:02d}:{s:02d}.{cs:02d} def split_karaoke_units(text): 将一行文本拆分成高亮单元。 带连字符的英文单词按字母拆分普通单词按整体保留。 units [] for word in text.split(): if - in word: # 例如 L-I-C-I-O-U-S - [L,I,C,I,O,U,S] letters [ch for ch in word if ch.isalpha()] # 去掉连字符只保留字母 units.extend(letters) else: units.append(word) return units def build_ass(timeline, output_path): width timeline.get(video_width, 1920) height timeline.get(video_height, 1080) font_name timeline.get(font_name, Microsoft YaHei) header f[Script Info] ScriptType: v4.00 PlayResX: {width} PlayResY: {height} WrapStyle: 0 ScaledBorderAndShadow: yes [V4 Styles] Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding Style: Karaoke,{font_name},80,H00FFFFFF,H0000FFFF,H00000000,H64000000,-1,0,0,0,100,100,0,0,1,4,2,2,40,40,40,1 [Events] Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text events [] for line in timeline[lines]: start line[start] duration line[duration] text line[text] units split_karaoke_units(text) if not units: continue # 每个高亮单元平分该行时长 unit_duration duration / len(units) unit_cs int(round(unit_duration * 100)) # 为每个单元生成 {\kXX} 标签 parts [] for unit in units: parts.append(f{{\\k{unit_cs}}}{unit}) karaoke_text .join(parts) end start duration start_str seconds_to_ass_time(start) end_str seconds_to_ass_time(end) events.append( fDialogue: 0,{start_str},{end_str},Karaoke,,0,0,0,,{karaoke_text} ) with open(output_path, w, encodingutf-8) as f: f.write(header \n.join(events) \n) print(f[OK] ASS 字幕已生成{output_path}) def main(): with open(TIMELINE_PATH, r, encodingutf-8) as f: timeline json.load(f) build_ass(timeline, OUTPUT_PATH) if __name__ __main__: main()这段代码做了几件事读取timeline.json拿到时间和文本数据seconds_to_ass_time把秒换算成 ASS 的时间格式split_karaoke_units把歌词拆成“词”或“字母”的高亮单元build_ass生成完整的 ASS 文件内容并写入output/目录。运行方式python scripts/build_ass.py如果一切正常会看到输出[OK] ASS 字幕已生成output/treasure_260809.ass打开生成的 ASS 文件可以看到类似这样的内容Dialogue: 0,0:00:00.50,0:00:04.00,Karaoke,,0,0,0,,{\k23}D {\k23}to {\k23}the {\k23}E, {\k23}to {\k23}the {\k23}L{\k23}I{\k23}C{\k23}I{\k23}O{\k23}U{\k23}S这里{\k23}表示每个字母高亮约 0.23 秒总时长 3.5 秒14 个单元平均每个单元 0.25 秒取整后是 23 厘秒左右合计时长会有一点误差。实际可以接受如果要求精确可以改成浮点毫秒再在标签里四舍五入但多数场景下 1/100 秒级别的误差不影响观看。4.3 扩展支持不同单词不同时长上面的方案是“平均分配”适合节奏均匀的口播或歌词。但真实音乐里长单词和短单词占的拍子不一样。如果希望更精确可以给 JSON 加上自定义时长字段。例如{ start: 0.5, duration: 3.5, text: D to the E, to the L-I-C-I-O-U-S, highlights: [ {unit: D, duration: 0.3}, {unit: to, duration: 0.2}, {unit: the, duration: 0.2} ] }脚本中优先读取highlights如果存在就不做平均分配而是按照每个单元的时间累加。这样能做出和音乐鼓点严格对齐的效果。5. 实战用 FFmpeg 烧录字幕到视频5.1 基础烧录命令ASS 文件生成后接下来就是把字幕画到视频画面上。在output/目录下执行ffmpeg -y \ -i input/treasure_260809.mp4 \ -vf assoutput/treasure_260809.ass:fontsdirfonts \ -c:v libx264 -crf 18 -preset medium \ -c:a copy \ output/treasure_260809_burned.mp4参数说明参数作用-i指定输入视频-vf应用视频滤镜ass...表示渲染 ASS 字幕fontsdir指定字体搜索目录指向项目里的fonts/文件夹-c:v libx264视频编码使用 H.264-crf 18画质参数数值越小质量越高18 是视觉无损的常见值-c:a copy音频直接复制不做重编码速度快且不损失音质如果你用的 FFmpeg 版本比较老没有ass滤镜也可以使用subtitles滤镜ffmpeg -y \ -i input/treasure_260809.mp4 \ -vf subtitlesoutput/treasure_260809.ass:fontsdirfonts \ -c:v libx264 -crf 18 \ -c:a copy \ output/treasure_260809_burned.mp45.2 中文显示为方框的问题这是最常遇到的问题。如果烧录出来的视频里中文全部变成方框通常不是 ASS 文件的问题而是 FFmpeg 渲染时找不到中文字体。排查分三步确认fonts/目录里确实存在中文字体文件确认 ASS 的Fontname写的是系统可识别的字体名而不是随意起的名字确认fontsdir路径正确。在 Linux 上如果fontsdir方式不生效可以先把字体安装到系统字体目录mkdir -p ~/.fonts cp fonts/msyh.ttc ~/.fonts/ fc-cache -f然后再执行 FFmpeg 命令。Windows 上通常安装的字体系统本身就能识别重点检查 ASS 里的字体名是否匹配。5.3 验证输出结果烧录完成后可以用 FFmpeg 抽取几帧画面检查字幕效果ffmpeg -i output/treasure_260809_burned.mp4 -ss 1 -vframes 1 output/frame_1s.png打开output/frame_1s.png如果字幕正常显示且能看到部分歌词高亮说明流程已经跑通。如果你使用的是带图形界面的系统也可以直接用播放器打开烧录后的视频确认效果。6. 常见问题与排查思路下面整理了几个高频问题基本覆盖了从生成字幕到烧录的完整链路。问题现象常见原因解决思路Python 脚本运行报ModuleNotFoundError没有安装依赖或路径错误本示例只使用标准库检查是否误用了第三方库确认脚本在当前项目根目录运行ASS 文件生成时间全是 0:00:00.00seconds_to_ass_time传入负数或 NaN检查timeline.json里的 start 和 duration 是否为正数烧录后中文字幕变成方框FFmpeg 找不到中文字体使用fontsdir指定字体目录或在系统里安装中文字体字幕整体不同步timeline.json里的时间轴不准用播放器逐帧确认歌词开始时间调整 start 值高亮颜色没有变化样式表中 PrimaryColour 和 SecondaryColour 相同检查 ASS 样式确保两个颜色值不同字母逐个高亮效果太碎L-I-C-I-O-U-S被平均分配了很短的时长增大该行 duration或改用highlights手工指定每个字母时长FFmpeg 报No such filter: assFFmpeg 编译时未开启 libass更换 FFmpeg 版本或用包管理器重新安装再补充一个容易踩的坑在 Windows 命令行中FFmpeg 滤镜里如果用反斜杠路径需要转义。比如-vf assoutput\\treasure_260809.ass否则可能解析失败。更稳妥的方式是先把当前目录切到项目根目录再使用相对路径。6.1 如何快速定位问题环节建议按下面的流程图排查生成视频素材 - 运行 build_ass.py - 打开 ASS 文件检查时间轴和颜色 | v FFmpeg 烧录 | v 播放器预览字幕是否出现中文是否正常高亮是否同步每一步都能独立验证。不要把 Python 和 FFmpeg 混在一起排查出错后先确认 ASS 文件在 Aegisub 里能否正常预览再考虑 FFmpeg 的参数问题。7. 最佳实践与工程建议7.1 时间轴设计逐字歌词字幕的核心是“卡上节奏”。项目初期建议先把音频导入剪辑软件标记出每个词或每个字母的起止时间再整理成 JSON。不要靠肉眼猜时间差 100 毫秒观看体验都会很怪。如果你的素材是音乐类内容可以考虑从 BPM 计算单位时长。假设一首歌是 120 BPM那么每拍 0.5 秒每半拍 0.25 秒把歌词单元时长设置成 0.25 秒的整数倍视觉节奏会更舒服。7.2 字体与编码生产环境中字幕文件统一使用 UTF-8 编码。中文歌词建议使用支持中文的字体例如思源黑体、阿里巴巴普惠体、汉仪字库等。注意字体授权问题商用项目优先选择可免费商用的字体避免字体版权风险。ASS 文件的样式尽量集中管理。如果做系列视频可以把[V4 Styles]部分抽成模板Python 脚本只负责动态生成[Events]这样后续调样式时只需要改模板不需要重新改代码。7.3 关键消息素材版权与平台合规这一点必须单独强调。如果你在短视频中使用了艺人素材或带版权的音乐需要提前确认授权范围。个人学习和技术测试没有问题但发布到公开平台时尤其是涉及商业变现的内容要遵守平台版权规则和著作权法。“盒盒短视频”这类项目如果涉及真实艺人建议优先使用官方提供的素材或已授权素材背景音乐尽量使用平台版权曲库或原创音乐字幕内容不要恶意剪辑、恶意解读面向公开传播的内容做好版权自查记录。7.4 自动化集成如果每周要生产多条类似视频可以把整个流程做成一条命令python scripts/build_ass.py ffmpeg -y \ -i input/treasure_260809.mp4 \ -vf assoutput/treasure_260809.ass:fontsdirfonts \ -c:v libx264 -crf 18 \ -c:a copy \ output/treasure_260809_burned.mp4也可以再接一个发布脚本把成品视频上传到对象存储或内容平台。只要时间轴 JSON 足够规范字幕生成和烧录就能完全自动化。7.5 性能优化FFmpeg 烧录字幕属于视频重编码会比较吃 CPU。如果只是本地调试可以把-crf调大到 23 以加快速度如果需要高质量成片再使用 18 或更低。批量处理时建议用-preset veryfast先出一版草稿确认效果后再用medium或slow压正式版。8. 总结这篇文章从一个具体的短视频需求出发完整演示了逐字歌词字幕的制作链路用 JSON 组织歌词时间轴用 Python 把歌词文本拆成词或字母级别的高亮单元生成 ASS 卡拉 OK 字幕用 FFmpeg 把字幕烧录到视频。通过这个案例你不仅掌握了一条实用的短视频字幕生产流程也理解了 ASS 字幕的核心语法和 FFmpeg 滤镜的基本用法。下一步可以继续学习 ASS 样式进阶比如动态字体、滚动字幕、跑马灯效果或者研究用librosa自动检测 BPM 来对齐音乐节奏。如果你正在做系列短视频内容建议把时间轴 JSON、字体模板、Python 脚本都沉淀成团队内部工具后续新视频只需要填时间轴就能出片效率会提升非常多。如果这篇文章对你有帮助可以收藏备用也欢迎在实际项目中复现后根据自己的需求继续扩展。
返回列表