Gunakan SSML (Speech Synthesis Markup Language) untuk menyesuaikan karakteristik ucapan seperti kecepatan, jeda, dan pelafalan.
Ikhtisar
SSML (Speech Synthesis Markup Language) adalah bahasa markup berbasis XML untuk sintesis ucapan. Sisipkan tag SSML dalam teks Anda guna mengatur laju ucapan, intonasi, jeda, dan volume, atau menambahkan musik latar serta efek suara agar menghasilkan output audio yang lebih kaya.
Kasus penggunaan umum meliputi:
-
Audiobook: atur jeda dan laju ucapan secara presisi serta tambahkan musik latar untuk pengalaman mendengarkan yang imersif
-
Layanan pelanggan cerdas: gunakan tag
<say-as>untuk memastikan pembacaan akurat nomor telepon, tanggal, dan informasi serupa -
Penyiaran multibahasa: gunakan tag
<phoneme>untuk menentukan pelafalan tepat kata-kata asing -
Pendidikan daring: ubah rumus LaTeX menjadi ucapan alami dengan fitur pembacaan rumus
Kedua fitur tersebut tersedia untuk keluarga model CosyVoice. Untuk panduan pemilihan model, lihat Sintesis suara.
SSML
Batasan
-
Model: SSML hanya didukung oleh cosyvoice-v3.5-flash, cosyvoice-v3.5-plus, cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2.
-
Voice: Hanya voice hasil kloning dan voice preset yang ditandai kompatibel dengan SSML di Daftar Voice CosyVoice yang didukung.
-
API: SSML didukung melalui API berikut:
-
Java SDK (versi 2.20.3 atau lebih baru): panggilan non-streaming dan streaming unidireksional
-
Python SDK (versi 1.23.4 atau lebih baru): panggilan non-streaming dan streaming unidireksional
-
WebSocket API: atur parameter
enable_ssmlketrue, dan kirim satu event continue-task
-
Mulai cepat
Contoh berikut menggunakan SSML untuk mengatur laju ucapan selama sintesis. Sebelum menjalankan kode, lengkapi prasyarat berikut:
-
Instal DashScope SDK (Python 1.23.4 atau lebih baru, Java 2.20.3 atau lebih baru). Untuk detailnya, lihat 安装SDK.
Model cosyvoice-v3.5-plus dan cosyvoice-v3.5-flash saat ini hanya tersedia di wilayah Beijing dan dirancang khusus untuk skenario kloning suara (tidak tersedia voice preset). Sebelum menggunakan model ini, buat voice target dengan mengikuti petunjuk di Kloning suara.
Java SDK
Panggilan non-streaming
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;
/**
* Deskripsi fitur SSML:
* 1. Hanya panggilan non-streaming dan panggilan streaming unidireksional yang mendukung fitur SSML
* 2. Hanya voice hasil kloning dari model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2, serta voice sistem yang ditandai mendukung SSML dalam daftar voice, yang mendukung fitur SSML (misalnya, voice longanyang dari model cosyvoice-v3-flash)
*/
public class Main {
private static String model = "cosyvoice-v3-flash";
private static String voice = "longanyang";
public static void main(String[] args) {
// URL wilayah Singapura. Ganti WorkspaceId dengan ID ruang kerja aktual Anda. URL berbeda-beda tergantung wilayah.
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()
// Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/en/model-studio/get-api-key
// Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: .apiKey("sk-xxx")
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.model(model)
.voice(voice)
.build();
SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
ByteBuffer audio = null;
try {
// Panggilan non-streaming, diblokir hingga audio dikembalikan
// Karakter khusus perlu di-escape
audio = synthesizer.call("<speak rate=\"2\">Laju bicara saya lebih cepat daripada orang biasa.</speak>");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// Tutup koneksi WebSocket ketika tugas selesai
synthesizer.getDuplexApi().close(1000, "bye");
}
if (audio != null) {
// Simpan data audio ke file lokal "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);
}
}
// Transmisi teks pertama memerlukan pembentukan koneksi WebSocket, sehingga latensi paket pertama mencakup waktu penyiapan koneksi
System.out.println(
"[Metric] requestId: "
+ synthesizer.getLastRequestId()
+ ", latensi paket pertama (ms): "
+ synthesizer.getFirstPackageDelay());
}
}
Panggilan streaming unidireksional
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;
/**
* Deskripsi fitur SSML:
* 1. Hanya panggilan non-streaming dan panggilan streaming unidireksional yang mendukung fitur SSML
* 2. Hanya voice hasil kloning dari model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2, serta voice sistem yang ditandai mendukung SSML dalam daftar voice, yang mendukung fitur SSML (misalnya, voice longanyang dari model cosyvoice-v3-flash)
*/
public class Main {
private static String model = "cosyvoice-v3-flash";
private static String voice = "longanyang";
public static void main(String[] args) {
// URL wilayah Singapura. Ganti WorkspaceId dengan ID ruang kerja aktual Anda. URL berbeda-beda tergantung wilayah.
Constants.baseWebsocketApiUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference";
streamAudioDataToSpeaker();
System.out.println("Audio telah disimpan ke file output.mp3");
System.exit(0);
}
public static void streamAudioDataToSpeaker() {
CountDownLatch latch = new CountDownLatch(1);
final FileOutputStream[] fileOutputStream = new FileOutputStream[1];
try {
fileOutputStream[0] = new FileOutputStream("output.mp3");
} catch (IOException e) {
System.err.println("Gagal membuat file output: " + e.getMessage());
return;
}
// Implementasikan antarmuka ResultCallback
ResultCallback<SpeechSynthesisResult> callback = new ResultCallback<SpeechSynthesisResult>() {
@Override
public void onEvent(SpeechSynthesisResult result) {
if (result.getAudioFrame() != null) {
// Tulis data audio ke file lokal
try {
byte[] audioData = result.getAudioFrame().array();
fileOutputStream[0].write(audioData);
fileOutputStream[0].flush();
} catch (IOException e) {
System.err.println("Gagal menulis data audio: " + e.getMessage());
}
}
}
@Override
public void onComplete() {
System.out.println("Menerima Complete, sintesis ucapan selesai");
closeFileOutputStream(fileOutputStream[0]);
latch.countDown();
}
@Override
public void onError(Exception e) {
System.out.println("Terjadi kesalahan: " + e.toString());
closeFileOutputStream(fileOutputStream[0]);
latch.countDown();
}
};
SpeechSynthesisParam param =
SpeechSynthesisParam.builder()
// Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/en/model-studio/get-api-key
// Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: .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 {
// Panggilan streaming unidireksional, langsung mengembalikan null (hasil aktual dikirimkan secara asinkron melalui antarmuka callback), ambil audio biner secara real-time dari metode onEvent pada antarmuka callback
// Karakter khusus perlu di-escape
synthesizer.call("<speak rate=\"2\">Laju bicara saya lebih cepat daripada orang biasa.</speak>");
// Tunggu hingga sintesis selesai
latch.await();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// Tutup koneksi WebSocket setelah tugas selesai
try {
synthesizer.getDuplexApi().close(1000, "bye");
} catch (Exception e) {
System.err.println("Gagal menutup koneksi WebSocket: " + e.getMessage());
}
// Pastikan aliran file ditutup
closeFileOutputStream(fileOutputStream[0]);
}
// Transmisi teks pertama memerlukan pembentukan koneksi WebSocket, sehingga latensi paket pertama mencakup waktu penyiapan koneksi
System.out.println(
"[Metric] requestId: "
+ synthesizer.getLastRequestId()
+ ", latensi paket pertama (ms): "
+ synthesizer.getFirstPackageDelay());
}
private static void closeFileOutputStream(FileOutputStream fileOutputStream) {
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
System.err.println("Gagal menutup aliran file: " + e.getMessage());
}
}
}
Python SDK
Panggilan non-streaming
# coding=utf-8
# Deskripsi fitur SSML:
# 1. Hanya panggilan non-streaming dan panggilan streaming unidireksional yang mendukung fitur SSML
# 2. Hanya voice hasil kloning dari model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2, serta voice sistem yang ditandai mendukung SSML dalam daftar voice, yang mendukung fitur SSML (misalnya, voice longanyang dari model cosyvoice-v3-flash)
import dashscope
from dashscope.audio.tts_v2 import *
import os
# Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/en/model-studio/get-api-key
# Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# URL wilayah Singapura. Ganti WorkspaceId dengan ID ruang kerja aktual Anda. URL berbeda-beda tergantung wilayah.
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"
# Buat instance SpeechSynthesizer dan masukkan parameter permintaan seperti model dan voice di konstruktor
synthesizer = SpeechSynthesizer(model=model, voice=voice)
# Panggilan non-streaming, diblokir hingga audio dikembalikan
# Karakter khusus perlu di-escape
audio = synthesizer.call("<speak rate=\"2\">Laju bicara saya lebih cepat daripada orang biasa.</speak>")
# Simpan audio ke file lokal
with open('output.mp3', 'wb') as f:
f.write(audio)
# Transmisi teks pertama memerlukan pembentukan koneksi WebSocket, sehingga latensi paket pertama mencakup waktu penyiapan koneksi
print('[Metric] requestId: {}, latensi paket pertama: {} ms'.format(
synthesizer.get_last_request_id(),
synthesizer.get_first_package_delay()))
Panggilan streaming unidireksional
# coding=utf-8
# Deskripsi fitur SSML:
# 1. Hanya panggilan non-streaming dan panggilan streaming unidireksional yang mendukung fitur SSML
# 2. Hanya voice hasil kloning dari model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2, serta voice sistem yang ditandai mendukung SSML dalam daftar voice, yang mendukung fitur SSML (misalnya, voice longanyang dari model cosyvoice-v3-flash)
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
# Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/en/model-studio/get-api-key
# Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: dashscope.api_key = "sk-xxx"
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
# URL wilayah Singapura. Ganti WorkspaceId dengan ID ruang kerja aktual Anda. URL berbeda-beda tergantung wilayah.
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"
# Definisikan antarmuka callback
class Callback(ResultCallback):
_player = None
_stream = None
def on_open(self):
# Buka file output untuk menulis data audio
self.file = open("output.mp3", "wb")
print("Koneksi dibentuk: " + get_timestamp())
def on_complete(self):
print("Sintesis ucapan selesai, semua hasil telah diterima: " + get_timestamp())
if hasattr(self, 'file') and self.file:
self.file.close()
self
# Transmisi teks pertama memerlukan pembentukan koneksi WebSocket, sehingga latensi paket pertama mencakup waktu penyiapan koneksi
print('[Metric] requestId: {}, latensi paket pertama: {} ms'.format(
self.synthesizer.get_last_request_id(),
self.synthesizer.get_first_package_delay()))
def on_error(self, message: str):
print(f"Terjadi kesalahan sintesis ucapan: {message}")
if hasattr(self, 'file') and self.file:
self.file.close()
def on_close(self):
print("Koneksi ditutup: " + 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() + " Panjang audio biner: " + str(len(data)))
# Tulis data audio ke file
self.file.write(data)
callback = Callback()
# Buat instance SpeechSynthesizer dan masukkan parameter permintaan seperti model dan voice di konstruktor
synthesizer = SpeechSynthesizer(
model=model,
voice=voice,
callback=callback,
)
# Tetapkan instance synthesizer ke callback untuk digunakan di on_complete
callback.synthesizer = synthesizer
# Panggilan streaming unidireksional, kirim teks untuk sintesis, ambil audio biner secara real-time dari metode on_data pada antarmuka callback
# Karakter khusus perlu di-escape
synthesizer.call("<speak rate=\"2\">Laju bicara saya lebih cepat daripada orang biasa.</speak>")
WebSocket API
Go
// Deskripsi fitur SSML:
// 1. Saat mengirim perintah run-task, atur parameter enable_ssml ke true untuk mengaktifkan dukungan SSML
// 2. Kirim teks yang berisi SSML melalui perintah continue-task, dan hanya satu perintah continue-task yang diizinkan
// 3. Hanya voice hasil kloning dari model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2, serta voice sistem yang ditandai mendukung SSML dalam daftar voice, yang mendukung fitur SSML (misalnya, voice longanyang dari model cosyvoice-v3-flash)
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/google/uuid"
"github.com/gorilla/websocket"
)
const (
// URL wilayah Singapura. Ganti WorkspaceId dengan ID ruang kerja aktual Anda. URL berbeda-beda tergantung wilayah.
wsURL = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/"
outputFile = "output.mp3"
)
func main() {
// Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/en/model-studio/get-api-key
// Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: apiKey := "sk-xxx"
apiKey := os.Getenv("DASHSCOPE_API_KEY")
// Hapus file output
os.Remove(outputFile)
os.Create(outputFile)
// Hubungkan ke 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("Koneksi gagal, kode status HTTP: %d\n", resp.StatusCode)
}
fmt.Println("Koneksi gagal:", err)
return
}
defer conn.Close()
// Hasilkan ID tugas
taskID := uuid.New().String()
fmt.Printf("ID tugas yang dihasilkan: %s\n", taskID)
// Kirim perintah run-task
runTaskCmd := map[string]interface{}{
"header": map[string]interface{}{
"action": "run-task",
"task_id": taskID,
"streaming": "duplex",
},
"payload": map[string]interface{}{
"task_group": "audio",
"task": "tts",
"function": "SpeechSynthesizer",
"model": "cosyvoice-v3-flash",
"parameters": map[string]interface{}{
"text_type": "PlainText",
"voice": "longanyang",
"format": "mp3",
"sample_rate": 22050,
"volume": 50,
"rate": 1,
"pitch": 1,
// Jika enable_ssml diatur ke true, hanya satu perintah continue-task yang diizinkan; jika tidak, kesalahan "Text request limit violated, expected 1." akan dikembalikan
"enable_ssml": true,
},
"input": map[string]interface{}{},
},
}
runTaskJSON, _ := json.Marshal(runTaskCmd)
fmt.Printf("Mengirim perintah run-task: %s\n", string(runTaskJSON))
err = conn.WriteMessage(websocket.TextMessage, runTaskJSON)
if err != nil {
fmt.Println("Gagal mengirim run-task:", err)
return
}
textSent := false
// Proses pesan
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
fmt.Println("Gagal membaca pesan:", err)
break
}
// Proses pesan biner
if messageType == websocket.BinaryMessage {
fmt.Printf("Menerima pesan biner, panjang: %d\n", len(message))
file, _ := os.OpenFile(outputFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
file.Write(message)
file.Close()
continue
}
// Proses pesan teks
messageStr := string(message)
fmt.Printf("Menerima pesan teks: %s\n", strings.ReplaceAll(messageStr, "\n", ""))
// Penguraian JSON sederhana untuk mendapatkan jenis event
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("Jenis event: %s\n", event)
switch event {
case "task-started":
fmt.Println("=== Menerima event task-started ===")
if !textSent {
// Kirim perintah continue-task. Saat menggunakan SSML, perintah ini hanya dapat dikirim sekali
continueTaskCmd := map[string]interface{}{
"header": map[string]interface{}{
"action": "continue-task",
"task_id": taskID,
"streaming": "duplex",
},
"payload": map[string]interface{}{
"input": map[string]interface{}{
// Karakter khusus perlu di-escape
"text": "<speak rate=\"2\">Laju bicara saya lebih cepat daripada orang biasa.</speak>",
},
},
}
continueTaskJSON, _ := json.Marshal(continueTaskCmd)
fmt.Printf("Mengirim perintah continue-task: %s\n", string(continueTaskJSON))
err = conn.WriteMessage(websocket.TextMessage, continueTaskJSON)
if err != nil {
fmt.Println("Gagal mengirim continue-task:", err)
return
}
textSent = true
// Tunda sebelum mengirim finish-task
time.Sleep(500 * time.Millisecond)
// Kirim perintah finish-task
finishTaskCmd := map[string]interface{}{
"header": map[string]interface{}{
"action": "finish-task",
"task_id": taskID,
"streaming": "duplex",
},
"payload": map[string]interface{}{
"input": map[string]interface{}{},
},
}
finishTaskJSON, _ := json.Marshal(finishTaskCmd)
fmt.Printf("Mengirim perintah finish-task: %s\n", string(finishTaskJSON))
err = conn.WriteMessage(websocket.TextMessage, finishTaskJSON)
if err != nil {
fmt.Println("Gagal mengirim finish-task:", err)
return
}
}
case "task-finished":
fmt.Println("=== Tugas selesai ===")
return
case "task-failed":
fmt.Println("=== Tugas gagal ===")
if header["error_message"] != nil {
fmt.Printf("Pesan kesalahan: %s\n", header["error_message"])
}
return
case "result-generated":
fmt.Println("Menerima event result-generated")
}
}
}
}
}
}
C#
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
// Deskripsi fitur SSML:
// 1. Saat mengirim perintah run-task, atur parameter enable_ssml ke true untuk mengaktifkan dukungan SSML
// 2. Kirim teks yang berisi SSML melalui perintah continue-task, dan hanya satu perintah continue-task yang diizinkan
// 3. Hanya voice hasil kloning dari model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2, serta voice sistem yang ditandai mendukung SSML dalam daftar voice, yang mendukung fitur SSML (misalnya, voice longanyang dari model cosyvoice-v3-flash)
class Program {
// Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/en/model-studio/get-api-key
// Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: private static readonly string ApiKey = "sk-xxx"
private static readonly string ApiKey = Environment.GetEnvironmentVariable("DASHSCOPE_API_KEY") ?? throw new InvalidOperationException("Variabel lingkungan DASHSCOPE_API_KEY belum diatur.");
// URL wilayah Singapura. Ganti WorkspaceId dengan ID ruang kerja aktual Anda. URL berbeda-beda tergantung wilayah.
private const string WebSocketUrl = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/";
// Jalur file output
private const string OutputFilePath = "output.mp3";
// Klien WebSocket
private static ClientWebSocket _webSocket = new ClientWebSocket();
// Sumber token pembatalan
private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
// ID tugas
private static string? _taskId;
// Apakah tugas telah dimulai
private static TaskCompletionSource<bool> _taskStartedTcs = new TaskCompletionSource<bool>();
static async Task Main(string[] args) {
try {
// Hapus file output
ClearOutputFile(OutputFilePath);
// Hubungkan ke layanan WebSocket
await ConnectToWebSocketAsync(WebSocketUrl);
// Mulai tugas penerimaan pesan
Task receiveTask = ReceiveMessagesAsync();
// Kirim perintah run-task
_taskId = GenerateTaskId();
await SendRunTaskCommandAsync(_taskId);
// Tunggu event task-started
await _taskStartedTcs.Task;
// Kirim perintah continue-task. Saat menggunakan SSML, perintah ini hanya dapat dikirim sekali
// Karakter khusus perlu di-escape
await SendContinueTaskCommandAsync("<speak rate=\"2\">Laju bicara saya lebih cepat daripada orang biasa.</speak>");
// Kirim perintah finish-task
await SendFinishTaskCommandAsync(_taskId);
// Tunggu hingga tugas penerimaan selesai
await receiveTask;
Console.WriteLine("Tugas selesai, koneksi ditutup.");
} catch (OperationCanceledException) {
Console.WriteLine("Tugas dibatalkan.");
} catch (Exception ex) {
Console.WriteLine($"Terjadi kesalahan: {ex.Message}");
} finally {
_cancellationTokenSource.Cancel();
_webSocket.Dispose();
}
}
private static void ClearOutputFile(string filePath) {
if (File.Exists(filePath)) {
File.WriteAllText(filePath, string.Empty);
Console.WriteLine("File output dihapus.");
} else {
Console.WriteLine("File output tidak ada, tidak perlu dihapus.");
}
}
private static async Task ConnectToWebSocketAsync(string url) {
var uri = new Uri(url);
if (_webSocket.State == WebSocketState.Connecting || _webSocket.State == WebSocketState.Open) {
return;
}
// Atur header koneksi WebSocket
_webSocket.Options.SetRequestHeader("Authorization", $"bearer {ApiKey}");
_webSocket.Options.SetRequestHeader("X-DashScope-DataInspection", "enable");
try {
await _webSocket.ConnectAsync(uri, _cancellationTokenSource.Token);
Console.WriteLine("Berhasil terhubung ke layanan WebSocket.");
} catch (OperationCanceledException) {
Console.WriteLine("Koneksi WebSocket dibatalkan.");
} catch (Exception ex) {
Console.WriteLine($"Koneksi WebSocket gagal: {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,
// Jika enable_ssml diatur ke true, hanya satu perintah continue-task yang diizinkan; jika tidak, kesalahan "Text request limit violated, expected 1." akan dikembalikan
enable_ssml = true
},
input = new { }
});
await SendJsonMessageAsync(command);
Console.WriteLine("Perintah run-task dikirim.");
}
private static async Task SendContinueTaskCommandAsync(string text) {
if (_taskId == null) {
throw new InvalidOperationException("ID tugas belum diinisialisasi.");
}
var command = CreateCommand("continue-task", _taskId, "duplex", new {
input = new {
text
}
});
await SendJsonMessageAsync(command);
Console.WriteLine("Perintah continue-task dikirim.");
}
private static async Task SendFinishTaskCommandAsync(string taskId) {
var command = CreateCommand("finish-task", taskId, "duplex", new {
input = new { }
});
await SendJsonMessageAsync(command);
Console.WriteLine("Perintah finish-task dikirim.");
}
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("Pengiriman pesan dibatalkan.");
}
}
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("Tugas dimulai.");
_taskStartedTcs.TrySetResult(true);
break;
case "task-finished":
Console.WriteLine("Tugas selesai.");
_cancellationTokenSource.Cancel();
break;
case "task-failed":
Console.WriteLine("Tugas gagal: " + response.RootElement.GetProperty("header").GetProperty("error_message").GetString());
_cancellationTokenSource.Cancel();
break;
default:
// result-generated dapat ditangani di sini
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) {
// Proses data biner
Console.WriteLine("Menerima data biner...");
// Simpan data biner ke 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("Penerimaan pesan dibatalkan.");
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
Struktur direktori kode contoh adalah:
my-php-project/
├── composer.json
├── vendor/
└── index.php
File composer.json berisi dependensi berikut. Sesuaikan nomor versi sesuai kebutuhan:
{
"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/"
}
}
}
File index.php berisi kode berikut:
<!-- Deskripsi fitur SSML: -->
<!-- 1. Saat mengirim perintah run-task, atur parameter enable_ssml ke true untuk mengaktifkan dukungan SSML -->
<!-- 2. Kirim teks yang berisi SSML melalui perintah continue-task, dan hanya satu perintah continue-task yang diizinkan -->
<!-- 3. Hanya voice hasil kloning dari model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2, serta voice sistem yang ditandai mendukung SSML dalam daftar voice, yang mendukung fitur SSML (misalnya, voice longanyang dari model cosyvoice-v3-flash) -->
<?php
require __DIR__ . '/vendor/autoload.php';
use Ratchet\Client\Connector;
use React\EventLoop\Loop;
use React\Socket\Connector as SocketConnector;
// Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/en/model-studio/get-api-key
// Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: $api_key = "sk-xxx"
$api_key = getenv("DASHSCOPE_API_KEY");
// URL wilayah Singapura. Ganti WorkspaceId dengan ID ruang kerja aktual Anda. URL berbeda-beda tergantung wilayah.
$websocket_url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/'; // Alamat server WebSocket
$output_file = 'output.mp3'; // Jalur file output
$loop = Loop::get();
if (file_exists($output_file)) {
// Hapus isi file
file_put_contents($output_file, '');
}
// Buat konektor kustom
$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 "Terhubung ke server WebSocket\n";
// Hasilkan ID tugas
$taskId = generateTaskId();
// Kirim perintah run-task
sendRunTaskMessage($conn, $taskId);
// Definisikan fungsi untuk mengirim perintah continue-task
$sendContinueTask = function() use ($conn, $loop, $taskId) {
// Kirim perintah continue-task. Saat menggunakan SSML, perintah ini hanya dapat dikirim sekali
$continueTaskMessage = json_encode([
"header" => [
"action" => "continue-task",
"task_id" => $taskId,
"streaming" => "duplex"
],
"payload" => [
"input" => [
// Karakter khusus perlu di-escape
"text" => "<speak rate=\"2\">Laju bicara saya lebih cepat daripada orang biasa.</speak>"
]
]
]);
$conn->send($continueTaskMessage);
// Kirim perintah finish-task
sendFinishTaskMessage($conn, $taskId);
};
// Bendera apakah event task-started telah diterima
$taskStarted = false;
// Dengarkan pesan
$conn->on('message', function($msg) use ($conn, $sendContinueTask, $loop, &$taskStarted, $taskId, $output_file) {
if ($msg->isBinary()) {
// Tulis data biner ke file lokal
file_put_contents($output_file, $msg->getPayload(), FILE_APPEND);
} else {
// Proses pesan non-biner
$response = json_decode($msg, true);
if (isset($response['header']['event'])) {
handleEvent($conn, $response, $sendContinueTask, $loop, $taskId, $taskStarted);
} else {
echo "Format pesan tidak dikenal\n";
}
}
});
// Dengarkan penutupan koneksi
$conn->on('close', function($code = null, $reason = null) {
echo "Koneksi ditutup\n";
if ($code !== null) {
echo "Kode tutup: " . $code . "\n";
}
if ($reason !== null) {
echo "Alasan tutup: " . $reason . "\n";
}
});
}, function ($e) {
echo "Tidak dapat terhubung: {$e->getMessage()}\n";
});
$loop->run();
/**
* Hasilkan ID tugas
* @return string
*/
function generateTaskId(): string {
return bin2hex(random_bytes(16));
}
/**
* Kirim perintah run-task
* @param $conn
* @param $taskId
*/
function sendRunTaskMessage($conn, $taskId) {
$runTaskMessage = json_encode([
"header" => [
"action" => "run-task",
"task_id" => $taskId,
"streaming" => "duplex"
],
"payload" => [
"task_group" => "audio",
"task" => "tts",
"function" => "SpeechSynthesizer",
"model" => "cosyvoice-v3-flash",
"parameters" => [
"text_type" => "PlainText",
"voice" => "longanyang",
"format" => "mp3",
"sample_rate" => 22050,
"volume" => 50,
"rate" => 1,
"pitch" => 1,
// Jika enable_ssml diatur ke true, hanya satu perintah continue-task yang diizinkan; jika tidak, kesalahan "Text request limit violated, expected 1." akan dikembalikan
"enable_ssml" => true
],
"input" => (object) []
]
]);
echo "Bersiap mengirim perintah run-task: " . $runTaskMessage . "\n";
$conn->send($runTaskMessage);
echo "Perintah run-task dikirim\n";
}
/**
* Baca file audio
* @param string $filePath
* @return bool|string
*/
function readAudioFile(string $filePath) {
$voiceData = file_get_contents($filePath);
if ($voiceData === false) {
echo "Tidak dapat membaca file audio\n";
}
return $voiceData;
}
/**
* Pisahkan data audio
* @param string $data
* @param int $chunkSize
* @return array
*/
function splitAudioData(string $data, int $chunkSize): array {
return str_split($data, $chunkSize);
}
/**
* Kirim perintah finish-task
* @param $conn
* @param $taskId
*/
function sendFinishTaskMessage($conn, $taskId) {
$finishTaskMessage = json_encode([
"header" => [
"action" => "finish-task",
"task_id" => $taskId,
"streaming" => "duplex"
],
"payload" => [
"input" => (object) []
]
]);
echo "Bersiap mengirim perintah finish-task: " . $finishTaskMessage . "\n";
$conn->send($finishTaskMessage);
echo "Perintah finish-task dikirim\n";
}
/**
* Tangani event
* @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 "Tugas dimulai, mengirim perintah continue-task...\n";
$taskStarted = true;
// Kirim perintah continue-task
$sendContinueTask();
break;
case 'result-generated':
// Abaikan event result-generated
break;
case 'task-finished':
echo "Tugas selesai\n";
$conn->close();
break;
case 'task-failed':
echo "Tugas gagal\n";
echo "Kode kesalahan: " . $response['header']['error_code'] . "\n";
echo "Pesan kesalahan: " . $response['header']['error_message'] . "\n";
$conn->close();
break;
case 'error':
echo "Kesalahan: " . $response['payload']['message'] . "\n";
break;
default:
echo "Event tidak dikenal: " . $response['header']['event'] . "\n";
break;
}
// Jika tugas selesai, tutup koneksi
if ($response['header']['event'] == 'task-finished') {
// Tunggu 1 detik untuk memastikan semua data telah ditransmisikan
$loop->addTimer(1, function() use ($conn) {
$conn->close();
echo "Klien menutup koneksi\n";
});
}
// Jika event task-started belum diterima, tutup koneksi
if (!$taskStarted && in_array($response['header']['event'], ['task-failed', 'error'])) {
$conn->close();
}
}
Node.js
Instal dependensi yang diperlukan:
npm install ws
npm install uuid
Kode contoh:
// Deskripsi fitur SSML:
// 1. Saat mengirim perintah run-task, atur parameter enable_ssml ke true untuk mengaktifkan dukungan SSML
// 2. Kirim teks yang berisi SSML melalui perintah continue-task, dan hanya satu perintah continue-task yang diizinkan
// 3. Hanya voice hasil kloning dari model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2, serta voice sistem yang ditandai mendukung SSML dalam daftar voice, yang mendukung fitur SSML (misalnya, voice longanyang dari model cosyvoice-v3-flash)
import fs from 'fs';
import WebSocket from 'ws';
import { v4 as uuid } from 'uuid'; // Untuk menghasilkan UUID
// Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/en/model-studio/get-api-key
// Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: const apiKey = "sk-xxx"
const apiKey = process.env.DASHSCOPE_API_KEY;
// URL wilayah Singapura. Ganti WorkspaceId dengan ID ruang kerja aktual Anda. URL berbeda-beda tergantung wilayah.
const url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/';
// Jalur file output
const outputFilePath = 'output.mp3';
// Hapus file output
fs.writeFileSync(outputFilePath, '');
// Buat klien WebSocket
const ws = new WebSocket(url, {
headers: {
Authorization: `bearer ${apiKey}`,
'X-DashScope-DataInspection': 'enable'
}
});
let taskStarted = false;
let taskId = uuid();
ws.on('open', () => {
console.log('Terhubung ke server WebSocket');
// Kirim perintah run-task
const runTaskMessage = JSON.stringify({
header: {
action: 'run-task',
task_id: taskId,
streaming: 'duplex'
},
payload: {
task_group: 'audio',
task: 'tts',
function: 'SpeechSynthesizer',
model: 'cosyvoice-v3-flash',
parameters: {
text_type: 'PlainText',
voice: 'longanyang', // Voice
format: 'mp3', // Format audio
sample_rate: 22050, // Laju sampel
volume: 50, // Volume
rate: 1, // Laju ucapan
pitch: 1, // Pitch
enable_ssml: true // Apakah akan mengaktifkan SSML. Jika enable_ssml diatur ke true, hanya satu perintah continue-task yang diizinkan; jika tidak, kesalahan "Text request limit violated, expected 1." akan dikembalikan
},
input: {}
}
});
ws.send(runTaskMessage);
console.log('Pesan run-task dikirim');
});
const fileStream = fs.createWriteStream(outputFilePath, { flags: 'a' });
ws.on('message', (data, isBinary) => {
if (isBinary) {
// Tulis data biner ke file
fileStream.write(data);
} else {
const message = JSON.parse(data);
switch (message.header.event) {
case 'task-started':
taskStarted = true;
console.log('Tugas dimulai');
// Kirim perintah continue-task
sendContinueTasks(ws);
break;
case 'task-finished':
console.log('Tugas selesai');
ws.close();
fileStream.end(() => {
console.log('Aliran file ditutup');
});
break;
case 'task-failed':
console.error('Tugas gagal:', message.header.error_message);
ws.close();
fileStream.end(() => {
console.log('Aliran file ditutup');
});
break;
default:
// result-generated dapat ditangani di sini
break;
}
}
});
function sendContinueTasks(ws) {
if (taskStarted) {
// Kirim perintah continue-task. Saat menggunakan SSML, perintah ini hanya dapat dikirim sekali
const continueTaskMessage = JSON.stringify({
header: {
action: 'continue-task',
task_id: taskId,
streaming: 'duplex'
},
payload: {
input: {
// Karakter khusus perlu di-escape
text: '<speak rate="2">Laju bicara saya lebih cepat daripada orang biasa.</speak>'
}
}
});
ws.send(continueTaskMessage);
// Kirim perintah finish-task
const finishTaskMessage = JSON.stringify({
header: {
action: 'finish-task',
task_id: taskId,
streaming: 'duplex'
},
payload: {
input: {}
}
});
ws.send(finishTaskMessage);
}
}
ws.on('close', () => {
console.log('Terputus dari server WebSocket');
});
Java
Untuk pengembangan Java, gunakan Java DashScope SDK. Untuk detailnya, lihat Java SDK.
Contoh WebSocket Java ini memerlukan dependensi berikut:
-
Java-WebSocket -
jackson-databind
Kelola dependensi ini dengan Maven atau Gradle:
pom.xml
<dependencies>
<!-- WebSocket Client -->
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.3</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
build.gradle
dependencies {
// WebSocket Client
implementation 'org.java-websocket:Java-WebSocket:1.5.3'
// JSON Processing
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
}
Kode 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.*;
/**
* Deskripsi fitur SSML:
* 1. Saat mengirim perintah run-task, atur parameter enable_ssml ke true untuk mengaktifkan dukungan SSML
* 2. Kirim teks yang berisi SSML melalui perintah continue-task, dan hanya satu perintah continue-task yang diizinkan
* 3. Hanya voice hasil kloning dari model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2, serta voice sistem yang ditandai mendukung SSML dalam daftar voice, yang mendukung fitur SSML (misalnya, voice longanyang dari model cosyvoice-v3-flash)
*/
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("Koneksi dibentuk");
// Kirim perintah run-task
// Jika enable_ssml diatur ke true, hanya satu perintah continue-task yang diizinkan; jika tidak, kesalahan "Text request limit violated, expected 1." akan dikembalikan
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("Menerima pesan dari server: " + message);
try {
// Uraikan pesan JSON
Map<String, Object> messageMap = new ObjectMapper().readValue(message, Map.class);
if (messageMap.containsKey("header")) {
Map<String, Object> header = (Map<String, Object>) messageMap.get("header");
if (header.containsKey("event")) {
String event = (String) header.get("event");
if ("task-started".equals(event)) {
System.out.println("Menerima event task-started dari server");
// Kirim perintah continue-task. Saat menggunakan SSML, perintah ini hanya dapat dikirim sekali
// Karakter khusus perlu di-escape
sendContinueTask("<speak rate=\\\"2\\\">Laju bicara saya lebih cepat daripada orang biasa.</speak>");
// Kirim perintah finish-task
sendFinishTask();
} else if ("task-finished".equals(event)) {
System.out.println("Menerima event task-finished dari server");
taskFinished = true;
closeConnection();
} else if ("task-failed".equals(event)) {
System.out.println("Tugas gagal: " + message);
closeConnection();
}
}
}
} catch (Exception e) {
System.err.println("Terjadi kesalahan: " + e.getMessage());
}
}
@Override
public void onMessage(ByteBuffer message) {
System.out.println("Menerima data audio biner, ukuran: " + message.remaining());
try (FileOutputStream fos = new FileOutputStream(outputFile, true)) {
byte[] buffer = new byte[message.remaining()];
message.get(buffer);
fos.write(buffer);
System.out.println("Data audio ditulis ke file lokal " + outputFile);
} catch (IOException e) {
System.err.println("Gagal menulis data audio ke file lokal: " + e.getMessage());
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
System.out.println("Koneksi ditutup: " + reason + " (" + code + ")");
}
@Override
public void onError(Exception ex) {
System.err.println("Kesalahan: " + 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 {
// Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/en/model-studio/get-api-key
// Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: String apiKey = "sk-xxx"
String apiKey = System.getenv("DASHSCOPE_API_KEY");
if (apiKey == null || apiKey.isEmpty()) {
System.err.println("Harap atur variabel lingkungan DASHSCOPE_API_KEY");
return;
}
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "bearer " + apiKey);
// URL wilayah Singapura. Ganti WorkspaceId dengan ID ruang kerja aktual Anda. URL berbeda-beda tergantung wilayah.
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("Gagal terhubung ke layanan WebSocket: " + e.getMessage());
e.printStackTrace();
}
}
}
Python
Untuk pengembangan Python, gunakan Python DashScope SDK. Untuk detailnya, lihat Python SDK.
Berikut adalah contoh WebSocket Python. Sebelum menjalankannya, instal dependensi yang diperlukan:
pip uninstall websocket-client
pip uninstall websocket
pip install websocket-client
Jangan beri nama file Python Anda "websocket.py". Hal ini menyebabkan konflik penamaan yang menghasilkan kesalahan (AttributeError: module 'websocket' has no attribute 'WebSocketApp'. Did you mean: 'WebSocket'?).
# Deskripsi fitur SSML:
# 1. Saat mengirim perintah run-task, atur parameter enable_ssml ke true untuk mengaktifkan dukungan SSML
# 2. Kirim teks yang berisi SSML melalui perintah continue-task, dan hanya satu perintah continue-task yang diizinkan
# 3. Hanya suara kloning dari model cosyvoice-v3-flash, cosyvoice-v3-plus, dan cosyvoice-v2, serta suara sistem yang ditandai sebagai didukung SSML dalam daftar suara, yang mendukung fitur SSML (misalnya, suara longanyang dari model cosyvoice-v3-flash)
import websocket
import json
import uuid
import os
import time
class TTSClient:
def __init__(self, api_key, uri):
"""
Menginisialisasi instans TTSClient
Parameter:
api_key (str): Kunci API untuk otentikasi
uri (str): Alamat layanan WebSocket
"""
self.api_key = api_key # Ganti dengan Kunci API Anda
self.uri = uri # Ganti dengan alamat WebSocket Anda
self.task_id = str(uuid.uuid4()) # Membuat ID tugas yang unik
self.output_file = f"output_{int(time.time())}.mp3" # Jalur file audio output
self.ws = None # Instans WebSocketApp
self.task_started = False # Apakah task-started telah diterima
self.task_finished = False # Apakah task-finished / task-failed telah diterima
def on_open(self, ws):
"""
Callback saat koneksi WebSocket dibuat
Mengirim perintah run-task untuk memulai tugas sintesis suara
"""
print("WebSocket terhubung")
# Membuat perintah run-task
run_task_cmd = {
"header": {
"action": "run-task",
"task_id": self.task_id,
"streaming": "duplex"
},
"payload": {
"task_group": "audio",
"task": "tts",
"function": "SpeechSynthesizer",
"model": "cosyvoice-v3-flash",
"parameters": {
"text_type": "PlainText",
"voice": "longanyang",
"format": "mp3",
"sample_rate": 22050,
"volume": 50,
"rate": 1,
"pitch": 1,
# Jika enable_ssml diatur ke True, hanya satu perintah continue-task yang diizinkan; jika tidak, error "Text request limit violated, expected 1." akan dikembalikan
"enable_ssml": True
},
"input": {}
}
}
# Mengirim perintah run-task
ws.send(json.dumps(run_task_cmd))
print("perintah run-task terkirim")
def on_message(self, ws, message):
"""
Callback saat pesan diterima
Menangani pesan teks dan biner secara terpisah
"""
if isinstance(message, str):
# Memproses pesan teks JSON
try:
msg_json = json.loads(message)
print(f"Menerima pesan JSON: {msg_json}")
if "header" in msg_json:
header = msg_json["header"]
if "event" in header:
event = header["event"]
if event == "task-started":
print("Tugas dimulai")
self.task_started = True
# Mengirim perintah continue-task. Saat menggunakan SSML, perintah ini hanya dapat dikirim sekali
# Karakter khusus perlu di-escape
self.send_continue_task("<speak rate=\"2\">My speaking rate is faster than a normal person's.</speak>")
# Mengirim finish-task setelah continue-task dikirim
self.send_finish_task()
elif event == "task-finished":
print("Tugas selesai")
self.task_finished = True
self.close(ws)
elif event == "task-failed":
error_msg = msg_json.get("error_message", "Error tidak diketahui")
print(f"Tugas gagal: {error_msg}")
self.task_finished = True
self.close(ws)
except json.JSONDecodeError as e:
print(f"Penguraian JSON gagal: {e}")
else:
# Memproses pesan biner (data audio)
print(f"Menerima pesan biner, ukuran: {len(message)} byte")
with open(self.output_file, "ab") as f:
f.write(message)
print(f"Data audio ditulis ke file lokal {self.output_file}")
def on_error(self, ws, error):
"""Callback saat terjadi error"""
print(f"Error WebSocket: {error}")
def on_close(self, ws, close_status_code, close_msg):
"""Callback saat koneksi ditutup"""
print(f"WebSocket ditutup: {close_msg} ({close_status_code})")
def send_continue_task(self, text):
"""Mengirim perintah continue-task dengan konten teks untuk disintesis"""
cmd = {
"header": {
"action": "continue-task",
"task_id": self.task_id,
"streaming": "duplex"
},
"payload": {
"input": {
"text": text
}
}
}
self.ws.send(json.dumps(cmd))
print(f"perintah continue-task terkirim, konten teks: {text}")
def send_finish_task(self):
"""Mengirim perintah finish-task untuk mengakhiri tugas sintesis suara"""
cmd = {
"header": {
"action": "finish-task",
"task_id": self.task_id,
"streaming": "duplex"
},
"payload": {
"input": {}
}
}
self.ws.send(json.dumps(cmd))
print("perintah finish-task terkirim")
def close(self, ws):
"""Menutup koneksi secara aktif"""
if ws and ws.sock and ws.sock.connected:
ws.close()
print("Koneksi ditutup secara aktif")
def run(self):
"""Memulai klien WebSocket"""
# Mengatur header permintaan (otentikasi)
header = {
"Authorization": f"bearer {self.api_key}",
"X-DashScope-DataInspection": "enable"
}
# Membuat instans WebSocketApp
self.ws = websocket.WebSocketApp(
self.uri,
header=header,
on_open=self.on_open,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close
)
print("Mendengarkan pesan WebSocket...")
self.ws.run_forever() # Memulai pendengar koneksi persisten
# Contoh penggunaan
if __name__ == "__main__":
# Kunci API untuk wilayah Singapura dan Beijing berbeda. Dapatkan Kunci API: https://www.alibabacloud.com/help/en/model-studio/get-api-key
# Jika Anda belum mengonfigurasi variabel lingkungan, ganti baris berikut dengan Kunci API Model Studio Anda: API_KEY = "sk-xxx"
API_KEY = os.environ.get("DASHSCOPE_API_KEY")
# URL wilayah Singapura. Ganti WorkspaceId dengan ID ruang kerja Anda yang sebenarnya. URL bervariasi menurut wilayah.
SERVER_URI = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/inference/"
client = TTSClient(API_KEY, SERVER_URI)
client.run()
Referensi tag
Implementasi SSML Alibaba Cloud didasarkan pada spesifikasi W3C SSML 1.0. Tidak semua tag standar didukung; layanan ini mengimplementasikan tag yang paling umum digunakan untuk skenario produksi.
-
Saat menggunakan SSML, semua konten teks harus diapit oleh tag
<speak></speak>. -
Beberapa tag
<speak>dapat digunakan secara berurutan (misalnya,<speak></speak><speak></speak>), tetapi tidak mendukung penumpukan (misalnya,<speak><speak></speak></speak>). -
Jika teks di dalam tag berisi karakter khusus XML, escape karakter tersebut sebagai berikut:
-
"(tanda kutip ganda) →" -
'(tanda kutip tunggal/apostrof) →' -
&(ampersand) →& -
<(tanda kurang dari) → < -
>(tanda lebih dari) → >
-
<speak>: elemen root
-
Deskripsi
<speak>adalah elemen root untuk semua konten SSML. Semua teks harus diapit oleh tag<speak></speak>. -
Sintaks
<speak>Teks yang memerlukan pemrosesan SSML</speak> -
Atribut
Atribut
Tipe
Wajib
Deskripsi
voice
String
Tidak
Menentukan voice.
Atribut ini memiliki prioritas lebih tinggi daripada parameter
voicedalam permintaan API.-
Nilai valid: nama voice tertentu. Untuk detailnya, lihat voice cosyvoice-v2.
-
Contoh:
<speak voice="longcheng_v2"> Saya adalah voice pria. </speak>
rate
String
Tidak
Menentukan laju ucapan. Atribut ini memiliki prioritas lebih tinggi daripada parameter
speech_ratedalam permintaan API.-
Nilai valid: angka desimal antara 0,5 dan 2 (inklusif)
-
Nilai default: 1
-
Nilai lebih besar dari 1 meningkatkan laju ucapan
-
Nilai kurang dari 1 menurunkan laju ucapan
-
-
Contoh:
<speak rate="2"> Laju ucapan saya lebih cepat daripada normal. </speak>
pitch
String
Tidak
Menentukan pitch. Atribut ini memiliki prioritas lebih tinggi daripada parameter
pitch_ratedalam permintaan API.-
Nilai valid: angka desimal antara 0,5 dan 2 (inklusif)
-
Nilai default: 1
-
Nilai lebih besar dari 1 meningkatkan pitch
-
Nilai kurang dari 1 menurunkan pitch
-
-
Contoh:
<speak pitch="0.5"> Namun, pitch saya lebih rendah daripada orang lain. </speak>
volume
String
Tidak
Menentukan volume. Atribut ini memiliki prioritas lebih tinggi daripada parameter
volumedalam permintaan API.-
Nilai valid: bilangan bulat antara 0 dan 100 (inklusif)
-
Nilai default: 50
-
Nilai lebih besar dari 50 meningkatkan volume
-
Nilai kurang dari 50 menurunkan volume
-
-
Contoh:
<speak volume="80"> Volume saya juga sangat tinggi. </speak>
effect
String
Tidak
Menentukan efek audio.
-
Nilai valid:
-
robot: efek voice robot
-
lolita: efek voice lolita
-
lowpass: efek filter low-pass
-
echo: efek gema
-
eq: equalizer (lanjutan)
-
lpfilter: filter low-pass (lanjutan)
-
hpfilter: filter high-pass (lanjutan)
Catatan-
eq, lpfilter, dan hpfilter adalah jenis efek lanjutan. Gunakan parameter
effectValueuntuk menyesuaikan efek spesifik. -
Setiap tag SSML hanya mendukung satu efek. Mengatur beberapa atribut
effectsecara bersamaan tidak diizinkan. -
Mengaktifkan efek audio meningkatkan latensi sintesis.
-
-
Contoh:
<speak effect="robot"> Apakah Anda menyukai robot WALL-E? </speak>
effectValue
String
Tidak
Mengonfigurasi perilaku spesifik efek audio (parameter
effect). Berlaku untuk tiga jenis efek lanjutan: eq, lpfilter, dan hpfilter.-
Nilai valid:
-
eq(equalizer): sistem mendukung 8 pita frekuensi secara default, sesuai dengan frekuensi berikut:["40 Hz","100 Hz", "200 Hz", "400 Hz", "800 Hz", "1600 Hz", "4000 Hz", "12000 Hz"].
Setiap pita memiliki bandwidth 1,0q.
Gunakan parameter
effectValueuntuk menentukan gain untuk setiap pita. Parameter ini adalah string 8 bilangan bulat berkisar antara -20 hingga 20, dipisahkan spasi. Nilai0berarti tidak ada penyesuaian gain untuk frekuensi tersebut.Contoh:
effectValue="1 1 1 1 1 1 1 1" -
lpfilter(filter low-pass): menentukan frekuensi cutoff. Nilai valid: bilangan bulat dalam rentang (0, target_sample_rate/2]. Contoh: effectValue="800". -
hpfilter(filter high-pass): menentukan frekuensi cutoff. Nilai valid: bilangan bulat dalam rentang (0, target_sample_rate/2]. Contoh: effectValue="1200".
-
-
Contoh:
<speak effect="eq" effectValue="1 -20 1 1 1 1 20 1"> Apakah Anda menyukai robot WALL-E? </speak> <speak effect="lpfilter" effectValue="1200"> Apakah Anda menyukai robot WALL-E? </speak> <speak effect="hpfilter" effectValue="1200"> Apakah Anda menyukai robot WALL-E? </speak>
bgm
String
Tidak
Menambahkan musik latar ke ucapan yang disintesis. File audio harus disimpan di Alibaba Cloud OSS (lihat Unggah objek), dan bucket harus memiliki akses baca-publik minimal.
Jika URL musik latar berisi karakter khusus XML (seperti
&,<,>), escape karakter tersebut.-
Persyaratan audio:
Tidak ada batas atas ukuran file musik latar, tetapi file yang lebih besar memerlukan waktu unduh lebih lama. Jika ucapan yang disintesis lebih panjang daripada musik latar, musik akan diputar ulang secara otomatis.
-
Laju sampel: 16 kHz
-
Saluran: mono
-
Format: WAV
Untuk mengonversi file non-WAV, gunakan
ffmpeg:ffmpeg -i input_audio -acodec pcm_s16le -ac 1 -ar 16000 output.wav -
Kedalaman bit: 16-bit
-
-
Contoh:
<speak bgm="http://nls.alicdn.com/bgm/2.wav" backgroundMusicVolume="30" rate="-500" volume="40"> <break time="2s"/> Pohon-pohon tua di tebing yang teduh diselimuti kabut <break time="700ms"/> Suara hujan masih terdengar di hutan bambu <break time="700ms"/> Saya tahu bahwa kapas berkontribusi pada rencana negara <break time="700ms"/> Pemandangan Mianzhou selalu menyedihkan <break time="2s"/> </speak>
PentingAnda bertanggung jawab atas hak cipta audio apa pun yang diunggah.
backgroundMusicVolume
String
Tidak
Menentukan volume musik latar. Gunakan atribut ini bersama dengan atribut
bgm. -
-
Hubungan tag
Tag <speak> dapat berisi teks dan tag anak berikut:
-
Contoh lain
-
Tanpa atribut
<speak> Teks yang memerlukan tag SSML </speak> -
Atribut gabungan (dipisahkan spasi)
<speak rate="200" pitch="-100" volume="80"> Jadi ketika digabungkan, suara saya terdengar seperti ini. </speak>
-
<break>: kontrol durasi jeda
-
Deskripsi
Menyisipkan jeda diam selama sintesis ucapan untuk mensimulasikan jeda alami dalam percakapan. Mendukung satuan waktu detik (s) dan milidetik (ms).
-
Sintaks
# Tanpa atribut <break/> # Dengan atribut time <break time="string"/> -
Atribut
CatatanTag <break> tanpa atribut menjeda selama 1 detik secara default.
Atribut
Tipe
Wajib
Deskripsi
time
String
Tidak
Menentukan durasi jeda, dalam detik atau milidetik (misalnya, "2s" atau "50ms").
-
Nilai valid:
-
Dalam detik (s): bilangan bulat antara 1 dan 10 (inklusif)
-
Dalam milidetik (ms): bilangan bulat antara 50 dan 10000 (inklusif)
-
-
Contoh:
<speak> Silakan tutup mata Anda dan beristirahat.<break time="500ms"/>Baiklah, silakan buka mata Anda. </speak>
PentingSaat beberapa tag
<break>digunakan secara berurutan, total durasi jeda adalah jumlah semua durasi individual. Jika total melebihi 10 detik, hanya 10 detik pertama yang diterapkan.Misalnya, dalam SSML berikut, durasi kumulatif
<break>adalah 15 detik. Karena ini melebihi batas 10 detik, jeda aktual dipotong menjadi 10 detik:<speak> Silakan tutup mata Anda dan beristirahat.<break time="5s"/><break time="5s"/><break time="5s"/>Baiklah, silakan buka mata Anda. </speak> -
-
Hubungan tag
<break> adalah elemen self-closing dan tidak dapat berisi elemen anak.
<sub>: ganti teks
-
Deskripsi
Mengganti teks tertentu dengan konten yang lebih cocok untuk ucapan. Misalnya, membaca "W3C" sebagai "World Wide Web Consortium."
-
Sintaks
<sub alias="string"></sub> -
Atribut
Atribut
Type
Wajib
Deskripsi
alias
String
Ya
Menentukan teks pengganti yang akan dibaca keras.
Contoh:
<speak> <sub alias="protokol jaringan">W3C</sub> </speak> -
Hubungan tag
Tag <sub> hanya dapat berisi teks biasa.
<phoneme>: tentukan pelafalan (pinyin/fonetik)
-
Deskripsi
Memberikan kontrol tepat atas cara teks dilafalkan. Teks Tionghoa mendukung notasi pinyin, dan teks Inggris mendukung notasi fonetik CMU. Ini berguna untuk menghilangkan ambiguitas karakter polifonik dan menangani pelafalan bahasa asing.
-
Sintaks
<phoneme alphabet="string" ph="string">Teks</phoneme> -
Atribut
Atribut
Tipe
Wajib
Deskripsi
alphabet
String
Ya
Menentukan jenis pelafalan: pinyin (untuk Tionghoa) atau simbol fonetik (untuk Inggris).
Nilai valid:
-
"py": pinyin
-
"cmu": simbol fonetik. Lihat The CMU Pronouncing Dictionary
ph
String
Ya
Menentukan notasi pinyin atau fonetik yang tepat. Aturan penggunaan:
-
Pisahkan pinyin untuk beberapa karakter dengan spasi. Jumlah entri pinyin harus sesuai dengan jumlah karakter.
-
Setiap entri pinyin terdiri dari pelafalan dan nomor nada. Nomor nada berkisar dari
1hingga5, di mana5mewakili nada netral. -
Contoh:
<speak> Bagaimana cara mengeja <phoneme alphabet="cmu" ph="S AY N">sin</phoneme>? </speak>
-
-
Hubungan tag
Tag <phoneme> hanya dapat berisi teks biasa.
<soundEvent>: sisipkan suara eksternal (nada dering, suara kucing, dll.)
-
Deskripsi
Menyisipkan file efek suara (seperti nada peringatan atau suara latar) pada titik tertentu dalam ucapan untuk memperkaya output audio.
-
Sintaks
<soundEvent src="URL"/> -
Atribut
Atribut
Tipe
Wajib
Deskripsi
src
String
Ya
Menentukan URL file audio eksternal.
File audio harus disimpan di Alibaba Cloud OSS (lihat Unggah objek), dan bucket harus memiliki akses baca-publik minimal. Jika URL berisi karakter khusus XML (seperti
&,<,>), escape karakter tersebut.-
Persyaratan audio:
-
Laju sampel: 16 kHz
-
Saluran: mono
-
Format: WAV
Untuk mengonversi file non-WAV, gunakan
ffmpeg:ffmpeg -i input_audio -acodec pcm_s16le -ac 1 -ar 16000 output.wav
-
Ukuran file: maksimum 2 MB
-
Kedalaman bit: 16-bit
-
-
Contoh:
<speak> Seekor kuda ketakutan<soundEvent src="http://nls.alicdn.com/sound-event/horse-neigh.wav"/>dan orang-orang tersebar untuk menghindarinya. </speak>
PentingAnda bertanggung jawab secara hukum atas hak cipta audio yang diunggah.
-
-
Hubungan tag
<soundEvent> adalah elemen self-closing dan tidak dapat berisi elemen anak.
<say-as>: atur interpretasi teks (angka, tanggal, nomor telepon, dll.)
-
Deskripsi
Menentukan jenis konten teks (seperti angka, tanggal, atau nomor telepon) sehingga sistem membacanya sesuai aturan yang sesuai untuk jenis tersebut.
-
Sintaks
<say-as interpret-as="string">Teks</say-as> -
Atribut
Atribut
Tipe
Wajib
Deskripsi
interpret-as
String
Ya
Menentukan jenis konten teks di dalam tag.
Nilai valid:
-
cardinal: dibaca sebagai bilangan bulat atau desimal standar
-
digits: membaca setiap digit secara individual (misalnya, 123 dibaca sebagai "satu dua tiga")
-
telephone: membaca digit demi digit dalam format nomor telepon standar
-
name: membaca menggunakan aturan pelafalan nama standar
-
address: membaca menggunakan aturan pelafalan alamat standar
-
id: membaca menggunakan aturan pelafalan pengenal (nama akun, nama panggilan) standar
-
characters: membaca setiap karakter dalam teks secara individual
-
punctuation: membaca nama setiap tanda baca
-
date: membaca menggunakan aturan pelafalan tanggal standar
-
time: membaca menggunakan aturan pelafalan waktu standar
-
currency: membaca menggunakan aturan pelafalan jumlah moneter standar
-
measure: membaca menggunakan aturan pelafalan satuan ukuran standar
-
-
Rentang yang didukung untuk setiap jenis <say-as>
-
cardinal
Format
Contoh
English output
Catatan
String digit
145
one hundred forty five
Rentang integer: bilangan bulat positif dan negatif hingga 13 digit, [-999999999999,999999999999].
Rentang desimal: tidak ada batas spesifik tempat desimal, tetapi disarankan 10 atau kurang.
String digit diawali nol
0145
one hundred forty five
Tanda minus + string digit
-145
minus hundred forty five
String digit dengan koma memisahkan setiap 3 digit
60,000
sixty thousand
Tanda minus + string digit dipisahkan koma
-208,000
minus two hundred eight thousand
String digit + titik desimal + nol
12.00
twelve
String digit + titik desimal + string digit
12.34
twelve point three four
String digit dipisahkan koma + titik desimal + string digit
1,000.1
one thousand point one
Tanda minus + string digit + titik desimal + string digit
-12.34
minus twelve point three four
Tanda minus + string digit dipisahkan koma + titik desimal + string digit
-1,000.1
minus one thousand point one
(String digit dipisahkan koma) + tanda hubung + (string digit dipisahkan koma)
1-1,000
one to one thousand
Pembacaan default lainnya
012.34
twelve point three four
Tidak ada
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
Contoh
Output Inggris
Catatan
String digit
12034
one two zero three four
Tidak ada batas spesifik panjang string digit, tetapi disarankan 20 digit atau kurang.
Saat string digit dikelompokkan dengan spasi atau tanda hubung, jeda koma disisipkan antar grup. Maksimal 5 grup didukung.
String digit + spasi/tanda hubung + string digit + spasi/tanda hubung + string digit + spasi/tanda hubung + string digit
1-23-456 7890
one, two three, four five six, seven eight nine zero
-
telephone
Format
Contoh
Output Inggris
Catatan
String digit
12034
one two oh three four
Tidak ada batas spesifik panjang string digit, tetapi disarankan 20 digit atau kurang.Saat string digit dikelompokkan dengan spasi atau tanda hubung, jeda koma disisipkan antar grup. Maksimal 5 grup didukung.
String digit + spasi/tanda hubung + string digit + spasi/tanda hubung + string digit
1-23-456 7890
one, two three, four five six, seven eight nine oh
Tanda plus + string digit + spasi/tanda hubung + string digit
+43-211-0567
plus four three, two one, oh five six seven
Tanda kurung kiri + string digit + tanda kurung kanan + spasi + string digit + spasi/tanda hubung + string digit
(21) 654-3210
(two one) six five four, three two one oh
-
address
Tag ini tidak didukung untuk teks Inggris.
-
id
Untuk teks Inggris, tag ini berfungsi sama seperti tag characters.
-
characters
Format
Contoh
Output Inggris
Catatan
String
*b+3$.c-0'=α
asterisk B plus three dollar dot C dash zero apostrophe equals alpha
Mendukung karakter Tionghoa, huruf besar dan kecil, digit 0-9, dan beberapa karakter full-width dan half-width.
Spasi dalam output menunjukkan jeda antar karakter, artinya setiap karakter dibaca secara individual.
Jika teks di dalam tag berisi karakter khusus XML, escape karakter tersebut.
-
punctuation
Untuk teks Inggris, tag ini berfungsi sama seperti tag characters.
-
date
Format
Contoh
Output Inggris
Catatan
Empat digit/dua digit atau empat digit-dua digit
2000/01
two thousand, oh one
Mencakup beberapa tahun.
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
Bilangan empat digit diawali 1 atau 2
2000
two thousand
Tahun 4 digit.
1900
nineteen hundred
1905
nineteen oh five
2021
twenty one
Hari - hari dalam minggu (tanda hubung)
atau
Hari - hari dalam minggu
atau
Hari & hari dalam minggu
mon-wed
monday to wednesday
Jika teks dalam tag rentang hari dalam minggu berisi karakter XML khusus, escape karakter tersebut.
tue~fri
tuesday to friday
sat&sun
saturday and sunday
DD-DD MMM, YYYY
atau
DD~DD MMM, YYYY
atau
DD&DD MMM, YYYY
19-20 Jan, 2000
the nineteen to the twentieth of january two thousand
DD: Hari 2 digit. MMM: Singkatan bulan 3 huruf atau kata lengkap. YYYY: Tahun 4 digit diawali 1 atau 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
atau
MMM DD~DD
atau
MMM DD&DD
Feb 01 - 03
feburary the first to the third
MMM: Singkatan bulan 3 huruf atau kata lengkap. DD: Hari 2 digit.
Aug 10~20
august the tenth to the twentieth
Dec 11&12
december the eleventh and the twelfth
MMM-MMM
atau
MMM~MMM
atau
MMM&MMM
Jan-Jun
january to june
MMM: Singkatan bulan 3 huruf atau kata lengkap.
jul ~ dec
july to december
sep&oct
september and october
YYYY-YYYY
atau
YYYY~YYYY
1990 - 2000
nineteen ninety to two thousand
YYYY: Tahun 4 digit diawali 1 atau 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 adalah singkatan 3 huruf atau nama lengkap hari dalam minggu. DD adalah hari 2 digit. MMM adalah singkatan 3 huruf atau nama lengkap bulan. MM adalah bulan 2 digit (atau singkatan 3 huruf atau nama lengkap bulan). YYYY adalah tahun 4 digit diawali 1 atau 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
Pembacaan default lainnya
10 Mar, 2001
the tenth of march two thousand one
Tidak ada
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
Contoh
Output Inggris
Catatan
HH:MM AM atau PM
09:00 AM
nine A M
HH: Jam 1 atau 2 digit. MM: Menit 2 digit. AM/PM: pagi/sore.
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
Titik waktu-Titik waktu
8:00 am - 05:30 pm
eight a m to five p m
Mendukung format waktu umum dan rentang.
7:05~10:15 AM
seven oh five to ten fifteen A M
09:00-13:00
nine oclock to thirteen hundred
-
currency
Format
Contoh
Output Inggris
Catatan
Angka + identifikasi mata uang
1.00 RMB
one yuan
Format angka yang didukung: bilangan bulat, desimal, dan notasi internasional dipisahkan koma.
Identifikasi mata uang yang didukung:
CN¥ (yuan)
CNY (yuan)
RMB (yuan)
AUD (dolar Australia)
CAD (dolar Kanada)
CHF (franc Swiss)
DKK (krone Denmark)
EUR (euro)
GBP (pound sterling Inggris)
HKD (dolar Hong Kong(China))
JPY (yen Jepang)
NOK (krone Norwegia)
SEK (krona Swedia)
SGD (dolar Singapura)
USD (dolar Amerika Serikat)
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
Identifikasi mata uang + angka
US$ 1.00
one US dollar
Format angka yang didukung: bilangan bulat, desimal, dan notasi internasional dipisahkan koma.
Identifikasi mata uang yang didukung:
US$ (dolar AS)
CA$ (dolar Kanada)
AU$ (dolar Australia)
SG$ (dolar Singapura)
HK$ (dolar Hong Kong)
C$ (dolar Kanada)
A$ (dolar Australia)
$ (dolar)
£ (pound)
€ (euro)
CN¥ (yuan)
CNY (yuan)
RMB (yuan)
AUD (dolar Australia)
CAD (dolar Kanada)
CHF (franc Swiss)
DKK (krone Denmark)
EUR (euro)
GBP (pound sterling Inggris)
HKD (dolar Hong Kong(China))
JPY (yen Jepang)
NOK (krone Norwegia)
SEK (krona Swedia)
SGD (dolar Singapura)
USD (dolar Amerika Serikat)
$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
Angka + klasifikasi + identifikasi mata uang
atau
Identifikasi mata uang + angka+Kuantifier
1.23 Tn RMB
one point two three trillion yuan
Format klasifikasi yang didukung:
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
Contoh
Output Inggris
Catatan
Angka + satuan ukuran
1.0 kg
one kilogram
Format angka yang didukung: bilangan bulat, desimal, dan notasi internasional dipisahkan koma.
Mendukung singkatan satuan umum.
1,234.01 km
one thousand two hundred thirty four point zero one kilometres.
Satuan ukuran
mm2
square millimetre
-
Tabel berikut menunjukkan cara simbol umum dibaca dengan <say-as>.
Simbol
Pelafalan Inggris
!
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
-
Tabel berikut menunjukkan cara satuan ukuran umum dibaca dengan <say-as>.
Format
Kategori
Contoh Inggris
Singkatan
Panjang
nm (nanometre), μm (micrometre), mm (millimetre), cm (centimetre), m (metre), km (kilometre), ft (foot), in (inch)
Wilayah
cm² (square centimetre), ㎡ (square metre), km2 (square kilometre), SqFt (square foot)
Volume
cm³ (cubic centimetre), m³ (cubic metre), km3 (cubic kilometre), mL (millilitre), L (millilitre), gal (gallon)
Berat
μg (microgram), mg (microgram), g (gram), kg (kilogram)
Waktu
min (minute), sec (second), ms (millisecond)
Elektromagnetisme
μA (microamp), mA (milliamp), Hz (hertz), kHz (kilohertz), MHz (megahertz), GHz (gigahertz), V (volt), kV (kilovolt), kWh (kilowatt hour)
Suara
dB (decibel)
Tekanan atmosfer
Pa (pascal), kPa (kilopascal), MPa (megapascal)
Satuan umum lainnya
Mendukung satuan di luar yang tercantum di atas, seperti tsp (teaspoon), rpm (revolutions per minute), KB (kilobyte), dan mmHg (millimetre of mercury).
-
-
Hubungan tag
Tag <say-as> dapat berisi teks dan <vhml/>.
-
Contoh
-
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> Nama sebelumnya adalah <say-as interpret-as="name">Zeng Xiaofan</say-as> </speak> -
address
<speak> <say-as interpret-as="address">Fulu International, Gedung 1, Unit 3, Kamar 304</say-as> </speak> -
id
<speak> <say-as interpret-as="id">myid_1998</say-as> </speak> -
characters
<speak> <say-as interpret-as="characters">Huruf Yunani αβ</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>
-