You have a podcast episode or a song as an MP3. YouTube, TikTok, Instagram and Facebook want a video file.

The standard fix is to put the audio under a still image. ffmpeg does it in one command. Here it is, what each flag does, and the one error that trips people up.

The command

ffmpeg -loop 1 -i cover.jpg -i song.mp3 \ -c:v libx264 -tune stillimage -pix_fmt yuv420p \ -c:a aac -b:a 192k \ -movflags +faststart \ -shortest out.mp4 

I ran this on ffmpeg 8.0.1 with a 5 second tone and a 1920x1080 image. It made a 1920x1080 H.264 video with an AAC audio track, exactly 5 seconds long.

What each flag does

  • -loop 1 before the image tells ffmpeg to repeat that one image forever. It is an option of the image2 demuxer, and it goes before -i cover.jpg.
  • -i song.mp3 is the audio.
  • -c:v libx264 encodes the video as H.264, which every platform accepts.
  • -tune stillimage is an x264 tuning meant for slideshow-like content, where almost nothing changes between frames.
  • -pix_fmt yuv420p uses the pixel format that browsers, phones and TVs can all play. Without it, some players show a black screen.
  • -c:a aac -b:a 192k encodes the audio as AAC at 192 kbit/s.
  • -movflags +faststart moves the index to the front of the file so it starts playing before it has fully downloaded.
  • -shortest stops when the shortest input ends. This one matters most.

Why -shortest is not optional

-loop 1 makes the image input endless. The audio is not. If you leave out -shortest, ffmpeg keeps writing video after the music stops and never finishes. The ffmpeg docs describe -shortest as finishing when the shortest output stream ends, which here is the audio.

You can use -t 180 instead to cut at a set number of seconds.

The "not divisible by 2" error

H.264 in yuv420p needs even width and height. A 1001x563 image fails. I tried it: libx264 printed width not divisible by 2 (1001x563), ffmpeg wrote nothing and ended with an invalid argument error.

Round the size down to even numbers (this turned my image into 1000x562):

-vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" 

Or force a standard frame and pad the rest with black bars:

-vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" 

That second filter turned my odd-size image into a clean 1920x1080 video. For vertical video, swap in 1080:1920.

Skip re-encoding the audio

If your source is already AAC, for example an M4A file, copy it:

ffmpeg -loop 1 -i cover.png -i song.m4a \ -c:v libx264 -tune stillimage -pix_fmt yuv420p \ -c:a copy -shortest out.mp4 

I checked this one too: the audio stream stayed AAC. With an MP3, encode to AAC as above, because AAC is what platforms expect inside an MP4.

Add a waveform

ffmpeg can draw the audio itself with the showwaves filter:

ffmpeg -loop 1 -i cover.png -i song.mp3 \ -filter_complex "[1:a]showwaves=s=1280x200:mode=cline:colors=white[w];[0:v][w]overlay=(W-w)/2:H-h-60,format=yuv420p[v]" \ -map "[v]" -map 1:a \ -c:v libx264 -c:a aac -b:a 192k -shortest wave.mp4 

It draws a white line wave 60 pixels above the bottom of the image. Change s=, mode= and colors= to taste.

No terminal? A no-install option

If you do not want to install ffmpeg, the MP3 to MP4 with image tool on Ziztu does the same job in the browser. There are other online converters. This is the one I use, and here is what its page says:

  • It is free, with no account and no credit card.
  • Audio: MP3, WAV or M4A, up to 10 minutes and 50 MB. OGG and FLAC are not supported.
  • Background: a JPEG, PNG or WebP image up to 10 MB, or a solid color. Animated GIF is not supported.
  • Pick 16:9 for YouTube, 9:16 for Reels and Shorts, or 1:1 for square posts.
  • Optional animated waveform drawn from the audio, and an optional title.
  • Output is a standard MP4 with H.264 video and AAC audio, the same format as the command above.
  • Limit: 5 conversions an hour.

Know the trade-off. Your audio and image are uploaded over HTTPS and converted on a server with ffmpeg. Both files and the MP4 are deleted once you download it, and anything left behind within 1 hour. For private audio, use the local command instead.

Which one to use

  • One file, once, no ffmpeg installed: the browser tool.
  • A folder of episodes, or a script in your pipeline: the command, in a loop.
  • Private audio: the command, because nothing leaves your machine.

Happy converting.

This post was written with AI assistance.