とある事情で音声を文字起こしする機会があったので、手順のメモ。OpenAI製のWhisperを使う。最後にコードを貼っています。
環境
- PC: MacBook Air Retina 13inch, 2019
- CPU: 1.6GHz Intel Core i5 デュアルコア
- メモリ: 8GB 2133MHz LPDDR3
- 言語: Python3.10
- 外部ライブラリ: Whisper、Mutagen
- 外部ソフト: ffmpeg
環境構築手順
pip3 install -U openai-whisper
sudo apt update && sudo apt install ffmpeg
pip3 install setuptools-rust
これだけで動く。オプションで言語を日本語に指定。一発目は学習済みモデルのダウンロードが走り、その後ファンの急速回転とともに推論開始(タイプスタンプと共に起こされた文字が1行ずつ標準出力)。
whisper hoge.mp3 --language Japanese
すごい、、ちょい数ヶ月前に触ったC++版には日本語モデル無かったのに。AIに仕事を奪われる感覚。
注意点
- はじめ、ネイティブ環境のpython3.7でやってみたところ、実行時にエラー。大人しく3.10へアップグレード
- 2時間強のmp3をぶっ込むと、自前PCだと落ちはしないが数時間待っても全く標準出力されなくなった→スクリプト内でffmpegを使って10分単位で動画を分割しながら推論処理をかけると、6時間くらいかけて全て文字起こしできた
所感
- 感覚的には70点くらい訳せている。喋り口調でよく発生する文節間の笑いとか咳払いとかが変に起こされておらず素晴らしい。
- 単に周波数と振幅だけでなく、明らかに文脈も読んだ上で起こされている(同音異義語の「言って」「行って」や「買って」「勝って」など)。
- 固有名詞は弱め。特に「シチュエーション」など和製英語。
- 一番謎なのが、音質は変わってないハズやのに突如「ヤンヤン」「樋口」など、意味不明な単語が連続して文中に挿入されてる時があること。人間の耳には聞き取れない音なので音声ファイル自体の問題?
- これまた謎なのが、句読点がやたら打たれるときもあれば、No区切りでひたすら起こされる時もある点。機械にも機嫌があるかのようで気味悪い。
- 当たり前やけど複数人喋ってる場合各人の同定はできない。けど半年後くらいには出来るようになってる世界
import os import sys import subprocess from mutagen.mp3 import MP3 SPLIT_MIN = 10 #[min] def cmd_split(mp3, base, begin, end): ss_hh = str(int(begin / 60)).zfill(2) ss_mm = str(int(begin % 60)).zfill(2) to_hh = str(int(end / 60)).zfill(2) to_mm = str(int(end % 60)).zfill(2) ss = str(ss_hh) + ':' + str(ss_mm) + ':00' to = str(to_hh) + ':' + str(to_mm) + ':00' dst = base + '/' + str(ss) + '.mp3' cmd = 'ffmpeg -i ' + mp3 + ' -ss ' + str(ss) + ' -to ' + str(to) cmd += ' -acodec copy -vcodec copy ' + dst subprocess.call(cmd.split()) return dst def cmd_whisper(dst): txt = os.path.splitext(dst)[0] + '.txt' cmd = 'whisper ' + dst + ' --language Japanese' print(cmd) subprocess.call(cmd.split()) return 0 def main(): mp3 = sys.argv[1] total = MP3(mp3).info.length print('total length: ' + str(round(total, 1)) + ' [min]') base = os.path.splitext(os.path.basename(mp3))[0] os.makedirs(base, exist_ok = True) num = int(total / SPLIT_MIN / 60) + 1 # 分割回数 start = 0 for idx in range(num): begin = idx * SPLIT_MIN end = begin + SPLIT_MIN dst = cmd_split(mp3, base, begin, end) ret = cmd_whisper(dst) main()