-
<speak> ルート要素。すべての SSML フラグメントは、このタグで囲む必要があります。</speak> -
<break> 一時停止を挿入します。一時停止の長さを指定できます。</break> -
<p> 段落の区切り。</p> -
<s> 文の区切り。</s> -
<say-as> コンテンツをどのように解釈し、読み上げるかを指定します。数式読み上げに使用します。</say-as> -
<prosody> 音声の音量、話速、ピッチを制御します。</prosody>
SSML (音声合成マークアップ言語) を使用すると、速度、ポーズ、発音などの音声特性を細かく調整できます。
概要
SSML (音声合成マークアップ言語) は、音声合成のための XML ベースのマークアップ言語です。テキストに SSML タグを埋め込むことで、話す速度、抑揚、間、音量を制御したり、背景音楽や効果音を追加したりして、より豊かなオーディオ出力を実現できます。
代表的なユースケースは次のとおりです。
-
オーディオブック:間や話す速度を精密に制御し、背景音楽を追加することで没入感のあるリスニング体験を提供します。
-
インテリジェントカスタマーサービス:
<say-as>タグを使用して、電話番号、日付など、これらに類似した情報を正確に読み上げます。 -
多言語放送:
<phoneme>タグを使用して、外国語の単語の正確な発音を指定します。 -
オンライン教育:数式読み上げ機能を使用して、LaTeX 数式を自然な音声に変換します。
これらの機能は、CosyVoice モデルファミリーでご利用いただけます。モデル選択のガイダンスについては、「音声合成」をご参照ください。
SSML
制限事項
-
モデル: SSML は cosyvoice-v3.5-flash、cosyvoice-v3.5-plus、cosyvoice-v3-flash、cosyvoice-v3-plus、および cosyvoice-v2 でのみサポートされています。
-
音声: CosyVoice 音声リスト で SSML 互換としてマークされているクローン音声とプリセット音声のみサポートされています。
-
API: SSML は、次の API でサポートされています。
-
Java SDK (バージョン 2.20.3 以降):非ストリーミングおよび単方向ストリーミングコール
-
Python SDK (バージョン 1.23.4 以降):非ストリーミングおよび単方向ストリーミングコール
-
WebSocket API:
enable_ssmlパラメーターをtrueに設定し、単一の continue-task イベントを送信します
-
クイックスタート
次の例では、SSML を使用して合成音声の話す速度を制御します。コードを実行する前に、次の前提条件を満たしてください:
-
DashScope SDK (Python 1.23.4 以降、Java 2.20.3 以降) をインストールします。詳細については、「SDK のインストール」をご参照ください。
cosyvoice-v3.5-plus および cosyvoice-v3.5-flash モデルは、現在北京リージョンでのみ利用可能で、音声クローニングシナリオ専用です(プリセット音声は提供されません)。これらのモデルを使用する前に、音声クローニングの手順に従ってターゲット音声を作成してください。
Java SDK
非ストリーミングコール
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
import com.alibaba.dashscope.utils.Constants;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* SSML 機能の説明:
* 1. 非ストリーミングコールと単方向ストリーミングコールのみが SSML 機能をサポートします
* 2. cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされたシステム音声のみが SSML 機能をサポートします (例: cosyvoice-v3-flash モデルの longanyang 音声)
*/
public class Main {
private static String model = "cosyvoice-v3-flash";
private static String voice = "longanyang";
public static void main(String[] args) {
// シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
streamAudioDataToSpeaker();
System.exit(0);
}
public static void streamAudioDataToSpeaker() {
SpeechSynthesisParam param =
SpeechSynthesisParam.builder()
// シンガポールリージョンと北京リージョンでは API キーが異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
// 環境変数を設定していない場合は、次の行を実際の Model Studio の API キーに置き換えてください: .apiKey("sk-xxx")
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.model(model)
.voice(voice)
.build();
SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
ByteBuffer audio = null;
try {
// 非ストリーミングコール。音声が返されるまでブロックします
// 特殊文字はエスケープする必要があります
audio = synthesizer.call("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// タスク終了時に WebSocket 接続を閉じます
synthesizer.getDuplexApi().close(1000, "bye");
}
if (audio != null) {
// 音声データをローカルファイル "output.mp3" に保存します
File file = new File("output.mp3");
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(audio.array());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 初回テキスト送信時には WebSocket 接続を確立する必要があるため、初回パケットレイテンシーには接続確立時間が含まれます
System.out.println(
"[Metric] requestId: "
+ synthesizer.getLastRequestId()
+ ", first packet latency (ms): "
+ synthesizer.getFirstPackageDelay());
}
}
単方向ストリーミングコール
import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisAudioFormat;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
import com.alibaba.dashscope.common.ResultCallback;
import com.alibaba.dashscope.utils.Constants;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
/**
* SSML 機能の説明:
* 1. 非ストリーミングコールと単方向ストリーミングコールのみが SSML 機能をサポートします
* 2. cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされたシステム音声のみが SSML 機能をサポートします (例: cosyvoice-v3-flash モデルの longanyang 音声)
*/
public class Main {
private static String model = "cosyvoice-v3-flash";
private static String voice = "longanyang";
public static void main(String[] args) {
// シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
streamAudioDataToSpeaker();
System.out.println("Audio has been saved to the output.mp3 file");
System.exit(0);
}
public static void streamAudioDataToSpeaker() {
CountDownLatch latch = new CountDownLatch(1);
final FileOutputStream[] fileOutputStream = new FileOutputStream[1];
try {
fileOutputStream[0] = new FileOutputStream("output.mp3");
} catch (IOException e) {
System.err.println("Failed to create output file: " + e.getMessage());
return;
}
// ResultCallback インターフェースを実装します
ResultCallback<SpeechSynthesisResult> callback = new ResultCallback<SpeechSynthesisResult>() {
@Override
public void onEvent(SpeechSynthesisResult result) {
if (result.getAudioFrame() != null) {
// 音声データをローカルファイルに書き込みます
try {
byte[] audioData = result.getAudioFrame().array();
fileOutputStream[0].write(audioData);
fileOutputStream[0].flush();
} catch (IOException e) {
System.err.println("Failed to write audio data: " + e.getMessage());
}
}
}
@Override
public void onComplete() {
System.out.println("Received Complete, speech synthesis finished");
closeFileOutputStream(fileOutputStream[0]);
latch.countDown();
}
@Override
public void onError(Exception e) {
System.out.println("An error occurred: " + e.toString());
closeFileOutputStream(fileOutputStream[0]);
latch.countDown();
}
};
SpeechSynthesisParam param =
SpeechSynthesisParam.builder()
// シンガポールリージョンと北京リージョンでは API キーが異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
// 環境変数を設定していない場合は、次の行を実際の Model Studio の API キーに置き換えてください: .apiKey("sk-xxx")
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.model(model)
.voice(voice)
.format(SpeechSynthesisAudioFormat.MP3_22050HZ_MONO_256KBPS)
.build();
SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, callback);
try {
// 単方向ストリーミングコール。すぐに null を返します (実際の結果はコールバックインターフェースを介して非同期に配信されます)。コールバックインターフェースの onEvent メソッドからリアルタイムでバイナリ音声を取得します
// 特殊文字はエスケープする必要があります
synthesizer.call("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>");
// 合成が完了するのを待ちます
latch.await();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// タスク終了後に WebSocket 接続を閉じます
try {
synthesizer.getDuplexApi().close(1000, "bye");
} catch (Exception e) {
System.err.println("Failed to close WebSocket connection: " + e.getMessage());
}
// ファイルストリームが確実に閉じられるようにします
closeFileOutputStream(fileOutputStream[0]);
}
// 初回テキスト送信時には WebSocket 接続を確立する必要があるため、初回パケットレイテンシーには接続確立時間が含まれます
System.out.println(
"[Metric] requestId: "
+ synthesizer.getLastRequestId()
+ ", first packet latency (ms): "
+ synthesizer.getFirstPackageDelay());
}
private static void closeFileOutputStream(FileOutputStream fileOutputStream) {
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
System.err.println("Failed to close file stream: " + e.getMessage());
}
}
}
Python SDK
非ストリーミング呼び出し
# coding=utf-8
# SSML 機能の説明:
# 1. SSML 機能は、非ストリーミング呼び出しと単方向ストリーミング呼び出しでのみサポートされます。
# 2. SSML 機能は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされたシステム音声でのみサポートされます (例: cosyvoice-v3-flash モデルの longanyang 音声)。
import dashscope
from dashscope.audio.tts_v2 import *
import os
# シンガポールと北京リージョンの API キーは異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
# 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# シンガポールリージョンの URL。WorkspaceId をご自身のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'
# モデル
model = "cosyvoice-v3-flash"
# 音声
voice = "longanyang"
# SpeechSynthesizer をインスタンス化し、コンストラクターに model や voice などのリクエストパラメーターを渡します
synthesizer = SpeechSynthesizer(model=model, voice=voice)
# 非ストリーミング呼び出し。音声が返されるまでブロックします
# 特殊文字はエスケープする必要があります
audio = synthesizer.call("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>")
# 音声をローカルファイルに保存します
with open('output.mp3', 'wb') as f:
f.write(audio)
# 最初のテキスト送信では WebSocket 接続の確立が必要なため、初回パケットレイテンシーには接続確立時間が含まれます
print('[Metric] requestId: {}, first packet latency: {} ms'.format(
synthesizer.get_last_request_id(),
synthesizer.get_first_package_delay()))
単方向ストリーミング呼び出し
# coding=utf-8
# SSML 機能の説明:
# 1. SSML 機能は、非ストリーミング呼び出しと単方向ストリーミング呼び出しでのみサポートされます。
# 2. SSML 機能は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされたシステム音声でのみサポートされます (例: cosyvoice-v3-flash モデルの longanyang 音声)。
import dashscope
from dashscope.audio.tts_v2 import *
import os
from datetime import datetime
def get_timestamp():
now = datetime.now()
formatted_timestamp = now.strftime("[%Y-%m-%d %H:%M:%S.%f]")
return formatted_timestamp
# シンガポールと北京リージョンの API キーは異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
# 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# シンガポールリージョンの URL。WorkspaceId をご自身のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'
# モデル
model = "cosyvoice-v3-flash"
# 音声
voice = "longanyang"
# コールバックインターフェイスを定義します
class Callback(ResultCallback):
_player = None
_stream = None
def on_open(self):
# 音声データを書き込むための出力ファイルを開きます
self.file = open("output.mp3", "wb")
print("Connection established: " + get_timestamp())
def on_complete(self):
print("Speech synthesis completed, all results have been received: " + get_timestamp())
if hasattr(self, 'file') and self.file:
self.file.close()
self
# 最初のテキスト送信では WebSocket 接続の確立が必要なため、初回パケットレイテンシーには接続確立時間が含まれます
print('[Metric] requestId: {}, first packet latency: {} ms'.format(
self.synthesizer.get_last_request_id(),
self.synthesizer.get_first_package_delay()))
def on_error(self, message: str):
print(f"Speech synthesis error occurred: {message}")
if hasattr(self, 'file') and self.file:
self.file.close()
def on_close(self):
print("Connection closed: " + get_timestamp())
if hasattr(self, 'file') and self.file:
self.file.close()
def on_event(self, message):
pass
def on_data(self, data: bytes) -> None:
print(get_timestamp() + " Binary audio length: " + str(len(data)))
# 音声データをファイルに書き込みます
self.file.write(data)
callback = Callback()
# SpeechSynthesizer をインスタンス化し、コンストラクターに model や voice などのリクエストパラメーターを渡します
synthesizer = SpeechSynthesizer(
model=model,
voice=voice,
callback=callback,
)
# on_complete で使用できるように、synthesizer インスタンスを callback に割り当てます
callback.synthesizer = synthesizer
# 単方向ストリーミング呼び出し。合成対象のテキストを送信し、コールバックインターフェイスの on_data メソッドからリアルタイムにバイナリ音声を取得します
# 特殊文字はエスケープする必要があります
synthesizer.call("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>")
WebSocket API
Go
// SSML 機能の説明:
// 1. run-task コマンドを送信する際に、enable_ssml パラメータを true に設定して SSML サポートを有効にします。
// 2. continue-task コマンドを介して SSML を含むテキストを送信し、continue-task コマンドは 1 回しか送信できません。
// 3. cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされているシステム音声のみが SSML 機能をサポートします (例:cosyvoice-v3-flash モデルの longanyang 音声)。
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/google/uuid"
"github.com/gorilla/websocket"
)
const (
// シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
wsURL = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/"
outputFile = "output.mp3"
)
func main() {
// シンガポールと北京リージョンの API キーは異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
// 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: apiKey := "sk-xxx"
apiKey := os.Getenv("DASHSCOPE_API_KEY")
// 出力ファイルをクリアします
os.Remove(outputFile)
os.Create(outputFile)
// WebSocket に接続します
header := make(http.Header)
header.Add("X-DashScope-DataInspection", "enable")
header.Add("Authorization", fmt.Sprintf("bearer %s", apiKey))
conn, resp, err := websocket.DefaultDialer.Dial(wsURL, header)
if err != nil {
if resp != nil {
fmt.Printf("Connection failed, HTTP status code: %d\n", resp.StatusCode)
}
fmt.Println("Connection failed:", err)
return
}
defer conn.Close()
// タスク ID を生成します
taskID := uuid.New().String()
fmt.Printf("Generated task ID: %s\n", taskID)
// run-task コマンドを送信します
runTaskCmd := map[string]interface{}{
"header": map[string]interface{}{
"action": "run-task",
"task_id": taskID,
"streaming": "duplex",
},
"payload": map[string]interface{}{
"task_group": "audio",
"task": "tts",
"function": "SpeechSynthesizer",
"model": "cosyvoice-v3-flash",
"parameters": map[string]interface{}{
"text_type": "PlainText",
"voice": "longanyang",
"format": "mp3",
"sample_rate": 22050,
"volume": 50,
"rate": 1,
"pitch": 1,
// enable_ssml を true に設定した場合、continue-task コマンドは 1 回しか送信できません。それ以外の場合、エラー "Text request limit violated, expected 1." が返されます
"enable_ssml": true,
},
"input": map[string]interface{}{},
},
}
runTaskJSON, _ := json.Marshal(runTaskCmd)
fmt.Printf("Sending run-task command: %s\n", string(runTaskJSON))
err = conn.WriteMessage(websocket.TextMessage, runTaskJSON)
if err != nil {
fmt.Println("Failed to send run-task:", err)
return
}
textSent := false
// メッセージを処理します
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
fmt.Println("Failed to read message:", err)
break
}
// バイナリメッセージを処理します
if messageType == websocket.BinaryMessage {
fmt.Printf("Received binary message, length: %d\n", len(message))
file, _ := os.OpenFile(outputFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
file.Write(message)
file.Close()
continue
}
// テキストメッセージを処理します
messageStr := string(message)
fmt.Printf("Received text message: %s\n", strings.ReplaceAll(messageStr, "\n", ""))
// イベントタイプを取得するための簡単な JSON 解析
var msgMap map[string]interface{}
if json.Unmarshal(message, &msgMap) == nil {
if header, ok := msgMap["header"].(map[string]interface{}); ok {
if event, ok := header["event"].(string); ok {
fmt.Printf("Event type: %s\n", event)
switch event {
case "task-started":
fmt.Println("=== Received task-started event ===")
if !textSent {
// continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
continueTaskCmd := map[string]interface{}{
"header": map[string]interface{}{
"action": "continue-task",
"task_id": taskID,
"streaming": "duplex",
},
"payload": map[string]interface{}{
"input": map[string]interface{}{
// 特殊文字はエスケープする必要があります
"text": "<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>",
},
},
}
continueTaskJSON, _ := json.Marshal(continueTaskCmd)
fmt.Printf("Sending continue-task command: %s\n", string(continueTaskJSON))
err = conn.WriteMessage(websocket.TextMessage, continueTaskJSON)
if err != nil {
fmt.Println("Failed to send continue-task:", err)
return
}
textSent = true
// finish-task を送信する前に待機します
time.Sleep(500 * time.Millisecond)
// finish-task コマンドを送信します
finishTaskCmd := map[string]interface{}{
"header": map[string]interface{}{
"action": "finish-task",
"task_id": taskID,
"streaming": "duplex",
},
"payload": map[string]interface{}{
"input": map[string]interface{}{},
},
}
finishTaskJSON, _ := json.Marshal(finishTaskCmd)
fmt.Printf("Sending finish-task command: %s\n", string(finishTaskJSON))
err = conn.WriteMessage(websocket.TextMessage, finishTaskJSON)
if err != nil {
fmt.Println("Failed to send finish-task:", err)
return
}
}
case "task-finished":
fmt.Println("=== Task completed ===")
return
case "task-failed":
fmt.Println("=== Task failed ===")
if header["error_message"] != nil {
fmt.Printf("Error message: %s\n", header["error_message"])
}
return
case "result-generated":
fmt.Println("Received result-generated event")
}
}
}
}
}
}
C#
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
// SSML 機能の説明:
// 1. run-task コマンドを送信する際に、enable_ssml パラメーターを true に設定して SSML サポートを有効にします
// 2. SSML を含むテキストを continue-task コマンドで送信します。このコマンドは 1 回のみ送信できます
// 3. cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応と記載されているシステム音声のみが SSML 機能をサポートします (例:cosyvoice-v3-flash モデルの longanyang 音声)
class Program {
// シンガポールリージョンと北京リージョンでは API キーが異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
// 環境変数を設定していない場合は、次の行を実際の Model Studio API キーに置き換えてください: private static readonly string ApiKey = "sk-xxx"
private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("DASHSCOPE_API_KEY environment variable is not set.");
// シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
private const string WebSocketUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/";
// 出力ファイルパス
private const string OutputFilePath = "output.mp3";
// WebSocket クライアント
private static ClientWebSocket _webSocket = new ClientWebSocket();
// キャンセル トークン ソース
private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
// タスク ID
private static string? _taskId;
// タスクが開始されたかどうか
private static TaskCompletionSource<bool> _taskStartedTcs = new TaskCompletionSource<bool>();
static async Task Main(string[] args) {
try {
// 出力ファイルをクリアします
ClearOutputFile(OutputFilePath);
// WebSocket サービスに接続します
await ConnectToWebSocketAsync(WebSocketUrl);
// メッセージ受信タスクを開始します
Task receiveTask = ReceiveMessagesAsync();
// run-task コマンドを送信します
_taskId = GenerateTaskId();
await SendRunTaskCommandAsync(_taskId);
// task-started イベントを待ちます
await _taskStartedTcs.Task;
// continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
// 特殊文字はエスケープする必要があります
await SendContinueTaskCommandAsync("<speak rate=\"2\">私の話す速度は普通の人より速いです。</speak>");
// finish-task コマンドを送信します
await SendFinishTaskCommandAsync(_taskId);
// 受信タスクが完了するのを待ちます
await receiveTask;
Console.WriteLine("Task completed, connection closed.");
} catch (OperationCanceledException) {
Console.WriteLine("Task was cancelled.");
} catch (Exception ex) {
Console.WriteLine($"An error occurred: {ex.Message}");
} finally {
_cancellationTokenSource.Cancel();
_webSocket.Dispose();
}
}
private static void ClearOutputFile(string filePath) {
if (File.Exists(filePath)) {
File.WriteAllText(filePath, string.Empty);
Console.WriteLine("Output file cleared.");
} else {
Console.WriteLine("Output file does not exist, no need to clear.");
}
}
private static async Task ConnectToWebSocketAsync(string url) {
var uri = new Uri(url);
if (_webSocket.State == WebSocketState.Connecting || _webSocket.State == WebSocketState.Open) {
return;
}
// WebSocket 接続ヘッダーを設定します
_webSocket.Options.SetRequestHeader("Authorization", $"bearer {ApiKey}");
_webSocket.Options.SetRequestHeader("X-DashScope-DataInspection", "enable");
try {
await _webSocket.ConnectAsync(uri, _cancellationTokenSource.Token);
Console.WriteLine("Successfully connected to the WebSocket service.");
} catch (OperationCanceledException) {
Console.WriteLine("WebSocket connection was cancelled.");
} catch (Exception ex) {
Console.WriteLine($"WebSocket connection failed: {ex.Message}");
throw;
}
}
private static async Task SendRunTaskCommandAsync(string taskId) {
var command = CreateCommand("run-task", taskId, "duplex", new {
task_group = "audio",
task = "tts",
function = "SpeechSynthesizer",
model = "cosyvoice-v3-flash",
parameters = new
{
text_type = "PlainText",
voice = "longanyang",
format = "mp3",
sample_rate = 22050,
volume = 50,
rate = 1,
pitch = 1,
// enable_ssml が true に設定されている場合、continue-task コマンドは 1 回しか送信できません。そうでない場合、エラー "Text request limit violated, expected 1." が返されます
enable_ssml = true
},
input = new { }
});
await SendJsonMessageAsync(command);
Console.WriteLine("run-task command sent.");
}
private static async Task SendContinueTaskCommandAsync(string text) {
if (_taskId == null) {
throw new InvalidOperationException("Task ID is not initialized.");
}
var command = CreateCommand("continue-task", _taskId, "duplex", new {
input = new {
text
}
});
await SendJsonMessageAsync(command);
Console.WriteLine("continue-task command sent.");
}
private static async Task SendFinishTaskCommandAsync(string taskId) {
var command = CreateCommand("finish-task", taskId, "duplex", new {
input = new { }
});
await SendJsonMessageAsync(command);
Console.WriteLine("finish-task command sent.");
}
private static async Task SendJsonMessageAsync(string message) {
var buffer = Encoding.UTF8.GetBytes(message);
try {
await _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
} catch (OperationCanceledException) {
Console.WriteLine("Message sending was cancelled.");
}
}
private static async Task ReceiveMessagesAsync() {
while (_webSocket.State == WebSocketState.Open) {
var response = await ReceiveMessageAsync();
if (response != null) {
var eventStr = response.RootElement.GetProperty("header").GetProperty("event").GetString();
switch (eventStr) {
case "task-started":
Console.WriteLine("Task started.");
_taskStartedTcs.TrySetResult(true);
break;
case "task-finished":
Console.WriteLine("Task finished.");
_cancellationTokenSource.Cancel();
break;
case "task-failed":
Console.WriteLine("Task failed: " + response.RootElement.GetProperty("header").GetProperty("error_message").GetString());
_cancellationTokenSource.Cancel();
break;
default:
// ここで result-generated を処理できます
break;
}
}
}
}
private static async Task<JsonDocument?> ReceiveMessageAsync() {
var buffer = new byte[1024 * 4];
var segment = new ArraySegment<byte>(buffer);
try {
WebSocketReceiveResult result = await _webSocket.ReceiveAsync(segment, _cancellationTokenSource.Token);
if (result.MessageType == WebSocketMessageType.Close) {
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", _cancellationTokenSource.Token);
return null;
}
if (result.MessageType == WebSocketMessageType.Binary) {
// バイナリデータを処理します
Console.WriteLine("Received binary data...");
// バイナリデータをファイルに保存します
using (var fileStream = new FileStream(OutputFilePath, FileMode.Append)) {
fileStream.Write(buffer, 0, result.Count);
}
return null;
}
string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
return JsonDocument.Parse(message);
} catch (OperationCanceledException) {
Console.WriteLine("Message receiving was cancelled.");
return null;
}
}
private static string GenerateTaskId() {
return Guid.NewGuid().ToString("N").Substring(0, 32);
}
private static string CreateCommand(string action, string taskId, string streaming, object payload) {
var command = new {
header = new {
action,
task_id = taskId,
streaming
},
payload
};
return JsonSerializer.Serialize(command);
}
}
PHP
サンプルコードのディレクトリ構造は次のとおりです。
my-php-project/
├── composer.json
├── vendor/
└── index.php
composer.json ファイルには、次の依存関係が含まれています。必要に応じてバージョン番号を調整してください。
{
"require": {
"react/event-loop": "^1.3",
"react/socket": "^1.11",
"react/stream": "^1.2",
"react/http": "^1.1",
"ratchet/pawl": "^0.4"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
index.php ファイルには、次のコードが含まれています。
<!-- SSML 機能の説明: -->
<!-- 1. run-task コマンドを送信する際に、enable_ssml パラメーターを true に設定して SSML サポートを有効にします -->
<!-- 2. SSML を含むテキストを continue-task コマンドで送信します。continue-task コマンドは1回しか送信できません -->
<!-- 3. cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされたシステム音声のみが SSML 機能をサポートします (例:cosyvoice-v3-flash モデルの longanyang 音声) -->
<?php
require __DIR__ . '/vendor/autoload.php';
use Ratchet\Client\Connector;
use React\EventLoop\Loop;
use React\Socket\Connector as SocketConnector;
// シンガポールリージョンと北京リージョンでは API キーが異なります。API キーの取得方法: https://www.alibabacloud.com/help/en/model-studio/get-api-key
// 環境変数を設定していない場合は、次の行を実際の Model Studio API キーに置き換えてください: $api_key = "sk-xxx"
$api_key = getenv("DASHSCOPE_API_KEY");
// シンガポールリージョンの URL。{WorkspaceId} を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
$websocket_url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/'; // WebSocket サーバーアドレス
$output_file = 'output.mp3'; // 出力ファイルパス
$loop = Loop::get();
if (file_exists($output_file)) {
// ファイルの内容をクリアします
file_put_contents($output_file, '');
}
// カスタムコネクタを作成します
$socketConnector = new SocketConnector($loop, [
'tcp' => [
'bindto' => '0.0.0.0:0',
],
'tls' => [
'verify_peer' => false,
'verify_peer_name' => false,
],
]);
$connector = new Connector($loop, $socketConnector);
$headers = [
'Authorization' => 'bearer ' . $api_key,
'X-DashScope-DataInspection' => 'enable'
];
$connector($websocket_url, [], $headers)->then(function ($conn) use ($loop, $output_file) {
echo "Connected to WebSocket server\n";
// タスク ID を生成します
$taskId = generateTaskId();
// run-task コマンドを送信します
sendRunTaskMessage($conn, $taskId);
// continue-task コマンドを送信する関数を定義します
$sendContinueTask = function() use ($conn, $loop, $taskId) {
// continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
$continueTaskMessage = json_encode([
"header" => [
"action" => "continue-task",
"task_id" => $taskId,
"streaming" => "duplex"
],
"payload" => [
"input" => [
// 特殊文字はエスケープする必要があります
"text" => "<speak rate=\"2\">私の話速は、一般的な人よりも速いです。</speak>"
]
]
]);
$conn->send($continueTaskMessage);
// finish-task コマンドを送信します
sendFinishTaskMessage($conn, $taskId);
};
// task-started イベントを受信したかどうかを示すフラグ
$taskStarted = false;
// メッセージをリッスンします
$conn->on('message', function($msg) use ($conn, $sendContinueTask, $loop, &$taskStarted, $taskId, $output_file) {
if ($msg->isBinary()) {
// バイナリデータをローカルファイルに書き込みます
file_put_contents($output_file, $msg->getPayload(), FILE_APPEND);
} else {
// バイナリ以外のメッセージを処理します
$response = json_decode($msg, true);
if (isset($response['header']['event'])) {
handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, $taskStarted);
} else {
echo "Unknown message format\n";
}
}
});
// 接続クローズをリッスンします
$conn->on('close', function($code = null, $reason = null) {
echo "Connection closed\n";
if ($code !== null) {
echo "Close code: " . $code . "\n";
}
if ($reason !== null) {
echo "Close reason: " . $reason . "\n";
}
});
}, function ($e) {
echo "Unable to connect: {$e->getMessage()}\n";
});
$loop->run();
/**
* タスク ID を生成します
* @return string
*/
function generateTaskId(): string {
return bin2hex(random_bytes(16));
}
/**
* run-task コマンドを送信します
* @param $conn
* @param $taskId
*/
function sendRunTaskMessage($conn, $taskId) {
$runTaskMessage = json_encode([
"header" => [
"action" => "run-task",
"task_id" => $taskId,
"streaming" => "duplex"
],
"payload" => [
"task_group" => "audio",
"task" => "tts",
"function" => "SpeechSynthesizer",
"model" => "cosyvoice-v3-flash",
"parameters" => [
"text_type" => "PlainText",
"voice" => "longanyang",
"format" => "mp3",
"sample_rate" => 22050,
"volume" => 50,
"rate" => 1,
"pitch" => 1,
// enable_ssml が true に設定されている場合、continue-task コマンドは 1 回しか送信できません。そうでない場合、エラー "Text request limit violated, expected 1." が返されます
"enable_ssml" => true
],
"input" => (object) []
]
]);
echo "Preparing to send run-task command: " . $runTaskMessage . "\n";
$conn->send($runTaskMessage);
echo "run-task command sent\n";
}
/**
* 音声ファイルを読み込みます
* @param string $filePath
* @return bool|string
*/
function readAudioFile(string $filePath) {
$voiceData = file_get_contents($filePath);
if ($voiceData === false) {
echo "Unable to read audio file\n";
}
return $voiceData;
}
/**
* 音声データを分割します
* @param string $data
* @param int $chunkSize
* @return array
*/
function splitAudioData(string $data, int $chunkSize): array {
return str_split($data, $chunkSize);
}
/**
* finish-task コマンドを送信します
* @param $conn
* @param $taskId
*/
function sendFinishTaskMessage($conn, $taskId) {
$finishTaskMessage = json_encode([
"header" => [
"action" => "finish-task",
"task_id" => $taskId,
"streaming" => "duplex"
],
"payload" => [
"input" => (object) []
]
]);
echo "Preparing to send finish-task command: " . $finishTaskMessage . "\n";
$conn->send($finishTaskMessage);
echo "finish-task command sent\n";
}
/**
* イベントを処理します
* @param $conn
* @param $response
* @param $sendContinueTask
* @param $loop
* @param $taskId
* @param $taskStarted
*/
function handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, &$taskStarted) {
switch ($response['header']['event']) {
case 'task-started':
echo "タスクが開始されました、continue-task コマンドを送信しています...\n";
$taskStarted = true;
// continue-task コマンドを送信します
$sendContinueTask();
break;
case 'result-generated':
// result-generated イベントを無視します
break;
case 'task-finished':
echo "タスクが完了しました\n";
$conn->close();
break;
case 'task-failed':
echo "タスクが失敗しました\n";
echo "エラーコード: " . $response['header']['error_code'] . "\n";
echo "エラーメッセージ: " . $response['header']['error_message'] . "\n";
$conn->close();
break;
case 'error':
echo "エラー: " . $response['payload']['message'] . "\n";
break;
default:
echo "不明なイベント: " . $response['header']['event'] . "\n";
break;
}
// タスクが完了した場合、接続を閉じます
if ($response['header']['event'] == 'task-finished') {
// すべてのデータが確実に送信されるよう、1秒間待機します
$loop->addTimer(1, function() use ($conn) {
$conn->close();
echo "クライアントが接続を閉じました\n";
});
}
// task-started イベントが受信されていない場合、接続を閉じます
if (!$taskStarted && in_array($response['header']['event'], ['task-failed', 'error'])) {
$conn->close();
}
}
Node.js
必要な依存関係をインストールします:
npm install ws
npm install uuid
サンプルコード:
// SSML 機能の説明:
// 1. run-task コマンドを送信する際に enable_ssml パラメーターを true に設定すると、SSML サポートが有効になります。
// 2. continue-task コマンドで SSML を含むテキストを送信します。continue-task コマンドは 1 回しか送信できません。
// 3. SSML 機能をサポートしているのは、cosyvoice-v3-flash、cosyvoice-v3-plus、および cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされているシステム音声のみです (例:cosyvoice-v3-flash モデルの longanyang 音声)。
import fs from 'fs';
import WebSocket from 'ws';
import { v4 as uuid } from 'uuid'; // UUID を生成するために使用します
// シンガポールと北京リージョンの API キーは異なります。API キーの取得:https://www.alibabacloud.com/help/model-studio/get-api-key
// 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください:const apiKey = "sk-xxx"
const apiKey = process.env.DASHSCOPE_API_KEY;
// シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
const url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/';
// 出力ファイルパス
const outputFilePath = 'output.mp3';
// 出力ファイルをクリアします
fs.writeFileSync(outputFilePath, '');
// WebSocket クライアントを作成します
const ws = new WebSocket(url, {
headers: {
Authorization: `bearer ${apiKey}`,
'X-DashScope-DataInspection': 'enable'
}
});
let taskStarted = false;
let taskId = uuid();
ws.on('open', () => {
console.log('Connected to WebSocket server');
// run-task コマンドを送信します
const runTaskMessage = JSON.stringify({
header: {
action: 'run-task',
task_id: taskId,
streaming: 'duplex'
},
payload: {
task_group: 'audio',
task: 'tts',
function: 'SpeechSynthesizer',
model: 'cosyvoice-v3-flash',
parameters: {
text_type: 'PlainText',
voice: 'longanyang', // 音声
format: 'mp3', // 音声フォーマット
sample_rate: 22050, // サンプルレート
volume: 50, // 音量
rate: 1, // 話速
pitch: 1, // ピッチ
enable_ssml: true // SSML を有効にするかどうか。enable_ssml を true に設定した場合、continue-task コマンドは 1 回しか送信できません。そうでない場合は、エラー "Text request limit violated, expected 1." が返されます
},
input: {}
}
});
ws.send(runTaskMessage);
console.log('run-task message sent');
});
const fileStream = fs.createWriteStream(outputFilePath, { flags: 'a' });
ws.on('message', (data, isBinary) => {
if (isBinary) {
// バイナリデータをファイルに書き込みます
fileStream.write(data);
} else {
const message = JSON.parse(data);
switch (message.header.event) {
case 'task-started':
taskStarted = true;
console.log('Task started');
// continue-task コマンドを送信します
sendContinueTasks(ws);
break;
case 'task-finished':
console.log('Task completed');
ws.close();
fileStream.end(() => {
console.log('File stream closed');
});
break;
case 'task-failed':
console.error('Task failed:', message.header.error_message);
ws.close();
fileStream.end(() => {
console.log('File stream closed');
});
break;
default:
// result-generated はここで処理できます
break;
}
}
});
function sendContinueTasks(ws) {
if (taskStarted) {
// continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
const continueTaskMessage = JSON.stringify({
header: {
action: 'continue-task',
task_id: taskId,
streaming: 'duplex'
},
payload: {
input: {
// 特殊文字はエスケープする必要があります
text: '<speak rate="2">My speaking rate is faster than a normal person's.</speak>'
}
}
});
ws.send(continueTaskMessage);
// finish-task コマンドを送信します
const finishTaskMessage = JSON.stringify({
header: {
action: 'finish-task',
task_id: taskId,
streaming: 'duplex'
},
payload: {
input: {}
}
});
ws.send(finishTaskMessage);
}
}
ws.on('close', () => {
console.log('Disconnected from WebSocket server');
});
Java
Java 開発では、Java DashScope SDK を使用します。詳細については、「Java SDK」をご参照ください。
この Java WebSocket の例では、次の依存関係が必要です:
-
Java-WebSocket -
jackson-databind
これらの依存関係は、Maven または Gradle で管理します:
pom.xml
<dependencies>
<!-- WebSocketクライアント -->
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.3</version>
</dependency>
<!-- JSON処理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
build.gradle
dependencies {
// WebSocketクライアント
implementation 'org.java-websocket:Java-WebSocket:1.5.3'
// JSON処理
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
}
Java コード:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.*;
/**
* SSML 機能の説明:
* 1. run-task コマンドを送信する際に、enable_ssml パラメーターを true に設定して SSML サポートを有効にします
* 2. continue-task コマンドで SSML を含むテキストを送信します。continue-task コマンドは 1 回のみ送信可能です
* 3. SSML 機能をサポートするのは、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声と、音声リストで SSML 対応とマークされたシステム音声のみです (例:cosyvoice-v3-flash モデルの longanyang 音声)
*/
public class TTSWebSocketClient extends WebSocketClient {
private final String taskId = UUID.randomUUID().toString();
private final String outputFile = "output_" + System.currentTimeMillis() + ".mp3";
private boolean taskFinished = false;
public TTSWebSocketClient(URI serverUri, Map<String, String> headers) {
super(serverUri, headers);
}
@Override
public void onOpen(ServerHandshake serverHandshake) {
System.out.println("Connection established");
// run-task コマンドを送信します
// enable_ssml が true に設定されている場合、continue-task コマンドは 1 回しか送信できません。そうでない場合、エラー "Text request limit violated, expected 1." が返されます
String runTaskCommand = "{ \"header\": { \"action\": \"run-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"task_group\": \"audio\", \"task\": \"tts\", \"function\": \"SpeechSynthesizer\", \"model\": \"cosyvoice-v3-flash\", \"parameters\": { \"text_type\": \"PlainText\", \"voice\": \"longanyang\", \"format\": \"mp3\", \"sample_rate\": 22050, \"volume\": 50, \"rate\": 1, \"pitch\": 1, \"enable_ssml\": true }, \"input\": {} }}";
send(runTaskCommand);
}
@Override
public void onMessage(String message) {
System.out.println("Received message from server: " + message);
try {
// JSON メッセージを解析します
Map<String, Object> messageMap = new ObjectMapper().readValue(message, Map.class);
if (messageMap.containsKey("header")) {
Map<String, Object> header = (Map<String, Object>) messageMap.get("header");
if (header.containsKey("event")) {
String event = (String) header.get("event");
if ("task-started".equals(event)) {
System.out.println("Received task-started event from server");
// continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
// 特殊文字はエスケープする必要があります
sendContinueTask("<speak rate=\\\"2\\\">私の話速は通常の人より速いです。</speak>");
// finish-task コマンドを送信します
sendFinishTask();
} else if ("task-finished".equals(event)) {
System.out.println("Received task-finished event from server");
taskFinished = true;
closeConnection();
} else if ("task-failed".equals(event)) {
System.out.println("Task failed: " + message);
closeConnection();
}
}
}
} catch (Exception e) {
System.err.println("An error occurred: " + e.getMessage());
}
}
@Override
public void onMessage(ByteBuffer message) {
System.out.println("Received binary audio data, size: " + message.remaining());
try (FileOutputStream fos = new FileOutputStream(outputFile, true)) {
byte[] buffer = new byte[message.remaining()];
message.get(buffer);
fos.write(buffer);
System.out.println("Audio data written to local file " + outputFile);
} catch (IOException e) {
System.err.println("Failed to write audio data to local file: " + e.getMessage());
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
System.out.println("Connection closed: " + reason + " (" + code + ")");
}
@Override
public void onError(Exception ex) {
System.err.println("Error: " + ex.getMessage());
ex.printStackTrace();
}
private void sendContinueTask(String text) {
String command = "{ \"header\": { \"action\": \"continue-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": { \"text\": \"" + text + "\" } }}";
send(command);
}
private void sendFinishTask() {
String command = "{ \"header\": { \"action\": \"finish-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": {} }}";
send(command);
}
private void closeConnection() {
if (!isClosed()) {
close();
}
}
public static void main(String[] args) {
try {
// シンガポールリージョンと北京リージョンでは API キーが異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
// 環境変数を設定していない場合は、次の行を実際の Model Studio API キーに置き換えてください: String apiKey = "sk-xxx"
String apiKey = System.getenv("DASHSCOPE_API_KEY");
if (apiKey == null || apiKey.isEmpty()) {
System.err.println("Please set the DASHSCOPE_API_KEY environment variable");
return;
}
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "bearer " + apiKey);
// シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/"), headers);
client.connect();
while (!client.isClosed() && !client.taskFinished) {
Thread.sleep(1000);
}
} catch (Exception e) {
System.err.println("Failed to connect to WebSocket service: " + e.getMessage());
e.printStackTrace();
}
}
}
Python
Python 開発には、Python DashScope SDK を使用します。詳細については、「Python SDK」をご参照ください。
以下は Python WebSocket の例です。実行する前に、必要な依存関係をインストールしてください:
pip uninstall websocket-client
pip uninstall websocket
pip install websocket-client
Python ファイルに "websocket.py" という名前を付けないでください。これにより命名の競合が発生し、エラー (AttributeError: module 'websocket' has no attribute 'WebSocketApp'. Did you mean: 'WebSocket'?) が発生します。
# SSML 機能の説明:
# 1. run-task コマンドを送信する際に、enable_ssml パラメーターを true に設定して SSML サポートを有効にします
# 2. SSML を含むテキストを continue-task コマンドで送信します。continue-task コマンドは1回しか使用できません
# 3. cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされたシステム音声のみが SSML 機能をサポートします (例: cosyvoice-v3-flash モデルの longanyang 音声)
import websocket
import json
import uuid
import os
import time
class TTSClient:
def __init__(self, api_key, uri):
"""
TTSClient インスタンスを初期化します
パラメーター:
api_key (str): 認証用の API キー
uri (str): WebSocket サービスアドレス
"""
self.api_key = api_key # API キーに置き換えてください
self.uri = uri # WebSocket アドレスに置き換えてください
self.task_id = str(uuid.uuid4()) # 一意のタスク ID を生成します
self.output_file = f"output_{int(time.time())}.mp3" # 出力音声ファイルパス
self.ws = None # WebSocketApp インスタンス
self.task_started = False # task-started を受信したかどうか
self.task_finished = False # task-finished / task-failed を受信したかどうか
def on_open(self, ws):
"""
WebSocket 接続が確立されたときのコールバック
run-task コマンドを送信して音声合成タスクを開始します
"""
print("WebSocket connected")
# run-task コマンドを構築します
run_task_cmd = {
"header": {
"action": "run-task",
"task_id": self.task_id,
"streaming": "duplex"
},
"payload": {
"task_group": "audio",
"task": "tts",
"function": "SpeechSynthesizer",
"model": "cosyvoice-v3-flash",
"parameters": {
"text_type": "PlainText",
"voice": "longanyang",
"format": "mp3",
"sample_rate": 22050,
"volume": 50,
"rate": 1,
"pitch": 1,
# enable_ssml が True に設定されている場合、continue-task コマンドは1回しか送信できません。そうでない場合、エラー "Text request limit violated, expected 1." が返されます
"enable_ssml": True
},
"input": {}
}
}
# run-task コマンドを送信します
ws.send(json.dumps(run_task_cmd))
print("run-task command sent")
def on_message(self, ws, message):
"""
メッセージを受信したときのコールバック
テキストメッセージとバイナリメッセージを別々に処理します
"""
if isinstance(message, str):
# JSON テキストメッセージを処理します
try:
msg_json = json.loads(message)
print(f"Received JSON message: {msg_json}")
if "header" in msg_json:
header = msg_json["header"]
if "event" in header:
event = header["event"]
if event == "task-started":
print("Task started")
self.task_started = True
# continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
# 特殊文字はエスケープする必要があります
self.send_continue_task("<speak rate=\"2\">私の話速は通常の人より速いです。</speak>")
# continue-task が送信された後、finish-task を送信します
self.send_finish_task()
elif event == "task-finished":
print("Task completed")
self.task_finished = True
self.close(ws)
elif event == "task-failed":
error_msg = msg_json.get("error_message", "Unknown error")
print(f"Task failed: {error_msg}")
self.task_finished = True
self.close(ws)
except json.JSONDecodeError as e:
print(f"JSON parsing failed: {e}")
else:
# バイナリメッセージ (音声データ) を処理します
print(f"Received binary message, size: {len(message)} bytes")
with open(self.output_file, "ab") as f:
f.write(message)
print(f"音声データをローカルファイル {self.output_file} に書き込みました")
def on_error(self, ws, error):
"""エラーが発生したときのコールバック"""
print(f"WebSocket error: {error}")
def on_close(self, ws, close_status_code, close_msg):
"""接続が閉じたときのコールバック"""
print(f"WebSocket closed: {close_msg} ({close_status_code})")
def send_continue_task(self, text):
"""合成するテキストコンテンツを含む continue-task コマンドを送信します"""
cmd = {
"header": {
"action": "continue-task",
"task_id": self.task_id,
"streaming": "duplex"
},
"payload": {
"input": {
"text": text
}
}
}
self.ws.send(json.dumps(cmd))
print(f"continue-task command sent, text content: {text}")
def send_finish_task(self):
"""音声合成タスクを終了するために finish-task コマンドを送信します"""
cmd = {
"header": {
"action": "finish-task",
"task_id": self.task_id,
"streaming": "duplex"
},
"payload": {
"input": {}
}
}
self.ws.send(json.dumps(cmd))
print("finish-task command sent")
def close(self, ws):
"""接続を能動的に閉じます"""
if ws and ws.sock and ws.sock.connected:
ws.close()
print("接続を能動的に閉じました")
def run(self):
"""WebSocket クライアントを開始します"""
# リクエストヘッダー (認証) を設定します
header = {
"Authorization": f"bearer {self.api_key}",
"X-DashScope-DataInspection": "enable"
}
# WebSocketApp インスタンスを作成します
self.ws = websocket.WebSocketApp(
self.uri,
header=header,
on_open=self.on_open,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close
)
print("WebSocket メッセージを待機中...")
self.ws.run_forever() # 永続的な接続リスナーを開始します
# 使用例
if __name__ == "__main__":
# シンガポールリージョンと北京リージョンでは API キーが異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
# 環境変数を設定していない場合は、次の行を実際の Model Studio API キーに置き換えてください: API_KEY = "sk-xxx"
API_KEY = os.environ.get("DASHSCOPE_API_KEY")
# シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
SERVER_URI = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/"
client = TTSClient(API_KEY, SERVER_URI)
client.run()
タグ リファレンス
Alibaba Cloud の SSML 実装は、W3C の SSML 1.0 仕様に基づいています。すべての標準タグがサポートされているわけではなく、本サービスでは、本番シナリオで最も一般的に使用されるタグを実装しています。
-
SSML を使用する場合、すべてのテキストコンテンツは
<speak></speak>タグで囲む必要があります。 -
複数の
<speak>タグを連続して使用すること (例:<speak></speak><speak></speak>) はできますが、ネスト (例:<speak><speak></speak></speak>) はサポートされていません。 -
タグ内のテキストに XML の特殊文字が含まれている場合は、次のようにエスケープしてください:
-
"(二重引用符) →" -
'(単一引用符/アポストロフィ) →' -
&(アンパサンド) →& -
<(小なり記号) →< -
>(大なり記号) →>
-
<speak>:ルート要素
-
説明
<speak>は、すべての SSML コンテンツのルート要素です。すべてのテキストは<speak></speak>タグで囲む必要があります。 -
構文
<speak>SSML 処理が必要なテキスト</speak> -
属性
属性
型
必須
説明
voice
String
いいえ
音声を指定します。
この属性は、API リクエストの
voiceパラメーターよりも優先されます。-
有効な値:特定の音声名。詳細については、「cosyvoice-v2 voices」をご参照ください。
-
例:
<speak voice="longcheng_v2"> 私は男性の声です。 </speak>
rate
String
いいえ
読み上げ速度を指定します。この属性は、API リクエストの
speech_rateパラメーターよりも優先されます。-
有効な値:0.5 から 2 までの小数 (両端を含む)
-
デフォルト値:1
-
1 より大きい値は読み上げ速度を速めます
-
1 より小さい値は読み上げ速度を遅めます
-
-
例:
<speak rate="2"> 私の読み上げ速度は通常より速いです。 </speak>
pitch
String
いいえ
ピッチを指定します。この属性は、API リクエストの
pitch_rateパラメーターよりも優先されます。-
有効な値:0.5 から 2 までの小数 (両端を含む)
-
デフォルト値:1
-
1 より大きい値はピッチを高くします
-
1 より小さい値はピッチを低くします
-
-
例:
<speak pitch="0.5"> しかし、私のピッチは他の人より低いです。 </speak>
volume
String
いいえ
音量を指定します。この属性は、API リクエストの
volumeパラメーターよりも優先されます。-
有効な値:0 から 100 までの整数 (両端を含む)
-
デフォルト値:50
-
50 より大きい値は音量を大きくします
-
50 より小さい値は音量を小さくします
-
-
例:
<speak volume="80"> 私の音量もとても大きいです。 </speak>
effect
String
いいえ
オーディオエフェクトを指定します。
-
有効な値:
-
robot:ロボット音声エフェクト
-
lolita:ロリータ音声エフェクト
-
lowpass:ローパスフィルターエフェクト
-
echo:エコーエフェクト
-
eq:イコライザー (高度)
-
lpfilter:ローパスフィルター (高度)
-
hpfilter:ハイパスフィルター (高度)
説明-
eq、lpfilter、hpfilter は高度なエフェクトタイプです。
effectValueパラメーターを使用して特定のエフェクトをカスタマイズします。 -
各 SSML タグは、1 つのエフェクトのみに対応します。複数の
effect属性を同時に設定することはできません。 -
オーディオエフェクトを有効にすると、合成レイテンシーが増加します。
-
-
例:
<speak effect="robot"> ロボットのウォーリーは好きですか? </speak>
effectValue
String
いいえ
オーディオエフェクト (
effectパラメーター) の具体的な動作を設定します。eq、lpfilter、hpfilter の 3 つの高度なエフェクトタイプに適用されます。-
有効な値:
-
eq(イコライザー):システムはデフォルトで 8 つの周波数帯をサポートし、それぞれ次の周波数に対応します:["40 Hz","100 Hz", "200 Hz", "400 Hz", "800 Hz", "1600 Hz", "4000 Hz", "12000 Hz"]
各帯域の帯域幅は 1.0q です。
effectValueパラメーターを使用して、各帯域のゲインを指定します。パラメーターは、-20 から 20 までの 8 つの整数をスペースで区切った文字列です。値0は、その周波数のゲイン調整がないことを意味します。例:
effectValue="1 1 1 1 1 1 1 1" -
lpfilter(ローパスフィルター):カットオフ周波数を指定します。有効な値:(0, target_sample_rate/2] の範囲内の整数。例:effectValue="800" -
hpfilter(ハイパスフィルター):カットオフ周波数を指定します。有効な値:(0, target_sample_rate/2] の範囲内の整数。例:effectValue="1200"
-
-
例:
<speak effect="eq" effectValue="1 -20 1 1 1 1 20 1"> ロボットのウォーリーは好きですか? </speak> <speak effect="lpfilter" effectValue="1200"> ロボットのウォーリーは好きですか? </speak> <speak effect="hpfilter" effectValue="1200"> ロボットのウォーリーは好きですか? </speak>
bgm
String
いいえ
合成音声に BGM を追加します。音声ファイルは Alibaba Cloud OSS に保存する必要があり (「オブジェクトのアップロード」をご参照ください)、バケットには少なくともパブリック読み取りアクセス権が必要です。
BGM の URL に XML の特殊文字 (
&、<、>など) が含まれている場合は、エスケープしてください。-
音声要件:
BGM ファイルのサイズに上限はありませんが、ファイルサイズが大きいほどダウンロードに時間がかかります。合成音声が BGM より長い場合、BGM は自動的にループします。
-
サンプルレート:16 kHz
-
チャネル:モノラル
-
フォーマット:WAV
WAV 以外のファイルを変換するには、
ffmpegを使用します:ffmpeg -i input_audio -acodec pcm_s16le -ac 1 -ar 16000 output.wav -
ビット深度:16 ビット
-
-
例:
<speak bgm="http://nls.alicdn.com/bgm/2.wav" backgroundMusicVolume="30" rate="0.8" volume="40"> <break time="2s"/> 陰崖の古木は霧に包まれ <break time="700ms"/> 雨音はなお竹林に響く <break time="700ms"/> 我知る、綿は国の計に資すると <break time="700ms"/> 綿州の景色は常に憐れむべし <break time="2s"/> </speak>
重要アップロードされた音声の著作権については、お客様が責任を負うものとします。
backgroundMusicVolume
String
いいえ
BGM の音量を指定します。この属性は
bgm属性と一緒に使用します。 -
-
タグの関係
<speak> タグは、テキストと次の子タグを含むことができます:
-
その他の例
-
属性なし
<speak> SSML タグが必要なテキスト </speak> -
属性の組み合わせ (スペース区切り)
<speak rate="1.5" pitch="0.8" volume="80"> すべてを組み合わせると、私の声はこんな風に聞こえます。 </speak>
-
<break>:ポーズ期間の制御
-
説明
音声合成中に無音のポーズを挿入し、会話における自然な間をシミュレートします。時間単位として、秒 (s) とミリ秒 (ms) をサポートします。
-
構文
# 属性なし <break/> # time 属性あり <break time="string"/> -
属性
説明属性のない <break> タグは、デフォルトで 1 秒間のポーズになります。
属性
型
必須
説明
time
文字列
いいえ
ポーズ期間を秒またはミリ秒で指定します (例: "2s" または "50ms")。
-
有効な値:
-
秒 (s) 単位:1 から 10 までの整数 (両端の値を含む)
-
ミリ秒 (ms) 単位:50 から 10000 までの整数 (両端の値を含む)
-
-
例:
<speak> 目を閉じて、休憩してください。<break time="500ms"/>はい、目を開けてください。 </speak>
重要複数の
<break>タグが連続して使用された場合、合計ポーズ期間は各期間の合計になります。合計が 10 秒を超える場合、10 秒に短縮されます。例えば、次の SSML では、
<break>の累積ポーズ期間は 15 秒です。これは 10 秒の制限を超えるため、実際のポーズは 10 秒に短縮されます:<speak> 目を閉じて、休憩してください。<break time="5s"/><break time="5s"/><break time="5s"/>はい、目を開けてください。 </speak> -
-
タグの関係
<break> は空要素であり、子要素を含むことはできません。
<sub>:テキストの置換
-
説明
指定されたテキストを、音声での読み上げに適したコンテンツに置き換えます。例えば、「W3C」を「World Wide Web コンソーシアム」として読み上げます。
-
構文
<sub alias="string"></sub> -
属性
属性
タイプ
必須
説明
エイリアス
文字列
はい
読み上げる代替テキストを指定します。
例:
<speak> <sub alias="World Wide Web コンソーシアム">W3C</sub> </speak> -
タグの関係
<sub> タグは、プレーンテキストのみを含めることができます。
<phoneme>:発音の指定 (ピンイン/発音記号)
-
説明
テキストの発音を正確に制御します。中国語テキストではピンイン表記、英語テキストでは CMU 発音記号をサポートします。これは、多音字の曖昧さを解消したり、外国語の発音を処理したりするのに役立ちます。
-
構文
<phoneme alphabet="文字列" ph="文字列">テキスト</phoneme> -
属性
属性
タイプ
必須
説明
alphabet
文字列
はい
発音のタイプを指定します:ピンイン (中国語の場合) または発音記号 (英語の場合)。
有効な値:
-
"py": ピンイン
-
"cmu": 発音記号。CMU 発音辞書 をご参照ください
ph
文字列
はい
正確なピンインまたは発音記号を指定します。使用ルール:
-
複数の文字に対応するピンインは、スペースで区切ります。ピンインの数は文字数と一致する必要があります。
-
各ピンインは、発音と声調番号で構成されます。声調番号の範囲は
1から5で、5は軽声を表します。 -
例:
<speak> どうやって <phoneme alphabet="cmu" ph="S AY N">sin</phoneme> を綴りますか? </speak>
-
-
タグの関係
<phoneme> タグには、プレーンテキストのみを含めることができます。
<soundEvent>:外部音声の挿入 (着信音、猫の鳴き声など)
-
説明
音声の特定のポイントに効果音ファイル (アラート音や環境音など) を挿入して、オーディオ出力を豊かにします。
-
構文
<soundEvent src="URL"/> -
属性
属性
タイプ
必須
説明
src
文字列
必須
外部音声ファイルの URL を指定します。
音声ファイルは Alibaba Cloud OSS に保存する必要があり (詳細については、「オブジェクトのアップロード」をご参照ください)、バケットには少なくともパブリック読み取りアクセス権限が必要です。URL に XML 特殊文字 (
&、<、>など) が含まれる場合は、エスケープしてください。-
音声要件:
-
サンプリングレート:16 kHz
-
チャンネル:モノラル
-
フォーマット:WAV
WAV 形式以外のファイルを変換するには、
ffmpegを使用します:ffmpeg -i input_audio -acodec pcm_s16le -ac 1 -ar 16000 output.wav
-
ファイルサイズ:最大 2 MB
-
ビット深度:16 ビット
-
-
例:
<speak> 馬が驚き、<soundEvent src="http://nls.alicdn.com/sound-event/horse-neigh.wav"/>人々は避けるために散らばりました。 </speak>
重要アップロードした音声の著作権について、法的責任を負うものとします。
-
-
タグの関係
<soundEvent> は空要素であり、子要素を含めることはできません。
<say-as> :テキスト解釈の設定 (数値、日付、電話番号など)
-
説明
テキストのコンテンツタイプ (数値、日付、電話番号など) を指定すると、システムはそのタイプに応じた適切なルールに従ってテキストを読み上げます。
-
構文:
<say-as interpret-as="string">Text</say-as> -
属性
属性
タイプ
必須
説明
interpret-as
文字列
はい
タグ内のテキストのコンテンツタイプを指定します。
有効な値:
-
cardinal :基数。標準的な整数または小数として読み上げます
-
digits :各数字を個別に読み上げます (例:123 は「いち に さん」と読み上げます)
-
telephone :標準的な電話番号形式で数字を 1 つずつ読み上げます
-
name :標準的な名前の発音ルールで読み上げます
-
address :標準的な住所の発音ルールで読み上げます
-
id :標準的な識別子 (アカウント名、ニックネーム) の発音ルールで読み上げます
-
characters :テキスト内の各文字を個別に読み上げます
-
punctuation :各句読点の名前を読み上げます
-
date :標準的な日付の発音ルールで読み上げます
-
time :標準的な時刻の発音ルールで読み上げます
-
currency :標準的な金額の発音ルールで読み上げます
-
measure :標準的な単位の発音ルールで読み上げます
-
-
各 <say-as> タイプでサポートされる範囲
-
基数
フォーマット
例
英語の出力
注記
数字列
145
one hundred forty five
整数の範囲:最大 13 桁までの正負の整数、[-999999999999,999999999999]。
小数の範囲:小数点以下の桁数に制限はありませんが、10 桁以下を推奨します。
先頭が 0 の数字列
0145
one hundred forty five
マイナス符号 + 数字列
-145
minus one hundred forty five
3桁ごとにカンマで区切られた数字列
60,000
sixty thousand
マイナス符号 + カンマ区切りの数字列
-208,000
minus two hundred eight thousand
数字列 + 小数点 + 0
12.00
twelve
数字列 + 小数点 + 数字列
12.34
twelve point three four
カンマ区切りの数字列 + 小数点 + 数字列
1,000.1
one thousand point one
マイナス符号 + 数字列 + 小数点 + 数字列
-12.34
minus twelve point three four
マイナス符号 + カンマ区切りの数字列 + 小数点 + 数字列
-1,000.1
minus one thousand point one
(カンマ区切りの) 数字列 + ハイフン + (カンマ区切りの) 数字列
1-1,000
one to one thousand
その他のデフォルトの読み上げ
012.34
twelve point three four
なし
1/2
one half
-3/4
minus three quarters
5.1/6
five point one over six
-3 1/2
minus three and a half
1,000.3^3
one thousand point three to the power of three
3e9.1
three times ten to the power of nine point one
23.10%
twenty three point one percent
-
数字
フォーマット
例
英語の出力
注
数字列
12034
one two zero three four
数字列の長さに特定の制限はありませんが、20 桁以下を推奨します。
数字列がスペースまたはハイフンでグループ化されている場合、グループ間に休止を示すカンマが挿入されます。最大 5 つのグループまで対応しています。
数字列 + スペース/ハイフン + 数字列 + スペース/ハイフン + 数字列 + スペース/ハイフン + 数字列
1-23-456 7890
one, two three, four five six, seven eight nine zero
-
電話
フォーマット
例
英語の出力
備考
数字文字列
12034
one two oh three four
数字文字列の長さに特定の制限はありませんが、20 桁以下を推奨します。数字文字列がスペースまたはハイフンでグループ化されている場合、グループ間にカンマによるポーズが挿入されます。5 つのグループまでサポートします。
数字文字列 + スペース/ハイフン + 数字文字列 + スペース/ハイフン + 数字文字列
1-23-456 7890
one, two three, four five six, seven eight nine oh
プラス記号 + 数字文字列 + スペース/ハイフン + 数字文字列
+43-211-0567
plus four three, two one one, oh five six seven
左括弧 + 数字文字列 + 右括弧 + スペース + 数字文字列 + スペース/ハイフン + 数字文字列
(21) 654-3210
two one, six five four, three two one oh
-
アドレス
このタグは英語のテキストにはサポートされていません。
-
ID
英語のテキストでは、このタグは文字タグと同じように機能します。
-
文字列
フォーマット
例
英語の出力
備考
文字列
*b+3$.c-0'=α
asterisk B plus three dollar dot C dash zero apostrophe equals alpha
中国語の文字、大文字と小文字、数字の 0~9、および一部の全角文字と半角文字をサポートします。
出力のスペースは文字間のポーズを表すもので、各文字が個別に読み上げられることを意味します。
タグ内のテキストに XML 特殊文字が含まれる場合は、エスケープしてください。
-
句読点
英語のテキストでは、このタグは文字タグと同様に機能します。
-
date
フォーマット
例
英語の出力
備考
4 桁/2 桁、または 4 桁-2 桁
2000/01
two thousand, oh one
年をまたぐ表記。
1900-01
nineteen hundred, oh one
2001-02
twenty oh one, oh two
2019-20
twenty nineteen, twenty
1998-99
nineteen ninety eight, ninety nine
1999-00
nineteen ninety nine, oh oh
1 または 2 で始まる 4 桁の数値
2000
two thousand
4 桁の年。
1900
nineteen hundred
1905
nineteen oh five
2021
twenty twenty-one
曜日の範囲 (ハイフン)
または
曜日の範囲 (チルダ)
または
曜日&曜日
mon-wed
monday to wednesday
曜日の範囲を指定するテキストに XML の特殊文字が含まれる場合は、文字をエスケープしてください。
tue~fri
tuesday to friday
sat&sun
saturday and sunday
DD-DD MMM, YYYY
または
DD~DD MMM, YYYY
または
DD&DD MMM, YYYY
19-20 Jan, 2000
the nineteen to the twentieth of january two thousand
DD:2 桁の日。MMM:3 文字の月の略語または月名。YYYY:1 または 2 で始まる 4 桁の年。
01 ~ 10 Jul, 2020
the first to the tenth of july twenty
05&06 Apr, 2009
the fifth and the sixth of april two thousand nine
MMM DD-DD
または
MMM DD~DD
または
MMM DD&DD
Feb 01 - 03
february the first to the third
MMM:3 文字の月の略語または月名。DD:2 桁の日。
Aug 10~20
august the tenth to the twentieth
Dec 11&12
december the eleventh and the twelfth
MMM-MMM
または
MMM~MMM
または
MMM&MMM
Jan-Jun
january to june
MMM:3 文字の月の略語または月名。
jul ~ dec
july to december
sep&oct
september and october
YYYY-YYYY
または
YYYY~YYYY
1990 - 2000
nineteen ninety to two thousand
YYYY:1 または 2 で始まる 4 桁の年。
2001~2021
two thousand one to twenty twenty-one
WWW DD MMM YYYY
Sun 20 Nov 2011
sunday the twentieth of november twenty eleven
WWW は曜日の 3 文字の略語または曜日名、DD は 2 桁の日、MMM は月の 3 文字の略語または月名、MM は 2 桁の月 (または月の 3 文字の略語または月名) 、YYYY は 1 または 2 で始まる 4 桁の年です。
WWW DD MMM
Sun 20 Nov
sunday the twentieth of november
WWW MMM DD YYYY
Sun Nov 20 2011
sunday november the twentieth twenty eleven
WWW MMM DD
Sun Nov 20
sunday november the twentieth
WWW YYYY-MM-DD
Sat 2010-10-01
saturday october the first twenty ten
WWW YYYY/MM/DD
Sat 2010/10/01
saturday october the first twenty ten
WWW MM/DD/YYYY
Sun 11/20/2011
sunday november the twentieth twenty eleven
MM/DD/YYYY
11/20/2011
november the twentieth twenty eleven
YYYY
1998
nineteen ninety eight
その他のデフォルトの読み上げ
10 Mar, 2001
the tenth of march two thousand one
なし
10 Mar
the tenth of march
Mar 2001
march two thousand one
Fri. 10/Mar/2001
friday the tenth of march two thousand one
Mar 10th, 2001
march the tenth two thousand one
Mar 10
march the tenth
2001/03/10
march the tenth two thousand one
2001-03-10
march the tenth two thousand one
2000s
two thousands
2010's
twenty tens
1900's
nineteen hundreds
1990s
nineteen nineties
-
時刻
フォーマット
例
英語の出力
備考
HH:MM AM または PM
09:00 AM
nine A M
HH: 時 (1桁または2桁)。MM: 分 (2桁)。AM/PM: 午前/午後。
09:03 PM
nine oh three P M
09:13 p.m.
nine thirteen p m
HH:MM
21:00
twenty one hundred
HHMM
0100
one hundred
時刻の範囲
8:00 am - 05:30 pm
eight a m to five thirty p m
一般的な時刻フォーマットと範囲をサポートします。
7:05~10:15 AM
seven oh five to ten fifteen A M
09:00-13:00
nine oclock to thirteen hundred
-
currency
フォーマット
例
英語出力
備考
数値 + 通貨識別子
1.00 RMB
one yuan
サポートされている数値フォーマット:整数、小数、カンマ区切りの国際表記。
サポートされている通貨識別子:
CN¥ (元)
CNY (元)
RMB (元)
AUD (オーストラリアドル)
CAD (カナダドル)
CHF (スイスフラン)
DKK (デンマーククローネ)
EUR (ユーロ)
GBP (英ポンド)
HKD (香港ドル)
JPY (日本円)
NOK (ノルウェークローネ)
SEK (スウェーデンクローナ)
SGD (シンガポールドル)
USD (米ドル)
2.02 CNY
two point zero two yuan
1,000.23 CN¥
one thousand point two three yuan
1.01 SGD
one singapore dollar and one cent
2.01 CAD
two canadian dollars and one cent
3.1 HKD
three hong kong dollars and ten cents
1,000.00 EUR
one thousand euros
通貨識別子 + 数値
US$ 1.00
one US dollar
サポートされている数値フォーマット:整数、小数、カンマ区切りの国際表記。
サポートされている通貨識別子:
US$ (米ドル)
CA$ (カナダドル)
AU$ (オーストラリアドル)
SG$ (シンガポールドル)
HK$ (香港ドル)
C$ (カナダドル)
A$ (オーストラリアドル)
$ (ドル)
£ (ポンド)
€ (ユーロ)
CN¥ (元)
CNY (元)
RMB (元)
AUD (オーストラリアドル)
CAD (カナダドル)
CHF (スイスフラン)
DKK (デンマーククローネ)
EUR (ユーロ)
GBP (英ポンド)
HKD (香港ドル)
JPY (日本円)
NOK (ノルウェークローネ)
SEK (スウェーデンクローナ)
SGD (シンガポールドル)
USD (米ドル)
$0.01
one cent
JPY 1.01
one japanese yen and one sen
£1.1
one pound and ten pence
€2.01
two euros and one cent
USD 1,000
one thousand united states dollars
数値 + 分類子 + 通貨識別子
または
通貨識別子 + 数値 + 分類子
1.23 Tn RMB
one point two three trillion yuan
サポートされている分類子フォーマット:
サウザンド
ミリオン
ビリオン
トリリオン
Mil (ミリオン)
mil (ミリオン)
Bil (ビリオン)
bil (ビリオン)
MM (ミリオン)
Bn (ビリオン)
bn (ビリオン)
Tn (トリリオン)
tn (トリリオン)
K (サウザンド)
k (サウザンド)
M (ミリオン)
m (ミリオン)
$1.2 K
one point two thousand dollars
-
測定単位
フォーマット
例
英語出力
備考
数値 + 測定単位
1.0 kg
one kilogram
サポートされている数値フォーマットは、整数、小数、およびカンマ区切りの国際表記です。
一般的な単位の省略形をサポートします。
1,234.01 km
one thousand two hundred thirty four point zero one kilometres.
測定単位
mm2
square millimetre
-
次の表では、一般的な記号が <say-as> でどのように読み上げられるかを示します。
記号
読み方
!
感嘆符
左ダブルクォーテーション
#
ハッシュ
$
ドル記号
%
パーセント
&
アンパサンド
‘
左シングルクォーテーション
(
左括弧
)
右括弧
*
アスタリスク
+
プラス
,
コンマ
-
ハイフン
.
ドット
/
スラッシュ
:
コロン
;
セミコロン
<
小なり
=
イコール
>
大なり
?
疑問符
@
アットマーク
[
左角括弧
\
バックスラッシュ
]
右角括弧
^
カレット
_
アンダースコア
バッククォート
{
左波括弧
|
縦棒
}
右波括弧
~
チルダ
!
感嘆符
左ダブルクォーテーション
右ダブルクォーテーション
‘
左シングルクォーテーション
’
右シングルクォーテーション
(
左括弧
)
右括弧
,
コンマ
。
句点
—
全角ダッシュ
:
コロン
;
セミコロン
?
疑問符
、
読点
…
省略記号
……
省略記号
《
左二重山括弧
》
右二重山括弧
¥
元記号
≥
大なりイコール
≤
小なりイコール
≠
ノットイコール
≈
ニアリーイコール
±
プラスマイナス
×
乗算記号
π
パイ
Α
アルファ
Β
ベータ
Γ
ガンマ
Δ
デルタ
Ε
イプシロン
Ζ
ゼータ
Θ
シータ
Ι
イオタ
Κ
カッパ
Λ
ラムダ
Μ
ミュー
Ν
ニュー
Ξ
クサイ
Ο
オミクロン
∏
パイ
Ρ
ロー
Σ
シグマ
Τ
タウ
Υ
ウプシロン
Φ
ファイ
Χ
カイ
Ψ
プサイ
Ω
オメガ
α
アルファ
β
ベータ
γ
ガンマ
δ
デルタ
ε
イプシロン
ζ
ゼータ
η
イータ
θ
シータ
ι
イオタ
κ
カッパ
λ
ラムダ
μ
ミュー
ν
ニュー
ξ
クサイ
ο
オミクロン
π
パイ
ρ
ロー
σ
シグマ
τ
タウ
υ
ウプシロン
φ
ファイ
χ
カイ
ψ
プサイ
ω
オメガ
-
次の表に、<say-as> を使用した場合の一般的な測定単位の読み上げ方を示します。
フォーマット
カテゴリ
英語の例
省略形
長さ
nm (nanometre), μm (micrometre), mm (millimetre), cm (centimetre), m (metre), km (kilometre), ft (foot), in (inch)
面積
cm² (square centimetre), ㎡ (square metre), km2 (square kilometre), SqFt (square foot)
体積
cm³ (cubic centimetre), m³ (cubic metre), km3 (cubic kilometre), mL (millilitre), L (litre), gal (gallon)
重量
μg (microgram), mg (milligram), g (gram), kg (kilogram)
時間
min (minute), sec (second), ms (millisecond)
電磁気
μA (microamp), mA (milliamp), Hz (hertz), kHz (kilohertz), MHz (megahertz), GHz (gigahertz), V (volt), kV (kilovolt), kWh (kilowatt hour)
音
dB (decibel)
気圧
Pa (pascal), kPa (kilopascal), MPa (megapascal)
その他の一般的な単位
上記以外の単位もサポートしています。例: tsp (teaspoon)、rpm (回転/分)、KB (キロバイト)、mmHg (millimetre of mercury)。
-
-
タグの関係
<say-as> タグは、テキストと <vhml/> を含めることができます。
-
例
-
基数
<speak> <say-as interpret-as="cardinal">12345</say-as> </speak><speak> <say-as interpret-as="cardinal">10234</say-as> </speak> -
数字
<speak> <say-as interpret-as="digits">12345</say-as> </speak><speak> <say-as interpret-as="digits">10234</say-as> </speak> -
電話番号
<speak> <say-as interpret-as="telephone">12345</say-as> </speak><speak> <say-as interpret-as="telephone">10234</say-as> </speak> -
名前
<speak> 彼女の旧姓は<say-as interpret-as="name">田中花子</say-as>です </speak> -
住所
<speak> <say-as interpret-as="address">東京都千代田区丸の内一丁目一番一号</say-as> </speak> -
ID
<speak> <say-as interpret-as="id">myid_1998</say-as> </speak> -
文字
<speak> <say-as interpret-as="characters">ギリシャ文字 αβ</say-as> </speak><speak> <say-as interpret-as="characters">*b+3.c$=α</say-as> </speak> -
句読点
<speak> <say-as interpret-as="punctuation"> -./:;</say-as> </speak> -
日付
<speak> <say-as interpret-as="date">1000-10-10</say-as> </speak><speak> <say-as interpret-as="date">10-01-2020</say-as> </speak> -
時刻
<speak> <say-as interpret-as="time">5:00am</say-as> </speak><speak> <say-as interpret-as="time">0500</say-as> </speak> -
通貨
<speak> <say-as interpret-as="currency">13,000,000.00RMB</say-as> </speak><speak> <say-as interpret-as="currency">$1,000.01</say-as> </speak> -
単位
<speak> <say-as interpret-as="measure">100m12cm6mm</say-as> </speak><speak> <say-as interpret-as="measure">1,000.01kg</say-as> </speak>
-