ホンモノのエンジニアになりたい

ITやビジネス、テクノロジーの話を中心とした雑記ブログです。

【Python】imghdr.what()でJPEGファイルが判定できない

タイトルの通り、imghdr.what()で画像ファイルのフォーマットを調べたら一部のJPEGファイルが上手く検出できない事象が発生しました。

幸いなことに既に原因と解決策を公開してくれている人がおりました。

qiita.com

このエントリでは私なりにもう半歩ほど踏み込んで事象を整理してみようと思います。

環境
CentOS Linux release 7.4.1708 (Core)
Python 3.6.5


何で検出できないか?

上で紹介したQiita記事に簡潔にまとめられていますが、ソースコードではこうなっていると。

def test_jpeg(h, f):
    """JPEG data in JFIF or Exif format"""
    if h[6:10] in (b'JFIF', b'Exif'):
        return 'jpeg'


ファイル先頭の7~10バイトのところに'JFIF'、'Exif'という文字列があればJpegと判定する仕様になっています。 WikipediaJPEGを調べてみると、以下のように書いてあります。

マジックナンバー \xff\xd8

標準では、特定の種類の画像の正式なフォーマットがなく、JFIF形式(マジックナンバー上は、6バイト目から始まる形式部分にJFIFと記されているもの)が事実上の標準ファイルフォーマットとなっている。 JPEG - Wikipedia


マジックナンバーは「FFD8」であるが、JPEGの事実上の標準はJFIFであり、それを表すのは6バイト目からの「JFIF」文字列(を表すバイナリ部分)であると。
手元のJPEGファイルをバイナリエディタで見てみるとこうなっている。

①普通のJPEG
f:id:kwnflog:20181230171758p:plain

②問題のJPEG f:id:kwnflog:20181230171844p:plain

Exif形式 f:id:kwnflog:20181230171904p:plain

問題のJPEGではマジックナンバーの直後に量子化テーブル定義という、あのー、あれだ、いわゆるあれが入っている(た、たぶん画像データ部分)。 本来はJFIFだと「FFE0」、Exifだと「FFE1」が来ないといけないっぽい。

ちなみにLinuxのfileコマンドでは以下のように表示されます。fileコマンドは偉大。

$ file JFIF-ari.jpg
JFIF-ari.jpg: JPEG image data, JFIF standard 1.01

$ file JFIF-nasi.jpg
JFIF-nasi.jpg: JPEG image data

$ file Exif.JPG
Exif.JPG: JPEG image data, EXIF standard 2.2


まとめると、どんな理由かはわかりませんがJPEGファイルのヘッダに事実上の標準とされているJFIF情報が存在しなく、そこを読んでファイル形式を判定するimghdrコマンドも機能しないというのがうまく判定できない理由となるようです。


JFIFExif無しファイルが来た時にどーするか?

冒頭でリンクを貼ったQiitaの人と同じような対応をしました。先頭の2バイトがFFD8ならJPEGと判定するロジックです。(ロジックというレベルじゃないですね)

f=open('/path/to/file','rb')
f_head = f.read()[:2]
f.close()
if f_head == b'\xff\xd8':
    print('JPEGです')


これをimghdr.what()に引っかからなかったファイルに実行してJPEG判定をする。

たぶんもっとエレガントな書き方、対応法があるんでしょうけど、目的である画像ファイルフォーマットの判定は出来たのでこれでよしとします。

おわり