discourse/app/models/optimized_image.rb

174 lines
4.6 KiB
Ruby
Raw Normal View History

require "digest/sha1"
2013-06-16 04:39:48 -04:00
class OptimizedImage < ActiveRecord::Base
belongs_to :upload
def self.create_for(upload, width, height, opts={})
2013-11-05 13:04:47 -05:00
return unless width > 0 && height > 0
2013-07-07 19:39:08 -04:00
2013-11-05 13:04:47 -05:00
# do we already have that thumbnail?
thumbnail = find_by(upload_id: upload.id, width: width, height: height)
2013-11-05 13:04:47 -05:00
# make sure the previous thumbnail has not failed
if thumbnail && thumbnail.url.blank?
thumbnail.destroy
thumbnail = nil
end
2015-02-03 12:44:18 -05:00
# return the previous thumbnail if any
return thumbnail unless thumbnail.nil?
2013-11-05 13:04:47 -05:00
# create the thumbnail otherwise
2015-02-03 12:44:18 -05:00
external_copy = Discourse.store.download(upload) if Discourse.store.external?
original_path = if Discourse.store.external?
external_copy.try(:path)
else
Discourse.store.path_for(upload)
end
2013-11-05 13:04:47 -05:00
2015-02-03 12:44:18 -05:00
if original_path.blank?
Rails.logger.error("Could not find file in the store located at url: #{upload.url}")
else
# create a temp file with the same extension as the original
extension = File.extname(original_path)
temp_file = Tempfile.new(["discourse-thumbnail", extension])
temp_path = temp_file.path
2015-02-03 12:44:18 -05:00
if extension =~ /\.svg$/i
FileUtils.cp(original_path, temp_path)
resized = true
else
2015-02-21 12:37:37 -05:00
resized = resize(original_path, temp_path, width, height, opts)
2015-02-03 12:44:18 -05:00
end
2015-02-03 12:44:18 -05:00
if resized
thumbnail = OptimizedImage.create!(
upload_id: upload.id,
sha1: Digest::SHA1.file(temp_path).hexdigest,
extension: extension,
width: width,
height: height,
url: "",
)
# store the optimized image and update its url
url = Discourse.store.store_optimized_image(temp_file, thumbnail)
if url.present?
thumbnail.url = url
thumbnail.save
2013-11-05 13:04:47 -05:00
else
2015-02-03 12:44:18 -05:00
Rails.logger.error("Failed to store avatar #{size} for #{upload.url} from #{source}")
2013-11-05 13:04:47 -05:00
end
2015-02-03 12:44:18 -05:00
else
Rails.logger.error("Failed to create optimized image #{width}x#{height} for #{upload.url}")
2013-11-05 13:04:47 -05:00
end
2015-02-03 12:44:18 -05:00
# close && remove temp file
temp_file.close!
2013-11-05 13:04:47 -05:00
end
2015-02-03 12:44:18 -05:00
# make sure we remove the cached copy from external stores
if Discourse.store.external?
external_copy.try(:close!) rescue nil
end
2015-02-03 12:44:18 -05:00
thumbnail
2013-06-16 19:00:25 -04:00
end
def destroy
OptimizedImage.transaction do
2013-08-13 16:09:27 -04:00
Discourse.store.remove_optimized_image(self)
super
end
end
2015-02-21 12:37:37 -05:00
def self.resize_instructions(from, to, width, height, opts={})
# NOTE: ORDER is important!
2015-02-21 12:37:37 -05:00
if !!opts[:allow_animation] && from =~ /\.GIF$/i
2014-11-13 17:30:34 -05:00
%W{
#{from}
-coalesce
-gravity center
-thumbnail #{width}x#{height}^
-extent #{width}x#{height}
-layers optimize
#{to}
2015-02-20 11:24:37 -05:00
}
2014-11-13 17:30:34 -05:00
else
%W{
#{from}[0]
-gravity center
2015-02-20 11:24:37 -05:00
-background transparent
2014-11-13 17:30:34 -05:00
-thumbnail #{width}x#{height}^
-extent #{width}x#{height}
-interpolate bicubic
-unsharp 2x0.5+0.7+0
-quality 98
#{to}
2015-02-20 11:24:37 -05:00
}
2014-11-13 17:30:34 -05:00
end
2015-02-20 11:24:37 -05:00
end
2015-02-21 12:37:37 -05:00
def self.downsize_instructions(from, to, max_width, max_height, opts={})
dimensions = "#{max_width}x#{max_height}"
dimensions += !!opts[:force_aspect_ratio] ? "\\!" : "\\>"
if !!opts[:allow_animation] && from =~ /\.GIF$/i
2015-02-20 11:24:37 -05:00
%W{
#{from}
-coalesce
-gravity center
-background transparent
2015-02-21 12:37:37 -05:00
-thumbnail #{dimensions}
2015-02-20 11:24:37 -05:00
-layers optimize
#{to}
}
else
2015-02-20 11:24:37 -05:00
%W{
#{from}[0]
-gravity center
-background transparent
2015-02-21 12:37:37 -05:00
-thumbnail #{dimensions}
2015-02-20 11:24:37 -05:00
#{to}
}
end
end
2015-02-21 12:37:37 -05:00
def self.resize(from, to, width, height, opts={})
instructions = resize_instructions(from, to, width, height, opts)
2015-02-20 11:24:37 -05:00
convert_and_optimize_with(instructions)
end
2015-02-21 12:37:37 -05:00
def self.downsize(from, to, max_width, max_height, opts={})
instructions = downsize_instructions(from, to, max_width, max_height, opts)
2015-02-20 11:24:37 -05:00
convert_and_optimize_with(instructions)
end
def self.convert_and_optimize_with(instructions)
`convert #{instructions.join(" ")}`
return false if $?.exitstatus != 0
ImageOptim.new.optimize_image(to) rescue nil
true
end
2013-06-16 04:39:48 -04:00
end
2013-06-16 20:48:58 -04:00
# == Schema Information
#
# Table name: optimized_images
#
# id :integer not null, primary key
# sha1 :string(40) not null
# extension :string(10) not null
2013-06-16 20:48:58 -04:00
# width :integer not null
# height :integer not null
# upload_id :integer not null
2013-08-13 16:09:27 -04:00
# url :string(255) not null
2013-06-16 20:48:58 -04:00
#
# Indexes
#
# index_optimized_images_on_upload_id (upload_id)
# index_optimized_images_on_upload_id_and_width_and_height (upload_id,width,height) UNIQUE
#