見出し画像

[Python]音声ファイルから無音除去、話者分離、文字起こし(WhisperAPI/Pydub/Pyannote)

はじめに

先日のWhisperAPIの続きです!

本記事では以下のコードを解説しています。

  • 音声ファイルから無音部分を除去

  • 話者ごとに音声を分離

  • 発話ごとに音声をテキストに変換

話者分離の精度はまあまあといったところですが、ChatGPTに投げて要約させる際に若干精度が上がる感じがするのと、なにより「文字起こし」らしい雰囲気が出るので好きです。

コード解説

必要なライブラリのインポート

まず、必要なライブラリをインポートします。これらのライブラリは、音声処理や話者分離、音声テキスト変換などのタスクを実行するために必要です。

from pydub import AudioSegment
from pydub.silence import split_on_silence
from pyannote.audio import Pipeline, Audio
import tempfile
import openai
import soundfile as sf

OpenAI APIキーの設定

次に、OpenAI APIキーを設定します。このAPIキーは、WhisperAPIを利用して音声をテキストに変換する際に必要です。


openai.api_key = "あなたのOpenAIAPIキー"

無音部分を削除する関数

ここでは、無音部分を削除するための関数remove_silenceを定義します。


def remove_silence(input_path, output_path): 
    # 音声ファイルを読み込み
    sound = AudioSegment.from_file(input_path)

    # 元の音声の長さを計算し、分単位で表示
    org_ms = len(sound)
    print('original: {:.2f} [min]'.format(org_ms/60/1000))

    # 無音部分を検出し、音声を分割
    chunks = split_on_silence(sound, min_silence_len=100, silence_thresh=-55, keep_silence=100)

    # 無音部分を除去した新しい音声を作成
    no_silence_audio = AudioSegment.empty()
    for chunk in chunks:
        no_silence_audio += chunk

    # 無音部分を除去した音声を出力
    no_silence_audio.export(output_path, format="mp3")
    org_ms = len(no_silence_audio)
    print('removed: {:.2f} [min]'.format(org_ms/60/1000))

この関数では、まず音声ファイルを読み込み、無音部分を検出して分割します。そして、無音部分を除去した新しい音声を作成し、指定された出力パスに保存します。

入力音声ファイルの指定と無音部分の除去

次に、入力音声ファイルのパスを指定し、無音部分を除去します。 

python source_file = 'path'
# 無音部分を除去した音声を保存するための一時ファイルを作成 
no_silence_audio_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") 
# 無音部分を除去 
remove_silence(source_file, no_silence_audio_file.name)

この部分では、source_fileに入力音声ファイルのパスを指定し、remove_silence関数を使用して無音部分を除去した音声を一時ファイルに保存しています。

無音部分を除去すると、ファイルにもよりますが、ざっくり3~5割くらいは時間が短くなります。その後のWhisperAPIは時間単位なので、わりと節約になります。

話者分離の実行

ここでは、話者分離用のモデルを読み込み、実行します。

pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization")
diarization = pipeline(no_silence_audio_file.name, num_speakers=2)

Pipeline.from_pretrained()関数を使って、pyannoteの話者分離モデルを読み込みます。そして、無音部分を除去した音声ファイルを入力として、話者分離を実行します。

num_speakerは話者があらかじめわかっているときに指定すると、精度が上がるとのこと。

話者ごとの音声を切り出し、テキストに変換

最後に、話者ごとに音声を切り出し、テキストに変換します。

audio = Audio(sample_rate=16000, mono=True)

for segment, _, speaker in diarization.itertracks(yield_label=True):
    # 音声ファイルから話者のセグメントを切り出す
    waveform, sample_rate = audio.crop(no_silence_audio_file.name, segment)

    # 切り出した音声を一時ファイルに保存
    with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as cropped_audio_file:
        sf.write(cropped_audio_file.name, waveform.squeeze().numpy(), sample_rate)

        # 一時ファイルから音声データを
        with open(cropped_audio_file.name, "rb") as f:

            # OpenAIのWhisperAPIを使って音声をテキストに変換
            response = openai.Audio.transcribe(
                "whisper-1",
                f,
                prompt="",
                language="ja"
                )
            # 変換結果を文字列に変換
            transcription = str(response["text"])

        # 話者と変換されたテキストを表示
        print(f"[{segment.start:03.1f}s - {segment.end:03.1f}s] {speaker}: {transcription}")

この部分では、話者分離の結果を使って、話者ごとの音声セグメントを切り出します。切り出した音声を一時ファイルに保存した後、OpenAIのWhisperAPIを使って音声をテキストに変換します。最後に、話者と変換されたテキストを表示します。

すべて

from pydub import AudioSegment
from pydub.silence import split_on_silence
from pyannote.audio import Pipeline, Audio
import tempfile
import openai
import soundfile as sf

# OpenAI APIキーを設定
openai.api_key = "あなたのOpenAIAPIキー"

# 無音部分を削除する関数
def remove_silence(input_path, output_path): 
    # 音声ファイルを読み込み
    sound = AudioSegment.from_file(input_path)

    # 元の音声の長さを計算し、分単位で表示
    org_ms = len(sound)
    print('original: {:.2f} [min]'.format(org_ms/60/1000))

    # 無音部分を検出し、音声を分割
    chunks = split_on_silence(sound, min_silence_len=100, silence_thresh=-55, keep_silence=100)

    # 無音部分を除去した新しい音声を作成
    no_silence_audio = AudioSegment.empty()
    for chunk in chunks:
        no_silence_audio += chunk

    # 無音部分を除去した音声を出力
    no_silence_audio.export(output_path, format="mp3") 
    org_ms = len(no_silence_audio)
    print('removed: {:.2f} [min]'.format(org_ms/60/1000))

# 入力音声ファイルのパス
source_file = 'path'

# 無音部分を除去した音声を保存するための一時ファイルを作成
no_silence_audio_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3")
# 無音部分を除去
remove_silence(source_file, no_silence_audio_file.name)

# 話者分離用のモデルを読み込み、実行
pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization")
diarization = pipeline(no_silence_audio_file.name, num_speakers=2)

# 音声ファイルの設定
audio = Audio(sample_rate=16000, mono=True)

for segment, _, speaker in diarization.itertracks(yield_label=True):
    # 音声ファイルから話者のセグメントを切り出す
    waveform, sample_rate = audio.crop(no_silence_audio_file.name, segment)

    # 切り出した音声を一時ファイルに保存
    with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as cropped_audio_file:
        sf.write(cropped_audio_file.name, waveform.squeeze().numpy(), sample_rate)
        # 一時ファイルから音声データを読み込む
        with open(cropped_audio_file.name, "rb") as f:
            # OpenAIのWhisperAPIを使って音声をテキストに変換
            response = openai.Audio.transcribe(
                "whisper-1",
                f,
                prompt="",
                language="ja"
            )
        # 変換結果を文字列に変換
        transcription = str(response["text"])

    # 話者と変換されたテキストを表示
    print(f"[{segment.start:03.1f}s - {segment.end:03.1f}s] {speaker}: {transcription}")

アウトプット

[0.5s - 3.7s] SPEAKER_00: あっ終わっちゃいました。
[0.7s - 6.2s] SPEAKER_01: 問題ないと思います
[6.2s - 9.2s] SPEAKER_00: また今度話しましょう
[9.0s - 13.2s] SPEAKER_01: 田中さん次何かありますか?
[14.4s - 17.5s] SPEAKER_00: 次1時間後なんで

こんな感じで話者分離されます。ちなみに話者分離部分はCPUでも動きますが、かなり時間がかかりますのでご認識おきください。

おわりに

  • 音声ファイルから無音部分を除去して

  • 話者ごとに音声を分離して

  • テキストに変換する

というPythonコードの解説は以上です。ちなみに前回にあったサイズ制限は分割でAPIに投げる関係で、ほとんどの場合、制限にかからないと思い、削除してます。

Whisperの精度は高いですが、話者分離はぼちぼちなので参考程度に使ってください。


この記事が気に入ったらサポートをしてみませんか?