asset_host_core 2.0.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.markdown +138 -0
- data/Rakefile +11 -0
- data/app/assets/images/asset_host_core/alert-overlay.png +0 -0
- data/app/assets/images/asset_host_core/arrow-left.gif +0 -0
- data/app/assets/images/asset_host_core/arrow-right.gif +0 -0
- data/app/assets/images/asset_host_core/fallback-img-rect.png +0 -0
- data/app/assets/images/asset_host_core/videoplayer-play.png +0 -0
- data/app/assets/images/asset_host_core/x.png +0 -0
- data/app/assets/javascripts/asset_host_core/admin/assets.js.coffee +221 -0
- data/app/assets/javascripts/asset_host_core/application.js +20 -0
- data/app/assets/javascripts/asset_host_core/assetadmin.js.coffee +56 -0
- data/app/assets/javascripts/asset_host_core/assethost.js.coffee.erb +17 -0
- data/app/assets/javascripts/asset_host_core/browserui.js.coffee +139 -0
- data/app/assets/javascripts/asset_host_core/chooserui.js.coffee +381 -0
- data/app/assets/javascripts/asset_host_core/client.js.coffee +29 -0
- data/app/assets/javascripts/asset_host_core/clients/BrightcoveVideo.js.coffee +64 -0
- data/app/assets/javascripts/asset_host_core/clients/templates/brightcove_embed.jst.eco +18 -0
- data/app/assets/javascripts/asset_host_core/clients/templates/vimeo_embed.jst.eco +1 -0
- data/app/assets/javascripts/asset_host_core/clients/templates/youtube_embed.jst.eco +1 -0
- data/app/assets/javascripts/asset_host_core/clients/vimeo_video.js.coffee +21 -0
- data/app/assets/javascripts/asset_host_core/clients/youtube_video.js.coffee +21 -0
- data/app/assets/javascripts/asset_host_core/cmsplugin.js.coffee +235 -0
- data/app/assets/javascripts/asset_host_core/models.js.coffee +586 -0
- data/app/assets/javascripts/asset_host_core/railsCMS.js.coffee +141 -0
- data/app/assets/javascripts/asset_host_core/slideshow.js.coffee +428 -0
- data/app/assets/javascripts/asset_host_core/templates/after_upload_button.jst.eco +3 -0
- data/app/assets/javascripts/asset_host_core/templates/asset_drop_asset.jst.eco +4 -0
- data/app/assets/javascripts/asset_host_core/templates/asset_modal.jst.eco +13 -0
- data/app/assets/javascripts/asset_host_core/templates/asset_preview.jst.eco +35 -0
- data/app/assets/javascripts/asset_host_core/templates/asset_search.jst.eco +2 -0
- data/app/assets/javascripts/asset_host_core/templates/browser_asset.jst.eco +1 -0
- data/app/assets/javascripts/asset_host_core/templates/browser_asset_tip.jst.eco +3 -0
- data/app/assets/javascripts/asset_host_core/templates/edit_modal.jst.eco +40 -0
- data/app/assets/javascripts/asset_host_core/templates/import_help.jst.eco +59 -0
- data/app/assets/javascripts/asset_host_core/templates/pagination_link.jst.eco +1 -0
- data/app/assets/javascripts/asset_host_core/templates/pagination_links.jst.eco +13 -0
- data/app/assets/javascripts/asset_host_core/templates/queued_file.jst.eco +11 -0
- data/app/assets/javascripts/asset_host_core/templates/save_and_close_view.jst.eco +4 -0
- data/app/assets/javascripts/asset_host_core/templates/upload_all_button.jst.eco +4 -0
- data/app/assets/javascripts/asset_host_core/templates/url_input.jst.eco +8 -0
- data/app/assets/stylesheets/asset_host_core/application.css.scss +384 -0
- data/app/assets/stylesheets/asset_host_core/jquery-ui.css +105 -0
- data/app/assets/stylesheets/asset_host_core/public.css.scss +204 -0
- data/app/assets/stylesheets/asset_host_core/slidetest.css.scss +93 -0
- data/app/controllers/asset_host_core/admin/api_users_controller.rb +72 -0
- data/app/controllers/asset_host_core/admin/assets_controller.rb +140 -0
- data/app/controllers/asset_host_core/admin/base_controller.rb +36 -0
- data/app/controllers/asset_host_core/admin/home_controller.rb +13 -0
- data/app/controllers/asset_host_core/admin/outputs_controller.rb +55 -0
- data/app/controllers/asset_host_core/api/assets_controller.rb +110 -0
- data/app/controllers/asset_host_core/api/base_controller.rb +43 -0
- data/app/controllers/asset_host_core/api/outputs_controller.rb +33 -0
- data/app/controllers/asset_host_core/application_controller.rb +43 -0
- data/app/controllers/asset_host_core/public_controller.rb +104 -0
- data/app/models/asset_host_core/api_user.rb +44 -0
- data/app/models/asset_host_core/api_user_permission.rb +6 -0
- data/app/models/asset_host_core/asset.rb +265 -0
- data/app/models/asset_host_core/asset_output.rb +69 -0
- data/app/models/asset_host_core/brightcove_video.rb +20 -0
- data/app/models/asset_host_core/output.rb +52 -0
- data/app/models/asset_host_core/permission.rb +19 -0
- data/app/models/asset_host_core/video.rb +8 -0
- data/app/models/asset_host_core/vimeo_video.rb +17 -0
- data/app/models/asset_host_core/youtube_video.rb +17 -0
- data/app/views/asset_host_core/admin/api_users/_form_fields.html.erb +5 -0
- data/app/views/asset_host_core/admin/api_users/edit.html.erb +26 -0
- data/app/views/asset_host_core/admin/api_users/index.html.erb +31 -0
- data/app/views/asset_host_core/admin/api_users/new.html.erb +17 -0
- data/app/views/asset_host_core/admin/api_users/show.html.erb +23 -0
- data/app/views/asset_host_core/admin/assets/index.html.erb +19 -0
- data/app/views/asset_host_core/admin/assets/metadata.html.erb +24 -0
- data/app/views/asset_host_core/admin/assets/show.html.erb +86 -0
- data/app/views/asset_host_core/admin/home/chooser.html.erb +49 -0
- data/app/views/asset_host_core/admin/outputs/_form_fields.html.erb +5 -0
- data/app/views/asset_host_core/admin/outputs/edit.html.erb +26 -0
- data/app/views/asset_host_core/admin/outputs/index.html.erb +27 -0
- data/app/views/asset_host_core/admin/outputs/new.html.erb +13 -0
- data/app/views/asset_host_core/admin/outputs/show.html.erb +17 -0
- data/app/views/asset_host_core/shared/_footerjs.html.erb +3 -0
- data/app/views/asset_host_core/shared/_navbar.html.erb +28 -0
- data/app/views/kaminari/_first_page.html.erb +3 -0
- data/app/views/kaminari/_gap.html.erb +3 -0
- data/app/views/kaminari/_last_page.html.erb +3 -0
- data/app/views/kaminari/_next_page.html.erb +3 -0
- data/app/views/kaminari/_page.html.erb +3 -0
- data/app/views/kaminari/_paginator.html.erb +17 -0
- data/app/views/kaminari/_prev_page.html.erb +3 -0
- data/app/views/layouts/asset_host_core/application.html.erb +54 -0
- data/app/views/layouts/asset_host_core/full_width.html.erb +32 -0
- data/app/views/layouts/asset_host_core/minimal.html.erb +45 -0
- data/config/initializers/simple_form.rb +142 -0
- data/config/initializers/simple_form_bootstrap.rb +45 -0
- data/config/locales/simple_form.en.yml +26 -0
- data/config/routes.rb +49 -0
- data/lib/asset_host_core.rb +38 -0
- data/lib/asset_host_core/config.rb +39 -0
- data/lib/asset_host_core/engine.rb +94 -0
- data/lib/asset_host_core/loaders.rb +34 -0
- data/lib/asset_host_core/loaders/asset_host.rb +30 -0
- data/lib/asset_host_core/loaders/base.rb +22 -0
- data/lib/asset_host_core/loaders/brightcove.rb +67 -0
- data/lib/asset_host_core/loaders/flickr.rb +114 -0
- data/lib/asset_host_core/loaders/url.rb +59 -0
- data/lib/asset_host_core/loaders/vimeo.rb +76 -0
- data/lib/asset_host_core/loaders/youtube.rb +90 -0
- data/lib/asset_host_core/model_methods.rb +61 -0
- data/lib/asset_host_core/paperclip.rb +4 -0
- data/lib/asset_host_core/paperclip/asset_thumbnail.rb +92 -0
- data/lib/asset_host_core/paperclip/attachment.rb +206 -0
- data/lib/asset_host_core/paperclip/trimmer.rb +33 -0
- data/lib/asset_host_core/resque_job.rb +13 -0
- data/lib/asset_host_core/version.rb +3 -0
- data/lib/tasks/asset_host_core_tasks.rake +4 -0
- data/spec/controllers/admin/api_users_controller_spec.rb +21 -0
- data/spec/controllers/admin/assets_controller_spec.rb +59 -0
- data/spec/controllers/admin/home_controller_spec.rb +4 -0
- data/spec/controllers/admin/outputs_controller_spec.rb +4 -0
- data/spec/controllers/api/assets_controller_spec.rb +133 -0
- data/spec/controllers/api/outputs_controller_spec.rb +51 -0
- data/spec/controllers/public_controller_spec.rb +4 -0
- data/spec/factories.rb +39 -0
- data/spec/features/api_users_spec.rb +78 -0
- data/spec/fixtures/api/brightcove/video.json +137 -0
- data/spec/fixtures/api/flickr/photos_getInfo.json +78 -0
- data/spec/fixtures/api/flickr/photos_getSizes.json +82 -0
- data/spec/fixtures/api/flickr/photos_licenses_getInfo.json +52 -0
- data/spec/fixtures/api/vimeo/video.json +28 -0
- data/spec/fixtures/api/youtube/discovery.json +5190 -0
- data/spec/fixtures/api/youtube/video.json +44 -0
- data/spec/fixtures/images/chipmunk.jpg +0 -0
- data/spec/fixtures/images/dude.jpg +0 -0
- data/spec/fixtures/images/ernie.jpg +0 -0
- data/spec/fixtures/images/fry.png +0 -0
- data/spec/fixtures/images/hat.jpg +0 -0
- data/spec/fixtures/images/spongebob.png +0 -0
- data/spec/fixtures/images/stars.jpg +0 -0
- data/spec/internal/app/controllers/application_controller.rb +16 -0
- data/spec/internal/app/controllers/sessions_controller.rb +24 -0
- data/spec/internal/app/models/user.rb +10 -0
- data/spec/internal/app/views/sessions/new.html.erb +14 -0
- data/spec/internal/config/database.yml +3 -0
- data/spec/internal/config/initializers/assethost_config.rb +57 -0
- data/spec/internal/config/routes.rb +7 -0
- data/spec/internal/db/combustion_test.sqlite +0 -0
- data/spec/internal/db/schema.rb +106 -0
- data/spec/internal/log/test.log +14769 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/internal/public/images/1_27f7745237849975ca90591c1fba5934_original. +0 -0
- data/spec/internal/public/images/1_7d33319deca787d5bb3f62ff06563ad2_original. +0 -0
- data/spec/internal/public/images/1_b6d48c8b1286104ce76649731e09645f_original. +0 -0
- data/spec/internal/public/images/1_b6d48c8b1286104ce76649731e09645f_original.jpg +0 -0
- data/spec/internal/public/images/1_b6d48c8b1286104ce76649731e09645f_original.txt +0 -0
- data/spec/internal/public/images/1_e179cbd27e07cb55042d0db36cdac095_original. +0 -0
- data/spec/internal/public/images/1_e669edd3dfd74be66fc38416e82e3a37_original. +0 -0
- data/spec/lib/asset_host_core/loaders/asset_host_spec.rb +33 -0
- data/spec/lib/asset_host_core/loaders/brightcove_spec.rb +51 -0
- data/spec/lib/asset_host_core/loaders/flickr_spec.rb +72 -0
- data/spec/lib/asset_host_core/loaders/url_spec.rb +42 -0
- data/spec/lib/asset_host_core/loaders/vimeo_spec.rb +51 -0
- data/spec/lib/asset_host_core/loaders/youtube_spec.rb +73 -0
- data/spec/lib/asset_host_core/loaders_spec.rb +4 -0
- data/spec/lib/asset_host_core/model_methods_spec.rb +4 -0
- data/spec/lib/asset_host_core/paperclip/asset_thumbnail_spec.rb +4 -0
- data/spec/lib/asset_host_core/paperclip/attachment_spec.rb +4 -0
- data/spec/lib/asset_host_core/resque_job_spec.rb +4 -0
- data/spec/lib/asset_host_core_spec.rb +4 -0
- data/spec/models/api_user_spec.rb +58 -0
- data/spec/models/asset_output_spec.rb +4 -0
- data/spec/models/asset_spec.rb +4 -0
- data/spec/models/output_spec.rb +4 -0
- data/spec/models/permission_spec.rb +4 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/support/fixture_loader.rb +9 -0
- data/spec/support/param_helper.rb +14 -0
- data/spec/support/permission_matcher.rb +17 -0
- data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_flat_10_000000_40x100.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_222222_256x240.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_228ef1_256x240.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_ef8c08_256x240.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_ffd27a_256x240.png +0 -0
- data/vendor/assets/images/jquery-ui/ui-icons_ffffff_256x240.png +0 -0
- data/vendor/assets/javascripts/backbone.js +1158 -0
- data/vendor/assets/javascripts/backbone.modelbinding.js +475 -0
- data/vendor/assets/javascripts/exif.js +695 -0
- data/vendor/assets/javascripts/jquery-ui.js +5614 -0
- data/vendor/assets/javascripts/simplemodal.js +698 -0
- data/vendor/assets/javascripts/spin.jquery.js +81 -0
- data/vendor/assets/javascripts/spin.min.js +1 -0
- data/vendor/assets/javascripts/underscore.min.js +1 -0
- metadata +658 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'mime/types'
|
2
|
+
require 'net/http'
|
3
|
+
require 'cgi'
|
4
|
+
|
5
|
+
module AssetHostCore
|
6
|
+
module Loaders
|
7
|
+
class URL < Base
|
8
|
+
SOURCE = "URL"
|
9
|
+
|
10
|
+
#-----------
|
11
|
+
|
12
|
+
def self.build_from_url(url)
|
13
|
+
uri = URI.parse(url)
|
14
|
+
return nil unless uri.is_a?(URI::HTTP)
|
15
|
+
|
16
|
+
response = Net::HTTP.get_response(uri)
|
17
|
+
|
18
|
+
# Check that it's actually an image we're grabbing
|
19
|
+
if response.content_type.match(/image/)
|
20
|
+
self.new(url: url, id: url)
|
21
|
+
else
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
#----------
|
27
|
+
|
28
|
+
def load
|
29
|
+
filename = File.basename(@url)
|
30
|
+
|
31
|
+
# build asset
|
32
|
+
asset = AssetHostCore::Asset.new(
|
33
|
+
:title => filename,
|
34
|
+
:url => @url,
|
35
|
+
:notes => "Fetched from URL: #{@url}",
|
36
|
+
:image => image_file
|
37
|
+
)
|
38
|
+
|
39
|
+
asset.save!
|
40
|
+
asset
|
41
|
+
end
|
42
|
+
|
43
|
+
#----------
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def image_file
|
48
|
+
@image_file ||= begin
|
49
|
+
response = open(@url)
|
50
|
+
|
51
|
+
tempfile = Tempfile.new('ah-brightcove', encoding: "ascii-8bit")
|
52
|
+
tempfile.write response.read
|
53
|
+
|
54
|
+
tempfile
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module AssetHostCore
|
2
|
+
module Loaders
|
3
|
+
class Vimeo < Base
|
4
|
+
SOURCE = "Vimeo"
|
5
|
+
|
6
|
+
#----------------
|
7
|
+
|
8
|
+
def self.build_from_url(url)
|
9
|
+
# Don't need a key just to read from the public API
|
10
|
+
url.match(/vimeo\.com\/(?<id>\d+)/i) do |m|
|
11
|
+
self.new(url: url, id: m[:id])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
#----------------
|
16
|
+
|
17
|
+
def load
|
18
|
+
data = fetch_data
|
19
|
+
|
20
|
+
video = data[0]
|
21
|
+
return nil if !video
|
22
|
+
|
23
|
+
native = AssetHostCore::VimeoVideo.create(
|
24
|
+
:videoid => video["id"]
|
25
|
+
)
|
26
|
+
|
27
|
+
asset = AssetHostCore::Asset.new(
|
28
|
+
:image => image_file(video["thumbnail_large"]),
|
29
|
+
:title => video["title"],
|
30
|
+
:caption => video["title"], # Description is too long
|
31
|
+
:url => @url,
|
32
|
+
:owner => "#{video["user_name"]} (via Vimeo)",
|
33
|
+
:notes => "Imported from Vimeo: #{@url}",
|
34
|
+
:image_taken => video["upload_date"],
|
35
|
+
:native => native
|
36
|
+
)
|
37
|
+
|
38
|
+
asset.save!
|
39
|
+
asset
|
40
|
+
end
|
41
|
+
|
42
|
+
#----------------
|
43
|
+
|
44
|
+
def fetch_data
|
45
|
+
response = connection.get do |request|
|
46
|
+
request.url "video/#{@id}.json"
|
47
|
+
end
|
48
|
+
|
49
|
+
response.body
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def image_file(url)
|
56
|
+
@image_file ||= begin
|
57
|
+
response = open(url)
|
58
|
+
|
59
|
+
tempfile = Tempfile.new('ah-vimeo', encoding: "ascii-8bit")
|
60
|
+
tempfile.write response.read
|
61
|
+
|
62
|
+
tempfile
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def connection
|
67
|
+
@connection ||= begin
|
68
|
+
Faraday.new('http://vimeo.com/api/v2') do |conn|
|
69
|
+
conn.response :json
|
70
|
+
conn.adapter Faraday.default_adapter
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module AssetHostCore
|
2
|
+
module Loaders
|
3
|
+
class YouTube < Base
|
4
|
+
SOURCE = "YouTube"
|
5
|
+
|
6
|
+
#----------------
|
7
|
+
|
8
|
+
def self.build_from_url(url)
|
9
|
+
return nil if AssetHostCore.config.google_api_key.blank?
|
10
|
+
|
11
|
+
url.match(/youtube\.com\/watch\?.*v=(?<id>[\w-]+)/i) do |m|
|
12
|
+
self.new(url: url, id: m[:id])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
#----------------
|
17
|
+
|
18
|
+
def load
|
19
|
+
data = fetch_data
|
20
|
+
|
21
|
+
video = data["items"][0]
|
22
|
+
return nil if !video
|
23
|
+
|
24
|
+
snippet = video["snippet"]
|
25
|
+
return nil if !snippet
|
26
|
+
|
27
|
+
native = AssetHostCore::YoutubeVideo.create(
|
28
|
+
:videoid => video["id"]
|
29
|
+
)
|
30
|
+
|
31
|
+
thumbnail = snippet["thumbnails"]["maxres"] ||
|
32
|
+
snippet["thumbnails"]["high"]
|
33
|
+
|
34
|
+
asset = AssetHostCore::Asset.new(
|
35
|
+
:image => image_file(thumbnail["url"]),
|
36
|
+
:title => snippet["title"],
|
37
|
+
:caption => snippet["description"],
|
38
|
+
:url => @url,
|
39
|
+
:owner => "#{snippet["channelTitle"]} (via YouTube)",
|
40
|
+
:notes => "Imported from YouTube: #{@url}",
|
41
|
+
:image_taken => snippet["publishedAt"],
|
42
|
+
:native => native
|
43
|
+
)
|
44
|
+
|
45
|
+
asset.save!
|
46
|
+
asset
|
47
|
+
end
|
48
|
+
|
49
|
+
#----------------
|
50
|
+
|
51
|
+
def fetch_data
|
52
|
+
response = client.execute!(
|
53
|
+
:api_method => youtube.videos.list,
|
54
|
+
:parameters => {
|
55
|
+
:id => @id,
|
56
|
+
:part => 'id,snippet,contentDetails,player'
|
57
|
+
}
|
58
|
+
)
|
59
|
+
|
60
|
+
JSON.parse(response.body)
|
61
|
+
end
|
62
|
+
|
63
|
+
#----------------
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def image_file(url)
|
68
|
+
@image_file ||= begin
|
69
|
+
response = open(url)
|
70
|
+
|
71
|
+
tempfile = Tempfile.new('ah-youtube', encoding: "ascii-8bit")
|
72
|
+
tempfile.write response.read
|
73
|
+
|
74
|
+
@url.match(/ah-noTrim/) ? tempfile : Paperclip::Trimmer.make(tempfile)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def client
|
79
|
+
@client ||= Google::APIClient.new(
|
80
|
+
:key => AssetHostCore.config.google_api_key,
|
81
|
+
:authorization => nil
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
def youtube
|
86
|
+
@youtube ||= client.discovered_api('youtube', 'v3')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'mini_exiftool'
|
2
|
+
|
3
|
+
module AssetHostCore
|
4
|
+
module ModelMethods
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def treat_as_image_asset(name)
|
9
|
+
include InstanceMethodsOnActivation
|
10
|
+
|
11
|
+
attachment_definitions[name][:delayed] = true
|
12
|
+
|
13
|
+
define_method "write_exif_data_for_#{name}" do
|
14
|
+
if self.send("#{name}").dirty?
|
15
|
+
# need to extract dimensions from the attachment
|
16
|
+
self.attachment_for(name).write_exif_data
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
define_method "enqueue_delayed_processing_for_#{name}" do
|
21
|
+
# we render on two things: image fingerprint changed, or image gravity changed
|
22
|
+
if self.previous_changes.include?("image_fingerprint") || self.previous_changes.include?("image_gravity")
|
23
|
+
self.attachment_for(name).enqueue
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# register our event handler
|
28
|
+
before_save :"write_exif_data_for_#{name}"
|
29
|
+
|
30
|
+
if respond_to?(:after_commit)
|
31
|
+
after_commit :"enqueue_delayed_processing_for_#{name}"
|
32
|
+
else
|
33
|
+
after_save :"enqueue_delayed_processing_for_#{name}"
|
34
|
+
end
|
35
|
+
|
36
|
+
# -- Style fingerprint interpolation -- #
|
37
|
+
|
38
|
+
::Paperclip.interpolates "sprint" do |attachment, style_name|
|
39
|
+
if style_name == :original
|
40
|
+
'original'
|
41
|
+
elsif ao = attachment.instance.output_by_style(style_name)
|
42
|
+
ao.fingerprint
|
43
|
+
else
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
module InstanceMethodsOnActivation
|
51
|
+
# borrowed from delayed_paperclip... combines with [:delayed] above to turn off the inline processing
|
52
|
+
def attachment_for(name)
|
53
|
+
@_paperclip_attachments ||= {}
|
54
|
+
@_paperclip_attachments[name] ||= ::Paperclip::Attachment.new(name, self, self.class.attachment_definitions[name]).tap do |a|
|
55
|
+
a.post_processing = false if self.class.attachment_definitions[name][:delayed]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end # ClassMethods
|
60
|
+
end # Paperclip
|
61
|
+
end # AssetHostCore
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Paperclip
|
2
|
+
class AssetThumbnail < Paperclip::Thumbnail
|
3
|
+
attr_accessor :prerender, :output, :asset
|
4
|
+
|
5
|
+
def initialize(file, options={}, attachment=nil)
|
6
|
+
@prerender = options[:prerender]
|
7
|
+
@size = options[:size]
|
8
|
+
@output = options[:output]
|
9
|
+
@asset = attachment ? attachment.instance : nil
|
10
|
+
@attachment = attachment
|
11
|
+
|
12
|
+
super
|
13
|
+
|
14
|
+
gravity = @asset.image_gravity? ? @asset.image_gravity : "Center"
|
15
|
+
|
16
|
+
@convert_options = [
|
17
|
+
"-gravity #{gravity}",
|
18
|
+
"-strip",
|
19
|
+
"-quality 95",
|
20
|
+
@convert_options
|
21
|
+
].flatten.compact
|
22
|
+
end
|
23
|
+
|
24
|
+
# Perform processing, if prerender == true or we've had to render
|
25
|
+
# this output before. Afterward, update our AssetOutput entry
|
26
|
+
def make
|
27
|
+
# do we have an AssetOutput already?
|
28
|
+
ao = @asset.outputs.where(output_id: @output).first
|
29
|
+
|
30
|
+
dst = nil
|
31
|
+
|
32
|
+
if @prerender || ao
|
33
|
+
if !ao
|
34
|
+
# register empty AssetObject to denote processing
|
35
|
+
ao = @asset.outputs.create(
|
36
|
+
:output_id => @output,
|
37
|
+
:image_fingerprint => @asset.image_fingerprint
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
if @size =~ /(\d+)?x?(\d+)?([\#>])?$/ && $~[3] == "#"
|
42
|
+
# crop... scale using dimensions as minimums, then crop to dimensions
|
43
|
+
scale = "-scale #{$~[1]}x#{$~[2]}^"
|
44
|
+
crop = "-crop #{$~[1]}x#{$~[2]}+0+0"
|
45
|
+
|
46
|
+
@convert_options = [
|
47
|
+
@convert_options.shift,
|
48
|
+
scale,
|
49
|
+
crop,
|
50
|
+
@convert_options
|
51
|
+
].flatten
|
52
|
+
else
|
53
|
+
# don't crop
|
54
|
+
scale = "-scale '#{$~[1]}x#{$~[2]}#{$~[3]}'"
|
55
|
+
@convert_options = [scale, @convert_options].flatten
|
56
|
+
end
|
57
|
+
|
58
|
+
# call thumbnail generator
|
59
|
+
dst = super
|
60
|
+
|
61
|
+
# need to get dimensions
|
62
|
+
width = height = nil
|
63
|
+
|
64
|
+
begin
|
65
|
+
geo = Geometry.from_file(dst.path)
|
66
|
+
width = geo.width.to_i
|
67
|
+
height = geo.height.to_i
|
68
|
+
rescue NotIdentifiedByImageMagickError => e
|
69
|
+
# hmmm... do nothing?
|
70
|
+
end
|
71
|
+
|
72
|
+
# get fingerprint
|
73
|
+
print = Digest::MD5.hexdigest(dst.read)
|
74
|
+
dst.rewind if dst.respond_to?(:rewind)
|
75
|
+
|
76
|
+
ao.attributes = {
|
77
|
+
:fingerprint => print,
|
78
|
+
:width => width,
|
79
|
+
:height => height,
|
80
|
+
:image_fingerprint => @asset.image_fingerprint
|
81
|
+
}
|
82
|
+
|
83
|
+
ao.save
|
84
|
+
|
85
|
+
# just to be safe...
|
86
|
+
@asset.outputs(true)
|
87
|
+
end
|
88
|
+
|
89
|
+
dst
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
module Paperclip
|
2
|
+
class Attachment
|
3
|
+
# Overwrite styles loader to allow caching despite dynamic loading
|
4
|
+
def styles
|
5
|
+
styling_option = @options[:styles]
|
6
|
+
|
7
|
+
if !@normalized_styles
|
8
|
+
@normalized_styles = ActiveSupport::OrderedHash.new
|
9
|
+
styling_option.call(self).each do |name, args|
|
10
|
+
@normalized_styles[name] = Paperclip::Style.new(name, args.dup, self)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
@normalized_styles
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
# overwrite to only delete original when clear() is called. styles will
|
18
|
+
# be deleted by the thumbnailer
|
19
|
+
def queue_existing_for_delete #:nodoc:
|
20
|
+
return unless file?
|
21
|
+
|
22
|
+
@queued_for_delete = [path(:original)]
|
23
|
+
|
24
|
+
instance_write(:file_name, nil)
|
25
|
+
instance_write(:content_type, nil)
|
26
|
+
instance_write(:file_size, nil)
|
27
|
+
instance_write(:updated_at, nil)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
#----------
|
32
|
+
|
33
|
+
def delete_path(path)
|
34
|
+
@queued_for_delete = [ path ]
|
35
|
+
self.flush_deletes
|
36
|
+
end
|
37
|
+
|
38
|
+
#----------
|
39
|
+
|
40
|
+
def enqueue
|
41
|
+
# queue up any outputs that a) already exist or b) are set to prerender
|
42
|
+
styles = [
|
43
|
+
AssetHostCore::Output.where(prerender: true).map(&:code_sym),
|
44
|
+
self.instance.outputs.map { |ao| ao.output.code_sym }
|
45
|
+
].flatten.uniq
|
46
|
+
|
47
|
+
enqueue_styles(*styles)
|
48
|
+
end
|
49
|
+
|
50
|
+
def enqueue_styles(*styles)
|
51
|
+
Resque.enqueue(
|
52
|
+
AssetHostCore::ResqueJob,
|
53
|
+
self.instance.class.name,
|
54
|
+
self.instance.id,
|
55
|
+
self.name,
|
56
|
+
styles
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
#----------
|
61
|
+
|
62
|
+
def width(style = default_style)
|
63
|
+
return nil if !self.instance_read("width")
|
64
|
+
|
65
|
+
if s = self.styles[style]
|
66
|
+
# load dimensions
|
67
|
+
if ao = self.instance.output_by_style(style)
|
68
|
+
return ao.width
|
69
|
+
else
|
70
|
+
# TODO: Need to add code to guess dimensions if we don't yet have an output
|
71
|
+
g = Paperclip::Geometry.parse(s.processor_options[:size])
|
72
|
+
if g.modifier == '#'
|
73
|
+
if g.square?
|
74
|
+
# match w/h from style
|
75
|
+
return g.width.to_i
|
76
|
+
else
|
77
|
+
return g.width.to_i
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
factor = self._compute_style_ratio(s)
|
82
|
+
width = ((self.instance_read("width") || 0) * factor).round
|
83
|
+
return width < self.instance_read("width") ? width : self.instance_read("width")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
|
90
|
+
#----------
|
91
|
+
|
92
|
+
def height(style = default_style)
|
93
|
+
return nil if !self.instance_read("height")
|
94
|
+
|
95
|
+
if s = self.styles[style]
|
96
|
+
# load dimensions
|
97
|
+
if ao = self.instance.output_by_style(style)
|
98
|
+
return ao.height
|
99
|
+
else
|
100
|
+
# TODO: Need to add code to guess dimensions if we don't yet have an output
|
101
|
+
g = Paperclip::Geometry.parse(s.processor_options[:size])
|
102
|
+
if g.modifier == '#'
|
103
|
+
# match w/h from style
|
104
|
+
return g.height.to_i
|
105
|
+
end
|
106
|
+
|
107
|
+
factor = self._compute_style_ratio(s)
|
108
|
+
height = ((self.instance_read("height") || 0) * factor).round
|
109
|
+
|
110
|
+
return height < self.instance_read("height") ? height : self.instance_read("height")
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
|
118
|
+
#----------
|
119
|
+
|
120
|
+
def isPortrait?
|
121
|
+
w = self.instance_read("width")
|
122
|
+
h = self.instance_read("height")
|
123
|
+
|
124
|
+
h > w
|
125
|
+
end
|
126
|
+
|
127
|
+
#----------
|
128
|
+
|
129
|
+
def _compute_style_ratio(style)
|
130
|
+
w = self.instance_read("width")
|
131
|
+
h = self.instance_read("height")
|
132
|
+
|
133
|
+
return 0 if !w || !h
|
134
|
+
|
135
|
+
g = Paperclip::Geometry.parse(style.processor_options[:size])
|
136
|
+
ratio = Paperclip::Geometry.new( g.width/w, g.height/h )
|
137
|
+
|
138
|
+
# we need to compute off the smaller number
|
139
|
+
factor = (ratio.width > ratio.height) ? ratio.height : ratio.width
|
140
|
+
end
|
141
|
+
|
142
|
+
#----------
|
143
|
+
|
144
|
+
def tags(args = {})
|
145
|
+
tags = {}
|
146
|
+
|
147
|
+
self.styles.each do |style,v|
|
148
|
+
tags[style] = self.tag(style,args)
|
149
|
+
end
|
150
|
+
|
151
|
+
tags
|
152
|
+
end
|
153
|
+
|
154
|
+
#----------
|
155
|
+
|
156
|
+
def tag(style = default_style, args={})
|
157
|
+
s = self.styles[style.to_sym]
|
158
|
+
return nil if !s
|
159
|
+
|
160
|
+
if (s.instance_variable_get :@other_args)[:rich] && self.instance.native
|
161
|
+
args = args.merge(self.instance.native.attrs)
|
162
|
+
end
|
163
|
+
|
164
|
+
htmlargs = args.collect { |k,v| %Q!#{k}="#{v}"! }.join(" ")
|
165
|
+
|
166
|
+
%Q(<img src="#{self.url(style)}" width="#{self.width(style)}" height="#{self.height(style)}" alt="#{self.instance.title.to_s.gsub('"', ERB::Util::HTML_ESCAPE['"'])}" #{htmlargs}/>).html_safe
|
167
|
+
end
|
168
|
+
|
169
|
+
#----------
|
170
|
+
|
171
|
+
def write_exif_data
|
172
|
+
return unless @queued_for_write[:original]
|
173
|
+
|
174
|
+
p = ::MiniExiftool.new(@queued_for_write[:original].path)
|
175
|
+
|
176
|
+
# -- determine metadata -- #
|
177
|
+
|
178
|
+
if p.credit =~ /Getty Images/
|
179
|
+
# smart import for Getty Images photos
|
180
|
+
copyright = [p.by_line,p.credit].join("/")
|
181
|
+
title = p.headline
|
182
|
+
description = p.description
|
183
|
+
|
184
|
+
elsif p.credit =~ /AP/
|
185
|
+
# smart import for AP photos
|
186
|
+
copyright = [p.by_line,p.credit].join("/")
|
187
|
+
title = p.title
|
188
|
+
description = p.description
|
189
|
+
|
190
|
+
else
|
191
|
+
copyright = p.byline || p.credit
|
192
|
+
title = p.title
|
193
|
+
description = p.description
|
194
|
+
end
|
195
|
+
|
196
|
+
instance_write(:width, p.image_width)
|
197
|
+
instance_write(:height, p.image_height)
|
198
|
+
instance_write(:title, title)
|
199
|
+
instance_write(:description, description)
|
200
|
+
instance_write(:copyright, copyright)
|
201
|
+
instance_write(:taken, p.datetime_original)
|
202
|
+
|
203
|
+
true
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|