跑团圈子里有一个很普遍的现象:团跑完了,群里的语音记录就沉底了。精彩的COC跑团现场和真实人生里的高光时刻一样,当时很快乐,事后却很难完整复述。于是有人把跑团过程整理成Replay,让没参加的人也能感受到那场团有多离谱、多紧张、多好笑。但很多人低估了一件事:做Replay的技术门槛,比讲好一个故事要高得多。
它不是一个“录个屏、剪一下”的简单工作,而是一条涉及文本清洗、语音合成、字幕对齐、画面包装的内容生产流水线。早期Replay作者靠手工逐句对齐音频和字幕,做一期内容可能要折腾一整个周末;现在借助免费TTS工具、脚本化的文本处理和FFmpeg自动化,一个人也能稳定产出。如果只看表面,很容易误以为Replay只是“把跑团过程录下来再剪辑”,实际上真正花时间的,是把一段非结构化的语音聊天记录,转换成带角色、带时间轴、带画面的结构化内容。
这篇文章以“【COC跑团/replay】”这个系列为案例,从技术视角拆解一条最小可用的Replay制作链路。读完你会明白:Replay在技术上到底在做什么;从跑团原始记录到成片需要经过哪些环节;哪些环节值得用脚本和代码自动化,哪些环节手工处理反而更快;以及一套可以直接照抄、替换成自己跑团素材的最小示例脚本。
1. 这篇文章真正要解决的问题
很多人第一次尝试做Replay时,最大的感受不是“不会讲故事”,而是“工具太多了,不知道从哪下手”。录音软件、语音转写、剪辑工具、字幕工具、配音工具,每个单独拿出来都不复杂,但串成一条流水线后,问题就出现了:音频格式不统一、字幕时间轴对不上、角色声音全是一个味、剪辑软件导出一次要半小时。
我整理了一下,Replay创作者最常见的痛点主要有五类:
第一,语音记录不可用。一场COC跑团通常两三个小时起步,语音里充满了口癖、重复、笑声、外设噪音,甚至多人同时说话。直接用原始录音做视频,观众大概率撑不过三分钟。
第二,文字log没有结构化。如果你们团习惯于文字跑团,聊天记录虽然完整,但夹杂着表情、括号动作、闲聊和投点结果。要把“角色台词”从一大段聊天记录里提取出来,纯手工整理非常耗时。
第三,配音和角色绑定困难。真人配音效果好,但需要凑齐一群人的时间;用TTS虽然方便,但默认读音、语气平淡,需要靠文本处理和参数调整来弥补。
第四,字幕和音频不同步。这是最劝退的一步。TTS生成的是独立音频文件,每一条台词的时长不同,字幕时间轴必须从音频时长反推,手工做几乎是灾难。
第五,批量生产不稳定。如果你打算做连续剧式Replay,而不是只做一期,那么“每期都重新拖时间轴”的方式会耗尽热情。正确做法是让脚本自动处理大部分重复劳动。
所以这篇文章真正要解决的问题可以概括为一句话:搭建一条从跑团原始素材到成片的可复用生产链路,把“手工导演”变成“流程设计 + 脚本执行”。
什么样的人最应该读这篇文章?如果你是一个跑团玩家,想把跑团过程做成视频留作纪念;如果你是一个视频创作者,想尝试Replay这种内容形式;或者你只是对“文本处理 + TTS + FFmpeg 自动生成视频”这种工程思路感兴趣,这篇文章都适合你。
2. COC跑团与Replay的基础概念
在进入技术细节之前,先统一一下概念。
COC跑团,全称是《克苏鲁的呼唤》(Call of Cthulhu)TRPG。TRPG即桌上角色扮演游戏,玩家扮演调查员,守秘人(Keeper)负责描述世界、控制NPC和判定结果。COC的特色是调查、理智值和克苏鲁神话元素,玩家在游戏里经常面临“好奇心害死猫”式的选择。一场典型的COC跑团会持续两三小时,语音或文字记录非常长。
Replay,在跑团语境里指的是对跑团过程的复盘和再演绎。它不是把原版录音直接放出来,而是把跑团中发生的剧情重新整理,形成更适合观看的内容。常见的Replay形式有三种:
| 形式 | 内容载体 | 制作成本 | 观众体验 |
|---|---|---|---|
| 图文Replay | 图片 + 文字 | 最低 | 安静,适合论坛读 |
| 音频Replay | 配音 + 音效 | 中 | 像广播剧,有画面感 |
| 视频Replay | 立绘 + 字幕 + 配音 | 最高 | 最接近观看体验 |
从技术视角看,视频Replay的本质是时间轴同步系统。你需要把三个轨道对齐:
- 文本轨道:每条台词的内容、顺序、所属角色。
- 音频轨道:每条台词对应的配音片段,以及它们的起始时间。
- 画面轨道:背景图、角色立绘、字幕、转场。
这三个轨道一旦对齐,Replay就完成了。大部分人做Replay时感到痛苦,是因为一直在手工做时间轴对齐这件事。而工具化的思路是:让文本作为唯一的数据源,音频和字幕都从文本自动生成,最后用FFmpeg统一合成。
这个概念是整个技术方案的核心。你不需要一开始就理解所有剪辑概念,只要记住一件事:跑团Replay的工程难点不在于画面多精美,而在于文本、音频、字幕三者能否稳定、精确地对齐。
3. Replay制作整体技术架构与工具选型
从数据流角度来看,一条完整的Replay生产链路可以分为五层:
- 原始素材层:跑团语音录音、文字聊天记录、跑团过程中的投点截图。
- 结构化数据层:把原始素材清洗成统一格式的结构化文本,比如“场景 + 角色 + 台词”。
- 音频生成层:把每条台词转换成音频文件,可以使用真人配音或TTS。
- 字幕生成层:根据音频文件时长,自动生成SRT格式字幕。
- 视频合成层:将背景图、音频、字幕用FFmpeg合成最终视频。
用这样一套架构,每个环节都是独立的,替换任何一个环节都不会影响其他环节。今天用TTS,以后想换真人配音,只需要替换音频生成层;今天用FFmpeg合成,以后想加转场特效,只需要修改视频合成层。
工具选型直接决定后期效率。我的建议是三个原则:免费优先、可脚本化优先、中文支持优先。
下面是常用工具对比:
| 功能环节 | 推荐工具 | 优势 | 注意点 |
|---|---|---|---|
| 语音转文字 | Whisper / 剪映自动字幕 | 大幅减少手打文本量 | 需要校对专有名词 |
| 文本清洗 | Python + 正则 | 灵活、可复用 | 需要基础编程能力 |
| TTS配音 | edge-tts / pyttsx3 | 免费、中文自然度可用 | 长文本需分段,情感表达有限 |
| 字幕制作 | Python生成SRT | 与音频时长自动对齐 | 需要维护脚本 |
| 视频合成 | FFmpeg | 自动化程度高 | 滤镜参数需要测试 |
| 可视化剪辑 | 剪映 / Shotcut | 直观、适合精修 | 不适合批量生产 |
这里值得多说一句:很多新人一上来就学专业剪辑软件,觉得只有PR、达芬奇才够专业。但实际上,如果目标是稳定产出Replay,FFmpeg的命令行处理能力远比赛手动拖时间轴可靠。剪辑软件更适合做“最后一分钟的微调”,而不是“从头到尾的制作主力”。
另外,不要一上来就追求复杂的特效。一套成功的Replay方案,应该先跑通“字幕 + 配音 + 背景图”的最小闭环,再逐步加转场、音效和动态立绘。这套思路和软件开发里的MVP理念是一样的:先打通主链路,再迭代细节。
4. 环境准备与前置依赖
在开始写脚本之前,先确认你的机器上有以下环境:
- 操作系统:Windows 10/11、macOS、主流Linux发行版都可以。
- Python:建议使用 Python 3.8 及以上版本,主要用于文本清洗和字幕生成。
- FFmpeg:视频和音频合成工具,命令行版本即可,不需要安装GUI。
- 基础的文本编辑器:VS Code、Notepad++ 或任意编辑器都可以。
Python 的安装方式根据不同系统略有差异,推荐直接使用官方安装包或系统包管理器。FFmpeg 在 Windows 上需要手动下载二进制包并把 bin 目录加到 PATH 中,在 macOS 上可以用 Homebrew 安装。
# Ubuntu / Debian sudo apt update sudo apt install ffmpeg python3 python3-pip # macOS brew install ffmpeg python3在 Windows 上,推荐使用 winget:
winget install ffmpeg winget install Python.Python.3.12安装完成后,在命令行里验证版本:
ffmpeg -version python --version如果你已经安装了 Python,但版本较旧,建议重新安装或使用虚拟环境,避免和系统Python冲突。这一步虽然简单,但后面所有脚本都依赖基础环境,值得提前确认。
接下来安装 Python 依赖。本文的示例会用到两个库:edge-tts用于文字转语音,pydub用于读取和处理音频时长。pydub只是辅助性质,实际批量生成时你可能只需要edge-tts和一个简单的方式查询音频时长。
pip install edge-tts pydub如果你需要使用pydub读取 mp3 文件的时长,还需要额外安装 ffmpeg 的 Python 绑定ffmpeg-python,或者直接用ffprobe命令来获取时长。在本文的示例中,我更推荐直接用ffprobe,因为它不增加额外的 Python 依赖,且命令本身随 FFmpeg 一起安装。
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 audio/output_001.mp3这条命令会输出音频文件的时长,单位是秒,例如3.746667。SSR字幕需要把这个时长转换为hh:mm:ss,mmm格式,后面在脚本中会处理。
版本问题需要提醒一下:具体工具的版本变化很快,本文给出的安装命令在大多数情况下可用,但如果遇到安装失败,请以官方文档为准,不要死磕某一条命令。很多跑团Replay制作新手在这里卡住,其实纯粹是版本或网络问题,换一个安装源或升级工具版本就能解决。
5. 核心流程拆解:从跑团记录到成片
这一节是整个文章的实务核心。我会把从跑团原始记录到成片的流程拆成五个环节,每一步说明做什么、为什么需要、关键操作是什么。
5.1 跑团记录阶段:素材质量决定后期成本
跑团Replay的第一个环节不是制作,而是跑团时的素材采集。素材质量直接决定后期的工作量。最好在跑团开始前就约定:使用OBS或系统录音工具录制整场语音,同时保留文字log的历史记录。如果跑团是在语音软件上进行的,建议录制“每个麦克风单独一声轨”的分轨录音,这样后期降噪会轻松很多。
如果你们是文字跑团,聊天记录会自动保存,但需要导出为纯文本格式。无论哪种来源,核心原则是:原始素材越整齐,后期清洗成本越低。
5.2 文本清洗阶段:从聊天记录到结构化数据
拿到原始记录后,你需要把无用的内容剔除,把有用的角色台词提取出来。这一步的目标是生成这样格式的中间文件:
【场景1】旧书店的夜晚 守秘人:你们推开吱呀作响的木门,目光停在书架角落的一封信上。 伊森:我决定先看信,不碰其他东西。 米尔:那我观察一下门口的脚印。如果原始记录是语音转文字,你会有大量“嗯”“啊”“对吧”之类的口头禅;如果原始记录是文字聊天,你会遇到动作描写和台词混在一行里的情况。清洗时可以按这个优先级处理:
- 去掉完全无关的内容:闲聊、签到、跑题。
- 统一角色称呼:把玩家昵称统一成角色名。
- 合并同一角色连续发言:避免一条台词被切成好几条短句。
- 给重要场景打标签:用
【场景XXX】标记新场景,方便后面分段制作。
这个阶段没有银弹,最有效的方式是“脚本初清洗 + 人工终审”。脚本负责干掉大部分明显噪声,人工负责判断语言质量,比如把口语化的句子改成更利落的书面语。
5.3 配音合成阶段:让每条台词变成音频文件
文本整理好之后,下一步是配音。如果你有稳定合作的配音演员,直接用录音棚或手机录音即可。但大部分个人Replay作者会用TTS,原因很简单:免费、快速、一个人也能完成。
在TTS工具里,edge-tts是当前个人项目里比较顺手的选择。它免费,支持中文语音,输出质量稳定,而且可以通过命令行直接调用,非常适合批量脚本化。基本用法是:
edge-tts --voice zh-CN-XiaoxiaoNeural --text "你好,调查员。" --write-media output.mp3在批量场景中,你会为每条台词单独调用一次,输出文件名建议包含“序号_角色_场景”等元信息,不要用无意义的命名,否则后面视频合成时会完全失控。
5.4 字幕生成阶段:从音频时长生成SRT
字幕不是一个独立环节,它必须和音频对齐。传统做法是人手动听写、打点,效率极低。自动化做法是:先获得每条音频的时长,然后按顺序计算时间轴,最后生成SRT字幕文件。
SRT字幕的基本格式是一个序号、一段起止时间、一行字幕文本,每个字幕块之间用空行分隔:
1 00:00:00,000 --> 00:00:03,746 你好,调查员。这里的时间是时:分:秒,毫秒格式,计算机程序可以很轻松地从音频时长推导出来。你需要决定每条台词之间的间隔长度,一般建议设置 300 到 500 毫秒的空隙,避免听感上过于仓促。
生成字幕的脚本并不复杂,后面会给出完整示例。
5.5 视频合成阶段:把一切交给FFmpeg
当背景图、音频文件、字幕文件都准备好之后,剩下就是合成。这里最常用的命令是:
ffmpeg -loop 1 -i background.png -i audio.mp3 -vf "subtitles=subtitle.srt" -c:v libx264 -tune stillimage -c:a aac -shortest output.mp4这条命令做的事情是:持续循环显示background.png这张图片作为画面;用audio.mp3作为音轨;在画面上渲染subtitle.srt字幕;最后编码输出为 MP4 文件。
如果你的字幕是中文,需要给 FFmpeg 指定中文字体,否则画面可能会出现方框乱码。具体参数写法在使用libass滤镜时会略有差异,常见的写法是在force_style中指定字体名称。这一点在常见问题里会详细说明。
6. 完整示例:最小可用的Replay制作脚本
下面用一个最小示例把整个流程串起来。假设你已经有一个整理好的跑团文本文件log.txt,内容是上一节展示过的“场景+角色+台词”格式。
6.1 示例原始文本
【场景1】旧书店的夜晚 守秘人:你们推开吱呀作响的木门,目光停在书架角落的一封信上。 伊森:我决定先看信,不碰其他东西。 米尔:那我观察一下门口的脚印。 【场景2】雨中的追猎 守秘人:身后的脚步声越来越近,雨声掩盖了来者的身份。 伊森:我拉着米尔跑进小巷,顺手把信藏进外套内侧。 米尔:我想回头看一眼追来的人是谁。这个文本很干净,你会看到【场景XXX】作为场景标记,每一行都是“角色:台词”结构。如果真实聊天记录没有这么整齐,可以先在文本编辑器里手工整理成这个格式,或者用脚本做半自动清洗,再人工确认。
6.2 脚本一:文本解析并生成结构化JSON
新建文件parse_log.py,输入以下内容:
# 文件路径:parse_log.py import json import re from pathlib import Path def parse_log(text: str) -> list[dict]: lines = text.strip().splitlines() items = [] current_scene = "未知场景" for line in lines: line = line.strip() if not line: continue # 场景标记:以【场景开头,以】结尾 scene_match = re.match(r"^【(.+?)】", line) if scene_match: current_scene = scene_match.group(1) continue # 台词行:支持“角色:台词”和“角色|台词”两种分隔 dialogue_match = re.match(r"^([^:|]+)[:|](.+)$", line) if dialogue_match: items.append({ "scene": current_scene, "character": dialogue_match.group(1).strip(), "line": dialogue_match.group(2).strip(), }) return items if __name__ == "__main__": raw = Path("log.txt").read_text(encoding="utf-8") data = parse_log(raw) with open("log.json", "w", encoding="utf-8") as f: json.dump(data, ensure_ascii=False, indent=2, fp=f) print(f"解析完成,共 {len(data)} 条台词")运行脚本:
python parse_log.py运行成功后,同目录下会出现log.json,内容类似:
[ { "scene": "场景1", "character": "守秘人", "line": "你们推开吱呀作响的木门,目光停在书架角落的一封信上。" }, { "scene": "场景1", "character": "伊森", "line": "我决定先看信,不碰其他东西。" }, { "scene": "场景1", "character": "米尔", "line": "那我观察一下门口的脚印。" } ]这个 JSON 会作为后面所有环节的数据源。它的作用是把“原始的纯文本”转换成“带场景和角色的结构化数据”,方便后续按角色生成音频、按场景生成字幕。
6.3 脚本二:用 edge-tts 批量生成音频
接下来,写一个 Python 脚本读取log.json,为每条台词生成音频文件,文件名遵循“编号_角色.mp3”的规则。
# 文件路径:generate_audio.py import asyncio import json from pathlib import Path import edge_tts VOICE = "zh-CN-XiaoxiaoNeural" # 你可以换成其他中英文语音 async def generate_one(text: str, output_path: str) -> None: communicate = edge_tts.Communicate(text, VOICE) await communicate.save(output_path) async def main() -> None: with open("log.json", encoding="utf-8") as f: items = json.load(f) audio_dir = Path("audio") audio_dir.mkdir(exist_ok=True) for index, item in enumerate(items, start=1): output_path = audio_dir / f"{index:03d}_{item['character']}.mp3" print(f"生成音频:{output_path} -> {item['line']}") await generate_one(item["line"], str(output_path)) if __name__ == "__main__": asyncio.run(main())运行:
python generate_audio.py这条脚本依赖edge-tts。如果你的网络环境访问该服务不稳定,建议换用本地TTS,比如pyttsx3。pyttsx3是离线方案,安装方式:
pip install pyttsx3生成音频后,可以抽查一个文件,确认语音内容正确、没有吞字、没有异常停顿。TTS 生成的内容基本不会有大的发音错误,但遇到人名、咒文、英文专有名词时,可能出现读音不自然的情况。这时候可以在文本里对关键名词做拼音或英文标注,让TTS引擎读得更准确。比如把“阿卡姆”写成“阿卡姆(Arkham)”,部分TTS引擎会尝试按英文读,实际效果需要试听确认。
6.4 脚本三:根据音频时长生成SRT字幕
生成音频之后,需要用ffprobe获取每条音频的时长,再累加时间轴,生成SRT字幕。写一个脚本实现这个逻辑。
# 文件路径:generate_srt.py import json import subprocess from pathlib import Path def get_audio_duration(file_path: str) -> float: """通过 ffprobe 获取音频时长,单位秒。""" cmd = [ "ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", file_path, ] result = subprocess.run(cmd, capture_output=True, text=True, check=True) return float(result.stdout.strip()) def format_srt_time(seconds: float) -> str: millis = int(seconds * 1000) hours = millis // 3600000 minutes = (millis % 3600000) // 60000 secs = (millis % 60000) // 1000 msecs = millis % 1000 return f"{hours:02d}:{minutes:02d}:{secs:02d},{msecs:03d}" def build_srt(entries: list[dict], gap_seconds: float = 0.4) -> str: blocks = [] cursor = 0.0 for index, entry in enumerate(entries, start=1): start = cursor end = cursor + entry["duration"] text = f"{entry['character']}:{entry['line']}" blocks.append( f"{index}\n" f"{format_srt_time(start)} --> {format_srt_time(end)}\n" f"{text}" ) cursor = end + gap_seconds return "\n\n".join(blocks) def main() -> None: with open("log.json", encoding="utf-8") as f: items = json.load(f) audio_dir = Path("audio") entries = [] for index, item in enumerate(items, start=1): audio_path = audio_dir / f"{index:03d}_{item['character']}.mp3" duration = get_audio_duration(str(audio_path)) entries.append({ "character": item["character"], "line": item["line"], "duration": duration, }) srt_content = build_srt(entries) Path("subtitle.srt").write_text(srt_content, encoding="utf-8") print(f"字幕生成完成:subtitle.srt,共 {len(entries)} 条字幕") if __name__ == "__main__": main()运行:
python generate_srt.py这里需要注意:ffprobe在安装 FFmpeg 后会自动出现在同目录下。如果系统提示ffprobe不是内部或外部命令,说明 FFmpeg 的 bin 目录没有加到 PATH 中,需要去系统环境变量里配置。
6.5 脚本四:FFmpeg 合成最终视频
音频和字幕准备好之后,准备一张背景图background.png,放在项目目录下,然后执行 FFmpeg 合成命令:
ffmpeg -loop 1 -i background.png -i audio/001_守秘人.mp3 -vf "subtitles=subtitle.srt:force_style='FontName=Microsoft YaHei,FontSize=24'" -c:v libx264 -tune stillimage -c:a aac -shortest output.mp4这里有一点需要重点说明:这条命令只能合成单个音频文件。如果整个Replay有很多条台词,你需要先把所有音频片段按顺序拼接成一个完整的音轨,或者把不同音频片段复制到不同时间点。最稳妥的方式是先拼接音频,再用拼接后的完整音频和总字幕去合成视频。
拼接音频这一步可以用 FFmpeg 的 concat 参数实现。假设音频目录下所有片段已经按文件名排序,可以先创建一个文件列表:
for f in audio/*.mp3; do echo "file '$PWD/$f'" >> concat_list.txt; done ffmpeg -f concat -safe 0 -i concat_list.txt -c copy audio_full.mp3然后使用总音频audio_full.mp3和总字幕subtitle.srt合成:
ffmpeg -loop 1 -i background.png -i audio_full.mp3 -vf "subtitles=subtitle.srt:force_style='FontName=Microsoft YaHei,FontSize=24'" -c:v libx264 -tune stillimage -c:a aac -shortest output.mp4这样得到的就是一个完整可播放的Replay视频。视频画面虽然只是静态背景加字幕,但结构是完整的,已经具备Replay的观看门槛。
7. 运行结果与效果验证
完成上面的步骤后,你手里应该有四个东西:log.json、audio/目录下的若干MP3文件、subtitle.srt、output.mp4。下面说一下如何验证每一步是否正确。
第一步:验证 JSON 数据。打开log.json,检查解析出的台词条数是否和原始文本一致,角色名是否统一。如果出现“守密人”和“守秘人”混用,或者同一个角色有两种名字,说明清洗阶段没做完,需要在解析前统一文本。
第二步:验证音频片段。用播放器随机打开几个MP3,确认内容没有缺失、没有杂音。最重要的一步是确认顺序:001、002、003应该对应log.json里的第1、2、3条台词。如果生成的audio_full.mp3顺序不对,问题几乎都出在文件名排序上,常见的坑是10.mp3排在2.mp3前面,所以生成脚本里用了001这种补零命名,目的就是规避排序问题。
第三步:验证字幕时间轴。打开subtitle.srt,检查每一条字幕的起止时间和顺序。一个简单的验证方式是:把subtitle.srt拖进任意播放器,同时播放audio_full.mp3,看字幕出现和消失的节奏是否与语音一致。这个步骤虽然还是手工,但比在剪辑软件里逐条对时间轴快得多。
第四步:验证最终视频。播放output.mp4,重点检查三件事:字幕是否出现中文乱码;字幕和语音是否同步;整个视频的总时长是否和音轨时长一致。如果字幕乱码,需要在中文字体配置上做调整,详见常见问题。
如果整个过程没有报错,输出视频可以正常播放,恭喜你,最小闭环已经跑通了。接下来你可以把重点从“能不能做出来”转移到“怎么做得更好”——比如为不同角色选择不同的TTS音色,或者给不同场景替换不同背景图。
8. 常见问题与排查思路
在实际制作Replay时,新手容易卡住的点比较集中。我整理了一张排查表,按出现频率排序。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| FFmpeg报错找不到 subtitles 滤镜 | 安装的FFmpeg版本过旧,或没有编译libass | 执行ffmpeg -filters | grep subtitles查看是否支持 | 下载最新版FFmpeg,或改用snail字幕烧伤(burn-in)方案 |
| 中文字幕乱码或显示为方框 | FFmpeg 找不到可用中文字体 | 在force_style中指定系统已安装的中文字体名 | Windows用FontName=Microsoft YaHei,macOS用Songti SC,Linux用Noto Sans CJK SC |
| 音频拼接顺序错乱 | 文件名排序问题 | 检查 concat_list.txt 中的顺序 | 统一使用001、002这种补零命名 |
| TTS读错人名或专有名词 | TTS对生僻词不友好 | 试听指定音频片段 | 在文本中把专有名词改成TTS能理解的英文或拼音 |
| 字幕整体提前或延后 | 拼接音频时没有保留字幕时间轴起点 | 对比 audio_full.mp3 开头和第一条字幕时间 | 重新生成SRT,确保字幕从0秒开始 |
edge-tts安装失败或无法连接 | 网络问题或依赖冲突 | 查看pip错误日志 | 改用pyttsx3等离线TTS方案 |
| 视频画面只有静态图,没有动态感 | 背景图太呆板 | 无,这是最小闭环的正常形态 | 后续可以用多张背景图拼接,或加入角色立绘切换逻辑 |
这里重点解释一下中文字体的问题。FFmpeg 的subtitles滤镜默认依赖 libass,字幕渲染时使用的是系统的字体库。如果你的系统里没有中文字体,或者 FontName 写错了,字幕就会变成方框。不同系统的可用字体名不同,Windows 一般用Microsoft YaHei,macOS 用PingFang SC或Songti SC,Linux 通常需要先安装fonts-noto-cjk再使用Noto Sans CJK SC。
如果不确定自己系统里的字体名,可以先用fc-list命令查看:
# Linux / macOS fc-list :lang=zh | head -n 20Windows 上可以到C:\Windows\Fonts里看,或者直接查字体属性页里的名称。这一步虽然小,但是新手最容易卡住的地方。
9. 最佳实践与工程优化建议
当最小链路跑通之后,接下来就是如何把这一套流程变得稳定、高效、可复用。以下建议来自实际制作过程中的经验总结,优先级从高到低排列。
第一,文本格式标准化是最大的杠杆。所有自动化都建立在结构化数据之上。建议在跑团开始前就和队友约定:文字跑团时使用固定的角色名,不要中途改昵称;语音跑团结束后尽快整理记录,避免回忆偏差。如果你的原始素材很乱,清洗文本会消耗最多的时间,这是无法完全避免的,但可以通过规范来降低。
第二,文件名命名必须带上序号、角色、场景元信息。例如001_守秘人_场景1.mp3比output.mp3更能避免后续混乱。脚本生成的临时文件,可以放到独立的build/或audio/目录里,不要在项目根目录堆一堆中间产物。如果你想在一个项目里做完整系列,建议每一期用一个独立目录,目录结构统一。
推荐目录结构如下:
replay-series/ ├── episode01/ │ ├── log.txt │ ├── background.png │ ├── parse_log.py │ ├── generate_audio.py │ ├── generate_srt.py │ └── output/ └── episode02/第三,TTS 音频不要直接拼接,要预留空隙。在生成SRT时,我在脚本里加入了gap_seconds = 0.4,也就是每条语音之间留0.4秒的空隙。这个参数在实际听感上比较重要,没有空隙会显得很急促,空隙太大又会让观众觉得拖沓。你可以根据自己的语速习惯调整,但建议先按0.4秒起步,再整体试听。
第四,每个中间产物都要能单独回放和检查。不要一步到位直接合成完整视频后才发现某条台词读错了,那样的返工成本最高。正确做法是:每生成一批音频,先随机抽几条试听;每生成一版字幕,先快速跳几个时间点检查;最后再合成视频。迭代节奏应该是“小步快跑”,而不是“全都做完再检查”。
第五,注意声音和内容合规。如果你使用了TTS生成的语音,一般没有问题,但如果使用真人配音或某个主播的声音,必须获得对方授权。跑团Replay素材可能涉及玩家的隐私信息,公开发布前最好得到全部参与者的同意。这是一个容易被忽略但很重要的工程边界。
第六,用版本管理工具维护你的脚本和文本。即使是一个人做Replay,也建议使用Git管理项目。跑团文本经过多次修改后,可能会改坏某句台词;使用Git可以随时回滚。这不是装样子,而是长期做系列内容时最有效的时间保险。
第七,遇到工具报错,不要马上换工具,先查版本和运行环境。大多数问题不是工具本身不行,而是参数、版本或系统环境不匹配。我的习惯是:报错信息先完整复制下来,去对应工具的官方文档或GitHub Issues里搜,大概率能找到答案。
10. 总结与后续学习方向
这篇文章真正讲清楚的核心链路是:跑团原始素材 → 文本结构化 → 音频生成 → 字幕对齐 → 视频合成。整个过程不需要专业剪辑软件,也不需要花钱买昂贵的TTS服务,依靠 Python、FFmpeg、edge-tts 等免费工具就能搭起一套最小可用的Replay生产线。
如果你只想做一期纪念视频,这篇文章已经足够支撑你动手实践。如果你打算把Replay做成一个长期更新的系列内容,下一步可以从这几个方向继续深入:
- 语音转文字:现在很多跑团没有文字log,只有语音录音。用 Whisper 等语音识别工具先把录音转成文本,再进入本文的流程,可以大幅减少手打文本的时间。
- 多角色音色区分:现在的脚本用同一种TTS音色生成所有角色台词。你可以手动维护一份“角色→voice”的映射,让不同角色用不同音色,视频观感会立刻上一个台阶。
- 动态画面与立绘切换:不要局限于单张背景图。你可以在
log.json里给每条台词增加background字段和character_image字段,视频合成时按顺序切换图片,这一步在FFmpeg里可以用-loop 1配合-t分段实现,也可以借助剪辑软件做可视化精修。 - 情感与停顿控制:TTS的天然短板是情感不足。你可以通过修改文本、加入标点符号和换行来控制朗读节奏。部分TTS服务支持情感标签,实际效果需要在具体工具里测试。
最后给一个实际项目的提醒:不要第一次就做太长的Replay。建议用10分钟左右的短片段跑通全流程,再逐步扩展到完整跑团。这个思路和开发项目做MVP是一样的——先让链路跑起来,再做优化和扩展。工具版本会变,但“文本→音频→字幕→合成”这条生产逻辑是稳定的,理解它之后,换工具只是替换流水线上某一个环节而已。