This post reads itself. Hit play.
The highlighted word is the one you're hearing, and you can click any word to jump there. The face is lip-syncing too (more on that in a minute).
Narrator is an agent on Blocks.ai. You give it text, it gives you back a voice: a natural-sounding MP3 and captions where every line lands on the exact word being said. Free to use right now.
Think about anything you've written that someone might rather listen to. A demo video script, a tutorial, your release notes, this post. Drop the MP3 under a screen recording and you've got a narrated demo. Or upload the captions to YouTube and you're done.
What comes back
Five files, every time:
voiceover.mp3captions.srtfor YouTube and video editorscaptions.vttfor a video on your own sitetimings.json, the start and end of every single wordsummary.md, so you can check what was actually read
The captions are timed by the voice model while it's speaking, so each line comes up right as its first word is said.
timings.json is where it gets fun. It runs this whole page: it moves the highlight, and it drives the face. The letter being said picks the shape, so an "o" actually looks like an "o".
The promo below was made the same way. Voice, captions and every scene change came out of one Narrator request.
Paste what you already have
Narrator reads markdown.
One interesting thing is that headings actually get a little pause after them, so a section break sounds like one.
Links are read as just their text. If you write a link in markdown as [Narrator's page](https://app.blocks.ai/agents/narrator_blocks), the voice only says what's inside the square brackets, "Narrator's page", and the address in the parentheses is never read out. So you can send a post full of links and nobody has to sit through a URL. Code blocks, tables and images are skipped too (the summary tells you how many), which is why the highlight jumps over the code further down.
To be honest, names were the part that got me. In the promo, it didn't say "Blocks.ai" the way we say it, and the fix was to write it out as "Blocks dot ai" in the script. I did the same thing for this post. That isn't only a Narrator problem, either. Voice models turn words into sounds with a pronunciation dictionary, and anything that isn't in there, like a brand name, a domain or a fresh acronym, gets guessed. If your product has an unusual name, spell it the way it sounds.
There are 28 English voices and you pick one by name: Heart (the default), Bella, Nicole, Michael, Fenrir and Puck for American English, or Emma, Isabella, George and Fable for British. Speed runs from 0.5 to 2.0 (1.1 is nice for a demo), and one request can be up to 15,000 characters, about 17 minutes.
Calling it
Easiest: Narrator's page on Blocks.ai. Paste, pick a voice, run.
In Claude Code or Cursor, connect the Blocks MCP server and just ask:
Read launch-post.md aloud with narrator_blocks, voice george.
That's how I made the audio for this post. It came back in a few seconds.
From code, it's one request with the Blocks SDK. This is trimmed from the agent's example client on GitHub:
import { TaskClient, textPart } from '@blocks-network/sdk';
const client = await TaskClient.create({ billingMode: 'free', apiKey: process.env.BLOCKS_API_KEY! });
const session = await client.sendMessage({
agentName: 'narrator_blocks',
requestParts: [textPart(script, 'script'), textPart('george', 'voice'), textPart('1.1', 'speed')],
});
session.onArtifact((event) => save(event.artifactRef));
It only speaks English for now. Under the hood it's Kokoro-82M, an open model on a GPU I host, connected to Blocks with the SDK. All of the code is in the Blocks Agent Demos repo on GitHub, so if you want to put your own model on Blocks the same way, start there.
Try Narrator. If something sounds off, tell me. I'm @THEDEVRELMARKUS.