Developer blog
Generate Subtitles and Transcripts From Any Video
The first three tutorials in this series were all about dubbing: one video in ten
lines, a whole batch of
languages, and a button inside your own
product. This one is about the output people
forget the pipeline already produces: the words. Subtitles, captions, a plain
speaker-labelled transcript. Sometimes you want the text on screen, not a new voice
over the top, and that path is cheaper.
The problem
Text under a video is not a nice-to-have. Most of your feed autoplays on mute, so
captions are the difference between someone watching and someone scrolling past.
Screen-reader users and anyone in a noisy room need them. Search engines index the
transcript, not the pixels. And a viewer who reads your language better than they
hear it will follow along with subtitles when they would give up on audio.
Building that yourself is the same stack as dubbing minus the last step: run ASR,
line the words up against the audio, figure out who is speaking, and write it all
out as timed cues. You do all of that work and still do not have a dub. If all you
need is the text, paying for the full voice-over is overkill.
What you'll build
A script that takes a video URL and gets back an .srt subtitle file: timed,
speaker-labelled, in the source language. No models, no alignment code, one POST and
the same poll loop from tutorial #1. Then a second thing you get for free: any dub
job you already run also hands you subtitles in both languages, so you may not need
a separate call at all.
Two ways to get subtitles
The pipeline writes subtitles in two situations, and which one you want depends on
whether you also need the dub.
- Transcribe only.
POST /api/v1/transcriberuns the speech-to-text half on its
own: no translation, no voice-over. You get a source-language SRT, billed at half
the per-minute rate a dub costs. Reach for this when you want captions in the
original language and nothing else. - Subtitles as a byproduct of a dub. Every finished dub job carries four
downloads, not just the video. Two of them are subtitle files:translationSrtUrl
(captions in the language you dubbed into) andtranscriptSrtUrl(the original
source transcript). If you are already dubbing the video, the subtitles are sitting
there in the same response for no extra minutes.
The rest of this post covers the transcribe path first, then how to pull the two
SRTs out of a dub you already have.
Step 1: transcribe the video
Same shape as a dub, minus the target language. Transcribe does not translate, so
there is no target_lang here: you are asking for the words that are already spoken.
# → 201 { "id": 7100, "state": "queued" }
You get back the same { id, state } a dub gives you. Grab the id and poll it
exactly like any other job. source_type also accepts "url" for a direct file or
an HLS playlist, not just YouTube.
Step 2: poll, then download the SRT
No webhooks, so you poll GET /api/v1/jobs/{id} on an interval until state is
done. Same gotcha as always: a failed job comes back as a 200 with
state: "failed" and an errorMessage, not an HTTP error, so branch on state.
For a transcribe job the SRT is the main output. It shows up in two places in the
status object, and both point at the same file:
archiveUrlis the primary download for the job, which for a transcribe job is
the SRT itself.artifacts.transcriptSrtUrlis the same file under a stable named key, if you
prefer to read artifacts by name.
// GET /api/v1/jobs/7100 once it's done
{
"id": 7100,
"mode": "transcribe",
"state": "done",
"archiveUrl": "https://…/transcript.srt",
"artifacts": {
"dubbedVideoUrl": null,
"dubbedAudioUrl": null,
"translationSrtUrl": null,
"transcriptSrtUrl": "https://…/transcript.srt"
}
}
The other three artifact keys are null on a transcribe job. That is on purpose:
the four keys are always present so you can rely on the shape, and they fill in only
when the artifact exists. A dub fills all four; a transcribe fills one.
Full code
Set DV_API_KEY in your environment. Each version transcribes, polls, and writes a
.srt next to the script.
Node (18+, global fetch)
// Node 18+ (global fetch). Set DV_API_KEY in your environment.
const KEY = process.env.
const base = 'https://dubanyvideo.com/api/v1'
const headers =
const const INPUT_URL = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
// 1. Start a transcribe job (no target language): 201 { id, state }
const = await .
// 2. Poll until it's done.
let job
throw
// 3. Download the SRT (archiveUrl, or artifacts.transcriptSrtUrl — same file).
const srt = await .
await
Python (pip install requests)
# pip install requests. Set DV_API_KEY in your environment.
=
=
=
# 1. Start a transcribe job (no target language): 201 { "id", "state" }
=
# 2. Poll until it's done.
=
break
# 3. Download the SRT (archiveUrl, or artifacts.transcriptSrtUrl — same file).
= .
Subtitles from a dub you already ran
If the video is getting dubbed anyway, you do not need a second call. A finished dub
job carries subtitles in both languages already. Point at a done dub job and read
its artifacts:
// GET /api/v1/jobs/{id} for a done DUB job (dubbed en → es)
{
"state": "done",
"targetLang": "es",
"archiveUrl": "https://…/dubbed.mp4",
"artifacts": {
"dubbedVideoUrl": "https://…/dubbed.mp4",
"dubbedAudioUrl": "https://…/dubbed.mp3",
"translationSrtUrl": "https://…/es.srt", // captions in the dubbed language
"transcriptSrtUrl": "https://…/en.srt" // the original source transcript
}
}
So one dub gives you the dubbed video, the dubbed audio track, target-language
subtitles, and the source transcript in a single job. Ship the .srt alongside the
video and viewers can read along in either language.
What the SRT looks like, and where it goes
It is a standard SubRip (.srt) file: numbered cues, start and end timings, and
speaker labels on the lines. That format drops straight into VLC, Premiere, DaVinci
Resolve, a YouTube caption upload, or an ffmpeg burn-in.
One thing to know if you are putting captions on an HTML5 <video>: the browser
<track> element wants WebVTT, not SRT. The two are nearly identical. Converting is
a header line and a punctuation swap: add WEBVTT as the first line and change the
, before the milliseconds in each timestamp to a .. Any subtitle library does it
in one call, or you can do it with a two-line replace.
Cost & limits
- Transcribe is billed from the same balance as everything else, at half the
per-minute rate of a dub. A 10-minute video transcribed costs 5 minutes. - Subtitles that come out of a dub cost nothing extra. You already paid the dub's
minutes; the two SRTs are part of that job. - Want the number before you commit?
POST /api/v1/estimateis free, creates no
job, and returns the source duration. Halve it for the transcribe cost. To check
your remaining balance,GET /api/v1/me/usagereturns your plan, monthly quota,
minutes used, and top-up balance. - Max source length is 30 minutes per job, and input is by link only (YouTube, HLS,
or direct URL). - Rate limits: 60 requests/min per key. New submissions are capped per minute by plan
(free 2 · starter 5 · medium 10 · super 20). - Prefer pay-per-job over a subscription? The same engine is on the
RapidAPI marketplace, billed per job.
Get your key
Grab a key and pull the text out of your next video:
dubanyvideo.com/api. That closes the loop on the pipeline: dub it,
localize it into a batch, put a button on it, and caption it, all from the same key.