eb8dbf66a1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
111 lines
3.5 KiB
Python
111 lines
3.5 KiB
Python
"""Tests para extract_exif_metadata."""
|
|
|
|
import os
|
|
import sys
|
|
|
|
from PIL import Image
|
|
from PIL.ExifTags import Base, GPS, IFD
|
|
from PIL.TiffImagePlugin import IFDRational
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
|
|
from cybersecurity.extract_exif_metadata import extract_exif_metadata
|
|
|
|
|
|
def _make_png_without_exif(path: str) -> None:
|
|
"""Crea un PNG pequeño sin EXIF."""
|
|
Image.new("RGB", (4, 4), (10, 20, 30)).save(path, format="PNG")
|
|
|
|
|
|
def _make_jpeg_with_exif(path: str) -> None:
|
|
"""Crea un JPEG pequeño con EXIF de camara/software/fecha."""
|
|
img = Image.new("RGB", (4, 4), (200, 100, 50))
|
|
exif = img.getexif()
|
|
exif[Base.Make.value] = "TestCam"
|
|
exif[Base.Model.value] = "Model X"
|
|
exif[Base.Software.value] = "PyTestRig 1.0"
|
|
exif[Base.DateTime.value] = "2024:08:12 19:43:07"
|
|
# DateTimeOriginal vive en el sub-IFD EXIF.
|
|
exif_ifd = exif.get_ifd(IFD.Exif)
|
|
exif_ifd[Base.DateTimeOriginal.value] = "2024:08:12 19:43:07"
|
|
img.save(path, format="JPEG", exif=exif)
|
|
|
|
|
|
def _make_jpeg_with_gps(path: str) -> None:
|
|
"""Crea un JPEG con GPSInfo en DMS, hemisferio sur y oeste."""
|
|
img = Image.new("RGB", (4, 4), (0, 128, 64))
|
|
exif = img.getexif()
|
|
gps_ifd = exif.get_ifd(IFD.GPSInfo)
|
|
# 40 deg, 25 min, 0.48 seg => 40.41680 grados.
|
|
gps_ifd[GPS.GPSLatitude.value] = (
|
|
IFDRational(40, 1),
|
|
IFDRational(25, 1),
|
|
IFDRational(48, 100),
|
|
)
|
|
gps_ifd[GPS.GPSLatitudeRef.value] = "S" # hemisferio sur => negativo
|
|
# 3 deg, 42 min, 13.68 seg => 3.7038 grados.
|
|
gps_ifd[GPS.GPSLongitude.value] = (
|
|
IFDRational(3, 1),
|
|
IFDRational(42, 1),
|
|
IFDRational(1368, 100),
|
|
)
|
|
gps_ifd[GPS.GPSLongitudeRef.value] = "W" # oeste => negativo
|
|
img.save(path, format="JPEG", exif=exif)
|
|
|
|
|
|
def test_imagen_sin_exif_png_devuelve_none(tmp_path):
|
|
"""imagen sin EXIF (PNG) devuelve campos None y raw vacio."""
|
|
p = str(tmp_path / "plain.png")
|
|
_make_png_without_exif(p)
|
|
|
|
meta = extract_exif_metadata(p)
|
|
|
|
assert meta["datetime"] is None
|
|
assert meta["camera_make"] is None
|
|
assert meta["camera_model"] is None
|
|
assert meta["software"] is None
|
|
assert meta["gps_lat"] is None
|
|
assert meta["gps_lon"] is None
|
|
assert meta["raw"] == {}
|
|
|
|
|
|
def test_imagen_con_exif_devuelve_camara_software_fecha(tmp_path):
|
|
"""imagen con EXIF devuelve camara, software y fecha."""
|
|
p = str(tmp_path / "withexif.jpg")
|
|
_make_jpeg_with_exif(p)
|
|
|
|
meta = extract_exif_metadata(p)
|
|
|
|
assert meta["camera_make"] == "TestCam"
|
|
assert meta["camera_model"] == "Model X"
|
|
assert meta["software"] == "PyTestRig 1.0"
|
|
assert meta["datetime"] == "2024:08:12 19:43:07"
|
|
assert meta["raw"] # no vacio
|
|
|
|
|
|
def test_gps_dms_a_grados_decimales_con_signo(tmp_path):
|
|
"""GPSInfo DMS se convierte a grados decimales con signo por hemisferio."""
|
|
p = str(tmp_path / "withgps.jpg")
|
|
_make_jpeg_with_gps(p)
|
|
|
|
meta = extract_exif_metadata(p)
|
|
|
|
assert meta["gps_lat"] is not None
|
|
assert meta["gps_lon"] is not None
|
|
# Sur y Oeste => ambos negativos.
|
|
assert meta["gps_lat"] < 0
|
|
assert meta["gps_lon"] < 0
|
|
assert abs(meta["gps_lat"] - (-40.41680)) < 1e-4
|
|
assert abs(meta["gps_lon"] - (-3.7038)) < 1e-4
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
with tempfile.TemporaryDirectory() as d:
|
|
test_imagen_sin_exif_png_devuelve_none(Path(d))
|
|
test_imagen_con_exif_devuelve_camara_software_fecha(Path(d))
|
|
test_gps_dms_a_grados_decimales_con_signo(Path(d))
|
|
print("Todos los tests pasaron.")
|