vosk を使ってローカルの音声ファイルを文字起こししたテキストファイルを作るスクリプト

# 概要

vosk を使って音声ファイルを文字起こししたテキストファイルを作るスクリプトを作成した。

備忘のため記事を残す。下記リポジトリの README と同等程度の情報です。

## リポジトリ

https://github.com/junjanjon/use-vosk

# 音声文字起こしツール

音声ファイル(.wav)に対して、文字起こしした結果をテキストファイル(.txt)に出力する。

## 使い方

### 動作確認

- Python 3.12.6
- macOS 14.4

### モデルをダウンロードする

https://alphacephei.com/vosk/models からモデルをダウンロードする。

vosk-model-ja-0.22 をダウンロードし、このディレクトリに展開している。ほかのモデルを使う場合は、`-m` オプションで指定する。

### ライブラリのインストール

```bash
pip install -r requirements.txt
```

### コマンド実行

```bash
usage: main.py [-h] -i INPUT_FILENME -o OUTPUT_FILENAME [-m MODEL_PATH]

音声ファイル(.wav)に対して、文字起こしした結果をテキストファイル(.txt)に出力する

options:
  -h, --help            show this help message and exit
  -i INPUT_FILENME, --input_filenme INPUT_FILENME
                        入力ファイルのパス
  -o OUTPUT_FILENAME, --output_filename OUTPUT_FILENAME
                        出力ファイルのパス
  -m MODEL_PATH, --model_path MODEL_PATH
                        モデルファイルのパス
```


# スクリプト

リポジトリ内の main.py

```python
import vosk
import wave
import time
import json
from logging import getLogger, INFO, StreamHandler, Formatter

logger = getLogger(__name__)
logger.setLevel(INFO)
formatter = Formatter("[%(asctime)s] [%(levelname)s] %(message)s")
handler = StreamHandler()
handler.setFormatter(formatter)
handler.setLevel(INFO)
logger.addHandler(handler)

# 引数の音声ファイル(.wav)に対して、文字起こしした結果をテキストファイル(.txt)に出力する

# モデルファイルのパス
MODEL_PATH = "vosk-model-ja-0.22"
# 1回の音声認識に使うフレーム数
FRAME_SPAN = 4000
# 進捗を表示する間隔
NOTIFY_TIME_INTERVAL = 10

def main(input_filename, output_filename, model_path):
    # モデルファイルのパス
    model = vosk.Model(model_path)
    logger.info(f"Loaded Model: {model_path}")

    # 音声ファイルを開く
    wf = wave.open(input_filename, "rb")
    logger.info(f"Open: {input_filename}")
    all_frames = wf.getnframes()

    # 開始時間を記録
    start_time = time.time()
    # 前回の通知時間を記録
    last_notify_time = start_time
    read_frames = 0

    # 音声認識器を作成
    rec = vosk.KaldiRecognizer(model, wf.getframerate())

    # 音声認識の実行
    results = []
    while True:
        data = wf.readframes(FRAME_SPAN)
        read_frames += FRAME_SPAN
        if len(data) == 0:
            break
        if rec.AcceptWaveform(data):
            result = rec.Result()
            json_dict = json.loads(result)
            text = json_dict["text"]
            if text != "":
                results.append(text)
                logger.debug(f"Result: {text}")
        # 進捗を表示
        last_notify_time = print_progress(read_frames, all_frames, start_time, last_notify_time)

    # 結果をテキストファイルに出力
    with open(output_filename, "w") as f:
        for result in results:
            f.write(result + "\n")
    logger.info(f"Output to {output_filename}")

def print_progress(read_frames, all_frames, start_time, last_notify_time):
    if read_frames > all_frames:
        return last_notify_time
    if time.time() - last_notify_time < NOTIFY_TIME_INTERVAL:
        return last_notify_time
    current_time = time.time()
    elapsed_time = current_time - start_time
    progress = (read_frames / all_frames) * 100
    logger.info(f"[{elapsed_time:.1f}s] {progress:.2f}%")
    return current_time


if __name__ == "__main__":
    # 引数を取得する
    import argparse
    parser = argparse.ArgumentParser(
        description="音声ファイル(.wav)に対して、文字起こしした結果をテキストファイル(.txt)に出力する"
    )
    parser.add_argument("-i", "--input_filenme", help="入力ファイルのパス", required=True)
    parser.add_argument("-o", "--output_filename", help="出力ファイルのパス", required=True)
    parser.add_argument("-m", "--model_path", help="モデルファイルのパス", default=MODEL_PATH)
    args = parser.parse_args()
    main(args.input_filenme, args.output_filename, args.model_path)
```

## スクリプト解説

### vosk の利用について

[vosk-api](https://github.com/alphacep/vosk-api/) に各言語のサンプルコードが記載されている。 

Python版: https://github.com/alphacep/vosk-api/tree/master/python

### 引数の扱い argparse

標準ライブラリの argparse を利用した。

https://docs.python.org/3/library/argparse.html

## 実行時の pyenv について

実行時は pyenv を利用している。

### pyenv の環境準備・利用

```bash
# 実行ディレクトリに venv ディレクトリが用意される
python -m venv venv
# venv の環境を利用する
source ./venv/bin/activate

...

pip install vosk Wave
pip freeze > requirements.txt
...
pip install -r requirements.txt

...

# venv の環境だと `deactivete` コマンドが実行できる
# `deactivete` コマンドを実行すると環境を抜けられる
deactivete
```