Lektor API ========== synchronously creates selectable Polish lektor audio and subtitles from uploaded MP4 or MKV media ROUTES: Method | Path | Description ------ | -------------------- | -------------------------- POST | /api/lektor | create Polish lektor media POST | /api/lektor/progress | create with live progress GET | /api/lektor/:id | download a finished job GET | /api | service info and defaults GET | /api/health | service status GET | / | usage reference POST /api/lektor ================ curl -X POST http://localhost:$PORT/api/lektor \ -F "media=@episode.mp4" \ -F "subtitles=@optional.srt" \ -F "subtitleSource=auto" \ -F "subtitleLanguage=auto" \ -F "subtitleQuery=The Office S01E01" \ -F "audioOnly=false" \ -F "embedPolishSubtitles=true" \ -F "duckingRatio=4" \ -F "densePauseMs=500" \ -F "denseSpeedupMin=0" \ -F "denseSpeedupMax=4" \ -F "fishVoice=4f63d293e42d4ec9adf3d9ab1ad2652c" \ -F "bare=true" \ -F "concurrency=6" \ -F "force=false" \ --output polish-lektor.mp4 the request remains open until the complete output file is ready concurrency is 1 to 16 concurrent TTS and translation jobs the endpoint has no authentication and permits 10 requests per client IP per UTC day an uploaded subtitles file overrides automatic subtitle discovery automatic discovery tries embedded Polish, embedded English, OpenSubtitles Polish, then OpenSubtitles English English subtitles are contextually translated to concise spoken Polish before synthesis uploaded subtitles with subtitleLanguage=auto are treated as English generated Polish subtitles are embedded as a selectable video track unless embedPolishSubtitles is false audioOnly=true skips video muxing and streams the Polish lektor narration WAV embedPolishSubtitles and duckingRatio apply only when the response is a video file video uploads may use MP4 or MKV containers; the response preserves the input container speech first uses fish-audio/s2.1-pro-free:free through OpenRouter; unavailable or rate-limited requests fall back to fish-audio/s2.1-pro the X-Lektor-Request-Id response header is the id for GET /api/lektor/:id subtitleSource values are auto, embedded, opensubtitles, or upload subtitleLanguage values are auto, pl, or en REQUEST FIELDS: Field | Type | Default -------------------- | ------- | ---------------------------------- media | File | - subtitles | File | null subtitleSource | string | "auto" subtitleLanguage | string | "auto" subtitleQuery | string | null audioOnly | boolean | false embedPolishSubtitles | boolean | true duckingRatio | number | 4 densePauseMs | number | 500 denseSpeedupMin | number | 0 denseSpeedupMax | number | 4 fishVoice | string | "4f63d293e42d4ec9adf3d9ab1ad2652c" bare | boolean | true concurrency | number | 6 force | boolean | false EXAMPLE RESPONSE: Streams the completed MP4 or MKV with selectable Polish Lektor audio and Polish subtitle tracks, or a WAV narration when audioOnly is true or the input has no video. ERROR RESPONSE: "error": "No Polish or English subtitles could be resolved for the uploaded media." POST /api/lektor/progress ======================== curl -N -X POST http://localhost:$PORT/api/lektor/progress \ -F "media=@episode.mp4" \ -F "subtitles=@optional.srt" \ -F "subtitleSource=auto" \ -F "subtitleLanguage=auto" \ -F "subtitleQuery=The Office S01E01" \ -F "audioOnly=false" \ -F "embedPolishSubtitles=true" \ -F "duckingRatio=4" \ -F "densePauseMs=500" \ -F "denseSpeedupMin=0" \ -F "denseSpeedupMax=4" \ -F "fishVoice=4f63d293e42d4ec9adf3d9ab1ad2652c" \ -F "bare=true" \ -F "concurrency=6" \ -F "force=false" the multipart fields are the same as POST /api/lektor audioOnly=true skips video muxing; download the narration WAV from GET /api/lektor/:id after done the response is a text/event-stream that stays open until processing finishes progress events carry percent and a status message status events carry a pipeline stage name and detail the done event includes requestId for GET /api/lektor/:id download the completed file from GET /api/lektor/:id after done http 400, 429, and 503 errors are json and happen before the stream starts pipeline failures are sent as an error event on the stream REQUEST FIELDS: Field | Type | Default -------------------- | ------- | ---------------------------------- media | File | - subtitles | File | null subtitleSource | string | "auto" subtitleLanguage | string | "auto" subtitleQuery | string | null audioOnly | boolean | false embedPolishSubtitles | boolean | true duckingRatio | number | 4 densePauseMs | number | 500 denseSpeedupMin | number | 0 denseSpeedupMax | number | 4 fishVoice | string | "4f63d293e42d4ec9adf3d9ab1ad2652c" bare | boolean | true concurrency | number | 6 force | boolean | false EXAMPLE RESPONSE: event: progress data: {"percent":25,"message":"translation complete"} event: done data: {"requestId":"7c9e6679-7425-40de-944b-e07fc1f90ae7"} ERROR RESPONSE: "error": "No Polish or English subtitles could be resolved for the uploaded media." GET /api/lektor/:id ================== curl http://localhost:$PORT/api/lektor/7c9e6679-7425-40de-944b-e07fc1f90ae7 \ --output polish-lektor.mp4 id is the UUID from X-Lektor-Request-Id or from the progress done event jobs started from either POST /api/lektor or POST /api/lektor/progress can be downloaded here the job remains available in the API output directory after the original response ends REQUEST FIELDS: Field | Type | Default ----- | ------ | ------- id | string | - EXAMPLE RESPONSE: Streams the completed MP4, MKV, or WAV for a finished job, with Content-Disposition: attachment. ERROR RESPONSE: "error": "Job not found"