#!/usr/bin/env python3 """Convert .npy face embeddings to pickle-free .f32emb before HF Space deploy. Uses only stdlib so GitHub Actions runners do not need numpy pre-installed. """ from __future__ import annotations import os import struct import sys _FR = os.path.dirname(os.path.abspath(__file__)) if _FR not in sys.path: sys.path.insert(0, _FR) MAGIC = b"CEPF32E1" HEADER = struct.Struct("<8sII") NPY_MAGIC = b"\x93NUMPY" def _parse_npy_header(raw: bytes) -> tuple[str, int]: if not raw.startswith(NPY_MAGIC): raise ValueError("Not a .npy file") major, minor = raw[6], raw[7] if (major, minor) == (1, 0): hlen = struct.unpack("= (2, 0): hlen = struct.unpack(" tuple[int, ...]: # e.g. {'descr': ' None: with open(npy_path, "rb") as fh: raw = fh.read() header, offset = _parse_npy_header(raw) shape = _shape_from_header(header) if len(shape) == 1: n_vec, dim = 1, shape[0] elif len(shape) == 2: n_vec, dim = shape else: raise ValueError(f"Unsupported embedding shape {shape}") data = raw[offset : offset + n_vec * dim * 4] if len(data) != n_vec * dim * 4: raise ValueError(f"Truncated npy payload in {npy_path}") os.makedirs(os.path.dirname(f32_path) or ".", exist_ok=True) with open(f32_path, "wb") as out: out.write(HEADER.pack(MAGIC, n_vec, dim)) out.write(data) def export_tree(root: str) -> int: if not os.path.isdir(root): return 0 converted = 0 for dirpath, _, files in os.walk(root): for fname in files: if not fname.endswith(".npy"): continue npy_path = os.path.join(dirpath, fname) f32_path = npy_path[:-4] + ".f32emb" try: npy_to_f32emb(npy_path, f32_path) converted += 1 print(f"Exported {npy_path} -> {f32_path}") except Exception as exc: print(f"WARN: could not export {npy_path}: {exc}", file=sys.stderr) return converted def main() -> int: roots = [ os.path.join(_FR, "faces_db"), os.path.join(_FR, "temp_faces_db"), ] total = sum(export_tree(r) for r in roots) print(f"Converted {total} embedding file(s) to .f32emb") return 0 if __name__ == "__main__": raise SystemExit(main())