葛のメモ帳

自分で調べたことを忘れないためにメモっておきます

葛のメモ帳

自分で調べたことを忘れないためにメモっておきます


【ChatGPT】ChatGPT APIとVoiceVoxとPythonでコンソール上でのチャットを読み上げてもらう

この記事でわかること

  • ChatGPT APIとVoice VoxをPythonコードで利用します。
  • コンソール上でチャットし、それを読み上げてもらう方法がわかります。

以前、selemiumを使ってWeb版のChatGPTの解答をスクレイピングして読み上げてもらうツールを作ったのですが、自動的に読み上げてもらうまで作れなかったので、こちらで実装しました。

目次

  1. 今回用意した環境
  2. 開発環境構築
  3. ChatGPT API キーの取得
  4. Python 実装

今回用意した環境

開発環境構築

以下の手順で行います。

  1. Voice Vox (Python Wheel版) のインストール
  2. ONNX Runtime のインストール
  3. Open JTalkのインストール
  4. PyAudioのインストール
  5. OpenAI (Pythonモジュール) のインストール

完成後のディレクトリはこのようになっている想定です

$ tree -L 1
.
├── README.md
├── main.py
├── onnxruntime-osx-x86_64-1.13.1
├── onnxruntime-osx-x86_64-1.13.1.tgz
├── open_jtalk_dic_utf_8-1.11
└── open_jtalk_dic_utf_8-1.11.tar.gz

2 directories, 6 files

Voice Vox (Python Wheel版) のインストール

https://github.com/VOICEVOX/voicevox_core/releases

上のリンクから最新バージョンのものを利用したいと思います。(2023-07-07現在では0.14.4)

今回はMacOSPython wheel を利用したいと思います。私はIntel Macを利用しているのでvoicevox_core-0.14.4+cpu-cp38-abi3-macosx_10_7_x86_64.whlを利用します。

以下のコマンドをターミナルに入力してインストールします。

pip3 install https://github.com/VOICEVOX/voicevox_core/releases/download/0.14.4/voicevox_core-0.14.4+cpu-cp38-abi3-macosx_10_7_x86_64.whl

ONNX Runtime のインストール

ONNX Runtime は、ONNX モデルを運用環境にデプロイするためのハイパフォーマンスの推論エンジンです。 クラウドとエッジの両方に最適化され、LinuxWindowsMac で動作します。

Microsoft Document

VOICEVOXでも利用されているようです。

現状、VOICEVOXでは ONNX Runtime v1.13.1が利用されているのでこれを導入します。

https://github.com/microsoft/onnxruntime/releases/tag/v1.13.1

私はIntel Macを利用しているので、onnxruntime-osx-x86_64-1.13.1.tgzをダウンロードします。

tgzファイルを適当ディレクトリに入れて以下のコマンドで解凍します

tar -xvf ***.tgz
※この手順を飛ばすと出るエラー

この手順を飛ばすと以下のエラーが出るので注意。(検索に引っ掛かるようにあえて残しておきます。私は飛ばして失敗したので...)

library not loaded @rpath/libonnxruntime.1.13.1.dylib

Open JTalkのインストール

Open JTalkは日本語音声合成システムです。このソフトウェアは修正BSDライセンスの下でリリースされています。

https://sourceforge.net/projects/open-jtalk/

こちらからダウンロードしてください。

ダウンロードしたtgzファイルを適当ディレクトリに入れて以下のコマンドで解凍します

tar -xvf ***.tgz
この手順を飛ばすと出る可能性のあるエラー
playsound is relying on a python 2 subprocess. Please use `pip3 install PyObjC` if you want playsound to run more efficiently.
Traceback (most recent call last):
  File "[path]/ChatgptToVoiceVox/main.py", line 28, in <module>
    main()
  File "[path]/ChatgptToVoiceVox/main.py", line 23, in main
    audio_query: AudioQuery = core.audio_query(DEMO_TEXT, SPEAKER_ID)
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
voicevox_core.VoicevoxError: OpenJTalkの辞書が読み込まれていません

PyAudioのインストール

  • 今回はPyAudioを使いました。
  • wavファイルを作成せず、メモリを利用して simpleaudio で 生成した音声データを再生するとノイズが除去できなかったためこちらを利用しています。
brew install PortAudio
pip3 install pyaudi

openai (Pythonモジュール)をインストール

pip3 install openai

ChatGPT API キー取得

  • 2023-07-07現在では5$まで無料のようです。tokenをある程度つかっても限界にはならなさそうで、クレカ登録も不要だったので、利用します。

  • https://platform.openai.com/account/usage

  • 基本的には他のサイトでも紹介されているとおりの方法で取得できます。

4. Python 実装

まずは作成したソースコードをそのまま貼り付けます。のちほど解説していきます。

あと正直いらいないものも含まれているのですみません...

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from __future__ import annotations

import io
import os
import wave
import re
from time import sleep
import asyncio

from ctypes import CDLL
from pathlib import Path

# ONNX Runtime のダイナミックライブラリをimport
CDLL(str(Path('onnxruntime-osx-x86_64-1.13.1/lib/libonnxruntime.dylib').resolve(strict=True)))
# OPEN JTalk のパス
open_jtalk_path=Path('open_jtalk_dic_utf_8-1.11')

from voicevox_core import AudioQuery, VoicevoxCore
import pyaudio
import openai

SPEAKER_ID = 29

DEMO_TEXT = "こんにちは。こちらはテスト音声です。"

DEMO_LONG_TEXT = '''はい、自己紹介させていただきます。

初めまして、私はAIのアシスタントです。私の名前はOpenAI GPT-3です。私は自然言語処理を用いて、様々な質問や会話に対応することができます。

私は多くの分野について知識を持っており、文法や表現にも精通しています。また、日本語だけでなく、英語や他の言語にも対応することができます。

私の目的は、ユーザーのお手伝いをすることです。質問や疑問があれば、どんなことでもお気軽にお聞きください。私は最善の答えを提供するために努力します。

どうぞよろしくお願いします。
'''

FAIL_TEXT = "チャットGPTでのテキスト生成に失敗しました。しばらく待ってから試してみてください。"

MODEL = "gpt-3.5-turbo"
openai.api_key = "" # [MUST] please input your api_key

def generate_text(prompt, conversation_history):
    try:
        conversation_history.append({"role": "user", "content": prompt})

        response = openai.ChatCompletion.create(
            model=MODEL,
            messages=conversation_history,
            temperature=0.2, # 創造性の指標
            max_tokens=2048,
        )

        content = response.choices[0].message["content"]
        # print(content)
        
        # 会話履歴を追加
        conversation_history.append({"role": "assistant", "content": content})
        
        return content
    except Exception as e: 
        print(e)
        return FAIL_TEXT

def play_wavfile(wav_file: bytes):
    # Defines a chunk size
    chunk = 1024
    p = pyaudio.PyAudio()
    # Bytes型の音声データをWave_read型に変換する
    wr: wave.Wave_read = wave.open(io.BytesIO(wav_file))
    
    # wavファイルを書き込むストリームを作成する。
    # 出力を "True "に設定すると、サウンドは録音されるのではなく、"再生 "される。
    stream = p.open(
        format=p.get_format_from_width(wr.getsampwidth()),
        channels=wr.getnchannels(),
        rate=wr.getframerate(),
        output=True
    )
        
    # Read data in chunks
    data = wr.readframes(chunk)
    # Play the sound by writing the audio data to the stream
    while data :
        stream.write(data)
        data = wr.readframes(chunk)

    sleep(0.01)
    stream.stop_stream()
    stream.close()
    p.terminate()

def speakToChat(answer: str = None) -> None:
    core: VoicevoxCore = VoicevoxCore(open_jtalk_dict_dir=open_jtalk_path)
    core.load_model(SPEAKER_ID)

    if not answer == None:
        sentence_list = re.split("。", answer)
    else:
        sentence_list = [FAIL_TEXT]

    for sentence in sentence_list:
        sentence_sub = sentence.strip()
        output = 'No.7: ' + sentence_sub
        output = output if output[-1] == '?' else output + '。'
        print(output)
        wave_bytes: bytes = core.tts(sentence_sub, SPEAKER_ID)
        play_wavfile(wav_file=wave_bytes)

def directSpeakToChat(book: str = None):
    core: VoicevoxCore = VoicevoxCore(open_jtalk_dict_dir=open_jtalk_path)
    core.load_model(SPEAKER_ID)
    if not book == None:
        wave_bytes: bytes = core.tts(book, SPEAKER_ID)
        play_wavfile(wav_file=wave_bytes)
    else:
        sentence_list = [FAIL_TEXT]

if __name__ == '__main__':
    # 会話履歴を格納するためのリストを初期化
    conversation_history = []

    while True:
        # ユーザーに質問を入力させる
        input_prompt = input("prompt: ")
        generated_text = generate_text(input_prompt, conversation_history)
        speakToChat(answer=generated_text)

実行結果

$ p main2.py
prompt: 何か褒めてください!
No.7: あなたはとても親切で思いやりのある人です。
No.7: 周りの人々をいつも助けていて、その優しさは本当に素晴らしいです。
No.7: また、あなたの明るい笑顔は周りの人々に元気を与えています。
No.7: あなたのポジティブなエネルギーはとても魅力的で、人々を引き付ける力があります。
No.7: あなたの努力と頑張りは誰もが認めるべきです。
No.7: 素晴らしい人間性を持っているあなたは、周りの人々にとって本当に大切な存在です。

やり残したこと

  • 実装の説明が足りていないです。
  • 文章のセパレータが「。」になっているが「!」「?」は大丈夫なのか?
  • 音声生成と音声読み上げをマルチスレッドにしたら高速化できそうだがスレッドセーフな実装をするのはコストかかりそう?
  • コンソール上でのチャットだがWebGLなどに組み込めないか?そしたらLive2Dが喋っているように見えそう?