Nástroje používateľa

Nástoje správy stránok


blog:odborny:2026-07-09-creating_static_one-image_video_with_ffmpeg

Creating static (one-image) video with ffmpeg

TODO Convert MediaWiki code to DokuWiki

After reading many different (working) answers on this question, I was still dissatisfied with the fact that the resulting video size was never (even roughly) the size of AUDIO + IMAGE. So let me first explain the reason for this, followed by bad and good ways to solve it. Lastly, I will give my bash function for creating staticimage videos.

This is a repost from AskUbuntu, StackOverflow, Video StackExchange and SuperUser posts.

💡 Why the video is always much larger than just (audio+image) size

The simple reason is that there is no way you could just say to a video container (like `mkv`): „Hey, just use this single image all the time.“ Every video container needs a real video, so an image you supply will always get converted into video – it will never remain just an image.

During encoding process, the image gets replicated into many frames of the video and the resulting size will always be higher than just (audio+cover). So the only thing you can actually alter is *how this video is encoded*. You can play with three parameters of the video:

1️⃣ Frame-rate:

In theory, this alters how many times the image will be replicated in the video – usually the video framerates are around 2530 fps (23.976 / 24/ 25 / 29.97). However, *each frame only stores differences from the previous frame, not the whole image*, and since we are only using static image, having more frames with virtually zero differences from the previous frame does not affect the resulting container size *that* much.

However, *the `framerate` setting directly affects encoding speed*, so it is still wise to lower it.

2️⃣ Encoding quality:<br>([ffmpeg-libx264](https://ffmpeg.org/ffmpeg-codecs.html#Options-41) parameter `-crf N`; range 0(=lossless)..51, default 23)

This is the quality in which the image will be encoded in the video. And since it is repeated many times (frames), the lower the encoding quality, the lower the “extra” size for the video.

Note that the quality is on a logarithmic scale; lowering the number by 6 roughly doubles the file size, while raising it by 6 roughly halves it. For a static image, I wasn't able to visually discern between quality of `23` (the default) and either `29` (50% size) or even `35` (25% size). `51` was noticeably worse (but still not terrible), with `41` (12.5% size) the difference was barely noticeable.

3️⃣ Keyframe-rate:<br>([ffmpeg-libx264](https://ffmpeg.org/ffmpeg-codecs.html#Options-41) parameter `-g N`; default 250)

Unlike common frames, keyframes always contain the whole image, not just the difference from previous frames. So *this parameter directly affects the size of the video, because each keyframe directly adds one more duplicate of your static image to the video*. Note that this sets number of frames after which a keyframe must be created, so setting it to `N` means every Nth frame will be a keyframe and thus contain the whole image. If the framerate is 25 and you set keyframerate to 250 (default), one frame every 10 seconds will be fully encoded in the video.

❗️ Bad ways to do it = What does not work (very well)

1️⃣ Creating a one-frame static video:<br>video overhead 0.1% (46kB) of 78.9MB (length 1:20:36, audio 78.2MB, image 135kB)

One way to “fake it” is to actually force the video to end after its first frame – this way, you will really have something similar to a video container with just an image, because the video will have only oneframe static image. The key for this is *not* setting the `loop 1` parameter for the image source and `shortest` parameter for the output, without which the encoder will never repeat it – instead, it will create only a single frame in the video for it.

*This is the only way you can actually achieve the container size to be exactly the size of (audio+cover)*, because the resulting video will consist only of exactly one frame. You can achieve it using this command:

ffmpeg -i image.jpg -i audio.mp4 -c:v libx264 -pix_fmt yuv420p -c:a copy video.mkv

However, this will produce a video which is unplayable or buggy in most environments. For example (anyone is free to add to this list):

❌ Many players will report the video length to be just 1 second, and thus make the video unplayable (or, more precisely, will only play the first second). ⚠️ VLC will play the video and report its correct duration, but when playing it, the actual time will remain frozen and “scroller” position in the video will not update either (tested 20260709). ❌ Vimeo won't play the video – it will report it to be just 1 second long and will only play this one second (tested 20260709). ⚠️❗️ QuickTime will play it, but seeking is completely impossible (although length is properly reported). ✅ YouTube always reencodes uploaded videos, so it is actually the only service on which I was able to run the video (tested 20260709).

2️⃣ Extremely lowering frame-rate:<br>video overhead 0.4% (288kB) of 79.1MB (length 1:20:36, audio 78.2MB, image 135kB)

Technically speaking, `framerate` parameter accepts fractions (like 1/10), so you can lower framerate below 1 frame per second. In fact, what actually controls the framerate of the output video is the `r N` parameter, while `framerate N` controls the input “grabbing frame rate”. But since `framerate` automatically sets `r` to the same value (unless the `r` is explicitly given too) and since it does not make sense to set `r` lower than the `framerate` (because the extragrabbed frames arw thrown away), specifying just `framerate` is enough. You can achieve it using this command:

ffmpeg -loop 1 -framerate 1/60 -i image.jpg -i audio.mp4 -c:v libx264 -pix_fmt yuv420p -c:a copy -shortest video.mkv

Again, this will produce a video which is unplayable or buggy in most environments. For example (anyone is free to add to this list):

⚠️ Almost everything will report wrong video length (rounded to next frame = for a `framerate 1/60` it means rounded to next full minute). ⚠️❗️ VLC will play the video, but report incorrect duration and when playing it, the actual time will remain frozen and “scroller” position in the video will not update either (tested 20260709). ⚠️ Vimeo will play the video, but report incorrect duration (tested 20260709). ⚠️ QuickTime will play it (surprisingly!), but will report wrong video length and seeking is possible only in steps of frames. ⚠️ YouTube always reencodes the video, so it is able to run the video, but it will report wrong video length (tested 20260709).

3️⃣ Extremely lowering keyframe-rate:<br>video overhead 1.4% (1.09MB) of 79.4MB (length 1:20:36, audio 78.2MB, image 135kB)

Since keyframes are the main culprit in adding redundant size in the video, another approach you can use is to set the keyframerate so high that a video will never create a second keyframe – for example by setting `g 9999`. Note that this only makes sense if you also lower `framerate`, since otherwise the encoding will take extremely long with a much higher video overhead (17.6%, 17.4MB). You can achieve it using this command:

ffmpeg -loop 1 -framerate 1 -i image.jpg -i audio.mp4 -c:v libx264 -g 9999 -pix_fmt yuv420p -c:a copy -shortest video.mkv

Once again, this will produce a video which is problematic, but mainly for local (nononline) environments. This is because the missing keyframes are making it impossible for local players to effectively seek inside the video. However, since online (streaming) environments usually send only parts of video to the browser anyway, they usually can cope with the missing keyframes. Some examples (anyone is free to add to this list):

⚠️ Most local players will have trouble seeking quickly (or even properly) inside the video. ❌ VLC will play the video and report proper video length, but it will take very long time to seek, since the video lacks keyframes, and the whole UI will become practically unusable because of extreme lagging (tested 20260709). ⚠️❗️ Vimeo will play the video and report correct duration, but it won't be able to play the last few seconds of the video (tested 20260709). ⚠️ QuickTime will play the video and report proper video length, it will just take some time to seek, since the video lacks keyframes. ✅ YouTube always reencodes the video, so it is able to run the video just fine (tested 20260709).

✅ Generating a good-sized static video:

We will combine several tweaks to gain as much as possible: `framerate 1`: set the framerate to minimal value accepted by all players. `preset veryslow`: an [ffmpegcodecs](https://ffmpeg.org/ffmpeg-codecs.html#Common-Options-1) option forces the encoder to use its most advanced, tightest compression algorithms. Because the video is only 1 FPS, this will still encode incredibly fast, but it will strip away every single byte of unnecessary frame bloat. `tune stillimage`: an [ffmpeglibx264](https://trac.ffmpeg.org/wiki/Encode/H.264#Tune) parameter which tells the H.264 encoder that the video is a single, unchanging picture and that it should optimize the video for still content. `crf 35`: set image quality to lowest visuallyimperceptible quality loss `g 300`: keyframe every 5 minutes, this keeps seeking time (and player responsiveness) at acceptable level `movflags +faststart`: an [ffmpegMPEG4](https://ffmpeg.org/ffmpeg-formats.html#Fragmentation) parameter moves the video's index metadata (moov atom) from the end of the file to the beginning, so the players can start playing the video instantly.

🎬 Final command (with `-crf 35`):<br>video overhead 3.4% (2.78MB) of 81.6MB (length 1:20:36, audio 78.2MB, image 135kB)

ffmpeg -loop 1 -framerate 1 -i image.jpg -i audio.mp4 -c:v libx264 -preset veryslow -tune stillimage -crf 35 -g 300 -pix_fmt yuv420p -c:a copy output.mkv

… or with `-crf 41`:<br>video overhead 2.4% (1.97MB) of 80.8MB (length 1:20:36, audio 78.2MB, image 135kB)

ffmpeg -loop 1 -framerate 1 -i image.jpg -i audio.mp4 -c:v libx264 -preset veryslow -tune stillimage -crf 41 -g 300 -pix_fmt yuv420p -c:a copy output.mkv

💾 Bash function to add into `~/.profile` file

# =========================================================================
# Static (one-image) video creating
# =========================================================================
# usage: stillvideo <image> <audio> <output> [<quality>?]
#
# See documentation:
#   https://trac.ffmpeg.org/wiki/Slideshow#Addingaudio
#   https://ffmpeg.org/ffmpeg.html
function stillvideo() {
  if [ $# -lt 3 ]
  then
    echo "usage: stillvideo <image> <audio> <output> [<quality>?]"
    echo "  <quality> is one of the bitrates (e.g. 128k/160k/192k/256k)."
    echo "  If no <quality> is specified, audio will be copied w/o conversion."
    return
  fi
 
  if [ $# -lt 4 ]
  then # copy audio
    audiocmd=(-c:a copy)
  else # convert audio to specified quality
    audiocmd=(-c:a aac -b:a $4)
  fi
 
  # save command to array
  crf=35 # video quality (0=lossless..51, default 23) - best is 35, 41 is OK
  g=300  # keyframe frequency, default is every 250-th frame - best is 300
  cmd=(ffmpeg -stats_period 2 -loop 1 -framerate 1 -i "$1" -i "$2" -c:v libx264 -preset veryslow -tune stillimage -crf $crf -g $g -pix_fmt yuv420p "${audiocmd[@]}" -shortest -movflags +faststart "$3")
 
  # execute command & print the info
  "${cmd[@]}"
  videostats "$3"
  echo $'\n'"Executed command:"$'\n'"  ${cmd[@]}"
}

Page visits
1338
total
3
today
1

Comments

blog/odborny/2026-07-09-creating_static_one-image_video_with_ffmpeg.txt · Posledná úprava: 2026/07/09 22:32 od Róbert Toth