はじめに
会議やちょっとしたメモ録音のために、AmazonでQZTというブランドの超小型ボイスレコーダーを買いました。
これが約4,000円と安いのに、コンパクトで50時間連続録音もできてかなり優秀です。
ただ、一つだけ仕様で困ったことがありました。
それは「長時間の録音データが30分おきに分割して保存される」ということです。
この録音データをAIに投げて議事録や要約を作らせたいのですが、ファイルが複数あると手作業で1つずつ文字起こしにかけるのがめちゃくちゃ面倒です。
クラウドの無料サービスも制限があるし、AIに直接音声データを投げるとトークンを爆食いしてしまいます。
そこで、完全無料・無制限で、複数ファイルを一気に1つのテキストに文字起こしするマシーンを作ろうと奮闘した備忘録です。
第一の罠:iPhoneのショートカットアプリの限界
最初は、スマホだけで完結させようとしました。iPhone標準のショートカットアプリにある「オーディオを書き起こす」アクションを使えば、無料でオンデバイス文字起こしができます。
なので、複数ファイルを選択してループ処理でテキスト化し、1つのファイルに結合するというショートカットを組もうとしました。
……が、ここで泥沼にハマります。
ショートカットの「オーディオを書き起こす」アクションは、なぜか複数ファイルの入力を受け付けないという頑固な仕様があるんです。
変数の型を強制的に指定してみたり、クリップボードを使って受け渡ししようとしたり、かなり足掻きました。
しかし、そもそも500MBもある音声ファイルをiPhoneのメモリで複数回そうとすると処理が落ちまくります。
結論として、スマホで大量の音声ファイルを一気に処理するのは無理でした。
第二の罠:Mac版Whisperでテキストが空欄になるバグ
スマホを諦め、MacのターミナルでOpenAIのWhisperを直接動かすことにしました。
Homebrewとffmpeg、そしてWhisperをインストールして、いざPythonで実行!
……エラーも出ずに完了したのに、出力されたテキストファイルが空欄でした。
原因はMacのチップ特有の問題でした。
WhisperはデフォルトでFP16という計算方式を使おうとしますが、Macの環境だとこれがうまく処理できず、エラーを出さずに完全に無音として処理されるというトラップがあったんです。
これを回避するには、プログラムのオプションで fp16=False を指定してやる必要がありました。
完成した環境とPythonコード
諸々の罠を乗り越え、最終的に完成したのが以下の構成です。
特定のフォルダに音声ファイルを投げ込むと、名前順に順番にWhisperで文字起こしを実行し、ファイル名を見出しとして1つのテキストファイルにどんどん追記していく仕様です。
デフォルトの base モデルだと少し精度が物足りないうえに、無音部分で「ご視聴ありがとうございました」などと同じ言葉を延々と繰り返す、Whisper特有のバグ(ハルシネーション)が頻発しました。
そこで、AIモデルをより高精度で処理も速い turbo モデルに。さらに、前の文章に引きずられないようにする設定(condition_on_previous_text=False)や、無音判定のしきい値を調整するパラメータを追加しています。
準備
Homebrewが入っている前提で、ターミナルで以下を実行して準備します。
brew install ffmpeg
pip3 install -U openai-whisper
Pythonコード (run.py)
デスクトップに WhisperTest フォルダを作り、その中に input フォルダと、以下のコードを書いた run.py を保存します。
import os
print("--- Whisper 文字起こしシステム (高精度版) ---")
# モデルを 'base' から 'turbo' に変更(精度が大幅に向上します)
# もし重すぎる場合は 'small' や 'medium' を試してください
print("AIモデル(turbo)をロード中...")
model = whisper.load_model("turbo")
# --- 設定部分 ---
input_folder = "/Volumes/NO NAME/RECORD"
desktop = os.path.expanduser("~/Desktop")
output_dir = os.path.join(desktop, "WhisperTest")
output_file = os.path.join(output_dir, "QZT_一括文字起こし.txt")
if not os.path.exists(output_dir):
os.makedirs(output_dir)
if not os.path.exists(input_folder):
print(f"❌ エラー:フォルダが見つかりません: {input_folder}")
exit()
valid_extensions = ('.mp3', '.wav', '.m4a', '.mp4', '.mp2', '.amr', '.aac')
audio_files = [
f for f in os.listdir(input_folder)
if f.lower().endswith(valid_extensions) and not f.startswith('.')
]
audio_files.sort()
if not audio_files:
print(f"❌ エラー:音声ファイルが見つかりません。")
exit()
print(f"✅ {len(audio_files)}個のファイルを検出しました。")
with open(output_file, "w", encoding="utf-8") as f:
for i, filename in enumerate(audio_files, 1):
print(f"[{i}/{len(audio_files)}] 処理中: {filename} ...")
file_path = os.path.join(input_folder, filename)
f.write(f"【ファイル名: {filename}】\n")
try:
# 精度向上のためのパラメータ設定
result = model.transcribe(
file_path,
language="ja",
fp16=False,
# ループ対策: 前の文章に引きずられないようにする
condition_on_previous_text=False,
# 無音判定のしきい値を上げ、ハルシネーションを抑制
no_speech_threshold=0.6,
logprob_threshold=-1.0,
compression_ratio_threshold=2.4
)
text = result["text"].strip()
if text == "":
print(f" -> ⚠️音声が聞き取れませんでした")
f.write("(※音声が認識できませんでした)\n\n")
else:
print(f" -> 成功!")
f.write(text + "\n\n")
except Exception as e:
print(f" -> ❌エラー発生: {e}")
f.write(f"(※エラーが発生しました: {e})\n\n")
f.flush()
print("\n---")
print(f"全工程が完了しました!")
print(f"結果の保存先: {output_file}")
おまけ:ダブルクリックで起動する魔法のボタン化
毎回ターミナルを開いてコマンドを打ち込むのは面倒なので、.commandファイルを作って専用アプリっぽくしました。
テキストエディタで以下の3行を書き、文字起こしスタート.command という名前で同じフォルダに保存します。
#!/bin/bash
cd ~/Desktop/WhisperTest
python3 run.py
ターミナルで1回だけ実行権限を与えます。
chmod +x ~/Desktop/WhisperTest/文字起こしスタート.command
これで、音声ファイルをフォルダに入れてファイルをダブルクリックするだけで、全自動文字起こしが完了する最高の環境ができました。
おわりに
スマホのショートカットで楽をしようとした結果、かえって遠回りしてしまいましたが、結果的にPythonとWhisperを組み合わせたことで最強のローカル環境が手に入りました。
出来上がった長文テキストをGeminiなどにコピペして要点をまとめさせれば、議事録が一瞬で完成します。
録音ファイルの分割に悩んでいる方や、MacでWhisperが動かない方の参考になれば幸いです。