Text Questions, AI Voices, and a Faster Call Kent Workflow
The Call Kent podcast has always relied on one thing: a recorded question from you. That was a barrier for some people—those uncomfortable recording themselves, non-native English speakers who prefer to write, or anyone who just wants more time to think before asking. Now there's a text-based path that gets those questions into the feed with an AI-generated voice.
What Changed on the Recording Page
On /calls/record, the original recorder with its waveform animation is still there for direct voice questions. What's new is a toggle that switches to a text input. After typing a question, you pick an AI voice, which generates the audio that later gets stitched into the episode. Voice choice applies only to text-submitted questions; recorded submissions work as before.
The text flow also addresses privacy concerns. Whoever submits has the option to check an "Anonymous" box, which swaps their profile photo in the episode artwork for the generic Kody avatar. That option works for both recorded and AI-voiced questions.
Automated Draft Processing on the Admin Side
Before publishing each episode, the site's backend now runs a draft pipeline that builds most of the post-production assets automatically. The code lives in startCallKentEpisodeDraftProcessing, which tracks a step field as the process moves through its stages. The pipeline covers file handling and normalization, which is the part where ffmpeg performs the audio stitching and level adjustment:
const responseAudio = parseBase64DataUrl(responseBase64).buffer
const created = await createEpisodeAudio(callAudio, responseAudio)
// In createEpisodeAudio(...)
const args = [
'-i', introPath,
'-i', callPath,
'-i', interstitialPath,
'-i', responsePath,
'-i', outroPath,
'-filter_complex', '...silenceremove + loudnorm + acrossfade...',
]
spawn('ffmpeg', args, { stdio: 'inherit' })
After I finish recording a response in the admin interface, the server passes the answer audio to the draft processor. That's when the system writes a full draft for editorial review—usually including the audio file, transcript, title, suggested description, and keywords—so most episodes are nearly ready at the moment of publish rather than starting from scratch.
Transcription Details and Model Differences
For the transcription model @cf/openai/whisper-large-v3-turbo, Workers AI expects the response audio to arrive as a base64 data URL (responseBase64). The admin UI sends audio that way, and the endpoint passes it through as JSON. For the non-turbo @cf/openai/whisper, raw binary audio/mpeg is acceptable, so the base64 approach is a quirk of this particular turbo model rather than a general requirement of Workers AI.
The turbo model gets used intentionally here because its API supports instruction fields next to the audio—initial_prompt in particular. Feeding context alongside the payload improves recognition of proper nouns and overall transcript accuracy, which matters in a Q&A podcast format like this one.
The Text-to-Speech Route
When a text question comes in, the server route validates both the question text and the chosen voice, then prepends an AI disclosure prefix if it isn't already there. Only then does it hit Workers AI through Cloudflare's AI Gateway:
export const AI_VOICE_DISCLOSURE_PREFIX = `This caller's voice was generated by AI.`
// Cloudflare AI Gateway -> Workers AI:
// https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/workers-ai/{model}
const generated = await synthesizeSpeechWithWorkersAi({
text: withAiDisclosurePrefix(questionText.trim()),
voice: selectedVoice,
model: getEnv().CLOUDFLARE_AI_TEXT_TO_SPEECH_MODEL, // I'm using `@cf/openai/deepgram/aura-2-en`,
})
That gateway layer also proxies the draft pipeline's transcription calls, and it lets the site's AI features route through a single managed endpoint rather than managing infrastructure for each model.
With transcription quality and audio automation handled heuristically, publishing ends up being mostly a matter of editing a draft instead of generating one from scratch.



