<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged fotio</title>
<link>https://coderlegion.com/tag/fotio</link>
<description>Powered by Question2Answer</description>
<item>
<title>How We Made a Photo-Heavy Site Load Fast: Image Optimization and Edge Caching That Actually Moved</title>
<link>https://coderlegion.com/22622/how-made-photo-heavy-site-load-fast-image-optimization-and-edge-caching-that-actually-moved</link>
<description>&lt;p&gt;If your site's main content &lt;em&gt;is&lt;/em&gt; images, performance is not a nice-to-have — it's the product. On a photography portfolio, every gallery is a wall of large files, and each one competes for bandwidth, blocks rendering, and drags down your Core Web Vitals.&lt;/p&gt;
&lt;p&gt;We ran into exactly this while building &lt;a rel=&quot;nofollow&quot; href=&quot;https://fotio.app&quot;&gt;fotio.app&lt;/a&gt;, a marketplace for booking professional photographers across 20+ cities in Spain. Portfolio pages routinely load dozens of high-resolution photos, and early on our Largest Contentful Paint (LCP) was embarrassing. This post walks through what we changed, what actually helped, and — importantly — the CDN and caching details that most &quot;just add a CDN&quot; articles skip.&lt;/p&gt;
&lt;h3 id=&quot;the-starting-point&quot;&gt;The starting point&lt;/h3&gt;
&lt;p&gt;A typical portfolio page looked like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;30–60 images per page, many well over 1 MB each&lt;/li&gt;
&lt;li&gt;Original JPEGs served at full resolution regardless of the viewport&lt;/li&gt;
&lt;li&gt;A single origin server, so a user in Mexico City pulled every byte across the Atlantic&lt;/li&gt;
&lt;li&gt;No lazy loading — everything fetched on page load, including images far below the fold&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result was a heavy page, a slow first paint, and layout that jumped around as images popped in. Let's fix it in layers.&lt;/p&gt;
&lt;h3 id=&quot;layer-1-serve-modern-formats&quot;&gt;Layer 1: Serve modern formats&lt;/h3&gt;
&lt;p&gt;The single biggest win was switching away from unoptimized JPEG. AVIF and WebP produce dramatically smaller files at equivalent visual quality — AVIF frequently lands 40–50% smaller than a comparable JPEG.&lt;/p&gt;
&lt;p&gt;The trick is to serve the best format the browser supports without forking your markup. The &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element handles this declaratively:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;picture&amp;gt;
  &amp;lt;source srcset=&quot;/img/session.avif&quot; type=&quot;image/avif&quot; /&amp;gt;
  &amp;lt;source srcset=&quot;/img/session.webp&quot; type=&quot;image/webp&quot; /&amp;gt;
  &amp;lt;img src=&quot;/img/session.jpg&quot; alt=&quot;Outdoor family session in Seville&quot; /&amp;gt;
&amp;lt;/picture&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The browser walks the sources top to bottom and uses the first format it understands, falling back to the plain JPEG for anything ancient.&lt;/p&gt;
&lt;h3 id=&quot;layer-2-stop-shipping-desktop-images-to-phones&quot;&gt;Layer 2: Stop shipping desktop images to phones&lt;/h3&gt;
&lt;p&gt;A 2400px-wide photo on a 390px phone screen is pure waste. Responsive &lt;code&gt;srcset&lt;/code&gt; lets the browser pick an appropriately sized file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;img
  src=&quot;/img/session-800.jpg&quot;
  srcset=&quot;/img/session-400.jpg 400w,
          /img/session-800.jpg 800w,
          /img/session-1600.jpg 1600w&quot;
  sizes=&quot;(max-width: 600px) 90vw, 800px&quot;
  alt=&quot;Couple love story session in Barcelona&quot;
  loading=&quot;lazy&quot;
  decoding=&quot;async&quot;
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two cheap wins hide in there: &lt;code&gt;loading=&quot;lazy&quot;&lt;/code&gt; defers off-screen images until the user scrolls near them, and &lt;code&gt;decoding=&quot;async&quot;&lt;/code&gt; keeps image decoding off the main thread.&lt;/p&gt;
&lt;h3 id=&quot;layer-3-reserve-space-so-the-layout-doesn-t-jump&quot;&gt;Layer 3: Reserve space so the layout doesn't jump&lt;/h3&gt;
&lt;p&gt;Lazy-loaded images that pop in without reserved space cause Cumulative Layout Shift (CLS) — content jumping around as photos arrive. Always give the browser the dimensions up front:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;img src=&quot;/img/session.jpg&quot; width=&quot;1600&quot; height=&quot;1067&quot; alt=&quot;...&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a nicer perceived load, we also render a tiny blurred placeholder (an LQIP — low-quality image placeholder) that's swapped for the real image once it arrives. It's a few hundred bytes of inlined base64 that makes the page feel instant even before the full photo lands.&lt;/p&gt;
&lt;h3 id=&quot;layer-4-cdn-and-edge-caching-the-part-people-gloss-over&quot;&gt;Layer 4: CDN and edge caching — the part people gloss over&lt;/h3&gt;
&lt;p&gt;Everything above shrinks the files. A CDN changes &lt;em&gt;where they come from&lt;/em&gt;, and this is where a photo-heavy site lives or dies.&lt;/p&gt;
&lt;h4 id=&quot;why-it-matters&quot;&gt;Why it matters&lt;/h4&gt;
&lt;p&gt;With a single origin, distance becomes latency. A user far from your server pays a round-trip penalty on every request, and images are your heaviest resource. A CDN copies your files to edge servers physically close to the user, so the bytes travel a short distance instead of across the world.&lt;/p&gt;
&lt;h4 id=&quot;how-the-cache-actually-behaves&quot;&gt;How the cache actually behaves&lt;/h4&gt;
&lt;p&gt;The first request for a file from a given region is a &lt;strong&gt;cache miss&lt;/strong&gt;: the CDN fetches it from your origin, stores a copy on the nearest edge node, and serves it. Every subsequent request from that region is a &lt;strong&gt;cache hit&lt;/strong&gt; — served straight from the edge, never touching your origin. Your job is to maximize hits and make each cached copy live as long as possible.&lt;/p&gt;
&lt;h4 id=&quot;cache-control-headers&quot;&gt;Cache-Control headers&lt;/h4&gt;
&lt;p&gt;For images that never change, tell browsers and the CDN to hold onto them essentially forever:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Cache-Control: public, max-age=31536000, immutable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's one year, and &lt;code&gt;immutable&lt;/code&gt; tells the browser not to even bother revalidating. HTML is the opposite — it changes, so it gets a short TTL or no caching at all. Applying one blanket rule to everything is a classic mistake; split your static assets from your dynamic documents.&lt;/p&gt;
&lt;h4 id=&quot;cache-busting-with-hashed-filenames&quot;&gt;Cache busting with hashed filenames&lt;/h4&gt;
&lt;p&gt;Once a file is cached &quot;forever,&quot; how do you ever update it? You don't change the content at the same URL — you change the URL. Embed a content hash in the filename:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;session.a3f9b2.avif   →   session.7c1e04.avif   (after re-export)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The old URL stays cached and harmless; the new one is a fresh miss that gets cached in turn. &lt;code&gt;immutable&lt;/code&gt; plus hashed filenames is the standard pairing, and it means you never fight a stale cache again.&lt;/p&gt;
&lt;h4 id=&quot;transform-at-the-edge&quot;&gt;Transform at the edge&lt;/h4&gt;
&lt;p&gt;Many CDNs (Cloudflare Images, Fastly, bunny.net, imgix) can convert and resize on the fly at the edge, reading the browser's &lt;code&gt;Accept&lt;/code&gt; header and returning AVIF or WebP from a single canonical URL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/cdn/session.jpg?format=auto&amp;amp;width=800
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This collapses the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;/&lt;code&gt;srcset&lt;/code&gt; gymnastics into one source of truth and offloads the work from your origin entirely.&lt;/p&gt;
&lt;h4 id=&quot;the-metric-this-moves&quot;&gt;The metric this moves&lt;/h4&gt;
&lt;p&gt;Edge caching lands squarely on &lt;strong&gt;TTFB&lt;/strong&gt; (time to first byte). A cache hit from a nearby edge answers in tens of milliseconds; a trip to a distant origin can be several hundred. Since your LCP element on a portfolio page is almost always an image, a faster TTFB on that image flows straight through to a faster LCP.&lt;/p&gt;
&lt;h3 id=&quot;what-the-numbers-looked-like&quot;&gt;What the numbers looked like&lt;/h3&gt;
&lt;p&gt;Rough before/after on a representative gallery page (measured with WebPageTest from a mid-tier connection):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Metric &lt;/th&gt;&lt;th&gt;Before &lt;/th&gt;&lt;th&gt;After&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Page weight &lt;/td&gt;&lt;td&gt;~9 MB &lt;/td&gt;&lt;td&gt;~1.8 MB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;LCP &lt;/td&gt;&lt;td&gt;~4.8 s &lt;/td&gt;&lt;td&gt;~1.6 s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TTFB (image, distant region) &lt;/td&gt;&lt;td&gt;~380 ms &lt;/td&gt;&lt;td&gt;~40 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CLS &lt;/td&gt;&lt;td&gt;0.21 &lt;/td&gt;&lt;td&gt;0.02&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Most of the weight reduction came from format + responsive sizing; most of the &lt;em&gt;latency&lt;/em&gt; reduction came from edge caching. They're complementary — you want both.&lt;/p&gt;
&lt;h3 id=&quot;takeaways&quot;&gt;Takeaways&lt;/h3&gt;
&lt;p&gt;If you're optimizing a photo-heavy site, work in this order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Modern formats&lt;/strong&gt; (AVIF/WebP with a JPEG fallback) — biggest byte savings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Responsive &lt;code&gt;srcset&lt;/code&gt; + lazy loading&lt;/strong&gt; — stop over-serving and over-fetching.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicit dimensions + LQIP&lt;/strong&gt; — kill layout shift and improve perceived speed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CDN with aggressive, correct caching&lt;/strong&gt; — long &lt;code&gt;immutable&lt;/code&gt; TTLs, hashed filenames, edge transforms.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;None of these are exotic, but the caching layer is the one most teams under-configure, and it's the one that decides whether a user two continents away has a good time.&lt;/p&gt;
&lt;p&gt;You can see the end result on a genuinely image-dense site at &lt;a rel=&quot;nofollow&quot; href=&quot;https://fotio.app&quot;&gt;fotio.app&lt;/a&gt; — every city and portfolio page leans on exactly this pipeline.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22622/how-made-photo-heavy-site-load-fast-image-optimization-and-edge-caching-that-actually-moved</guid>
<pubDate>Tue, 14 Jul 2026 11:59:43 +0000</pubDate>
</item>
<item>
<title>Your Photos Are Telling Strangers Where You Live: A Developer's Guide to EXIF and Privacy</title>
<link>https://coderlegion.com/21183/your-photos-are-telling-strangers-where-you-live-a-developers-guide-to-exif-and-privacy</link>
<description>&lt;p&gt;Every JPEG your phone or camera produces ships with a hidden passenger: &lt;strong&gt;EXIF metadata&lt;/strong&gt;. Most people never look at it. Most developers never strip it. And that gap has leaked everything from celebrities' home addresses to the exact location of an active-duty soldier's base.&lt;/p&gt;
&lt;p&gt;If you build anything that touches user-uploaded images — a marketplace, a social app, a photo gallery — this is one of those quiet responsibilities that's easy to ignore until it's a headline. Here's what's actually in that metadata, how to read it, and how to strip it in a few lines of code.&lt;/p&gt;
&lt;h3 id=&quot;what-s-hiding-in-a-photo&quot;&gt;What's hiding in a photo&lt;/h3&gt;
&lt;p&gt;EXIF (Exchangeable Image File Format) is a block of key–value data embedded in the file itself. A typical photo carries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GPS coordinates&lt;/strong&gt; — latitude, longitude, sometimes altitude. Precise enough to drop a pin on your front door.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timestamps&lt;/strong&gt; — when the shot was taken, down to the second.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Camera &amp;amp; lens&lt;/strong&gt; — make, model, and often the &lt;strong&gt;camera body's serial number&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Settings&lt;/strong&gt; — ISO, aperture, shutter speed, focal length.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Software&lt;/strong&gt; — what edited the file and when.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Individually, harmless. Together, they're a movement log. Geotagged photos posted over time draw a map of where you live, work, sleep, and travel — and the timestamps tell anyone &lt;em&gt;when&lt;/em&gt; you're not home.&lt;/p&gt;
&lt;h3 id=&quot;reading-exif-so-you-can-see-the-problem-&quot;&gt;Reading EXIF (so you can see the problem)&lt;/h3&gt;
&lt;h4 id=&quot;python-pillow-&quot;&gt;Python (Pillow)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS

img = Image.open(&quot;photo.jpg&quot;)
exif = img.getexif()

for tag_id, value in exif.items():
    tag = TAGS.get(tag_id, tag_id)
    print(f&quot;{tag:25}: {value}&quot;)

# GPS lives in its own sub-block (IFD)
gps_ifd = exif.get_ifd(0x8825)  # GPSInfo
for key, val in gps_ifd.items():
    print(f&quot;{GPSTAGS.get(key, key):25}: {val}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Turning the raw GPS tuples into decimal degrees:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def to_decimal(dms, ref):
    degrees, minutes, seconds = [float(x) for x in dms]
    dec = degrees + minutes / 60 + seconds / 3600
    return -dec if ref in (&quot;S&quot;, &quot;W&quot;) else dec

lat = to_decimal(gps_ifd[2], gps_ifd[1])   # GPSLatitude, GPSLatitudeRef
lon = to_decimal(gps_ifd[4], gps_ifd[3])   # GPSLongitude, GPSLongitudeRef
print(f&quot;https://maps.google.com/?q={lat},{lon}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run that on a geotagged photo and you'll get a clickable map link straight to wherever it was taken. That's the whole point — it's &lt;em&gt;that&lt;/em&gt; easy for anyone else, too.&lt;/p&gt;
&lt;h4 id=&quot;javascript-browser-with-exifr-&quot;&gt;JavaScript (browser, with &lt;code&gt;exifr&lt;/code&gt;)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import exifr from 'exifr';

// Read just the GPS — works on a File from an &amp;lt;input&amp;gt;
const gps = await exifr.gps(file);
console.log(gps?.latitude, gps?.longitude);

// Or pull everything
const data = await exifr.parse(file);
console.log(data.Make, data.Model, data.DateTimeOriginal);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;command-line-exiftool-&quot;&gt;Command line (ExifTool)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;exiftool photo.jpg            # dump everything
exiftool -gps:all photo.jpg   # just the location data
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;stripping-exif-so-you-stop-being-the-problem-&quot;&gt;Stripping EXIF (so you stop being the problem)&lt;/h3&gt;
&lt;h4 id=&quot;one-off-cli&quot;&gt;One-off, CLI&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# ExifTool — removes all metadata
exiftool -all= photo.jpg

# ImageMagick — re-encode without metadata
convert photo.jpg -strip clean.jpg
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;python&quot;&gt;Python&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# piexif: surgical removal, keeps the pixels untouched
import piexif
piexif.remove(&quot;photo.jpg&quot;, &quot;clean.jpg&quot;)

# Pillow: re-save without the exif kwarg
from PIL import Image
img = Image.open(&quot;photo.jpg&quot;)
img.save(&quot;clean.jpg&quot;)  # no exif= passed → metadata dropped
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;server-side-on-upload-node-sharp-&quot;&gt;Server-side on upload (Node, &lt;code&gt;sharp&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;This is the one that matters in production. &lt;strong&gt;Strip metadata as part of your upload pipeline, automatically, for every image.&lt;/strong&gt; Don't trust users to do it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import sharp from 'sharp';

await sharp(inputBuffer)
  .rotate()            // bake in EXIF orientation BEFORE you drop it
  .jpeg({ quality: 82 })
  .toFile('clean.jpg'); // sharp strips metadata by default
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Gotcha:&lt;/strong&gt; EXIF has an &lt;code&gt;Orientation&lt;/code&gt; tag that tells viewers to rotate the image. If you strip metadata without applying that rotation first, photos that looked fine suddenly appear sideways. Always auto-orient (&lt;code&gt;sharp&lt;/code&gt;'s &lt;code&gt;.rotate()&lt;/code&gt; with no args) &lt;strong&gt;before&lt;/strong&gt; discarding metadata.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;client-side-before-it-ever-uploads&quot;&gt;Client-side, before it ever uploads&lt;/h4&gt;
&lt;p&gt;Drawing an image onto a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; and re-exporting it discards EXIF as a side effect:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function stripExif(file) {
  return new Promise((resolve) =&amp;gt; {
    const img = new Image();
    img.onload = () =&amp;gt; {
      const canvas = document.createElement('canvas');
      canvas.width = img.naturalWidth;
      canvas.height = img.naturalHeight;
      canvas.getContext('2d').drawImage(img, 0, 0);
      canvas.toBlob(resolve, 'image/jpeg', 0.9);
    };
    img.src = URL.createObjectURL(file);
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Same orientation caveat applies — handle rotation before you rely on this.)&lt;/p&gt;
&lt;h3 id=&quot;-doesn-t-the-platform-strip-it-for-me-&quot;&gt;&quot;Doesn't the platform strip it for me?&quot;&lt;/h3&gt;
&lt;p&gt;Sometimes. Most large social networks strip EXIF on upload — but the behavior is inconsistent and changes without notice. Direct file delivery (email attachments, cloud links, raw downloads, messaging apps that send &quot;as file&quot;) frequently keeps &lt;strong&gt;everything&lt;/strong&gt; intact. The safe assumption: &lt;strong&gt;if you didn't strip it, it's still there.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There's a real trade-off, too. Photographers and archivists often &lt;em&gt;want&lt;/em&gt; metadata — copyright, camera settings, capture time. So the right rule isn't &quot;always nuke it,&quot; it's &lt;strong&gt;be deliberate&lt;/strong&gt;: strip location and serial numbers from anything public-facing, and keep the rest only where it's genuinely useful and the user consents.&lt;/p&gt;
&lt;h3 id=&quot;where-this-shows-up-in-the-real-world&quot;&gt;Where this shows up in the real world&lt;/h3&gt;
&lt;p&gt;When you run an image-heavy product, metadata handling stops being a curiosity and becomes part of your duty of care. At &lt;a rel=&quot;nofollow&quot; href=&quot;https://fotio.app&quot;&gt;Fotio&lt;/a&gt;, where the whole business is delivering professional photo galleries from shoots across Spain, this is exactly the kind of detail that separates a polished pipeline from a leaky one — high-resolution images that look great, without quietly broadcasting a client's GPS trail. If you're curious how an image-first product is put together end to end, you can poke around at &lt;a rel=&quot;nofollow&quot; href=&quot;https://fotio.app&quot;&gt;fotio.app&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-one-line-takeaway&quot;&gt;The one-line takeaway&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;Treat EXIF like any other untrusted, potentially sensitive payload: &lt;strong&gt;inspect it, strip what's risky, and never assume someone else already handled it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Add a metadata-stripping step to your upload pipeline today. It's a handful of lines, and it closes a privacy hole most apps don't even know they have.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Do you strip EXIF in your projects — client-side, server-side, or not at all? And have you ever found something alarming in a photo's metadata? Share it in the comments.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21183/your-photos-are-telling-strangers-where-you-live-a-developers-guide-to-exif-and-privacy</guid>
<pubDate>Tue, 23 Jun 2026 07:13:01 +0000</pubDate>
</item>
</channel>
</rss>