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

Alibaba Cloud Model Studio:SSML

最終更新日:Jun 23, 2026
  • <speak> ルート要素。すべての SSML フラグメントは、このタグで囲む必要があります。</speak>

  • <break> 一時停止を挿入します。一時停止の長さを指定できます。</break>

  • <p> 段落の区切り。</p>

  • <s> 文の区切り。</s>

  • <say-as> コンテンツをどのように解釈し、読み上げるかを指定します。数式読み上げに使用します。</say-as>

  • <prosody> 音声の音量、話速、ピッチを制御します。</prosody>

SSML (音声合成マークアップ言語) を使用すると、速度、ポーズ、発音などの音声特性を細かく調整できます。

概要

SSML (音声合成マークアップ言語) は、音声合成のための XML ベースのマークアップ言語です。テキストに SSML タグを埋め込むことで、話す速度、抑揚、間、音量を制御したり、背景音楽や効果音を追加したりして、より豊かなオーディオ出力を実現できます。

代表的なユースケースは次のとおりです。

  • オーディオブック:間や話す速度を精密に制御し、背景音楽を追加することで没入感のあるリスニング体験を提供します。

  • インテリジェントカスタマーサービス:<say-as> タグを使用して、電話番号、日付など、これらに類似した情報を正確に読み上げます。

  • 多言語放送:<phoneme> タグを使用して、外国語の単語の正確な発音を指定します。

  • オンライン教育:数式読み上げ機能を使用して、LaTeX 数式を自然な音声に変換します。

これらの機能は、CosyVoice モデルファミリーでご利用いただけます。モデル選択のガイダンスについては、「音声合成」をご参照ください。

SSML

制限事項

  • モデル: SSML は cosyvoice-v3.5-flash、cosyvoice-v3.5-plus、cosyvoice-v3-flash、cosyvoice-v3-plus、および cosyvoice-v2 でのみサポートされています。

  • 音声: CosyVoice 音声リスト で SSML 互換としてマークされているクローン音声とプリセット音声のみサポートされています。

  • API: SSML は、次の API でサポートされています。

    • Java SDK (バージョン 2.20.3 以降):非ストリーミングおよび単方向ストリーミングコール

    • Python SDK (バージョン 1.23.4 以降):非ストリーミングおよび単方向ストリーミングコール

    • WebSocket API: enable_ssml パラメーターを true に設定し、単一の continue-task イベントを送信します

クイックスタート

次の例では、SSML を使用して合成音声の話す速度を制御します。コードを実行する前に、次の前提条件を満たしてください:

  1. API キーの取得

  2. DashScope SDK (Python 1.23.4 以降、Java 2.20.3 以降) をインストールします。詳細については、「SDK のインストール」をご参照ください。

重要

cosyvoice-v3.5-plus および cosyvoice-v3.5-flash モデルは、現在北京リージョンでのみ利用可能で、音声クローニングシナリオ専用です(プリセット音声は提供されません)。これらのモデルを使用する前に、音声クローニングの手順に従ってターゲット音声を作成してください。

Java SDK

非ストリーミングコール

import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
import com.alibaba.dashscope.utils.Constants;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * SSML 機能の説明:
 *     1. 非ストリーミングコールと単方向ストリーミングコールのみが SSML 機能をサポートします
 *     2. cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされたシステム音声のみが SSML 機能をサポートします (例: cosyvoice-v3-flash モデルの longanyang 音声)
 */
public class Main {
    private static String model = "cosyvoice-v3-flash";
    private static String voice = "longanyang";

    public static void main(String[] args) {
        // シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
        Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
        streamAudioDataToSpeaker();
        System.exit(0);
    }

    public static void streamAudioDataToSpeaker() {
        SpeechSynthesisParam param =
                SpeechSynthesisParam.builder()
                        // シンガポールリージョンと北京リージョンでは API キーが異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
                        // 環境変数を設定していない場合は、次の行を実際の Model Studio の API キーに置き換えてください: .apiKey("sk-xxx")
                        .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                        .model(model)
                        .voice(voice)
                        .build();

        SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
        ByteBuffer audio = null;
        try {
            // 非ストリーミングコール。音声が返されるまでブロックします
            // 特殊文字はエスケープする必要があります
            audio = synthesizer.call("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>");
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // タスク終了時に WebSocket 接続を閉じます
            synthesizer.getDuplexApi().close(1000, "bye");
        }
        if (audio != null) {
            // 音声データをローカルファイル "output.mp3" に保存します
            File file = new File("output.mp3");
            try (FileOutputStream fos = new FileOutputStream(file)) {
                fos.write(audio.array());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        // 初回テキスト送信時には WebSocket 接続を確立する必要があるため、初回パケットレイテンシーには接続確立時間が含まれます
        System.out.println(
                "[Metric] requestId: "
                        + synthesizer.getLastRequestId()
                        + ", first packet latency (ms): "
                        + synthesizer.getFirstPackageDelay());
    }
}

単方向ストリーミングコール

import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisAudioFormat;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
import com.alibaba.dashscope.common.ResultCallback;
import com.alibaba.dashscope.utils.Constants;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;

/**
 * SSML 機能の説明:
 *     1. 非ストリーミングコールと単方向ストリーミングコールのみが SSML 機能をサポートします
 *     2. cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされたシステム音声のみが SSML 機能をサポートします (例: cosyvoice-v3-flash モデルの longanyang 音声)
 */
public class Main {
    private static String model = "cosyvoice-v3-flash";
    private static String voice = "longanyang";

    public static void main(String[] args) {
        // シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
        Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
        streamAudioDataToSpeaker();
        System.out.println("Audio has been saved to the output.mp3 file");
        System.exit(0);
    }

    public static void streamAudioDataToSpeaker() {
        CountDownLatch latch = new CountDownLatch(1);
        final FileOutputStream[] fileOutputStream = new FileOutputStream[1];

        try {
            fileOutputStream[0] = new FileOutputStream("output.mp3");
        } catch (IOException e) {
            System.err.println("Failed to create output file: " + e.getMessage());
            return;
        }

        // ResultCallback インターフェースを実装します
        ResultCallback<SpeechSynthesisResult> callback = new ResultCallback<SpeechSynthesisResult>() {
            @Override
            public void onEvent(SpeechSynthesisResult result) {
                if (result.getAudioFrame() != null) {
                    // 音声データをローカルファイルに書き込みます
                    try {
                        byte[] audioData = result.getAudioFrame().array();
                        fileOutputStream[0].write(audioData);
                        fileOutputStream[0].flush();
                    } catch (IOException e) {
                        System.err.println("Failed to write audio data: " + e.getMessage());
                    }
                }
            }

            @Override
            public void onComplete() {
                System.out.println("Received Complete, speech synthesis finished");
                closeFileOutputStream(fileOutputStream[0]);
                latch.countDown();
            }

            @Override
            public void onError(Exception e) {
                System.out.println("An error occurred: " + e.toString());
                closeFileOutputStream(fileOutputStream[0]);
                latch.countDown();
            }
        };

        SpeechSynthesisParam param =
                SpeechSynthesisParam.builder()
                        // シンガポールリージョンと北京リージョンでは API キーが異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
                        // 環境変数を設定していない場合は、次の行を実際の Model Studio の API キーに置き換えてください: .apiKey("sk-xxx")
                        .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                        .model(model)
                        .voice(voice)
                        .format(SpeechSynthesisAudioFormat.MP3_22050HZ_MONO_256KBPS)
                        .build();

        SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, callback);

        try {
            // 単方向ストリーミングコール。すぐに null を返します (実際の結果はコールバックインターフェースを介して非同期に配信されます)。コールバックインターフェースの onEvent メソッドからリアルタイムでバイナリ音声を取得します
            // 特殊文字はエスケープする必要があります
            synthesizer.call("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>");
            // 合成が完了するのを待ちます
            latch.await();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // タスク終了後に WebSocket 接続を閉じます
            try {
                synthesizer.getDuplexApi().close(1000, "bye");
            } catch (Exception e) {
                System.err.println("Failed to close WebSocket connection: " + e.getMessage());
            }

            // ファイルストリームが確実に閉じられるようにします
            closeFileOutputStream(fileOutputStream[0]);
        }

        // 初回テキスト送信時には WebSocket 接続を確立する必要があるため、初回パケットレイテンシーには接続確立時間が含まれます
        System.out.println(
                "[Metric] requestId: "
                        + synthesizer.getLastRequestId()
                        + ", first packet latency (ms): "
                        + synthesizer.getFirstPackageDelay());
    }

    private static void closeFileOutputStream(FileOutputStream fileOutputStream) {
        try {
            if (fileOutputStream != null) {
                fileOutputStream.close();
            }
        } catch (IOException e) {
            System.err.println("Failed to close file stream: " + e.getMessage());
        }
    }
}

Python SDK

非ストリーミング呼び出し

# coding=utf-8
# SSML 機能の説明:
#     1. SSML 機能は、非ストリーミング呼び出しと単方向ストリーミング呼び出しでのみサポートされます。
#     2. SSML 機能は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされたシステム音声でのみサポートされます (例: cosyvoice-v3-flash モデルの longanyang 音声)。

import dashscope
from dashscope.audio.tts_v2 import *
import os

# シンガポールと北京リージョンの API キーは異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
# 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# シンガポールリージョンの URL。WorkspaceId をご自身のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'

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

# SpeechSynthesizer をインスタンス化し、コンストラクターに model や voice などのリクエストパラメーターを渡します
synthesizer = SpeechSynthesizer(model=model, voice=voice)
# 非ストリーミング呼び出し。音声が返されるまでブロックします
# 特殊文字はエスケープする必要があります
audio = synthesizer.call("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>")

# 音声をローカルファイルに保存します
with open('output.mp3', 'wb') as f:
    f.write(audio)

# 最初のテキスト送信では WebSocket 接続の確立が必要なため、初回パケットレイテンシーには接続確立時間が含まれます
print('[Metric] requestId: {}, first packet latency: {} ms'.format(
    synthesizer.get_last_request_id(),
    synthesizer.get_first_package_delay()))

単方向ストリーミング呼び出し

# coding=utf-8
# SSML 機能の説明:
#     1. SSML 機能は、非ストリーミング呼び出しと単方向ストリーミング呼び出しでのみサポートされます。
#     2. SSML 機能は、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされたシステム音声でのみサポートされます (例: cosyvoice-v3-flash モデルの longanyang 音声)。

import dashscope
from dashscope.audio.tts_v2 import *
import os
from datetime import datetime

def get_timestamp():
    now = datetime.now()
    formatted_timestamp = now.strftime("[%Y-%m-%d %H:%M:%S.%f]")
    return formatted_timestamp

# シンガポールと北京リージョンの API キーは異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
# 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# シンガポールリージョンの URL。WorkspaceId をご自身のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'

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

# コールバックインターフェイスを定義します
class Callback(ResultCallback):
    _player = None
    _stream = None

    def on_open(self):
        # 音声データを書き込むための出力ファイルを開きます
        self.file = open("output.mp3", "wb")
        print("Connection established: " + get_timestamp())

    def on_complete(self):
        print("Speech synthesis completed, all results have been received: " + get_timestamp())
        if hasattr(self, 'file') and self.file:
            self.file.close()
        self
        # 最初のテキスト送信では WebSocket 接続の確立が必要なため、初回パケットレイテンシーには接続確立時間が含まれます
        print('[Metric] requestId: {}, first packet latency: {} ms'.format(
            self.synthesizer.get_last_request_id(),
            self.synthesizer.get_first_package_delay()))

    def on_error(self, message: str):
        print(f"Speech synthesis error occurred: {message}")
        if hasattr(self, 'file') and self.file:
            self.file.close()

    def on_close(self):
        print("Connection closed: " + get_timestamp())
        if hasattr(self, 'file') and self.file:
            self.file.close()

    def on_event(self, message):
        pass

    def on_data(self, data: bytes) -> None:
        print(get_timestamp() + " Binary audio length: " + str(len(data)))
        # 音声データをファイルに書き込みます
        self.file.write(data)

callback = Callback()

# SpeechSynthesizer をインスタンス化し、コンストラクターに model や voice などのリクエストパラメーターを渡します
synthesizer = SpeechSynthesizer(
    model=model,
    voice=voice,
    callback=callback,
)

# on_complete で使用できるように、synthesizer インスタンスを callback に割り当てます
callback.synthesizer = synthesizer

# 単方向ストリーミング呼び出し。合成対象のテキストを送信し、コールバックインターフェイスの on_data メソッドからリアルタイムにバイナリ音声を取得します
# 特殊文字はエスケープする必要があります
synthesizer.call("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>")

WebSocket API

Go

// SSML 機能の説明:
//     1. run-task コマンドを送信する際に、enable_ssml パラメータを true に設定して SSML サポートを有効にします。
//     2. continue-task コマンドを介して SSML を含むテキストを送信し、continue-task コマンドは 1 回しか送信できません。
//     3. cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされているシステム音声のみが SSML 機能をサポートします (例:cosyvoice-v3-flash モデルの longanyang 音声)。

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strings"
    "time"

    "github.com/google/uuid"
    "github.com/gorilla/websocket"
)

const (
    // シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
    wsURL      = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/"
    outputFile = "output.mp3"
)

func main() {
    // シンガポールと北京リージョンの API キーは異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
    // 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください: apiKey := "sk-xxx"
    apiKey := os.Getenv("DASHSCOPE_API_KEY")

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

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

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

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

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

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

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

    textSent := false

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

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

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

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

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

                        if !textSent {
                            // continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
                            continueTaskCmd := map[string]interface{}{
                                "header": map[string]interface{}{
                                    "action":    "continue-task",
                                    "task_id":   taskID,
                                    "streaming": "duplex",
                                },
                                "payload": map[string]interface{}{
                                    "input": map[string]interface{}{
                                        // 特殊文字はエスケープする必要があります
                                        "text": "<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>",
                                    },
                                },
                            }

                            continueTaskJSON, _ := json.Marshal(continueTaskCmd)
                            fmt.Printf("Sending continue-task command: %s\n", string(continueTaskJSON))

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

                            textSent = true

                            // finish-task を送信する前に待機します
                            time.Sleep(500 * time.Millisecond)

                            // finish-task コマンドを送信します
                            finishTaskCmd := map[string]interface{}{
                                "header": map[string]interface{}{
                                    "action":    "finish-task",
                                    "task_id":   taskID,
                                    "streaming": "duplex",
                                },
                                "payload": map[string]interface{}{
                                    "input": map[string]interface{}{},
                                },
                            }

                            finishTaskJSON, _ := json.Marshal(finishTaskCmd)
                            fmt.Printf("Sending finish-task command: %s\n", string(finishTaskJSON))

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

                    case "task-finished":
                        fmt.Println("=== Task completed ===")
                        return

                    case "task-failed":
                        fmt.Println("=== Task failed ===")
                        if header["error_message"] != nil {
                            fmt.Printf("Error message: %s\n", header["error_message"])
                        }
                        return

                    case "result-generated":
                        fmt.Println("Received result-generated event")
                    }
                }
            }
        }
    }
}

C#

using System.Net.WebSockets;
using System.Text;
using System.Text.Json;

// SSML 機能の説明:
//     1. run-task コマンドを送信する際に、enable_ssml パラメーターを true に設定して SSML サポートを有効にします
//     2. SSML を含むテキストを continue-task コマンドで送信します。このコマンドは 1 回のみ送信できます
//     3. cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応と記載されているシステム音声のみが SSML 機能をサポートします (例:cosyvoice-v3-flash モデルの longanyang 音声)
class Program {
    // シンガポールリージョンと北京リージョンでは API キーが異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
    // 環境変数を設定していない場合は、次の行を実際の Model Studio API キーに置き換えてください: private static readonly string ApiKey = "sk-xxx"
    private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("DASHSCOPE_API_KEY environment variable is not set.");

    // シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
    private const string WebSocketUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/";
    // 出力ファイルパス
    private const string OutputFilePath = "output.mp3";

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

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

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

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

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

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

            // continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
            // 特殊文字はエスケープする必要があります
            await SendContinueTaskCommandAsync("<speak rate=\"2\">私の話す速度は普通の人より速いです。</speak>");

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

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

            Console.WriteLine("Task completed, connection closed.");
        } catch (OperationCanceledException) {
            Console.WriteLine("Task was cancelled.");
        } catch (Exception ex) {
            Console.WriteLine($"An error occurred: {ex.Message}");
        } finally {
            _cancellationTokenSource.Cancel();
            _webSocket.Dispose();
        }
    }

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

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

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

        try {
            await _webSocket.ConnectAsync(uri, _cancellationTokenSource.Token);
            Console.WriteLine("Successfully connected to the WebSocket service.");
        } catch (OperationCanceledException) {
            Console.WriteLine("WebSocket connection was cancelled.");
        } catch (Exception ex) {
            Console.WriteLine($"WebSocket connection failed: {ex.Message}");
            throw;
        }
    }

    private static async Task SendRunTaskCommandAsync(string taskId) {
        var command = CreateCommand("run-task", taskId, "duplex", new {
            task_group = "audio",
            task = "tts",
            function = "SpeechSynthesizer",
            model = "cosyvoice-v3-flash",
            parameters = new
            {
                text_type = "PlainText",
                voice = "longanyang",
                format = "mp3",
                sample_rate = 22050,
                volume = 50,
                rate = 1,
                pitch = 1,
                // enable_ssml が true に設定されている場合、continue-task コマンドは 1 回しか送信できません。そうでない場合、エラー "Text request limit violated, expected 1." が返されます
                enable_ssml = true
            },
            input = new { }
        });

        await SendJsonMessageAsync(command);
        Console.WriteLine("run-task command sent.");
    }

    private static async Task SendContinueTaskCommandAsync(string text) {
        if (_taskId == null) {
            throw new InvalidOperationException("Task ID is not initialized.");
        }

        var command = CreateCommand("continue-task", _taskId, "duplex", new {
            input = new {
                text
            }
        });

        await SendJsonMessageAsync(command);
        Console.WriteLine("continue-task command sent.");
    }

    private static async Task SendFinishTaskCommandAsync(string taskId) {
        var command = CreateCommand("finish-task", taskId, "duplex", new {
            input = new { }
        });

        await SendJsonMessageAsync(command);
        Console.WriteLine("finish-task command sent.");
    }

    private static async Task SendJsonMessageAsync(string message) {
        var buffer = Encoding.UTF8.GetBytes(message);
        try {
            await _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, _cancellationTokenSource.Token);
        } catch (OperationCanceledException) {
            Console.WriteLine("Message sending was cancelled.");
        }
    }

    private static async Task ReceiveMessagesAsync() {
        while (_webSocket.State == WebSocketState.Open) {
            var response = await ReceiveMessageAsync();
            if (response != null) {
                var eventStr = response.RootElement.GetProperty("header").GetProperty("event").GetString();
                switch (eventStr) {
                    case "task-started":
                        Console.WriteLine("Task started.");
                        _taskStartedTcs.TrySetResult(true);
                        break;
                    case "task-finished":
                        Console.WriteLine("Task finished.");
                        _cancellationTokenSource.Cancel();
                        break;
                    case "task-failed":
                        Console.WriteLine("Task failed: " + response.RootElement.GetProperty("header").GetProperty("error_message").GetString());
                        _cancellationTokenSource.Cancel();
                        break;
                    default:
                        // ここで result-generated を処理できます
                        break;
                }
            }
        }
    }

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

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

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

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

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

                return null;
            }

            string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
            return JsonDocument.Parse(message);
        } catch (OperationCanceledException) {
            Console.WriteLine("Message receiving was cancelled.");
            return null;
        }
    }

    private static string GenerateTaskId() {
        return Guid.NewGuid().ToString("N").Substring(0, 32);
    }

    private static string CreateCommand(string action, string taskId, string streaming, object payload) {
        var command = new {
            header = new {
                action,
                task_id = taskId,
                streaming
            },
            payload
        };

        return JsonSerializer.Serialize(command);
    }
}

PHP

サンプルコードのディレクトリ構造は次のとおりです。

my-php-project/

├── composer.json

├── vendor/

└── index.php

composer.json ファイルには、次の依存関係が含まれています。必要に応じてバージョン番号を調整してください。

{
    "require": {
        "react/event-loop": "^1.3",
        "react/socket": "^1.11",
        "react/stream": "^1.2",
        "react/http": "^1.1",
        "ratchet/pawl": "^0.4"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

index.php ファイルには、次のコードが含まれています。

<!-- SSML 機能の説明: -->
<!--     1. run-task コマンドを送信する際に、enable_ssml パラメーターを true に設定して SSML サポートを有効にします -->
<!--     2. SSML を含むテキストを continue-task コマンドで送信します。continue-task コマンドは1回しか送信できません -->
<!--     3. cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされたシステム音声のみが SSML 機能をサポートします (例:cosyvoice-v3-flash モデルの longanyang 音声) -->

<?php

require __DIR__ . '/vendor/autoload.php';

use Ratchet\Client\Connector;
use React\EventLoop\Loop;
use React\Socket\Connector as SocketConnector;

// シンガポールリージョンと北京リージョンでは API キーが異なります。API キーの取得方法: https://www.alibabacloud.com/help/en/model-studio/get-api-key
// 環境変数を設定していない場合は、次の行を実際の Model Studio API キーに置き換えてください: $api_key = "sk-xxx"
$api_key = getenv("DASHSCOPE_API_KEY");
// シンガポールリージョンの URL。{WorkspaceId} を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
$websocket_url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/'; // WebSocket サーバーアドレス
$output_file = 'output.mp3'; // 出力ファイルパス

$loop = Loop::get();

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

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

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

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

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

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

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

    // continue-task コマンドを送信する関数を定義します
    $sendContinueTask = function() use ($conn, $loop, $taskId) {
        // continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
        $continueTaskMessage = json_encode([
            "header" => [
                "action" => "continue-task",
                "task_id" => $taskId,
                "streaming" => "duplex"
            ],
            "payload" => [
                "input" => [
                    // 特殊文字はエスケープする必要があります
                    "text" => "<speak rate=\"2\">私の話速は、一般的な人よりも速いです。</speak>"
                ]
            ]
        ]);
        $conn->send($continueTaskMessage);

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

    // task-started イベントを受信したかどうかを示すフラグ
    $taskStarted = false;

    // メッセージをリッスンします
    $conn->on('message', function($msg) use ($conn, $sendContinueTask, $loop, &$taskStarted, $taskId, $output_file) {
        if ($msg->isBinary()) {
            // バイナリデータをローカルファイルに書き込みます
            file_put_contents($output_file, $msg->getPayload(), FILE_APPEND);
        } else {
            // バイナリ以外のメッセージを処理します
            $response = json_decode($msg, true);

            if (isset($response['header']['event'])) {
                handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, $taskStarted);
            } else {
                echo "Unknown message format\n";
            }
        }
    });

    // 接続クローズをリッスンします
    $conn->on('close', function($code = null, $reason = null) {
        echo "Connection closed\n";
        if ($code !== null) {
            echo "Close code: " . $code . "\n";
        }
        if ($reason !== null) {
            echo "Close reason: " . $reason . "\n";
        }
    });
}, function ($e) {
    echo "Unable to connect: {$e->getMessage()}\n";
});

$loop->run();

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

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

/**
 * 音声ファイルを読み込みます
 * @param string $filePath
 * @return bool|string
 */
function readAudioFile(string $filePath) {
    $voiceData = file_get_contents($filePath);
    if ($voiceData === false) {
        echo "Unable to read audio file\n";
    }
    return $voiceData;
}

/**
 * 音声データを分割します
 * @param string $data
 * @param int $chunkSize
 * @return array
 */
function splitAudioData(string $data, int $chunkSize): array {
    return str_split($data, $chunkSize);
}

/**
 * finish-task コマンドを送信します
 * @param $conn
 * @param $taskId
 */
function sendFinishTaskMessage($conn, $taskId) {
    $finishTaskMessage = json_encode([
        "header" => [
            "action" => "finish-task",
            "task_id" => $taskId,
            "streaming" => "duplex"
        ],
        "payload" => [
            "input" => (object) []
        ]
    ]);
    echo "Preparing to send finish-task command: " . $finishTaskMessage . "\n";
    $conn->send($finishTaskMessage);
    echo "finish-task command sent\n";
}

/**
 * イベントを処理します
 * @param $conn
 * @param $response
 * @param $sendContinueTask
 * @param $loop
 * @param $taskId
 * @param $taskStarted
 */
function handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, &$taskStarted) {
    switch ($response['header']['event']) {
        case 'task-started':
            echo "タスクが開始されました、continue-task コマンドを送信しています...\n";
            $taskStarted = true;
            // continue-task コマンドを送信します
            $sendContinueTask();
            break;
        case 'result-generated':
            // result-generated イベントを無視します
            break;
        case 'task-finished':
            echo "タスクが完了しました\n";
            $conn->close();
            break;
        case 'task-failed':
            echo "タスクが失敗しました\n";
            echo "エラーコード: " . $response['header']['error_code'] . "\n";
            echo "エラーメッセージ: " . $response['header']['error_message'] . "\n";
            $conn->close();
            break;
        case 'error':
            echo "エラー: " . $response['payload']['message'] . "\n";
            break;
        default:
            echo "不明なイベント: " . $response['header']['event'] . "\n";
            break;
    }

    // タスクが完了した場合、接続を閉じます
    if ($response['header']['event'] == 'task-finished') {
        // すべてのデータが確実に送信されるよう、1秒間待機します
        $loop->addTimer(1, function() use ($conn) {
            $conn->close();
            echo "クライアントが接続を閉じました\n";
        });
    }

    // task-started イベントが受信されていない場合、接続を閉じます
    if (!$taskStarted && in_array($response['header']['event'], ['task-failed', 'error'])) {
        $conn->close();
    }
}

Node.js

必要な依存関係をインストールします:

npm install ws
npm install uuid

サンプルコード:

// SSML 機能の説明:
//     1. run-task コマンドを送信する際に enable_ssml パラメーターを true に設定すると、SSML サポートが有効になります。
//     2. continue-task コマンドで SSML を含むテキストを送信します。continue-task コマンドは 1 回しか送信できません。
//     3. SSML 機能をサポートしているのは、cosyvoice-v3-flash、cosyvoice-v3-plus、および cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされているシステム音声のみです (例:cosyvoice-v3-flash モデルの longanyang 音声)。

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

// シンガポールと北京リージョンの API キーは異なります。API キーの取得:https://www.alibabacloud.com/help/model-studio/get-api-key
// 環境変数を設定していない場合は、次の行を Model Studio API キーに置き換えてください:const apiKey = "sk-xxx"
const apiKey = process.env.DASHSCOPE_API_KEY;
// シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
const url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/';
// 出力ファイルパス
const outputFilePath = 'output.mp3';

// 出力ファイルをクリアします
fs.writeFileSync(outputFilePath, '');

// WebSocket クライアントを作成します
const ws = new WebSocket(url, {
  headers: {
    Authorization: `bearer ${apiKey}`,
    'X-DashScope-DataInspection': 'enable'
  }
});

let taskStarted = false;
let taskId = uuid();

ws.on('open', () => {
  console.log('Connected to WebSocket server');

  // run-task コマンドを送信します
  const runTaskMessage = JSON.stringify({
    header: {
      action: 'run-task',
      task_id: taskId,
      streaming: 'duplex'
    },
    payload: {
      task_group: 'audio',
      task: 'tts',
      function: 'SpeechSynthesizer',
      model: 'cosyvoice-v3-flash',
      parameters: {
        text_type: 'PlainText',
        voice: 'longanyang', // 音声
        format: 'mp3', // 音声フォーマット
        sample_rate: 22050, // サンプルレート
        volume: 50, // 音量
        rate: 1, // 話速
        pitch: 1, // ピッチ
        enable_ssml: true // SSML を有効にするかどうか。enable_ssml を true に設定した場合、continue-task コマンドは 1 回しか送信できません。そうでない場合は、エラー "Text request limit violated, expected 1." が返されます
      },
      input: {}
    }
  });
  ws.send(runTaskMessage);
  console.log('run-task message sent');
});

const fileStream = fs.createWriteStream(outputFilePath, { flags: 'a' });
ws.on('message', (data, isBinary) => {
  if (isBinary) {
    // バイナリデータをファイルに書き込みます
    fileStream.write(data);
  } else {
    const message = JSON.parse(data);

    switch (message.header.event) {
      case 'task-started':
        taskStarted = true;
        console.log('Task started');
        // continue-task コマンドを送信します
        sendContinueTasks(ws);
        break;
      case 'task-finished':
        console.log('Task completed');
        ws.close();
        fileStream.end(() => {
          console.log('File stream closed');
        });
        break;
      case 'task-failed':
        console.error('Task failed:', message.header.error_message);
        ws.close();
        fileStream.end(() => {
          console.log('File stream closed');
        });
        break;
      default:
        // result-generated はここで処理できます
        break;
    }
  }
});

function sendContinueTasks(ws) {

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

    // finish-task コマンドを送信します
    const finishTaskMessage = JSON.stringify({
      header: {
        action: 'finish-task',
        task_id: taskId,
        streaming: 'duplex'
      },
      payload: {
        input: {}
      }
    });
    ws.send(finishTaskMessage);
  }
}

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

Java

Java 開発では、Java DashScope SDK を使用します。詳細については、「Java SDK」をご参照ください。

この Java WebSocket の例では、次の依存関係が必要です:

  • Java-WebSocket

  • jackson-databind

これらの依存関係は、Maven または Gradle で管理します:

pom.xml

<dependencies>
    <!-- WebSocketクライアント -->
    <dependency>
        <groupId>org.java-websocket</groupId>
        <artifactId>Java-WebSocket</artifactId>
        <version>1.5.3</version>
    </dependency>

    <!-- JSON処理 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.13.0</version>
    </dependency>
</dependencies>

build.gradle

dependencies {
  // WebSocketクライアント
  implementation 'org.java-websocket:Java-WebSocket:1.5.3'
  // JSON処理
  implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
}

Java コード:

import com.fasterxml.jackson.databind.ObjectMapper;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.*;

/**
 * SSML 機能の説明:
 *     1. run-task コマンドを送信する際に、enable_ssml パラメーターを true に設定して SSML サポートを有効にします
 *     2. continue-task コマンドで SSML を含むテキストを送信します。continue-task コマンドは 1 回のみ送信可能です
 *     3. SSML 機能をサポートするのは、cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声と、音声リストで SSML 対応とマークされたシステム音声のみです (例:cosyvoice-v3-flash モデルの longanyang 音声)
 */
public class TTSWebSocketClient extends WebSocketClient {
    private final String taskId = UUID.randomUUID().toString();
    private final String outputFile = "output_" + System.currentTimeMillis() + ".mp3";
    private boolean taskFinished = false;

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

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

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

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

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

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

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

                        // continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
                        // 特殊文字はエスケープする必要があります
                        sendContinueTask("<speak rate=\\\"2\\\">私の話速は通常の人より速いです。</speak>");

                        // finish-task コマンドを送信します
                        sendFinishTask();
                    } else if ("task-finished".equals(event)) {
                        System.out.println("Received task-finished event from server");
                        taskFinished = true;
                        closeConnection();
                    } else if ("task-failed".equals(event)) {
                        System.out.println("Task failed: " + message);
                        closeConnection();
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("An error occurred: " + e.getMessage());
        }
    }

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

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

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

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

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

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

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

    public static void main(String[] args) {
        try {
            // シンガポールリージョンと北京リージョンでは API キーが異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
            // 環境変数を設定していない場合は、次の行を実際の Model Studio API キーに置き換えてください: String apiKey = "sk-xxx"
            String apiKey = System.getenv("DASHSCOPE_API_KEY");
            if (apiKey == null || apiKey.isEmpty()) {
                System.err.println("Please set the DASHSCOPE_API_KEY environment variable");
                return;
            }

            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", "bearer " + apiKey);
            // シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
            TTSWebSocketClient client = new TTSWebSocketClient(new URI("wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/"), headers);

            client.connect();

            while (!client.isClosed() && !client.taskFinished) {
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            System.err.println("Failed to connect to WebSocket service: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Python

Python 開発には、Python DashScope SDK を使用します。詳細については、「Python SDK」をご参照ください。

以下は Python WebSocket の例です。実行する前に、必要な依存関係をインストールしてください:

pip uninstall websocket-client
pip uninstall websocket
pip install websocket-client
重要

Python ファイルに "websocket.py" という名前を付けないでください。これにより命名の競合が発生し、エラー (AttributeError: module 'websocket' has no attribute 'WebSocketApp'. Did you mean: 'WebSocket'?) が発生します。

# SSML 機能の説明:
#     1. run-task コマンドを送信する際に、enable_ssml パラメーターを true に設定して SSML サポートを有効にします
#     2. SSML を含むテキストを continue-task コマンドで送信します。continue-task コマンドは1回しか使用できません
#     3. cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2 モデルのクローン音声、および音声リストで SSML 対応とマークされたシステム音声のみが SSML 機能をサポートします (例: cosyvoice-v3-flash モデルの longanyang 音声)

import websocket
import json
import uuid
import os
import time

class TTSClient:
    def __init__(self, api_key, uri):
        """
    TTSClient インスタンスを初期化します

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

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

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

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

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

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

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

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

                            # continue-task コマンドを送信します。SSML を使用する場合、このコマンドは 1 回しか送信できません
                            # 特殊文字はエスケープする必要があります
                            self.send_continue_task("<speak rate=\"2\">私の話速は通常の人より速いです。</speak>")

                            # continue-task が送信された後、finish-task を送信します
                            self.send_finish_task()

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

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

            except json.JSONDecodeError as e:
                print(f"JSON parsing failed: {e}")
        else:
            # バイナリメッセージ (音声データ) を処理します
            print(f"Received binary message, size: {len(message)} bytes")
            with open(self.output_file, "ab") as f:
                f.write(message)
            print(f"音声データをローカルファイル {self.output_file} に書き込みました")

    def on_error(self, ws, error):
        """エラーが発生したときのコールバック"""
        print(f"WebSocket error: {error}")

    def on_close(self, ws, close_status_code, close_msg):
        """接続が閉じたときのコールバック"""
        print(f"WebSocket closed: {close_msg} ({close_status_code})")

    def send_continue_task(self, text):
        """合成するテキストコンテンツを含む continue-task コマンドを送信します"""
        cmd = {
            "header": {
                "action": "continue-task",
                "task_id": self.task_id,
                "streaming": "duplex"
            },
            "payload": {
                "input": {
                    "text": text
                }
            }
        }

        self.ws.send(json.dumps(cmd))
        print(f"continue-task command sent, text content: {text}")

    def send_finish_task(self):
        """音声合成タスクを終了するために finish-task コマンドを送信します"""
        cmd = {
            "header": {
                "action": "finish-task",
                "task_id": self.task_id,
                "streaming": "duplex"
            },
            "payload": {
                "input": {}
            }
        }

        self.ws.send(json.dumps(cmd))
        print("finish-task command sent")

    def close(self, ws):
        """接続を能動的に閉じます"""
        if ws and ws.sock and ws.sock.connected:
            ws.close()
            print("接続を能動的に閉じました")

    def run(self):
        """WebSocket クライアントを開始します"""
        # リクエストヘッダー (認証) を設定します
        header = {
            "Authorization": f"bearer {self.api_key}",
            "X-DashScope-DataInspection": "enable"
        }

        # WebSocketApp インスタンスを作成します
        self.ws = websocket.WebSocketApp(
            self.uri,
            header=header,
            on_open=self.on_open,
            on_message=self.on_message,
            on_error=self.on_error,
            on_close=self.on_close
        )

        print("WebSocket メッセージを待機中...")
        self.ws.run_forever()  # 永続的な接続リスナーを開始します

# 使用例
if __name__ == "__main__":
    # シンガポールリージョンと北京リージョンでは API キーが異なります。API キーの取得: https://www.alibabacloud.com/help/model-studio/get-api-key
    # 環境変数を設定していない場合は、次の行を実際の Model Studio API キーに置き換えてください: API_KEY = "sk-xxx"
    API_KEY = os.environ.get("DASHSCOPE_API_KEY")
    # シンガポールリージョンの URL。WorkspaceId を実際のワークスペース ID に置き換えてください。URL はリージョンによって異なります。
    SERVER_URI = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/"

    client = TTSClient(API_KEY, SERVER_URI)
    client.run()

タグ リファレンス

説明

Alibaba Cloud の SSML 実装は、W3C の SSML 1.0 仕様に基づいています。すべての標準タグがサポートされているわけではなく、本サービスでは、本番シナリオで最も一般的に使用されるタグを実装しています。

  • SSML を使用する場合、すべてのテキストコンテンツは <speak></speak> タグで囲む必要があります。

  • 複数の <speak> タグを連続して使用すること (例: <speak></speak><speak></speak>) はできますが、ネスト (例: <speak><speak></speak></speak>) はサポートされていません。

  • タグ内のテキストに XML の特殊文字が含まれている場合は、次のようにエスケープしてください:

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

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

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

    • < (小なり記号) → <

    • > (大なり記号) → >

<speak>:ルート要素

  • 説明

    <speak> は、すべての SSML コンテンツのルート要素です。すべてのテキストは <speak></speak> タグで囲む必要があります。

  • 構文

     <speak>SSML 処理が必要なテキスト</speak>
  • 属性

    属性

    必須

    説明

    voice

    String

    いいえ

    音声を指定します。

    この属性は、API リクエストの voice パラメーターよりも優先されます。

    • 有効な値:特定の音声名。詳細については、「cosyvoice-v2 voices」をご参照ください。

    • 例:

      <speak voice="longcheng_v2">
        私は男性の声です。
      </speak>

    rate

    String

    いいえ

    読み上げ速度を指定します。この属性は、API リクエストの speech_rate パラメーターよりも優先されます。

    • 有効な値:0.5 から 2 までの小数 (両端を含む)

    • デフォルト値:1

      • 1 より大きい値は読み上げ速度を速めます

      • 1 より小さい値は読み上げ速度を遅めます

    • 例:

      <speak rate="2">
        私の読み上げ速度は通常より速いです。
      </speak>

    pitch

    String

    いいえ

    ピッチを指定します。この属性は、API リクエストの pitch_rate パラメーターよりも優先されます。

    • 有効な値:0.5 から 2 までの小数 (両端を含む)

    • デフォルト値:1

      • 1 より大きい値はピッチを高くします

      • 1 より小さい値はピッチを低くします

    • 例:

      <speak pitch="0.5">
        しかし、私のピッチは他の人より低いです。
      </speak>

    volume

    String

    いいえ

    音量を指定します。この属性は、API リクエストの volume パラメーターよりも優先されます。

    • 有効な値:0 から 100 までの整数 (両端を含む)

    • デフォルト値:50

      • 50 より大きい値は音量を大きくします

      • 50 より小さい値は音量を小さくします

    • 例:

      <speak volume="80">
        私の音量もとても大きいです。
      </speak>

    effect

    String

    いいえ

    オーディオエフェクトを指定します。

    • 有効な値:

      • robot:ロボット音声エフェクト

      • lolita:ロリータ音声エフェクト

      • lowpass:ローパスフィルターエフェクト

      • echo:エコーエフェクト

      • eq:イコライザー (高度)

      • lpfilter:ローパスフィルター (高度)

      • hpfilter:ハイパスフィルター (高度)

      説明
      • eq、lpfilter、hpfilter は高度なエフェクトタイプです。effectValue パラメーターを使用して特定のエフェクトをカスタマイズします。

      • 各 SSML タグは、1 つのエフェクトのみに対応します。複数の effect 属性を同時に設定することはできません。

      • オーディオエフェクトを有効にすると、合成レイテンシーが増加します。

    • 例:

      <speak effect="robot">
        ロボットのウォーリーは好きですか?
      </speak>

    effectValue

    String

    いいえ

    オーディオエフェクト (effect パラメーター) の具体的な動作を設定します。eq、lpfilter、hpfilter の 3 つの高度なエフェクトタイプに適用されます。

    • 有効な値:

      • eq (イコライザー):システムはデフォルトで 8 つの周波数帯をサポートし、それぞれ次の周波数に対応します:

        ["40 Hz","100 Hz", "200 Hz", "400 Hz", "800 Hz", "1600 Hz", "4000 Hz", "12000 Hz"]

        各帯域の帯域幅は 1.0q です。

        effectValue パラメーターを使用して、各帯域のゲインを指定します。パラメーターは、-20 から 20 までの 8 つの整数をスペースで区切った文字列です。値 0 は、その周波数のゲイン調整がないことを意味します。

        例:effectValue="1 1 1 1 1 1 1 1"

      • lpfilter (ローパスフィルター):カットオフ周波数を指定します。有効な値:(0, target_sample_rate/2] の範囲内の整数。例:effectValue="800"

      • hpfilter (ハイパスフィルター):カットオフ周波数を指定します。有効な値:(0, target_sample_rate/2] の範囲内の整数。例:effectValue="1200"

    • 例:

      <speak effect="eq" effectValue="1 -20 1 1 1 1 20 1">
        ロボットのウォーリーは好きですか?
      </speak>
      
      <speak effect="lpfilter" effectValue="1200">
        ロボットのウォーリーは好きですか?
      </speak>
      
      <speak effect="hpfilter" effectValue="1200">
        ロボットのウォーリーは好きですか?
      </speak>

    bgm

    String

    いいえ

    合成音声に BGM を追加します。音声ファイルは Alibaba Cloud OSS に保存する必要があり (「オブジェクトのアップロード」をご参照ください)、バケットには少なくともパブリック読み取りアクセス権が必要です。

    BGM の URL に XML の特殊文字 (&<> など) が含まれている場合は、エスケープしてください。

    • 音声要件:

      BGM ファイルのサイズに上限はありませんが、ファイルサイズが大きいほどダウンロードに時間がかかります。合成音声が BGM より長い場合、BGM は自動的にループします。

      • サンプルレート:16 kHz

      • チャネル:モノラル

      • フォーマット:WAV

        WAV 以外のファイルを変換するには、ffmpeg を使用します:

        ffmpeg -i input_audio -acodec pcm_s16le -ac 1 -ar 16000 output.wav
      • ビット深度:16 ビット

    • 例:

      <speak bgm="http://nls.alicdn.com/bgm/2.wav" backgroundMusicVolume="30" rate="0.8" volume="40">
        <break time="2s"/>
        陰崖の古木は霧に包まれ
        <break time="700ms"/>
        雨音はなお竹林に響く
        <break time="700ms"/>
        我知る、綿は国の計に資すると
        <break time="700ms"/>
        綿州の景色は常に憐れむべし
        <break time="2s"/>
      </speak>
    重要

    アップロードされた音声の著作権については、お客様が責任を負うものとします。

    backgroundMusicVolume

    String

    いいえ

    BGM の音量を指定します。この属性は bgm 属性と一緒に使用します。

  • タグの関係

    <speak> タグは、テキストと次の子タグを含むことができます:

  • その他の例

    • 属性なし

      <speak>
        SSML タグが必要なテキスト
      </speak>
    • 属性の組み合わせ (スペース区切り)

      <speak rate="1.5" pitch="0.8" volume="80">
        すべてを組み合わせると、私の声はこんな風に聞こえます。
      </speak>

<break>:ポーズ期間の制御

  • 説明

    音声合成中に無音のポーズを挿入し、会話における自然な間をシミュレートします。時間単位として、秒 (s) とミリ秒 (ms) をサポートします。

  • 構文

    # 属性なし
    <break/>
    # time 属性あり
    <break time="string"/>
  • 属性

    説明

    属性のない <break> タグは、デフォルトで 1 秒間のポーズになります。

    属性

    必須

    説明

    time

    文字列

    いいえ

    ポーズ期間を秒またはミリ秒で指定します (例: "2s" または "50ms")。

    • 有効な値:

      • 秒 (s) 単位:1 から 10 までの整数 (両端の値を含む)

      • ミリ秒 (ms) 単位:50 から 10000 までの整数 (両端の値を含む)

    • 例:

      <speak>
        目を閉じて、休憩してください。<break time="500ms"/>はい、目を開けてください。
      </speak>
    重要

    複数の <break> タグが連続して使用された場合、合計ポーズ期間は各期間の合計になります。合計が 10 秒を超える場合、10 秒に短縮されます。

    例えば、次の SSML では、<break> の累積ポーズ期間は 15 秒です。これは 10 秒の制限を超えるため、実際のポーズは 10 秒に短縮されます:

    <speak>
      目を閉じて、休憩してください。<break time="5s"/><break time="5s"/><break time="5s"/>はい、目を開けてください。
    </speak>
  • タグの関係

    <break> は空要素であり、子要素を含むことはできません。

<sub>:テキストの置換

  • 説明

    指定されたテキストを、音声での読み上げに適したコンテンツに置き換えます。例えば、「W3C」を「World Wide Web コンソーシアム」として読み上げます。

  • 構文

    <sub alias="string"></sub>
  • 属性

    属性

    タイプ

    必須

    説明

    エイリアス

    文字列

    はい

    読み上げる代替テキストを指定します。

    例:

     <speak>
       <sub alias="World Wide Web コンソーシアム">W3C</sub>
     </speak>
  • タグの関係

    <sub> タグは、プレーンテキストのみを含めることができます。

<phoneme>:発音の指定 (ピンイン/発音記号)

  • 説明

    テキストの発音を正確に制御します。中国語テキストではピンイン表記、英語テキストでは CMU 発音記号をサポートします。これは、多音字の曖昧さを解消したり、外国語の発音を処理したりするのに役立ちます。

  • 構文

    <phoneme alphabet="文字列" ph="文字列">テキスト</phoneme>
  • 属性

    属性

    タイプ

    必須

    説明

    alphabet

    文字列

    はい

    発音のタイプを指定します:ピンイン (中国語の場合) または発音記号 (英語の場合)。

    有効な値:

    • "py": ピンイン

    • "cmu": 発音記号。CMU 発音辞書 をご参照ください

    ph

    文字列

    はい

    正確なピンインまたは発音記号を指定します。使用ルール:

    • 複数の文字に対応するピンインは、スペースで区切ります。ピンインの数は文字数と一致する必要があります。

    • 各ピンインは、発音と声調番号で構成されます。声調番号の範囲は 1 から 5 で、5 は軽声を表します。

    • 例:

      <speak>
        どうやって <phoneme alphabet="cmu" ph="S AY N">sin</phoneme> を綴りますか?
      </speak>
  • タグの関係

    <phoneme> タグには、プレーンテキストのみを含めることができます。

<soundEvent>:外部音声の挿入 (着信音、猫の鳴き声など)

  • 説明

    音声の特定のポイントに効果音ファイル (アラート音や環境音など) を挿入して、オーディオ出力を豊かにします。

  • 構文

    <soundEvent src="URL"/>
  • 属性

    属性

    タイプ

    必須

    説明

    src

    文字列

    必須

    外部音声ファイルの URL を指定します。

    音声ファイルは Alibaba Cloud OSS に保存する必要があり (詳細については、「オブジェクトのアップロード」をご参照ください)、バケットには少なくともパブリック読み取りアクセス権限が必要です。URL に XML 特殊文字 (&<> など) が含まれる場合は、エスケープしてください。

    • 音声要件:

      • サンプリングレート:16 kHz

      • チャンネル:モノラル

      • フォーマット:WAV

        WAV 形式以外のファイルを変換するには、ffmpeg を使用します:

        ffmpeg -i input_audio -acodec pcm_s16le -ac 1 -ar 16000 output.wav
      • ファイルサイズ:最大 2 MB

      • ビット深度:16 ビット

    • 例:

      <speak>
        馬が驚き、<soundEvent src="http://nls.alicdn.com/sound-event/horse-neigh.wav"/>人々は避けるために散らばりました。
      </speak>
    重要

    アップロードした音声の著作権について、法的責任を負うものとします。

  • タグの関係

    <soundEvent> は空要素であり、子要素を含めることはできません。

<say-as> :テキスト解釈の設定 (数値、日付、電話番号など)

  • 説明

    テキストのコンテンツタイプ (数値、日付、電話番号など) を指定すると、システムはそのタイプに応じた適切なルールに従ってテキストを読み上げます。

  • 構文:

     <say-as interpret-as="string">Text</say-as>
  • 属性

    属性

    タイプ

    必須

    説明

    interpret-as

    文字列

    はい

    タグ内のテキストのコンテンツタイプを指定します。

    有効な値:

    • cardinal :基数。標準的な整数または小数として読み上げます

    • digits :各数字を個別に読み上げます (例:123 は「いち に さん」と読み上げます)

    • telephone :標準的な電話番号形式で数字を 1 つずつ読み上げます

    • name :標準的な名前の発音ルールで読み上げます

    • address :標準的な住所の発音ルールで読み上げます

    • id :標準的な識別子 (アカウント名、ニックネーム) の発音ルールで読み上げます

    • characters :テキスト内の各文字を個別に読み上げます

    • punctuation :各句読点の名前を読み上げます

    • date :標準的な日付の発音ルールで読み上げます

    • time :標準的な時刻の発音ルールで読み上げます

    • currency :標準的な金額の発音ルールで読み上げます

    • measure :標準的な単位の発音ルールで読み上げます

  • 各 <say-as> タイプでサポートされる範囲

    • 基数

      フォーマット

      英語の出力

      注記

      数字列

      145

      one hundred forty five

      整数の範囲:最大 13 桁までの正負の整数、[-999999999999,999999999999]。

      小数の範囲:小数点以下の桁数に制限はありませんが、10 桁以下を推奨します。

      先頭が 0 の数字列

      0145

      one hundred forty five

      マイナス符号 + 数字列

      -145

      minus one hundred forty five

      3桁ごとにカンマで区切られた数字列

      60,000

      sixty thousand

      マイナス符号 + カンマ区切りの数字列

      -208,000

      minus two hundred eight thousand

      数字列 + 小数点 + 0

      12.00

      twelve

      数字列 + 小数点 + 数字列

      12.34

      twelve point three four

      カンマ区切りの数字列 + 小数点 + 数字列

      1,000.1

      one thousand point one

      マイナス符号 + 数字列 + 小数点 + 数字列

      -12.34

      minus twelve point three four

      マイナス符号 + カンマ区切りの数字列 + 小数点 + 数字列

      -1,000.1

      minus one thousand point one

      (カンマ区切りの) 数字列 + ハイフン + (カンマ区切りの) 数字列

      1-1,000

      one to one thousand

      その他のデフォルトの読み上げ

      012.34

      twelve point three four

      なし

      1/2

      one half

      -3/4

      minus three quarters

      5.1/6

      five point one over six

      -3 1/2

      minus three and a half

      1,000.3^3

      one thousand point three to the power of three

      3e9.1

      three times ten to the power of nine point one

      23.10%

      twenty three point one percent

    • 数字

      フォーマット

      英語の出力

      数字列

      12034

      one two zero three four

      数字列の長さに特定の制限はありませんが、20 桁以下を推奨します。

      数字列がスペースまたはハイフンでグループ化されている場合、グループ間に休止を示すカンマが挿入されます。最大 5 つのグループまで対応しています。

      数字列 + スペース/ハイフン + 数字列 + スペース/ハイフン + 数字列 + スペース/ハイフン + 数字列

      1-23-456 7890

      one, two three, four five six, seven eight nine zero

    • 電話

      フォーマット

      英語の出力

      備考

      数字文字列

      12034

      one two oh three four

      数字文字列の長さに特定の制限はありませんが、20 桁以下を推奨します。数字文字列がスペースまたはハイフンでグループ化されている場合、グループ間にカンマによるポーズが挿入されます。5 つのグループまでサポートします。

      数字文字列 + スペース/ハイフン + 数字文字列 + スペース/ハイフン + 数字文字列

      1-23-456 7890

      one, two three, four five six, seven eight nine oh

      プラス記号 + 数字文字列 + スペース/ハイフン + 数字文字列

      +43-211-0567

      plus four three, two one one, oh five six seven

      左括弧 + 数字文字列 + 右括弧 + スペース + 数字文字列 + スペース/ハイフン + 数字文字列

      (21) 654-3210

      two one, six five four, three two one oh

    • アドレス

      このタグは英語のテキストにはサポートされていません。

    • ID

      英語のテキストでは、このタグは文字タグと同じように機能します。

    • 文字列

      フォーマット

      英語の出力

      備考

      文字列

      *b+3$.c-0'=α

      asterisk B plus three dollar dot C dash zero apostrophe equals alpha

      中国語の文字、大文字と小文字、数字の 0~9、および一部の全角文字と半角文字をサポートします。

      出力のスペースは文字間のポーズを表すもので、各文字が個別に読み上げられることを意味します。

      タグ内のテキストに XML 特殊文字が含まれる場合は、エスケープしてください。

    • 句読点

      英語のテキストでは、このタグは文字タグと同様に機能します。

    • date

      フォーマット

      英語の出力

      備考

      4 桁/2 桁、または 4 桁-2 桁

      2000/01

      two thousand, oh one

      年をまたぐ表記。

      1900-01

      nineteen hundred, oh one

      2001-02

      twenty oh one, oh two

      2019-20

      twenty nineteen, twenty

      1998-99

      nineteen ninety eight, ninety nine

      1999-00

      nineteen ninety nine, oh oh

      1 または 2 で始まる 4 桁の数値

      2000

      two thousand

      4 桁の年。

      1900

      nineteen hundred

      1905

      nineteen oh five

      2021

      twenty twenty-one

      曜日の範囲 (ハイフン)

      または

      曜日の範囲 (チルダ)

      または

      曜日&曜日

      mon-wed

      monday to wednesday

      曜日の範囲を指定するテキストに XML の特殊文字が含まれる場合は、文字をエスケープしてください。

      tue~fri

      tuesday to friday

      sat&sun

      saturday and sunday

      DD-DD MMM, YYYY

      または

      DD~DD MMM, YYYY

      または

      DD&DD MMM, YYYY

      19-20 Jan, 2000

      the nineteen to the twentieth of january two thousand

      DD:2 桁の日。MMM:3 文字の月の略語または月名。YYYY:1 または 2 で始まる 4 桁の年。

      01 ~ 10 Jul, 2020

      the first to the tenth of july twenty

      05&06 Apr, 2009

      the fifth and the sixth of april two thousand nine

      MMM DD-DD

      または

      MMM DD~DD

      または

      MMM DD&DD

      Feb 01 - 03

      february the first to the third

      MMM:3 文字の月の略語または月名。DD:2 桁の日。

      Aug 10~20

      august the tenth to the twentieth

      Dec 11&12

      december the eleventh and the twelfth

      MMM-MMM

      または

      MMM~MMM

      または

      MMM&MMM

      Jan-Jun

      january to june

      MMM:3 文字の月の略語または月名。

      jul ~ dec

      july to december

      sep&oct

      september and october

      YYYY-YYYY

      または

      YYYY~YYYY

      1990 - 2000

      nineteen ninety to two thousand

      YYYY:1 または 2 で始まる 4 桁の年。

      2001~2021

      two thousand one to twenty twenty-one

      WWW DD MMM YYYY

      Sun 20 Nov 2011

      sunday the twentieth of november twenty eleven

      WWW は曜日の 3 文字の略語または曜日名、DD は 2 桁の日、MMM は月の 3 文字の略語または月名、MM は 2 桁の月 (または月の 3 文字の略語または月名) 、YYYY は 1 または 2 で始まる 4 桁の年です。

      WWW DD MMM

      Sun 20 Nov

      sunday the twentieth of november

      WWW MMM DD YYYY

      Sun Nov 20 2011

      sunday november the twentieth twenty eleven

      WWW MMM DD

      Sun Nov 20

      sunday november the twentieth

      WWW YYYY-MM-DD

      Sat 2010-10-01

      saturday october the first twenty ten

      WWW YYYY/MM/DD

      Sat 2010/10/01

      saturday october the first twenty ten

      WWW MM/DD/YYYY

      Sun 11/20/2011

      sunday november the twentieth twenty eleven

      MM/DD/YYYY

      11/20/2011

      november the twentieth twenty eleven

      YYYY

      1998

      nineteen ninety eight

      その他のデフォルトの読み上げ

      10 Mar, 2001

      the tenth of march two thousand one

      なし

      10 Mar

      the tenth of march

      Mar 2001

      march two thousand one

      Fri. 10/Mar/2001

      friday the tenth of march two thousand one

      Mar 10th, 2001

      march the tenth two thousand one

      Mar 10

      march the tenth

      2001/03/10

      march the tenth two thousand one

      2001-03-10

      march the tenth two thousand one

      2000s

      two thousands

      2010's

      twenty tens

      1900's

      nineteen hundreds

      1990s

      nineteen nineties

    • 時刻

      フォーマット

      英語の出力

      備考

      HH:MM AM または PM

      09:00 AM

      nine A M

      HH: 時 (1桁または2桁)。MM: 分 (2桁)。AM/PM: 午前/午後。

      09:03 PM

      nine oh three P M

      09:13 p.m.

      nine thirteen p m

      HH:MM

      21:00

      twenty one hundred

      HHMM

      0100

      one hundred

      時刻の範囲

      8:00 am - 05:30 pm

      eight a m to five thirty p m

      一般的な時刻フォーマットと範囲をサポートします。

      7:05~10:15 AM

      seven oh five to ten fifteen A M

      09:00-13:00

      nine oclock to thirteen hundred

    • currency

      フォーマット

      英語出力

      備考

      数値 + 通貨識別子

      1.00 RMB

      one yuan

      サポートされている数値フォーマット:整数、小数、カンマ区切りの国際表記。

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

      CN¥ (元)

      CNY (元)

      RMB (元)

      AUD (オーストラリアドル)

      CAD (カナダドル)

      CHF (スイスフラン)

      DKK (デンマーククローネ)

      EUR (ユーロ)

      GBP (英ポンド)

      HKD (香港ドル)

      JPY (日本円)

      NOK (ノルウェークローネ)

      SEK (スウェーデンクローナ)

      SGD (シンガポールドル)

      USD (米ドル)

      2.02 CNY

      two point zero two yuan

      1,000.23 CN¥

      one thousand point two three yuan

      1.01 SGD

      one singapore dollar and one cent

      2.01 CAD

      two canadian dollars and one cent

      3.1 HKD

      three hong kong dollars and ten cents

      1,000.00 EUR

      one thousand euros

      通貨識別子 + 数値

      US$ 1.00

      one US dollar

      サポートされている数値フォーマット:整数、小数、カンマ区切りの国際表記。

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

      US$ (米ドル)

      CA$ (カナダドル)

      AU$ (オーストラリアドル)

      SG$ (シンガポールドル)

      HK$ (香港ドル)

      C$ (カナダドル)

      A$ (オーストラリアドル)

      $ (ドル)

      £ (ポンド)

      € (ユーロ)

      CN¥ (元)

      CNY (元)

      RMB (元)

      AUD (オーストラリアドル)

      CAD (カナダドル)

      CHF (スイスフラン)

      DKK (デンマーククローネ)

      EUR (ユーロ)

      GBP (英ポンド)

      HKD (香港ドル)

      JPY (日本円)

      NOK (ノルウェークローネ)

      SEK (スウェーデンクローナ)

      SGD (シンガポールドル)

      USD (米ドル)

      $0.01

      one cent

      JPY 1.01

      one japanese yen and one sen

      £1.1

      one pound and ten pence

      €2.01

      two euros and one cent

      USD 1,000

      one thousand united states dollars

      数値 + 分類子 + 通貨識別子

      または

      通貨識別子 + 数値 + 分類子

      1.23 Tn RMB

      one point two three trillion yuan

      サポートされている分類子フォーマット:

      サウザンド

      ミリオン

      ビリオン

      トリリオン

      Mil (ミリオン)

      mil (ミリオン)

      Bil (ビリオン)

      bil (ビリオン)

      MM (ミリオン)

      Bn (ビリオン)

      bn (ビリオン)

      Tn (トリリオン)

      tn (トリリオン)

      K (サウザンド)

      k (サウザンド)

      M (ミリオン)

      m (ミリオン)

      $1.2 K

      one point two thousand dollars

    • 測定単位

      フォーマット

      英語出力

      備考

      数値 + 測定単位

      1.0 kg

      one kilogram

      サポートされている数値フォーマットは、整数、小数、およびカンマ区切りの国際表記です。

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

      1,234.01 km

      one thousand two hundred thirty four point zero one kilometres.

      測定単位

      mm2

      square millimetre

    • 次の表では、一般的な記号が <say-as> でどのように読み上げられるかを示します。

      記号

      読み方

      !

      感嘆符

      左ダブルクォーテーション

      #

      ハッシュ

      $

      ドル記号

      %

      パーセント

      &

      アンパサンド

      左シングルクォーテーション

      左括弧

      右括弧

      *

      アスタリスク

      +

      プラス

      ,

      コンマ

      -

      ハイフン

      .

      ドット

      /

      スラッシュ

      コロン

      セミコロン

      <

      小なり

      =

      イコール

      >

      大なり

      ?

      疑問符

      @

      アットマーク

      [

      左角括弧

      \

      バックスラッシュ

      ]

      右角括弧

      ^

      カレット

      _

      アンダースコア

      バッククォート

      {

      左波括弧

      |

      縦棒

      }

      右波括弧

      ~

      チルダ

      感嘆符

      左ダブルクォーテーション

      右ダブルクォーテーション

      左シングルクォーテーション

      右シングルクォーテーション

      左括弧

      右括弧

      コンマ

      句点

      全角ダッシュ

      コロン

      セミコロン

      疑問符

      読点

      省略記号

      ……

      省略記号

      左二重山括弧

      右二重山括弧

      元記号

      大なりイコール

      小なりイコール

      ノットイコール

      ニアリーイコール

      ±

      プラスマイナス

      ×

      乗算記号

      π

      パイ

      Α

      アルファ

      Β

      ベータ

      Γ

      ガンマ

      Δ

      デルタ

      Ε

      イプシロン

      Ζ

      ゼータ

      Θ

      シータ

      Ι

      イオタ

      Κ

      カッパ

      Λ

      ラムダ

      Μ

      ミュー

      Ν

      ニュー

      Ξ

      クサイ

      Ο

      オミクロン

      パイ

      Ρ

      ロー

      Σ

      シグマ

      Τ

      タウ

      Υ

      ウプシロン

      Φ

      ファイ

      Χ

      カイ

      Ψ

      プサイ

      Ω

      オメガ

      α

      アルファ

      β

      ベータ

      γ

      ガンマ

      δ

      デルタ

      ε

      イプシロン

      ζ

      ゼータ

      η

      イータ

      θ

      シータ

      ι

      イオタ

      κ

      カッパ

      λ

      ラムダ

      μ

      ミュー

      ν

      ニュー

      ξ

      クサイ

      ο

      オミクロン

      π

      パイ

      ρ

      ロー

      σ

      シグマ

      τ

      タウ

      υ

      ウプシロン

      φ

      ファイ

      χ

      カイ

      ψ

      プサイ

      ω

      オメガ

    • 次の表に、<say-as> を使用した場合の一般的な測定単位の読み上げ方を示します。

      フォーマット

      カテゴリ

      英語の例

      省略形

      長さ

      nm (nanometre), μm (micrometre), mm (millimetre), cm (centimetre), m (metre), km (kilometre), ft (foot), in (inch)

      面積

      cm² (square centimetre), ㎡ (square metre), km2 (square kilometre), SqFt (square foot)

      体積

      cm³ (cubic centimetre), m³ (cubic metre), km3 (cubic kilometre), mL (millilitre), L (litre), gal (gallon)

      重量

      μg (microgram), mg (milligram), g (gram), kg (kilogram)

      時間

      min (minute), sec (second), ms (millisecond)

      電磁気

      μA (microamp), mA (milliamp), Hz (hertz), kHz (kilohertz), MHz (megahertz), GHz (gigahertz), V (volt), kV (kilovolt), kWh (kilowatt hour)

      dB (decibel)

      気圧

      Pa (pascal), kPa (kilopascal), MPa (megapascal)

      その他の一般的な単位

      上記以外の単位もサポートしています。例: tsp (teaspoon)、rpm (回転/分)、KB (キロバイト)、mmHg (millimetre of mercury)。

  • タグの関係

    <say-as> タグは、テキストと <vhml/> を含めることができます。

    • 基数

      <speak>
        <say-as interpret-as="cardinal">12345</say-as>
      </speak>
      <speak>
        <say-as interpret-as="cardinal">10234</say-as>
      </speak>
    • 数字

      <speak>
        <say-as interpret-as="digits">12345</say-as>
      </speak>
      <speak>
        <say-as interpret-as="digits">10234</say-as>
      </speak>
    • 電話番号

      <speak>
        <say-as interpret-as="telephone">12345</say-as>
      </speak>
      <speak>
        <say-as interpret-as="telephone">10234</say-as>
      </speak>
    • 名前

      <speak>
        彼女の旧姓は<say-as interpret-as="name">田中花子</say-as>です
      </speak>
    • 住所

      <speak>
        <say-as interpret-as="address">東京都千代田区丸の内一丁目一番一号</say-as>
      </speak>
    • ID

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

      <speak>
        <say-as interpret-as="characters">ギリシャ文字 αβ</say-as>
      </speak>
      <speak>
        <say-as interpret-as="characters">*b+3.c$=α</say-as>
      </speak>
    • 句読点

      <speak>
        <say-as interpret-as="punctuation"> -./:;</say-as>
      </speak>
    • 日付

      <speak>
        <say-as interpret-as="date">1000-10-10</say-as>
      </speak>
      <speak>
        <say-as interpret-as="date">10-01-2020</say-as>
      </speak>
    • 時刻

      <speak>
        <say-as interpret-as="time">5:00am</say-as>
      </speak>
      <speak>
        <say-as interpret-as="time">0500</say-as>
      </speak>
    • 通貨

      <speak>
        <say-as interpret-as="currency">13,000,000.00RMB</say-as>
      </speak>
      <speak>
        <say-as interpret-as="currency">$1,000.01</say-as>
      </speak>
    • 単位

      <speak>
        <say-as interpret-as="measure">100m12cm6mm</say-as>
      </speak>
      <speak>
        <say-as interpret-as="measure">1,000.01kg</say-as>
      </speak>