Podcast Generator
Overview
This cookbook builds a full-stack app that turns any PDF into a listenable, two-host audio podcast. It chains together three Sarvam AI APIs, Document Digitization to extract the document’s text, a Sarvam chat model to turn that text into a conversational script, and Bulbul v3 TTS to voice each line, and runs the whole pipeline as a background job so the UI stays responsive while a long document is processed.
What you’ll build:
- A Next.js app where a user uploads a PDF and picks a target language
- A background pipeline (PDF → text → script → audio) that runs as durable, resumable steps
- A host/guest podcast script generated directly in the target language
- Per-line audio segments stitched into a playable episode with a transcript view
- Support for 11 languages (10 Indian + English), including auto-summarization for documents too long to fit in a single prompt
Browse the full source for this app in the sarvam-ai-cookbook repo.
Jump to Clone and Install to set this demo up on your machine.
Business Value
- Turn reports, research papers, and policy documents into audio for commuting or accessibility
- Localize long-form written content into regional languages without a human translator
- Repurpose existing documentation or course material into a more engaging audio format
EdTech
Enterprise Knowledge
Publishing & Media
Convert textbook chapters or study notes into a podcast-style discussion, in the student’s preferred language.
How It Works
Parse the PDF
The uploaded PDF is sent to Sarvam’s Document Digitization API, which extracts its text as Markdown, preserving reading order and table structure.
Summarize if needed
If the extracted text is too long to fit in a single prompt, it’s split into chunks and each chunk is summarized by a Sarvam chat model before moving on, so very large documents don’t break the pipeline.
All four steps run inside a background job orchestrated by Inngest, so a slow TTS call or a large document doesn’t block the request or time out the browser.
1. Prerequisites
- Node.js 18 or higher
- A Sarvam API key, sign up on the Sarvam AI Dashboard to get one
- A free Inngest account, used to run the PDF-to-podcast pipeline as a durable background job
- A PDF you want to turn into a podcast
Redis and UploadThing are also referenced in the app’s configuration but are optional for local development. See Configure Environment Variables below for what each one actually does out of the box.
2. Clone and Install
macOS/Linux
Windows
3. Configure Environment Variables
Create a .env file in the project root:
As shipped, the app’s job store always keeps job state in an in-memory map, and its audio upload helper always stores generated audio as in-memory base64 data URLs, regardless of what you set for REDIS_URL or UPLOADTHING_TOKEN. This is fine for local, single-process development, but it won’t survive a server restart or work across multiple serverless instances. Wire up real Redis and UploadThing clients in lib/job-store.ts and lib/upload-audio.ts before deploying.
Because audio is stored as raw base64 instead of a real file URL, each TTS segment’s “URL” is actually several hundred KB of inline data, and Inngest persists that as the step’s return value. On long podcasts this can occasionally hit Inngest’s per-step output size limit (output_too_large in the inngest dev logs); Inngest’s automatic step retry recovers from it with no data loss, just extra latency. A real UploadThing token resolves this at the source, since steps would then store a short URL instead of the raw audio.
4. Run It
Start the Next.js app and the Inngest dev server in two terminals:
Open http://localhost:3000, pick a target language, upload a PDF, and watch the status update as the pipeline runs. The Inngest dev dashboard at http://localhost:8288 shows every step, script generation, each TTS call, retries, in real time, which is the fastest way to see what the pipeline is actually doing.
5. Pipeline Walkthrough
Step 1: Parse the PDF
app/api/parse-pdf/route.ts drives Document Digitization’s job API directly: create a job, get a presigned upload URL, upload the PDF, start the job, poll until it completes, then download and unzip the Markdown output.
pollStatus throws if the job reaches a Failed state, so that failure is caught here too. The route returns the caught error as a 500 with a JSON error field instead of crashing the request.
This app calls the REST endpoints directly so it can unzip the output itself. The sarvamai SDK wraps the same job lifecycle behind create_job() / upload_file() / start() / wait_until_complete() / download_output(), which is simpler to use if you’re starting a pipeline from scratch.
Sarvam’s role: This step runs entirely on Sarvam’s Document Digitization API. Sarvam does the actual PDF parsing, layout analysis, table structure, and reading order, and hands back clean Markdown; the route above is just a REST client driving that job’s create → upload → start → poll → download lifecycle.
Step 2: Generate the script
inngest/podcast-generation.ts sends the extracted text to sarvam-105b via the Chat Completion API, with a prompt that requests a JSON object containing a script array of {speaker, text} turns:
This is itself wrapped in a helper (callSarvamChat) that also retries on a 429 with exponential backoff and retries once more on a bare network failure before giving up, so a transient rate limit or dropped connection doesn’t fail the whole pipeline run. The prompt asks for at least 10-15 host/guest exchanges and instructs the model to reply with only JSON, no Markdown code fences, which the app strips defensively anyway before calling JSON.parse. If the document’s text is too large for a single prompt, it’s chunked and each chunk is summarized by the same chat model first, and the summaries (not the raw text) become the input to script generation.
This is the same Chat Completion API used for regular conversational completions, there’s no separate “structured output” endpoint. The app gets JSON back by asking for it in the prompt and defensively parsing the response.
Sarvam’s role: Sarvam’s sarvam-105b chat model is what actually writes the podcast. It turns the (possibly summarized) document text into a host/guest conversation in the target language and returns the structured JSON the app parses; the same model also produces the chunk summaries for oversized documents earlier in the pipeline.
Step 3: Voice each line
Every script line is sent to Bulbul v3, with the host and guest mapped to different speakers:
Like the chat call above, this is wrapped in a helper (textToSpeech) that also retries on 429s with backoff. The app maps host to the priya voice and guest to shubh for every supported language, so the two speakers stay distinguishable throughout the episode. See Available Options below for the full voice list if you want to customize this.
Sarvam’s role: Every audio segment is synthesized by Sarvam’s Bulbul v3 text-to-speech model. Sarvam handles the actual voice generation for each line, host and guest included, this step is what turns the written script into the podcast’s audio.
Step 4: Orchestrate with Inngest
The three steps above run inside one Inngest function (generatePodcastFunction), with each unit of work wrapped in step.run(...) so Inngest can retry it independently on failure without redoing the rest of the pipeline:
step.sleep and per-segment step.run calls are what make the pipeline resilient: if the process restarts mid-podcast, Inngest resumes from the last completed step instead of starting over. The inner try/catch deliberately swallows a single segment’s failure instead of rethrowing it, marking that segment success: false so the episode still assembles with a few dropped lines rather than failing the whole job. (After the loop, the function does still throw if literally zero segments came back.)
Available Options
Supported Languages
Default Voices (Bulbul v3)
Bulbul v3 supports a much larger set of speakers. See Text-to-Speech for the full list if you want each language to use a different voice pair instead of the same one across all 11.
Customization
Use different voices per language
VOICE_MAPPINGS in inngest/podcast-generation.ts maps every language to the same { host: 'priya', guest: 'shubh' } pair. Give a language its own entry to use a different voice for it:
Change how long the podcast is
The script-generation prompt in generatePodcastScript asks for an “8-10 minute” conversation with “10-15 dialog exchanges.” Adjust that wording, and the script.length < 10 padding logic just below it, to target a shorter or longer episode.
Move off the in-memory stubs
Before deploying beyond local testing, replace the in-memory Map in lib/job-store.ts with a real Redis client, and the base64 stub in lib/upload-audio.ts / lib/uploadthing.ts with a real UploadThing client. Both files are already structured around REDIS_URL and UPLOADTHING_TOKEN, they’re just not wired up to real clients in the version shipped in the repo.
Tips & Best Practices
- Document length: Very large PDFs can push the extracted text past what fits in a single script-generation prompt. The app automatically chunks and summarizes in that case, but extremely long documents may still lose detail in the process, consider trimming to the most relevant sections first.
- Rate limiting: TTS and chat completion calls are spaced out with durable
step.sleepcalls (1.2s between TTS calls, 1.5s between chunk summaries) to stay under rate limits. If you see429errors anyway, increase these delays. - API key security: Load
SARVAM_API_KEYfrom an environment variable rather than hardcoding it, especially outside local experimentation. - Production storage: Don’t ship the in-memory job store or base64 audio stub to production, see Move off the in-memory stubs above.
Error Handling
You may encounter these errors while using the underlying APIs:
- 403 Forbidden (
invalid_api_key_error), invalid API key. Use a valid key from the Sarvam AI Dashboard. - 429 Too Many Requests (
insufficient_quota_error/rate_limit_exceeded_error), credits exhausted or rate limit hit. The app already retries both chat completion and TTS calls with exponential backoff; if it still fails, slow down the pipeline further. - 422 Unprocessable Entity (
max_page_limit_exceeded), the uploaded PDF exceeds Document Digitization’s page limit for a single job. - 500 Internal Server Error (
internal_server_error), issue on our servers. Try again later; contact support if persistent.
For the full error-code table and SDK exception reference, see Errors & Troubleshooting.
Additional Resources
- Documentation: docs.sarvam.ai
- Related APIs: Document Digitization · Chat Completion · Text-to-Speech
- Full source: sarvam-podcast-generator on GitHub
- Community: Join the Discord Community
Final Notes
- Keep your API key secure, prefer environment variables over hardcoding.
- The in-memory job store and audio stub are fine for trying the app locally, but need to be swapped for Redis and UploadThing before deploying.
- The pipeline’s chunking, retries, and durable sleeps mean it degrades gracefully rather than failing outright on large documents or transient rate limits.
Keep Building! 🚀