文案先负责把事情讲清楚,再成为制作的时间骨架。先确定问题、解释路径与章节推进,通读全文检查因果和语气;确认内容后再处理字幕、读法与配音时序。本文说明唯一源稿、多语言与配音的写法。
人类阅读和逐句校对通过 script-review.md 的导出稿与意见记录进行。明确确认前不调用配音;主语言定稿后翻译。源稿只维护下面这一份,不另建手工同步的旁白全文。
先写自然连贯的正文,再拆制作字幕。导出的交审稿先给连贯阅读视图,再给带句号的逐句表;两者都由 script.js 生成,不新增一份手工维护的正文。按 F12 独立设计语义停顿、连读和专名读法;不要为了满足字幕长度把解释写成碎句。音色、断句与读音用实际代表段试听,不能只依据 TTS 参数或 ASR 字面吻合。
按 F13,陌生或历史题材的开场先交代对象是什么、观众为何值得了解、所处年代和本片路线;新版对照注明具体产品、版本与同一任务。背景服务于理解,不扩成漫长编年史。
按 F14,连贯文案只包含会被说出的正文。片尾名单在 project.json 的 credits,由片尾场景读出来;时间线用 ['credits', null, { pause: 8, credits: true }] 保留无旁白片尾。不能把“策划、模型、开源链接”或“片尾只显示……”放进 TTS。校对导出中若列出静默 cue,须明确标“无旁白”,不混进正文段落。
脚本在 src/js/script.js,浏览器与工具共用同一份。结构:
const RAW = [
{
id: 'open', chap: null, lead: 0.8, tail: 0.4,
cues: [
['o1', { zh: '一个 Lua 脚本,写完了', en: 'A Lua script. Finished.' }, { gap: 0.55 }],
['o2', { zh: '要交给别人运行,对方得先装好 Lua', en: 'To run it, the other person needs Lua installed,' }, { gap: 0.3 }],
['oT', null, { pause: 3.0 }],
],
},
{
id: 'what', chap: ['02', { zh: '它做什么', en: 'What it does' }], lead: 2.3, tail: 0.5,
cues: [ … ],
},
];场景
| 字段 | 含义 |
|---|---|
id |
场景号。场景文件里用 scene('<id>', …) 实现它 |
chap |
[编号, 标题],有章节卡;null 表示没有 |
lead |
场景开始到第一句旁白之间的时间,章节卡就放在这段时间里(常用 2.3 秒;没有章节卡时 0.8–1.0 秒) |
tail |
最后一句旁白结束到场景结束之间的留白(缺省 0.6 秒) |
cue:[句号, 字幕, 选项]
| 部分 | 含义 |
|---|---|
| 句号 | 全片唯一。习惯上每个场景用一个字母作前缀:o1、a3、h17 |
| 字幕 | 显示在画面底部的文本。*词* 标出术语(字幕上显示为强调),第一次出现时画面要有它的注解(scenes.md 第 7 节);只为加重语气的词不标。多语言时写成 { zh: '…', en: '…' } |
tts |
送去合成的文本。缺省时由字幕按读法表换算(见第 4 节)。也可写成 { zh, en } |
gap |
这句结束到下一句开始的停顿,缺省 0.3 秒。可写成 { zh, en } |
pause |
这一条不是旁白,而是纯画面的停顿(标题、转场),值为秒数;字幕写 null |
交审前沿同一个具体例子通读:开头的问题是否值得回答,观众能否跟上「输入 → 状态变化 → 结果」,转折是否来自已取证的条件或反例,结尾是否回答开头并说明边界。章与章之间要有尚未回答的问题推动;能删掉而不损失解释的背景或术语,优先删掉。不要为套用「钩子—转折」结构制造夸张或未经证实的因果。
- 每条制作字幕不超过一行:中文不超过 26 个字宽(汉字算 1,拉丁字母与数字算 0.5),英文不超过 44 个字宽(约 88 个字符)。上限在
project.json的check.caption_width里按语言设定;scan.js另量实际宽度。一个自然口播句可跨多条字幕,因果、条件和转折保持完整。 - 单条非静默 cue 不超过 6 秒(
check.max_cue_seconds)。超过时按语义边界拆 cue,连读处安排相应 gap;不强行把长句改成互不衔接的短句。 node kit/tools/stats.js <视频>逐章给出语速、最快 cue、最长 cue、过快和停顿过短的候选。候选再用代表段试听判断,不把速度数字当作口播质量。- 停顿是节奏的一部分:结论句之后留 0.6 秒以上;一个场景里换话题的地方留 0.7–1.1 秒;镜头要移到下一站时,把上一句的
gap留够镜头走完的时间。 - 开头 5 秒要有钩子:一个具体的现象、一个反差、一个问题。
- 语体见 standards.md 第 5 节。
缺省只做中文。策划者原话(2026-10-24):
另外, 之后视频默认只有中文.
需要英文版的片子在片单条目(或系列)的 languages 写明 ["zh", "en"],project.json 随之一致;此前的双语片已在片单上逐条写明。本节以下各条适用于有英文版的片子。
- 以中文为底本:先定稿中文,再按中文的语气与用词译成其他语言。译文写成该语言里自然、克制的句子,不逐字对译。新增第三种语言时同样从中文译,不从英文转译。
- 各语言的句号一一对应、数量相同;时长各不相同,时间轴各自排布。
- 语言的选择:浏览器取地址里的
?lang=,工具取--lang;script.js里的pick()把{ zh, en }取成当前语言的值。 - 画面里的文字用
tr('中文', 'English');时间点用T('c5', { zh: '读', en: 'reads' })按语言取词。场景代码只有一套。 - 版面先按较长的那种语言排(通常是英文),再看另一种。
- 每种语言各有一份
src/js/timing.<语言>.js、audio/<语言>/、out/<语言>/,成片文件名带语言。 - 字幕内嵌在成片里:字幕条由引擎渲染进画面,各语言的成片自带本语言的字幕,交付与投稿不依赖外挂字幕文件;
finish.js另导出同文同步的.srt,只是附属文件。 - 只做一种语言的片子把
languages写成["zh"],脚本里的字幕直接写字符串。
字幕写产品的正式拼写;读法统一放在 script.js 的 SAY 表里,按语言各一组正则替换,长词写在前面:
const SAY = {
zh: [ [/luainstaller/g, 'lua installer'], [/月相/g, '月象'], [/SHA-256/g, 'S H A 二五六'] ],
en: [ [/luainstaller/g, 'Lua installer'], [/\bluai\b/g, 'lua-eye'], [/\bELF\b/g, 'E L F'] ],
};已知的规律:
- 连写的英文专名常被读错或被逐字母拼读。
luainstaller会被读成「low installer」,拆成lua installer才对;MyISAM要写成My ISAM。 - 多音字换成同音的单音字:数据「行」会被读成 xíng,合成文本里换成「航」(「执行」「进行」除外);「月相」换成「月象」。
- 缩写要逐字母读的,字母之间加空格:
S R lua、L G P L。 - 版本号、选项名写成读得出来的样子:
5.1 到 5.5→五点一到五点五;--file→file 选项(英文dash dash file)。
读法拿不准时先试再定:
node kit/tools/tts_probe.js <视频> --lang zh "luainstaller 是一个工具" "lua installer 是一个工具"它把每种写法各合成一句,用语音识别回听,并列出词边界。音频留在 audio/probe/,可以直接试听。
node kit/tools/tts.js <视频> # 全部语言
node kit/tools/tts.js <视频> --lang en # 只做英文- 用 edge-tts 逐句合成,取回每句的时长与词边界,写成
src/js/timing.<语言>.js(入库),并打印各场景的起止时刻与总长。需要联网。 - 音色与语速在
project.json的voices、rates里按语言设定。缺省音色:中文zh-CN-YunyangNeural,英文en-US-AndrewNeural。语速写成"+5%"、"-4%"。Yunyang 在 −4% 时约每秒 5.3 字(含标点的合成文本口径)。 - 以(音色、语速、文本)的哈希做缓存,放在
audio/<语言>/(不入库)。改了哪句只重新合成哪句;脚本里删掉的句子,音频一并清掉。 - 改了
script.js的任何文本之后必须重新运行tts.js,否则时间线用的是旧时长。 - 换一台没有缓存的机器时,
tts.js会重新合成全部句子。在线服务的输出可能随时间变化,时长随之略有不同;画面的时间点都由T()推出,会自动跟着走,但已渲染的画面与已混好的声音要重做。
T('c5') // c5 这句开始的时刻
T('c5', '读') // c5 里「读」字被读到的时刻
T('c5', '读', -0.2) // 同上,再提前 0.2 秒
T('c5', { zh: '读', en: 'reads' }) // 多语言
Tend('c5') // c5 结束的时刻- 词在合成文本里找(引擎先用
SAY表把词换成读法再找),取首次出现的位置。同一句里一个词出现两次、想取第二个时,换一个只出现一次的相邻词。 - 查某句可用的词与各自的时刻:
node kit/tools/timeline.js <视频> c5。 - 找不到词时页面日志里有
word not in cue,scan.js把它报为PAGE错误。 - 时间点一律写成
T(句号, 词)的形式,不写死秒数:旁白改了,画面才能自动跟着走。
混音之后:
node kit/tools/asr.js <视频>用 Whisper 转写仅旁白的音轨,逐句并排打印「合成文本」与「听到的文本」。读法:
- 同音错字、英文大小写与拼写偏差属于识别误差,不算错。
- 要看的是:有没有漏词、有没有把英文单词逐字母拼读、数字读法是否正确。
- 多音字读没读对,ASR 查不出:Whisper 把 cháng、zhǎng 都转成「长」。对着交审稿的「多音字清单」(script-review.md 第 2 节)逐条用耳朵听(F12)。
- 读错了就改
SAY表或该句的tts,重新合成、重新混音、再回听。
不同音色的峰值与均值之比差别很大(Yunyang 与 Andrew 相差约 7 dB)。混音按响度对齐旁白,不按峰值归一;成片增益按实测响度计算。两种语言的成片因此响度一致。
策划者原话(2026-10-24):
原则上, 每个视频和上一个之前配音音色, 除非特别说明.
之后,视频加入固定流程, 定稿后, 选取合适, 尽可能和之前不同的10个配音(5中文5英文), Demo片段, 我来选择具体用哪个, 然后到正式制作. 除非我特别说明自己选择配音, 不要问我.
文案定稿之后,给出 5 个配音候选供策划者选:用本片同一段代表句做成 Demo,音色契合本片的对象,尽可能和前几部不同;读法平实清楚,不做史诗腔。默认只有中文;片子有英文版时,英文另给 5 个。策划者原话(2026-10-24,P67,更正了 P65 的误读):「这三个都不好 这是配音, 要五个, 我说的是配乐史诗感」。「3 个调调」与质感、史诗感说的是配乐,见声音第 2 节。选定后再合成全片。策划者在会话里直接点名音色时,就用点名的,不再做 Demo,也不再问。没有这句特别说明时,不要问「用哪个音色」。
上一部已选定的音色(下一部避开,除非策划者要求沿用):
| 日期 | 片子 | 中文 | 英文 | 模型 |
|---|---|---|---|---|
| 2026-10-24 | 《256 秒搞懂 SSH》(已选定,尚未合成) | Chinese (Mandarin)_Radio_Host(电台男主播) |
Friendly_Person(Friendly Person) |
MiniMax speech-2.8-hd,语速 1.0 |
| 2026-10-24 | 《一个数据库引擎是如何实现的?——从 MyISAM 说起》新版 | Chinese (Mandarin)_Gentleman(温润男声) |
(只有中文) | MiniMax speech-2.8-hd,语速 1.1 |
再往前已进成片的,同样不要当作默认可重复:Tmux 用过 Chinese (Mandarin)_Reliable_Executive 与 English_Trustworthy_Man;HTTP 状态码用过 Chinese (Mandarin)_News_Anchor 与 English_ConfidentWoman。停用音色见下表,不再进入这 10 段。
选音色之前先真的听一遍:
node kit/tools/voices.js # edge-tts 与 MiniMax 的全部中英文音色
node kit/tools/voices.js --engine minimax --lang zh --only Mandarin # 只看一部分
node kit/tools/voices.js --zip # 另打成 out/voices-<日期>.zip,带评价表,交给策划者离线试听它用同一段范例文本(写在 kit/tools/voices.py 开头),把本机能用的每个音色各合成一遍,放进仓库根的 out/voices/(不入库),并生成试听页 out/voices/index.html:每个音色一行,可以逐个播放或整组连续播放,列出时长、语速和下表里已有的评价。范例文本覆盖配音最容易出问题的几样:解释句与长句、年份、英文缩写与版本号(HTTP/3、QUIC)、数字(404、500)、问句与结论句。语速一律用引擎缺省值(edge +0%、MiniMax 1.0),只比较音色本身;片子里实际用的语速记在各片的 project.json。
音色由服务方提供,列表会变:以试听页上实际列出的为准,合成失败的标「不可用」。MiniMax 的音色列表接口只列出少数英文音色,voices.py 另带一份实测可用的英文音色 id。
选音色前先看这张表。标「停用」的不再选用(F17);其余评价针对当时那一片的气质或整体配音语气,再用时照样按 F17 先试听代表段。策划者听过试听库之后的评价也记在这里,原话照录、注明日期。
| 音色 | 引擎 | 用在 | WaterRun 的评价 | 以后 |
|---|---|---|---|---|
Chinese (Mandarin)_Radio_Host |
MiniMax speech-2.8-hd | 《256 秒搞懂 SSH》中文,语速 1.0。2026-10-24 从试听库点选,尚未合成 | 点名使用,没有另写听感 | 本片用。下一部避开,除非特别说明沿用 |
Friendly_Person |
MiniMax speech-2.8-hd | 《256 秒搞懂 SSH》英文,语速 1.0。同上 | 点名「Friendly Person」,没有另写听感 | 同上 |
English_Aussie_Bloke |
MiniMax | Web 登录英文(语速 1.08) | 「英文配音很一般,之后不要用这个音色了」(2026-10-20,P38) | 停用 |
Chinese (Mandarin)_Reliable_Executive |
MiniMax | Tmux 中文、OpenCart 中文 | 配音太「懒洋洋」(2026-10-19,P29) | 评价针对整体语气,未停用音色;再用须先试听 |
English_Trustworthy_Man |
MiniMax | Tmux 英文 | 同上(P29) | 同上 |
Chinese (Mandarin)_News_Anchor |
MiniMax | PostgreSQL 试听(语速 0.95);HTTP 状态码中文(语速 1.2) | PostgreSQL:「这个配音太老气了,换一个」(2026-10-19)。HTTP 状态码:整片看片后「没问题了」(2026-10-21),没有单独评价音色 | 两次的语速与片子的气质都不同,评价也不同;再用须先试听 |
female-yujie |
MiniMax | PostgreSQL 试听 | 「太妩媚」 | 未用;针对 PostgreSQL 的气质 |
Chinese (Mandarin)_Male_Announcer |
MiniMax | PostgreSQL 中文(语速 1.1);MyISAM 新版「史诗·纪录片」调调试听(语速 1.1) | PostgreSQL:试听成片后「都没问题」(2026-10-19)。MyISAM:与 Lyrical_Voice、Wise_Women 一起「这三个都不好」(2026-10-24,P67),评价针对按史诗腔挑的读法 |
可用,先试听 |
Chinese (Mandarin)_Lyrical_Voice |
MiniMax speech-2.8-hd | MyISAM 新版「抒情·讲故事」调调试听(语速 1.25) | 「这三个都不好」(2026-10-24,P67) | 未用;评价针对史诗腔的挑法,再用须先试听 |
Chinese (Mandarin)_Wise_Women |
MiniMax speech-2.8-hd | MyISAM 新版「阅历·知性」调调试听(语速 1.1) | 同上 | 同上 |
Chinese (Mandarin)_Gentleman |
MiniMax speech-2.8-hd | MyISAM 新版中文,语速 1.1 | 5 个候选(另有 male-qn-jingying-jingpin、Chinese (Mandarin)_Sincere_Adult、female-chengshu-jingpin、Chinese (Mandarin)_Mature_Woman)中点选,没有另写听感 |
本片用。下一部避开,除非特别说明沿用 |
male-qn-jingying(精英青年) |
MiniMax | Web 登录中文(语速 1.05) | 没有单独评价 | 可用,先试听 |
English_ConfidentWoman |
MiniMax | HTTP 状态码英文(语速 1.25) | 整片看片后「没问题了」(2026-10-21),没有单独评价音色 | 可用,先试听 |
zh-CN-YunyangNeural |
edge-tts | 缺省中文音色,早期片子 | 没有单独评价 | 先在试听库里听 |
en-US-AndrewNeural |
edge-tts | 缺省英文音色,早期片子 | 没有单独评价 | 先在试听库里听 |