Tous les produits
Search
Centre de documentation

Alibaba Cloud Model Studio:SSML

Dernière mise à jour :Oct 11, 2026

Le langage SSML (Speech Synthesis Markup Language) permet d'affiner les caractéristiques vocales telles que la vitesse, les pauses et la prononciation.

Présentation

Le SSML (Speech Synthesis Markup Language) est un langage de balisage basé sur XML dédié à la synthèse vocale. L'intégration de balises SSML dans votre texte offre un contrôle précis du débit, de l'intonation, des pauses et du volume. Elle permet également d'ajouter une musique de fond et des effets sonores pour enrichir le rendu audio.

Voici quelques cas d'usage courants :

  • Livres audio : contrôlez précisément les pauses et le débit de parole, et ajoutez une musique de fond pour une écoute immersive
  • Service client intelligent : utilisez la balise <say-as> pour lire correctement les numéros de téléphone, les dates et les informations similaires
  • Diffusion multilingue : utilisez la balise <phoneme> pour préciser la prononciation des mots étrangers
  • Enseignement en ligne : convertissez les formules LaTeX en parole naturelle avec la lecture de formules

Ces deux fonctionnalités sont disponibles pour la famille de modèles CosyVoice. Pour obtenir des conseils sur le choix du modèle, consultez la rubrique Synthèse vocale.

SSML

Limites

  • Modèles : SSML est pris en charge uniquement par qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3.5-flash, cosyvoice-v3.5-plus, cosyvoice-v3-flash, cosyvoice-v3-plus et cosyvoice-v2.

  • Voix : seules les voix clonées et les voix prédéfinies indiquées comme compatibles SSML dans la liste des voix CosyVoice sont prises en charge.

  • API : SSML est disponible via les API suivantes :

    • SDK Java (version 2.20.3 ou ultérieure) : appels sans streaming ou avec streaming unidirectionnel
    • SDK Python (version 1.23.4 ou ultérieure) : appels sans streaming ou avec streaming unidirectionnel
    • API WebSocket : définissez enable_ssml sur true et envoyez un seul événement continue-task

Démarrage rapide

L'exemple ci-dessous utilise le format SSML pour contrôler la vitesse de parole lors de la synthèse. Avant d'exécuter le code, assurez-vous de remplir les conditions préalables suivantes :

  1. Obtenir une clé API
  2. Installez le SDK DashScope (Python 1.23.4 ou ultérieur, Java 2.20.3 ou ultérieur). Consultez Installer le SDK.

ImportantLes modèles cosyvoice-v3.5-plus et cosyvoice-v3.5-flash sont actuellement disponibles uniquement dans la région de Pékin. Ils sont conçus exclusivement pour les scénarios de clonage vocal et ne fournissent aucune voix prédéfinie. Avant d'utiliser ces modèles, créez une voix cible en suivant les instructions décrites dans Clonage vocal.

SDK Java

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 feature description:
 *     1. Only non-streaming calls and unidirectional streaming calls support the SSML feature
 *     2. Only cloned voices of the qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3-flash, cosyvoice-v3-plus, and cosyvoice-v2 models, as well as system voices marked as SSML-supported in the voice list, support the SSML feature (for example, the longanyang voice of the cosyvoice-v3-flash model)
 */
public class Main {
    private static String model = "cosyvoice-v3-flash";
    private static String voice = "longanyang";

    public static void main(String[] args) {
        // Singapore region URL. Replace WorkspaceId with your actual workspace ID. The URL varies by region.
        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()
                        // The API Keys for the Singapore and Beijing regions are different. Get an API Key: https://www.alibabacloud.com/help/model-studio/get-api-key
                        // If you have not configured an environment variable, replace the following line with your Model Studio API Key: .apiKey("sk-xxx")
                        .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                        .model(model)
                        .voice(voice)
                        .build();

        SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
        ByteBuffer audio = null;
        try {
            // Non-streaming call, blocks until audio is returned
            // Special characters need to be escaped
            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 {
            // Close the WebSocket connection when the task ends
            synthesizer.getDuplexApi().close(1000, "bye");
        }
        if (audio != null) {
            // Save audio data to local file "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);
            }
        }

        // The first text transmission requires establishing a WebSocket connection, so the first packet latency includes the connection setup time
        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 feature description:
 *     1. Only non-streaming calls and unidirectional streaming calls support the SSML feature
 *     2. Only cloned voices of the qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3-flash, cosyvoice-v3-plus, and cosyvoice-v2 models, as well as system voices marked as SSML-supported in the voice list, support the SSML feature (for example, the longanyang voice of the cosyvoice-v3-flash model)
 */
public class Main {
    private static String model = "cosyvoice-v3-flash";
    private static String voice = "longanyang";

    public static void main(String[] args) {
        // Singapore region URL. Replace WorkspaceId with your actual workspace ID. The URL varies by region.
        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;
        }

        // Implement the ResultCallback interface
        ResultCallback<SpeechSynthesisResult> callback = new ResultCallback<SpeechSynthesisResult>() {
            @Override
            public void onEvent(SpeechSynthesisResult result) {
                if (result.getAudioFrame() != null) {
                    // Write audio data to local file
                    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()
                        // The API Keys for the Singapore and Beijing regions are different. Get an API Key: https://www.alibabacloud.com/help/model-studio/get-api-key
                        // If you have not configured an environment variable, replace the following line with your Model Studio API Key: .apiKey("sk-xxx")
                        .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                        .model(model)
                        .voice(voice)
                        .format(SpeechSynthesisAudioFormat.MP3_22050HZ_MONO_256KBPS)
                        .build();

        SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, callback);

        try {
            // Unidirectional streaming call, returns null immediately (actual results are delivered asynchronously via the callback interface), retrieve binary audio in real time from the onEvent method of the callback interface
            // Special characters need to be escaped
            synthesizer.call("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>");
            // Wait for synthesis to complete
            latch.await();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            // Close the WebSocket connection after the task ends
            try {
                synthesizer.getDuplexApi().close(1000, "bye");
            } catch (Exception e) {
                System.err.println("Failed to close WebSocket connection: " + e.getMessage());
            }

            // Ensure the file stream is closed
            closeFileOutputStream(fileOutputStream[0]);
        }

        // The first text transmission requires establishing a WebSocket connection, so the first packet latency includes the connection setup time
        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());
        }
    }
}

SDK Python

# coding=utf-8
# SSML feature description:
#     1. Only non-streaming calls and unidirectional streaming calls support the SSML feature
#     2. Only cloned voices of the qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3-flash, cosyvoice-v3-plus, and cosyvoice-v2 models, as well as system voices marked as SSML-supported in the voice list, support the SSML feature (for example, the longanyang voice of the cosyvoice-v3-flash model)

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

# The API Keys for the Singapore and Beijing regions are different. Get an API Key: https://www.alibabacloud.com/help/model-studio/get-api-key
# If you have not configured an environment variable, replace the following line with your Model Studio API Key: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# Singapore region URL. Replace WorkspaceId with your actual workspace ID. The URL varies by region.
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'

# Model
model = "cosyvoice-v3-flash"
# Voice
voice = "longanyang"

# Instantiate SpeechSynthesizer and pass request parameters such as model and voice in the constructor
synthesizer = SpeechSynthesizer(model=model, voice=voice)
# Non-streaming call, blocks until audio is returned
# Special characters need to be escaped
audio = synthesizer.call("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>")

# Save the audio to a local file
with open('output.mp3', 'wb') as f:
    f.write(audio)

# The first text transmission requires establishing a WebSocket connection, so the first packet latency includes the connection setup time
print('[Metric] requestId: {}, first packet latency: {} ms'.format(
    synthesizer.get_last_request_id(),
    synthesizer.get_first_package_delay()))
# coding=utf-8
# SSML feature description:
#     1. Only non-streaming calls and unidirectional streaming calls support the SSML feature
#     2. Only cloned voices of the qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3-flash, cosyvoice-v3-plus, and cosyvoice-v2 models, as well as system voices marked as SSML-supported in the voice list, support the SSML feature (for example, the longanyang voice of the cosyvoice-v3-flash model)

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

# The API Keys for the Singapore and Beijing regions are different. Get an API Key: https://www.alibabacloud.com/help/model-studio/get-api-key
# If you have not configured an environment variable, replace the following line with your Model Studio API Key: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')

# Singapore region URL. Replace WorkspaceId with your actual workspace ID. The URL varies by region.
dashscope.base_websocket_api_url='wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference'

# Model
model = "cosyvoice-v3-flash"
# Voice
voice = "longanyang"

# Define the callback interface
class Callback(ResultCallback):
    _player = None
    _stream = None

    def on_open(self):
        # Open the output file for writing audio data
        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
        # The first text transmission requires establishing a WebSocket connection, so the first packet latency includes the connection setup time
        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)))
        # Write audio data to file
        self.file.write(data)

callback = Callback()

# Instantiate SpeechSynthesizer and pass request parameters such as model and voice in the constructor
synthesizer = SpeechSynthesizer(
    model=model,
    voice=voice,
    callback=callback,
)

# Assign the synthesizer instance to callback for use in on_complete
callback.synthesizer = synthesizer

# Unidirectional streaming call, send text for synthesis, retrieve binary audio in real time from the on_data method of the callback interface
# Special characters need to be escaped
synthesizer.call("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>")

API WebSocket

Go

// SSML feature description:
//     1. When sending the run-task command, set the enable_ssml parameter to true to enable SSML support
//     2. Send text containing SSML via the continue-task command, and only one continue-task command is allowed
//     3. Only cloned voices of the qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3-flash, cosyvoice-v3-plus, and cosyvoice-v2 models, as well as system voices marked as SSML-supported in the voice list, support the SSML feature (for example, the longanyang voice of the cosyvoice-v3-flash model)

package main

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

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

const (
    // Singapore region URL. Replace WorkspaceId with your actual workspace ID. The URL varies by region.
    wsURL      = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/"
    outputFile = "output.mp3"
)

func main() {
    // The API Keys for the Singapore and Beijing regions are different. Get an API Key: https://www.alibabacloud.com/help/model-studio/get-api-key
    // If you have not configured an environment variable, replace the following line with your Model Studio API Key: apiKey := "sk-xxx"
    apiKey := os.Getenv("DASHSCOPE_API_KEY")

    // Clear the output file
    os.Remove(outputFile)
    os.Create(outputFile)

    // Connect to 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()

    // Generate task ID
    taskID := uuid.New().String()
    fmt.Printf("Generated task ID: %s\n", taskID)

    // Send run-task command
    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,
                // If enable_ssml is set to true, only one continue-task command is allowed; otherwise the error "Text request limit violated, expected 1." will be returned
                "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

    // Process messages
    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            fmt.Println("Failed to read message:", err)
            break
        }

        // Process binary messages
        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
        }

        // Process text messages
        messageStr := string(message)
        fmt.Printf("Received text message: %s\n", strings.ReplaceAll(messageStr, "\n", ""))

        // Simple JSON parsing to get event type
        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 {
                            // Send continue-task command. When using SSML, this command can only be sent once
                            continueTaskCmd := map[string]interface{}{
                                "header": map[string]interface{}{
                                    "action":    "continue-task",
                                    "task_id":   taskID,
                                    "streaming": "duplex",
                                },
                                "payload": map[string]interface{}{
                                    "input": map[string]interface{}{
                                        // Special characters need to be escaped
                                        "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

                            // Delay before sending finish-task
                            time.Sleep(500 * time.Millisecond)

                            // Send finish-task command
                            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 feature description:
//     1. When sending the run-task command, set the enable_ssml parameter to true to enable SSML support
//     2. Send text containing SSML via the continue-task command, and only one continue-task command is allowed
//     3. Only cloned voices of the qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3-flash, cosyvoice-v3-plus, and cosyvoice-v2 models, as well as system voices marked as SSML-supported in the voice list, support the SSML feature (for example, the longanyang voice of the cosyvoice-v3-flash model)
class Program {
    // The API Keys for the Singapore and Beijing regions are different. Get an API Key: https://www.alibabacloud.com/help/model-studio/get-api-key
    // If you have not configured an environment variable, replace the following line with your Model Studio API Key: private static readonly string ApiKey = "sk-xxx"
    private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("DASHSCOPE_API_KEY environment variable is not set.");

    // Singapore region URL. Replace WorkspaceId with your actual workspace ID. The URL varies by region.
    private const string WebSocketUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/";
    // Output file path
    private const string OutputFilePath = "output.mp3";

    // WebSocket client
    private static ClientWebSocket _webSocket = new ClientWebSocket();
    // Cancellation token source
    private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
    // Task ID
    private static string? _taskId;
    // Whether the task has started
    private static TaskCompletionSource<bool> _taskStartedTcs = new TaskCompletionSource<bool>();

    static async Task Main(string[] args) {
        try {
            // Clear the output file
            ClearOutputFile(OutputFilePath);

            // Connect to the WebSocket service
            await ConnectToWebSocketAsync(WebSocketUrl);

            // Start the message receiving task
            Task receiveTask = ReceiveMessagesAsync();

            // Send run-task command
            _taskId = GenerateTaskId();
            await SendRunTaskCommandAsync(_taskId);

            // Wait for the task-started event
            await _taskStartedTcs.Task;

            // Send continue-task command. When using SSML, this command can only be sent once
            // Special characters need to be escaped
            await SendContinueTaskCommandAsync("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>");

            // Send finish-task command
            await SendFinishTaskCommandAsync(_taskId);

            // Wait for the receiving task to complete
            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;
        }

        // Set WebSocket connection headers
        _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,
                // If enable_ssml is set to true, only one continue-task command is allowed; otherwise the error "Text request limit violated, expected 1." will be returned
                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 can be handled here
                        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) {
                // Process binary data
                Console.WriteLine("Received binary data...");

                // Save binary data to file
                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

La structure du répertoire de code exemple est la suivante :

my-php-project/

├── composer.json

├── vendor/

└── index.php

Le fichier composer.json contient les dépendances suivantes. Ajustez les numéros de version selon vos besoins :

{
    "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/"
        }
    }
}

Le fichier index.php contient le code suivant :

<!-- SSML feature description: -->
<!--     1. When sending the run-task command, set the enable_ssml parameter to true to enable SSML support -->
<!--     2. Send text containing SSML via the continue-task command, and only one continue-task command is allowed -->
<!--     3. Only cloned voices of the qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3-flash, cosyvoice-v3-plus, and cosyvoice-v2 models, as well as system voices marked as SSML-supported in the voice list, support the SSML feature (for example, the longanyang voice of the cosyvoice-v3-flash model) -->

<?php

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

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

// The API Keys for the Singapore and Beijing regions are different. Get an API Key: https://www.alibabacloud.com/help/model-studio/get-api-key
// If you have not configured an environment variable, replace the following line with your Model Studio API Key: $api_key = "sk-xxx"
$api_key = getenv("DASHSCOPE_API_KEY");
// Singapore region URL. Replace WorkspaceId with your actual workspace ID. The URL varies by region.
$websocket_url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/'; // WebSocket server address
$output_file = 'output.mp3'; // Output file path

$loop = Loop::get();

if (file_exists($output_file)) {
    // Clear file contents
    file_put_contents($output_file, '');
}

// Create a custom connector
$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";

    // Generate task ID
    $taskId = generateTaskId();

    // Send run-task command
    sendRunTaskMessage($conn, $taskId);

    // Define the function to send continue-task command
    $sendContinueTask = function() use ($conn, $loop, $taskId) {
        // Send continue-task command. When using SSML, this command can only be sent once
        $continueTaskMessage = json_encode([
            "header" => [
                "action" => "continue-task",
                "task_id" => $taskId,
                "streaming" => "duplex"
            ],
            "payload" => [
                "input" => [
                    // Special characters need to be escaped
                    "text" => "<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>"
                ]
            ]
        ]);
        $conn->send($continueTaskMessage);

        // Send finish-task command
        sendFinishTaskMessage($conn, $taskId);
    };

    // Flag for whether task-started event has been received
    $taskStarted = false;

    // Listen for messages
    $conn->on('message', function($msg) use ($conn, $sendContinueTask, $loop, &$taskStarted, $taskId, $output_file) {
        if ($msg->isBinary()) {
            // Write binary data to local file
            file_put_contents($output_file, $msg->getPayload(), FILE_APPEND);
        } else {
            // Process non-binary messages
            $response = json_decode($msg, true);

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

    // Listen for connection close
    $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();

/**
 * Generate task ID
 * @return string
 */
function generateTaskId(): string {
    return bin2hex(random_bytes(16));
}

/**
 * Send run-task command
 * @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,
                // If enable_ssml is set to true, only one continue-task command is allowed; otherwise the error "Text request limit violated, expected 1." will be returned
                "enable_ssml" => true
            ],
            "input" => (object) []
        ]
    ]);
    echo "Preparing to send run-task command: " . $runTaskMessage . "\n";
    $conn->send($runTaskMessage);
    echo "run-task command sent\n";
}

/**
 * Read audio file
 * @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;
}

/**
 * Split audio data
 * @param string $data
 * @param int $chunkSize
 * @return array
 */
function splitAudioData(string $data, int $chunkSize): array {
    return str_split($data, $chunkSize);
}

/**
 * Send finish-task command
 * @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";
}

/**
 * Handle events
 * @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;
            // Send continue-task command
            $sendContinueTask();
            break;
        case 'result-generated':
            // Ignore result-generated events
            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 the task is completed, close the connection
    if ($response['header']['event'] == 'task-finished') {
        // Wait 1 second to ensure all data has been transmitted
        $loop->addTimer(1, function() use ($conn) {
            $conn->close();
            echo "Client closed connection\n";
        });
    }

    // If task-started event has not been received, close the connection
    if (!$taskStarted && in_array($response['header']['event'], ['task-failed', 'error'])) {
        $conn->close();
    }
}

Node.js

Installez les dépendances requises :

npm install ws
npm install uuid

Code exemple :

// SSML feature description:
//     1. When sending the run-task command, set the enable_ssml parameter to true to enable SSML support
//     2. Send text containing SSML via the continue-task command, and only one continue-task command is allowed
//     3. Only cloned voices of the qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3-flash, cosyvoice-v3-plus, and cosyvoice-v2 models, as well as system voices marked as SSML-supported in the voice list, support the SSML feature (for example, the longanyang voice of the cosyvoice-v3-flash model)

import fs from 'fs';
import WebSocket from 'ws';
import { v4 as uuid } from 'uuid'; // For generating UUIDs

// The API Keys for the Singapore and Beijing regions are different. Get an API Key: https://www.alibabacloud.com/help/model-studio/get-api-key
// If you have not configured an environment variable, replace the following line with your Model Studio API Key: const apiKey = "sk-xxx"
const apiKey = process.env.DASHSCOPE_API_KEY;
// Singapore region URL. Replace WorkspaceId with your actual workspace ID. The URL varies by region.
const url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/';
// Output file path
const outputFilePath = 'output.mp3';

// Clear the output file
fs.writeFileSync(outputFilePath, '');

// Create WebSocket client
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');

  // Send run-task command
  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', // Voice
        format: 'mp3', // Audio format
        sample_rate: 22050, // Sample rate
        volume: 50, // Volume
        rate: 1, // Speech rate
        pitch: 1, // Pitch
        enable_ssml: true // Whether to enable SSML. If enable_ssml is set to true, only one continue-task command is allowed; otherwise the error "Text request limit violated, expected 1." will be returned
      },
      input: {}
    }
  });
  ws.send(runTaskMessage);
  console.log('run-task message sent');
});

const fileStream = fs.createWriteStream(outputFilePath, { flags: 'a' });
ws.on('message', (data, isBinary) => {
  if (isBinary) {
    // Write binary data to file
    fileStream.write(data);
  } else {
    const message = JSON.parse(data);

    switch (message.header.event) {
      case 'task-started':
        taskStarted = true;
        console.log('Task started');
        // Send continue-task command
        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 can be handled here
        break;
    }
  }
});

function sendContinueTasks(ws) {

  if (taskStarted) {
    // Send continue-task command. When using SSML, this command can only be sent once
    const continueTaskMessage = JSON.stringify({
      header: {
        action: 'continue-task',
        task_id: taskId,
        streaming: 'duplex'
      },
      payload: {
        input: {
          // Special characters need to be escaped
          text: '<speak rate="2">My speaking rate is faster than a normal person's.</speak>'
        }
      }
    });
    ws.send(continueTaskMessage);

    // Send finish-task command
    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

Pour le développement Java, utilisez le SDK DashScope Java. Pour plus de détails, consultez SDK Java.

Cet exemple WebSocket Java nécessite les dépendances suivantes :

  • Java-WebSocket
  • jackson-databind

Gérez ces dépendances avec Maven ou Gradle :

<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>
dependencies {
  // WebSocket Client
  implementation 'org.java-websocket:Java-WebSocket:1.5.3'
  // JSON Processing
  implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
}

Code 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 feature description:
 *     1. When sending the run-task command, set the enable_ssml parameter to true to enable SSML support
 *     2. Send text containing SSML via the continue-task command, and only one continue-task command is allowed
 *     3. Only cloned voices of the qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3-flash, cosyvoice-v3-plus, and cosyvoice-v2 models, as well as system voices marked as SSML-supported in the voice list, support the SSML feature (for example, the longanyang voice of the cosyvoice-v3-flash model)
 */
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");

        // Send run-task command
        // If enable_ssml is set to true, only one continue-task command is allowed; otherwise the error "Text request limit violated, expected 1." will be returned
        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 {
            // Parse JSON message
            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");

                        // Send continue-task command. When using SSML, this command can only be sent once
                        // Special characters need to be escaped
                        sendContinueTask("<speak rate=\\\"2\\\">My speaking rate is faster than a normal person's.</speak>");

                        // Send finish-task command
                        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 {
            // The API Keys for the Singapore and Beijing regions are different. Get an API Key: https://www.alibabacloud.com/help/model-studio/get-api-key
            // If you have not configured an environment variable, replace the following line with your Model Studio API Key: String apiKey = "sk-xxx"
            String apiKey = System.getenv("DASHSCOPE_API_KEY");
            if (apiKey == null || apiKey.isEmpty()) {
                System.err.println("Please set the DASHSCOPE_API_KEY environment variable");
                return;
            }

            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", "bearer " + apiKey);
            // Singapore region URL. Replace WorkspaceId with your actual workspace ID. The URL varies by region.
            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

Pour le développement en Python, utilisez le SDK DashScope pour Python. Pour plus d'informations, consultez SDK Python.

L'exemple suivant illustre l'utilisation de WebSocket avec Python. Avant de l'exécuter, installez la dépendance requise :

pip uninstall websocket-client
pip uninstall websocket
pip install websocket-client

ImportantNe nommez pas votre fichier Python « websocket.py ». Cela provoquerait un conflit de noms entraînant une erreur (AttributeError: module 'websocket' has no attribute 'WebSocketApp'. Did you mean: 'WebSocket'?).

# SSML feature description:
#     1. When sending the run-task command, set the enable_ssml parameter to true to enable SSML support
#     2. Send text containing SSML via the continue-task command, and only one continue-task command is allowed
#     3. Only cloned voices of the qwen-audio-3.0-tts-flash, qwen-audio-3.0-tts-plus, cosyvoice-v3-flash, cosyvoice-v3-plus, and cosyvoice-v2 models, as well as system voices marked as SSML-supported in the voice list, support the SSML feature (for example, the longanyang voice of the cosyvoice-v3-flash model)

import websocket
import json
import uuid
import os
import time

class TTSClient:
    def __init__(self, api_key, uri):
        """
    Initialize a TTSClient instance

    Parameters:
        api_key (str): API Key for authentication
        uri (str): WebSocket service address
    """
        self.api_key = api_key  # Replace with your API Key
        self.uri = uri  # Replace with your WebSocket address
        self.task_id = str(uuid.uuid4())  # Generate a unique task ID
        self.output_file = f"output_{int(time.time())}.mp3"  # Output audio file path
        self.ws = None  # WebSocketApp instance
        self.task_started = False  # Whether task-started has been received
        self.task_finished = False  # Whether task-finished / task-failed has been received

    def on_open(self, ws):
        """
    Callback when WebSocket connection is established
    Sends run-task command to start the speech synthesis task
    """
        print("WebSocket connected")

        # Construct run-task command
        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,
                    # If enable_ssml is set to True, only one continue-task command is allowed; otherwise the error "Text request limit violated, expected 1." will be returned
                    "enable_ssml": True
                },
                "input": {}
            }
        }

        # Send run-task command
        ws.send(json.dumps(run_task_cmd))
        print("run-task command sent")

    def on_message(self, ws, message):
        """
    Callback when a message is received
    Handles text and binary messages separately
    """
        if isinstance(message, str):
            # Process JSON text messages
            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

                            # Send continue-task command. When using SSML, this command can only be sent once
                            # Special characters need to be escaped
                            self.send_continue_task("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>")

                            # Send finish-task after continue-task is sent
                            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:
            # Process binary messages (audio data)
            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):
        """Callback when an error occurs"""
        print(f"WebSocket error: {error}")

    def on_close(self, ws, close_status_code, close_msg):
        """Callback when connection is closed"""
        print(f"WebSocket closed: {close_msg} ({close_status_code})")

    def send_continue_task(self, text):
        """Send continue-task command with the text content to synthesize"""
        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):
        """Send finish-task command to end the speech synthesis 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):
        """Actively close the connection"""
        if ws and ws.sock and ws.sock.connected:
            ws.close()
            print("Connection closed actively")

    def run(self):
        """Start the WebSocket client"""
        # Set request headers (authentication)
        header = {
            "Authorization": f"bearer {self.api_key}",
            "X-DashScope-DataInspection": "enable"
        }

        # Create WebSocketApp instance
        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()  # Start persistent connection listener

# Example usage
if __name__ == "__main__":
    # The API Keys for the Singapore and Beijing regions are different. Get an API Key: https://www.alibabacloud.com/help/model-studio/get-api-key
    # If you have not configured an environment variable, replace the following line with your Model Studio API Key: API_KEY = "sk-xxx"
    API_KEY = os.environ.get("DASHSCOPE_API_KEY")
    # Singapore region URL. Replace WorkspaceId with your actual workspace ID. The URL varies by region.
    SERVER_URI = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/"

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

Référence des balises

RemarqueL'implémentation SSML d'Alibaba Cloud repose sur la spécification SSML 1.0 du W3C. Elle ne prend pas en charge toutes les balises standard, mais implémente les plus utilisées en production.

  • Avec SSML, tout le contenu textuel doit être placé entre des balises <speak></speak>.

  • Plusieurs balises <speak> peuvent se succéder, par exemple <speak></speak><speak></speak>, mais elles ne peuvent pas être imbriquées, comme dans <speak><speak></speak></speak>.

  • Si le texte contient des caractères spéciaux XML, échappez-les comme suit :

    • " (guillemet double) → &quot;
    • ' (guillemet simple ou apostrophe) → &apos;
    • & (esperluette) → &amp;
    • < (signe inférieur à) → &lt;
    • > (signe supérieur à) → &gt;

<speak> : élément racine

  • Description

    <speak> constitue l'élément racine de tout contenu SSML. L'intégralité du texte doit figurer entre des balises <speak></speak>.

  • Syntaxe

     <speak>Text that requires SSML processing</speak>
    
  • Attributs

    AttributTypeObligatoireDescription

    voice

    Chaîne

    Non

    Définit la voix à utiliser.

    Cet attribut prévaut sur le paramètre voice de la requête API.

    • Valeurs valides : un nom de voix spécifique. Pour plus de détails, consultez Voix cosyvoice-v2.

    • Exemple :

      <speak voice="longcheng_v2">
        I am a male voice.
      </speak>
      

    rate

    Chaîne

    Non

    Détermine la vitesse d'élocution. Cet attribut prévaut sur le paramètre speech_rate de la requête API.

    • Valeurs autorisées : nombre décimal compris entre 0.5 et 2, bornes incluses

    • Valeur par défaut : 1

      • Une valeur supérieure à 1 accélère l'élocution
      • Une valeur inférieure à 1 ralentit l'élocution
    • Exemple :

      <speak rate="2">
        My speech rate is faster than normal.
      </speak>
      

    pitch

    Chaîne

    Non

    Ajuste la hauteur de la voix. Cet attribut prévaut sur le paramètre pitch_rate de la requête API.

    • Valeurs autorisées : nombre décimal compris entre 0.5 et 2, bornes incluses

    • Valeur par défaut : 1

      • Une valeur supérieure à 1 élève la hauteur
      • Une valeur inférieure à 1 abaisse la hauteur
    • Exemple :

      <speak pitch="0.5">
        However, my pitch is lower than others.
      </speak>
      

    volume

    Chaîne

    Non

    Règle le volume sonore. Cet attribut prévaut sur le paramètre volume de la requête API.

    • Valeurs valides : entier compris entre 0 et 100 (inclus)

    • Valeur par défaut : 50

      • Une valeur supérieure à 50 augmente le volume
      • Une valeur inférieure à 50 diminue le volume
    • Exemple :

      <speak volume="80">
        My volume is also very high.
      </speak>
      

    effect

    Chaîne

    Non

    Applique un effet audio spécifique.

    • Valeurs valides :

      • robot : effet voix robotique
      • lolita : effet voix lolita
      • lowpass : effet filtre passe-bas
      • echo : effet écho
      • eq : égaliseur (avancé)
      • lpfilter : filtre passe-bas (avancé)
      • hpfilter : filtre passe-haut (avancé)

      Remarque

      • Les types eq, lpfilter et hpfilter sont des effets avancés. Utilisez le paramètre effectValue pour personnaliser ces effets spécifiques.
      • Chaque balise SSML n'accepte qu'un seul effet. Il est impossible de définir plusieurs attributs effect simultanément.
      • L'activation des effets audio accroît la latence de synthèse.
    • Exemple :

      <speak effect="robot">
        Do you like the robot WALL-E?
      </speak>
      

    effectValue

    Chaîne

    Non

    Configure le comportement précis de l'effet audio (paramètre effect). S'applique aux trois types d'effets avancés : eq, lpfilter et hpfilter.

    • Valeurs valides :

      • eq (égaliseur) : le système prend en charge 8 bandes de fréquences par défaut, correspondant aux fréquences suivantes :

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

        Chaque bande a une largeur de 1.0q.

        Utilisez le paramètre effectValue pour spécifier le gain de chaque bande. Ce paramètre est une chaîne composée de 8 entiers allant de -20 à 20, séparés par des espaces. Une valeur de 0 signifie qu'aucun ajustement de gain n'est appliqué à cette fréquence.

        Exemple : effectValue="1 1 1 1 1 1 1 1"

      • lpfilter (filtre passe-bas) : définit la fréquence de coupure. Valeurs valides : entier dans l'intervalle (0, target_sample_rate/2]. Exemple : effectValue="800".

      • hpfilter (filtre passe-haut) : définit la fréquence de coupure. Valeurs valides : entier dans l'intervalle (0, target_sample_rate/2]. Exemple : effectValue="1200".

    • Exemple :

      <speak effect="eq" effectValue="1 -20 1 1 1 1 20 1">
        Do you like the robot WALL-E?
      </speak>
      
      <speak effect="lpfilter" effectValue="1200">
        Do you like the robot WALL-E?
      </speak>
      
      <speak effect="hpfilter" effectValue="1200">
        Do you like the robot WALL-E?
      </speak>
      

    bgm

    Chaîne

    Non

    Ajoute une musique de fond à la parole synthétisée. Le fichier audio doit être stocké sur Alibaba Cloud OSS (voir Téléverser des objets) et le bucket doit disposer d'un accès public-read au minimum.

    Si l'URL de la musique de fond contient des caractères spéciaux XML (tels que &, <, >), vous devez les échapper.

    • Prérequis audio :

      La taille du fichier de musique de fond n'est pas limitée, mais les fichiers volumineux nécessitent un temps de téléchargement plus long. Si la durée de la parole synthétisée dépasse celle de la musique de fond, cette dernière est répétée en boucle automatiquement.

      • Fréquence d'échantillonnage : 16 kHz

      • Canaux : mono

      • Format : WAV

        Pour convertir un fichier non-WAV, utilisez ffmpeg :

        ffmpeg -i input_audio -acodec pcm_s16le -ac 1 -ar 16000 output.wav
        
      • Profondeur de bits : 16 bits

    • Exemple :

      <speak bgm="http://nls.alicdn.com/bgm/2.wav" backgroundMusicVolume="30" rate="-500" volume="40">
        <break time="2s"/>
        The old trees on the shady cliff are shrouded in mist
        <break time="700ms"/>
        The sound of rain is still in the bamboo forest
        <break time="700ms"/>
        I know that cotton contributes to the country's plan
        <break time="700ms"/>
        The scenery of Mianzhou is always pitiable
        <break time="2s"/>
      </speak>
      

    ImportantVous êtes responsable des droits d'auteur de tout audio téléversé.

    backgroundMusicVolume

    Chaîne

    Non

    Définit le volume de la musique de fond. Cet attribut s'utilise conjointement avec l'attribut bgm.

  • Relations entre les balises

    La balise <speak> peut contenir du texte ainsi que les balises enfants suivantes :

  • Autres exemples

    • Sans attribut

      <speak>
        Text that requires SSML tags
      </speak>
      
    • Attributs combinés, séparés par des espaces

      <speak rate="200" pitch="-100" volume="80">
        So when put together, my voice sounds like this.
      </speak>
      

<break> : contrôler la durée des pauses

  • Description

    Insère une pause silencieuse lors de la synthèse vocale afin de simuler les pauses naturelles d'une conversation. Les unités de temps acceptées sont les secondes (s) et les millisecondes (ms).

  • Syntaxe

    # No attributes
    <break/>
    # With time attribute
    <break time="string"/>
    
  • Attributs

    RemarqueSans attribut, une balise <break> insère une pause de 1 seconde par défaut.

    AttributTypeObligatoireDescription

    time

    Chaîne

    Non

    Indique la durée de la pause, en secondes ou en millisecondes (par exemple, « 2s » ou « 50ms »).

    • Valeurs valides :

      • En secondes (s) : entier compris entre 1 et 10 (inclus)
      • En millisecondes (ms) : entier compris entre 50 et 10000, bornes incluses
    • Exemple :

      <speak>
        Please close your eyes and take a rest.<break time="500ms"/>Okay, please open your eyes.
      </speak>
      

    ImportantLorsque plusieurs balises <break> se succèdent, la durée totale de la pause correspond à la somme des durées individuelles. Si ce total excède 10 secondes, seules les 10 premières secondes sont appliquées.

    Par exemple, dans le code SSML ci-dessous, la durée cumulée des balises <break> atteint 15 secondes. Puisque cela dépasse la limite de 10 secondes, la pause effective est tronquée à 10 secondes :

    <speak>
      Please close your eyes and take a rest.<break time="5s"/><break time="5s"/><break time="5s"/>Okay, please open your eyes.
    </speak>
    
  • Relations entre les balises

    <break> est un élément auto-fermant qui ne peut pas contenir d'éléments enfants.

<sub> : remplacer du texte

  • Description

    Remplace un texte donné par un contenu mieux adapté à la synthèse vocale. Par exemple, « W3C » sera lu comme « World Wide Web Consortium ».

  • Syntaxe

    <sub alias="string"></sub>
    
  • Attributs

    AttributTypeObligatoireDescription

    alias

    Chaîne

    Oui

    Définit le texte de remplacement à lire à haute voix.

    Exemple :

     <speak>
       <sub alias="network protocol">W3C</sub>
     </speak>
    
  • Relations entre les balises

    La balise <sub> ne peut contenir que du texte brut.

<phoneme> : préciser la prononciation (pinyin ou phonétique)

  • Description

    Offre un contrôle précis de la prononciation du texte. Le texte chinois admet la notation pinyin, tandis que le texte anglais prend en charge la notation phonétique CMU. Cette fonctionnalité s'avère particulièrement utile pour lever les ambiguïtés des caractères polyphoniques et gérer la prononciation des langues étrangères.

  • Syntaxe

    <phoneme alphabet="string" ph="string">Text</phoneme>
    
  • Attributs

    AttributTypeObligatoireDescription

    alphabet

    Chaîne

    Oui

    Définit le type de prononciation : pinyin pour le chinois ou symboles phonétiques pour l'anglais. Valeurs autorisées :

    ph

    Chaîne

    Oui

    Spécifie la notation pinyin ou phonétique exacte. Règles d'utilisation :

    • Séparez les pinyins de plusieurs caractères par des espaces. Le nombre d'entrées pinyin doit correspondre exactement au nombre de caractères.

    • Chaque entrée pinyin se compose de la prononciation suivie d'un numéro de ton. Les numéros de ton vont de 1 à 5, où 5 représente le ton neutre.

    • Exemple :

      <speak>
        How to spell <phoneme alphabet="cmu" ph="S AY N">sin</phoneme>?
      </speak>
      
  • Relations entre les balises

    La balise <phoneme> ne peut contenir que du texte brut.

<soundEvent> : insérer un son externe (sonnerie, miaulement, etc.)

  • Description

    Insère un fichier d'effet sonore (tel qu'une alerte ou un son d'ambiance) à un endroit précis de la parole afin d'enrichir le rendu audio.

  • Syntaxe

     <soundEvent src="URL"/>
    
  • Attributs

    AttributTypeObligatoireDescription

    src

    Chaîne

    Oui

    Indique l'URL d'un fichier audio externe.

    Le fichier audio doit être stocké sur Alibaba Cloud OSS (voir Téléverser des objets) et le bucket doit disposer d'un accès public-read au minimum. Si l'URL contient des caractères spéciaux XML (tels que &, <, >), vous devez les échapper.

    • Prérequis audio :

      • Fréquence d'échantillonnage : 16 kHz

      • Canaux : mono

      • Format : WAV

        Pour convertir un fichier non-WAV, utilisez ffmpeg :

        ffmpeg -i input_audio -acodec pcm_s16le -ac 1 -ar 16000 output.wav
        
      • Taille du fichier : 2 Mo maximum

      • Profondeur de bits : 16 bits

    • Exemple :

      <speak>
        A horse was frightened<soundEvent src="http://nls.alicdn.com/sound-event/horse-neigh.wav"/>and people scattered to avoid it.
      </speak>
      

    ImportantVous êtes légalement responsable des droits d'auteur du fichier audio téléversé.

  • Relations entre les balises

    <soundEvent> est un élément auto-fermant qui ne peut pas contenir d'éléments enfants.

<say-as> : définir la lecture du texte (nombres, dates, téléphones, etc.)

  • Description

    Cet élément précise le type de contenu du texte (tel que des nombres, des dates ou des numéros de téléphone) afin que le système le lise selon les règles appropriées à ce type.

  • Syntaxe

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

    Attribut

    Type

    Obligatoire

    Description

    interpret-as

    Chaîne

    Oui

    Définit le type de contenu du texte situé à l'intérieur de la balise.

    Valeurs autorisées:

    • cardinal: lit un entier ou un nombre décimal de manière standard

    • digits: lit chaque chiffre séparément, par exemple 123 se lit « one two three »

    • telephone: lit les chiffres selon le format téléphonique standard

    • name: applique les règles standard de prononciation des noms

    • address: applique les règles standard de prononciation des adresses

    • id: applique les règles standard de prononciation des identifiants, tels que les noms de compte et pseudonymes

    • characters: lit chaque caractère séparément

    • punctuation: prononce le nom de chaque signe de ponctuation

    • date: applique les règles standard de lecture des dates

    • time: applique les règles standard de lecture des heures

    • currency: applique les règles standard de lecture des montants

    • measure: applique les règles standard de lecture des unités de mesure

  • Plages prises en charge pour chaque type <say-as>

    • cardinal

      Format

      Exemple

      Sortie en anglais

      Remarques

      Chaîne de chiffres

      145

      one hundred forty five

      Entiers: entiers positifs et négatifs jusqu'à 13 chiffres, [-999999999999,999999999999].

      Décimaux: aucune limite particulière du nombre de décimales ; 10 au maximum sont recommandées.

      Chaîne de chiffres commençant par zéro

      0145

      one hundred forty five

      Signe moins + chaîne de chiffres

      -145

      minus hundred forty five

      Chaîne de chiffres avec virgule séparant chaque groupe de 3 chiffres

      60,000

      sixty thousand

      Signe moins + chaîne de chiffres séparés par des virgules

      -208,000

      minus two hundred eight thousand

      Chaîne de chiffres + point décimal + zéro

      12.00

      twelve

      Chaîne de chiffres + point décimal + chaîne de chiffres

      12.34

      twelve point three four

      Chaîne de chiffres séparés par des virgules + point décimal + chaîne de chiffres

      1,000.1

      one thousand point one

      Signe moins + chaîne de chiffres + point décimal + chaîne de chiffres

      -12.34

      minus twelve point three four

      Signe moins + chaîne de chiffres séparés par des virgules + point décimal + chaîne de chiffres

      -1,000.1

      minus one thousand point one

      Chaîne de chiffres (séparés par des virgules) + trait d'union + chaîne de chiffres (séparés par des virgules)

      1-1,000

      one to one thousand

      Autres lectures par défaut

      012.34

      twelve point three four

      Aucune

      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

      Format

      Exemple

      Sortie en anglais

      Remarques

      Chaîne de chiffres

      12034

      one two zero three four

      Aucune limite spécifique sur la longueur de la chaîne de chiffres, mais il est recommandé de ne pas dépasser 20 chiffres.

      Lorsque les chaînes de chiffres sont regroupées par des espaces ou des traits d'union, une pause marquée par une virgule est insérée entre les groupes. Jusqu'à 5 groupes sont pris en charge.

      Suite de chiffres + espace/tiret + suite de chiffres + espace/tiret + suite de chiffres + espace/tiret + suite de chiffres

      1-23-456 7890

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

    • telephone

      Format

      Exemple

      Sortie en anglais

      Remarques

      Chaîne de chiffres

      12034

      one two oh three four

      Aucune limite spécifique sur la longueur de la chaîne de chiffres, mais il est recommandé de ne pas dépasser 20 chiffres. Lorsque les chaînes de chiffres sont regroupées par des espaces ou des traits d'union, une pause marquée par une virgule est insérée entre les groupes. Jusqu'à 5 groupes sont pris en charge.

      Suite de chiffres + espace/tiret + suite de chiffres + espace/tiret + suite de chiffres

      1-23-456 7890

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

      Signe plus + suite de chiffres + espace/tiret + suite de chiffres

      +43-211-0567

      plus four three, two one, oh five six seven

      Parenthèse ouvrante + suite de chiffres + parenthèse fermante + espace + suite de chiffres + espace/tiret + suite de chiffres

      (21) 654-3210

      (two one) six five four, three two one oh

    • address

      Cette balise n'est pas prise en charge pour le texte en anglais.

    • id

      Pour le texte en anglais, cette balise fonctionne de la même manière que la balise characters.

    • characters

      Format

      Exemple

      Sortie en anglais

      Remarques

      Chaîne

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

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

      Prend en charge les caractères chinois, les lettres majuscules et minuscules, les chiffres de 0 à 9, ainsi que certains caractères pleine chasse et demi-chasse.

      Les espaces dans la sortie indiquent des pauses entre les caractères, ce qui signifie que chaque caractère est lu individuellement.

      Si le texte à l'intérieur de la balise contient des caractères spéciaux XML, échappez-les.

    • punctuation

      Pour le texte en anglais, cette balise fonctionne de la même manière que la balise characters.

    • date

      Format

      Exemple

      Sortie en anglais

      Remarques

      Quatre chiffres/deux chiffres ou quatre chiffres-deux chiffres

      2000/01

      two thousand, oh one

      S'étend sur plusieurs années.

      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

      Nombre à quatre chiffres commençant par 1 ou 2

      2000

      two thousand

      Année à 4 chiffres.

      1900

      nineteen hundred

      1905

      nineteen oh five

      2021

      twenty one

      Jour - jour de la semaine (trait d'union)

      ou

      Jour - jour de la semaine

      ou

      Jour & jour de la semaine

      mon-wed

      monday to wednesday

      Si le texte dans la balise de plage de jours de la semaine contient des caractères spéciaux XML, échappez ces caractères.

      tue~fri

      tuesday to friday

      sat&sun

      saturday and sunday

      DD-DD MMM, YYYY

      ou

      DD~DD MMM, YYYY

      ou

      DD&DD MMM, YYYY

      19-20 Jan, 2000

      the nineteen to the twentieth of january two thousand

      DD: jour sur 2 chiffres. MMM: mois abrégé sur 3 lettres ou en toutes lettres. YYYY: année sur 4 chiffres commençant par 1 ou 2.

      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

      ou

      MMM DD~DD

      ou

      MMM DD&DD

      Feb 01 - 03

      feburary the first to the third

      MMM: mois abrégé sur 3 lettres ou en toutes lettres. DD: jour sur 2 chiffres.

      Aug 10~20

      august the tenth to the twentieth

      Dec 11&12

      december the eleventh and the twelfth

      MMM-MMM

      ou

      MMM~MMM

      ou

      MMM&MMM

      Jan-Jun

      january to june

      MMM: mois abrégé sur 3 lettres ou en toutes lettres.

      jul ~ dec

      july to december

      sep&oct

      september and october

      YYYY-YYYY

      ou

      YYYY~YYYY

      1990 - 2000

      nineteen ninety to two thousand

      YYYY: année sur 4 chiffres commençant par 1 ou 2.

      2001~2021

      two thousand one to twenty one

      WWW DD MMM YYYY

      Sun 20 Nov 2011

      sunday the twentieth of november twenty eleven

      WWW est l'abréviation sur trois lettres ou le nom complet du jour de la semaine. DD est le jour sur deux chiffres. MMM est le mois abrégé sur trois lettres ou en toutes lettres. MM est le mois sur deux chiffres, abrégé sur trois lettres ou en toutes lettres. YYYY est l'année sur quatre chiffres commençant par 1 ou 2.

      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

      aturday 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

      Autres lectures par défaut

      10 Mar, 2001

      the tenth of march two thousand one

      Aucune

      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

      Format

      Exemple

      Sortie en anglais

      Remarques

      HH:MM AM ou PM

      09:00 AM

      nine A M

      HH: heure sur 1 ou 2 chiffres. MM: minutes sur 2 chiffres. AM/PM: matin/après-midi.

      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

      Point horaire-Point horaire

      8:00 am - 05:30 pm

      eight a m to five p m

      Prend en charge les formats et plages horaires courants.

      7:05~10:15 AM

      seven oh five to ten fifteen A M

      09:00-13:00

      nine oclock to thirteen hundred

    • currency

      Format

      Exemple

      Sortie en anglais

      Remarques

      Nombre + identifiant de devise

      1.00 RMB

      one yuan

      Formats numériques pris en charge: entiers, décimaux et notation internationale avec virgules de séparation.

      Identifiants monétaires pris en charge:

      CN¥ (yuan)

      CNY (yuan)

      RMB (yuan)

      AUD (dollar australien)

      CAD (dollar canadien)

      CHF (franc suisse)

      DKK (couronne danoise)

      EUR (euro)

      GBP (livre sterling)

      HKD (dollar de Hong Kong (Chine))

      JPY (yen japonais)

      NOK (couronne norvégienne)

      SEK (couronne suédoise)

      SGD (dollar de Singapour)

      USD (dollar des États-Unis)

      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

      Identifiant de devise + nombre

      US$ 1.00

      one US dollar

      Formats numériques pris en charge: entiers, décimaux et notation internationale avec virgules de séparation.

      Identifiants monétaires pris en charge:

      US$ (dollar américain)

      CA$ (dollar canadien)

      AU$ (dollar australien)

      SG$ (dollar de Singapour)

      HK$ (dollar de Hong Kong)

      C$ (dollar canadien)

      A$ (dollar australien)

      $ (dollar)

      £ (livre)

      € (euro)

      CN¥ (yuan)

      CNY (yuan)

      RMB (yuan)

      AUD (dollar australien)

      CAD (dollar canadien)

      CHF (franc suisse)

      DKK (couronne danoise)

      EUR (euro)

      GBP (livre sterling)

      HKD (dollar de Hong Kong (Chine))

      JPY (yen japonais)

      NOK (couronne norvégienne)

      SEK (couronne suédoise)

      SGD (dollar de Singapour)

      USD (dollar des États-Unis)

      $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

      Nombre + classificateur + identifiant de devise

      ou

      Identifiant de devise + nombre + quantificateur

      1.23 Tn RMB

      one point two three trillion yuan

      Formats de multiplicateurs pris en charge:

      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

      Format

      Exemple

      Sortie en anglais

      Remarques

      Nombre + unité de mesure

      1.0 kg

      one kilogram

      Formats numériques pris en charge: entiers, décimaux et notation internationale avec virgules de séparation.

      Prend en charge les abréviations d'unités courantes.

      1,234.01 km

      one thousand two hundred thirty four point zero one kilometres.

      Unité de mesure

      mm2

      square millimetre

    • Le tableau suivant présente la prononciation des symboles courants avec <say-as>.

      Symbole

      Prononciation en anglais

      !

      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

      \

      back slash

      ]

      right bracket

      ^

      caret

      _

      underscore

      `

      back quote

      {

      left brace

      |

      vertical bar

      }

      right brace

      ~

      tilde

      !

      exclamation mark

      “

      left double quote

      ”

      right double qute

      ‘

      left quote

      ’

      right quote

      (

      left parenthesis

      )

      right parenthesis

      ,

      comma

      。

      full stop

      —

      em dash

      :

      colon

      ;

      semicolon

      ?

      question mark

      、

      enumeration comma

      …

      ellipsis

      ……

      ellipsis

      《

      left guillemet

      》

      right guillemet

      ¥

      yuan

      ≥

      greater than or equal to

      ≤

      less than or equal to

      ≠

      not equal

      ≈

      approximately equal

      ±

      plus or minus

      ×

      times

      π

      pi

      Α

      alpha

      Β

      beta

      Γ

      gamma

      Δ

      delta

      Ε

      epsilon

      Ζ

      zeta

      Θ

      theta

      Ι

      iota

      Κ

      kappa

      ∧

      lambda

      Μ

      mu

      Ν

      nu

      Ξ

      ksi

      Ο

      omicron

      ∏

      pi

      Ρ

      rho

      ∑

      sigma

      Τ

      tau

      Υ

      upsilon

      Φ

      phi

      Χ

      chi

      Ψ

      psi

      Ω

      omega

      α

      alpha

      β

      beta

      γ

      gamma

      δ

      delta

      ε

      epsilon

      ζ

      zeta

      η

      eta

      θ

      theta

      ι

      iota

      κ

      kappa

      λ

      lambda

      μ

      mu

      ν

      nu

      ξ

      ksi

      ο

      omicron

      π

      pi

      ρ

      rho

      σ

      sigma

      τ

      tau

      υ

      upsilon

      φ

      phi

      χ

      chi

      ψ

      psi

      ω

      omega

    • Le tableau suivant présente la prononciation des unités de mesure courantes avec <say-as>.

      Format

      Catégorie

      Exemple en anglais

      Abréviation

      Longueur

      nm (nanomètre), μm (micromètre), mm (millimètre), cm (centimètre), m (mètre), km (kilomètre), ft (pied), in (pouce)

      Surface

      cm² (centimètre carré), ㎡ (mètre carré), km2 (kilomètre carré), SqFt (pied carré)

      Volume

      cm³ (centimètre cube), m³ (mètre cube), km3 (kilomètre cube), mL (millilitre), L (millilitre), gal (gallon)

      Masse

      μg (microgramme), mg (microgramme), g (gramme), kg (kilogramme)

      Temps

      min (minute), sec (seconde), ms (milliseconde)

      Électromagnétisme

      μA (microampère), mA (milliampère), Hz (hertz), kHz (kilohertz), MHz (mégahertz), GHz (gigahertz), V (volt), kV (kilovolt), kWh (kilowattheure)

      Son

      dB (décibel)

      Pression atmosphérique

      Pa (pascal), kPa (kilopascal), MPa (mégapascal)

      Autres unités courantes

      Prend également en charge des unités non listées ci-dessus, telles que tsp (cuillère à café), rpm (tours par minute), KB (kilo-octet) et mmHg (millimètre de mercure).

  • Relations entre les balises

    La balise <say-as> peut contenir du texte et du <vhml/>.

  • Exemples

    • cardinal

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

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

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

      <speak>
        Her former name is <say-as interpret-as="name">Zeng Xiaofan</say-as>
      </speak>
      
    • address

      <speak>
        <say-as interpret-as="address">Fulu International, Building 1, Unit 3, Room 304</say-as>
      </speak>
      
    • id

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

      <speak>
        <say-as interpret-as="characters">Greek letters αβ</say-as>
      </speak>
      
      <speak>
        <say-as interpret-as="characters">*b+3.c$=α</say-as>
      </speak>
      
    • punctuation

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

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

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

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

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