Last active
November 8, 2024 10:33
-
-
Save MrSimbax/b22d68f08d4b469ae837fa7bf65544fb to your computer and use it in GitHub Desktop.
A script to convert a video to a desired file size.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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