cloudinary 1.0.82 → 1.0.83
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +58 -7
- data/CHANGELOG +13 -0
- data/Gemfile +1 -1
- data/cloudinary.gemspec +13 -3
- data/lib/cloudinary/active_support/core_ext/hash/keys.rb +166 -0
- data/lib/cloudinary/active_support/core_ext/hash/readme.md +1 -0
- data/lib/cloudinary/api.rb +2 -1
- data/lib/cloudinary/carrier_wave/preloaded.rb +1 -1
- data/lib/cloudinary/helper.rb +38 -21
- data/lib/cloudinary/missing.rb +11 -10
- data/lib/cloudinary/uploader.rb +18 -20
- data/lib/cloudinary/utils.rb +187 -75
- data/lib/cloudinary/version.rb +1 -1
- data/lib/cloudinary/video_helper.rb +126 -0
- data/spec/api_spec.rb +23 -22
- data/spec/cloudinary_helper_spec.rb +34 -20
- data/spec/spec_helper.rb +75 -5
- data/spec/uploader_spec.rb +32 -3
- data/spec/utils_methods_spec.rb +18 -0
- data/spec/utils_spec.rb +73 -78
- data/spec/video_tag_spec.rb +186 -0
- data/spec/video_url_spec.rb +169 -0
- data/vendor/assets/html/cloudinary_cors.html +47 -0
- data/vendor/assets/javascripts/cloudinary/canvas-to-blob.min.js +1 -0
- data/vendor/assets/javascripts/cloudinary/index.js +4 -0
- data/vendor/assets/javascripts/cloudinary/jquery.cloudinary.js +898 -0
- data/vendor/assets/javascripts/cloudinary/jquery.fileupload-image.js +315 -0
- data/vendor/assets/javascripts/cloudinary/jquery.fileupload-process.js +172 -0
- data/vendor/assets/javascripts/cloudinary/jquery.fileupload-validate.js +119 -0
- data/vendor/assets/javascripts/cloudinary/jquery.fileupload.js +1460 -0
- data/vendor/assets/javascripts/cloudinary/jquery.iframe-transport.js +214 -0
- data/vendor/assets/javascripts/cloudinary/jquery.ui.widget.js +558 -0
- data/vendor/assets/javascripts/cloudinary/load-image.min.js +1 -0
- data/vendor/assets/javascripts/cloudinary/processing.js +5 -0
- metadata +44 -7
data/lib/cloudinary/uploader.rb
CHANGED
@@ -71,7 +71,7 @@ class Cloudinary::Uploader
|
|
71
71
|
params = build_upload_params(options)
|
72
72
|
if file.is_a?(Pathname)
|
73
73
|
params[:file] = File.open(file, "rb")
|
74
|
-
elsif file.respond_to?(:read) || file =~ /^https?:|^s3:|^data:[^;]*;base64,([a-zA-Z0-9\/+\n=]+)$/
|
74
|
+
elsif file.respond_to?(:read) || file =~ /^ftp:|^https?:|^s3:|^data:[^;]*;base64,([a-zA-Z0-9\/+\n=]+)$/
|
75
75
|
params[:file] = file
|
76
76
|
else
|
77
77
|
params[:file] = File.open(file, "rb")
|
@@ -80,7 +80,7 @@ class Cloudinary::Uploader
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
-
# Upload large
|
83
|
+
# Upload large files. Note that public_id should include an extension for best results.
|
84
84
|
def self.upload_large(file, public_id_or_options={}, old_options={})
|
85
85
|
if public_id_or_options.is_a?(Hash)
|
86
86
|
options = public_id_or_options
|
@@ -96,11 +96,13 @@ class Cloudinary::Uploader
|
|
96
96
|
filename = "cloudinaryfile"
|
97
97
|
end
|
98
98
|
upload = upload_id = nil
|
99
|
-
index =
|
99
|
+
index = 0
|
100
|
+
chunk_size = options[:chunk_size] || 20_000_000
|
100
101
|
while !file.eof?
|
101
|
-
buffer = file.read(
|
102
|
-
|
103
|
-
|
102
|
+
buffer = file.read(chunk_size)
|
103
|
+
current_loc = index*chunk_size
|
104
|
+
range = "bytes #{current_loc}-#{current_loc+buffer.size - 1}/#{file.size}"
|
105
|
+
upload = upload_large_part(Cloudinary::Blob.new(buffer, :original_filename=>filename), options.merge(:public_id=>public_id, :content_range=>range))
|
104
106
|
public_id = upload["public_id"]
|
105
107
|
index += 1
|
106
108
|
end
|
@@ -108,19 +110,11 @@ class Cloudinary::Uploader
|
|
108
110
|
end
|
109
111
|
|
110
112
|
|
111
|
-
# Upload large
|
113
|
+
# Upload large files. Note that public_id should include an extension for best results.
|
112
114
|
def self.upload_large_part(file, options={})
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
:type=>options[:type],
|
117
|
-
:public_id=>options[:public_id],
|
118
|
-
:backup=>options[:backup],
|
119
|
-
:final=>options[:final],
|
120
|
-
:part_number=>options[:part_number],
|
121
|
-
:tags=>options[:tags] && Cloudinary::Utils.build_array(options[:tags]).join(","),
|
122
|
-
:upload_id=>options[:upload_id]
|
123
|
-
}
|
115
|
+
options[:resource_type] ||= :raw
|
116
|
+
call_api("upload_chunked", options) do
|
117
|
+
params = build_upload_params(options)
|
124
118
|
if file.is_a?(Pathname) || !file.respond_to?(:read)
|
125
119
|
params[:file] = File.open(file, "rb")
|
126
120
|
else
|
@@ -171,6 +165,8 @@ class Cloudinary::Uploader
|
|
171
165
|
:public_id=> public_id,
|
172
166
|
:callback=> options[:callback],
|
173
167
|
:eager=>build_eager(options[:eager]),
|
168
|
+
:eager_notification_url=>options[:eager_notification_url],
|
169
|
+
:eager_async=>Cloudinary::Utils.as_safe_bool(options[:eager_async]),
|
174
170
|
:headers=>build_custom_headers(options[:headers]),
|
175
171
|
:tags=>options[:tags] && Cloudinary::Utils.build_array(options[:tags]).join(","),
|
176
172
|
:face_coordinates => options[:face_coordinates] && Cloudinary::Utils.encode_double_array(options[:face_coordinates])
|
@@ -277,12 +273,14 @@ class Cloudinary::Uploader
|
|
277
273
|
params[:signature] = Cloudinary::Utils.api_sign_request(params.reject{|k,v| non_signable.include?(k)}, api_secret)
|
278
274
|
params[:api_key] = api_key
|
279
275
|
end
|
276
|
+
timeout = options[:timeout] || Cloudinary.config.timeout || 60
|
280
277
|
|
281
278
|
result = nil
|
282
279
|
|
283
280
|
api_url = Cloudinary::Utils.cloudinary_api_url(action, options)
|
284
|
-
|
285
|
-
|
281
|
+
headers = {"User-Agent" => Cloudinary::USER_AGENT}
|
282
|
+
headers['Content-Range'] = options[:content_range] if options[:content_range]
|
283
|
+
RestClient::Request.execute(:method => :post, :url => api_url, :payload => params.reject{|k, v| v.nil? || v==""}, :timeout=> timeout, :headers => headers) do
|
286
284
|
|response, request, tmpresult|
|
287
285
|
raise CloudinaryException, "Server returned unexpected status code - #{response.code} - #{response.body}" if ![200,400,401,403,404,500].include?(response.code)
|
288
286
|
begin
|
data/lib/cloudinary/utils.rb
CHANGED
@@ -6,9 +6,9 @@ require 'aws_cf_signer'
|
|
6
6
|
|
7
7
|
class Cloudinary::Utils
|
8
8
|
# @deprecated Use Cloudinary::SHARED_CDN
|
9
|
-
SHARED_CDN = Cloudinary::SHARED_CDN
|
9
|
+
SHARED_CDN = Cloudinary::SHARED_CDN
|
10
10
|
DEFAULT_RESPONSIVE_WIDTH_TRANSFORMATION = {:width => :auto, :crop => :limit}
|
11
|
-
|
11
|
+
|
12
12
|
# Warning: options are being destructively updated!
|
13
13
|
def self.generate_transformation_string(options={})
|
14
14
|
if options.is_a?(Array)
|
@@ -16,21 +16,21 @@ class Cloudinary::Utils
|
|
16
16
|
end
|
17
17
|
# Symbolize keys
|
18
18
|
options.keys.each do |key|
|
19
|
-
options[key.to_sym] = options.delete(key)
|
19
|
+
options[(key.to_sym rescue key)] = options.delete(key)
|
20
20
|
end
|
21
21
|
|
22
22
|
responsive_width = config_option_consume(options, :responsive_width)
|
23
23
|
size = options.delete(:size)
|
24
|
-
options[:width], options[:height] = size.split("x") if size
|
24
|
+
options[:width], options[:height] = size.split("x") if size
|
25
25
|
width = options[:width]
|
26
26
|
width = width.to_s if width.is_a?(Symbol)
|
27
27
|
height = options[:height]
|
28
|
-
has_layer =
|
29
|
-
|
28
|
+
has_layer = options[:overlay].present? || options[:underlay].present?
|
29
|
+
|
30
30
|
crop = options.delete(:crop)
|
31
31
|
angle = build_array(options.delete(:angle)).join(".")
|
32
32
|
|
33
|
-
no_html_sizes = has_layer ||
|
33
|
+
no_html_sizes = has_layer || angle.present? || crop.to_s == "fit" || crop.to_s == "limit" || crop.to_s == "lfill"
|
34
34
|
options.delete(:width) if width && (width.to_f < 1 || no_html_sizes || width == "auto" || responsive_width)
|
35
35
|
options.delete(:height) if height && (height.to_f < 1 || no_html_sizes || responsive_width)
|
36
36
|
|
@@ -41,40 +41,84 @@ class Cloudinary::Utils
|
|
41
41
|
|
42
42
|
color = options.delete(:color)
|
43
43
|
color = color.sub(/^#/, 'rgb:') if color
|
44
|
-
|
44
|
+
|
45
45
|
base_transformations = build_array(options.delete(:transformation))
|
46
46
|
if base_transformations.any?{|base_transformation| base_transformation.is_a?(Hash)}
|
47
47
|
base_transformations = base_transformations.map do
|
48
48
|
|base_transformation|
|
49
49
|
base_transformation.is_a?(Hash) ? generate_transformation_string(base_transformation.clone) : generate_transformation_string(:transformation=>base_transformation)
|
50
50
|
end
|
51
|
-
else
|
51
|
+
else
|
52
52
|
named_transformation = base_transformations.join(".")
|
53
53
|
base_transformations = []
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
effect = options.delete(:effect)
|
57
57
|
effect = Array(effect).flatten.join(":") if effect.is_a?(Array) || effect.is_a?(Hash)
|
58
|
-
|
58
|
+
|
59
59
|
border = options.delete(:border)
|
60
60
|
if border.is_a?(Hash)
|
61
61
|
border = "#{border[:width] || 2}px_solid_#{(border[:color] || "black").sub(/^#/, 'rgb:')}"
|
62
|
-
elsif border.to_s =~ /^\d+$/ # fallback to html border attribute
|
62
|
+
elsif border.to_s =~ /^\d+$/ # fallback to html border attribute
|
63
63
|
options[:border] = border
|
64
64
|
border = nil
|
65
65
|
end
|
66
66
|
flags = build_array(options.delete(:flags)).join(".")
|
67
67
|
dpr = config_option_consume(options, :dpr)
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
68
|
+
|
69
|
+
if options.include? :offset
|
70
|
+
options[:start_offset], options[:end_offset] = split_range options.delete(:offset)
|
71
|
+
end
|
72
|
+
|
73
|
+
params = {
|
74
|
+
:a => angle,
|
75
|
+
:b => background,
|
76
|
+
:bo => border,
|
77
|
+
:c => crop,
|
78
|
+
:co => color,
|
79
|
+
:dpr => dpr,
|
80
|
+
:e => effect,
|
81
|
+
:fl => flags,
|
82
|
+
:h => height,
|
83
|
+
:t => named_transformation,
|
84
|
+
:w => width
|
85
|
+
}
|
86
|
+
{
|
87
|
+
:ac => :audio_codec,
|
88
|
+
:br => :bit_rate,
|
89
|
+
:cs => :color_space,
|
90
|
+
:d => :default_image,
|
91
|
+
:dl => :delay,
|
92
|
+
:dn => :density,
|
93
|
+
:du => :duration,
|
94
|
+
:eo => :end_offset,
|
95
|
+
:f => :fetch_format,
|
96
|
+
:g => :gravity,
|
97
|
+
:l => :overlay,
|
98
|
+
:o => :opacity,
|
99
|
+
:p => :prefix,
|
100
|
+
:pg => :page,
|
101
|
+
:q => :quality,
|
102
|
+
:r => :radius,
|
103
|
+
:af => :audio_frequency,
|
104
|
+
:so => :start_offset,
|
105
|
+
:u => :underlay,
|
106
|
+
:vc => :video_codec,
|
107
|
+
:vs => :video_sampling,
|
108
|
+
:x => :x,
|
109
|
+
:y => :y,
|
110
|
+
:z => :zoom
|
72
111
|
}.each do
|
73
112
|
|param, option|
|
74
113
|
params[param] = options.delete(option)
|
75
|
-
end
|
114
|
+
end
|
115
|
+
|
116
|
+
params[:vc] = process_video_params params[:vc] if params[:vc].present?
|
117
|
+
[:so, :eo, :du].each do |range_value|
|
118
|
+
params[range_value] = norm_range_value params[range_value] if params[range_value].present?
|
119
|
+
end
|
76
120
|
|
77
|
-
transformation = params.reject{|
|
121
|
+
transformation = params.reject{|_k,v| v.blank?}.map{|k,v| "#{k}_#{v}"}.sort.join(",")
|
78
122
|
raw_transformation = options.delete(:raw_transformation)
|
79
123
|
transformation = [transformation, raw_transformation].reject(&:blank?).join(",")
|
80
124
|
transformations = base_transformations << transformation
|
@@ -90,13 +134,13 @@ class Cloudinary::Utils
|
|
90
134
|
options[:hidpi] = true
|
91
135
|
end
|
92
136
|
|
93
|
-
transformations.reject(&:blank?).join("/")
|
137
|
+
transformations.reject(&:blank?).join("/")
|
94
138
|
end
|
95
|
-
|
139
|
+
|
96
140
|
def self.api_string_to_sign(params_to_sign)
|
97
141
|
params_to_sign.map{|k,v| [k.to_s, v.is_a?(Array) ? v.join(",") : v]}.reject{|k,v| v.nil? || v == ""}.sort_by(&:first).map{|k,v| "#{k}=#{v}"}.join("&")
|
98
142
|
end
|
99
|
-
|
143
|
+
|
100
144
|
def self.api_sign_request(params_to_sign, api_secret)
|
101
145
|
to_sign = api_string_to_sign(params_to_sign)
|
102
146
|
Digest::SHA1.hexdigest("#{to_sign}#{api_secret}")
|
@@ -107,24 +151,24 @@ class Cloudinary::Utils
|
|
107
151
|
|
108
152
|
type = options.delete(:type)
|
109
153
|
|
110
|
-
options[:fetch_format] ||= options.delete(:format) if type == :fetch
|
154
|
+
options[:fetch_format] ||= options.delete(:format) if type == :fetch
|
111
155
|
transformation = self.generate_transformation_string(options)
|
112
156
|
|
113
157
|
resource_type = options.delete(:resource_type) || "image"
|
114
158
|
version = options.delete(:version)
|
115
159
|
format = options.delete(:format)
|
116
160
|
cloud_name = config_option_consume(options, :cloud_name) || raise(CloudinaryException, "Must supply cloud_name in tag or in configuration")
|
117
|
-
|
161
|
+
|
118
162
|
secure = options.delete(:secure)
|
119
163
|
ssl_detected = options.delete(:ssl_detected)
|
120
164
|
secure = ssl_detected || Cloudinary.config.secure if secure.nil?
|
121
|
-
private_cdn = config_option_consume(options, :private_cdn)
|
122
|
-
secure_distribution = config_option_consume(options, :secure_distribution)
|
123
|
-
cname = config_option_consume(options, :cname)
|
124
|
-
shorten = config_option_consume(options, :shorten)
|
165
|
+
private_cdn = config_option_consume(options, :private_cdn)
|
166
|
+
secure_distribution = config_option_consume(options, :secure_distribution)
|
167
|
+
cname = config_option_consume(options, :cname)
|
168
|
+
shorten = config_option_consume(options, :shorten)
|
125
169
|
force_remote = options.delete(:force_remote)
|
126
|
-
cdn_subdomain = config_option_consume(options, :cdn_subdomain)
|
127
|
-
secure_cdn_subdomain = config_option_consume(options, :secure_cdn_subdomain)
|
170
|
+
cdn_subdomain = config_option_consume(options, :cdn_subdomain)
|
171
|
+
secure_cdn_subdomain = config_option_consume(options, :secure_cdn_subdomain)
|
128
172
|
sign_url = config_option_consume(options, :sign_url)
|
129
173
|
secret = config_option_consume(options, :api_secret)
|
130
174
|
sign_version = config_option_consume(options, :sign_version) # Deprecated behavior
|
@@ -136,33 +180,33 @@ class Cloudinary::Utils
|
|
136
180
|
original_source = source
|
137
181
|
return original_source if source.blank?
|
138
182
|
if defined?(CarrierWave::Uploader::Base) && source.is_a?(CarrierWave::Uploader::Base)
|
139
|
-
source = format.blank? ? source.filename : source.full_public_id
|
183
|
+
source = format.blank? ? source.filename : source.full_public_id
|
140
184
|
end
|
141
185
|
source = source.to_s
|
142
|
-
if !force_remote
|
186
|
+
if !force_remote
|
143
187
|
return original_source if (type.nil? || type == :asset) && source.match(%r(^https?:/)i)
|
144
|
-
if source.start_with?("/")
|
188
|
+
if source.start_with?("/")
|
145
189
|
if source.start_with?("/images/")
|
146
190
|
source = source.sub(%r(/images/), '')
|
147
191
|
else
|
148
192
|
return original_source
|
149
193
|
end
|
150
|
-
end
|
194
|
+
end
|
151
195
|
@metadata ||= defined?(Cloudinary::Static) ? Cloudinary::Static.metadata : {}
|
152
196
|
if type == :asset && @metadata["images/#{source}"]
|
153
|
-
return original_source if !Cloudinary.config.static_image_support
|
197
|
+
return original_source if !Cloudinary.config.static_image_support
|
154
198
|
source = @metadata["images/#{source}"]["public_id"]
|
155
199
|
source += File.extname(original_source) if !format
|
156
200
|
elsif type == :asset
|
157
201
|
return original_source # requested asset, but no metadata - probably local file. return.
|
158
202
|
end
|
159
203
|
end
|
160
|
-
|
204
|
+
|
161
205
|
resource_type, type = finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten)
|
162
206
|
source, source_to_sign = finalize_source(source, format, url_suffix)
|
163
|
-
|
164
|
-
version ||= 1 if source_to_sign.include?("/") and !source_to_sign.match(/^v[0-9]+/) and !source_to_sign.match(/^https?:\//)
|
165
|
-
version &&= "v#{version}"
|
207
|
+
|
208
|
+
version ||= 1 if source_to_sign.include?("/") and !source_to_sign.match(/^v[0-9]+/) and !source_to_sign.match(/^https?:\//)
|
209
|
+
version &&= "v#{version}"
|
166
210
|
|
167
211
|
transformation = transformation.gsub(%r(([^:])//), '\1/')
|
168
212
|
if sign_url
|
@@ -180,19 +224,19 @@ class Cloudinary::Utils
|
|
180
224
|
source = smart_escape(source)
|
181
225
|
source_to_sign = source
|
182
226
|
else
|
183
|
-
source = smart_escape(URI.decode(source))
|
227
|
+
source = smart_escape(URI.decode(source))
|
184
228
|
source_to_sign = source
|
185
229
|
unless url_suffix.blank?
|
186
230
|
raise(CloudinaryException, "url_suffix should not include . or /") if url_suffix.match(%r([\./]))
|
187
|
-
source = "#{source}/#{url_suffix}"
|
231
|
+
source = "#{source}/#{url_suffix}"
|
188
232
|
end
|
189
233
|
if !format.blank?
|
190
|
-
source = "#{source}.#{format}"
|
191
|
-
source_to_sign = "#{source_to_sign}.#{format}"
|
234
|
+
source = "#{source}.#{format}"
|
235
|
+
source_to_sign = "#{source_to_sign}.#{format}"
|
192
236
|
end
|
193
237
|
end
|
194
238
|
[source, source_to_sign]
|
195
|
-
end
|
239
|
+
end
|
196
240
|
|
197
241
|
def self.finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten)
|
198
242
|
type ||= :upload
|
@@ -200,7 +244,7 @@ class Cloudinary::Utils
|
|
200
244
|
if resource_type.to_s == "image" && type.to_s == "upload"
|
201
245
|
resource_type = "images"
|
202
246
|
type = nil
|
203
|
-
elsif resource_type.to_s == "raw" && type.to_s == "upload"
|
247
|
+
elsif resource_type.to_s == "raw" && type.to_s == "upload"
|
204
248
|
resource_type = "files"
|
205
249
|
type = nil
|
206
250
|
else
|
@@ -220,12 +264,12 @@ class Cloudinary::Utils
|
|
220
264
|
type = nil
|
221
265
|
end
|
222
266
|
[resource_type, type]
|
223
|
-
end
|
224
|
-
|
267
|
+
end
|
268
|
+
|
225
269
|
# cdn_subdomain and secure_cdn_subdomain
|
226
270
|
# 1) Customers in shared distribution (e.g. res.cloudinary.com)
|
227
271
|
# if cdn_domain is true uses res-[1-5].cloudinary.com for both http and https. Setting secure_cdn_subdomain to false disables this for https.
|
228
|
-
# 2) Customers with private cdn
|
272
|
+
# 2) Customers with private cdn
|
229
273
|
# if cdn_domain is true uses cloudname-res-[1-5].cloudinary.com for http
|
230
274
|
# if secure_cdn_domain is true uses cloudname-res-[1-5].cloudinary.com for https (please contact support if you require this)
|
231
275
|
# 3) Customers with cname
|
@@ -252,7 +296,7 @@ class Cloudinary::Utils
|
|
252
296
|
prefix = "http://#{subdomain}#{cname}"
|
253
297
|
else
|
254
298
|
host = [private_cdn ? "#{cloud_name}-" : "", "res", cdn_subdomain ? "-#{(Zlib::crc32(source) % 5) + 1}" : "", ".cloudinary.com"].join
|
255
|
-
prefix = "http://#{host}"
|
299
|
+
prefix = "http://#{host}"
|
256
300
|
end
|
257
301
|
prefix += "/#{cloud_name}" if shared_domain
|
258
302
|
|
@@ -274,23 +318,23 @@ class Cloudinary::Utils
|
|
274
318
|
params[:api_key] = api_key
|
275
319
|
params
|
276
320
|
end
|
277
|
-
|
321
|
+
|
278
322
|
def self.private_download_url(public_id, format, options = {})
|
279
323
|
cloudinary_params = sign_request({
|
280
|
-
:timestamp=>Time.now.to_i,
|
281
|
-
:public_id=>public_id,
|
282
|
-
:format=>format,
|
324
|
+
:timestamp=>Time.now.to_i,
|
325
|
+
:public_id=>public_id,
|
326
|
+
:format=>format,
|
283
327
|
:type=>options[:type],
|
284
|
-
:attachment=>options[:attachment],
|
328
|
+
:attachment=>options[:attachment],
|
285
329
|
:expires_at=>options[:expires_at] && options[:expires_at].to_i
|
286
330
|
}, options)
|
287
|
-
|
288
|
-
return Cloudinary::Utils.cloudinary_api_url("download", options) + "?" + cloudinary_params.to_query
|
331
|
+
|
332
|
+
return Cloudinary::Utils.cloudinary_api_url("download", options) + "?" + cloudinary_params.to_query
|
289
333
|
end
|
290
334
|
|
291
335
|
def self.zip_download_url(tag, options = {})
|
292
336
|
cloudinary_params = sign_request({:timestamp=>Time.now.to_i, :tag=>tag, :transformation=>generate_transformation_string(options)}, options)
|
293
|
-
return Cloudinary::Utils.cloudinary_api_url("download_tag.zip", options) + "?" + cloudinary_params.to_query
|
337
|
+
return Cloudinary::Utils.cloudinary_api_url("download_tag.zip", options) + "?" + cloudinary_params.to_query
|
294
338
|
end
|
295
339
|
|
296
340
|
def self.signed_download_url(public_id, options = {})
|
@@ -303,7 +347,7 @@ class Cloudinary::Utils
|
|
303
347
|
expires_at = options[:expires_at] || (Time.now+3600)
|
304
348
|
signer.sign(url, :ending => expires_at)
|
305
349
|
end
|
306
|
-
|
350
|
+
|
307
351
|
def self.cloudinary_url(public_id, options = {})
|
308
352
|
if options[:type].to_s == 'authenticated' && !options[:sign_url]
|
309
353
|
result = signed_download_url(public_id, options)
|
@@ -318,16 +362,16 @@ class Cloudinary::Utils
|
|
318
362
|
ext = path.extname
|
319
363
|
md5 = Digest::MD5.hexdigest(data)
|
320
364
|
public_id = "#{path.basename(ext)}-#{md5}"
|
321
|
-
"#{public_id}#{ext}"
|
365
|
+
"#{public_id}#{ext}"
|
322
366
|
end
|
323
|
-
|
324
|
-
# Based on CGI::unescape. In addition does not escape / :
|
367
|
+
|
368
|
+
# Based on CGI::unescape. In addition does not escape / :
|
325
369
|
def self.smart_escape(string)
|
326
370
|
string.gsub(/([^a-zA-Z0-9_.\-\/:]+)/) do
|
327
371
|
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
328
372
|
end
|
329
373
|
end
|
330
|
-
|
374
|
+
|
331
375
|
def self.random_public_id
|
332
376
|
sr = defined?(ActiveSupport::SecureRandom) ? ActiveSupport::SecureRandom : SecureRandom
|
333
377
|
sr.base64(20).downcase.gsub(/[^a-z0-9]/, "").sub(/^[0-9]+/, '')[0,20]
|
@@ -336,7 +380,7 @@ class Cloudinary::Utils
|
|
336
380
|
def self.signed_preloaded_image(result)
|
337
381
|
"#{result["resource_type"]}/#{result["type"] || "upload"}/v#{result["version"]}/#{[result["public_id"], result["format"]].reject(&:blank?).join(".")}##{result["signature"]}"
|
338
382
|
end
|
339
|
-
|
383
|
+
|
340
384
|
@@json_decode = false
|
341
385
|
def self.json_decode(str)
|
342
386
|
if !@@json_decode
|
@@ -347,7 +391,7 @@ class Cloudinary::Utils
|
|
347
391
|
begin
|
348
392
|
require 'active_support/json'
|
349
393
|
rescue LoadError
|
350
|
-
raise LoadError, "Please add the json gem or active_support to your Gemfile"
|
394
|
+
raise LoadError, "Please add the json gem or active_support to your Gemfile"
|
351
395
|
end
|
352
396
|
end
|
353
397
|
end
|
@@ -361,7 +405,7 @@ class Cloudinary::Utils
|
|
361
405
|
else [array]
|
362
406
|
end
|
363
407
|
end
|
364
|
-
|
408
|
+
|
365
409
|
def self.encode_hash(hash)
|
366
410
|
case hash
|
367
411
|
when Hash then hash.map{|k,v| "#{k}=#{v}"}.join("|")
|
@@ -369,7 +413,7 @@ class Cloudinary::Utils
|
|
369
413
|
else hash
|
370
414
|
end
|
371
415
|
end
|
372
|
-
|
416
|
+
|
373
417
|
def self.encode_double_array(array)
|
374
418
|
array = build_array(array)
|
375
419
|
if array.length > 0 && array[0].is_a?(Array)
|
@@ -378,24 +422,24 @@ class Cloudinary::Utils
|
|
378
422
|
return array.join(",")
|
379
423
|
end
|
380
424
|
end
|
381
|
-
|
382
|
-
IMAGE_FORMATS = %w(bmp png tif tiff jpg jpeg gif pdf ico eps jpc jp2 psd)
|
383
|
-
|
425
|
+
|
426
|
+
IMAGE_FORMATS = %w(bmp png tif tiff jpg jpeg gif pdf ico eps jpc jp2 psd)
|
427
|
+
|
384
428
|
def self.supported_image_format?(format)
|
385
429
|
format = format.to_s.downcase
|
386
430
|
extension = format =~ /\./ ? format.split('.').last : format
|
387
431
|
IMAGE_FORMATS.include?(extension)
|
388
432
|
end
|
389
|
-
|
433
|
+
|
390
434
|
def self.resource_type_for_format(format)
|
391
435
|
self.supported_image_format?(format) ? 'image' : 'raw'
|
392
436
|
end
|
393
|
-
|
394
|
-
def self.config_option_consume(options, option_name, default_value = nil)
|
437
|
+
|
438
|
+
def self.config_option_consume(options, option_name, default_value = nil)
|
395
439
|
return options.delete(option_name) if options.include?(option_name)
|
396
|
-
return Cloudinary.config.send(option_name) || default_value
|
440
|
+
return Cloudinary.config.send(option_name) || default_value
|
397
441
|
end
|
398
|
-
|
442
|
+
|
399
443
|
def self.as_bool(value)
|
400
444
|
case value
|
401
445
|
when nil then nil
|
@@ -408,7 +452,7 @@ class Cloudinary::Utils
|
|
408
452
|
raise "Invalid boolean value #{value} of type #{value.class}"
|
409
453
|
end
|
410
454
|
end
|
411
|
-
|
455
|
+
|
412
456
|
def self.as_safe_bool(value)
|
413
457
|
case as_bool(value)
|
414
458
|
when nil then nil
|
@@ -416,8 +460,76 @@ class Cloudinary::Utils
|
|
416
460
|
when FalseClass then 0
|
417
461
|
end
|
418
462
|
end
|
419
|
-
|
463
|
+
|
420
464
|
def self.safe_blank?(value)
|
421
465
|
value.nil? || value == "" || value == []
|
422
466
|
end
|
467
|
+
|
468
|
+
private
|
469
|
+
def self.number_pattern
|
470
|
+
"([0-9]*)\\.([0-9]+)|([0-9]+)"
|
471
|
+
end
|
472
|
+
|
473
|
+
def self.offset_any_pattern
|
474
|
+
"(#{number_pattern})([%pP])?"
|
475
|
+
end
|
476
|
+
|
477
|
+
def self.offset_any_pattern_re
|
478
|
+
/((([0-9]*)\.([0-9]+)|([0-9]+))([%pP])?)\.\.((([0-9]*)\.([0-9]+)|([0-9]+))([%pP])?)/
|
479
|
+
end
|
480
|
+
|
481
|
+
# Split a range into the start and end values
|
482
|
+
def self.split_range(range) # :nodoc:
|
483
|
+
case range
|
484
|
+
when Range
|
485
|
+
[range.first, range.last]
|
486
|
+
when String
|
487
|
+
range.split ".." if offset_any_pattern_re =~ range
|
488
|
+
when Array
|
489
|
+
[range.first, range.last]
|
490
|
+
else
|
491
|
+
nil
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
# Normalize an offset value
|
496
|
+
# @param [String] value a decimal value which may have a 'p' or '%' postfix. E.g. '35%', '0.4p'
|
497
|
+
# @return [Object|String] a normalized String of the input value if possible otherwise the value itself
|
498
|
+
def self.norm_range_value(value) # :nodoc:
|
499
|
+
offset = /^#{offset_any_pattern}$/.match( value.to_s)
|
500
|
+
if offset
|
501
|
+
modifier = offset[5].present? ? 'p' : ''
|
502
|
+
value = "#{offset[1]}#{modifier}"
|
503
|
+
end
|
504
|
+
value
|
505
|
+
end
|
506
|
+
|
507
|
+
# A video codec parameter can be either a String or a Hash.
|
508
|
+
#
|
509
|
+
# @param [Object] param <code>vc_<codec>[ : <profile> : [<level>]]</code>
|
510
|
+
# or <code>{ codec: 'h264', profile: 'basic', level: '3.1' }</code>
|
511
|
+
# @return [String] <code><codec> : <profile> : [<level>]]</code> if a Hash was provided
|
512
|
+
# or the param if a String was provided.
|
513
|
+
# Returns NIL if param is not a Hash or String
|
514
|
+
def self.process_video_params(param)
|
515
|
+
case param
|
516
|
+
when Hash
|
517
|
+
video = ""
|
518
|
+
if param.has_key? :codec
|
519
|
+
video = param[:codec]
|
520
|
+
if param.has_key? :profile
|
521
|
+
video.concat ":" + param[:profile]
|
522
|
+
if param.has_key? :level
|
523
|
+
video.concat ":" + param[:level]
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
527
|
+
video
|
528
|
+
when String
|
529
|
+
param
|
530
|
+
else
|
531
|
+
nil
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
423
535
|
end
|