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 targetLang submitted to /api/translate must be the code in 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’s targetLang
  • name — the display name under the UI language specified by lang

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 default voice

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/mp4
  • Content-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 the X-ZWPlayer auth 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 via URL.createObjectURL.

TTS server implementation checklist

When implementing a TTS backend, satisfy these contracts:

  1. POST /tts/conversions: accept multipart/form-data (subtitle + language + optional audio); return 202 with { "job_id": "<task-id>" }. The subtitle field is an SRT text file.
  2. GET /tts/conversions/{id}: return the current task status as JSON containing a status field (values listed above). Include error on failed; voice_id is recommended on completed for debugging.
  3. GET /tts/conversions/{id}/audio: return M4A audio after completed (Content-Type: audio/mp4); return 409 if not yet completed.
  4. DELETE /tts/conversions/{id}: cancel the task; return 204.
  5. 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 targetLang to be a language code (e.g. en, zh), with the selectable list obtained via GET /api/languages.
  • To keep your server robust against all clients, it is recommended that the language validation in /api/translate accept both codes and display names — recognizing en as 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.