What a photo's EXIF says, in two forms: a summary worth keeping, and the whole dump to look at.
ImageMagick (CaptureDate.read_exif/1) prints every tag as text, most of them
as fractions: FNumber=1433/512, GPSLatitude=45/1,28/1,1199/100,
ExposureTime=10/1250. This module turns that into something a person or a
map can use.
The summary
summary/1 is what is stored in the file's metadata["exif"]: a small,
JSON-safe map with only the groups the photo has.
%{
"camera" => %{"make" => "Apple", "model" => "iPhone 17 Pro",
"lens_model" => "...", "software" => "27.0"},
"exposure" => %{"focal_length" => 16.9, "focal_length_35mm" => 200,
"f_number" => 2.8, "exposure_time" => "1/125", "iso" => 50,
"flash" => false},
"dates" => %{"original" => "2026-10-05T18:42:46", "digitized" => "...",
"modified" => "...", "offset" => "+02:00"},
"gps" => %{"latitude" => 45.4698, "longitude" => 10.7209,
"altitude" => 105.0, "speed_kmh" => 0.9, "direction" => 352.8,
"timestamp" => "2026-10-05T16:42:46Z"},
"orientation" => 1
}A photo whose tags fall in none of these groups has only %{"tags" => count},
so it can be told from one with no EXIF at all (%{}).
The camera's serial number and the maker note are not kept. The position is
also written to the file's latitude / longitude columns (coordinates/1),
the copy a map searches.
The whole dump
groups/1 lists every tag the file carries, grouped as web EXIF viewers do
(Camera, Exposure, Dates, Location, Image, Other), with fractions shown as
numbers. It is read from the original on request, never stored.
Summary
Functions
The GPS position of tags as {latitude, longitude} in decimal degrees, or
nil: no position, an impossible one, or the 0, 0 a camera without a fix
writes.
What to record on a file for tags: %{exif: summary, latitude: lat, longitude: lon}
(the position nil when there is none). summary is %{} for a photo with no
EXIF: stored as such, it says "read, and there was nothing", which is not the
same as never read.
The titles of the groups groups/1 uses, in order: [{group, title}].
Every tag in tags, grouped for a viewer: [{group, [{label, value}]}] in the
order of group_titles/0, empty groups left out. Fractions are shown as numbers
and a GPS position as decimal degrees.
The EXIF tags of the image at path (CaptureDate.read_exif/1): %{} when it has none.
The curated, JSON-safe summary of tags; %{} when there is nothing worth keeping.
Types
Functions
The GPS position of tags as {latitude, longitude} in decimal degrees, or
nil: no position, an impossible one, or the 0, 0 a camera without a fix
writes.
What to record on a file for tags: %{exif: summary, latitude: lat, longitude: lon}
(the position nil when there is none). summary is %{} for a photo with no
EXIF: stored as such, it says "read, and there was nothing", which is not the
same as never read.
The titles of the groups groups/1 uses, in order: [{group, title}].
Every tag in tags, grouped for a viewer: [{group, [{label, value}]}] in the
order of group_titles/0, empty groups left out. Fractions are shown as numbers
and a GPS position as decimal degrees.
The EXIF tags of the image at path (CaptureDate.read_exif/1): %{} when it has none.
The curated, JSON-safe summary of tags; %{} when there is nothing worth keeping.