chore: initialize WanderQ MiniAPP 2.0 repository

This commit is contained in:
inman
2026-07-23 15:48:34 +08:00
commit b4c0a1c516
93 changed files with 53525 additions and 0 deletions

View 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()