| 
									
										
										
										
											2024-10-20 19:23:47 +02:00
										 |  |  | import os | 
					
						
							|  |  |  | import re | 
					
						
							|  |  |  | import hashlib | 
					
						
							|  |  |  | from pathlib import Path | 
					
						
							|  |  |  | from PIL import Image | 
					
						
							|  |  |  | import cairosvg | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Define paths | 
					
						
							|  |  |  | ROOT_DIR = Path(__file__).resolve().parent.parent | 
					
						
							|  |  |  | SVG_DIR = ROOT_DIR / "svg" | 
					
						
							|  |  |  | PNG_DIR = ROOT_DIR / "png" | 
					
						
							|  |  |  | WEBP_DIR = ROOT_DIR / "webp" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Ensure the output folders exist | 
					
						
							|  |  |  | PNG_DIR.mkdir(parents=True, exist_ok=True) | 
					
						
							|  |  |  | WEBP_DIR.mkdir(parents=True, exist_ok=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Track results | 
					
						
							|  |  |  | failed_files = [] | 
					
						
							|  |  |  | converted_pngs = 0 | 
					
						
							|  |  |  | converted_webps = 0 | 
					
						
							|  |  |  | total_icons = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def file_size_readable(size_bytes): | 
					
						
							|  |  |  |     """Convert bytes to a human-readable format.""" | 
					
						
							|  |  |  |     for unit in ['B', 'KB', 'MB', 'GB']: | 
					
						
							|  |  |  |         if size_bytes < 1024: | 
					
						
							|  |  |  |             return f"{size_bytes:.2f} {unit}" | 
					
						
							| 
									
										
										
										
											2024-10-20 20:46:29 +02:00
										 |  |  |             #size_bytes /= 1024 | 
					
						
							| 
									
										
										
										
											2024-10-20 19:23:47 +02:00
										 |  |  |         size_bytes /= 1024 | 
					
						
							| 
									
										
										
										
											2024-10-20 20:46:29 +02:00
										 |  |  |     return f"{size_bytes:.2f} TB" | 
					
						
							| 
									
										
										
										
											2024-10-20 19:23:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | def hash_file(file_path): | 
					
						
							|  |  |  |     """Generate an MD5 hash for a file.""" | 
					
						
							|  |  |  |     hash_md5 = hashlib.md5() | 
					
						
							|  |  |  |     with open(file_path, "rb") as f: | 
					
						
							|  |  |  |         for chunk in iter(lambda: f.read(4096), b""): | 
					
						
							|  |  |  |             hash_md5.update(chunk) | 
					
						
							|  |  |  |     return hash_md5.hexdigest() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def convert_to_kebab_case(name): | 
					
						
							|  |  |  |     """Convert a filename to kebab-case.""" | 
					
						
							|  |  |  |     cleaned = re.sub(r'[^a-zA-Z0-9\s-]', '', name) | 
					
						
							|  |  |  |     kebab_case_name = re.sub(r'[\s_]+', '-', cleaned).lower() | 
					
						
							|  |  |  |     return kebab_case_name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def rename_if_needed(file_path): | 
					
						
							|  |  |  |     """Ensure the filename is in kebab-case; rename if necessary.""" | 
					
						
							|  |  |  |     new_name = convert_to_kebab_case(file_path.stem) + file_path.suffix | 
					
						
							|  |  |  |     new_path = file_path.parent / new_name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if new_path != file_path: | 
					
						
							|  |  |  |         if new_path.exists(): | 
					
						
							|  |  |  |             raise FileExistsError(f"File conflict: {new_path} already exists.") | 
					
						
							|  |  |  |         file_path.rename(new_path) | 
					
						
							|  |  |  |         print(f"Renamed: {file_path} -> {new_path}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return new_path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def needs_conversion(output_file, data=None): | 
					
						
							|  |  |  |     """Check if a file needs to be converted or overwritten.""" | 
					
						
							|  |  |  |     if output_file.exists(): | 
					
						
							|  |  |  |         if data: | 
					
						
							|  |  |  |             existing_hash = hash_file(output_file) | 
					
						
							|  |  |  |             new_hash = hashlib.md5(data).hexdigest() | 
					
						
							|  |  |  |             return existing_hash != new_hash | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  |     return True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def convert_svg_to_png(svg_path, png_path): | 
					
						
							|  |  |  |     """Convert SVG to PNG.""" | 
					
						
							|  |  |  |     global converted_pngs | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         png_data = cairosvg.svg2png(url=str(svg_path), output_height=512) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if needs_conversion(png_path, png_data): | 
					
						
							|  |  |  |             with open(png_path, 'wb') as f: | 
					
						
							|  |  |  |                 f.write(png_data) | 
					
						
							|  |  |  |             print(f"Converted PNG: {png_path} ({file_size_readable(png_path.stat().st_size)})") | 
					
						
							|  |  |  |             converted_pngs += 1 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             print(f"PNG already up-to-date: {png_path}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     except Exception as e: | 
					
						
							|  |  |  |         print(f"Failed to convert {svg_path} to PNG: {e}") | 
					
						
							|  |  |  |         failed_files.append(svg_path) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-20 20:46:29 +02:00
										 |  |  | def convert_image_to_webp(image_path, webp_path): | 
					
						
							|  |  |  |     """Convert an image (PNG or other) to WEBP.""" | 
					
						
							| 
									
										
										
										
											2024-10-20 19:23:47 +02:00
										 |  |  |     global converted_webps | 
					
						
							|  |  |  |     try: | 
					
						
							| 
									
										
										
										
											2024-10-20 20:46:29 +02:00
										 |  |  |         image = Image.open(image_path).convert("RGBA") | 
					
						
							| 
									
										
										
										
											2024-10-20 19:23:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if needs_conversion(webp_path): | 
					
						
							|  |  |  |             image.save(webp_path, format='WEBP') | 
					
						
							|  |  |  |             print(f"Converted WEBP: {webp_path} ({file_size_readable(webp_path.stat().st_size)})") | 
					
						
							|  |  |  |             converted_webps += 1 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             print(f"WEBP already up-to-date: {webp_path}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     except Exception as e: | 
					
						
							| 
									
										
										
										
											2024-10-20 20:46:29 +02:00
										 |  |  |         print(f"Failed to convert {image_path} to WEBP: {e}") | 
					
						
							|  |  |  |         failed_files.append(image_path) | 
					
						
							| 
									
										
										
										
											2024-10-20 19:23:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | def clean_up_files(folder, valid_basenames): | 
					
						
							| 
									
										
										
										
											2024-10-20 20:46:29 +02:00
										 |  |  |     """Remove files that no longer have corresponding SVG or PNG files.""" | 
					
						
							| 
									
										
										
										
											2024-10-20 19:23:47 +02:00
										 |  |  |     removed_files = 0 | 
					
						
							|  |  |  |     for file_path in folder.glob('*'): | 
					
						
							|  |  |  |         if file_path.stem not in valid_basenames: | 
					
						
							|  |  |  |             file_path.unlink() | 
					
						
							|  |  |  |             print(f"Removed: {file_path}") | 
					
						
							|  |  |  |             removed_files += 1 | 
					
						
							|  |  |  |     return removed_files | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							| 
									
										
										
										
											2024-10-20 20:46:29 +02:00
										 |  |  |     # Track valid basenames (from SVG and PNG files) | 
					
						
							| 
									
										
										
										
											2024-10-20 19:23:47 +02:00
										 |  |  |     valid_basenames = set() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Process all SVG files | 
					
						
							|  |  |  |     for svg_file in SVG_DIR.glob("*.svg"): | 
					
						
							|  |  |  |         total_icons += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Ensure the filename is in kebab-case | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             svg_path = rename_if_needed(svg_file) | 
					
						
							|  |  |  |         except Exception as e: | 
					
						
							|  |  |  |             print(f"Error renaming {svg_file}: {e}") | 
					
						
							|  |  |  |             failed_files.append(svg_file) | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         valid_basenames.add(svg_path.stem) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Set paths for PNG and WEBP | 
					
						
							|  |  |  |         png_path = PNG_DIR / f"{svg_path.stem}.png" | 
					
						
							|  |  |  |         webp_path = WEBP_DIR / f"{svg_path.stem}.webp" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Convert SVG to PNG | 
					
						
							|  |  |  |         convert_svg_to_png(svg_path, png_path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Convert PNG to WEBP | 
					
						
							|  |  |  |         if png_path.exists(): | 
					
						
							| 
									
										
										
										
											2024-10-20 20:46:29 +02:00
										 |  |  |             convert_image_to_webp(png_path, webp_path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Process PNG-only files | 
					
						
							|  |  |  |     for png_file in PNG_DIR.glob("*.png"): | 
					
						
							|  |  |  |         if png_file.stem not in valid_basenames: | 
					
						
							|  |  |  |             # Ensure the filename is in kebab-case | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 png_path = rename_if_needed(png_file) | 
					
						
							|  |  |  |             except Exception as e: | 
					
						
							|  |  |  |                 print(f"Error renaming {png_file}: {e}") | 
					
						
							|  |  |  |                 failed_files.append(png_file) | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             valid_basenames.add(png_path.stem) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Set path for WEBP | 
					
						
							|  |  |  |             webp_path = WEBP_DIR / f"{png_path.stem}.webp" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Convert PNG to WEBP | 
					
						
							|  |  |  |             convert_image_to_webp(png_path, webp_path) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Clean up unused files in PNG and WEBP directories | 
					
						
							|  |  |  |     # Only remove files that don't have corresponding SVG or PNG files | 
					
						
							|  |  |  |     removed_pngs = clean_up_files(PNG_DIR, valid_basenames.union({p.stem for p in SVG_DIR.glob("*.svg")})) | 
					
						
							| 
									
										
										
										
											2024-10-20 19:23:47 +02:00
										 |  |  |     removed_webps = clean_up_files(WEBP_DIR, valid_basenames) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Display summary | 
					
						
							|  |  |  |     if converted_pngs == 0 and converted_webps == 0 and removed_pngs == 0 and removed_webps == 0: | 
					
						
							|  |  |  |         print("\nAll icons are already up-to-date.") | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         print(f"\nConverted {converted_pngs} PNGs and {converted_webps} WEBPs out of {total_icons} icons.") | 
					
						
							|  |  |  |         print(f"Removed {removed_pngs} PNGs and {removed_webps} WEBPs.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Display any failed conversions | 
					
						
							|  |  |  |     if failed_files: | 
					
						
							|  |  |  |         print("\nThe following files failed to convert:") | 
					
						
							|  |  |  |         for file in failed_files: | 
					
						
							|  |  |  |             print(file) |