jonathondoes.ai
โ† Back to all posts
AI Tutorials

Auto-generating podcast audio for blog posts

Jonathon Hinchley ยท August 8, 2026
Listen to this post

I wanted a way to listen to my posts while driving, without recording myself reading every single one. So the first version of this is fully automated: write the post, run a script, get an mp3 read by an AI voice. Here's how it works.

Why a script, not a pipeline

The obvious way to automate this is a Sanity Function that fires on publish, or a custom Studio action that calls an API route. I skipped both. A Sanity Function means setting up Blueprints and deploying a serverless function just to call an API. A Studio action means a new authenticated API route on the Next.js app, plus a secret shared between Studio and the app. Both are more infrastructure than a personal blog needs โ€” especially since the plan is to swap AI narration for real recordings eventually anyway.

So it's a plain Node script: write the post in the Studio, run `pnpm generate-audio <slug>`, done. No deploy, no secrets to share across apps, no server to keep running.

The schema

The post schema got three new fields: `audio` (a file field, audio only), and two hidden fields โ€” `audioSource` (`ai` or `manual`) and `audioSourceHash`. The hash is a fingerprint of the post body text. If it matches what's already stored, the script skips regenerating audio that hasn't gone stale. The source flag exists so a manually uploaded recording is never silently overwritten by a re-run of the AI script โ€” once `audioSource` is `manual`, the script refuses to touch it unless you pass `--force`.

Turning Portable Text into plain text

Sanity stores the post body as Portable Text โ€” an array of typed blocks, not a plain string. Text-to-speech needs a string, so the script filters the array down to `block`-type entries (skipping inline images) and joins each block's `children[].text` into paragraphs.

Calling ElevenLabs

That plain text gets POSTed to ElevenLabs' text-to-speech endpoint with an `xi-api-key` header and a voice ID, using the `eleven_multilingual_v2` model. The response body is the raw mp3 bytes.

Uploading it back to Sanity

The mp3 buffer gets uploaded as a Sanity asset, then a patch sets the post's `audio` field to reference it, sets `audioSource` to `ai`, and stores the new hash โ€” so the next run knows this post is up to date.

Using it

After drafting a post in the Studio: `pnpm generate-audio my-post-slug`. Edit the post later and run it again โ€” it only regenerates if the text actually changed, or if you pass `--force`.

What's next

AI narration is the placeholder, not the destination. The plan is to record real audio myself for posts that matter, and the schema already supports that without any code changes โ€” just upload a file in the Studio and set the source to manual.