FFmpeg lossless filtering

Preserving Data

Running a simple FFmpeg filter command:

>ffmpeg -i input.vid -filter_complex "[v:0]copy" output.vid

… one might expect the output video to be the same as the input video. Note that both input and output videos use the same container format (file name extension), and the command just uses the “copy” (do nothing) filter, so there should be no format compatibility issues, and nothing should be modified.

However, FFmpeg defaults are not designed for lossless filtering, and thus the output video can differ substantially from the input. To help address this, the following command overrides default settings to improve the situation, and is annotated with explanations:

>ffmpeg -i input.vid \
   -filter_complex "[v:0]copy" \

Note: Replace “copy” with desired filter.

   -map 0 \

Transcode all streams.
By default FFmpeg only transcodes at most 1 video, audio, and subtitle streams; all other streams are dropped.

   -c copy -c:v:0 encoder \

Copy unmodified streams.
By default FFmpeg reencodes all streams, including unmodified ones. The encoder (eg, libx264) of the modified stream must be specified explicitly; if you are unsure, then do a dry-run with -t 1 and see what encoder FFmpeg uses by default.

   -map_metadata:s:0 0:s:0 ... \
   -map_metadata:s:n 0:s:n \

Copy per-stream metadata.
The -filter_complex does not map metadata from the original stream. Use -filter instead whenever possible, or map metadata manually; note that once you map 1 stream manually, then you must map them all manually, including unfiltered streams!

   -vsync passthrough \

Preserve video frame-rate.
By default FFmpeg converts variable frame-rate (VFR) video to constant frame-rate (CFR).


Mezzanine Formats

Of course, when applying an actual filter (ie, not copy), a modified output is expected. However, depending on the filter, modification may only apply to a portion of the video. Consider the case of modifying a single frame:

>ffmpeg -i video.vid -i image.png -filter_complex "[0:v:0][1]overlay=eof_action=pass" output.vid

… one might like the output video to be the same as the input video, minus the 1st frame.

However, again, FFmpeg defaults are not designed for lossless filtering. If you are modifying video with the expectation of doing additional work on it in the future, and would like to work as losslessly as possible so that quality does not degrade with each iteration, then look at using a “mezzanine” (lossless intermediate) output file format, such as:

>ffmpeg -i video.vid -i image.png -filter_complex "[0:v:0][1]overlay=eof_action=pass:format=auto" -c:v ffv1 output.mkv


>ffmpeg -i video.vid -i image.png -filter_complex "[0:v:0][1]overlay=eof_action=pass:format=auto" -c:v libx264 -qp 0 -preset ultrafast output.mp4


>ffmpeg -i video.vid -i image.png -filter_complex "[0:v:0][1]overlay=eof_action=pass:format=auto" -c:v libx264rgb -crf 0 -preset ultrafast output.mov

Pick an option that is compatible with the input video format and colour profile, and note that lossless codecs inevitably produce much larger output files than lossy ones. Also remember to add options for preserving data as needed, but keeping in mind that whenever the output container format (file name extension) differs from input, data cannot always be preserved. For example, mp4 supports a timescale (time granularity) of 90000/s, while mkv is limited to 1000.

Note also that the overlay filter in particular requires a format=auto parameter to work losslessly, as exampled.

There are many lossless codec options available, but compatibility with the input video (and sometimes output format requirements) restrict what will actually work in practice. Thus, testing the results is important. This can be done with the SSIM filter:

>ffmpeg -i input.vid -i output.vid -filter_complex ssim=difference.ssim -f null -

The resulting difference.ssim file contains a frame-by-frame comparison of the 2 videos. For example, here the first frame is different, and the rest are unchanged, as expected:

n:1 R:0.082736 G:0.106563 B:0.095026 All:0.094775 (0.432435)
n:2 R:1.000000 G:1.000000 B:1.000000 All:1.000000 (inf)
n:3 R:1.000000 G:1.000000 B:1.000000 All:1.000000 (inf)

Testing can also be done by extracting images from the videos and comparing the resulting files.

Leave a Reply

Your email address will not be published. Required fields are marked *