Developer blog
Auto-Localize Your YouTube Uploads Into 30+ Languages
In the first tutorial we dubbed one video into one language in about ten lines. This time we do the thing creators actually ask for: take a single upload and fan it out into a whole batch of languages at once, then download every dub. It's a script you can hang off your upload pipeline so a new video ships localized on day one.
The problem
You publish a video. Your audience is global, but the video is in one language. The manual fixes don't scale. Re-recording, hiring voice actors, running each language through a separate tool: all of it falls apart past a couple of languages, never mind thirty. You want one call that says "dub this into Spanish, Hindi, Portuguese, Arabic, …" and a folder of finished MP4s at the end.
What you'll build
A script that takes a video URL and a list of target languages, submits one dub request for the whole batch, polls each job to completion, and downloads each dubbed video named by its language. Point it at a YouTube URL (or any direct or HLS link) right after upload and the whole video comes back localized in one run.
The one call that does it: target_langs
POST /api/v1/dub takes either a single target_lang or an array target_langs. Pass the array and you get one job per language back, all created in a single request. The minute hold is all-or-nothing across the batch: either every language is reserved or none is, so you never end up with a half-charged partial run.
// POST /api/v1/dub
{
"source_type": "youtube",
"input_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"target_langs": ["es", "hi", "pt", "ar", "de"]
}
// → 201
{
"jobs": [
{ "id": 5001, "state": "queued", "targetLang": "es" },
{ "id": 5002, "state": "queued", "targetLang": "hi" },
{ "id": 5003, "state": "queued", "targetLang": "pt" },
{ "id": 5004, "state": "queued", "targetLang": "ar" },
{ "id": 5005, "state": "queued", "targetLang": "de" }
]
}
Each entry carries its own id and its targetLang, so you can map jobs back to languages. From here every job is polled and downloaded exactly like the single-language flow: GET /api/v1/jobs/{id} until state: "done", then grab archiveUrl. One reminder from tutorial #1: a failed job comes back as a 200 with state: "failed" and an errorMessage, not an HTTP error, so branch on state.
Two rules the API enforces, worth knowing before you wire this in:
- Send both
target_langandtarget_langsin the same call and you get422 { "error", "message": "Pass either target_lang or target_langs, not both." }. - An unsupported language code gets named back to you instead of being silently dropped:
422 … "Unsupported target language: xx. See the supported list at /api.". Validate your list against the supported languages first.
Full code
Set DV_API_KEY in your environment. Both scripts submit the batch, poll all jobs concurrently, and write dubbed-<lang>.mp4 for each.
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'
const LANGS =
// 1. One request -> one job per language: 201 { jobs: [{ id, state, targetLang }] }
const = await .
// 2. Poll each job to completion (in parallel) and 3. download its dubbed MP4.
await Promise.
Python (pip install requests)
# pip install requests. Set DV_API_KEY in your environment.
=
=
=
=
# 1. One request -> one job per language: 201 { "jobs": [{ "id", "state", "targetLang" }] }
=
# 2. Poll one job to completion and 3. download its dubbed MP4.
=
=
break
return
# Poll all languages in parallel.
One voice across every language (optional)
By default each job auto-detects speakers and gives them their own voices. If you'd rather use one fixed voice for the whole batch, say a single narrator across every language, add diarization: "skip" with a voice. It applies to every language in the batch:
{
"source_type": "youtube",
"input_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"target_langs": ["es", "hi", "pt"],
"diarization": "skip",
"voice": "…" // pick from the voices listed on /api
}
Omit both to keep automatic per-speaker voices.
Wiring it into your upload flow
- Trigger it on publish. Call this right after a new video is uploaded (a webhook from your CMS or YouTube ingestion, a queue worker, a CI step) with your standard language list.
- Estimate first if you want a preflight number.
POST /api/v1/estimateis free and returns the duration; multiply by your language count to preview the minute cost before you commit the batch. - Store the
id → targetLangmap from the create response so a later poll worker can pick jobs up asynchronously instead of blocking on one long request.
Cost & limits
- You're billed video-duration minutes per language. A 4-minute video into 5 languages costs 20 minutes. The batch hold is all-or-nothing.
- One balance covers both the dashboard and the API. 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), and a
target_langsbatch counts as one submission, so fanning out into many languages doesn't burn your submit budget. - Prefer pay-per-dub? The same engine is on the RapidAPI marketplace, billed per job.
Get your key
Grab a key and localize your next upload into every language your audience speaks: dubanyvideo.com/api. Next in this series: adding a "Dub this video" button to your own platform.