-
-
Save ESWZY/a420a308d3118f21274a0bc3a6feb1ff to your computer and use it in GitHub Desktop.
# Simplified version and explanation at: https://stackoverflow.com/a/64439347/12866353 | |
import os | |
import ffmpeg | |
def compress_video(video_full_path, size_upper_bound, two_pass=True, filename_suffix='cps_'): | |
""" | |
Compress video file to max-supported size. | |
:param video_full_path: the video you want to compress. | |
:param size_upper_bound: Max video size in KB. | |
:param two_pass: Set to True to enable two-pass calculation. | |
:param filename_suffix: Add a suffix for new video. | |
:return: out_put_name or error | |
""" | |
filename, extension = os.path.splitext(video_full_path) | |
extension = '.mp4' | |
output_file_name = filename + filename_suffix + extension | |
# Adjust them to meet your minimum requirements (in bps), or maybe this function will refuse your video! | |
total_bitrate_lower_bound = 11000 | |
min_audio_bitrate = 32000 | |
max_audio_bitrate = 256000 | |
min_video_bitrate = 100000 | |
try: | |
# Bitrate reference: https://en.wikipedia.org/wiki/Bit_rate#Encoding_bit_rate | |
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 = (size_upper_bound * 1024 * 8) / (1.073741824 * duration) | |
if target_total_bitrate < total_bitrate_lower_bound: | |
print('Bitrate is extremely low! Stop compress!') | |
return False | |
# Best min size, in kB. | |
best_min_size = (min_audio_bitrate + min_video_bitrate) * (1.073741824 * duration) / (8 * 1024) | |
if size_upper_bound < best_min_size: | |
print('Quality not good! Recommended minimum size:', '{:,}'.format(int(best_min_size)), 'KB.') | |
# return False | |
# Target audio bitrate, in bps. | |
audio_bitrate = audio_bitrate | |
# 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 | |
if video_bitrate < 1000: | |
print('Bitrate {} is extremely low! Stop compress.'.format(video_bitrate)) | |
return False | |
i = ffmpeg.input(video_full_path) | |
if two_pass: | |
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() | |
else: | |
ffmpeg.output(i, output_file_name, | |
**{'c:v': 'libx264', 'b:v': video_bitrate, 'c:a': 'aac', 'b:a': audio_bitrate} | |
).overwrite_output().run() | |
if os.path.getsize(output_file_name) <= size_upper_bound * 1024: | |
return output_file_name | |
elif os.path.getsize(output_file_name) < os.path.getsize(video_full_path): # Do it again | |
return compress_video(output_file_name, size_upper_bound) | |
else: | |
return False | |
except FileNotFoundError as e: | |
print('You do not have ffmpeg installed!', e) | |
print('You can install ffmpeg by reading https://github.com/kkroening/ffmpeg-python/issues/251') | |
return False | |
if __name__ == '__main__': | |
file_name = compress_video('input.mp4', 50 * 1000) | |
print(file_name) |
Thats all really strange. the print of probe is empty.
that´s because ytdl does not merge audio and video before the hook starts.
This is my code:
def compress_video(self, 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)
print("Probe")
print(probe)
# 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()
def my_hook(self, d):
if d['status'] == 'finished':
filename = d['filename']
print(filename)
self.compress_video(filename, 'output.mp4', 50 * 1000)
print('Done downloading, now converting ...')
async def download(self):
ydl_opts = {'cookiefile': 'cookies.txt',
'output': 'C:/xxx/videos',
'outtmpl': 'input.mp4',
'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
'progress_hooks': [self.my_hook]}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download('https://www.youtube.com/watch?v=CXkkUOCfnOQ')
with this i get
audio_bitrate = float(next((s for s in probe['streams'] if s['codec_type'] == 'audio'), None)['bit_rate'])
TypeError: 'NoneType' object is not subscriptable
because there is a input.f137.mp4 file with no audio only.
ytdl does not merge my files before i convert with ffmpeg. really strange
@Inge1234567890 Yes, because the d['filename']
is just the video part, not the full part.
Maybe you can ues meta = ydl.extract_info(url, download=True)
instead. And read the meta['ext']
(or meta['entries'][0]['ext']
) to get the full filename.
@ESWZY hi I used your code and it works perfectly for my project. I am new to python so I struggling on how to get the stdout to get the realtime status to a progressbar.can you help me out?
@ESWZY hi I used your code and it works perfectly for my project. I am new to python so I struggling on how to get the stdout to get the realtime status to a progressbar.can you help me out?
That's interesting! But I think it is difficult to get the stdout directly from the code above without modifications.
As an idea, the ffmpeg library in Python
just calls the ffmpeg binary
, you can use this library as an alternative (just rename this library as ffmpeg
):
https://github.com/althonos/ffpb
Or, you can parse the output of this Python snippet by following answers:
https://stackoverflow.com/questions/747982/can-ffmpeg-show-a-progress-bar
Do you have any function to compress a video file in Django in production? Thanks
skvideo.io.FFmpegWriter con esta Clase es posible utiliar H264, una fiesta
Thanks for your report! I have not tested videos without audio, and I will fix it.
It's weird that your
probe
already has this fieldformat
, but reports missing here. Please print it again to have a look.🧐