cloudinary 1.11.1 → 1.18.0
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/.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
|
|