Python における Exif ヘッダー削除・除去の実装例

本年もどうぞよろしくお願いします。ほんとはこんなの書いてる場合じゃないんだけど、軽く書いてみる。

Exif といえば言わずと知れた Jpeg 画像に封入できるメタデータの一種ですが、個人使用では非常に便利なものの、昨今流行の画像共有サイトを中心に Exif データによる情報流出がいろいろ話題になっていたりします。カメラ種別や撮影日時はまだ良いものの、GPS位置情報までもが封入されていたりするとなかなか厄介で、いっそのこと Exif データだけ丸ごと削除してしまう方が何かと無難で、特に API を叩いて画像をアップロードするような場合、専用ツールによるヘッダ除去は非常に面倒くさいため、スクリプト内部で処理してしまうと楽です。

というわけで、 Python による Exif ヘッダを除去する実装例。

Exif の仕様については既にいろんなサイトで紹介されているので割愛しますが、基本的には

FFD8FFE1D84545786966000049492A00...
Start of Image(SOI)APP1 MarkerExif Header SizeEXIF \0 \0TIFF Header...

という形になっています(参考: http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif.html#ExifMarker)。赤で示した領域は固定値のはず。なので、SOI部分の有無を見て JPEG イメージであることを確認し、APP1マーカーの有無で Exif ヘッダの有無を確認するというのが手っ取り早く*1、最初4バイトを見て確認を取ってからExifヘッダのサイズを取得し、ファイルポインタを移動させて実データを抜き出す、という手法をとれば、簡単にExifヘッダを除去することが出来ます。

上記の例ではヘッダサイズは 0xD8 0x45 となっていますが、これは 0xD845 (=55365)となります。

infile = open(InFileName, "rb")
buf = infile.read(4)
if buf == '\xFF\xD8\xFF\xE1':
  size = ord(infile.read(1))*2**8 + ord(infile.read(1))
  infile.seek(size - 2, 1)
  c = '\xFF\xD8' + infile.read()
else:
  infile.flush()
  infile.seek()
  c = infile.read()

infile.close()
outfile = open(OutFileName, "wb")
outfile.write(c)
outfile.close()

この例では一度画像ファイルの全データを変数に読み込んでいます。巨大なデータだと色々と問題が起こる可能性があるので注意してください。

実データを抜き取る際、実データ部の開始点は FF DB になってたりするので、最初にSOIヘッダを付加しています(c = '\xFF\xD8' + infile.read() の部分)。SOIヘッダがなければ多くのビューアーソフトでは壊れたファイルと見なされてしまうので、注意が必要です。

ソースはhttp://beta.bloglines.com/b/preview?siteid=11745014を参考にしました。

*1:本当は後半の EXIF\0\0も見た方が良いけど、結局同じことなので。