REST API(Representational State Transfer Application Programming Interface)とは、アプリケーションの機能を外部プログラムから利用できるようにするための仕組みです。HTTP通信を通じてデータの送受信を行い、音声合成のリクエストや状態確認、結果の取得などを自動化できます。
Voisona TalkのREST APIを有効にすることで、PythonやC++、JavaScriptなど、さまざまなプログラミング言語から音声合成を制御できるようになります。これにより、独自のアプリケーションやWebサービス、チャットボット、ゲームなどにVoisona Talkの合成音声を組み込むことが可能になります。
以降のチュートリアルでは、実際にVoisona TalkのREST APIを有効化し、Pythonを用いて音声合成を実行する手順を紹介します。
※REST API機能は現在ベータ版となっております。
サンプルコードの全体を以下に示します。このチュートリアルで、それぞれの内容を順に解説していきます。
import argparse
import json
import os
import sys
import time
import xml.etree.ElementTree as ET
import requests
parser = argparse.ArgumentParser()
parser.add_argument("--user", type=str, required=True, help="User name")
parser.add_argument("--password", type=str, required=True, help="API password")
parser.add_argument("--port", type=int, default=32766, help="Port number")
parser.add_argument("--output-wav", type=str, default="test.wav", help="WAV filename")
args = parser.parse_args()
auth = (args.user, args.password)
base_url = f"<http://localhost>:{args.port}/api/talk/v1/"
def get_voice_libraries():
response = requests.get(base_url + "voices", auth=auth)
response.raise_for_status()
voice_libraries = response.json()["items"]
print("利用可能なボイスライブラリの一覧は以下です。")
print(json.dumps(voice_libraries, indent=2, ensure_ascii=False))
return voice_libraries
def synthesize_text(voice_library):
payload = {
"text": "こんにちは",
"language": voice_library["languages"][0],
"voice_name": voice_library["voice_name"],
"voice_version": voice_library["voice_version"],
"force_enqueue": True,
}
response = requests.post(base_url + "speech-syntheses", auth=auth, json=payload)
response.raise_for_status()
uuid = response.json()["uuid"]
print("リクエストの送信が完了しました。")
return uuid
def check_status(uuid, synth=True, timeout=30):
start = time.time()
endpoint = "speech-syntheses" if synth else "text-analyses"
while True:
response = requests.get(base_url + endpoint + "/" + uuid, auth=auth)
response.raise_for_status()
state = response.json()["state"]
if state == "succeeded":
break
if time.time() - start > timeout:
raise TimeoutError("処理に時間がかかり過ぎています。")
time.sleep(0.1)
print("リクエストの処理が完了しました。")
return response
def delete_request(uuid):
response = requests.delete(base_url + "speech-syntheses/" + uuid, auth=auth)
response.raise_for_status()
print("リクエストの削除が完了しました。")
def synthesize_text_and_save(voice_library):
payload = {
"text": "こんにちは",
"language": voice_library["languages"][0],
"voice_name": voice_library["voice_name"],
"voice_version": voice_library["voice_version"],
"can_overwrite_file": True,
"destination": "file",
"output_file_path": os.path.abspath(args.output_wav),
}
response = requests.post(base_url + "speech-syntheses", auth=auth, json=payload)
response.raise_for_status()
uuid = response.json()["uuid"]
print("リクエストの送信が完了しました。")
return uuid
def synthesize_text_with_global_parameters(voice_library):
payload = {
"text": "こんにちは",
"language": voice_library["languages"][0],
"voice_name": voice_library["voice_name"],
"voice_version": voice_library["voice_version"],
"global_parameters": {
"alp": 0.0,
"huskiness": 0.0,
"intonation": 1.0,
"pitch": 0.0,
"speed": 2.0,
"style_weights": [],
"volume": 0.0,
},
}
response = requests.post(base_url + "speech-syntheses", auth=auth, json=payload)
response.raise_for_status()
uuid = response.json()["uuid"]
print("リクエストの送信が完了しました。")
return uuid
def analyze_text():
payload = {
"text": "こんにちは",
"language": "ja_JP",
}
response = requests.post(base_url + "text-analyses", auth=auth, json=payload)
uuid = response.json()["uuid"]
response = check_status(uuid, synth=False)
analyzed_text = response.json()["analyzed_text"]
print("テキストの解析が完了しました。")
print(analyzed_text)
return analyzed_text
def synthesize_text_with_analyzed_text(voice_library, analyzed_text):
root = ET.fromstring(analyzed_text)
word = root.find(".//word")
word.set("hl", "hllll")
word.set("pronunciation", "コンニチハ")
modified_analyzed_text = ET.tostring(root, encoding="unicode")
print(modified_analyzed_text)
payload = {
"analyzed_text": modified_analyzed_text,
"language": voice_library["languages"][0],
"voice_name": voice_library["voice_name"],
"voice_version": voice_library["voice_version"],
}
response = requests.post(base_url + "speech-syntheses", auth=auth, json=payload)
response.raise_for_status()
uuid = response.json()["uuid"]
print("リクエストの送信が完了しました。")
return uuid
try:
voice_libraries = get_voice_libraries()
if len(voice_libraries) == 0:
print("ボイスライブラリをダウンロードしてください。")
sys.exit(1)
voice_library = voice_libraries[0]
uuid = synthesize_text(voice_library)
check_status(uuid)
delete_request(uuid)
uuid = synthesize_text_and_save(voice_library)
check_status(uuid)
time.sleep(2)# 前の音声の再生の終了を待ちます。
uuid = synthesize_text_with_global_parameters(voice_library)
check_status(uuid)
time.sleep(2)# 前の音声の再生の終了を待ちます。
analyzed_text = analyze_text()
uuid = synthesize_text_with_analyzed_text(voice_library, analyzed_text)
check_status(uuid)
print("チュートリアルをエラーなく完了しました。")
except requests.exceptions.ConnectionError as e:
print("接続に失敗しました。サーバの状態と設定を見直してください。")
print(e)
except requests.exceptions.HTTPError as e:
print("HTTPエラーが発生しました。")
print(e)
except Exception as e:
print("予期しないエラーです。")
print(e)
サンプルコードの実行のためにはrequestsパッケージが必要です。
pip install requests
以下はサンプルコードの実行例です。
python sample.py --user [email protected] --password 1234
ユーザ名とパスワードはAPIサーバの設定に合わせて変更してください。
ボイスライブラリの一覧は以下のようにして取得することができます。
auth = (args.user, args.password)
base_url = f"<http://localhost>:{args.port}/api/talk/v1/"
def get_voice_libraries():
response = requests.get(base_url + "voices", auth=auth)
response.raise_for_status()
voice_libraries = response.json()["items"]
print("利用可能なボイスライブラリの一覧は以下です。")
print(json.dumps(voice_libraries, indent=2, ensure_ascii=False))
return voice_libraries
以下は出力の例です。
[
{
"display_names": [
{
"language": "ja_JP",
"name": "田中傘"
},
{
"language": "en_US",
"name": "Tanaka San"
}
],
"languages": [
"ja_JP"
],
"voice_name": "tanaka-san_ja_JP",
"voice_version": "2.0.0"
}
]
エディタ上でボイスライブラリをダウンロードしていない場合は空となります。
以下では、音声合成のリクエストをサーバに送信します。
def synthesize_text(voice_library):
payload = {
"text": "こんにちは",
"language": voice_library["languages"][0],
"voice_name": voice_library["voice_name"],
"voice_version": voice_library["voice_version"],
"force_enqueue": True,
}
response = requests.post(base_url + "speech-syntheses", auth=auth, json=payload)
response.raise_for_status()
uuid = response.json()["uuid"]
print("リクエストの送信が完了しました。")
return uuid
サンプルコードでは、取得したボイスライブラリの中から最初に検出されたものを、合成に使用するボイスライブラリとして指定しています。
音声の合成が完了すると、デフォルトのオーディオデバイスから「こんにちは」という音声が再生されます。
なお、サーバにはリクエストの上限があり、上限に達するとリクエストの処理に失敗します。リクエスト時にforce_enqueue をTrueに設定しておくと、古いリクエストから自動で削除されるようになります。
synthesize_text関数を実行することで得られたUUIDから、送信したリクエストの状態を次のようにして知ることができます。
def check_status(uuid, timeout=30):
start = time.time()
while True:
response = requests.get(base_url + "speech-syntheses/" + uuid, auth=auth)
response.raise_for_status()
state = response.json()["state"]
if state == "succeeded":
break
if time.time() - start > timeout:
raise TimeoutError("処理に時間がかかり過ぎています。")
time.sleep(0.1)
print("リクエストの処理が完了しました。")
return response
レスポンスのstateの内容が queued であればサーバがまだリクエストを処理してない状態で、succeeded ならリクエストの処理が完了していることを表します。
また、UUIDを指定してリクエストの削除を行うことができます。
def delete_request(uuid):
response = requests.delete(base_url + "speech-syntheses/" + uuid, auth=auth)
response.raise_for_status()
print("リクエストの削除が完了しました。")
合成音声を直接オーディオデバイスに送信せず、ファイルに保存することができます。なお、ファイルパスは絶対パスである必要があります。
def synthesize_text_and_save(voice_library):
payload = {
"text": "こんにちは",
"language": voice_library["languages"][0],
"voice_name": voice_library["voice_name"],
"voice_version": voice_library["voice_version"],
"can_overwrite_file": True,
"destination": "file",
"output_file_path": os.path.abspath(args.output_wav),
}
response = requests.post(base_url + "speech-syntheses", auth=auth, json=payload)
response.raise_for_status()
print("リクエストの送信が完了しました。")
音声の表現を変化させたい場合は、global_parametersを入力に含めてください。以下は合成音声の話速を2倍にする例です。
def synthesize_text_with_global_parameters(voice_library):
payload = {
"text": "こんにちは",
"language": voice_library["languages"][0],
"voice_name": voice_library["voice_name"],
"voice_version": voice_library["voice_version"],
"global_parameters": {
"alp": 0.0,
"huskiness": 0.0,
"intonation": 1.0,
"pitch": 0.0,
"speed": 2.0,
"style_weights": [],
"volume": 0.0,
},
}
response = requests.post(base_url + "speech-syntheses", auth=auth, json=payload)
response.raise_for_status()
print("リクエストの送信が完了しました。")
音声合成のリクエストの送信時に、メタ情報を付与したテキストを含めることで、発話情報を制御することができます。そのためには、まずテキスト解析のリクエストを送信し、所望のテキストの解析結果を取得します。
def analyze_text():
payload = {
"text": "こんにちは",
"language": "ja_JP",
}
response = requests.post(base_url + "text-analyses", auth=auth, json=payload)
uuid = response.json()["uuid"]
response = check_status(uuid, synth=False)
analyzed_text = response.json()["analyzed_text"]
print("テキストの解析が完了しました。")
print(analyzed_text)
return analyzed_text
以下が「こんにちは」を解析した結果になります。
<tsml><acoustic_phrase><word chain="0" hl="lhhhh" original="こんにちは" phoneme="k,o|N|n,i|ch,i|w,a" pos="感動詞" pronunciation="コ
ンニチワ">こんにちは</word></acoustic_phrase></tsml>
この解析結果を修正してサーバに渡すことで、アクセントや発音を変えることができます。以下はXMLパーサを用いた修正の例です。
def synthesize_text_with_analyzed_text(voice_library, analyzed_text):
root = ET.fromstring(analyzed_text)
word = root.find(".//word")
word.set("hl", "hllll")
word.set("pronunciation", "コンニチハ")
modified_analyzed_text = ET.tostring(root, encoding="unicode")
print(modified_analyzed_text)
payload = {
"analyzed_text": modified_analyzed_text,
"language": voice_library["languages"][0],
"voice_name": voice_library["voice_name"],
"voice_version": voice_library["voice_version"],
}
response = requests.post(base_url + "speech-syntheses", auth=auth, json=payload)
response.raise_for_status()
print("リクエストの送信が完了しました。")
その他詳細に関しては、Voisona Talkエディタの環境設定のAPIタブのリンクから「Talk APIリファレンス」を参照してください。