There are no problems we cannot solve together, and very few that we can solve by ourselves, Lyndon B. Johnson.
Learn everything you can, anytime you can, from anyone you can – there will always come a time when you will be grateful you did, Sarah Caldwell.
If the only tool you have is a hammer, you tend to see every problem as a nail, Abraham Maslow.
The aim is to provide a simple script to resize and compress all your images recursively at once, that is, bulk resize and compress images with Python and Pillow.
"""
File: compressImage.py
Author: Máximo Núñez Alarcón
Description: It aims to automate the process of resizing and compressing images in a directory and provides logging to track the changes.
Usage: python compressImage.py (it needs to activate the environment)
"""
import os # Interact with the operating system.
from PIL import Image # PIL allows opening, manipulating, and saving many different image file formats.
import shutil # Functions to operate on files and directories
from util import check_all_files, printUrl, append_to_file, cleanLog
def compress_image(image_path, max_size_mb=2, max_height=1700):
"""
Compresses an image to reduce its file size while maintaining aspect ratio.
Args:
image_path (str): The path to the image file.
max_size_mb (float, optional): The maximum allowed size in megabytes (defaults is 2 mb).
max_height (int, optional): The maximum height of the image in pixels (defaults to 1700).
Returns:
None
"""
# If the image is corrupted or the path does not exist, then remove it and/or return
try:
with Image.open(image_path) as img:
pass
except FileNotFoundError:
print(f"File '{image_path}' does not exist.")
append_to_file(f"File '{image_path}' does not exist.")
return
except Exception as e:
os.remove(image_path)
print(f"An error occurred while processing '{image_path}': {e}")
append_to_file(f"An error occurred while processing '{image_path}': {e}")
return
# Open the image
with Image.open(image_path) as img:
# os.path.getsize(image_path) Get the image size in bytes.
img_size_mb = os.path.getsize(image_path) / (1024 * 1024) # Convert bytes to megabytes
# Check if the image size is already within the limit
if img_size_mb <= max_size_mb:
# Return if image is already within the size limit
return
# Get the original width and height in pixels of the image
width, height = img.size
# Calculate the new width and height while maintaining aspect ratio
if height > max_height:
# height --> new_height
# width --> new_width
new_height = max_height
new_width = int((width / height) * max_height)
else:
new_width = width
new_height = height
# Resize the image. The method size() required: size in pixels, as a 2-tuple: (width, height),
# resample, an optional resampling filter. PIL.Image.LANCZOS is a high-quality downsampling filter.
# It resize the image. It doesn't modify the original.
img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
# Save the resized image under the given filename with reduced quality
img.save(image_path, quality=80)
# Get the new size of the compressed image
new_size_mb = os.path.getsize(image_path) / (1024 * 1024)
append_to_file(f"Image {image_path} compressed to {new_size_mb:.2f} MB with dimensions {new_width}x{new_height} pixels.")
def apply_func_image_files(path, func):
"""
Recursively list and apply "func" to all image files in a directory and its subdirectories.
Args:
- path (str): The path to the directory to search.
- func (function): The function to apply to each image file found.
"""
# Iterate over all files and directories in the given path.
# os.walk() generate the file names in a directory tree by traversing recursively the tree.
# For each directory in the tree rooted at directory top (including top itself), it yields a 3-tuple (dirpath, dirnames, filenames).
for root, dirs, files in os.walk(path, topdown=True):
for file in files:
# Check if the file is an image based on its extension
if file.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp')):
# Get the full path of the image file
image_path = os.path.join(root, file)
# Call the provided function with the image path
func(image_path)
def get_directory_size(directory):
# Get the size of the directory in bytes
size_in_bytes = shutil.disk_usage(directory).used
# Convert the size from bytes to megabytes
size_in_mb = size_in_bytes / (1024 * 1024)
# Return the size in megabytes
return size_in_mb
def my_main():
cleanLog()
directory_path = "/your/path" # From this directory, the script will recursively work.
size_in_mb = get_directory_size(directory_path) # Track the size of your directory before and after the script
append_to_file("-------------------BEGIN--------------------------------------")
# The f before the string indicates that it is a formatted string literal,
# and {} are placeholders for variables to be inserted into the string.
append_to_file(f"Total size of {directory_path}: {size_in_mb:.2f} MB")
apply_func_image_files(directory_path, compress_image)
size_in_mb = get_directory_size(directory_path)
append_to_file(f"Total size of {directory_path}: {size_in_mb:.2f} MB")
append_to_file("-------------------END--------------------------------------")
if __name__ == "__main__":
my_main()