Пережаты картинки
This commit is contained in:
132
Report/scripts/compress_screenshots.py
Executable file
132
Report/scripts/compress_screenshots.py
Executable file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Сжатие только скриншотов в Report/images/ (не PlantUML PNG).
|
||||
|
||||
JPEG: quality=28 (ImageMagick).
|
||||
PNG-скриншоты (Gradle, ручная схема): pngquant quality 25–40.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Имена из IMAGES_REGISTRY: Скриншот UI, Gradle, Ручная схема
|
||||
SCREENSHOT_STEMS = {
|
||||
"fig_05_local_vaults",
|
||||
"fig_06_encrypt_dialog",
|
||||
"fig_07_open_close_dialog",
|
||||
"fig_08_rename_delete_dialog",
|
||||
"fig_09_remote_vaults",
|
||||
"fig_10_yandex_oauth",
|
||||
"fig_12_tasks_screen",
|
||||
"fig_13_tasks_notification",
|
||||
"fig_33_storage_secrets_2fa",
|
||||
"fig_34_2fa_single_token",
|
||||
"fig_24_domain_class_manual",
|
||||
"fig_27_gradle_domain_test",
|
||||
"fig_28_gradle_usecases_test",
|
||||
"fig_29_gradle_ui_test",
|
||||
"fig_30_gradle_test_summary",
|
||||
"fig_31_gradle_connected_test",
|
||||
}
|
||||
|
||||
JPEG_QUALITY = "28"
|
||||
PNGQUANT_QUALITY = "25-40"
|
||||
|
||||
|
||||
def stem(path: Path) -> str:
|
||||
return path.stem
|
||||
|
||||
|
||||
def is_screenshot(path: Path) -> bool:
|
||||
if path.suffix.lower() == ".jpg":
|
||||
return stem(path) in SCREENSHOT_STEMS or path.name.startswith("fig_")
|
||||
if path.suffix.lower() == ".png":
|
||||
return stem(path) in SCREENSHOT_STEMS
|
||||
return False
|
||||
|
||||
|
||||
def size_kb(path: Path) -> float:
|
||||
return path.stat().st_size / 1024
|
||||
|
||||
|
||||
def compress_jpeg(path: Path, magick: str) -> None:
|
||||
tmp = path.with_suffix(path.suffix + ".tmp")
|
||||
subprocess.run(
|
||||
[magick, str(path), "-strip", "-quality", JPEG_QUALITY, str(tmp)],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
tmp.replace(path)
|
||||
|
||||
|
||||
def compress_png(path: Path, pngquant: str) -> bool:
|
||||
tmp = path.with_suffix(".pngquant.tmp.png")
|
||||
for quality in (PNGQUANT_QUALITY, "15-55", "10-80"):
|
||||
try:
|
||||
subprocess.run(
|
||||
[
|
||||
pngquant,
|
||||
f"--quality={quality}",
|
||||
"--force",
|
||||
"--output",
|
||||
str(tmp),
|
||||
str(path),
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
tmp.replace(path)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
if tmp.exists():
|
||||
tmp.unlink()
|
||||
print(f" WARN: pngquant skip {path.name}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def main() -> int:
|
||||
report = Path(__file__).resolve().parents[1]
|
||||
images = report / "images"
|
||||
magick = shutil.which("magick") or shutil.which("convert")
|
||||
pngquant = shutil.which("pngquant")
|
||||
if not magick:
|
||||
print("ERROR: ImageMagick (magick/convert) not found", file=sys.stderr)
|
||||
return 1
|
||||
if not pngquant:
|
||||
print("ERROR: pngquant not found", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
targets = sorted(
|
||||
p for p in images.iterdir() if p.is_file() and is_screenshot(p)
|
||||
)
|
||||
if not targets:
|
||||
print("No screenshots to compress", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
total_before = 0.0
|
||||
total_after = 0.0
|
||||
for path in targets:
|
||||
before = size_kb(path)
|
||||
total_before += before
|
||||
if path.suffix.lower() in {".jpg", ".jpeg"}:
|
||||
compress_jpeg(path, magick)
|
||||
else:
|
||||
compress_png(path, pngquant)
|
||||
after = size_kb(path)
|
||||
total_after += after
|
||||
pct = (1 - after / before) * 100 if before else 0
|
||||
print(f" {path.name}: {before:.0f} KiB → {after:.0f} KiB (−{pct:.0f}%)")
|
||||
|
||||
saved = (1 - total_after / total_before) * 100 if total_before else 0
|
||||
print(
|
||||
f"OK: {len(targets)} screenshots, "
|
||||
f"{total_before:.0f} KiB → {total_after:.0f} KiB (−{saved:.0f}%)"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user