FFmpeg is a free and open-source video editing tool capable of trimming, cropping, concatenating, muxing, and transcoding almost any type of media file you throw at it.
It's also a very robust solution for implementing video automation, as we use it extensively in our own video editing API. For this tutorial we'll use FFmpeg 5.1.2, but any recent version will do.
Use the zoompan filter to create a zoom effect on an image:
$ ffmpeg -loop 1 -framerate 60 -i image.jpg -vf "scale=8000:-1,zoompan=z='zoom+0.001':x=iw/2-(iw/zoom/2):y=ih/2-(ih/zoom/2):d=5*60:s=1920x1280:fps=60" -t 5 -c:v libx264 -pix_fmt yuv420p output.mp4
Using x and y expressions, you can specify where the image zooms in. To get the input width, input height, and zoom factor, we use iw, ih, and zoom, respectively. Here are some examples of zooming into different locations, while keeping everything else the same.
Zooming to the top-left corner:
$ ffmpeg -loop 1 -framerate 60 -i image.jpg -vf "scale=8000:-1,zoompan=z='zoom+0.001':x=0:y=0:d=5*60:s=1920x1280:fps=60" -t 5 -c:v libx264 -pix_fmt yuv420p output.mp4
Zooming to the top-right corner:
$ ffmpeg -loop 1 -framerate 60 -i image.jpg -vf "scale=8000:-1,zoompan=z='zoom+0.001':x=iw-(iw/zoom):y=0:d=5*60:s=1920x1280:fps=60" -t 5 -c:v libx264 -pix_fmt yuv420p output.mp4
Zooming to the bottom-left corner:
$ ffmpeg -loop 1 -framerate 60 -i image.jpg -vf "scale=8000:-1,zoompan=z='zoom+0.001':x=0:y=ih-(ih/zoom):d=5*60:s=1920x1280:fps=60" -t 5 -c:v libx264 -pix_fmt yuv420p output.mp4
Zooming to the bottom-right corner:
$ ffmpeg -loop 1 -framerate 60 -i image.jpg -vf "scale=8000:-1,zoompan=z='zoom+0.001':x=iw-(iw/zoom):y=ih-(ih/zoom):d=5*60:s=1920x1280:fps=60" -t 5 -c:v libx264 -pix_fmt yuv420p output.mp4
We need a slightly different approach if we want to zoom out instead of in. Rather than starting with a zoom factor of 1.0, we need to set the zoom to a high value and decrease it with each frame. Unfortunately, FFmpeg doesn't let us change the initial zoom factor. There is, however, a way around this:
$ ffmpeg -loop 1 -framerate 60 -i image.jpg -vf "scale=8000:-1,zoompan=z='if(lte(zoom,1.0),1.5,max(1.001,zoom-0.0015))':x=iw/2-(iw/zoom/2):y=ih/2-(ih/zoom/2):d=5*60:s=1920x1280:fps=60" -t 5 -c:v libx264 -pix_fmt yuv420p output.mp4
Here's what's happening. With an if statement, we're changing the initial zoom factor to 1.5 from 1.0 at the first frame. Next, the value decreases as each subsequent frame passes, but never below 1.001 to keep it from being reset. This causes the zoom factor to decrease from 1.5 to 1.001, giving the impression that the image is zooming out.
By default, zoompan only works on still images. If you were using one of the previous examples with a video as input, you will see that only the first frame is being used. We can make zoompan work with videos by doing this:
$ ffmpeg -i video.mp4 -vf "fps=60,scale=8000:-1,zoompan=z='pzoom+0.001':x=iw/2-(iw/zoom/2):y=ih/2-(ih/zoom/2):d=1:s=1920x1280:fps=60" -t 5 -c:v libx264 -pix_fmt yuv420p output.mp4