cloudinary 1.0.82 → 1.0.83
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.
- 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
|