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

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

最終更新日:Dec 26, 2025

音声合成マークアップ言語 (Speech Synthesis Markup Language、SSML) は、音声合成のための XML ベースのマークアップ言語です。これにより、大規模な音声合成モデルでリッチテキストコンテンツを処理できるようになり、話速、ピッチ、ポーズ、音量などの音声の特徴を詳細に制御できます。また、背景音楽を追加して、より表現力豊かな音声効果を作成することも可能です。このトピックでは、CosyVoice の SSML の特徴とその使用方法について説明します。

重要

中国 (北京) リージョンのモデルを使用するには、中国 (北京) リージョンの API キーページに移動してください。

制限事項

  • モデル: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 命令を 1 回だけ送信します。詳細については、「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 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) {
        streamAudioDataToSpeaker();
        System.exit(0);
    }

    public static void streamAudioDataToSpeaker() {
        SpeechSynthesisParam param =
                SpeechSynthesisParam.builder()
                        // 環境変数を設定していない場合は、次の行を .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 speech rate is faster than normal.</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 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) {
        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()
                        // 環境変数を設定していない場合は、次の行を .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 speech rate is faster than normal.</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

# 環境変数を設定していない場合は、次の行を dashscope.api_key = "sk-xxx" に置き換えてください
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# モデル
model = "cosyvoice-v3-flash"
# 音声
voice = "longanyang"

# SpeechSynthesizer をインスタンス化し、モデル、音声、その他のリクエストパラメーターをコンストラクターに渡します
synthesizer = SpeechSynthesizer(model=model, voice=voice)
# 非ストリーミング呼び出し。音声が返されるまでブロックします
# 特殊文字をエスケープします
audio = synthesizer.call("<speak rate=\"2\">My speech rate is faster than normal.</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

# 環境変数を設定していない場合は、次の行を dashscope.api_key = "sk-xxx" に置き換えてください
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# モデル
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 で使用するために、synthesizer インスタンスをコールバックに割り当てます
callback.synthesizer = synthesizer

# 単方向ストリーミング呼び出し。合成するテキストを送信し、コールバックの on_data メソッドを介してリアルタイムでバイナリ音声を受信します
# 特殊文字をエスケープします
synthesizer.call("<speak rate=\"2\">My speech rate is faster than normal.</speak>")

WebSocket API

Go

// SSML 機能の注意事項:
//     1. run-task コマンドで enable_ssml パラメーターを true に設定して SSML を有効にします。
//     2. continue-task コマンドを使用して SSML テキストを送信します。このコマンドは 1 回しか送信できません。
//     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 (
    wsURL      = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/"
    outputFile = "output.mp3"
)

func main() {
    // API キーを環境変数として設定していない場合は、次の行を apiKey := "your_api_key" に置き換えてください。
    // 漏洩のリスクを減らすため、本番コードに API キーを直接ハードコーディングしないでください。
    apiKey := os.Getenv("DASHSCOPE_API_KEY")

    // 出力ファイルをクリアします
    os.Remove(outputFile)
    os.Create(outputFile)

    // WebSocket に接続します
    header := make(http.Header)
    header.Add("X-DashScope-DataInspection", "enable")
    header.Add("Authorization", fmt.Sprintf("bearer %s", apiKey))

    conn, resp, err := websocket.DefaultDialer.Dial(wsURL, header)
    if err != nil {
        if resp != nil {
            fmt.Printf("Connection failed. HTTP status code: %d\n", resp.StatusCode)
        }
        fmt.Println("Connection failed:", err)
        return
    }
    defer conn.Close()

    // タスク ID を生成します
    taskID := uuid.New().String()
    fmt.Printf("Generated task ID: %s\n", taskID)

    // run-task コマンドを送信します
    runTaskCmd := map[string]interface{}{
        "header": map[string]interface{}{
            "action":    "run-task",
            "task_id":   taskID,
            "streaming": "duplex",
        },
        "payload": map[string]interface{}{
            "task_group": "audio",
            "task":       "tts",
            "function":   "SpeechSynthesizer",
            "model":      "cosyvoice-v3-flash",
            "parameters": map[string]interface{}{
                "text_type":   "PlainText",
                "voice":       "longanyang",
                "format":      "mp3",
                "sample_rate": 22050,
                "volume":      50,
                "rate":        1,
                "pitch":       1,
                // enable_ssml が true に設定されている場合、continue-task コマンドは 1 回しか送信できません。
                // そうしないと、「Text request limit violated, expected 1.」というエラーが表示されます。
                "enable_ssml": true,
            },
            "input": map[string]interface{}{},
        },
    }

    runTaskJSON, _ := json.Marshal(runTaskCmd)
    fmt.Printf("Sending run-task command: %s\n", string(runTaskJSON))

    err = conn.WriteMessage(websocket.TextMessage, runTaskJSON)
    if err != nil {
        fmt.Println("Failed to send run-task:", err)
        return
    }

    textSent := false

    // メッセージを処理します
    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            fmt.Println("Failed to read message:", err)
            break
        }

        // バイナリメッセージを処理します
        if messageType == websocket.BinaryMessage {
            fmt.Printf("Received binary message, length: %d\n", len(message))
            file, _ := os.OpenFile(outputFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
            file.Write(message)
            file.Close()
            continue
        }

        // テキストメッセージを処理します
        messageStr := string(message)
        fmt.Printf("Received text message: %s\n", strings.ReplaceAll(messageStr, "\n", ""))

        // JSON を解析してイベントタイプを取得します
        var msgMap map[string]interface{}
        if json.Unmarshal(message, &msgMap) == nil {
            if header, ok := msgMap["header"].(map[string]interface{}); ok {
                if event, ok := header["event"].(string); ok {
                    fmt.Printf("Event type: %s\n", event)

                    switch event {
                    case "task-started":
                        fmt.Println("=== Received task-started event ===")

                        if !textSent {
                            // continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
                            continueTaskCmd := map[string]interface{}{
                                "header": map[string]interface{}{
                                    "action":    "continue-task",
                                    "task_id":   taskID,
                                    "streaming": "duplex",
                                },
                                "payload": map[string]interface{}{
                                    "input": map[string]interface{}{
                                        // 特殊文字をエスケープします
                                        "text": "<speak rate=\"2\">My speech rate is faster than normal.</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 テキストを送信します。このコマンドは 1 回しか送信できません。
//     3. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、
//        および音声リストで SSML サポートとマークされたシステム音声 (例:cosyvoice-v3-flash の longanyang 音声) でのみサポートされます。
class Program {
    // API キーを環境変数として設定していない場合は、次の行を private const string ApiKey="your_api_key" に置き換えてください。
    // 漏洩のリスクを減らすため、本番コードに API キーを直接ハードコーディングしないでください。
    private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("DASHSCOPE_API_KEY environment variable is not set.");

    // WebSocket サーバーの URL
    private const string WebSocketUrl = "wss://dashscope.aliyuncs.com/api-ws/v1/inference/";
    // 出力ファイルのパス
    private const string OutputFilePath = "output.mp3";

    // WebSocket クライアント
    private static ClientWebSocket _webSocket = new ClientWebSocket();
    // キャンセルトークンソース
    private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
    // タスク ID
    private static string? _taskId;
    // タスクが開始されたかどうかを示します
    private static TaskCompletionSource<bool> _taskStartedTcs = new TaskCompletionSource<bool>();

    static async Task Main(string[] args) {
        try {
            // 出力ファイルをクリアします
            ClearOutputFile(OutputFilePath);

            // WebSocket サービスに接続します
            await ConnectToWebSocketAsync(WebSocketUrl);

            // メッセージの受信を開始します
            Task receiveTask = ReceiveMessagesAsync();

            // run-task コマンドを送信します
            _taskId = GenerateTaskId();
            await SendRunTaskCommandAsync(_taskId);

            // task-started イベントを待ちます
            await _taskStartedTcs.Task;

            // continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
            // 特殊文字をエスケープします
            await SendContinueTaskCommandAsync("<speak rate=\"2\">My speech rate is faster than normal.</speak>");

            // finish-task コマンドを送信します
            await SendFinishTaskCommandAsync(_taskId);

            // 受信タスクが完了するのを待ちます
            await receiveTask;

            Console.WriteLine("Task completed; connection closed.");
        } catch (OperationCanceledException) {
            Console.WriteLine("Task canceled.");
        } catch (Exception ex) {
            Console.WriteLine($"Error occurred: {ex.Message}");
        } finally {
            _cancellationTokenSource.Cancel();
            _webSocket.Dispose();
        }
    }

    private static void ClearOutputFile(string filePath) {
        if (File.Exists(filePath)) {
            File.WriteAllText(filePath, string.Empty);
            Console.WriteLine("Output file cleared.");
        } else {
            Console.WriteLine("Output file does not exist; no need to clear.");
        }
    }

    private static async Task ConnectToWebSocketAsync(string url) {
        var uri = new Uri(url);
        if (_webSocket.State == WebSocketState.Connecting || _webSocket.State == WebSocketState.Open) {
            return;
        }

        // WebSocket 接続ヘッダーを設定します
        _webSocket.Options.SetRequestHeader("Authorization", $"bearer {ApiKey}");
        _webSocket.Options.SetRequestHeader("X-DashScope-DataInspection", "enable");

        try {
            await _webSocket.ConnectAsync(uri, _cancellationTokenSource.Token);
            Console.WriteLine("Successfully connected to WebSocket service.");
        } catch (OperationCanceledException) {
            Console.WriteLine("WebSocket connection 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 コマンドは 1 回しか送信できません。
                // そうしないと、「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 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 send 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 completed.");
                        _cancellationTokenSource.Cancel();
                        break;
                    case "task-failed":
                        Console.WriteLine("Task failed: " + response.RootElement.GetProperty("header").GetProperty("error_message").GetString());
                        _cancellationTokenSource.Cancel();
                        break;
                    default:
                        // 必要に応じてここで result-generated を処理します
                        break;
                }
            }
        }
    }

    private static async Task<JsonDocument?> ReceiveMessageAsync() {
        var buffer = new byte[1024 * 4];
        var segment = new ArraySegment<byte>(buffer);

        try {
            WebSocketReceiveResult result = await _webSocket.ReceiveAsync(segment, _cancellationTokenSource.Token);

            if (result.MessageType == WebSocketMessageType.Close) {
                await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", _cancellationTokenSource.Token);
                return null;
            }

            if (result.MessageType == WebSocketMessageType.Binary) {
                // バイナリデータを処理します
                Console.WriteLine("Received binary data...");

                // バイナリデータをファイルに保存します
                using (var fileStream = new FileStream(OutputFilePath, FileMode.Append)) {
                    fileStream.Write(buffer, 0, result.Count);
                }

                return null;
            }

            string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
            return JsonDocument.Parse(message);
        } catch (OperationCanceledException) {
            Console.WriteLine("Message receive 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 テキストを送信します。このコマンドは 1 回しか送信できません。 -->
<!--     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_key="your_api_key" に置き換えてください。
// 漏洩のリスクを減らすため、本番コードに API キーを直接ハードコーディングしないでください。
$api_key = getenv("DASHSCOPE_API_KEY");
$websocket_url = 'wss://dashscope.aliyuncs.com/api-ws/v1/inference/'; // WebSocket サーバーの URL
$output_file = 'output.mp3'; // 出力ファイルのパス

$loop = Loop::get();

if (file_exists($output_file)) {
    // ファイルの内容をクリアします
    file_put_contents($output_file, '');
}

// カスタムコネクタを作成します
$socketConnector = new SocketConnector($loop, [
    'tcp' => [
        'bindto' => '0.0.0.0:0',
    ],
    'tls' => [
        'verify_peer' => false,
        'verify_peer_name' => false,
    ],
]);

$connector = new Connector($loop, $socketConnector);

$headers = [
    'Authorization' => 'bearer ' . $api_key,
    'X-DashScope-DataInspection' => 'enable'
];

$connector($websocket_url, [], $headers)->then(function ($conn) use ($loop, $output_file) {
    echo "Connected to WebSocket server\n";

    // タスク ID を生成します
    $taskId = generateTaskId();

    // run-task コマンドを送信します
    sendRunTaskMessage($conn, $taskId);

    // continue-task コマンドを送信する関数を定義します
    $sendContinueTask = function() use ($conn, $loop, $taskId) {
        // continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
        $continueTaskMessage = json_encode([
            "header" => [
                "action" => "continue-task",
                "task_id" => $taskId,
                "streaming" => "duplex"
            ],
            "payload" => [
                "input" => [
                    // 特殊文字をエスケープします
                    "text" => "<speak rate=\"2\">My speech rate is faster than normal.</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 "Connection failed: {$e->getMessage()}\n";
});

$loop->run();

/**
 * タスク ID を生成します
 * @return string
 */
function generateTaskId(): string {
    return bin2hex(random_bytes(16));
}

/**
 * run-task コマンドを送信します
 * @param $conn
 * @param $taskId
 */
function sendRunTaskMessage($conn, $taskId) {
    $runTaskMessage = json_encode([
        "header" => [
            "action" => "run-task",
            "task_id" => $taskId,
            "streaming" => "duplex"
        ],
        "payload" => [
            "task_group" => "audio",
            "task" => "tts",
            "function" => "SpeechSynthesizer",
            "model" => "cosyvoice-v3-flash",
            "parameters" => [
                "text_type" => "PlainText",
                "voice" => "longanyang",
                "format" => "mp3",
                "sample_rate" => 22050,
                "volume" => 50,
                "rate" => 1,
                "pitch" => 1,
                // enable_ssml が true に設定されている場合、continue-task コマンドは 1 回しか送信できません。
                // そうしないと、「Text request limit violated, expected 1.」というエラーが表示されます。
                "enable_ssml" => true
            ],
            "input" => (object) []
        ]
    ]);
    echo "Preparing to send run-task command: " . $runTaskMessage . "\n";
    $conn->send($runTaskMessage);
    echo "run-task command sent\n";
}

/**
 * 音声ファイルを読み取ります
 * @param string $filePath
 * @return bool|string
 */
function readAudioFile(string $filePath) {
    $voiceData = file_get_contents($filePath);
    if ($voiceData === false) {
        echo "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 completed\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 closed 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 テキストを送信します。このコマンドは 1 回しか送信できません。
//     3. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、
//        および音声リストで SSML サポートとマークされたシステム音声 (例:cosyvoice-v3-flash の longanyang 音声) でのみサポートされます。

import fs from 'fs';
import WebSocket from 'ws';
import { v4 as uuid } from 'uuid'; // UUID を生成するため

// API キーを環境変数として設定していない場合は、次の行を apiKey = 'your_api_key' に置き換えてください。
// 漏洩のリスクを減らすため、本番コードに API キーを直接ハードコーディングしないでください。
const apiKey = process.env.DASHSCOPE_API_KEY;
// WebSocket サーバーの URL
const url = 'wss://dashscope.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 を有効にします。true に設定した場合、continue-task コマンドは 1 回しか送信できません。
                         // そうしないと、「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 started');
        // continue-task コマンドを送信します
        sendContinueTasks(ws);
        break;
      case 'task-finished':
        console.log('Task completed');
        ws.close();
        fileStream.end(() => {
          console.log('File stream closed');
        });
        break;
      case 'task-failed':
        console.error('Task failed:', message.header.error_message);
        ws.close();
        fileStream.end(() => {
          console.log('File stream closed');
        });
        break;
      default:
        // 必要に応じてここで result-generated を処理します
        break;
    }
  }
});

function sendContinueTasks(ws) {
  
  if (taskStarted) {
    // continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
    const continueTaskMessage = JSON.stringify({
      header: {
        action: 'continue-task',
        task_id: taskId,
        streaming: 'duplex'
      },
      payload: {
        input: {
          // 特殊文字をエスケープします
          text: '<speak rate="2">My speech rate is faster than normal.</speak>'
        }
      }
    });
    ws.send(continueTaskMessage);
    
    // finish-task コマンドを送信します
    const finishTaskMessage = JSON.stringify({
      header: {
        action: 'finish-task',
        task_id: taskId,
        streaming: 'duplex'
      },
      payload: {
        input: {}
      }
    });
    ws.send(finishTaskMessage);
  }
}

ws.on('close', () => {
  console.log('Disconnected from WebSocket server');
});

Java

Java を使用する場合は、Java DashScope SDK の使用を推奨します。詳細については、「Java SDK」をご参照ください。

以下は Java WebSocket の呼び出し例です。サンプルを実行する前に、次の依存関係がインポートされていることを確認してください:

  • Java-WebSocket

  • jackson-databind

Maven または Gradle を使用して依存関係を管理することを推奨します。設定例:

pom.xml

<dependencies>
    <!-- WebSocket 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 テキストを送信します。このコマンドは 1 回しか送信できません。
 *     3. SSML は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、
 *        および音声リストで SSML サポートとマークされたシステム音声 (例:cosyvoice-v3-flash の longanyang 音声) でのみサポートされます。
 */
public class TTSWebSocketClient extends WebSocketClient {
    private final String taskId = UUID.randomUUID().toString();
    private final String outputFile = "output_" + System.currentTimeMillis() + ".mp3";
    private boolean taskFinished = false;

    public TTSWebSocketClient(URI serverUri, Map<String, String> headers) {
        super(serverUri, headers);
    }

    @Override
    public void onOpen(ServerHandshake serverHandshake) {
        System.out.println("Connection successful");

        // run-task コマンドを送信します
        // enable_ssml が true に設定されている場合、continue-task コマンドは 1 回しか送信できません。
        // そうしないと、「Text request limit violated, expected 1.」というエラーが表示されます。
        String runTaskCommand = "{ \"header\": { \"action\": \"run-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"task_group\": \"audio\", \"task\": \"tts\", \"function\": \"SpeechSynthesizer\", \"model\": \"cosyvoice-v3-flash\", \"parameters\": { \"text_type\": \"PlainText\", \"voice\": \"longanyang\", \"format\": \"mp3\", \"sample_rate\": 22050, \"volume\": 50, \"rate\": 1, \"pitch\": 1, \"enable_ssml\": true }, \"input\": {} }}";
        send(runTaskCommand);
    }

    @Override
    public void onMessage(String message) {
        System.out.println("Received message from server: " + message);
        try {
            // JSON メッセージを解析します
            Map<String, Object> messageMap = new ObjectMapper().readValue(message, Map.class);

            if (messageMap.containsKey("header")) {
                Map<String, Object> header = (Map<String, Object>) messageMap.get("header");

                if (header.containsKey("event")) {
                    String event = (String) header.get("event");

                    if ("task-started".equals(event)) {
                        System.out.println("Received task-started event from server");

                        // continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
                        // 特殊文字をエスケープします
                        sendContinueTask("<speak rate=\\\"2\\\">My speech rate is faster than normal.</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("Error occurred: " + e.getMessage());
        }
    }

    @Override
    public void onMessage(ByteBuffer message) {
        System.out.println("Received binary audio data size: " + message.remaining());

        try (FileOutputStream fos = new FileOutputStream(outputFile, true)) {
            byte[] buffer = new byte[message.remaining()];
            message.get(buffer);
            fos.write(buffer);
            System.out.println("Audio data written to local file " + outputFile);
        } catch (IOException e) {
            System.err.println("Failed to write audio data to local file: " + e.getMessage());
        }
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        System.out.println("Connection closed: " + reason + " (" + code + ")");
    }

    @Override
    public void onError(Exception ex) {
        System.err.println("Error: " + ex.getMessage());
        ex.printStackTrace();
    }

    private void sendContinueTask(String text) {
        String command = "{ \"header\": { \"action\": \"continue-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": { \"text\": \"" + text + "\" } }}";
        send(command);
    }

    private void sendFinishTask() {
        String command = "{ \"header\": { \"action\": \"finish-task\", \"task_id\": \"" + taskId + "\", \"streaming\": \"duplex\" }, \"payload\": { \"input\": {} }}";
        send(command);
    }

    private void closeConnection() {
        if (!isClosed()) {
            close();
        }
    }

    public static void main(String[] args) {
        try {
            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);
            TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://dashscope.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 が発生します。

# SSML 機能の注意事項:
#     1. run-task コマンドで enable_ssml パラメーターを true に設定して SSML を有効にします。
#     2. continue-task コマンドを使用して SSML テキストを送信します。このコマンドは 1 回しか送信できません。
#     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 インスタンスを初期化します

    Args:
        api_key (str): 認証用の API キー
        uri (str): WebSocket サービス URL
    """
        self.api_key = api_key  # ご利用の API キーに置き換えてください
        self.uri = uri  # ご利用の WebSocket URL に置き換えてください
        self.task_id = str(uuid.uuid4())  # 一意のタスク ID を生成します
        self.output_file = f"output_{int(time.time())}.mp3"  # 出力音声ファイルのパス
        self.ws = None  # WebSocketApp インスタンス
        self.task_started = False  # task-started が受信されたかどうか
        self.task_finished = False  # task-finished/task-failed が受信されたかどうか

    def on_open(self, ws):
        """
    WebSocket 接続が確立されたときのコールバック
    音声合成を開始するために run-task コマンドを送信します
    """
        print("WebSocket connected")

        # run-task コマンドを構築します
        run_task_cmd = {
            "header": {
                "action": "run-task",
                "task_id": self.task_id,
                "streaming": "duplex"
            },
            "payload": {
                "task_group": "audio",
                "task": "tts",
                "function": "SpeechSynthesizer",
                "model": "cosyvoice-v3-flash",
                "parameters": {
                    "text_type": "PlainText",
                    "voice": "longanyang",
                    "format": "mp3",
                    "sample_rate": 22050,
                    "volume": 50,
                    "rate": 1,
                    "pitch": 1,
                    # enable_ssml が True に設定されている場合、continue-task コマンドは 1 回しか送信できません。
                    # そうしないと、「Text request limit violated, expected 1.」というエラーが表示されます。
                    "enable_ssml": True
                },
                "input": {}
            }
        }

        # run-task コマンドを送信します
        ws.send(json.dumps(run_task_cmd))
        print("Sent run-task command")

    def on_message(self, ws, message):
        """
    メッセージが受信されたときのコールバック
    テキストメッセージとバイナリメッセージの両方を処理します
    """
        if isinstance(message, str):
            # JSON テキストメッセージを処理します
            try:
                msg_json = json.loads(message)
                print(f"Received JSON message: {msg_json}")

                if "header" in msg_json:
                    header = msg_json["header"]

                    if "event" in header:
                        event = header["event"]

                        if event == "task-started":
                            print("Task started")
                            self.task_started = True

                            # continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
                            # 特殊文字をエスケープします
                            self.send_continue_task("<speak rate=\"2\">My speech rate is faster than normal.</speak>")

                            # continue-task の後に finish-task を送信します
                            self.send_finish_task()

                        elif event == "task-finished":
                            print("Task completed")
                            self.task_finished = True
                            self.close(ws)

                        elif event == "task-failed":
                            error_msg = msg_json.get("error_message", "Unknown error")
                            print(f"Task failed: {error_msg}")
                            self.task_finished = True
                            self.close(ws)

            except json.JSONDecodeError as e:
                print(f"JSON parsing failed: {e}")
        else:
            # バイナリメッセージ (音声データ) を処理します
            print(f"Received binary message, size: {len(message)} bytes")
            with open(self.output_file, "ab") as f:
                f.write(message)
            print(f"Audio data written to 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: {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 closed actively")

    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_KEY = os.environ.get("DASHSCOPE_API_KEY")  # 環境変数として設定されていない場合は、API_KEY をご利用の API キーに設定してください
    SERVER_URI = "wss://dashscope.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 OSS (Object Storage Service) に保存する必要があり (「ファイルのアップロード」をご参照ください)、そのバケットには少なくとも公開読み取り権限が必要です。

    背景音楽の 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>
        How to spell <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:基数 (整数または 10 進数) として読み上げます。

    • digits:個々の数字として読み上げます。例えば、123 は「いち に さん」と読み上げられます。

    • telephone:電話番号として読み上げます。

    • name:名前として読み上げます。

    • address:住所として読み上げます。

    • id:アカウント名やニックネームに適しています。従来の方法で読み上げます。

    • characters:タグ内のテキストを 1 文字ずつ読み上げます。

    • punctuation:タグ内のテキストを句読点として読み上げます。

    • date:日付として読み上げます。

    • time:時刻として読み上げます。

    • currency:通貨額として読み上げます。

    • measure:測定単位として読み上げます。

  • 各 <say-as> タイプのサポート形式

    • cardinal

      フォーマット

      英語出力

      説明

      数字列

      145

      one hundred forty five

      整数入力範囲:13 桁以内の正または負の整数、[-999999999999, 999999999999]。

      10 進数入力範囲:小数点以下の桁数に特別な制限はありませんが、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

      フォーマット

      英語出力

      説明

      string

      *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

      the nineteen to the twentieth of january two thousand

      DD は 2 桁の日を示します。MMM は月の 3 文字の略語またはフルネームを示します。YYYY は 1 または 2 で始まる 4 桁の年を示します。

      01 ~ 10 Jul, 2020

      the first to the tenth of july twenty 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

      july to dcember

      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

      サポートされている数字形式:整数、10 進数、およびカンマを桁区切り記号として使用する国際形式。

      サポートされている通貨識別子:

      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

      サポートされている数字形式:整数、10 進数、およびカンマを桁区切り記号として使用する国際形式。

      サポートされている通貨識別子:

      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

      整数、10 進数、およびカンマ区切り記号付きの国際表記をサポートします。

      一般的な単位の略語をサポートします。

      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

      感嘆符

      左二重引用符

      右二重引用符

      左引用符

      右引用符

      左括弧

      右括弧

      読点

      句点

      全角ダッシュ

      :

      コロン

      セミコロン

      疑問符

      読点

      三点リーダー

      ……

      三点リーダー

      左二重山括弧

      右二重山括弧

      以上

      以下

      等しくない

      ほぼ等しい

      ±

      プラスマイナス

      ×

      かける

      π

      パイ

      Α

      アルファ

      Β

      ベータ

      Γ

      ガンマ

      Δ

      デルタ

      Ε

      イプシロン

      Ζ

      ゼータ

      Θ

      シータ

      Ι

      イオタ

      Κ

      カッパ

      ラムダ

      Μ

      ミュー

      Ν

      ニュー

      Ξ

      ksi

      Ο

      オミクロン

      パイ

      Ρ

      ロー

      シグマ

      Τ

      タウ

      Υ

      ウプシロン

      Φ

      ファイ

      Χ

      chi

      Ψ

      psi

      Ω

      オメガ

      α

      アルファ

      β

      ベータ

      γ

      ガンマ

      δ

      デルタ

      ε

      イプシロン

      ζ

      ゼータ

      η

      イータ

      θ

      シータ

      ι

      イオタ

      κ

      カッパ

      λ

      ラムダ

      μ

      ミュー

      ν

      ニュー

      ξ

      ksi

      ο

      オミクロン

      π

      パイ

      ρ

      ロー

      σ

      シグマ

      τ

      タウ

      υ

      ウプシロン

      φ

      ファイ

      χ

      chi

      ψ

      プサイ

      ω

      オメガ

    • 次の表に、<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>
        彼女の旧姓は <say-as interpret-as="name">曽暁帆</say-as> です
      </speak>
    • address

      <speak>
        <say-as interpret-as="address">福禄国際ビル1号棟3単元304室</say-as>
      </speak>
    • id

      <speak>
        <say-as interpret-as="id">myid_1998</say-as>
      </speak>
    • characters

      <speak>
        <say-as interpret-as="characters">ギリシャ文字 αβ</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>