Probe remote image dimensions (width × height) from just a URL.

Image formats carry their dimensions in the file header, so Dims fetches only the first ~128 KB via an HTTP Range request, parses the JPEG/PNG/WebP/GIF header, and discards the bytes — milliseconds and a few KB per image instead of full downloads.

Dims.probe("https://cdn.example.com/page1.jpg")
#=> %{width: 800, height: 8000}

Dims.probe_all(urls)
#=> [%{url: ..., width: 800, height: 1200}, ...]

Built for the real world of images you don't host:

  • Batch probingprobe_all/2 runs a bounded-concurrency parallel sweep, preserving input order.
  • Median backfill — a probe that fails or times out gets the median dimensions of its successful siblings instead of a constant fallback; image sets (chapter pages, galleries) are near-uniform, so the estimate is close and never wrecks layouts built from relative sizes.
  • Samplingprobe_sampled/2 probes ~20 evenly-distributed URLs of a huge list and median-fills the rest (accuracy within a percent or two for uniform sets, ~25× less traffic). probe_auto/2 picks full vs sampled by list length.
  • Caching — results cache in ETS keyed by URL (30-day TTL by default); a URL's bytes don't change, so its dimensions don't either.

Options

All functions accept:

  • :headers — extra request headers, e.g. a referer for CDNs that check it: headers: [{"referer", "https://source.example/"}]
  • :headers_fun(url -> headers) when headers depend on the URL (per-host referers); merged over :headers
  • :probe_bytes — Range size to request (default 131_071)
  • :receive_timeout — per-request timeout ms (default 8_000)
  • :cachefalse to bypass the cache (default true)
  • :cache_ttl — cache entry TTL ms (default 30 days)

Batch functions additionally accept:

  • :max_concurrency — parallel probes (default 8)
  • :sample_size — for probe_sampled/2 (default 20)
  • :full_threshold — for probe_auto/2, list length at or below which a full probe runs (default 80)

URL fragments are stripped before fetching and caching (they never reach the server anyway).

Summary

Functions

Backfill nil dimensions with the median width/height of the entries that did probe. Exposed for callers assembling their own probe results; leaves the list untouched when nothing probed.

Probe a single URL. Returns %{width:, height:} or nil when the fetch fails or the format is unrecognized.

Probe every URL in parallel. Returns [%{url:, width:, height:}] preserving input order; failed probes carry the median dimensions of the successful ones (or nil dims when nothing probed).

Full probe for lists up to :full_threshold (default 80), sampled beyond — the right default when exactness matters for normal-sized sets (per-image aspect decisions) but huge sets are near-uniform.

Probe up to :sample_size evenly-distributed URLs and median-fill the rest. Falls through to probe_all/2 for small lists.

Types

dims()

@type dims() :: %{width: pos_integer(), height: pos_integer()}

Functions

fill_missing_with_median(images)

@spec fill_missing_with_median([map()]) :: [map()]

Backfill nil dimensions with the median width/height of the entries that did probe. Exposed for callers assembling their own probe results; leaves the list untouched when nothing probed.

probe(url, opts \\ [])

@spec probe(
  String.t(),
  keyword()
) :: dims() | nil

Probe a single URL. Returns %{width:, height:} or nil when the fetch fails or the format is unrecognized.

probe_all(urls, opts \\ [])

@spec probe_all(
  [String.t()],
  keyword()
) :: [map()]

Probe every URL in parallel. Returns [%{url:, width:, height:}] preserving input order; failed probes carry the median dimensions of the successful ones (or nil dims when nothing probed).

probe_auto(urls, opts \\ [])

@spec probe_auto(
  [String.t()],
  keyword()
) :: [map()]

Full probe for lists up to :full_threshold (default 80), sampled beyond — the right default when exactness matters for normal-sized sets (per-image aspect decisions) but huge sets are near-uniform.

probe_sampled(urls, opts \\ [])

@spec probe_sampled(
  [String.t()],
  keyword()
) :: [map()]

Probe up to :sample_size evenly-distributed URLs and median-fill the rest. Falls through to probe_all/2 for small lists.