FIX: Use ImageMagick to detect animated images (#11702)

FIX: Use ImageMagick to detect animated images (#11702)

This is a fallback when FastImage cannot be used (animated WEBP images).

diff --git a/lib/upload_creator.rb b/lib/upload_creator.rb
index 9c2f4ab..3d8b0e8 100644
--- a/lib/upload_creator.rb
+++ b/lib/upload_creator.rb
@@ -126,7 +126,7 @@ class UploadCreator
       if is_image
         @upload.thumbnail_width, @upload.thumbnail_height = ImageSizer.resize(*@image_info.size)
         @upload.width, @upload.height = @image_info.size
-        @upload.animated = FastImage.animated?(@file)
+        @upload.animated = animated?
       end
 
       add_metadata!
@@ -267,13 +267,13 @@ class UploadCreator
   end
 
   def should_alter_quality?
-    return false if FastImage.animated?(@file)
+    return false if animated?
 
     @upload.target_image_quality(@file.path, SiteSetting.recompress_original_jpg_quality).present?
   end
 
   def should_downsize?
-    max_image_size > 0 && filesize >= max_image_size && !FastImage.animated?(@file)
+    max_image_size > 0 && filesize >= max_image_size && !animated?
   end
 
   def downsize!
@@ -351,7 +351,7 @@ class UploadCreator
   end
 
   def should_crop?
-    return false if ['profile_background', 'card_background', 'custom_emoji'].include?(@opts[:type]) && FastImage.animated?(@file)
+    return false if ['profile_background', 'card_background', 'custom_emoji'].include?(@opts[:type]) && animated?
 
     TYPES_TO_CROP.include?(@opts[:type])
   end
@@ -427,4 +427,30 @@ class UploadCreator
     @upload.secure = UploadSecurity.new(@upload, @opts).should_be_secure?
   end
 
+  private
+
+  def animated?
+    return @animated if @animated != nil
+
+    @animated ||= begin
+      is_animated = FastImage.animated?(@file)
+      type = @image_info.type.to_s
+
+      if is_animated != nil
+        # FastImage will return nil if it cannot determine if animated
+        is_animated
+      elsif type == "gif" || type == "webp"
+        # Only GIFs, WEBPs and a few other unsupported image types can be animated
+        OptimizedImage.ensure_safe_paths!(@file.path)
+
+        command = ["identify", "-format", "%n\\n", @file.path]
+        frames = Discourse::Utils.execute_command(*command).to_i rescue 1
+
+        frames > 1
+      else
+        false
+      end
+    end
+  end
+
 end
diff --git a/spec/fixtures/images/animated.webp b/spec/fixtures/images/animated.webp
new file mode 100644
index 0000000..5b00711
Binary files /dev/null and b/spec/fixtures/images/animated.webp differ
diff --git a/spec/lib/upload_creator_spec.rb b/spec/lib/upload_creator_spec.rb
index 23442bc..2de42a6 100644
--- a/spec/lib/upload_creator_spec.rb
+++ b/spec/lib/upload_creator_spec.rb
@@ -137,6 +137,9 @@ RSpec.describe UploadCreator do
       let(:animated_filename) { "animated.gif" }
       let(:animated_file) { file_from_fixtures(animated_filename) }
 
+      let(:animated_webp_filename) { "animated.webp" }
+      let(:animated_webp_file) { file_from_fixtures(animated_webp_filename) }
+
       before do
         SiteSetting.png_to_jpg_quality = 1
       end
@@ -208,6 +211,20 @@ RSpec.describe UploadCreator do
           expect(File.extname(upload.url)).to eq('.gif')
           expect(upload.original_filename).to eq('animated.gif')
         end
+
+        it 'should not convert animated WEBP images' do
+          expect do
+            UploadCreator.new(animated_webp_file, animated_webp_filename,
+              force_optimize: true
+            ).create_for(user.id)
+          end.to change { Upload.count }.by(1)
+
+          upload = Upload.last
+
+          expect(upload.extension).to eq('webp')
+          expect(File.extname(upload.url)).to eq('.webp')
+          expect(upload.original_filename).to eq('animated.webp')
+        end
       end
     end
 

GitHub sha: 74b95c88

This commit appears in #11702 which was approved by CvX and Falco. It was merged by nbianca.