Siddharthan Asokan commited on
Commit
0c5499c
·
1 Parent(s): dcfa95b

Create optimize_image.py

Browse files
Files changed (1) hide show
  1. optimize_image.py +235 -0
optimize_image.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import sys
4
+ from PIL import Image
5
+ import glob
6
+ from pathlib import Path
7
+ import shutil
8
+
9
+ # Configuration
10
+ TARGET_SIZE = 100 * 1024 # 100KB in bytes
11
+ TARGET_FOLDERS = ["alt", "thumbnail"]
12
+ QUALITY_STEP = 5 # How much to reduce quality on each iteration
13
+ MIN_QUALITY = 20 # Don't go below this quality
14
+ INITIAL_QUALITY = 85 # Starting quality
15
+ RESIZE_FACTOR = 0.9 # Resize factor when quality reduction alone isn't enough
16
+
17
+ def get_file_size(file_path):
18
+ """Get file size in bytes"""
19
+ return os.path.getsize(file_path)
20
+
21
+ def optimize_image(image_path, target_size=TARGET_SIZE):
22
+ """
23
+ Optimize an image to be under the target size
24
+ Returns: (success, original_size, new_size)
25
+ """
26
+ # Check if file exists
27
+ if not os.path.exists(image_path):
28
+ print(f"File not found: {image_path}")
29
+ return False, 0, 0
30
+
31
+ # Get original size
32
+ original_size = get_file_size(image_path)
33
+
34
+ # If already under target size, no need to optimize
35
+ if original_size <= target_size:
36
+ print(f"Already optimized: {image_path} ({original_size} bytes)")
37
+ return True, original_size, original_size
38
+
39
+ # Create a backup copy
40
+ backup_path = f"{image_path}.backup"
41
+ try:
42
+ shutil.copy2(image_path, backup_path)
43
+ except Exception as e:
44
+ print(f"Failed to create backup of {image_path}: {str(e)}")
45
+ return False, original_size, 0
46
+
47
+ try:
48
+ # Open the image
49
+ img = Image.open(image_path)
50
+
51
+ # Get format and extension
52
+ img_format = img.format
53
+ extension = os.path.splitext(image_path)[1].lower()
54
+
55
+ # Convert PNG to JPG if it's not transparent
56
+ if img_format == "PNG" and extension == ".png":
57
+ if not img.mode == 'RGBA' or not has_transparency(img):
58
+ img = img.convert('RGB')
59
+ img_format = "JPEG"
60
+ new_path = os.path.splitext(image_path)[0] + ".jpg"
61
+ else:
62
+ new_path = image_path
63
+ else:
64
+ new_path = image_path
65
+ if img.mode == 'RGBA':
66
+ # JPG doesn't support transparency, convert to RGB
67
+ if img_format == "JPEG" or extension in [".jpg", ".jpeg"]:
68
+ img = img.convert('RGB')
69
+
70
+ # Start with original size and initial quality
71
+ quality = INITIAL_QUALITY
72
+ width, height = img.size
73
+
74
+ # Try to optimize by reducing quality first
75
+ while quality >= MIN_QUALITY:
76
+ if img_format == "JPEG" or img_format == "PNG":
77
+ img.save(new_path, format=img_format, quality=quality, optimize=True)
78
+ else:
79
+ # For other formats, just save with PIL's default settings
80
+ img.save(new_path, format=img_format)
81
+
82
+ # Check new size
83
+ new_size = get_file_size(new_path)
84
+
85
+ if new_size <= target_size:
86
+ # If we changed from PNG to JPG, remove the original
87
+ if new_path != image_path:
88
+ os.remove(image_path)
89
+
90
+ # Remove the backup
91
+ os.remove(backup_path)
92
+
93
+ # Log the result
94
+ size_reduction = original_size - new_size
95
+ percentage = (size_reduction / original_size) * 100
96
+ print(f"Optimized: {image_path} - {original_size} → {new_size} bytes ({percentage:.1f}% reduction)")
97
+ return True, original_size, new_size
98
+
99
+ # Reduce quality for next iteration
100
+ quality -= QUALITY_STEP
101
+
102
+ # If reducing quality didn't work, try resizing the image
103
+ scale_factor = RESIZE_FACTOR
104
+ quality = INITIAL_QUALITY
105
+
106
+ while scale_factor > 0.1: # Don't shrink image too much
107
+ # Resize the image
108
+ new_width = int(width * scale_factor)
109
+ new_height = int(height * scale_factor)
110
+ resized_img = img.resize((new_width, new_height), Image.LANCZOS)
111
+
112
+ # Try different qualities with this size
113
+ current_quality = quality
114
+ while current_quality >= MIN_QUALITY:
115
+ if img_format == "JPEG" or img_format == "PNG":
116
+ resized_img.save(new_path, format=img_format, quality=current_quality, optimize=True)
117
+ else:
118
+ resized_img.save(new_path, format=img_format)
119
+
120
+ # Check new size
121
+ new_size = get_file_size(new_path)
122
+
123
+ if new_size <= target_size:
124
+ # If we changed from PNG to JPG, remove the original
125
+ if new_path != image_path:
126
+ os.remove(image_path)
127
+
128
+ # Remove the backup
129
+ os.remove(backup_path)
130
+
131
+ # Log the result
132
+ size_reduction = original_size - new_size
133
+ percentage = (size_reduction / original_size) * 100
134
+ print(f"Optimized: {image_path} - {original_size} → {new_size} bytes ({percentage:.1f}% reduction)")
135
+ print(f" - Resized from {width}x{height} to {new_width}x{new_height}, quality: {current_quality}")
136
+ return True, original_size, new_size
137
+
138
+ # Reduce quality for next iteration
139
+ current_quality -= QUALITY_STEP
140
+
141
+ # Reduce size for next iteration
142
+ scale_factor *= RESIZE_FACTOR
143
+
144
+ # If we couldn't optimize the image, restore from backup
145
+ print(f"Failed to optimize {image_path} to under {TARGET_SIZE/1024}KB")
146
+ if os.path.exists(backup_path):
147
+ shutil.move(backup_path, image_path)
148
+ return False, original_size, 0
149
+
150
+ except Exception as e:
151
+ print(f"Error optimizing {image_path}: {str(e)}")
152
+ # Restore from backup
153
+ if os.path.exists(backup_path):
154
+ shutil.move(backup_path, image_path)
155
+ return False, original_size, 0
156
+
157
+ def has_transparency(img):
158
+ """Check if image has transparency"""
159
+ if img.mode == 'RGBA':
160
+ extrema = img.getextrema()
161
+ if len(extrema) >= 4: # Check alpha channel
162
+ if extrema[3][0] < 255: # Check if minimum alpha value is less than 255
163
+ return True
164
+ return False
165
+
166
+ def find_images(root_dir, target_folders=TARGET_FOLDERS):
167
+ """
168
+ Find all images in target folders
169
+ Returns: list of image paths
170
+ """
171
+ image_paths = []
172
+
173
+ # Walk through all directories
174
+ for folder in target_folders:
175
+ # Use glob to find alt and thumbnail folders
176
+ folder_pattern = os.path.join(root_dir, "**", folder, "*")
177
+ for image_path in glob.glob(folder_pattern, recursive=True):
178
+ # Check if it's a file and an image
179
+ if os.path.isfile(image_path) and is_image_file(image_path):
180
+ image_paths.append(image_path)
181
+
182
+ return image_paths
183
+
184
+ def is_image_file(path):
185
+ """Check if file is an image based on extension"""
186
+ ext = os.path.splitext(path)[1].lower()
187
+ return ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
188
+
189
+ def main():
190
+ # Get the root directory
191
+ if len(sys.argv) > 1:
192
+ root_dir = sys.argv[1]
193
+ else:
194
+ root_dir = os.getcwd()
195
+
196
+ print(f"Finding images in {root_dir}...")
197
+
198
+ # Find all images
199
+ image_paths = find_images(root_dir)
200
+
201
+ if not image_paths:
202
+ print("No images found in target folders.")
203
+ return
204
+
205
+ print(f"Found {len(image_paths)} images. Starting optimization...")
206
+
207
+ # Keep track of statistics
208
+ total_processed = 0
209
+ successful = 0
210
+ total_original_size = 0
211
+ total_new_size = 0
212
+
213
+ # Process each image
214
+ for image_path in image_paths:
215
+ success, original_size, new_size = optimize_image(image_path)
216
+ total_processed += 1
217
+ if success and new_size < original_size:
218
+ successful += 1
219
+ total_original_size += original_size
220
+ total_new_size += new_size
221
+
222
+ # Print summary
223
+ print("\nOptimization Summary:")
224
+ print(f"Processed {total_processed} images")
225
+ print(f"Successfully optimized {successful} images")
226
+
227
+ if successful > 0:
228
+ total_saved = total_original_size - total_new_size
229
+ percentage = (total_saved / total_original_size) * 100
230
+ print(f"Total size reduction: {total_saved / 1024:.2f}KB ({percentage:.1f}%)")
231
+ print(f"Average size before: {total_original_size / successful / 1024:.2f}KB")
232
+ print(f"Average size after: {total_new_size / successful / 1024:.2f}KB")
233
+
234
+ if __name__ == "__main__":
235
+ main()