""" Image-family extractor: jpg/jpeg, png. Reads EXIF (including capture date) into metadata, writes a downscaled JPEG thumbnail, and — when options['ocr'] is set and pytesseract is available — OCRs the image into content. Without OCR, content is a short placeholder note. """ import io from pathlib import Path from .base import ExtractionResult EXTRACTOR_ID = "image/pillow@1" THUMB_MAX = 640 # longest edge, px def _exif(img) -> dict: try: from PIL.ExifTags import TAGS except ImportError: return {} raw = getattr(img, "_getexif", lambda: None)() if not raw: return {} out = {} for tag_id, value in raw.items(): name = TAGS.get(tag_id, str(tag_id)) if name in ("DateTime", "DateTimeOriginal", "Make", "Model", "Orientation"): out[name] = str(value) return out def _thumbnail(img) -> bytes: from PIL import Image # noqa: F401 (ensure Pillow present) im = img.copy() im.thumbnail((THUMB_MAX, THUMB_MAX)) if im.mode not in ("RGB", "L"): im = im.convert("RGB") buf = io.BytesIO() im.save(buf, format="JPEG", quality=80) return buf.getvalue() def extract(source: Path, options: dict) -> ExtractionResult: from PIL import Image # lazy warnings = [] metadata = {} content = "" thumb = None with Image.open(source) as img: metadata["width"], metadata["height"] = img.size metadata["mode"] = img.mode exif = _exif(img) if exif: metadata["exif"] = exif if "DateTimeOriginal" in exif or "DateTime" in exif: metadata["captured"] = exif.get("DateTimeOriginal", exif.get("DateTime")) try: thumb = _thumbnail(img) except Exception as e: warnings.append(f"thumbnail failed: {e}") if options.get("ocr"): try: import pytesseract content = pytesseract.image_to_string(img).strip() metadata["ocr"] = True except ImportError: warnings.append("--ocr requested but pytesseract not installed") except Exception as e: warnings.append(f"ocr failed: {e}") if not content: content = f"![{source.name}](thumb.jpg)\n\n_Image {metadata.get('width')}×{metadata.get('height')}. Run with --ocr to extract any text._\n" return ExtractionResult(content=content, metadata=metadata, thumb=thumb, warnings=warnings, extractor=EXTRACTOR_ID)