""" EXIF metadata provider. Delegates all parsing to cores.metadata.extractor — no duplicated Pillow open / EXIF tag walk / GPS conversion logic. """ from __future__ import annotations from config.settings import Settings, settings as _default_settings from cores.metadata import extract_all from pipeline.feature_extraction import PipelineOutput from providers.base import BaseProvider, ProviderCapability class EXIFProvider(BaseProvider): name = "exif" capability = ProviderCapability.METADATA def __init__(self, settings: Settings | None = None) -> None: super().__init__(settings=settings or _default_settings) def is_available(self) -> bool: try: import PIL # noqa: F401 return True except ImportError: return False def _run(self, pipeline_output: PipelineOutput) -> tuple[dict, dict]: original = pipeline_output.original_bytes if not original: raw = {"error": "No original bytes available for EXIF extraction"} normalized = { "format": pipeline_output.original_format, "exif": {}, "xmp": {}, "iptc": {}, } return raw, normalized data = extract_all(original) raw = { "format": data["format"], "exif_tags_count": len(data["exif"]), "gps_tags_count": len(data["gps"]), "exif": data["exif"], "gps": data["gps"], "camera_make": data["camera_make"], "camera_model": data["camera_model"], "software": data["software"], "capture_time": data["capture_time"], "error": data["error"], } normalized = { "format": data["format"], "exif": data["exif"], "xmp": {} if not data["xmp"] else {"raw_length": len(data["xmp"])}, "iptc": data["iptc"], "gps": data["gps_coords"], "camera_make": data["camera_make"], "camera_model": data["camera_model"], "software": data["software"], "capture_time": data["capture_time"], } return raw, normalized