ActiveStorage カスタム アラライザーで「Vips::Error Exception: VipsJpeg: out of order read at line 1440」を取得する
概要
私のカスタムアナライザー
class BlurhashAnalyzer < ActiveStorage::Analyzer::ImageAnalyzer::Vips
def metadata
read_image do |image|
if rotated_image?(image)
{ width: image.height, height: image.width }
else
{ width: image.width, height: image.height }
end.merge blurhash(image)
end
end
private
def blurhash(vips_image)
# Create a thumbnail first, otherwise the Blurhash encoding is very slow
byebug
processed_image = ImageProcessing::Vips.source(vips_image).resize_and_pad(200, 200).call
thumbnail = ::Vips::Image.new_from_file processed_image.path
{
blurhash: Blurhash.encode(
thumbnail.width,
thumbnail.height,
::Vips::Region.new(thumbnail).fetch(0, 0, thumbnail.width, thumbnail.height).unpack('C*')
)
}
rescue StandardError => e
raise e if Rails.env.development?
Rails.logger.error "Error while encoding Blurhash: #{e}"
{}
end
end
Blurhash メソッドの例外
(process:29640): VIPS-WARNING **: 17:25:01.323: error in tile 0 x 120
*** Vips::Error Exception: VipsJpeg: out of order read at line 1440
ただし、同じファイルで新しい Vips::Image を作成すると、機能します。
(byebug) ImageProcessing::Vips.source(::Vips::Image.new_from_file vips_image.filename).resize_and_pad(200, 200).call
#<Tempfile:/var/folders/_j/m395qb5d2yscnx89dswmrxgm0000gn/T/image_processing20220624-29640-aflgbs.jpg>
(byebug) ImageProcessing::Vips.source(::Vips::Image.new_from_file vips_image.filename, access: :sequential).resize_and_pad(200, 200).call
#<Tempfile:/var/folders/_j/m395qb5d2yscnx89dswmrxgm0000gn/T/image_processing20220624-29640-eflx0v.jpg>
Rails 7.0.3 Analyzer::ImageAnalyzer::Vips のソースコードを確認しました。
...
def read_image
download_blob_to_tempfile do |file|
require "ruby-vips"
image = instrument("vips") do
::Vips::Image.new_from_file(file.path, access: :sequential)
end
上で行ったのと同じ Vips::Image の作成方法ですが、直接使用すると例外が発生します。
この問題が https://github.com/libvips/pyvips/issues/96 に関連していることはわかっていますが、ここではローテーションしませんでした。
解決策
これは、イメージをストリーミング モードで開いた後、そのイメージを複数回読み取ろうとした場合に発生します。
ドキュメントには背景を説明した章があります。
https://translate.google.com/translate?hl=ja&sl=en&tl=ja&u=https://www.libvips.org/API/current/How-it-opens-files.md.html
たとえば、次のようにファイルを処理するとします。
image = Vips::Image.new_from_file "something.jpg", access: :sequential
image = 255 - image
image.write_to_file "something-inverted.jpg"
libvips は、最後の write_to_file まですべての計算を遅らせてから、イメージをストリーミングします。デコード、処理、再エンコードはすべて同時に並行して実行され、画像のごく一部のみがメモリに保持されます。
欠点は、ワンショット処理しかできないことです。これは、たとえば次のように失敗します。
image = Vips::Image.new_from_file "something.jpg", access: :sequential
image = 255 - image
image.write_to_file "something-inverted.jpg"
avg = image.avg()
画像が読み取られ、処理され、破棄され、ピクセルが残っていないため、パイプラインの実行後は平均を計算できません。
デフォルトのランダム アクセス モードを使用すると、正常に動作します。
image = Vips::Image.new_from_file "something.jpg"
image = 255 - image
image.write_to_file "something-inverted.jpg"
avg = image.avg()
JPG ファイルはメモリ配列にデコードされ、処理と保存のみが並行して実行され、ピクセルは後で平均を計算するためにまだ存在します。
あなたの場合、画像はシーケンシャルモードで開かれていますが、ピクセルを2回読み取ろうとしています。オリジナルをランダム アクセス モードで開くか、image = image.copy_memory() を実行してメモリ内に再利用可能なコピーを作成する必要があります。