0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

写真のEXIF撮影日時をブラウザで書き換える(時計ズレ・時差補正)。日時タグは3つある問題とpiexifjs

0
Posted at

3行まとめ

  • JPEGのEXIF撮影日時を書き換えるツールをブラウザ完結で作った。カメラの時計ズレ補正・時差のある旅行写真の一括シフトに使える
  • EXIFの撮影日時は DateTimeOriginal / DateTime / DateTimeDigitized の3つある。1つだけ直すとアプリによって新旧の日時が混在して表示される
  • piexifjs は base64 データURL 経由でしか動かない。EXIFの日時フォーマットは YYYY:MM:DD HH:MM:SS(日付の区切りがコロン)という独特な形式

撮影日時がズレた写真、地味に困る。カメラの内蔵時計を合わせ忘れていた、海外旅行で時差を直さず撮った、スキャンした古い写真に正しい日付を入れたい——。EXIFの撮影日時を後から直したいケースは意外と多い。

ぱんだツールズに作ったEXIF撮影日時エディタは、これをブラウザ内でやる。JPEGのバイナリを直接いじる必要があるので piexifjs を使うが、EXIFの日時まわりには「日時タグが複数ある」「フォーマットが独特」といった罠があるのでまとめておく。

EXIFの撮影日時タグは1つではない

まず一番ハマるのがこれ。EXIFには撮影日時を表すタグが3つある。

  • DateTimeOriginal(Exif IFD)— 撮影された瞬間の日時。いわゆる「撮影日時」の主役
  • DateTime(0th IFD)— ファイルの更新日時。0th IFD(基本情報)側にある
  • DateTimeDigitized(Exif IFD)— デジタル化された日時。スキャン等

問題は、アプリやSNSによって、どのタグを「撮影日時」として表示するかがバラバラなこと。あるビューアは DateTimeOriginal を見るが、別のアプリは DateTime を見る。なので DateTimeOriginal だけ書き換えると、別のアプリでは古い日時が表示されたままになり、新旧が混在して「直したはずなのに直ってない」状態になる。

対策はシンプルで、3つとも同じ値に揃える

exifData['0th'][piexif.ImageIFD.DateTime]            = newDateStr
exifData['Exif'][piexif.ExifIFD.DateTimeOriginal]    = newDateStr
exifData['Exif'][piexif.ExifIFD.DateTimeDigitized]   = newDateStr

0th(基本情報)と Exif(拡張情報)という別の IFD(情報ブロック)にまたがっているので、両方のブロックに書き込む。これで、どのタグを参照するアプリでも一貫した日時が表示される。

EXIFの日時フォーマットは YYYY:MM:DD HH:MM:SS

次の罠がフォーマット。EXIFの日時は ISO 8601 でもスラッシュ区切りでもなく、YYYY:MM:DD HH:MM:SS という独特な形式。日付部の区切りまでコロンになっているのが特徴。

function toExifDateStr(date: Date): string {
  const pad2 = (n: number) => String(n).padStart(2, '0')
  const year = date.getFullYear()
  const month = pad2(date.getMonth() + 1)
  const day = pad2(date.getDate())
  // ...
  return `${year}:${month}:${day} ${hours}:${minutes}:${seconds}`
}

読み取り側も同じ形式を前提に正規表現でパースする。2026:06:05 09:30:00 のように、年月日もコロン区切りで来る。

export function parseExifDateStr(exifStr: string): Date | null {
  const m = exifStr.match(/^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/)
  if (!m) return null
  const [, y, mo, d, h, mi, s] = m
  const dt = new Date(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), Number(s))
  return isNaN(dt.getTime()) ? null : dt
}

Date に渡すときは月が0始まりなので Number(mo) - 1 を忘れずに。ここを間違えると1ヶ月ズレる。

piexifjs は base64 データURL 経由でしか動かない

piexifjs の癖として、入出力が ArrayBuffer ではなく base64 のデータURLdata:image/jpeg;base64,...)になっている。File.arrayBuffer() で得たバイナリをそのまま渡せないので、変換が要る。

const dataUrl = arrayBufferToDataUrl(buf, 'image/jpeg')
const exifData = piexif.load(dataUrl)            // EXIF読み取り
// ...日時タグを書き換え...
const exifBytes = piexif.dump(exifData)          // EXIFバイト列に戻す
const newDataUrl = piexif.insert(exifBytes, dataUrl) // 元画像に差し込む
const newBuffer = dataUrlToArrayBuffer(newDataUrl)   // バイナリに戻す
  • piexif.load(dataUrl) で EXIF を { '0th': {...}, 'Exif': {...} } のオブジェクトに展開
  • 日時タグを書き換えたら piexif.dump() で EXIF バイト列に戻し、piexif.insert(exifBytes, dataUrl)元画像に差し込む(画像ピクセルはそのまま、EXIFブロックだけ差し替え)
  • 結果もデータURLなので dataUrlToArrayBuffer でバイナリに戻す

ArrayBuffer ↔ データURL の変換は btoa / atobString.fromCharCode のループで書く。バイナリを1バイトずつ文字に積む素朴な実装だが、これが piexifjs と Web の File API を繋ぐのに必要。

function arrayBufferToDataUrl(buf: ArrayBuffer, mimeType: string): string {
  const bytes = new Uint8Array(buf)
  let binary = ''
  for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i])
  return `data:${mimeType};base64,${btoa(binary)}`
}

ポイントは、ピクセルデータには一切触らないこと。piexif.insert は EXIF メタデータブロックだけを差し替えるので、画像そのものは再エンコードされない=画質劣化なし。日時を直すだけで写真がボケる、みたいなことは起きない。

「絶対指定」と「相対シフト」の2モード

日時の直し方には2通り欲しい。

  • set(絶対指定): 「この写真の日時を 2026/6/5 9:30 にする」と日時を指定して上書き
  • shift(相対シフト): 「全部の写真を +3時間ずらす」のように、既存の日時を起点に増減

特に shift が効くのが、カメラの時計が一定量ズレていた / 時差を直し忘れたケース。撮影の前後関係は正しいので、絶対値で1枚ずつ直すより「まとめて+9時間」みたいにずらすほうが速い。

shift は、既存の日時をパースして起点にし、日・時・分をミリ秒に直して加算する。

const baseDate = parseExifDateStr(beforeDateStr)   // 既存の日時を起点に
const shiftMs =
  days * 24 * 60 * 60 * 1000 +
  hours * 60 * 60 * 1000 +
  minutes * 60 * 1000
const newDate = new Date(baseDate.getTime() + shiftMs)  // getTimeに加算

getTime()(エポックミリ秒)への加算なら、時刻の繰り上がり・日またぎ・月またぎを Date が全部面倒を見てくれる。+15時間で日付が変わっても勝手に翌日になる。

EXIFが無い画像はスキップする(捏造しない)

設計上の判断として、EXIFの日時情報が無いJPEGは書き換えをスキップして、元バッファをそのまま返している。

const hasExif = original != null || dateTime != null || digitized != null
if (!hasExif) {
  return { buffer: buf, beforeDateStr: null, afterDateStr: '', skipped: true }
}

EXIFが無い画像に新規で日時を「付与」することもできるが、このツールは既存の撮影日時の補正に用途を絞っている。元々日時を持たない画像(スクショやWeb保存画像)に、それっぽい撮影日時を後付けするのは「捏造」に近い。補正と捏造は別物なので、EXIFが無ければ何もせずスキップ、という線引きにした。読み取り時も、3タグのどれか1つでもあれば hasExif とみなす。

まとめ

EXIF撮影日時をブラウザで書き換えるときの要点。

  • 撮影日時タグは DateTimeOriginal / DateTime / DateTimeDigitized の3つ。アプリで参照先が違うので 3つとも同じ値に揃える(0th と Exif の両IFDにまたがる)
  • EXIFの日時は YYYY:MM:DD HH:MM:SS(日付の区切りもコロン)。Date 変換時は月0始まりに注意
  • piexifjs は base64 データURL 経由load → タグ書き換え → dumpinsert の流れ。ArrayBuffer ↔ dataURL の変換が要る
  • insertEXIFブロックだけ差し替えでピクセル無変更=画質劣化なし
  • set(絶対)と shift(相対)の2モード。shift は getTime() 加算で日またぎを Date に丸投げ
  • EXIFが無い画像は スキップ(無い日時を後付けする"捏造"はしない)

EXIFは「日時タグが複数ある」「フォーマットが独特」の2点さえ押さえれば、あとは piexifjs が面倒を見てくれる。写真をサーバーに送らずローカルだけで撮影日時を直せるので、人が写った写真でも安心して扱える。

ぱんだツールズ では他にもEXIF情報の削除・画像の圧縮・形式変換・モザイクなど、開発者向けのブラウザ完結ツールを多数公開中。全部無料・登録不要・ファイルはサーバーに送られない。
https://sakutto-panda.com


この記事は Zenn にも同じ内容を投稿しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?