すべてのプロダクト
Search
ドキュメントセンター

Alibaba Cloud Model Studio:音声合成マークアップ言語 (SSML)

最終更新日:Feb 12, 2026

音声合成マークアップ言語 (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」をご参照ください。

クイックスタート

コードを実行する前に、次の手順を完了してください:

  1. API キーの取得

  2. 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-WebSocket

  • jackson-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 特殊文字はエスケープする必要があります。一般的な特殊文字とそのエスケープ形式は次のとおりです:

    • " (二重引用符) → &quot;

    • ' (単一引用符/アポストロフィ) → &apos;

    • & (アンパサンド) → &amp;

    • < (不等号 (より小)) → &lt;

    • > (不等号 (より大)) → &gt;

<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

    はい

    発音タイプを指定します:ピンイン (中国語用) または音標文字 (英語用)。

    有効値:

    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>