initial import: random background api
This commit is contained in:
161
build.py
Normal file
161
build.py
Normal file
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import venv
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
MANIFEST_PATH = BASE_DIR / "manifest.json"
|
||||
|
||||
VARIANTS = {
|
||||
"mobile": [BASE_DIR / "mobile-image"],
|
||||
"desktop": [BASE_DIR / "desketop-image"],
|
||||
}
|
||||
|
||||
INPUT_EXTENSIONS = {
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".png",
|
||||
".bmp",
|
||||
".gif",
|
||||
".tif",
|
||||
".tiff",
|
||||
".webp",
|
||||
}
|
||||
|
||||
GENERATED_NAME_RE = re.compile(r"^\d+\.webp$", re.IGNORECASE)
|
||||
VENV_DIR = BASE_DIR / ".build-venv"
|
||||
|
||||
|
||||
def ensure_pillow() -> None:
|
||||
try:
|
||||
from PIL import Image # noqa: F401
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
venv_python = VENV_DIR / "bin" / "python"
|
||||
if not venv_python.exists():
|
||||
print("Pillow not found, creating a local build venv...", file=sys.stderr)
|
||||
builder = venv.EnvBuilder(with_pip=True)
|
||||
builder.create(VENV_DIR)
|
||||
|
||||
has_pillow = subprocess.run(
|
||||
[str(venv_python), "-c", "import PIL"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
check=False,
|
||||
).returncode == 0
|
||||
|
||||
if not has_pillow:
|
||||
print("Installing Pillow into the local build venv...", file=sys.stderr)
|
||||
subprocess.check_call([str(venv_python), "-m", "pip", "install", "--quiet", "Pillow"])
|
||||
os.execv(str(venv_python), [str(venv_python), str(Path(__file__).resolve()), *sys.argv[1:]])
|
||||
|
||||
|
||||
def load_pillow():
|
||||
ensure_pillow()
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
return Image, ImageOps
|
||||
|
||||
|
||||
def iter_source_files(directory: Path) -> list[Path]:
|
||||
if not directory.exists():
|
||||
return []
|
||||
files: list[Path] = []
|
||||
for path in sorted(directory.iterdir(), key=lambda item: item.name.lower()):
|
||||
if not path.is_file():
|
||||
continue
|
||||
if GENERATED_NAME_RE.match(path.name):
|
||||
continue
|
||||
if path.suffix.lower() not in INPUT_EXTENSIONS:
|
||||
continue
|
||||
files.append(path)
|
||||
return files
|
||||
|
||||
|
||||
def select_source_directory(candidates: list[Path]) -> Path:
|
||||
for directory in candidates:
|
||||
if iter_source_files(directory):
|
||||
return directory
|
||||
for directory in candidates:
|
||||
if directory.exists():
|
||||
return directory
|
||||
return candidates[0]
|
||||
|
||||
|
||||
def clear_generated_files(directory: Path) -> None:
|
||||
if not directory.exists():
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
return
|
||||
for path in directory.iterdir():
|
||||
if path.is_file() and GENERATED_NAME_RE.match(path.name):
|
||||
path.unlink()
|
||||
|
||||
|
||||
def convert_to_webp(source: Path, target: Path, image_mod, image_ops) -> None:
|
||||
with image_mod.open(source) as image:
|
||||
image = image_ops.exif_transpose(image)
|
||||
if image.mode not in {"RGB", "RGBA", "LA", "P"}:
|
||||
image = image.convert("RGBA")
|
||||
elif image.mode == "P":
|
||||
image = image.convert("RGBA")
|
||||
elif image.mode == "LA":
|
||||
image = image.convert("RGBA")
|
||||
elif image.mode == "RGB":
|
||||
pass
|
||||
image.save(target, format="WEBP", quality=92, method=6)
|
||||
|
||||
|
||||
def build_variant(name: str, directories: list[Path], image_mod, image_ops) -> dict:
|
||||
directory = select_source_directory(directories)
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
clear_generated_files(directory)
|
||||
|
||||
sources = iter_source_files(directory)
|
||||
images: list[str] = []
|
||||
for index, source in enumerate(sources, start=1):
|
||||
target = directory / f"{index}.webp"
|
||||
try:
|
||||
convert_to_webp(source, target, image_mod, image_ops)
|
||||
images.append(target.name)
|
||||
print(f"[{name}] {source.name} -> {target.name}")
|
||||
except Exception as exc:
|
||||
print(f"[{name}] skip {source.name}: {exc}", file=sys.stderr)
|
||||
|
||||
return {
|
||||
"folder": directory.name,
|
||||
"count": len(images),
|
||||
"images": images,
|
||||
}
|
||||
|
||||
|
||||
def write_manifest(manifest: dict) -> None:
|
||||
MANIFEST_PATH.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
image_mod, image_ops = load_pillow()
|
||||
|
||||
manifest = {
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"variants": {},
|
||||
}
|
||||
|
||||
for name, directories in VARIANTS.items():
|
||||
manifest["variants"][name] = build_variant(name, directories, image_mod, image_ops)
|
||||
|
||||
write_manifest(manifest)
|
||||
print(f"Wrote manifest: {MANIFEST_PATH}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user