<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[AudioVText]]></title><description><![CDATA[AudioVText]]></description><link>https://audiovtext.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ad641fcd6a35e862c51fbe5/6442f46d-8a1b-4d73-8e85-d9dee32350f5.png</url><title>AudioVText</title><link>https://audiovtext.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 24 Oct 2026 09:04:42 GMT</lastBuildDate><atom:link href="https://audiovtext.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Preparing audio for speech-to-text with ffmpeg: what to change and what to leave alone]]></title><description><![CDATA[ffmpeg commands that help before transcription (extract, convert, split channels, normalize) and two edits that quietly break timecodes.]]></description><link>https://audiovtext.hashnode.dev/preparing-audio-for-speech-to-text-with-ffmpeg-what-to-change-and-what-to-leave-alone</link><guid isPermaLink="true">https://audiovtext.hashnode.dev/preparing-audio-for-speech-to-text-with-ffmpeg-what-to-change-and-what-to-leave-alone</guid><category><![CDATA[FFmpeg]]></category><category><![CDATA[audio]]></category><category><![CDATA[Speech Recognition]]></category><category><![CDATA[Transcription]]></category><category><![CDATA[whisper]]></category><dc:creator><![CDATA[Andrey Petrov]]></dc:creator><pubDate>Mon, 19 Oct 2026 16:24:17 GMT</pubDate><content:encoded><![CDATA[<p>Most recordings can go to a speech-to-text system exactly as they are. A few ffmpeg commands still earn their place: they shrink an upload, keep two speakers apart, or prepare a file for a model you run yourself. Two other edits look harmless and quietly ruin timecodes. This post covers both groups.</p>
<p>We build <a href="https://audiovtext.com/">AudioVText</a>, a service that transcribes audio and video into text. AudioVText supports 99 languages and is tuned for Russian, its primary language. The commands below are plain ffmpeg and work the same with any recognizer.</p>
<h2>Look at the file before changing it</h2>
<p>ffprobe shows what is inside a recording, one line per stream:</p>
<pre><code class="language-bash">ffprobe -v error -show_entries stream=index,codec_type,codec_name,sample_rate,channels:format=duration -of compact meeting.mp4
</code></pre>
<p>Four fields matter here. <code>codec_name</code> tells you whether the audio can be copied as is. <code>channels</code> tells you whether there is a stereo track worth keeping. <code>sample_rate</code> and <code>duration</code> tell you what size to expect after conversion. A screen recording often carries AAC audio at 48 kHz in stereo, even when both channels hold the same signal.</p>
<h2>Extract the audio only when size matters</h2>
<p>A video file is mostly picture. If the upload is slow or hits a size limit, take the audio track out without re-encoding it:</p>
<pre><code class="language-bash">ffmpeg -i meeting.mp4 -vn -c:a copy meeting.m4a
</code></pre>
<p><code>-vn</code> drops the video, and <code>-c:a copy</code> keeps the original audio bytes, so nothing is lost and the command finishes in seconds. The container has to match the codec: AAC goes into <code>.m4a</code>, Opus into <code>.opus</code> or <code>.webm</code>. If they don't match, ffmpeg stops with an error instead of writing a broken file.</p>
<p>When the file must get smaller still, re-encode the speech to mono Opus:</p>
<pre><code class="language-bash">ffmpeg -i meeting.mp4 -vn -ac 1 -c:a libopus -b:a 32k meeting.opus
</code></pre>
<p>At 32 kbit/s an hour of audio takes about 14.4 MB. For uploads to AudioVText none of this is required: the service accepts the video file itself, or a link to YouTube, VK Video, Rutube or OK.ru, and reads the audio from there.</p>
<h2>Preparing input for a local model</h2>
<p>OpenAI's Whisper converts every input to 16 kHz mono before recognition. Its <code>load_audio</code> helper calls ffmpeg with exactly those settings. If you transcribe the same files more than once, or feed them to several models, convert once and keep the WAV:</p>
<pre><code class="language-bash">ffmpeg -i meeting.mp4 -vn -ac 1 -ar 16000 -c:a pcm_s16le meeting.wav
</code></pre>
<p>This format costs 32,000 bytes per second, about 115 MB per hour. Going below 16 kHz throws away frequencies the model expects. Going above it gains nothing, because the model resamples down anyway.</p>
<h2>Stereo call recordings: split, don't mix</h2>
<p><code>-ac 1</code> adds the channels together. That is fine for a meeting recorded on one microphone. Call recorders are different: some of them put each side of the call on its own channel. Mixing the channels throws that separation away, and speaker separation then has to tell the voices apart by sound alone.</p>
<p>Split such a file into two mono tracks:</p>
<pre><code class="language-bash">ffmpeg -i call.wav -filter_complex "channelsplit=channel_layout=stereo[l][r]" -map "[l]" left.wav -map "[r]" right.wav
</code></pre>
<p>Listen to ten seconds of each track before naming them, because the recorder decides which side goes to the left channel. Each track can then be transcribed on its own. Merged by timestamp, the two transcripts give a dialogue where the speaker of every line is known.</p>
<h2>Loudness: helpful, with one ffmpeg quirk</h2>
<p>Uneven levels are common when one person sits next to the laptop and the other speaks from across the room. EBU R128 normalization evens them out:</p>
<pre><code class="language-bash">ffmpeg -i interview.m4a -af loudnorm=I=-16:TP=-1.5:LRA=11 -ar 48000 -c:a aac -b:a 96k interview-norm.m4a
</code></pre>
<p>Keep <code>-ar 48000</code> in the command. In its default single-pass mode the <code>loudnorm</code> filter outputs 192 kHz audio, and without <code>-ar</code> the file comes out several times larger with nothing gained for speech. Normalization also helps the person who later checks the transcript against the audio.</p>
<h2>Two edits that break timecodes</h2>
<p>A transcript with timecodes points to moments in the original recording. AudioVText adds timecodes automatically and exports SRT and VTT subtitles. Any edit that changes the timeline makes those references wrong.</p>
<p>The first such edit is silence removal:</p>
<pre><code class="language-bash">ffmpeg -i interview.m4a -af silenceremove=stop_periods=-1:stop_duration=1:stop_threshold=-45dB shorter.m4a
</code></pre>
<p>This command deletes every pause longer than one second. The file gets shorter and the words stay, but each timestamp now drifts by the total length of the pauses before it. Say a one-hour interview had four minutes of pauses: lines near the end will point about four minutes away from the real moment in the video. Subtitles built from such a transcript run ahead of the speaker. If the timecodes matter, leave the pauses in.</p>
<p>The second is cutting a long file into equal pieces:</p>
<pre><code class="language-bash">ffmpeg -i lecture.wav -f segment -segment_time 600 -c copy part_%02d.wav
</code></pre>
<p>The segment muxer cuts at fixed points, often in the middle of a word, and that word gets lost or garbled on both sides of the cut. Each piece also restarts its clock at zero. A better split uses the pauses. First find them:</p>
<pre><code class="language-bash">ffmpeg -i lecture.wav -af silencedetect=noise=-35dB:d=0.6 -f null - 2&gt;&amp;1 | grep silence_end
</code></pre>
<p>Each line reports the end of a pause, for example <code>silence_end: 612.48 | silence_duration: 0.81</code>. Pick one pause near every ten-minute mark and cut there:</p>
<pre><code class="language-bash">ffmpeg -i lecture.wav -f segment -segment_times 612.1,1205.7 -reset_timestamps 1 -c copy part_%02d.wav
</code></pre>
<p>Keep the list of cut points. The second piece starts at 612.1 seconds of the original, so 612.1 must be added to every timestamp in its transcript, and 1205.7 to those of the third.</p>
<h2>Test noise reduction before trusting it</h2>
<p>ffmpeg ships a spectral denoiser, <code>afftdn</code>. Cleaner-sounding audio does not always come back as cleaner text, because denoising can also soften quiet consonants and the ends of words. Test it on a sample instead of the whole file. Cut three minutes from a noisy stretch and process only that:</p>
<pre><code class="language-bash">ffmpeg -ss 600 -t 180 -i noisy.m4a -c copy sample.m4a
ffmpeg -i sample.m4a -af afftdn -c:a aac -b:a 96k sample-afftdn.m4a
</code></pre>
<p>Transcribe both versions and compare the two texts side by side. Short words, names and numbers show the difference first. Keep the filter only if the denoised version wins on your own recordings.</p>
]]></content:encoded></item></channel></rss>