API đọc văn bản tiếng Việt

API chuyển văn bản tiếng Việt thành giọng nói, trả audio ngay trong lúc sinh. Dùng được theo ba cách:

Tài liệu này dành cho người tích hợp. Phần vận hành server xem README.md và DEPLOY.md.

Trong các ví dụ, $BASE là địa chỉ server (ví dụ https://tts.example.com) và $KEY là API key.

Bắt đầu nhanh

curl $BASE/v1/audio/speech -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
     -d '{"input": "Xin chào, tôi là trợ lý ảo."}' -o chao.mp3
from mon_ai import Client            # pip install ./clients/python
client = Client(api_key=KEY, base_url=BASE)
open("chao.mp3", "wb").write(client.speech("Xin chào!"))
import { Client } from "@mon-ai/sdk";    // clients/js
const client = new Client({ apiKey: KEY, baseUrl: BASE });
const mp3 = await client.speech("Xin chào!");

Thư viện openai cũng gọi được, chỉ cần đổi base_url:

from openai import OpenAI
client = OpenAI(api_key=KEY, base_url=f"{BASE}/v1")
client.audio.speech.create(model="tts-1", voice="alloy", input="Xin chào!").write_to_file("chao.mp3")

Xác thực và giới hạn

Đọc văn bản qua HTTP: POST /v1/audio/speech

Body JSON giống OpenAI, thêm sample_rate:

Trường Kiểu Mặc định
input chuỗi (bắt buộc) văn bản, tối đa 5000 ký tự
voice chuỗi giọng mặc định tên trong GET /v1/voices, hoặc id giọng riêng (v_…). Tên giọng của OpenAI (alloy, nova…) được hiểu là giọng mặc định
response_format chuỗi mp3 mp3, opus (Ogg), aac (ADTS), flac, wav, pcm (16-bit little-endian, mono), mulaw (G.711 µ-law)
sample_rate số 24000 (mulaw: 8000) 24000, 16000 hoặc 8000
speed số 1.0 0,5–2,0; đổi tốc độ, giữ nguyên cao độ
model chuỗi không bắt buộc: tts-1, tts-1-hd, gpt-4o-mini-tts, vi-pocket đều là cùng một model

Audio được gửi dần ngay khi sinh (chunked):

Header trả về:

File wav khi stream có header ghi độ dài "không xác định". Đa số trình phát vẫn đọc được; SDK tự sửa header khi tải trọn file.

Văn bản được chuẩn hoá trước khi đọc. Không cần tự viết số, ngày, giờ, tiền, số điện thoại, đơn vị, từ tiếng Anh hay công thức ra chữ: 25-30°C, 2.500.000đ, 14h30, H2SO4, Vinhomes Grand Park đều được đọc đúng.

Độ trễ thấp nhất và mốc theo câu: WebSocket /v1/audio/stream

Mở wss://…/v1/audio/stream với header Authorization: Bearer <key>, hoặc với ?api_key=<key>. Sau đó gửi một văn bản dưới dạng JSON (text frame):

{"text": "Chào mừng quý vị. Chúc một ngày tốt lành!", "format": "pcm", "sample_rate": 24000, "speed": 1.0}

format là pcm (mặc định) hoặc mulaw; các trường khác giống HTTP. Server gửi lại các message sau:

Message Ý nghĩa
frame nhị phân audio (pcm: 16-bit little-endian mono; mulaw: 1 byte mỗi mẫu)
{"type": "segment", "index": 0, "text": "Chào mừng quý vị.", "start": 0.0} đến trước audio của một đoạn: một hoặc vài câu nguyên vẹn của văn bản, đúng như bạn viết. start là số giây tính từ đầu audio của văn bản này.
{"type": "done", "audio_sec": 4.2, "ttfa_ms": 41, "chars": 41, "cancelled": false} hết văn bản: số giây audio, thời gian tới byte đầu (phía server), số ký tự đã tính, có bị huỷ không
{"type": "error", "message": "...", "code": "insufficient_quota"} văn bản không đọc được; kết nối vẫn mở

Giọng đọc

Lồng tiếng từ phụ đề: /v1/dubs

Đọc từng câu của file phụ đề tiếng Việt đúng thời điểm của nó, rồi trộn vào video hay âm thanh gốc. Việc chạy ở nền: gửi lên, rồi xem tiến độ và tải kết quả.

curl $BASE/v1/dubs -H "Authorization: Bearer $KEY" \
     -F subtitles=@tap1.srt -F media=@tap1.mp4 -F mode=voiceover -F name="Tập 1"
# {"id": "d_8f2k…", "status": "queued", "chars": 12840, ...}
curl $BASE/v1/dubs/d_8f2k… -H "Authorization: Bearer $KEY"          # status, done / total
curl "$BASE/v1/dubs/d_8f2k…/files/video?download=1" -H "Authorization: Bearer $KEY" -o tap1-long-tieng.mp4

Trường của POST /v1/dubs (multipart):

Trường Mặc định Ý nghĩa
subtitles file SRT hoặc WebVTT (UTF-8, UTF-16 hoặc Windows-1258), tối đa 2 MB
lines thay cho subtitles: JSON [{"start": 1.5, "end": 3.2, "text": "...", "voice": null}] (giây; voice của riêng câu đó)
media không có video hoặc âm thanh gốc; không có thì chỉ xuất giọng đọc theo thời gian phụ đề
mode voiceover voiceover: thuyết minh, giữ âm thanh gốc và hạ nhỏ khi giọng đọc nói; replace: bỏ âm thanh gốc
background không có với replace: track nhạc nền và tiếng động (không có lời) để trộn vào
duck_db −15 (voiceover), −6 (replace) âm thanh gốc hay track nền nhỏ đi bao nhiêu dB khi giọng đọc nói, −40 đến 0
voice giọng mặc định giọng chung của các câu
speed 1.0 tốc độ đọc mọi câu, 0.5–2
max_speed 1.3 câu dài hơn khoảng trống tới câu sau được đọc nhanh dần, tối đa tới mức này; quá nữa thì câu sau chờ
name tên file tên dự án

Các endpoint khác:

Nội dung lồng tiếng là do AI tạo. Khi đăng tải, khách hàng chịu trách nhiệm ghi nhãn theo Luật Trí tuệ nhân tạo, và phải có quyền với video gốc.

Lượng dùng: GET /v1/usage

?start=YYYY-MM-DD&end=YYYY-MM-DD; mặc định là tháng này.

{"customer": "acme",
 "quota": {"month": "2026-10", "monthly_chars": 1000000, "used_chars": 37408, "remaining_chars": 962592,
           "resets_at": "2026-11-01T00:00:00+07:00", "max_streams": 2, "running": 0},
 "period": {"start": "2026-10-01", "end": "2026-10-10"},
 "total": {"requests": 329, "chars": 37408, "audio_sec": 1807.3, "cancelled": 8, "errors": 0},
 "daily": [{"date": "2026-10-09", "requests": 281, "chars": 34645, "audio_sec": 1637.6, "cancelled": 4, "errors": 0}],
 "by_key": [...]}

GET /health không cần key: {"status": "ok", ...}, hoặc 503 khi server chưa sẵn sàng.

Lỗi

Mọi lỗi HTTP có dạng như của OpenAI: {"error": {"message": "...", "type": "...", "code": "..."}}.

HTTP type / code Khi nào
400 invalid_request_error văn bản trống hoặc quá dài, sai giọng (kèm danh sách voices), sai định dạng hay tần số mẫu; no_voice khi server chưa mở giọng nào; với giọng riêng còn có consent_required, bad_recording, file_too_large, voice_limit; với lồng tiếng: bad_subtitles, bad_dub, dub_limit
401 authentication_error thiếu key, sai key, key đã thu hồi
403 permission_error / account_disabled tài khoản bị khoá
404 giọng riêng hay dự án lồng tiếng không có hoặc không thuộc tài khoản
409 dub_running xuất lại một dự án đang chạy: huỷ trước
413 file_too_large file lồng tiếng lớn hơn giới hạn của server
429 insufficient_quota hết hạn mức tháng
429 rate_limit_error / concurrency_limit quá số request cùng lúc của tài khoản
429 rate_limit_error server đầy, thử lại sau
500 server_error lỗi phía server

Riêng WebSocket: key sai hoặc tài khoản bị khoá thì kết nối bị từ chối ngay khi mở (HTTP 403). Các lỗi khác đến dưới dạng message error.

SDK và tích hợp

Cài Dùng
Python pip install ./clients/python Client / AsyncClient: speech, speech_stream, stream (WebSocket: bytes, Segment, Done), connect() để giữ kết nối, voices, create_voice, usage. clients/python
JavaScript (trình duyệt, Node 18+) clients/js Client: speech, speechStream, stream, connect(), voices, createVoice, usage. clients/js
LiveKit Agents pip install "./clients/python[livekit]" AgentSession(tts=mon_ai.livekit.TTS())
Pipecat pip install "./clients/python[pipecat]" MonTTSService(settings=MonTTSService.Settings(speed=1.0)) trong pipeline
Thư viện OpenAI có sẵn openai (Python), plugin OpenAI của LiveKit (base_url=f"{BASE}/v1"); OpenAITTSService của Pipecat chỉ nhận tên giọng OpenAI, khi đó được giọng mặc định

Với voice agent, LLM trả lời theo kiểu stream. LiveKit và Pipecat tự gom chữ thành từng câu, rồi gửi mỗi câu là một request: câu đầu có tiếng sau khoảng 50 ms. Với tổng đài, dùng sample_rate 8000, hoặc mulaw qua WebSocket, để khỏi phải đổi tần số mẫu.