|
| 1 | +import os |
| 2 | +import re |
| 3 | +from PIL import Image |
| 4 | + |
| 5 | +def convert_to_webp(directory, quality=85): |
| 6 | + converted_files = {} # original_rel_path: new_rel_path |
| 7 | + |
| 8 | + for root, dirs, files in os.walk(directory): |
| 9 | + for file in files: |
| 10 | + if file.lower().endswith(('.png', '.jpg', '.jpeg')): |
| 11 | + original_path = os.path.join(root, file) |
| 12 | + name, ext = os.path.splitext(file) |
| 13 | + new_file = f"{name}.webp" |
| 14 | + new_path = os.path.join(root, new_file) |
| 15 | + |
| 16 | + try: |
| 17 | + with Image.open(original_path) as img: |
| 18 | + # Convert to RGB if necessary (e.g. for RGBA PNGs) |
| 19 | + # Actually WebP supports alpha, so we can keep it. |
| 20 | + img.save(new_path, "WEBP", quality=quality) |
| 21 | + |
| 22 | + old_size = os.path.getsize(original_path) |
| 23 | + new_size = os.path.getsize(new_path) |
| 24 | + reduction = (old_size - new_size) / (1024 * 1024) |
| 25 | + print(f"Converted: {original_path} -> {new_path} (Saved {reduction:.2f} MB)") |
| 26 | + |
| 27 | + # Store mapping for replacement |
| 28 | + rel_original = os.path.relpath(original_path, start=".") |
| 29 | + rel_new = os.path.relpath(new_path, start=".") |
| 30 | + # Use forward slashes for web paths |
| 31 | + rel_original_web = rel_original.replace("\\", "/") |
| 32 | + rel_new_web = rel_new.replace("\\", "/") |
| 33 | + converted_files[rel_original_web] = rel_new_web |
| 34 | + |
| 35 | + except Exception as e: |
| 36 | + print(f"Error converting {original_path}: {e}") |
| 37 | + |
| 38 | + return converted_files |
| 39 | + |
| 40 | +def update_references(target_files, mapping): |
| 41 | + for file_path in target_files: |
| 42 | + if not os.path.exists(file_path): |
| 43 | + continue |
| 44 | + |
| 45 | + try: |
| 46 | + with open(file_path, 'r', encoding='utf-8') as f: |
| 47 | + content = f.read() |
| 48 | + |
| 49 | + original_content = content |
| 50 | + updated_count = 0 |
| 51 | + |
| 52 | + # Sort mapping keys by length (longest first) to avoid partial replacements |
| 53 | + # e.g. "image.png" vs "small_image.png" |
| 54 | + for old_path, new_path in sorted(mapping.items(), key=lambda x: len(x[0]), reverse=True): |
| 55 | + # Use regex to find the file extension and replace it if it matches our list |
| 56 | + # Specifically targeting paths that look like "images/.../*.png" |
| 57 | + # But simple string replacement for the specific filename is safer here |
| 58 | + if old_path in content: |
| 59 | + content = content.replace(old_path, new_path) |
| 60 | + updated_count += 1 |
| 61 | + |
| 62 | + # Generic replacement for extensions if they are used as part of dynamically built paths |
| 63 | + # e.g. some_path + ".png" -> some_path + ".webp" |
| 64 | + # But let's stick to the specific mapping first. |
| 65 | + |
| 66 | + if content != original_content: |
| 67 | + with open(file_path, 'w', encoding='utf-8') as f: |
| 68 | + f.write(content) |
| 69 | + print(f"Updated references in: {file_path} ({updated_count} replacements)") |
| 70 | + except Exception as e: |
| 71 | + print(f"Error updating {file_path}: {e}") |
| 72 | + |
| 73 | +def cleanup_originals(mapping): |
| 74 | + for old_path_web in mapping.keys(): |
| 75 | + old_path = old_path_web.replace("/", os.sep) |
| 76 | + if os.path.exists(old_path): |
| 77 | + try: |
| 78 | + os.remove(old_path) |
| 79 | + print(f"Deleted: {old_path}") |
| 80 | + except Exception as e: |
| 81 | + print(f"Error deleting {old_path}: {e}") |
| 82 | + |
| 83 | +if __name__ == "__main__": |
| 84 | + print("Starting Image Conversion to WebP...") |
| 85 | + mapping = convert_to_webp("images") |
| 86 | + |
| 87 | + if not mapping: |
| 88 | + print("No images found to convert.") |
| 89 | + else: |
| 90 | + print(f"\nConverted {len(mapping)} images. Updating references...") |
| 91 | + |
| 92 | + # Files to sweep for references |
| 93 | + target_files = [ |
| 94 | + "index.html", "subject.html", "header.html", "footer.html", |
| 95 | + "js/courses.json", "js/game_dev.json", "js/computer_vision.json", |
| 96 | + "js/calculus.json", "js/computer_organization.json", "js/script.js", "js/main.js" |
| 97 | + ] |
| 98 | + |
| 99 | + update_references(target_files, mapping) |
| 100 | + |
| 101 | + print("\nCleanup phase: deleting original image files...") |
| 102 | + # cleanup_originals(mapping) # Uncomment after verification if needed, |
| 103 | + # but I'll run it now since the user asked for it. |
| 104 | + cleanup_originals(mapping) |
| 105 | + |
| 106 | + print("\nAll tasks completed!") |
0 commit comments