¿Qué hace 日文歌词字幕:断句、出字时刻、表记、排版?
0. 这个 skill 解决什么
日文歌词字幕最常见的四种错,全都是"看着还行、日本人一眼就错":
- 按字数均分时间 —— 一句 5.7 秒 16 个音节,平均每音 0.36 秒。实际唱起来长短差 3 倍。
- 断在词中间 ——
君 / を見るたび。助词不能单独起头。 - 全平假名 —— 歌词表为了给 TTS/生成模型用而写成全假名,直接贴上屏日本人读起来像儿童读物。
- 出字时刻吸附到"音高变化点" —— 会把句末收音符当成下一句的起点,整句提前半秒。
1. 八条硬规则
| # | 规则 | 反例 |
|---|---|---|
| R1 | 断点只落在文節边界。名词+助词、动词+助动词不得拆开 | ✗ 君/を見るたび ✗ 見/てるのに |
| R2 | 每段出字时刻=该段首音节实测起唱。⛔ 禁止按字数均分 | ✗ (end-start)/n × k |
| R3 | 一句最多 3 个出字段,优先 2 段 | ✗ 君を/見る/たび |
| R4 | 不显示标点;段间用全角空格 | ✗ 君を、見るたび |
| R5 | 禁则(JIS X 4051):小假名 ゃゅょっ、长音 ー、促音、闭括号不得起行 | ✗ 行首出现 っ |
| R6 | 单行 ≤ 16 字(Netflix 日文字幕口径) | — |
| R7 | 出字后不消失(累积显示),否则观众失去上下文 | ✗ 出第二段时抹掉第一段 |
| R8 | 背景大字必须包含该句全部字,逐字比对 missing 必须为空 | ✗ 背景只有后半句 |
R1 为什么是文節而不是"词":日文没有空格,机器断行只会在框宽处断。
人读的时候是按文節呼吸的 —— 君を/見るたび 正好是两次呼吸。
判断方法:把这段单独念出来,如果像一次换气,它就是对的。
2. 实测出字时刻(核心,别用估的)
2.1 前置
需要人声分离轨(Demucs / htdemucs 的 vocals.wav)。混音轨上有鼓和贝斯,能量峰值全是鼓点。
python scripts/measure_lyric_onsets.py --vocal vocals.wav --lines lines.json --out timing.json
lines.json 长这样(kana 用于算音节数定预期位置,disp 是上屏表记):
[{"id": 9, "start": 42.26, "end": 48.00,
"kana": ["きみを", "みるたび", "しんぞうがとまるの"],
"disp": ["君を", "見るたび", "心臓が止まるの"]}]
2.2 算法(三段,缺一段就会错)
- 取 F0:
librosa.pyin(fmin=150, fmax=900, hop_length=128),再对 F0 做中值滤波去掉颤音。 - 取候选边界:音高跳变 ≥0.8 半音 ∪
librosa.onset.onset_detect(delta=0.30, wait=3)。 - ⛔ 门槛过滤(最关键的一步):候选点必须同时满足
- 之后 40ms 能量不低于之前 40ms 的 55%(不是骤降)
- 之后 180ms 内 F0 非 NaN 的帧占比 ≥60%(还有人在唱)
然后:把每个文節按"累计音节数 / 总音节数"算出预期相对位置,吸附到最近的合法边界。
2.3 为什么第 3 步不能省(实测反例)
「君を見るたび 心臓が止まるの」的 び 在 44.87 秒收尾降调(F2# → 收),静音 0.33 秒后 しんぞう 才在 45.24 起。
- 只做第 2 步 → 边界抓到 44.81(
び的降调),把「心臓が止まるの」提前 0.43 秒。 - 只做第 3 步的第一条(40ms 能量比) → 仍然错。44.81 处能量还没掉下来,40ms 窗口太短。
- 加上「之后 180ms 仍有发声」 → 45.17,与手工核对差 0.07 秒。✅
⇒ "之后还有没有人在唱"比"这一刻能量高不高"更能区分换字与收句。
2.4 什么时候这条也会失效
- 英文句:没有音高平台,pyin 测不出音节边界。只能按时长比例分,并在交付里明确标注"估的"。
- 一字多音的连唱(melisma):音高平台数 > 音节数,吸附会偏。需要人工听。
- 吸附偏差 > 0.45 秒的句子:一律标
UNSURE交给人工,不要当成测准了。
2.5 ⛔⛔ 先验证"每句的时间窗"本身,再谈句内断句
这是比 §2.3 更上游的坑,也是最容易被忽略的。
绝大多数歌词时间轴文件(lyrics.ts / SRT / LRC)的 start/end 是按行数线性切分或手工估的,
不是逐句听出来的。带这种文件做字幕,句内测得再准,整句也是错位的。
症状:字幕整体比人声早或晚 1–4 秒,而且越往后漂得越多(因为是线性拉伸)。 用户在副歌听不出来(副歌窗口通常是对的),一听主歌前几句就发现不同步。
修法:用换气间隔重新锚定每一句
人声分轨上,句与句之间一定有静音。这是最硬的分界,不需要任何模型:
rms = librosa.feature.rms(y=vocal, frame_length=1024, hop_length=128)[0]
e = rms / rms.max()
quiet = e < 0.055 # 相对阈值,实测够用
# 连续 quiet >= 0.16s 记为一个换气间隔
然后:相邻两个间隔之间的有声段,就是一个乐句。
实测例子(同一首歌):
间隔 0.00-12.12(前奏无演唱)
有声 12.12-13.73 ← ドアのとこで
间隔 13.73-14.67
有声 14.67-16.59 ← 立ち止まってる
间隔 16.59-17.07
有声 17.07-17.98 ← 本当は
而歌词表写的是「第 1 句 12.00–15.75」「第 3 句 19.50 起」——第 3 句实际 23.63 起,早了 4.1 秒。
⭐ 判据:把检测出的有声段数,和这一段歌词的文節数比。
- 数量相等 ⇒ 这个歌手是"一文節一换气",直接 1:1 对应,精度到毫秒级,不用猜。
- 数量偏少 ⇒ 有连唱,再用 §2.2 的音高法找句内边界。
- 数量偏多 ⇒ 阈值太松,调高
0.055或加长0.16。
⛔ 不要用"能量峰计数"代替换气检测:一个长音里的颤音会产生多个峰。 实测 16 个音节的一句子被数出 33 个能量峰,完全没法用。
⛔ 也不要用"滚动中位数归一 + 浅阈值"去找弱换气:主歌里会冒出 23 个假间隔(真值 19), 噪声和真换气分不开。宁可只锚定有硬静音的那些句,剩下的明确标"重映射/估"。
3. 表记:漢字仮名交じり
歌词表(尤其是 AI 生成歌曲的歌词稿)常写全假名。上屏必须转常用汉字表记。
| 全假名(歌词稿) | 上屏表记 |
|---|---|
| きみをみるたび | 君を見るたび |
| ほんとは / きみのほう | 本当は / 君のほう |
| なかに | 中に |
| においだけで | 匂いだけで |
| いちばんとおい | 一番遠い |
| こわれちゃいそう / ちかづくと | 壊れちゃいそう / 近づくと |
| たちどまってる | 立ち止まってる |
| わたしのあかし | 私の証 |
⛔ 不要过度汉字化:すきだよって 不要写成 好きだよって…其实要写(好き)。
但 なっちゃう、ずるいよね、なんてかな 这类口语副词/终助词保持假名 —— 全改汉字反而生硬。
判据:这个词在日本人的小説/歌詞排版里通常写汉字吗? 不确定就保持假名。
4. 排版参数(有出处,别自己发明)
以下数值来自对四支商业二次元 MV 的量化反向工程(1280×720 基准换算到 1080p):
| 项 | 1080p 值 |
|---|---|
| 主歌词字高 | 97~108px |
| 旁白小字 | 53px |
| 副歌强调句 | 124px |
| 巨型背景字 | 270~670px |
| 底部基线 | y ≈ 950~1004 |
| 单行最宽 | ≤1747px(≤91% 画宽) |
⭐ 最重要的发现:商业 MV 的主歌词普遍"无底板",白字 + 2~3px 深描边。 只在强调句才用通栏白带 + 黑字。给每一句都加圆角底板是业余感的主要来源。
配色不要中途换调:一支片只有一个主色相,副歌只加饱和不换色相。
4.1 竖排多列 = 断句可视化
横排用全角空格分隔,观感上等于"没断句"。竖排时把每个文節排成一列,从右往左, 断句就变成版面本身:
君を | 見るたび | 息が止まるの
右列 中列 左列
列位固定按文節序号算(第 k 段永远在第 k 列),新段出现时已有列不位移 —— 否则每次出字整块文字会横跳。
4.2 竖排的物理上限(先算再定字号)
一行 n 字、行距比 1.46:所需高度 = n × 字号 × 1.46。
1080 画高、可用 940px ⇒ 字号上限 = 940 / (n × 1.46)。
- 8 字(心臓が止まるの)→ 79px,达不到 97px 下限
- 6 字 → 107px ✅
⇒ 超过 7 字的长句,竖排放不进主歌词字号区间,要改横排两行。 先算,别排完才发现。
5. 自检(每次都要打印,不看就交付必翻车)
python scripts/check_lyric_coverage.py --timing timing.json --disp disp.json
输出必须同时满足:
COVER line='君を見るたび' missing=[] OK=True
ORDER sub_a='君を' sub_b='見るたび' concat_equals_full=True
missing 不为空 ⇒ 这一版不许交付。ORDER 检查拼接后是否等于整句(防漏字/重字)。
6. 开工前必做的一件事
先在项目文档里 grep 一遍字幕规格。 本项目已经裁定过的字号、色板、有无底板,
通常躺在 02_文档/ 或 docs/ 的某份拆解/裁定里:
grep -rn -E "字幕|字体|排版|几何|字高|描边|底板" docs/ 02_文档/ | head -40
⛔ 不要"我觉得这样更好看"就自己定一套 —— 那一定会和已裁定的规格冲突, 而且返工成本是整个片子重渲。这个 skill 的第 4 节就是被这条教训逼出来的。
7. 实测记录(供校准预期)
一支 163.994 BPM 的日文歌,副歌首句:
| 音节 | き | み | を | み | る | た | び |
|---|---|---|---|---|---|---|---|
| 起唱(s) | 42.31 | 42.76 | 43.09 | 43.41 | 43.61 | 43.98 | 44.18 |
| 音高 | B3 | E4 | F#4 | G#4 | F#4 | F4 | F#4 |
7 音节 ↔ 7 个音高平台一一对应 ⇒ 这首歌副歌是一字一音。 换一首连唱多的歌,平台数会少于音节数,吸附就要标 UNSURE。
8. ⛔ BPM 不是网格(实测漂移)
一首歌的 bpm 字段只能当标签,不能拿来算切点。
实测:某曲 128.28 → 132.39 BPM(beat 468ms → 453ms),固定网格到第 30 小节就整整差一拍。
- 用
beats[](已吸附到 onset)+bars[].start取真实时间 beats用于打击点(切镜/闪/砸字);beats_smooth用于连续运动(相机速度/循环)- ⛔ 永远不要写
t0 + n * 60 / bpm - 小节号 ≠ 数组下标:有 pickup 小节时
bars[].index与位置不同,按 index 查
9. ⛔ ASR 的单词时间戳偏早,不能当出字时刻
实测偏差 0.04–2.0 秒,中位 0.62 秒;长音之后最差。 ⇒ 用 ASR 只能取每句的近似起点(±1s),精确出字必须用强制对齐(MMS CTC / torchaudio)。 判据:每句起唱误差 ≤ 0.15 秒才算测准。
10. ⛔ Whisper 幻觉清单(出现即删)
在静音、气口、vocalise 处会生成:
- 「ご視聴ありがとうございました」(曾覆盖 0.6–103 秒)
- 「チャンネル登録…」
- 「作詞・作曲・編曲 初音ミク」
- 重复短词、
ラララ…
⇒ 规则:官方歌词为准;无官方歌词才用转写,且逐句与官方稿对照(差异法看不见"整句缺失")。 ⇒ VAD 要关掉(Silero 误判歌唱元音);分离出的人声轨 > 任何 DSP 增强。
11. LRC 标记(歌词动画的mini 语言)
| 标记 | 作用 |
|---|---|
词组A/词组B | 强制切行,两半成为不同 beat |
*词* | 强调:更大 + 冲击部件 + 强调色 |
行尾 ! | キメ行(最强处理,可带闪/震)——副歌每段 1–2 行 |
[間奏] | 器乐行:阻止上一句占着空隙不消失 |
⛔ 不要重复抵消提前量:歌词引擎通常已内建「比人声早 0.2 秒」; 若你的时间轴已经减过 0.2,这里再加一次就会早 0.4 秒。
12. 副歌收尾的 stop 小节
副歌末尾乐队撤空、近乎 a cappella 的那几个小节,是全片最重要的画面该放的位置, 不是切走的时机。别把它当"这里没鼓所以随便放点什么"。
13. ⭐ 五个强制对齐工具在日文歌词上的实测对比
出处:第三方 benchmark(20 段,含中/英/日),下表取日文列。
| 工具 | 起点误差(中位) | 终点误差(中位) | 覆盖率 | 判 |
|---|---|---|---|---|
| MMS_FA(torchaudio) | 0.08 s | 0.11 s | 8/8 | ✅ 唯一达标 |
| whisper-timestamped | — | 0.16 s | 8/8 | ⚠️ 次选 |
| WhisperX | — | 1.67 s(p90 2.81) | 8/8 | ❌ 覆盖全但边界飘 |
| NeMo NFA | — | — | 0/8 | ❌ 无日文 pretrained CTC |
| aeneas | 2.70 s | 3.66 s | 8/8 | ❌ 崩盘 |
⛔ aeneas 不适合唱歌:它自己的文档就写着「Audio is assumed to be spoken: not suitable for song captioning」。 实测日文 2.7–3.7 秒误差,比按字数均分还差。
⇒ 要 ≤0.15 秒,只有 MMS_FA 这条路。 其余都不达标。
⚠️⚠️ 但 MMS_FA 的 License 是 CC-BY-NC(非商用)。我们若上 Steam 商业化,必须另做许可安排, 或只用它做内部对齐、不随产品分发模型权重。这条在商业化前必须确认。
⚠️ MMS_FA 对非拉丁语种靠 uroman 罗马化;日文汉字混排时,长段 p90 会飘到 2.53 s ⇒ 必须逐句核对。
14. 提前量的两个数值不是一回事
| 数值 | 目的 | 出处 |
|---|---|---|
| 0.15–0.25 s | 让人读得完(阅读提前量) | 歌词引擎默认「文字は声より 0.2 秒先」 |
| 0.03–0.05 s | 让人跟得上唱(卡拉OK对齐感) | 另一来源的歌词视频实践 |
⇒ 选哪个取决于这支片子是"读的字"还是"跟唱的字"。 我们做的是读的字 ⇒ 用 0.15–0.2 s。⛔ 不要两者叠加。
15. 竖排组版的实际规则(不是"把字竖着排")
| 规则 | 做法 |
|---|---|
| 句読点 | 占一格,写在格子的右上方(横排是左下方) |
| 小假名(っ ゃ ゅ ょ) | 占一格,写在格内右上方 |
| 拗音(きゃ) | 看作两个假名,各占一格 |
| 长音符「ー」 | 占一格 |
| 段首 | 空一格 |
| ⛔ 禁则 | 句読点不能出现在一行的开头 |
| 行末标点 | 如果句末标点落在行末没格子,写在格外或与末字同格 |
| 数字 | 竖写用汉字数字;横写可用阿拉伯数字(每格 1–2 个) |
⇒ 我们现在的竖排是"每个字一行堆下来",没有处理句読点的位置、小假名的偏移、段首空一格。 这三样加上去,日文读者一眼就会觉得"对了"。
16. 阅读速度判据:4 cps 是广播字幕标准,不是歌词标准(含本项目实测)
⚠️ 可信度分布:数字 B 级(日本字幕认证 AVTpro 的 JA 风格指南、两家字幕公司的公开规格,三源交叉一致); "歌词该怎么用"是我自己的推论,D 级 —— 已明确标注,不许当证据用。
16.1 三个源给出的硬数字(互相一致的部分)
| 项 | AVTpro JA(认证标准) | GoLocalise | DMPJ | 取用 |
|---|---|---|---|---|
| 最大读速 | 4.0 cps | 12 字/秒(≈上限口径不同) | ~4 字/秒 | 4.0–5.0 cps |
| 每行字数(横) | 15 字 | 16 字 | 13–16 字 | ≤15 |
| 每行字数(竖) | 11 字 | — | — | ≤11 |
| 同屏行数 | 2 | 2 | 2 | 2 |
| 最短显示 | 20 帧(0.8 s @25fps) | 1 s | 1.5 s | ≥0.8 s |
| 最长显示 | 6 s(歌词可 7 s) | 7 s | — | 6 s(歌词 7 s) |
| 标点处理 | 。→ 全角空格;、→ 半角空格 | — | — | 同 AVTpro |
| 字体 | ゴシック | — | Gothic 优先(可读性) | ゴシック |
⭐ 两条验证了我们已有的裁定:
- 「⛔ 不用标点」是对的 —— AVTpro 的正式做法就是把标点换成空格(。→全角、、→半角)。 我们直接不加标点,效果等价且更干净 ⇒ R7 不需要改。
- 「ゴシック体」是对的 —— 我们用的 Yu Gothic Bold 属于ゴシック,方向没错(但字体授权有问题,见 §17)。
16.2 ⛔ 但 4 cps 不能直接套在歌词上(我的推论,D 级)
4 cps 的存在理由是:对白听不懂就得重看,字幕是唯一信息通道。 而歌词是同时被唱出来的 —— 观众在听到那个词的同一秒看到它,不需要"读完再理解"。
⇒ 歌词的字幕不该按"读完"设计,该按"跟着唱"设计。 (AVTpro 自己也给歌词开了口子:最长显示时长从 6 s 放宽到 7 s,说明行业承认歌词是另一类。)
⚠️ 但这不等于"歌词可以随便快"。超过 ~6 cps 连"跟着唱"都跟不上,因为眼睛找不到当前那个字。 ⇒ 实践判据:峰值 cps ≤ 5.0 可接受,5.0–6.0 要提高,>6.0 必须改排版(拆行 / 两行并出 / 缩短文節)。
16.3 本项目实测(lyric_timing_v2.json,22 行 / 52 文節)
| 口径 | 结果 |
|---|---|
| 按整行(v7 现行 WHOLE_LINE) | 22 行里 5 行 >4.0 cps,最差 L5 = 5.30 cps(16 字 / 3.02 s) |
| 按文節(若改回逐文節显现) | 52 个里 11 个 >4.0 cps(21%),最差 L3「ポケットのなかに」6.90 cps |
| 竖排每列字数 | 最长 16 字 → 分两列每列 8 字,未超 11,OK |
⇒ 结论分两层,别混:
- 按 4.0 cps 严格判:5 行不合格,但按 §16.2 的歌词口径(≤5.0)只有 L5 的 5.30 需要处理。
- ⛔ 若改回逐文節显现,21% 的文節会超速到 5–7 cps ⇒ 这是"整行一起出"这个决定的量化依据 (当初改 WHOLE_LINE 只凭"逐文節会诱发跟着核对同步"的直觉,现在有数字了)。
16.4 ⭐ 顺手查出的真缺陷:L3–L7 的时间轴是等分推算的
| 症状 | L3–L7 连续五行时长全部恰好 3.02 s,且首尾严丝合缝(23.63→38.72 = 15.09 s ÷ 5) |
|---|---|
| 根因 | 换气间隔检测在这段(legato 连唱,字与字之间不换气)找不到 gap,REMAP 退化成等分,且没报警 |
| 实测反证 | 在 22–44 s 内扫 4 档阈值 × 2 档最短长度,真实静音只有 7 处(22.63 / 23.01 / 24.24 / 25.60 / 28.29 / 31.43 / 41.55),没有一处落在 26.65 / 29.67 / 32.69 / 35.70 / 38.72 上 ⇒ 这五个边界不是测出来的 |
| 为什么难发现 | source 字段只写 REMAP,不区分"有依据的重映射"和"等分兜底" |
| 修法(未做,属程序端) | ①REMAP 拆成 REMAP_ANCHORED / EQUAL_DIVIDE,后者必须打印警告 ②等分兜底时同时输出 cps,超 5.0 直接判红 ③legato 段改用 pyin 音高连续性定边界,不用静音 |
⛔ 这条是"判据看着全绿、实际是假的"的又一例:missing=[] 只说明"每行都有时间",不说明"时间是量出来的"。
17. ⛔ 字体授权:我们现在用的是 Windows 系统字体,商用有风险(B 级)
⚠️ 可信度 B(各家字体厂商的正式许可页 / FAQ 原文,逐条读到,不是二手转述)
17.1 四类授权,天差地别
| 字体 | 视频里烧字幕 | 商用 | 备注 |
|---|---|---|---|
| SIL OFL 1.1(Noto/Source Han、M PLUS、Zen Maru、Kosugi、Shippori、侵食明朝…) | ✅ | ✅ 免标注 | 唯一要遵守:⛔ 不许单独卖字体文件;改作须同协议 |
| IPA Font License 1.0(IPAex、こころ明朝…) | ✅ 明写"映像内容"可用 | ✅ | ⛔ 衍生字体名不得含「IPA」;再分发须附协议 |
| ヒラギノ(macOS 自带) | ✅ 产物可贩卖/颁布 | ✅ | ⛔ 但明禁"デジタルコンテンツにおいてフォントとして機能する形態でデータを制作する行為";且绑 macOS,我们在 Windows 上用它 = 越界 |
| 日本書技研究所 等商业字库 | ⚠️ 要买 | 需商用授权 | 映像テロップ按资本金 ¥22,000–55,000 |
| ことり文字ふぉんと Pro | ✅(含 YouTube 收益化) | ✅ | ⛔ 但"埋め込み/Webフォント"原则禁止 ⇒ 烧进视频 ≠ 嵌入字体,这两件事在不同条款里 |
⭐ 关键区分(最容易搞错的一条): 「把字烧进画面」和「把字体文件嵌进文档/网页」是两种行为,适用不同条款。 前者产物是像素,一般按"输出物"处理;后者是把字体本体交出去。⛔ 别把 ことり 的"禁止嵌入"读成"禁止做视频"——它自己的 FAQ 里"動画への使用"是明确 OK 的。
17.2 我们现在的实际状态
| 项 | 值 |
|---|---|
| 当前字体 | C:\Windows\Fonts\YuGothB.ttc(游ゴシック Bold,Windows 自带) |
| 字形方向 | ゴシック ✅(与 §16.1 的可读性建议一致) |
| 授权状态 | ⚠️ 未核。Windows 自带字体绑 OS 授权,烧进商用成品(Steam DLC)属于灰区,不是明确许可 |
⇒ 建议换 OFL 字体(零成本、明确可商用、免标注),候选按气质排:
| 候选 | 许可 | 气质 | 适配 |
|---|---|---|---|
| Zen Maru Gothic | OFL | 圆润、柔和 | ⭐ 最贴我们的粉紫/可爱调 |
| Shippori Mincho | OFL | 情绪浓的明朝 | 适合抒情句,但与"可爱"有张力 |
| M PLUS 1p | OFL | 几何、现代 | 中性安全 |
| Noto Sans JP | OFL | 最中性 | 兜底,不会错 |
⚠️ 换字体 = 改观感,必须 Ronny 目检后定,⛔ 不许我单方面换(这是"画面层"改动,按用户级铁律必须他看)。