WhisperX实战:3步部署语音转文字,精准识别说话人

一、为什么你需要 WhisperX?

假设你手头有一段 2 小时的会议录音,要整理成逐字稿,还得标注"张三说了什么、李四说了什么"。传统的做法是:先扔进某个转写工具拿到纯文本,再人工听一遍去猜每句话是谁说的------一份 1 小时的音频至少要花 3-4 小时手动整理

OpenAI 开源的 Whisper 模型能做语音转文字,但它有两个硬伤:没有精确的时间戳对齐更不认说话人 。WhisperX 就是来解决这个问题的------它在 Whisper 基础上叠加了强制对齐(forced alignment) 和**说话人分离(speaker diarization)**两套技术,一条命令就能拿到"谁在什么时间说了什么"的完整结果。

上图是 WhisperX 的完整处理管线:音频输入 → Whisper 转录 → 强制对齐时间戳 → 说话人分离 → 带说话人标签的最终文本。

本文用 3 个步骤 带你跑通整个流程,每一步都有完整可运行代码和输出示例


二、环境准备

硬件要求

| 配置项 | 最低要求 | 推荐配置 |

|--------|---------|----------|

| GPU 显存 | 4 GB(small 模型) | 10 GB+(large-v3) |

| 内存 | 8 GB | 16 GB+ |

| 磁盘 | 5 GB | 10 GB+ |

纯 CPU 也能跑,但一段 10 分钟的音频可能转写 5-10 分钟------有 GPU 会快 10 倍以上。

第 1 步:安装依赖

bash 复制代码
# 1. 安装 ffmpeg(音频解码必须)
# Ubuntu/Debian
sudo apt install ffmpeg -y

# macOS
brew install ffmpeg

# Windows: 从 https://ffmpeg.org/download.html 下载,添加至 PATH

# 2. 安装 WhisperX
pip install whisperx

# 3. 验证安装
python -c "import whisperx; print(whisperx.__version__)"

运行结果:

text 复制代码
3.1.2    # 版本号以实际为准

第 2 步:获取 Hugging Face Token

说话人分离依赖 pyannote 模型,需要你同意其使用协议:

⚠️ 这一步很容易漏掉!如果不点 Agree,运行时会报 `403 Forbidden`,卡住半天不知道原因。


三、实战步骤

准备工作:准备一段测试音频

建议先用一段有 2-3 个人对话、时长 30 秒到 2 分钟的音频测试。可以用手机录一段同事讨论,或者从播客截取一小段。格式支持 mp3 / wav / m4a / ogg。

下文中 AUDIO_FILE 变量统一指向你的音频文件路径。


第 1 步:Whisper 语音转文字(含词级时间戳)

这一步拿到带单词级别时间戳的转录结果------每个单词精确到它在音频的第几秒到第几秒出现。

python 复制代码
import whisperx
import gc

# ========== 配置 ==========
AUDIO_FILE = "meeting_audio.mp3"    # 替换为你的音频文件
DEVICE = "cuda"                     # 无 GPU 改为 "cpu"
COMPUTE_TYPE = "float16"            # CPU 用 "float32",低显存用 "int8"
BATCH_SIZE = 16                     # 显存不够就改小到 8 或 4
# ==========================

# 1. 加载音频(自动重采样到 16kHz 单声道)
audio = whisperx.load_audio(AUDIO_FILE)
print(f"音频加载完成,时长: {len(audio)/16000:.1f} 秒")

# 2. 加载 Whisper 模型并转录
model = whisperx.load_model("large-v3", device=DEVICE, compute_type=COMPUTE_TYPE)
result = model.transcribe(audio, batch_size=BATCH_SIZE)

print(f"检测到语言: {result['language']}")
print(f"\n转录片段数: {len(result['segments'])}")
print("-" * 50)
for seg in result["segments"][:5]:   # 先看前 5 条
    print(f"[{seg['start']:.2f}s → {seg['end']:.2f}s] {seg['text'].strip()}")

# 释放模型显存
gc.collect()
del model

运行结果示例:

text 复制代码
音频加载完成,时长: 87.3 秒
检测到语言: zh

转录片段数: 23
--------------------------------------------------
[0.00s → 4.82s] 今天我们來討論一下第二季度產品的迭代計劃
[5.10s → 9.34s] 首先請產品經理介紹一下目前的進展
[9.68s → 15.21s] 好的 目前我們完成了用戶調研階段 收集了大概兩百份有效問卷
[15.68s → 21.03s] 數據顯示用戶最關心的三個功能分別是批量導出 權限管理 和自動備份
[21.45s → 24.87s] 批量導出的優先級是最高的 建議下個版本先上

第 1 步完成。纯文本 + 时间戳已经到手,但还不知道每句话是谁说的。


第 2 步:词级对齐(强制对齐)

Whisper 原生的时间戳是估算 的------特别是中文和混合语言场景下,秒数经常不准。WhisperX 使用**音素级强制对齐(forced alignment)**来逐词校准位置。

python 复制代码
import torch

# 1. 加载对齐模型(语言要和上一步检测结果一致)
align_model, metadata = whisperx.load_align_model(
    language_code=result["language"],
    device=DEVICE
)

# 2. 执行对齐------给每个单词精确标定时间
result = whisperx.align(
    result["segments"],
    align_model,
    metadata,
    audio,
    device=DEVICE,
    return_char_alignments=False      # 不需要字级对齐,省内存
)

# 3. 查看对齐后的词级结果
print("对齐后的词级时间戳(前 20 个词):")
print("-" * 60)
word_count = 0
for seg in result["segments"]:
    if "words" not in seg:
        continue
    for w in seg["words"]:
        if word_count >= 20:
            break
        print(f"  {w['word']:<12s}  {w['start']:.2f}s → {w['end']:.2f}s  (置信度: {w.get('score', 0):.2f})")
        word_count += 1

# 释放对齐模型
gc.collect()
torch.cuda.empty_cache()
del align_model

运行结果示例:

text 复制代码
对齐后的词级时间戳(前 20 个词):
--------------------------------------------------------------
  今天          0.02s → 0.38s  (置信度: 0.92)
  我們          0.38s → 0.74s  (置信度: 0.88)
  來            0.74s → 0.90s  (置信度: 0.95)
  討論          0.90s → 1.28s  (置信度: 0.91)
  一下          1.28s → 1.62s  (置信度: 0.87)
  第二季度      1.62s → 2.30s  (置信度: 0.93)
  產品          2.30s → 2.66s  (置信度: 0.90)
  的            2.66s → 2.78s  (置信度: 0.96)
  迭代          2.78s → 3.14s  (置信度: 0.89)
  計劃          3.14s → 3.58s  (置信度: 0.94)
  ...

第 2 步完成。现在每个词都有精准的时间锚点------这是下一步说话人分离的基础。


第 3 步:说话人分离(Speaker Diarization)

这是 WhisperX 的核心增值能力。利用 pyannote 模型,它能分析音频的声纹特征,自动判断"这段是谁在说话"。

python 复制代码
# ========== Hugging Face Token ==========
HF_TOKEN = "hf_你的token"   # ← 替换为你的 HuggingFace Token
# =========================================

# 1. 初始化说话人分离管线
diarize_model = whisperx.DiarizationPipeline(
    use_auth_token=HF_TOKEN,
    device=DEVICE
)

# 2. 执行说话人分离
#    min_speakers / max_speakers 可选------如果你知道有几个人,就传进去
diarize_segments = diarize_model(
    audio,
    min_speakers=2,    # 至少 2 个人
    max_speakers=4     # 最多 4 个人
)

# 3. 将说话人标签赋给每个单词
result = whisperx.assign_word_speakers(diarize_segments, result)

# 4. 打印最终结果
print("\n" + "=" * 70)
print("   最终转录结果(含说话人标签)")
print("=" * 70 + "\n")

prev_speaker = None
for seg in result["segments"]:
    speaker = seg.get("speaker", "UNKNOWN")
    start = seg["start"]
    end = seg["end"]
    text = seg["text"].strip()

    # 说话人切换时加分隔线
    if speaker != prev_speaker:
        print(f"\n--- {speaker} ---\n")

    timestamp = f"{int(start//60):02d}:{start%60:05.2f}"
    print(f"[{timestamp}] {text}")
    prev_speaker = speaker

print("\n" + "=" * 70)

# 清理资源
gc.collect()
torch.cuda.empty_cache()
del diarize_model

运行结果示例:

text 复制代码
======================================================================
   最终转录结果(含说话人标签)
======================================================================

--- SPEAKER_00 ---

[00:00.00] 今天我们來討論一下第二季度產品的迭代計劃
[00:05.10] 首先請產品經理介紹一下目前的進展

--- SPEAKER_01 ---

[00:09.68] 好的 目前我們完成了用戶調研階段 收集了大概兩百份有效問卷
[00:15.68] 數據顯示用戶最關心的三個功能分別是批量導出 權限管理 和自動備份
[00:21.45] 批量導出的優先級是最高的 建議下個版本先上

--- SPEAKER_02 ---

[00:25.32] 我補充一點 從客服那邊反饋來看 自動備份的需求也很強烈
[00:29.87] 很多用戶因為沒有自動備份丟過數據

--- SPEAKER_00 ---

[00:33.14] 好的 那這兩個功能我們都納入下個版本 開發週期大概需要多久

======================================================================

第 3 步完成。至此,你拿到了结构化的"谁→何时→说了什么"完整数据。


四、一键脚本:3 步合并

把上面三步合并成一个可复用函数------以后换一段音频,改一下文件路径就能跑:

python 复制代码
import whisperx
import gc
import torch


def transcribe_with_diarization(
    audio_path: str,
    hf_token: str,
    model_name: str = "large-v3",
    device: str = "cuda",
    compute_type: str = "float16",
    batch_size: int = 16,
    min_speakers: int = None,
    max_speakers: int = None,
):
    """一键完成 转录 + 对齐 + 说话人分离"""
    # --- 加载音频 ---
    audio = whisperx.load_audio(audio_path)
    print(f"✓ 音频加载完成: {len(audio)/16000:.1f}s")

    # --- Step 1: 转录 ---
    print(f"→ 加载 Whisper 模型: {model_name}...")
    model = whisperx.load_model(model_name, device=device, compute_type=compute_type)
    result = model.transcribe(audio, batch_size=batch_size)
    print(f"  检测语言: {result['language']}, 片段数: {len(result['segments'])}")

    gc.collect()
    torch.cuda.empty_cache()
    del model

    # --- Step 2: 对齐 ---
    print(f"→ 加载对齐模型...")
    align_model, metadata = whisperx.load_align_model(
        language_code=result["language"], device=device
    )
    result = whisperx.align(
        result["segments"], align_model, metadata, audio, device=device
    )
    print(f"  词级对齐完成")

    gc.collect()
    torch.cuda.empty_cache()
    del align_model

    # --- Step 3: 说话人分离 ---
    print(f"→ 加载说话人分离模型...")
    diarize_model = whisperx.DiarizationPipeline(
        use_auth_token=hf_token, device=device
    )
    diarize_segments = diarize_model(
        audio, min_speakers=min_speakers, max_speakers=max_speakers
    )
    result = whisperx.assign_word_speakers(diarize_segments, result)

    # 统计说话人
    speakers = set()
    for seg in result["segments"]:
        if "speaker" in seg:
            speakers.add(seg["speaker"])
    print(f"  检测到 {len(speakers)} 位说话人: {sorted(speakers)}")

    gc.collect()
    torch.cuda.empty_cache()
    del diarize_model

    return result


# ========== 使用示例 ==========
if __name__ == "__main__":
    result = transcribe_with_diarization(
        audio_path="meeting_audio.mp3",
        hf_token="hf_你的token",
        model_name="large-v3",
        min_speakers=2,
        max_speakers=4,
    )

    # 保存为带说话人标签的 SRT 字幕
    with open("output_diarized.srt", "w", encoding="utf-8") as f:
        for i, seg in enumerate(result["segments"], start=1):
            speaker = seg.get("speaker", "UNKNOWN")
            text = seg["text"].strip()

            def fmt(sec):
                h, m, s = int(sec//3600), int((sec%3600)//60), int(sec%60)
                ms = int((sec - int(sec)) * 1000)
                return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"

            f.write(f"{i}\n{fmt(seg['start'])} --> {fmt(seg['end'])}\n")
            f.write(f"[{speaker}] {text}\n\n")

    print("\n✓ SRT 字幕已保存至 output_diarized.srt")

运行结果:

text 复制代码
✓ 音频加载完成: 87.3s
→ 加载 Whisper 模型: large-v3...
  检测语言: zh, 片段数: 23
→ 加载对齐模型...
  词级对齐完成
→ 加载说话人分离模型...
  检测到 3 位说话人: ['SPEAKER_00', 'SPEAKER_01', 'SPEAKER_02']
✓ SRT 字幕已保存至 output_diarized.srt

五、常见问题与调优建议

1. 显存不够怎么办?

| 问题 | 解决方案 |

|------|----------|

| CUDA out of memory | ① 换小模型 large-v3mediumsmall;② compute_type 改用 "int8";③ batch_size 降到 4 |

| 纯 CPU 环境 | device="cpu", compute_type="float32"------能用但慢 |

| 长音频(>1小时) | 先切分成 5-10 分钟的片段分别处理 |

2. 说话人检测不准?

  • **指定人数**:如果你知道音频里有几个人,传 `min_speakers` 和 `max_speakers` 参数能显著提升准确率
  • **音频质量**:背景噪音大、多人重叠说话时准确率会明显下降------这是 pyannote 的已知局限
  • **中文优化**:Whisper `large-v3` 对中文识别效果最好,不要用 `turbo` 模型(不支持 diarization)

3. 模型选择参考

| 模型 | 显存 | 速度 | 中文识别 | 适用场景 |

|------|------|------|----------|----------|

| tiny / base | 1-2 GB | 极快 | 一般 | 英文测试 |

| small / medium | 2.5-5 GB | 中等 | 较好 | 个人项目 |

| large-v3 | ~10 GB | 较慢 | 最佳 | 生产环境 |


六、总结

WhisperX 把原本需要多个工具拼接 + 大量手工操作的音频转写任务,压缩成了一条 Python 管线:

text 复制代码
音频文件 → [Whisper 转录] → [强制对齐] → [说话人分离] → 带说话人标签的逐字稿

三个核心步骤,不到 60 行代码,就能解决会议纪要、访谈整理、播客字幕等实际场景的痛点。

如果你在实际使用中遇到问题,推荐查阅 WhisperX 官方仓库(https://github.com/m-bain/whisperX) 的 Issues 区------12k+ star 的社区非常活跃,常见问题基本都有解答。


本文首发于 CSDN,转载请注明出处。