Indexing tells an investigator what exists. The next step is extraction: pulling the useful content out, and in digital forensics the useful content is often metadata the file's author never meant to share.
Reading a photograph's hidden diary
A photograph from a phone or camera typically carries EXIF metadata: the device model, the date and time, and frequently the GPS coordinates of where it was taken, unless an app has stripped them.
extract_exif.py
# extract_exif.py
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
def extract_exif(image_path):
raw = Image.open(image_path)._getexif()
if not raw:
return {}
return {TAGS.get(tag_id, tag_id): value for tag_id, value in raw.items()}
def extract_gps(exif_data):
gps = exif_data.get("GPSInfo")
if not gps:
return None
return {GPSTAGS.get(k, k): v for k, v in gps.items()}
data = extract_exif("evidence_copy/photo_001.jpg")
print(f"Camera model: {data.get('Model', 'unknown')}")
print(f"Date taken: {data.get('DateTimeOriginal', 'unknown')}")
gps = extract_gps(data)
print(f"GPS data present: {gps}" if gps else "No GPS data in this image")
Sample output
Camera model: iPhone 13
Date taken: 2026:09:14 18:42:07
GPS data present: {'GPSLatitudeRef': 'N', 'GPSLatitude': (23.0, 13.0, 4.44), 'GPSLongitudeRef': 'E', 'GPSLongitude': (72.0, 38.0, 12.84)}
Those coordinates place the photograph in Gandhinagar to within a few metres. A picture that looks innocent on screen just disclosed exactly where it was taken, which is the whole lesson of the McAfee case below.
Python concept: _getexif() is Pillow's older, simpler method
_getexif() is a private method Pillow keeps for backward compatibility, and it conveniently flattens the standard Exif sub-fields and the GPS block into one plain dictionary, exactly as used above. Current Pillow code is encouraged to use the public .getexif() method instead, together with .get_ifd(0x8825) for the GPS block, which is more explicit about where each field actually lives inside the file. Either approach reads the same underlying data; _getexif() is simply the shorter route for a script like this one.
GPS coordinates in EXIF are stored as degrees, minutes and seconds, not plain decimal degrees, which is why GPSLatitude prints as a tuple like (23.0, 13.0, 4.44) rather than a single number such as 23.2179. Converting between the two is ordinary arithmetic:
dms_to_decimal.py
def dms_to_decimal(dms, ref):
degrees, minutes, seconds = dms
decimal = degrees + minutes / 60 + seconds / 3600
return -decimal if ref in ("S", "W") else decimal
lat = dms_to_decimal((23.0, 13.0, 4.44), "N")
lon = dms_to_decimal((72.0, 38.0, 12.84), "E")
print(f"{lat:.4f}, {lon:.4f}")
Sample output
23.2179, 72.6369
Paste those two numbers into any map and the exact spot appears.
Real incident: John McAfee, 2012: a photograph that gave away a hideout
In December 2012, the software entrepreneur John McAfee was wanted for questioning by authorities in Belize and had gone into hiding. While on the run, he gave an interview to journalists from Vice magazine, who posted a photo of him to their website from a mobile phone. The photograph's EXIF metadata, the exact fields the code above reads, still contained the GPS coordinates where it was taken, which placed McAfee at a specific location in Guatemala. The coordinates were noticed within hours of the photo going online, and he was located shortly afterward.
What this teaches: the GPS fields extract_gps() pulls out are not a theoretical risk. A single photograph, shared without stripping its metadata, located a man who was actively trying not to be found. For an investigator this is a lead; for everyone else it is a reminder that a photograph carries a hidden diary of where and when it was taken.
Try it yourself: Check your own photographs
Most phones let you check whether location tagging was on when a photo was taken. Pick a recent photo of your own, run extract_exif.py against it, and see what it reveals. If GPS data is present, you have just reproduced, on a photo you control, exactly what located McAfee.
Check your understanding
Question 1: What kinds of information can EXIF metadata reveal about a photograph that are not visible on screen?
The device model and manufacturer, the exact date and time the photo was taken, and often the GPS coordinates of where it was taken, all stored separately from the visible picture and invisible to anyone just looking at the image.
Question 2: Why does extract_gps() need a separate function instead of reading the coordinates straight off the top-level exif data?
GPS fields in EXIF are stored inside their own nested block, identified by the key GPSInfo, with their own set of tag numbers that need the separate GPSTAGS lookup table to become readable field names. Treating them exactly like the top-level fields would either miss them or produce unreadable numeric keys.
Question 3: In the McAfee case, what specifically let investigators locate him, and why was it invisible to someone just viewing the photo?
The GPS coordinates embedded in the photograph's EXIF metadata. They are invisible because EXIF metadata sits alongside, not inside, the visible picture; nothing about looking at the photo on screen reveals that the coordinates are riding along with it.
Key takeaway: Key takeaway
A photograph is two documents in one: the picture anyone can see, and a hidden diary of camera, timestamp and often exact location. Pillow reads that diary in a few lines, and the McAfee case shows what one unstripped photograph can give away.

