
一个跑团 Replay 系列做到第九话真正的瓶颈通常不是剧情灵感而是素材整理和导出流程。以《P4角色桌 Replay》这个项目的完结篇为例章节标题是“雪山密室——第九话雪山密室”。这一话要发布的成品看起来只是一段带有字幕和章节标记的视频但背后至少涉及原始录音、转写文本、字幕时间轴、封面图、平台发布稿和归档副本。手工逐项处理这些文件很容易出现字幕对不上人声、章节标记错位、重复剪辑相同片段一类问题。这篇文章用这个完结篇作为具体场景整理一条从录音到成品的完整 Replay 制作链路。读者不需要是跑团玩家只要平时会接触视频剪辑、字幕处理和内容发布就能把其中的方法迁移到自己的项目里。文章会覆盖工具链选择、目录规划、语音转写、字幕时间轴处理、FFmpeg 剪辑、发布前检查和归档策略并在最后给出可复用的故障排查顺序。1. 先理解 Replay 发布为什么是工程问题1.1 Replay 的常见内容形态Replay 是角色扮演类桌面游戏TRPG的实况记录。大多数 Replay 项目会从原始语音记录开始经过后期加工后对外发布。常见成品形态有三种视频 Replay以录音或录屏为基础叠加立绘、台词字幕、背景音乐、角色头像和章节标题。文字 Replay把录音整理成人称和叙事清晰的稿件按场景分段发布。音频播客保留原始对话只做降噪、音量平衡和剪辑配合节目简介发布。“雪山密室——第九话雪山密室”作为完结篇通常不是单一形态。即使先出文字版也很可能需要同步准备视频版或音频版。成品形态越多素材版本越容易失控。最简单的失控场景是剪辑师改了字幕时间轴但文字稿里没有同步更新同一句台词或者视频章节标记写的是第九话但平台简介还保留第八话的封面。所以Replay 制作本质上是媒体资产管理问题。需要用工程方法管理文件、时间轴和版本而不是只靠剪辑软件里的图层。1.2 “雪山密室——第九话”这类完结篇需要哪些素材以一个完整的视频 Replay 为例发布前至少需要准备以下素材素材说明常见缺失后果原始录音主持人、玩家、旁白的分轨或混音文件后期无法单独降噪或补录转写文本录音对应的逐字稿标注说话人字幕制作只能人工听写字幕文件SRT 或 ASS 格式的台本文件观众无法理解语音视频片段录屏、立绘、背景图、角色立绘画面和音频无法对齐章节标记各场景开始时间点视频进度条没有可跳转节点封面和简介平台发布所需的视觉和文案素材发布效率低且风格不统一在一期完整 Replay 中“章节标记”这类技术细节最容易遗漏。比如“雪山密室”这一话可能包含开场、探索、推理、高潮和结局几个场景。如果没有统一格式的章节标记观众只能手动拖进度条视频完播率会明显受影响。2. 准备工作工具链和目录结构要提前定好2.1 工具选型只保留能自动化检查的环节我推荐一条以本地命令行工具为核心的链路原因是跑团录音通常很长图形界面剪辑软件在时间轴修正、批量偏移和版本复现上不如脚本可靠。工具用途为什么选用FFmpeg音频提取、片段裁剪、视频合并免费开源支持批量处理可写入脚本Whisper语音转简体中文初稿本地运行输出带时间戳的字幕和文本Python处理 SRT/ASS 字幕批量重命名生态成熟适合编写小型批处理脚本Aegisub手动校对外语或疑难语音片段字幕轨道可视化可逐条调整时间轴Git管理字幕、脚本、发布稿和配置保留每一话的修改记录方便回滚VS Code / Obsidian编辑文字稿和归档笔记支持全局搜索适合跨话检索实际项目可以根据自己的系统选择macOS 用户可以直接安装 FFmpegWindows 用户可以使用已编译好的二进制包或包管理器。关键不是“哪款工具最强”而是所有工具都能进入命令行工作流这样后续更新时间轴、批量替换角色名时不需要打开多个图形界面。2.2 环境安装与版本确认在开始处理“第九话”之前先确认以下环境Python 3.9 或更高版本。FFmpeg 可执行文件已加入系统 PATH。语音转写模型所需磁盘空间足够Whisper 的 large 模型可能需要数 GB 空间。视频播放器本地安装用于预览最终导出文件。在终端中确认版本python --version ffmpeg -version如果其中一个命令无法识别先修复环境变量或安装路径再继续后续操作。否则脚本里调用ffmpeg时会出现command not found排查起来反而浪费时间。2.3 目录结构一期一目录避免第九话覆盖第八话Replay 项目最怕“同名覆盖”。如果所有素材都堆在同一个目录后续想找回第八话的某个时间轴就非常困难。推荐按以下方式组织目录replay-p4-snow/ ├── 01-opening/ ├── 02-snow/ ├── ... ├── 09-snow-final/ │ ├── audio/ │ │ ├── raw/ │ │ └── denoised/ │ ├── video/ │ │ ├── source/ │ │ ├── clips/ │ │ └── final/ │ ├── transcript/ │ ├── subtitles/ │ │ ├── srt/ │ │ └── ass/ │ ├── publish/ │ │ ├── cover/ │ │ └── notes/ │ └── archive/ └── scripts/ ├── split_audio.py ├── offset_srt.py └── make_chapters.py这套结构的要点是原始音频和成品视频分离字幕文件单独管理每一话都有独立目录。脚本放在项目根目录这样第九话和第八话共用同一套处理逻辑方便后续批量修改。实际操作中如果某个环节产生了临时文件可以放入tmp/子目录不参与 Git 跟踪。3. 从录音到字幕用语音转写生成初稿3.1 先做音频提取和预处理如果原始素材是录屏视频或会议录音需要先提取纯音频。Whisper 对 16kHz 单声道 WAV 的兼容性最好因此先做一次转码ffmpeg -i input.mkv -vn -acodec pcm_s16le -ar 16000 -ac 1 audio/raw/snow-final.wav参数解释-vn表示不要视频流。-acodec pcm_s16le使用 16 位 PCM 音频编码。-ar 16000将采样率调整为 16000 Hz。-ac 1合并为单声道。如果原始录音里有明显环境噪声可以先做降噪。但降噪不是必须的只要语音清晰Whisper 通常能正常转写。这里不建议在早期就引入复杂的降噪模型因为过度处理会让转写文字失真后面校对更费劲。3.2 使用 Whisper 输出带时间戳的文本把转好的 WAV 文件交给 Whisperwhisper audio/raw/snow-final.wav --model small --language zh --output_format srt --output_dir subtitles/srt参数说明--model small在速度和准确率之间比较平衡。如果录音里有大量专有名词可以换medium。--language zh固定为中文识别避免自动检测出错。--output_format srt直接生成字幕文件。--output_dir指定字幕输出目录。第一次运行会下载模型文件后续再跑时会使用本地缓存。模型文件较大请预留足够的磁盘空间。如果你发现识别结果中角色名经常出错可以在转写后统一替换而不是重新跑整个模型。3.3 把转写结果整理成带角色标注的文稿Whisper 生成的 SRT 只包含文本块不会区分说话人。Replay 需要角色标注。常见的做法是先调出 SRT 里的时间轴然后按说话内容补充角色名。例如原始 SRT 的一段1 00:00:01,000 -- 00:00:04,500 今天这场雪下得不太对劲。人工校对时可以改成1 00:00:01,000 -- 00:00:04,500 GM今天这场雪下得不太对劲。如果不想直接改动 SRT也可以单独维护一个 Markdown 稿件用角色名加冒号的格式GM今天这场雪下得不太对劲。 【PC1】北川窗外的脚印只有进去的没有出来的。 【PC2】夏目这意味着我们已经被困在密室里了。这份稿件要作为“唯一事实来源”后续生成字幕、简介和章节文本都从它派生。4. 字幕时间轴与格式的工程化处理4.1 为什么我推荐 SRT 作为中间格式SRT 是最通用的字幕格式几乎播放器都支持。它的时间戳非常简单适合脚本处理。ASS 则支持更复杂样式比如角色专属颜色、字体、描边和位置但文件结构也更复杂。特性SRTASS通用性极高较高角色颜色不支持单行独立颜色支持多种样式时间轴修改简单用文本替换即可需要维护样式块脚本处理容易较容易但语法更多在 Replay 项目里建议把 SRT 作为中间格式导出最终视频前再用 Aegisub 或脚本转换成 ASS。这样既能保证最早阶段只处理文本和时间轴又能保留最后阶段美化字幕样式的空间。4.2 用 Python 批量偏移字幕时间轴视频剪辑后经常需要整体调整字幕。比如把某段素材向前移动 1.5 秒所有字幕时间都要跟着改。手动改几百条字幕不现实可以用脚本处理。下面是一个可用的 SRT 时间偏移脚本import re from datetime import timedelta def parse_timecode(tc: str) - timedelta: 将 SRT 时间戳 00:00:01,000 转为 timedelta。 tc tc.replace(,, .) hours, minutes, seconds tc.split(:) return timedelta( hoursint(hours), minutesint(minutes), secondsfloat(seconds), ) def format_timecode(td: timedelta) - str: 将 timedelta 转为 SRT 时间戳格式 00:00:01,000。 total_ms int(td.total_seconds() * 1000) hours, remainder divmod(total_ms, 3_600_000) minutes, remainder divmod(remainder, 60_000) seconds, milliseconds divmod(remainder, 1000) return f{hours:02}:{minutes:02}:{seconds:02},{milliseconds:03} def offset_srt(input_path: str, output_path: str, offset_seconds: float): offset timedelta(secondsoffset_seconds) time_pattern re.compile(r(\d{2}:\d{2}:\d{2},\d{3}) -- (\d{2}:\d{2}:\d{2},\d{3})) with open(input_path, r, encodingutf-8) as f: lines f.readlines() with open(output_path, w, encodingutf-8) as f: for line in lines: match time_pattern.search(line) if match: start parse_timecode(match.group(1)) offset end parse_timecode(match.group(2)) offset if start.total_seconds() 0: start timedelta(0) if end.total_seconds() 0: end timedelta(0) new_line f{format_timecode(start)} -- {format_timecode(end)}\n line time_pattern.sub(new_line, line) f.write(line)使用方法python scripts/offset_srt.py subtitles/srt/snow-final.srt subtitles/srt/snow-final-shift.srt -1.5这段脚本不依赖第三方库核心逻辑是解析时间戳加上偏移量再格式化回 SRT。注意偏移量可能是负数如果某条字幕开始时间小于 0要钳制为 0避免生成非法时间戳。4.3 角色颜色与字幕样式统一如果要做视频 Replay建议为每个角色固定一种字幕颜色。比如主持人用白色PC1 用浅蓝色PC2 用淡黄色。这样观众能快速区分说话人。在 ASS 中可以为每个角色定义独立样式。一个简化示例[Script Info] ScriptType: v4.00 [V4 Styles] Format: Name, Fontname, Fontsize, PrimaryColour, OutlineColour, Bold, Outline, Shadow Style: Default,Noto Sans CJK SC,32,H00FFFFFF,H00000000,0,2,1 Style: GM,Noto Sans CJK SC,32,H00FFFFFF,H00000000,0,2,1 Style: PC1,Noto Sans CJK SC,32,H00A8E6FF,H00000000,0,2,1 Style: PC2,Noto Sans CJK SC,32,H00B4E6A8,H00000000,0,2,1 [Events] Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text Dialogue: 0,0:00:01.00,0:00:04.50,GM,,0,0,0,,今天这场雪下得不太对劲。 Dialogue: 0,0:00:05.00,0:00:08.00,PC1,,0,0,0,,窗外的脚印只有进去的没有出来的。 Dialogue: 0,0:00:08.50,0:00:11.00,PC2,,0,0,0,,这意味着我们已经被困在密室里了。这里PrimaryColour是 BGR 格式的颜色值和常见的 RGB 顺序相反。例如H00A8E6FF表示浅蓝色但实际写颜色时要按蓝、绿、红、透明度排序。如果你不想手工换算可以直接在 Aegisub 里选择颜色让它生成对应的颜色值。需要特别注意字体名称。Noto Sans CJK SC不是每个系统都预装。在 Windows 上系统字体可能是Microsoft YaHei在 macOS 上可能是PingFang SC。发布到不同平台前最好在目标播放环境再检查一次。5. 视频剪辑用 FFmpeg 完成片段裁剪和合并5.1 精确裁剪片段很多 Replay 视频需要删除长时间沉默、重复试音或游戏加载画面。FFmpeg 可以按时间点裁剪ffmpeg -ss 00:01:00 -to 00:02:30 -i input.mkv -c copy clips/scene-1.mkv参数解释-ss 00:01:00表示从第 1 分钟开始。-to 00:02:30表示到第 2 分 30 秒结束。-c copy表示直接复制音视频流不重新编码。-c copy速度很快但可能不够精确因为关键帧对齐问题会导致起始画面有误差。如果需要逐帧精准可以把-ss放到-i前面并使用重新编码ffmpeg -ss 00:01:00 -to 00:02:30 -i input.mkv -c:v libx264 -c:a aac clips/scene-1.mkv如果是第一次尝试建议先用前一种方式快速预览再对重要场景用后一种方式微调。5.2 使用章节文件为视频添加分段章节标记可以通过 FFmpeg 写入视频文件。先准备一个章节元数据文件;FFMETADATA1 [CHAPTER] TIMEBASE1/1000 START0 END60000 title开场 [CHAPTER] TIMEBASE1/1000 START60000 END180000 title探索雪山密室然后执行ffmpeg -i final.mp4 -i chapters.txt -map_metadata 1 -codec copy final-with-chapters.mp4部分播放器不支持视频内置章节但主流播放器和视频平台通常能读取。章节标题不要写“第几话”直接写具体场景名例如“开场”“线索发现”“高潮”“结局”这样观众看进度条时更有辨识度。5.3 合并多个片段时保持参数一致把多个片段合并成一个完整视频最简单的做法是使用 concat 列表。先创建list.txtfile clips/scene-1.mkv file clips/scene-2.mkv file clips/scene-3.mkv再执行ffmpeg -f concat -safe 0 -i list.txt -c copy video/final/snow-final.mp4-safe 0用于允许文件路径中包含相对路径。这个方法要求所有片段使用相同的编码参数、分辨率和音频采样率。如果片段分别来自不同设备或软件出现“分辨率不一致”“音频采样率不一致”时需要先统一转码再合并。常见错误是播放前 10 秒正常切换到第二个片段后音画不同步。这就是因为两个片段编码参数不同-c copy直接拼接时无法自动处理。6. 发布与归档完结篇需要做的事比预览多6.1 平台发布前检查清单Replay 发布到视频平台、博客或微信公众号前不能只看画面和字幕。以下清单适用于“雪山密室——第九话”这类完结篇检查项具体操作错误示例字幕语言用播放器加载字幕快进抽查至少三处只看了开头片尾字幕错位角色颜色检查同一角色在不同场景的颜色是否一致前 5 分钟是蓝色后面变成白色章节标题快速拖动进度条确认章节从 0 开始开场前遗留 5 秒黑屏音频电平检查峰值是否过载玩家大笑时爆音简介字段确认标题包含“完结”和“第九话”还写着“第八话”封面图确认文件名和时间轴一致封面是上一话的截图这里的每项都可以通过脚本或人工抽检完成。尤其是“简介字段”看似简单但发布时最容易被遗漏。6.2 用 Git 管理字幕、脚本和发布稿Replay 项目的多数文件是文本文件非常适合放进 Git 仓库。原始视频文件较大可以不纳入 Git只保存原始文件路径或下载说明。字幕、脚本、章节文件、发布稿和配置都应该纳入版本管理。在完结篇目录下执行git add video/final/snow-final.mp4 2/dev/null || true git add subtitles/ transcript/ publish/ scripts/ git commit -m 完成第九话雪山密室字幕和章节标记 git tag -a v9-snow-final -m 第九话完结篇这里把最终视频文件排除在 Git 之外是一种常见做法因为视频文件会让仓库膨胀。字幕和稿件是文本体积小必须入库。为什么要打标签因为“完结”意味着后续可能复盘、修订或重新压制视频。如果没有标签三个月后想恢复第九话发布时的完整状态只能翻 commit 历史效率很低。6.3 用静态站点做 Replay 索引页如果 Replay 系列有多话内容推荐用静态站点生成器做一个索引页。Hugo 的config.toml可以这样配置baseURL https://example.com/replay/ title P4角色桌 Replay 归档 languageCode zh-cn单话页面使用 Markdown front matter 记录元数据--- title: 雪山密室——第九话雪山密室 episode: 9 season: P4角色桌 Replay status: 完结 date: 2024-01-01 tags: - TRPG - Replay - 雪山密室 ---这样每一话都可以独立成页最终生成列表页时还能自动按episode排序。相比手动维护一个汇总文档静态站点更可检索也方便后续追加新系列。7. 常见问题排查时间、字幕和编码三类故障7.1 音画不同步现象视频播放到中段后人物口型和语音明显错位。排查顺序检查源文件本身是否音画同步。用播放器播放原始视频若不同步则重新录制或修复源文件。检查 FFmpeg 裁剪命令是否用了-ss参数。如果-ss在-i之后FFmpeg 会先解码再跳转耗时但准确如果放在-i之前且用了-c copy可能出现关键帧跳转误差。检查多个片段合并时是否使用了不同采样率或帧率。用以下命令查看ffprobe -v error -select_streams a:0 -show_entries streamsample_rate -of defaultnoprint_wrappers1 input.mkv ffprobe -v error -select_streams v:0 -show_entries streamr_frame_rate -of defaultnoprint_wrappers1 input.mkv解决办法统一转码后再合并。虽然耗时但避免后续反复返工。7.2 字幕时间轴漂移现象字幕开始时能对上越往后越慢或越快。排查顺序确认原始录音和成片音频是否来自同一个版本。如果替换过音频轨时间轴必然变化。检查是否整体偏移量设置正确。offset_srt.py只能做整体平移无法解决速率漂移。如果漂移速率为固定比例说明成片播放速度被改动过。需要使用支持线性拉伸的工具重新对齐。预防在字幕文件命名中加入源音频的采样率和时长比如snow-final_16000_48min.srt避免误用旧字幕。7.3 中文字体在导出后消失现象在本机预览时字幕正常上传到视频平台或换一台电脑后字幕变成方框或默认字体。原因ASS 中引用的字体不存在于渲染环境。排查在 Aegisub 中打开字幕检查字体名称。在最终视频生成前使用字体嵌入方式如 libass 的fontsdir或把字体文件打包到项目中。对视频平台发布优先使用平台字幕功能或烧录字幕避免依赖观众本地字体。7.4 发布前快速排查顺序推荐按“文件 - 时间轴 - 字幕 - 封面 - 平台字段”的顺序排查文件命名是否包含话数和场景名。时间轴是否基于最终成片生成。字幕是否有越界时间和角色名错误。封面图是否与本期内容一致。平台简介是否已更新为完结篇信息。8. 最佳实践与扩展方向8.1 建立可复用的发布前检查清单任何 Replay 项目都应该把检查清单保存在项目根目录。下面是一个可以直接复制使用的版本[ ] 原始录音已备份到独立目录。[ ] 字幕文件与最终音频版本一致。[ ] 所有角色名在字幕中统一。[ ] 视频文件中已写入章节标记。[ ] 封面图标题包含话数和关键词。[ ] 平台简介中更新了完结状态。[ ] Git 已提交补丁已打标签。这份清单不针对特定平台可以平行移植到 Bilibili、西瓜视频、YouTube 或自建博客。8.2 从手动到半自动模板化配置如果每一话都手工执行 FFmpeg 和 Whisper 命令重复性劳动仍然很多。可以把命令写入一个 Makefile 或 shell 脚本按目录结构自动处理# process.sh #!/usr/bin/env bash set -euo pipefail EPISODE_DIR${1:?用法: ./process.sh 09-snow-final} RAW_AUDIO$EPISODE_DIR/audio/raw/raw.wav ffmpeg -i $RAW_AUDIO -vn -acodec pcm_s16le -ar 16000 -ac 1 $EPISODE_DIR/audio/denoised/speech.wav whisper $EPISODE_DIR/audio/denoised/speech.wav --model small --language zh --output_format srt --output_dir $EPISODE_DIR/subtitles/srt脚本化之后每一话的初始处理都是同一套逻辑减少漏步骤的概率。8.3 下一步可以做的自动化如果系列很长还可以进一步自动化的方向包括用 Python 脚本检查 SRT 是否存在时间重叠或负时长。用正则表达式批量替换误识别的角色名和专有名词。在 Git 提交前自动执行字幕格式校验。使用 CI 在推送后生成全系列索引页。对完结构“雪山密室——第九话”来说这条链路的核心不是某一款工具而是让字幕、时间轴、视频和发布稿始终指向同一个事实版本。多花一点时间在目录结构和脚本上后续修订和复盘会轻松很多。