TTS API for video voiceover: render one file per scene
MiniMax speech-2.8-hd and speech-2.8-turbo are available on YiduoChan through the OpenAI-compatible /v1/audio/speech endpoint, billed per input character. This page covers writing a script to a target duration, rendering one audio file per scene, picking a format your editor can use, and what a finished minute of narration actually costs.
Write to a duration before you render
Video voiceover goes wrong in a predictable place. Someone writes a script, renders the whole thing, discovers it runs 7:40 against a 6:00 cut, and then time-stretches the audio or trims frames to make it fit. Both repairs are worse than writing to length in the first place, and both are avoidable. The MiniMax speech models are billed per input character, so the one number you can measure before spending anything — the character count of the script — is also the number that predicts duration and price.
Three planning estimates are enough to size a script. Treat them as estimates: real delivery speed varies with language, punctuation, and the voice you choose.
- About 900 characters per minute of finished speech.
- About 54,000 characters per hour.
- About 6 characters per English word, counting the trailing space.
Combined, they give you a target you can enforce in a text editor instead of in an NLE. A one-minute video wants roughly 900 characters, or about 150 words. A five-minute video wants about 4,500 characters (750 words). A ten-minute video wants about 9,000 characters (1,500 words). If a draft comes back at 11,400 characters for a ten-minute slot, you are 2,400 characters long — roughly 2.7 minutes — and the fix is to cut text, not audio.
Budget the characters per scene
Do the division before anyone writes a word. A six-minute explainer is about 5,400 characters; split across twelve scenes that is an average of 450 characters per scene, which is a concrete brief a writer or a chat model can hit. Keep the budget in the same JSON file that drives rendering so the check is mechanical rather than editorial. If you want the script drafted rather than written by hand, MiniMax-M2.7 is on the same key and the same base URL, and you can hand it the per-scene character budget as part of the prompt.
One audio file per scene
Render each scene to its own file. The temptation is to send the whole script in one request and get back a single long track, and it is the wrong shape for video work. Video edits change: a product name gets corrected, a price moves, legal asks for one clause to be reworded. With one file per scene you re-render that scene, drop the new file over the old one in the timeline, and everything downstream stays where it was. With a single track you re-render the entire narration and re-sync every cut.
The cost argument points the same way. A 40-word line is about 240 characters, which is roughly $0.02 at list on speech-2.8-hd. Re-rendering one line of a ten-minute video costs about two cents; re-rendering the whole thing costs about $0.90. Per-scene files also make retries cheap and reviews easy — a reviewer can flag 07-pricing.wav instead of a timecode.
Give the files sortable, stable names: a zero-padded scene index, a slug, and the format extension. Keep the name identical across re-renders and across languages, so the edit's file references never have to change.
wav or pcm for the timeline, mp3 for delivery
The /v1/audio/speech endpoint accepts response_format values of mp3, wav, flac and pcm; if you omit the field you get mp3. For an editing timeline that default is the wrong choice. Render wav for anything that will be cut, ducked under music, loudness-normalised, or re-exported, and keep mp3 for the final delivery encode only. Every intermediate lossy encode is a generation of quality you cannot get back, and mp3's encoder padding is a nuisance when you are aligning speech to a frame boundary.
Use pcm when you want raw samples with no container at all — useful if you are concatenating scenes or feeding a processing chain that writes its own header. The trade-off is that raw PCM carries no metadata, so the importer has to be told what it is looking at. That is where the sample-rate passthrough matters.
Passing audio_setting.sample_rate
Provider-specific extras travel in a metadata object on the request. For video work the useful one is audio_setting.sample_rate: the supported values are not published here, so test the rate your project uses before committing to a batch. If your sequence is 48 kHz, ask for 48 kHz. The same object carries voice_setting.emotion if you want to steer delivery between, say, a product hero shot and a compliance disclaimer. Treat these as passthrough fields — the endpoint itself stays OpenAI-compatible, so any client that can call audio.speech.create works, as covered in the OpenAI SDK guide.
Rendering a JSON script, one file per scene
The script below reads a JSON array of scenes, renders each one to its own file, and prints the character count and estimated duration as it goes so a script that is running long is obvious before the edit starts.
# render_scenes.py - one audio file per scene
import json, os, pathlib, requests
ENDPOINT = "https://yiduochan.com/v1/audio/speech"
KEY = os.environ["YIDUOCHAN_API_KEY"]
OUT = pathlib.Path("render")
OUT.mkdir(exist_ok=True)
# script.json:
# [{"id": "01-hook", "voice": "English_expressive_narrator",
# "text": "Most teams ship the wrong length first."}, ...]
scenes = json.loads(pathlib.Path("script.json").read_text())
def render(scene, model="speech-2.8-hd", fmt="wav", sample_rate=48000):
body = {
"model": model,
"input": scene["text"],
"voice": scene["voice"],
"response_format": fmt,
"metadata": {"audio_setting": {"sample_rate": sample_rate}},
}
r = requests.post(
ENDPOINT,
headers={"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json"},
json=body,
timeout=300,
)
r.raise_for_status()
path = OUT / f"{scene['id']}.{fmt}"
path.write_bytes(r.content)
n = len(scene["text"])
print(f"{path} {n} chars ~{n / 900:.2f} min")
return n
total = sum(render(s) for s in scenes)
print(f"total {total} chars ~{total / 900:.1f} min")
print(f"list cost ${total * 0.0001:.2f} (speech-2.8-hd)")
To re-render a single line after a copy change, call the endpoint directly and overwrite one file. Nothing else in the edit moves.
curl -s https://yiduochan.com/v1/audio/speech \
-H "Authorization: Bearer $YIDUOCHAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "speech-2.8-turbo",
"input": "Scene four. Measure the script once, then render.",
"voice": "female-shaonv",
"response_format": "pcm",
"metadata": {"audio_setting": {"sample_rate": 48000}}
}' \
--output render/04-measure.pcm
What a finished minute of narration costs
Speech is billed per input character, so cost is a function of script length and nothing else — not of render time, not of the voice, not of the output format. Two models are available: speech-2.8-hd and speech-2.8-turbo.
| Model | List per 1M characters | With current 5% group discount | List per 1,000 characters |
|---|---|---|---|
speech-2.8-hd | $100 | $95 | $0.10 |
speech-2.8-turbo | $60 | $57 | $0.06 |
Applying the 900-characters-per-minute estimate gives per-video totals. Script lengths in this table are estimates, so treat the money as an estimate of the same accuracy.
| Finished duration | Script (est. characters) | Approx. words | hd, list | hd, −5% | turbo, list | turbo, −5% |
|---|---|---|---|---|---|---|
| 1 minute | 900 | 150 | $0.09 | $0.09 | $0.05 | $0.05 |
| 5 minutes | 4,500 | 750 | $0.45 | $0.43 | $0.27 | $0.26 |
| 10 minutes | 9,000 | 1,500 | $0.90 | $0.86 | $0.54 | $0.51 |
| 60 minutes | 54,000 | 9,000 | $5.40 | $5.13 | $3.24 | $3.08 |
Every account currently sits in a group whose ratio is 0.95, so the amount actually billed is 5% below list. That is a current group discount rather than a permanent guarantee, and at one minute the 5% is smaller than a cent, which is why the first row's two columns round to the same figure. The hour row shows the gap plainly: $5.40 versus $5.13 on speech-2.8-hd. List prices are MiniMax's own published pay-as-you-go rates, matched here. The full breakdown lives on the speech-2.8 pricing page, and the cost calculator takes a character count directly.
The practical reading: narration is not the expensive part of a video. A weekly ten-minute explainer channel spends about $0.90 per episode at list on speech-2.8-hd, or $0.855 with the current 5% group discount. Choose between the two models on delivery quality and latency for your material, not on the price difference — render one representative scene through both and listen.
Localisation is a re-render, not a re-integration
Because scenes are addressed by ID rather than by timecode, a localised version is the same pipeline with a different translation and a different voice value. Keep the JSON structure and scene IDs identical, swap the text for the translated line and the voice for a suitable voice ID — English_expressive_narrator, male-qn-qingse and female-shaonv are among those you can name directly — and write the output into a per-language directory such as render/de/07-pricing.wav.
Budget accordingly: five language versions of a ten-minute video is 45,000 characters, which is $4.50 at list on speech-2.8-hd ($4.28 with the current discount) or $2.70 on speech-2.8-turbo ($2.57 discounted). Expect the translated scene to differ in length from the source, so re-check the per-scene character budget after translation rather than assuming the timing survives.
Limits and what is not offered
Some boundaries are worth knowing before you design around them.
- No voice cloning. You cannot supply a reference recording of a presenter and get their voice back. Pick from the published voice IDs.
- Streaming audio is not documented. Treat each request as producing one complete file. Do not build against a chunked or event-stream response shape for speech; chunk your script instead.
- No published per-request character limit. The limit is not documented, so do not rely on one. Splitting per scene keeps every request comfortably small and gives you a natural retry unit.
- Failed requests are never charged, so a retry loop around a scene render costs nothing extra when it fails.
MiniMax-H3 video generation is temporarily unavailable on this platform, so this pipeline covers the audio half of a production only. For long-form spoken content with the same per-file discipline, see the companion write-up on TTS for audiobooks, or the speech-2.8 model page for parameter details.
Getting set up
Create a key at /register and top it up. Billing is prepaid USD credits, pay-as-you-go, with no subscription: the minimum top-up is $5, presets run $5 / $10 / $20 / $50 / $100 / $200 / $500 or a custom amount, and credits are valid for twelve months. There are no free credits and no trial, so budget a few dollars for test renders — which, at these rates, buys a great deal of test narration. Current rates for every model are listed on the pricing page. The base URL is https://yiduochan.com/v1 and authentication is a standard Authorization: Bearer header carrying <your API key>. Questions go to [email protected].
FAQ
How long should the script be for a five-minute video?
About 4,500 characters, using the planning estimate of roughly 900 characters per minute of speech. Render one representative scene and measure it before committing the whole script.
What does a ten-minute voiceover cost?
Roughly 9,000 characters, which is $0.90 at list on speech-2.8-hd and $0.54 on speech-2.8-turbo. With the current 5% group discount that becomes $0.86 and $0.51.
Which response_format should I use for an editing timeline?
Use wav for anything that will be cut or processed, or pcm if you want raw samples with no container; flac is also available. Keep mp3, which is the default when the field is omitted, for the final delivery encode.
Can I clone a presenter's voice?
No, voice cloning is not offered. You choose from the published voice IDs, such as English_expressive_narrator, male-qn-qingse and female-shaonv.
Is streaming supported on the speech endpoint?
Streaming audio is not documented, so treat every request as returning one complete file. Split long scripts into per-scene requests rather than building against a stream.
How do I produce the same video in another language?
Re-render the translated script through the same per-scene pipeline with a different voice ID, keeping the scene IDs and file names identical. Re-check the per-scene character budget afterwards, since translations rarely match the source length.