Files
pokemon_pinball_wiki/scripts/harvest_loose_openable_assets.py
2026-07-06 20:06:06 -05:00

103 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Copy loose rootfs assets with viewer-friendly extensions.
This preserves target files unchanged and writes renamed copies under assets/.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import shutil
import struct
from pathlib import Path
def sha256_file(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()
def safe_parts(path: Path) -> list[str]:
parts: list[str] = []
for part in path.parts:
if part in ("", ".", ".."):
continue
clean = re.sub(r"[^A-Za-z0-9._' -]+", "_", part).strip()
parts.append(clean or "_")
return parts
def detect_extension(data: bytes) -> str | None:
if data.startswith(b"\x89PNG\r\n\x1a\n"):
return ".png"
if data.startswith((b"\x00\x01\x00\x00", b"OTTO", b"ttcf")):
return ".ttf"
if len(data) >= 12 and data[4:8] == b"ftyp":
major = data[8:12]
return ".mov" if major == b"qt " else ".mp4"
return None
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--source", type=Path, default=Path("games/pokemon_pro/assets"))
parser.add_argument("--output", type=Path, default=Path("assets/openable/loose"))
parser.add_argument("--manifest", type=Path, default=Path("assets/loose_manifest.json"))
args = parser.parse_args()
copied = []
skipped = 0
for src in sorted(args.source.rglob("*")):
if not src.is_file() or src.name == "main.pck":
continue
with src.open("rb") as f:
head = f.read(64)
ext = detect_extension(head)
if ext is None:
skipped += 1
continue
rel = src.relative_to(args.source)
rel_parts = safe_parts(rel)
out = args.output.joinpath(*rel_parts)
if out.suffix.lower() != ext:
out = out.with_name(out.name + ext)
out.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(src, out)
copied.append(
{
"source": str(src),
"source_target_path": "/games/pokemon_pro/assets/" + str(rel),
"output": str(out),
"size": src.stat().st_size,
"sha256": sha256_file(src),
"detected_extension": ext,
}
)
args.manifest.parent.mkdir(parents=True, exist_ok=True)
args.manifest.write_text(
json.dumps(
{
"source_root": str(args.source),
"output_root": str(args.output),
"copied": len(copied),
"skipped_non_openable": skipped,
"entries": copied,
},
indent=2,
),
encoding="utf-8",
)
print(json.dumps({"copied": len(copied), "skipped_non_openable": skipped}, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())