paperclip-youtube 2.3.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +26 -0
- data/README.md +91 -0
- data/Rakefile +80 -0
- data/generators/paperclip/USAGE +5 -0
- data/generators/paperclip/paperclip_generator.rb +27 -0
- data/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
- data/init.rb +1 -0
- data/lib/generators/paperclip/USAGE +8 -0
- data/lib/generators/paperclip/paperclip_generator.rb +31 -0
- data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
- data/lib/paperclip.rb +378 -0
- data/lib/paperclip/attachment.rb +376 -0
- data/lib/paperclip/callback_compatability.rb +61 -0
- data/lib/paperclip/command_line.rb +86 -0
- data/lib/paperclip/geometry.rb +115 -0
- data/lib/paperclip/interpolations.rb +130 -0
- data/lib/paperclip/iostream.rb +45 -0
- data/lib/paperclip/matchers.rb +33 -0
- data/lib/paperclip/matchers/have_attached_file_matcher.rb +57 -0
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +75 -0
- data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +54 -0
- data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +95 -0
- data/lib/paperclip/processor.rb +58 -0
- data/lib/paperclip/railtie.rb +24 -0
- data/lib/paperclip/storage.rb +3 -0
- data/lib/paperclip/storage/filesystem.rb +73 -0
- data/lib/paperclip/storage/s3.rb +192 -0
- data/lib/paperclip/storage/youtube.rb +331 -0
- data/lib/paperclip/style.rb +90 -0
- data/lib/paperclip/thumbnail.rb +79 -0
- data/lib/paperclip/upfile.rb +55 -0
- data/lib/paperclip/version.rb +3 -0
- data/lib/tasks/paperclip.rake +72 -0
- data/rails/init.rb +2 -0
- data/shoulda_macros/paperclip.rb +118 -0
- data/test/attachment_test.rb +921 -0
- data/test/command_line_test.rb +138 -0
- data/test/database.yml +4 -0
- data/test/fixtures/12k.png +0 -0
- data/test/fixtures/50x50.png +0 -0
- data/test/fixtures/5k.png +0 -0
- data/test/fixtures/bad.png +1 -0
- data/test/fixtures/s3.yml +8 -0
- data/test/fixtures/text.txt +0 -0
- data/test/fixtures/twopage.pdf +0 -0
- data/test/fixtures/uppercase.PNG +0 -0
- data/test/geometry_test.rb +177 -0
- data/test/helper.rb +146 -0
- data/test/integration_test.rb +570 -0
- data/test/interpolations_test.rb +143 -0
- data/test/iostream_test.rb +71 -0
- data/test/matchers/have_attached_file_matcher_test.rb +24 -0
- data/test/matchers/validate_attachment_content_type_matcher_test.rb +47 -0
- data/test/matchers/validate_attachment_presence_matcher_test.rb +26 -0
- data/test/matchers/validate_attachment_size_matcher_test.rb +51 -0
- data/test/paperclip_test.rb +301 -0
- data/test/processor_test.rb +10 -0
- data/test/storage_test.rb +386 -0
- data/test/style_test.rb +141 -0
- data/test/thumbnail_test.rb +227 -0
- data/test/upfile_test.rb +36 -0
- metadata +195 -0
@@ -0,0 +1,331 @@
|
|
1
|
+
module Paperclip
|
2
|
+
module Interpolations
|
3
|
+
# Returns the youtube_id of the instance.
|
4
|
+
def youtube_id attachment, style
|
5
|
+
attachment.instance.youtube_id
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module Storage
|
10
|
+
# Youtube video hosting, easy place to share videos for
|
11
|
+
# You can find at http://www.youtube.com/
|
12
|
+
# There are a few S3-specific options for has_attached_file:
|
13
|
+
# * +youtube_credentials+: Takes a path, a File, or a Hash. The path (or File) must point
|
14
|
+
# to a YAML file containing the +login_name+ and +login_password+ and +youttube_username+
|
15
|
+
# and +developer_key+ that you can got it from your account on Youtube.
|
16
|
+
# You can 'environment-space' this just like you do to your
|
17
|
+
# database.yml file, so different environments can use different accounts:
|
18
|
+
# development:
|
19
|
+
# login_name: dr-click...
|
20
|
+
# login_password: 123...
|
21
|
+
# production:
|
22
|
+
# login_name: dr-click...
|
23
|
+
# login_password: 123...
|
24
|
+
#
|
25
|
+
|
26
|
+
module Youtube
|
27
|
+
def self.extended base
|
28
|
+
begin
|
29
|
+
require 'net/http'
|
30
|
+
rescue LoadError => e
|
31
|
+
log("(Error) #{e.message}")
|
32
|
+
e.message << " (Can't find required library 'net/http')"
|
33
|
+
raise e
|
34
|
+
end
|
35
|
+
begin
|
36
|
+
require 'net/https'
|
37
|
+
rescue LoadError => e
|
38
|
+
log("(Error) #{e.message}")
|
39
|
+
e.message << " (Can't find required library 'net/https')"
|
40
|
+
raise e
|
41
|
+
end
|
42
|
+
begin
|
43
|
+
require 'mime/types'
|
44
|
+
rescue LoadError => e
|
45
|
+
log("(Error) #{e.message}")
|
46
|
+
e.message << " (You may need to install the mime-types gem)"
|
47
|
+
raise e
|
48
|
+
end
|
49
|
+
begin
|
50
|
+
require 'builder'
|
51
|
+
rescue LoadError => e
|
52
|
+
log("(Error) #{e.message}")
|
53
|
+
e.message << " (You may need to install the builder gem)"
|
54
|
+
raise e
|
55
|
+
end
|
56
|
+
begin
|
57
|
+
require 'rexml/document'
|
58
|
+
rescue LoadError => e
|
59
|
+
log("(Error) #{e.message}")
|
60
|
+
e.message << " (Can't find required library 'rexml/document')"
|
61
|
+
raise e
|
62
|
+
end
|
63
|
+
|
64
|
+
base.instance_eval do
|
65
|
+
@youtube_options = @options[:youtube_options] || {}
|
66
|
+
|
67
|
+
@developer_key = @options[:developer_key] || @youtube_options[:developer_key]
|
68
|
+
@login_name = @options[:login_name] || @youtube_options[:login_name]
|
69
|
+
@login_password = @options[:login_password] || @youtube_options[:login_password]
|
70
|
+
@youttube_username = @options[:youttube_username] || @youtube_options[:youttube_username]
|
71
|
+
|
72
|
+
@auth_host = @options[:auth_host] || @youtube_options[:auth_host] || 'www.google.com'
|
73
|
+
@auth_path = @options[:auth_path] || @youtube_options[:auth_path] || '/youtube/accounts/ClientLogin'
|
74
|
+
|
75
|
+
@upload_host = @options[:upload_host] || @youtube_options[:upload_host] || 'uploads.gdata.youtube.com'
|
76
|
+
@upload_path = @options[:upload_path] || @youtube_options[:upload_path] || "/feeds/api/users/#{@youttube_username}/uploads"
|
77
|
+
|
78
|
+
@data_host = @options[:data_host] || @youtube_options[:data_host] || 'gdata.youtube.com'
|
79
|
+
end
|
80
|
+
|
81
|
+
Paperclip.interpolates(:youtube_url) do |attachment, style|
|
82
|
+
style = :thumbnail_1 if style == :thumbnail
|
83
|
+
if style == :original
|
84
|
+
'http://www.youtube.com/watch?v=:youtube_id'
|
85
|
+
else
|
86
|
+
"http://i.ytimg.com/vi/:youtube_id/#{style.to_s.split('_').last.to_i}.jpg"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def login_name
|
92
|
+
@login_name
|
93
|
+
end
|
94
|
+
def login_password
|
95
|
+
@login_password
|
96
|
+
end
|
97
|
+
def developer_key
|
98
|
+
@developer_key
|
99
|
+
end
|
100
|
+
def youttube_username
|
101
|
+
@youttube_username
|
102
|
+
end
|
103
|
+
def auth_host
|
104
|
+
@auth_host
|
105
|
+
end
|
106
|
+
def auth_path
|
107
|
+
@auth_path
|
108
|
+
end
|
109
|
+
def upload_host
|
110
|
+
@upload_host
|
111
|
+
end
|
112
|
+
def upload_path
|
113
|
+
@upload_path
|
114
|
+
end
|
115
|
+
def data_host
|
116
|
+
@data_host
|
117
|
+
end
|
118
|
+
def token
|
119
|
+
@token ||= begin
|
120
|
+
http = Net::HTTP.new("www.google.com", 443)
|
121
|
+
http.use_ssl = true
|
122
|
+
body = "Email=#{YoutubeChain.esc login_name}&Passwd=#{YoutubeChain.esc login_password}&service=youtube&source=#{YoutubeChain.esc youttube_username}"
|
123
|
+
response = http.post("/youtube/accounts/ClientLogin", body, "Content-Type" => "application/x-www-form-urlencoded")
|
124
|
+
raise response.body[/Error=(.+)/,1] if response.code.to_i != 200
|
125
|
+
@token = response.body[/Auth=(.+)/, 1]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def update_youtube_id(data)
|
130
|
+
doc = REXML::Document.new data
|
131
|
+
id = doc.root.elements["id"].text.split("/").last if doc && doc.root
|
132
|
+
|
133
|
+
if id
|
134
|
+
video = Video.find self.instance.id
|
135
|
+
video.update_attribute(:youtube_id, id)
|
136
|
+
else
|
137
|
+
log("(Error) Video has no id, please reupload to Youtube.")
|
138
|
+
raise "Video has no id, please reupload to Youtube."
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def youtube_delete
|
143
|
+
http = Net::HTTP.new(data_host)
|
144
|
+
headers = {
|
145
|
+
'Content-Type' => 'application/atom+xml',
|
146
|
+
'Authorization' => "GoogleLogin auth=#{token}",
|
147
|
+
'GData-Version' => '2',
|
148
|
+
'X-GData-Key' => "key=#{developer_key}"
|
149
|
+
}
|
150
|
+
|
151
|
+
resp = http.delete(upload_path+"/#{self.instance.youtube_id}", headers)
|
152
|
+
if resp.code != "200"
|
153
|
+
log("(Error) Couldn't delete the video from Youtube")
|
154
|
+
raise "Couldn't delete the video from Youtube"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def exists?(style_name = default_style)
|
159
|
+
http = Net::HTTP.new(data_host)
|
160
|
+
headers = {
|
161
|
+
'Content-Type' => 'application/atom+xml',
|
162
|
+
'Authorization' => "GoogleLogin auth=#{token}",
|
163
|
+
'GData-Version' => '2',
|
164
|
+
'X-GData-Key' => "key=#{developer_key}"
|
165
|
+
}
|
166
|
+
|
167
|
+
resp= http.get(upload_path+"/#{self.instance.youtube_id}", headers)
|
168
|
+
if resp.code == "200"
|
169
|
+
return true
|
170
|
+
elsif
|
171
|
+
log("(Error) Couldn't find the video '#{self.instance.youtube_id}'")
|
172
|
+
return false
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Returns representation of the data of the file assigned to the given
|
177
|
+
# style, in the format most representative of the current storage.
|
178
|
+
def to_file style_name = default_style
|
179
|
+
end
|
180
|
+
|
181
|
+
def boundary
|
182
|
+
"An43094fu"
|
183
|
+
end
|
184
|
+
|
185
|
+
def request_xml(opts)
|
186
|
+
b = Builder::XmlMarkup.new
|
187
|
+
b.instruct!
|
188
|
+
b.entry(:xmlns => "http://www.w3.org/2005/Atom", 'xmlns:media' => "http://search.yahoo.com/mrss/", 'xmlns:yt' => "http://gdata.youtube.com/schemas/2007") do | m |
|
189
|
+
m.tag!("media:group") do | mg |
|
190
|
+
mg.tag!("media:title", opts[:title], :type => "plain")
|
191
|
+
mg.tag!("media:description", opts[:description], :type => "plain")
|
192
|
+
mg.tag!("media:keywords", opts[:keywords].join(","))
|
193
|
+
mg.tag!('media:category', opts[:category], :scheme => "http://gdata.youtube.com/schemas/2007/categories.cat")
|
194
|
+
mg.tag!('yt:private') if opts[:private]
|
195
|
+
end
|
196
|
+
end.to_s
|
197
|
+
end
|
198
|
+
|
199
|
+
def request_io(data, opts)
|
200
|
+
post_body = [
|
201
|
+
"--#{boundary}\r\n",
|
202
|
+
"Content-Type: application/atom+xml; charset=UTF-8\r\n\r\n",
|
203
|
+
request_xml(opts),
|
204
|
+
"\r\n--#{boundary}\r\n",
|
205
|
+
"Content-Type: #{opts[:mime_type]}\r\nContent-Transfer-Encoding: binary\r\n\r\n",
|
206
|
+
data,
|
207
|
+
"\r\n--#{boundary}--\r\n",
|
208
|
+
]
|
209
|
+
|
210
|
+
YoutubeChain.new(post_body)
|
211
|
+
end
|
212
|
+
|
213
|
+
def authorization_headers
|
214
|
+
{
|
215
|
+
"Authorization" => "GoogleLogin auth=#{token}",
|
216
|
+
"X-GData-Client" => "#{youttube_username}",
|
217
|
+
"X-GData-Key" => "key=#{developer_key}"
|
218
|
+
}
|
219
|
+
end
|
220
|
+
|
221
|
+
def youtube_upload(data, video_file)
|
222
|
+
opts = { :mime_type => MIME::Types.type_for(video_file).join(),
|
223
|
+
:title => self.instance.respond_to?(:title) && !self.instance.title.blank? ? self.instance.title.blank? : video_file,
|
224
|
+
:description => self.instance.respond_to?(:description) && !self.instance.description.blank? ? self.instance.description.blank? : video_file,
|
225
|
+
:category => 'People',
|
226
|
+
:keywords => [],
|
227
|
+
:filename => video_file}
|
228
|
+
|
229
|
+
post_body_io = request_io(data, opts)
|
230
|
+
upload_headers = authorization_headers.merge({
|
231
|
+
"Slug" => "#{opts[:filename]}",
|
232
|
+
"Content-Type" => "multipart/related; boundary=#{boundary}",
|
233
|
+
"Content-Length" => "#{post_body_io.expected_length}"
|
234
|
+
})
|
235
|
+
|
236
|
+
path = upload_path
|
237
|
+
|
238
|
+
Net::HTTP.start(upload_host) do | session |
|
239
|
+
post = Net::HTTP::Post.new(path, upload_headers)
|
240
|
+
post.body_stream = post_body_io
|
241
|
+
response = session.request(post)
|
242
|
+
|
243
|
+
if response.code == "201"
|
244
|
+
update_youtube_id response.body
|
245
|
+
else
|
246
|
+
log("(Error) Couldn't upload the video to Youtube >> #{response.body}")
|
247
|
+
raise "Couldn't upload the video to Youtube >> #{response.body}"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def flush_writes #:nodoc:
|
253
|
+
@queued_for_write.each do |style, file|
|
254
|
+
begin
|
255
|
+
log("Uploading to youtube : #{self.instance.media_file_name}")
|
256
|
+
youtube_upload(File.open(file.path), self.instance.media_file_name)
|
257
|
+
rescue => e
|
258
|
+
log("(Error) #{e.message} - #{e.backtrace.inspect}")
|
259
|
+
raise e.message
|
260
|
+
end
|
261
|
+
end
|
262
|
+
@queued_for_write = {}
|
263
|
+
end
|
264
|
+
|
265
|
+
def flush_deletes #:nodoc:
|
266
|
+
@queued_for_delete.each do |path|
|
267
|
+
begin
|
268
|
+
log("deleting from youtube : #{self.instance.youtube_id}")
|
269
|
+
youtube_delete
|
270
|
+
rescue => e
|
271
|
+
log("(Error) #{e.message}")
|
272
|
+
raise e.message
|
273
|
+
end
|
274
|
+
end
|
275
|
+
@queued_for_delete = []
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
|
284
|
+
class YoutubeChain
|
285
|
+
attr_accessor :autoclose
|
286
|
+
def self.esc(s)
|
287
|
+
s.to_s.gsub(/[^ \w.-]+/n){'%'+($&.unpack('H2'*$&.size)*'%').upcase}.tr(' ', '+')
|
288
|
+
end
|
289
|
+
|
290
|
+
def initialize(*any_ios)
|
291
|
+
@autoclose = true
|
292
|
+
@chain = any_ios.flatten.map{|e| e.respond_to?(:read) ? e : StringIO.new(e.to_s) }
|
293
|
+
end
|
294
|
+
|
295
|
+
def read(buffer_size = 1024)
|
296
|
+
current_io = @chain.shift
|
297
|
+
return false if !current_io
|
298
|
+
|
299
|
+
buf = current_io.read(buffer_size)
|
300
|
+
if !buf && @chain.empty? # End of streams
|
301
|
+
release_handle(current_io) if @autoclose
|
302
|
+
false
|
303
|
+
elsif !buf # This IO is depleted, but next one is available
|
304
|
+
release_handle(current_io) if @autoclose
|
305
|
+
read(buffer_size)
|
306
|
+
elsif buf.length < buffer_size # This IO is depleted, but we were asked for more
|
307
|
+
release_handle(current_io) if @autoclose
|
308
|
+
buf + (read(buffer_size - buf.length) || '') # and recurse
|
309
|
+
else # just return the buffer
|
310
|
+
@chain.unshift(current_io) # put the current back
|
311
|
+
buf
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def expected_length
|
316
|
+
@chain.inject(0) do | len, io |
|
317
|
+
if io.respond_to?(:length)
|
318
|
+
len + (io.length - io.pos)
|
319
|
+
elsif io.is_a?(File)
|
320
|
+
len + File.size(io.path) - io.pos
|
321
|
+
else
|
322
|
+
raise "Cannot predict length of #{io.inspect}"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
private
|
328
|
+
def release_handle(io)
|
329
|
+
io.close if io.respond_to?(:close)
|
330
|
+
end
|
331
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Paperclip
|
3
|
+
# The Style class holds the definition of a thumbnail style, applying
|
4
|
+
# whatever processing is required to normalize the definition and delaying
|
5
|
+
# the evaluation of block parameters until useful context is available.
|
6
|
+
|
7
|
+
class Style
|
8
|
+
|
9
|
+
attr_reader :name, :attachment, :format
|
10
|
+
|
11
|
+
# Creates a Style object. +name+ is the name of the attachment,
|
12
|
+
# +definition+ is the style definition from has_attached_file, which
|
13
|
+
# can be string, array or hash
|
14
|
+
def initialize name, definition, attachment
|
15
|
+
@name = name
|
16
|
+
@attachment = attachment
|
17
|
+
if definition.is_a? Hash
|
18
|
+
@geometry = definition.delete(:geometry)
|
19
|
+
@format = definition.delete(:format)
|
20
|
+
@processors = definition.delete(:processors)
|
21
|
+
@other_args = definition
|
22
|
+
else
|
23
|
+
@geometry, @format = [definition, nil].flatten[0..1]
|
24
|
+
@other_args = {}
|
25
|
+
end
|
26
|
+
@format = nil if @format.blank?
|
27
|
+
end
|
28
|
+
|
29
|
+
# retrieves from the attachment the processors defined in the has_attached_file call
|
30
|
+
# (which method (in the attachment) will call any supplied procs)
|
31
|
+
# There is an important change of interface here: a style rule can set its own processors
|
32
|
+
# by default we behave as before, though.
|
33
|
+
def processors
|
34
|
+
@processors || attachment.processors
|
35
|
+
end
|
36
|
+
|
37
|
+
# retrieves from the attachment the whiny setting
|
38
|
+
def whiny
|
39
|
+
attachment.whiny
|
40
|
+
end
|
41
|
+
|
42
|
+
# returns true if we're inclined to grumble
|
43
|
+
def whiny?
|
44
|
+
!!whiny
|
45
|
+
end
|
46
|
+
|
47
|
+
def convert_options
|
48
|
+
attachment.send(:extra_options_for, name)
|
49
|
+
end
|
50
|
+
|
51
|
+
# returns the geometry string for this style
|
52
|
+
# if a proc has been supplied, we call it here
|
53
|
+
def geometry
|
54
|
+
@geometry.respond_to?(:call) ? @geometry.call(attachment.instance) : @geometry
|
55
|
+
end
|
56
|
+
|
57
|
+
# Supplies the hash of options that processors expect to receive as their second argument
|
58
|
+
# Arguments other than the standard geometry, format etc are just passed through from
|
59
|
+
# initialization and any procs are called here, just before post-processing.
|
60
|
+
def processor_options
|
61
|
+
args = {}
|
62
|
+
@other_args.each do |k,v|
|
63
|
+
args[k] = v.respond_to?(:call) ? v.call(attachment) : v
|
64
|
+
end
|
65
|
+
[:processors, :geometry, :format, :whiny, :convert_options].each do |k|
|
66
|
+
(arg = send(k)) && args[k] = arg
|
67
|
+
end
|
68
|
+
args
|
69
|
+
end
|
70
|
+
|
71
|
+
# Supports getting and setting style properties with hash notation to ensure backwards-compatibility
|
72
|
+
# eg. @attachment.styles[:large][:geometry]@ will still work
|
73
|
+
def [](key)
|
74
|
+
if [:name, :convert_options, :whiny, :processors, :geometry, :format].include?(key)
|
75
|
+
send(key)
|
76
|
+
elsif defined? @other_args[key]
|
77
|
+
@other_args[key]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def []=(key, value)
|
82
|
+
if [:name, :convert_options, :whiny, :processors, :geometry, :format].include?(key)
|
83
|
+
send("#{key}=".intern, value)
|
84
|
+
else
|
85
|
+
@other_args[key] = value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Paperclip
|
2
|
+
# Handles thumbnailing images that are uploaded.
|
3
|
+
class Thumbnail < Processor
|
4
|
+
|
5
|
+
attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options, :source_file_options
|
6
|
+
|
7
|
+
# Creates a Thumbnail object set to work on the +file+ given. It
|
8
|
+
# will attempt to transform the image into one defined by +target_geometry+
|
9
|
+
# which is a "WxH"-style string. +format+ will be inferred from the +file+
|
10
|
+
# unless specified. Thumbnail creation will raise no errors unless
|
11
|
+
# +whiny+ is true (which it is, by default. If +convert_options+ is
|
12
|
+
# set, the options will be appended to the convert command upon image conversion
|
13
|
+
def initialize file, options = {}, attachment = nil
|
14
|
+
super
|
15
|
+
|
16
|
+
geometry = options[:geometry]
|
17
|
+
@file = file
|
18
|
+
@crop = geometry[-1,1] == '#'
|
19
|
+
@target_geometry = Geometry.parse geometry
|
20
|
+
@current_geometry = Geometry.from_file @file
|
21
|
+
@source_file_options = options[:source_file_options]
|
22
|
+
@convert_options = options[:convert_options]
|
23
|
+
@whiny = options[:whiny].nil? ? true : options[:whiny]
|
24
|
+
@format = options[:format]
|
25
|
+
|
26
|
+
@source_file_options = @source_file_options.split(/\s+/) if @source_file_options.respond_to?(:split)
|
27
|
+
@convert_options = @convert_options.split(/\s+/) if @convert_options.respond_to?(:split)
|
28
|
+
|
29
|
+
@current_format = File.extname(@file.path)
|
30
|
+
@basename = File.basename(@file.path, @current_format)
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns true if the +target_geometry+ is meant to crop.
|
35
|
+
def crop?
|
36
|
+
@crop
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns true if the image is meant to make use of additional convert options.
|
40
|
+
def convert_options?
|
41
|
+
!@convert_options.nil? && !@convert_options.empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
# Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile
|
45
|
+
# that contains the new image.
|
46
|
+
def make
|
47
|
+
src = @file
|
48
|
+
dst = Tempfile.new([@basename, @format ? ".#{@format}" : ''])
|
49
|
+
dst.binmode
|
50
|
+
|
51
|
+
begin
|
52
|
+
parameters = []
|
53
|
+
parameters << source_file_options
|
54
|
+
parameters << ":source"
|
55
|
+
parameters << transformation_command
|
56
|
+
parameters << convert_options
|
57
|
+
parameters << ":dest"
|
58
|
+
|
59
|
+
parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
|
60
|
+
|
61
|
+
success = Paperclip.run("convert", parameters, :source => "#{File.expand_path(src.path)}[0]", :dest => File.expand_path(dst.path))
|
62
|
+
rescue PaperclipCommandLineError => e
|
63
|
+
raise PaperclipError, "There was an error processing the thumbnail for #{@basename}" if @whiny
|
64
|
+
end
|
65
|
+
|
66
|
+
dst
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the command ImageMagick's +convert+ needs to transform the image
|
70
|
+
# into the thumbnail.
|
71
|
+
def transformation_command
|
72
|
+
scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
|
73
|
+
trans = []
|
74
|
+
trans << "-resize" << %["#{scale}"] unless scale.nil? || scale.empty?
|
75
|
+
trans << "-crop" << %["#{crop}"] << "+repage" if crop
|
76
|
+
trans
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|