懶人包
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(選用) |
我讀了 pyproject.toml,目前版本標 3.8.7rc1,最新的正式 release 是 v3.8.6(2026-05-25),需要 Python >=3.10, <3.14,主要相依是 faster-whisper>=1.2.0、pyannote-audio>=4.0.0、torch~=2.8.0、ctranslate2>=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() 是 True、torch.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
模型權重都已快取的情況下,兩種模式的耗時:
| 模式 | 耗時 | 相對音檔長度 |
|---|---|---|
| base + 對齊 | 35.11 秒 | 約 2.0 倍 |
base + --no_align | 25.71 秒 | 約 1.5 倍 |
輸出的 JSON 結構是這樣,segments[0].words 裡每個元素都有 start、end、score:
{"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),不指定會先做語言偵測 |
--device | cpu 或 cuda;Apple Silicon 用 cpu |
--compute_type | int8 / float32 / float16,CPU 上 int8 最快 |
--no_align | 跳過對齊,只要段落級時間戳時用 |
--diarize | 開啟語者分離,需搭配 --hf_token |
--output_format | srt / vtt / json / tsv / txt / aud / all |
--max_line_width | 單行字數上限,配合字級時間戳切字幕 |
--batch_size | 批次大小,記憶體不足就調小 |
--vad_method | pyannote 或 silero |
適合誰用
從實測結果看,WhisperX 的甜蜜點是需要精確時間軸的場景:
- 做字幕:字級時間戳讓你想切哪就切哪,時間跟著走。
- 做剪輯:知道每個字的秒數,就能用文字搜尋定位到音檔位置。
- 做會議紀錄:語者分離加上時間戳,可以還原誰在第幾分鐘說了什麼。
- 做語料品質篩選:
score欄位直接標出哪些字對齊得心虛,適合抽出來人工複查。
--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 一定要補裝,否則對齊那步跑完會什麼都不輸出。