Search code examples
pythonvideo-compression

How to compress video to target size by Python?


I am uploading text and videos to a site by Python program. This site says they only receive video files of up to 50 MB in size. Otherwise, they will reject the video and other associated information.

To ensure I can send video continuously, I want to compress it to target size (e.g. 50 MB) before sending. Because no loss of quality is impossible, it is ok to have moderate clarity loss in video or audio.

Is there any elegant way in Python for this purpose? Thanks!


Solution

  • Compress video files by Python and FFmpeg

    Tools

    FFmpeg is a powerful tool for video editing. And there is a great Python binding named ffmpeg-python (API Reference) for this. Firstly, pip install ffmpeg-python and install FFmpeg.

    Steps

    Probe the configuration of video by function ffmpeg.probe() to get duration, audio & video bit rate and so on. And calculate the bit rate of the target file based on what we have. Then, construct commands by ffmpeg.input() and ffmpeg.output(). Finally, run it.

    Codes

    Following is the example code. Change the compression algo for your situation if you want. For easy follow-up, I hided the code of boundary condition. The code I am using is in GitHub Gist. Any bug report is welcomed!

    import os, ffmpeg
    def compress_video(video_full_path, output_file_name, target_size):
        # Reference: https://en.wikipedia.org/wiki/Bit_rate#Encoding_bit_rate
        min_audio_bitrate = 32000
        max_audio_bitrate = 256000
    
        probe = ffmpeg.probe(video_full_path)
        # Video duration, in s.
        duration = float(probe['format']['duration'])
        # Audio bitrate, in bps.
        audio_bitrate = float(next((s for s in probe['streams'] if s['codec_type'] == 'audio'), None)['bit_rate'])
        # Target total bitrate, in bps.
        target_total_bitrate = (target_size * 1024 * 8) / (1.073741824 * duration)
    
        # Target audio bitrate, in bps
        if 10 * audio_bitrate > target_total_bitrate:
            audio_bitrate = target_total_bitrate / 10
            if audio_bitrate < min_audio_bitrate < target_total_bitrate:
                audio_bitrate = min_audio_bitrate
            elif audio_bitrate > max_audio_bitrate:
                audio_bitrate = max_audio_bitrate
        # Target video bitrate, in bps.
        video_bitrate = target_total_bitrate - audio_bitrate
    
        i = ffmpeg.input(video_full_path)
        ffmpeg.output(i, os.devnull,
                      **{'c:v': 'libx264', 'b:v': video_bitrate, 'pass': 1, 'f': 'mp4'}
                      ).overwrite_output().run()
        ffmpeg.output(i, output_file_name,
                      **{'c:v': 'libx264', 'b:v': video_bitrate, 'pass': 2, 'c:a': 'aac', 'b:a': audio_bitrate}
                      ).overwrite_output().run()
    
    # Compress input.mp4 to 50MB and save as output.mp4
    compress_video('input.mp4', 'output.mp4', 50 * 1000)
    

    Notes

    1. Don't waste your time! Judge the file size before compressing.

    2. You can disable two-pass function by only keeping second ffmpeg.output() without parameter 'pass': 2.

    3. If video bit rate < 1000, it will throw exception Bitrate is extremely low.

    4. The biggest min file size I recommend is:

    # Best min size, in kB.
    best_min_size = (32000 + 100000) * (1.073741824 * duration) / (8 * 1024)
    
    1. If you specify a extremely small target file size, the size of generated file maybe exceed it. For most time, this will not happen.