<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[ZephyrTran's Dev Blog]]></title><description><![CDATA[ZephyrTran's Dev Blog]]></description><link>https://zephyrtran.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 16 Oct 2026 06:45:24 GMT</lastBuildDate><atom:link href="https://zephyrtran.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Lossless EXIF & GPS stripping in the browser — no library needed]]></title><description><![CDATA[Every photo your phone takes usually embeds EXIF metadata — GPS coordinates, camera model, timestamp. Before sharing a photo you often want that gone. The neat part: you can strip it in the browser, l]]></description><link>https://zephyrtran.hashnode.dev/lossless-exif-gps-stripping-in-the-browser-no-library-needed</link><guid isPermaLink="true">https://zephyrtran.hashnode.dev/lossless-exif-gps-stripping-in-the-browser-no-library-needed</guid><category><![CDATA[webdev]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[privacy]]></category><dc:creator><![CDATA[ZephyrTran]]></dc:creator><pubDate>Tue, 11 Aug 2026 03:57:36 GMT</pubDate><content:encoded><![CDATA[<p>Every photo your phone takes usually embeds <strong>EXIF</strong> metadata — GPS coordinates, camera model, timestamp. Before sharing a photo you often want that gone. The neat part: you can strip it <strong>in the browser, losslessly</strong>, without re-encoding the image.</p>
<h2>The naive way (and why it's not ideal)</h2>
<p>The obvious approach is to draw the image onto a <code>&lt;canvas&gt;</code> and export it again. Canvas output has no metadata — but it <strong>re-encodes</strong> the JPEG, so you lose a little quality every time.</p>
<h2>The better way: drop the metadata segments</h2>
<p>A JPEG is just a sequence of <strong>marker segments</strong>. Each starts with <code>0xFF</code> then a marker byte. EXIF/GPS live in the <strong>APP1</strong> segment (<code>0xFFE1</code>); other <code>APPn</code> and comment (<code>COM</code>) segments hold similar metadata. The actual image data starts at the <strong>SOS</strong> marker (<code>0xFFDA</code>).</p>
<p>So the algorithm is simple: walk the segments from offset 2, and for each marker read its 2-byte length. If the marker is <code>APP1</code>–<code>APP15</code> (<code>0xE1</code>–<code>0xEF</code>) or <code>COM</code> (<code>0xFE</code>), <strong>skip it</strong>; otherwise keep the segment as-is. When you reach <code>SOS</code> (<code>0xDA</code>), copy the entire rest of the file and stop. Concatenate the kept parts and you have a valid JPEG with the metadata gone — <strong>byte-for-byte identical image data, no quality loss</strong>.</p>
<p>That's about 15 lines of plain JavaScript with <code>Uint8Array.subarray</code> — no library required. To <em>show</em> users what's being removed, read the tags first with a small library like <code>exifr</code> (GPS, make/model, date), then strip.</p>
<h2>Why do this client-side?</h2>
<p>The point is <strong>privacy</strong> — you shouldn't have to upload a photo to a server just to remove its location. Everything runs in the browser, so the image never leaves the device. I turned this into a free <a href="https://snapvi.app/remove-metadata">image metadata remover</a>.</p>
<p>Anyone else strip metadata before posting? Curious what edge cases you've hit.</p>
]]></content:encoded></item><item><title><![CDATA[Building a privacy-first image & PDF toolkit that runs 100% in the browser]]></title><description><![CDATA[I kept reaching for online tools to compress an image, convert a PDF, or grab audio from a video — and every one of them uploads your file to a server. For anything remotely personal, that felt wrong.]]></description><link>https://zephyrtran.hashnode.dev/building-a-privacy-first-image-pdf-toolkit-that-runs-100-in-the-browser</link><guid isPermaLink="true">https://zephyrtran.hashnode.dev/building-a-privacy-first-image-pdf-toolkit-that-runs-100-in-the-browser</guid><category><![CDATA[webdev]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[privacy]]></category><dc:creator><![CDATA[ZephyrTran]]></dc:creator><pubDate>Fri, 07 Aug 2026 07:16:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a7584369fff04bf522265d8/db0cb693-a411-4428-b06f-251e3e1e920a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I kept reaching for online tools to compress an image, convert a PDF, or grab audio from a video — and every one of them uploads your file to a server. For anything remotely personal, that felt wrong. So I built <a href="https://snapvi.app">Snapvi</a>: a set of free image / PDF / video tools that run <strong>entirely in the browser</strong>. Your file never leaves your device.</p>
<p>A few things I learned building it:</p>
<ul>
<li><p><code>canvas</code> <strong>+</strong> <code>toBlob</code> covers a surprising amount: format conversion (incl. WebP), resizing, cropping, collage — all client-side.</p>
</li>
<li><p><strong>pdf-lib</strong> and <strong>pdf.js</strong> handle merge / split / compress / page-numbering / PDF↔image without a backend.</p>
</li>
<li><p><strong>Web Audio</strong> <code>decodeAudioData</code> + a small JS MP3 encoder (lamejs) extracts audio from a video to MP3/WAV locally.</p>
</li>
<li><p>For the AI tools (background removal, <a href="https://snapvi.app/image-upscaler">image upscaler</a>, <a href="https://snapvi.app/sharpen-image">sharpener</a>) I run the models in-browser via TensorFlow.js — the image still never uploads. The trade-off is the model download + slower on weak phones, which I gate with size caps and a "fast vs max" toggle.</p>
</li>
<li><p>Lossless EXIF/GPS stripping is just parsing the JPEG APP segments and dropping them — no re-encode. Turned that into a <a href="https://snapvi.app/remove-metadata">metadata remover</a>.</p>
</li>
</ul>
<p>It's a static site on Cloudflare Pages, so hosting is basically free and there's no server to leak anything.</p>
<p>Happy to answer anything about the client-side approach — what would you want to see run in-browser next?</p>
]]></content:encoded></item></channel></rss>