import os import argparse import shutil from PIL import Image # --- Konfiguracija skripte za projekt "Hermina" --- # Predpostavka: ta skripta se nahaja v korenski mapi projekta. PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) # Pot do mape, kjer se nahajajo slike, ki jih želimo optimizirati. IMAGES_DIR_PATH = os.path.join("assets", "images") # Ime podmape znotraj IMAGES_DIR_PATH, kamor se bodo shranile originalne slike. ORIGINAL_IMAGES_SUBDIR_NAME = "original" # Mape, ki jih želimo PREGLEDATI za posodobitev referenc na slike. # '.' pomeni, da začnemo v korenski mapi in pregledamo vse podmape. DIRECTORIES_TO_SCAN = ['.'] # Mape, ki jih želimo IZKLJUČITI iz pregledovanja (za hitrejše delovanje in varnost). # Skripta ne bo iskala referenc v teh mapah. DIRECTORIES_TO_EXCLUDE_FROM_SCAN = ['.git', '.gitea', '.vscode', '__pycache__'] # Končnice datotek, v katerih iščemo in posodabljamo reference na slike. FILE_EXTENSIONS_TO_UPDATE = ['.html', '.css', '.js'] # Nastavitve optimizacije WEBP_QUALITY = 85 # Kakovost kompresije za WebP (0-100). Višje = boljša kvaliteta, večja datoteka. MAX_IMAGE_DIMENSION = 1920 # Slike, večje od te dimenzije (širina ali višina), bodo pomanjšane. 0 za izklop. # Končnice slik, ki jih želimo optimizirati. IMAGE_EXTENSIONS_TO_OPTIMIZE = ['.jpg', '.jpeg', '.png'] # Imena datotek, ki jih želimo preskočiti pri optimizaciji (npr. logotipi, ikone). EXCLUDE_FILES_FROM_OPTIMIZATION = [ 'favicon.ico' # Dodajte imena drugih datotek po potrebi, npr. 'logo.png' ] # --- Pomožne funkcije (večinoma nespremenjene) --- def optimize_image(image_path, output_path, quality, max_dim, dry_run=False): """ Naloži sliko, jo po potrebi pomanjša, optimizira in pretvori v WebP format. """ try: with Image.open(image_path) as img: # Pretvorba slik s paleto (kot so nekatere PNG) v RGBA za ohranitev prosojnosti. if img.mode in ('P', 'LA'): img = img.convert("RGBA") if max_dim > 0 and (img.width > max_dim or img.height > max_dim): print(f" Pomanjšujem sliko {os.path.basename(image_path)} iz {img.width}x{img.height} ...", end="") img.thumbnail((max_dim, max_dim), Image.Resampling.LANCZOS) print(f" na {img.width}x{img.height}") else: print(f" Obdelujem sliko {os.path.basename(image_path)} ({img.width}x{img.height})...", end="") if not dry_run: img.save(output_path, "webp", quality=quality, method=6) print(f" Shrani v {os.path.basename(output_path)} (kvaliteta: {quality}).") else: print(f" DRY RUN: Shranil bi v {os.path.basename(output_path)} (kvaliteta: {quality}).") return True except FileNotFoundError: print(f" Napaka: Izvorna slika '{image_path}' ni najdena.") return False except Exception as e: print(f" Napaka pri optimizaciji slike '{image_path}': {e}") return False def update_file_references(file_path, old_filename, new_filename, dry_run=False): """ V dani datoteki zamenja vse pojavitve starega imena slike z novim. """ try: with open(file_path, 'r', encoding='utf-8') as f: content = f.read() except Exception: # Ignoriramo binarne ali neberljive datoteke return False if old_filename in content: updated_content = content.replace(old_filename, new_filename) if not dry_run: try: with open(file_path, 'w', encoding='utf-8') as f: f.write(updated_content) print(f" Posodobljene reference v: '{os.path.relpath(file_path, PROJECT_ROOT)}'") return True except Exception as e: print(f" Napaka pri zapisovanju v '{file_path}': {e}") return False else: print(f" DRY RUN: Posodobil bi reference v: '{os.path.relpath(file_path, PROJECT_ROOT)}'") return True return False def find_files_to_update(scan_dirs, exclude_dirs): """ Rekurzivno poišče vse datoteke z ustreznimi končnicami, pri tem pa ignorira izključene mape. """ found_files = [] for directory in scan_dirs: scan_path = os.path.join(PROJECT_ROOT, directory) for dirpath, dirnames, filenames in os.walk(scan_path): # Učinkovit način za izključitev map: odstranimo jih iz seznama za nadaljnje pregledovanje dirnames[:] = [d for d in dirnames if d not in exclude_dirs] for filename in filenames: if any(filename.endswith(ext) for ext in FILE_EXTENSIONS_TO_UPDATE): found_files.append(os.path.join(dirpath, filename)) return sorted(list(set(found_files))) # --- Glavna funkcija --- def main(): parser = argparse.ArgumentParser(description="Optimizira slike v WebP in posodobi reference v projektu.") parser.add_argument("--dry-run", action="store_true", help="Prikaže, katere spremembe bi se zgodile, a jih ne izvede.") args = parser.parse_args() images_full_path = os.path.join(PROJECT_ROOT, IMAGES_DIR_PATH) original_images_full_path = os.path.join(images_full_path, ORIGINAL_IMAGES_SUBDIR_NAME) # Dodamo mapo z originali v seznam za izključitev, da ne skeniramo še tam. DIRECTORIES_TO_EXCLUDE_FROM_SCAN.append(ORIGINAL_IMAGES_SUBDIR_NAME) if not os.path.isdir(images_full_path): print(f"Napaka: Mapa s slikami '{images_full_path}' ne obstaja. Preverite pot v konfiguraciji.") return if not args.dry_run: os.makedirs(original_images_full_path, exist_ok=True) print(f"Mapa za originale: '{original_images_full_path}'") else: print(f"DRY RUN: Mapa za originale bi bila '{original_images_full_path}'.") optimized_images_map = [] print("\n--- FAZA 1: OPTIMIZACIJA SLIK ---") for dirpath, dirnames, filenames in os.walk(images_full_path): # preskoči mapo z originali, če že obstaja dirnames[:] = [ d for d in dirnames if os.path.relpath(os.path.join(dirpath, d), images_full_path) != ORIGINAL_IMAGES_SUBDIR_NAME ] for filename in filenames: file_path = os.path.join(dirpath, filename) if not os.path.isfile(file_path): continue name, ext = os.path.splitext(filename) ext = ext.lower() if filename in EXCLUDE_FILES_FROM_OPTIMIZATION: print(f"- Preskakujem izključeno datoteko: '{filename}'") continue if ext in IMAGE_EXTENSIONS_TO_OPTIMIZE: new_filename = f"{name}.webp" new_file_path = os.path.join(dirpath, new_filename) rel_dir = os.path.relpath(dirpath, images_full_path) rel_dir = "" if rel_dir == "." else rel_dir old_rel_path = os.path.join(rel_dir, filename) if rel_dir else filename new_rel_path = os.path.join(rel_dir, new_filename) if rel_dir else new_filename old_rel_path = old_rel_path.replace(os.sep, "/") new_rel_path = new_rel_path.replace(os.sep, "/") # Preveri, če WebP verzija že obstaja in je novejša if os.path.exists(new_file_path) and os.path.getmtime(new_file_path) > os.path.getmtime(file_path): print(f"- WebP '{new_rel_path}' že obstaja in je posodobljen. Preskakujem optimizacijo.") optimized_images_map.append({'old': filename, 'new': new_filename, 'old_path': old_rel_path, 'new_path': new_rel_path}) continue if optimize_image(file_path, new_file_path, WEBP_QUALITY, MAX_IMAGE_DIMENSION, args.dry_run): optimized_images_map.append({'old': filename, 'new': new_filename, 'old_path': old_rel_path, 'new_path': new_rel_path}) if not args.dry_run: try: destination_dir = os.path.join(original_images_full_path, rel_dir) if rel_dir else original_images_full_path os.makedirs(destination_dir, exist_ok=True) shutil.move(file_path, os.path.join(destination_dir, filename)) print(f" Original '{old_rel_path}' premaknjen v '{ORIGINAL_IMAGES_SUBDIR_NAME}/'.") except Exception as e: print(f" NAPAKA pri premikanju '{old_rel_path}': {e}") else: print(f" DRY RUN: Original '{old_rel_path}' bi bil premaknjen v '{ORIGINAL_IMAGES_SUBDIR_NAME}/'.") if not optimized_images_map: print("\nNi bilo najdenih novih slik za optimizacijo.") print("\n--- FAZA 2: POSODABLJANJE REFERENC V PROJEKTU ---") files_to_scan = find_files_to_update(DIRECTORIES_TO_SCAN, DIRECTORIES_TO_EXCLUDE_FROM_SCAN) if not files_to_scan: print("Ni najdenih .html, .css ali .js datotek za pregled.") else: print(f"Najdenih {len(files_to_scan)} datotek za pregled...") updated_files_count = 0 for file_path in files_to_scan: file_was_updated = False for img_info in optimized_images_map: replaced = False if update_file_references(file_path, img_info['old_path'], img_info['new_path'], args.dry_run): replaced = True # za nazaj združljivost: če je v datoteki samo ime brez poti if update_file_references(file_path, img_info['old'], img_info['new'], args.dry_run): replaced = True if replaced: file_was_updated = True if file_was_updated: updated_files_count += 1 print(f"\nPregledanih je bilo {len(files_to_scan)} datotek.") print(f"Posodobljenih je bilo {updated_files_count} datotek.") print("\n--- SKRIPTA JE ZAKLJUČILA Z DELOM ---") if __name__ == "__main__": main()