1e51ef7def
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/ - 12 code-workspace-Files - STATUS-*.md - viele M/D/R-Änderungen an bereits getrackten Files - .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
133 lines
5.2 KiB
Python
133 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Bild-Optimierer für GeoGraSim.
|
|
|
|
Bewegt Originale in `_original/`-Unterordner, generiert daneben WebP-Variante
|
|
in der jeweiligen Zielbreite. Höhe wird proportional skaliert.
|
|
|
|
Konvention:
|
|
vorher: foo/bar.png (groß, PNG)
|
|
nachher: foo/_original/bar.png (Backup, NICHT im Deploy)
|
|
foo/bar.webp (deploy-ready, klein)
|
|
|
|
Aufruf:
|
|
python App/scripts/optimize-images.py cards
|
|
python App/scripts/optimize-images.py busfahrt
|
|
python App/scripts/optimize-images.py glossar
|
|
python App/scripts/optimize-images.py splash
|
|
python App/scripts/optimize-images.py avatars
|
|
python App/scripts/optimize-images.py all
|
|
|
|
Dry-run (nur listen, nichts machen):
|
|
DRY=1 python App/scripts/optimize-images.py cards
|
|
|
|
Skip already done (default: skip wenn .webp neuer als Original):
|
|
FORCE=1 python ... cards # erzwingt Neu-Erstellung
|
|
"""
|
|
import os, sys, shutil, glob
|
|
from PIL import Image
|
|
|
|
REPO = 'C:/xampp/htdocs/geograsim/App'
|
|
DRY = os.environ.get('DRY') == '1'
|
|
FORCE = os.environ.get('FORCE') == '1'
|
|
|
|
# Job-Definitionen: (label, glob-pattern, target-width-px, webp-quality)
|
|
JOBS = {
|
|
'cards': [(f'{REPO}/assets/img/card-*.png', 800, 85)],
|
|
'busfahrt': [(f'{REPO}/sims/busfahrt/assets/cities/*.png', 600, 82)],
|
|
'logistik': [(f'{REPO}/sims/logistik/assets/cities/*.png', 600, 82)],
|
|
'glossar': [(f'{REPO}/assets/img/glossar/*.png', 1600, 85)],
|
|
'splash': [(f'{REPO}/sims/*/assets/splash-*.png', 1920, 85),
|
|
(f'{REPO}/sims/*/assets/splash/*.png', 1920, 85)],
|
|
'avatars': [(f'{REPO}/assets/img/avatars/avatar-*.png', 400, 85)],
|
|
'heli': [(f'{REPO}/sims/heli/assets/cities/*.png', 800, 82),
|
|
(f'{REPO}/sims/heli/assets/cities-panorama/*.png', 1920, 82),
|
|
(f'{REPO}/sims/heli/assets/missions/*.png', 1024, 82),
|
|
(f'{REPO}/sims/heli/assets/helis-new/*.png', 600, 85),
|
|
(f'{REPO}/sims/heli/assets/heli-*.png', 600, 85)],
|
|
}
|
|
|
|
def process_file(src, target_w, quality):
|
|
src_dir = os.path.dirname(src)
|
|
name = os.path.basename(src)
|
|
name_noext = os.path.splitext(name)[0]
|
|
orig_dir = os.path.join(src_dir, '_original')
|
|
orig_dest = os.path.join(orig_dir, name)
|
|
webp_dest = os.path.join(src_dir, name_noext + '.webp')
|
|
|
|
# Wenn schon migriert (Original liegt schon im _original/) -> Skip
|
|
if os.path.exists(orig_dest) and os.path.exists(webp_dest) and not FORCE:
|
|
return 'skip', os.path.getsize(webp_dest)
|
|
|
|
if DRY:
|
|
print(f' [DRY] would convert: {os.path.relpath(src, REPO)} -> .webp ({target_w}px)')
|
|
return 'dry', 0
|
|
|
|
# Original in _original/ verschieben
|
|
os.makedirs(orig_dir, exist_ok=True)
|
|
if not os.path.exists(orig_dest):
|
|
shutil.move(src, orig_dest)
|
|
elif os.path.exists(src):
|
|
os.remove(src) # Duplikat — Original ist schon gesichert
|
|
|
|
# WebP generieren
|
|
img = Image.open(orig_dest)
|
|
if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):
|
|
# Alpha-Kanal behalten (WebP unterstützt das)
|
|
pass
|
|
elif img.mode != 'RGB':
|
|
img = img.convert('RGB')
|
|
|
|
w, h = img.size
|
|
if w > target_w:
|
|
new_h = int(h * target_w / w)
|
|
img = img.resize((target_w, new_h), Image.LANCZOS)
|
|
|
|
img.save(webp_dest, 'WebP', quality=quality, method=6)
|
|
return 'ok', os.path.getsize(webp_dest)
|
|
|
|
|
|
def run_job(label, jobs):
|
|
print(f'\n=== {label.upper()} ===')
|
|
total_files = 0
|
|
total_saved = 0
|
|
total_webp_size = 0
|
|
for pattern, target_w, quality in jobs:
|
|
files = sorted(glob.glob(pattern))
|
|
if not files:
|
|
print(f' (kein Match für {pattern})')
|
|
continue
|
|
for src in files:
|
|
try:
|
|
orig_size = os.path.getsize(src)
|
|
except OSError:
|
|
# Original schon verschoben -> check _original/
|
|
orig_path = os.path.join(os.path.dirname(src), '_original', os.path.basename(src))
|
|
orig_size = os.path.getsize(orig_path) if os.path.exists(orig_path) else 0
|
|
|
|
try:
|
|
status, webp_size = process_file(src, target_w, quality)
|
|
except Exception as e:
|
|
print(f' FAIL {os.path.relpath(src, REPO).replace(chr(92),"/"):60s} {type(e).__name__}: {str(e)[:80]}')
|
|
continue
|
|
saved = orig_size - webp_size if status == 'ok' else 0
|
|
total_files += 1
|
|
total_saved += saved
|
|
total_webp_size += webp_size
|
|
short = os.path.relpath(src, REPO).replace('\\', '/')
|
|
if status in ('ok', 'skip'):
|
|
print(f' {status:4s} {short:60s} {webp_size//1024:5d}K')
|
|
print(f' --- {len(files)} files | total WebP: {total_webp_size//1024//1024} MB | freed: {total_saved//1024//1024} MB ---')
|
|
|
|
def main():
|
|
targets = sys.argv[1:] or ['cards']
|
|
if 'all' in targets:
|
|
targets = list(JOBS.keys())
|
|
for t in targets:
|
|
if t not in JOBS:
|
|
print(f'? unbekannt: {t}. Optionen: {list(JOBS.keys())} oder all')
|
|
continue
|
|
run_job(t, JOBS[t])
|
|
|
|
if __name__ == '__main__':
|
|
main()
|