Windows ローカル環境へWebUIのWhisperを構築する(無料で文字起こし)

概要

Windows11でPythonを使って有料のクラウド版を使わず、PC上でWhisperによる音声から文字起こしを行う事を目的として環境構築を行う説明。
自分の備忘録としてのメモがてらの投稿です。所要時間は30分程度でしょうか。

想定スキル

Powershellでコマンドの入力が出来る。Pythonの実行ができる。その位の初級者でも大丈夫です。

マシン環境

Windows 11、計算を早くしたい場合はNvidiaのグラフィックボードがあると理想的。

Python, FFmpegのインストール

Python環境を使うためと、WhisperでFFmepgを使うためインストールします。
インストールはchocolateyで行うと楽な為、chocolateyを 次のサイトからインストールします。
chocolatey公式サイト

chocolateyのインストール後は次のコマンドでpythonとFFmpegをインストールします。
choco install python --version=3.10.0
choco install ffmpeg
を実行しておきます。二つ同時にインストールです。

Whisper WebUI構築手順

今回はユーザープロファイルのドキュメントフォルダ内へ作成します。
Terminal (またはPowershell)を起動。
cd ~\documents を入力するとカレントユーザーへのドキュメントフォルダへ移動します。
mkdir WisPy 今回は WisPyというフォルダで作成。

次に、今回のPython実行環境の為の仮想環境を作成する。
cd WisPy
python -m venv venv 今回はvenvというフォルダで仮想環境を作成。もしPythonコマンドを受け付けてくれないエラーの場合は一度、Terminalを再起動してから再度行う。
cd venv 次の作業の為にvenvへ移動
explorer . を入力してフォルダをエクスプローラーで開く

venvフォルダ内に次の二つのファイルを作成します。

install.txt作成
フォルダにテキストファイルを開き、テキストには次のように入力して保存する。

openai-whisper>=20231117
streamlit>=1.27.0
torch>=2.0.0
torchaudio>=2.0.0
numpy==1.26.4
pydub>=0.25.1
librosa>=0.10.0
soundfile>=0.12.1

Pythonの仮想環境をアクティブにします。
Terminalへ戻って次のコマンドでアクティブにする。
.\Scripts\activate

もし、Powershellが実行出来ないエラー、例えば、cannot be loaded because running scripts disabled on this system. For more infomation, see about_Execution_Policies ~というようなポリシーによる理由だった場合は、次のコマンドでポリシー変更をします。
Set-ExecutionPolicy RemoteSigned

正しく実行されると (venv) PS C:~ というような表示に変わります。
ここから仮想環境内でのコマンド実行になります。

install.txt内で指定したPythonライブラリをインストールする。
pip install -r .\install.txt

もし、ライブラリのインストール中にFailed to “numba” ~などのエラーが表示される場合、Windowsのファイルシステムの設定が影響しているかもしれません。この場合、長いPATHを許可する設定に変更する必要があります。

regedit.exeを起動し、HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem
にある、LongPathsEnabled の値を 0から1へ変更してWindowsを再起動してください。再起動後は、
Terminalでvenvフォルダへ移動して、.\Scripts\activateを再度実行する事は忘れないようにして、pip install -r .\install.txt を再度実行します。

Wisp.py作成
Webアプリです。このファイルがブラウザでWhisperを動作出来るようにします。

#!/usr/bin/env python3
"""
Whisper 文字起こし Web アプリ(Streamlit・Windows 向け・設定をメイン画面に表示)
"""

import os
import time
import tempfile
from datetime import datetime

import streamlit as st
import torch
import whisper

# ページ設定
st.set_page_config(
    page_title="Whisper文字起こしツール",
    page_icon="🔊",
    layout="wide",
)


@st.cache_resource
def load_whisper_model(model_name: str):
    """Whisper モデルをロード(キャッシュあり)"""
    device = "cuda" if torch.cuda.is_available() else "cpu"
    return whisper.load_model(model_name, device=device)


def check_ffmpeg():
    """FFmpeg がインストールされているか確認(Windows 用に NUL を使用)"""
    if os.system("ffmpeg -version > NUL 2>&1") != 0:
        st.error(
            "⚠️ FFmpeg がインストールされていません。\n"
            "  例: 管理者権限の PowerShell で `choco install ffmpeg` を実行してから、\n"
            "      ターミナルを開き直して再度お試しください。chocolateyがインストールされている場合の例です。"
        )
        st.stop()


def get_available_models():
    """選択可能な Whisper モデル一覧"""
    return ["tiny", "base", "small", "medium", "large"]


def main():
    st.title("Whisper 文字起こしツール(設定メイン表示版)")
    st.write(
        "ローカル環境(Windows)で Whisper を使って、音声ファイルをテキストに変換します。"
        "言語指定をすると精度、速度が良くなる場合があります"
    )

    # FFmpeg の確認
    check_ffmpeg()

    # デバイス情報
    if torch.cuda.is_available():
        device_label = "GPU (CUDA)"
        st.info(f"使用デバイス: {device_label}")
    else:
        device_label = "CPU"
        st.info(
            f"使用デバイス: {device_label}(CUDA 対応 GPU がないため、CPU で処理します。長時間の音声は時間がかかる場合があります)"
        )

    st.markdown("---")

    # ===== 設定エリア(メイン画面上部) =====
    st.subheader("設定")

    col1, col2 = st.columns(2)

    with col1:
        model_name = st.selectbox(
            "モデルサイズ",
            options=get_available_models(),
            index=1,  # base をデフォルト
            help="大きいモデルほど精度は上がりますが、処理時間とメモリ使用量も増えます。",
        )

    with col2:
        language_code = st.selectbox(
            "言語(空欄なら自動検出)",
            options=["", "en", "ja", "zh", "de", "fr", "es", "ko", "ru"],
            index=0,
            format_func=lambda x: {
                "": "自動検出",
                "en": "英語",
                "ja": "日本語",
                "zh": "中国語",
                "de": "ドイツ語",
                "fr": "フランス語",
                "es": "スペイン語",
                "ko": "韓国語",
                "ru": "ロシア語",
            }.get(x, x),
            help="音声の言語が分かっている場合は指定すると精度・速度が安定します。",
        )

    st.markdown("---")

    # ===== 音声ファイルアップロード =====
    st.subheader("音声ファイル")

    uploaded_file = st.file_uploader(
        "mp3 / m4a / wav などの音声ファイルをアップロードしてください",
        type=["mp3", "wav", "m4a", "ogg", "flac"],
    )

    if uploaded_file is None:
        st.info("音声ファイルをアップロードすると、ここに情報が表示される。")
        return

    # 拡張子取得・簡単な情報表示
    ext = uploaded_file.name.split(".")[-1].lower()
    file_size_mb = uploaded_file.size / (1024 * 1024)
    st.write(f"- ファイル名: `{uploaded_file.name}`(約 {file_size_mb:.2f} MB)")

    # 音声の試聴
    st.audio(uploaded_file, format=f"audio/{ext}")

    st.markdown("### 文字起こし")

    if st.button("文字起こし開始", type="primary"):
        progress_text = st.empty()
        progress_bar = st.progress(0)

        try:
            # 1. 一時ファイル作成
            progress_text.text("一時ファイルを作成中...")
            progress_bar.progress(10)

            with tempfile.NamedTemporaryFile(delete=False, suffix=f".{ext}") as tmp:
                tmp.write(uploaded_file.getvalue())
                temp_path = tmp.name

            # 2. モデルロード
            progress_text.text(f"モデル `{model_name}` をロード中...")
            load_start = time.time()
            model = load_whisper_model(model_name)
            load_end = time.time()
            progress_bar.progress(40)
            progress_text.text(
                f"モデルロード完了({load_end - load_start:.2f} 秒)。文字起こしを開始します..."
            )

            # 3. 文字起こし
            transcribe_start = time.time()
            progress_bar.progress(50)

            transcribe_options = {}
            if language_code:
                transcribe_options["language"] = language_code

            result = model.transcribe(temp_path, **transcribe_options)

            transcribe_end = time.time()
            progress_bar.progress(100)
            progress_text.text("文字起こしが完了しました。結果を表示しています...")

            total_time = transcribe_end - load_start
            transcribe_time = transcribe_end - transcribe_start

        except Exception as e:
            progress_bar.progress(0)
            progress_text.empty()
            st.error(f"エラーが発生しました: {e}")
            return

        finally:
            # 一時ファイル削除
            if "temp_path" in locals() and os.path.exists(temp_path):
                try:
                    os.unlink(temp_path)
                except OSError:
                    pass

        # ===== 結果表示 =====
        st.success(
            f"文字起こし完了(文字起こし処理: {transcribe_time:.2f} 秒 / 合計: {total_time:.2f} 秒)"
        )

        text = result.get("text", "")

        st.markdown("### 結果(テキスト全文)")
        st.text_area("文字起こし結果", value=text, height=250)

        st.download_button(
            label="テキストをダウンロード",
            data=text,
            file_name=f"{os.path.splitext(uploaded_file.name)[0]}_transcript.txt",
            mime="text/plain",
        )

if __name__ == "__main__":
    main()

構築できた環境を試しに起動します。
streamlit run .\web.py

Webブラウザに次のようなウィンドウが開きます。
Browse Filesをクリックし、対応した音声ファイルを選択して、文字起こし開始を実行して動作確認をします。

マシンスペックによって

使い方

使う時は次の手順で起動する。

  1. Terminalを開く
  2. venvフォルダへ移動して .\Scripts\activate
  3. streamlit run .\web.py でアプリを起動
    • streamlitの初起動時には、email入力を求められますが、無視してEnter Keyで進めます。
    • Python実行をWindowsセキュリティが確認してくるかもしれません。Allowで進めます。

Python仮想環境、WebアプリはPCを再起動すると停止した状態になります。毎回起動する必要がある為、起動用batファイルを作っておくと楽です。

一般的なビジネス向けのPC(Intel Core i5、メモリ16GB)で、モデルをBaseにした場合、2分程度の会議データで110秒程度で文字起こしが出来ました。