Ahosting Logo
Knowledge Base

How to Stabilize Shaky Video with FFmpeg

Stabilisation is two passes, and it costs frameFirst pass: analysethe whole video,writing a filedescribing the cameramotionSecond pass: applythe frame is moved tocounteract that motionThe edgesmoving the frameexposes the edges, soit crops or fillsDecide the trademore smoothing meansmore cropStabilisation cannot invent picture outside the frame, so every bit of correction costs either field of view or afilled edge.

Handheld footage can be smoothed, and the mechanism explains the trade-off completely: the filter moves each frame to cancel out the camera's movement, which means part of the picture moves outside the frame.

Check the filter exists

ffmpeg -hide_banner -filters | grep vidstab

Stabilisation comes from an optional library, and many distribution builds omit it. If nothing is listed, the filter is not there, which is a build problem instead of a syntax one.

Pass one: analyse

ffmpeg -i shaky.mp4 -vf vidstabdetect=shakiness=5:accuracy=15 -f null -

This produces no video. It reads the whole file and writes transforms.trf describing how the camera moved in every frame.

shakiness runs from 1 to 10 and describes how much movement to expect. accuracy raises the analysis quality and the time it takes.

The file is tied to this exact source. Trim or scale the video and the analysis no longer matches, which is why stabilisation goes first in any chain of operations.

Pass two: correct

ffmpeg -i shaky.mp4 -vf vidstabtransform=smoothing=30:zoom=0:optzoom=1 \
 -c:v libx264 -crf 20 -c:a copy stable.mp4

smoothing is the number of frames averaged over. Higher is steadier and less responsive, at very high values a deliberate pan starts to look like a slow drift, because the filter is trying to remove it.

30 is a reasonable start for handheld footage. Reduce it if intentional camera movement is being flattened.

Zoom is the crop

When a frame is shifted, an empty edge appears. optzoom=1 lets the filter work out the minimum zoom that hides the empty areas across the whole video.

That zoom is the cost. The finished video is more cropped and slightly softer, because a smaller area of the original is being enlarged to fill the frame.

Shakier source, more crop. On badly shaken footage the result can lose a noticeable share of the picture, and at that point the honest answer is that the footage is not recoverable to broadcast quality.

If you know in advance that footage will be stabilised, shoot wider than your final framing and let the crop take the margin.

Order matters

Stabilise first, scale afterwards.

ffmpeg -i shaky.mp4 -vf "vidstabtransform=smoothing=30:optzoom=1,scale=1280:-2" \
 -c:v libx264 -crf 20 -c:a copy out.mp4

Scaling before stabilising throws away detail the analysis needed, and the analysis file was made against the original dimensions anyway. Understanding FFmpeg filters walks through why chains run left to right.

The one-pass alternative

ffmpeg -i shaky.mp4 -vf deshake -c:v libx264 -crf 20 -c:a copy out.mp4

deshake needs no analysis pass and is noticeably worse. It works on a short window instead of the whole clip, so it removes jitter and leaves drift.

It is useful when processing must be automatic and single-pass. A queue of user uploads, where two passes over every file is not affordable. For anything you will publish, the two-pass route is worth the time.

Rolling shutter

Phone and mirrorless footage often has a wobble where vertical lines lean and bend during movement. Stabilisation does not fix that. It may make it more obvious by holding the frame still while the distortion continues.

That is a separate correction, and if it dominates the footage, stabilisation alone will disappoint.

Cost, and where it belongs

Two full decodes plus the analysis. On long files this is slow, and it is entirely processor-bound.

It belongs in a queue rather than in front of a waiting user, building a batch video processing pipeline goes into the arrangement, and performance optimisation walks through making the encode side faster once the analysis is done.

Judge whether the footage is worth stabilising

Stabilisation crops and softens the picture, so material that is only slightly unsteady is frequently better left alone.

ffmpeg -i in.mp4 -vf "vidstabdetect=shakiness=5:accuracy=15:result=t.trf" -f null - 2>&1 | tail -3
head -5 t.trf

The analysis file records the movement detected per frame. Large values throughout mean genuine camera shake; small ones mean the movement is mostly intentional and correcting it will look wrong.

Footage with deliberate panning is the case to be careful with. Stabilisation resists that motion as well, producing a result where the camera appears to fight the operator.

The crop is the cost, and it is adjustable

Correcting movement means moving the frame, which leaves empty edges that have to be hidden by zooming in.

ffmpeg -i in.mp4 -vf "vidstabtransform=input=t.trf:zoom=0:optzoom=1:smoothing=15" out.mp4
ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 out.mp4

Automatic zoom calculates the minimum needed, which is usually less than a fixed value would be. The smoothing value controls how much movement is removed, and a higher figure requires more crop.

Read the output dimensions against the input. Losing ten per cent is normal; losing a quarter means the smoothing is set higher than the footage justifies.

Two passes are not optional for good results

The single pass alternative exists and produces noticeably worse output, because it corrects without knowing what comes next.

The analysis pass reads the whole file and records the motion. The correction pass uses that complete picture to smooth movement across the entire clip rather than reacting frame by frame.

time ffmpeg -nostdin -i in.mp4 -vf vidstabdetect=result=t.trf -f null -
time ffmpeg -nostdin -i in.mp4 -vf vidstabtransform=input=t.trf out.mp4

The first pass is fast because it decodes without encoding. The second is a full encode. Together they take roughly the time of one encode plus a decode, which is the honest cost.

Apply it before anything else in the pipeline

Stabilisation should operate on the original material, not on something already processed.

Correcting after a resize means the analysis was performed on fewer pixels and the result is less accurate. Correcting after a crop means the edges needed for the correction have already been discarded.

ffmpeg -i original.mp4 -vf "vidstabtransform=input=t.trf,scale=1280:-2" -c:a copy out.mp4

Combining both in one filter chain, in that order, does the work once and applies each step to the right input. Running them as separate commands costs two encodes and one generation of quality for no benefit.

Confirm the correction did not introduce worse problems

Stabilised footage can develop artefacts that were not in the source, particularly on material with rolling shutter distortion.

ffmpeg -ss 10 -i out.mp4 -frames:v 1 corrected.png
ffmpeg -ss 10 -i in.mp4 -frames:v 1 original.png

Compare the same frame before and after. Straight lines that now bend, or edges that appear to wobble, mean the correction is fighting distortion in the source rather than camera movement.

Where that happens, correcting the distortion first and stabilising afterwards produces a better result than increasing the smoothing, which makes the artefact more pronounced rather than less.

Keep the analysis file with the source

The first pass produces a file describing the movement, and it is specific to that exact input.

ls -la t.trf
ffprobe -v error -show_entries format=duration -of csv=p=0 in.mp4

Reusing it against a different file, or against the same file after trimming, produces corrections applied to the wrong frames and a result that is worse than the original.

Name it after the source and delete it once the corrected file is confirmed. A directory of analysis files with no obvious owner is the state that leads to somebody using the wrong one.