はじめに
会議や授業の録音とかをスムーズに文字起こしできるよう、faster-whisper を WSL2 上に構築して PowerShell からエイリアスのように起動できる環境を作りました。
faster-whisper は OpenAI の Whisper を CTranslate2 で再実装したもので、同じ精度のまま最大 4 倍の高速化とメモリ使用量の削減を実現しています。授業 1 コマ (90 分) を 10〜15 分で処理できるのは、この高速化があってこそです。
グラボはRTX5060Tiつかってます。
最新の RTX 50 シリーズ (Blackwell, sm_120) は ML 系ライブラリのサポートがまだ過渡期で、地味につまずきポイントが多かったので、寄り道も含めて全部記録しておきます。
ゴール
PowerShell で
f-whisper .\授業01.m4a
と打つだけで、WSL 内の faster-whisper (large-v3 モデル)が GPU で書き起こしを実行し、同じフォルダに .txt / .timestamp.txt / .srt の 3 形式で結果を出力する。
環境
- Windows 11
- NVIDIA RTX 5060 Ti 16GB (Blackwell, sm_120)
- WSL2 + Ubuntu 26.04
- Python 3.12 (uv で管理)
- faster-whisper 最新版
寄り道 1: WSL2 ネイティブか Windows ネイティブか
最初に悩んだのがここでした。最終的には、WSL2使うことにしました。
理由:
- faster-whisper は GPU 推論に cuDNN を要求するが、Windows ネイティブだと
cudnn_ops_infer64_8.dllの PATH 問題でハマる人が多い - WSL2 なら Windows 側のドライバを共有して、WSL 内部には CUDA Toolkit を入れなくていい
- pip 経由で
nvidia-cublas-cu12nvidia-cudnn-cu12を入れる方式が公式推奨で、Linux の方が情報が多い - 50 シリーズ (Blackwell) 対応の修正は Linux 側に先に入る傾向がある
Windows ネイティブが向くのは「リアルタイムでマイク入力を扱う」「GUI アプリと統合する」みたいなケース。バッチ的にファイルを食わせるだけなら WSL2 で確実に幸せになれます。
ステップ 1: WSL2 + Ubuntu 26.04 セットアップ
WSL を別ドライブに置きたい場合(C ドライブを圧迫したくない場合)、--locationでインストール時に保存場所を指定できます。
wsl --install -d Ubuntu-26.04 --location W:\WSL\Ubuntu-26.04
確認:
wsl -l -v
Ubuntu-26.04 Stopped 2 のように VERSION が 2 になっていれば OK。VERSION 1 だと GPU が使えません。
ステップ 2: WSL から GPU が見えるか確認
WSL に入って:
nvidia-smi
グラボが認識されていれば成功です。ここでは CUDA Version: 13.x のような表示が出ることがありますが、これは「ドライバが対応する最大の CUDA バージョン」を表示しているだけで、実際に使うランタイム(CUDA 12)とは別物なので気にしなくて大丈夫です。
WSL 側に CUDA Toolkit を別途インストールする必要はありません。
つまずきポイント 1: Python 3.14 は使えない
Ubuntu 26.04 の標準 Python はバージョン 3.14。これがそのまま使えると思ったら罠でした。
$ python3 --version
Python 3.14.4
faster-whisper の中核である ctranslate2 (および PyAV) は C 拡張を含むため、Python のマイナーバージョンごとに wheel が必要ですが、3.14 用の wheel はまだ存在しません。
公式リポジトリの Issue でも「3.14 で失敗、3.13 で成功」という報告があり、関連エコシステム (whisply, WhisperX) もパッケージ要件として Python <3.14, >=3.10 を指定しています。
3.12が安定しているので、そちらをいれます。
ステップ 3: uv のインストール
Python 3.12 をどう入れるか。Ubuntu 26.04 の apt には 3.12 のパッケージが無いので、pyenv か uv か conda か、選択肢があります。
今回は uv を選択。Rust 製で速く、Python のバージョン管理 + パッケージ管理 + venv 管理を一つでこなせる。(物知りげに言っていますが初めて使いました)
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
uv --version
ステップ 4: 作業ディレクトリと venv の作成
cd ~
mkdir -p tools/fasterWhisper
cd tools/fasterWhisper
uv venv --python 3.12
source .venv/bin/activate
python --version # Python 3.12.x
uv が Python 3.12 を自動でダウンロードして .venv/ を作ってくれます。
寄り道: 作業場所のおすすめ
私はWSL ディストリ本体 (ext4.vhdx) を W ドライブに置いたので、/home/... 配下に何を作ってもすべて W ドライブの中に書かれます。/mnt/c/... (Windows の C を直接見るマウントポイント) を作業場所にすると I/O が極端に遅くなるので、必ず Ubuntu ホーム配下で作業します。
ステップ 5: faster-whisper + CUDA ライブラリのインストール
uv pip install faster-whisper
uv pip install nvidia-cublas-cu12 "nvidia-cudnn-cu12==9.*"
nvidia-cudnn-cu12==9.* がポイント。最新の ctranslate2 は CUDA 12 + cuDNN 9 のみサポート。cuDNN 8 を入れてしまうと ctranslate2==4.4.0 までダウングレードが必要になります。
Blackwell (5060 Ti) は CUDA 12.8+ / cuDNN 9 が必須なので、最新のままで OK。
ちなみに ffmpeg は 不要。faster-whisper は内部で PyAV (ffmpeg バンドル版) を使うので、m4a / mp4 / mp3 などをそのまま渡せます。
ステップ 6: 動作確認
つまずきポイント 2: LD_LIBRARY_PATH の組み立て
公式 README にあるコマンドをそのまま実行したらエラーになりました。
export LD_LIBRARY_PATH=$(python -c 'import os; import nvidia.cublas.lib; import nvidia.cudnn.lib; print(os.path.dirname(nvidia.cublas.lib.__file__) + ":" + os.path.dirname(nvidia.cudnn.lib.__file__))')
TypeError: expected str, bytes or os.PathLike object, not NoneType
これは Python 3.12+ と最新の nvidia 系 wheel の組み合わせで起きる namespace package 問題で、__file__ が None を返すケースがあるためです。
→ __path__ を使う書き方に修正:
export LD_LIBRARY_PATH=$(python -c "import nvidia.cublas, nvidia.cudnn; import os; print(os.path.join(list(nvidia.cublas.__path__)[0], 'lib') + ':' + os.path.join(list(nvidia.cudnn.__path__)[0], 'lib'))")
GPU でモデルロード確認
python -c "
from faster_whisper import WhisperModel
m = WhisperModel('tiny', device='cuda', compute_type='float16')
print('GPU model loaded OK')
"
GPU model loaded OK が出れば成功。Blackwell 環境での最大の関門突破です。
Warning: You are sending unauthenticated requests to the HF Hub という警告が出ますが、これは Hugging Face のレート制限の話で、初回モデルダウンロード以降はアクセスしないので無視して OK です。
ステップ 7: テスト音源で書き起こし確認
定番テスト音源 (Whisper エコシステムでよく使われる JFK の演説) をダウンロード:
curl -LO https://github.com/openai/whisper/raw/main/tests/jfk.flac
python -c "
from faster_whisper import WhisperModel
m = WhisperModel('tiny', device='cuda', compute_type='float16')
segments, info = m.transcribe('jfk.flac')
for s in segments:
print(f'[{s.start:.2f}-{s.end:.2f}] {s.text}')
print(f'lang={info.language} prob={info.language_probability:.2f}')
"
JFK の有名な一節が出力されれば成功。
そのまま 'tiny' を 'large-v3' に変えて、本番モデルのダウンロード(約 3GB)と動作も確認しておきます。
ステップ 8: transcribe.py を作る
引数を受け取って文字起こし結果を出力するスクリプト。
~/tools/fasterWhisper/transcribe.py:
import argparse, os, sys
from faster_whisper import WhisperModel
p = argparse.ArgumentParser()
p.add_argument("audio", help="音声/動画ファイルのパス")
p.add_argument("--model", default="large-v3", help="モデル名 (tiny/base/small/medium/large-v3)")
p.add_argument("--language", default="auto", help="言語コード (ja/en/auto)")
p.add_argument("--device", default="cuda")
p.add_argument("--compute-type", default="float16", help="float16/float32/int8_float16")
p.add_argument("--no-vad", action="store_true", help="VADを無効化")
p.add_argument("--output-dir", default=None, help="出力先 (省略時は入力と同じ場所)")
p.add_argument("--format", default="txt,srt", help="出力形式 カンマ区切り (txt,srt,vtt)")
p.add_argument("--initial-prompt", default=None, help="専門用語などのヒント")
args = p.parse_args()
lang = None if args.language == "auto" else args.language
print(f"Loading model: {args.model} ({args.compute_type}) on {args.device}...", file=sys.stderr)
model = WhisperModel(args.model, device=args.device, compute_type=args.compute_type)
print(f"Transcribing: {args.audio}", file=sys.stderr)
segments, info = model.transcribe(
args.audio,
language=lang,
vad_filter=not args.no_vad,
initial_prompt=args.initial_prompt,
)
print(f"Detected language: {info.language} (prob={info.language_probability:.2f})", file=sys.stderr)
audio_dir, audio_name = os.path.split(os.path.abspath(args.audio))
base_name = os.path.splitext(audio_name)[0]
out_dir = args.output_dir or audio_dir
os.makedirs(out_dir, exist_ok=True)
base = os.path.join(out_dir, base_name)
def ts(t, sep=","):
h, rem = divmod(int(t), 3600)
m, s = divmod(rem, 60)
ms = int((t - int(t)) * 1000)
return f"{h:02}:{m:02}:{s:02}{sep}{ms:03}"
def ts_short(t):
h, rem = divmod(int(t), 3600)
m, s = divmod(rem, 60)
return f"{h:02}:{m:02}:{s:02}"
formats = [f.strip() for f in args.format.split(",")]
files = {}
if "txt" in formats:
files["txt"] = open(f"{base}.txt", "w", encoding="utf-8")
files["timestamp_txt"] = open(f"{base}.timestamp.txt", "w", encoding="utf-8")
if "srt" in formats:
files["srt"] = open(f"{base}.srt", "w", encoding="utf-8")
if "vtt" in formats:
files["vtt"] = open(f"{base}.vtt", "w", encoding="utf-8")
files["vtt"].write("WEBVTT\n\n")
for i, seg in enumerate(segments, 1):
text = seg.text.strip()
if "txt" in files:
files["txt"].write(text + "\n")
files["timestamp_txt"].write(f"[{ts_short(seg.start)} - {ts_short(seg.end)}] {text}\n")
if "srt" in files:
files["srt"].write(f"{i}\n{ts(seg.start)} --> {ts(seg.end)}\n{text}\n\n")
if "vtt" in files:
files["vtt"].write(f"{ts(seg.start,'.')} --> {ts(seg.end,'.')}\n{text}\n\n")
print(f"[{seg.start:6.2f} - {seg.end:6.2f}] {text}", file=sys.stderr)
for f in files.values():
f.close()
output_exts = []
if "txt" in formats:
output_exts.extend(["txt", "timestamp.txt"])
if "srt" in formats:
output_exts.append("srt")
if "vtt" in formats:
output_exts.append("vtt")
print(f"Done. Output: {base}.[{','.join(output_exts)}]", file=sys.stderr)
出力例
jfk.txt (素のテキスト):
And so my fellow Americans,
ask not what your country can do for you,
ask what you can do for your country.
jfk.timestamp.txt (タイムスタンプ付き):
[00:00:00 - 00:00:03] And so my fellow Americans,
[00:00:03 - 00:00:07] ask not what your country can do for you,
[00:00:07 - 00:00:11] ask what you can do for your country.
jfk.srt (字幕用):
1
00:00:00,000 --> 00:00:03,000
And so my fellow Americans,
...
ステップ 9: run.sh (シェルラッパー) を作る
PowerShell から呼ばれる入り口。venv 有効化と LD_LIBRARY_PATH 設定をここで吸収します。
~/tools/fasterWhisper/run.sh:
#!/usr/bin/env bash
set -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$DIR/.venv/bin/activate"
export LD_LIBRARY_PATH=$(python -c "import nvidia.cublas, nvidia.cudnn; import os; print(os.path.join(list(nvidia.cublas.__path__)[0], 'lib') + ':' + os.path.join(list(nvidia.cudnn.__path__)[0], 'lib'))")
exec python "$DIR/transcribe.py" "$@"
実行権限を付与:
chmod +x run.sh
なぜ LD_LIBRARY_PATH を毎回設定する必要があるのか
pip でインストールした NVIDIA 系ライブラリの .so ファイルは .venv/lib/.../nvidia/cublas/lib/ などの奥まった場所に置かれます。OS のローダーは標準ではここを見ないので、Python 起動前に明示的に教えてあげる必要があります。
システムレベルで CUDA を入れた場合(apt install)は標準パスに置かれるので不要ですが、その方式はバージョン管理が面倒なので、現代では pip + venv + LD_LIBRARY_PATH が公式推奨です。
ステップ 10: PowerShell プロファイルに f-whisper を登録
ここからは Windows 側の作業。
つまずきポイント 3: 実行ポリシー
最初にプロファイルを読み込もうとすると:
このシステムではスクリプトの実行が無効になっているため...
というエラーが出ます。Windows のデフォルトでは PowerShell スクリプトの実行が制限されているためです。
解決:
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
-
CurrentUserスコープなので、自分のユーザーアカウントだけに適用 (管理者権限不要) -
RemoteSignedはローカルスクリプトは許可、ネットからダウンロードしたものは署名必要
つまずきポイント 4: プロファイルファイルがそもそも無い
$PROFILE 変数はパスを返してくれますが、実体のファイルは自分で作る必要があります。
Test-Path $PROFILE
False が返ったら作成:
New-Item -Path $PROFILE -Type File -Force
-Force で親フォルダごと作ってくれます。
プロファイルを編集
notepad $PROFILE
以下を貼り付けて保存:
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
function f-whisper {
if ($args.Count -eq 0) {
Write-Host "Usage: f-whisper <audio-file> [--model large-v3] [--language ja] [--format txt,srt] ..."
Write-Host ""
Write-Host "Examples:"
Write-Host " f-whisper .\lecture01.m4a"
Write-Host " f-whisper .\lecture01.mp4 --model medium"
Write-Host " f-whisper .\lecture01.m4a --initial-prompt '情報科学。固有名詞: 田中先生'"
return
}
$file = $args[0]
if (Test-Path $file) {
$resolved = (Resolve-Path $file).Path
$drive = $resolved.Substring(0, 1).ToLower()
$rest_path = $resolved.Substring(2).Replace('\', '/')
$file = "/mnt/$drive$rest_path"
} else {
Write-Host "Warning: file not found locally: $file" -ForegroundColor Yellow
}
$rest = @()
if ($args.Count -gt 1) {
$rest = $args[1..($args.Count - 1)]
}
wsl -d Ubuntu-26.04 【ここにrun.shまでのpath】 $file @rest
}
ポイント
-
@rest: PowerShell の splatting。配列を個別の引数として展開する書き方。これで--model mediumなどのオプションが argparse にそのまま流れる -
wsl -d Ubuntu-26.04: 明示的にディストリ指定。複数 WSL を入れていても確実に Ubuntu-26.04 が呼ばれる
プロファイル反映
. $PROFILE
確認
Get-Command f-whisper
Function f-whisper と出れば登録成功です。
動作確認
デスクトップに test.m4a という音声ファイルがあったとします。
cd C:\Users\xxx\Desktop
f-whisper .\test.m4a
実行後、デスクトップに以下が生成されます:
-
test.txt(素のテキスト) -
test.timestamp.txt(範囲付きタイムスタンプ) -
test.srt(字幕)
使い方バリエーション
# 軽量モデルで高速処理
f-whisper .\test.m4a --model medium
# 言語を明示
f-whisper .\test.mp4 --language ja
# 専門用語を伝えて精度向上
f-whisper .\統計学01.m4a --initial-prompt "統計学。固有名詞: 田中先生、ベイズ統計、最尤推定"
# vtt も含めて全形式出力
f-whisper .\test.m4a --format txt,srt,vtt
# 出力先を変える
f-whisper .\test.m4a --output-dir C:\Users\xxx\Documents\transcripts
# 量子化で速度を稼ぐ
f-whisper .\test.m4a --compute-type int8_float16
速度の目安 (RTX 5060 Ti, large-v3, float16)
90 分授業を 約 10〜15 分 で処理。コマンド打ってコーヒー入れて戻ってきたら終わっている感覚です。
まとめ
レシピが固まれば、あとは PowerShell からシュッと呼べる定型作業になります。
私はやったことすぐ忘れるので備忘録てきな記事なのですが、まあ同じ構成で困っている人の参考になれば幸いです。