WebSocket ストリーミング(TTS)
WebSocket TTS API では、完全なテキストを送信し、生成された音声を 1 本の接続でストリーミングして受け取れます。合成が完了する前に再生を開始できます。
wss://api.shisa.ai/ws/tts/realtime
パス名に反して、これはリアルタイム ASR のような入力ストリーミング API ではありません。完全な tts.speak テキストリクエストを送信すると、サービスが生成された音声をバイナリフレームまたは base64 JSON チャンクとしてストリーミングで返します。
音声ファイル全体を一度に返す 1 回のリクエスト/レスポンスが必要な場合は、代わりに POST /tts エンドポイントを使用してください。
要件
- 選択した音声を使用できる API キー(WebSocket TTS のクォータとレート制限は
shisa/ttsサービスで管理されます)。 GET /tts/voicesから取得した音声の UUID — 音声カタログを参照してください。Authorizationヘッダーを設定できるサーバーサイドの WebSocket クライアント。API キーはサーバーサイドに保管してください。
ハンドシェイク時に、標準のベアラートークンで認証します:
Authorization: Bearer YOUR_API_KEY
仕組み
Authorizationヘッダーを付けて WebSocket を開きます。- 必須の
voice_idとformat(および任意のsample_rate、temperature、audio_transport)を含むsession.updateを 1 回送信します。 session.createdを待ちます。- 完全なテキストを含む
tts.speakリクエストを 1 回送信します。 tts.audio.start、音声フレーム、tts.audio.done、tts.usageを読み取ります。- さらに
tts.speakリクエストを順番に送信するか、ソケットをクローズします。
1 セッションあたりアクティブにできる合成は 1 つだけです。合成が進行中に 2 つ目の tts.speak を送信すると、synthesis_in_progress エラーが返されます。次のリクエストを送信する前に tts.usage を待ってください。
クライアントメッセージ
session.update
合成の前に session.update を 1 回送信します:
{
"type": "session.update",
"id": "cfg_0001",
"session": {
"voice_id": "61ba1141-60aa-4bc3-a3b3-be1ec20700b3",
"format": "mp3",
"temperature": 0.7,
"audio_transport": "binary"
}
}
| フィールド | 必須 | 備考 |
|---|---|---|
id | 任意 | クライアント相関 ID。設定エラー時にエコーされます。 |
voice_id | 必須 | GET /tts/voices から取得した公開音声の UUID。 |
format | 必須 | 出力音声形式 — mp3、wav、ogg、pcm、flac のいずれか。使用可能な値は選択した音声によって異なります。 |
sample_rate | 任意 | 省略するか 0 を設定するとバックエンドのデフォルト(24000 Hz)になります。 |
temperature | 任意 | 一部の音声で受け付けられる、音声のばらつきを制御するプロバイダー固有のパラメータ。デフォルトの場合は省略します。明示的な 0.0 は実値として送信されます。 |
audio_transport | 任意 | binary(デフォルト)または base64_json。音声トランスポートを参照してください。 |
tts.speak
合成する完全なテキストを送信します:
{
"type": "tts.speak",
"id": "utt_0001",
"text": "こんにちは。WebSocket TTS のテストです。"
}
| フィールド | 必須 | 備考 |
|---|---|---|
id | 任意 | クライアント相関 ID。このリクエストの TTS イベントでエコーされます。 |
text | 必須 | 合成する完全なテキスト。デフォルトの上限は 5000 文字です。 |
サービスイベント
session.created
セッションが設定され、tts.speak の準備が整いました:
{
"type": "session.created",
"session": {
"id": "router-session-request-id",
"service": "shisa/tts",
"voice_id": "61ba1141-60aa-4bc3-a3b3-be1ec20700b3",
"format": "mp3",
"sample_rate": 24000,
"temperature": 0.7,
"model": "kokoro"
}
}
tts.audio.start
受け入れられた合成リクエストを示し、受け取る音声を記述します:
{
"type": "tts.audio.start",
"id": "utt_0001",
"request_id": "synthesis-request-id",
"format": "mp3",
"content_type": "audio/mpeg",
"sample_rate": 24000,
"voice_id": "61ba1141-60aa-4bc3-a3b3-be1ec20700b3",
"model": "kokoro",
"audio_transport": "binary"
}
音声トランスポート
音声バイトの届き方は、session.update で設定した audio_transport によって異なります:
binary(デフォルト) — 生成された音声は生のバイナリ WebSocket フレームとして届きます。順番に連結してください。base64_json— 音声はtts.audio.deltaJSON メッセージとして届き、base64 エンコードされたバイトがaudioに、増加するseqが含まれます:
{
"type": "tts.audio.delta",
"id": "utt_0001",
"request_id": "synthesis-request-id",
"seq": 1,
"audio": "<base64-audio-bytes>"
}
tts.audio.done
このリクエストの合成が完了しました:
{
"type": "tts.audio.done",
"id": "utt_0001",
"request_id": "synthesis-request-id",
"audio_bytes": 12345,
"chunks": 4
}
tts.usage
リクエストの最終的な利用量です。受け入れられた tts.speak ごとに利用記録が 1 件作成されます:
{
"type": "tts.usage",
"request_id": "synthesis-request-id",
"session_id": "router-session-request-id",
"client_id": "utt_0001",
"model": "kokoro",
"usage": {
"input_chars": 24,
"input_bytes": 72,
"audio_bytes": 12345,
"status": "completed",
"final": true
}
}
拒否されたリクエスト(例: 不正な voice_id や format)は tts.error を返し、利用記録は作成されません。
最小限の Python クライアント
依存関係をインストールします:
python -m pip install websockets
キーを設定して実行します:
export SHISA_API_KEY="shsk:..."
export TTS_WS_URL="wss://api.shisa.ai/ws/tts/realtime"
export TTS_VOICE_ID="61ba1141-60aa-4bc3-a3b3-be1ec20700b3"
python websocket_tts.py > output.mp3
websocket_tts.py:
#!/usr/bin/env python3
import asyncio
import json
import os
import sys
import websockets
async def main() -> None:
url = os.environ.get("TTS_WS_URL", "wss://api.shisa.ai/ws/tts/realtime")
api_key = os.environ["SHISA_API_KEY"]
voice_id = os.environ["TTS_VOICE_ID"]
text = os.environ.get("TTS_TEXT", "こんにちは。WebSocket TTS のテストです。")
audio_format = os.environ.get("TTS_FORMAT", "mp3")
headers = [("Authorization", f"Bearer {api_key}")]
audio = bytearray()
async with websockets.connect(url, additional_headers=headers, max_size=None) as ws:
await ws.send(
json.dumps(
{
"type": "session.update",
"id": "cfg_0001",
"session": {
"voice_id": voice_id,
"format": audio_format,
"audio_transport": "binary",
},
}
)
)
# session.created を待ってから発話します。
while True:
event = json.loads(await ws.recv())
if event.get("type") == "session.created":
break
if event.get("type") in {"tts.error", "router.error", "error"}:
raise RuntimeError(event)
await ws.send(json.dumps({"type": "tts.speak", "id": "utt_0001", "text": text}))
# バイナリフレームは音声、JSON フレームはイベント。tts.usage で終了します。
while True:
msg = await ws.recv()
if isinstance(msg, bytes):
audio.extend(msg)
continue
event = json.loads(msg)
event_type = event.get("type")
if event_type == "tts.usage":
print(json.dumps(event, ensure_ascii=False), file=sys.stderr)
break
if event_type in {"tts.error", "router.error", "error"}:
raise RuntimeError(event)
sys.stdout.buffer.write(audio)
if __name__ == "__main__":
asyncio.run(main())
次のステップ
- 音声ファイル全体を 1 回の呼び出しで返すには
POST /ttsエンドポイントを使用します。 - 利用可能な音声は音声カタログで閲覧できます。
- 利用がどのように課金されるかは料金を参照してください。