mirror of
https://github.com/discourse/discourse.git
synced 2025-02-07 02:16:31 +08:00
automatically resizes images
This commit is contained in:
parent
97ba06d954
commit
c2e58b61c9
3
Gemfile
3
Gemfile
|
@ -26,6 +26,9 @@ gem 'hiredis'
|
||||||
# note: for image_optim to correctly work you need
|
# note: for image_optim to correctly work you need
|
||||||
# sudo apt-get install -y advancecomp gifsicle jpegoptim libjpeg-progs optipng pngcrush
|
# sudo apt-get install -y advancecomp gifsicle jpegoptim libjpeg-progs optipng pngcrush
|
||||||
gem 'image_optim'
|
gem 'image_optim'
|
||||||
|
# note: for image_sorcery to correctly work you need
|
||||||
|
# sudo apt-get install -y imagemagick
|
||||||
|
gem 'image_sorcery'
|
||||||
gem 'jquery-rails'
|
gem 'jquery-rails'
|
||||||
gem 'minitest'
|
gem 'minitest'
|
||||||
gem 'multi_json'
|
gem 'multi_json'
|
||||||
|
|
|
@ -219,6 +219,7 @@ GEM
|
||||||
in_threads (~> 1.1.1)
|
in_threads (~> 1.1.1)
|
||||||
progress (~> 2.4.0)
|
progress (~> 2.4.0)
|
||||||
image_size (1.1.1)
|
image_size (1.1.1)
|
||||||
|
image_sorcery (1.1.0)
|
||||||
in_threads (1.1.1)
|
in_threads (1.1.1)
|
||||||
ipaddress (0.8.0)
|
ipaddress (0.8.0)
|
||||||
jasminerice (0.0.10)
|
jasminerice (0.0.10)
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
# example, inserting the onebox content, or image sizes.
|
# example, inserting the onebox content, or image sizes.
|
||||||
|
|
||||||
require_dependency 'oneboxer'
|
require_dependency 'oneboxer'
|
||||||
|
require_dependency 'image_optimizer'
|
||||||
|
|
||||||
class CookedPostProcessor
|
class CookedPostProcessor
|
||||||
require 'open-uri'
|
|
||||||
|
|
||||||
def initialize(post, opts={})
|
def initialize(post, opts={})
|
||||||
@dirty = false
|
@dirty = false
|
||||||
|
@ -34,21 +34,14 @@ class CookedPostProcessor
|
||||||
images = @doc.search("img")
|
images = @doc.search("img")
|
||||||
return unless images.present?
|
return unless images.present?
|
||||||
|
|
||||||
# Extract the first image from the first post and use it as the 'topic image'
|
|
||||||
if @post.post_number == 1
|
|
||||||
img = images.first
|
|
||||||
@post.topic.update_column :image_url, img['src'] if img['src'].present?
|
|
||||||
end
|
|
||||||
|
|
||||||
images.each do |img|
|
images.each do |img|
|
||||||
src = img['src']
|
src = img['src']
|
||||||
src = Discourse.base_url_no_prefix + src if src[0] == "/"
|
src = Discourse.base_url_no_prefix + src if src[0] == "/"
|
||||||
|
|
||||||
if src.present? && (img['width'].blank? || img['height'].blank?)
|
if src.present?
|
||||||
|
|
||||||
w,h =
|
if img['width'].blank? || img['height'].blank?
|
||||||
get_size_from_image_sizes(src, @opts[:image_sizes]) ||
|
w, h = get_size_from_image_sizes(src, @opts[:image_sizes]) || image_dimensions(src)
|
||||||
image_dimensions(src)
|
|
||||||
|
|
||||||
if w && h
|
if w && h
|
||||||
img['width'] = w.to_s
|
img['width'] = w.to_s
|
||||||
|
@ -57,31 +50,39 @@ class CookedPostProcessor
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if src.present?
|
|
||||||
if src != img['src']
|
if src != img['src']
|
||||||
img['src'] = src
|
img['src'] = src
|
||||||
@dirty = true
|
@dirty = true
|
||||||
end
|
end
|
||||||
|
|
||||||
convert_to_link!(img)
|
convert_to_link!(img)
|
||||||
img.set_attribute('src', optimize_image(src))
|
img['src'] = optimize_image(img)
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def optimize_image(src)
|
# Extract the first image from the first post and use it as the 'topic image'
|
||||||
# uri = get_image_uri(src)
|
if @post.post_number == 1
|
||||||
# uri.open(read_timeout: 20) do |f|
|
img = images.first
|
||||||
#
|
@post.topic.update_column :image_url, img['src'] if img['src'].present?
|
||||||
# end
|
end
|
||||||
|
|
||||||
src
|
end
|
||||||
|
|
||||||
|
def optimize_image(img)
|
||||||
|
src = img["src"]
|
||||||
|
|
||||||
|
# supports only local uploads
|
||||||
|
return src if SiteSetting.enable_imgur? || SiteSetting.enable_s3_uploads?
|
||||||
|
|
||||||
|
width, height = img["width"].to_i, img["height"].to_i
|
||||||
|
|
||||||
|
ImageOptimizer.new(src).optimized_image_url(width, height)
|
||||||
end
|
end
|
||||||
|
|
||||||
def convert_to_link!(img)
|
def convert_to_link!(img)
|
||||||
src = img["src"]
|
src = img["src"]
|
||||||
width = img["width"].to_i
|
width, height = img["width"].to_i, img["height"].to_i
|
||||||
height = img["height"].to_i
|
|
||||||
|
|
||||||
return unless src.present? && width > SiteSetting.auto_link_images_wider_than
|
return unless src.present? && width > SiteSetting.auto_link_images_wider_than
|
||||||
|
|
||||||
|
|
|
@ -1,35 +1,22 @@
|
||||||
|
#
|
||||||
# This class is used to download and optimize images.
|
# This class is used to download and optimize images.
|
||||||
#
|
#
|
||||||
# I have not had a chance to implement me, and will not for about 3 weeks.
|
|
||||||
# If you are looking for a small project this simple API would be a good stint.
|
require 'image_sorcery'
|
||||||
#
|
require 'digest/sha1'
|
||||||
# Implement the following methods. With tests, the tests are a HUGE PITA cause
|
require 'open-uri'
|
||||||
# network, disk and external dependencies are involved.
|
|
||||||
|
|
||||||
class ImageOptimizer
|
class ImageOptimizer
|
||||||
attr_accessor :url, :root_dir
|
attr_accessor :url
|
||||||
|
|
||||||
# url is a url of an image ex:
|
# url is a url of an image ex:
|
||||||
# 'http://site.com/image.png'
|
# 'http://site.com/image.png'
|
||||||
# '/uploads/site/image.png'
|
# '/uploads/site/image.png'
|
||||||
#
|
def initialize(url)
|
||||||
# root_dir is the path where we
|
@url = url
|
||||||
# store optimized images
|
# make sure directories exists
|
||||||
def initialize(opts = {})
|
FileUtils.mkdir_p downloads_dir
|
||||||
@url = opts[:url]
|
FileUtils.mkdir_p optimized_dir
|
||||||
@root_dir = opts[:root_dir]
|
|
||||||
end
|
|
||||||
|
|
||||||
# attempt to refresh the original image, if refreshed
|
|
||||||
# remove old downsized copies
|
|
||||||
def refresh_local!
|
|
||||||
end
|
|
||||||
|
|
||||||
# clear all local copies of the images
|
|
||||||
def clear_local!
|
|
||||||
end
|
|
||||||
|
|
||||||
# yield a list of relative paths to local images cached
|
|
||||||
def each_local
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# return the path of an optimized image,
|
# return the path of an optimized image,
|
||||||
|
@ -42,7 +29,68 @@ class ImageOptimizer
|
||||||
# at the basic level it runs through image_optim https://github.com/toy/image_optim
|
# at the basic level it runs through image_optim https://github.com/toy/image_optim
|
||||||
# it also has a failsafe that converts jpg to png or the opposite. if jpg size is 1.5*
|
# it also has a failsafe that converts jpg to png or the opposite. if jpg size is 1.5*
|
||||||
# as efficient as png it flips formats.
|
# as efficient as png it flips formats.
|
||||||
def optimized_image_path(width=nil, height=nil)
|
def optimized_image_url (width = nil, height = nil)
|
||||||
|
begin
|
||||||
|
unless has_been_uploaded?
|
||||||
|
return @url unless SiteSetting.crawl_images?
|
||||||
|
# download the file if it hasn't been cached yet
|
||||||
|
download! unless File.exists?(cached_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
# resize the image using Image Magick
|
||||||
|
result = ImageSorcery.new(cached_path).convert(optimized_path, resize: "#{width}x#{height}")
|
||||||
|
return optimized_url if result
|
||||||
|
@url
|
||||||
|
rescue
|
||||||
|
@url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def public_dir
|
||||||
|
@public_dir ||= "#{Rails.root}/public"
|
||||||
|
end
|
||||||
|
|
||||||
|
def downloads_dir
|
||||||
|
@downloads_dir ||= "#{public_dir}/downloads/#{RailsMultisite::ConnectionManagement.current_db}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def optimized_dir
|
||||||
|
@optimized_dir ||= "#{public_dir}/images/#{RailsMultisite::ConnectionManagement.current_db}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_been_uploaded?
|
||||||
|
@url.start_with?(Discourse.base_url_no_prefix)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cached_path
|
||||||
|
@cached_path ||= if has_been_uploaded?
|
||||||
|
"#{public_dir}#{@url[Discourse.base_url_no_prefix.length..-1]}"
|
||||||
|
else
|
||||||
|
"#{downloads_dir}/#{file_name(@url)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def optimized_path
|
||||||
|
@optimized_path ||= "#{optimized_dir}/#{file_name(cached_path)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def file_name (uri)
|
||||||
|
image_info = FastImage.new(uri)
|
||||||
|
name = Digest::SHA1.hexdigest(uri)[0,16]
|
||||||
|
name << ".#{image_info.type}"
|
||||||
|
name
|
||||||
|
end
|
||||||
|
|
||||||
|
def download!
|
||||||
|
File.open(cached_path, "wb") do |f|
|
||||||
|
f.write open(@url, "rb", read_timeout: 20).read
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def optimized_url
|
||||||
|
@optimized_url ||= Discourse::base_uri + "/images/#{RailsMultisite::ConnectionManagement.current_db}/#{file_name(cached_path)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,6 +41,7 @@ describe CookedPostProcessor do
|
||||||
before do
|
before do
|
||||||
@topic = Fabricate(:topic)
|
@topic = Fabricate(:topic)
|
||||||
@post = Fabricate.build(:post_with_image_url, topic: @topic, user: @topic.user)
|
@post = Fabricate.build(:post_with_image_url, topic: @topic, user: @topic.user)
|
||||||
|
ImageSorcery.any_instance.stubs(:convert).returns(false)
|
||||||
@cpp = CookedPostProcessor.new(@post, image_sizes: {'http://www.forumwarz.com/images/header/logo.png' => {'width' => 111, 'height' => 222}})
|
@cpp = CookedPostProcessor.new(@post, image_sizes: {'http://www.forumwarz.com/images/header/logo.png' => {'width' => 111, 'height' => 222}})
|
||||||
@cpp.expects(:get_size).returns([111,222])
|
@cpp.expects(:get_size).returns([111,222])
|
||||||
end
|
end
|
||||||
|
@ -63,6 +64,7 @@ describe CookedPostProcessor do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
FastImage.stubs(:size).returns([123, 456])
|
FastImage.stubs(:size).returns([123, 456])
|
||||||
|
ImageSorcery.any_instance.stubs(:convert).returns(false)
|
||||||
CookedPostProcessor.any_instance.expects(:image_dimensions).returns([123, 456])
|
CookedPostProcessor.any_instance.expects(:image_dimensions).returns([123, 456])
|
||||||
creator = PostCreator.new(user, raw: Fabricate.build(:post_with_images).raw, topic_id: topic.id)
|
creator = PostCreator.new(user, raw: Fabricate.build(:post_with_images).raw, topic_id: topic.id)
|
||||||
@post = creator.create
|
@post = creator.create
|
||||||
|
@ -70,7 +72,7 @@ describe CookedPostProcessor do
|
||||||
|
|
||||||
it "adds a topic image if there's one in the post" do
|
it "adds a topic image if there's one in the post" do
|
||||||
@post.topic.reload
|
@post.topic.reload
|
||||||
@post.topic.image_url.should == "/path/to/img.jpg"
|
@post.topic.image_url.should == "http://test.localhost/path/to/img.jpg"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "adds the height and width to images that don't have them" do
|
it "adds the height and width to images that don't have them" do
|
||||||
|
|
|
@ -3,6 +3,10 @@ require_dependency 'post_destroyer'
|
||||||
|
|
||||||
describe PostAction do
|
describe PostAction do
|
||||||
|
|
||||||
|
before do
|
||||||
|
ImageSorcery.any_instance.stubs(:convert).returns(false)
|
||||||
|
end
|
||||||
|
|
||||||
it { should belong_to :user }
|
it { should belong_to :user }
|
||||||
it { should belong_to :post }
|
it { should belong_to :post }
|
||||||
it { should belong_to :post_action_type }
|
it { should belong_to :post_action_type }
|
||||||
|
|
|
@ -3,6 +3,10 @@ require_dependency 'post_destroyer'
|
||||||
|
|
||||||
describe PostAlertObserver do
|
describe PostAlertObserver do
|
||||||
|
|
||||||
|
before do
|
||||||
|
ImageSorcery.any_instance.stubs(:convert).returns(false)
|
||||||
|
end
|
||||||
|
|
||||||
let!(:evil_trout) { Fabricate(:evil_trout) }
|
let!(:evil_trout) { Fabricate(:evil_trout) }
|
||||||
let(:post) { Fabricate(:post) }
|
let(:post) { Fabricate(:post) }
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,10 @@ require_dependency 'post_destroyer'
|
||||||
|
|
||||||
describe Post do
|
describe Post do
|
||||||
|
|
||||||
|
before do
|
||||||
|
ImageSorcery.any_instance.stubs(:convert).returns(false)
|
||||||
|
end
|
||||||
|
|
||||||
it { should belong_to :user }
|
it { should belong_to :user }
|
||||||
it { should belong_to :topic }
|
it { should belong_to :topic }
|
||||||
it { should validate_presence_of :raw }
|
it { should validate_presence_of :raw }
|
||||||
|
|
Loading…
Reference in New Issue
Block a user