メインコンテンツまでスキップ

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

仕組み

  1. Authorization ヘッダーを​付けて​ WebSocket を​開きます。
  2. 必須の​ voice_id と​ format(および​任意の​ sample_ratetemperatureaudio_transport)を​含む session.update を​ 1 回送信します。
  3. session.created を​待ちます。
  4. 完全な​テキストを​含む tts.speak リクエストを​ 1 回送信します。
  5. tts.audio.start、​音声フレーム、tts.audio.donetts.usage を​読み取ります。
  6. さらに​ tts.speak リクエストを​順番に​送信するか、​ソケットを​クローズします。
一度に 1 つの合成のみ

1 セッションあたりアクティブに​できる​合成は​ 1 つだけです。​合成が​進行中に​ 2 つ目の​ tts.speak を​送信すると、synthesis_in_progress エラーが​返されます。​バックエンドで​開始された​試行では​ tts.usage を、​バックエンド到達前の​拒否では​終端と​なる​ tts.error を​受信してから、​次の​リクエストを​送信してください。backend_request_failed の​場合は、​直後に​続く​ tts.usage も​読み取ります。

クライアントメッセージ

session.update

合成の​前に​ session.update を​ 1 回送信します:

{
"type": "session.update",
"id": "cfg_0001",
"session": {
"voice_id": "61ba1141-60aa-4bc3-a3b3-be1ec20700b3",
"format": "mp3",
"audio_transport": "binary"
}
}
フィールド必須備​考
id任意クライアント相関 ID。​設定エラー時に​エコーされます。
voice_id必須GET /tts/voices から​取得した​公開音声の​ UUID。
format必須出力​音声​形式​ —​ mp3wavoggpcmflac の​いずれか。​選択した​音声で​対応し、​かつ​その​プロバイダーで​ストリーミング可能な​形式を​指定します。
sample_rate任意省略するか0を​設定すると​バックエンドの​デフォルト​(24000 Hz)に​なります。​選択した​音声で​設定可能な​値のみを​使用します。​Qwen系音声のoggでは​上書きできません。
temperature任意現在は​Qwen系音声で​受け付けられる、​音声の​ばらつきを​制御する​プロバイダー固有の​パラメータ。​その​他の​音声および​デフォルト値を​使う​場合は​省略します。​明示的な0.0は​実値として​送信されます。
audio_transport任意binary(デフォルト)または​ base64_json音声トランスポートを​参照してください。
形式ごとのストリーミング対応

音声カタログのstreaming: trueは​必要条件ですが、​すべてのformats値が​ストリーミング可能と​いう​意味では​ありません。​この​エンドポイントは​常に​出力を​ストリーミングする​ため、​非対応の​プロバイダーと​形式の​組み合わせにはcode: "unsupported_streaming_format"tts.errorが​返ります。

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,
"model": "speech-2.8-hd"
}
}

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": "speech-2.8-hd",
"audio_transport": "binary"
}

音声トランスポート

音声バイトの​届き方は、session.update で​設定した​ audio_transport に​よって​異なります:

  • binary(デフォルト)​ — 生成された​音声は​生の​バイナリ WebSocket フレームと​して​届きます。​順番に​連結してください。
  • base64_json — 音声は​ tts.audio.delta JSON メッセージと​して​届き、​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.audio.done の​後に​送信されます:

{
"type": "tts.usage",
"request_id": "synthesis-request-id",
"session_id": "router-session-request-id",
"client_id": "utt_0001",
"model": "speech-2.8-hd",
"usage": {
"input_chars": 24,
"input_bytes": 72,
"audio_bytes": 12345,
"status": "completed",
"final": true
}
}

バックエンドリクエストが​失敗した​場合は、code: "backend_request_failed" の​ tts.error に​続いて​ tts.usage が​送信されます。​音声が​返らなかった​場合の​ usage.status は​ backend_error、​一部の​音声が​すでに​返った​場合は​ partial_backend_error です。

バックエンドに​到達した​試行に​ついて、​ルーターは​失敗時も​利用記録を​作成します​(接続が​書き込み可能な間は​ tts.usage も​送信します)。​一方、​入力検証、​アクセス制御、​レート制限、​バックエンド選択の​段階で​拒否された​リクエストは​ tts.error のみを​返し、​利用記録を​作成しません。​したがって、​すべての​ tts.error の​後に​ tts.usage が​続くわけでは​ありません。

tts.error と​ router.error

WebSocket の​アップグレード前に​失敗した​場合は、​WebSocketイベントではなく​構造化された​HTTPエラーが​返ります。​代表的な​コードは、invalid_websocket_upgradeauth_token_requiredinvalid_token_formatinvalid_tokenlegacy_api_key_rejectedws_origin_deniedunknown_servicews_connection_limit_exceededws_connection_quota_unavailablewebsocket_drainingです。

TTS の​設定、​発話、​バックエンドに​関する​エラーは​次の​形です。id は、​利用​可能な​場合に​クライアントの​相関 ID を​エコーします:

{
"type": "tts.error",
"id": "utt_0001",
"code": "backend_request_failed",
"message": "Backend TTS request failed"
}
コード意味
invalid_message, invalid_id, id_too_long, invalid_session認識された​メッセージ、​相関 ID、​または​ルーターの​セッション状態が​不正です。
session_already_configuredこの​接続では​ session.update が​すでに​正常に​処理されています。
missing_voice_id, unknown_voice_id, tts_service_unavailable, service_access_denied音声の​選択、​サービスの​可用性、​または​アクセス権に​問題が​あります。
unsupported_audio_format, unsupported_sample_rate, unsupported_audio_transport, unsupported_streaming_format要求した​出力設定を、​選択した​音声または​バックエンドで​利用できません。
session_not_configured, empty_text, tts_text_too_long, synthesis_in_progresstts.speak の​状態または​テキストが​不正です。
rate_limited, service_rate_limitedAPI キーまたは​ TTS サービスの​レート制限を​超えました。
rate_limiter_unavailable, service_rate_limiter_unavailableレート制限サービスが​一時的に​利用できません。
backend_unavailable現在利用できる​ TTS バックエンドが​ありません。​バックエンドリクエストは​開始されていないため、​利用記録は​ありません。
backend_request_failedバックエンドで​開始された​合成が​失敗しました。​この​エラーの​後に​最終 tts.usage が​続きます。
tts_control_frame_too_largeJSON制御メッセージが​設定済みの​バイト上限を​超えています。​読み取り上限で​先に​検出された​場合、​接続は​コード1009で​閉じられます。

メッセージ種別を​判別する​前の​制御エラーは、type: "router.error" と、invalid_jsonmissing_message_typeunknown_message_type などの​コードを​使用します。​クライアントから​バイナリフレームを​送信すると​接続は​ WebSocket コード 1003 で​閉じられ、​制御フレームが​大きすぎる​場合は​コード 1009 で​閉じられます。

最小限の​ 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 フレームはイベント。
backend_error = None
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.error":
if event.get("code") == "backend_request_failed":
# バックエンド失敗時は、この後に最終利用量が続きます。
backend_error = event
continue
raise RuntimeError(event)
if event_type == "tts.usage":
print(json.dumps(event, ensure_ascii=False), file=sys.stderr)
if backend_error is not None:
raise RuntimeError(backend_error)
break
if event_type in {"router.error", "error"}:
raise RuntimeError(event)

sys.stdout.buffer.write(audio)


if __name__ == "__main__":
asyncio.run(main())

次の​ステップ

  • 音声ファイル全体を​ 1 回の​呼び出しで​返すには​ POST /tts エンドポイントを​使用します。
  • 利用​可能な​音声は音声カタログで​閲覧できます。
  • 利用が​どのように​課金されるかは料​金を​参照してください。