音声認識APIを用いて動画データからLLM用の発言データセットを作る

## はじめに
2024/04/10〜2024/05/21の期間で音声認識APIの記事執筆キャンペーンが開催されています。

https://qiita.com/official-events/23333339b694ca71810a

今回は上記音声認識APIを用いてYoutubeの動画を文字起こし、LLM用の発言データセットを作ってみたいと思います。

## 前提: AmiVoice API
音声をテキストに変換する音声認識APIであるAmiVoice APIを使います。

AmiVoice APIを使えば音声対応可能なアプリケーションを作成できるのが特徴です。（今回は音声認識アプリを作る訳ではありませんが。）

また今回詳細は触れませんが、複数音声からの話者推定機能や感情分析機能などがあります。


https://docs.amivoice.com/amivoice-api/manual/

## 動画データの準備

今回は先輩である「北サブ」さんに、データの提供を全面的にご協力いただいています。
北サブさんの直近の動画はドライブメインです。

https://www.youtube.com/channel/UC_eYRLr0QG49y6P72_0jK4g/videos


取得したデータは`.mkv`形式のデータで、約23分のドライブ動画です。

https://www.youtube.com/watch?v=L_hVJdka74k


## 動画データを音声データへ変換
`.mkv`データを音声データ(`.wav`)に変換します。

`moviepy`ライブラリの`AudioFileClip`を使うと簡単に実装できます。

```bash 
% pip install moviepy
```

```python 
from moviepy.editor import AudioFileClip
import glob 

def convert_mkv_to_wav(input_path, output_path):
    """
    input_path: 入力用のmkvファイルパス
    output_path: 出力用のwavファイルパス
    """
    video = AudioFileClip(input_path)
    video.write_audiofile(output_path)
```

今回は実装が最も簡単なAmiVoice APIの同期 HTTP インタフェースを使います。
その制約として音声ファイルのサイズを16MB 以下に抑えなければいけないので、音声データを16 MB以下に区切ります。

今回の音声データは60秒当たり約11 MB程度だったので、60秒区切りで音声データを分割し、連番で保存します。


```python 
from moviepy.editor import AudioFileClip
from pydub import AudioSegment
import glob 

def convert_mkv_to_wav(input_path, output_path):
    """
    input_path: 入力用のmkvファイルパス
    output_path: 出力用のwavファイルパス
    """
    video = AudioFileClip(input_path)
    video.write_audiofile(output_path)


def split_wav_by_duration(input_path, output_prefix, duration_ms):
    audio = AudioSegment.from_wav(input_path)
    for i, chunk in enumerate(audio[::duration_ms]):
        chunk.export(f"{output_prefix}_{i}.wav", format="wav") #連番で保存


if __name__ == "__main__":
    input_file_list = glob.glob("*.mkv") #動画が複数ある場合に対応
    for input_path in input_file_list:
        wav_path = input_path.replace(".mkv", ".wav")
        convert_mkv_to_wav(input_path, wav_path) #mkv->wavに変換

        output_prefix = wav_path.replace(".wav", "")
        duration_ms = 60_000  # 60_000ミリ秒=60秒
        split_wav_by_duration(wav_path, output_prefix, duration_ms)
```

## 音声データから文字起こし
ここから本題のAmiVoice APIを使って文字起こしをしてみます。

AmiVoiceを使用するまでの流れ&クーポン適用方法は以下のサイトを参考にしてください。

https://acp.amivoice.com/blog/qiita_api240424/

音声入力_汎用のエンジンである`-a-general-input`を使います。

また今回音声データを連番で保存しているので、プログラムから呼び出す時は連番の順番でファイルを呼び出す必要があります。

`glob`、`natsort`ライブラリを使うと以下のように分割した音声データを連番で取得できます。

```bash 
% pip install natsort
```

```python 
import glob
from natsort import natsorted

for file_path in natsorted(glob.glob("*.wav")):
    print(file_path)
```

複数音声データを音声認識APIで文字起こしするためのプログラムは以下です。
また時間計測のプログラムも付け加えています。

:::note warn 
簡単のためエラーハンドリングは書いていません。
音声ファイルが多い場合は都度ファイルを出力しておくと良いでしょう。
:::


```python 
import requests
import glob
from natsort import natsorted
import time

app_key = "your_API_KEY"

url = "https://acp-api.amivoice.com/v1/recognize"
params = {"d": "-a-general-input", "u": app_key}

output_text = ""
output_file_path = "response.txt"

start_time = time.time()

for file_path in natsorted(glob.glob("*.wav")):
    print(file_path)
    with open(file_path, "rb") as f:
        files = {"a": f}
        response = requests.post(url, data=params, files=files)

    response_json = response.json()
    japanese_text = response_json["text"]
    output_text += japanese_text

end_time = time.time()
elapsed_time = end_time - start_time
elapsed_minutes = int(elapsed_time // 60)
elapsed_seconds = elapsed_time % 60
print((f"Elapsed time: {elapsed_minutes} minutes and {elapsed_seconds:.2f} seconds\n"))

with open(output_file_path, "w", encoding="utf-8") as output_file:
    output_file.write(output_text + "\n")

print(f"Response saved to {output_file_path}")
```

上記で文字起こしができるはずです。
ちなみに自分の場合は、約23分の音声データを6分15秒で文字起こしできました。

:::note warn 
公式サイトによれば、音声データの時間の0.5倍から1.5倍の時間が必要だそうです。

> 音声認識にかかる時間は、送信した音声の0.5〜1.5倍程度です。

https://docs.amivoice.com/amivoice-api/manual/

:::

文字起こしの品質は良好で、体感7,8割くらい正しいです。一方でクリーンで高品質なデータセットを作るには、何らかの後処理が必要かもしれません。

AmiVoice APIでは句読点を自動的に挿入してくれます。一方で、今回入力した音声データでは句点の挿入が失敗しているケースもありました。

> 具体例:
文字起こし:「~リベンジしようと思ってますご飯はキャンセルという~」

ただ上記のような例は一部ルールベースで解決できそうです。（今回の記事では実施しません。）

## LLM用の発言データセットの作成
最後にデータセットを加工して、Hugging Faceにプッシュしていきます。

まずライブラリのインストールとHugging Faceへのログインをします。

```bash 
% pip install huggingface_hub datasets
% huggingface-cli login
```

次に文字起こししたデータを「。」で区切り、空白を削除します。
そしてHugging Faceのデータセットの形式に合わせてプッシュします。

:::note 
AmiVoice APIで句点をある程度自動挿入できるのがポイント
:::

```python 
from datasets import Dataset, DatasetDict

file_path = "response.txt"
with open(file_path, "r", encoding="utf-8") as f:
    text = f.read()

sentences = text.split("。")
sentences = [sentence for sentence in sentences if sentence.strip()]

dataset = Dataset.from_dict({"text": sentences})
dataset_dict = DatasetDict({"train": dataset})

dataset_dict.push_to_hub("your_dataset_name")
```

上記のプログラムでプッシュしたデータセットはパブリックである点、注意してください。


## おわりに
いかがだったでしょうか。

今後は、AmiVoice APIと他音声認識サービスの比較等を実施する&AmiVoice APIの他機能を使ってみたいと思います。