paperclip-youtube 2.3.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|