ミツカリ技術ブログ

株式会社ミツカリの開発チームのブログです

OpenAIのWhisperを用いてSlackのhuddleの議事録をリアルタイムで取る試み

こんにちは。ミツカリのたなしゅんです。 ミツカリはフルリモート環境のため、Slack上でのテキストコミュニケーションが基本です。 もちろん、さっと集まって話した方が早く済むこともありますし、リアルタイムでのディスカッションでしか得られない経験もあるので、通話コミュニケーションが行われないわけではありません。 しかし、通話によるコミュニケーションはテキストとは異なり、形に残りにくかったり、仮に録画を残していたとしても検索しにくかったり、見返す前提に立ったときにはテキストコミュニケーションに劣る部分があります。

通話のコミュニケーションをテキストとして残せれば色々解決できそうです。 もちろんこれを実現できるSaaSは既に世の中に溢れています。 それらを契約するのももちろんひとつの解決法ですが、そこはエンジニアですから、自らコーディングできるならしてみたいですよね。

というわけで本題です。 OpenAIが出しているOSSの音声データの文字起こしAI「Whisper」を使ってリアルタイムでの文字起こしアプリを作ってみようと思います!

成果物

以下で公開していますのでご覧ください。 WhisperはPythonライブラリとして公開されているので実装もPythonで行いました。

github.com

方針

whisperでできるのは音声データを文字起こしすることです。そのためにはまずはリアルタイムにスピーカーから流れる音とマイクに入力される音をレコーディングして音声データにする必要があります。そして、作成した音声データをwhisperに渡して文字起こしさせ、結果をテキストファイルに書き出していく、といったサイクルを回し続ける形になりそうです。

まとめると、

  1. マイクとスピーカーに流れる音声を音声データの一時ファイルとして保存
  2. 作られた音声データをwhisperに文字起こしさせる
  3. 文字起こしデータをテキストファイルに書き込む

となります。

実装

音声データの書き出し

音声データをファイル化していく部分の詳細な設計は以下になります。

  1. PC端末のオーディオデバイスからデバイスを特定
  2. そのオーディオデバイスに流れる音をデジタル数値に取り出す
  3. 音声ファイルとして保存する

実装は以下のようになりました。

import sounddevice as sd
import scipy.io.wavfile as wav
import numpy
import tempfile

# 音声データを操作するクラス
class Audio:
    def __init__(self, device_name, duration, sample_rate, mic_channel, speaker_channel):
        self.duration = duration
        self.sample_rate = sample_rate
        self.mic_channel = mic_channel
        self.speaker_channel = speaker_channel
        
        # デバイス名からデバイスIDを取得
        self.device_id = None
        devices = sd.query_devices()
        for id, device in enumerate(devices):
            if device_name in device['name']:
                self.device_id = id
                break
        if self.device_id is None:
            raise ValueError(f"デバイス '{device_name}' が見つかりませんでした。処理を中止します。利用可能なデバイス: {[d['name'] for d in devices]}")

    # マイク入力とスピーカー出力を合成した音声ファイル(.wav)を作成
    def create_wav_file(self):
        # 音声データを取得
        audio_data = self.__rec()
        
        # マイク入力とスピーカー出力を選択
        mic_audio = audio_data[:, self.mic_channel]
        speaker_audio = audio_data[:, self.speaker_channel]
        
        # 2つのチャンネルを結合
        combined_audio = numpy.column_stack((mic_audio, speaker_audio))
        
        # float32からint16に変換
        combined_audio_int16 = (combined_audio * 32767).astype(numpy.int16)
        
        with tempfile.NamedTemporaryFile(suffix=".wav", delete=False, dir="./output") as temp_audio_file:
            wav.write(temp_audio_file.name, self.sample_rate, combined_audio_int16)
            return temp_audio_file.name
        
    # 音声の入力デバイスを設定
    def __setup_device(self):
        sd.default.device = (self.device_id, None)
        
    # 音声の録音
    def __rec(self):
        self.__setup_device()

        # 入力音声を録音
        flames = int(self.duration * self.sample_rate)
        audio_data = sd.rec(flames, samplerate=self.sample_rate, channels=3, dtype='float32')
        sd.wait()
        return audio_data

whisper文字起こし

続いてwhisperで音声ファイルを文字起こしする部分です。

それだけであれば実はこの1行でできてしまいます。

text = mlx_whisper.transcribe("音声ファイル名", path_or_hf_repo="モデル名")["text"]

なんて簡単。これをリアルタイムで行うようにスレッドを作り、録音と音声認識の開始や停止、一時停止などのインターフェースを実装すると以下のようになりました。

import datetime
import mlx_whisper
import os
import threading
import queue

import modules.audio as audio
import modules.logger as logger

class Whisper:
    def __init__(self, logger: logger.Logger, audio: audio.Audio, model):
        self.logger = logger
        self.stop_event = None
        self.thread = None
        self.audio_queue = queue.Queue()
        self.audio = audio
        self.model = model
        self.finished = False
        self.__set_output_file()

    # リアルタイム音声認識を開始する
    def start(self):
        if self.finished:
            self.__set_output_file()
            self.finished = False
        self.__start_main_thread()
        self.logger.write("リアルタイム音声認識を開始しました")
        self.logger.write(f"結果は {self.output_file} に出力されます")

    # リアルタイム音声認識を停止する
    def stop(self):
        self.logger.write("リアルタイム音声認識を停止します。しばらくお待ち下さい。")
        self.stop_event.set()
        self.thread.join()
        # 録音されたものがまだ残っている場合は処理する
        while not self.audio_queue.empty():
            self.__put_text_transcription()
        self.logger.write("リアルタイム音声認識を停止しました")
        self.logger.write(f"結果は {self.output_file} に出力されています")

        self.finished = True

    # リアルタイム音声認識を一時停止する
    def pause(self):
        self.logger.write("リアルタイム音声認識を一時停止します。")
        self.stop_event.set()
        self.thread.join()
        self.logger.write("リアルタイム音声認識を一時停止しました")

    # サンプル音声ファイルを文字起こしする
    def sample(self):
        self.logger.write("これはテストです。正しく音声認識ができているかを確認しています。")
        self.logger.write("という合成音声の文字起こしを行います。このあとに文字起こしの結果が表示されます。")
        return self.__transcribe_audio(
            audio_file="sample.mp3",
            should_delete=False
        )
    
    # 起動中かどうかを返す
    def is_running(self):
        return self.thread is not None and self.thread.is_alive()
    
    # スレッドを立て直す
    def __start_main_thread(self):
        self.stop_event = threading.Event()
        self.thread = threading.Thread(target=self.__realtime_transcription)
        self.thread.start()

    # 出力ファイル名を設定する
    def __set_output_file(self):
        start_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
        self.output_file = os.path.join("./output", f'{start_time}.txt')

    # キューから音声ファイルを取り出して文字起こしし、ファイルに書き込む
    def __put_text_transcription(self):
        # キューから音声ファイルを取得し、Whisperで文字起こし
        audio_file = self.audio_queue.get(timeout=1)
        text = self.__transcribe_audio(audio_file)

        self.logger.write(text)

        # テキストをファイルに改行区切りで書き込み
        with open(self.output_file, 'a', encoding='utf-8') as f:
            f.write(text + '\n')

    # 音声を録音してファイルを次々と作成し、キューに追加していく
    def __capture_audio(self):
        while not self.stop_event.is_set():
            # durationごとに音声ファイルを作成し、キューに追加
            wav_file = self.audio.create_wav_file()
            self.audio_queue.put(wav_file)

    # キューから音声ファイルを取得し、Whisperで文字起こし
    def __process_audio(self):
        while not self.stop_event.is_set():
            try:
                self.__put_text_transcription()
            except queue.Empty:
                # キューが空の場合は何もしないで待つ
                continue

    # 音声ファイルをリアルタイムで文字起こしするためのスレッドを起動する
    def __realtime_transcription(self):
        capture_thread = threading.Thread(target=self.__capture_audio)
        process_thread = threading.Thread(target=self.__process_audio)

        capture_thread.start()
        process_thread.start()

        capture_thread.join()
        process_thread.join()

    # 音声ファイルをWhisperで文字起こしする
    def __transcribe_audio(self, audio_file, should_delete=True):
        text = mlx_whisper.transcribe(audio_file, path_or_hf_repo=self.model)["text"]

        # 読み取りが終わったら音声ファイルを削除
        if should_delete and os.path.exists(audio_file):
            os.remove(audio_file)
        
        return text

GUI(おまけ)

コマンドだけで操作するのも悪くはないのですが、とっつきづらくなってしまうので、GUIも用意します。 PythonではTkinterという標準ライブラリがあるのですが、私のローカルではボタン以外の要素が表示されず、pyenvでpythonをinstallしているとTkinterが壊れるなどの情報を見ましたが、環境に依存するものは使いにくいのでやめました。 私はWebエンジニアですので、スタイリングのやりやすさも考慮してpywebviewというライブラリを用いてGUIを作ることにしました。

pywebviewはhtmlをwebviewで描画し、pythonコード側とのやり取りをAPIサーバを構えることなく実装することができます。

まず、webviewとして描画されるhtmlファイルを用意します。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Webviewサンプル</title>
</head>
<body>
    <h1>Hello World</h1>
</body>

htmlをpython側からpywebviewで呼び出します。

webview.create_window(
    title="Webviewサンプル",
    url="index.html",
    js_api=ApiClass # APIとしてコールされるクラスのインスタンスを渡す
)
webview.start()

これで実行時にGUIが起動するようになりました。

続いてGUI側からpython処理を呼び出す部分です。以下のようなjavascriptで呼び出すことができます。

async function start() {
    const res = await pywebview.api.start();
    // startの部分はApiClassに定義したPublicメソッド名
}

簡単ですね。エンドポイントやスキーマ等何も考える必要がありません。

やり取りはこれだけなので、あとはどんなイベントが必要かを考えて実装します。成果物としては以下のようになりました。

GUIスクショ

コマンド操作に慣れないジュニアエンジニアでも十分使えそうな形に仕上がりました!

実装は以上です。whisperの仕様など調べるのに時間はかかりましたが、実際自体はサクッとできました。

whisperモデルの比較

AIはモデルが命です。whisperはあくまでフレームワークなので、そこにどんな命を吹き込むかで結果が大きく変わってきます。

whisperで使えるモデルの一覧は以下です。

huggingface.co

使ってみたモデルの感想

いくつかのモデルを使用してみたので文字起こしにかかった時間や体感からレビューをしてみます。

なお、パフォーマンスの計測は以下のようなコードを用いました。

class Whisper:
    def transcribe_audio(self, audio_file):
        start_time = datetime.datetime.now()
        print(f"Transcribing... {audio_file}")
        text = mlx_whisper.transcribe(audio_file, path_or_hf_repo="mlx-community/whisper-turbo")["text"]
        end_time = datetime.datetime.now()
        print(f"Transcribed {audio_file} in {end_time - start_time} seconds")

上記の計測コードにリアルタイムのdurationに設定しているのと同じ10秒程度の短い音声と、4分13秒という長い音声をそれぞれ文字起こしさせてみました。

ちなみにそれぞれの音声の中身は、

  • 短い音声:アメンボ赤いなあいうえお。浮き藻に小エビも泳いでる。柿の木栗の木かきくけこ。キツツキこつこつ枯れけやき。
  • 長い音声:外郎売全文

を使用しました。

whisper-turbo

  • 短い音声:2.040930ミリ秒
  • 長い音声:24.054214ミリ秒

短い音声の文字起こし結果

アメンボ赤いなあゆえお 浮きもに小エビも泳いでる柿のキクリのキカキクケコ キツツキコツコツ枯れ毛焼き

長い音声の文字起こし結果

拙者親方と申すは、お立ち会いのうちにご存知のお方もござりましょうが、お江戸を立って二十里上方、宗宗小田原意識町をお過ぎなされて、青物町を上りへおいでなさるれば、蘭関橋虎や東江門、ただいま提発いたして、遠祭と名乗りまする。眼帳より大都合森までお手に入れまするこの薬は、昔、陳の国の当人、ウイローという人を我が町へ来たり、帝三大の檻からこの薬を深く込め置き、持ち得る時は一流ずつ、冠の隙間より取り出す。よってその名を帝より、東陳公と賜る。すなわち文字には、いただき、すく、においと書いて、東陳公と申す。ただいまはこの薬、ことのほか世上に広まり、方々に偽看板を言い出し、いや小田原の、灰田原の、三田原の、墨田原のと、いろいろに申せども、ひらがなを持ってウイローとしるせしは、親方遠祭ばかり。もしやお立ち会いのうちに、熱海か東の沢へ、陶寺においでなさるるか、または伊勢三宮の檻からは、必ず角違いなされまするな。お登りならば右の方、奥田になれば左側。八方が八棟、表が三棟、玉堂作り。ハフには菊に霧の塔の御門を、御社面あって、敬ず正しき薬でござる。いや最善より、亀への自慢ばかり申しても、ご存じない方には、正真の故障の丸のみ、白川与船、さらば一流食べかけて、その気味合いを、お目にかけましょう。まずこの薬を、火曜に一粒舌の上に乗せまして、服内へ納めますると、いやどうも言えぬわ。いっしん、はい、缶が健やかになって、君風のんどより来たり、甲虫微量を生ずるが如し。魚腸、キノコ、麺類の食い合わせその他、万病速攻あること神の如し。さてこの薬、第一の奇妙には、舌の回ることが、ゼニゴマが裸足で逃げる。ひょっと舌が回り出すと、矢も盾もたまらぬじゃん。そりゃそりゃそりゃそりゃ、町、会合爽やかに、赤サタナハマエラは、おこそとのほもよろぼ。ひとつヘギヘギに、ヘギホシはじかみ、ボンマメボンゴメボンゴボウ、積み立て積み豆、積み三将。諸者さんの謝荘上、小米の生神、小米の生神、こん小米の小生神、しゅすひじゅすしゅすしゅちん。親もかへい、子もかへい、親かへい、子かへい、子かへい、親かへい、古くりの木の古きりくし、みのらにゅらい、にむのらにゅらい、ちょっと先の小仏に、おけつまずけるな、細溝に土壌におろい、今日の生だらなら、生まながつを、ちょっとしごかんめ、おちゃたちょ、ちゃたちょ、ちゃっとたちょ、ちゃたちょ、青竹茶せんで、おちゃちゃっとたちゃ、来るは来るは、何が来る、荒野の山のおこけら小僧、たぬき百匹、八百銭、天目百杯、棒八百本、ブグバグ、ブグバグ、みブグバグ、あわせてブグバグ、あわせて、あわせて、何かにも当時の羅生門には茨城童子が腕ぐりゴンゴを掴んでおむしゃるかの雷光の膝元さらずふな、きんかん、しいたけ、さらめて五段なそば生地そうめん、うどんか五段なこしんぼちこだなのこしたのこうけにこみそがこわるぞ、こじゃくしこもってこすくってこよこせおっとがってんだ心得たんぼの川崎、かながわ、ほどがやとすかわ、走っていけばやいとすりむく三里ばかりか藤沢、平塚、おぼいそがしや、こいそのしくを七つおきして、蒼天早々、蒼州、小田原、東陳公隠れござらぬ寄仙軍樹の花のお江戸の花ういろ、あれあの花を見てお心をおやわらぎゃといううぶごはうこに至るまでこのういろのご評判、ご存じないとは申されまいまいつぶり角だせ棒だせ棒棒まゆに、薄きねすり鉢バチバチバチグアラグアラグアラと羽目を外して今日おいでのいずれまさまに、あげねばならぬ、うらねばならぬと生き生引っ張り東方世界の薬のもとじめ、薬師如来も将来あれと頬をうやまって、ういろはいらっしゃりませぬかご視聴ありがとうございました

文字起こしの精度はまぁ人間が補完できなくもなさそうなレベルです。 速度はターボなだけあってさすがに速いですね。

whisper-large-v3-turbo

  • 短い音声:2.012718秒
  • 長い音声:24.097919秒

短い音声の文字起こし結果

アメンボ赤いなあゆえお 浮きもに小エビも泳いでる柿のキクリのキカキクケコ キツツキコツコツ枯れ毛焼き

長い音声の文字起こし結果

拙者親方と申すは、お立ち会いのうちにご存知のお方もござりましょうが、お江戸を立って二十里上方、宗宗小田原意識町をお過ぎなされて、青物町を上りへおいでなさるれば、蘭関橋虎や東江門、ただいま提発いたして、遠祭と名乗りまする。眼帳より大都合森までお手に入れまするこの薬は、昔、陳の国の当人、ウイローという人を我が町へ来たり、帝三大の檻からこの薬を深く込め置き、持ち得る時は一流ずつ、冠の隙間より取り出す。よってその名を帝より、東陳公と賜る。すなわち文字には、いただき、すく、においと書いて、東陳公と申す。ただいまはこの薬、ことのほか世上に広まり、方々に偽看板を言い出し、いや小田原の、灰田原の、三田原の、墨田原のと、いろいろに申せども、ひらがなを持ってウイローとしるせしは、親方遠祭ばかり。もしやお立ち会いのうちに、熱海か東の沢へ、陶寺においでなさるるか、または伊勢三宮の檻からは、必ず角違いなされまするな。お登りならば右の方、奥田になれば左側。八方が八棟、表が三棟、玉堂作り。ハフには菊に霧の塔の御門を、御社面あって、敬ず正しき薬でござる。いや最善より、亀への自慢ばかり申しても、ご存じない方には、正真の故障の丸のみ、白川与船、さらば一流食べかけて、その気味合いを、お目にかけましょう。まずこの薬を、火曜に一粒舌の上に乗せまして、服内へ納めますると、いやどうも言えぬわ。いっしん、はい、缶が健やかになって、君風のんどより来たり、甲虫微量を生ずるが如し。魚腸、キノコ、麺類の食い合わせその他、万病速攻あること神の如し。さてこの薬、第一の奇妙には、舌の回ることが、ゼニゴマが裸足で逃げる。ひょっと舌が回り出すと、矢も盾もたまらぬじゃん。そりゃそりゃそりゃそりゃ、町、会合爽やかに、赤サタナハマエラは、おこそとのほもよろぼ。ひとつヘギヘギに、ヘギホシはじかみ、ボンマメボンゴメボンゴボウ、積み立て積み豆、積み三将。諸者さんの謝荘上、小米の生神、小米の生神、こん小米の小生神、しゅすひじゅすしゅすしゅちん。親もかへい、子もかへい、親かへい、子かへい、子かへい、親かへい、古くりの木の古きりくし、みのらにゅらい、にむのらにゅらい、ちょっと先の小仏に、おけつまずけるな、細溝に土壌におろい、今日の生だらなら、生まながつを、ちょっとしごかんめ、おちゃたちょ、ちゃたちょ、ちゃっとたちょ、ちゃたちょ、青竹茶せんで、おちゃちゃっとたちゃ、来るは来るは、何が来る、荒野の山のおこけら小僧、たぬき百匹、八百銭、天目百杯、棒八百本、ブグバグ、ブグバグ、みブグバグ、あわせてブグバグ、んんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんんん何かにも当時の羅生門には茨城童子が腕ぐりゴンゴを掴んでおむしゃるかの雷光の膝元さらずふな、きんかん、しいたけ、さらめて五段なそば生地そうめん、うどんか五段なこしんぼちこだなのこしたのこうけにこみそがこわるぞ、こじゃくしこもってこすくってこよこせおっとがってんだ心得たんぼの川崎、かながわ、ほどがやとすかわ、走っていけばやいとすりむく三里ばかりか藤沢、平塚、おぼいそがしや、こいそのしくを七つおきして、蒼天早々、蒼州、小田原、東陳公隠れござらぬ寄仙軍樹の花のお江戸の花ういろ、あれあの花を見てお心をおやわらぎゃといううぶごはうこに至るまでこのういろのご評判、ご存じないとは申されまいまいつぶり角だせ棒だせ棒棒まゆに、薄きねすり鉢バチバチバチグアラグアラグアラと羽目を外して今日おいでのいずれまさまに、あげねばならぬ、うらねばならぬと生き生引っ張り東方世界の薬のもとじめ、薬師如来も将来あれと頬をうやまって、ういろはいらっしゃりませぬかご視聴ありがとうございました

意外にも実行時間は無印ターボとほぼ変わりませんでした。 文字起こしの精度もどっちもどっちな感じがしますね。 これしか違いがないなら無印ターボで十分な気がしました。

whisper-small-mlx

  • 短い音声:5.735576秒
  • 長い音声:11.970536秒

短い音声の文字起こし結果

アメンボ赤いなあゆえを 浮きもに小海美も泳いでるかきのきくりのきかきくけこ きつつきこつこつかれけやき

長い音声の文字起こし結果

接者親方とモースはお立ち合いのうちにご存知のお方もござりましょうが親戸を立って二十売神型草州おだわらい敷町をおすぎなされて青物町を上りへおいでなされれば乱鑑橋トラや東絵門ただいま定発いたして延載と名乗りまする岩町より大都合盛までお手に入れまするこの薬は昔陣の国の当人ウイルオという人は我が町へ来たり見課堂へ三大の織からこの薬を深く込めおき持ちうる時は一流ずつ寛無理の隙間より取り出すよってその名を見課堂より東進港とたまあるすなわち文字にはいただきすく匂いと書いて東進港とモースただいまはこの薬ことのほか世上に広まり頬帽に二世間版をいだしいやおだわらの廃だわらの三大の住みだわらのといろいろにモースでもひらがなをもってウイルオと知るせしは親方延載ばかりもしやお立ち合いのうちにアタミカ東の沢へ当時においでなさるるかまたは伊勢三宮のおりからは必ず過度違いなされまするなおのぼにならば右の方奥谷になれば左側発泡がやつむね表がみつむね玉藤作り歯符にはきくに霧の塔の五門を御斜めになって経図正しく薬でござるいや最前より加名の自慢ばかり申してもご存じない方には小心の故障のおまるのみ白川よふねさらば一流食べかけてその気味合いをお目にかけましょうまずこの薬を火用に一粒下の上に乗せまして服内を収めまするといやどうも言えぬわ一審はい缶がスクヨカになって君風のんどよりきたり高中微量を消するがごとし魚丁キノコ麺類の食い合せそのほか満秒速攻あること神のごとしさてこの薬第一の奇妙には下の回ることが全にごまが肌子で逃げるひょっと下が回り出すと山立てもたまらぬじゃんそりゃそりゃそりゃ回ってきたわ回ってくるわ泡野のんどサタナナ下に影差しよん浜の二つは口ビルの形長介護さわやかに赤沙田の浜枝はおこそとの褒いろも一つヘギヘギにヘギホッシャジカミボンマメボンゴメボンゴボウつみたでつみまめつみさんしよう初者さんの車操乗小米の生紙小米の生紙こん小米の粉紙シュスヒジスシュスシュチン親もかへい小もかへい親かへい小かへい小かへい親かへいふるくりの木のふるきりくし甘がっぱがばんがっぱが貴様の気販もかわげ販我らが気販もかわげ販しっかわばかまのしっぽころびを見張り張りながにちょっと縫うて縫うてちょっと分だせかわらなでしこのせきちくのらにるらいのらにるらいみのらにるらいにむのらにるらいちょっと先のおこぼときにおけつまずけるな細みぞに土壌におろいきょうの生ならなら生まながつをちょっとしごかんめおちゃたちょちゃたちょちゃったちょちゃたちょあおだけ茶線でおちゃちゃったちゃくるはくるはなにがくるこうやの山のおこけらこぞたぬきはっぴきはしやくぜんてんぼくやっぱいぼうはっぴっぴっぽんぼぐばぐぼぐばぐみぼぐばぐあわせてぼぐばぐむぼぐばぐきくくりきくくりみきくくりあわせてきくくりむきくくりむぎごみむぎごみみむぎごみあわせてむぎごみむむぎごみあのなげしのながなぎなたはたがなげしのながなぎなたぞむこうのごまがらはえのごまがらかまごまがらかあれこそほんのまごまがらがらぴーがらぴーかさぐるまおきゃがれこぼしおきゃがれこぼうしゆんべもこぼしてまたこぼしたたぽぽたぽぽぽちりからちりからつたっぽたっぽたっぽいっちょうだくおうちたらにてくおうにてもやいてもくわれぬものはごとくてっきゅうかなぐまどうじにしぐまいしもちとらぐまとらきすなかにもとうじのらしょうもんにはいばらきどうじがうでぐりごんごをつかんでおむしゃるかのらいこうのひざもとさらずふなきんかんしいたけさらめてごだんなそばきりそうめんうどんかぐどんなこしんぼちこだなのこしたのこうけにこみそがこわるぞこすくってこよこせおっとがってんだこころへたんぼのかわさきかながわほどがやとすかわはしってゆけばやいとすりむくさんりばかりかふじさわひらつかおおいすがしやこいそのしくをななつおきしてそうてんそうそうそうしゅうおだわらとうちんこうかくれござらぬきせんぐんじゅうのはなのおえどのはなういろあれあのはなをみておこころおやわらげあと言ううぶごはうこにいたるまでこのういろのごひょうばんごぞんじないとはもうされまいまいつぶにつのだせぼうだせぼうぼうまよにうすきねすりばちばちばちぐわらぐわらぐわらとはめをはずしてこんにちおいでのいずれまさまにあげねばならぬうらねばならぬといきせいひっぱいとうほうせかいのくすりのもとじめやくしにょらいもしょうらんがれとほほうをうやまってういろはいらっしゃりませぬか

実行時間はとてもターボよりもさらに速い結果となりましたが、精度は酷いですね。日本語を単語としてほぼ理解できていないです。

whisper-large-v3-mlx

  • 短い音声:31.571063秒
  • 長い音声:152.404089秒

短い音声の文字起こし結果

アメンボアカイナーゆえお 浮きもに小海老も泳いでる柿の木くりの木かきくけこ キツツキコツコツ枯れ毛焼き

長い音声の文字起こし結果

拙者親方と申すは、お立ち会いのうちにご存知のお方もござりましょうが、お江戸をたって二十里上方、宗宗小田原石木町をお過ぎなされて、青物町を上りへおいでなさるれば、乱寒橋虎谷東江門、ただいま提発いたして、遠西と名乗りまする。岩町より大都合もりまでお手に入れまするこの薬は、昔、陳の国の東人、ウイロウという人を我が町へ来たり、三角へ三代の折からこの薬を深く込め置き、持ち得る時は一流ずつ、冠の隙間より取り出す。よってその名を味方より東陳公と賜る。すなわち文字には、いただき、すく、においと書いて東陳公と申す。ただいまはこの薬、ことのほか世常に広まり、方々に偽看板をいだし、いや小田原の、灰田原の、三田原の、墨田原のといろいろに申せども、平仮名をもってウイロウとしるせしは、親方遠才ばかり。もしやお立ち会いのうちに、熱海か遠野沢へ当時においでなさるるか、または伊勢三宮の折からは、必ず門違いなされまするな。お上りならば右の方、奥田になれば左側、八方が八つ棟、表が三つ棟、玉堂づくり、破風には菊に霧の塔の御門を御舎面あって、敬図正しき薬でござる。いや最善より、仮名の自慢ばかり申しても、ご存じない方には、正真の故障の丸のみ白川余船、さらば一流食べかけて、その気味合いをお目にかけましょう。まずこの薬を火曜に一粒舌の上に乗せまして、腹内へ納めますると、いやどうも言えぬは、一心、肺、肝が健やかになって、くん風のんどより来たり、口中微量を生ずるが如し、魚腸、きのこ、麺類の食い合わせその他、万病速攻あること神の如し。さてこの薬、第一の奇妙には、舌の回ることが銭ごまが裸足で逃げる。ひょっと舌が回りだすと矢も盾もたまらぬじゃん。そりゃそりゃそらそりゃ回ってきたわ回ってくるわ、あわやのんど、さたらな舌に影さしよん、浜の二つは唇の慶長、かいごをさわやかに、赤さたな浜やらはおこそとのほもよろぼ。ひとつへぎへぎに、へぎほしはじかみ、ぼん豆ぼんごめぼんごぼう、つみたてつみ豆つみさんしょう、しょしゃざんのしゃそうじょう、こごめの生髪、こごめの生髪、こんこごめのこ生髪、しゅすひじゅすしゅすしゅちん、おやもかへいこもかへい、おやかへいこかへい、こかへいおやかへい、ふるくりのきのふるきりくし、あまがっぱがばんがっぱが、きさまのけはんもかわげはん、われらがけはんもかわげはん、しっかわばかものしっぽころびを、みはりはりながに、ちょとぬうて、ぬうてちょとぶんだせ、かわらなでしこのせきちく、のらにゅらい、のらにゅらい、みのらにゅらいに、むのらにゅらい、ちょっとさきのおこぼときに、おけつまずけるな、ほそみぞにどじょうにょろい、きょうのなまだらなら、なままながつを、ちょとしごかんめ、おちゃたちょちゃたちょ、ちゃっとたちょちゃたちょ、あおだけちゃせんで、おちゃちゃっとたちゃ、くるはくるは、なにがくる、こうやのやまの、おこけらこぞう、たぬきやっぴき、はしやくぜん、てんもきやっぱい、ぼうはっぴょっぽん、ぶぐばぐぶぐばぐ、みぶぐばぐ、あわせてぶぐばぐ、むぶぐばぐ、きくくりきくくり、みきくくり、あわせてきくくり、むきくくり、むぎごみむぎごみ、みむぎごみ、あわせてむぎごみ、むむぎごみあの投げしの長投げなたはたが投げしの長投げなたぞ向こうのごまがらはえのごまがらかまごまがらかあれこそほんのまごまがらがらぴーがらぴーかさぐるまおきゃがれこぼしおきゃがれこぼしゆんべもこぼしてまたこぼしたたっぽぽたっぽぽちりからちりからつったっぽたっぽたっぽいっちょうだこおちたらにてくおにてもやいてもくわれぬものはごとくてっきゅうかなぐまどうじにいしぐまいしもちとらぐまとらきすなかにもとうじのらしょうもんにはいばらきどうじがうでぐりごんごをつかんでおむしゃるかのらいこうのひざもとさらずふなきんかんしいたけさらめてごだんなそばけじそうめんうどんかぐどんなこしんぼちこだなのこしたのこうけにこみそがこわるぞこじゃくしこもってこすくってこよこせおっとがってんだこころえたんぼのかわさきかながわほどがやとすかははしってゆけばいやいとすりむくさんりばかりかふじさわひらつかおおいそがしやこいそのしくをななつおきしてそうてんそうそうそうしゅうおだわらおやわらぎゃといううぶごはうこにいたるまでこのういろうのごひょうばんごぞんじないとはもうされまいまいつぶりつのだせぼうだせぼうぼうまゆにうすきねすりばちばちばちぐわらぐわらぐわらとはめをはずしてこんにちおいでのいずれまさまにあげねばならぬうらねばならぬといきせいひっぱりとうほうせかいのくすりのもとじめやくしにょらいもしょうらんあれとほほうをうやまってういろうはいらっしゃりませぬかごきげんよう

遅い、遅すぎる。しかし精度は最高ですね。やっぱりトレードオフなんですね。 リアルタイムに利用するのは無理ですね。10秒の文字起こしに30秒かかってますから、どんどん遅延していってしまいます。 録音したものを後から文字起こしにかけるならこれがベストでしょう。

計測結果まとめ

モデル 短い音声 長い音声
whisper-turbo 2.040930sec 24.054214sec
whisper-large-v3-turbo 2.012718sec 24.097919sec
whisper-small-mlx 5.735576sec 11.970536sec
whisper-large-v3-mlx 31.571063sec 152.404089sec

まとめ

流行りに乗ってみた感が強いですがいかがだったでしょうか。 話者の特定ができなかったり、無言を謎の英文にしてしまったり、まだまだ発展の余地は残っていると感じました。 このあたりの課題も別のアプローチで解決できるかもしれないので引き続き遊んでみようと思います。 また進展があればブログにしようと思います!


現在、ミツカリではITエンジニアを募集しています。プライベートでアプリ作っちゃう技術好きな方はぜひ! 興味のある方はぜひお気軽にご連絡ください!

herp.careers