日文歌詞字幕:斷句、出字時機、表記、排版 は何をしますか?
0. 這個技能解決什麼
日文歌詞字幕最常見的四種錯誤,全都是「看起來還行、日本人一看就錯」:
- 按字數均分時間 —— 一句 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
⛔ 不要「我覺得這樣更好看」就自己定一套 —— 那一定會和已裁定的規格衝突, 而且返工成本是整個片子重新渲染。這個技能的第 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 標記(歌詞動畫的迷你語言)
| 標記 | 作用 |
|---|---|
詞組A/詞組B | 強制切行,兩半成為不同 beat |
*詞* | 強調:更大 + 衝擊部件 + 強調色 |
行尾 ! | キメ行(最強處理,可帶閃爍/震動)——副歌每段 1–2 行 |
[間奏] | 器樂行:阻止上一句佔著空隙不消失 |
⛔ 不要重複抵銷提前量:歌詞引擎通常已內建「比人聲早 0.2 秒」; 若你的時間軸已經減過 0.2,這裡再加一次就會早 0.4 秒。
12. 副歌收尾的 stop 小節
副歌末尾樂隊撤空、近乎 a cappella 的那幾個小節,是全片最重要的畫面該放的位置, 不是切走的時機。別把它當「這裡沒鼓所以隨便放點什麼」。
13. ⭐ 五個強制對齊工具在日文歌詞上的實測對比
出處:第三方基準測試(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 的授權是 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 目檢後決定,⛔ 不許我單方面更換(這是「畫面層」改動,按用戶級鐵律必須他看)。