Skip to content

Instantly share code, notes, and snippets.

@MrSimbax
Last active November 8, 2024 10:33
Show Gist options
  • Select an option

  • Save MrSimbax/b22d68f08d4b469ae837fa7bf65544fb to your computer and use it in GitHub Desktop.

Select an option

Save MrSimbax/b22d68f08d4b469ae837fa7bf65544fb to your computer and use it in GitHub Desktop.
A script to convert a video to a desired file size.
#!/usr/bin/env python
# A script to convert a video to a desired filesize.
# Example use case: 8 MB Discord upload limit
# NO WARRANTY
#
# Requirements:
# - Python 3
# - ffmpeg should be in PATH
# - ffmpeg-python
import argparse
import os
import ffmpeg
import math
def get_output_path_from_input_path(in_path, postfix, format):
in_dir = os.path.dirname(in_path)
in_basename = os.path.basename(in_path)
out_name = os.path.splitext(in_basename)[0] + postfix
# out_ext = os.path.splitext(in_basename)[1]
out_ext = format
out_basename = out_name + '.' + out_ext
return os.path.join(in_dir, out_basename)
def convert_mb_to_kbit(size_in_mb):
return size_in_mb * 8000
def get_video_duration_in_seconds(in_path):
metadata = ffmpeg.probe(in_path)
return math.ceil(float(metadata['format']['duration']))
def calc_output_bitrate(desired_file_size_in_kbit,
video_duration_in_seconds,
audio_bitrate_in_kbit_per_sec):
return int(desired_file_size_in_kbit / video_duration_in_seconds) - audio_bitrate_in_kbit_per_sec
def null_output_path():
if os.name == 'nt':
return 'NUL'
else:
return '/dev/null'
def print_ffmpeg_args(stream):
print('ffmpeg ' + ' '.join(ffmpeg.get_args(stream)))
def as_kbit_ffmpeg_arg(bitrate):
return str(bitrate) + 'k'
def append_conditional_ffmpeg_args(ffmpeg_args, args):
if args.output_height:
ffmpeg_args['filter:v'] = 'scale=-1:{}'.format(args.output_height)
if args.output_codec == 'vp9':
# because vp9 is very slow, these speed it up at least a little bit
ffmpeg_args['row-mt'] = 1 # free performance, it is not enabled by default for some reason
ffmpeg_args['threads'] = os.cpu_count() or 1
# these reduce quality, but also significantly improve performance
ffmpeg_args['cpu-used'] = 4
ffmpeg_args['tile-columns'] = 4
def run_ffmpeg(ffmpeg_args, args, output):
stream = ffmpeg.input(args.input)
stream = ffmpeg.output(stream, output, **ffmpeg_args)
stream = ffmpeg.overwrite_output(stream)
print_ffmpeg_args(stream)
out, _ = ffmpeg.run(stream, capture_stdout=True)
print(out)
def main():
parser = argparse.ArgumentParser(description='Convert a video to the desired file size by using ffmpeg (by default 8 MB)')
parser.add_argument('input', help='Path to the video to be converted')
parser.add_argument('-o', '--output', help='Path to the final video', type=str)
parser.add_argument('-s', '--output_size', help='The desired size of the video in MB', type=float, default=8)
parser.add_argument('-a', '--output_audio_bitrate', help='The desired audio bitrate in kb/s', type=int, default=128)
parser.add_argument('-r', '--output_height', help='The desired resolution height', type=int)
parser.add_argument('-c', '--output_codec', help='The desired video codec', type=str, default='h264', choices=['h264', 'vp9'])
args = parser.parse_args()
codec_video = 'INVALID'
codec_audio = 'INVALID'
file_format = 'INVALID'
if args.output_codec == 'h264':
codec_video = 'libx264'
codec_audio = 'aac'
file_format = 'mp4'
elif args.output_codec == 'vp9':
codec_video = 'libvpx-vp9'
codec_audio = 'libopus'
file_format = 'webm'
if args.output_codec == 'vp9':
print('WARNING: due to a bug in ffmpeg, progress on first pass might not be shown, but ffmpeg is working in the background')
print(' See https://trac.ffmpeg.org/ticket/9882')
print('WARNING: note that VP9 is a slower encoder than h264, in exchange for better compression efficiency')
if not args.output:
args.output = get_output_path_from_input_path(args.input, postfix='_small', format=file_format)
print('Output path: ' + args.output)
desired_bitrate = calc_output_bitrate(
convert_mb_to_kbit(args.output_size),
get_video_duration_in_seconds(args.input),
args.output_audio_bitrate)
print('Calculated video bitrate: {} kbit/s'.format(desired_bitrate))
# Based on https://trac.ffmpeg.org/wiki/Encode/H.264#twopass
# and https://trac.ffmpeg.org/wiki/Encode/VP9#twopass
# 1st pass
ffmpeg_args = {
'pass': 1,
'an': None,
'c:v': codec_video,
'b:v': as_kbit_ffmpeg_arg(desired_bitrate),
'format': file_format
}
append_conditional_ffmpeg_args(ffmpeg_args, args)
run_ffmpeg(ffmpeg_args, args, null_output_path())
# 2nd pass
ffmpeg_args = {
'pass': 2,
'c:a': codec_audio,
'b:a': as_kbit_ffmpeg_arg(args.output_audio_bitrate),
'c:v': codec_video,
'b:v': as_kbit_ffmpeg_arg(desired_bitrate),
'format': file_format
}
append_conditional_ffmpeg_args(ffmpeg_args, args)
run_ffmpeg(ffmpeg_args, args, args.output)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment