Ahosting Logo
Knowledge Base

How to Turn Audio into Video for Podcasts and Music

Three ways to give audio a picture, in increasing costA single still imageheld for the whole duration; byfar the cheapestA still with a waveformgenerated, so it moves with thesoundA full animated visualisationexpensive to render, per minuteFor a still image, encode it as a very low frame rate video with keyframes: an hour of unchanging picture should notcost much.

Some platforms accept only video. A podcast episode, a music track or a recorded talk therefore needs a picture attached, and the picture can be as simple as one image.

One image, held for the whole track

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

Three flags carry the weight.

-loop 1 repeats the still image indefinitely, and -shortest ends the output when the audio does, without it, the encode never finishes.

-pix_fmt yuv420p is not optional in practice. Without it the output can use a format many players and platforms refuse, and the symptom is a file that plays perfectly on your machine and is rejected on upload.

The settings that decide the size

A still image should produce a file barely larger than the audio. Left at defaults it does not, because the encoder is writing 25 or 30 identical frames every second and a full keyframe every couple of seconds.

ffmpeg -loop 1 -framerate 2 -i cover.jpg -i audio.mp3 \
 -c:v libx264 -tune stillimage -r 2 -g 240 \
 -c:a aac -b:a 192k -pix_fmt yuv420p -shortest out.mp4

-r 2 outputs two frames a second, which is invisible for a static picture. -g 240 spaces the keyframes two minutes apart.

On a one-hour episode the difference is substantial, and nothing about the result looks different.

Check the image dimensions are even numbers, odd ones are rejected by many encoders, and scale=1280:-2 fixes it.

A waveform instead

ffmpeg -i audio.mp3 -filter_complex \
 "[0:a]showwaves=s=1280x720:mode=cline:colors=0x38bdf8[v]" \
 -map "[v]" -map 0:a -c:v libx264 -crf 23 -c:a aac -b:a 192k -pix_fmt yuv420p out.mp4

A moving line drawn from the audio. More engaging than a still and genuinely more expensive, because every frame is different and must actually be encoded.

mode=cline gives a centred line; p2p and point are alternatives worth a look.

Waveform over an image

ffmpeg -loop 1 -i cover.jpg -i audio.mp3 -filter_complex \
 "[1:a]showwaves=s=1280x200:mode=cline:colors=white[wave]; \
 [0:v]scale=1280:720[bg];[bg][wave]overlay=0:520[v]" \
 -map "[v]" -map 1:a -c:v libx264 -crf 23 -c:a aac -b:a 192k \
 -pix_fmt yuv420p -shortest out.mp4

Cover art with the waveform along the bottom, which is the format most podcast video looks like. Understanding FFmpeg filters goes into reading that graph.

A spectrum

ffmpeg -i audio.mp3 -filter_complex \
 "[0:a]showspectrum=s=1280x720:mode=combined:color=intensity:slide=scroll[v]" \
 -map "[v]" -map 0:a -c:v libx264 -crf 23 -c:a aac -pix_fmt yuv420p out.mp4

The most computationally expensive of the three and the most visually busy. Worth it for music, rarely for speech.

Do not re-encode audio that is already right

If the source audio is already AAC at a suitable bitrate:

-c:a copy

Faster, and lossless. Re-encoding an already-compressed file loses quality for nothing. Remuxing and stream copy deals with the principle.

MP3 audio in an MP4 works but is unusual; converting to AAC once is the safer choice for wide playback.

Batching a back catalogue

for f in *.mp3; do
 ffmpeg -loop 1 -framerate 2 -i cover.jpg -i "$f" \
 -c:v libx264 -tune stillimage -r 2 -g 240 \
 -c:a aac -b:a 192k -pix_fmt yuv420p -shortest "${f%.mp3}.mp4"
done

Still images encode quickly, so a whole catalogue is realistic to process in one run, building a batch pipeline goes over doing it reliably at scale, and editing metadata and chapters goes over titling the results.

Keep the file small when the picture never changes

A still image held for an hour should produce a small file, and the obvious command produces a large one.

ffmpeg -loop 1 -framerate 2 -i cover.jpg -i audio.mp3 \
  -c:v libx264 -tune stillimage -crf 28 -pix_fmt yuv420p \
  -c:a copy -shortest out.mp4

A low frame rate is the main saving, since nothing moves. Two frames a second is sufficient and reduces the video stream to almost nothing.

The still image tuning tells the encoder what it is working with, and the keyframe interval matters for platforms that require one at a specific spacing. Check the resulting size against the audio size; the video should add very little.

Match the dimensions the destination expects

Platforms reject or reprocess files whose dimensions are unusual, and an image used directly is rarely the right shape.

ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 cover.jpg
ffmpeg -loop 1 -i cover.jpg -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" \
  -i audio.mp3 -c:v libx264 -crf 28 -pix_fmt yuv420p -c:a copy -shortest out.mp4

Scaling with padding preserves the image proportions and fills the rest, which is better than stretching it to fit.

Odd numbered dimensions are rejected by the encoder outright, which is why a scale expression producing an even result is worth using rather than a fixed number taken from the source image.

Check the audio survived unchanged

The whole point of this operation is the audio, and it is the part most easily degraded by accident.

for f in audio.mp3 out.mp4; do
  printf '%-12s %s\n' "$f" "$(ffprobe -v error -select_streams a:0 -show_entries stream=codec_name,bit_rate,sample_rate -of csv=p=0 "$f")"
done

If the codec and bitrate match the source, the audio was copied. If they differ, it was re-encoded, and a second lossy encode of already lossy audio is a loss for no benefit.

Where the destination requires a format the source is not in, one conversion is unavoidable. Doing it from the highest quality source available rather than from an already compressed copy is what limits the damage.

Batch a back catalogue without supervision

The operation is identical per file, which makes it the sort of job that should run unattended.

for a in audio/*.mp3; do
  b=$(basename "$a" .mp3)
  [ -f "out/$b.mp4" ] && continue
  ffmpeg -nostdin -loglevel error -loop 1 -framerate 2 -i "covers/$b.jpg" -i "$a" \
    -c:v libx264 -tune stillimage -crf 28 -pix_fmt yuv420p -c:a copy -shortest "out/$b.mp4"
done

Skipping files that already exist means an interrupted run can be restarted without redoing everything.

Preventing the process from reading standard input is what stops the first job consuming the rest of the loop, which is the failure that produces one output and no error.

Confirm the result plays where it is going

A file that plays locally can still be rejected on upload, and the reasons are a short list.

ffprobe -v error -show_entries stream=codec_name,pix_fmt,width,height,r_frame_rate -of default=nw=1 out.mp4
ffprobe -v error -show_entries format=duration,size,bit_rate -of default=nw=1 out.mp4

The pixel format is the usual cause. Anything other than the widely supported one plays in desktop players and fails in browsers and on some platforms.

Duration is worth reading too. A mismatch between the audio length and the file length means the shortest input option was omitted, and the video continues in silence after the track ends.

Keep the source files organised before batching

The operation pairs an audio file with an image, and the pairing is where a batch run goes wrong.

ls audio/ | sed 's/\.[^.]*$//' | sort > /tmp/a.txt
ls covers/ | sed 's/\.[^.]*$//' | sort > /tmp/c.txt
diff /tmp/a.txt /tmp/c.txt

Matching names is the simplest arrangement and the difference shows immediately which tracks have no artwork.

Decide in advance what happens to those. A default image is better than a failed job, and better than a video with no picture at all, which is what an unmatched run produces.

Keep the artwork at a sensible resolution

An image far larger than the output dimensions is scaled down on every encode, which costs time and gains nothing. Resize the artwork once to the target size and keep that copy for the batch. The reverse also matters: an image smaller than the output is enlarged and looks soft, and no encoder setting recovers detail the source does not have. Check the image dimensions against the video dimensions before running a catalogue through.