AI 工具 18 min read

WhisperX 字級時間戳與語者分離:M2 MacBook 純 CPU 實測

Max Bain 的 BSD-2-Clause 開源專案,在 faster-whisper 的逐字稿之上多接 wav2vec2 強制對齊與 pyannote 語者分離,把時間戳細到每一個字。我在 M2 / 8GB 的 MacBook 純 CPU 實測 17.66 秒中文:base 加對齊 35 秒,86 個字全部拿到 start / end / score——但 NLTK 的 punkt_tab 沒補裝的話,對齊那步會跑完什麼都不輸出。

WhisperX 字級時間戳與語者分離:M2 MacBook 純 CPU 實測
本文目錄 · 12
懶人包
WhisperX 是 Max Bain 的開源專案(BSD-2-Clause),在 faster-whisper 轉出的逐字稿之上,多接兩件事:用 wav2vec2 做強制對齊,把時間戳細到每個字;用 pyannote 做語者分離,標出每句話是誰講的。
我在 M2 MacBook(8GB、純 CPU)實測 17.66 秒中文語音:base 模型加對齊約 35 秒,86 個字全部拿到 start / end / score。字幕切分、中文對齊模型、torchcodec 警告這三個地方有實際細節要注意。

📌 目錄

WhisperX 是什麼

WhisperX 是一個把語音轉成帶精確時間戳的逐字稿的 Python 工具。作者是 Max Bain,論文發表在 INTERSPEECH 2023,授權是 BSD-2-Clause。

它的定位很明確:Whisper 給你「這段 17 秒的音檔講了什麼」,WhisperX 給你「第 1.393 秒到 1.534 秒這個字是『陳』,信心分數 0.969」。

整條管線分成四段,每段各有專責的模型:

階段做的事用什麼
VAD找出音檔裡哪幾段真的有人在講話pyannote 或 silero
轉錄把語音變成文字faster-whisper(CTranslate2 後端)
對齊把每個字對回音檔的時間軸wav2vec2 強制對齊
語者分離標出每句是誰講的pyannote(選用)
轉錄本身不是它自己寫的,是呼叫 faster-whisper。WhisperX 的價值在前後那三段:先用 VAD 把靜音切掉再送進模型,轉完再用一個獨立的音素模型把時間戳磨細,需要的話最後再貼上語者標籤。

我讀了 pyproject.toml,目前版本標 3.8.7rc1,最新的正式 release 是 v3.8.6(2026-05-25),需要 Python >=3.10, <3.14,主要相依是 faster-whisper>=1.2.0pyannote-audio>=4.0.0torch~=2.8.0ctranslate2>=4.5.0

字級時間戳是怎麼做出來的

這是 WhisperX 最值得講的設計,也是它跟「直接跑 Whisper 再自己切」差最多的地方。

Whisper 本身是個序列生成模型,它輸出的時間戳是模型「順便預測」出來的,不是量測出來的,所以會漂。WhisperX 的做法是換一個工具做這件事:強制對齊(forced alignment)

它另外載入一個 wav2vec2 音素辨識模型,這種模型的輸出是「每一個時間框,各個音素的機率」。既然逐字稿已經知道了,剩下的問題就不是「這段音講了什麼」,而是「已知講了這串字,怎麼把它們排進時間軸最合理」——這是一個可以用動態規劃解的對齊問題,不是猜測。

所以時間戳的精度來自一個專門做時間解析的模型,而不是要求生成模型多做一件它不擅長的事。這也解釋了為什麼對齊是獨立的一步、可以用 --no_align 關掉:它跟轉錄用的是兩個不同的模型。

英文、法文、德文、西班牙文、義大利文有預設的 torchaudio 對齊模型,其他語言走 HuggingFace。中文對應的是 jonatasgrosman/wav2vec2-large-xlsr-53-chinese-zh-cn

安裝

我用 uv 裝到獨立 venv,避免污染系統 Python:

uv venv /tmp/whisperx-venv --python 3.11
uv pip install --python /tmp/whisperx-venv/bin/python whisperx

裝完確認版本與後端:

/tmp/whisperx-venv/bin/python -c \
  "import torch, faster_whisper; print(torch.__version__, faster_whisper.__version__)"

我這台跑出來是 2.8.0 1.2.1,whisperx 本身是 3.8.6

有一件事先講清楚:Apple Silicon 上這是 CPU 在跑。我確認過 torch.backends.mps.is_available()Truetorch.cuda.is_available()False,但轉錄後端 CTranslate2 走的是 CPU 路徑,所以要明確指定:

--device cpu --compute_type int8

README 上寫的「70x realtime with large-v2」是 GPU(CUDA)環境的數字,跟 Mac 沒有關係,下面我給的是自己這台實際跑出來的秒數。

M2 MacBook 實測

測試檔案是我自己錄的一段 17.66 秒中文語音,硬體是 M2 MacBook、8GB 記憶體,純 CPU。

whisperx /tmp/zh-test.wav \
  --model base --language zh \
  --device cpu --compute_type int8 \
  --output_dir /tmp/whisperx-out --output_format json
WhisperX 對 17.66 秒中文語音跑完 VAD、轉錄、對齊三階段後,輸出 JSON 中每個字帶 start、end、score 的字級時間戳

模型權重都已快取的情況下,兩種模式的耗時:

模式耗時相對音檔長度
base + 對齊35.11 秒約 2.0 倍
base + --no_align25.71 秒約 1.5 倍
對齊這一步在我這台大約多花 9 秒,換到的是 86 個字全部帶時間戳。

輸出的 JSON 結構是這樣,segments[0].words 裡每個元素都有 startendscore

{"word": "我", "start": 0.993, "end": 1.193, "score": 0.984}
{"word": "是", "start": 1.193, "end": 1.393, "score": 0.903}
{"word": "陳", "start": 1.393, "end": 1.534, "score": 0.969}
{"word": "彥", "start": 1.534, "end": 1.594, "score": 0.668}

86 個字,86 個都有時間戳,沒有漏。score 是對齊的信心分數,上面「彥」是 0.668 明顯低於鄰居,剛好對應到轉錄把我的名字聽成了同音字——低分的位置通常就是值得人工複查的位置,這個欄位拿來做品質篩選很實用。

要說明的是,中文的「字級」字面上就是單字:模型是按字元對齊,不是按詞。上面每個元素都是一個中文字,標點符號也各自佔一個時間區間。英文才會是一個單詞一個元素。

用字級時間戳切字幕

字級時間戳最直接的用途是切字幕。這裡有個預設值要注意。

直接輸出 SRT,預設是「一句一段」:

whisperx /tmp/zh-test.wav --model base --language zh \
  --device cpu --compute_type int8 --output_format srt

結果是這樣:

1
00:00:00,031 --> 00:00:17,662
字位好,我是陳彥銅,今天要介紹的是Wisper.cpp,這是一個用權c加加寫成的語音轉文字工具,不需要安裝python,在蘋果晶片的筆電上可以,直接使用metal加速執行。

17 秒一整塊,當字幕沒辦法用。原因是中文整段沒有句號斷開,句子切分就把它當成一句。

有了字級時間戳,就可以要求它按長度切:

whisperx /tmp/zh-test.wav --model base --language zh \
  --device cpu --compute_type int8 --output_format srt \
  --max_line_width 16 --max_line_count 1 --segment_resolution chunk
1
00:00:00,031 --> 00:00:03,597
字位好,我是陳彥銅,今天要介紹的

2
00:00:03,597 --> 00:00:06,663
是Wisper.cpp,這是一個

3
00:00:06,663 --> 00:00:10,549
用權c加加寫成的語音轉文字工具,

每一塊的起訖時間都是從字級時間戳算出來的,切在哪裡時間就跟到哪裡。這是沒有字級時間戳做不到的事——只有整段時間戳的話,切分點的時間只能靠字數比例去估。

不過要注意切分是按字元數,不看語意。上面第 2 塊「是Wisper.cpp,這是一個」結尾斷在一半,後面幾塊還會把 metal 從中間切成 meta + l。做正式字幕的話,這裡通常要自己拿 words 陣列,按標點或語意重新分組,而不是直接用 --max_line_width 的輸出。

語者分離

多人對話的場景可以加 --diarize,讓每段文字帶上 speaker 標籤:

whisperx audio.wav --diarize --hf_token <你的 token> \
  --min_speakers 2 --max_speakers 4

原理是 pyannote 先做語者分段(哪個時間區間是哪個人),再跟已經對齊好的字去比對時間重疊,把 speaker 標籤貼回每個字和每個段落。字級時間戳在這裡又派上用場:時間切得越準,語者交界處分得越乾淨。

預設模型是 pyannote/speaker-diarization-community-1。這是 HuggingFace 上的 gated model,要先去該模型頁面接受使用條款,再帶自己的 access token 進來。--min_speakers / --max_speakers 可以在已知人數時縮小搜尋範圍。

這段我沒有實測。 我的測試音檔是我自己一個人講的,而且拿 gated 模型要掛個人 token,所以上面是我從 whisperx/diarize.py 和 README 讀出來的行為,不是我跑出來的結果。要用在正式場合建議自己先跑一次確認。

三個實測才踩到的坑

torchcodec 載入失敗的警告可以無視

第一次跑會噴一長串紅字:

Could not load libtorchcodec. Likely causes:
  1. FFmpeg is not properly installed in your environment.
  ...
Library not loaded: @rpath/libavutil.59.dylib
  Reason: no LC_RPATH's found

看起來很嚴重,但轉錄照跑、結果照出。它只是找不到 torchcodec 要用的 FFmpeg 動態連結庫,接著就退回其他方式讀音檔了。想清掉的話裝一份 FFmpeg(brew install ffmpeg)即可,不裝也不影響輸出。

對齊需要 NLTK 的 punkt_tab,而且預設沒附

這個會真的擋住輸出。第一次跑帶對齊的完整流程時,最後噴:

Resource punkt_tab not found.
  Please use the NLTK Downloader to obtain the resource:
  >>> nltk.download('punkt_tab')

輸出目錄是空的——跑了五分鐘,什麼都沒產出來。句子切分要用 NLTK 的斷句模型,但這份資料不會跟著套件一起裝。

補裝就好:

python -c "import nltk; nltk.download('punkt_tab')"

我這台補裝時還撞到 CERTIFICATE_VERIFY_FAILED(Python 找不到根憑證)。正規解法是跑 macOS Python 附的 Install Certificates.command;我當下是先用臨時關閉驗證的方式抓下來,資料本身抓完就永久放在 ~/nltk_data,只會遇到這一次。

補完再跑,同一條指令 43 秒完成,對齊正常。

從 /tmp 執行 Python 會被擋 import

這個跟 WhisperX 沒關係,但我實測時撞到了,值得記一下。在 /tmp 底下跑 Python 腳本時噴:

ImportError: Blocked import of regex from current working directory
for security reasons.

Python 會把當前目錄放進模組搜尋路徑,/tmp 是全域可寫的目錄,所以直接擋掉——這是防止有人在共用目錄丟一個假的 regex.py 讓你載入。換到自己的目錄執行,或加 PYTHONSAFEPATH=1,就過了。

常用參數

參數用途
--model模型大小,base / small / medium / large-v3
--language指定語言(如 zh),不指定會先做語言偵測
--devicecpucuda;Apple Silicon 用 cpu
--compute_typeint8 / float32 / float16,CPU 上 int8 最快
--no_align跳過對齊,只要段落級時間戳時用
--diarize開啟語者分離,需搭配 --hf_token
--output_formatsrt / vtt / json / tsv / txt / aud / all
--max_line_width單行字數上限,配合字級時間戳切字幕
--batch_size批次大小,記憶體不足就調小
--vad_methodpyannotesilero

適合誰用

從實測結果看,WhisperX 的甜蜜點是需要精確時間軸的場景:

  • 做字幕:字級時間戳讓你想切哪就切哪,時間跟著走。
  • 做剪輯:知道每個字的秒數,就能用文字搜尋定位到音檔位置。
  • 做會議紀錄:語者分離加上時間戳,可以還原誰在第幾分鐘說了什麼。
  • 做語料品質篩選score 欄位直接標出哪些字對齊得心虛,適合抽出來人工複查。
如果只是要一份純文字逐字稿、不在意時間精度,那對齊這一步帶來的複雜度和依賴(PyTorch、pyannote、NLTK 資料、對應語言的 wav2vec2 模型)就不一定划算——--no_align 或直接用更輕的方案都可以。

我前一篇寫的 whisper.cpp 就是另一端:純 C/C++、一支執行檔、沒有 Python,但也沒有字級對齊和語者分離。兩者解的是不同問題,不是替代關係——實務上甚至可以先用 whisper.cpp 快速轉稿,需要精細時間軸時再用 WhisperX 走一次對齊。

延伸資源

小結

WhisperX 把「語音轉文字」拆成四個各有專責模型的階段,其中對齊那一步是它的核心:用 wav2vec2 做強制對齊,把時間戳從「模型順便猜的」變成「另一個模型算出來的」。

我在 M2 MacBook 純 CPU 上實測 17.66 秒中文,base 模型加對齊 35 秒,86 個字全部拿到 start / end / score,字幕可以按字級時間戳精確切分。中文的對齊粒度是單字,切分只看字元數不看語意,要做正式字幕還是得自己拿 words 陣列重新分組。

安裝上有兩個一定會遇到的東西:torchcodec 的警告可以無視,NLTK 的 punkt_tab 一定要補裝,否則對齊那步跑完會什麼都不輸出。

author
陳彥彤

AI 工程師 · AI 顧問。Java 後端 8 年、AI 工程師 2 年。AI 內訓 · AI 導入顧問 · 前後端與雲端培訓。

support

覺得文章有用可以到 GitHub 給個 star,或是透過信箱聊聊 AI 內訓、AI 導入顧問或前後端 / 雲端培訓。