【音声から文字おこし】HuggingFaceのオープンソースを用いて実装

# 概要
- 音声ファイルからHugging Faceにて「Pyannote.audio」のモデルを使用
- 文字おこしを行うpythonプログラムを作成

## 実行環境
- Rocky Linux9
- Python3.9以降

## Hugging Faceのトークンを取得
- 下記のブログを参考に取得


https://zenn.dev/protoout/articles/73-hugging-face-setup

## FFmpegのインストール
- FFmpegとは
  - オープンソースで開発されているマルチメディアフレームワーク
  - 動画や音声の記録、変換、再生、ストリーミングなどの機能を提供するソフトウェア

### Rocky Linux9でのFFmpegのインストール手順
- 1：ターミナルを開く
- 2：以下のコマンドを実行してFFmpegをインストール
```sh
# EPELリポジトリとPowerTools（CRB）を有効
dnf install epel-release
dnf config-manager --set-enabled crb
# RPM Fusionリポジトリを追加
dnf install --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm -y
dnf install --nogpgcheck https://mirrors.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-$(rpm -E %rhel).noarch.rpm -y
# FFmpegをインストール
dnf install ffmpeg ffmpeg-devel
```

- 3： インストールが完了したら、以下のコマンドでFFmpegのバージョンを確認

```sh
ffmpeg -version
```

## Pythonライブラリのインストール
```sh
pip install pyannote.audio
pip install pydub
pip install openai-whisper
pip install ffmpeg
```

## Pythonコード
```python
import whisper
from pyannote.audio import Pipeline
from pydub import AudioSegment
from pyannote.core import Segment
from pyannote.audio import Audio
## HugingFaceのAPIトークンを設定
hf_token =[HugingFaceのAPIトークンを入力]
# モデルの読み込み(確認用)
model = whisper.load_model('small') 
result = model.transcribe('./meigen01.m4a')
print(result['text'])
# 音声のモデリング準備
pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization", use_auth_token=hf_token)
# 音声ファイルの変換(必要)
audio = AudioSegment.from_file("./meigen01.m4a", format="m4a")
# wavファイルとして保存
audio.export("meigen01.wav", format="wav")
# file_pathを指定 (wav形式)
audio_file="meigen01.wav"
# ダイアライゼーション実行
diarization = pipeline(audio_file,num_speakers=2)
# 音声の読み込み (PyAnnote用)
audio = Audio(sample_rate=16000, mono=True)
# 音声ファイルの正確な長さを取得（フレーム数 → 秒）
waveform_all, sample_rate = audio(audio_file)
duration_exact = waveform_all.shape[1] / sample_rate
# モデルの読み込み
model = whisper.load_model('small')
# 結果を保存するファイル名
output_path = "voice_to_text.txt"
MAX_TOLERANCE = 0.3
# 書き込み用にファイルを開く
with open(output_path, "w", encoding="utf-8") as f:
  for segment, _, speaker in diarization.itertracks(yield_label=True):
    start = segment.start
    end = segment.end
    # 長さ超過時の処理
    overshoot = end - duration_exact
    if overshoot > 0:
      if overshoot <= MAX_TOLERANCE:
        end = duration_exact
      else:
        # 長さ超過が許容範囲を超える場合はスキップ
        print(f"スキップ: セグメント {end:.3f}s は音声長 {duration_exact:.3f}s を {overshoot:.3f}s 超過")
        continue
    safe_segment = Segment(start, end)
    try:
      waveform, sample_rate = audio.crop(audio_file, safe_segment)
    except Exception as e:
      print(f"cropエラー: {e}")
      continue
    # 音声認識（numpy → Whisper）
    try:
      text = model.transcribe(waveform.squeeze().numpy())["text"]
    except Exception as e:
      print(f"認識エラー: {e}")
      text = ""
    # 結果の出力
    line = f"[{safe_segment.start:03.1f}s - {safe_segment.end:03.1f}s] {speaker}: {text}"
    print(line)
    f.write(line + "\n")
```

## 出力結果(voice_to_text.txt)
```tex
[0.4s - 1.3s] SPEAKER_01: さっき戦えて
[1.9s - 5.5s] SPEAKER_01: あれどういう意味ですか?そのままズバリでせっかくなんで聞かせてもらえませんか?
[7.4s - 7.9s] SPEAKER_01: いいだろ
[8.8s - 9.5s] SPEAKER_01: 3つある
[10.1s - 11.0s] SPEAKER_01: 一つ
[11.7s - 19.0s] SPEAKER_01: 正しいことを正しいと言えることに一つ組織の常識と世間の常識が一致していることで
[19.0s - 19.2s] SPEAKER_00: 
[19.9s - 26.0s] SPEAKER_00: 一つ、北武器で誠実に働いたものがきちんと評価されること僕が
[26.0s - 31.5s] SPEAKER_01: 深いですねやたり前だよ その当たり前が今の組織はできていないだから誰かが戦
[32.4s - 33.8s] SPEAKER_01: 仕事は客のためにする
[35.0s - 36.6s] SPEAKER_01: 聞いては世の中のためにする
[37.9s - 42.8s] SPEAKER_01: その大原則を忘れた時人は自分のためだけに仕事をするようになる
[44.5s - 49.0s] SPEAKER_01: 自分のためにした仕事は内向きでひくつで
[47.5s - 49.9s] SPEAKER_00: ひくつで
[49.9s - 51.1s] SPEAKER_01: 見に行く風歪んでく
[51.8s - 54.4s] SPEAKER_01: 組織約されば世の中も薬
[55.8s - 56.3s] SPEAKER_00: 
[57.0s - 60.2s] SPEAKER_00: 最初の敵はいつも自分自身だよ
```

## 最後に
- Hugging Faceには様々なモデルが共有されております。
- 作成者のドキュメント等を読んで機械学習を進めていきましょう。
- ここまで読んで頂き、ありがとうございます。