easy-thumbnailsというPythonライブラリのエラー調査をしたので備忘録
InvalidImageFormatError
が発生したら
easy-thumbnailsでは、画像読み込み時のエラーを丸めてInvalidImageError
というエラーとして送出します。
このエラーが発生した場合は、easy-thumbnailsの画像を読み込む関数source_generators.pil_image()
に直接画像を渡してみると、実際に発生しているエラーのスタックトレースを確認できます。
>>> from easy_thumbnails.source_generators import pil_image >>> with open("<対象画像ファイルのパス>", "rb") as f: >>> pil_image(f)
実際に私が遭遇した例は以下。
- 空の(0バイトの)画像ファイル
- 画像生成処理に不具合があり、空の画像を登録していた
- tEXtチャンクに大量のテキストを含むファイル
- ファイルサイズの大きい画像のテスト用にこちらのサイトで取得したものを登録していた
- サイズのかさ増しのためにtEXtチャンクという領域にデータを詰めているらしく、このサイズが大きすぎてエラーが発生した
- 発生したエラー:
ValueError: Too much memory used in text chunks: 104857293>MAX_TEXT_MEMORY
※なお、SVGファイルの場合は、SVGファイルを読み込む際に使用されるsource_generators.vil_image()
に渡してみると良さそうです。(動作未確認)
easy-thumbnailsについて
easy-thumbnailsは任意のサイズのサムネイルを動的に生成してくれるライブラリです。
DjangoのImageField
を継承したThumbnailerImageField
というフィールドを実装しており、このフィールドに画像を入れておくことで、欲しいサイズのサムネイル画像を簡単に生成することができます。
以下、ドキュメントのサンプルコードから拝借。
settings.py
にて、avatar
というエイリアスで設定を定義:
THUMBNAIL_ALIASES = { '': { 'avatar': {'size': (50, 50), 'crop': True}, }, }
models.py
:
from easy_thumbnails.fields import ThumbnailerImageField class Profile(models.Model): user = models.OneToOneField('auth.User') photo = ThumbnailerImageField(upload_to='photos', blank=True)
テンプレート内でエイリアスavatar
を使用して画像を取得:
{% load thumbnail %} <img src="{{ profile.photo.avatar.url }}" alt="" />
Pythonコード内でエイリアスavatar
にアクセスして画像を取得:
thumb_url = profile.photo['avatar'].url
https://github.com/SmileyChris/easy-thumbnails#example-usage
なお、エイリアスavatar
にアクセスしたタイミングで動的にサムネイル画像が生成される。画像はDjangoに使用しているファイルストレージ(S3など)にキャッシュされ、2度目以降のアクセスはキャッシュから画像が取得されます。
InvalidImageFormatErrorの発生箇所を追ってみる
InvalidImageFormatError
をraiseしているのは、Thumbnailer
というクラスのgenerate_thumbnail()
という、その名の通りサムネイルを生成する関数。Thumbnailer
は、ThumbnailerImageField
でも継承されているサムネイル生成機能を提供するクラス。
この処理の中で、engine.generate_source_image()
という、生成するサムネイルの元画像を生成する関数を読んでおり、この戻り値がNone
の場合にInvalidImageFormatError
をraiseしている。
image = engine.generate_source_image( self, thumbnail_options, self.source_generators, fail_silently=silent_template_exception) if image is None: msg = "The source file does not appear to be an image: '{name}'" raise exceptions.InvalidImageFormatError(msg.format(name=self.name))
https://github.com/SmileyChris/easy-thumbnails/blob/2.8.5/easy_thumbnails/files.py#L387
このgenerate_source_image()
の中では、generator
関数を実行している。デフォルトでは、このgenerator()
としてpil_image()
, vil_image()
の2つの関数が順に実行され、どちらかが正常な画像を返せばその画像が返却される。
これらの処理の中で例外が発生した場合、fail_silently
フラグがFalse
の場合は明示的にreturnしない(None
を返す)挙動となっている。なお、このfail_silently
というフラグは、profile.photo['avatar'].url
のようなエイリアスへのアクセス時のサムネイル生成ではFalse
になっている。
try: image = generator(source, **processor_options) except Exception as e: if not fail_silently: if len(generators) == 1: raise exceptions.append(e) image = None
https://github.com/SmileyChris/easy-thumbnails/blob/2.8.5/easy_thumbnails/engine.py#L114-L121
このgenerator()
として実行されるのが、冒頭に記載したpil_image()
, vil_image()
の関数です。これらの中で画像を開いた際に発生した例外が、easy-thumbnails側でInvalidImageFormatError
として丸められているということらしい。
pil_image()
, vil_image()
の実装:
https://github.com/SmileyChris/easy-thumbnails/blob/2.8.5/easy_thumbnails/source_generators.py
まとめ
というわけで、冒頭に書いた通り、InvalidImageFormatError
が発生した場合は直接pil_image()
, vil_image()
に画像を渡してみると良さそうです。