10

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!

武状元 Woa
  • 690
  • 3
  • 10
  • 24

3 Answers3

20

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.
武状元 Woa
  • 690
  • 3
  • 10
  • 24
  • 1
    How do you do the same with audio files? – MmBaguette Jul 01 '21 at 22:51
  • @MmBaguette Perhaps, this deserves a new question. There are 2 solutions. 1) Convert your audio file to video by ffmpeg, and use this snippet directly. 2) Only pass `audio_bitrate` when running `ffmpeg.output`. Like `ffmpeg.output(i, output_file_name, **{'b:a': audio_bitrate, 'f': 'mp3'}).overwrite_output().run()`. – 武状元 Woa Jul 02 '21 at 07:05
  • Hello, I'm Getting the Following Error even after specifying the whole path of the input and output video file: ` p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) File "D:\Coding\Python\lib\subprocess.py", line 854, in __init__ self._execute_child(args, executable, preexec_fn, close_fds, File "D:\Coding\Python\lib\subprocess.py", line 1307, in _execute_child hp, ht, pid, tid = _winapi.CreateProcess(executable, args, FileNotFoundError: [WinError 2] The system cannot find the file specified ` – Dhruv_2676 Jul 03 '21 at 16:00
  • @Dhruv-2676 It looks like you do not have ffmpeg installed, do you? – 武状元 Woa Jul 03 '21 at 16:20
  • I Think I Downloaded It Properly, If You Have Any Idea For Checking It, Please Tell Me... – Dhruv_2676 Jul 05 '21 at 06:35
  • @Dhruv-2676 Did you have the same error as [this issue](https://github.com/kkroening/ffmpeg-python/issues/251#issuecomment-529064124)? – 武状元 Woa Jul 06 '21 at 09:44
  • @Dhruv-2676 In `ffmpeg.probe(...)` you need to give the argument `cmd="path/to/ffprobe.exe"` and in `.run(...)` you need to give `cmd="path/to/ffmpeg.exe"`. – Guimoute Nov 28 '21 at 16:48
  • I'm facing Error while getting media from postman and passing in to it ```subprocess.run(f'ffmpeg -i {media} -vcodec h264 -acodec mp2 temp_location/{new_uuid}')``` *Error is :* ```wa.mp4: No such file or directory``` – MdHassan413 Sep 28 '22 at 12:09
  • @MdHassan413 looks like a path problem, are you sure there is a `wa.mp4`? – 武状元 Woa Sep 28 '22 at 12:19
  • Actually, media coming from Postman so I think there is no path for that `{media}` it is media directly... -*Q .* Can I store media without an extension like `wa` only? – MdHassan413 Sep 28 '22 at 13:01
  • @MdHassan413 Because we have to extract some information from the video, we must first download it locally... – 武状元 Woa Sep 28 '22 at 13:06
  • OK, So if I wanted to store without an extension like `wa` only? I had tried to do it but was not able to store it without an extension also used `-f` and this is not working. – MdHassan413 Sep 28 '22 at 13:09
  • @MdHassan413 Yes, you should use `-f` to specify the type. You can have a look at https://stackoverflow.com/questions/9869120/ffmpeg-output-file-format-with-no-extension – 武状元 Woa Sep 28 '22 at 13:25
1

I have used MoviePy in the past with good results. Another option (not the safest) might be to run FFMpeg as an external process to compress or resize the media file.

0

Split the video into segments, compress each segment, and then merge the compressed segments back into a single video file. This will reduce the execution time into half. Here's an explanation of the code:

import time
import os
from moviepy.editor import VideoFileClip, concatenate_videoclips
from threading import Thread

input_file = r'D:\d_test\video_1.mp4'
output_dir = r'D:\d_test\output'
os.makedirs(output_dir, exist_ok=True)

start_time = time.time()
split_duration = 60
video = VideoFileClip(input_file)
video_duration = video.duration
num_splits = int(video_duration / split_duration) + 1
def process_video_segment(start_time, end_time, output_file):
    segment = video.subclip(start_time, end_time)
    compressed_segment = segment.resize(width=640, height=480).fx('codecname', 'libx265', bitrate='500k')
    compressed_segment.write_videofile(output_file, codec='libx264')

threads = []
for i in range(num_splits):
    split_start_time = i * split_duration
    split_end_time = min((i + 1) * split_duration, video_duration)
    output_file = os.path.join(output_dir, f'compressed_segment{i+1}.mp4')
    process_thread = Thread(target=process_video_segment, args=(split_start_time, split_end_time, output_file))
    process_thread.start()
    threads.append(process_thread)

for thread in threads:
    thread.join()

print("Video segments processed successfully.")
compressed_files = [os.path.join(output_dir, f'compressed_segment{i+1}.mp4') for i in range(num_splits)]

merged_file = os.path.join(output_dir, 'merged_video.mp4')
clips = [VideoFileClip(file) for file in compressed_files]
final_clip = concatenate_videoclips(clips)
final_clip.write_videofile(merged_file, codec='libx264')

print("Video merging completed successfully.")

end_time = time.time()
execution_time = end_time - start_time
print(f"Execution time: {execution_time} seconds")
  • 1
    Welcome to Stack Overflow! Most or all of your six answers here appear likely to have been entirely or partially written by AI (e.g., ChatGPT). Please be aware that [posting AI-generated content is not allowed here](//meta.stackoverflow.com/q/421831). If you used an AI tool to assist with any answer, I would encourage you to delete it. We do hope you'll stick around and become a valuable part of our community by posting *your own* quality content. Thanks! – NotTheDr01ds Jul 03 '23 at 16:45
  • **Readers should review this answer carefully and critically, as AI-generated information often contains fundamental errors and misinformation.** If you observe quality issues and/or have reason to believe that this answer was generated by AI, please leave feedback accordingly. The moderation team can use your help to identify quality issues. – NotTheDr01ds Jul 03 '23 at 16:45
  • This answer looks like it was generated by an AI (like ChatGPT), not by an actual human being. You should be aware that [posting AI-generated output is officially **BANNED** on Stack Overflow](https://meta.stackoverflow.com/q/421831). If this answer was indeed generated by an AI, then I strongly suggest you delete it before you get yourself into even bigger trouble: **WE TAKE PLAGIARISM SERIOUSLY HERE.** Please read: [Why posting GPT and ChatGPT generated answers is not currently acceptable](https://stackoverflow.com/help/gpt-policy). – tchrist Jul 03 '23 at 21:26
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 04 '23 at 12:18
  • I did the code. First i didn't use threading and splitting. It took around 2 minutes to compress 2.30 minutes video. Then after threading, the execution time is reduced to 45 seconds, where 25 MB file is compressed to 10 MB file. I also tried Handbrake, AV and other approaches. Out of all FFMPEG and MOVIEPY did good – Python-Turtle Jul 05 '23 at 06:43