Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 789
武状元 Woa Avatar asked Mar 02 '23 23:03

武状元 Woa


1 Answers

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.
like image 171
武状元 Woa Avatar answered Mar 11 '23 21:03

武状元 Woa