Skip to content

Instantly share code, notes, and snippets.

@jfrobbins
Last active December 6, 2024 13:32
Show Gist options
  • Select an option

  • Save jfrobbins/0f8ef94b1a7ff780f74a320a2fd9530c to your computer and use it in GitHub Desktop.

Select an option

Save jfrobbins/0f8ef94b1a7ff780f74a320a2fd9530c to your computer and use it in GitHub Desktop.
Transcode MP4 video file for smaller file size (lower bitrate)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
transcode_mp4_files_x265.py
A script to batch transcode .mp4 files using FFmpeg with configurable CRF values.
source: https://gist.github.com/jfrobbins/0f8ef94b1a7ff780f74a320a2fd9530c
############################################################################
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>
"""
###############
# example:
# python transcode_mp4_files_x265.py ./your_directory 28
###############
import os
import sys
import subprocess
# Default CRF value
default_crf_value = 30
# Parse command-line arguments
if len(sys.argv) < 2:
print("Usage: python transcode_files.py <input_directory> [crf_value]")
sys.exit(1)
input_dir = sys.argv[1]
crf_value = int(sys.argv[2]) if len(sys.argv) > 2 else default_crf_value
# Resolve the absolute path of the input directory
input_dir = os.path.abspath(input_dir)
# Output directory
output_dir = os.path.join(input_dir, f"crf_{crf_value}")
os.makedirs(output_dir, exist_ok=True) # Create the output directory if it doesn't exist
# Loop through all files in the input directory
for file in os.listdir(input_dir):
if file.endswith(".mp4"): # Only process .mp4 files
input_file = os.path.join(input_dir, file)
# Generate the output filename
file_name, file_ext = os.path.splitext(file)
output_file = f"{file_name}_crf-{crf_value}{file_ext}"
output_path = os.path.join(output_dir, output_file)
# Construct and run the ffmpeg command
command = [
"ffmpeg",
"-i", input_file,
"-vcodec", "libx265",
"-crf", str(crf_value),
output_path
]
print(f"Processing: {input_file} -> {output_path}")
subprocess.run(command)

Transcode MP4 video file for smaller file size (lower bitrate)

Source:

Using the H.265 codec, we can compress the video more for the same quality (vs H.264), or gives higher quality for the same size.

Push the compression lever further by increasing the CRF value — a reasonable range for H.265 may be 24 to 30.

Even higher CRF values will reduce filesize further, but sacrifices quality (depending on the video and goals).

Note that lower CRF values correspond to higher bitrates, and hence produce higher quality videos.

ffmpeg -i input.mp4 -vcodec libx265 -crf 28 output.mp4

You can also specify a libx265 preset, which affects file size and quality.

Examples

CRF Filesize
- 482.6 MB
30 56.5 MB
35 21.3 MB
40 9.5 MB

script

to iterate over all of the files in a directory

note: I used ChatGPT to create this code :-)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
transcode_files.py

A script to batch transcode .mp4 files using FFmpeg with configurable CRF values.

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <https://unlicense.org>
"""


###############
# example:
#   python transcode_files.py ./your_directory 23
###############


import os
import sys
import subprocess

# Default CRF value
default_crf_value = 30

# Parse command-line arguments
if len(sys.argv) < 2:
    print("Usage: python transcode_files.py <input_directory> [crf_value]")
    sys.exit(1)

input_dir = sys.argv[1]
crf_value = int(sys.argv[2]) if len(sys.argv) > 2 else default_crf_value

# Resolve the absolute path of the input directory
input_dir = os.path.abspath(input_dir)

# Output directory
output_dir = os.path.join(input_dir, f"crf_{crf_value}")
os.makedirs(output_dir, exist_ok=True)  # Create the output directory if it doesn't exist

# Loop through all files in the input directory
for file in os.listdir(input_dir):
    if file.endswith(".mp4"):  # Only process .mp4 files
        input_file = os.path.join(input_dir, file)
        
        # Generate the output filename
        file_name, file_ext = os.path.splitext(file)
        output_file = f"{file_name}_crf-{crf_value}{file_ext}"
        output_path = os.path.join(output_dir, output_file)
        
        # Construct and run the ffmpeg command
        command = [
            "ffmpeg",
            "-i", input_file,
            "-vcodec", "libx265",
            "-crf", str(crf_value),
            output_path
        ]
        print(f"Processing: {input_file} -> {output_path}")
        subprocess.run(command)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment