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_ssmlsurtrueet 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 :
- Obtenir une clé API
- 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-WebSocketjackson-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) →"'(guillemet simple ou apostrophe) →'&(esperluette) →&<(signe inférieur à) → <>(signe supérieur à) → >
<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
Attribut Type Obligatoire Description voice
Chaîne
Non
Définit la voix à utiliser.
Cet attribut prévaut sur le paramètre
voicede 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_ratede 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_ratede 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
volumede 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
effectValuepour personnaliser ces effets spécifiques. - Chaque balise SSML n'accepte qu'un seul effet. Il est impossible de définir plusieurs attributs
effectsimultané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
effectValuepour 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 de0signifie 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.
Attribut Type Obligatoire Description 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
Attribut Type Obligatoire Description 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
Attribut Type Obligatoire Description alphabet
Chaîne
Oui
Définit le type de prononciation : pinyin pour le chinois ou symboles phonétiques pour l'anglais. Valeurs autorisées :
- "py" : pinyin
- "cmu" : symboles phonétiques. Consultez The CMU Pronouncing Dictionary.
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ù5repré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
Attribut Type Obligatoire Description 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>
-