A script goes in. A narrated video comes out.
Narration is a replacement value, not a production step. Put a tts object where you would put a URL and Renderly generates the voice, drops it into the template's narration slot, and renders - in the same API call. No microphone, no quiet room, no second pass.
jordan. Nothing was recorded.This is the entire script
Every word you hear in the video above came from this text. It is about 450 characters, which costs half a credit to narrate.
Just listed in Montauk: twenty-seven Dune Crest Road. Tucked where the dunes meet the door, this is coastal living at its finest. Inside - sun-drenched spaces, walls of glass, a chef's kitchen in white oak, and a wrap-around deck over the water. Four bedrooms. Three baths. Thirty-two hundred square feet. And open sand, just a two-minute walk away. Offered at three million, four hundred ninety-five thousand. Book your private tour with Salt and Cedar today.
Note the price is spelled out. Text-to-speech reads $3,495,000 unreliably, so the script says "three million, four hundred ninety-five thousand" while the on-screen text keeps the numerals. A number-to-words helper is the one piece of glue worth writing, and it is the difference between narrated and obviously a robot reading a database.
Narration is a variable
Most pipelines treat voice as a separate stage: generate an mp3 somewhere, upload it, reference the URL, then render. Three systems and a file to keep track of, per video.
In Renderly the template has a sound overlay marked isDynamic and named narration, shipped with an empty src. One request fills it. Send a URL and it plays that; send a tts object and Renderly writes the audio first.
If the generated audio runs longer than the composition, the overlay and the composition stretch to its measured length, and billing follows the real duration. A script that overruns produces a longer video rather than a truncated one.
{
"projectId": "your_project_id",
"replacements": {
"listing_address": "27 Dune Crest Road",
"city_location": "Montauk, New York",
"price": "$3,495,000",
"narration": {
"tts": {
"text": "Just listed in Montauk: twenty-seven Dune Crest Road...",
"voice": "jordan"
}
}
}
}The same shape works with inputProps instead of projectId.
Twelve curated voices, one line each
Every sample below reads the same sentence from the script, so what you are hearing is the voice and nothing else. Set speed between 0.5x and 4x when a read needs pacing rather than a different voice.
"Tucked where the dunes meet the door, this is coastal living at its finest."
Six of the twelve curated voices shown. The full curated set is claudette, emily, geffenv1, henry, hugh_32, jacob, jordan, julie, monica, nick, phil, ruby, and any voice id from Speechify's 992-voice catalogue across 36 locales also works.
Captions that cannot drift
Add one more replacement and the same call also burns in word-synced captions, transcribed from the narration generated in that request. Because the source is the voiceover, sync is not a hope - the captions cannot drift from audio they were transcribed out of.
We checked it end to end: the finished video went back through the captions engine and the transcript came back verbatim, with the spoken price formatted to $3,495,000. Script to speech to text and back, including the number formatting.
{
"replacements": {
"narration": { "tts": { "text": "...", "voice": "jordan" } },
"subtitles": { "transcribe": { "source": "narration" } }
}
}At most three AI replacement values per render.
What it costs
- 0.5 credits
- per started 1,000 characters
- 4,096
- characters per request
- Refunded
- automatically if it fails
Voiceover draws on the same credit balance as rendering, so there is no second wallet to top up. Every operation is logged as an AI job you can inspect. See pricing for render rates.