ai-lab.org
ExifPeel

ExifPeel ができるまで — ブラウザだけで画像のEXIFを確認・剥離する設計

exifr (EXIF パーサ) と piexifjs (JPEG EXIF 操作) と Canvas API をブラウザ内で動かし、 GPS / 撮影機種 / 編集ソフト履歴を覗いて剥がすツールの設計記録。 JPEG ロスレス strip と PNG/WebP の Canvas 再エンコード戦略、 GPS 警告 UI まで解説します。

判断: 改善

ExifPeel ができるまで — ブラウザだけで画像の中身を覗いて剥がす設計

ExifPeel は、画像のメタデータ (EXIF / GPS / 撮影情報) を サーバーに送らずに 確認・剥離する Webサービスです。 exifr (パース) + piexifjs (JPEG ロスレス strip) + Canvas API (他形式の再エンコード strip) をブラウザ内だけで組み合わせ、 写真は一歩もデバイス外に出ません。 メディア処理ラボ 10本柱に続く、 11本目。 「処理」 でも 「変換」 でも 「転送」 でもなく、 「インスペクション + プライバシー保全」 という新カテゴリで設計しました。

なぜこの形にしたか

iPhone や Android で撮った写真の EXIF には、 多くの場合:

  • 撮影場所の 緯度経度 (自宅・職場・行動圏の即バレ)
  • 撮影機種 / レンズ / シリアル番号
  • 編集ソフトの履歴 (Photoshop / Lightroom / カメラの内蔵ファームウェア)
  • ホスト端末名 ("Taro's iPhone" 等)

が埋め込まれています。 SNS にそのまま投稿してプライバシー事故というのは何年も繰り返されている話で、 検査・剥離ツールへの日常的な需要はあります。 ただ既存の Web ツールは画像をいったんサーバーへ POST する作りが多く、 「個人写真の GPS を抜くためにまずサーバーに渡す」 という前提自体に違和感がありました。

exifr と piexifjs は pure JS / WASM 不要で、 ブラウザ完結で組み合わせれば一切アップロードなしのメタデータ検査 + 剥離が成立する。 ラボ thesis にそのまま乗ります。

visual direction

フォレンジック・クロム × ウラニウム・グロウ。

  • 深いスレート (#0c1014) を背景に、 ボーンホワイト (#e2e8f0) のテキスト
  • ウラニウム・グロウ (lime-200 #d9f99d / lime-300 #bef264) を主アクセントに、 鑑識ルームのワークライトのような蛍光
  • GPS 検出時はハザード・マゼンタ (#ec4899) で警告を強調
  • 既存10本 (mint / amber / violet / cobalt / lime / gold / rose / indigo / emerald / vermilion) と完全別軸の cool monochrome 系

「鑑識ルームで写真のレイヤーを 1枚ずつ剥がして調べる」 メタファー。

実装の見どころ

1. exifr で全フィールドを引き出す

exifr.parse(file, { gps, tiff, exif, iptc, xmp, ifd0, ifd1, ihdr, jfif, translateKeys, translateValues, reviveValues, mergeOutput }) を 1発で呼んで、 全 segment のフィールドを 1枚のフラットなオブジェクトとして取り出します。 GPS 経緯度は exifr が自動的に latitude / longitude の数値プロパティに正規化してくれるので、 後段の判定が楽。

const raw = await exifr.parse(file, {
  gps: true, tiff: true, ifd0: true, ifd1: true, exif: true,
  iptc: true, xmp: true, mergeOutput: true,
})

撮影日時のような DateTimeOriginalDate オブジェクトとして返ってくるので、 表示時には ISO 化して整形しています。

2. グループ分けして表示

EXIF は 100フィールド近く返ることがあるので、 そのまま全部出すと読めません。 4 つの意味ブロックに分割:

  • GPS (緯度経度・高度) — 別パネル + ハザード色 + OpenStreetMap リンク
  • カメラ / レンズ (Make / Model / LensModel / シリアル)
  • 撮影設定 (DateTimeOriginal / ExposureTime / FNumber / ISO / FocalLength 等)
  • ソフトウェア / 端末 (Software / HostComputer)
  • 作成者 / 著作権 (Artist / Copyright / UserComment / XPAuthor)

各グループはホワイトリストで拾うので、 「読めるけど意味の解釈に専門知識が要る」 ような bytes-level フィールドは混ぜずに済みます。

3. JPEG はロスレスで EXIF だけ剥がす

JPEG の EXIF は APP1 セグメントに格納されているので、 piexifjs の remove() で マーカーごと抜くだけで完了。 画素データ自体は触らないので 画質劣化ゼロ

const dataUrl = await fileToDataUrl(file)
const stripped = piexif.remove(dataUrl)   // APP1 marker を除外
const blob = dataUrlToBlob(stripped)

4. PNG / WebP / HEIC は Canvas で再エンコード

これらの形式は EXIF が tEXt / iTXt / EXIF chunk のように混ざっていて、 ピンポイントで抜く実装は重い。 v1 では Canvas API で createImageBitmap → drawImage → toBlob を通して再描画してメタデータを全消去する戦略を採りました。 透過は維持。 画質は 95% で再エンコード (PNG は lossless なので品質パラメータは無視される)。

const bitmap = await createImageBitmap(file)
const canvas = document.createElement("canvas")
canvas.width = bitmap.width
canvas.height = bitmap.height
canvas.getContext("2d")!.drawImage(bitmap, 0, 0)
const blob = await new Promise<Blob>((res) => canvas.toBlob(res, mime, 0.95))

createImageBitmap() は WebP / PNG / JPEG / HEIC をブラウザ標準でデコードしてくれるので、 ライブラリ追加なしで形式横断のクリーン化が回ります。

5. GPS は別格で扱う

GPS が見つかったときは:

  • マゼンタ枠の専用パネルで強調
  • 緯度経度・高度を数値表示
  • OpenStreetMap への mlat / mlon リンク (新規タブで開く)
  • 「これが画像と一緒に公開されると位置情報が漏れる」 という直接的な警告文

UX 的に 「ここが一番危ない」 を視覚的に最優先で伝える設計にしています。

6. バッチ strip

「剥がして保存」 タブは複数ファイル同時投入対応。 ループで stripMetadata() を呼び、 進捗バーで何枚目を処理しているか表示。 各ファイルに個別の download ボタンを並べて、 必要な分だけ書き出せる作り。

苦労したところ

  • exifr の dynamic import: 同梱の default export と named parse 関数がどちらでも呼べるパスを両方フォールバックしないと、 環境によって読めないケースがあった。 exifr.parse ?? exifr.default?.parse で吸収。
  • piexifjs の型定義: @types/piexifjs は 1.0 系で実体は古い。 import * as piexifMod した後、 default か module 直接かを実行時に分岐する形で型を当てた。
  • HEIC → strip: createImageBitmap() は HEIC を Safari / モバイル Chrome ではデコードできることが多いが、 デスクトップ Chrome は OS の HEIC コーデックに依存する。 失敗時はエラーリストに残してユーザーに見せ、 他ファイルの処理を止めない方針。

今後の拡張

  • 選択削除: GPS だけ剥がして著作権は残す等のタグ単位コントロール
  • RAW / DNG 対応: カメラRAW のメタデータも見られるように
  • PNG textChunk / iTXt の細かい解析: 著作権 / コメント / プロファイル名等
  • バッチ zip 出力: 大量画像をまとめて1ファイルで書き出す
  • GPS のインラインマップ: OpenStreetMap タイルを直接描画
  • SNS-safe プリセット: 「Twitter 推奨」 「Instagram 推奨」 で最低限剥がす要素を1クリックで設定

このサービスから言える事

ラボの 11本柱:

  • voice-scribe / clip-cast / bg-snap / text-pluck / pdf-anvil / pixel-lift / pic-flip / mind-cell / beam-drop / word-warp / exif-peel

カテゴリの数:

  • 認識 (voice-scribe / text-pluck / bg-snap)
  • 変換 (clip-cast / pdf-anvil / pixel-lift / pic-flip / word-warp)
  • 生成 (mind-cell)
  • 転送 (beam-drop)
  • インスペクション / プライバシー (exif-peel) ← 新

「ブラウザ完結で何ができるか」 の射程を、 画像の中身を覗いて剥がすところまで拡げた格好。 「写真を SNS に上げる前にとりあえずここに通す」 の習慣化を狙えると、 ラボの中で beam-drop / pic-flip / bg-snap への動線も自然に作れます。

Next Action

読んだあとに、そのまま使って確かめる。

この開発ログは ExifPeel をどう育てているかの記録です。読んだらそのままサービス本体へ戻って、価値を確かめられるようにしています。

Keep Reading, Keep Trying

読むだけで終わらせない主力サービス

改善ログで方向を理解したあとに、そのまま使って価値を確かめやすい主力を並べています。

hack-sim主力

HackSim

/hack-sim

1分で始められるSNS向けハッキング体験シミュレーター

· エンタメ· 公開中· 49 pv
prompt-stock主力

PromptStock

/prompt-stock

使うだけで終わらせず、改善して育てるためのプロンプト実用品ストック

· 改善· 改善して伸ばす· 34 pv

More Logs

次に読みたい改善ログ

記事一覧へ