Dub 40 nine-minute course lessons into Spanish: up to $54.94

Dubbing 40 nine-minute lessons into Spanish costs up to $54.94 on Sume: about $1.37 a lesson, with the timeline render at $0.90 as the largest line.

5 min readSume
All posts

Dubbing 40 nine-minute course lessons into Spanish costs up to $54.94 on Sume, about $1.37 per lesson, at list rates. The render is the biggest line at $0.90 a lesson, and Index-Translate's listed free API handles the translation.

Assumptions: 9 minutes per lesson, 850 characters per minute of speech so 7,650 characters per lesson, and one language. Speech-to-text takes up to 10 minutes of audio per request, so a nine-minute lesson is one request. Rates are from the pricing page, Audio detach docs and Timeline docs, read 2026-10-05.

One lesson, then the course

Each step is captured at its own compute and never above the reservation, so these are ceilings.

Cost of dubbing one 9-minute lesson, and 40, at list rates (read 2026-10-05)
StepPer lessonFor 40 lessons
Detach the audio$0.01$0.40
Speech-to-text, 9 minutes$0.09$3.60
Sume TTS, 7,650 characters$0.3634$14.54
Concat speech for its length$0.01$0.40
Timeline render, 9 minutes$0.90$36.00
Total, up to$1.373$54.94

Plan the run

Use the lesson number and language in every idempotency key, such as course-l07-es-render, so a rerun reuses finished work. Slides that change between languages may need a new video track, which this bill does not include.

  • Source video must be on media.sume.com; import it first.
  • Detach takes up to 1,800 seconds of source, so lessons longer than 30 minutes need splitting first.
  • Keep each Spanish spine near the lesson length: a spine that speaks past 9:00 adds a billable minute.

Render one lesson

This joins the speech for one lesson for its duration and renders it under the lesson video.

import os, time, requests
API = "https://api.sume.com"
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}

def run(path, body, key):
    r = requests.post(API + path, headers={**H, "Idempotency-Key": key}, json=body, timeout=60)
    r.raise_for_status()
    job = r.json()["data"]
    while not requests.get(job["status_url"], headers=H, timeout=30).json()["data"]["terminal"]:
        time.sleep(2)
    res = requests.get(job["result_url"], headers=H, timeout=30)
    res.raise_for_status()
    return res.json()["data"]["result"]

VIDEO, SPEECH = os.environ["VIDEO_URL"], os.environ["SPEECH_URL"]   # both on media.sume.com
joined = run("/v1/timeline-1.0/audio", {"operation": "concat", "parts": [{"url": SPEECH}]}, "dub-0412-de-spine")
secs = joined["duration_seconds"]
body = {"audio": {"url": joined["audio_url"], "duration_seconds": secs},
        "video": [{"source_url": VIDEO, "start": 0, "duration": secs}]}
out = run("/v1/timeline-1.0/render", body, "dub-0412-de-render")
print(out["video_url"])

Sources

Related posts

More in Use cases

All Use cases posts

Written by Sume