agentleFS
sarvamaisarvamai/skills/dubbing/SKILL.md

dubbing

Skill72 starsChanged 45 days agoApache-2.0View on GitHub

Write correct Sarvam Dubbing API code — async create → upload → start → poll → export pipeline for localizing video/audio into 12 Indian languages with speaker voice cloning. Use this skill when building a dub-a-video/audio feature in Python or JS/TS. For translating written documents, use translate/document instead. For live dubbing in chat via MCP, use sarvam-mcp instead.

  • Reads credentials
  • Sends data out

4 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions4 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 4

    1. elevenlabs/skills★ 539same name
    2. perso-ai/perso-dubbing-plugin★ 40same name
    3. elevenlabs/plugin★ 3same name
    4. RobStrayer/voice-agent-skills★ 1same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository7 other files it gives its agents.

The file

We split this skill into the rules it gives an agent

1 rule. 0 are common, 1 is distinctive, and 0 are very specific to this project.

  • 0

    Common

    Most of the 4 edited versions we read keep these.

  • 1

    Distinctive

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Every integration follows five steps: create → upload → start → poll status → poll exports.

    line 30

“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Dubbing — Sarvam AI

Live in-chat dubbing → sarvam-mcp (sarvam_tools_dub). This skill = SDK code.

Translating a PDF/Word/Excel file's text? → translate/document — a different API.

[!IMPORTANT] Auth: api-subscription-key header — NOT Authorization: Bearer. Base URL: https://api.sarvam.ai. client.dubbing.upload() requires sarvamai>=0.1.31a1.

What this API does

Localizes a video or audio file into up to 12 Indian languages in one job, preserving each speaker's voice via voice cloning, and exports dubbed video, isolated audio, and SRT subtitles. Async, like translate/document, but exports fire automatically once the pipeline finishes — no manual per-language trigger call needed (unlike document translation).

Every integration follows five steps: create → upload → start → poll status → poll exports.

Quick Start (Python)

import os, time
from pathlib import Path
from sarvamai import SarvamAI

client = SarvamAI(api_subscription_key=os.environ["SARVAM_API_KEY"])
media = Path("sample.mp4")

# 1. Create job (does not accept the file itself)
created = client.dubbing.create(
    source_language_code="en-IN",
    target_language_codes=["hi-IN", "ta-IN"],
    export_options=["video", "srt"],
    voice_cloning=True,
    num_speakers=1,
    job_name=media.name,
)
job_id = created.data.job_id

# 2. Upload — the SDK handles the signed PUT + x-ms-blob-type header for you
client.dubbing.upload(created.data.upload_url, media)

# 3. Start the pipeline — wrap in try/except (see Gotchas: start() currently
#    throws a pydantic ValidationError even on success in some SDK versions)
try:
    client.dubbing.start(job_id=job_id)
except Exception:
    pass  # job starts server-side regardless — confirm via get_live_status below

# 4. Poll until the dub itself is done (exports still render after this)
TERMINAL = {"completed", "partial_failure", "failed", "deleted"}
while True:
    job = client.dubbing.get_live_status(job_id=job_id).data
    if job.status in TERMINAL:
        break
    time.sleep(15)

if job.status == "failed":
    raise RuntimeError(job.error_message or "job failed")

# 5. Poll export-status for the actual download links (limit defaults to 5 — set it high)
exports = client.dubbing.get_export_status(job_id=job_id, limit=100).data.exports
downloads = {(e.target_language, e.export_type): e.download_url for e in exports if e.status == "completed"}
print(downloads)

Quick Start (JavaScript/TypeScript)

import fs from "fs";
import { SarvamAIClient } from "sarvamai";

const client = new SarvamAIClient({ apiSubscriptionKey: process.env.SARVAM_API_KEY });

// 1. Create job — JS wants the RAW WIRE field names src_lang/target_langs
//    here, NOT source_language_code/target_language_codes (see Gotchas —
//    this differs from what Sarvam's own docs sample currently shows)
const created = await client.dubbing.create({
  src_lang: "en-IN",
  target_langs: ["hi-IN", "ta-IN"],
  export_options: ["video", "srt"],
  voice_cloning: true,
  num_speakers: 1,
  job_name: "sample.mp4",
});
const jobId = created.data.job_id;

// 2. Upload to the signed URL (no documented JS upload helper — PUT it yourself)
await fetch(created.data.upload_url, {
  method: "PUT",
  headers: { "Content-Type": "video/mp4", "x-ms-blob-type": "BlockBlob" },
  body: fs.readFileSync("sample.mp4"),
});

// 3. Start — job_id is positional
await client.dubbing.start(jobId);

// 4. Poll live status — also positional
let job;
do {
  job = (await client.dubbing.getLiveStatus(jobId)).data;
  await new Promise((r) => setTimeout(r, 15000));
} while (!["completed", "partial_failure", "failed", "deleted"].includes(job.status));

// 5. Poll export status for download links
const exports = (await client.dubbing.getExportStatus(jobId, { limit: 100 })).data.exports;
console.log(exports.filter((e) => e.status === "completed"));

Gotchas

GotchaDetail
Exports are automaticUnlike translate/document's per-language trigger_export, dubbing auto-produces every format in export_options once the pipeline finishes (as long as editor_flow stays false, the default). There is no manual export-trigger call.
editor_flow: true doubles the cost₹80/min vs ₹40/min on Starter, and it suppresses auto-export entirely (exports must be triggered manually in Creator Studio instead) — leave it false for API integrations.
Python has a documented upload helperclient.dubbing.upload(upload_url, media) (sarvamai>=0.1.31a1) handles the signed PUT including the x-ms-blob-type: BlockBlob header, and accepts a path or an open binary file. No JS equivalent exists — PUT the file yourself with fetch and the same header, same as translate/document.
Python start() can throw even when the job starts fineVerified live against sarvamai==0.1.34: client.dubbing.start(job_id=...) raised pydantic.ValidationError: data.job_id Field required — the server's actual response body is {"data": {"project_id": ..., "status": "queued", ...}}, no job_id key, which the Python SDK's response model doesn't expect. The job does start server-side regardless (confirmed via get_live_status and a raw REST call) — wrap the start() call in try/except and move on to polling get_live_status(), which parses fine. This is Python-specific: client.dubbing.start(jobId) in JS (sarvamai@1.1.10) returned the same project_id-shaped body without error, since the JS SDK doesn't validate response shape as strictly. Re-check if a newer sarvamai (Python) has fixed this before assuming it's still broken.
JS create() needs src_lang/target_langs, not source_language_code/target_language_codesVerified live against sarvamai@1.1.10: passing source_language_code/target_language_codes — the exact field names shown in Sarvam's own published TS "SDK Code" sample for this endpoint — returns 400 InvalidInputError: src_lang: Field required, target_langs: Field required. The JS SDK does not translate those field names to wire format for this call; only src_lang/target_langs work. This is a bug in Sarvam's own docs example, not a hypothetical — don't trust that sample as-is. (Python's source_language_code/target_language_codes do work correctly — this is JS-only.)
job_id is positional in JSclient.dubbing.start(jobId), .getLiveStatus(jobId), .getExportStatus(jobId, { limit }) — not { job_id }. Python keeps the kwarg form (job_id=...).
limit defaults to 5 on export-statusA 2-language × 3-format job has 6 export entries; the default limit=5 silently truncates the list. Pass limit comfortably above languages × formats (max 100).
export vs exports in live-statusA single-target-language job populates export (object) and leaves exports null; 2+ languages does the reverse. Handle both keys — don't assume one is always populated. Use export-status (not live-status) as the source of truth for downloads either way.
partial_failure is terminal and has real outputTreat it alongside completed when deciding to stop polling — some languages may have succeeded even if others failed. failed is the only terminal status with nothing to collect.
completed job ≠ downloadable files yetExports render after the dub finishes. Poll export-status and only download entries whose own status is completed.
Odia is or-IN hereSame as translate/document, NOT od-IN (the STT/text-translation code) — dubbing and document translation share the or-IN convention.
Media validated at start, not at uploadStorage accepts whatever bytes you send; an unsupported/corrupt file only surfaces as a failed job (check error_message) after start, not as an upload-time error.
Signed URLs expireupload_url and download_url are short-lived (~24h for downloads). Re-poll export-status for a fresh download link rather than caching the URL.
File limitsMax size/duration depend on plan: 2GB/1hr (Starter), 3GB/1hr (Pro), 4GB/4hr (Business).

Full Docs

Fetch voice options, register/tone control, and the full job-lifecycle reference from:

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.