把“自动生成视频”拆成 10 个可重跑阶段:我在 tenmin 里的工程实践
我做了一个叫 tenmin 的命令行工具,目标是把番剧素材加工成解说方案和配音成片。
它接收 SRT 字幕或视频素材,经过字幕/对白处理、节奏信号提取、解说稿生成、配音、时间轴重算、混音与渲染,最终输出 MP4。项目支持按集处理,也支持把多集登记在同一个项目里批量运行。
先说明定位:tenmin 是一个面向个人创作者的自动化制作工具和工程实验,不是“按一下就能得到专业成片”的承诺。解说稿仍可以人工检查和编辑;OCR、ASR、模型生成和内容检查也各有适用范围与边界。
难点不只是“让模型写一段解说”
如果只是把字幕扔给大模型生成一段文案,演示很容易,稳定地做完一集却会遇到更多问题:
- 字幕可能来自 SRT、视频软字幕、画面硬字幕,或者语音转写;
- OP/ED 与 staff 字要尽可能从剧情素材中区分出来;
- 解说文案需要对应原始画面时间,也要控制长度和节奏;
- TTS 的实际时长与字数估计并不完全相同,时间轴需要重新安排;
- 音频、字幕、视频片段要在最终成片里对齐;
- 某一步失败或输入变更时,最好只重跑必要部分,而不是从头再花一遍时间和模型费用。
所以我把它设计成阶段化流水线,而不是一段巨大的“生成视频”函数。
一条从素材到成片的流水线
当前主流程由十个阶段组成:
ingest → translate → signals → script → docgen
→ voice → timeline → audio → render → check
每一阶段各司其职:
- ingest:生成统一的对白轨。输入可以是手动提供的 SRT、视频里的文本字幕轨、画面硬字幕 OCR,或语音转写。
- translate:按对白来源决定是否需要翻译。ASR 得到的日语对白可以翻译为简体中文并生成中文字幕;OCR 字幕可直接交付,不必再次调用模型。
- signals:从对白间隔、语速和密度变化中提取节奏信号,辅助寻找适合解说的片段。
- script:调用 LLM 生成分幕解说稿,并进行结构与语义相关的校验。
- docgen:生成便于人工审阅的解说方案,以及单独的配音文本。
- voice:通过 Edge-TTS 合成语音,并记录实际语音片段时长。
- timeline:根据实际配音时长重新计算画面时间轴和字幕。
- audio:压低原声并叠加旁白,生成混音音轨。
- render:通过 FFmpeg 完成切片、拼接、烧字幕和音轨合成。
- check:可选的本地图片评级与报告阶段,用于筛出值得人工查看的时间点。
这样拆分的收益不只是代码更好读。某集的 script 已经确认、只想修改文档输出时,可以只重跑 docgen;已经有配音,调整时间轴后也不必从字幕识别重新开始。
工程取舍一:同一条对白轨,多个来源
输入源差异很大,因此 ingest 把来源解析与后续生产解耦:
手传 SRT
↓
视频软字幕轨
↓
画面硬字幕 OCR(项目声明启用时)
↓
语音转写
↓
统一对白轨
优先使用损失较小、成本较低的文本字幕。画面 OCR 只在项目声明片源带硬字幕时使用;语音转写则作为没有可用字幕时的来源。统一的对白轨让后续 signals、script 和文档生成无需关心最初的字幕是怎么来的。
这里有一个重要的边界:OCR 和 ASR 都不是“万能兜底”。OCR 目前面向画面底部、水平居中的硬字幕,且依赖 macOS Apple Vision;ASR 使用可选依赖,当前实现依赖 Apple Silicon 上的 mlx-whisper。某些位图字幕轨也不能直接作为文本字幕抽取。遇到不支持的素材,工具会提示问题,而不是悄悄把输入切到可能更差的路径。
工程取舍二:用真实时长修正时间轴
旁白文案的字数只能粗略估计时长,TTS 合成出来的音频才是后续剪辑需要对齐的实际对象。
因此 tenmin 先合成语音、读取每个片段的实际时长,再重新安排时间轴并生成字幕。之后将原声 ducking 与配音混音,最后统一渲染。这种顺序把“文字大概多长”与“声音实际上多长”区分开,避免把估算时间误当成最终时间。
项目也把人工可编辑的边界保留下来:03_script/E{NN}.script.json 是解说稿的主要编辑入口;修改后可以重跑文档生成等下游步骤,而不必重新调用 LLM。
工程取舍三:缓存不是“文件存在就算完成”
视频制作阶段往往比文本处理更慢。重复生成配音、重编码视频,或者为了一处修改重跑整集,都会增加等待时间与成本。
tenmin 会根据输入产物、配置切片和文件新鲜度判断阶段是否可以复用。不同阶段也有意采用不同策略:生成结果可按内容是否变化避免无意义地更新时间;而 OCR、ASR 字幕会保留为人能手动修订的文件,不能因为改了模型参数就静默覆盖人的编辑。
配置也按阶段参与新鲜度判断。修改某个阶段相关配置时,尽量只让受影响阶段失效;阶段之间仍通过文件产物衔接。对于人工编辑的 OCR/ASR SRT,重新识别需要显式处理对应缓存文件,这是为了保护人的修改,而不是遗漏了自动刷新。
另外两个实用功能
批量导入与 OP/ED 参考匹配
tenmin import 可以扫描目录中的视频和配对字幕,推断集号并预览导入计划。集号仍有歧义时可以尝试 LLM 辅助判断,但无法确认的素材会留在预览中,而不是盲目登记。
如果提供 OP/ED 参考截图,工具可以用画面相似度定位起点,并在多集之间比较音频特征,尝试估计歌曲长度或 ED 终点。证据不足时会把结果标为估计,保留人工核对空间。该功能不是通过 AI 判断“这是不是 OP”,而是组合使用画面哈希和跨集音频包络相关性。
可选的内容检查与人工遮罩
可选的 check 阶段会在本地抽帧并生成图片评级报告,列出需要人工复核的候选时间点。分数是模型的图片评级原始输出,不是经过校准的概率,也不能视为准确的边界检测。
报告页还支持人工标注需要模糊的区间,再通过 mask 文件交给渲染流程;也可以对任意视频单独应用遮罩。这让检查结果与最终处理之间多了一条明确的人工接管路径。
怎么运行
基础安装:
uv sync
创建项目并处理一集:
uv run tenmin init my-anime
uv run tenmin run my-anime --episode 1 --srt episode-01.srt --video episode-01.mp4
如果视频本身有软字幕轨,可以不传 --srt,让工具尝试抽取字幕:
uv run tenmin run my-anime --episode 1 --video episode-01.mkv
项目配置中需要填写可用的 LLM 服务密钥。默认模型服务为 MiniMax,也支持 Gemini 和单一 OpenAI-compatible 服务。需要画面硬字幕 OCR 时,另装 ocr extra(仅 macOS);需要无字幕视频语音转写时,另装 asr extra(当前为 Apple Silicon 路径)。渲染还需要带 libass 的 FFmpeg。
阶段可以单独运行,例如:
uv run tenmin run my-anime --episode 1 --only script
uv run tenmin run my-anime --episode 1 --only docgen --force
uv run tenmin run my-anime --episode 1 --from voice
命令和配置说明见 README:项目仓库
当前边界与适用人群
tenmin 更适合愿意管理素材、检查解说稿并接受命令行操作的个人创作者或开发者。它能自动化大量重复步骤,但不能替代内容判断、事实核对、节奏审美和成片审核。
几个具体边界:
- 画面 OCR 依赖 macOS,主要识别画面下方居中的字幕;
- ASR 是可选安装路径,首次安装的依赖较重;
- 位图字幕轨(例如 PGS/VobSub)不能直接作为文本轨抽取;
- 自动推断的 OP/ED 区间可能需要人工修正;
- 本地内容检查用于提供人工复核候选,不应解读为准确率保证;
- 配音与渲染需要相应的网络服务和 FFmpeg 环境。
项目目前是 CLI 工具,Web GUI 仍在路线图中。代码和详细说明:项目仓库
结语
这个项目让我反复面对的一个问题是:自动化的目标不该只是“让模型多做一点”,还应该让每一步有清晰输入输出、失败时能够定位、结果可以检查,必要时也能让人接手。
对于视频生产工具来说,LLM 只是流水线中的一个阶段。字幕来源、时间轴、TTS 实际时长、音频混合、渲染、缓存和人工复核,同样决定了工具最终是否可用。
如果你对 Python、FFmpeg、媒体处理流水线,或“怎么把 LLM 放进一个可维护的工具”感兴趣,欢迎看看项目:项目仓库。也欢迎讨论:你会优先改进素材导入、解说稿编辑,还是成片审核体验?
项目流程图
—— 快乐流星