Pinch Dubbing
2026-03-17

How to Dub Videos with Python: A Developer's Guide

Step-by-step tutorial for dubbing videos programmatically with Python. Use the Pinch Dubbing API to translate video and audio files into 10 languages with a few lines of code.

What you’ll build

By the end of this guide, you’ll have a Python script that takes a video file, dubs it into a target language, and downloads the result — all in about 30 lines of code. No machine learning setup, no GPU, no model downloads. Just HTTP requests to a REST API.

This is useful for:

  • Batch processing video libraries
  • Automating multilingual content pipelines
  • Integrating dubbing into CI/CD or content management workflows
  • Building tools that need localized video output

Prerequisites

  • Python 3.8+
  • A Pinch API key (sign up free — $5 credits included, no credit card)
  • The requests library (pip install requests)

The complete script

Here’s the full working example. We’ll break down each part below.

import requests
import time
import sys

API_BASE = "https://api.startpinch.com/api/dubbing"
API_KEY = "YOUR_API_KEY"  # Replace with your key

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}


def dub_video(source_url: str, target_lang: str) -> str:
    """Submit a dubbing job and wait for the result."""

    # 1. Submit the job
    resp = requests.post(
        f"{API_BASE}/jobs",
        headers=headers,
        json={"source_url": source_url, "target_lang": target_lang},
    )
    resp.raise_for_status()
    job = resp.json()
    job_id = job["id"]
    print(f"Job submitted: {job_id}")

    # 2. Poll until complete
    while True:
        status_resp = requests.get(f"{API_BASE}/jobs/{job_id}", headers=headers)
        status_resp.raise_for_status()
        data = status_resp.json()

        if data["status"] == "completed":
            print("Dubbing complete.")
            return data["result_url"]
        elif data["status"] == "failed":
            raise RuntimeError(f"Job failed: {data.get('error', 'unknown')}")

        print(f"Status: {data['status']}... waiting")
        time.sleep(5)


def download(url: str, output_path: str):
    """Download the dubbed file."""
    resp = requests.get(url, stream=True)
    resp.raise_for_status()
    with open(output_path, "wb") as f:
        for chunk in resp.iter_content(chunk_size=8192):
            f.write(chunk)
    print(f"Saved to {output_path}")


if __name__ == "__main__":
    source = sys.argv[1] if len(sys.argv) > 1 else "https://example.com/video.mp4"
    lang = sys.argv[2] if len(sys.argv) > 2 else "es"

    result_url = dub_video(source, lang)
    download(result_url, f"dubbed_{lang}.mp4")

How it works

Submitting a job

The /jobs endpoint accepts a JSON body with two required fields:

  • source_url — A publicly accessible URL to your video or audio file. Supported formats: MP4, MOV, WebM, MP3, WAV, M4A.
  • target_lang — An ISO 639-1 language code. Pinch currently supports: es, fr, de, it, pt, ru, ja, ko, zh, and en.

The API returns a job object with an id that you use to track progress.

Polling for completion

Dubbing jobs are asynchronous. A 5-minute video typically takes 1–3 minutes to process. The script polls every 5 seconds until the status is completed or failed.

The response object includes:

  • status — One of processing, completed, failed
  • result_url — Available when status is completed. A signed URL valid for 48 hours.
  • error — Available when status is failed. Describes what went wrong.

Downloading the result

The result_url is a direct download link to the dubbed file. The output format matches the input — if you sent an MP4, you get an MP4 back with the original video and dubbed audio track.

Batch processing multiple videos

For a library of videos, you can submit all jobs first, then poll them in parallel:

import concurrent.futures

videos = [
    ("https://cdn.example.com/lesson-1.mp4", "es"),
    ("https://cdn.example.com/lesson-2.mp4", "es"),
    ("https://cdn.example.com/lesson-3.mp4", "es"),
]

with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    futures = {
        executor.submit(dub_video, url, lang): (url, lang)
        for url, lang in videos
    }
    for future in concurrent.futures.as_completed(futures):
        url, lang = futures[future]
        try:
            result_url = future.result()
            filename = url.split("/")[-1].replace(".mp4", f"_{lang}.mp4")
            download(result_url, filename)
        except Exception as e:
            print(f"Failed: {url} -> {e}")

This processes all videos concurrently, significantly reducing total wall-clock time for large batches.

Dubbing into multiple languages

To dub a single video into all supported languages:

LANGUAGES = ["es", "fr", "de", "it", "pt", "ru", "ja", "ko", "zh"]

source = "https://cdn.example.com/my-video.mp4"

for lang in LANGUAGES:
    result_url = dub_video(source, lang)
    download(result_url, f"my-video_{lang}.mp4")

At $0.50/min with Pinch, dubbing a 5-minute video into all 9 non-English languages costs $22.50 total.

Error handling

Production code should handle common failure modes:

  • Network timeouts — Add timeout to requests calls and implement retry logic
  • Rate limits — The API returns 429 if you exceed rate limits. Back off exponentially.
  • Invalid URLs — Ensure source URLs are publicly accessible. Private/authenticated URLs will fail.
  • Unsupported formats — Check file format before submitting. The API returns a clear error for unsupported types.

Next steps

Try Pinch Dubbing free

Sign up and get $5 of free credits — enough for 10 minutes of dubbing. Upload a video in your browser or integrate via our API.

No credit card required · $0.50/min · No watermarks