"""WebP-копии картинок сайта: рядом с «имя.jpg» кладёт «имя.jpg.webp». Сервер (internal/api/media_webp.go) отдаёт копию браузерам, которые принимают WebP; пути в базе не меняются. Копия сохраняется, только если она меньше оригинала. Повторный запуск пересоздаёт копию, если оригинал новее. python make_webp.py D:/dana/media # локально python make_webp.py /data/media --quality 80 # на сервере, в контейнере с Pillow """ import argparse import os import sys from PIL import Image, ImageOps EXTS = ('.jpg', '.jpeg', '.png') def convert(src, quality): dst = src + '.webp' if os.path.exists(dst) and os.path.getmtime(dst) >= os.path.getmtime(src): return 'fresh', 0, 0 with Image.open(src) as im: im = ImageOps.exif_transpose(im) # фото с телефона: поворот из EXIF lossless = src.lower().endswith('.png') and im.mode in ('P', 'L') and max(im.size) <= 256 if im.mode not in ('RGB', 'RGBA'): im = im.convert('RGBA' if 'A' in im.getbands() or im.info.get('transparency') is not None else 'RGB') tmp = dst + '.tmp' im.save(tmp, 'WEBP', quality=quality, method=6, lossless=lossless) before, after = os.path.getsize(src), os.path.getsize(tmp) if after >= before: os.remove(tmp) if os.path.exists(dst): os.remove(dst) return 'bigger', before, before os.replace(tmp, dst) return 'done', before, after def main(): ap = argparse.ArgumentParser() ap.add_argument('root') ap.add_argument('--quality', type=int, default=80) a = ap.parse_args() stats = {'done': 0, 'fresh': 0, 'bigger': 0, 'error': 0} before = after = 0 for dirpath, _, files in os.walk(a.root): for f in files: if not f.lower().endswith(EXTS): continue p = os.path.join(dirpath, f) try: state, b, s = convert(p, a.quality) except Exception as e: # битый файл не должен останавливать остальные stats['error'] += 1 print('ошибка:', p, e, file=sys.stderr) continue stats[state] += 1 before += b after += s print(f"готово: {stats['done']}, уже были: {stats['fresh']}, WebP не меньше оригинала: {stats['bigger']}, ошибок: {stats['error']}") if before: print(f'обработанные файлы: {before / 1e6:.1f} МБ → {after / 1e6:.1f} МБ') if __name__ == '__main__': main()