""" Skriv og les bib-metadata i bildefiler via EXIF UserComment (JSON). Bruker piexif + Pillow som allerede er i requirements.txt. Metadata lagres i Exif.UserComment som: b"ASCII\x00\x00\x00" + JSON-bytes Eksempel-innhold: {"bibs": ["42", "87"], "station": "finish", "confidence": 0.93, "confirmed": true} """ import json import logging from pathlib import Path from typing import Optional import piexif from PIL import Image logger = logging.getLogger(__name__) _ASCII_PREFIX = b"ASCII\x00\x00\x00" def write_bib_tags( image_path: Path, bibs: list[str], *, station: Optional[str] = None, race_id: Optional[str] = None, confidence: Optional[float] = None, confirmed: bool = False, ) -> None: """ Skriv startnummer-metadata til bildefil som EXIF UserComment (JSON). Bevarer all eksisterende EXIF. Feiler stille — krasjer ikke ingest-prosessen. """ data: dict = {"bibs": bibs} if station: data["station"] = station if race_id: data["race_id"] = race_id if confidence is not None: data["confidence"] = round(confidence, 4) if confirmed: data["confirmed"] = True json_bytes = json.dumps(data, ensure_ascii=True, separators=(",", ":")).encode("ascii") comment_bytes = _ASCII_PREFIX + json_bytes try: img = Image.open(image_path) raw_exif = img.info.get("exif") if raw_exif: exif_dict = piexif.load(raw_exif) else: exif_dict = {"0th": {}, "Exif": {}, "GPS": {}, "Interop": {}, "1st": {}} exif_dict.setdefault("Exif", {})[piexif.ExifIFD.UserComment] = comment_bytes new_exif = piexif.dump(exif_dict) # Lagre til midlertidig fil, rename for atomisk skriving tmp = image_path.with_suffix(".tmp" + image_path.suffix) img.save(tmp, exif=new_exif) tmp.replace(image_path) logger.debug("EXIF-tags skrevet til %s: bibs=%s", image_path.name, bibs) except Exception as e: logger.warning("Kunne ikke skrive EXIF-tags til %s: %s", image_path, e) def read_bib_tags(image_path: Path) -> Optional[dict]: """ Les startnummer-metadata fra EXIF UserComment. Returnerer dict (med nøkkel 'bibs') eller None hvis ikke satt. """ try: img = Image.open(image_path) raw_exif = img.info.get("exif") if not raw_exif: return None exif_dict = piexif.load(raw_exif) comment = exif_dict.get("Exif", {}).get(piexif.ExifIFD.UserComment) if not comment or not comment.startswith(_ASCII_PREFIX): return None json_str = comment[len(_ASCII_PREFIX):].decode("ascii", errors="replace") return json.loads(json_str) except Exception: return None