Subtitle Service API Protocol
1. Overview
This document targets third-party integration and describes the HTTP endpoints of the subtitle service. Developers can implement their own backend against it. The service covers two capabilities:
| Capability | Purpose | Endpoints |
|---|---|---|
| Subtitle translation | Translate subtitles into another language | GET /api/languages · POST /api/translate |
| Speech synthesis (TTS) | Subtitles → strictly time-aligned dubbing audio | POST /api/tts/conversions · GET /api/tts/conversions/{id} · GET /api/tts/conversions/{id}/audio · DELETE /api/tts/conversions/{id} |
The upstream engine is your choice: translation can use any LLM (e.g. Alibaba Cloud DashScope
qwen-mt-flash, DeepSeek, etc.); TTS can use any speech synthesis engine (e.g.cosyvoice-v3-flash, Edge-TTS, Azure, etc.), and whether voice cloning is supported depends on the chosen engine. This document only specifies the interface protocol, not the implementation.
Translation is a synchronous, blocking endpoint; TTS is an asynchronous task (submit then poll).
2. Common Conventions
2.1 Base URL
- Direct connection:
http(s)://<host>:<port> - Behind a reverse proxy: may carry a path prefix, e.g.
https://<host>/subtitle-api
The examples below assume http://localhost:3000; replace it with your actual deployment. The port is controlled by PORT / HTTPS_PORT in the server-side .env.
2.2 Encoding & Format
- Requests and responses are UTF-8.
- Endpoints with file uploads (translation, TTS submit) use
multipart/form-data. - Query endpoints use
application/json. - Translation JSON responses share a unified structure: success is
{ "success": true, "data": ... }, failure is{ "success": false, "error": "..." }. - TTS endpoints use a separate response format (see §6).
2.3 Authentication
When the server enables authentication ([auth] enabled = true), all /api/* routes are protected except GET /health. Passing either of the following grants access:
| Method | Description |
|---|---|
| IP allowlist | The client’s real IP is in the server’s [auth] allowed_ips list |
| Header token | The request carries a non-empty X-ZWPlayer header (any non-empty value works) |
# When the client IP is not in the allowlist, attach:
-H "X-ZWPlayer: <your-token>"
When authentication fails the server returns 401:
{ "success": false, "error": "未授权:IP 不在白名单且 API Key 无效" }
3. Subtitle Translation — Supported Target Languages
There are 13 target languages, each with a stable code (ISO 639-1) that serves as the contract value submitted to /api/translate. The display name changes with the UI language, but the code is constant.
| code | Chinese name | Native name |
|---|---|---|
zh |
中文 | 中文 |
en |
英语 | English |
ja |
日语 | 日本語 |
ko |
韩语 | 한국어 |
fr |
法语 | Français |
de |
德语 | Deutsch |
es |
西班牙语 | Español |
ru |
俄语 | Русский |
pt |
葡萄牙语 | Português |
it |
意大利语 | Italiano |
ar |
阿拉伯语 | العربية |
th |
泰语 | ไทย |
vi |
越南语 | Tiếng Việt |
The
targetLangsubmitted to/api/translatemust be thecodein the table above (e.g.en), not a display name (e.g. “English” or “英语”). Submitting a legacy Chinese name will be rejected.
4. Subtitle Translation GET /api/languages
Fetch the list of supported target languages, optionally localized by UI language.
4.1 Request
| Param | Location | Required | Description |
|---|---|---|---|
lang |
query | no | UI language code; controls the display language of name. Accepts the 13 codes above. Falls back to zh when omitted or unknown. Case-insensitive. |
4.2 Response
data is an array whose items look like { "code": string, "name": string }:
code— the stable language code, the contract value for/api/translate’stargetLangname— the display name under the UI language specified bylang
Example: GET /api/languages?lang=zh (default)
{
"success": true,
"data": [
{ "code": "zh", "name": "中文" },
{ "code": "en", "name": "英语" },
{ "code": "ja", "name": "日语" },
{ "code": "ko", "name": "韩语" },
{ "code": "fr", "name": "法语" },
{ "code": "de", "name": "德语" },
{ "code": "es", "name": "西班牙语" },
{ "code": "ru", "name": "俄语" },
{ "code": "pt", "name": "葡萄牙语" },
{ "code": "it", "name": "意大利语" },
{ "code": "ar", "name": "阿拉伯语" },
{ "code": "th", "name": "泰语" },
{ "code": "vi", "name": "越南语" }
]
}
4.3 Behavior
| Scenario | Result |
|---|---|
Missing / empty lang |
Equivalent to lang=zh |
Known code (ko, ja, ru …) |
Returns localized names for that UI language |
Uppercase (KO, En) |
Normalized to lowercase, then looked up |
Unknown code (xx) |
That item’s name falls back to Chinese (names.zh) |
5. Subtitle Translation POST /api/translate
Upload a .srt / .vtt subtitle file and return the translated subtitle content.
5.1 Request
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
file |
File | yes | Subtitle file (.srt or .vtt, max 10MB) |
targetLang |
string | yes | Target language code, e.g. en, zh, ja (taken from the code of /api/languages); case-insensitive |
apiKey |
string | no | Translation API key; if omitted, the server-side DEFAULT_API_KEY is used; if neither is present a 500 is returned |
model |
string | no | Translation model; if omitted, the server-side DEFAULT_MODEL is used (default qwen-mt-flash) |
batchSize |
number | no | Subtitles per batch, range 1–50, default 20; clamped into range when out of bounds |
outputFormat |
string | no | Output format: vtt (default) or srt; anything other than srt is treated as vtt |
5.2 Success Response
{
"success": true,
"data": {
"content": "WEBVTT\n\n1\n00:00:01.000 --> 00:00:03.000\nHello World\n\n",
"format": "vtt",
"totalSubtitles": 120,
"filename": "movie.translated.vtt"
}
}
| Field | Description |
|---|---|
content |
The full translated subtitle text |
format |
Output format: vtt or srt |
totalSubtitles |
Number of subtitle entries |
filename |
Suggested download filename (<original>.translated.<ext>) |
5.3 Status Codes
| HTTP status | Trigger |
|---|---|
200 |
Translation succeeded |
400 |
No file uploaded; empty targetLang; targetLang is not a valid code (including legacy Chinese names) |
422 |
Subtitle parsing failed, or parsed content is empty |
500 |
Server has no DEFAULT_API_KEY configured and the request carries no apiKey |
502 |
Upstream translation API call failed |
For an invalid targetLang, the error message lists all valid codes so the client can surface a helpful prompt, e.g.:
{
"success": false,
"error": "不支持的目标语言代码 \"英语\",有效代码:zh、en、ja、ko、fr、de、es、ru、pt、it、ar、th、vi"
}
6. Submit TTS Conversion POST /api/tts/conversions
Upload a .srt / .vtt subtitle file and convert it into strictly time-aligned M4A audio. The task runs asynchronously and returns a job_id immediately.
You may optionally upload a reference audio clip to trigger voice cloning, or pass an existing voice_id to reuse a voice. The generated dubbing audio can be used as an external audio track in ZWPlayer (see Audio Tracks).
9.1 Request
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
subtitle |
File | yes | Subtitle file, .srt or .vtt |
audio |
File | no | Reference audio (WAV/MP3/M4A, recommended 10–20s, ≤ 10 MB); triggers voice cloning |
voice_id |
string | no | Existing voice ID. When provided, cloning is skipped; takes priority over audio |
language |
string | no | Target synthesis language; overrides the server default |
Voice priority:
voice_id>audio> server defaultvoice
language values
zh en ja ko fr de ru pt th id vi
9.2 Success Response
202 Accepted
{
"job_id": "5233b92a98ef49f68452edfade00d6f4"
}
9.3 Errors
| HTTP status | Trigger |
|---|---|
401 |
Authentication failed |
413 |
Multipart body too large (~10 MB) |
422 |
Missing subtitle / illegal extension / audio over 10 MB / language not in the allowed list |
7. Query TTS Task GET /api/tts/conversions/{id}
Query the current status of a TTS conversion task.
10.1 Success Response
200 OK
{
"id": "5233b92a98ef49f68452edfade00d6f4",
"status": "completed",
"created_at": 1783309806.19,
"started_at": 1783309806.20,
"finished_at": 1783309807.18,
"voice_id": "cosyvoice-v3-flash_gf_abc123",
"model_used": "cosyvoice-v3-flash",
"error": "",
"params": {
"subtitle_filename": "subtitle.srt",
"audio_filename": "voice_sample.wav",
"requested_voice_id": "",
"requested_language": "zh"
}
}
10.2 status State Machine
queued → parsing ─┬─→ cloning_voice → synthesizing → aligning → mixing → completed
└─→ synthesizing → aligning → mixing → completed
(cloning_voice is skipped when no audio is uploaded)
Any stage failure → failed
DELETE → canceled + working directory removed
| status | Meaning | Terminal |
|---|---|---|
queued |
Enqueued | no |
parsing |
Parsing subtitles | no |
cloning_voice |
Cloning voice | no |
synthesizing |
Concurrent TTS | no |
aligning |
Aligning (atempo / apad) | no |
mixing |
Concatenating + AAC encoding | no |
completed |
Done, ready to download | yes |
failed |
Failed; reason in error |
yes |
canceled |
Canceled by DELETE | yes |
Poll every 1–2 seconds and stop at a terminal status (
completed/failed/canceled).
8. Download TTS Audio GET /api/tts/conversions/{id}/audio
Download the M4A audio of a completed task.
11.1 Success Response
200 OK
Content-Type: audio/mp4Content-Disposition: attachment; filename="{id}.m4a"- Body: binary AAC audio
11.2 Errors
| HTTP status | Trigger |
|---|---|
404 |
job_id not found / output file missing |
409 |
Task not completed (status is not completed) |
9. Cancel TTS Task DELETE /api/tts/conversions/{id}
Cancel a task (if still running) and delete its working directory and all intermediate artifacts. Irreversible.
12.1 Success Response
204 No Content (empty body)
12.2 Errors
| HTTP status | Trigger |
|---|---|
404 |
job_id not found |
10. Error Code Quick Reference
| HTTP status | Meaning | Common cause |
|---|---|---|
400 |
Bad request | Missing required field, unsupported format, illegal language code |
401 |
Unauthorized | No X-ZWPlayer and IP not in allowlist |
404 |
Not found | TTS task ID not found / file missing |
409 |
Conflict | Downloading audio before the TTS task completes |
413 |
Payload too large | Over upload limits (subtitle 10 MB / TTS reference 10 MB) |
422 |
Parse failed | Malformed subtitle, illegal extension, TTS language not allowed |
500 |
Server config missing | No public_base_url / API key configured |
502 |
Upstream failure | DashScope / translation API error |
11. Connecting to ZWPlayer
ZWPlayer points its subtitle service via the translateApi initialization option. It expects the base URL of the service (the common prefix shared by all endpoints); the player automatically appends each endpoint to it:
const player = new ZWPlayer({
url: 'http://example.com/vod/movie.mp4',
playerElm: '#player-holder',
translateApi: 'https://your-server.com/subtitle-api/api'
});
Once configured, two entries automatically appear in the player’s subtitle menu (CC menu) — both shown only when translateApi is set. See Subtitle Setup for full usage of the subtitle menu.
11.1 Subtitle Translation Integration
The player actually requests (base URL + endpoint):
GET https://your-server.com/subtitle-api/api/languages?lang=<UI-lang>POST https://your-server.com/subtitle-api/api/translate
User flow: pick a target language → translate the current subtitle and load it as a subtitle track.
11.2 Subtitle to Speech (TTS) Integration
The player actually requests (base URL + endpoint):
| When | Method | Endpoint | Purpose |
|---|---|---|---|
| Submit | POST |
/tts/conversions |
Upload subtitle + optional reference audio; returns job_id |
| Poll status | GET |
/tts/conversions/{job_id} |
Queried every 1.5s until a terminal status |
| Download audio | GET |
/tts/conversions/{job_id}/audio |
Download M4A after completed |
| Cancel | DELETE |
/tts/conversions/{job_id} |
Called when the user clicks “Cancel” |
FormData fields of the submit request
The player submits with multipart/form-data. Fields:
| Field | Always sent | Content |
|---|---|---|
subtitle |
yes | The current subtitle serialized as SRT text; filename fixed to subtitle.srt, MIME text/plain |
language |
yes | Target voice language code (e.g. zh/en/ja), from the panel dropdown |
audio |
only when the user selected a reference audio | Reference audio file (WAV/MP3/M4A, ≤10MB), for voice cloning |
The SRT serialization is done inside the player (millisecond-precision timestamps
HH:MM:SS,mmm); the server does not need to handle subtitle format conversion.
Client state machine and polling behavior
After submission the player polls and shows a stage message based on the server-returned status. Mapping of server status to user-visible text:
Server status |
User-visible prompt |
|---|---|
queued |
Queued |
parsing |
Parsing subtitle |
cloning_voice |
Cloning voice (only when a reference audio was uploaded) |
synthesizing |
Synthesizing |
aligning |
Aligning |
mixing |
Mixing & encoding |
completed |
✅ Synthesis complete (triggers audio download and track mounting) |
failed |
Synthesis failed (shows the error field) |
canceled |
Canceled |
The player stops polling on any terminal status (completed / failed / canceled). The server only needs to return one of the above status values in the GET /tts/conversions/{id} response.
Audio download and mounting
After completed, the player downloads the audio as a Blob via fetch (with the X-ZWPlayer header), generates a blob URL, then calls the internal addAudioTrack to mount it as an external audio track and automatically switches playback to it.
Why fetch instead of
<audio src>: the TTS audio endpoint requires theX-ZWPlayerauth header, but an HTML<audio>element cannot attach custom request headers. Therefore the audio must first be fetched as a Blob and then turned into a same-origin blob URL viaURL.createObjectURL.
TTS server implementation checklist
When implementing a TTS backend, satisfy these contracts:
POST /tts/conversions: acceptmultipart/form-data(subtitle+language+ optionalaudio); return202with{ "job_id": "<task-id>" }. Thesubtitlefield is an SRT text file.GET /tts/conversions/{id}: return the current task status as JSON containing astatusfield (values listed above). Includeerroronfailed;voice_idis recommended oncompletedfor debugging.GET /tts/conversions/{id}/audio: return M4A audio aftercompleted(Content-Type: audio/mp4); return409if not yet completed.DELETE /tts/conversions/{id}: cancel the task; return204.- Audio time alignment: the synthesized audio must be strictly aligned with the subtitle timeline (each cue’s start/end times must match), otherwise the player’s sync will drift. Servers typically use atempo/apad for time stretching.
Any server satisfying the request/response structure above can be used by ZWPlayer’s TTS feature. The synthesis engine (CosyVoice, Edge-TTS, Azure, etc.) is the server’s choice — the player does not care.
11.3 Protocol Compatibility Note
This document describes the target protocol specification (translation uses ISO language codes). When implementing a server, note that the actual targetLang value sent by a ZWPlayer client depends on the client version:
- The target protocol requires
targetLangto be a language code (e.g.en,zh), with the selectable list obtained viaGET /api/languages. - To keep your server robust against all clients, it is recommended that the language validation in
/api/translateaccept both codes and display names — recognizingenas well as legacy inputs like英语/English, and returning a clear error message otherwise (see §5.3).
A server that satisfies the request/response structure in this document can be called by ZWPlayer.