cloudinary 1.11.1 → 1.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
- data/.github/pull_request_template.md +24 -0
- data/.gitignore +6 -1
- data/.travis.yml +15 -5
- data/CHANGELOG.md +149 -0
- data/Rakefile +3 -45
- data/cloudinary.gemspec +20 -4
- data/lib/active_storage/blob_key.rb +20 -0
- data/lib/active_storage/service/cloudinary_service.rb +229 -0
- data/lib/cloudinary.rb +31 -22
- data/lib/cloudinary/api.rb +173 -5
- data/lib/cloudinary/auth_token.rb +6 -4
- data/lib/cloudinary/carrier_wave.rb +3 -1
- data/lib/cloudinary/carrier_wave/remote.rb +3 -2
- data/lib/cloudinary/carrier_wave/storage.rb +2 -1
- data/lib/cloudinary/cloudinary_controller.rb +2 -4
- data/lib/cloudinary/helper.rb +30 -3
- data/lib/cloudinary/railtie.rb +7 -3
- data/lib/cloudinary/uploader.rb +35 -6
- data/lib/cloudinary/utils.rb +112 -40
- data/lib/cloudinary/version.rb +1 -1
- data/lib/cloudinary/video_helper.rb +96 -22
- data/lib/tasks/cloudinary/fetch_assets.rake +48 -0
- data/lib/tasks/{cloudinary.rake → cloudinary/sync_static.rake} +0 -0
- data/tools/allocate_test_cloud.sh +9 -0
- data/tools/get_test_cloud.sh +9 -0
- data/tools/update_version +29 -11
- data/vendor/assets/javascripts/cloudinary/jquery.cloudinary.js +48 -14
- data/vendor/assets/javascripts/cloudinary/jquery.fileupload.js +24 -4
- data/vendor/assets/javascripts/cloudinary/jquery.ui.widget.js +741 -561
- data/vendor/assets/javascripts/cloudinary/load-image.all.min.js +1 -1
- metadata +62 -67
- data/spec/access_control_spec.rb +0 -102
- data/spec/api_spec.rb +0 -567
- data/spec/archive_spec.rb +0 -129
- data/spec/auth_token_spec.rb +0 -77
- data/spec/cache_spec.rb +0 -109
- data/spec/cloudinary_helper_spec.rb +0 -325
- data/spec/cloudinary_spec.rb +0 -32
- data/spec/data/sync_static/app/assets/javascripts/1.coffee +0 -1
- data/spec/data/sync_static/app/assets/javascripts/1.js +0 -1
- data/spec/data/sync_static/app/assets/stylesheets/1.css +0 -3
- data/spec/docx.docx +0 -0
- data/spec/favicon.ico +0 -0
- data/spec/image_spec.rb +0 -107
- data/spec/logo.png +0 -0
- data/spec/rake_spec.rb +0 -160
- data/spec/sample_asset_file.tsv +0 -4
- data/spec/search_spec.rb +0 -109
- data/spec/spec_helper.rb +0 -266
- data/spec/storage_spec.rb +0 -44
- data/spec/streaminig_profiles_api_spec.rb +0 -74
- data/spec/support/helpers/temp_file_helpers.rb +0 -22
- data/spec/support/shared_contexts/rake.rb +0 -19
- data/spec/uploader_spec.rb +0 -392
- data/spec/utils_methods_spec.rb +0 -54
- data/spec/utils_spec.rb +0 -970
- data/spec/video_tag_spec.rb +0 -253
- data/spec/video_url_spec.rb +0 -185
data/lib/cloudinary/railtie.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
class Cloudinary::Railtie < Rails::Railtie
|
2
2
|
rake_tasks do
|
3
|
-
Dir[File.join(File.dirname(__FILE__),'../tasks
|
4
|
-
end
|
3
|
+
Dir[File.join(File.dirname(__FILE__),'../tasks/**/*.rake')].each { |f| load f }
|
4
|
+
end
|
5
5
|
config.after_initialize do |app|
|
6
6
|
ActionView::Base.send :include, CloudinaryHelper
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
|
+
ActiveSupport.on_load(:action_controller_base) do
|
10
|
+
ActionController::Base.send :include, Cloudinary::CloudinaryController
|
11
|
+
end
|
12
|
+
end
|
data/lib/cloudinary/uploader.rb
CHANGED
@@ -5,8 +5,7 @@ require 'cloudinary/cache'
|
|
5
5
|
|
6
6
|
class Cloudinary::Uploader
|
7
7
|
|
8
|
-
REMOTE_URL_REGEX =
|
9
|
-
|
8
|
+
REMOTE_URL_REGEX = Cloudinary::Utils::REMOTE_URL_REGEX
|
10
9
|
# @deprecated use {Cloudinary::Utils.build_eager} instead
|
11
10
|
def self.build_eager(eager)
|
12
11
|
Cloudinary::Utils.build_eager(eager)
|
@@ -28,6 +27,7 @@ class Cloudinary::Uploader
|
|
28
27
|
:backup => Cloudinary::Utils.as_safe_bool(options[:backup]),
|
29
28
|
:callback => options[:callback],
|
30
29
|
:categorization => options[:categorization],
|
30
|
+
:cinemagraph_analysis => Cloudinary::Utils.as_safe_bool(options[:cinemagraph_analysis]),
|
31
31
|
:colors => Cloudinary::Utils.as_safe_bool(options[:colors]),
|
32
32
|
:context => Cloudinary::Utils.encode_context(options[:context]),
|
33
33
|
:custom_coordinates => Cloudinary::Utils.encode_double_array(options[:custom_coordinates]),
|
@@ -37,6 +37,7 @@ class Cloudinary::Uploader
|
|
37
37
|
:eager_async => Cloudinary::Utils.as_safe_bool(options[:eager_async]),
|
38
38
|
:eager_notification_url => options[:eager_notification_url],
|
39
39
|
:exif => Cloudinary::Utils.as_safe_bool(options[:exif]),
|
40
|
+
:eval => options[:eval],
|
40
41
|
:face_coordinates => Cloudinary::Utils.encode_double_array(options[:face_coordinates]),
|
41
42
|
:faces => Cloudinary::Utils.as_safe_bool(options[:faces]),
|
42
43
|
:folder => options[:folder],
|
@@ -64,6 +65,8 @@ class Cloudinary::Uploader
|
|
64
65
|
:unique_filename => Cloudinary::Utils.as_safe_bool(options[:unique_filename]),
|
65
66
|
:upload_preset => options[:upload_preset],
|
66
67
|
:use_filename => Cloudinary::Utils.as_safe_bool(options[:use_filename]),
|
68
|
+
:accessibility_analysis => Cloudinary::Utils.as_safe_bool(options[:accessibility_analysis]),
|
69
|
+
:metadata => Cloudinary::Utils.encode_context(options[:metadata])
|
67
70
|
}
|
68
71
|
params
|
69
72
|
end
|
@@ -77,7 +80,10 @@ class Cloudinary::Uploader
|
|
77
80
|
params = build_upload_params(options)
|
78
81
|
if file.is_a?(Pathname)
|
79
82
|
params[:file] = File.open(file, "rb")
|
80
|
-
elsif file.
|
83
|
+
elsif file.is_a?(StringIO)
|
84
|
+
file.rewind
|
85
|
+
params[:file] = Cloudinary::Blob.new(file.read, options)
|
86
|
+
elsif file.respond_to?(:read) || Cloudinary::Utils.is_remote?(file)
|
81
87
|
params[:file] = file
|
82
88
|
else
|
83
89
|
params[:file] = File.open(file, "rb")
|
@@ -95,7 +101,7 @@ class Cloudinary::Uploader
|
|
95
101
|
public_id = public_id_or_options
|
96
102
|
options = old_options
|
97
103
|
end
|
98
|
-
if
|
104
|
+
if Cloudinary::Utils.is_remote?(file)
|
99
105
|
return upload(file, options.merge(:public_id => public_id))
|
100
106
|
elsif file.is_a?(Pathname) || !file.respond_to?(:read)
|
101
107
|
filename = file
|
@@ -248,7 +254,7 @@ class Cloudinary::Uploader
|
|
248
254
|
end
|
249
255
|
end
|
250
256
|
|
251
|
-
# options may include 'exclusive' (boolean) which causes clearing this tag from all other resources
|
257
|
+
# options may include 'exclusive' (boolean) which causes clearing this tag from all other resources
|
252
258
|
def self.add_tag(tag, public_ids = [], options = {})
|
253
259
|
exclusive = options.delete(:exclusive)
|
254
260
|
command = exclusive ? "set_exclusive" : "add"
|
@@ -267,6 +273,29 @@ class Cloudinary::Uploader
|
|
267
273
|
return self.call_tags_api(nil, "remove_all", public_ids, options)
|
268
274
|
end
|
269
275
|
|
276
|
+
# Populates metadata fields with the given values. Existing values will be overwritten.
|
277
|
+
#
|
278
|
+
# Any metadata-value pairs given are merged with any existing metadata-value pairs
|
279
|
+
# (an empty value for an existing metadata field clears the value).
|
280
|
+
#
|
281
|
+
# @param [Hash] metadata A list of custom metadata fields (by external_id) and the values to assign to each of them.
|
282
|
+
# @param [Array] public_ids An array of Public IDs of assets uploaded to Cloudinary.
|
283
|
+
# @param [Hash] options
|
284
|
+
# @option options [String] :resource_type The type of file. Default: image. Valid values: image, raw, video.
|
285
|
+
# @option options [String] :type The storage type. Default: upload. Valid values: upload, private, authenticated
|
286
|
+
# @return mixed a list of public IDs that were updated
|
287
|
+
# @raise [Cloudinary::Api:Error]
|
288
|
+
def self.update_metadata(metadata, public_ids, options = {})
|
289
|
+
self.call_api("metadata", options) do
|
290
|
+
{
|
291
|
+
timestamp: (options[:timestamp] || Time.now.to_i),
|
292
|
+
type: options[:type],
|
293
|
+
public_ids: Cloudinary::Utils.build_array(public_ids),
|
294
|
+
metadata: Cloudinary::Utils.encode_context(metadata)
|
295
|
+
}
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
270
299
|
private
|
271
300
|
|
272
301
|
def self.call_tags_api(tag, command, public_ids = [], options = {})
|
@@ -317,7 +346,7 @@ class Cloudinary::Uploader
|
|
317
346
|
params[:signature] = Cloudinary::Utils.api_sign_request(params.reject { |k, v| non_signable.include?(k) }, api_secret)
|
318
347
|
params[:api_key] = api_key
|
319
348
|
end
|
320
|
-
timeout = options
|
349
|
+
timeout = options.fetch(:timeout) { Cloudinary.config.to_h.fetch(:timeout, 60) }
|
321
350
|
|
322
351
|
result = nil
|
323
352
|
|
data/lib/cloudinary/utils.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# Copyright Cloudinary
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
2
4
|
require 'digest/sha1'
|
3
5
|
require 'zlib'
|
4
6
|
require 'uri'
|
@@ -24,7 +26,8 @@ class Cloudinary::Utils
|
|
24
26
|
"*" => 'mul',
|
25
27
|
"/" => 'div',
|
26
28
|
"+" => 'add',
|
27
|
-
"-" => 'sub'
|
29
|
+
"-" => 'sub',
|
30
|
+
"^" => 'pow'
|
28
31
|
}
|
29
32
|
|
30
33
|
PREDEFINED_VARS = {
|
@@ -39,9 +42,32 @@ class Cloudinary::Utils
|
|
39
42
|
"page_x" => "px",
|
40
43
|
"page_y" => "py",
|
41
44
|
"tags" => "tags",
|
45
|
+
"initial_duration" => "idu",
|
46
|
+
"duration" => "du",
|
42
47
|
"width" => "w"
|
43
48
|
}
|
44
49
|
|
50
|
+
SIMPLE_TRANSFORMATION_PARAMS = {
|
51
|
+
:ac => :audio_codec,
|
52
|
+
:af => :audio_frequency,
|
53
|
+
:br => :bit_rate,
|
54
|
+
:cs => :color_space,
|
55
|
+
:d => :default_image,
|
56
|
+
:dl => :delay,
|
57
|
+
:dn => :density,
|
58
|
+
:du => :duration,
|
59
|
+
:eo => :end_offset,
|
60
|
+
:f => :fetch_format,
|
61
|
+
:g => :gravity,
|
62
|
+
:ki => :keyframe_interval,
|
63
|
+
:p => :prefix,
|
64
|
+
:pg => :page,
|
65
|
+
:so => :start_offset,
|
66
|
+
:sp => :streaming_profile,
|
67
|
+
:vc => :video_codec,
|
68
|
+
:vs => :video_sampling
|
69
|
+
}.freeze
|
70
|
+
|
45
71
|
URL_KEYS = %w[
|
46
72
|
api_secret
|
47
73
|
auth_token
|
@@ -113,6 +139,11 @@ class Cloudinary::Utils
|
|
113
139
|
zoom
|
114
140
|
].map(&:to_sym)
|
115
141
|
|
142
|
+
REMOTE_URL_REGEX = %r(^ftp:|^https?:|^s3:|^gs:|^data:([\w-]+\/[\w-]+(\+[\w-]+)?)?(;[\w-]+=[\w-]+)*;base64,([a-zA-Z0-9\/+\n=]+)$)
|
143
|
+
|
144
|
+
LONG_URL_SIGNATURE_LENGTH = 32
|
145
|
+
SHORT_URL_SIGNATURE_LENGTH = 8
|
146
|
+
|
116
147
|
def self.extract_config_params(options)
|
117
148
|
options.select{|k,v| URL_KEYS.include?(k)}
|
118
149
|
end
|
@@ -216,7 +247,7 @@ class Cloudinary::Utils
|
|
216
247
|
:l => overlay,
|
217
248
|
:o => normalize_expression(options.delete(:opacity)),
|
218
249
|
:q => normalize_expression(options.delete(:quality)),
|
219
|
-
:r =>
|
250
|
+
:r => process_radius(options.delete(:radius)),
|
220
251
|
:t => named_transformation,
|
221
252
|
:u => underlay,
|
222
253
|
:w => normalize_expression(width),
|
@@ -224,26 +255,7 @@ class Cloudinary::Utils
|
|
224
255
|
:y => normalize_expression(options.delete(:y)),
|
225
256
|
:z => normalize_expression(options.delete(:zoom))
|
226
257
|
}
|
227
|
-
|
228
|
-
:ac => :audio_codec,
|
229
|
-
:af => :audio_frequency,
|
230
|
-
:br => :bit_rate,
|
231
|
-
:cs => :color_space,
|
232
|
-
:d => :default_image,
|
233
|
-
:dl => :delay,
|
234
|
-
:dn => :density,
|
235
|
-
:du => :duration,
|
236
|
-
:eo => :end_offset,
|
237
|
-
:f => :fetch_format,
|
238
|
-
:g => :gravity,
|
239
|
-
:ki => :keyframe_interval,
|
240
|
-
:p => :prefix,
|
241
|
-
:pg => :page,
|
242
|
-
:so => :start_offset,
|
243
|
-
:sp => :streaming_profile,
|
244
|
-
:vc => :video_codec,
|
245
|
-
:vs => :video_sampling
|
246
|
-
}.each do
|
258
|
+
SIMPLE_TRANSFORMATION_PARAMS.each do
|
247
259
|
|param, option|
|
248
260
|
params[param] = options.delete(option)
|
249
261
|
end
|
@@ -293,19 +305,17 @@ class Cloudinary::Utils
|
|
293
305
|
# Translates the condition if provided.
|
294
306
|
# @return [string] "if_" + ifValue
|
295
307
|
# @private
|
296
|
-
def self.process_if(
|
297
|
-
|
298
|
-
ifValue = normalize_expression(ifValue)
|
299
|
-
|
300
|
-
ifValue = "if_" + ifValue
|
301
|
-
end
|
308
|
+
def self.process_if(if_value)
|
309
|
+
"if_" + normalize_expression(if_value) unless if_value.to_s.empty?
|
302
310
|
end
|
303
311
|
|
304
|
-
EXP_REGEXP = Regexp.new(PREDEFINED_VARS.keys.join("|")+'|('+CONDITIONAL_OPERATORS.keys.reverse.map { |k| Regexp.escape(k) }.join('|')+')(?=[ _])')
|
312
|
+
EXP_REGEXP = Regexp.new('(?<!\$)('+PREDEFINED_VARS.keys.join("|")+')'+'|('+CONDITIONAL_OPERATORS.keys.reverse.map { |k| Regexp.escape(k) }.join('|')+')(?=[ _])')
|
305
313
|
EXP_REPLACEMENT = PREDEFINED_VARS.merge(CONDITIONAL_OPERATORS)
|
306
314
|
|
307
315
|
def self.normalize_expression(expression)
|
308
|
-
if expression
|
316
|
+
if expression.nil?
|
317
|
+
''
|
318
|
+
elsif expression.is_a?( String) && expression =~ /^!.+!$/ # quoted string
|
309
319
|
expression
|
310
320
|
else
|
311
321
|
expression.to_s.gsub(EXP_REGEXP,EXP_REPLACEMENT).gsub(/[ _]+/, "_")
|
@@ -375,6 +385,17 @@ class Cloudinary::Utils
|
|
375
385
|
end
|
376
386
|
private_class_method :process_layer
|
377
387
|
|
388
|
+
# Parse radius options
|
389
|
+
# @return [string] radius transformation string
|
390
|
+
# @private
|
391
|
+
def self.process_radius(radius)
|
392
|
+
if radius.is_a?(Array) && !radius.length.between?(1, 4)
|
393
|
+
raise(CloudinaryException, "Invalid radius parameter")
|
394
|
+
end
|
395
|
+
Array(radius).map { |r| normalize_expression(r) }.join(":")
|
396
|
+
end
|
397
|
+
private_class_method :process_radius
|
398
|
+
|
378
399
|
LAYER_KEYWORD_PARAMS =[
|
379
400
|
[:font_weight ,"normal"],
|
380
401
|
[:font_style ,"normal"],
|
@@ -395,6 +416,10 @@ class Cloudinary::Utils
|
|
395
416
|
keywords.push("letter_spacing_#{letter_spacing}") unless letter_spacing.blank?
|
396
417
|
line_spacing = layer[:line_spacing]
|
397
418
|
keywords.push("line_spacing_#{line_spacing}") unless line_spacing.blank?
|
419
|
+
font_antialiasing = layer[:font_antialiasing]
|
420
|
+
keywords.push("antialias_#{font_antialiasing}") unless font_antialiasing.blank?
|
421
|
+
font_hinting = layer[:font_hinting]
|
422
|
+
keywords.push("hinting_#{font_hinting}") unless font_hinting.blank?
|
398
423
|
if !font_size.blank? || !font_family.blank? || !keywords.empty?
|
399
424
|
raise(CloudinaryException, "Must supply font_family for text in overlay/underlay") if font_family.blank?
|
400
425
|
raise(CloudinaryException, "Must supply font_size for text in overlay/underlay") if font_size.blank?
|
@@ -455,6 +480,7 @@ class Cloudinary::Utils
|
|
455
480
|
|
456
481
|
resource_type = options.delete(:resource_type)
|
457
482
|
version = options.delete(:version)
|
483
|
+
force_version = config_option_consume(options, :force_version, true)
|
458
484
|
format = options.delete(:format)
|
459
485
|
cloud_name = config_option_consume(options, :cloud_name) || raise(CloudinaryException, "Must supply cloud_name in tag or in configuration")
|
460
486
|
|
@@ -474,6 +500,7 @@ class Cloudinary::Utils
|
|
474
500
|
url_suffix = options.delete(:url_suffix)
|
475
501
|
use_root_path = config_option_consume(options, :use_root_path)
|
476
502
|
auth_token = config_option_consume(options, :auth_token)
|
503
|
+
long_url_signature = config_option_consume(options, :long_url_signature)
|
477
504
|
unless auth_token == false
|
478
505
|
auth_token = Cloudinary::AuthToken.merge_auth_token(Cloudinary.config.auth_token, auth_token)
|
479
506
|
end
|
@@ -505,7 +532,12 @@ class Cloudinary::Utils
|
|
505
532
|
resource_type, type = finalize_resource_type(resource_type, type, url_suffix, use_root_path, shorten)
|
506
533
|
source, source_to_sign = finalize_source(source, format, url_suffix)
|
507
534
|
|
508
|
-
|
535
|
+
if version.nil? && force_version &&
|
536
|
+
source_to_sign.include?("/") &&
|
537
|
+
!source_to_sign.match(/^v[0-9]+/) &&
|
538
|
+
!source_to_sign.match(/^https?:\//)
|
539
|
+
version = 1
|
540
|
+
end
|
509
541
|
version &&= "v#{version}"
|
510
542
|
|
511
543
|
transformation = transformation.gsub(%r(([^:])//), '\1/')
|
@@ -513,7 +545,7 @@ class Cloudinary::Utils
|
|
513
545
|
raise(CloudinaryException, "Must supply api_secret") if (secret.nil? || secret.empty?)
|
514
546
|
to_sign = [transformation, sign_version && version, source_to_sign].reject(&:blank?).join("/")
|
515
547
|
to_sign = fully_unescape(to_sign)
|
516
|
-
signature =
|
548
|
+
signature = compute_signature(to_sign, secret, long_url_signature)
|
517
549
|
end
|
518
550
|
|
519
551
|
prefix = unsigned_download_url_prefix(source, cloud_name, private_cdn, cdn_subdomain, secure_cdn_subdomain, cname, secure, secure_distribution)
|
@@ -533,7 +565,7 @@ class Cloudinary::Utils
|
|
533
565
|
source = smart_escape(source)
|
534
566
|
source_to_sign = source
|
535
567
|
else
|
536
|
-
source = smart_escape(
|
568
|
+
source = smart_escape(smart_unescape(source))
|
537
569
|
source_to_sign = source
|
538
570
|
unless url_suffix.blank?
|
539
571
|
raise(CloudinaryException, "url_suffix should not include . or /") if url_suffix.match(%r([\./]))
|
@@ -704,6 +736,18 @@ class Cloudinary::Utils
|
|
704
736
|
download_archive_url(options.merge(:target_format => "zip"))
|
705
737
|
end
|
706
738
|
|
739
|
+
# Creates and returns a URL that when invoked creates an archive of a folder.
|
740
|
+
#
|
741
|
+
# @param [Object] folder_path Full path (from the root) of the folder to download.
|
742
|
+
# @param [Hash] options Additional options.
|
743
|
+
#
|
744
|
+
# @return [String]
|
745
|
+
def self.download_folder(folder_path, options = {})
|
746
|
+
resource_type = options[:resource_type] || "all"
|
747
|
+
|
748
|
+
download_archive_url(options.merge(:resource_type => resource_type, :prefixes => folder_path))
|
749
|
+
end
|
750
|
+
|
707
751
|
def self.signed_download_url(public_id, options = {})
|
708
752
|
aws_private_key_path = options[:aws_private_key_path] || Cloudinary.config.aws_private_key_path
|
709
753
|
if aws_private_key_path
|
@@ -737,13 +781,18 @@ class Cloudinary::Utils
|
|
737
781
|
"#{public_id}#{ext}"
|
738
782
|
end
|
739
783
|
|
740
|
-
# Based on CGI::
|
784
|
+
# Based on CGI::escape. In addition does not escape / :
|
741
785
|
def self.smart_escape(string, unsafe = /([^a-zA-Z0-9_.\-\/:]+)/)
|
742
786
|
string.gsub(unsafe) do |m|
|
743
787
|
'%' + m.unpack('H2' * m.bytesize).join('%').upcase
|
744
788
|
end
|
745
789
|
end
|
746
790
|
|
791
|
+
# Based on CGI::unescape. In addition keeps '+' character as is
|
792
|
+
def self.smart_unescape(string)
|
793
|
+
CGI.unescape(string.sub('+', '%2B'))
|
794
|
+
end
|
795
|
+
|
747
796
|
def self.random_public_id
|
748
797
|
sr = defined?(ActiveSupport::SecureRandom) ? ActiveSupport::SecureRandom : SecureRandom
|
749
798
|
sr.base64(20).downcase.gsub(/[^a-z0-9]/, "").sub(/^[0-9]+/, '')[0,20]
|
@@ -811,7 +860,7 @@ class Cloudinary::Utils
|
|
811
860
|
end
|
812
861
|
end
|
813
862
|
|
814
|
-
IMAGE_FORMATS = %w(ai bmp bpg djvu eps eps3 flif gif hdp hpx ico j2k jp2 jpc jpe jpg miff pdf png psd svg tif tiff wdp webp zip )
|
863
|
+
IMAGE_FORMATS = %w(ai bmp bpg djvu eps eps3 flif gif hdp hpx ico j2k jp2 jpc jpe jpeg jpg miff pdf png psd svg tif tiff wdp webp zip )
|
815
864
|
|
816
865
|
AUDIO_FORMATS = %w(aac aifc aiff flac m4a mp3 ogg wav)
|
817
866
|
|
@@ -831,10 +880,8 @@ class Cloudinary::Utils
|
|
831
880
|
case
|
832
881
|
when self.supported_format?(format, IMAGE_FORMATS)
|
833
882
|
'image'
|
834
|
-
when self.supported_format?(format, VIDEO_FORMATS)
|
883
|
+
when self.supported_format?(format, VIDEO_FORMATS), self.supported_format?(format, AUDIO_FORMATS)
|
835
884
|
'video'
|
836
|
-
when self.supported_format?(format, AUDIO_FORMATS)
|
837
|
-
'audio'
|
838
885
|
else
|
839
886
|
'raw'
|
840
887
|
end
|
@@ -842,7 +889,8 @@ class Cloudinary::Utils
|
|
842
889
|
|
843
890
|
def self.config_option_consume(options, option_name, default_value = nil)
|
844
891
|
return options.delete(option_name) if options.include?(option_name)
|
845
|
-
|
892
|
+
option_value = Cloudinary.config.send(option_name)
|
893
|
+
option_value.nil? ? default_value : option_value
|
846
894
|
end
|
847
895
|
|
848
896
|
def self.as_bool(value)
|
@@ -851,7 +899,7 @@ class Cloudinary::Utils
|
|
851
899
|
when String then value.downcase == "true" || value == "1"
|
852
900
|
when TrueClass then true
|
853
901
|
when FalseClass then false
|
854
|
-
when
|
902
|
+
when Integer then value != 0
|
855
903
|
when Symbol then value == :true
|
856
904
|
else
|
857
905
|
raise "Invalid boolean value #{value} of type #{value.class}"
|
@@ -928,6 +976,7 @@ class Cloudinary::Utils
|
|
928
976
|
:keep_derived=>Cloudinary::Utils.as_safe_bool(options[:keep_derived]),
|
929
977
|
:tags=>options[:tags] && Cloudinary::Utils.build_array(options[:tags]),
|
930
978
|
:public_ids=>options[:public_ids] && Cloudinary::Utils.build_array(options[:public_ids]),
|
979
|
+
:fully_qualified_public_ids=>options[:fully_qualified_public_ids] && Cloudinary::Utils.build_array(options[:fully_qualified_public_ids]),
|
931
980
|
:prefixes=>options[:prefixes] && Cloudinary::Utils.build_array(options[:prefixes]),
|
932
981
|
:expires_at=>options[:expires_at],
|
933
982
|
:transformations => build_eager(options[:transformations]),
|
@@ -1096,4 +1145,27 @@ class Cloudinary::Utils
|
|
1096
1145
|
end
|
1097
1146
|
end
|
1098
1147
|
|
1148
|
+
def self.is_remote?(url)
|
1149
|
+
REMOTE_URL_REGEX === url
|
1150
|
+
end
|
1151
|
+
|
1152
|
+
# Computes a short or long signature based on a message and secret
|
1153
|
+
# @param [String] message The string to sign
|
1154
|
+
# @param [String] secret A secret that will be added to the message when signing
|
1155
|
+
# @param [Boolean] long_signature Whether to create a short or long signature
|
1156
|
+
# @return [String] Properly formatted signature
|
1157
|
+
def self.compute_signature(message, secret, long_url_signature)
|
1158
|
+
combined_message_secret = message + secret
|
1159
|
+
|
1160
|
+
algo, signature_length =
|
1161
|
+
if long_url_signature
|
1162
|
+
[Digest::SHA256, LONG_URL_SIGNATURE_LENGTH]
|
1163
|
+
else
|
1164
|
+
[Digest::SHA1, SHORT_URL_SIGNATURE_LENGTH]
|
1165
|
+
end
|
1166
|
+
|
1167
|
+
"s--#{Base64.urlsafe_encode64(algo.digest(combined_message_secret))[0, signature_length]}--"
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
private_class_method :compute_signature
|
1099
1171
|
end
|
data/lib/cloudinary/version.rb
CHANGED
@@ -3,12 +3,33 @@ module CloudinaryHelper
|
|
3
3
|
DEFAULT_POSTER_OPTIONS = { :format => 'jpg', :resource_type => 'video' }
|
4
4
|
DEFAULT_SOURCE_TYPES = %w(webm mp4 ogv)
|
5
5
|
DEFAULT_VIDEO_OPTIONS = { :resource_type => 'video' }
|
6
|
+
DEFAULT_SOURCES = [
|
7
|
+
{
|
8
|
+
:type => "mp4",
|
9
|
+
:codecs => "hev1",
|
10
|
+
:transformations => { :video_codec => "h265" }
|
11
|
+
},
|
12
|
+
{
|
13
|
+
:type => "webm",
|
14
|
+
:codecs => "vp9",
|
15
|
+
:transformations => { :video_codec => "vp9" }
|
16
|
+
},
|
17
|
+
{
|
18
|
+
:type => "mp4",
|
19
|
+
:transformations => { :video_codec => "auto" }
|
20
|
+
},
|
21
|
+
{
|
22
|
+
:type => "webm",
|
23
|
+
:transformations => { :video_codec => "auto" }
|
24
|
+
}
|
25
|
+
]
|
6
26
|
|
7
27
|
# Creates an HTML video tag for the provided +source+
|
8
28
|
#
|
9
29
|
# ==== Options
|
10
30
|
# * <tt>:source_types</tt> - Specify which source type the tag should include. defaults to webm, mp4 and ogv.
|
11
31
|
# * <tt>:source_transformation</tt> - specific transformations to use for a specific source type.
|
32
|
+
# * <tt>:sources</tt> - list of sources (overrides :source_types when present)
|
12
33
|
# * <tt>:poster</tt> - override default thumbnail:
|
13
34
|
# * url: provide an ad hoc url
|
14
35
|
# * options: with specific poster transformations and/or Cloudinary +:public_id+
|
@@ -20,6 +41,17 @@ module CloudinaryHelper
|
|
20
41
|
# cl_video_tag("mymovie.webm", :source_types => [:webm, :mp4], :poster => {:effect => 'sepia'}) do
|
21
42
|
# content_tag( :span, "Cannot present video!")
|
22
43
|
# end
|
44
|
+
# cl_video_tag("mymovie", :sources => [
|
45
|
+
# {
|
46
|
+
# :type => "mp4",
|
47
|
+
# :codecs => "hev1",
|
48
|
+
# :transformations => { :video_codec => "h265" }
|
49
|
+
# },
|
50
|
+
# {
|
51
|
+
# :type => "webm",
|
52
|
+
# :transformations => { :video_codec => "auto" }
|
53
|
+
# }
|
54
|
+
# ])
|
23
55
|
def cl_video_tag(source, options = {}, &block)
|
24
56
|
source = strip_known_ext(source)
|
25
57
|
video_attributes = [:autoplay,:controls,:loop,:muted,:poster, :preload]
|
@@ -48,30 +80,12 @@ module CloudinaryHelper
|
|
48
80
|
video_options[:poster] = cl_video_thumbnail_path(source, options)
|
49
81
|
end
|
50
82
|
|
51
|
-
|
52
|
-
source_types = Array(options.delete(:source_types))
|
53
|
-
fallback = (capture(&block) if block_given?) || options.delete(:fallback_content)
|
83
|
+
fallback = (capture(&block) if block_given?) || options.delete(:fallback_content)
|
54
84
|
|
55
|
-
if
|
56
|
-
|
57
|
-
content_tag('video', tag_options.merge(video_options)) do
|
58
|
-
source_tags = source_types.map do |type|
|
59
|
-
transformation = source_transformation[type.to_sym] || {}
|
60
|
-
cloudinary_tag("#{source}.#{type}", options.merge(transformation)) do |url, _tag_options|
|
61
|
-
mime_type = "video/#{(type == 'ogv' ? 'ogg' : type)}"
|
62
|
-
tag("source", :src => url, :type => mime_type)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
source_tags.push(fallback.html_safe) unless fallback.blank?
|
66
|
-
safe_join(source_tags)
|
67
|
-
end
|
68
|
-
end
|
85
|
+
if options[:sources]
|
86
|
+
video_tag_from_sources(source, options, video_options, fallback)
|
69
87
|
else
|
70
|
-
|
71
|
-
video_options[:src] = cl_video_path("#{source}.#{source_types.first.to_sym}", transformation.merge(options))
|
72
|
-
cloudinary_tag(source, options) do |_source, tag_options|
|
73
|
-
content_tag('video', fallback, tag_options.merge(video_options))
|
74
|
-
end
|
88
|
+
video_tag_from_source_types(source, options, video_options, fallback)
|
75
89
|
end
|
76
90
|
end
|
77
91
|
|
@@ -96,6 +110,66 @@ module CloudinaryHelper
|
|
96
110
|
name.sub(/\.(#{DEFAULT_SOURCE_TYPES.join("|")})$/, '')
|
97
111
|
end
|
98
112
|
|
113
|
+
private
|
114
|
+
|
115
|
+
def video_tag_from_source_types(source_name, options, video_options, fallback)
|
116
|
+
source_transformation = options.delete(:source_transformation) || {}
|
117
|
+
source_types = Array(options.delete(:source_types))
|
118
|
+
|
119
|
+
if source_types.size > 1
|
120
|
+
sources = source_types.map do |type|
|
121
|
+
{
|
122
|
+
:type => type,
|
123
|
+
:transformations => source_transformation[type.to_sym] || {}
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
generate_tag_from_sources(:source_name => source_name,
|
128
|
+
:sources => sources,
|
129
|
+
:options => options,
|
130
|
+
:video_options => video_options,
|
131
|
+
:fallback => fallback)
|
132
|
+
else
|
133
|
+
transformation = source_transformation[source_types.first.to_sym] || {}
|
134
|
+
video_options[:src] = cl_video_path("#{source_name}.#{source_types.first.to_sym}", transformation.merge(options))
|
135
|
+
cloudinary_tag(source_name, options) do |_source, tag_options|
|
136
|
+
content_tag('video', fallback, tag_options.merge(video_options))
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def video_tag_from_sources(source_name, options, video_options, fallback)
|
142
|
+
sources = options.delete(:sources)
|
143
|
+
|
144
|
+
generate_tag_from_sources(:source_name => source_name,
|
145
|
+
:sources => sources,
|
146
|
+
:options => options,
|
147
|
+
:video_options => video_options,
|
148
|
+
:fallback => fallback)
|
149
|
+
end
|
150
|
+
|
151
|
+
def generate_tag_from_sources(params)
|
152
|
+
source_name, sources, options, video_options, fallback = params.values_at(:source_name, :sources, :options, :video_options, :fallback)
|
153
|
+
|
154
|
+
cloudinary_tag(source_name, options) do |_source, tag_options|
|
155
|
+
content_tag('video', tag_options.merge(video_options)) do
|
156
|
+
source_tags = sources.map do |source|
|
157
|
+
type = source[:type]
|
158
|
+
transformation = source[:transformations] || {}
|
159
|
+
cloudinary_tag("#{source_name}.#{type}", options.merge(transformation)) do |url, _tag_options|
|
160
|
+
mime_type = "video/#{(type == 'ogv' ? 'ogg' : type)}"
|
161
|
+
if source[:codecs]
|
162
|
+
codecs = source[:codecs].is_a?(Array) ? source[:codecs].join(", ") : source[:codecs]
|
163
|
+
mime_type = "#{mime_type}; codecs=#{codecs}"
|
164
|
+
end
|
165
|
+
tag("source", :src => url, :type => mime_type)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
source_tags.push(fallback.html_safe) unless fallback.blank?
|
169
|
+
safe_join(source_tags)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
99
173
|
end
|
100
174
|
|
101
175
|
|