nikkie-ftnextの日記

イベントレポートや読書メモを発信

声をPythonに聴かせて(新編:簡潔な、実装で、がんばるぞ、おー✊)

はじめに

映画『メタモルフォーゼの縁側めっちゃよかった…、みんな観て🙏、nikkieです。

今回は、私の熱い『アイの歌声を聴かせて』ファン活動(アイカツ!)の様子をお届けします1
前後編とアウトプットした「声をPythonに聴かせて」の続編(新編)です2

※この投稿はアイうた円盤発売・配信開始アドカレ(後述)への投稿ではありません(本日18日目の投稿はひしさんです!)。
ですが、20日目以降のアドカレへの投稿と、後日扱いを変えるかもしれません。

目次

これまでの「声をPythonに聴かせて」

アイの歌声を聴かせて』のシオン v0.0.1実装プロジェクトの中で、「音声を認識してテキストに変換する」(=音声認識)機能を実装しました。
スタンドアローンで動かすために機械学習モデルを導入したところ、マイクから入力した音声の書き起こしがイマイチという事象が発生。
前回の後編でこれを解決しました。

解決法はWorkaround(一時的な解決方法)だったんですが、このたび余計な処理を削ぎ落としてシュッとした実装にできたのでアウトプットします。

動作環境については前編を参照ください。

TL; DR (シュッとした実装)

from io import BytesIO

import numpy as np
import soundfile as sf
import speech_recognition as sr
from espnet2.bin.asr_inference import Speech2Text

speech2text = Speech2Text.from_pretrained(
    "kan-bayashi/csj_asr_train_asr_transformer_raw_char_sp_valid.acc.ave"
)

r = sr.Recognizer()
with sr.Microphone(sample_rate=16_000) as source:
    print("なにか話してください")
    audio_data = r.listen(source)

# -- リファクタリングした箇所 --
wav_bytes = audio_data.get_wav_data()  # (1)
wav_stream = BytesIO(wav_bytes)  # (2)
audio_array, sampling_rate = sf.read(wav_stream)  # (3)
# -- リファクタリングした箇所 終わり --

nbests = speech2text(audio_array)
text, tokens, *_ = nbests[0]
print(text)
  • ライブラリSpeechRecognitionAudioDataに音声をwav形式のbytesに変換するメソッドを発見:(1)
  • インメモリストリームを介して、wav形式のbytesをsoundfile.read:(2) & (3)

インメモリストリームで一時ファイルが不要と気づく

from scipy.io import wavfile

# リファクタリングした箇所をまず以下のように変えました
frame_bytes = audio_data.get_raw_data()
speech_array = np.frombuffer(frame_bytes, dtype=np.int16)
speech_stream = BytesIO()  # (4)
wavfile.write(speech_stream, audio_data.sample_rate, speech_array)
audio_array, sampling_rate = sf.read(speech_stream)

ファイルの読み書きは遅いと認識しています(ディスクアクセスのため)。
(PyCon APAC 2022の発表準備3の中で)ふと「インメモリストリームでいけそうじゃん!」と気付きました。
scipy.io.wavfile.writesf.readどちらもインメモリストリームを扱えることを確認したうえで、書き換えを試します。

  • scipy.io.wavfile.writeの第1引数には文字列だけでなく、open file handleも渡せます4(file-like objectと理解しました)
  • sf.readの第1引数も文字列だけでなく、file-like objectも渡せます5

上記コードは動作し、脱tempfileできました!🙌

AudioDataからwav形式のbytesが作れることに気づく

WorkaroundはライブラリSpeechRecognitionAudioDataget_raw_dataメソッドで音声をbytesに変えるのですが、「もしかして形式を指定できる?」とリファレンスを覗いてみました。
そこで見つかったのがget_wav_dataメソッド!

https://github.com/Uberi/speech_recognition/blob/3.8.1/reference/library-reference.rst#audiodata_instanceget_wav_dataconvert_rate-unionint-none--none-convert_width-unionint-none--none---bytes

Returns a byte string representing the contents of a WAV file containing the audio represented by the AudioData instance.

このメソッドでwav形式のbytesが取得できたので、「これをインメモリストリームにして6sf.readで読み込めば…」と実装したところ、動作しました!7
get_wav_dataメソッドとインメモリストリームによりscipy.io.wavfileも不要になり、スッキリしましたね。

PyCon JP 2020 「インメモリー ストリーム活用術」

https://pycon.jp/2020/timetable/?id=203893

これはリアルタイムで聴講して記憶に残っていたのですが、(おそらく発表準備のプレッシャーによる火事場の馬鹿力で)シオン v0.0.1の音声認識のworkaroundカイゼンに結びつきました。

今回特に参考になったのは「PNGをBytesIOで取り扱う例」です。

終わりに

シオン v0.0.1の音声認識機能(スタンドアローン対応)は、workaroundを脱して動作する最小限の実装となりました!🎉
KISS原則(Keep it Short and Simple)8にもかないます!Spark joy!!
締切の力は偉大ですね。

それではこの辺で発表準備に戻りますー ノシ

P.S. アイうた円盤発売・配信開始アドベントカレンダー賑わってます!

7/27のBlu-ray & DVD 発売、そしてレンタル配信 & デジタルセル配信 開始をカウントダウンする企画です。

Blu-ray&DVD|映画『アイの歌声を聴かせて』公式サイト

オンデマンド|映画『アイの歌声を聴かせて』公式サイト

直近は

というラインナップです👏
企画したnikkieの出番がこんなに回ってこない(=たくさんの参加者で賑わう)というのは全く想定していませんでした。
アドカレに関してご参加やいいね・RTで盛り上げていただき、誠にありがとうございます!!

アドベントカレンダーは、どなたのご参加も大歓迎です!


  1. 映画(10th STORY)いいらしいので、観に行きたいなー

  2. 前後新、まどマギリスペクトです

  3. 聞いて聞いて! #アイの歌声を聴かせて のシオン v0.0.1実装プロジェクトを海外のカンファレンスで発表します - nikkie-ftnextの日記

  4. scipy.io.wavfile.write — SciPy v1.8.1 Manual

  5. https://pysoundfile.readthedocs.io/en/latest/#soundfile.read

  6. (2)のコードにありますが、bytesを渡してBytesIOインスタンスを初期化できます。ref: https://docs.python.org/ja/3/library/io.html#binary-i-o

  7. get_raw_dataメソッドのときと異なり、np.frombufferに渡すと「ValueError: buffer size must be a multiple of element size」が送出されます。追っていませんが、wav形式に変換されたbytesのためかと理解しています(先頭を少し見比べました)

  8. 過去にこんなエントリも書きました。KISS原則も登場します:「あってもなくても同じなら捨てる」という片付けの考え方が(こんまりメソッドと)プログラミングに通じていました - nikkie-ftnextの日記

  9. https://twitter.com/hisibird/status/1548684588775485442