<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Toshius Klay</title>
    <description>The latest articles on DEV Community by Toshius Klay (@toshiusklay).</description>
    <link>https://dev.to/toshiusklay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4009655%2Fd87026f3-4a7d-410d-9f76-3672efb11140.png</url>
      <title>DEV Community: Toshius Klay</title>
      <link>https://dev.to/toshiusklay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/toshiusklay"/>
    <language>en</language>
    <item>
      <title>A Developer Workflow for Turning Podcast Episodes into Markdown Blog Posts</title>
      <dc:creator>Toshius Klay</dc:creator>
      <pubDate>Tue, 14 Jul 2026 17:35:03 +0000</pubDate>
      <link>https://dev.to/toshiusklay/a-developer-workflow-for-turning-podcast-episodes-into-markdown-blog-posts-4mik</link>
      <guid>https://dev.to/toshiusklay/a-developer-workflow-for-turning-podcast-episodes-into-markdown-blog-posts-4mik</guid>
      <description>&lt;p&gt;Transcribing my first podcast episode by hand took four hours. My fingers hurt. The draft was full of "[awkward laugh]" and sentences that went nowhere. I almost quit before I built a pipeline that does the hard work for me.&lt;/p&gt;

&lt;p&gt;Now I go from WAV file to published Markdown without typing every "um" and "you know." Here's the exact workflow I use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Extract Clean Mono Audio
&lt;/h2&gt;

&lt;p&gt;Transcription models are picky. Stereo files confuse them. Low sample rates make them hallucinate words. I learned this after Whisper turned a quiet guest channel into nonsense punctuation.&lt;/p&gt;

&lt;p&gt;Before you send anything to the transcriber, strip silence and convert to 16 kHz mono. If you recorded a video, pull the audio track first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; raw_recording.mp3 &lt;span class="nt"&gt;-ar&lt;/span&gt; 16000 &lt;span class="nt"&gt;-ac&lt;/span&gt; 1 &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"silenceremove=start_periods=1:start_duration=0.5:start_threshold=-50dB"&lt;/span&gt; episode_clean.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command forces 16 kHz, collapses to one channel, and strips dead air longer than half a second. It saves you from reading "[long pause]" every thirty seconds. I wish I'd known this sooner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Transcribe with Whisper and Export JSON
&lt;/h2&gt;

&lt;p&gt;I run Whisper locally because I batch episodes and I don't trust cloud services with unreleased content. JSON output is the only format I use. Plain text dumps lose timestamps, and timestamps are your lifeline when you need to verify a technical term at 00:47:12.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;whisper episode_clean.wav &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model&lt;/span&gt; medium &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--language&lt;/span&gt; English &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output_format&lt;/span&gt; json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output_dir&lt;/span&gt; ./transcripts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No GPU? The tiny model still works fine for clean speech. You'll get segments, start times, and confidence scores in a file named &lt;code&gt;episode_clean.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Build a Searchable Draft Script
&lt;/h2&gt;

&lt;p&gt;Raw JSON is unreadable. I wrote a five-minute script to flatten it into a draft I can actually scan.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcripts/episode_clean.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draft.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
        &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it once and you get timestamps next to every line. Suddenly you can spot the exact moment your co-host said the thing about database locks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[00:03:42] The real issue wasn't the database. It was the N+1 query we introduced in the last deploy.
[00:04:15] We didn't catch it in staging because the dataset there is tiny...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can scan, delete fluff, and annotate without touching the original audio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Outline Before You Write
&lt;/h2&gt;

&lt;p&gt;A blog post is not a transcript. I used to paste chunks of dialogue and wonder why readers bounced. Turns out people skim. They want a problem, a fix, and proof. Before I write a single paragraph, I tag the draft with structural markers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Outline&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Hook: The production outage on Tuesday
&lt;span class="p"&gt;-&lt;/span&gt; Context: Why standard ORM patterns failed us
&lt;span class="p"&gt;-&lt;/span&gt; Technical breakdown: Query analysis and indexing
&lt;span class="p"&gt;-&lt;/span&gt; The fix: Cursor pagination and batching
&lt;span class="p"&gt;-&lt;/span&gt; Takeaways: Three rules for ORM performance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the skeleton is solid, the rewrite is fast. You're connecting dots instead of inventing structure at midnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Rewrite Speech into Scannable Prose
&lt;/h2&gt;

&lt;p&gt;Spoken language is loose. You'll find filler words, fragments, and hedges like "kind of" and "sort of." Your job is to tighten them without making the speaker sound like a press release.&lt;/p&gt;

&lt;p&gt;Before:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;So, yeah, basically what happened was we, uh, we pushed this change and then, like, the latency just totally exploded and we were like, okay, this is bad.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We pushed the change. Latency exploded immediately. We knew we had a problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I used to over-edit and kill the voice. Don't do that. Keep the honesty. Drop the noise. Break long thoughts into short paragraphs. Use bold for the terms a reader is scanning for. If two speakers debated a point, turn it into a comparison list. It reads faster than dialogue formatting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Add Dev.to-Ready Frontmatter and Metadata
&lt;/h2&gt;

&lt;p&gt;Before I paste into the CMS, I lock down metadata. Good frontmatter makes the post discoverable and keeps my formatting consistent across platforms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;N+1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Query&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Took&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Down&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Our&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;API"&lt;/span&gt;
&lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database, performance, backend, podcast&lt;/span&gt;
&lt;span class="na"&gt;canonical_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://yourpodcast.com/episodes/42&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use your primary keyword in the first 100 words and in at least one H2. Keep the slug readable. If you reference a repo or tool, drop an inline link. Internal links to related episodes keep people around.&lt;/p&gt;

&lt;p&gt;Dev.to renders your title as the H1, so start the body with H2. Nest H3 under it. Don't skip levels. Search engines notice, and screen readers break.&lt;/p&gt;

&lt;p&gt;One trick I stole from smarter writers: embed a short audio player or a timestamped link back to the original episode. Some people want the full tone. Let them jump to the minute they need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Final Polish and Publish
&lt;/h2&gt;

&lt;p&gt;I store drafts in Git, so I run everything through a formatter before publishing. Insane line lengths make diffs unreadable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prettier &lt;span class="nt"&gt;--prose-wrap&lt;/span&gt; always &lt;span class="nt"&gt;--write&lt;/span&gt; post.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Preview on Dev.to before you hit publish. Check that code blocks scroll and headings nest. Audio content is rich. Written content is searchable. Combine the two, and one recording keeps working while you sleep.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>productivity</category>
      <category>podcast</category>
      <category>writing</category>
    </item>
    <item>
      <title>Transcribe Audio to Text Like a Developer: From File to Final Text</title>
      <dc:creator>Toshius Klay</dc:creator>
      <pubDate>Tue, 14 Jul 2026 17:24:49 +0000</pubDate>
      <link>https://dev.to/toshiusklay/transcribe-audio-to-text-like-a-developer-from-file-to-final-text-38cd</link>
      <guid>https://dev.to/toshiusklay/transcribe-audio-to-text-like-a-developer-from-file-to-final-text-38cd</guid>
      <description>&lt;p&gt;You've got a two-hour architecture review sitting in your Downloads folder. Last month, mine was a 94-minute mess where three engineers talked over each other to debug a stuck Kafka consumer. I needed that in text. Not because I enjoy typing, but because text is searchable, diff-able, and way easier to share than a raw MP3. Manual transcription burns hours you don't have. Automated tools get you 90% there, but the last 10% is what separates a usable transcript from word salad.&lt;/p&gt;

&lt;p&gt;For developers, text wins over audio every time. You can't grep a WAV file. You can't copy a function name from a podcast without listening to fifteen minutes of chatter. Once audio is text, you can index it, run diffs, feed it to an LLM, or generate subtitles. A single recording turns into searchable meeting notes, blog drafts, or captioned tutorials. It also makes content accessible to screen readers and non-native speakers.&lt;/p&gt;

&lt;p&gt;Before you upload anything, pick your approach. There are three realistic options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automated APIs or local models.&lt;/strong&gt; Tools like OpenAI Whisper running locally, or cloud speech APIs. Fast, cheap, and fine for clear audio with minimal crosstalk.&lt;br&gt;
&lt;strong&gt;Manual transcription.&lt;/strong&gt; You listen, you type. Still the best choice for courtroom-level accuracy or audio recorded in a crowded hallway.&lt;br&gt;
&lt;strong&gt;Hybrid.&lt;/strong&gt; Automated first, then a quick editorial pass. This is the default for technical content and the workflow I'll break down below.&lt;/p&gt;

&lt;p&gt;If your recording is a screen share, a meeting, or a solo voice memo, go automated. If two people shouted over each other in a cafe, manual might be faster than fixing chaos.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step-by-step workflow
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Prepare the audio
&lt;/h3&gt;

&lt;p&gt;Garbage in, garbage out. Models struggle with low bitrate, stereo separation, and background hum. Normalize your file before you process it.&lt;/p&gt;

&lt;p&gt;Most tools accept MP3, WAV, M4A, MP4, and OGG. For speech recognition, mono WAV at 16 kHz is the safest bet. Use ffmpeg to clean things up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Convert to 16kHz mono WAV&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-ar&lt;/span&gt; 16000 &lt;span class="nt"&gt;-ac&lt;/span&gt; 1 &lt;span class="nt"&gt;-c&lt;/span&gt;:a pcm_s16le cleaned.wav

&lt;span class="c"&gt;# Strip leading silence to save processing time&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; cleaned.wav &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"silenceremove=start_periods=1:start_duration=0.5:start_threshold=-50dB"&lt;/span&gt; final.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your levels. If the waveform looks like a flat line or a brick wall, fix it before you waste a transcription run. I learned this the hard way after Whisper produced five minutes of hallucinated text from a track that was only audible in the left stereo channel. Mono fixes that.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Generate the raw transcript
&lt;/h3&gt;

&lt;p&gt;You have two practical paths. Run Whisper locally if you care about privacy and cost. Hit an API if you want zero setup.&lt;/p&gt;

&lt;p&gt;Local Python with Whisper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;whisper&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;whisper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# or "small", "medium", "large"
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transcribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloud API route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;audio_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transcriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;whisper-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;audio_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;response_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A thirty-minute file usually finishes in under a minute locally on a modern laptop. Cloud is roughly real-time or faster.&lt;/p&gt;

&lt;p&gt;I used to run &lt;code&gt;base&lt;/code&gt; for everything. Then I watched it turn "Kubernetes deployment" into "coorneddies deployment" during a mumbled standup. For anything with proper nouns, CLI commands, or made-up product names, &lt;code&gt;medium&lt;/code&gt; or &lt;code&gt;large&lt;/code&gt; is worth the extra seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Capture structure
&lt;/h3&gt;

&lt;p&gt;Don't just dump the text string. The JSON output contains segments with start and end times. You will need those for subtitles or for referencing specific moments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# or transcript.segments depending on your client
&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store this as an intermediate JSON file. It is much easier to format later than re-running inference.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Clean with code
&lt;/h3&gt;

&lt;p&gt;Raw transcripts are messy. Fillers, repeated words, and homophone errors creep in. Here is an actual snippet from that Kafka call:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[14:23] okay so um if you look at the um the logs it looks like the uh consumer group is like stuck and you know the lag is just um growing&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;My first thought was a regex to nuke every "um", "uh", "like", and "you know". I tried it. Then I ran it on a different transcript about Postgres and watched "you know, use a LIKE clause" turn into "use a clause". Not great.&lt;/p&gt;

&lt;p&gt;Now I keep cleanup conservative. I only strip obvious stumbles, and I leave sentence words alone. Here is what actually runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Only remove isolated filler sounds, not words that appear in normal sentences
&lt;/span&gt;    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\s*\b(uh|um)\b[,.;:]?\s*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Collapse multiple spaces
&lt;/span&gt;    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\s+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Fix spaces before punctuation
&lt;/span&gt;    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\s+([.,;!?])&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Apply to each segment
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clean_chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will still need to eyeball technical terms. Whisper loves to hallucinate "main" as "mane" or turn "kubectl" into "cube cuddle" if the speaker trails off. A quick grep for your known terms catches the worst offenders.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Add timestamps and speaker labels
&lt;/h3&gt;

&lt;p&gt;If you are building subtitles, export SRT or VTT. If you are building notes, Markdown with timestamps works better.&lt;/p&gt;

&lt;p&gt;Markdown formatter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;to_markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Meeting Notes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;# &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;02.0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;02.0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]** &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Speaker labels are harder. Whisper does basic speaker diarization in newer versions, but it is not perfect. For two-person interviews, you can often infer the speaker from context. For group meetings, consider a dedicated diarization tool like pyannote.audio, then merge the outputs.&lt;/p&gt;

&lt;p&gt;I tried pyannote on a five-person standup once. It worked, but the dependency chain was heavier than the transcript itself. For 1:1s, I skip it. For all-hands, I bite the bullet.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Export and reuse
&lt;/h3&gt;

&lt;p&gt;Match the format to the destination.&lt;/p&gt;

&lt;p&gt;Markdown for blog posts or documentation.&lt;br&gt;
SRT or VTT for video subtitles.&lt;br&gt;
Plain text for LLM context windows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Quick plain-text dump
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One recording should have multiple lives. Turn a tech talk transcript into a docs page, a Twitter thread, and a set of searchable meeting notes without re-listening to the whole thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accuracy and gotchas
&lt;/h2&gt;

&lt;p&gt;Even large models hallucinate on jargon, acronyms, and numbers. Expect to spend five to ten minutes editing per thirty minutes of clean audio. Budget more if speakers overlap or if the content is heavy on CLI commands and made-up product names.&lt;/p&gt;

&lt;p&gt;If your API supports it, pass a prompt with domain context. For Whisper, the &lt;code&gt;initial_prompt&lt;/code&gt; parameter lets you seed terms like "Next.js, Redis, and kubectl" so the model knows the vocabulary ahead of time.&lt;/p&gt;

&lt;p&gt;During that Kafka review, seeding "consumer lag, partition, and rebalance" stopped Whisper from inventing "consumer leg" and "reborn". Small prompt, big difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final checklist
&lt;/h2&gt;

&lt;p&gt;Before you publish or archive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Correct all function names, CLI flags, and API endpoints&lt;/li&gt;
&lt;li&gt;Verify that timestamps land near the actual speaker changes&lt;/li&gt;
&lt;li&gt;Confirm speaker labels if you added them manually&lt;/li&gt;
&lt;li&gt;Pick the right export format for your pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Transcription is not magic. It is just another data transformation. Prep your audio, run inference, clean the output, and ship it. Your future self will thank you when you can grep last month's architecture decision in under a second.&lt;/p&gt;

</description>
      <category>transcription</category>
      <category>python</category>
      <category>audio</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Transcribe Audio to Text: A Practical Workflow for Developers</title>
      <dc:creator>Toshius Klay</dc:creator>
      <pubDate>Tue, 14 Jul 2026 17:24:24 +0000</pubDate>
      <link>https://dev.to/toshiusklay/how-to-transcribe-audio-to-text-a-practical-workflow-for-developers-54hc</link>
      <guid>https://dev.to/toshiusklay/how-to-transcribe-audio-to-text-a-practical-workflow-for-developers-54hc</guid>
      <description>&lt;p&gt;You've got a 45-minute meeting recording sitting on your desktop. Maybe it's a podcast raw file, or an interview you did for a project. You need it in text, now. Searchable. Timestamped. Clean enough to paste into docs or shove into an LLM context window.&lt;/p&gt;

&lt;p&gt;Typing it out by hand sucks. It takes about four times the audio length, and your fingers will hate you. I spent a rainy Tuesday afternoon doing exactly that once. Never again.&lt;/p&gt;

&lt;p&gt;Transcription is the bridge between spoken content and text you can actually use. Once audio is structured text, you can grep it, diff it, subtitle it, or feed it to automation. This is the workflow I use now: prep the file, run speech-to-text, clean the output, and export to formats developers actually need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why audio to text still matters
&lt;/h2&gt;

&lt;p&gt;Video gets all the hype, but audio is where the actual work lives. Standups, conference talks, voice memos, screencasts. Text makes all of that scannable. It improves accessibility. It creates training data. It lets you pull an exact quote from a user interview without scrubbing through a waveform like you're tuning an old radio.&lt;/p&gt;

&lt;p&gt;For developers, transcripts turn unstructured rambling into structured assets. Meeting minutes become markdown files. Tutorial audio becomes documentation you can version control. You can index transcripts in Elasticsearch, embed them for RAG pipelines, or diff versions to see how your docs evolved. I keep a folder of interview transcripts that I treat as a searchable database. Way faster than relistening.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose your approach
&lt;/h2&gt;

&lt;p&gt;There are two broad paths: automated and manual. Most real workflows use both, because neither is perfect alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automated transcription&lt;/strong&gt; means models like OpenAI Whisper, cloud APIs, or local engines. It's fast, and for clear audio it's surprisingly good. The second someone crosstalks, mumbles, or has a heavy accent, accuracy tanks. I've seen Whisper hallucinate entire sentences because a truck drove by.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual transcription&lt;/strong&gt; is just you, a text editor, and a media player. It's tedious. I only do this when accuracy is non-negotiable, like legal stuff or medical content where a wrong word matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The hybrid method&lt;/strong&gt; is the sweet spot. Run automation first, then edit. You get 80 to 90 percent of the way there in minutes. Then you spend human time on polish instead of grunt work. That's where I live now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step: from raw audio to clean text
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Prepare your audio file
&lt;/h3&gt;

&lt;p&gt;Garbage in, garbage out. Models perform best on clean, single-channel audio at 16 kHz. If your source is a noisy Zoom call or a multi-speaker session, do a quick cleanup pass.&lt;/p&gt;

&lt;p&gt;Most tools accept MP3, WAV, M4A, MP4, AAC, FLAC, OGG, WEBM. If you're scripting a pipeline, standardize on WAV or FLAC for lossless input. ffmpeg handles the conversion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Extract audio from video, downmix to mono, resample to 16kHz&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; recording.mp4 &lt;span class="nt"&gt;-ar&lt;/span&gt; 16000 &lt;span class="nt"&gt;-ac&lt;/span&gt; 1 &lt;span class="nt"&gt;-c&lt;/span&gt;:a pcm_s16le cleaned.wav

&lt;span class="c"&gt;# Strip leading/trailing silence to save processing time&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; cleaned.wav &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"silenceremove=start_periods=1:start_duration=0.5:start_threshold=-50dB"&lt;/span&gt; trimmed.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your levels. If one speaker whispers and another shouts, normalize it. Consistent volume drops the word-error rate more than you'd think. I learned this the hard way with a podcast where the host was ten decibels quieter than the guest. The transcript was a mess until I fixed the gain.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Transcribe with code
&lt;/h3&gt;

&lt;p&gt;For developers, the most transparent option is running Whisper locally or hitting an API. You control the model, the parameters, and the output format. No black boxes.&lt;/p&gt;

&lt;p&gt;Here's a minimal Python snippet using the &lt;code&gt;openai-whisper&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;whisper&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;whisper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transcribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trimmed.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Save raw text
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Save segments with timestamps as JSON
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Need subtitles? Generate an SRT directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;whisper.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_writer&lt;/span&gt;

&lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_writer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;srt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subtitles.srt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloud services like AssemblyAI or Deepgram offer speaker diarization out of the box. That's handy when you need to label who said what in a meeting. Just watch the API costs. They add up fast if you're processing hours of audio, and nothing hurts like an unexpected bill because you forgot to delete a test file.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Edit the raw transcript
&lt;/h3&gt;

&lt;p&gt;Automated output is never final. You'll find missing punctuation, homophone errors, and completely invented words. My favorite was when Whisper decided a speaker said "Kubernetes" but meant "commuter news."&lt;/p&gt;

&lt;p&gt;The fastest cleanup method is reading while listening at 1.5x speed. Fix spelling, break up wall-of-text paragraphs, and correct technical terms. Your ears catch what your eyes miss.&lt;/p&gt;

&lt;p&gt;If you're building a pipeline, add a programmatic first pass. A simple regex strips filler words like "um" and "uh" if you want a clean read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\b(um|uh|like,|you know,)\b[,]?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\s+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# collapse extra spaces
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decide on verbatim versus clean. Verbatim keeps false starts and repeats. Clean is easier to read. Match the style to the destination. Documentation wants clean. User research quotes might need verbatim so you don't misrepresent someone's intent.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Add structure
&lt;/h3&gt;

&lt;p&gt;Raw text is brutal to scan. Add speaker labels and timestamps.&lt;/p&gt;

&lt;p&gt;If Whisper gave you segments, map them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;04.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;04.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For multi-speaker recordings, use diarization. Libraries like &lt;code&gt;pyannote.audio&lt;/code&gt; integrate with Whisper to label speakers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Pseudo-code for diarization pipeline
&lt;/span&gt;&lt;span class="n"&gt;diarization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pyannote_pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trimmed.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speaker&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;diarization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;itertracks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yield_label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Speaker &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;speaker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not every project needs this. But when you're turning a standup recording into structured minutes, speaker labels are essential. I skipped this once and spent twenty minutes trying to figure out which engineer suggested the database migration.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Export for your stack
&lt;/h3&gt;

&lt;p&gt;Transcripts are useless if they're trapped in a weird format. Export to whatever your workflow eats.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Markdown&lt;/strong&gt; for docs, blogs, and Git repos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON&lt;/strong&gt; for downstream parsing, LLM context windows, or search indexing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SRT/VTT&lt;/strong&gt; for video subtitles and web players.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a quick script to convert Whisper segments to markdown with timestamps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;# Meeting Transcript&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;** &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transcript.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writelines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store the raw JSON alongside the cleaned text. Future you will want those timestamps when someone asks, "What did they say at minute twelve?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Accuracy tips that actually matter
&lt;/h2&gt;

&lt;p&gt;Model choice matters, but audio hygiene matters more.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use a decent microphone.&lt;/strong&gt; A $20 lavalier beats a laptop mic every time. I use one for all my calls now, and the transcription accuracy jumped noticeably.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce echo.&lt;/strong&gt; Record in smaller rooms or use software noise suppression before transcription. Bare walls are the enemy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid overlapping speech.&lt;/strong&gt; Diarization breaks when people talk over each other, and manual cleanup becomes a nightmare.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spell out acronyms.&lt;/strong&gt; Models often hallucinate letter sequences. I keep a post-processing dictionary for recurring product names and jargon. It saves me from fixing "AWS" becoming "Ay-Double-U-Ess" or some other nonsense.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting it into practice
&lt;/h2&gt;

&lt;p&gt;A solid transcription workflow is just a pipeline. Audio enters, text leaves. Prep, inference, cleanup, export. Script what you can. Automate the boring parts. Keep a human review step for anything public-facing.&lt;/p&gt;

&lt;p&gt;Start with one file. Convert it, run Whisper, clean the output, and export to markdown. Time yourself. You'll spot your bottleneck fast, whether it's audio quality, editing, or formatting. Fix that first. The rest is just repetition.&lt;/p&gt;

</description>
      <category>audio</category>
      <category>python</category>
      <category>productivity</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>How to Turn Interview Audio into Analysis-Ready Transcripts</title>
      <dc:creator>Toshius Klay</dc:creator>
      <pubDate>Fri, 03 Jul 2026 14:10:02 +0000</pubDate>
      <link>https://dev.to/toshiusklay/how-to-turn-interview-audio-into-analysis-ready-transcripts-289f</link>
      <guid>https://dev.to/toshiusklay/how-to-turn-interview-audio-into-analysis-ready-transcripts-289f</guid>
      <description>&lt;p&gt;Last year I transcribed forty hours of developer interviews by hand because I didn't trust the AI tools. My wrists hurt. I missed a deadline. I still botched a quote. One participant said they hated Docker. I typed "loved Docker." That single error skewed my feature priority matrix for a week.&lt;/p&gt;

&lt;p&gt;Now I use a workflow that is boring, repeatable, and won't let garbage audio wreck your dataset. It goes like this.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Record audio that won't wreck your accuracy
&lt;/h3&gt;

&lt;p&gt;I learned this in a glass-walled conference room with a laptop mic. The echo was so bad that "Git" became "get" for two straight hours. I had to guess context on twelve different lines. Never again.&lt;/p&gt;

&lt;p&gt;Use a directional mic or a decent USB interface. Laptop mics grab keyboard clatter and fan hum. Record in a small, carpeted room if you can. Hard surfaces bounce sound and confuse speech engines.&lt;/p&gt;

&lt;p&gt;For remote sessions, make participants wear headphones. It stops their speakers from bleeding into your recording. Ask people not to talk over each other. If you need speaker labels later, have them introduce themselves at the top.&lt;/p&gt;

&lt;p&gt;If you are pulling audio from a Zoom recording, normalize it first. ffmpeg handles this in one pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; interview.mp4 &lt;span class="nt"&gt;-vn&lt;/span&gt; &lt;span class="nt"&gt;-acodec&lt;/span&gt; pcm_s16le &lt;span class="nt"&gt;-ar&lt;/span&gt; 16000 &lt;span class="nt"&gt;-ac&lt;/span&gt; 1 interview.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mono WAV at 16kHz. Speech engines prefer it. Mono removes stereo separation weirdness, and 16kHz covers the vocal range without bloating your file.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Generate the draft automatically
&lt;/h3&gt;

&lt;p&gt;I upload the file to whatever engine I'm using that month. Lately it is Whisper.cpp running locally because I got paranoid about participant data hitting cloud APIs. Last year I burned through Otter credits. The service matters less than the settings.&lt;/p&gt;

&lt;p&gt;I pick verbatim mode when I am looking for hesitation or power dynamics. It keeps the ums, false starts, and pauses. I pick clean mode for thematic analysis or when I am handing quotes to a PM who just wants the point, not the verbal stumbles.&lt;/p&gt;

&lt;p&gt;If the tool offers auto-detect for language, verify it. I once ran a mixed English-German session and the engine tagged the whole file as Dutch. The gibberish propagated for pages before I noticed.&lt;/p&gt;

&lt;p&gt;Do not treat the raw file as final. Automated transcripts hit maybe 85-95% accuracy in ideal conditions. Accents, jargon, and crosstalk drop that number fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Review like you are being audited
&lt;/h3&gt;

&lt;p&gt;This is the step I used to skip. It cost me.&lt;/p&gt;

&lt;p&gt;Open the draft next to the audio. Play it at 1.0x or 1.25x. Fix these exact things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Misheard domain terms.&lt;/strong&gt; "React" becomes "reactant." "Kubernetes" turns into phonetic mush. These look tiny but destroy coding accuracy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speaker labels.&lt;/strong&gt; Auto-tools merge speakers during crosstalk. Label each turn yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crosstalk and drops.&lt;/strong&gt; When two people talk at once, the transcript may mash both voices into nonsense. Mark gaps with &lt;code&gt;[inaudible]&lt;/code&gt; so you do not code silence as agreement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Punctuation for meaning.&lt;/strong&gt; A missing comma can flip enthusiasm into sarcasm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nonverbal cues only if they matter.&lt;/strong&gt; I mark laughter or long pauses with a standard notation. I skip them if I am just hunting for feature requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the template I paste into my editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[00:03:15] Interviewer: Walk me through how you deploy to production.
[00:03:18] Participant: Usually we just run the script, wait for the build, and then... actually, sometimes we check the logs first.
[00:03:24] [pause 3s]
[00:03:27] Participant: If it's a Friday, we don't deploy at all.
[00:03:30] [laughter]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ISO timestamps and bracketed tags. I keep lines under 100 characters so they import cleanly into qualitative tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Format for your analysis stack
&lt;/h3&gt;

&lt;p&gt;I've imported transcripts into NVivo, Dovetail, Atlas.ti, and plain Git repos. Consistency matters more than aesthetics. NVivo chokes on inconsistent timestamps. Dovetail gets weird if your speaker labels change format between files.&lt;/p&gt;

&lt;p&gt;Standardize these before you call it done:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speaker names.&lt;/strong&gt; Pick "Interviewer / Participant" or "P1 / P2" and stick with it across every file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paragraph breaks.&lt;/strong&gt; Start a new paragraph when the topic shifts, not just when someone stops talking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timestamps.&lt;/strong&gt; Drop them every 30-60 seconds, or at every speaker turn if your tool requires it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your team works async across time zones, timestamps are the only way another researcher can pull the original clip for context.&lt;/p&gt;

&lt;p&gt;When I store transcripts in Git for team review, I add YAML frontmatter so we can search later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;onboarding-research&lt;/span&gt;
&lt;span class="na"&gt;session_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2024-06-12_p5&lt;/span&gt;
&lt;span class="na"&gt;participant_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;P5&lt;/span&gt;
&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;semi-structured-interview&lt;/span&gt;
&lt;span class="na"&gt;transcript_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clean&lt;/span&gt;
&lt;span class="na"&gt;duration_minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns a folder of text files into something you can actually query.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Export and version your files
&lt;/h3&gt;

&lt;p&gt;Save two copies. Every time.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Raw automated output. This is your audit trail.&lt;/li&gt;
&lt;li&gt;Corrected transcript with final labels and formatting.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Export depends on your pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TXT or MD for coding platforms.&lt;/li&gt;
&lt;li&gt;DOCX if your team lives in Word comments.&lt;/li&gt;
&lt;li&gt;JSON if you are feeding them into a custom NLP pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep both. Six months later, when a stakeholder asks if that brutal quote is real, you need to trace it back to the source audio without starting from zero.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verbatim vs. clean: pick one and lock it in
&lt;/h3&gt;

&lt;p&gt;I once switched formats mid-project because I got lazy. I had to re-review every file. Do not be me.&lt;/p&gt;

&lt;p&gt;Use verbatim when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are studying language patterns, pauses, or interaction dynamics.&lt;/li&gt;
&lt;li&gt;Your methodology is discourse or conversation analysis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use clean when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are hunting for themes, pain points, or feature requests.&lt;/li&gt;
&lt;li&gt;A PM or executive will read it and only cares about the content, not the delivery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of my UX work uses clean transcripts. Academic work usually needs verbatim. You can generate both. Keep the verbatim file as your source of truth, then derive a cleaned copy for reporting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Closing the loop
&lt;/h3&gt;

&lt;p&gt;Good transcription is a quality gate for your whole project. A transcript with mislabeled speakers or missing context will send your coding sideways. You will not notice until you are writing findings at 11 PM and questioning your sanity.&lt;/p&gt;

&lt;p&gt;Record clean audio. Generate a draft. Review it line by line with the original playing. Format it consistently. Lock in your raw and edited versions.&lt;/p&gt;

&lt;p&gt;Your future self, staring at fifty coded segments at 11 PM, will thank you.&lt;/p&gt;

</description>
      <category>userresearch</category>
      <category>transcription</category>
      <category>ux</category>
      <category>researchmethods</category>
    </item>
    <item>
      <title>Build a Reliable AI Transcription Pipeline: A Developer’s Field Guide</title>
      <dc:creator>Toshius Klay</dc:creator>
      <pubDate>Fri, 03 Jul 2026 14:09:08 +0000</pubDate>
      <link>https://dev.to/toshiusklay/build-a-reliable-ai-transcription-pipeline-a-developers-field-guide-31ba</link>
      <guid>https://dev.to/toshiusklay/build-a-reliable-ai-transcription-pipeline-a-developers-field-guide-31ba</guid>
      <description>&lt;p&gt;You shipped the feature last Tuesday. Upload audio, hit transcribe, display text. By Friday your users were complaining about garbled timestamps, missing speaker labels, and a bill that made your CFO flinch. Raw API output isn't enough for production. You need a pipeline.&lt;/p&gt;

&lt;p&gt;Most speech-to-text tutorials stop at &lt;code&gt;curl&lt;/code&gt;. They don't cover audio preprocessing, model selection, or how to clean up the mess that comes back when three people talk over each other in a Zoom recording. This guide walks through what actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the sausage gets made
&lt;/h2&gt;

&lt;p&gt;AI transcription isn't a black box you lob files into. It's a chain of decisions. Audio gets normalized, chunked, fed to an acoustic model, and reconstructed into text. Then a language model guesses punctuation and paragraphs. If you want speaker labels, a separate diarization model runs alongside it.&lt;/p&gt;

&lt;p&gt;The pipeline looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Audio input and format normalization&lt;/li&gt;
&lt;li&gt;Chunking and resampling&lt;/li&gt;
&lt;li&gt;Model inference (ASR)&lt;/li&gt;
&lt;li&gt;Post-processing (punctuation, formatting)&lt;/li&gt;
&lt;li&gt;Speaker diarization (optional but painful)&lt;/li&gt;
&lt;li&gt;Export and storage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Skip step 1 or 2 and you'll pay for step 3 twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Fix your audio before it hits the API
&lt;/h2&gt;

&lt;p&gt;Developers love to send whatever comes out of the browser's &lt;code&gt;&amp;lt;input type="file"&amp;gt;&lt;/code&gt; straight to the cloud. Don't. APIs have preferences, and your users upload garbage.&lt;/p&gt;

&lt;p&gt;Standardize on these specs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Format&lt;/strong&gt;: Mono WAV or FLAC. Stereo confuses some models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample rate&lt;/strong&gt;: 16 kHz or 24 kHz. Resample if you have to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bitrate&lt;/strong&gt;: 16-bit PCM. No 32-bit float surprises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preprocessing&lt;/strong&gt;: Normalize loudness to -16 LUFS. Strip silence longer than 2 seconds if your ASR bills by duration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use ffmpeg. It's ugly but it's everywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.m4a &lt;span class="nt"&gt;-ar&lt;/span&gt; 16000 &lt;span class="nt"&gt;-ac&lt;/span&gt; 1 &lt;span class="nt"&gt;-sample_fmt&lt;/span&gt; s16 output.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one line fixes half the accuracy issues you'll see in production. It converts variable-bitrate user uploads into something the model actually expects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Pick the right engine for the job
&lt;/h2&gt;

&lt;p&gt;Not all transcription APIs are the same. They optimize for different things. Here's the honest breakdown:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI Whisper (API or self-hosted)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Great accuracy across languages&lt;/li&gt;
&lt;li&gt;Cheap on API, cheaper if you run &lt;code&gt;base&lt;/code&gt; or &lt;code&gt;small&lt;/code&gt; locally on a CPU&lt;/li&gt;
&lt;li&gt;No native speaker diarization&lt;/li&gt;
&lt;li&gt;Slower than cloud providers on long files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Google Cloud Speech-to-Text&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excellent real-time streaming via gRPC&lt;/li&gt;
&lt;li&gt;Good speaker diarization (up to 8 speakers in some configs)&lt;/li&gt;
&lt;li&gt;Pricier, especially with premium models like &lt;code&gt;latest_long&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Needs audio in specific containers (LINEAR16, MULAW, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AWS Transcribe&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solid medical and call analytics variants&lt;/li&gt;
&lt;li&gt;Speaker partitioning works but lags behind Google&lt;/li&gt;
&lt;li&gt;Turnaround is batch-oriented; real-time exists but feels bolted on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Deepgram Nova&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast. Like, actually fast.&lt;/li&gt;
&lt;li&gt;Good at messy audio (background noise, accents)&lt;/li&gt;
&lt;li&gt;Speaker diarization is decent but costs extra tiers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most apps, Whisper hits the sweet spot of cost and accuracy. If you need live captions during a WebRTC call, Google Cloud's streaming API is hard to beat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Code a resilient batch pipeline
&lt;/h2&gt;

&lt;p&gt;Here's a minimal Python worker that handles the full flow. It preprocesses with ffmpeg, sends to an API, and structures the output. Swap in your provider of choice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalize_audio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ffmpeg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-i&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-ar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-ac&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-sample_fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transcribe_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;audio_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Example using Whisper API; swap for Deepgram, AWS, etc.
&lt;/span&gt;        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.openai.com/v1/audio/transcriptions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;whisper-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response_format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verbose_json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp_granularities[]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;clean_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;normalize_audio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clean_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transcribe_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clean_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Post-process: rebuild transcript with word-level timestamps
&lt;/span&gt;    &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;words&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="n"&gt;segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;current_segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;current_segment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="c1"&gt;# New sentence heuristic
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="n"&gt;current_segment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_segment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;current_segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what actually matters in that snippet. We ask for &lt;code&gt;verbose_json&lt;/code&gt; and word timestamps. That granularity lets you rebuild sentences cleanly instead of accepting a wall of text. We also normalize audio before upload. Don't let users foot the bill for weird codecs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Add speaker labels without losing your mind
&lt;/h2&gt;

&lt;p&gt;Speaker diarization is still the hardest part of transcription. Most APIs that offer it charge more, and the accuracy drops when speakers interrupt each other.&lt;/p&gt;

&lt;p&gt;If your provider supports it, enable it at the API level. If not, you'll need a separate model like &lt;code&gt;pyannote.audio&lt;/code&gt; or AWS's channel-based routing.&lt;/p&gt;

&lt;p&gt;A cheap heuristic that works for interviews: force single-channel audio and ask the API to partition speakers. If that fails, fall back to a secondary diarization pass on the normalized file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Pseudo-code for dual-pass pipeline
&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transcribe_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clean_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;diarization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_pyannote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clean_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Merge word timestamps with speaker segments
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;words&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;speaker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;diarization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_speaker_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;speaker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;speaker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not perfect. You'll still need manual review for content that ships to customers. But it gets you 90% of the way there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Format for humans, not machines
&lt;/h2&gt;

&lt;p&gt;Nobody wants a raw JSON dump. Your end users want paragraphs, timestamps they can click, and speaker names.&lt;/p&gt;

&lt;p&gt;Structure your final output like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"segments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"speaker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The API returns words, but humans read sentences."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Export to SRT if you're building subtitles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
00:00:00,000 --&amp;gt; 00:00:04,500
The API returns words, but humans read sentences.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And always store the raw API response. When a user reports an error, you'll want to replay it without burning more credits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tradeoff framework you actually need
&lt;/h2&gt;

&lt;p&gt;You'll face three knobs in production. Here's how to turn them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed vs. accuracy&lt;/strong&gt;&lt;br&gt;
Fast modes use smaller models. Use them for search indexing and internal notes. Use best-quality models for customer-facing captions and compliance logs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost vs. precision&lt;/strong&gt;&lt;br&gt;
Batch processing is cheaper per minute than real-time. If you don't need live captions, don't pay for streaming. Reserve premium engines (Google's &lt;code&gt;latest_long&lt;/code&gt;, Nova-2) for your highest-value content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speaker labels vs. complexity&lt;/strong&gt;&lt;br&gt;
Don't enable diarization unless someone reads the labels. If it's just a giant blob of text for full-text search, skip it. You'll save money and processing time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timestamps drift&lt;/strong&gt; on long files over 30 minutes. Chunk at 10-minute boundaries if your API allows it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code switching&lt;/strong&gt; (mixing languages in one file) breaks most monolingual models. Split by language if possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Profanity filters&lt;/strong&gt; in enterprise APIs will asterisk out words in medical or legal transcripts. Disable them if your provider lets you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebRTC audio&lt;/strong&gt; is often sampled at 48 kHz stereo. Downsample before sending.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Building with AI transcription isn't hard. Building it so it doesn't break in production is. Preprocess your audio, pick an engine that matches your latency budget, and post-process the output into something readable. Treat the API like a component, not a magic wand.&lt;/p&gt;

&lt;p&gt;Your users will thank you. Your wallet will too.&lt;/p&gt;

</description>
      <category>speechtotext</category>
      <category>ai</category>
      <category>api</category>
      <category>audioprocessing</category>
    </item>
  </channel>
</rss>
