Python で画像ファイルの破損をチェックするコード例を書きました。
Python から ImageMagick を呼び出して、画像データ(jpeg, png, gif など)の破損の有無を調べました。
ImageMagick は、小さな異常でも、どんどん検出してくれました。
たとえば、壊れているのに「画像として読み込めてしまうようなデータ」でも、警告やエラーを出してくれました。
壊れた画像のチェックを自動化することができて、とても便利でした。
あと、Web や外部接続機器から受信したデータをチェックして、画像の再取得が必要かどうかを判断するのにも役立ちました。
再取得しても同じデータが返ってきたときは、「サーバー上のデータが壊れている可能性が高い」と判断できます。
(もちろん、ImageMagick で警告やエラーが出たからと言って、必ずしも画像に問題があるとか、画像がまったく使用できない、というわけではなかったです。)
ほかにも、壊れた画像を分析プログラムに渡さないようにするためのチェックに、とても有用でした。
(関連記事)ファイルの「破損チェック」に関する記事です。
⇒ 【Python】画像の破損をチェックするコード例【Pillow】(簡易的で高速)
⇒ 【Python】PDF の破損をチェックするコード例【QPDF】
ImageMagick マニュアル
コード例で使用した ImageMagick 機能のマニュアルの場所です。
自分はポータブル版の ImageMagick-7.1.0-portable-Q16-x64.zip を使用しました。
(ImageMagick) Windows Binary (Download)
(ImageMagick) -regard-warnings
(Basic Usage)
(ImageMagick) -regard-warnings
(Annotated List of Command-line Options)
(ImageMagick) -identify
(Annotated List of Command-line Options)
(ImageMagick) Image Format and Characteristics (magick identify -regard-warnings
)
(ImageMagick) MagickCore: resource.c Source File (MAGICK_TEMPORARY_PATH, MAGICK_TMPDIR, TMPDIR, TMP, TEMP, …) 一時フォルダの場所(環境変数)
(ImageMagick) Resources (configure.xml, policy.xml) (MAGICK_TEMPORARY_PATH)
Python マニュアル
コード例で使用した Python 機能のマニュアルの場所です。
(Python) class pathlib.Path(*pathsegments)
(Python) Path.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)
(Python) re.match(pattern, string, flags=0)
(Python) class subprocess.CompletedProcess
(Python) bytes.decode(encoding="utf-8", errors="strict")
(Python) 標準エンコーディング ('ascii', 'utf-8', ...
)
(Python) エラーハンドラ ('strict', 'ignore', 'replace', 'backslashreplace', ...
)
(Python) フォーマット済み文字列リテラル (f-string, f''
)
(Python) print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
コード例
画像ファイルの破損をチェックする Python コード例です。
フォルダの中のファイルを列挙して、破損の有無をチェックします。
"""
ImageMagick の identify.exe を使用して、
画像データ (jpg, png, gif など) の
破損をチェックする Python コード例です。
[A] 標準入力 (stdin) からでも
[B] 画像のファイルパス からでも
チェックすることができました。
"""
from pathlib import Path
import re
import subprocess
def main():
"""メイン関数です。"""
print('start\n')
# (1/5) 画像ファイルの場所を決めます。
src_dir = Path(r'F:\project\sample_images')
print(f'(src_dir) {src_dir}')
# (2/5) ImageMagick の設定を決めます。
# ImageMagick の identify.exe の場所です。
exe_file = r'F:\project\tools\ImageMagick\identify.exe'
print(f'(exe_file) {exe_file}')
# ImageMagick が一時ファイルを書き込む場所です。(省略可能)
env_dict = {'MAGICK_TEMPORARY_PATH': r'F:\project\image_magick_temp'}
print(f"(MAGICK_TEMPORARY_PATH) {env_dict['MAGICK_TEMPORARY_PATH']}")
# 省略した場合は、例えば
# 'C:\Users\%USERNAME%\AppData\Local\Temp'
# のフォルダを ImageMagick が使用していました。
# (3/5) フォルダの中のファイルを列挙します。
# (p の型の例) <class 'pathlib.WindowsPath'>
for p in src_dir.glob('*'):
# ファイルでなければ、スキップします。
if not p.is_file():
continue
# (4/5) 拡張子が画像でなければ、スキップします。
# p.suffix: パスの拡張子の部分。
# flags=re.IGNORECASE: 大文字小文字を無視。
if not re.match(r'^\.(?:jpg|png|gif)$', p.suffix, flags=re.IGNORECASE):
continue
# (情報) Web や外部機器から取得した画像を ImageMagick に渡す場合は、
# ImageMagick に同梱されている policy.xml で、
# 扱う画像形式やメモリの使用量を制限すると、少し安全になります。
# (policy.xml の場所の例) 'F:\project\tools\ImageMagick\policy.xml'
# True: 標準入力 (stdin) False: 画像ファイルパス
if True:
########################################################
# (5/5 [A]) 画像データをチェックします。標準入力(stdin)
########################################################
# [A] 画像を『標準入力 (stdin)』から読み込む場合です。
# 画像のバイナリーデータを取得します。
# 各種外部機器から取得したデータでも OK です。
# requests.get() で取得したデータ (r.content) でも OK です。
# zipfile.ZipFile() で zip から取得したデータでも OK です。
with p.open('rb') as f:
bin_data = f.read()
# (bin_data の例) <class 'bytes'>
# b'GIF89a\x00\n\xa0\x05\xf7\x00\x00 ...'
# 標準入力に画像のバイナリーデータを渡すコマンドを書きます。
cmd = (exe_file, '-regard-warnings', '-')
# (cmd の例)
# (
# r'F:\project\tools\ImageMagick\identify.exe',
# '-regard-warnings',
# '-',
# )
# 画像をチェックします。バイナリーデータを
# 標準入力 (stdin) に渡すときは、input を使用します。
cp = subprocess.run(
cmd, # コマンドのリスト (list) かタプル (tuple)
input=bin_data, # 画像のバイナリーデータ
stdout=subprocess.PIPE, # 標準出力
stderr=subprocess.PIPE, # 標準エラー
env=env_dict, # 環境変数 (省略可能)
)
else:
########################################################
# (5/5 [B]) 画像データをチェックします。ファイルパス
########################################################
# [B] 画像を『ファイルパス』から読み込む場合です。
# 画像のファイルパスを渡してチェックするコマンドを書きます。
cmd = (exe_file, '-regard-warnings', str(p))
# (cmd の例)
# (
# r'F:\project\tools\ImageMagick\identify.exe',
# '-regard-warnings',
# r'F:\project\sample_images\image_g1.gif',
# )
# 画像をチェックします。
cp = subprocess.run(
cmd, # コマンドのリスト (list) かタプル (tuple)
stdout=subprocess.PIPE, # 標準出力
stderr=subprocess.PIPE, # 標準エラー
# env=env_dict, # 環境変数 (省略可能)
)
# (デバッグ) 結果を表示します。
# cp は CompletedProcess の略です。
# <class 'subprocess.CompletedProcess'>
if cp.returncode == 0:
# エラーは検出できなかった。
print(f'\nok {p.name} (returncode) {cp.returncode}')
# # 標準出力 (stdout) の内容を表示します。
# print(f'(stdout) {cp.stdout}')
# # 標準エラー (stderr) の内容を表示します。
# print(f'(stderr) {cp.stderr}')
else:
# 何らかのエラーがあった。
print(f'\nNG {p.name} (returncode) {cp.returncode}')
# # 標準出力 (stdout) の内容を表示します。
# print(f'(stdout) {cp.stdout}')
# 標準エラー (stderr) の内容を表示します。
print(f'(stderr) {cp.stderr}')
# # UnicodeDecodeError を回避しつつ、文字列にデコードしたいときは、
# # たとえば以下のように書くとうまくいきました。
# print(f'(stderr) {cp.stderr.decode("utf-8", errors="backslashreplace")}')
print('\nend')
return
if __name__ == '__main__':
main()
zip ファイルの中にある画像データは、zipfile モジュールで読み込むことができました。
実行結果
画像の破損チェックの実行結果です。
[A] 画像を標準入力 (stdin) から渡した場合の結果です。
start
(src_dir) F:\project\sample_images
(exe_file) F:\project\tools\ImageMagick\identify.exe
(MAGICK_TEMPORARY_PATH) F:\project\image_magick_temp
ok image_g1.gif (returncode) 0
NG image_g2.gif (returncode) 1
(stderr) b"identify.exe: CorruptImage
`F:/project/image_magick_temp/magick-ImaiPQO2O8XiPdxngSZfYG7b_ziYa8nG'
@ error/gif.c/DecodeImage/506.\r\n
identify.exe: CorruptImage
`F:/project/image_magick_temp/magick-ImaiPQO2O8XiPdxngSZfYG7b_ziYa8nG'
@ error/gif.c/ReadGIFImage/1371.\r\n"
ok image_j1.jpg (returncode) 0
NG image_j2.jpg (returncode) 1
(stderr) b"identify.exe: Premature end of JPEG file
`F:/project/image_magick_temp/magick-B9l5a-CcTSAZX37mdIJ-zbTRG6d7WWi5'
@ warning/jpeg.c/JPEGWarningHandler/402.\r\n
identify.exe: Corrupt JPEG data: premature end of data segment
`F:/project/image_magick_temp/magick-B9l5a-CcTSAZX37mdIJ-zbTRG6d7WWi5'
@ warning/jpeg.c/JPEGWarningHandler/402.\r\n"
ok image_p1.png (returncode) 0
NG image_p2.png (returncode) 1
(stderr) b"identify.exe: Expected 5715 bytes; found 5573 bytes
`F:/project/image_magick_temp/magick-PHgWO8gZbn-bBwCg9w8HiNWJqW9gMITF'
@ warning/png.c/MagickPNGWarningHandler/1746.\r\n
identify.exe: Read Exception
`F:/project/image_magick_temp/magick-PHgWO8gZbn-bBwCg9w8HiNWJqW9gMITF'
@ error/png.c/MagickPNGErrorHandler/1713.\r\n"
end
gif と jpg と png で、それぞれ「正常な画像」と「データの後半をわざと削った画像」を用意しましたが、壊れた画像を判定できていました。
成功です。