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 のデータURL(data: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 / atob と String.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→ タグ書き換え →dump→insertの流れ。ArrayBuffer ↔ dataURL の変換が要る -
insertは EXIFブロックだけ差し替えでピクセル無変更=画質劣化なし -
set(絶対)と shift(相対)の2モード。shift は
getTime()加算で日またぎをDateに丸投げ - EXIFが無い画像は スキップ(無い日時を後付けする"捏造"はしない)
EXIFは「日時タグが複数ある」「フォーマットが独特」の2点さえ押さえれば、あとは piexifjs が面倒を見てくれる。写真をサーバーに送らずローカルだけで撮影日時を直せるので、人が写った写真でも安心して扱える。
ぱんだツールズ では他にもEXIF情報の削除・画像の圧縮・形式変換・モザイクなど、開発者向けのブラウザ完結ツールを多数公開中。全部無料・登録不要・ファイルはサーバーに送られない。
https://sakutto-panda.com
この記事は Zenn にも同じ内容を投稿しています。