音声合成マークアップ言語 (SSML) は、音声合成のための XML ベースのマークアップ言語です。これにより、大規模な音声合成モデルがリッチテキストコンテンツを処理できるようになり、話速、ピッチ、ポーズ、音量などの音声の特徴を詳細にコントロールできます。また、背景音楽を追加して、より表現力豊かな音声効果を作成することもできます。このトピックでは、CosyVoice の SSML の特徴とその使用方法について説明します。
制限事項
モデル:SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、および cosyvoice-v2 モデルのみをサポートします。
音声:SSML は、クローン音声と、音声リストで SSML をサポートするとマークされているシステム音声のみをサポートします。
API:SSML は一部の API のみをサポートします。
Java SDK (バージョン 2.20.3 以降):SSML は、非ストリーミング呼び出しと単方向ストリーミング呼び出しでのみサポートされます。詳細については、「SSML サポート - Java SDK」をご参照ください。
Python SDK (バージョン 1.23.4 以降):SSML は、非ストリーミング呼び出しと単方向ストリーミング呼び出しでのみサポートされます。詳細については、「SSML サポート - Python SDK」をご参照ください。
WebSocket API:run-task 命令を送信する際に、
enable_ssmlパラメーターをtrueに設定し、continue-task 命令は一度だけ送信します。詳細については、「SSML サポート - WebSocket API」をご参照ください。
クイックスタート
コードを実行する前に、次の手順を完了してください:
SDK のインストール (Java/Python SDK のサンプルを実行する場合)
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. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、および cosyvoice-v2 モデルのクローン音声、
* および音声リストで 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 を wss://dashscope.aliyuncs.com/api-ws/v1/inference に置き換えてください
Constants.baseWebsocketApiUrl = "wss://dashscope-intl.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 をご参照ください
// 環境変数を設定していない場合は、次の行を .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] Request ID: "
+ 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. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、および cosyvoice-v2 モデルのクローン音声、
* および音声リストで 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 を wss://dashscope.aliyuncs.com/api-ws/v1/inference に置き換えてください
Constants.baseWebsocketApiUrl = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference";
streamAudioDataToSpeaker();
System.out.println("Audio saved to output.mp3");
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("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 をご参照ください
// 環境変数を設定していない場合は、次の行を .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 を返します (結果はコールバックを通じて非同期に配信されます)
// 特殊文字をエスケープします
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] Request ID: "
+ 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 をご参照ください
# 環境変数を設定していない場合は、次の行を dashscope.api_key = "sk-xxx" に置き換えてください
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# 北京リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/inference に置き換えてください
dashscope.base_websocket_api_url='wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference'
# モデル
model = "cosyvoice-v3-flash"
# 音声
voice = "longanyang"
# SpeechSynthesizer をインスタンス化し、モデル、音声、その他のリクエストパラメーターをコンストラクターに渡します
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] Request ID: {}, 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 をご参照ください
# 環境変数を設定していない場合は、次の行を dashscope.api_key = "sk-xxx" に置き換えてください
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# 北京リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/inference に置き換えてください
dashscope.base_websocket_api_url='wss://dashscope-intl.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 received: " + get_timestamp())
if hasattr(self, 'file') and self.file:
self.file.close()
self
# 先頭パケットのレイテンシには、WebSocket 接続の確立に必要な時間が含まれます
print('[Metric] Request ID: {}, 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: {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 をインスタンス化し、モデル、音声、その他のリクエストパラメーターをコンストラクターに渡します
synthesizer = SpeechSynthesizer(
model=model,
voice=voice,
callback=callback,
)
# on_complete で使用するために、シンセサイザーインスタンスをコールバックに割り当てます
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 テキストを送信します。このコマンドは一度しか送信できません。
// 3. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、および cosyvoice-v2 モデルのクローン音声、
// および音声リストで 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 を wss://dashscope.aliyuncs.com/api-ws/v1/inference/ に置き換えてください
wsURL = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference/"
outputFile = "output.mp3"
)
func main() {
// シンガポールリージョンと北京リージョンの API キーは異なります。API キーの取得方法については、https://www.alibabacloud.com/help/model-studio/get-api-key をご参照ください
// 環境変数を設定していない場合は、次の行を 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 コマンドは一度しか送信できません。
// そうしないと、「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 を使用する場合、このコマンドは一度しか送信できません
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 finished ===")
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. continue-task コマンドを使用して SSML テキストを送信します。このコマンドは一度しか送信できません。
// 3. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、および cosyvoice-v2 モデルのクローン音声、
// および音声リストで SSML 対応とマークされたシステム音声 (例:cosyvoice-v3-flash の longanyang 音声) でのみサポートされます。
class Program {
// シンガポールリージョンと北京リージョンの API キーは異なります。API キーの取得方法については、https://www.alibabacloud.com/help/model-studio/get-api-key をご参照ください
// 環境変数を設定していない場合は、次の行を 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 を wss://dashscope.aliyuncs.com/api-ws/v1/inference/ に置き換えてください
private const string WebSocketUrl = "wss://dashscope-intl.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 機能を使用する場合、このコマンドは一度しか送信できません。
// 特殊文字はエスケープする必要があります。
await SendContinueTaskCommandAsync("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>");
// finish-task コマンドを送信します
await SendFinishTaskCommandAsync(_taskId);
// 受信タスクが完了するのを待ちます
await receiveTask;
Console.WriteLine("Task completed, connection closed.");
} catch (OperationCanceledException) {
Console.WriteLine("The task was canceled.");
} 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("The output file has been cleared.");
} else {
Console.WriteLine("The output file does not exist and does not need to be cleared.");
}
}
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 canceled.");
} 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 コマンドは一度しか送信できません。
// そうしないと、「Text request limit violated, expected 1.」というエラーが表示されます。
enable_ssml = true
},
input = new { }
});
await SendJsonMessageAsync(command);
Console.WriteLine("Sent run-task command.");
}
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("Sent continue-task command.");
}
private static async Task SendFinishTaskCommandAsync(string taskId) {
var command = CreateCommand("finish-task", taskId, "duplex", new {
input = new { }
});
await SendJsonMessageAsync(command);
Console.WriteLine("Sent finish-task command.");
}
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 canceled.");
}
}
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("Receiving 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 reception was canceled.");
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. continue-task コマンドを使用して SSML を含むテキストを送信します。このコマンドは一度しか送信できません。 -->
<!-- 3. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、および cosyvoice-v2 モデルのクローン音声、および音声リストで 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/model-studio/get-api-key をご参照ください
// 環境変数を設定していない場合は、次の行を $api_key = "sk-xxx" に置き換えてください
$api_key = getenv("DASHSCOPE_API_KEY");
// 次の URL はシンガポールリージョン用です。北京リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/inference/ に置き換えてください
$websocket_url = 'wss://dashscope-intl.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 機能を使用する場合、このコマンドは一度しか送信できません。
$continueTaskMessage = json_encode([
"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>"
]
]
]);
$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 "Could not 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 コマンドは一度しか送信できません。
// そうしないと、「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 "Failed 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 "Task started, sending continue-task command...\n";
$taskStarted = true;
// continue-task コマンドを送信します
$sendContinueTask();
break;
case 'result-generated':
// result-generated イベントを無視します
break;
case 'task-finished':
echo "Task finished\n";
$conn->close();
break;
case 'task-failed':
echo "Task failed\n";
echo "Error code: " . $response['header']['error_code'] . "\n";
echo "Error message: " . $response['header']['error_message'] . "\n";
$conn->close();
break;
case 'error':
echo "Error: " . $response['payload']['message'] . "\n";
break;
default:
echo "Unknown event: " . $response['header']['event'] . "\n";
break;
}
// タスクが終了した場合、接続を閉じます
if ($response['header']['event'] == 'task-finished') {
// すべてのデータが転送されるように 1 秒待機します
$loop->addTimer(1, function() use ($conn) {
$conn->close();
echo "Client closes connection\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 テキストを送信します。このコマンドは一度しか送信できません。
// 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 をご参照ください
// 環境変数を設定していない場合は、次の行を const apiKey = "sk-xxx" に置き換えてください
const apiKey = process.env.DASHSCOPE_API_KEY;
// 次の URL はシンガポールリージョン用です。北京リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/inference/ に置き換えてください
const url = 'wss://dashscope-intl.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 コマンドは一度しか送信できません。そうしないと、「Text request limit violated, expected 1.」というエラーが報告されます。
},
input: {}
}
});
ws.send(runTaskMessage);
console.log('Sent run-task message');
});
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 has started');
// continue-task コマンドを送信します
sendContinueTasks(ws);
break;
case 'task-finished':
console.log('Task has finished');
ws.close();
fileStream.end(() => {
console.log('File stream has been closed');
});
break;
case 'task-failed':
console.error('Task failed: ', message.header.error_message);
ws.close();
fileStream.end(() => {
console.log('File stream has been closed');
});
break;
default:
// ここで result-generated を処理できます
break;
}
}
});
function sendContinueTasks(ws) {
if (taskStarted) {
// continue-task コマンドを送信します。SSML 機能を使用する場合、このコマンドは一度しか送信できません。
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 the WebSocket server');
});Java
Java プログラミング言語を使用する場合、開発には Java DashScope SDK を使用することを推奨します。詳細については、「Java SDK」をご参照ください。
以下は Java WebSocket のサンプルです。サンプルを実行する前に、次の依存関係をインポートしていることを確認してください:
Java-WebSocketjackson-databind
Maven または Gradle を使用して依存関係パッケージを管理することを推奨します。構成は次のとおりです:
pom.xml
<dependencies>
<!-- WebSocket Client -->
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.3</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>build.gradle
// 他のコードを省略
dependencies {
// WebSocket Client
implementation 'org.java-websocket:Java-WebSocket:1.5.3'
// JSON Processing
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 を含むテキストを送信します。このコマンドは一度しか送信できません。
* 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 successful");
// run-task コマンドを送信します
// enable_ssml が true に設定されている場合、continue-task コマンドは一度しか送信できません。
// そうしないと、「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 機能を使用する場合、このコマンドは一度しか送信できません。
// 特殊文字はエスケープする必要があります。
sendContinueTask("<speak rate=\\\"2\\\">My speaking rate is faster than a normal person's.</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 exception occurred: " + e.getMessage());
}
}
@Override
public void onMessage(ByteBuffer message) {
System.out.println("Size of received binary audio data: " + 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 has been written to the 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 をご参照ください
// 環境変数を設定していない場合は、次の行を 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 を wss://dashscope.aliyuncs.com/api-ws/v1/inference/ に置き換えてください
TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://dashscope-intl.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. continue-task コマンドを使用して SSML を含むテキストを送信します。このコマンドは一度しか送信できません。
# 3. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、および cosyvoice-v2 モデルのクローン音声、および音声リストで 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 connection established")
# 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 コマンドは一度しか送信できません。
# そうしないと、「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 機能を使用する場合、このコマンドは一度しか送信できません。
# 特殊文字はエスケープする必要があります。
self.send_continue_task("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>")
# continue-task が送信された後、finish-task を送信します。
self.send_finish_task()
elif event == "task-finished":
print("Task finished")
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"Audio data has been written to the local file {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"Sent continue-task command, 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("Sent finish-task command")
def close(self, ws):
"""接続を能動的に閉じます。"""
if ws and ws.sock and ws.sock.connected:
ws.close()
print("Connection actively closed")
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("Listening for WebSocket messages...")
self.ws.run_forever() # 持続的接続リスナーを開始します。
# 使用例
if __name__ == "__main__":
# シンガポールリージョンと北京リージョンの API キーは異なります。API キーの取得方法については、https://www.alibabacloud.com/help/model-studio/get-api-key をご参照ください
# 環境変数を設定していない場合は、次の行を API_KEY = "sk-xxx" に置き換えてください
API_KEY = os.environ.get("DASHSCOPE_API_KEY")
# 次の URL はシンガポールリージョン用です。北京リージョンのモデルを使用する場合は、URL を wss://dashscope.aliyuncs.com/api-ws/v1/inference/ に置き換えてください
SERVER_URI = "wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference/"
client = TTSClient(API_KEY, SERVER_URI)
client.run()タグ
音声合成サービスの SSML 実装は、W3C SSML 1.0 仕様に基づいています。ただし、さまざまなビジネスシナリオに対応するため、すべての標準タグがサポートされているわけではありません。代わりに、最も実用的なタグのコレクションをサポートしています。
SSML の特徴を使用するすべてのテキストコンテンツは、
<speak></speak>タグで囲む必要があります。複数の
<speak>タグを連続して使用できます (例:<speak></speak><speak></speak>)。ネストすることはできません (例:<speak><speak></speak></speak>)。タグのテキストコンテンツ内の XML 特殊文字はエスケープする必要があります。一般的な特殊文字とそのエスケープ形式は次のとおりです:
"(二重引用符) →"'(単一引用符/アポストロフィ) →'&(アンパサンド) →&<(不等号 (より小)) → <>(不等号 (より大)) → >
<speak>:ルートノード
説明
<speak>タグは、すべての SSML ドキュメントのルートノードです。SSML の特徴を使用するすべてのテキストは、<speak></speak>タグで囲む必要があります。構文
<speak>SSML の特徴を必要とするテキスト</speak>プロパティ
プロパティ
タイプ
必須
説明
voice
String
いいえ
音声を指定します。
このプロパティは、API リクエストの
voiceパラメーターよりも優先度が高くなります。有効値:特定の音声の詳細については、「cosyvoice-v2 の音声」をご参照ください。
例:
<speak voice="longcheng_v2"> 私は男性の声です。 </speak>
rate
String
いいえ
話速を指定します。このプロパティは、API リクエストの
speech_rateパラメーターよりも優先度が高くなります。有効値:0.5 から 2 までの 10 進数。
デフォルト値:1
1 より大きい値は話速が速いことを示します。
1 より小さい値は話速が遅いことを示します。
例:
<speak rate="2"> 私の話速は通常より速いです。 </speak>
pitch
String
いいえ
ピッチを指定します。このプロパティは、API リクエストの
pitch_rateパラメーターよりも優先度が高くなります。有効値:0.5 から 2 までの 10 進数。
デフォルト値: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(イコライザー):システムはデフォルトで 8 つの周波数レベルをサポートします:["40 Hz", "100 Hz", "200 Hz", "400 Hz", "800 Hz", "1600 Hz", "4000 Hz", "12000 Hz"]。
各周波数帯の帯域幅は 1.0 q です。
この効果を使用する場合、
effectValueパラメーターを使用して各周波数帯のゲイン値を指定する必要があります。このパラメーターは、スペースで区切られた 8 つの整数の文字列です。各整数の値は -20 から 20 の範囲です。0の値は、対応する周波数のゲインが調整されないことを示します。例:
effectValue="1 1 1 1 1 1 1 1"lpfilter(ローパスフィルター):ローパスフィルターの周波数値を入力します。値は (0, ターゲットサンプルレート/2] の範囲の整数です。例:effectValue="800"。hpfilter(ハイパスフィルター):ハイパスフィルターの周波数値を入力します。値は (0, ターゲットサンプルレート/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
いいえ
合成された音声に指定された背景音楽を追加します。背景音楽ファイルは Alibaba Cloud Object Storage Service (OSS) (「ファイルのアップロード」をご参照ください) に保存し、そのバケットには少なくとも公開読み取り権限が必要です。
背景音楽の URL に
&、<、>などの XML 特殊文字が含まれている場合は、エスケープする必要があります。音声要件:
音声ファイルのサイズに上限はありませんが、ファイルサイズが大きいとダウンロード時間が長くなる可能性があります。合成されたコンテンツの長さが背景音楽の長さを超える場合、背景音楽は合成された音声の長さに合わせて自動的にループします。
サンプルレート: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="-500" volume="40"> <break time="2s"/> 陰崖の古木は霧に包まれ <break time="700ms"/> 雨の音はまだ竹林の中に <break time="700ms"/> 綿が国の計画に貢献することを知っている <break time="700ms"/> 綿州の景色はいつも哀れだ <break time="2s"/> </speak>
重要アップロードされた音声の著作権については、法的な責任を負うものとします。
backgroundMusicVolume
String
いいえ
背景音楽の音量をコントロールします。これは
backgroundMusicVolumeプロパティを使用して構成されます。タグの関係
<speak> タグには、テキストと次のタグを含めることができます:
その他の例
空の属性
<speak> SSML タグを必要とするテキスト </speak>属性の組み合わせ (スペースで区切る)
<speak rate="200" pitch="-100" volume="80"> まとめると、私の声はこのように聞こえます。 </speak>
<break>:ポーズ時間の制御
説明
音声合成中に無音期間を追加して、自然なポーズをシミュレートします。期間は秒 (s) またはミリ秒 (ms) で設定できます。このタグはオプションです。
構文
# 空の属性 <break/> # time 属性付き <break time="string"/>プロパティ
説明属性なしで <break> タグを使用する場合、デフォルトのポーズ時間は 1 秒です。
プロパティ
タイプ
必須
説明
time
String
いいえ
ポーズ時間を秒またはミリ秒で設定します (例:「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」というテキストを「ネットワークプロトコル」として読み上げることができます。このタグはオプションです。
構文
<sub alias="string"></sub>プロパティ
プロパティ
タイプ
必須
説明
alias
String
はい
テキストの一部を、より読みやすいテキストに置き換えます。
例:
<speak> <sub alias="ネットワークプロトコル">W3C</sub> </speak>タグの関係
<sub> タグにはテキストのみを含めることができます。
<phoneme>:発音の指定 (ピンイン/音標文字)
説明
特定のテキスト文字列の発音を制御します。中国語にはピンイン、英語には CMU などの音標文字を使用できます。このタグは、正確な発音が必要なシナリオに適しており、オプションです。
構文
<phoneme alphabet="string" ph="string">text</phoneme>プロパティ
プロパティ
タイプ
必須
説明
alphabet
String
はい
発音タイプを指定します:ピンイン (中国語用) または音標文字 (英語用)。
有効値:
"py":ピンイン
"cmu":音標文字。詳細については、「The CMU Pronouncing Dictionary」をご参照ください。
ph
String
はい
特定のピンインまたは音標文字を指定します:
各文字のピンインはスペースで区切られ、ピンインの音節数は文字数と一致する必要があります。
各ピンイン音節は、発音部分と声調で構成されます。声調は
1から5までの整数で、5は軽声を示します。例:
<speak> <phoneme alphabet="py" ph="dian3 dang4 hang2">典当行</phoneme>に行ってこの品物を<phoneme alphabet="py" ph="dang4 diao4">当掉</phoneme>する </speak> <speak> <phoneme alphabet="cmu" ph="S AY N">sin</phoneme> のスペルはどう書きますか? </speak>
タグの関係
<phoneme> タグにはテキストのみを含めることができます。
<soundEvent>:外部音声の挿入 (着信音や猫の鳴き声など)
説明
プロンプト音や環境音などの効果音ファイルを合成音声に挿入して、オーディオ出力を豊かにすることができます。このタグはオプションです。
構文
<soundEvent src="URL"/>プロパティ
プロパティ
タイプ
必須
説明
src
String
はい
外部音声の URL を設定します。
音声ファイルは 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
String
はい
タグ内のテキストの情報タイプを示します。
有効値:
cardinal:基数 (整数または小数) として読み上げます。
digits:個々の数字として読み上げます。例えば、123 は「いち に さん」と読み上げられます。
telephone:電話番号として読み上げます。
name:名前として読み上げます。
address:住所として読み上げます。
id:アカウント名やニックネームに適しています。従来の読み方で読み上げます。
characters:タグ内のテキストを文字ごとに読み上げます。
punctuation:タグ内のテキストを句読点として読み上げます。
date:日付として読み上げます。
time:時刻として読み上げます。
currency:通貨額として読み上げます。
measure:測定単位として読み上げます。
各 <say-as> タイプでサポートされる形式
cardinal
フォーマット
例
英語の出力
説明
数字の文字列
145
one hundred forty five
整数の入力範囲:13 桁以内の正または負の整数、[-999999999999, 999999999999]。
小数の入力範囲:小数点以下の桁数に特別な制限はありませんが、10 桁を超えないことを推奨します。
ゼロで始まる数字の文字列
0145
one hundred forty five
負の符号 + 数字の文字列
-145
minus hundred forty five
カンマで区切られた 3 桁の数字の文字列
60,000
sixty thousand
負の符号 + カンマで区切られた 3 桁の数字の文字列
-208,000
minus two hundred eight thousand
数字の文字列 + 小数点 + ゼロ
12.00
twelve
数字の文字列 + 小数点 + 数字の文字列
12.34
twelve point three four
カンマで区切られた 3 桁の数字の文字列 + 小数点 + 数字の文字列
1,000.1
one thousand point one
負の符号 + 数字の文字列 + 小数点 + 数字の文字列
-12.34
minus twelve point three four
負の符号 + カンマで区切られた 3 桁の数字の文字列 + 小数点 + 数字の文字列
-1,000.1
minus one thousand point one
(カンマで区切られた 3 桁の) 数字の文字列 + ハイフン + (カンマで区切られた 3 桁の) 数字
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
digits
フォーマット
例
英語の出力
説明
数字の文字列
12034
one two zero three four
数字の文字列の長さに特別な制限はありませんが、20 桁を超えないことを推奨します。
数字の文字列がスペースまたはハイフンでグループ化されている場合、グループ間にカンマが挿入され、適切なポーズが作成されます。最大 5 グループまでサポートされます。
数字の文字列 + スペースまたはハイフン + 数字の文字列 + スペースまたはハイフン + 数字の文字列 + スペースまたはハイフン + 数字の文字列
1-23-456 7890
one, two three, four five six, seven eight nine zero
telephone
フォーマット
例
英語の出力
説明
数字の文字列
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
address
このタグは英語テキストではサポートされていません。
id
英語テキストの場合、このタグは characters タグと同じ機能を持ちます。
characters
フォーマット
例
英語の出力
説明
文字列
*b+3$.c-0'=α
asterisk B plus three dollar dot C dash zero apostrophe equals alpha
漢字、大文字・小文字の英字、アラビア数字 0-9、一部の全角および半角文字をサポートします。
出力のスペースは、各文字の間にポーズが挿入されることを示し、文字が 1 文字ずつ読み上げられます。
タグ内のテキストに XML 特殊文字が含まれている場合は、エスケープする必要があります。
punctuation
英語テキストの場合、このタグは characters タグと同じ機能を持ちます。
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
2000年1月19日~20日
DD は 2 桁の日を示します。MMM は月の 3 文字略称または正式名称を示します。YYYY は 1 または 2 で始まる 4 桁の年を示します。
01 ~ 10 Jul、2020
the first to the tenth of july twenty 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
feburary 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
7月から12月まで
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
time
フォーマット
例
英語の出力
説明
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
100
one oclock
時刻-時刻
8:00 am - 05:30 pm
eight a m to five 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
サポートされる量詞のフォーマットには以下が含まれます:
thousand
million
billion
trillion
Mil (million)
mil (million)
Bil (billion)
bil (billion)
MM (million)
Bn (billion)
bn (billion)
Tn (trillion)
tn (trillion)
K(thousand)
k (thousand)
M (million)
m (million)
$1.2 K
one point two thousand dollars
measure
フォーマット
例
英語の出力
説明
数字 + 測定単位
1.0 kg
one kilogram
整数、小数、カンマ区切りの国際表記をサポートします。
一般的な単位の略称をサポートします。
1,234.01 km
one thousand two hundred thirty-four point zero one kilometers
測定単位
mm2
square millimeter
次の表は、<say-as> の一般的な記号の発音を示しています。
記号
英語の発音
!
exclamation mark
“
double quote
#
pound
$
dollar
%
percent
&
and
‘
left quote
(
left parenthesis
)
right parenthesis
*
asterisk
+
plus
,
comma
-
dash
.
dot
/
slash
:
Solon
;
semicolon
<
less than
=
equals
>
greater than
?
question mark
@
at
[
left bracket
\
backslash
]
right bracket
^
caret
_
underscore
`
backtick
{
left brace
|
vertical bar
}
right brace
~
tilde
!
exclamation mark
“
left double quote
”
right double quote
‘
left quote
’
right quote
(
left parenthesis
)
right parenthesis
,
comma
。
full stop
—
em dash
:
colon
;
semicolon
?
question mark
、
enumeration comma
…
ellipsis
……
ellipsis
《
left guillemet
》
right guillemet
¥
yuan
≥
greater than or equal to
≤
less than or equal to
≠
not equal
≈
approximately equal
±
plus or minus
×
times
π
pi
Α
alpha
Β
beta
Γ
gamma
Δ
delta
Ε
epsilon
Ζ
zeta
Θ
theta
Ι
iota
Κ
kappa
∧
lambda
Μ
mu
Ν
nu
Ξ
ksi
Ο
omicron
∏
pi
Ρ
rho
∑
sigma
Τ
tau
Υ
upsilon
Φ
phi
Χ
chi
Ψ
psi
Ω
omega
α
alpha
β
beta
γ
gamma
δ
delta
ε
epsilon
ζ
zeta
η
eta
θ
theta
ι
iota
κ
kappa
λ
lambda
μ
mu
ν
nu
ξ
ksi
ο
omicron
π
pi
ρ
rho
σ
sigma
τ
tau
υ
upsilon
φ
phi
χ
chi
ψ
psi
ω
omega
次の表は、<say-as> の一般的な測定単位を示しています。
フォーマット
カテゴリ
英語の例
略称
長さ
nm (ナノメートル)、μm (マイクロメートル)、mm (ミリメートル)、cm (センチメートル)、m (メートル)、km (キロメートル)、ft (フィート)、in (インチ)
面積
cm² (平方センチメートル)、m² (平方メートル)、km² (平方キロメートル)、SqFt (平方フィート)
体積
cm³ (立方センチメートル)、m³ (立方メートル)、km3 (立方キロメートル)、mL (ミリリットル)、L (リットル)、gal (ガロン)
重量
μg (マイクログラム)、mg (ミリグラム)、g (グラム)、kg (キログラム)
時間
min (分)、sec (秒)、ms (ミリ秒)
電磁気学
μA (マイクロアンペア)、mA (ミリアンペア)、Hz (ヘルツ)、kHz (キロヘルツ)、MHz (メガヘルツ)、GHz (ギガヘルツ)、V (ボルト)、kV (キロボルト)、kWh (キロワット時)
音響
dB (デシベル)
大気圧
Pa (パスカル)、kPa (キロパスカル)、MPa (メガパスカル)
その他の一般的な単位
前述のカテゴリに限定されない測定単位をサポートします。例えば、tsp (ティースプーン)、rpm (回転数/分)、KB (キロバイト)、mmHg (水銀柱ミリメートル) などです。
関係
<say-as> タグにはテキストと <vhml/> タグを含めることができます。
例
cardinal
<speak> <say-as interpret-as="cardinal">12345</say-as> </speak><speak> <say-as interpret-as="cardinal">10234</say-as> </speak>digits
<speak> <say-as interpret-as="digits">12345</say-as> </speak><speak> <say-as interpret-as="digits">10234</say-as> </speak>telephone
<speak> <say-as interpret-as="telephone">12345</say-as> </speak><speak> <say-as interpret-as="telephone">10234</say-as> </speak>name
<speak> Her former name is <say-as interpret-as="name">Zeng Xiaofan</say-as> </speak>address
<speak> <say-as interpret-as="address">Fulu International, Building 1, Unit 3, Room 304</say-as> </speak>id
<speak> <say-as interpret-as="id">myid_1998</say-as> </speak>characters
<speak> <say-as interpret-as="characters">Greek letters αβ</say-as> </speak><speak> <say-as interpret-as="characters">*b+3.c$=α</say-as> </speak>punctuation
<speak> <say-as interpret-as="punctuation"> -./:;</say-as> </speak>date
<speak> <say-as interpret-as="date">1000-10-10</say-as> </speak><speak> <say-as interpret-as="date">10-01-2020</say-as> </speak>time
<speak> <say-as interpret-as="time">5:00am</say-as> </speak><speak> <say-as interpret-as="time">0500</say-as> </speak>currency
<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>measure
<speak> <say-as interpret-as="measure">100m12cm6mm</say-as> </speak><speak> <say-as interpret-as="measure">1,000.01kg</say-as> </speak>