An FFmpeg audiogram is one filter. The rest is what breaks.

You have a 45-minute MP3, a square cover image, and a social team that wants a 60-second clip with a bouncing waveform on it. Doing that in Premiere or Headliner works once. Doing it for every episode, every week, for a back catalog of 200 shows, is a job for a filter graph.
Quick answer: To create an FFmpeg audiogram, run the audio through theshowwavesfilter to render an animated waveform video, then overlay it on your looped cover image:ffmpeg -loop 1 -i cover.jpg -i episode.mp3 -filter_complex "[1:a]showwaves=s=1080x260:mode=cline:rate=30:colors=#00E5A0[w];[0:v]scale=1080:820,pad=1080:1080[bg];[bg][w]overlay=0:820:shortest=1[v]" -map "[v]" -map 1:a -pix_fmt yuv420p -shortest audiogram.mp4. Useshowwavespicinstead when you want a single static waveform PNG.
Conventional wisdom says the waveform is the hard part. Most tutorials spend their whole word count on showwaves options and stop there. But the filter is the easy bit. It's four parameters and it works on the first try. What actually breaks audiogram automation is everything wrapped around it: the waveform renders on an opaque black background that eats your cover art, x264 refuses odd pixel dimensions, and a 45-minute episode at 1080x1080 takes long enough to blow past every serverless timeout you own.
showwaves vs showwavespic: which filter do you want?
showwaves produces a video stream with a waveform that scrolls or pulses in sync with the audio. showwavespic produces one frame containing the entire file's waveform, which is what you want for a static thumbnail or a Spotify-style episode graphic.
Both default to 600x240 at 25 fps if you don't set s and rate.
# Static waveform PNG of the whole episode
ffmpeg -i episode.mp3 -filter_complex \
"showwavespic=s=1200x300:colors=#00E5A0" \
-frames:v 1 waveform.png
The mode parameter is the one that changes the look most:
| mode | What it draws | Good for |
|---|---|---|
| `point` | One pixel per sample | Dense, gritty texture |
| `line` | Vertical line per column | Classic DAW look |
| `p2p` | Peak-to-peak connected | Smooth, continuous ribbon |
| `cline` | Centered vertical lines | The standard podcast audiogram bar look |
Add split_channels=1 to stack left and right separately, and scale=log if a quiet interview segment renders as a flat line. colors takes pipe-separated values, one per channel: colors=#00E5A0|#00E5A0 keeps stereo channels the same color.
Build the audiogram: cover image plus animated waveform
The reliable layout puts the artwork on top and the waveform in a solid band underneath, so you never fight transparency. This renders a 1080x1080 square clip from the first 60 seconds.
ffmpeg -ss 00:12:30 -t 60 -i episode.mp3 -loop 1 -i cover.jpg \
-filter_complex "\
[1:v]scale=1080:820:force_original_aspect_ratio=increase,\
crop=1080:820,setsar=1,pad=1080:1080:0:0:black[bg];\
[0:a]showwaves=s=1080x260:mode=cline:rate=30:colors=#00E5A0[wave];\
[bg][wave]overlay=0:820:shortest=1[v]" \
-map "[v]" -map 0:a \
-c:v libx264 -preset veryfast -crf 20 -pix_fmt yuv420p \
-c:a aac -b:a 192k -shortest -movflags +faststart audiogram.mp4
A few things are doing real work here. -ss 00:12:30 -t 60 before the input seeks to your pull quote and cuts 60 seconds, so you're not rendering 45 minutes of video to throw away 44 of them. The 820 and 260 add to exactly 1080, so the overlay lands flush. -map 0:a is what keeps the audio, because showwaves output is video only and FFmpeg won't guess. And -movflags +faststart moves the MP4 index to the front so the file starts playing before it finishes downloading, which matters on every social platform.
If you want the waveform sitting on the artwork instead of below it, key out the black background:
[0:a]showwaves=s=1080x300:mode=cline:colors=white,\
format=rgba,colorkey=black:0.02:0.05[wave]
Be warned: cline is antialiased, so aggressive colorkey similarity values leave gray fringes on the bar edges. Start at 0.02 and raise it slowly.
Vertical audiograms for Reels, TikTok, and Shorts
For a 1080x1920 vertical clip, keep the cover square and centered, then put the waveform in the lower third where captions don't usually land.
ffmpeg -ss 00:12:30 -t 60 -i episode.mp3 -loop 1 -i cover.jpg \
-filter_complex "\
color=c=#101418:s=1080x1920:r=30[canvas];\
[1:v]scale=800:800,setsar=1[art];\
[canvas][art]overlay=140:420[base];\
[0:a]showwaves=s=1080x220:mode=cline:rate=30:colors=#00E5A0,\
format=rgba,colorkey=black:0.02:0.05[wave];\
[base][wave]overlay=0:1320:shortest=1[v]" \
-map "[v]" -map 0:a -c:v libx264 -crf 20 -pix_fmt yuv420p \
-c:a aac -b:a 192k -shortest -movflags +faststart vertical.mp4
Instagram Reels and TikTok both want 1080x1920 at 30 fps. If you're producing both square and vertical from the same source, our notes on converting between vertical and horizontal formats cover the padding math.
Add the episode title with drawtext
Chain drawtext after your background is composited. It needs FFmpeg built with --enable-libfreetype, which the official builds include.
drawtext=fontfile=/Library/Fonts/Inter-Bold.ttf:\
text='Ep 142: Why We Killed Our Roadmap':\
fontcolor=white:fontsize=44:box=1:boxcolor=black@0.55:boxborderw=18:\
x=(w-text_w)/2:y=h-360
Colons and single quotes inside text have to be escaped with a backslash, which is the single most common reason a drawtext filter fails to parse. If your title comes from an RSS feed, sanitize it before it hits the command line. Reading the text from a file with textfile=title.txt sidesteps the escaping problem entirely.
Common pitfalls
- Silent output. You added
showwavesand got video with no sound.showwavesconsumes the audio stream and emits video, so you need an explicit-map 0:ato carry the original audio through. - The render never finishes.
-loop 1on a still image produces an infinite stream. Without-shortest(and ideallyshortest=1on the overlay), FFmpeg will happily encode forever. width not divisible by 2. libx264 with yuv420p requires even dimensions. A cover cropped to 1081 pixels wide fails. Usescale=1080:-2so the height rounds to an even number automatically.- Plays on your Mac, black on Twitter. Leaving out
-pix_fmt yuv420pproduces yuv444p, which QuickTime and most browsers won't decode. This is the number one cause of "the file works locally" audiogram bugs. - Flat-looking waveform. Podcast dialogue is heavily compressed and sits in a narrow amplitude range. Add
scale=sqrtorscale=logtoshowwavesbefore you start fiddling with colors. - Timeouts on full episodes. A 45-minute audiogram at 1080x1080 is a multi-thousand-frame render. It will exceed the 60-second default on Vercel functions, the 540-second cap on Firebase, and every n8n HTTP node's patience. Trim first, or move the render off your app server.
The batch problem: 200 episodes, one call each
Here's where the local command stops scaling. Every episode needs FFmpeg installed with libfreetype, the cover downloaded, the clip range picked, the render queued somewhere it won't block, and the output pushed to storage. If you're running that inside n8n or Make, you're now maintaining a media microservice next to your automation.
| Running it yourself | FFmpeg Micro |
|---|---|
| Install and pin FFmpeg on every worker | No FFmpeg to install |
| Provision CPU for burst episode drops | No servers to run |
| Handle 5-minute renders inside a 60s HTTP timeout | Submit a job, get a webhook |
| Manage temp disk and cleanup | Download the finished MP4 |
FFmpeg Micro exposes the same filter graph as a REST job. You submit the audio URL, the cover URL, and the filter chain you already tested locally, then poll or take a webhook when the MP4 is ready.
curl -X POST https://api.ffmpeg-micro.com/jobs \
-H "Authorization: Bearer $FFMPEG_MICRO_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": [
{ "url": "https://cdn.example.com/ep142.mp3" },
{ "url": "https://cdn.example.com/cover.jpg", "loop": true }
],
"filter_complex": "[1:v]scale=1080:820,crop=1080:820,setsar=1,pad=1080:1080:0:0:black[bg];[0:a]showwaves=s=1080x260:mode=cline:rate=30:colors=#00E5A0[wave];[bg][wave]overlay=0:820:shortest=1[v]",
"output": { "format": "mp4", "video_codec": "libx264", "pix_fmt": "yuv420p" }
}'
Check the docs for the exact job schema and webhook payload. In n8n, that's an HTTP Request node plus a Wait node on the callback, which is the pattern described in fixing n8n timeout errors on video work. It works the same from Make, Zapier, or an AI agent through the MCP server.
When not to use FFmpeg for this
If you need burned-in animated captions that highlight word by word, showwaves plus drawtext isn't the right tool. Word-level karaoke captions need a transcript with timestamps and an ASS subtitle file, and at that point Headliner or Descript will get you there faster for a handful of clips. FFmpeg wins when the same template runs 50 times a week without a human opening an app.
Same story for one-off hero videos with custom motion design. Use After Effects. Use FFmpeg for the repeatable 90%.
FAQ
What is an audiogram?
An audiogram is a short video made from an audio clip, usually showing a static cover image with an animated waveform that moves in time with the sound. Podcasters use them because audio-only files can't be posted natively to Instagram, TikTok, or LinkedIn.
Can FFmpeg make a waveform video without a background image?
Yes. Drop the image input entirely: ffmpeg -i episode.mp3 -filter_complex "[0:a]showwaves=s=1280x720:mode=cline:rate=30:colors=#00E5A0[v]" -map "[v]" -map 0:a -pix_fmt yuv420p -shortest out.mp4. You get white or colored waves on solid black.
How do I change the audiogram waveform color in FFmpeg?
Set the colors option on showwaves or showwavespic using hex or named colors, one per audio channel, separated by pipes: colors=#00E5A0|#FF5C8A. A stereo file with a single color listed applies that color to channel one only.
Why is my FFmpeg audiogram file so large?
Long audiograms encode thousands of nearly identical frames with a moving element, which defeats x264's motion prediction. Lower rate to 24 or 25 on the showwaves filter, raise -crf to 23, and trim to 60 seconds. Our guide on compressing video for the web covers the bitrate targets per platform.
Do I need FFmpeg installed to make audiograms?
Not if you call the filter graph as an API job. If you'd rather run it locally, the install guide for Windows, macOS, and Linux covers getting a build with libfreetype for drawtext.
Test your filter graph locally on one episode until the layout looks right, then paste that exact filter_complex string into a job and let it run against the whole back catalog. Sign up free and the free tier is enough to render your first batch of episode clips.
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

How to Install the FFmpeg Library for Audacity (Windows and Mac)
Install the Audacity FFmpeg library the right way: match avformat versions to your Audacity build, use the Locate dialog, and fix the macOS Homebrew mismatch.

How to Replace Audio in Video with FFmpeg (Track Swap API Guide)
Replace audio in video with FFmpeg by mapping the video and new audio streams, or run the same track swap as one API call with FFmpeg Micro's hosted API.

FFmpeg Scene Detection: Auto-Split a Long Video at Scene Changes
FFmpeg scene detection with select='gt(scene,0.4)',showinfo finds cut points automatically, then split a long video into clips by scene, no hand-marked timestamps.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free