chore: initialize WanderQ MiniAPP 2.0 repository
This commit is contained in:
129
scripts/prepare-static-webp.py
Normal file
129
scripts/prepare-static-webp.py
Normal file
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert the repository's runtime image assets to WebP.
|
||||
|
||||
This is intentionally a one-time migration helper. It keeps the source files
|
||||
untouched and writes converted files into a caller-provided temporary folder.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
|
||||
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png"}
|
||||
LOSSLESS_PATH_PARTS = {"routes", "train-intro", "volumes", "products", "source"}
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--manifest", type=Path, required=True)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def source_files(root: Path) -> list[tuple[Path, str]]:
|
||||
public_root = root / "public" / "assets"
|
||||
files: list[tuple[Path, str]] = []
|
||||
for path in sorted(public_root.rglob("*")):
|
||||
if path.is_file() and path.suffix.lower() in IMAGE_EXTENSIONS:
|
||||
relative = path.relative_to(public_root).as_posix()
|
||||
files.append((path, relative))
|
||||
|
||||
logo = root / "apps" / "miniprogram" / "src" / "assets" / "wanderq-logo.png"
|
||||
if logo.is_file():
|
||||
files.append((logo, "brand/wanderq-logo.png"))
|
||||
return files
|
||||
|
||||
|
||||
def should_use_lossless(source: Path, relative: str) -> bool:
|
||||
if source.suffix.lower() == ".png":
|
||||
return True
|
||||
parts = set(Path(relative).parts)
|
||||
return bool(parts & LOSSLESS_PATH_PARTS) or relative.endswith("train/price-overview.jpg")
|
||||
|
||||
|
||||
def convert(source: Path, target: Path, lossless: bool) -> None:
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
with Image.open(source) as opened:
|
||||
image = ImageOps.exif_transpose(opened)
|
||||
save_options = {
|
||||
"format": "WEBP",
|
||||
"method": 6,
|
||||
"lossless": lossless,
|
||||
}
|
||||
if not lossless:
|
||||
save_options["quality"] = 84
|
||||
icc_profile = image.info.get("icc_profile")
|
||||
if icc_profile:
|
||||
save_options["icc_profile"] = icc_profile
|
||||
image.save(target, **save_options)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
root = args.root.resolve()
|
||||
output = args.output.resolve()
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
manifest: list[dict[str, object]] = []
|
||||
files = source_files(root)
|
||||
if not files:
|
||||
raise SystemExit("未找到本地静态图片源;为避免生成空清单,请确认源素材仍在 public/assets 或小程序 logo 路径中")
|
||||
|
||||
for source, relative in files:
|
||||
output_relative = f"{Path(relative).with_suffix('')}.webp"
|
||||
target = output / output_relative
|
||||
lossless = should_use_lossless(source, relative)
|
||||
convert(source, target, lossless)
|
||||
source_bytes = source.stat().st_size
|
||||
output_bytes = target.stat().st_size
|
||||
digest = hashlib.sha256(target.read_bytes()).hexdigest()
|
||||
if relative == "brand/wanderq-logo.png":
|
||||
reference = None
|
||||
object_key = "wanqu/miniapp/static/brand/wanderq-logo.webp"
|
||||
source_path = "apps/miniprogram/src/assets/wanderq-logo.png"
|
||||
else:
|
||||
reference = f"/assets/{relative}"
|
||||
object_key = f"wanqu/miniapp/static/{output_relative}"
|
||||
source_path = f"public/assets/{relative}"
|
||||
manifest.append(
|
||||
{
|
||||
"sourcePath": source_path,
|
||||
"reference": reference,
|
||||
"outputPath": output_relative,
|
||||
"objectKey": object_key,
|
||||
"lossless": lossless,
|
||||
"sourceBytes": source_bytes,
|
||||
"outputBytes": output_bytes,
|
||||
"sha256": digest,
|
||||
}
|
||||
)
|
||||
|
||||
args.manifest.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.manifest.write_text(json.dumps({"assets": manifest}, ensure_ascii=False, indent=2) + os.linesep)
|
||||
source_total = sum(int(item["sourceBytes"]) for item in manifest)
|
||||
output_total = sum(int(item["outputBytes"]) for item in manifest)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"assets": len(manifest),
|
||||
"sourceBytes": source_total,
|
||||
"outputBytes": output_total,
|
||||
"savedBytes": source_total - output_total,
|
||||
"lossless": sum(1 for item in manifest if item["lossless"]),
|
||||
"lossy": sum(1 for item in manifest if not item["lossless"]),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user