retry loading gifs as jpg/png on failure
This commit is contained in:
@@ -144,34 +144,50 @@ class BlobEntriesController < ApplicationController
|
||||
).returns(T.nilable([String, String]))
|
||||
end
|
||||
def thumbnail_image_file(blob_file, width, height, file_ext)
|
||||
blob_file_path = blob_file.absolute_file_path
|
||||
|
||||
if file_ext == "gif"
|
||||
Rack::MiniProfiler.step("vips: load gif") do
|
||||
# Use libvips' gifload with n=-1 to load all frames
|
||||
image = Vips::Image.gifload(blob_file.absolute_file_path, n: -1)
|
||||
num_frames = image.get("n-pages")
|
||||
image_width, image_height = image.width, (image.height / num_frames)
|
||||
VipsUtil.try_load_gif(
|
||||
blob_file_path,
|
||||
load_gif: -> do
|
||||
Rack::MiniProfiler.step("vips: load gif") do
|
||||
# Use libvips' gifload with n=-1 to load all frames
|
||||
image = Vips::Image.gifload(blob_file_path, n: -1)
|
||||
num_frames = image.get("n-pages")
|
||||
image_width, image_height = image.width, (image.height / num_frames)
|
||||
|
||||
if width >= image_width && height >= image_height
|
||||
logger.info("gif is already smaller than requested thumbnail size")
|
||||
return [
|
||||
File.read(blob_file.absolute_file_path, mode: "rb"),
|
||||
"image/gif"
|
||||
]
|
||||
end
|
||||
if width >= image_width && height >= image_height
|
||||
logger.info(
|
||||
"gif is already smaller than requested thumbnail size",
|
||||
)
|
||||
return File.binread(blob_file_path), "image/gif"
|
||||
end
|
||||
|
||||
Rack::MiniProfiler.step("vips: thumbnail gif") do
|
||||
image = image.thumbnail_image(width, height: height)
|
||||
image_buffer =
|
||||
image.gifsave_buffer(
|
||||
dither: 1,
|
||||
effort: 1,
|
||||
interframe_maxerror: 16,
|
||||
interpalette_maxerror: 10,
|
||||
interlace: true,
|
||||
)
|
||||
[image_buffer, "image/gif"]
|
||||
end
|
||||
end
|
||||
Rack::MiniProfiler.step("vips: thumbnail gif") do
|
||||
image = image.thumbnail_image(width, height: height)
|
||||
image_buffer =
|
||||
image.gifsave_buffer(
|
||||
dither: 1,
|
||||
effort: 1,
|
||||
interframe_maxerror: 16,
|
||||
interpalette_maxerror: 10,
|
||||
interlace: true,
|
||||
)
|
||||
[image_buffer, "image/gif"]
|
||||
end
|
||||
end
|
||||
end,
|
||||
on_load_failed: ->(detected_content_type) do
|
||||
case detected_content_type
|
||||
when %r{image/png}
|
||||
thumbnail_image_file(blob_file, width, height, "png")
|
||||
when %r{image/jpeg}, %r{image/jpg}
|
||||
thumbnail_image_file(blob_file, width, height, "jpeg")
|
||||
else
|
||||
raise
|
||||
end
|
||||
end,
|
||||
)
|
||||
else
|
||||
# Original static image thumbnailing logic
|
||||
image_buffer =
|
||||
@@ -185,7 +201,10 @@ class BlobEntriesController < ApplicationController
|
||||
|
||||
Rack::MiniProfiler.step("vips: thumbnail image") do
|
||||
logger.info("rendering thumbnail as jpeg")
|
||||
[image_buffer.jpegsave_buffer(interlace: true, Q: 95), "image/jpeg"]
|
||||
[
|
||||
T.let(image_buffer.jpegsave_buffer(interlace: true, Q: 95), String),
|
||||
"image/jpeg",
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,7 +14,13 @@ class LoadedMedia
|
||||
def self.from_file(content_type, media_path)
|
||||
case content_type
|
||||
when %r{image/gif}
|
||||
LoadedMedia::Gif.new(media_path)
|
||||
VipsUtil.try_load_gif(
|
||||
media_path,
|
||||
load_gif: -> { LoadedMedia::Gif.new(media_path) },
|
||||
on_load_failed: ->(detected_content_type) do
|
||||
return from_file(detected_content_type, media_path)
|
||||
end,
|
||||
)
|
||||
when %r{image/jpeg}, %r{image/jpg}, %r{image/png}, %r{image/bmp}
|
||||
LoadedMedia::StaticImage.new(media_path)
|
||||
when %r{video/webm}
|
||||
|
||||
30
app/lib/vips_util.rb
Normal file
30
app/lib/vips_util.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
# typed: strict
|
||||
module VipsUtil
|
||||
extend T::Sig
|
||||
|
||||
sig do
|
||||
type_parameters(:T)
|
||||
.params(
|
||||
media_path: String,
|
||||
load_gif: T.proc.returns(T.nilable(T.type_parameter(:T))),
|
||||
on_load_failed:
|
||||
T
|
||||
.proc
|
||||
.params(detected_content_type: String)
|
||||
.returns(T.nilable(T.type_parameter(:T))),
|
||||
)
|
||||
.returns(T.nilable(T.type_parameter(:T)))
|
||||
end
|
||||
def self.try_load_gif(media_path, load_gif:, on_load_failed:)
|
||||
begin
|
||||
load_gif.call
|
||||
rescue Vips::Error
|
||||
raise unless $!.message.include?("gifload: no frames in GIF")
|
||||
content_type = T.let(`file -i #{media_path}`, T.nilable(String))
|
||||
raise unless $?.success?
|
||||
content_type = content_type&.split(":")&.last&.split(";")&.first&.strip
|
||||
raise unless content_type
|
||||
return on_load_failed.call(content_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -10,6 +10,8 @@ unless defined?(Rack::MiniProfiler)
|
||||
end
|
||||
|
||||
RSpec.describe BlobEntriesController, type: :controller do
|
||||
render_views
|
||||
|
||||
let(:blob_entry) do
|
||||
create(
|
||||
:blob_entry,
|
||||
@@ -92,6 +94,32 @@ RSpec.describe BlobEntriesController, type: :controller do
|
||||
end
|
||||
end
|
||||
|
||||
context "png that has a gif extension and content type" do
|
||||
let(:blob_entry) do
|
||||
create(
|
||||
:blob_entry,
|
||||
content_type: "image/gif",
|
||||
content:
|
||||
File.read(
|
||||
Rails.root.join("test/fixtures/files/images/fa.24326406.gif"),
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
it "renders the image as a gif" do
|
||||
get :show,
|
||||
params: {
|
||||
sha256: sha256_hex,
|
||||
thumb: "content-container",
|
||||
format: "gif",
|
||||
}
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.content_type).to eq("image/jpeg")
|
||||
expect(response.headers["Content-Disposition"]).to include("inline")
|
||||
expect(response.body).to_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it "sets cache headers" do
|
||||
get :show, params: { sha256: sha256_hex }
|
||||
expect(response.headers["Expires"]).to be_present
|
||||
|
||||
@@ -91,5 +91,35 @@ RSpec.describe Domain::PostFile::Thumbnail, type: :model do
|
||||
expect(thumbs_content_container.first.frame).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a .gif file that is actually a .png" do
|
||||
let(:post_file) do
|
||||
create(
|
||||
:domain_post_file,
|
||||
:gif_file,
|
||||
log_entry:
|
||||
create(
|
||||
:http_log_entry,
|
||||
content_type: "image/gif",
|
||||
response:
|
||||
create(
|
||||
:blob_file,
|
||||
content_type: "image/gif",
|
||||
contents:
|
||||
File.binread(
|
||||
Rails.root.join(
|
||||
"test/fixtures/files/images/fa.24326406.gif",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
it "successfully creates a thumbnail from a .gif file that is actually a .png" do
|
||||
thumbnails = described_class.create_for_post_file!(post_file)
|
||||
expect(thumbnails.size).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
BIN
test/fixtures/files/images/fa.24326406.gif
vendored
Normal file
BIN
test/fixtures/files/images/fa.24326406.gif
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
Reference in New Issue
Block a user