拡張子を信じたい人生であった。
画像ファイルがネットワークを介して飛んでくる。 拡張子を信じて保存をしたいが、拡張子だけ書き換えたり中身が改ざんされているかもしれない。 画像フォーマットごとにあるヘッダを読んで画像を識別したい。
表題には「Pythonで」と書いたが、本質的にバイナリ列と正規表現が扱えるプログラミング言語ならば大差は無いと思われる。 意外とPythonで書かれた記事が見当たらないので一応まとめておこうという気になって書いたのがこのエントリ公開の動機である。
基本方針
大抵の画像フォーマットには仕様が定められている。 つまり、何らかの決まったヘッダーが存在する。 そのヘッダー情報を正規表現で読み取って判別しよう、というのが基本方針である。
JPEG
JPEGは離散コサイン変換を利用した非可逆圧縮の画像フォーマットである。
ISO/IEC 10918-1で仕様が定められ、JISでもJIS X 4301で定まっている。
仕様を読み解くと*1、画像の始まりを示すマーカー(SOI)として\xff\xd8
が定義されている。
よって、JPEGがどうかを判定するには次のようにすればよい。
import re import typing def is_jpg(b: bytes) -> bool: """バイナリの先頭部分からJPEGファイルかどうかを判定する。""" return bool(re.match(b"^\xff\xd8", b[:2]))
PNG
PNGは可逆圧縮の画像フォーマットである。
ISO/IEC 15948で仕様が定められ、PNGファイルの最初の8バイトは\x89\x50\x4e\x47\x0d\x0a\x1a\x0a
であると定められている*2。
よって、PNGがどうかを判定するには次のようにすればよい。
import re import typing def is_png(b: bytes) -> bool: """バイナリの先頭部分からPNGファイルかどうかを判定する。""" return bool(re.match(b"^\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", b[:8]))
GIF
GIFは可逆圧縮の画像フォーマットである。 圧縮アルゴリズムのライセンスをめぐるゴタゴタがあったそうであるが私はその歴史について全く知らない。 私にとってGIFは画像ファイルというよりアニメーションのためのフォーマットである。
https://www.w3.org/Graphics/GIF/spec-gif89a.txtにあるGIFの仕様を眺めると、ヘッダーとして最初はGIF
、つまり\x47\x49\x46
となり、続いてバージョン番号である87a
か89a
が続く。
よって、GIF8
がヘッダにあるかどうかを調べればよい*3。
import re import typing def is_gif(b: bytes) -> bool: """バイナリの先頭部分からGIFファイルかどうかを判定する。""" return bool(re.match(b"^\x47\x49\x46\x38", b[:4]))
PDFはAdobe社が開発したドキュメントフォーマットであるが、画像として送り付けられる場合もある。
ヘッダは%PDF-
なので、それを調べればよい*4。
import re import typing def is_pdf(b: bytes) -> bool: """バイナリの先頭部分からPDFファイルかどうかを判定する。""" return bool(re.match(b"^%PDF", b[:4]))
Windows Bitmap
Windows BitmapはIBMとMicrosoftが開発した画像フォーマットである。
ここ最近、見かけた覚えがないがネットワークの向こう側にいる人間にとっては身近な画像フォーマットかもしれないのだ。
冒頭がBM
つまり\x42\x4d
から始まるのでそれをチェックすればよい。
import re import typing def is_bmp(b: bytes) -> bool: """バイナリの先頭部分からWindows Bitmapファイルかどうかを判定する。""" return bool(re.match(b"^\x42\x4d", b[:2]))
拡張子を信じることができる世界ならば
今までの前提の否定、つまり拡張子と実際のデータが一致している場合は既存のライブラリで間に合う。
それはmimetypes
モジュールである。
19.5. mimetypes — ファイル名を MIME 型へマップする — Python 3.6.1 ドキュメント
最後に
よくあるフォーマットをまとめて、バイナリを入れるといい感じにContent-Typeを返してくれるライブラリが欲しいなあとか思ったり思わなかったりする。