45f7a77171
Build & Deploy / build-and-deploy (push) Successful in 46s
- 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>
90 lines
2.7 KiB
Python
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
|