Processing podcast MP3s with sox and ffmpeg

Adjusting playback speed and removing silence

  •  5 mins  •  

I recently acquired a MP3 player-like device. I wanted to try using it to listen to podcasts. The problem I faced was that I usually listen to podcasts at about 1.25 speed, with silent parts of the audio automatically removed (for example, using AntennaPod). The player itself has pretty limited playback controls, and certainly does not have any functionality to change the playback speed to cut out silence.

It therefore seemed clear that I would have to pre-process the MP3s to change the playback speed and remove silence before loading the MP3s onto the device.

I fiddled around with sox a bit and put together a suitable command for my use case:1

sox --show-progress $input_file $temp_file silence -l 1 0.1 1% -1 0.4 1% tempo 1.25

The sox command above essentially applies 2 filters on the input MP3: (1) removes periods of silence in the audio; and (2) use atempo to adjust the playback speed to 25% (this does not change the pitch of the audio). For (1), more specifically, it considers any audio less than 1% in volume to be silence, and will cut all periods of silence > 1s in length to 0.4s max.

I turned this into a simple bash script with the assistance of DeepSeek-V3.2 for the scaffolding:

#!/bin/sh

if [ -z "$1" ]; then
  echo "Usage: $0 <input.mp3>"
  exit 1
fi

input_file="$1"

# Extract directory and filename
dir=$(dirname "$input_file")
filename=$(basename "$input_file" .mp3)

temp_file="${dir}/${filename}.processed.wav"

# Output file path
output_file="${dir}/${filename}.processed.mp3"

sox --show-progress $input_file $temp_file silence -l 1 0.1 1% -1 0.4 1% tempo 1.25

ffmpeg -i $temp_file -q:a 0 -vn "$output_file"

# Check if succeeded
if [ $? -eq 0 ]; then
  echo "Processing complete. Output saved to: $output_file"
else
  echo "Processing failed. Check for errors."
fi

This does require you to have sox and ffmpeg installed. On NixOS, this is as simple as:

nix-shell -p sox ffmpeg

sox does not have MP3 output support built in (or at least the version packaged in NixOS does not), hence .wav output and subsequent conversion to .mp3 via ffmpeg.

The script works well:

[nix-shell:~/Desktop]$ sh script.sh ~/Downloads/GlobalNewsPodcast-20251205-FourCountriesBoycottEurovisionOverIsraelsInclusion.mp3

Input File     : '/home/huey/Downloads/GlobalNewsPodcast-20251205-FourCountriesBoycottEurovisionOverIsraelsInclusion.mp3'
Channels       : 1
Sample Rate    : 44100
Precision      : 16-bit
Duration       : 00:36:49.80 = 97452288 samples = 165735 CDDA sectors
File Size      : 17.8M
Bit Rate       : 64.3k
Sample Encoding: MPEG audio (layer I, II or III)

In:100%  00:36:49.59 [00:00:00.21] Out:73.0M [======|======] Hd:2.3 Clip:454  
sox WARN dither: dither clipped 454 samples; decrease volume?
Done.
ffmpeg version 8.0 Copyright (c) 2000-2025 the FFmpeg developers
  built with gcc 14.3.0 (GCC)
  configuration: --disable-static --prefix=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-ffmpeg-full-8.0 --target_os=linux --arch=x86_64 --pkg-config=pkg-config --enable-gpl --enable-version3 --disable-nonfree --disable-static --enable-shared --enable-pic --disable-thumb --disable-small --enable-runtime-cpudetect --enable-gray --enable-swscale-alpha --enable-hardcoded-tables --enable-safe-bitstream-reader --enable-pthreads --disable-w32threads --disable-os2threads --enable-network --enable-pixelutils --datadir=/nix/store/rvx1lss70sfq3qpx0v2crlfqls6mj3kh-ffmpeg-full-8.0-data/share/ffmpeg --enable-ffmpeg --enable-ffplay --enable-ffprobe --bindir=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-ffmpeg-full-8.0-bin/bin --enable-avcodec --enable-avdevice --enable-avfilter --enable-avformat --enable-avutil --enable-swresample --enable-swscale --libdir=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-ffmpeg-full-8.0-lib/lib --incdir=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-ffmpeg-full-8.0-dev/include --enable-doc --enable-htmlpages --enable-manpages --mandir=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-ffmpeg-full-8.0-man/share/man --enable-podpages --enable-txtpages --docdir=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-ffmpeg-full-8.0-doc/share/doc/ffmpeg --enable-alsa --enable-amf --enable-libaom --enable-libaribb24 --enable-libaribcaption --enable-libass --enable-avisynth --enable-libbluray --enable-libbs2b --enable-bzlib --enable-libcaca --enable-libcdio --enable-libcelt --enable-chromaprint --enable-libcodec2 --enable-cuda --enable-cuda-llvm --disable-cuda-nvcc --enable-cuvid --enable-libdav1d --enable-libdavs2 --enable-libdc1394 --enable-libdrm --enable-libdvdnav --enable-libdvdread --disable-libfdk-aac --enable-ffnvcodec --enable-libflite --enable-fontconfig --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libfribidi --enable-libgme --enable-gmp --enable-gnutls --enable-libgsm --enable-libharfbuzz --enable-iconv --enable-libilbc --enable-libjack --enable-libjxl --enable-libkvazaar --enable-ladspa --enable-liblc3 --enable-liblcevc-dec --enable-lcms2 --enable-lzma --disable-metal --disable-libmfx --enable-libmodplug --enable-libmp3lame --enable-libmysofa --disable-libnpp --enable-nvdec --enable-nvenc --enable-openal --enable-liboapv --enable-opencl --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-opengl --enable-libopenh264 --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libplacebo --enable-libpulse --enable-libqrencode --enable-libquirc --enable-librav1e --enable-librist --disable-librtmp --enable-librubberband --enable-libsmbclient --enable-sdl2 --enable-libshaderc --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-librsvg --enable-libsvtav1 --disable-libtensorflow --enable-libtheora --enable-libtwolame --enable-libuavs3d --enable-libv4l2 --enable-v4l2-m2m --enable-vaapi --enable-vdpau --enable-libvpl --enable-libvidstab --enable-libvmaf --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-vulkan --enable-libvvenc --enable-libwebp --enable-whisper --enable-libx264 --enable-libx265 --enable-libxavs --enable-libxavs2 --enable-libxcb --enable-libxcb-shape --enable-libxcb-shm --enable-libxcb-xfixes --enable-libxevd --enable-libxeve --enable-xlib --enable-libxml2 --enable-libxvid --enable-libzimg --enable-zlib --enable-libzmq --enable-libzvbi --disable-debug --enable-optimizations --disable-extra-warnings --disable-stripping
  libavutil      60.  8.100 / 60.  8.100
  libavcodec     62. 11.100 / 62. 11.100
  libavformat    62.  3.100 / 62.  3.100
  libavdevice    62.  1.100 / 62.  1.100
  libavfilter    11.  4.100 / 11.  4.100
  libswscale      9.  1.100 /  9.  1.100
  libswresample   6.  1.100 /  6.  1.100
[aist#0:0/pcm_s16le @ 0x55f5052989c0] Guessed Channel Layout: mono
Input #0, wav, from '/home/huey/Downloads/GlobalNewsPodcast-20251205-FourCountriesBoycottEurovisionOverIsraelsInclusion.processed.wav':
  Duration: 00:27:36.46, bitrate: 705 kb/s
  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, mono, s16, 705 kb/s
Stream mapping:
  Stream #0:0 -> #0:0 (pcm_s16le (native) -> mp3 (libmp3lame))
Press [q] to stop, [?] for help
Output #0, mp3, to '/home/huey/Downloads/GlobalNewsPodcast-20251205-FourCountriesBoycottEurovisionOverIsraelsInclusion.processed.mp3':
  Metadata:
    TSSE            : Lavf62.3.100
  Stream #0:0: Audio: mp3, 44100 Hz, mono, s16p
    Metadata:
      encoder         : Lavc62.11.100 libmp3lame
[out#0/mp3 @ 0x55f5052851c0] video:0KiB audio:24931KiB subtitle:0KiB other streams:0KiB global headers:0KiB muxing overhead: 0.000885%
size=   24931KiB time=00:27:36.45 bitrate= 123.3kbits/s speed= 174x elapsed=0:00:09.54    
Processing complete. Output saved to: /home/huey/Downloads/GlobalNewsPodcast-20251205-FourCountriesBoycottEurovisionOverIsraelsInclusion.processed.mp3

At the moment, I do have to manually download and process the relevant MP3. One way I do this with AntennaPod is to browse to the relevant podcast episode in AntennaPod and share this to my computer via KDE Connect, then run the script on the downloaded file.

If I find myself using this workflow enough I may look into augmenting the script to further automate this process.