Files
timing/backend/image_tagger.py
T
steinhelge 45f7a77171
Build & Deploy / build-and-deploy (push) Successful in 46s
Støtte for flere bibs per bilde, EXIF-metadata og zoom i gjennomgang
- OCR: ny read_all_bibs() returnerer alle unike startnumre (≥2 sifre) per bilde
- Ingest: oppretter én passering per bib (ikke bare beste), ingen bib → needs_review
- image_tagger.py: skriv/les bib-metadata som JSON i EXIF UserComment (piexif)
- Ingest + resolve: tagger bildefilen med bibs automatisk og ved manuell bekreftelse
- API: POST /api/passages/{id}/reanalyze — re-kjør OCR på eksisterende bilde
- API: POST /api/passages/{id}/resolve oppdaterer nå EXIF med bekreftet bib
- races: ny kolonne bib_filter_enabled (med automatisk migrering) + per-løp toggle
- ReviewPage: Re-analyser-knapp og klikk-for-zoom med scroll/drag

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 09:01:51 +01:00

90 lines
2.7 KiB
Python

"""
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