68 lines
2.8 KiB
Python
68 lines
2.8 KiB
Python
"""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()
|