joshua.seigler.net - ffmpeg Personal homepage of Joshua Seigler 2025-06-28T00:00:00Z https://joshua.seigler.net/ FFmpeg audio cleanup 2025-06-26T00:00:00Z https://joshua.seigler.net/posts/ffmpeg-audio-cleanup/ <p>I recently needed to process 20+ phone audio recordings. The files are mp3 recordings in stereo, made in an environment with echoes and noise from fans/heaters.</p> <p>Although I could do it easily with <a href="https://tenacityaudio.org/" target="_blank" rel="noopener">Tenacity</a> I didn’t want to use a manual process, since it would take days. So I tried using FFmpeg filters and Bash scripting.</p> <p>I found an FFmpeg filter called <a href="https://ffmpeg.org/ffmpeg-filters.html#compand" target="_blank" rel="noopener">compand</a> which lets you map an input decibel range to an output decibel range. I also used the <a href="https://ffmpeg.org/ffmpeg-filters.html#anlmdn" target="_blank" rel="noopener">anlmdn</a> filter to reduce noise, and the <a href="https://ffmpeg.org/ffmpeg-filters.html#highpass" target="_blank" rel="noopener">highpass</a> filter to help with clarity.</p> <p>I ran into a couple gotchas.</p> <ol> <li><code>mpv</code> does something special for audio playback that prevents audio from clipping. <code>vlc</code> plays the file as it is.</li> <li>Because the compressor has an attack and decay (which is necessary for things to sound good) it can cause clipping if the volume rises sharply. Applying a <code>delay</code> parameter with half the duration of the attack length fixed this.</li> </ol> <p>Here is the script:</p> <p><code>process-audio.sh</code></p> <pre class="language-bash"><code class="language-bash"><span class="highlight-line"><span class="token shebang important">#!/bin/bash</span></span> <span class="highlight-line"><span class="token keyword">if</span> <span class="token punctuation">[</span> <span class="token string">"<span class="token variable">$#</span>"</span> <span class="token operator">==</span> <span class="token string">"0"</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span></span> <span class="highlight-line"> <span class="token builtin class-name">echo</span> <span class="token string">"Error: no arguments provided."</span></span> <span class="highlight-line"> <span class="token builtin class-name">echo</span> <span class="token string">"Usage: <span class="token variable">$0</span> file1 file2 file3 ..."</span></span> <span class="highlight-line"> <span class="token builtin class-name">echo</span> <span class="token string">"or <span class="token variable">$0</span> *.ext"</span></span> <span class="highlight-line"> <span class="token builtin class-name">exit</span> <span class="token number">1</span></span> <span class="highlight-line"><span class="token keyword">fi</span></span> <span class="highlight-line"></span> <span class="highlight-line"><span class="token builtin class-name">trap</span> <span class="token string">"exit"</span> INT</span> <span class="highlight-line"></span> <span class="highlight-line"><span class="token keyword">while</span> <span class="token punctuation">[</span> <span class="token string">"<span class="token variable">$#</span>"</span> <span class="token operator">!=</span> <span class="token string">"0"</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">do</span></span> <span class="highlight-line"> <span class="token assign-left variable">path</span><span class="token operator">=</span><span class="token string">"<span class="token variable">${1<span class="token operator">%</span><span class="token operator">/</span>*}</span>"</span></span> <span class="highlight-line"> <span class="token assign-left variable">file</span><span class="token operator">=</span><span class="token string">"<span class="token variable">${1<span class="token operator">##</span>*<span class="token operator">/</span>}</span>"</span></span> <span class="highlight-line"> <span class="token assign-left variable">outfile</span><span class="token operator">=</span><span class="token string">"./normalized--<span class="token variable">$file</span>"</span></span> <span class="highlight-line"> <span class="token keyword">if</span> <span class="token punctuation">[</span> <span class="token operator">!</span> <span class="token parameter variable">-f</span> <span class="token string">"<span class="token variable">$outfile</span>"</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span></span> <span class="highlight-line"> <span class="token builtin class-name">echo</span> <span class="token string">"Processing <span class="token variable">$1</span>"</span></span> <span class="highlight-line"> ffmpeg <span class="token parameter variable">-i</span> <span class="token string">"<span class="token variable">$1</span>"</span> <span class="token parameter variable">-v</span> warning <span class="token parameter variable">-ac</span> <span class="token number">1</span> <span class="token parameter variable">-af</span> <span class="token string">"compand=attacks=0.3:decays=0.3:delay=0.15:points=-80/-300|-45/-25|-27/-15|0/-12|20/-12,anlmdn=s=10,highpass=f=500"</span> <span class="token parameter variable">-threads</span> <span class="token number">4</span> <span class="token string">"<span class="token variable">$outfile</span>"</span></span> <span class="highlight-line"> <span class="token keyword">else</span></span> <span class="highlight-line"> <span class="token builtin class-name">echo</span> <span class="token string">"Skipping <span class="token variable">$1</span>, already processed."</span></span> <span class="highlight-line"> <span class="token keyword">fi</span></span> <span class="highlight-line"> <span class="token builtin class-name">shift</span></span> <span class="highlight-line"><span class="token keyword">done</span></span></code></pre> <p>If this is useful to you please leave a comment or send an email, I would love to hear about it.</p>