COEIROINK(v2)のAPIを使ってテキストファイルを音声化する
はじめに
COEIROINK を使うと、簡単にテキストを自分の好きなキャラクターに読み上げてもらうことができます。これを使って音声作品的なものを作ってみたいと思いました。
ある程度長さのある作品を作る場合、画面上で一行一行入力するより、テキストファイルで一括で読み込ませた方が効率的です。
標準機能でもテキストファイルからの読み込みはできますが、見出しやコメントをつけてシナリオを整理したり、間を取らせる(無音の時間を入れる)といったことはできないです。
そこで、COEIROINKのバックエンドで動いているAPIを利用して、テキストファイルから合成音声を作成するツールを作ってみようと思います。
使用したCOEIROINKのバージョン
本記事のコードは、本記事の作成時点で最新のバージョンであるv.2.5.0で動作確認しています
(v1→v2でAPIの仕様が変わっているので、v1系では動作しないです)。
同じようにCOEIROINK(v2)のAPIを利用してみたい方の参考になれば幸いです。
COEIROINKのAPIについて
COEIROINKの音声合成を行う部分はWEB APIになっており、COEIROINKの画面からでなくとも、そのAPIを叩くことで音声合成などの機能を利用することができます。
COEIROINKを起動すると、http://127.0.0.1:50032
でAPIサーバーが起動した状態になります。http://127.0.0.1:50032/docs
にブラウザでアクセスすれば、利用可能なAPIについてのドキュメントを参照することもできます。
APIドキュメントを表示した様子
以降、コードを実行するときは、事前にCOEIROINKを起動しておいてください。
APIを叩いてみる
簡単なサンプルで文字列を音声化してみましょう。
以下のスクリプトは、変数 text
に設定された文字列を、COEIROINKのAPIを使用して音声化し、音声ファイル audio.wav
に保存します。
声はCOEIROINKにデフォルトで入っている、「つくよみちゃん」の「れいせい」です。声の変更手順は後で説明します。
作成されたaudio.wav
を開き、設定した文字列をしゃべってくれていればOKです。
import os
import json
import requests
# しゃべらせたい文字列
text = "テスト"
# リクエストボディ
query = {
"speakerUuid": "3c37646f-3881-5374-2a83-149267990abc",
"styleId": 0,
"text": text,
"speedScale": 1.0,
"volumeScale": 1.0,
"prosodyDetail": [],
"pitchScale": 0.0,
"intonationScale": 1.0,
"prePhonemeLength": 0.1,
"postPhonemeLength": 0.5,
"outputSamplingRate": 24000,
}
# 音声合成を実行
response = requests.post(
"http://127.0.0.1:50032/v1/synthesis",
headers={"Content-Type": "application/json"},
data=json.dumps(query),
)
response.raise_for_status()
# 結果をwavファイルに保存![](https://storage.googleapis.com/zenn-user-upload/3e0478bf3544-20240625.png)
with open("audio.wav", "wb") as f_temp:
f_temp.write(response.content)
必要なものの準備
FFMPEG関連のインストール
FFMPEG という、コマンドラインで音声ファイルや動画ファイルをいじることができるツールを使います。
ダウンロードページから自分の環境用のバイナリを落としてきて、パスを通します。
また、ffmpegのPytonラッパーであるffmpeg-pythonをインストールしておきます。
pip install ffmpeg-python
無音データの用意
音声に無音時間を入れられるように、1秒間の無音データを用意します。
わたしは Audacity を使って作成しました。
作成済みのファイルを以下からダウンロードできます。
音声素材などを入れる場所として assets
ディレクトリを作成し、ダウンロードしたファイルは
assets/silent.wav
に配置してください。
テキストファイルの仕様
音声ファイルの作成元となるテキストファイルは、以下のような仕様にしました。
- ファイル名は
scenario.txt
-
#
から始まる行は見出しとして、音声化しない -
//
から始まる行はコメントとして、音声化しない -
<<silent>>
という文字列のみの行があった場合、1秒間の無音区間を挿入する - それ以外の行は通常の音声化対象の行として、音声化する
# 見出し
これは、テスト音声です。
// これはコメント
// 三秒間、沈黙する
<<silent>>
<<silent>>
<<silent>>
おわり
ツール本体
ツール本体のコードは以下の通りです。
メイン処理でテキストファイルを一行ずつ読み込んで処理し、関数 synthesis
で文字列の音声化、関数 append_audio
で一個の音声ファイルに結合を行っています。
import os
import json
import requests
import ffmpeg
API_SERVER = "http://127.0.0.1:50032"
INPUT_FILE = "scenario.txt"
OUTPUT_FILE = "audio.wav"
SPEAKER_UUID = "3c37646f-3881-5374-2a83-149267990abc"
STYLE_ID = 0
def synthesis(text: str):
"""
文字列を音声化する
"""
query = {
"speakerUuid": SPEAKER_UUID,
"styleId": STYLE_ID,
"text": text,
"speedScale": 1.0,
"volumeScale": 1.0,
"prosodyDetail": [],
"pitchScale": 0.0,
"intonationScale": 1.0,
"prePhonemeLength": 0.1,
"postPhonemeLength": 0.5,
"outputSamplingRate": 24000,
}
# 音声合成を実行
response = requests.post(
f"{API_SERVER}/v1/synthesis",
headers={"Content-Type": "application/json"},
data=json.dumps(query),
)
response.raise_for_status()
return response.content
def append_audio(audio1: str, audio2: str):
"""
audio1の後ろにaudio2を結合する
"""
old_file = "old.wav"
os.rename(audio1, old_file)
(
ffmpeg.concat(ffmpeg.input(old_file), ffmpeg.input(audio2), v=0, a=1)
.output(audio1)
.run()
)
os.remove(old_file)
if __name__ == "__main__":
with open(INPUT_FILE, "r", encoding="utf-8") as f:
count = 0
for line in f:
line = line.strip()
# 見出しやコメントをスキップ
if line.startswith("#") or line.startswith("//") or line == "":
continue
# 無音区間の挿入
if line == "<<silent>>":
append_audio(OUTPUT_FILE, "assets/silent.wav")
continue
# テキストを音声化
audio = synthesis(line)
if count == 0:
with open(OUTPUT_FILE, "wb") as f_temp:
f_temp.write(audio)
else:
temp_file = "temp.wav"
with open(temp_file, "wb") as f_temp:
f_temp.write(audio)
append_audio(OUTPUT_FILE, temp_file)
os.remove(temp_file)
count += 1
使い方
python generate_audio.py
で音声ファイル audio.wav
を出力します。
キャラクター・スタイルの変更方法
音声合成に使用する声を変更するには、定数 SPEAKER_UUID
と STYLE_ID
を使用したいキャラクターおよびスタイルに合わせて調整します。
以下のスクリプトは、ダウンロード済みのキャラクターとスタイルの情報をCOEIROINKのAPIから取得し、speakers.json
に保存します。
import json
import requests
API_SERVER = "http://127.0.0.1:50032"
def save_speakers():
"""
利用可能なSpeaker情報をファイル出力する
"""
speakers = []
response = requests.get(
f"{API_SERVER}/v1/speakers",
)
for item in json.loads(response.content):
speaker = {
"speakerName": item["speakerName"],
"speakerUuid": item["speakerUuid"],
"styles": [
{"styleName": style["styleName"], "styleId": style["styleId"]}
for style in item["styles"]
],
"version": item["version"],
}
speakers.append(speaker)
# 出力ファイルに保存
with open("speakers.json", "w", encoding="utf-8") as f:
json.dump(speakers, f, ensure_ascii=False, indent=4)
save_speakers()
COEIROINKのキャラクターダウンロード画面で好きなキャラクターとスタイルをダウンロードしてから、以下のように実行してください。
python save_speakers.py
以下のようなJSONファイルが出力されます。
[
{
"speakerName": "KANA",
"speakerUuid": "297a5b91-f88a-6951-5841-f1e648b2e594",
"styles": [
{
"styleName": "のーまる",
"styleId": 30
},
{
"styleName": "ないしょばなし",
"styleId": 33
}
],
"version": "2.0.0"
},
{
"speakerName": "つくよみちゃん",
"speakerUuid": "3c37646f-3881-5374-2a83-149267990abc",
"styles": [
{
"styleName": "れいせい",
"styleId": 0
}
],
"version": "2.0.0"
}
]
使用したいキャラクターの speakerUuid
を SPEAKER_UUID
に、styleId
を STYLE_ID
に設定すればOKです。
おわりに
簡単にですが、COEIROINK(v2)のAPIの使用例を紹介しました。
APIを利用した作業効率化に役立てば幸いです。
Discussion