-
-
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) |
I am getting this error with the first code example and the simplified version on stackoverflow. Any solution?
audio_bitrate = float(next((s for s in probe['streams'] if s['codec_type'] == 'audio'), None)['bit_rate'])
TypeError: 'NoneType' object is not subscriptable
Please print out the probe
variable. Maybe something is missing here.
Spoiler - fixed it allready
{'streams': [{'index': 0, 'codec_name': 'h264', 'codec_long_name': 'H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10', 'profile': 'High', 'codec_type': 'video', 'codec_time_base': '1001/60000', 'codec_tag_string': 'avc1', 'codec_tag': '0x31637661', 'width': 1920, 'height': 1080, 'coded_width': 1920, 'coded_height': 1088, 'closed_captions': 0, 'has_b_frames': 1, 'sample_aspect_ratio': '1:1', 'display_aspect_ratio': '16:9', 'pix_fmt': 'yuv420p', 'level': 40, 'color_range': 'tv', 'color_space': 'bt709', 'color_transfer': 'bt709', 'color_primaries': 'bt709', 'chroma_location': 'left', 'field_order': 'progressive', 'refs': 1, 'is_avc': 'true', 'nal_length_size': '4', 'r_frame_rate': '30000/1001', 'avg_frame_rate': '30000/1001', 'time_base': '1/30000', 'start_pts': 0, 'start_time': '0.000000', 'duration_ts': 23635612, 'duration': '787.853733', 'bit_rate': '18752', 'bits_per_raw_sample': '8', 'disposition': {'default': 1, 'dub': 0, 'original': 0, 'comment': 0, 'lyrics': 0, 'karaoke': 0, 'forced': 0, 'hearing_impaired': 0, 'visual_impaired': 0, 'clean_effects': 0, 'attached_pic': 0, 'timed_thumbnails': 0}, 'tags': {'creation_time': '2021-04-03T13:57:24.000000Z', 'language': 'und', 'handler_name': 'ISO Media file produced by Google Inc.'}}], 'format': {'filename': 'input.mp4', 'nb_streams': 1, 'nb_programs': 0, 'format_name': 'mov,mp4,m4a,3gp,3g2,mj2', 'format_long_name': 'QuickTime / MOV', 'start_time': '0.000000', 'duration': '787.853733', 'size': '196029230', 'bit_rate': '1990513', 'probe_score': 100, 'tags': {'major_brand': 'dash', 'minor_version': '0', 'compatible_brands': 'iso6avc1mp41', 'creation_time': '2021-04-03T13:57:24.000000Z'}}}my ytdl params are ydl_opts = {'cookiefile': 'cookies.txt',
'output': 'C:/xxx/videos',
'outtmpl': 'input.mp4',
'format': 'bestvideo[ext=mp4]',
'progress_hooks': [self.my_hook]}
My format was without audio, so i have to add
'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo+bestaudio',
Next problem:
Spoiler - fixed it allready too
Now a new problem occures:raise Error('ffprobe', out, err)
ffmpeg._run.Error: ffprobe error (see stderr output for detail). i found out that there is a missing av1 encoder, but not how to solve the problem.
Fixed it allready by updating ffmpeg.
Now this error occures:
duration = float(probe['format']['duration'])
KeyError: 'format'
Spoiler - fixed it allready
{'streams': [{'index': 0, 'codec_name': 'h264', 'codec_long_name': 'H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10', 'profile': 'High', 'codec_type': 'video', 'codec_time_base': '1001/60000', 'codec_tag_string': 'avc1', 'codec_tag': '0x31637661', 'width': 1920, 'height': 1080, 'coded_width': 1920, 'coded_height': 1088, 'closed_captions': 0, 'has_b_frames': 1, 'sample_aspect_ratio': '1:1', 'display_aspect_ratio': '16:9', 'pix_fmt': 'yuv420p', 'level': 40, 'color_range': 'tv', 'color_space': 'bt709', 'color_transfer': 'bt709', 'color_primaries': 'bt709', 'chroma_location': 'left', 'field_order': 'progressive', 'refs': 1, 'is_avc': 'true', 'nal_length_size': '4', 'r_frame_rate': '30000/1001', 'avg_frame_rate': '30000/1001', 'time_base': '1/30000', 'start_pts': 0, 'start_time': '0.000000', 'duration_ts': 23635612, 'duration': '787.853733', 'bit_rate': '18752', 'bits_per_raw_sample': '8', 'disposition': {'default': 1, 'dub': 0, 'original': 0, 'comment': 0, 'lyrics': 0, 'karaoke': 0, 'forced': 0, 'hearing_impaired': 0, 'visual_impaired': 0, 'clean_effects': 0, 'attached_pic': 0, 'timed_thumbnails': 0}, 'tags': {'creation_time': '2021-04-03T13:57:24.000000Z', 'language': 'und', 'handler_name': 'ISO Media file produced by Google Inc.'}}], 'format': {'filename': 'input.mp4', 'nb_streams': 1, 'nb_programs': 0, 'format_name': 'mov,mp4,m4a,3gp,3g2,mj2', 'format_long_name': 'QuickTime / MOV', 'start_time': '0.000000', 'duration': '787.853733', 'size': '196029230', 'bit_rate': '1990513', 'probe_score': 100, 'tags': {'major_brand': 'dash', 'minor_version': '0', 'compatible_brands': 'iso6avc1mp41', 'creation_time': '2021-04-03T13:57:24.000000Z'}}}
my ytdl params are ydl_opts = {'cookiefile': 'cookies.txt', 'output': 'C:/xxx/videos', 'outtmpl': 'input.mp4', 'format': 'bestvideo[ext=mp4]', 'progress_hooks': [self.my_hook]}My format was without audio, so i have to add
'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo+bestaudio',
Next problem:
Spoiler - fixed it allready too
Now a new problem occures:raise Error('ffprobe', out, err)
ffmpeg._run.Error: ffprobe error (see stderr output for detail). i found out that there is a missing av1 encoder, but not how to solve the problem.
Fixed it allready by updating ffmpeg.
Now this error occures:
duration = float(probe['format']['duration'])
KeyError: 'format'
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 field format
, but reports missing here. Please print it again to have a look.🧐
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
I am getting this error with the first code example and the simplified version on stackoverflow. Any solution?
TypeError: 'NoneType' object is not subscriptable