Last active
March 31, 2022 18:25
-
-
Save monkishtypist/7eefacee3c3a3935d9928dd0181bb866 to your computer and use it in GitHub Desktop.
Image Compression and Resize with Python3 and Pillow
This file contains 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
from PIL import Image | |
import getopt | |
import os | |
import shutil | |
import sys | |
def process_images(argv): | |
arg_help = "{0} -i <input_folder> -o <output_folder> -a -c -d <delimiter> -f -s".format(argv[0]) | |
# Args | |
# -i, --input <input_folder> change the imput folder | |
# -o, --output <output_folder> change the output/destination folder | |
# -a, --append <delimiter> append image width to output file names | |
# -n, --clean clean the destination folder before processing | |
# -f, --flatten flatten the output destination folder structure (no sub-directories) | |
# -s, --sizes generate source set sizes | |
input_folder = "./images/" # image files source | |
output_folder = "./compressed_images/" # image files output | |
append = False # append image width to output file names | |
clean = False # clean up output folder before starting | |
delimiter = "__" # delimits output file naming convention {file name}{delimiter}{resized image width}{file extension} | |
flatten = False # flatten output folders | |
sizes = False # create source set sizes | |
# Image handling | |
compress_quality = 75 # JPEG compression quality | |
compress_level = 7 # PNG compression level | |
optimize = True # run image optimization on save | |
output_widths = [2880, 1440, 768] # widths at which the files are resized down to | |
file_types = ['.jpg', '.png'] # supported image file types for compression and resizing | |
try: | |
opts, args = getopt.gnu_getopt(argv[1:], "hi:o:acd:fs", ["help", "input=", "output=", "append", "clean", "delimiter=", "flatten", "sizes"]) | |
except: | |
print(arg_help) | |
sys.exit(2) | |
for opt, arg in opts: | |
if opt in ("-h", "--help"): | |
print(arg_help) # print the help message | |
sys.exit(2) | |
elif opt in ("-i", "--input"): | |
input_folder = os.path.join(arg, "") | |
elif opt in ("-o", "--output"): | |
output_folder = os.path.join(arg, "") | |
elif opt in ("-a", "--append"): | |
append = True | |
elif opt in ("-c", "--clean"): | |
clean = True | |
elif opt in ("-d", "--delimit"): | |
delimiter = str(arg) | |
elif opt in ("-f", "--flatten"): | |
flatten = True | |
elif opt in ("-s", "--sizes"): | |
sizes = True | |
# Clean up output directory/files | |
if clean : | |
try : | |
shutil.rmtree(output_folder) | |
except FileNotFoundError : | |
print("'%s' directory not found" % output_folder) | |
pass | |
else : | |
print("Successfully removed the directory '%s'" % output_folder) | |
# (Re)Create our output location | |
try : | |
os.makedirs(output_folder) | |
except OSError: | |
print("Creation of the directory '%s' failed" % output_folder) | |
else : | |
print("Successfully created the directory '%s'" % output_folder) | |
# Do the work | |
for root, dirs, files in os.walk(input_folder, topdown = True) : | |
# Maintain folder structure or flatten? | |
if not flatten : | |
for dir in dirs : | |
full_dir = os.path.join(output_folder, dir) | |
try : | |
os.makedirs(full_dir) | |
except OSError: | |
print("Creation of the directory '%s' failed" % full_dir) | |
else : | |
print("Successfully created the directory '%s'" % full_dir) | |
# Process files | |
for file in files : | |
name, ext = os.path.splitext(file) | |
if ext not in file_types : | |
print("Skipped file %s, extension not valid" % file) | |
else : | |
with Image.open(os.path.join(root, file)) as im : | |
original_width = int(im.size[0]) | |
original_height = int(im.size[1]) | |
max_output_height = original_width * 2 | |
# Maintain folder structure or flatten? | |
if flatten : | |
output_destination = output_folder | |
else : | |
output_destination = root.replace(input_folder, output_folder) | |
# Generate output file name | |
if append : | |
output_file = name + delimiter + str(original_width) + ext | |
else : | |
output_file = file | |
# Generate compressed copy of original image | |
try : | |
im.save(os.path.join(output_destination, output_file), im.format, optimize=optimize, quality=compress_quality, compress_level=compress_level) | |
except OSError : | |
print("There was an error saving %s" % output_file) | |
else : | |
print("Image %s successfully saved" % output_file) | |
# Generate resized images for source sets | |
if sizes : | |
for output_width in output_widths: | |
if output_width < original_width : | |
output_file = name + delimiter + str(output_width) + ext # resized image file names are always appended | |
im.thumbnail((output_width, max_output_height)) # We use .thumbnail() because it preserves aspect ratio | |
try : | |
im.save(os.path.join(output_destination, output_file), im.format, optimize=optimize, quality=compress_quality, compress_level=compress_level) | |
except OSError : | |
print("There was an error saving %s" % output_file) | |
else : | |
print("Image %s successfully saved" % output_file) | |
if __name__ == "__main__": | |
process_images(sys.argv) | |
# Do this: | |
# Make sure you have Python3 and Pillow installed | |
# Put all your JPG and PNG images into the <input_folder> | |
# You can change compression quality with compress_quality variable [for JPEG] and the compress_level variable [for PNG] | |
# Run script with python3: `python3 imageprocess.py` | |
# Reference materials: | |
# https://pillow.readthedocs.io/en/stable/reference/Image.html | |
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment