'ffmpeg - how to apply filters without losing quality
Here is a simple request, it has an input, output, and two watermarks. From what I gathered I can't apply -codec copy because I'm using a filter.
ffmpeg -i input.mp4 -i wm-bl.png -i wm-br.png -filter_complex "overlay=x=0:y=H-h,overlay=x=W-w:y=H-h" output.mp4
This does the trick, as far as watermarking is concerned, but the output is compressed into half the original file size.
Is it possible to watermark without losing video quality?
Solution 1:[1]
You must re-encode to perform any filtering
Therefore any attempt at stream copying while filtering will be ignored. This is why -codec copy does nothing for you.
Lossy, but looks lossless
Alternatively you can use proper encoding settings that look "visually lossless", but technically are not truly lossless. Example for H.264:
ffmpeg -i input -codec:v libx264 -crf 18 -preset slow -vf format=yuv420p out.mp4
-crfcontrols quality: range is a log scale of 0-51, 0 is lossless, ~18 is often considered visually lossless, and 23 is default.-presetcontrols encoding speed and therefore compression efficiency: ultrafast, superfast, veryfast, faster, fast, medium (the default), slow, slower, veryslow.-vf format=yuv420puses a compatible chroma subsampling required by most players.
Using a lossless format
Although you must re-encode for filtering it does not mean that you have to lose quality. You can use a lossless encoder, such as:
-codec:v libx264 -crf 0 -preset veryslow-codec:v ffv1
The output file size can be huge–generally much bigger than your input file.
Use CSS or your player to add the watermark
Avoid encoding completely and just deal with the watermark with CSS or with your HTML5 video player.
Also see
Solution 2:[2]
Unfortunately it is not possible when using the overlay filter (at least with FFmpeg version 4.1.6-1~deb10u1 that I uses), even you use a lossless encoder for the final output file. Because the overlay filter itself uses a lossy encoding during the process, the image quality will certainly degrade after overlay; however this degradation usually won't be noticeable, and this filter is well suitable for most videos.
But if your input files are already in lossless formats, and if you absolutely want a lossless result, then don't use the overlay filter. I don't have a good solution for this, but here's my workaround.
- Extract the original video, frame by frame, into PNG files:
mkdir frames
ffmpeg -i input.mp4 frames/%d.png
- Add watermarks or anything you want to overlay onto the video; GraphicsMagick is used in this example:
for f in frames/*.png; do gm composite -geometry +0+640 wm-bl.png "$f" "$f"; gm composite -geometry +1100+640 wm-br.png "$f" "$f"; done
- Reassemble frames into a new video file, using a lossless encoding format; also remember to specify the same framerate as original video:
ffmpeg -framerate 30 -i frames/%d.png -qp 0 -pix_fmt yuv444p output.mp4
It is obvious that this workaround is very inefficient -- modifying a large number of PNG files one by one would take a very long time, especially the original video is long; storing these temporary PNG files will take a lot of space on the file system, and the progress would take even longer if the file system isn't fast enough on accessing that many files.
But it worked for me, I think sharing it may just help other people with a similar issue.
Solution 3:[3]
Try adding format=auto to the details of the -filter_complex parameter.
Or if the value auto isn't suitable in your case, try another - they are listed in the overlay section of FFmpeg documentation.
See the specific example command in my comment of Skinkie's answer of my question "How to losslessly replace a specific area in a range of frames".
Solution 4:[4]
In case anyone could benefit from it:
I have been trying to find a solution to keep video quality after adding drawtext (has Same quality issue as adding filter) on Android (using FFMpegKit lib)
Adding -crf 0, -preset speed or any other common suggestions didn't help.
Using the original video bitrate have helped to solve it:
- get bitrate:
val mediaInformation = FFprobeKit.getMediaInformation(input.path)
val bitrate = mediaInformation.mediaInformation.bitrate.toLong()/1000L
- add it to your FFmpeg command:
"-y -i input.mp4 -vcodec mpeg4 (as example) -b:v ${bitrate}k -acodec copy -vp (whatever filter) output.mp4"
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Low power |
| Solution 3 | Petr DiblÃk |
| Solution 4 |
